builder2: #1020 deliverable-address gate, #1105/#1235/#1273 single-definition cleanups (stranded branch) - #428
Conversation
… checks (BACKLOG #1105) The docstring said `APPROVED_KEX_GROUPS` reaches "zero of this function's six call sites" and, three paragraphs later, "a call at six sites with zero effect". Counted on origin/main rather than taken from the item: there are SEVEN sites that build and harden a real TLS context (api/tls.py:55, config/tls_policy.py:1001, transports/ dicom.py:145 and :463, transports/mllp.py:552 and :591, transports/remotefile.py:248) plus one report-only reference in kex_groups_report's throwaway probe at :181. The fix is not six-to-seven. A hardcoded seven is the same defect one increment later: a second definition of a fact the tests already DERIVE (test_tls_policy.py:594-630 scans for the call marker instead of listing sites), and nothing checks the docstring against it. So the count is removed and the derived list is named as the source. This is the rule the project already applies to the backlog reader and to ENGINE_UI_SEAM: one definition, derived, never a hand-maintained integer beside it. The item said the docstring was "the only place it is asserted". It was not. The same wrong count sat in two more places, both found by grep and both fixed here: tests/test_tls_floor_probe.py:206 and tests/test_tls_policy.py:124. Ledger copies are left alone -- authoring ledger content is not a builder's (owner ruling 2026-08-13), and docs/archive/backlog/BACKLOG-CLOSED.md additionally carries stale LINE numbers for the same sites, which is a separate correction for whoever owns that file. No behaviour change: comments and docstrings only. Verified: ruff check + ruff format --check clean on all three files; mypy strict clean on tls_policy.py; pytest tests/test_tls_policy.py tests/test_tls_floor_probe.py = 81 passed, exit 0. Import resolution confirmed to this worktree, not the primary checkout, before trusting that run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s (BACKLOG #1020) The PHI startup gate computes `security_channel_ready` from the SMTP transport alone (`notify_security_events` + `email_smtp_host` + `email_from`). That answers "is a transport configured" and never "can the account that matters actually receive" -- SDS-3.8, the instrument answering the adjacent question. The two come apart on exactly the instance the gate protects: `_ensure_bootstrap_admin` creates the account holding `frozenset(Permission)` with no `email=`, and `SecurityEventNotifier.notify` opens `if not event.email: return`, so all ten notice types no-op for it while the gate reports a healthy channel. `AuthService.has_notifiable_admin()` is the missing half. Scoped to the ROLE, not the bootstrap account: `email` is optional in `UserCreateRequest` and is not required for the Administrator role, so a hand-created privileged account has the identical hole, and keying on the bootstrap user would close this instance while leaving the class open. This is the predicate only. The startup gate that consumes it is the next layer, and its PLACEMENT is a separate decision (ADR 0167) -- measured: raising after `engine.start()` hangs (BACKLOG #1257), and raising earlier in the lifespan exits with uvicorn's 3 rather than the 2 every `_serve` refusal returns. THE ARMS ARE ASYMMETRIC ON PURPOSE, and the mutation proves it discriminates rather than merely passing. Removing the role test reds `test_a_non_administrator_with_an_address_is_ not_enough` ALONE, with the other two still green -- so the arms separate "keys on a deliverable ADMIN" from "is merely hard to satisfy". A uniform failure would have taught nothing (COMMON 4.5.2). The bootstrap arm reproduces the defect on the REAL first-run path rather than a hand-built fixture. RECORDED, NOT FIXED: this is now the THIRD copy of "who is an enabled administrator" in this file, beside `is_last_enabled_admin` and `_other_enabled_admin_exists`. Their agreement is a convention with nothing binding it -- if one gains a lockout or auth_provider condition the others keep the old answer silently. The docstring says so rather than claiming the copies cannot drift, which would be an unenforced property of exactly the kind #1105 was about. Extracting a shared enumeration is its own item; doing it here would rewrite two guards this change has no business touching. Verified: ruff check + ruff format --check clean; mypy strict clean on service.py; pytest tests/test_notifiable_admin.py tests/test_last_admin_guard.py = 7 passed, exit 0 (the sibling suite included deliberately -- it exercises the enumeration this sits beside). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…le address, early in the lifespan
Records the PLACEMENT decision for BACKLOG #1020, which the owner ruling (2026-08-13,
option (b): gate startup on a deliverable channel) settled the WHAT of but not the WHERE.
The defect: `security_channel_ready` is `notify_security_events and email_smtp_host and
email_from` -- SMTP wiring alone. It asks "is a transport configured" and never "can the
account that matters actually receive" (SDS-3.8). Those come apart on exactly the
instance the gate protects: the bootstrap admin holds `frozenset(Permission)` with no
`email=`, and the notifier opens `if not event.email: return`.
PLACEMENT, settled by measurement rather than preference:
LIFESPAN after engine.start() -- OUT. #1257: an exception there unwinds nothing and
HANGS, which is strictly worse than the defect.
PREFLIGHT in _serve -- recommended, then WITHDRAWN BY ITS AUTHOR on
measurement: _serve opens a store zero times across
1042-2833 and list_users() is async, so it is the
first store open in a preflight that never has one.
EARLY-LIFESPAN -- CHOSEN. The 191-line window between app.py:5540
(open_store) and :5731 (engine.start()); store open,
no engine tasks, check is a plain await.
THE EXIT CODE CHANGES AND THE DIVERGENCE IS FORCED. Measured: raising there exits 3 in
0.49s, and `sys.exit(2)` there ALSO exits 3 -- uvicorn catches SystemExit and treats it
as a startup failure. Against a positive control that reached a RUNNING server and
self-stopped with a distinct 99, which is what makes the exits mean anything rather than
being the only outcome the rig could produce.
The cost is stated no larger than it is, and an earlier draft is corrected in place:
_serve returns 2 at 32 sites and DEPLOYMENT.md says "(exit 2)" twice, but BOTH citations
are scoped to specific refusals and no line generalises it -- so this is an inconsistency
with two documented refusals, not a contradiction of a universal claim. And it must not
be claimed exit 2 gives a clean stop today: install-service.ps1:463 sets NSSM `AppExit
Default Restart`, so the wrapper restarts on any code.
Rejected and recorded rather than omitted: a sentinel catching SystemExit at the
uvicorn.run() call site to re-exit 2. UNTESTED -- that "if" was never measured -- and a
cross-layer mechanism bought to remove an inconsistency the NSSM finding makes nearly
free. An unmentioned alternative gets re-derived; a declined one with a reason does not.
The ADR states plainly what it does NOT demonstrate: the termination evidence is a
MINIMAL REPRO, not the real gate, so it does not discharge #1020's rider. A rider that
exists because someone inferred is not satisfied by an inference.
The DEPLOYMENT.md exit-3 line deliberately does NOT land here -- it lands with the gate,
because documenting a startup-stage refusal before one exists describes behaviour the
code does not have.
Verified: pytest tests/test_feature_map_claims.py = 9 passed, exit 0 (the doc-link and
ADR-index checks); ruff/format clean via pre-commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ixed on PR #394 The ADR disqualified the LIFESPAN placement because raising after `engine.start()` hangs. That measurement was taken against `origin/main`, and I wrote it as though it were a property of the codebase. It is not. VERIFIED BY READING THE ARTIFACT, not the claim that reached me: `tests/test_lifespan_startup_unwinds.py` exists at refs/pull/394/head, docstring "BACKLOG #1257: a startup failure after `engine.start()` must let the PROCESS exit." So #1257's fix is already built, on a PR, while the item still reads "not started" on main -- the same unpushed-work-is-invisible shape that produced tonight's duplicate #1105 build and the #1216/#1272 duplicate filing. THE DECISION IS UNCHANGED AND DOES NOT DEPEND ON THE HANG. EARLY-LIFESPAN avoids the post-`engine.start()` window entirely, so it is correct whether or not #1257 has landed. What changes is only WHY the alternative was rejected. Why this correction is worth a commit rather than a footnote: a structural-sounding argument about a defect that no longer exists is worse than no argument. The next reader finds no hang, concludes the ADR is wrong, and distrusts the parts that are still right. This one was only ever right about a ref, and now says so. Also adds an open check the ADR could not have had: the exit-code arms were a minimal repro, unaffected by #394, so they remain valid about uvicorn -- but the REAL gate's behaviour inside a lifespan that now unwinds properly is unmeasured by anyone. Plausibly unchanged, and plausible is not measured. Verified: pytest tests/test_feature_map_claims.py = 9 passed, exit 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…n the window I chose
Found by writing the code, not by reading it. The ADR chose EARLY-LIFESPAN (after
open_store at api/app.py:5540, before engine.start() at :5731). That placement is
IMPOSSIBLE for this check, for two independent reasons either of which is fatal.
Measured, one lifespan, in order:
5540 store = await open_store(...) <- the chosen window starts
5731 await engine.start() <- the chosen window ends
5837 auth = AuthService(...) <- the service does not EXIST until here
5852 bootstrap = await auth.initialize() <- CREATES the bootstrap admin
5923 yield
(1) There is no AuthService in that window to call has_notifiable_admin() on.
(2) On a first run there is no administrator at all -- initialize() is what creates it
(auth/service.py:517, _ensure_bootstrap_admin). A check there would REFUSE EVERY
FIRST RUN, before the account it is about exists, converting a gate that reports a
wrong answer into one that prevents startup.
SO THE #1257 DEPENDENCY IS REAL AND RETURNS. The check must sit after :5852, inside the
post-engine.start() window #1257 records as hanging. The Dispatcher's original ruling --
do not close #1020 before #1257 -- was right on its own terms, and my narrowing of it
was wrong.
THIS VINDICATES THE RIDER'S AUTHOR, who assumed the lifespan placement and was right for
a reason none of the three seats arguing about it identified: not "the store is there",
but THE DATA THE CHECK NEEDS DOES NOT EXIST UNTIL AFTER THE ENGINE HAS STARTED.
Unchanged: the decision to gate on a deliverable address, the predicate (29a026e), the
scoping to the ROLE, and the exit-code findings -- which were always about the post-start
path.
The placements table gains the column that actually decides it: "has the data?". The
first two versions compared store access, termination and exit code -- three real
properties, none of them the binding constraint.
The ADR's TITLE is now wrong and is left alone deliberately: renaming the file would
break the index row and every citation, and the overturn is stated in the first line of
the Decision. Retract in place, keep the retraction.
Verified: pytest tests/test_feature_map_claims.py = 9 passed, exit 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ACKLOG #1235)
#1235's residual (1): `main()`'s exit code and its `--advisory` escape had ZERO
coverage. Its last line IS the fail-closed contract --
return 1 if (live and not args.advisory) else 0
-- and nothing asserted it. Invert the `not`, or return 0 unconditionally, and the whole
suite stayed green. This file's own header calls a detector that cannot fail "not a
gate"; its exit code was in exactly that state.
HOW THE GAP WAS FOUND, because the method transfers better than the finding: a SET
DIFFERENCE over the module's public surface, not a grep for something missing. The
module defines six top-level names (_load_backlog_module, allocation_floor,
allocated_numbers, citations_in, unresolved_citations, main); the suite exercised four.
`main` was the sole untouched public entry point. Positive enumeration on both sides, so
it rests on nobody's choice of pattern -- an earlier token-grep version of the same claim
was weaker and I replaced it.
FIVE ARMS, AND THE LAST TWO ARE THE POINT. Both produce HITS and both must exit 0,
because the contract keys on the LIVE SHAPE rather than the hit count: a number at or
below the allocator's high-water mark can never be issued, and a PR/foreign-repo
reference is not a backlog citation at all. Without those arms a gate that fired on any
hit would pass -- and it would red the tree today on 26 permanently-harmless citations,
which is how a gate gets switched off.
TWO MUTATIONS, KILLING DISJOINT SETS, so the coverage is proven in both directions:
return 0 -> reds {live, advisory} (cannot fail)
return 1 if hits else 0 -> reds {advisory, below-floor, PR-shaped} (over-fires)
Neither alone would have shown it. The detector was reverted after each and verified
byte-identical to HEAD, with a control confirming the diff instrument fires on the file
that did change.
The below-floor number is DERIVED (first hole under the floor), not hardcoded: a literal
becomes a RESOLVING number the day it is filed, at which point the arm stops testing the
below-floor branch and nothing says so.
Verified: pytest tests/test_dangling_citation_check.py = 39 passed, exit 0; ruff check +
format clean; mypy strict clean -- it caught a real one, `cc` is importlib-loaded so
allocation_floor() is Any and the arithmetic silently widened the return type.
No ledger edit: authoring the banner is not a builder's (owner ruling 2026-08-13). This
closes residual (1) only -- (2) the rule existing twice, and (3) the gate not running on
doc-only PRs (#1262), are untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…35 residual 2)
The predicate the gate's exit code keys on existed TWICE: inline in `main()`, and
re-derived inside the gate test. They agreed by convention with nothing binding them --
a rule with two definitions, inside the very tool that exists to catch that shape in
other people's gates.
`is_live_shape(hit, floor)` is now the one definition. `main()` calls it, and the test's
`_live_shape_citations` calls it plus `unresolved_citations` instead of re-implementing
both halves (`number in filed` duplicated the latter; `number <= floor or pr_shaped`
duplicated the former).
THE CONSEQUENCE IS CHECKABLE, AND IT IS THE PROOF RATHER THAN THE CLAIM. Mutating
`is_live_shape` to `return True` now reds THREE tests across BOTH consumers:
test_no_docs_citation_names_a_number_that_can_still_be_issued <- the real-tree GATE
test_a_citation_BELOW_the_floor_is_reported_but_does_not_fail <- exit-code arm
test_a_PR_SHAPED_reference_is_reported_but_does_not_fail <- exit-code arm
Before this, the gate test carried its own copy: a mutation to main()'s inline predicate
left it GREEN. That divergence is precisely what "agreeing by convention" buys, and one
mutation reaching both consumers is what shows the convention has been replaced by a
mechanism.
The detector was reverted after the mutation and verified to carry no MUTANT text, with
the suite back to green.
Verified: pytest tests/test_dangling_citation_check.py = 39 passed, exit 0; ruff check +
format clean on both files; mypy strict clean on both.
No ledger edit -- authoring the banner is not a builder's. Residual (3), the gate not
running on doc-only PRs, is #1262 and is untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…LOG #1273)
`controlchars.py` exists to state `ord(ch) < 0x20 or ord(ch) == 0x7F` once -- its own
docstring records that it replaced the expression written seven times across six files.
It then kept TWO copies of its own, one in each public arm.
`_is_control_char` is now the one definition; both arms call it.
THE RISK WAS ASYMMETRIC, AND I MEASURED IT RATHER THAN ASSERTING IT -- on the two-copy
structure, before changing it:
widen the PREDICATE arm alone -> CAUGHT. 4 tests red, because
test_c1_and_unicode_separators_are_deliberately_NOT_caught pins the alphabet
widen the STRIP arm alone -> NOT CAUGHT. 47 passed, exit 0
So the copies were PARTLY bound, and the unguarded direction is the one that matters.
Widening the alphabet is the stated reason this module exists ("a behaviour change at
seven call sites at once ... make it deliberately"), and a deliberate widening applied to
the neutraliser would silently strip more than the screen refuses -- a screen and its
neutraliser disagreeing about their own alphabet, with every test green.
I PREDICTED THE SUITE WOULD MISS BOTH DIRECTIONS AND THE MEASUREMENT REFUTED HALF OF IT.
The prediction is recorded here because the refutation is the useful part: the item says
"nothing binds them", I said the tests would stay green, and the truth is neither -- one
direction is pinned by an explicit alphabet test and the other is wide open. A claim
about which mutations a suite kills is checkable in one command and is worth checking
before it goes in a docstring.
Proof that the definition is now shared: mutating `_is_control_char` to `return False`
reds 39 tests spanning BOTH arms -- the has_control_char parametrisation, the strip
tests, and the two cross-checks. One mutation reaching every consumer is the property;
the probes above are what show it was not already true.
Both probes and the mutant were reverted and the file verified to carry no probe text.
Verified: pytest tests/test_controlchars.py = 47 passed, exit 0; ruff check + format
clean; mypy strict clean. No behaviour change -- the alphabet is byte-identical.
No ledger edit: authoring the banner is not a builder's.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Do not merge without rebasing first: this would silently DELETE content from
|
Correction to my previous comment: I quoted the wrong side of the statMy earlier comment said 532 deletions. That number is what this branch ADDS, not what merging would lose. I read The corrected figure: merging would lose 24 line(s) that The finding itself stands and so does the remedy -- rebase before merging, do not clear BEHIND and merge on green -- because a clean merge still drops those 24 lines with no marker. But the magnitude was overstated and the number in my first comment should not be quoted. |
RETRACTION: my "would silently delete content" comments on this PR were wrongBoth of my earlier comments are withdrawn. The finding does not survive a discriminator, and the remedy I attached to it was justified by a defect that is not there. What I did wrongI counted removed lines in a merge result and reported them as lost content. Those are different questions. A line-for-line REPLACEMENT scores identically to a DELETION under a line count -- and this branch's changes are overwhelmingly rewrites. The discriminator, run on this PRFor every line Zero. Every removed line's payload is present in the merged result. The concrete case that broke my own claimI said this branch deletes Not deleted -- expanded, plus a new mechanism (ADR 0164, What still stands, and what does notDoes not stand: any claim that merging this drops content, and the "never update-branch-and-merge-on-green" remedy insofar as it rested on that. Still true, but unremarkable: this branch is BEHIND, and branch protection is Credit where due: a peer session caught this by verifying one file in #437 -- |
…er-authored banners) ADR 0165 pairing on a stranded branch whose author session is gone. The fixes are that lane's; these three ledger entries are the lander's, because a builder may not author ledger content. 1105 CLOSES. The docstring's call-site count was DELETED rather than corrected, which is the right fix: the site list is derived by the tests, so a number in prose is a second definition nothing checks. Correcting six to seven would have produced a true sentence measuring the wrong thing and would have read as freshly checked forever. 1273 STAYS OPEN, and the banner says why. controlchars.py now states the set once and both its functions read it -- but logging_setup.py:66-70 still re-derives the same set and the branch never touches that file. The item names three spellings and asks for one definition of the SET. Two of three now share one. 1020 STAYS OPEN, and its stated hold condition has EXPIRED. The item says do not close before 1257 lands; 1257 HAS landed, content-tested against origin/main rather than read from its banner, which still reads open. The live reason is different and is measurable: __main__.py:2350-2354 still computes readiness from SMTP wiring alone, and this branch adds the predicate without wiring it in. A reason that can expire silently is a worse reason even while it is still true. 1235 IS DELIBERATELY UNTOUCHED. It belongs to the dispatcher seat's current wave; two independently-authored banners for one item is worse than a conflict, because both look correct and a merge may silently take one. parse_items before/after: 312/231/81 -> 312/230/82, the expected 0 items / -1 open / +1 closed. Neither open-item amendment contains any character from the status alphabets, asserted in the edit script, so neither can be misread as a second banner. Built with plumbing (read-tree, update-index, commit-tree); no working tree touched. Plumbing bypasses pre-commit, so the backlog status check was run by hand against the result.
Lander note -- the ledger commit is on, and the red above was NOT what it looked likeThe body above is retained unedited, including the author's correction, because that correction is the evidence for what follows and editing it would delete the thing that made this checkable. Why the required check was red, and it was not this PR's claim
The paragraph explaining the gate is what tripped the gate. That defect is not fixed by this PR, and the green it is about to get is not evidence it is fixed. The banner was owed on the merits and the matcher is separately wrong; those are independent, and letting the green settle it would retire a real defect with a coincidence. What the lander addedOne commit,
Built with plumbing ( Expect a red that is not this PR's
Discriminator, stated in advance: a red in |
…llision PR #428 landed as 4c28bad adding ADR 0167's index row; this branch adds 0169's. Both are appended as the LAST line of docs/adr/README.md, so git sees one line changed two ways. Every other path content-merged cleanly, including docs/BACKLOG.md and auth/service.py, which are changed on BOTH sides. RESOLVED AGAINST A COMPUTED TARGET, NOT BY EYE, because an index is exactly where keep-both-sides is a hope rather than a resolution: A = rows at merge-base 157 B = rows at PR head 158 (adds 0169) C = rows at origin/main 158 (adds 0167) target = C + (B - A) 159 result 159 rows, 0167 present, 0169 present, ZERO duplicate numbers Built with plumbing; no working tree touched.
…rning runs an index test) (#465) * backlog: close #1261 -- the cutover ratchet fix is on main by content parse_items 313 items / 231 open / 82 closed, from 232/81. Expected closure delta: 0 items, -1 open, +1 closed. backlog_status_check reports 549 across both files, each declaring exactly one status. Closed on the release condition this project uses -- the fix TEXT on main -- and verified that way rather than on a merged badge. The tests/test_cutover_slug_rot.py blob is 13a711b on origin/main, on PR #461's head and on 9d781ed: all three identical. Recorded in the banner because it will be reached for again: the cumulative patch-id test does NOT match here, branch 43f5a21a against squash 148226e6, and that proves nothing. A cherry-pick onto a different base plus an update-branch breaks the equality without the work being absent. On a squash-merging repo a patch-id MATCH proves landed and a NO-MATCH proves nothing. Blob identity settled it in one command. The banner's closed-alphabet character was LIFTED PROGRAMMATICALLY from an already-closed item rather than typed, and asserted a member of _CLOSED before use. Typing a status glyph by hand is how a wrong one enters the file, and the alphabet is machine-parsed. Held until the fix landed rather than closing on the PR, because main carried the bare pattern at zero slack until then and a premature close leaves main one innocent documentation edit from red. * backlog: file #1298 -- the archive dialog's discard warning is an index test, not a loss test The session-archive dialog lists the worktree's dirty set and says the files "will be permanently discarded". For a ?? entry that is a statement about THIS worktree's index, not about the content: a worktree whose HEAD predates a commit sees every file that landed since as untracked, because they are absent from its index while being tracked on main. Measured on a live instance. It warned that tests/test_notifiable_admin.py and docs/adr/0167-*.md would be permanently discarded. Both are tracked on origin/main -- the test was added at 4c28bad (PR #428), which merged that same session -- and every local copy was byte-identical to main's blob in all six worktrees holding them. Archiving would have discarded nothing. Reproduced on this seat's own tree, which is what establishes the mechanism rather than the coincidence: at dd655da, two behind origin/main, git status showed the identical ?? pair; fast-forwarding to 1df0c07 converted both to tracked with no edit and no conflict. The files never changed, the base did. The failure is systematic and its timing is adversarial -- the warning is loudest exactly when the fleet lands fastest, and wrong in those cases. Both responses are damaging: believing it refuses a safe archive, and learning to dismiss it trains the response that defeats a real one. Scope is stated in the item so it is not mis-built: the dialog is the Claude Code harness and is not this repository's code. In scope here is a WORKTREES.md subsection plus a scripts/coord/ helper that answers the recoverability question for a whole dirty set and prints what it examined, refusing to call anything it cannot read safe. Also records #1297 as a permanent hole in the erratum. It was allocated from the PRIMARY checkout because the allocating shell's cwd was the primary, so the ledger gate correctly refused that commit and the item was re-filed at #1298. alloc.ps1 records the claim against whatever tree it runs in. Verified with the repo's own gate: each item declares exactly one status.
Opened by a different session to make stranded work visible. Not reviewed by me, and deliberately not armed for auto-merge.
This branch existed only in a local checkout — never on
origin, no PR — while 5 ledger claims (#1020, #1105, #1235, #1267, #1273) pointed at a worktree directory that no longer exists. The claims could not be released, because releasing them would invite a rebuild of work that already exists. The commits were durably backed the whole time (snapshot/b2-modest-ellis-20260815donorigin), so nothing was ever at risk of loss — but nothing could land either.What is here
8 commits, last dated 2026-08-14, all novel against
main(checked by patch-id: 0 already present):#1020a deliverable-address predicate for enabled administrators, plus ADR 0167 gating PHI-notification readiness on a deliverable address early in the ASGI lifespan — including two follow-up commits that scope and then overturn the ADR's placement#1105remove aharden_kex_groupscall-site count that nothing checks rather than incrementing it#1235pindangling_citation_checkmain()'s exit-code contract, then collapse the live-shape rule to one definition#1273collapse the C0/DEL predicate incontrolchars.py, which stated it twice in the module that exists to state it once10 files, +532/−24.
Expected failure, stated so nobody debugs it
a PR that implements BACKLOG #N must update BACKLOG.mdwill fail. The commits cite four BACKLOG numbers and the branch does not touchBACKLOG.md. That gate is correct and this PR genuinely does not satisfy it.I have not updated
BACKLOG.mdmyself, because doing so asserts these items are complete and I cannot verify that for another session's work. Whoever owns this decides what those entries should say.Note also
#1267, whose claim records TEXT READY, NOT APPLIED —seat-tick.ps1is machine-global with no source of record in the repo, so there is no commit here for it. That claim's content is in the claim note and nowhere else.🤖 Generated with Claude Code
Correction (author, later the same day)
The "Expected failure" section above is wrong. The backlog-hygiene gate ran on this PR and passed.
I predicted it would fail because the commits cite four
BACKLOG #Nnumbers while the branch does not touchdocs/BACKLOG.md. That is not what the gate checks. Reading.github/workflows/backlog-hygiene.ymlrather than assuming, it fires only when all three hold:BACKLOG #<digits>token appears in the pull request title or body — not in commit messages, and a bare#123deliberately does not count, because in this repo that is usually a PR number;messagefoundry/,ide/ormessagefoundry_webconsole/;docs/BACKLOG.mdordocs/archive/backlog/.This PR meets (2) — three engine files — but not (1), because I wrote the item numbers as bare
#1020and so on. So the gate correctly reports "no claim to enforce" and exits 0.Which means the banner question is unresolved rather than answered. If this work does complete those items, a reviewer adding
BACKLOG #1020to the title or body is what makes the gate ask, and the entry would then genuinely be owed. Leaving it as I wrote it means nothing is enforcing that, and the green tick is not evidence to the contrary.Every other statement above stands. All 13 required contexts ran and passed on this PR.