docs(contributing): document the blocker axis and merge-next ownership #139

Merged
dan-claude-bot merged 1 commit from docs/contributing-blocker-axis into main 2026-07-20 19:07:21 +00:00
dan-claude-bot commented 2026-07-20 18:38:41 +00:00 (Migrated from github.com)

Follow-up to #138. CONTRIBUTING.md was not touched by the two-axis refactor and is now stale in three places.

What is wrong

The who-sets-what table never mentions blocker:*. That table is the day-to-day answer to can I move this by hand, and it silently omits a whole machine-owned label family. The predictable failure is someone hand-applying blocker:conflict to flag a PR, then being surprised when the next sweep strips it — or worse, removing one and assuming that unblocked something.

merge-next is missing too, and that gap predates this refactor — it arrived with #137 and was never documented here. It is also the row that most needs writing down, because merge-next is the one label in the machine's vocabulary the machine deliberately does not set. A reader of this table would reasonably assume the workflow owns it like everything else.

Step 6 overstates what a review request does. It reads as though requesting the maintainer is what flips state:needs-human. Since #138 that is necessary but not sufficient: needs-human requires zero blockers, so the request does nothing on a conflicted or red PR. Someone following step 6 literally would request a review, watch the label not move, and have nothing telling them why.

What this does

Three edits, no behaviour change:

  • adds a blocker:* row — same workflow, same facts, any number per PR, never by hand
  • adds a merge-next row — yours or the queue agent's to set; the workflow only ever clears it
  • qualifies step 6 with the no-blockers condition

Also sharpens the state:* row to say exactly one per PR: whose ball is it, since that contrast is what makes the two axes legible.

LABELS.md already documents all of this correctly — it was updated in #138. This is purely the contributor-facing summary catching up.

Verification

Docs only; no code paths touched. The same change goes to heavy-duty/rig and heavy-duty/cast — the labels section of CONTRIBUTING.md is byte-identical across the three.

Follow-up to #138. `CONTRIBUTING.md` was not touched by the two-axis refactor and is now stale in three places. ### What is wrong **The who-sets-what table never mentions `blocker:*`.** That table is the day-to-day answer to *can I move this by hand*, and it silently omits a whole machine-owned label family. The predictable failure is someone hand-applying `blocker:conflict` to flag a PR, then being surprised when the next sweep strips it — or worse, removing one and assuming that unblocked something. **`merge-next` is missing too**, and that gap predates this refactor — it arrived with #137 and was never documented here. It is also the row that most needs writing down, because `merge-next` is the one label in the machine's vocabulary the machine deliberately *does not* set. A reader of this table would reasonably assume the workflow owns it like everything else. **Step 6 overstates what a review request does.** It reads as though requesting the maintainer is what flips `state:needs-human`. Since #138 that is necessary but not sufficient: needs-human requires **zero blockers**, so the request does nothing on a conflicted or red PR. Someone following step 6 literally would request a review, watch the label not move, and have nothing telling them why. ### What this does Three edits, no behaviour change: - adds a `blocker:*` row — same workflow, same facts, any number per PR, never by hand - adds a `merge-next` row — yours or the queue agent's to set; the workflow only ever clears it - qualifies step 6 with the no-blockers condition Also sharpens the `state:*` row to say *exactly one per PR: whose ball is it*, since that contrast is what makes the two axes legible. `LABELS.md` already documents all of this correctly — it was updated in #138. This is purely the contributor-facing summary catching up. ### Verification Docs only; no code paths touched. The same change goes to `heavy-duty/rig` and `heavy-duty/cast` — the labels section of `CONTRIBUTING.md` is byte-identical across the three.
danmt (Migrated from github.com) reviewed 2026-07-20 18:38:41 +00:00
codex-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-20 18:44:59 +00:00
codex-bot-andresmgsl (Migrated from github.com) left a comment

Verdict: I agree with everything and have no additional feedback.

Verdict: I agree with everything and have no additional feedback.
claude-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-20 18:52:55 +00:00
claude-bot-andresmgsl (Migrated from github.com) left a comment

Approved — I agree with all of this, no concerns.

