From c74f31829dda2036d74b5f2d076ca2bb4ebcb224 Mon Sep 17 00:00:00 2001 From: cluade-reviewer-andresmgsl Date: Tue, 4 Aug 2026 19:09:16 +0000 Subject: [PATCH] fix(labels): panel and triage name identities that exist on this forge (#195) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `.github/labels.conf` named five identities and every one of them 404s on this instance — a GitHub-shaped team that was never minted here. Both consumers of that roster were inoperable in consequence: `panel=` becomes the required-verdict set, so a review round could never converge, and `triage-actors=` is the arrival author gate, so every issue was a stray mint owing `needs-triage` that nobody the conf recognized could clear. Measured on #191: stamped `needs-triage` four seconds after mint by the arrival path working correctly, then unclosable for the rest of the day. The mapping is @andres's ruling, recorded on #191. `cluade` holds triage and reviews but does not build; `codex` builds; `kimi` reviews; `grok` comes off the bench; the human row is `andres`. CONTRIBUTING's roster table moves with the conf, including the approval count: panel-minus-author resolves to two on this roster, not three, because the only builder is itself on the panel. The rule is unchanged and still stated as panel-minus-author — only the number it currently comes to is named honestly. test/labels.test.sh now holds the conf and the table to the same set in both directions. It cannot reach the half that actually broke — two files agreeing with each other and neither with the forge — but it does catch a roster edit that touches one file and not the other, which is how a deliberate swap becomes a silent one. Not touched, deliberately: drills/*.md, which record runs that really happened under the old names; REVIEWER.md, whose old-name hits are citations and a past-event anecdote rather than roster definition. Refs #195 Co-Authored-By: Claude Opus 5 (1M context) --- .github/labels.conf | 4 ++-- CONTRIBUTING.md | 35 +++++++++++++++++++++------------ changelog.d/195.md | 16 +++++++++++++++ test/labels.test.sh | 48 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 15 deletions(-) create mode 100644 changelog.d/195.md diff --git a/.github/labels.conf b/.github/labels.conf index 3a779e4..405dcbc 100644 --- a/.github/labels.conf +++ b/.github/labels.conf @@ -1,5 +1,5 @@ -panel=claude-bot-andresmgsl codex-bot-andresmgsl grok-bot-andresmgsl kimi-bot-andresmgsl -triage-actors=dan-claude-bot +panel=codex-reviewer-andresmgsl kimi-reviewer-andresmgsl cluade-reviewer-andresmgsl +triage-actors=cluade-reviewer-andresmgsl scope:release-flow|C5DEF5|The reusable release workflow, decide, the doors scope:guards|C5DEF5|changelog-armed / changelog-monotonic / drill-recorded scope:labels|C5DEF5|The labels workflow, reconciler, the taxonomy diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b1666c..bf46b13 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,28 +41,37 @@ only ceremony-specific facts such as the roster and code conventions. ### Roster -Five identities share the work (org team `agents`), each living in its own +Three identities share the work (org team `agents`), each living in its own [box](https://github.com/heavy-duty/box) — one box per credential, because the box is the blast-radius boundary; roles are what a session is told, and [AGENTS.md](AGENTS.md) routes from there: | identity | box (rig tenant) | standing work | |---|---|---| -| `dan-claude-bot` | `triage` (claude-box) | **triage** — the only door issues come through; this identity mints issues and nothing else writes them (#18's `triage-actors`) | -| `claude-bot-andresmgsl` | claude-box | build (release-flow and guards machinery) + review | -| `codex-bot-andresmgsl` | codex-box | build (scaffolding, conversions) + review | -| `grok-bot-andresmgsl` | grok-box | review | -| `kimi-bot-andresmgsl` | kimi-box | review — builder trial on a small mechanical issue once its verdicts have a track record | +| `cluade-reviewer-andresmgsl` | `triage` (claude-box) | **triage** — the only door issues come through; this identity mints issues and nothing else writes them (#18's `triage-actors`) — and review. It does not build. | +| `codex-reviewer-andresmgsl` | codex-box | build + review | +| `kimi-reviewer-andresmgsl` | kimi-box | review | **The review panel for any PR is every bench identity except its author** — recusal by construction, enforced by the reconciler (#10): the required -verdicts are the panel minus the PR's author, so convergence always means -three cross-vendor approvals of the current head. Builders and triage -default to different models so the issue contract is honestly exercised — -a spec gap should surface as a question on the issue, not be silently filled -by shared priors. Humans (`danmt`) decide in discussions and merge; the -roster is config, not doctrine — swapping a vendor is an edit to this table -(and to `panel=` in `.github/labels.conf` once #10 lands), nothing more. +verdicts are the panel minus the PR's author. On this roster that resolves +to **two** cross-vendor approvals of the current head, because the only +builder is itself on the panel and recuses from its own PRs; the rule is +panel-minus-author, and two is what it currently comes to, not a second +rule. Builders and triage default to different models so the issue contract +is honestly exercised — a spec gap should surface as a question on the +issue, not be silently filled by shared priors. Humans (`andres`) decide in +discussions and merge; the roster is config, not doctrine — swapping a +vendor is an edit to this table (and to `panel=` in +`.github/labels.conf` once #10 lands), nothing more. + +The identities named here must be the identities `.github/labels.conf` +names, and both must exist on the forge the repo lives on. A roster that +agrees with itself and disagrees with the instance is the failure #195 +records: `panel=` naming absent users cannot converge and +`triage-actors=` naming an absent user makes every issue a stray mint that +nobody can normalize. `test/labels.test.sh` holds this table and the conf +to the same set, in both directions. Each governed repo names its own roster in its CONTRIBUTING; this one is ceremony's. Its `scope:*` set is the same kind of repo-specific fact: diff --git a/changelog.d/195.md b/changelog.d/195.md new file mode 100644 index 0000000..91451e8 --- /dev/null +++ b/changelog.d/195.md @@ -0,0 +1,16 @@ +### Fixed + +- `.github/labels.conf` names identities that exist on the forge this repo + lives on. All five it named before were absent, so `panel=` could never + converge a review round and `triage-actors=` made every issue a stray mint + nobody could normalize (#195). + +- `CONTRIBUTING.md`'s roster table matches the conf: three identities, the + human row is `andres`, and the approval count states what panel-minus-author + actually resolves to on this roster rather than a stale three (#195). + +### Added + +- `test/labels.test.sh` holds the conf's roster and `CONTRIBUTING.md`'s roster + table to the same set, in both directions, so a roster edit that touches one + file and not the other goes red instead of drifting quietly (#195). diff --git a/test/labels.test.sh b/test/labels.test.sh index e08d7c0..ecf38cb 100755 --- a/test/labels.test.sh +++ b/test/labels.test.sh @@ -126,4 +126,52 @@ sed 's/opened, closed/closed, opened/' "$STUB" >"$mut_stub" check "an issue-list reorder in one file only goes red" 1 "" \ types_in_sync issues "$CALLER" "$mut_stub" +# --- #195: the conf's roster and CONTRIBUTING's roster table are one set ---- +# The rot this catches: labels.conf named five identities, CONTRIBUTING named +# the same five, and none of the five existed on the forge — two files in +# perfect agreement with each other and none with reality. No offline check +# can reach the second half; what it can hold is that a roster edit touching +# one file and not the other goes red, which is the drift that turns a +# deliberate swap into a silent one. + +# roster_from_conf — every identity panel= and triage-actors= name. +roster_from_conf() { + sed -nE 's/^(panel|triage-actors)=//p' "$1" | tr ' ' '\n' | sed '/^$/d' | sort -u +} + +# roster_from_doc — the identities the "### Roster" table's +# first column names. Anchored to the section rather than to the table's +# shape: another table elsewhere in the file must not be able to join the +# roster by looking like one. +# shellcheck disable=SC2016 # the backticks below are the table's, not a subshell +roster_from_doc() { + awk '/^### Roster$/ { inside = 1; next } + inside && /^#+ / { exit } + inside' "$1" | + sed -nE 's/^\| `([^`]+)`.*/\1/p' | sort -u +} + +roster_in_sync() { # + local conf="$1" doc="$2" drift + drift="$(diff <(roster_from_conf "$conf") <(roster_from_doc "$doc"))" && return 0 + echo "roster drift ('<' conf only, '>' table only):" >&2 + printf '%s\n' "$drift" >&2 + return 1 +} + +CONF="$ROOT/.github/labels.conf" +CONTRIB="$ROOT/CONTRIBUTING.md" +check "the real conf and the real roster table name the same identities" 0 "" \ + roster_in_sync "$CONF" "$CONTRIB" +# the failing cases, in both directions — a one-way check would have passed +# all week on the rot that produced #195 +mut_conf="$TMP/mut-labels.conf" mut_contrib="$TMP/mut-contributing.md" +sed 's/^panel=/panel=ghost-bot /' "$CONF" >"$mut_conf" +check "an identity in the conf but not the table goes red" 1 "ghost-bot" \ + roster_in_sync "$mut_conf" "$CONTRIB" +# shellcheck disable=SC2016 # the backticks are the table's, not a subshell +sed 's/^| `kimi-reviewer-andresmgsl`/| `ghost-bot`/' "$CONTRIB" >"$mut_contrib" +check "an identity in the table but not the conf goes red" 1 "ghost-bot" \ + roster_in_sync "$CONF" "$mut_contrib" + summary