Skip to content

fix(harness): enforce the reconcile's read >= sent//2 floor unconditionally - #26

Merged
wshallwshall merged 1 commit into
mainfrom
reconcile-floor
Jul 28, 2026
Merged

fix(harness): enforce the reconcile's read >= sent//2 floor unconditionally#26
wshallwshall merged 1 commit into
mainfrom
reconcile-floor

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

The load harness's no-loss reconcile documented, in all three copies plus the
test docstring and the 0.3.2 changelog entry, that its unconfirmed-send excusal
"keeps read >= sent // 2 always required". It did not.

The excusal is capped at max(unconfirmed_budget, sent // 2), and every one of
the five call sites passes a CONNECTION COUNT as unconfirmed_budget
(report.py pool_size x targets; connscale/runner.py and estate/runner.py the
step's count verbatim; connscale/remote.py n x count; multishard.py
engines x count_per_engine). max() takes that count as a FLOOR, not a ceiling,
so whenever the connection count exceeds half the sends -- the normal shape of
a short, low-rate step -- the count wins and the bound degrades to
read >= sent - connections. At connscale-smoke's N=100 cell (~105 sends against
100 connections, budget max(100, 52) = 100) that is read >= 5: 95% of the run
excusable, which is the exact vacuity the cap was written to prevent. Nothing
clamps the excusal to sent either, so timeouts > sent degraded it to
read >= 0 outright.

PR #17 did not create this hole. The bound before it was unconfirmed_budget
alone -- the identical value in exactly these cells -- so in the floor-dominated
regime the pre-#17 and post-#17 verdicts are bit-for-bit the same. #17 left the
hole where it was and attached to it a guarantee it does not deliver there.
Nor was the gap detectable: every test used a budget of 2 or 4 against 36 or 90
sends, where sent // 2 always won the max(), so deleting the floor arm from all
three copies left the suite fully green -- a surviving mutant.

The fix asserts the guarantee separately, as an intake floor the excusal cannot
lower:

floor_short = sent // 2 - read

folded into ok in all three copies with its own detail string. The
half-the-run cap and the over_budget systemic-no-ACK determination are
UNCHANGED, so #17's de-flake survives intact (the 84-of-90 red-CI regression
and the 45-of-90 at-cap pin both still pass). The floor is 0 at sent == 0 and
rounds DOWN on an odd sent, so it is never stricter than the documented bound.
In report.py it is deliberately not subject to tolerance: the tolerance is an
operator knob on the shortfall, not a licence to lower the anti-vacuity floor.

Also fixed, found while tracing the call sites: the estate copy lacked the
honest-reporting branch its two siblings have, so a bounded-excused estate run
printed the flat "read>=sent, sink_received>=written, backlog drained" detail
while read was demonstrably below sent -- a claim false on its own numbers. Its
over-budget detail string had drifted from its siblings' too; a new test pins
the three in step, since four comments and the changelog asserted that they were
and nothing enforced it. And five stale comments still recited the "~one
stranded in-flight frame per connection" model #17 retired, including the two
that justify the budget VALUE being passed (connscale/runner.py, multishard.py)
and the rate-SLO sample-floor comment in report.py.

Thirteen new tests pin it, all mutation-verified against the real functions:
the floor-binding regime at connscale-smoke's shape (budget 100 vs 105 sends),
that it is the floor and not the budget doing the work there, exact rounding on
an odd run size (52 of 105 passes, 51 fails), timeouts > sent, an empty run,
tolerance not lowering the floor, estate's detail honesty in both directions,
cross-copy detail parity, and -- guarding the other way -- that ~16% teardown
stranding still passes at each smoke shape. Neutralising the floor kills 7 of
them across all three copies; rounding it up instead of down kills the boundary
pin; dropping estate's suffix kills the parity pin.

Known gap, not addressed here and now stated as such in the code rather than
contradicted by it: over_budget is still gated on the same
max(unconfirmed_budget, sent // 2), so a totally dead ACK path still passes as
clean whenever unconfirmed_budget >= sent (verified: sent=90, timeouts=90,
read=90, budget=100 -> ok=True on all three copies). An intake floor
structurally cannot catch a fault whose signature is high read with no ACKs;
that arm needs the budget itself bounded. Follow-up.


🤖 Generated with Claude Code

…onally

The load harness's no-loss reconcile documented, in all three copies plus the
test docstring and the 0.3.2 changelog entry, that its unconfirmed-send excusal
"keeps read >= sent // 2 always required". It did not.

The excusal is capped at max(unconfirmed_budget, sent // 2), and every one of
the five call sites passes a CONNECTION COUNT as unconfirmed_budget
(report.py pool_size x targets; connscale/runner.py and estate/runner.py the
step's count verbatim; connscale/remote.py n x count; multishard.py
engines x count_per_engine). max() takes that count as a FLOOR, not a ceiling,
so whenever the connection count exceeds half the sends -- the normal shape of
a short, low-rate step -- the count wins and the bound degrades to
read >= sent - connections. At connscale-smoke's N=100 cell (~105 sends against
100 connections, budget max(100, 52) = 100) that is read >= 5: 95% of the run
excusable, which is the exact vacuity the cap was written to prevent. Nothing
clamps the excusal to `sent` either, so timeouts > sent degraded it to
read >= 0 outright.

PR #17 did not create this hole. The bound before it was `unconfirmed_budget`
alone -- the identical value in exactly these cells -- so in the floor-dominated
regime the pre-#17 and post-#17 verdicts are bit-for-bit the same. #17 left the
hole where it was and attached to it a guarantee it does not deliver there.
Nor was the gap detectable: every test used a budget of 2 or 4 against 36 or 90
sends, where sent // 2 always won the max(), so deleting the floor arm from all
three copies left the suite fully green -- a surviving mutant.

The fix asserts the guarantee separately, as an intake floor the excusal cannot
lower:

    floor_short = sent // 2 - read

folded into `ok` in all three copies with its own detail string. The
half-the-run cap and the over_budget systemic-no-ACK determination are
UNCHANGED, so #17's de-flake survives intact (the 84-of-90 red-CI regression
and the 45-of-90 at-cap pin both still pass). The floor is 0 at sent == 0 and
rounds DOWN on an odd `sent`, so it is never stricter than the documented bound.
In report.py it is deliberately not subject to `tolerance`: the tolerance is an
operator knob on the shortfall, not a licence to lower the anti-vacuity floor.

Also fixed, found while tracing the call sites: the estate copy lacked the
honest-reporting branch its two siblings have, so a bounded-excused estate run
printed the flat "read>=sent, sink_received>=written, backlog drained" detail
while read was demonstrably below sent -- a claim false on its own numbers. Its
over-budget detail string had drifted from its siblings' too; a new test pins
the three in step, since four comments and the changelog asserted that they were
and nothing enforced it. And five stale comments still recited the "~one
stranded in-flight frame per connection" model #17 retired, including the two
that justify the budget VALUE being passed (connscale/runner.py, multishard.py)
and the rate-SLO sample-floor comment in report.py.

Thirteen new tests pin it, all mutation-verified against the real functions:
the floor-binding regime at connscale-smoke's shape (budget 100 vs 105 sends),
that it is the floor and not the budget doing the work there, exact rounding on
an odd run size (52 of 105 passes, 51 fails), timeouts > sent, an empty run,
tolerance not lowering the floor, estate's detail honesty in both directions,
cross-copy detail parity, and -- guarding the other way -- that ~16% teardown
stranding still passes at each smoke shape. Neutralising the floor kills 7 of
them across all three copies; rounding it up instead of down kills the boundary
pin; dropping estate's suffix kills the parity pin.

Known gap, not addressed here and now stated as such in the code rather than
contradicted by it: `over_budget` is still gated on the same
max(unconfirmed_budget, sent // 2), so a totally dead ACK path still passes as
clean whenever unconfirmed_budget >= sent (verified: sent=90, timeouts=90,
read=90, budget=100 -> ok=True on all three copies). An intake floor
structurally cannot catch a fault whose signature is high read with no ACKs;
that arm needs the budget itself bounded. Follow-up.
@wshallwshall
wshallwshall enabled auto-merge (squash) July 28, 2026 19:55
@wshallwshall
wshallwshall merged commit 88373cd into main Jul 28, 2026
32 checks passed
@wshallwshall
wshallwshall deleted the reconcile-floor branch July 28, 2026 22:57
wshallwshall added a commit that referenced this pull request Jul 29, 2026
…bes (#41)

* docs(backlog): close eight engine/transport/DR items whose banners described merged code as unbuilt

The published docs/BACKLOG.md is a 2026-07-12 snapshot; the code on origin/main is
current through 2026-07-28. Eight banners here contradicted the code, and a stale
banner is the mechanism by which merged work gets rebuilt.

Every citation below was resolved against this worktree before the banner was
written -- none was copied from another ledger.

- #213 accepts= seam: ~1,500 merged lines were described as an unstarted big bet.
  Highest double-build risk in the set.
- #97 / #117: merged in PR #1220 (2026-07-24), not stranded on lane dg-s5.
  ADR 0124 is on main, and the #117 x #82 interaction is a WiringError, not a doc.
- #82: the banner asserted a "Confirmed gap" that verify_ack_control_id closed.
  Retracted explicitly rather than silently dropped.
- #102 / #223: the DR seed gate has teeth on all three backends; #223's option (a)
  was declined by the owner, so neither carries a residual.
- #142: the cross-backend dedup ledger the banner called missing exists.
- #187: ADR 0079 is Accepted with mechanism 2 built; the Kerberos residual is
  closed. The ad_session_recheck_seconds default flip is flagged as a separate
  lane, since this one is docs-only by design.

* docs(backlog): close seven alerting and IDE items that were built after the snapshot

Same reconcile as the previous commit, on the alerting and IDE surfaces. Each
banner cites paths resolved in this worktree.

Two of these banners did not merely lag the code, they asserted the opposite of
it, so the retraction is explicit rather than a silent rewrite:

- #144 read "notify-only"; a validated control-action vocabulary dispatches
  restart_inbound/restart_outbound before the transport-suppression return.
- #145 read "only log at INFO"; leadership and DR transitions are first-class
  alert events, with the lost/released edges wired as auto-resolving inverses.

The rest were simply built: #118 (POST /alerts/test-email), #143 (windowed
notification-only suspend, durable on all three backends), #48 (36 snippets +
quick-pick), #221 (ADR 0100 native surface), #222 (all three lens phases).

#48 keeps its non-status 🔶 note; the ✅ banner leads and marks it historical.

* docs(backlog): close four harness/bench items — the instruments exist

#216 is the sharpest case: its banner said "no existing harness covers it"
while harness/load/estate/ (1,342 lines across four modules), harness/config/estate/
and `python -m harness --estate` were all on main. That premise is retracted.

Two things are deliberately NOT presented as closed work:

