Skip to content

feat(coord): the dispatch gate green-lit the two verdicts that mean DO NOT BUILD (BACKLOG #1334) - #582

Open
wshallwshall wants to merge 5 commits into
mainfrom
claude/builder-2-1334-dispatch-gate
Open

feat(coord): the dispatch gate green-lit the two verdicts that mean DO NOT BUILD (BACKLOG #1334)#582
wshallwshall wants to merge 5 commits into
mainfrom
claude/builder-2-1334-dispatch-gate

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

dispatch_gate.py's judge() tested exactly one verdict value. demand-gate and owner-ruling -- the two verdicts that mean an item is not a builder's to just start -- fell through the same path as ordinary build work and came back ok. docs/BACKLOG.md untouched (verified, count 0) -- not this PR's row to author.

Structurally cannot widen a bypass: the fix only appends to notes, and a non-empty notes forces advise. Measured base vs fixed over the live ledger: {advise 131, refuse 344, ok 124} identical on both sides -- zero items gained ok.

What it does NOT catch, stated up front rather than implied: it would not have caught #1336 (the item that prompted filing this) -- at 883f773^ that item's banner read Verdict: build, which is a legitimately wrong banner, and a verdict-field reader cannot detect a wrong verdict field. PR 578 fixed the data; this fixes the reader. Different defects.

Changes no level on today's ledger: all 31 items carrying a gated verdict are already advise on their closing act, so this changes the stated REASON, not the count. The level only fires when a gated-verdict item also carries a code closing act, and none of today's 31 do.

Mutation-proved, three mutants each with a unique asserted anchor and a changed-file-hash check before scoring: branch-deleted (6 red), research-guard-mirrored (3 red, the exact two discriminators), verdict != build (4 red including an existing test -- proving the plain-build case does NOT guard the over-broad shape it was claimed to guard).

Verification, explicitly scoped: 29 tests pass (test_coord_dispatch_gate.py 20 cases, up from 13; plus test_tooling_partition.py). dispatch_gate.py --self-test passes 9 cases, up from 5. ruff/ruff format/mypy clean. Full suite not run.

…O NOT BUILD (BACKLOG #1334)

`judge()` tested exactly one verdict value -- `research`. `demand-gate` and `owner-ruling`,
the two that mean the item is not yours to just start, fell through and were returned as
ordinary build work. Confirmed by driving judge() rather than reading it: both returned
`ok` with a `code` closing act, against a positive control (`build` -> ok), a second
positive control (`research`/none -> advise) and a negative control (a nonsense verdict,
which also returned ok and is the residual noted below).

THE OBVIOUS JUSTIFICATION FOR THIS ITEM IS FALSE AND I ALMOST SHIPPED IT.

This gate green-lit BACKLOG #1336 to a dispatcher on 2026-08-24, and #1336 was not
startable -- an owner ruling ~105 lines below its banner put its only remaining route out
of scope. That is true, it cost this lane a slot, and it is what the item was filed on.

But measured at 883f773^, #1336's banner read `Verdict: build, Closing-act: code`. This
change keys on the VERDICT FIELD, so it returns `ok` on that row exactly as the old code
did. IT WOULD NOT HAVE CAUGHT #1336. The banner was wrong, and a reader of the banner
cannot detect a wrong banner. What fixed #1336 was a person reading the body and correcting
the row (PR 578). The two are complementary -- that corrected the DATA, this corrects the
READER -- and the commit says so rather than letting a true story imply a false mechanism.

WHAT IT BUYS, AT ITS REAL SIZE. 31 items at 883f773 declare a gated verdict. Every one is
ALREADY `advise` on its closing act, so on today's corpus this changes NO LEVEL: it changes
the REASON for 31 items, from one naming only who closes them to one naming what gates them
and who lifts it. Measured, same ledger, base vs fixed:

    base   {advise 131, refuse 344, ok 124}   gated-note 0
    fixed  {advise 131, refuse 344, ok 124}   gated-note 31
    items that GAINED ok: []      items that LOST ok: []

The empty GAINED set is the safety property and it is structural, not luck: the branch only
appends to `notes`, and non-empty `notes` forces `advise`. The level arm is real and
currently unexercised -- it fires when a gated-verdict item carries a `code` closing act,
which today's 31 do not (29 close by owner-ruling, 2 by blocked). The self-test drives that
case directly rather than waiting for the ledger to grow one.

THE BRANCH IS UNCONDITIONAL ON `Research:` AND GOES FIRST, and both are the fix:

* Not mirroring the research branch's `research in ("", "none")` guard is deliberate. A
  demand gate is lifted by a RULING, not by finishing research. Mirroring it is the
  plausible wrong fix and re-greens the item the moment a completed pass is recorded.
* The closing-act note ends "That is a complete outcome, not a failure." Left to lead, it
  tells the reader of a gated item that shipping the code finishes the job. Ordering gets an
  assertion, not a comment.

MUTATION-PROVED WITH THREE MUTANTS, NOT TWO, and the third is the point. Each asserts its
anchor is unique and the file hash CHANGED before scoring:

    A  branch deleted            6 red   the four gated cases, the ordering test, self-test
    B  research guard mirrored   3 red   EXACTLY the two discriminators, plus self-test
    C  `verdict != "build"`      4 red   test_completed_research_with_a_code_closing_act_passes

Three mutants, three distinct red sets. C is why the third exists: the design this was built
from claimed its plain-build case guards against the over-broad shape. It does not --
measured, `verdict != "build"` leaves build items untouched, so that case passes over it,
and the guard is the EXISTING research test. Do not trim that test as redundant. A protocol
that never mutates INTO the broad shape cannot see this, which is how the claim survived.

Also adds `test_the_gated_verdicts_are_a_subset_of_the_closed_vocabulary`, which makes the
constant's own "must stay a subset" comment executable against `_VERDICTS` in
verdict_divergence_check.py. A typo in GATED_VERDICTS would silently gate nothing and every
other test here would still pass, because they all drive judge() with the spelling this
module defines.

SCOPE FENCE HONOURED: parts one and two only. scripts/docs/backlog_status_check.py is NOT
touched -- open rows #1324 and #1338 are against that file. Two files changed, both named
in the dispatch.

RESIDUAL, PRE-EXISTING, NOT CLOSED HERE: an unknown or misspelled verdict still green-lights
-- `zzznonsense`, `demand gate` with a space, `demandgate` all return ok, before and after.
Zero instances on today's ledger. Closing it means either importing a private from another
module or writing a fourth copy of the vocabulary, and that is a design call rather than
part of this row.

Verification, with scope: tests/test_coord_dispatch_gate.py (20, was 13) and
tests/test_tooling_partition.py -- 29 passed, on .venv/Scripts/python.exe (CPython 3.14.6
non-freethreaded, seven CI extras). `dispatch_gate.py --self-test` PASS at 9 cases, was 5.
ruff 0.15.22 (== the constraints.lock pin) check and format --check clean; mypy strict clean
on both files. NOT the full suite.

No test was removed or weakened. The ledger row is not mine to author and is not in this
commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@wshallwshall
wshallwshall enabled auto-merge (squash) August 25, 2026 17:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant