Uh oh!
There was an error while loading. Please reload this page.
fix(kanban): an INVERTED anchor pair is unreadable, not "nothing deployed" (backend#1994) - #259
Merged
Merged
Conversation
…oyed" (backend#1994) `classify_column` checked that both anchors EXIST and never that they are IN ORDER. Since its whole thesis is that POSITION decides rather than a name, the board's Status option ORDER is load-bearing -- and it was the one thing never asserted. Drag "Prod" above "On dev" and the range `>= _d && <= _p` becomes unsatisfiable, so EVERY column comes back `no`, "Prod" itself included: the router writes Done over shipped state and kanban-reconcile then asserts it. That is exactly the fail-open this classification exists to close, reachable by one board reorder nobody would file as risky. Inverted anchors now classify as `noboard`, the same fail-closed verdict as a missing one -- a reordered board is UNREADABLE, not evidence that nothing deployed (workspace CLAUDE.md rule 3: "cannot tell" must block). Both workflows' existing noboard policies already do the right thing with that verdict and are unchanged: the router exits 1 loudly, reconcile skips the card. The router's noboard message now names both failure modes, so an operator who hits an inverted board is not sent looking for a deleted column. Applied character-for-character to both copies -- kanban-reconcile.yml carries a byte-identical `classify_column` and case 0 of the selftest asserts that. The gap in the test was the same shape: case 7 removes an anchor, and nothing covered both anchors present but REVERSED. New case 7b builds that board by swapping BOARD's two anchors (derived, not restated), asserts the input really is inverted and otherwise unchanged, and runs it through `classify()` so both copies must agree. Verdict must be `noboard` for a mid-pipeline column and for "Prod" itself. Mutation-proven: reverting the `-gt` clause in both copies reddens exactly the two new cases, each reporting `-> no` -- the bug verbatim, "Prod" classified as free to overwrite. Reverting it in only ONE copy additionally reddens case 0 and turns 7b into a DISAGREE, confirming both copies are really exercised. Restored: 27 passed, 0 failed; `make check` green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
LukasWodka
commented
Aug 14, 2026
ContributorAuthor
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit bf3ef60. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Bugbot High on the staging promotion PR .github#258, which held the 2026-08-14
develop -> staginghop. Fixed ondevelopas an ordinary reviewed PR — nothing was pushed onto the promotion PR.classify_columnvalidated that both anchors exist and never that they are in order. Its whole thesis is that POSITION decides rather than a name, which makes the board's Status option ORDER load-bearing — and order was the one thing never asserted.If the Status options get dragged so
Prodsorts beforeOn dev, then_d > _p, the range>= _d && <= _pis unsatisfiable, and every column comes backno— includingProditself. The router then writesDoneover shipped state andkanban-reconcile.ymlasserts it. That is exactly the fail-open this classification was added to close, reachable by one board reorder nobody would file as risky.Fix
Inverted anchors classify as
noboard, the same fail-closed verdict as a missing anchor:A reordered board is unreadable, not evidence that nothing deployed — workspace
CLAUDE.mdrule 3, "cannot tell" must block. The comment above the helper now says why.Both workflows' existing
noboardpolicies already do the right thing with that verdict and are unchanged: the router exits 1 loudly, reconcile skips the card. Section 8 of the selftest already covers both. The router'snoboardmessage now names both failure modes ("missing or out of order"), so an operator who hits an inverted board is not sent looking for a deleted column.Applied character-for-character to both copies —
kanban-reconcile.ymlcarries a byte-identicalclassify_columnand case 0 of the selftest asserts that byte-identity. Verified post-edit: both regions hash toe1bf87cba9322626….Test
The gap in the test had the same shape: case 7 removes an anchor, and nothing covered both anchors present but reversed. New case 7b builds that board by swapping
BOARD's two anchors (derived fromBOARD, not a restated list), asserts the input really is inverted and otherwise unchanged (an inert input and a working guard produce the same green line), and runs it through the existingclassify()helper so both copies are exercised and must agree. Verdict must benoboardfor a mid-pipeline column and forProditself.make check(ruff + shellcheck + actionlint + house-rules + every selftest): green.Mutation evidence
Mutation A — revert the
-gtclause in BOTH copies (anchor asserted present in both before mutating, so the mutation cannot be inert):Exactly the two new cases redden, and the observed verdict is
no— the bug verbatim, withProdclassified as free to overwrite. No pre-existing case notices, which is why 7b was needed.Mutation B — revert it in ONE copy only (router fixed, reconcile broken), proving both copies are really under test rather than one standing in for the other:
Restored → 27 passed, 0 failed, and the two classify regions hash equal again.
Fixes tracebloc/backend#1994
🤖 Generated with Claude Code
Note
Medium Risk
Changes kanban automation that guards deploy columns from Done overwrites; scope is small and fail-closed, but mistakes could block legitimate Done routing until the board order is fixed.
Overview
classify_columninkanban-closure-router.ymlandkanban-reconcile.ymlnow returnsnoboardwhen On dev sorts after Prod, not only when an anchor is missing. Before that, an inverted Status option order made the deploy range empty so every column (including Prod) looked like “not deployed,” which could let Done overwrite shipped state.Existing
noboardhandling is unchanged (router fails the step; reconcile skips the card). The closure router’s error text now mentions anchors missing or out of order.kanban-deploy-state-selftest.pyadds case 7b for inverted anchors so both workflow copies stay in sync.Reviewed by Cursor Bugbot for commit bf3ef60. Bugbot is set up for automated code reviews on this repo. Configure here.