- #216's simple_fraction=0.72 and hub_fanout=3 are recorded as still needing
  OWNER SIGN-OFF, together with the shape discrepancy against the item's own
  "17% hub, H=20, N=4" text. The instrument is built; the shape is the owner's call.
- #209's H=20 rig run is named as bench time against capacity the project does
  not own, so it cannot reopen the item.

#207 closes on ADR 0141, which names it. Recorded honestly: txn/msg is measured,
but bytes/msg STAYS REFUSED with copies-per-message shipping as the sizing proxy
— an ADR decision, not an unbuilt residual.

#220 ships the piecewise same-PID-set CPU sum the item specified.

* docs(backlog): close five measurement items — the experiments ran, the owner ratified

These five are measurement/decision items, not builds. Each was already answered;
leaving them open invites re-running published experiments, and #215 alone is five
900-second AWS soaks.

Required framings, recorded deliberately:

- #212 — DECIDED: ships OFF. settings.py:295 already carries default=1, so no code
  change closes it. Priced at ~+4.7% against the +8% PROCEED bar (ADR 0107).
  Revisit only on a latency or store-load rationale, never a throughput one.
- #211 — characterization-only. Explicitly NOT a licence to flip the claim_mode
  default (the 1,500-lane claim storm is why the default stands) and NOT a rig ask.
- #208 — the residual is OFF-REPO; no in-repo change can close it. Published with
  no sizing figure at all, since a prior sizing claim was refuted.
- #215 — Phase 5 closed, R in [2,3); the m7i.8xlarge upsize is retired at :1719.
- #218 — answered DECLINING (1.36x for 4x shards), soft-magnitude caveat carried.

#218's C1 json artifacts are not on origin/main, so the banner cites the status
document lines and says plainly that the artifacts are held off-repo, rather than
printing a path that resolves to nothing.

* docs(backlog): record six declines the ledger had not published

Each was already ruled on; the published file still showed them open, which is
how a declined design gets rebuilt by a session that only reads this file.

#231 was the live trap: it still read "🔢 Filed" although it was declined
2026-07-20 against the #26 guardrail. Its "Open question" is now answered in the
banner rather than left inviting work.

Two declines carry an explicit anti-deletion clause, because reading ⛔ as
"remove the code" would destroy shipped work:

- #157 — do NOT delete messagefoundry/transports/direct.py; the outbound S/MIME
  half ships and stays. Only the inbound/HISP/XDR remainder is declined.
- #210 — ADR 0114 deliberately PRESERVES the four tempdb table variables in
  sqlserver.py:702-717; they are load-bearing for per-lane FIFO. Removing them is
  a rejected design, not an unfinished one.

#217 is dead by measurement three times (ADR 0069 -> 0099 -> 0107, which also
stamps ADR 0057 DO NOT PROMOTE). #91 is declined with its re-open trigger stated
in ADR 0053's own terms rather than left as a standing invitation. #87 is owner-
closed recon that ships nothing.

* docs(backlog): close #185 as superseded, and re-price four items the ledger misprices

#185 — closed as SUPERSEDED by the ADR 0115 re-partition into #242-#246. It is an
index-only umbrella owning no findings and shipping nothing runnable, so an index
whose contents moved has nothing left to index. The banner states explicitly that
this is NOT a claim that ASVS is done: the programme continued past this baseline
(which ends at #231) and docs/security/ is gitignored post-cutover, so the ASVS
state cannot be read off this item in either direction.

Four amendments — re-priced, deliberately NOT closed, each with the reason it
still cannot be scheduled:

- #214 -> stays 🚧. Mechanism merged and tested; exposing transform_concurrency
  is DECLINED (unmeasured, and inert unless per_lane AND fifo_claim_batch>1 AND
  not the SQL Server fused path — all three gated at wiring_runner.py:4819).
- #105 -> the synthetic-schema blocker is discharged by ADR 0086 §2(a'), but the
  real gate is #313, which is invisible from this baseline. Not a green light.
- #94  -> difficulty 8 -> 5-6, because ADR 0105/#149 shipped the substrate and
  reserved the deref seam. Still ADR-first, still demand-gated.
- #99  -> not a 6/6 build. (g) shipped via #274/ADR 0142; only (e) remains and it
  is provisioning (a real DC + AD CS + gMSA), gated behind #275.

#99 also flags that its own OFF-LOOPBACK-DEPLOYMENT.md links no longer resolve
from the public repo, so a reader does not mistake a publishing boundary for rot.

* docs(backlog): re-sync both ranked tables with the corrected banners

Final commit of the reconcile, deliberately last: rows in both tables sit inside
git's 3-line merge context (#169/#179, #96/#141/#180, #208/#211), so touching them
earlier would have made every prior commit conflict-prone. This lane is the only
writer of docs/BACKLOG.md, so one late pass is safe. merge=union is NOT set — it
would duplicate banners and break the one-status rule.

33 rows: 17 in the main table, 16 in the post-re-score addendum. Tier cells now
carry the real state (✅ SHIPPED / ✅ CLOSED / ⛔ DECLINED / 🚧 PARTIAL) and each
Why cell carries the evidence, so the tables can no longer contradict the banners
below them.

Four Why cells retract a specific false claim rather than quietly replacing it —
#82 ("_check_ack matches MSA-1/MSA-3 only"), #144 ("notify-only"), #145 ("only log
at INFO") and #216 ("no existing harness covers it") — because those sentences are
what would send a session off to rebuild merged work.

Rows for #169, #179, #180, #141 and #96 are untouched: demand-gated, triggers not
fired. #94's difficulty moves 8 -> 5-6 but the row stays in place, and the cell
says so, rather than silently re-sorting a dated snapshot table.

The Distribution/Tiers paragraph above the table is left alone: it is an explicit
2026-07-10 re-score snapshot, and prior shipped items did not recompute it either.

* docs(backlog): lead #117 with its status banner, not its historical note

#117 was the one closed item whose leading blockquote still opened with the 2026-07-09
"Decline overturned ... this is an unfired demand-gate" note, with the ✅ underneath.
A reader scanning leading banners — which is exactly how the double-build failure mode
starts — would have hit "unfired demand-gate" first on an item that shipped in PR #1220.

The 🛠 note is retained (it is not a status glyph and the rules allow it to stay), moved
below the ✅ and marked historical, with its build-constraints list labelled as met.
No wording in either block is otherwise changed. Matches how #48 already reads.

* docs(backlog): correct banners an adversarial review proved wrong

A 13-agent adversarial pass over this reconcile confirmed 24 findings. Every one
below was re-verified by hand against the code before editing. Two were the same
failure mode this reconcile exists to stop, pointed the other way — a banner
stating something false.

Materially wrong, now fixed:

- #214 said "the residual is one settings field". It is not. The ~40x headline
  comes from commit-collapse, which is UNBUILT: Store.transform_handoff is
  strictly single-row (store/base.py:331-334) and the in-repo plan sizes the
  remainder as a batched multi-row handoff across all 3 backends, XL, new ADR
  required (BACKLOG-EXECUTION-PLAN-2026-07-24.md:129). The banner now leads with
  that residual instead of hiding it behind the declined knob.
- #91 called the wall "transaction-shaped" citing ADR 0098 and ADR 0107. Both say
  the opposite: 0098 withdrew that exact phrasing from its own title as WRONG
  (:3-11) and 0107 measured transaction-reduction elasticity at -0.115 (:57-59).
  The decline stands on the CPU argument; the false support is gone.
- #223 asserted the owner DECLINED option (a) on 2026-07-20. ADR 0102 records it
  as DEFERRED (:67, :128) and no in-repo record of a decline exists. Now states
  both, and says not to restate the decline as in-repo fact until an ADR records it.
- #231 implied #26 had already declined Block. ADR 0106 (Accepted, :20/:64/:146)
  DEFERRED it here. The decline is the later owner ruling superseding that.
- #82 claimed it "retracted" a false gap while the same claim still stands in the
  item body, which rule 1 barred me from editing. Now says so explicitly.

Citation precision: #142 (honoured at :762-765, not :355), #145 (DrCoordinator
fire sites dr.py:281/:341, not the AlertSink Protocol stubs), #217 (ADR 0099
withdrew group-commit superseding ADR 0055 and gated inline fusion), #221 (MEFOR
Live lives in liveDebug.ts), #48 (36 total = 32 idioms + 4 scaffolds).

Disclosed residuals that were missing: #207 committed_txns is hardcoded 0 on
PostgreSQL (postgres.py:793), so both figures degrade there; #144 auto-STOP is
declined by design, not pending (ADR 0128:31-32).

Table hygiene: #219 row carried P2 over a ✅ BUILT banner; the frozen
Distribution/Tiers snapshot now says it is frozen and not a current census.

* docs(backlog): close 23 more items a full sweep proved were already merged

The first pass closed the 31 items it was handed. A review then found #109, #147
and #177 by accident — which meant the sweep had been incomplete, not that three
items were missed. So this swept ALL 78 remaining open items against the code.

Method: one pass per item to find merged code, then TWO independent adversarial
lenses per candidate — one hunting a missing half, one hunting wrong-thing
evidence (stubs, dead code, docstrings, declines misread as ships). Only
candidates BOTH lenses failed to refute are closed here. 23 of 31 candidates
survived; 8 were refuted and are deliberately left open.

That bar earned its keep: #177 was refuted by both lenses. Its API half is merged,
but the Scope asks for an endpoint PLUS a console view and no such view exists —
I had been about to close it. It stays open, as do #81 #95 #114 #124 #125 #172
#228, each with a real remainder.

Every banner discloses what the close does NOT cover, because a close that
overstates is the same defect as a banner that understates. Notable:

- #67 carries a REAL DEFECT into its close: the `{ ? = CALL proc(:x) }` shape is
  the canonical example in the docstring, the gate error and the test fixture,
  but _parse_named_params substitutes only `:name`, so the return-value `?` is
  never bound. Warrants a new item.
- #121 ships the mechanism but defaults to OFF, not the "four hours" the item
  asked for (ADR 0137:79-83 chose the [retention] keep/off convention).
- #147 has a live gap: _start_schedulers runs only from start(), never from
  config reload, so an edited schedule needs a restart.
- #227's secondary ask is off-repo and cannot be produced from this repository.
- #168 adds a NEW PHI-at-rest surface (plaintext VS Code workspace storage).
- #230 leaves two "optional fast-follow" items unbuilt; they need re-filing.

Seven ADR links I first wrote were wrong filenames — the citation audit caught
every one before commit. All 121 cited paths and 196 line refs now resolve.

* docs(backlog): name the BUILT half of eight items the sweep refuted

These eight survived the sweep as OPEN — two adversarial lenses each found a real
remainder, so none is closed. But every one has a substantial merged half, and an
item that reads wholly unbuilt invites rebuilding it. Each banner now names what
exists, what is genuinely missing, and where the missing part starts.

#177 is the cautionary one. I had been ready to close it on a merged endpoint;
both lenses refuted that, and they were right — the Scope says endpoint PLUS
console view, and no console route, page card or apiclient wrapper exists. The
amendment says so, and says the endpoint must not be rebuilt.

