LABELS.md + issueflow-reconcile — post-merge: the fourth queue state, for a merged PR whose acceptance criteria remain #175

Closed
opened 2026-07-25 00:06:36 +00:00 by dan-claude-bot · 5 comments
dan-claude-bot commented 2026-07-25 00:06:36 +00:00 (Migrated from github.com)

Context

The board has no way to say "the PR landed; post-merge acceptance criteria remain." Discussion #172 collected one evening's cost of that gap, all four failure modes live on incubator's board the same night: state riding in prose re-stated every comment (the attention lesson from #83, inverted), the operator reading claimed as "builder still owes work" (incubator#55), the 48h reclaim unable to tell a parked post-merge claim from a stale one, and two independent duty loops treating a merged build/* branch under a still-open claimed issue as interrupted work (phantom resumes, patched per-box in crew#19 and in the shared engine in crew#16 — bridges, because the board doesn't carry the fact).

The doctrine half already exists: #151's contract made a post-merge criterion carry Refs #N, triage-owned close, in the criterion itself (TRIAGE.md L88–L99, BUILDER.md L123–L132). This issue gives that contract its queue state.

Code this touches, pinned at 486bbd1:

Decisions below were converged on the discussion (triage and codex aligned on all three open questions; operator's re-entry ruling recorded 23:01Z).

Spec

  • D1 — the state is post-merge, the fourth work-queue label. Color #006B75. Means: the Refs-linked PR merged; post-merge acceptance criteria remain; the claim is released — nothing here is buildable and nobody owes a draft. Set by the sweep or by triage; the exactly-one invariant becomes ready / claimed / blocked / post-merge. Rejected names, for the record: verifying asserts activity that is false during an external wait; landed describes the PR, not the issue.
  • D2 — the transition is machine-owned. The sweep flips claimed → post-merge and clears the assignee when a merged PR referencing the issue with Refs #N exists and the issue body still carries unchecked checkboxes. How merged PRs are gathered is the builder's detail (the open-PR scan at OPEN_PR_ISSUES is the shape to extend). Non-triggers, each deliberate: an open PR (nothing landed); a Closes-linked merge (the close itself is the event; a hand-reopened #137-shape issue is triage's, not the sweep's); a merged Refs PR with zero unchecked boxes (that is a triage close, and the sweep never closes).
  • D3 — the flip carries a transition comment; triage completes it. The sweep's comment names the unchecked criteria verbatim, states the claim is released, and says triage owes the handoff completion — owner and wake condition — in a follow-up comment; automation never guesses intent, and those two facts are judgment. When triage or the operator flips by hand, triage writes the full transition comment (remaining criteria, owner, wake condition) in the same tick. A bare flag is noise; same doctrine as needs-ruling.
  • D4 — composition: never with blocked (the wait lives in the transition comment, not on a second queue label); never with attention (assignee-clear is part of the invariant — there is nobody parked-for); needs-ruling still composes. A post-merge issue carrying an assignee is flagged, not repaired — a hand-assign is intent, and the sweep does not undo the operator's hand.
  • D5 — the reclaim ignores post-merge. No claim, no clock: a post-merge issue quiet for weeks is the state working, not staleness.
  • D6 — re-entry releases to ready, no special standing (operator ruling, 2026-07-24 23:01Z): when the remainder becomes buildable, triage flips post-merge → ready or mints a fresh issue; any builder claims from current main; the original builder has no standing and attention is not set on re-entry.
  • D7 — the close is #151's contract, unchanged: triage ticks the verified criteria and closes. This issue adds the label under that contract, not a new close path.
  • D8 — the duty-engine bridges are out of scope. crew#16 / crew#19 key on the merged PR, not the label, so they are correct today and stay correct after this lands. Nothing here edits crew.
  • The queue-conflict message (L272) gains post-merge in its label list.

Tasks

  • LABELS.md: the post-merge row, the four-state invariant sentence, the sweep paragraph (transition, transition comment, reclaim exemption), and the composition sentences (D4).
  • TRIAGE.md: the post-merge-criterion bullet points at the state; transition-comment ownership and re-entry (D3, D6).
  • BUILDER.md: the Refs #N paragraph says the merge releases the claim — the builder walks away at merge and does not hold claimed through the wait.
  • actions/labels-reconcile/labels-reconcile.sh core_label_rows(): the post-merge|006B75|... row (and whatever registry-count assertions pin it in test/labels-reconcile.test.sh).
  • actions/issueflow-reconcile/issueflow-reconcile.sh: QUEUE_LABELS gains post-merge; the D2 transition + assignee clear + D3 comment; the D5 exemption; the D4 assignee-present flag; the conflict-message text.
  • test/issueflow-reconcile.test.sh: the cases in the test plan, with the sweep-body paths in an executed subprocess (#91's lesson).
  • changelog.d/ fragment.

Acceptance criteria

  • A claimed issue whose Refs-linked PR merged with unchecked boxes is, one tick later: post-merge, unassigned, with the transition comment naming the unchecked criteria and the triage-owes-completion line.
  • A post-merge issue with no open PR and no activity for over 48 hours is untouched — no reclaim comment, no relabel.
  • claimed + post-merge together draws the conflict flag, and the flag's message lists all six labels.
  • Non-triggers hold: an open Refs PR, a merged Closes PR, and a merged Refs PR with zero unchecked boxes each leave the issue exactly as found.
  • A post-merge issue with an assignee is flagged, not unassigned.
  • LABELS.md, TRIAGE.md and BUILDER.md all carry the same word, post-merge, and the release-at-merge / re-entry doctrine (D3, D6).
  • Post-merge criterion — triage owns the close and this PR references this issue with Refs #N, not Closes #N: after the operator's next labels bootstrap dispatch, post-merge exists on this repo and the missing-core-labels warning does not fire on the following sweep.

Test plan

Must fail:

  • Merged Refs PR + unchecked boxes and the sweep does not flip → red.
  • Reclaim fires on a quiet post-merge issue → red.
  • Conflict message missing post-merge from its list → red.

Boundaries: open Refs PR untouched; merged Closes PR untouched; merged Refs PR with all boxes checked untouched; hand-assigned post-merge flagged not unassigned; post-merge + needs-ruling composes without conflict.

Control: existing reclaim behavior on a genuinely stale claimed issue is byte-identical before and after.

Dependencies

None — no open issue or PR gates this. From discussion #172. Related, not gating: #151 (the contract this labels), crew#16/crew#19 (engine-side bridges, out of scope per D8), discussion #122 (PR-side state:* question, separate axis).

## Context The board has no way to say "the PR landed; post-merge acceptance criteria remain." [Discussion #172](https://github.com/heavy-duty/ceremony/discussions/172) collected one evening's cost of that gap, all four failure modes live on incubator's board the same night: state riding in prose re-stated every comment (the `attention` lesson from #83, inverted), the operator reading `claimed` as "builder still owes work" ([incubator#55](https://github.com/heavy-duty/incubator/issues/55)), the 48h reclaim unable to tell a parked post-merge claim from a stale one, and two independent duty loops treating a merged `build/*` branch under a still-open `claimed` issue as interrupted work (phantom resumes, patched per-box in crew#19 and in the shared engine in crew#16 — bridges, because the board doesn't carry the fact). The doctrine half already exists: [#151's contract](https://github.com/heavy-duty/ceremony/issues/151) made a post-merge criterion carry `Refs #N`, triage-owned close, in the criterion itself ([TRIAGE.md L88–L99](https://github.com/heavy-duty/ceremony/blob/486bbd10a49b25a6169cab42a2e89601d5593141/TRIAGE.md#L88-L99), [BUILDER.md L123–L132](https://github.com/heavy-duty/ceremony/blob/486bbd10a49b25a6169cab42a2e89601d5593141/BUILDER.md#L123-L132)). This issue gives that contract its queue state. Code this touches, pinned at `486bbd1`: - [LABELS.md L46–L61](https://github.com/heavy-duty/ceremony/blob/486bbd10a49b25a6169cab42a2e89601d5593141/LABELS.md#L46-L61) — the three-state queue table and the exactly-one invariant. - [issueflow-reconcile.sh L24](https://github.com/heavy-duty/ceremony/blob/486bbd10a49b25a6169cab42a2e89601d5593141/actions/issueflow-reconcile/issueflow-reconcile.sh#L24) (`QUEUE_LABELS`), [L94–L118](https://github.com/heavy-duty/ceremony/blob/486bbd10a49b25a6169cab42a2e89601d5593141/actions/issueflow-reconcile/issueflow-reconcile.sh#L94-L118) (claim decisions), [L270–L307](https://github.com/heavy-duty/ceremony/blob/486bbd10a49b25a6169cab42a2e89601d5593141/actions/issueflow-reconcile/issueflow-reconcile.sh#L270-L307) (conflict message + the claimed block that would reclaim a parked post-merge claim today). - [labels-reconcile.sh L456–L477](https://github.com/heavy-duty/ceremony/blob/486bbd10a49b25a6169cab42a2e89601d5593141/actions/labels-reconcile/labels-reconcile.sh#L456-L477) — `core_label_rows()`, the registry the bootstrap upserts from. Decisions below were converged on the discussion (triage and codex aligned on all three open questions; operator's re-entry ruling recorded [23:01Z](https://github.com/heavy-duty/ceremony/discussions/172#discussioncomment-17773178)). ## Spec - **D1 — the state is `post-merge`, the fourth work-queue label.** Color `#006B75`. Means: *the Refs-linked PR merged; post-merge acceptance criteria remain; the claim is released — nothing here is buildable and nobody owes a draft.* Set by the sweep or by triage; the exactly-one invariant becomes `ready` / `claimed` / `blocked` / `post-merge`. Rejected names, for the record: `verifying` asserts activity that is false during an external wait; `landed` describes the PR, not the issue. - **D2 — the transition is machine-owned.** The sweep flips `claimed → post-merge` and clears the assignee when a **merged** PR referencing the issue with `Refs #N` exists and the issue body still carries unchecked checkboxes. How merged PRs are gathered is the builder's detail (the open-PR scan at `OPEN_PR_ISSUES` is the shape to extend). Non-triggers, each deliberate: an open PR (nothing landed); a `Closes`-linked merge (the close itself is the event; a hand-reopened #137-shape issue is triage's, not the sweep's); a merged Refs PR with zero unchecked boxes (that is a triage close, and the sweep never closes). - **D3 — the flip carries a transition comment; triage completes it.** The sweep's comment names the unchecked criteria verbatim, states the claim is released, and says triage owes the handoff completion — **owner and wake condition** — in a follow-up comment; automation never guesses intent, and those two facts are judgment. When triage or the operator flips by hand, triage writes the full transition comment (remaining criteria, owner, wake condition) in the same tick. A bare flag is noise; same doctrine as `needs-ruling`. - **D4 — composition:** never with `blocked` (the wait lives in the transition comment, not on a second queue label); never with `attention` (assignee-clear is part of the invariant — there is nobody parked-for); `needs-ruling` still composes. A `post-merge` issue carrying an assignee is **flagged**, not repaired — a hand-assign is intent, and the sweep does not undo the operator's hand. - **D5 — the reclaim ignores `post-merge`.** No claim, no clock: a `post-merge` issue quiet for weeks is the state working, not staleness. - **D6 — re-entry releases to `ready`, no special standing** (operator ruling, 2026-07-24 23:01Z): when the remainder becomes buildable, triage flips `post-merge → ready` or mints a fresh issue; any builder claims from current `main`; the original builder has no standing and `attention` is not set on re-entry. - **D7 — the close is #151's contract, unchanged:** triage ticks the verified criteria and closes. This issue adds the label under that contract, not a new close path. - **D8 — the duty-engine bridges are out of scope.** crew#16 / crew#19 key on the merged PR, not the label, so they are correct today and stay correct after this lands. Nothing here edits crew. - The queue-conflict message ([L272](https://github.com/heavy-duty/ceremony/blob/486bbd10a49b25a6169cab42a2e89601d5593141/actions/issueflow-reconcile/issueflow-reconcile.sh#L272)) gains `post-merge` in its label list. ## Tasks - [x] `LABELS.md`: the `post-merge` row, the four-state invariant sentence, the sweep paragraph (transition, transition comment, reclaim exemption), and the composition sentences (D4). - [x] `TRIAGE.md`: the post-merge-criterion bullet points at the state; transition-comment ownership and re-entry (D3, D6). - [x] `BUILDER.md`: the `Refs #N` paragraph says the merge releases the claim — the builder walks away at merge and does not hold `claimed` through the wait. - [x] `actions/labels-reconcile/labels-reconcile.sh` `core_label_rows()`: the `post-merge|006B75|...` row (and whatever registry-count assertions pin it in `test/labels-reconcile.test.sh`). - [x] `actions/issueflow-reconcile/issueflow-reconcile.sh`: `QUEUE_LABELS` gains `post-merge`; the D2 transition + assignee clear + D3 comment; the D5 exemption; the D4 assignee-present flag; the conflict-message text. - [x] `test/issueflow-reconcile.test.sh`: the cases in the test plan, with the sweep-body paths in an executed subprocess (#91's lesson). - [x] `changelog.d/` fragment. ## Acceptance criteria - [x] A `claimed` issue whose Refs-linked PR merged with unchecked boxes is, one tick later: `post-merge`, unassigned, with the transition comment naming the unchecked criteria and the triage-owes-completion line. - [x] A `post-merge` issue with no open PR and no activity for over 48 hours is untouched — no reclaim comment, no relabel. - [x] `claimed` + `post-merge` together draws the conflict flag, and the flag's message lists all six labels. - [x] Non-triggers hold: an open Refs PR, a merged `Closes` PR, and a merged Refs PR with zero unchecked boxes each leave the issue exactly as found. - [x] A `post-merge` issue with an assignee is flagged, not unassigned. - [x] `LABELS.md`, `TRIAGE.md` and `BUILDER.md` all carry the same word, `post-merge`, and the release-at-merge / re-entry doctrine (D3, D6). - [x] **Post-merge criterion** — triage owns the close and this PR references this issue with `Refs #N`, not `Closes #N`: after the operator's next labels bootstrap dispatch, `post-merge` exists on this repo and the missing-core-labels warning does not fire on the following sweep. ## Test plan Must fail: - Merged Refs PR + unchecked boxes and the sweep does **not** flip → red. - Reclaim fires on a quiet `post-merge` issue → red. - Conflict message missing `post-merge` from its list → red. Boundaries: open Refs PR untouched; merged Closes PR untouched; merged Refs PR with all boxes checked untouched; hand-assigned `post-merge` flagged not unassigned; `post-merge` + `needs-ruling` composes without conflict. Control: existing reclaim behavior on a genuinely stale `claimed` issue is byte-identical before and after. ## Dependencies None — no open issue or PR gates this. From [discussion #172](https://github.com/heavy-duty/ceremony/discussions/172). Related, not gating: #151 (the contract this labels), crew#16/crew#19 (engine-side bridges, out of scope per D8), [discussion #122](https://github.com/heavy-duty/ceremony/discussions/122) (PR-side `state:*` question, separate axis).
codex-bot-andresmgsl commented 2026-07-25 00:08:09 +00:00 (Migrated from github.com)

Claimed by @codex-bot-andresmgsl. Starting build now on build/175-post-merge-queue-state; I will open a draft PR after the first commit.

Claimed by @codex-bot-andresmgsl. Starting build now on `build/175-post-merge-queue-state`; I will open a draft PR after the first commit.
github-actions[bot] commented 2026-07-25 10:31:51 +00:00 (Migrated from github.com)

The Refs-linked PR merged with these acceptance criteria still unchecked:

  • LABELS.md: the post-merge row, the four-state invariant sentence, the sweep paragraph (transition, transition comment, reclaim exemption), and the composition sentences (D4).
  • TRIAGE.md: the post-merge-criterion bullet points at the state; transition-comment ownership and re-entry (D3, D6).
  • BUILDER.md: the Refs #N paragraph says the merge releases the claim — the builder walks away at merge and does not hold claimed through the wait.
  • actions/labels-reconcile/labels-reconcile.sh core_label_rows(): the post-merge|006B75|... row (and whatever registry-count assertions pin it in test/labels-reconcile.test.sh).
  • actions/issueflow-reconcile/issueflow-reconcile.sh: QUEUE_LABELS gains post-merge; the D2 transition + assignee clear + D3 comment; the D5 exemption; the D4 assignee-present flag; the conflict-message text.
  • test/issueflow-reconcile.test.sh: the cases in the test plan, with the sweep-body paths in an executed subprocess (#91's lesson).
  • changelog.d/ fragment.
  • A claimed issue whose Refs-linked PR merged with unchecked boxes is, one tick later: post-merge, unassigned, with the transition comment naming the unchecked criteria and the triage-owes-completion line.
  • A post-merge issue with no open PR and no activity for over 48 hours is untouched — no reclaim comment, no relabel.
  • claimed + post-merge together draws the conflict flag, and the flag's message lists all six labels.
  • Non-triggers hold: an open Refs PR, a merged Closes PR, and a merged Refs PR with zero unchecked boxes each leave the issue exactly as found.
  • A post-merge issue with an assignee is flagged, not unassigned.
  • LABELS.md, TRIAGE.md and BUILDER.md all carry the same word, post-merge, and the release-at-merge / re-entry doctrine (D3, D6).
  • Post-merge criterion — triage owns the close and this PR references this issue with Refs #N, not Closes #N: after the operator's next labels bootstrap dispatch, post-merge exists on this repo and the missing-core-labels warning does not fire on the following sweep.

The merge releases the claim; no builder owes a draft. Triage owes completion in a follow-up comment that names the owner and wake condition.

<!-- issueflow:post-merge-transition-pr-176 --> The Refs-linked PR merged with these acceptance criteria still unchecked: - [ ] `LABELS.md`: the `post-merge` row, the four-state invariant sentence, the sweep paragraph (transition, transition comment, reclaim exemption), and the composition sentences (D4). - [ ] `TRIAGE.md`: the post-merge-criterion bullet points at the state; transition-comment ownership and re-entry (D3, D6). - [ ] `BUILDER.md`: the `Refs #N` paragraph says the merge releases the claim — the builder walks away at merge and does not hold `claimed` through the wait. - [ ] `actions/labels-reconcile/labels-reconcile.sh` `core_label_rows()`: the `post-merge|006B75|...` row (and whatever registry-count assertions pin it in `test/labels-reconcile.test.sh`). - [ ] `actions/issueflow-reconcile/issueflow-reconcile.sh`: `QUEUE_LABELS` gains `post-merge`; the D2 transition + assignee clear + D3 comment; the D5 exemption; the D4 assignee-present flag; the conflict-message text. - [ ] `test/issueflow-reconcile.test.sh`: the cases in the test plan, with the sweep-body paths in an executed subprocess (#91's lesson). - [ ] `changelog.d/` fragment. - [ ] A `claimed` issue whose Refs-linked PR merged with unchecked boxes is, one tick later: `post-merge`, unassigned, with the transition comment naming the unchecked criteria and the triage-owes-completion line. - [ ] A `post-merge` issue with no open PR and no activity for over 48 hours is untouched — no reclaim comment, no relabel. - [ ] `claimed` + `post-merge` together draws the conflict flag, and the flag's message lists all six labels. - [ ] Non-triggers hold: an open Refs PR, a merged `Closes` PR, and a merged Refs PR with zero unchecked boxes each leave the issue exactly as found. - [ ] A `post-merge` issue with an assignee is flagged, not unassigned. - [ ] `LABELS.md`, `TRIAGE.md` and `BUILDER.md` all carry the same word, `post-merge`, and the release-at-merge / re-entry doctrine (D3, D6). - [ ] **Post-merge criterion** — triage owns the close and this PR references this issue with `Refs #N`, not `Closes #N`: after the operator's next labels bootstrap dispatch, `post-merge` exists on this repo and the missing-core-labels warning does not fire on the following sweep. The merge releases the claim; no builder owes a draft. Triage owes completion in a follow-up comment that names the owner and wake condition.
dan-claude-bot commented 2026-07-25 10:40:28 +00:00 (Migrated from github.com)

Completion follow-up per the transition above — owner and wake condition.

Build side is done and verified on main at ede2767 (PR #176): the post-merge|006B75 row in core_label_rows(), QUEUE_LABELS + the D2 transition + D3 comment + D4 flag + D5 exemption in issueflow-reconcile.sh, the test-plan cases in both test files, the doctrine word in LABELS.md / TRIAGE.md / BUILDER.md, and the changelog fragment. Ticked in the body; one criterion remains.

Remaining criterion — owner: @danmt. Wake condition: post-merge exists in this repo's label registry. The labels workflow's manual bootstrap dispatch on main creates it — the registry row is already there since #176. Triage cannot substitute: the label create returns 404 on this token, and the sweep's own attempt already failed live, see below.

Known interim board noise, recorded so nobody chases it as a bug. This issue was the first live exercise of its own transition: the sweep fired at 10:31Z, wrote the comment above and released the claim, but --add-label post-merge cannot apply a label the repo does not have — so this issue now carries no queue label, and the next sweep tick will add needs-triage (the missing-queue-state repair). That is bootstrap ordering — the issue that introduces the fourth state necessarily merges before the dispatch that mints its label — not a sweep defect. After the dispatch: triage clears the interim label, verifies the missing-core-labels warning is gone on the following sweep, ticks the last criterion, and closes under #151's contract.

**Completion follow-up per the transition above — owner and wake condition.** Build side is done and verified on `main` at `ede2767` (PR #176): the `post-merge|006B75` row in `core_label_rows()`, `QUEUE_LABELS` + the D2 transition + D3 comment + D4 flag + D5 exemption in `issueflow-reconcile.sh`, the test-plan cases in both test files, the doctrine word in LABELS.md / TRIAGE.md / BUILDER.md, and the changelog fragment. Ticked in the body; one criterion remains. **Remaining criterion — owner: @danmt. Wake condition: `post-merge` exists in this repo's label registry.** The labels workflow's manual bootstrap dispatch on `main` creates it — the registry row is already there since #176. Triage cannot substitute: the label create returns 404 on this token, and the sweep's own attempt already failed live, see below. **Known interim board noise, recorded so nobody chases it as a bug.** This issue was the first live exercise of its own transition: the sweep fired at 10:31Z, wrote the comment above and released the claim, but `--add-label post-merge` cannot apply a label the repo does not have — so this issue now carries **no queue label**, and the next sweep tick will add `needs-triage` (the missing-queue-state repair). That is bootstrap ordering — the issue that introduces the fourth state necessarily merges before the dispatch that mints its label — not a sweep defect. After the dispatch: triage clears the interim label, verifies the missing-core-labels warning is gone on the following sweep, ticks the last criterion, and closes under #151's contract.
danmt commented 2026-07-25 10:53:39 +00:00 (Migrated from github.com)

I just ran the workflow, some else to close this?

I just ran the workflow, some else to close this?
dan-claude-bot commented 2026-07-25 11:05:37 +00:00 (Migrated from github.com)

Nothing else — closing now. The last criterion verified: your dispatch (run 30155221049, 10:51:50Z) created post-merge in the registry, and the following sweep (run 30155570694, 11:03:24Z) ran with zero warnings — the missing-core-labels warning is gone. Interim needs-triage cleared, post-merge applied (this issue is the label's first wearer), final box ticked in the body. All criteria met; closing under #151's contract.

Nothing else — closing now. The last criterion verified: your dispatch ([run 30155221049](https://github.com/heavy-duty/ceremony/actions/runs/30155221049), 10:51:50Z) created `post-merge` in the registry, and the following sweep ([run 30155570694](https://github.com/heavy-duty/ceremony/actions/runs/30155570694), 11:03:24Z) ran with zero warnings — the missing-core-labels warning is gone. Interim `needs-triage` cleared, `post-merge` applied (this issue is the label's first wearer), final box ticked in the body. All criteria met; closing under #151's contract.
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#175
No description provided.