ceremony/test
cluade-reviewer-andresmgsl 0f20f4b6ef fix(labels): a label removal that cannot happen fails the sweep, and removal itself now works (#192)
Two defects, one cause, and the second is why the first survived a week.

THE WRITE. Removal was a per-label `DELETE .../labels/{id}` loop. On this
instance that call returns HTTP 500 for every removal under the token the
sweep actually holds — measured inside Actions, probe run 701, where the same
`PUT .../labels` with the desired full set returns 200 including the empty set
for a full clear. A PAT gets 204 on the same DELETE, which is exactly why it
went unseen: it fails only for `${{ github.token }}`.

Net effect before this: on Forgejo the state machine could only ever ADD
labels. Every `state:*` transition needing the previous state cleared and every
`blocker:*` that should lift was inert. Both PRs open right now carry stale
`blocker:*` labels that are false and that nothing can remove.

So the removal path is read-current, compute-wanted, one PUT — the same shape
the assignee branch beside it already used. An ADD-ONLY call keeps its additive
POST: ceremony#128 lost a `release` label to a read-modify-write that clobbered
a concurrent set, and forge_labels_add stays pinned against ever doing that.
The window is accepted here and only here, where the caller asked to REMOVE
and no additive verb can say that. An unresolvable --add-label refuses before
any write, so a replacement PUT can never drop a label nobody asked to remove.

THE REPORTING. `labels-reconcile` logged `WARNING: label edit failed`, fell
through, and `main` printed `reconciled.` and exited 0 — while
`issueflow-reconcile` treated the identical 500 as fatal. One cause, two
contradictory policies, and the wrong one hid the write defect.

A failed write is fatal now, and the tally reaches main's exit code. That
second half is load-bearing: making reconcile_pr fatal alone is not enough,
because the loop swallows a per-PR non-zero into a log line and finishes. The
per-PR tolerance is right and stays — one bad PR must not blind the board — but
it now applies to READS. A sweep that could not write exits non-zero and never
prints `reconciled.`

The diagnostic says what was attempted and that it did not happen. The old text
blamed a missing label and told the operator to bootstrap, when the label was
present and the call returned 500 — #101's rule is report, do not diagnose.

Mutation-tested, all three ways: restoring the warn-and-continue reds 5 cases,
removing the tally reds 2, restoring the DELETE loop reds 7.

test/run.sh 22 files 0 failed under jq 1.7 and jq 1.6; shellcheck 0.10.0 and
actionlint clean.

Refs #192
2026-08-05 12:48:09 +00:00
..
fixtures Merge pull request #29 from codex-bot-andresmgsl/build/4-changelog-section 2026-07-22 20:12:01 +01:00
changelog-armed.test.sh fix: the sentinel's one-line contract is checked on the file, not the $(cat) word 2026-07-25 13:29:02 +00:00
changelog-assemble.test.sh test: sentinel rows across the shape predicate, the armed guard, and the assembler 2026-07-25 13:12:30 +00:00
changelog-assembled.test.sh test: cover grouped anchored release replay 2026-07-24 16:17:02 +00:00
changelog-monotonic.test.sh test: changelog-monotonic contract suite — constructed git histories 2026-07-22 21:11:46 +00:00
changelog.test.sh fix: the sentinel's one-line contract is checked on the file, not the $(cat) word 2026-07-25 13:29:02 +00:00
closes_references.test.sh feat(forge): replace both gh api graphql sites with REST + a body parser 2026-08-02 18:41:03 +00:00
decide.test.sh feat: lib/decide.sh — the merge door's decision, pure and exhaustively tested 2026-07-22 18:59:29 +00:00
docs-sync.test.sh fix: refuse non-regular nodes everywhere; machine-verify the mirror README 2026-07-22 21:59:37 +00:00
drill-recorded.test.sh feat: actions/drill-recorded — a release carries its evidence 2026-07-22 21:28:06 +00:00
facts.test.sh fix(forge): the release doors speak the shim, and an unread fact refuses (#191) 2026-08-04 11:31:06 +00:00
forge-backends.test.sh fix(labels): a label removal that cannot happen fails the sweep, and removal itself now works (#192) 2026-08-05 12:48:09 +00:00
forge.test.sh fix(labels-scope): jq 1.6 cannot parse $label — the runner image ships 1.6 2026-08-02 20:34:02 +00:00
harness.sh feat(forge): port every reconciler call site onto the shim 2026-08-02 19:43:58 +00:00
issueflow-reconcile.test.sh fix(issueflow): the issue/PR discriminator is GitHub-only 2026-08-02 20:44:58 +00:00
labels-reconcile.test.sh fix(labels): a label removal that cannot happen fails the sweep, and removal itself now works (#192) 2026-08-05 12:48:09 +00:00
labels-scope.test.sh fix(round): labeler.yml header tells the truth; labels-scope maps to scope:labels 2026-07-24 12:22:09 +00:00
labels-triggers.test.sh labels: keep edited/reopened on issues; correct 0.3.0 adoption prose (#199 round) 2026-07-28 19:04:27 +00:00
labels.test.sh fix(labels): panel and triage name identities that exist on this forge (#195) 2026-08-04 19:09:16 +00:00
release-chain.test.sh fix(forge): the release doors speak the shim, and an unread fact refuses (#191) 2026-08-04 11:31:06 +00:00
ruling.test.sh feat(forge): port every reconciler call site onto the shim 2026-08-02 19:43:58 +00:00
run.sh ci: scaffold test and lint workspace 2026-07-22 16:56:25 +00:00
runner-isolated.test.sh test(guards): runner-isolated fixture matrix — 12 spec rows plus the block-sequence cases (#58) 2026-07-23 12:03:55 +00:00
self-ref.test.sh feat: the reusable release workflow — both doors, one implementation 2026-07-22 19:55:39 +00:00
version.test.sh feat: lib/version.sh — one version abstraction, two backends 2026-07-22 18:19:50 +00:00