Three remainders are worse than "not built yet" and are called out as such:

- #124's console half is DEAD CODE — the JS binds [data-mf-msg-export], which no
  page emits, and fetches /ui/messages/export, which has no route.
- #114 silently accepts File(validate_directory=True) on an outbound and ignores
  it, with no WiringError — an operator gets no fail-fast and no error either.
- #95 accepts a `provider` it never reads and always sends the Anthropic wire
  body, so every other backend fails as an opaque 502 rather than a config error.

#125 also records that "save" appears nowhere in ADR 0134, not even its
out-of-scope list, so it needs an explicit build-or-decline rather than drift.

Two citation errors of mine were caught by the audit before commit: a wrong ADR
0135 filename, and alert_sinks.py:2366-2367, which was really api/app.py.

* docs(backlog): re-sync ranked tables for the swept items, and correct #177

18 rows flip to a closed tier for the items the sweep closed, and 6 rows gain a
PARTIAL note naming the built half so the tables stop reading as fully-unbuilt
work. Kept last again, since these rows sit inside git's 3-line merge context.

#177's row is a correction, not a re-sync: it advertised SHIPPED over an item the
sweep proved is PARTIAL (the endpoint exists; the console view the Scope requires
does not). The row now says so and flags that it previously read SHIPPED, because
a table that overstates is the same defect as a banner that understates — it just
fails in the other direction.

Caught in verification: a `cert import|inventory` cell contained a literal pipe
and split a markdown row into 9 cells. All main-table and addendum rows re-checked
for column count.
wshallwshall added a commit that referenced this pull request Jul 30, 2026
Filed #232-#239. Five are gaps the evaluation surfaced in our own surface
rather than anything the vendors would have fixed; three are ideas worth
borrowing from Windmill without adopting it.

  #232  routers get no Steps view at all (ADR 0076 §3 scoped them out;
        a router returns handler names, so the row contract has no kind
        that fits -- ADR-first, it widens the grammar)
  #233  blockExtent/walkMove/resolveDrop each exist TWICE, in stepsModel.ts
        and again in the CSP-isolated stepsWebview.js; only the model side
        is tested, so a drift shows up as a mis-landed drop with green tests
  #234  projection refreshes on save only -- filed as REVISIT, not a bug:
        it is ADR 0076 §5's deliberate InterSystems guardrail, and the
        live-value skip is #225's correctness fix for disk-vs-buffer line
        misalignment. Any change amends the ADR.
  #235  parameter forms from Python type hints (widens what is editable
        WITHOUT widening the recognition grammar)
  #236  test-this-step / test-up-to-step with pinned upstream values
  #237  per-argument input modes -- a presentation of the value-expression
        classes ADR 0089 §5 already computes
  #238  OpenFlow attributes as a completeness CHECKLIST; explicitly not a
        compatibility target, and not a declarative artifact (#26, 0076 §7)
  #239  re-measure estate coverage

On #239, correcting the evaluation's own error: it claimed nobody had
measured the opaque-row fraction. Wrong. ADR 0089 scanned 87 files / 486
functions / 3,852 statements and found ~66% opaque, ~42% editable after
Phase A, and says in §5 the scan is repeatable by design. What is unknown
is the number TODAY, after the palette, fan-out and picker landed -- so
this is a re-measure, and the item says so.

The first attempt at this commit was blocked by the forbidden-content gate:
the estate note named the corpus by its customer token. The gate was right
and the name is gone -- #239 now says to confirm the path with the owner.

Numbers allocated via scripts/coord/alloc.ps1 (ledger gate).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
wshallwshall added a commit that referenced this pull request Jul 30, 2026
)