Verified each claim against .github/scripts/labels-reconcile.sh at head rather than against LABELS.md, since the point of the PR is that prose had drifted from behaviour:

  • Step 6's no-blockers provisodecide_state() (.github/scripts/labels-reconcile.sh:224) downgrades state:needs-human to state:addressing whenever blockers() is non-empty, so a review request genuinely does not flip the label on a conflicted or red PR. The proviso is attached to the flip itself rather than to either request path, which is right: the automatic path at :330 is gated on the same $desired and is equally subject to it.
  • blocker:* is a set, machine-ownedBLOCKERS at :35 is three independent facts, and the converge loop at :349-355 adds/removes each one on its own against blockers(). A hand-applied blocker:conflict is provably stripped on the next sweep, so "never by hand" is not just advice.
  • merge-next cleared, never set:408-410 is the only mention outside the bootstrap block; there is no add path. "The moment the PR stops being something a human could merge" is exactly [ "$desired" != state:needs-human ].
  • Table completeness — the reconciler touches state:*, blocker:*, merge-next, and stale; with these two rows the table now accounts for all four, plus the human-owned families. No remaining gap.

Also confirmed the cross-repo claim: the added lines are byte-identical in heavy-duty/cast#130 and heavy-duty/rig#94 (only the surrounding context and hunk offsets differ), and the reconcile scripts themselves are identical across all three apart from the per-repo scope:* label list — so the shared wording describes shared behaviour, not a coincidence.

Docs-only, no behaviour change, and the sharpened state:* row ("exactly one per PR: whose ball is it") against the new blocker:* row ("any number, or none") is what makes the two-axis split legible at a glance.

automated review by claude-bot-andresmgsl · heavy-duty-review-bot

✅ **Approved — I agree with all of this, no concerns.** Verified each claim against `.github/scripts/labels-reconcile.sh` at head rather than against `LABELS.md`, since the point of the PR is that prose had drifted from behaviour: - **Step 6's no-blockers proviso** — `decide_state()` (`.github/scripts/labels-reconcile.sh:224`) downgrades `state:needs-human` to `state:addressing` whenever `blockers()` is non-empty, so a review request genuinely does not flip the label on a conflicted or red PR. The proviso is attached to the flip itself rather than to either request path, which is right: the automatic path at `:330` is gated on the same `$desired` and is equally subject to it. - **`blocker:*` is a set, machine-owned** — `BLOCKERS` at `:35` is three independent facts, and the converge loop at `:349-355` adds/removes each one on its own against `blockers()`. A hand-applied `blocker:conflict` is provably stripped on the next sweep, so "never by hand" is not just advice. - **`merge-next` cleared, never set** — `:408-410` is the only mention outside the bootstrap block; there is no add path. "The moment the PR stops being something a human could merge" is exactly `[ "$desired" != state:needs-human ]`. - **Table completeness** — the reconciler touches `state:*`, `blocker:*`, `merge-next`, and `stale`; with these two rows the table now accounts for all four, plus the human-owned families. No remaining gap. Also confirmed the cross-repo claim: the added lines are byte-identical in `heavy-duty/cast#130` and `heavy-duty/rig#94` (only the surrounding context and hunk offsets differ), and the reconcile scripts themselves are identical across all three apart from the per-repo `scope:*` label list — so the shared wording describes shared behaviour, not a coincidence. Docs-only, no behaviour change, and the sharpened `state:*` row ("exactly one per PR: *whose ball is it*") against the new `blocker:*` row ("any number, or none") is what makes the two-axis split legible at a glance. — _automated review by `claude-bot-andresmgsl` · heavy-duty-review-bot_
grok-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-20 18:55:48 +00:00
grok-bot-andresmgsl (Migrated from github.com) left a comment

Verdict: Approve — I agree with this as-is.

Docs-only CONTRIBUTING catch-up after the two-axis label refactor. Checked the three prose claims against .github/scripts/labels-reconcile.sh on each tip (same script family across box/rig/cast):

  • Step 6 no-blockers provisodecide_state downgrades state:needs-human when blockers is non-empty (:224), so a maintainer request alone does not flip the label on a conflicted/red PR.
  • blocker:* rowBLOCKERS is a set converged from facts (:35, :349); hand-applied labels are stripped next sweep. "Never by hand" is accurate.
  • merge-next row — clear-only path at :408-410 when desired ≠ needs-human; no machine set path. Ownership wording is correct.
  • state:* sharpening — exactly-one / whose-ball contrast makes the axes legible.

Cross-repo added lines are byte-identical; CI green. No blockers.

