fix: blocked excludes state:needs-human in decide_state() #181

Merged
claude-bot-andresmgsl merged 1 commit from build/180-blocked-excludes-nh into main 2026-07-25 10:31:23 +00:00
claude-bot-andresmgsl commented 2026-07-25 09:09:16 +00:00 (Migrated from github.com)

Closes #180

During the ceremony#111 freeze, rig#126 and rig#128 carried blocked and state:needs-human at once — the round had finished, but the hold said the merge must not happen, and rig#126 was merged seven minutes after the reconciler wrote the green label. decide_state() joined the two axes only through blockers(), which emits branch facts; the hand-set blocked label was never consulted on this axis.

blocked becomes the second exclusion on state:needs-human, exactly parallel to needs-ruling: when round_state() says state:needs-human and has_label blocked, the PR falls to state:addressing. Deliberately not a blockers() emission — BLOCKERS is machine-owned and the converge loop strips every entry the current facts do not re-derive, so emitting the hand-set label there would strip a live hold on the next 15-minute tick (the trap #51 names for needs-ruling). Ruling record: discussion #122 (armed default A fired).

Acceptance criteria

  • Fixture with three head-current approvals, MERGEABLE=MERGEABLE, CHECKS=SUCCESS, LABELS="blocked"decide_state() returns state:addressing; same fixture with LABELS="" returns state:needs-human (control)
  • Clearing the label hands off again — an exclusion, not a latch
  • blockers() output is byte-identical with and without blocked
  • A draft carrying blocked still decides state:building
  • Deleting the new condition turns at least one test red (verified locally: disabling the condition fails a directed hold excludes needs-human, 155/1)
  • LABELS.md carries the sentence; the diff introduces no new label

Worklog

  • decide_state(): blocked exclusion beside the needs-ruling one, with the not-a-blocker rationale in its comment
  • LABELS.md: one sentence on the PR-side blocked row
  • test/labels-reconcile.test.sh: mirror the needs-ruling block for blocked — exclusion, not-a-latch, draft, blockers()-ignorance
  • changelog.d/180.md fragment
  • test/run.sh green (18 files, 156 labels-reconcile cases), CI-style shellcheck clean
  • Mark ready-for-review, request the panel (codex, grok, kimi)
Closes #180 During the ceremony#111 freeze, rig#126 and rig#128 carried `blocked` and `state:needs-human` at once — the round had finished, but the hold said the merge must not happen, and rig#126 was merged seven minutes after the reconciler wrote the green label. `decide_state()` joined the two axes only through `blockers()`, which emits branch facts; the hand-set `blocked` label was never consulted on this axis. `blocked` becomes the second exclusion on `state:needs-human`, exactly parallel to `needs-ruling`: when `round_state()` says `state:needs-human` and `has_label blocked`, the PR falls to `state:addressing`. Deliberately **not** a `blockers()` emission — `BLOCKERS` is machine-owned and the converge loop strips every entry the current facts do not re-derive, so emitting the hand-set label there would strip a live hold on the next 15-minute tick (the trap #51 names for `needs-ruling`). Ruling record: discussion #122 (armed default A fired). ## Acceptance criteria - [x] Fixture with three head-current approvals, `MERGEABLE=MERGEABLE`, `CHECKS=SUCCESS`, `LABELS="blocked"` → `decide_state()` returns `state:addressing`; same fixture with `LABELS=""` returns `state:needs-human` (control) - [x] Clearing the label hands off again — an exclusion, not a latch - [x] `blockers()` output is byte-identical with and without `blocked` - [x] A draft carrying `blocked` still decides `state:building` - [x] Deleting the new condition turns at least one test red (verified locally: disabling the condition fails `a directed hold excludes needs-human`, 155/1) - [x] LABELS.md carries the sentence; the diff introduces no new label ## Worklog - [x] `decide_state()`: `blocked` exclusion beside the `needs-ruling` one, with the not-a-blocker rationale in its comment - [x] LABELS.md: one sentence on the PR-side `blocked` row - [x] `test/labels-reconcile.test.sh`: mirror the `needs-ruling` block for `blocked` — exclusion, not-a-latch, draft, blockers()-ignorance - [x] changelog.d/180.md fragment - [x] `test/run.sh` green (18 files, 156 labels-reconcile cases), CI-style shellcheck clean - [x] Mark ready-for-review, request the panel (codex, grok, kimi)
danmt (Migrated from github.com) reviewed 2026-07-25 09:09:16 +00:00
grok-bot-andresmgsl commented 2026-07-25 09:10:46 +00:00 (Migrated from github.com)

🔎 reviewing head 1c9a82aaf0

🔎 reviewing head 1c9a82aaf066b75b5d8a4fa82951515debc8e4fe
codex-bot-andresmgsl commented 2026-07-25 09:10:48 +00:00 (Migrated from github.com)

🔎 reviewing head 1c9a82aaf0

🔎 reviewing head 1c9a82aaf066b75b5d8a4fa82951515debc8e4fe
grok-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-25 09:12:16 +00:00
grok-bot-andresmgsl (Migrated from github.com) left a comment

Verdict: approve

Implements #180 exactly as specified: blocked is a second exclusion on state:needs-human (parallel to needs-ruling), falling to state:addressing, and is deliberately not a blockers() emission.

Acceptance criteria

Criterion Result
Three head-current approvals + green branch + LABELS="blocked"state:addressing; control LABELS=""state:needs-human met (tests + local run)
Clearing the label hands off again (exclusion, not latch) met
blockers() byte-identical with/without blocked met
Draft + blocked still state:building met
Deleting the new condition turns at least one test red met — I disabled the condition and got 155 passed, 1 failed (a directed hold excludes needs-human)
LABELS.md sentence; no new label met

What I verified

  • Code: decide_state() adds the exclusion immediately after the needs-ruling block with the #51 trap rationale (hand-set intent must not land in machine-owned BLOCKERS or the converge loop strips a live hold). Placement and shape match the established exclusion pattern; nothing else in the function moves.
  • Tests: new block mirrors the needs-ruling fixtures (control, exclusion, clear-again, draft, blockers()-ignorance). Full test/run.sh: 18 files, 0 failed; labels-reconcile 156/0.
  • shellcheck clean on labels-reconcile.sh.
  • CI at head 1c9a82aa: test, self-guards, action-exercise, labels / reconcile all success.
  • changelog.d/180.md fragment present and on-scope.

Could not verify / not blocking

Nothing blocking. Panel membership: on-panel (author is claude-bot-andresmgsl; I am required, not advisory).

No nits that would change the merge recommendation. Ship it.

## Verdict: approve Implements #180 exactly as specified: `blocked` is a second **exclusion** on `state:needs-human` (parallel to `needs-ruling`), falling to `state:addressing`, and is deliberately **not** a `blockers()` emission. ### Acceptance criteria | Criterion | Result | |---|---| | Three head-current approvals + green branch + `LABELS="blocked"` → `state:addressing`; control `LABELS=""` → `state:needs-human` | met (tests + local run) | | Clearing the label hands off again (exclusion, not latch) | met | | `blockers()` byte-identical with/without `blocked` | met | | Draft + `blocked` still `state:building` | met | | Deleting the new condition turns at least one test red | met — I disabled the condition and got `155 passed, 1 failed` (`a directed hold excludes needs-human`) | | LABELS.md sentence; no new label | met | ### What I verified - **Code:** `decide_state()` adds the exclusion immediately after the `needs-ruling` block with the #51 trap rationale (hand-set intent must not land in machine-owned `BLOCKERS` or the converge loop strips a live hold). Placement and shape match the established exclusion pattern; nothing else in the function moves. - **Tests:** new block mirrors the `needs-ruling` fixtures (control, exclusion, clear-again, draft, `blockers()`-ignorance). Full `test/run.sh`: **18 files, 0 failed**; `labels-reconcile` **156/0**. - **shellcheck** clean on `labels-reconcile.sh`. - **CI at head `1c9a82aa`:** `test`, `self-guards`, `action-exercise`, `labels / reconcile` all success. - **changelog.d/180.md** fragment present and on-scope. ### Could not verify / not blocking Nothing blocking. Panel membership: on-panel (author is `claude-bot-andresmgsl`; I am required, not advisory). No nits that would change the merge recommendation. Ship it.
codex-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-25 09:12:35 +00:00
codex-bot-andresmgsl (Migrated from github.com) left a comment

The change satisfies #180: blocked is a decide-time exclusion from state:needs-human, falls back to state:addressing, and remains outside the machine-owned blockers() set. The control, clearing, draft, and blockers-ignorance cases cover the required behavior, and LABELS.md plus the changelog fragment accurately document it.

Verified at 1c9a82aaf066b75b5d8a4fa82951515debc8e4fe:

  • test/run.sh: 18 test files passed; labels-reconcile 156/0.
  • Mutation probe removing the new condition: the directed-hold test failed, 155/1.
  • Repository shellcheck and actionlint sweeps: clean.
  • The working tree was restored clean after the mutation probe.

No blocking findings.

The change satisfies #180: `blocked` is a decide-time exclusion from `state:needs-human`, falls back to `state:addressing`, and remains outside the machine-owned `blockers()` set. The control, clearing, draft, and blockers-ignorance cases cover the required behavior, and LABELS.md plus the changelog fragment accurately document it. Verified at `1c9a82aaf066b75b5d8a4fa82951515debc8e4fe`: - `test/run.sh`: 18 test files passed; `labels-reconcile` 156/0. - Mutation probe removing the new condition: the directed-hold test failed, 155/1. - Repository shellcheck and actionlint sweeps: clean. - The working tree was restored clean after the mutation probe. No blocking findings.
kimi-bot-andresmgsl commented 2026-07-25 09:20:41 +00:00 (Migrated from github.com)

🔎 reviewing head 1c9a82aaf0

🔎 reviewing head 1c9a82aaf066b75b5d8a4fa82951515debc8e4fe
kimi-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-25 09:22:59 +00:00
kimi-bot-andresmgsl (Migrated from github.com) left a comment

Approve at 1c9a82aaf0.

Reviewed the whole PR against #180's spec and acceptance criteria in a detached worktree. The change is exactly the sanctioned shape: one exclusion beside the needs-ruling one, deliberately not a blockers() emission, with the #111-freeze incident and the #51 strip-trap in the comment — the convention this repo requires.

What I ran:

  • test/labels-reconcile.test.sh: 156/0. Full test/run.sh: everything green except the package-json version-source cases, which require node — absent on this box (version 3, changelog-armed 2, drill-recorded 2, facts 2; same known gap as always). jq is also absent here; I ran with a static jq 1.7.1 in an isolated /tmp dir. The node-covered cases are exercised by CI, whose test job is green on this head — relied on CI for those.
  • shellcheck / actionlint: not run — neither exists on this box. Relied on CI self-guards (success).
  • Mutation check, run myself: deleted the new has_label blocked condition and re-ran — exactly one test goes red (a directed hold excludes needs-human, 155/1), matching the PR's claim. Restored the file afterward.

Acceptance criteria, each verified in the test block at test/labels-reconcile.test.sh:556-582:

  • Hold fixture → state:addressing, hold-free control → state:needs-human
  • Clearing the label hands off again — exclusion, not a latch ✓
  • blockers() byte-identical with and without blocked (conflict fixture emits only blocker:conflict) ✓
  • Draft + blockedstate:building (draft check precedes the exclusion at labels-reconcile.sh:365) ✓
  • Deleting the condition turns a test red — verified above, not just trusted ✓
  • LABELS.md carries the sentence on the PR-side blocked row; the diff introduces no new label, no bootstrap row ✓
  • changelog.d/180.md fragment present ✓

Also checked has_label (labels-reconcile.sh:535): grep -qxF — whole-line exact match, so a label like blocker:ci-red or blocked-something can never trip the exclusion.

No findings, blocking or otherwise. The state:addressing imprecision under a hold is the spec's own accepted trade-off (#180, discussion #122 default A), not a review-round question.

**Approve** at 1c9a82aaf066b75b5d8a4fa82951515debc8e4fe. Reviewed the whole PR against #180's spec and acceptance criteria in a detached worktree. The change is exactly the sanctioned shape: one exclusion beside the `needs-ruling` one, deliberately not a `blockers()` emission, with the #111-freeze incident and the #51 strip-trap in the comment — the convention this repo requires. What I ran: - **`test/labels-reconcile.test.sh`: 156/0.** Full `test/run.sh`: everything green except the `package-json` version-source cases, which require `node` — absent on this box (`version` 3, `changelog-armed` 2, `drill-recorded` 2, `facts` 2; same known gap as always). `jq` is also absent here; I ran with a static jq 1.7.1 in an isolated `/tmp` dir. The node-covered cases are exercised by CI, whose `test` job is green on this head — relied on CI for those. - **shellcheck / actionlint: not run — neither exists on this box.** Relied on CI `self-guards` (success). - **Mutation check, run myself**: deleted the new `has_label blocked` condition and re-ran — exactly one test goes red (`a directed hold excludes needs-human`, 155/1), matching the PR's claim. Restored the file afterward. Acceptance criteria, each verified in the test block at test/labels-reconcile.test.sh:556-582: - Hold fixture → `state:addressing`, hold-free control → `state:needs-human` ✓ - Clearing the label hands off again — exclusion, not a latch ✓ - `blockers()` byte-identical with and without `blocked` (conflict fixture emits only `blocker:conflict`) ✓ - Draft + `blocked` → `state:building` (draft check precedes the exclusion at labels-reconcile.sh:365) ✓ - Deleting the condition turns a test red — verified above, not just trusted ✓ - LABELS.md carries the sentence on the PR-side `blocked` row; the diff introduces no new label, no bootstrap row ✓ - `changelog.d/180.md` fragment present ✓ Also checked `has_label` (labels-reconcile.sh:535): `grep -qxF` — whole-line exact match, so a label like `blocker:ci-red` or `blocked-something` can never trip the exclusion. No findings, blocking or otherwise. The `state:addressing` imprecision under a hold is the spec's own accepted trade-off (#180, discussion #122 default A), not a review-round question.
claude-bot-andresmgsl commented 2026-07-25 09:26:34 +00:00 (Migrated from github.com)

Round summary — handing off

What shipped. decide_state() now treats a hand-set blocked label as a second exclusion on state:needs-human, exactly parallel to the needs-ruling one: a PR whose round has passed but which carries blocked decides state:addressing instead of handing off. This closes the ceremony#111-freeze gap where rig#126/rig#128 carried blocked and state:needs-human at once and rig#126 was merged seven minutes after the reconciler wrote the green label. The exclusion is deliberately not a blockers() emission — BLOCKERS is machine-owned and the converge loop would strip a hand-set hold on the next 15-minute tick (the trap #51 documents for needs-ruling); the rationale lives in the code comment. Ruling record: discussion #122 (armed default A fired).

Rounds. One round, one commit (1c9a82a). The full panel — codex, grok, kimi — approved the head on the first pass with no changes requested, so no revisions were made between rounds.

What was verified.

  • All six acceptance criteria from #180, checked in the PR body: exclusion fires (LABELS="blocked"state:addressing), control hands off (LABELS=""state:needs-human), clearing the label re-hands-off (exclusion, not a latch), blockers() output byte-identical with and without blocked, a draft carrying blocked still decides state:building, and mutation coverage (disabling the condition fails a directed hold excludes needs-human, 155/1).
  • test/run.sh green locally: 18 files, 156 labels-reconcile cases; shellcheck clean.
  • CI green at head 1c9a82a: test, self-guards, action-exercise, docs-sync-exercise, release-exercise (fixture-chain, step-replay consumer + dogfood), labels/reconcile.
  • All three reviewers verified at the head SHA and independently re-ran the suite; kimi additionally reviewed in a detached worktree.

Post-merge residue. None. No new label is introduced (the exclusion reads the existing PR-side blocked row, and LABELS.md carries the sentence), no migration or fixture backfill is owed, and changelog.d/180.md ships with the PR for the release assembly. The only remaining move is the merge.

## Round summary — handing off **What shipped.** `decide_state()` now treats a hand-set `blocked` label as a second exclusion on `state:needs-human`, exactly parallel to the `needs-ruling` one: a PR whose round has passed but which carries `blocked` decides `state:addressing` instead of handing off. This closes the ceremony#111-freeze gap where rig#126/rig#128 carried `blocked` and `state:needs-human` at once and rig#126 was merged seven minutes after the reconciler wrote the green label. The exclusion is deliberately **not** a `blockers()` emission — `BLOCKERS` is machine-owned and the converge loop would strip a hand-set hold on the next 15-minute tick (the trap #51 documents for `needs-ruling`); the rationale lives in the code comment. Ruling record: discussion #122 (armed default A fired). **Rounds.** One round, one commit (`1c9a82a`). The full panel — codex, grok, kimi — approved the head on the first pass with no changes requested, so no revisions were made between rounds. **What was verified.** - All six acceptance criteria from #180, checked in the PR body: exclusion fires (`LABELS="blocked"` → `state:addressing`), control hands off (`LABELS=""` → `state:needs-human`), clearing the label re-hands-off (exclusion, not a latch), `blockers()` output byte-identical with and without `blocked`, a draft carrying `blocked` still decides `state:building`, and mutation coverage (disabling the condition fails `a directed hold excludes needs-human`, 155/1). - `test/run.sh` green locally: 18 files, 156 labels-reconcile cases; shellcheck clean. - CI green at head `1c9a82a`: test, self-guards, action-exercise, docs-sync-exercise, release-exercise (fixture-chain, step-replay consumer + dogfood), labels/reconcile. - All three reviewers verified at the head SHA and independently re-ran the suite; kimi additionally reviewed in a detached worktree. **Post-merge residue.** None. No new label is introduced (the exclusion reads the existing PR-side `blocked` row, and LABELS.md carries the sentence), no migration or fixture backfill is owed, and `changelog.d/180.md` ships with the PR for the release assembly. The only remaining move is the merge.
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/ceremony#181
No description provided.