Filed #232-#239. Five are gaps the evaluation surfaced in our own surface
rather than anything the vendors would have fixed; three are ideas worth
borrowing from Windmill without adopting it.

  #232  routers get no Steps view at all (ADR 0076 §3 scoped them out;
        a router returns handler names, so the row contract has no kind
        that fits -- ADR-first, it widens the grammar)
  #233  blockExtent/walkMove/resolveDrop each exist TWICE, in stepsModel.ts
        and again in the CSP-isolated stepsWebview.js; only the model side
        is tested, so a drift shows up as a mis-landed drop with green tests
  #234  projection refreshes on save only -- filed as REVISIT, not a bug:
        it is ADR 0076 §5's deliberate InterSystems guardrail, and the
        live-value skip is #225's correctness fix for disk-vs-buffer line
        misalignment. Any change amends the ADR.
  #235  parameter forms from Python type hints (widens what is editable
        WITHOUT widening the recognition grammar)
  #236  test-this-step / test-up-to-step with pinned upstream values
  #237  per-argument input modes -- a presentation of the value-expression
        classes ADR 0089 §5 already computes
  #238  OpenFlow attributes as a completeness CHECKLIST; explicitly not a
        compatibility target, and not a declarative artifact (#26, 0076 §7)
  #239  re-measure estate coverage

On #239, correcting the evaluation's own error: it claimed nobody had
measured the opaque-row fraction. Wrong. ADR 0089 scanned 87 files / 486
functions / 3,852 statements and found ~66% opaque, ~42% editable after
Phase A, and says in §5 the scan is repeatable by design. What is unknown
is the number TODAY, after the palette, fan-out and picker landed -- so
this is a re-measure, and the item says so.

The first attempt at this commit was blocked by the forbidden-content gate:
the estate note named the corpus by its customer token. The gate was right
and the name is gone -- #239 now says to confirm the path with the owner.

Numbers allocated via scripts/coord/alloc.ps1 (ledger gate).

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
wshallwshall added a commit that referenced this pull request Aug 6, 2026
…ection 12 carve-out (BACKLOG #232) (#225)

* docs(adr): ADR 0076 Amendment D adds a `route` row kind so `@router` defs get a Steps view (BACKLOG #232)

Design-only lane. No feature code, no lens rule, no .ts -- the build is handed
off, not done.

- ADR 0076 Amendment D (owner-ratified 2026-08-05) widens the grammar under its
  own S2 rule, exactly as Amendment A did: S3's row enum gains `route` and S4's
  recognition grammar gains one router-return rule, so a `@router` gets the same
  Steps projection a `@handler` already has. S3's "routers out of v1 scope" is
  superseded for `@router` defs. The finding recorded: routers were out of v1
  because the field-mutation vocabulary had no kind for a routing return -- a
  grammar gap, not a veto.

- CLAUDE.md S12 carve-out widened to name Routers. The "Handlers" wording is
  found INCIDENTAL: the carve-out's stated rationale is the plain-.py,
  single-artifact, single-execution-path property, which a `@router` shares
  identically, so naming Routers does not cross the #26 line. The `route` row
  kind itself is the genuine grammar expansion on the owner's judgement.

- New docs/releases/HANDOFF-232-router-steps.md: the four build components, the
  exact files with verified anchors, the ADRs still to amend at build time, the
  `editor-toolbar.test.ts:62` red-by-design tripwire PLUS at least two further
  published assertions that invert at build time (tests/test_lens_parse.py:435
  test_routers_are_out_of_scope, and master-test-plan STEPS-52/-46), the
  `return []` disambiguation by enclosing decorator, contract-version skew, and
  the open questions.

- docs/adr/README.md 0076 index row records Amendment D (same A/B/C convention).

- Spec-completeness (verified this pass): the S D.4 return-disambiguation table
  and AC-R2 now list a bare `return` in the routed-nowhere row -- the engine's
  `_handler_names` (pipeline/dryrun.py:98-101) normalizes value-less `return` to
  `[]`, identical to `return None`.

This is a NOT-DEPLOYED beta; #232 is a low-severity capability gap with no
correctness or security risk and no prior router-Steps contract to preserve.

* backlog: #232 banner -- ADR gate discharged, build handed off (BACKLOG #232)

Flip #232's status banner only. The ADR gate is discharged (ADR 0076
Amendment D) and the build is handed off (docs/releases/HANDOFF-232-router-steps.md),
so the banner moves from "not started" to "in progress" -- both OPEN statuses.
The item is NOT closed: the Steps-for-routers build is handed off, not done.

The banner prose also drops the "S3 grammar" shorthand for the accurate
"S3 row enum + S4 recognition grammar" (per ADR 0076 S2, the grammar rule is S4).

Scope: only #232's banner line under its own heading changed. The ranked table
and the four census distribution lines were NOT touched, and the census was NOT
recomputed.
wshallwshall added a commit that referenced this pull request Aug 7, 2026
…e, which is where it rots

Caught by the b5-backlog-archival session. The marker exists precisely to outlive
#1073, and it cited `docs/BACKLOG.md #1073` -- but #1073 is closed, and closed items
LEAVE that file for docs/archive/backlog/BACKLOG-CLOSED.md.

The failure is invisible to every check: the markdown link to BACKLOG.md still
resolves, so nothing 404s. Only the human-readable "#1073" silently stops being
findable there. That is exactly how section 12's EXISTING #26/#27 pointers already
went stale -- verified against origin/main: neither heading is in docs/BACKLOG.md,
both are in the archive.

Now names both locations rather than guessing an archive anchor that may not match
the slug the archival pass generates. The repo's deep-link-to-archive convention
(44 occurrences in docs/BACKLOG.md) is the precedent.

NOT FIXED HERE, deliberately: the pre-existing #26/#27 staleness in the same list.
It is the same defect class but not this PR's scope, and the session that found it
is taking it.

I had noticed the #26/#27 staleness earlier in this session and judged it "not worth
chasing", then reproduced the identical defect in a marker whose whole purpose is
durability. Recording that, because the lesson is not "fix the pointer" -- it is that
a rot I declined to fix is one I had already stopped seeing.
wshallwshall added a commit that referenced this pull request Aug 7, 2026
…ose #1073, file #1089-#1093 (#269)

* backlog: ship the ASCQM catalogue pass, file #1089-#1093, mark the 5055 decline in CLAUDE.md (closes #1073)

The BACKLOG #1073 pass ran over all 74 live ASCQM 1.1 elements with an adversarial
refutation stage on every non-not-applicable verdict. The measure stays declined; the
catalogue earned its keep on two narrow grounds and this commit records both.

FINDINGS FILED
  #1089  HL7 parse_path accepts component 0, so PID-5.0 reads AND OVERWRITES the last
         component. Reproduced by execution. The X12 twin validates and is tested; the
         HL7 side -- the default content type, the one carrying PHI -- has neither.
  #1090  write_reference_snapshot's json.dumps has no default hook; the FILE reference
         source does not coerce where its DATABASE sibling does. Every existing test
         uses CSV, so the suite cannot reach it.
  #1091  Credential detection reads neither .ps1 nor .yaml, and the entropy gate floors
         out on low-entropy secrets. Written in the conditional: this is a control that
         cannot see the class, NOT a live exposure.
  #1092  Eight verdicts flipped under measurement and ALL EIGHT flipped covered->gap.
         Three falsify a specific written claim, including the quality record's
         "import/layer rules are machine-checked in CI".
  #1093  Inventory of the rest, with the ~19 metric findings explicitly NOT to be filed
         (section 4.1: a count is not an item) and two unverified premises flagged.

THE DECLINE MARKER IS THE PART THAT OUTLIVES THE ITEM. CLAUDE.md section 12 now carries
it. A decline recorded only in a backlog item disappears when that item archives --
verified: #26 and #27 are both in BACKLOG-CLOSED.md today and survive as binding
decisions only because their markers were lifted into section 12.

COUNTS RESOLVED: the CISQ-vs-ASCQM conflict was a UNITS problem. CISQ counts CWEs
including children; ASCQM counts elements. Performance Efficiency = 15 is now confirmed
and its unverified mark is lifted; the "139 total" stays unconfirmed and that mark stays.

RECORDED AGAINST MYSELF: the first run silently judged 62 of 74 elements after one
triage batch died, and returned a confident report that gave no sign a sixth of the
catalogue was unread. Caught by arithmetic, not by the run. One of the missed elements
became a filed finding, so it was not harmless. It is this repo's own section 4.0
failure mode reproduced inside the tool built to hunt for it.

VERIFIED: backlog_status_check.py exit 0, 350 items each declaring one status; five new
headings, one banner each; zero prose glyphs in the new section.
NOT RUN: pytest, mypy -- no .venv in this worktree. Docs-only diff.

* docs(CLAUDE): the 5055 decline marker cited only the LIVE backlog file, which is where it rots

Caught by the b5-backlog-archival session. The marker exists precisely to outlive
#1073, and it cited `docs/BACKLOG.md #1073` -- but #1073 is closed, and closed items
LEAVE that file for docs/archive/backlog/BACKLOG-CLOSED.md.

The failure is invisible to every check: the markdown link to BACKLOG.md still
resolves, so nothing 404s. Only the human-readable "#1073" silently stops being
findable there. That is exactly how section 12's EXISTING #26/#27 pointers already
went stale -- verified against origin/main: neither heading is in docs/BACKLOG.md,
both are in the archive.

Now names both locations rather than guessing an archive anchor that may not match
the slug the archival pass generates. The repo's deep-link-to-archive convention
(44 occurrences in docs/BACKLOG.md) is the precedent.

NOT FIXED HERE, deliberately: the pre-existing #26/#27 staleness in the same list.
It is the same defect class but not this PR's scope, and the session that found it
is taking it.

I had noticed the #26/#27 staleness earlier in this session and judged it "not worth
chasing", then reproduced the identical defect in a marker whose whole purpose is
durability. Recording that, because the lesson is not "fix the pointer" -- it is that
a rot I declined to fix is one I had already stopped seeing.
wshallwshall added a commit that referenced this pull request Aug 7, 2026
Resolves the CLAUDE.md section 12 conflict predicted before #269 landed. #269
merged as f28359e while this PR was being opened.

KEPT BOTH SIDES, as the two changes are semantically independent:
  ours   -- the #26 / #27 / #222 pointers re-derived onto BACKLOG-CLOSED.md
  theirs -- #269's new ISO/IEC 5055 / ASCQM decline bullet

DROPPED, and this is the only deletion: main's stale
`([docs/BACKLOG.md](docs/BACKLOG.md) #27, ...)` line. It is the pre-fix text of
the very bullet this branch rewrites, carried in as context by #269's insertion
directly beneath it -- not content #269 authored. Keeping it would have restored
the rot.

Verified after resolution rather than assumed:
  - no conflict markers remain
  - all four spans present: #26, #27, #222 rewrites AND the ISO 5055 bullet
  - the stale #27 line is gone
  - every link target in the merged section 12 resolves (8 unique, all OK),
    with a known-bad path run through the same checker to prove it reports a miss
  - re-resolved every cited number against the post-merge tree: #26/#27/#222
    archived, #232 live, #1073 live-but-closed -- so #269's "once archived"
    wording is correct and its dual citation is satisfied

Merged rather than rebased so the PR's auto-merge arming survives.
wshallwshall added a commit that referenced this pull request Aug 7, 2026
…ed (BACKLOG #1073 R1/R2) (#271)

* docs(CLAUDE.md): re-derive every section 12 pointer; 3 of 12 had rotted

Section 12's decline markers exist so a decision stays binding after its backlog
item closes and archives. The mechanism works -- #26 and #27 survive only because
they were lifted here. Their own pointers were the ones that had decayed.

Scanned all 12 pointers in section 12 against origin/main (the primary checkout
runs behind and returns confident false negatives). Re-derived each target rather
than trusting the text.

ROTTED, now fixed -- all three named the live ledger for an archived item:
  #26  visual/template authoring -> docs/archive/backlog/BACKLOG-CLOSED.md
  #27  serial / ASTM             -> docs/archive/backlog/BACKLOG-CLOSED.md
  #222 typed action vocabulary   -> docs/archive/backlog/BACKLOG-CLOSED.md
       (#222 was a bare "BACKLOG #222" with no path, which reads as the live
       ledger; it is closed. Not named in the brief -- found by the sweep.)

LEFT AS-IS, verified to resolve:
  ADR 0037 / 0063 / 0039 paths     -- all three files exist
  ADR 0007 (bare, "see section 1") -- section 1 carries the path, file exists
  ADR 0076 Amendment D             -- exists, 0076-typed-action-...md:658
  BACKLOG #232                     -- genuinely still open in docs/BACKLOG.md
  docs/CONNECTIONS.md              -- still carries the serial decline, :2436
  parse_items, section 11, section 1 -- resolve

Added the ADR 0076 path inline, since Amendment D was cited by bare number only.

Verification: extracted every markdown link target in section 12 and resolved it
(7 unique, all OK), with a deliberately-broken path run through the same checker
to prove it can report a miss.

No gate added. A backlog number resolving to an archived item is not mechanically
distinguishable from one resolving to nothing without encoding the archive's
shape, and a gate that fails on a legitimate archive is one people delete.

NOT included: #1073's marker. It is not on origin/main -- it is in PR #269, still
open, and it already cites the archive correctly. Basing on an unmerged PR head is
the stacking trap, so this branch is cut from origin/main. PR #269 inserts a new
bullet immediately after the #27 bullet this commit edits; the two are
semantically independent but adjacent, so expect a textual conflict and keep both.

* docs: DECIDED -- Code_Quality_Standards.md section 4.1 gets NO back-pointer to #1073

R2 of the #1073 leftovers. The B5 brief left this "optionally" and nobody had
chosen. Deciding it NO, and recording the decision, because an unresolved
"optionally" is indistinguishable from a deliberate omission six months later --
which is exactly how #1073's own unpinned status came about.

Deliberately no file change. The decision is not to add an artifact, so an empty
commit is the whole record.

The case for was real: section 4.1 is the anti-metric rule the ISO 5055 decline
turns on, and a reader wondering whether 4.1 has ever been applied to a live
proposal gets no answer from 4.1 itself.

Rejected because:

1. R1, in this same branch, is the evidence. Three of section 12's twelve
   pointers had rotted, and the two rotted ones named in the brief were #26 and
   #27 -- the very entries cited as proof that lifting a decline into section 12
   makes it outlive its item. The decision survived; the trail back to it did
   not. This estate's demonstrated failure mode is pointers decaying, not
   decisions being unfindable.

2. It would be a fourth copy of the same pointer (the backlog item, the section
   12 marker, PR #269's prose, and 4.1), in a repo already bitten by the install
   procedure in three copies and the reference table in two.

3. It would be born rotted. #1073 is closed by PR #269 and archives on merge, so
   a clause written today naming docs/BACKLOG.md acquires the exact defect R1
   just cleaned up.

4. Section 4.1 is a four-sentence hard rule carrying no worked examples for any
   of the metrics it bans. A #1073 example would be the only one, which reads as
   though 5055 were the rule's primary case rather than one application of it.

Section 12 holds the binding decision, which is what that mechanism is for.
Reopening needs a reason that outweighs the maintenance cost, not just the
observation that the cross-reference is absent.
wshallwshall added a commit that referenced this pull request Aug 7, 2026
…rchives (#272)

* backlog: file #1094, section 12 decline markers rot when their item archives (BACKLOG #1094)

Two changes, both small, both about the same load-bearing property: a decline
lifted into CLAUDE.md section 12 exists to OUTLIVE the backlog item that recorded
it, so the pointer back to that item has to survive archiving too.

#1094 records that it does not. Section 12 cites docs/BACKLOG.md #26 and #27;
retiring an item moves it verbatim into docs/archive/backlog/BACKLOG-CLOSED.md.
Measured on origin/main: both headings are absent from the live file and present
in the archive. Both pointers are already dead, and nothing here can catch the
class -- the markdown link still resolves, because it targets the file, not the
item; only the human-readable number stops being findable, and no tool reads it.
That is the argument for whatever check gets proposed, and it is why two
instances sat unnoticed alongside 44 correctly-formed archive citations in
docs/BACKLOG.md.

Code_Quality_Standards.md section 4.1 gains a POINTER, deliberately not a
restatement. Section 4.1 is one of the three reasons the ISO 5055 decline rests
on, so naming the relationship closes a loop; the reasons themselves stay in
section 12, stated once, per CLAUDE.md section 11. A second copy would be a
second thing to keep in sync, and section 4.1's subject is the anti-metric rule,
not the standard.

Verified rather than assumed:
  - backlog_status_check.py exits 0 on this tree (346 items, 151 live).
  - The green is evidence: planting a second status banner on #1094 makes it
    exit 1 naming line 5846 and item #1094, so the checker does reach the item
    this commit adds.
  - The number came from scripts/coord/alloc.ps1, not from grepping, and no
    other ref carries a #1094 heading.
  - ledger_check.py exits 0.
No pytest or mypy run: this worktree has no .venv, so the local quartet is not
available here and CI is the real check. Docs-only change, no code touched.

One negative result worth recording, because it was nearly filed as a defect.
Running the checker BARE, with docs/BACKLOG.md emptied, prints OK and exits 0
(scanned: 0) -- which looks like the section 4.0 liveness class, a gate that
cannot fail. It is not. ci.yml:148 runs it as `--min-items 300`, and under that
invocation the same empty file gives exit 1: "found 195 backlog items, below the
required floor of 300", naming both scanned paths. The tool's docstring already
says --min-items is the anti-narrowing floor and is not optional in CI, and CI
honours it. The bare CLI is not the deployed gate, and measuring it answered a
narrower question than the one that mattered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(quality): address the 5055 decline by name, not only by section number

The 4.1 clause added in the previous commit cited "../CLAUDE.md section 12" and
nothing else. That is the same rot #1094 is about, one file over: a section
number cannot be re-resolved once it is wrong, and nothing checks one, so a
renumbering leaves the pointer aimed confidently at whatever now occupies the
slot. Pointing nowhere is recoverable; pointing convincingly at the wrong thing
is not.

The clause now names the target three ways -- the "ISO/IEC 5055" string, the
section title "Do / Don't Quick Reference", and the number. Only the number dies
in a renumbering, and either survivor re-resolves it by grep. This mirrors the
form already shipped in the other direction: CLAUDE.md section 12's marker cites
"the anti-metric rule in docs/Code_Quality_Standards.md 4.1", where the NAME is
what makes it survivable.

Measured rather than assumed, because the guidance came with a count taken
before this clause existed:
  - "anti-metric rule": 9 occurrences across the two files (CLAUDE.md 1,
    Code_Quality_Standards.md 8) -- a good label, too common to be an address.
  - "ISO/IEC 5055": 1 in CLAUDE.md on the branch carrying the section 12 marker,
    plus 1 here. So it is TWO post-merge, not the one reported. That is fine and
    is the intended shape -- one occurrence is the pointer, one is the target --
    but the premise "unique" stops being true the moment this clause lands, so it
    is recorded here rather than carried forward as a stale measurement.
  - Section title verified verbatim: "## 12. Do / Don't Quick Reference".

Docs-only, one line changed. No pytest or mypy: no .venv in this worktree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
wshallwshall added a commit that referenced this pull request Aug 7, 2026
)

* docs(research): OpenFlow step-attribute vocabulary gap-map (BACKLOG #238)

A findings note comparing Windmill's seven OpenFlow step-attributes
(retry, timeout, stop_after_if, skip_if, continue_on_error, mock,
cache_ttl) against MessageFoundry's engine/handler vocabulary. Per
attribute: what it is, the engine analogue (grounded in a real
symbol/file), and the gap (covered-different-locus / partial / absent).

This is a review, not a feature. OpenFlow is explicitly NOT a
compatibility target; the note is an informational vocabulary map, not a
gap-to-close list. Adopting a declarative artifact stays declined by
ADR 0076 section 7 and BACKLOG #26. Framing is conditional throughout
(not-deployed beta).

Lands at docs/research/openflow-step-attributes.md, mirroring the
existing docs/research/ review-note convention (config-ux-review.md,
message-model-eval.md, ide-low-code-options.md).

The mock row cites config/db_lookup.py / config/fhir_lookup.py for the
pure-dry-run raise guard (config/db_lookup.py raise DbLookupError /
config/fhir_lookup.py raise FhirLookupError), not config/wiring.py whose
raise text is about the router phase.

* docs(backlog): flip #238 banner to CLOSED (BACKLOG #238)

The findings note (docs/research/openflow-step-attributes.md) is the
item's expected output and is now delivered, so #238's banner flips from
open to closed. One banner line only, under the #238 heading.

Census NOT recomputed: this commit changes only #238's banner line and
does not touch the ranked table or the four census distribution lines.
wshallwshall added a commit that referenced this pull request Aug 7, 2026
…napshot, file #1095 (#276)

Three corrections to the ledger's own accuracy, in one commit because they
cross-reference: #1094 and the ranking note both point at #1095, so splitting
them leaves an intermediate commit citing an item that does not exist yet.

1. #1094 CLOSED as already satisfied when filed; no work performed.

   Its premise is false on origin/main. The repoint it asks for merged as
   befe997 (PR #271) ONE COMMIT BEFORE the item itself landed (7ecff8a, PR
   #272) -- a filing race, not a wrong finding. Re-verified after both: CLAUDE.md
   section 12 now reads "BACKLOG #26 -- closed, so it lives in
   docs/archive/backlog/BACKLOG-CLOSED.md, not in the live ledger", same for #27.

   Banner flipped from the OPEN glyph to a CLOSED one -- replaced, not added, so
   the item still declares exactly one status. The analysis is kept: its point
   that no gate in this repo can catch the class is the argument any future
   check has to answer, and it is now attached to #1095 at true scale.

2. The "Connector & feature-breadth gaps vs. Mirth Connect" section marked a
   historical snapshot.

   All TEN backlog numbers it cites -- #7, #20-#27, #35 -- have closed and moved
   to the archive; none is in this file. So "#7 above" and "#35 below" are false
   directions out of the document, and "P1 -- close first" names work that
   shipped: #20 (FHIR, ADR 0022) and #21 (observability, PR #407). The section
   marks #24 and #35 SHIPPED inline, which makes the unmarked #20/#21 read as
   still open. A reader planning from this picks up finished work.

   Deliberately NOT repointed per-number. Every cited item is archived, so
   attaching an archive path to only the two decline-by-design lines would assert
   by contrast that the other eight are live. Uniform staleness is at least
   detectable; differentiated staleness is not.

3. #1095 filed for the systemic class. Number allocated via
   scripts/coord/alloc.ps1, never grepped.

   Measured on origin/main with parse_items (imported, not re-derived): of 129
   path-bearing BACKLOG.md citations, AT LEAST 69 distinct sites across AT LEAST
   35 files name the live ledger for an archived item. Plus 13 hrefs that do not
   resolve at all, 12 line anchors past EOF (file is 6318 lines; one cites 8429),
   and 31 in-range anchors that drifted onto unrelated text.

   The item's central point is DETECTABILITY, because getting this wrong means
   someone closes it with a linter having fixed a third of it: the 13 broken
   hrefs and 12 past-EOF anchors are catchable, but the 69 wrong-file citations
   and the 31 drifted anchors are NOT -- those links resolve perfectly, and what
   rots is the number or the line beside them.

   It also records that the test is "does the cited FILE contain the item", not
   "is the item CLOSED". Those differ: #1073 is closed and still legitimately in
   the live ledger, so a sweep keyed on closure would corrupt correct citations.

   Prior art found and named rather than duplicated: MIG-35 is already "the
   BACKLOG-reference classifier" folding into MIG-74 in the master test plan
   (:128). The item notes MIG-74 as worded -- "every doc path resolves" -- would
   pass the largest class untouched, since those paths do resolve.

Verification:
  - parse_items diffed before and after: exactly two items changed state, #1094
    (open -> closed) and #1095 (new). No unintended banner churn.
  - backlog_status_check.py: OK, 365 items, each declaring exactly one status.
  - All 7 link targets introduced were resolved from docs/, with a known-missing
    path run through the same checker to prove it can report a miss.
  - The MIG-74 quote was confirmed verbatim in the source file, not paraphrased
    from an agent's summary.
  - Line endings normalized to CRLF to match the file; diff stayed at 50/2
    rather than whole-file churn.

Not included: the ~69-site sweep itself and any gate. Those are #1095's scope,
and a partial repoint is worse than none for the reason given in item 2.
wshallwshall added a commit that referenced this pull request Aug 7, 2026
…251 #252 #257 #260 #273) (#274)

* feat(asvs): prove an absence claim bites, not just that its pattern matches (BACKLOG #1006)

`check_absences` admits an ASVS absence claim on `re.search(a.pattern, a.mutation)`
-- one TOML field matched against another. That proves the mutation is well-formed;
it never proves the mutation BITES. A reintroduction raised into a swallowing
handler, written to a field nobody reads, or behind a flag nobody branches on
satisfies every failure mode `check_absences` has and changes nothing observable.
A green gate that is not evidence.

Add an opt-in `--prove-absences` mode (`scripts/asvs/scorecard.py`) that executes
the claim rather than grepping it:

- Two optional `Absence` fields, `mutation_path` and `observable` (a pytest node
  id). When both are set the mode copies the tree to a scratch dir, runs the
  observable (baseline must be green), appends the mutation, and requires the
  observable to go RED -- and to fail as a test failure (exit 1). It fails closed
  on every other code: an already-red baseline, an uncollectable node, or a
  mutation that only breaks import is a PROVE-ERROR, never a proof. A claim that
  reddens nothing is UNPROVEN and fails the mode.
- A coarse same-file static backstop screens claims carrying `mutation_path` but no
  `observable`: a `raise` landing in a file whose every handler swallows. It is a
  screen, not a proof (it cannot see a swallow in a caller), documented as such.
- The whole pass runs in a TemporaryDirectory scratch copy, so it never mutates the
  tracked tree and never trips the committed-tree scan on itself.

Both fields default empty and load without being refused: the vault's ~81 existing
absence claims carry neither and must stay loadable (ADR 0156 §7). Absent means
"not yet proven by execution", surfaced by the mode, never "proven vacuous".

Review hardening carried in this change (the mode's own helpers):

- `_scratch_ignore` refuses `.env*`, `*.db` (+ WAL sidecars) and `docs/security`
  when copying the tree. The vault runs this module against the REAL tree (ADR 0156
  §7); a scratch copy carrying those would spill secrets / the local store / vault
  posture data into a world-default temp dir, which CLAUDE.md §9 forbids. The
  public-repo path never sees them; this is defence for the eventual vault run.
- `_is_within_tree` refuses a `mutation_path` that is absolute or contains `..`
  before anything is applied, so an authored path cannot escape the scratch copy.

Tests (tests/test_asvs_scorecard.py): eight fixture tests drive `prove_absences`
directly (proved / UNPROVEN / already-red baseline / collection-error / two static
backstop arms including a re-raise reach control / root-untouched / load
round-trip), plus three that drive the CLI contract CI depends on -- `main([...,
"--prove-absences"])` exit 0 on a biting fixture and 1 on a non-biting one, and
`main([...])` without `--corpus` exit 2 -- plus the secrets-exclusion and
path-traversal guards. Every new test was falsified (broken on purpose, watched
red, restored).

MessageFoundry is a not-deployed beta: the mode is opt-in, the default `verify`
path is byte-unchanged, and no authored claim carries an `observable` yet, so
nothing new is blocked by this alone today. Wiring the mode over the vault claims
and backfilling their observables is the owner's follow-up.

* docs(backlog): flip #1006 to shipped -- the absence gate can now prove behaviour (BACKLOG #1006)

Flip the #1006 banner from filed to shipped. It is written as a capability claim,
not a closure claim: the `--prove-absences` mode CAN catch a well-formed-but-vacuous
reintroduction once a claim carries an `observable`, but the default `verify` path
is byte-unchanged and no authored claim carries one yet, so nothing new is blocked
by this alone today -- the honest present-tense state for a not-deployed beta.

Banner lines of #1006 ONLY. The ranked table, the four census distribution lines,
and every other item's banner are untouched. The status census was NOT recomputed.

* test(connscale): dynamic contiguous inbound-port allocation, drop the flaky marker (BACKLOG #1014)

The connscale SQLite smoke test hard-coded base_port=41000 and needs 24
contiguous inbound ports, so two worktrees running the suite at once
contended for the same fixed block; a @pytest.mark.flaky(reruns=2) marker
retried past the collision, relabelling a determinate resource conflict as
CI noise. On the first parallel run it would keep masking exactly this class.

Replace the fixed block with _free_contiguous_ports(), which anchors an
n-wide block at a RANDOM base inside a bounded window, probes each port with
a no-REUSEADDR bind, and returns the range only when all n bind. The random
anchor over a wide window de-correlates concurrent worktrees; a genuine
future collision now surfaces as a red, not a masked retry. Contiguity is
asserted at the acquisition site and the allocator fails loudly -- never a
silent fixed fallback -- via two branches: an up-front width guard when the
block cannot fit the window, and a post-loop raise when no free block is
found after `tries` attempts.

The window is [20000,30000): the lower bound sits ABOVE the sibling MLLP
fixed-port band (other tests bind fixed inbound ports in the 11xxx-19xxx
range, e.g. 15099/19601), and the upper bound stays BELOW the OS ephemeral
floors (Linux 32768+, Windows/macOS 49152+) so a kernel-assigned ephemeral
port -- the sink/API ports, or any unrelated connection -- can never land in
the block after it is probed.

Drop the @pytest.mark.flaky marker: the collision was the cause, so keeping
it would re-hide the class this removes. Add three helper tests -- contiguity
and in-window, post-loop exhaustion (tries=0), and the width guard -- each
pinned to its branch (match=) and falsified by mutation.

Test-only change; no product code is touched.

* backlog: close #1014 -- dynamic connscale port allocation ships, flaky marker dropped

Flip #1014's status banner from open (filed) to shipped: the dynamic
contiguous inbound-port allocation and the flaky-marker removal land in the
same branch (commit 3450c3f).

This edits the #1014 banner line ONLY. The ranked table and the four census
distribution lines are untouched, and the census was NOT recomputed.

* feat(anon): structural PHI-shape detectors + coverage report + token-floor signal on the leak-check (BACKLOG #331)

The fail-closed leak-check verified only that MAPPED fields were pseudonymized;
PHI sitting in a field no rule mapped would pass the check clean on first
deployment (a real MRN is not a denylisted string). Scoped to the fields
anonymize did NOT rewrite, add:

- high-precision structural detectors over unmapped fields: dashed SSN,
  punctuated NANP phone, and CX MR/MRN-typed identifier. Deliberately narrow, to
  avoid the mass false-positives a broad digit-run search produces on HL7 bodies
  dense with dates/order-numbers/set-ids (ADR 0030 section 5).
- LeakReport: an unmapped-field coverage report (addresses only, never a value)
  plus token_tables_live / token_floor_reason. Reasons name the shape + field
  ADDRESS only, so a raised LeakError / log line never carries PHI.
- token_floor_failure() folded into the fail-closed decision under the
  require_live_denylist opt-IN lever (default off, so token-less CI/OSS/fork runs
  stay green with the structural detectors as the live backstop).

The whole structural block is mirrored byte-identical into tee/anon/leak.py; a
new engine/tee leak_report parity test pins it. Each detector was falsified
(removed it, watched the unmapped-PHI dataset slip through, restored); a
false-positive guard proves a benign unmapped field (14-digit EVN timestamp,
order number, coded observation id) does not trip.

Docs are written to the shipped DEFAULT behaviour, not an overclaim: the
coverage report and token-floor reason are RECORDED and surfaced on a refusal or
via the on_report hook (not an unconditional clean-path catch-all), and the
strict refusal is opt-IN. anon/__init__.py (both copies) necessarily changed to
export LeakReport/leak_report/coverage_clause and wire the lever. ADR 0030
section 5 / section 7 / Consequences amended (the "deferred" phrasing was stale
against the shipped code).

NOT-DEPLOYED beta: worded as "would let unmapped PHI through on first
deployment", no present-tense exposure claim.

* docs(backlog): flip #331 banner to SHIPPED, worded to the default behaviour (BACKLOG #331)

Banner-only flip of #331 to SHIPPED. Worded to the shipped DEFAULT behaviour, not
an overclaim: the coverage report and token_floor_reason are RECORDED and surfaced
on a refusal or via the on_report hook (not an unconditional clean-path catch-all),
and require_live_denylist is the strict opt-IN lever (default off).

Census NOT recomputed: only the #331 banner line changed. The ranked table, the
four census distribution lines, and every other item's banner are untouched.

* test(sandbox): a static ast guard pins the codec+worker import boundary (BACKLOG #346)

The sandbox's import boundary (DEFAULT_FORBIDDEN_MODULES in pipeline/sandbox.py --
socket/ssl/asyncio, the I/O- and secret-bearing messagefoundry.* subpackages, cryptography)
is enforced only at RUNTIME and only inside the off-by-default [sandbox].mode=subprocess
child. Nothing statically pins that the two modules which run inside that boundary --
_sandbox_codec.py and _sandbox_worker.py -- do not themselves import a forbidden module. Both
are clean today; a future edit reintroducing a forbidden import would make mode=subprocess DOA
on first deployment while the default-mode suite stayed green -- the failure inverts, hitting
the most security-conscious installs hardest and quietest.

This is defence-in-depth test coverage, not a code change: neither sandbox.py nor the codec is
touched. tests/test_sandbox_import_boundary.py walks the two files' own ast import nodes
(ast.Import/ast.ImportFrom, including nested/function-level and relative imports resolved to
absolute) and asserts none resolves under a DEFAULT_FORBIDDEN_MODULES prefix. The forbidden set
is imported from the runtime constant, never copied, so the guard tracks whatever the sandbox
forbids. It ships with a positive control (each static import form the walker handles is seen,
including the load-bearing from-parent alias-append) and a negative control (benign
messagefoundry.* imports raise zero flags).

Scope is the two files' DIRECT imports, deliberately not a transitive walk: importing the codec
pulls asyncio/cryptography/store/transports/auth into sys.modules, so a transitive walker would
red on clean shipped code and prove nothing. sandbox.py is out of scope per BACKLOG #346 even
though the worker child imports it; the docstring records that residual for the owner.

Falsified: planting `import socket` into the real _sandbox_codec.py reddens the live guard
naming it; removing the walker's alias-append reddens only the alias-append positive-control
case; an over-broad matcher reddens the negative control. All plants restored before commit.

* backlog: flip #346 to SHIPPED -- the static ast import-boundary guard landed (BACKLOG #346)

The #346 banner alone: OPEN -> SHIPPED, pointing at tests/test_sandbox_import_boundary.py
(the static ast guard added in the preceding commit). The completeness wording is softened from
"every forbidden import form is seen" to "each static import form the walker handles" -- a
static walker cannot see dynamic importlib/__import__ forms, and CLAUDE.md section 11 prefers a
bounded claim to an enumeration.

Only the #346 banner line changed; the ranked table, the four census distribution lines, and
every other item's banner are untouched. The census was NOT recomputed.

* fix(tls): route four insecure-TLS escape cells through the ADR-0092 clamp (BACKLOG #329)

LDAPS (auth/ldap.py), SFTP host-key (transports/remotefile.py), the webhook sink
(pipeline/alert_sinks.py) and the AI-broker (transports/ai_broker.py) read the raw
MEFOR_ALLOW_INSECURE_TLS escape directly; on an enforcing-PHI instance each would
otherwise honour the env var on first deployment. Each now routes through the ADR-0092
weakened_tls_escape helper: SFTP is built in-gate so it uses _here(); the other three
are built outside the hop scope, so the instance posture is threaded explicitly through
AuthService / notifier_from_settings / ai_broker_from_settings (additive, default None =
byte-identical for existing callers). The fifth cell the item names (direct.py) was
already clamped in #323, so this converts the remaining four. Docs (CONNECTIONS/DEPLOYMENT/
PHI) corrected from 'not clamped'/'unclamped' to clamped.

* docs(backlog): flip #329 to shipped -- four insecure-TLS cells clamped (BACKLOG #329)

Banner line only (leaves the 2026-08-03 amendment note); census not recomputed.

* fix(dev): anchor setup-leak-gate.ps1 to its own checkout, not the cwd (BACKLOG #1063)

`$repo` came from `git rev-parse --show-toplevel`, which resolves against the CURRENT
directory rather than the path the script was handed. Invoked by absolute `-File` path
from another worktree -- the ordinary shape on a clone carrying dozens of them -- it armed
the CALLER's checkout and printed CONFIGURED about that one, while the checkout the
operator named kept no token source and went on failing closed. An absolute `-File`
invocation is naming the checkout to act on; it must not then consult a different one.

Now `Split-Path -Parent (Split-Path -Parent $PSScriptRoot)`, the form postgres.ps1:37 and
sqlserver.ps1:56 in the same directory already use, plus an assert that the derived root
actually carries scripts/security/ -- a wrong root should say so where it is derived
rather than surface later as a confusing scanner failure.

Tested by the DIVERGENCE, which is the only shape that can fail: two temp checkouts that
both carry scripts/security/, the script invoked by absolute path while the shell stands
in the other one. A test run from inside the target passes with the bug still in, because
cwd and script root are then the same directory. Reverted to the old line, the same test
reports "the named checkout was not armed" -- the negative control was run, not assumed.

The fixture copies only the three files the script reaches for, never the whole of
scripts/security/: a maintainer running this suite has the real token list sitting in that
directory, and a copytree would sweep it into a temp dir.

Also corrects this item's own prose. It called alloc.ps1:51 "byte-equivalent"; it is not
-- alloc.ps1 carries --path-format=absolute and this script does not. The defect is
identical, the bytes are not, and "byte-equivalent" is the kind of claim a later reader
greps for and then trusts.

* fix(coord): anchor alloc.ps1 and claim.ps1 to their own checkout (BACKLOG #1060)

Both took `$repo` from an unanchored `git rev-parse --show-toplevel`, which resolves
against the CURRENT directory rather than the path the script was handed. Invoked by
absolute `-File` path from worktree A while intending to commit from worktree B, the claim
was recorded to A; the ledger gate then refused B's commit -- correctly, it fails closed --
but far from the cause and with a message about the wrong thing, and it cost a number.

`git -C $PSScriptRoot`, not `Split-Path`. The recorded `worktree` value has THREE readers:
ledger_check.py:227, this script's own `-List`, and prune-merged.ps1's orphan-claim release,
whose comment at :787 names the producing command -- "records `worktree = $repo` from
`git rev-parse --path-format=absolute`" -- and matches on the full normalised path because
a false positive there hands a live session's key to someone else. All three fold
separators, so `Split-Path` would not have broken anything; it would have silently
falsified that comment, in a destructive tool, for no gain.

THE FILING NAMED ONE OF FOUR CWD-DERIVED READS, and the other three are measured in the
negative control below:

  * the `branch` recorded with the claim was the CALLER's branch;
  * the floor's boundary was parsed from the CALLER's scripts/hooks/ledger_check.py;
  * the floor's WORKING-TREE term read the CALLER's docs/BACKLOG.md.

The third is not friction and the item's severity paragraph is corrected in the same
commit. That term exists to catch a number written but committed NOWHERE. Reading the
caller's tree makes a number drafted in the target worktree invisible, so the allocator
hands it out as free and two items share it -- both owned by that worktree, so owns()
passes and the ledger gate never fires. The silent collision the docstring says this script
exists to prevent, reached through the script. Narrow, since anything committed on any ref
is still caught by the all-refs term, but a correctness hole rather than friction.

claim.ps1:54 carried the same construct and was never filed -- found by inspection here,
fixed in the same commit. Its enforcing hook, claim_check.py, reads the repo from cwd and
is RIGHT to: a commit hook's cwd IS the committing worktree. Hook right, tool wrong, and
only the tool can be invoked from somewhere else.

Both scripts now print a NOTE when the shell is standing somewhere else. Anchoring is
correct but surprising, and the item's other half -- showing the recorded worktree -- was
already built (`claimed by:` / `by :`); what was missing is saying so when it diverges,
instead of leaving it to surface as a refused commit later. Silent on the ordinary
same-tree invocation, so it stays worth reading.

THE FIX TURNED TWO SANDBOXED TEST FILES INTO WRITERS ON THE LIVE REGISTRY, which is worse
than the red suite it also caused, and is the reason those fixtures changed here.
test_coord_claim_{refresh,liveness}.py ran the REAL scripts/coord/claim.ps1 with cwd set to
a temp repo -- scoped to a throwaway registry purely by ambient cwd, and one of them said so
("it scopes itself to the cwd's repo"). Once the script stopped consulting cwd, the passing
half of the run wrote real claims into this clone's shared registry: two strays, `k` and a
date-shaped key, were created and removed by hand. Both fixtures now stage and COMMIT a copy
of the script inside the temp repo, so the sandbox is structural rather than ambient, and a
linked worktree of the fixture carries its own copy -- which is how the peer-holds-the-key
tests still produce a claim recorded against the peer. test_ledger_check.py already did
exactly this for alloc.ps1, which is why it was the one that did not break.

Tested by the DIVERGENCE, with -ShowFloor so no numbers are burned: allocation is a one-way
door and a test that allocated would leave permanent holes in the shared registry for every
worktree of this clone. Two temp checkouts draft different numbers and carry different
PUBLIC_BACKLOG_FLOOR stubs; the caller's number is deliberately HIGHER, because the floor is
a maximum and an equal or lower one would pass with the bug in. Reverted to the old lines,
the same test reports floor 7777, boundary 1900 and a watermark under Caller/.git -- three
independent signals, all pointing at the wrong tree.

* docs(backlog): record the test-isolation trap #1060's fix walked into (BACKLOG #1060)

A cwd-dependence that reads as a defect in the tool can be load-bearing ISOLATION in its
tests. Both claim test files were scoped to a throwaway registry purely by ambient cwd --
one said so in a docstring -- so anchoring the script turned the passing half of the run
into a writer on this clone's shared registry before the rest of it went red.

Recorded in the item rather than only in the commit message, because #1057 and #1059 are
the remaining instances of the same class and will hit the same trap: check what a test is
isolated BY before changing what the code reads.

* docs(CONNECTIONS): repoint the serial/ASTM decline at the archive; #27 is closed

The connector-parity row for Serial (RS-232) / ASTM E1381/E1394/E1318 cited the
decline as ([BACKLOG.md](BACKLOG.md) #27). Item 27 is closed and lives at
docs/archive/backlog/BACKLOG-CLOSED.md:994; it is not in the live ledger. The
pointer sent a reader to the wrong file.

This is the second half of a designated two-marker pair. The archived item's own
banner names both markers -- "marker landed in PR #411 (CLAUDE.md section 12 +
docs/CONNECTIONS.md Serial row)" -- and commit 8a14602 repointed the CLAUDE.md
half while logging this one as still carrying the decline, because that commit
was scoped to section 12. The two halves disagreed about where #27 lives until
now.

Form: an anchored link, matching the sibling convention already used in this same
directory for this same target (docs/AOAG-DEPLOYMENT.md:389 and :476). The cell
already opens with "declined-by-design (v0.2+)", so the citation's only job is to
resolve; restating "closed" in the cell would duplicate a fact the cell asserts
two clauses earlier.

Relative path: (archive/backlog/BACKLOG-CLOSED.md), NOT (docs/archive/...). The
link is repo-relative from inside docs/. CLAUDE.md is at the repo root and
correctly uses the docs/-prefixed form; copying that form here would resolve to
docs/docs/archive/... and 404.

Verified, not assumed:
  - The anchor slug was derived by a rule first replayed against three anchors
    already committed in the repo (#100, #101, #52) -- 3 of 3 exact -- then
    applied to #27's heading, then confirmed to match exactly one real "## "
    heading in the target file. A bogus anchor was run through the same check
    and found nothing, so the check can report a miss.
  - Item locations come from parse_items imported from
    scripts/docs/backlog_status_check.py, per CLAUDE.md section 11 -- not a
    hand-rolled scan of the banner alphabet.
  - backlog_status_check.py still reports 363 items, unchanged.
  - Read from origin/main throughout; the primary checkout runs behind.

NOT changed, deliberately, with the reason:
  - docs/BACKLOG.md:574 -- bare number inside the section headed "Value &
    priority analysis (recorded 2026-06-19) - superseded". A superseded snapshot
    is a historical record; it has no path to rot.
  - docs/testing/FEATURE-COVERAGE-PLAN.md:41 -- names the features in prose and
    carries no number or path at all. Nothing to rot; adding a pointer would be
    new scope, not a repair.
  - docs/testing/master-test-plan/00-strategy-and-governance.md:699 -- bare
    #26/#27 that resolve to nothing rather than to wrong content. Repairing one
    link here would leave a single correct relative link among 28 broken
    root-relative ones in the same file; it belongs in the doc-set-wide sweep
    that class needs.
  - docs/BACKLOG.md:906 -- a real defect, but larger than a pointer repair and
    in a file several sessions are editing. Reported separately for a decision.

A wider scan (127 path-bearing BACKLOG citations) found roughly 90 more naming
the live ledger for an archived item. Not touched here: the staleness is
currently uniform, and repointing a subset would assert by contrast that the
untouched siblings are live. That class needs one pass, not a trickle.
wshallwshall added a commit that referenced this pull request Aug 7, 2026
…256 #259 #264) (#275)

* feat(api): report-only TLS key-exchange groups posture field; correct stale "pinned" doc claims (BACKLOG #338)

The engine's TLS key-exchange (KEX) groups are INHERITED from OpenSSL's
default group list, not pinned to the approved set. harden_kex_groups pins
nothing until SSLContext.set_groups lands in Python 3.15, so on every
interpreter this project currently runs on the approved pin is inert. This
is documentation accuracy plus observability -- it changes no live TLS
behaviour (the TLS 1.2+ floor is the enforced control), and on a
NOT-DEPLOYED beta there is no exposure today; the pin is a future 3.15
hardening.

Two parts:

1. Report-only surfacing. New pure helper config/tls_policy.kex_groups_report()
   builds a throwaway probe context and asks the ONE authority,
   harden_kex_groups, what it manages to pin -- so the read-out can never
   drift from what the connectors actually do. It returns "inherited (...)"
   on a pre-3.15 interpreter and "pinned: ..." on 3.15+. Surfaced as an
   additive SecurityPosture.kex_groups field (str | None, default None),
   wired in create_app beside fips_attestation(), rendered as a status-page
   row in the web console beside the FIPS/OpenSSL rows. Report-only: it
   reflects, and changes, no TLS behaviour.

2. Three doc-accuracy edits correcting restatements that still read as
   "pinned": CONTAINER-EXPOSURE-EVALUATION.md (verification table),
   ASVS-L2-PHASE0-CHANGES.md (PQC roadmap row), and #200's Closes line in
   docs/archive/backlog/BACKLOG-CLOSED.md (11.6.2 annotated PARTIAL). Each
   links to PHI.md's data-in-transit section, the single source of record
   for the measured accepted set, rather than restating it.

The two Python-3.15 tripwire tests in test_tls_policy.py that fire when
set_groups/get_groups land are left untouched -- they are the signal to
actually set the pin.

Engine UI seam bumped 17 -> 18: the golden seam snapshot introspects
SecurityPosture's field set, so a purely additive field trips the
handshake; SUPPORTED_ENGINE_SEAMS and the golden snapshot updated to match.

Tests: test_tls_policy.test_kex_groups_report_reports_inherited_today
(the helper reports inherited + names the approved list, never "pinned:");
test_api_auth.test_security_posture_reports_kex_groups (the field flows
through the MONITORING_READ-gated, audited posture route and matches the
helper); a status-builder assertion that the console renders the row. All
three falsified: blanking the helper reddens the two report tests
(assert 'inherited' in ''); removing the console row reddens the
status-builder test (assert 'key-exchange' in html).

* docs(backlog): flip #338 banner to shipped (BACKLOG #338)

Flip the #338 status banner from filed/not-started to shipped, now that the
report-only kex_groups posture field and the three doc-accuracy corrections
have landed. The banner records that the KEX groups are documented as
inherited (the pin is inert until Python 3.15) plus the report-only
surfacing behind engine seam v18.

Banner line only, under the #338 heading, verified by number. The ranked
table, the four census distribution lines, and every other item's banner
are untouched. The census was NOT recomputed.

* docs(research): OpenFlow step-attribute vocabulary gap-map (BACKLOG #238)

A findings note comparing Windmill's seven OpenFlow step-attributes
(retry, timeout, stop_after_if, skip_if, continue_on_error, mock,
cache_ttl) against MessageFoundry's engine/handler vocabulary. Per
attribute: what it is, the engine analogue (grounded in a real
symbol/file), and the gap (covered-different-locus / partial / absent).

This is a review, not a feature. OpenFlow is explicitly NOT a
compatibility target; the note is an informational vocabulary map, not a
gap-to-close list. Adopting a declarative artifact stays declined by
ADR 0076 section 7 and BACKLOG #26. Framing is conditional throughout
(not-deployed beta).

Lands at docs/research/openflow-step-attributes.md, mirroring the
existing docs/research/ review-note convention (config-ux-review.md,
message-model-eval.md, ide-low-code-options.md).

The mock row cites config/db_lookup.py / config/fhir_lookup.py for the
pure-dry-run raise guard (config/db_lookup.py raise DbLookupError /
config/fhir_lookup.py raise FhirLookupError), not config/wiring.py whose
raise text is about the router phase.

* docs(backlog): flip #238 banner to CLOSED (BACKLOG #238)

The findings note (docs/research/openflow-step-attributes.md) is the
item's expected output and is now delivered, so #238's banner flips from
open to closed. One banner line only, under the #238 heading.

Census NOT recomputed: this commit changes only #238's banner line and
does not touch the ranked table or the four census distribution lines.

* fix(serve): auth-off startup arm now refuses a declared terminator (BACKLOG #1013)

The `[auth] enabled=false` startup arm keyed on the bind alone
(`not settings.api.is_loopback`), so it did not fire for a loopback bind
behind a declared upstream TLS terminator. A PHI instance with
authentication entirely off behind a declared terminator would have
started with no refusal and no warning on first deployment, while the same
topology with auth on but MFA off is already refused by the gate #326
fixed. The two arms disagreed about what "exposed" means in the same file.

Hoist the single `instance_exposed` definition (#326: an off-loopback bind
OR a declared upstream TLS terminator) above the auth-off arm and widen the
arm to consult it, so it refuses on a non-loopback bind OR a declared
terminator. The existing loopback refusal is kept; the condition is
widened, not replaced.

Load order verified: `instance_exposed`'s inputs -- `settings.api.host`
(through `is_loopback`) and `settings.api.tls_terminated_upstream` -- are
read straight off the loaded config, and the only in-place mutation of
`settings.api.*` between the hoisted definition and the former site is
`serve_ui` (twice), which the predicate does not read. The definition
remains defined exactly once.

Tests (tests/test_cli.py): auth off + declared terminator on a loopback
bind refuses (positive); auth off + true loopback with no terminator still
starts (negative control); auth on + declared terminator is unaffected by
the arm. Each was falsified -- reverting the arm to the bare bind check
reds the positive test, firing on any auth-off reds the negative control,
and dropping the auth check reds the auth-on test; each was restored.

Docs updated so the contract travels with the code (CLAUDE.md 11):
DEPLOYMENT.md, SYSTEM-REQUIREMENTS.md, SECURITY.md, REMOTE-CONSOLE.md and
SECURITY-LOOSENING.md now describe the auth-off refusal as firing on an
exposed instance (a non-loopback bind OR a declared terminator), not on the
bind alone.

BACKLOG #1013

* docs(backlog): mark #1013 fixed; record the resolved load-order question (BACKLOG #1013)

Flip the #1013 banner from filed/open to fixed and record, in the AMENDED
blockquote, that the load-order prerequisite the item flagged as unproven
holds: `instance_exposed`'s inputs resolve where the auth-off arm runs.
Name the single-definition pointer comment rather than pin its line number,
since the hoist shifts that line.

Banner flip only: the ranked table and the four census distribution lines
were NOT recomputed.

BACKLOG #1013

* docs(coord): the session record has no branch, and two rosters disagree about one

A session was told a coordinator "might not be there" on the strength of the
session-list MCP tool's `isRunning: false`, and separately the two rosters
reported different branches for one checkout. Both readings were wrong the same
way -- a field answering a question adjacent to the one asked -- and neither trap
was written down anywhere a reader would look.

MEASURED 2026-08-06. A session record holds exactly cwd, entrypoint, kind, name,
nameSource, peerProtocol, pid, procStart, sessionId, startedAt, version. There is
NO branch field. So any branch printed beside a session came from elsewhere, and
the two sources answer different questions while both being labelled "branch":

    presence.ps1 / occupancy.ps1   the WORKTREE's branch, live from
                                   `git worktree list --porcelain`. Current.
    session-list MCP tool          a SESSION attribute captured at registration.
                                   Does not follow a later `git switch`.

For one checkout they reported two different names -- the live roster the branch
that checkout had been switched onto, the session list the one it registered
with. Neither was wrong. A disagreement is not evidence that either roster is
broken, and a branch from the session list must never be quoted as a checkout's
current branch.

ALSO RECORDED, same family: `isRunning` means "currently EXECUTING A TURN", not
"alive". An idle session between turns reads false while being perfectly
reachable. It is not a liveness fence and must not be used as one --
Get-SessionLiveness is, subject to the rule already stated directly above it that
ONLY THE POSITIVE ANSWER IS SAFE TO ACT ON. That rule is why the original
inference was doubly wrong: it drew a negative conclusion from a signal that
cannot support one, using a field that answers a different question.

The concrete branch names are deliberately NOT quoted -- the leak gate refused
the first attempt because a real worktree slug is an internal project name, and
the lesson does not need them.

Documentation only; no behaviour change. Both files parse, presence.ps1 still
runs, 272 tests pass across the coord/presence/occupancy suites.

* docs(supply-chain): correct at least two claims the shipped v0.3.2 release assets do not support

Verified against the actual release assets, not the prose: `gh release download v0.3.2` gives an
SBOM with licenses on 40/40 components and hashes on 0/40, and a VEX with `"statements": []`.

Two claims did not survive that check.

VEX contents. `docs/SUPPLY-CHAIN.md` described the OpenVEX asset as "our per-CVE exploitability
assessments" and told the reader it "records, per CVE, whether the vulnerable code is reachable" --
a statement about the contents of a published artifact, false in any tense. It sat immediately after
"Do not demand a zero-CVE clean scan", so a procurement reader who applied the VEX, saw no
suppressions, and read that as an assessed all-clear would have had no assessment behind it: a
compensating control resting on a false premise (CLAUDE.md section 11). The page now names the
artifact, says what a statement carries when one exists, and says plainly that where we have not
assessed a CVE the document is silent and the scanner's finding stands. The empty-state rule stays
stated once, at security/vex/README.md:17-18 and ADR 0149, and is linked rather than re-copied --
the removed sentence was itself the divergent third copy.

Component hashes. The inventory sentence enumerated "components, versions, PackageURLs, hashes, and
licenses". Backfilling hashes was investigated and rejected on semantics rather than effort:
CycloneDX `component.hashes` means the hash of THE file, while requirements-core.lock carries 301
`--hash=sha256:` lines over 41 packages and no package with exactly one (cryptography alone has 40).
cyclonedx-py deliberately routes lock hashes to `externalReferences` for that reason, the 1.6 schema
imposes no uniqueness constraint so a multi-entry set would validate clean as a silent false claim,
and `pip` is an inventoried component with no lock line at all. So the sentence is corrected instead:
"at least" replaces the closed enumeration, the reason given is the verified one, and no substitute
integrity control is offered -- Sigstore and SLSA attest the SBOM document and our own release files,
which is an adjacent question, and the lock is not a released artifact.

The same false enumeration sat in scripts/security/sbom_finalize.py's docstring, one hop from the
corrected page, which names that script by path in its "for auditors" section. Fixed there too
rather than leaving the repo self-contradictory on the fact this commit is about.

Nothing is deployed, so nobody has been misled; the defect is that the shipped page WOULD mislead a
first reader who tried to verify components against hashes the SBOM does not carry.

Deliberately unchanged: the true "hash-locked" phrases at :16 and :86 refer to the lock the inventory
is built from, and a blanket scrub of the word would have deleted accurate claims -- the new text
disambiguates them instead. No VEX statement is written here; see the notes handed to the coordinator.
wshallwshall added a commit that referenced this pull request Aug 7, 2026
Retiring a backlog item moves it verbatim from docs/BACKLOG.md into
docs/archive/backlog/BACKLOG-CLOSED.md. Every citation that named the live file
keeps pointing at a file the item is no longer in. The link still resolves, so
nothing in CI can see it. #1094 fixed two such markers in CLAUDE.md section 12;
this is the same defect at repo scale.

73 citations across 34 files, href-only. No prose was rewritten. Visible labels
changed ONLY where leaving them would contradict the target -- a label reading
`BACKLOG.md` pointing at the archive -- and then only to `BACKLOG-CLOSED.md`.

THE TEST IS "DOES THE CITED FILE CONTAIN THE ITEM", NOT "IS THE ITEM CLOSED".
Those differ, and keying on closure would corrupt correct citations: #1073 is
closed and still legitimately in the live ledger. Item locations came from
parse_items imported from scripts/docs/backlog_status_check.py, per CLAUDE.md
section 11 -- never a hand-rolled scan of the banner alphabet.

DELIBERATELY NOT TOUCHED, each for a stated reason:

  Both ledger files -- ZERO edits to docs/BACKLOG.md and BACKLOG-CLOSED.md.
    Only two sites named them and both are excluded, so this change costs no
    conflict against the merge trains or the pending #1096 filing. The one real
    site (#322 at BACKLOG.md:2720) is left because the file is the most
    contended in the repo and the item number is visible in plain text a search
    away.

  docs/CONNECTIONS.md:2436 -- the #27 serial/ASTM row. Already fixed on a branch
    inside merge train #274. Sweeping it from origin/main would re-fix stale
    text and collide.

  QUOTATIONS OF THE DEFECT. docs/BACKLOG.md:6319, inside #1094, reads "Two of
    its markers CITED [`docs/BACKLOG.md`](BACKLOG.md) #26 and #27" -- past
    tense, describing rot that is already fixed. Repointing it would corrupt a
    historical record. A regex cannot tell this from a live pointer, which is
    the reason this was not done with sed.

  THE WRONG-NUMBER CLASS, which is a different defect and must not be swept into
    this one. ADR 0068:10 cites #11 and ADR 0113:9 cites #239; both numbers are
    absent from the live ledger, but the ARCHIVE's #11 ("`check` dry-run
    cross-products") and #239 ("Re-measure Steps view estate coverage") are
    unrelated to WebAuthn passkeys and to a Windows tray manager respectively.
    Repointing would convert a vague reference into a confidently wrong one that
    lands the reader on the wrong item. Left, and reported.

  MIXED-LOCATION LINKS, where one link covers items in both files so no single
    target is correct: docs/AI-OFF-MATRIX.md:50 (six items), docs/adr/0001:13
    (#1 archived, #3 live), THROUGHPUT-IMPROVEMENTS.md:215 (#62 live, so its
    link is already correct).

VERIFICATION
  - Plan applied by literal replacement on the named line only, requiring the
    quoted string to occur EXACTLY ONCE there; a mismatch aborts rather than
    fuzzy-matching. Dry run: 73/73 clean, 0 problems, before anything was
    written.
  - All 35 distinct anchor fragments introduced match exactly one real "## N."
    heading in the archive, checked after applying, with a known-bad fragment
    run through the same check to prove it can report a miss. Fragments were
    derived with a slugger that does NOT collapse consecutive spaces -- the
    doubled hyphens are correct, not typos.
  - All 74 archive hrefs in the changed files resolve to the archive from their
    own directory depth; the relative prefix differs by depth and was computed
    per file, not pattern-matched.
  - Coverage confirmed with a DELIBERATELY LOOSER regex than the one that built
    the work list: it finds exactly one wrong-file site outside this change set,
    docs/CONNECTIONS.md:2436, which is the intended exclusion.
  - backlog_status_check.py: OK, 365 items. No mixed line endings introduced.
    All 34 changed files are markdown; diff is 67 insertions / 67 deletions,
    line-for-line.
  - Staged by explicit path from the plan, cross-checked against git's modified
    set, so nothing another session is editing was swept in.

Not included: the broken-href class (13 sites, mostly (docs/BACKLOG.md) written
from inside docs/testing/master-test-plan/), the 12 line anchors past EOF, and
the 31 in-range anchors that drifted onto unrelated text. Those are separate
classes under #1095 and are catchable by a link checker, which this repo still
does not run.
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