**Verdict: Approve** — I agree with this as-is. Docs-only CONTRIBUTING catch-up after the two-axis label refactor. Checked the three prose claims against `.github/scripts/labels-reconcile.sh` on each tip (same script family across box/rig/cast): - **Step 6 no-blockers proviso** — `decide_state` downgrades `state:needs-human` when `blockers` is non-empty (`:224`), so a maintainer request alone does not flip the label on a conflicted/red PR. - **`blocker:*` row** — `BLOCKERS` is a set converged from facts (`:35`, `:349`); hand-applied labels are stripped next sweep. "Never by hand" is accurate. - **`merge-next` row** — clear-only path at `:408-410` when desired ≠ `needs-human`; no machine set path. Ownership wording is correct. - **`state:*` sharpening** — exactly-one / whose-ball contrast makes the axes legible. Cross-repo added lines are byte-identical; CI green. No blockers.
dan-claude-bot commented 2026-07-20 19:04:27 +00:00 (Migrated from github.com)

@danmt — handoff. All three bots approved on the current head; ready for your review.

Docs only, one file, +10/−5. CONTRIBUTING.md was not touched by the two-axis refactor (box#138 / rig#90 / cast#129) and was stale in three places:

  • The who-sets-what table never mentioned blocker:* — a whole machine-owned family. That table is the day-to-day answer to can I move this by hand, so the omission invites someone to hand-apply blocker:conflict and be puzzled when the sweep strips it.
  • merge-next was missing too, and that gap predates the refactor — it arrived with box#137 / rig#88 / cast#128 and was never documented here. It is the row that most needs saying, because it is the one label the machine deliberately does not set.
  • Step 6 overstated a review request. It read as though requesting you flips state:needs-human. Since the refactor that is necessary but not sufficient — needs-human requires zero blockers, so the request does nothing on a conflicted or red PR.

Clean round: no reviewer requested a change or suggested wording, so there are no round-replies on these threads.

The ## Labels — who sets what section and step 6 are byte-identical across box/rig/cast (md5 67c0d37d), verified on the three branch tips — by me and independently by two of the bots. These should land together.

Live confirmation the refactor is working, from the board this documents: box#120 currently carries blocker:conflict and blocker:ci-red. Under the single-axis design it could only ever have said one of them, and would have told an agent to rebase when what it owed was a test fix.

One thing you already hit, worth writing down somewhere if it recurs: running workflow_dispatch within a minute of merging lands in the window where GitHub still reports mergeable: UNKNOWN, so no conflict blockers get applied on that pass. It is the deliberate anti-flap behaviour, and the next sweep corrects it — but the board looks wrong in the meantime.

@danmt — handoff. All three bots approved on the current head; ready for your review. Docs only, one file, +10/−5. `CONTRIBUTING.md` was not touched by the two-axis refactor (box#138 / rig#90 / cast#129) and was stale in three places: - **The who-sets-what table never mentioned `blocker:*`** — a whole machine-owned family. That table is the day-to-day answer to *can I move this by hand*, so the omission invites someone to hand-apply `blocker:conflict` and be puzzled when the sweep strips it. - **`merge-next` was missing too**, and that gap predates the refactor — it arrived with box#137 / rig#88 / cast#128 and was never documented here. It is the row that most needs saying, because it is the one label the machine deliberately does *not* set. - **Step 6 overstated a review request.** It read as though requesting you flips `state:needs-human`. Since the refactor that is necessary but not sufficient — needs-human requires zero blockers, so the request does nothing on a conflicted or red PR. Clean round: no reviewer requested a change or suggested wording, so there are no round-replies on these threads. The `## Labels — who sets what` section and step 6 are byte-identical across box/rig/cast (md5 `67c0d37d`), verified on the three branch tips — by me and independently by two of the bots. These should land together. **Live confirmation the refactor is working**, from the board this documents: box#120 currently carries `blocker:conflict` **and** `blocker:ci-red`. Under the single-axis design it could only ever have said one of them, and would have told an agent to rebase when what it owed was a test fix. One thing you already hit, worth writing down somewhere if it recurs: running `workflow_dispatch` within a minute of merging lands in the window where GitHub still reports `mergeable: UNKNOWN`, so no conflict blockers get applied on that pass. It is the deliberate anti-flap behaviour, and the next sweep corrects it — but the board looks wrong in the meantime.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: heavy-duty/box#139
No description provided.