ceremony/actions
cluade-reviewer-andresmgsl 062e016a42 fix(labels): all four review gaps — preserved ids, zero-write no-op, every mutation counted, no success token (#192)
@codex-reviewer-andresmgsl's four gaps, all real, all taken.

1. PRESERVED IDS COME FROM THE ISSUE. The removal path read only .labels[].name
   and then re-resolved every preserved label through the repository-wide list
   — so preservation depended on a paginated read with nothing to do with this
   issue, and an incomplete one would drop a bystander. It now keeps
   name<TAB>id from the issue payload, subtracts removals by name, and resolves
   ONLY added names. Fixture: a bystander on the issue with id 14 that is
   absent from the repo-list fixture entirely must still survive the PUT.

2. AN ABSENT REMOVAL WRITES NOTHING. I had it PUT the unchanged set, arguing
   the write proved the sweep reached the forge. The GET already proves that,
   and replacing a set with itself opens ceremony#128's window for no state
   change — most calls here are exactly this case, since the reconcilers call
   --remove-label unconditionally. Short-circuits when the wanted set equals
   the current one. This was the policy-shaped choice flagged for @andres; the
   reviewer's reasoning is better than mine was.

3. EVERY LABEL MUTATION REACHES THE TALLY. The marker was only on the primary
   state edit, so clearing `merge-next` and both `stale` edits could fail into
   the generic per-PR branch and still finish `reconciled.` and exit 0. All
   four sites go through one `label_write` helper, so a future call site cannot
   reopen it by forgetting to mark itself. Probe: a failed NON-primary write
   (unstale on a blocked PR) must fail the sweep.

4. NO SUCCESS TOKEN IN A FAILURE TAIL. "NOT reconciled." still contains
   "reconciled.", which a log-tail consumer greps for. The line is now
   "sweep incomplete", and the test asserts the whole output is free of the
   token rather than only of the success prefix.

Also added the two fault boundaries the acceptance plan named and the fixtures
never proved: a failed current-label GET and a failed replacement PUT, each
non-zero with the backend's verb/path/status diagnostic.

Mutation-tested, each gap separately: bypassing the tally reds 3, re-resolving
preserved ids reds 7, writing the unchanged set reds 1.

forge-backends 115/115 (was 110), labels-reconcile 175/175 (was 172),
test/run.sh 22/22 under jq 1.7 and jq 1.6, shellcheck 0.10.0 and actionlint
clean.

Refs #192
2026-08-05 13:03:25 +00:00
..
changelog-armed feat: enforce changelog shape in guard and assembler 2026-07-24 16:14:35 +00:00
changelog-assembled feat: changelog-assembled — the release section must be exactly the fragments it consumed 2026-07-24 09:29:07 +00:00
changelog-monotonic feat: actions/changelog-monotonic — shipped headings are append-only 2026-07-22 21:09:59 +00:00
docs-sync fix: refuse non-regular nodes everywhere; machine-verify the mirror README 2026-07-22 21:59:37 +00:00
drill-recorded feat: actions/drill-recorded — a release carries its evidence 2026-07-22 21:28:06 +00:00
issueflow-reconcile fix(issueflow): the issue/PR discriminator is GitHub-only 2026-08-02 20:44:58 +00:00
labels-reconcile fix(labels): all four review gaps — preserved ids, zero-write no-op, every mutation counted, no success token (#192) 2026-08-05 13:03:25 +00:00
labels-scope fix(labels-scope): jq 1.6 cannot parse $label — the runner image ships 1.6 2026-08-02 20:34:02 +00:00
runner-isolated feat(guards): actions/runner-isolated — the scan, the two-condition rule, the exit codes (#58) 2026-07-23 12:03:55 +00:00
.gitkeep ci: scaffold test and lint workspace 2026-07-22 16:56:25 +00:00