forked from heavy-duty/ceremony
Merge pull request 'labels.conf + CONTRIBUTING — the panel and triage rosters name identities that exist on this forge (#195)' (#196) from build/195-roster-mapping into main
Reviewed-on: heavy-duty/ceremony#196 Reviewed-by: codex-reviewer-andresmgsl <andres+2@heavyduty.builders> Reviewed-by: kimi-reviewer-andresmgsl <andres+4@heavyduty.builders>
This commit is contained in:
commit
dad99ddfb9
4 changed files with 88 additions and 15 deletions
4
.github/labels.conf
vendored
4
.github/labels.conf
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
panel=claude-bot-andresmgsl codex-bot-andresmgsl grok-bot-andresmgsl kimi-bot-andresmgsl
|
panel=codex-reviewer-andresmgsl kimi-reviewer-andresmgsl cluade-reviewer-andresmgsl
|
||||||
triage-actors=dan-claude-bot
|
triage-actors=cluade-reviewer-andresmgsl
|
||||||
scope:release-flow|C5DEF5|The reusable release workflow, decide, the doors
|
scope:release-flow|C5DEF5|The reusable release workflow, decide, the doors
|
||||||
scope:guards|C5DEF5|changelog-armed / changelog-monotonic / drill-recorded
|
scope:guards|C5DEF5|changelog-armed / changelog-monotonic / drill-recorded
|
||||||
scope:labels|C5DEF5|The labels workflow, reconciler, the taxonomy
|
scope:labels|C5DEF5|The labels workflow, reconciler, the taxonomy
|
||||||
|
|
|
||||||
|
|
@ -41,28 +41,37 @@ only ceremony-specific facts such as the roster and code conventions.
|
||||||
|
|
||||||
### Roster
|
### 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
|
[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
|
the box is the blast-radius boundary; roles are what a session is told, and
|
||||||
[AGENTS.md](AGENTS.md) routes from there:
|
[AGENTS.md](AGENTS.md) routes from there:
|
||||||
|
|
||||||
| identity | box (rig tenant) | standing work |
|
| 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`) |
|
| `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. |
|
||||||
| `claude-bot-andresmgsl` | claude-box | build (release-flow and guards machinery) + review |
|
| `codex-reviewer-andresmgsl` | codex-box | build + review |
|
||||||
| `codex-bot-andresmgsl` | codex-box | build (scaffolding, conversions) + review |
|
| `kimi-reviewer-andresmgsl` | kimi-box | 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 |
|
|
||||||
|
|
||||||
**The review panel for any PR is every bench identity except its author** —
|
**The review panel for any PR is every bench identity except its author** —
|
||||||
recusal by construction, enforced by the reconciler (#10): the required
|
recusal by construction, enforced by the reconciler (#10): the required
|
||||||
verdicts are the panel minus the PR's author, so convergence always means
|
verdicts are the panel minus the PR's author. On this roster that resolves
|
||||||
three cross-vendor approvals of the current head. Builders and triage
|
to **two** cross-vendor approvals of the current head, because the only
|
||||||
default to different models so the issue contract is honestly exercised —
|
builder is itself on the panel and recuses from its own PRs; the rule is
|
||||||
a spec gap should surface as a question on the issue, not be silently filled
|
panel-minus-author, and two is what it currently comes to, not a second
|
||||||
by shared priors. Humans (`danmt`) decide in discussions and merge; the
|
rule. Builders and triage default to different models so the issue contract
|
||||||
roster is config, not doctrine — swapping a vendor is an edit to this table
|
is honestly exercised — a spec gap should surface as a question on the
|
||||||
(and to `panel=` in `.github/labels.conf` once #10 lands), nothing more.
|
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
|
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:
|
ceremony's. Its `scope:*` set is the same kind of repo-specific fact:
|
||||||
|
|
|
||||||
16
changelog.d/195.md
Normal file
16
changelog.d/195.md
Normal file
|
|
@ -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).
|
||||||
|
|
@ -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 "" \
|
check "an issue-list reorder in one file only goes red" 1 "" \
|
||||||
types_in_sync issues "$CALLER" "$mut_stub"
|
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 <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 <contributing> — 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() { # <conf> <contributing>
|
||||||
|
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
|
summary
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue