docs: land the stranded ADR 0074 capacity corrections, and unstale two trackers - #95
Merged
Conversation
…o trackers Three independent doc defects, all verified against origin/main before editing. 1. BACKLOG #96 carried three pieces of capacity guidance that ADR 0074's 2026-07-14 validity re-check found measured-false and unsafe, and the correction never landed - it sat only on a stranded commit (bd969b0e) in a ref namespace whose remote no longer exists. B14 named four downstream artifacts; three were amended on 2026-07-14 and #96 was the last one left, so ADR 0074 required-change 13 still read as fully outstanding. Corrected here: the sum-across-interfaces aggregate rule (measured-false, ~11x over-report - 16 lanes summed predicts 960/s against a measured 87/s); the "only trustworthy success gate", which over-reports 3-5.5x on its own and needs the non-filling companion #96 already named; and the poller-zero remedy, which is circular. Adds the BUILD GATED (measurement layer) advisory and marks the quick-win/Difficulty-5 score as no longer trustworthy. ADR 0074 B14 and required-change 13 are closed out to match. Deviations from bd969b0e, each deliberate: it used a leading ⛔ banner, which the backlog checker reads as a CLOSED status against #96's OPEN 🔢 banner and fails "item contradicts itself" -⚠️ is the sanctioned non-status advisory glyph, per the ⛔+⚠️ stacking that landed in #92. Its "11 open decisions" count is unsourced (the Amendment lists 14) and is dropped rather than restated. Two of its quotations were inexact and are corrected against the file. "zero code" became "only TOML comments, zero code" - the grep returns 10 TOML hits and no Python. 2. BACKLOG #171, #131 and #127 carried only their pre-build demand-gate banners although work has shipped. Each gets an AMENDED banner rather than a close, because verification refuted a full close in all three cases: #171's API half is built but its console half is dead code (app.js registers the handlers, no page emits the attributes, and the /ui routes do not exist); #131 built the connection flag and the Flagged-only filter, but "every configuration object" is a scope fork ADR 0007 ratified deliberately; #127 built Basic, and Digest for http destinations, while NTLM and Windows are refused at construction and recorded as deferred - so this is explicitly not four-scheme parity. 3. The demand-gate tracker claimed 11 lanes "Not started" that have all shipped. Each lane was verified individually by ADR status plus code symbol; the public history is snapshot-collapsed across the 2026-07-17..24 window, so no merge SHA can be cited for any of them. Verified: tests/test_backlog_status_check.py (15 passed, including test_the_real_backlog_satisfies_the_invariant, which the ⛔ form fails), plus the ledger, runbook, doc-ref and doc-drift suites - 100 passed, 89 skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
wshallwshall
added a commit
that referenced
this pull request
Aug 4, 2026
The 2026-08-03 re-score flagged 24 open items as misdescribing what the code does. Re-verified each against the tree as it stands -- after the archive move and after PR #162, both of which post-date the findings -- and 10 survived. The other 14 did not, and are recorded here rather than silently dropped: #84 #95 #99 #105 #114 #124 #125 #127 #133 #137 #167 #169 #214 #228 Most of those already carry an amendment that covers the stale sentence (#95, #99, #105, #114, #124, #125, #127, #133, #228), and stacking a second ruling saying the same thing is noise. The rest did not survive verification: the finding was itself wrong or overstated, and a wrong correction in a ledger is worse than a stale one. CORRECTIONS ARE ADDED AS DATED AMENDMENTS, NOT PROSE REWRITES. This file's convention is to leave the original claim standing and rule against it, so the record shows what was believed and what replaced it. Silently editing the stale sentence would destroy the evidence that makes the correction checkable. Applied to #62 #64 #131 #166 #179 #182 #237 #321 #329 #336. Representative: - #329 "Five MEFOR_ALLOW_INSECURE_TLS cells": the census is FOUR. #323 landed and routed transports/direct.py through the clamp; it now holds no call to the raw predicate at all (:63, :197, :215). - #321 "no test asserts the detectors can see a site code": false -- tests/test_scan_forbidden.py has per-class hit tests for at least the site code (:126), a customer name (:83), a case-sensitive code (:91) and a routable IP (:107). The detector-coverage half of its Proposed 2 is already in the tree. - #62 plans a dual-read over "existing mfenc:v1 rows", but cell-bound mfenc:v2 is the default writer (settings.py:383 -> base.py:1841; crypto.py:36), and v2 folds (table, column, pk) into the GCM tag -- so a body landing under a different column must be RE-ENCRYPTED, not merely re-encoded. That tightens the catch. - #64's ordered plan still reads live ("Nothing builds before it"), but the measure-first phase completed 2026-07-12 (ADR 0051) and its step-2 lever is refused outright (ADR 0055 withdrawn; ADR 0107 "Do not build F2 or F3"). The refuters removed two overclaims before they landed: #62's draft asserted a live store holds both mfenc markers (a fresh store under the shipped default holds only v2 -- the defensible claim is that a MIGRATION must expect both), and #64's asserted the multi-DB log split still remains, which could not be verified against ADR 0098 and would have been a fresh false claim. No item closes here: in every case the correction narrows the remainder rather than discharging it, and the 2026-08-03 scores already price the remainder. backlog_status_check.py --min-items 277: OK, 277 items, one status banner each.
wshallwshall
added a commit
that referenced
this pull request
Aug 4, 2026
…ones (#163) * docs(backlog): close BACKLOG #226 — the estate Hybrid-layout sweep is done, off-repo The per-feed Hybrid split (connections.toml / <INBOUND>_router.py / <INBOUND>_handler.py / _<feed>_transforms.py) landed across the ported estate in the maintainer-internal migration repository. Owner-attested; nothing in this repository changes, which is also why leaving the item open could never have closed it. Both "Also" clauses are recorded as NOT delivered, with the reason each is not a residual of this item: - "align the IDE Corepoint-import / scaffold path to emit the Hybrid layout" — there is no Corepoint-import path in ide/ to align. That tooling is #105, still open, so the clause is a constraint on #105's design rather than work #226 can perform. The scaffold half is misaddressed too: Insert Element (#48) drops per-file idioms into the current buffer (ide/src/insertElement.ts:1-5) and emits no multi-file feed layout. - "consider a recursive-glob / folder-per-feed loader enhancement" — filed as a consider, and not taken: load_config still globs *.py non-recursively (config/wiring.py:4162), the flat-merge behaviour the Hybrid layout is built around. Follows the #227 precedent: close the primary, state the off-repo/misaddressed residuals explicitly so the item is not re-opened for them. backlog_status_check.py: OK — 277 items, each declaring exactly one status. * fix(ledger): teach the number-space gates to span an archive, and fix two holes found proving it Prerequisite for moving the 185 closed BACKLOG items into docs/archive/backlog/. No item has moved yet; this only makes the guards able to see one when it does. The item namespace will span two paths, so every guard now reads their UNION: - backlog_status_check.py: scan() takes (label, text) pairs and parses them as ONE namespace. A number re-used across BACKLOG.md and the archive was structurally undetectable before -- `seen` was per-parse -- which is the erratum's own shape. - ledger_check.py: triggers on any backlog-bearing path, not the one literal, and builds head/base as the union. Reading the union on both sides also removes a false positive: the move relocates 185 items, so head-union == base-union and `head - base` stays empty, where a per-file view would report 185 vanished numbers with a remedy that renumbers cited items. - alloc.ps1: sweeps both paths in the all-refs term and the working-tree term. - backlog-hygiene.yml: accepts a banner updated in either location. Two pre-existing defects surfaced only because the gates were made to fail on purpose first, neither of which is about the archive: 1. alloc.ps1's working-tree term has NEVER worked. `[regex]'^...'` anchors at the start of the STRING; the term feeds it `Get-Content -Raw`, one string starting "# Backlog". Measured: 0 of 277 headings matched without Multiline, 277 with. The all-refs term hid it by covering every number committed somewhere -- i.e. every case except the uncommitted one this term exists for. 2. backlog-hygiene.yml diffed BASE_SHA..HEAD_SHA (two-dot), which credits a PR for main-side changes to paths it never touched. One main-side edit to BACKLOG.md -- the move being a large one -- would let every PR with an older base pass the "must update BACKLOG.md" required check while enforcing nothing. Now three-dot, matching ci.yml's form for the same question. Anti-narrowing, because a green gate over a shrunken corpus is the failure mode: - `--min-items N` fails when fewer items are found than required, and CI pins 277. Without it, 277 -> 92 fails nothing. - The scanned files are always printed with the count; a bare integer cannot distinguish "items closed" from "a file stopped being read". - A liveness receipt in the test suite asserts the same floor. - An explicitly-named --backlog path that does not exist is an error, not a skip. alloc.ps1 gains `-ShowFloor`: print the floor and the swept paths, allocate nothing. Allocation is a one-way door, so before this the only way to ask what the floor could see was to spend a number on the question -- which is how it ran a whole release reading two refs while its header promised all of them. Get-Floor takes -Peek so the inspection cannot advance the high-water ratchet; the first -ShowFloor run against a planted number moved this clone's watermark 316 -> 990 before that was fixed. Proofs run, each observed failing BEFORE the fix: - archive-only unallocated #1007 staged: old gate rc=0, new gate BLOCKED. - #990 planted in the archive: old sweep floor 353 (blind), new sweep 990. - cross-file duplicate #118: detected, naming the other file. - banner violations inside the archive only: detected. - --min-items over a narrowed corpus: rc=1 with the scanned-file list. - -ShowFloor twice against a plant: watermark unchanged at 316. ruff + mypy --strict clean; 43 gate tests pass. * docs(backlog): move the 185 closed items into docs/archive/backlog/BACKLOG-CLOSED.md docs/BACKLOG.md becomes the ~92 items someone can act on: 8,742 -> 3,648 lines. The closed items are not deleted, summarised, or rewritten -- they are relocated verbatim, so the file that gets opened, grepped and edited daily is the open set. MOVED, NOT REWRITTEN. Every relocated block is byte-identical to the one that left BACKLOG.md, headings included. Verified mechanically against a pre-move copy: - 277 items before = 92 after + 185 archived, no overlap, union identical - every OPEN block byte-identical to its source - every ARCHIVED block byte-identical to its source - all non-item prose in BACKLOG.md preserved verbatim Byte-identical headings are load-bearing, not tidiness: GitHub derives anchor slugs from heading text, so all 64 archived->archived cross-references keep resolving with no edit at all. That is the whole argument for one archive file rather than a split by status, year, or cluster -- #52 alone receives 99 of the 110 in-file anchors, and its citers span #65 to #184, so no cut isolates them. Cutting item blocks at the next '## ' heading of EITHER kind, not the next numbered item: 4 blocks in this file are followed by a section header, which a naive cut would have dragged into the archive along with the prose beneath it. Anchors, all 127 re-resolved against real headings after the edit: - 44 rewritten in BACKLOG.md -> archive/backlog/BACKLOG-CLOSED.md#<same-slug> - 1 rewritten in the archive -> ../../BACKLOG.md#<same-slug> (#226 -> #105) - 3 cross-file links repointed: AOAG-DEPLOYMENT.md (#100, #101), ADR 0026 (#30) - 64 archived->archived untouched, by design 13 anchors still do not resolve, and ALL 13 WERE ALREADY DEAD BEFORE THIS COMMIT -- confirmed by running the same check over the pre-move file, which returns the identical multiset (11 bare-number self-anchors: #40 x4, #323 x3, #28, #29, #329, #333; plus 2 links to #13 in COUNSEL-ENGAGEMENT-BRIEF.md, a number this sequence never had). They are left dead and documented in the archive header rather than repointed at a plausible neighbour: a citation resolving to the WRONG item is the erratum's failure mode, and unlike a dead link it looks like success. The archive carries its retirement banner inline rather than in a sibling README -- docs/archive/throughput/ needs a README because it indexes five documents; one file does not, and two documents that must agree is a drift surface. It states the rules that keep the namespace honest: never renumber, re-open by moving the block back (never by copying, which creates the cross-file duplicate the status check now fails), and add any future archive file to alloc.ps1's $backlogPaths AND backlog_status_check.py's DEFAULT_SOURCES in the same commit -- a file named in neither is policed by nothing. Gates verified post-move: - backlog_status_check.py --min-items 277: OK, 277 items, and it now PRINTS "scanned: docs/BACKLOG.md (92), docs/archive/backlog/BACKLOG-CLOSED.md (185)" - ledger_check.py on the staged move: rc=0 (relocation adds no numbers, because head-union == base-union -- the exact false positive the union view removes) - alloc.ps1 -ShowFloor: floor 353 across both paths, next 1000 - 43 gate tests pass Note the floor is unchanged at 353 because the highest item (#353) is open and stays in BACKLOG.md. The archive-sweep fix is therefore PROSPECTIVE, not a save: it starts mattering the first time a top-of-range item closes and moves. * docs(backlog): re-score all 92 open items on the ten-level scale (2026-08-03) Every open item now carries a current value x difficulty score. Before this, 23 had none at all and the other 69 were from the frozen 2026-07-10 pass, which predates the 2026-07-28 reconcile that closed 31 items -- and a stale score reads exactly like a fresh one. Method, unchanged from the pass it supersedes: scored from each item's own Scope / Why / Trigger / Nearest-existing-mechanism text rather than rescaled from the old number, then adversarially verified against the code -- a second reader per batch attacking build state first, then verdict/tier, then value and difficulty. 26 of 92 scores were overturned by that pass and carry the refuter's number. The banner is the live record and the table is a view of it; both are written here and a mechanical check confirms 92 banners and 92 rows agree on every triple. THE RATIONALE IS REPLACED, NOT JUST THE NUMBERS. Carrying an old justification under a new score is how a banner comes to argue against itself: - #114's surviving "clean workaround via the on-demand test probe" is a claim PR #162 explicitly retracted -- both destinations' test_connection CREATE the target dir, so the probe cannot answer the question the toggle asks. That is what lifts it off the parity-with-a-workaround band to 6/3. Its replacement rationale was ALSO stale (it described the silent-ignore #162 had just fixed) and is hand-corrected. - #105's "large greenfield 71-action mapper needing its own ADR" describes an importer that has since shipped under ADR 0086. Scheduling barely moved, which is the reassuring result: only TWO tiers changed -- #64 DEMAND-GATE -> P3 (an index over levers that live in #62/#63/#47/#34, so it ships nothing runnable of its own) and #105 P3 -> DEMAND-GATE. Neither contradicts an explicit demand-gate/on-trigger ruling in its own body; that was checked for all 51 items carrying a prior tier. Distribution is RECOMPUTED with the table rather than carried forward, and all four lines sum to 92. The superseded table keeps its own frozen lines and now says so. Tiers: P1 4, P2 19, P3 17, DEMAND-GATE 52 Quadrants: quick win 22, big bet 5, fill-in 56, money pit 9 The four P1s: #341 (9/3, a handler returning a tuple/set of Sends delivers nothing silently -- an accept-and-drop CLAUDE.md §12 forbids), #324 (7/2), #325 (6/2), #327 (6/2). NOT in this commit: 24 items were found to misdescribe their own build state -- prose asserting a gap that has since shipped, or citing messagefoundry/console/, a package retired with #103. Those are banner corrections and land separately; the scores here already price the remainder rather than the original scope. Two mechanical faults were caught by reading the output rather than trusting the run: the quadrant regex omitted the hyphen in "fill-in", so 57 of 69 items took the fallback branch and got a SECOND score inserted beside the first; and the synthesizer's own distribution lines did not follow from its own table (11 quadrant mismatches, 8 ordering violations, difficulty summing to 95 of 92). The script now refuses to write when any line carries two score spans or the scored count is not 92. backlog_status_check.py --min-items 277: OK, 277 items across both files. * docs(backlog): correct 10 items whose own prose misdescribed build state The 2026-08-03 re-score flagged 24 open items as misdescribing what the code does. Re-verified each against the tree as it stands -- after the archive move and after PR #162, both of which post-date the findings -- and 10 survived. The other 14 did not, and are recorded here rather than silently dropped: #84 #95 #99 #105 #114 #124 #125 #127 #133 #137 #167 #169 #214 #228 Most of those already carry an amendment that covers the stale sentence (#95, #99, #105, #114, #124, #125, #127, #133, #228), and stacking a second ruling saying the same thing is noise. The rest did not survive verification: the finding was itself wrong or overstated, and a wrong correction in a ledger is worse than a stale one. CORRECTIONS ARE ADDED AS DATED AMENDMENTS, NOT PROSE REWRITES. This file's convention is to leave the original claim standing and rule against it, so the record shows what was believed and what replaced it. Silently editing the stale sentence would destroy the evidence that makes the correction checkable. Applied to #62 #64 #131 #166 #179 #182 #237 #321 #329 #336. Representative: - #329 "Five MEFOR_ALLOW_INSECURE_TLS cells": the census is FOUR. #323 landed and routed transports/direct.py through the clamp; it now holds no call to the raw predicate at all (:63, :197, :215). - #321 "no test asserts the detectors can see a site code": false -- tests/test_scan_forbidden.py has per-class hit tests for at least the site code (:126), a customer name (:83), a case-sensitive code (:91) and a routable IP (:107). The detector-coverage half of its Proposed 2 is already in the tree. - #62 plans a dual-read over "existing mfenc:v1 rows", but cell-bound mfenc:v2 is the default writer (settings.py:383 -> base.py:1841; crypto.py:36), and v2 folds (table, column, pk) into the GCM tag -- so a body landing under a different column must be RE-ENCRYPTED, not merely re-encoded. That tightens the catch. - #64's ordered plan still reads live ("Nothing builds before it"), but the measure-first phase completed 2026-07-12 (ADR 0051) and its step-2 lever is refused outright (ADR 0055 withdrawn; ADR 0107 "Do not build F2 or F3"). The refuters removed two overclaims before they landed: #62's draft asserted a live store holds both mfenc markers (a fresh store under the shipped default holds only v2 -- the defensible claim is that a MIGRATION must expect both), and #64's asserted the multi-DB log split still remains, which could not be verified against ADR 0098 and would have been a fresh false claim. No item closes here: in every case the correction narrows the remainder rather than discharging it, and the 2026-08-03 scores already price the remainder. backlog_status_check.py --min-items 277: OK, 277 items, one status banner each. * docs(backlog): file BACKLOG #1000 — prove each required merge context can fail Escalated by the coordinator on the ground that it outlives the PR that fixed it. Deliberately NOT filed as "fix the two-dot diff": that instance already landed in 39b62bf, and filing shipped work is the rot the hygiene gate exists to prevent. The item is the CLASS. `.github/required-contexts.txt` names 13 contexts that block merge, and not one of them is proven able to go red. The deliverable is a negative control per context -- a fixture carrying the exact violation that context exists to catch -- plus a CI job that fails when a required context has none, so the coverage cannot silently decay as contexts are added. Scoped narrower than "test the gates" on purpose: it does not re-test what each gate checks, since the gates' own suites do that. It asserts one property per context -- this gate is capable of failing. The argument is that the class has now fired at least four times here, each found by hand and none by CI: #334 semgrep, required and blocking, scans a two-directory allow-list #327 six .gitignore rules are the sole control over maintainer-internal docs, and nothing asserts they still match anything #321 the forbidden-content gate exited 0 on a real site code and partner product #325 the same gate's home-path detector misses 1 of 4 spellings of a Windows path Each is correctly filed as its own defect. None of them establishes the property that would have caught all four before they shipped, and that property is a different artifact from any of the individual fixes. Value 7 / Difficulty 3, quick win, P1 -- not demand-gated; the trigger fired four times. Ranked table and all four distribution lines recomputed to 93 open items; a mechanical check confirms 93 banners and 93 rows agree on every triple. Number allocated atomically via scripts/coord/alloc.ps1 (#1000 -- the first in the post-partition public sequence, clamped to >= PUBLIC_BACKLOG_FLOOR), never grepped. backlog_status_check.py --min-items 277: OK, 278 items across both files. The floor is a floor, so growth passes it; it is there to catch shrinkage.
wshallwshall
added a commit
that referenced
this pull request
Aug 12, 2026
…surface (BACKLOG #1220) (#346) * feat(seam): discover the webconsole seam surface instead of enumerating it (BACKLOG #1220) Layer 1 of #1220, and it is INERT: nothing consumes this yet. The generator still reads its five hand-maintained tuples and ENGINE_UI_SEAM is still the hand-chosen integer 20. Separating coverage from derivation means a bisect can tell which one broke something. WHY THIS AND NOT A HASH OVER THE EXISTING LISTS. #1220's ruling is that the hash input must be DISCOVERED, never enumerated, because a content hash over a hand-maintained list stops colliding while still failing to move when an unlisted model changes -- and a hash LOOKS like it covers content, so the next reader trusts it more than the list it replaced. Measured at ebf4882, three of the five tuples had already drifted: _API_MODELS_DTOS 30 curated, 55 reachable. 7 DTOs the console imports directly are absent (UploadedFileList among them); the other 18 are reachable only by NESTING, and _dto_fields records field names ONE level deep with no recursion, so a nested model's field set is absent from the contract entirely. _API_SECURITY_SYMBOLS missing client_ip and enforce_phi_read_pacing, carrying five names the console no longer imports. That is why _ui_seam.py asserts the console imports six symbols directly -- false for five of six. _AUTH_SERVICE_METHODS AuthService.has_action_step_up is called at _auth.py:620 and absent. Six of the seven additions are PROPERTIES, on which inspect.signature raises, so the curated list held methods only because the renderer could not do more -- the instrument's limitation had silently defined what counted as contract. The proof the defect is real rather than theoretical is in the history: 40a4d5d added a REQUIRED scope field to UploadedFileList, which pages/uploaded_logs.py renders unconditionally, and touched no seam file across 17 changed files. The seam moved later, by hand, in review. CALIBRATION. _APP_STATE_ATTRS was the one tuple with zero drift in either direction, and the two-sided rule (console reads intersected with engine writes, plus console writes) reproduces its eight names EXACTLY. A walk that merely returned "more" would be consistent with measuring the wrong thing; reproducing the un-drifted list is the evidence that it measures the contract. The AuthService rule is annotation-driven -- attribute access on a receiver annotated AuthService or assigned from get_auth() -- rather than a bare name intersection. The bare rule yields 38 names, ~11 of which are present only because they collide with AdminHandlers field names, and a gate that moves for methods the console never calls is one people stop reading. Annotation-driven yields 34: the 27 curated plus 7 verified real, nothing dropped. FAIL LOUD, never skip. A star import, an alias escaping attribute position, a dynamic getattr on a DTO module, or an unresolvable model raises SeamDiscoveryError. A silent skip would recreate the enumeration blind spot inside the walk's control flow, which is strictly worse than the tuple it replaces -- a 30-line tuple was at least reviewable. Every loud case measures zero occurrences today, so the guard costs nothing now and its first firing is a genuine new idiom. The closure deliberately crosses module boundaries: a rendered DTO that exposes a model defined elsewhere still ships that model's field set across the seam. Discovery runs in the generator and the test only -- messagefoundry/ must not import the console or scripts/, and a seam computed at import time would make every proof condition pass vacuously. Verified: ruff check + format clean, mypy strict clean, 21 new tests plus the 3 existing seam tests pass. Full-suite baseline at this merge-base was 11882 passed / 861 skipped / 0 failed. NOT verified: no CI leg run; the SQL Server and Postgres legs skip locally. * feat(seam): feed discovery into the snapshot and delete the curated tuples (BACKLOG #1220) Layer 2 of #1220. The generator now reads the DISCOVERED surface; the five hand-maintained tuples are gone. ENGINE_UI_SEAM is STILL the hand-chosen 20 -- deriving the value is layer 3. Isolating coverage from derivation keeps a bisect able to say which one broke something. WHAT THE GOLDEN GAINS. It went from 196 lines to 237, and the additions are the measured blind spot: UploadedFileList appeared ZERO times in the old golden. It now carries its field set AND its Literal values. This is the DTO whose required `scope` field landed in 40a4d5d without moving any seam file -- the gate stayed green through exactly the change it exists to catch. 25 more DTOs 7 the console imports directly, 18 reachable only by nesting. The closure matters because _dto_fields records names one level deep with no recursion, so a nested model's fields were absent entirely. 6 AuthService properties action_step_up_required, ad_enabled, kerberos_available, oidc_enabled, oidc_flow_ttl_seconds, store -- all read by the console across the seam, none recorded. inspect.signature RAISES on a property, which is why the retired tuple held methods only: the renderer's capability had silently defined what counted as the contract. _member() renders them now. has_action_step_up called at _auth.py:620, absent from the golden, and on the ADR 0077 per-action step-up path. client_ip, imported by the console and never hashed. The same correction drops five enforce_phi_read_pacing stale names the console no longer imports -- which is why _ui_seam.py asserted the console imports six api.security symbols directly, false for five of six. That assertion is deleted with the tuple it described. enum + Literal sets Field NAMES are not the contract. pages/uploaded_logs.py renders UploadedFileList.scope as _SCOPE_NOTES[data.scope], so renaming a literal from "own" to "owner" would KeyError at runtime while a field-name-only snapshot stayed byte-identical. NO SEAM BUMP, DELIBERATELY. The snapshot changed because the GENERATOR changed, not because the engine's contract did. A console built against seam 20 is no less compatible than it was an hour ago -- recording more of an unchanged contract cannot make an older console incompatible. Bumping here would spend a hard console-wheel refusal (SUPPORTED_ENGINE_SEAMS holds exactly one value, BACKLOG #279) on a bookkeeping change. The calibration tests now assert against the retired tuples FROZEN AS LITERALS rather than reading them from the generator. Reading them would make the calibration vacuous the moment the generator stopped carrying them, which is this commit. Frozen, they keep proving the two properties that matter: app.state is reproduced EXACTLY (the one tuple with zero drift), and no previously-curated name is ever dropped. Verified: ruff check + format clean, mypy strict clean on both scripts, 24 seam tests pass, plus tests/test_ui_oidc_interstitial_route.py and the full packaging/messagefoundry-webconsole suite -- those are the only three test files in the tree that reference the seam, the golden, or the generator (grep over tests/ and packaging/.../tests/ for ENGINE_UI_SEAM|webconsole_seam| SUPPORTED_ENGINE_SEAMS|engine_seam). NOT verified: no CI leg run; full suite re-run deferred to the end of the layer stack. * feat(seam): derive ENGINE_UI_SEAM from the contract surface (BACKLOG #1220) Layer 3, and the one that closes #1220. ENGINE_UI_SEAM is no longer a number anyone picks: it is a 16-hex SHA-256 of the DISCOVERED contract surface, produced by `python scripts/webconsole_seam_snapshot.py --write`. Value: 494a51230dce5730. WHY THIS FIXES THE COLLISION AND GATING THE INTEGER WOULD NOT. Two branches bumping to 19 for two independent contract changes produced a COSMETIC conflict in a comment block while the golden snapshot auto-merged CLEAN carrying both changes under one seam -- so resolving the visible conflict correctly still shipped the fault. A digest has no such failure mode: two branches changing different surfaces derive different values, and their MERGED surface derives a THIRD matching neither, so the merge reds. Uniqueness stops being something a human maintains. str, NOT a truncated int, and the old skew test is why. It asserted `ENGINE_UI_SEAM - 1` is refused, under a docstring claiming "one seam older AND one newer". Under an int digest that arithmetic still evaluates, the assertion still PASSES, and the docstring silently becomes false -- a passing test making a claim the value no longer supports, which is the same defect class #1220 was filed against. A str makes it a TypeError instead. It also removes the hand-typable affordance: someone can write 21 and it looks legitimate; nobody types a hex digest by hand and believes it. ANTI-CIRCULARITY IS BY CONSTRUCTION. contract_sections() never reads ENGINE_UI_SEAM, so the seam cannot feed its own digest -- there is no "strip the seam line" filter that could quietly stop matching. The test asserts it by MOVING the constant and requiring the digest to hold still, not by checking the seam's text is absent from the input: a substring check is defeated by a short or coincidental value (the header already contains the literal 0065). Mutation-proved before trusting it -- a generator that folds the seam into its own input makes that test FAIL, as it must. Four tests, three of them new: the stored value equals the derived digest (the gate); the digest holds still when the seam moves (anti-circularity); the digest MOVES when a rendered DTO gains a field, with 40a4d5d as the historical control where it did not; and the skew test rewritten to refuse the empty string, a truncation, an extension and an arbitrary digest, preserving the both-directions intent BACKLOG #279 put in the original. The failure message is rewritten and is now pure ASCII. The old one carried U+2014 (a replacement character on a cp1252 console, the developer-facing half of #1221) and told the reader to "bump ENGINE_UI_SEAM" -- an action that no longer exists. A gate whose message names the wrong repair teaches the wrong repair. --write rewrites the constant and the golden, and REFUSES rather than guesses if the constant line does not match exactly once. It deliberately does NOT write the console's SUPPORTED_ENGINE_SEAMS: that is the independent half of a two-wheel handshake, and a tool writing both sides turns it into a self-consistent tautology. The message spells out the one-line manual edit instead. Also fixes tests/test_ui_oidc_interstitial_route.py, which passed engine_seam=0 into a field now typed str. Nothing automated would have caught it -- tests/ is outside mypy's configured scope and a dataclass does not validate at runtime -- so it was found by grepping every consumer deliberately. Verified: ruff check + format clean; mypy strict clean on messagefoundry_webconsole; 40 tests pass across the three seam-touching test files. mypy on messagefoundry reports 21 errors in 4 files (auth/webauthn.py, parsing/dicom/_deps.py, parsing/fhir/_deps.py, transports/dicom.py) -- all pre-existing and all downstream of four optional extras absent from this local venv (pydicom, pynetdicom, webauthn, fhir), which CI installs. None of the four is in this diff and none references the seam. NOT verified in this commit: the two-branch merge proof and the full suite; both run next. * fix(seam): refuse an unresolved ForwardRef instead of walking past it (BACKLOG #1220) Found by running #1220's own acceptance proof, which is the entire reason for running it rather than reasoning about it. Proof (c2) plants a DTO reachable ONLY as a field of one the console imports, renames that nested DTO's field, and requires the gate to red. It stayed GREEN. MECHANISM. Pydantic resolves string annotations at class-build time, but NOT when the referenced class is defined later in the module and nobody calls model_rebuild(). The field's annotation then stays a ForwardRef; typing.get_args() returns () on it; and the closure walked straight past a nested DTO whose field set belongs in the contract. Measured directly: SystemStatus.model_fields['proof'].annotation -> ForwardRef("'ProofNested | None'", is_class=True) typing.get_args(...) -> [] That is a SILENT SKIP inside the walk -- precisely what this module's own docstring forbids, and the defect it was written to remove, reproduced one level down. A skip here is indistinguishable from full coverage at every downstream vantage point: the digest is computed, the gate is green, and the uncovered model looks exactly like a covered one. The fix raises SeamDiscoveryError naming the class and the ref, and says how to resolve it (define the referenced model first, or call model_rebuild). Zero occurrences in the tree today, so this costs nothing now; its first firing is a real hole. With the guard in place the corrected proof (c2) discriminates as it should -- renaming the nested field reds the gate on this tree and leaves origin/main GREEN, which is the defect the closure exists to close. Same edit, opposite outcomes. Verified: ruff check + format clean, mypy strict clean, 28 tests in tests/test_seam_discovery.py pass including a new one that drives the ForwardRef case directly. * docs(seam): record the seam digest in the crypto inventory (BACKLOG #1220) The full suite caught this, not review: three tests failed on the #1220 stack, all one cause -- scripts/webconsole_seam_snapshot.py now calls hashlib.sha256 and the crypto-inventory gate refuses an undocumented crypto call site. The gate is correct and this is the entry it asked for. Recorded in all three places the convention requires, because they are three INDEPENDENT enumerations of the same fact and a fix that updates one leaves the other two lying: scripts/security/crypto_inventory_check.py INVENTORY -- what the gate enforces tests/test_security_static.py _CRYPTO_SITES_OUTSIDE_THE_PACKAGE -- the test-side pin that covers roots the gate does not yet walk (#282) docs/ASVS-L2-PHASE0-CHANGES.md section 4 the human inventory the gate calls itself the machine-readable companion to WHAT THE RATIONALE HAS TO SAY, because "we hash something" is not a justification: it is a CHANGE DETECTOR, not a security control. No secret, no key, no message authentication, and nothing user- or PHI-derived is hashed -- the input is a serialization of public type signatures, field names, enum members and Literal values. The property required is accidental-collision avoidance across the contract surfaces this project will ever produce, which at 64 bits is 2.7e-12 for 10,000 distinct surfaces (about 500x the ~20 seam moves to date). Preimage resistance buys nothing: anyone able to craft a colliding surface already has commit access to _ui_seam.py, where writing the constant directly is strictly easier. SHA-256 rather than BLAKE2 or a truncated non-approved digest only because the engine renders a fips_mode attestation, and a non-approved hash in the shipped surface invites a FIPS question for zero benefit. Verified: tests/test_security_static.py + tests/test_crypto_inventory_scanner.py, 64 passed, 0 failed -- the three that failed on the previous full run. ruff check + format clean. * feat(ai): refuse an unserviceable [ai].provider at config time (BACKLOG #95) Validator ONLY, per the owner's ruling -- no per-provider wire shapes. THE DEMAND GATE ON #95 WAS LIFTED BY THE OWNER IN SESSION on 2026-08-12, in response to a pause-and-ask that named the item, its scope and its cost. Recording that here because the ledger row cannot cite a source otherwise, and this project has already resolved one lane-vs-lane dispute over an unsourced claim that a gate had been lifted. WHAT WAS WRONG. settings.py declared `provider: str = "claude"` with no validation, so any string loaded clean -- azure_openai, gpt-4o-gateway, '', even '../../etc/passwd'. Verified through the real config path (load_settings), not just the constructor. WHY IT MATTERS EVEN WHEN THE BROKER IS NEVER BUILT, which is the part that decided the design: the value is recorded in the PER-USE AUDIT regardless of mode (api/app.py). A config naming a provider the engine cannot service therefore made the audit trail assert something untrue -- so the validator is field-level and refuses in every mode, rather than a model_validator gated on managed_endpoint. The narrower form was the defensible alternative; this is the deliberate choice, not the default. THE ALLOWLIST IS ONE ENTRY, AND ITS SOURCE OF TRUTH IS A HARD-CODED REQUEST BODY. AiBroker.chat builds one wire shape unconditionally -- an Anthropic Messages body with x-api-key and anthropic-version, and an _extract_text that assumes Anthropic's content-block list. There is no provider registry, no dispatch, and AiBroker.provider has ZERO readers, so nothing can derive the serviceable set. That makes the list hand-maintained, which is a real cost and is written down as one. A test pins it to chat()'s body so widening the list without teaching chat() a second shape fails loudly. THE INVERSE FAILURE IS THE TEMPTING ONE. Listing aspirational names (azure_openai, bedrock, ollama) would ACCEPT configurations the broker still cannot service, turning a clean config-time refusal back into the opaque runtime failure this item exists to remove. The list describes what chat() can send, never what the roadmap intends. DOCS. The item names docs/AI.md:22. That line is stale, but docs/CONFIGURATION.md:637 carried a STRONGER false claim the item does not mention -- that provider selects "the broker's request shape", which is not true in the code and directly contradicted AI.md. Fixing only the named line would have left the more actively misleading one in place. Also corrected: AI.md asserted "No model-provider or engine broker integration exists yet" while managed_endpoint has been BUILT since ADR 0135, and the mode tables omitted managed_endpoint entirely (the string appears zero times in that file). The ai_broker module docstring said the Anthropic shape "is used when the provider is claude", implying a branch that does not exist. Verified: ruff check + format clean, mypy strict clean on both changed modules, 94 tests in tests/test_ai_policy.py + tests/test_ai_broker.py pass, and 916 passed / 0 failed across every doc-drift, link and config test in the tree. NOT verified: no CI leg run. * fix(uploads): attribute the upload.prune audit row to the system (BACKLOG #1224) Both sites, because closing one leaves the same false attribution reachable by the other path: the save-time opportunistic sweep in the upload handler, and the background UploadRetentionRunner's audit closure in the API lifespan. Located by the literal "upload.prune", not by line number -- the item's own cited lines had already moved 87 and 121 lines when #1152 landed above them. THE DEFECT IS CROSS-OWNER, AND IT IS STRUCTURAL RATHER THAN INCIDENTAL. prune_expired() is deliberately UNSCOPED -- it has to be, since the per-uploader quota and the sweep both need to see every file -- so the operator whose upload triggers a pass is in general NOT the owner of what it prunes. The row named the pruned file's OWNER as actor while stamping the TRIGGERING operator's address as client, asserting that X deleted their own file from Y's host. That is true of neither of them. The fix applies a decision this repo already made rather than inventing one. actor="system" matches pipeline/retention.py's retention_purge (plus dr, dr_backup, reference_sync and the auth bootstrap). Dropping client is not tidying: _record_reload_audit's own contract is that client is the address OF THE ACTOR NAMED IN THE ROW, and once the actor is the system principal no address is in scope -- ADR 0150 decision 4 rejects exactly this pairing for dual-control config reload, calling a stamped approver address "worse than NULL". Note the engine-internal-writes rule does NOT justify it here, because site 1 genuinely runs inside a request; the actor-address correspondence is what does. The uploader is not lost -- it stays in detail.uploader, which is where a reader can still see whose file went without the row claiming they went and deleted it. RED-FIRST, TWICE, because one mutant was not enough. Reverting the actor reds the test with actor == 'op' while op2 triggered the sweep -- the cross-owner defect in one assertion. And a PARTIAL-FIX mutant (actor corrected, client=client_ip(request) left behind) reds on `assert not '127.0.0.1'`. That second mutant is the point: a test asserting only the actor string would have passed the half-fix, which is the likeliest way this gets "fixed" wrong. Zero tests referenced "upload.prune" before this one, so the defect had no guard at all. Verified: 97 tests across tests/test_upload_api.py, tests/test_uploads.py and tests/test_audit_integrity.py pass -- the last because dropping client changes the ADR 0150 hash-chain payload shape for these rows. ruff check + format clean, mypy strict clean on app.py. NOT verified: no CI leg; SQL Server and Postgres record_audit implementations were read, not executed -- the audit-integrity suite covers the SQLite path only in a local run. * fix(search): key saved presets on the immutable user_id, not the username (BACKLOG #1225) Four sites in api/app.py, and the WRITE went first because it is the setter the other three read -- re-keying only the readers would have made every newly created preset invisible to its own creator. Located by store-method name, not line number: the item's cited lines had all moved +122 when #1152 landed above them. THE ENABLING HALF THE ITEM DOES NOT CITE. A username being reassignable is only half the defect. The other half is that delete_user (store/store.py) removes user_roles, sessions and webauthn_credentials and NEVER preset rows -- so a departed operator's presets outlive the account under a name that is now free to reissue, and a recreated account of that name inherited them, including the PHI-shaped `criteria` that the layered compose returns. Both halves are asserted in the new test. WHY THE EXISTING SUITE WAS NOT EVIDENCE. test_preset_crud_and_owner_scoping compares two operators who are BOTH LIVE, and it passes on the defective code. The defect needs an account to go away and its name to come back; nothing exercised that. The new test does, through the list route, the delete route and the layered compose. RED-FIRST, AND THE FIRST RED WAS THE WRONG ONE. Restoring username-keying at all four sites initially failed on my own PRECONDITION -- which queried by user_id and so could not survive a username-keyed build -- masking the assertion that matters. Made key-agnostic, the mutant now reds exactly where it should: the recycled account sees {'name': 'mine'} where it must see []. A test that goes red for the wrong reason proves nothing, and it looks identical to one that does not. I ALSO TRIED TO CLEAN UP THE STRANDED ROWS AND BACKED IT OUT. The re-key leaves pre-existing rows matching no user_id: unreachable by every route, with their encrypted criteria lingering. I added a one-shot DELETE keyed on `owner NOT IN (SELECT id FROM users)` -- exact, idempotent, and it would have collected delete_user's orphans too. tests/test_retention.py caught it: test_reopening_a_pre_306_db_migrates_last_used_at_in asserts that migration PRESERVES rows, and my statement ran on every open and destroyed data. That is a behaviour change well outside this item, so it is reverted. CONSEQUENCE, stated rather than hidden: a developer with an existing dev store will find their old presets unreachable and should recreate the store. There is no correct username-to-user_id backfill -- a freed name may since have been reissued, which IS the defect. THIS IS A NARROWING, NOT A CLOSURE, and the ADR now says so: _upsert_ad_user re-binds a surviving mirror row's user_id when a directory-side sAMAccountName is recycled without a MessageFoundry delete_user, so a user_id-keyed check is still defeated on that path. BACKLOG #1143 is the real close. Also corrected: the schema comment that read "the owning username" and would have become a lie, the field_authz seed that wrote presets under a username (a row no route can now reach), and ADR 0136, which asserted `owner = identity.username` as the design. NOT swept: identity.username appears 60 times in app.py and 46 are audit actor= fields, where recording a NAME is correct. Only the four ACCESS KEYS moved. Verified: ruff check + format clean, mypy strict clean on both changed modules; 212 passed across every preset/retention/field-authz/schema/PHI-inventory/doc-drift selection, and 447 passed across every doc, ADR and link test. NOT verified: no CI leg. The Postgres and SQL Server preset paths were read, not executed -- they skip locally, and per project experience a blind DB-leg change fails only in CI. Their DDL is unchanged by this commit (the column still holds a string) but their tests seed owners by name. * fix(lint): harden the ASVS tally gate's own stdout against a cp1252 console (BACKLOG #1221) The gate prints DOCUMENT CONTENT, and the corpus it scans is documentation -- so a hit whose line carried a glyph killed the run with UnicodeEncodeError before the finding could be read. THE DEFECT IS INVISIBLE UNTIL THE GATE HAS SOMETHING TO SAY. Against the real corpus it exits 0 and looks perfectly healthy; it fails exactly when it is needed. Reproduced deterministically here before fixing: a one-line doc with a new tally and a U+2192 dies at the findings loop, and the same run is green on the shipped corpus. STREAM-LEVEL, NOT A TARGETED SAFE-PRINT AT THE OBVIOUS SITE -- and that is a correctness choice, not a style one. Python's \d is Unicode-aware, so _AGAINST_TOTAL and _ARITHMETIC take m.group(0) VERBATIM into the token: idioms_for_line() over Arabic-Indic digits yields a token carrying non-ASCII. That token flows into the --print-keys output, the stale-baseline report AND the baseline file, so hardening only the findings loop leaves the same class open in three other places. The two fix shapes are not equally defensible, which is what the item asked to have decided explicitly. Copied rather than imported: this file is on MIRRORED_TOOLS and is contractually stdlib-only, so it cannot reach for a shared helper. The guard mirrors messagefoundry/__main__.py's shape including its reason for being guarded -- some stream wrappers lack reconfigure or reject it, and hardening must never itself crash the tool. THE TEST IS A SUBPROCESS, DELIBERATELY. The existing 45 tests never exercised the print path at all. Forcing the encoding in-process would NOT be the same instrument: pytest's capture wrapper is one of the very objects the hardening guards against, so an in-process test can pass having measured nothing. The child runs under PYTHONIOENCODING=cp1252 with no error handler, so it starts at errors='strict' and only the fix can save it -- a test of the fix rather than of the environment. Mutation-proved: removing the _harden_stdout() call reds the new test with the child's own UnicodeEncodeError traceback in the failure message. Verified: 45 tests in tests/test_asvs_tally_lint.py plus the vault mirror contract pass; ruff check + format clean. The real-corpus run still exits 0. NOT verified: no CI leg. The lint's CI job runs on ubuntu (utf-8), which is why the test forces the encoding rather than relying on the platform -- the defect is not reproducible there by default. * test(seam): classify the seam digest as non-key material (BACKLOG #1220) The full suite caught this, and it is the SECOND gate to fire on the same commit -- the crypto inventory wanted the call site recorded, and this one wants every ASVS section 4 row classified as key material or explicitly not. Both are correct, and between them they are why "we hash something" cannot be waved through here. Classified as NOT key material, with the reason the gate asks for: it is a keyless content hash over PUBLIC type signatures, field names, enum members and Literal values -- a change detector for the engine/console handshake, not a key, a secret, or a message authenticator. Nothing user- or PHI-derived is hashed, and the digest is committed in source on both sides of the seam, so there is no scope to state and nothing to rotate. That places it with the Config fingerprint, the ASVS corpus pin and the engine wheel attestation -- the three existing rows of exactly this shape. Verified: 69 tests across tests/test_key_usage_scope_inventory.py, tests/test_security_static.py and tests/test_crypto_inventory_scanner.py pass. ruff clean. This was the ONLY failure in the full-suite run over the whole lane (1 failed, 11914 passed, 861 skipped). * feat(uploads): record the immutable owner key on the prune audit row (BACKLOG #1224) Owner-approved follow-on to #1224, raised rather than folded in silently because it is beyond that item's stated scope -- #1224 was about the ACTOR field, this is the DETAIL. WHY IT IS WORTH THE KEYSTROKES. The prune row is a permanent record of a deletion whose subject cannot be recovered afterwards: the file is gone, so `detail` is the only surviving description of whose it was. It recorded `uploader`, a username, and a username is reassignable -- which is the entire finding of #1225, shipped two commits ago. A row read a year from now could name a different person than it meant, and nothing would flag it. UploadedFileMeta already carries both fields and its docstring says the split is deliberate (uploads.py:116: "uploader_id is the account's IMMUTABLE..."), so the data was in hand at both sites. This records both: the username because that is what a human reads, the id because that is what still identifies the account after the name moves. FIRST-OF-KIND, AND SAID OUT LOUD. No other audit row in api/app.py carries an id in its detail, so this sets a convention rather than following one. That was the honest argument against doing it, and the counter-argument that won: you fix things where you touch them, and the alternative -- an item proposing the convention for every owner-bearing audit row -- improves nothing today and may sit. If that broader sweep is ever wanted, these two rows are the precedent to point at. No schema change: `detail` is a free-form JSON string, so existing rows still verify against the ADR 0150 hash chain. Ran the audit-integrity suite rather than assuming that. The test asserts uploader_id is present AND that it differs from the username, so a fix that populated it with the name would still red. Verified: 68 tests across tests/test_upload_api.py and tests/test_audit_integrity.py pass; ruff check + format clean; mypy strict clean. * backlog: close BACKLOG #1220 -- the derived seam, and the numbers this item published are now void The fix is the building lane's (claude/instruments-config-handoff-e64adc). This commit adds only the banner, which is the coordinator's to write: allocation and the commit that files it cannot be split across worktrees, and the claim gate requires the banner in the SAME PR as code citing BACKLOG #N. I will not buy that check's green by removing the citation from the PR body -- a green there is evidence nothing looked. The banner records three things beyond "shipped": 1. It shipped the option THIS ITEM PREFERRED. The Scope says derive the seam from a hash so collisions are impossible by construction, and warns the gate-an-integer alternative is "one more thing to remember". The built form is the derived one, and the surface is DISCOVERED rather than enumerated, which was the stated condition. 2. THE ITEM'S OWN NUMBERS ARE VOID, kept rather than deleted. It publishes origin/main = 18 and "the second branch to land re-bumps to 20". Measured 2026-08-12: main reached 20 when #1152 landed, so BOTH held branches sat at 19 -- BELOW main -- and that rule had already inverted before anyone applied it. 3. WHAT IT BREAKS. The seam becomes a str digest, so the two held branches no longer carry a stale integer, they carry a mypy-strict TYPE error. Their bumps are a rewrite, not a renumber. Sequence #1227's class fix after this, never concurrently. Attribution is split in the banner: what the lane reported (the three acceptance criteria, the 25-DTO coverage figure) is labelled as reported, and what was measured here (main at 20, both branches at 19) is labelled as measured.
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 free
to 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.
Three independent documentation defects, each verified against
origin/mainbefore editing, each with an adversarial second pass. No code changes —ruffcorrectly skipped this commit.1. BACKLOG #96 — stranded capacity corrections, finally landed
ADR 0074's 2026-07-14 validity re-check found three pieces of #96's adopter-facing capacity guidance measured-false and unsafe. The correction was written the same day and never landed — it survives only on
bd969b0e, in a ref namespace whose remote no longer exists. B14 named four downstream artifacts; three were amended on 2026-07-14, and #96 was the last one outstanding, so required-change 13 still read as fully open./statszeroes the field the gate reads, so the failure mode satisfies the gateDeviations from
bd969b0e, each deliberate:⛔ BUILD GATEDbanner reads as a CLOSED status against fix(harness): the two-box batch_ab rig could not start — restore the bind override and move its ports #96's OPEN🔢banner, andbacklog_status_checkfails "item fix(harness): the two-box batch_ab rig could not start — restore the bind override and move its ports #96 contradicts itself". Confirmed by executing the checker's ownscan()/parse_items(), not by reading it.⚠️is the sanctioned non-status advisory glyph — the same⛔+⚠️stacking that landed green in backlog: decline 250 and 251 — external tooling does not drive MessageFoundry #92.Every figure traces to a repo line —
THROUGHPUT-STATUS-2026-07-10.md§4, ADR 0074 B2/B4,THROUGHPUT.md§7. Nothing invented.2. Three stale demand-gate banners — AMENDED, not closed
Verification refuted a full close on all three, so each gets an AMENDED banner recording exactly what shipped and what did not:
app.jsregisters[data-mf-log-level]and[data-mf-log-viewer], but no page emits either attribute and/ui/logging/leveland/ui/logs/tailhave no route. Worse than missing: wired to nothing.3. Demand-gate tracker — 11 lanes wrongly "Not started"
All eleven shipped. Each verified individually by ADR status plus code symbol. No merge SHA can be cited for any of them — the public history is snapshot-collapsed across the 2026-07-17..24 window, so the evidence is necessarily ADR-plus-symbol. S7a is marked In progress rather than merged (ADR 0137, branch
dg-s7a); if you prefer a strictly binary table, that cell is the one to argue with.Deliberately NOT in this PR
docs/AOAG-DEPLOYMENT.md§4.5. It was on the same worklist — it mandates a planned cluster-resource outage for theRegisterAllProvidersIP=0workaround, which[store].multi_subnet_failovershould make unnecessary. Verification returned PARTIAL and I am not shipping it, because there is a configuration where the outage is still required: a brownfield listener already atRegisterAllProvidersIP = 0. Two further gaps — the setting has an unstated engine-version floor, and thedb_lookupconnector builds its own ODBC string with noMultiSubnetFailoverat all. Telling a DBA they can skip an outage when they cannot is worse than leaving stale text in place, so this needs an owner decision rather than a guess.Verification
tests/test_backlog_status_check.py— 15 passed, includingtest_the_real_backlog_satisfies_the_invariant, which the⛔form fails. Plus ledger, runbook, doc-ref and doc-drift suites: 100 passed, 89 skipped. Pre-commit: ledger gate passed, PHI leak guard passed, ruff skipped (no Python).🤖 Generated with Claude Code