Skip to content

fix(release): releases were disabled by the mirror-era guard; plus the leak-gate bootstrap (F.1, C.3) - #7

Merged
wshallwshall merged 3 commits into
mainfrom
leak-gate-bootstrap
Jul 27, 2026
Merged

fix(release): releases were disabled by the mirror-era guard; plus the leak-gate bootstrap (F.1, C.3)#7
wshallwshall merged 3 commits into
mainfrom
leak-gate-bootstrap

Conversation

@wshallwshall

@wshallwshall wshallwshall commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Two cutover-completion items. Read the first one — it means no release could ship.


1. Releases were disabled entirely (runbook F.1)

Both jobs in release.yml were gated:

if: github.repository != 'MEFORORG/MessageFoundry'

Correct when this repo was the published mirror — the mirror must never release, and publish.ps1 rewrote the private slug to the public one across *.yml, so != public-slug was the only rewrite-proof form.

Both premises died at the cutover. publish.ps1 is retired, and MEFORORG is now the source. Left inverted, the guard skips the release on the only repo that can publish — PyPI Trusted Publishing is bound to MEFORORG/MessageFoundry + release.yml (see the release.yml header, and F.1's owner prereq). The job guard and the OIDC binding pointed in opposite directions: a pushed v* tag would have silently skipped and shipped nothing.

Nobody noticed because no tag has been cut since the cutover. F.1 — "validate the release under the new OIDC binding" — is precisely the step that would have caught it.

Every other slug guard (ci.yml, codeql.yml, quality-advisory.yml, scorecard.yml) was already flipped to == at the cutover. release.yml was missed. == is also the safe form now: a fork, or the retired vault, still cannot release.

The test was pinning the bug

tests/test_release_pipeline.py asserted the inverted guard appeared at least twice, with a comment explaining the publish.ps1 rewrite. It was right for the old topology and outlived it — so the broken state looked deliberate and was enforced. Rewritten to assert the post-cutover invariant, reject the old form explicitly, and state plainly that it used to assert the opposite. Mutation-verified: restoring the inversion fails it.

F.1 is still open. Cutting vX.Y.Z-rc1 publishes to production PyPI — an owner action, not something to slip into a PR.


2. The leak-gate bootstrap (runbook C.3)

forbidden-content passes --require-tokens and fails closed — correct, since without it the gate loads zero detectors and passes every commit green. The flag stays. But the token list is git-ignored, so it doesn't travel with a clone or git worktree add, and the setup step was documented only inside a file you had to already know to open. CONTRIBUTING.md mentioned neither pre-commit nor the token file.

Outside contributors were permanently blocked — they can never have the real list, making the hook stricter than both CI (which branches to structural-only for fork PRs) and the scanner's own default. The supported answer is to copy the synthetic template.

Which creates the hazard this actually closes

The synthetic example populates every section, so it clears the count floor while matching nothing real. A maintainer who copied it instead of installing the real list gets a green gate that is blind — and no count could tell. So the scanner now recognises its own template and labels the run:

loaded names=7, estate=13, estate_file_scanned=12, site_prefixes=1
loaded names=5, estate=3,  ...  [SYNTHETIC EXAMPLE TOKENS — blind to real customer tokens; CI is authoritative]
loaded names=0, estate=0,  ...  [STRUCTURAL-ONLY: no token source configured]

Three states that exit 0 collapses into one. Only the first is evidence. Compared on parsed content, not bytes, so adding a comment can't silently reclassify a synthetic set as real — the direction that fails open.

File Change
scripts/dev/setup-leak-gate.ps1 new. -From <path> (maintainers) / -Synthetic (contributors). Always ends by running the scanner and printing per-section counts, exiting non-zero if nothing loaded. Verifies the destination is git-ignored, deleting it rather than risk committing the token list.
CONTRIBUTING.md A Leak gate section: why it fails closed, both paths, how to read the three modes.
scripts/worktree/new.ps1 Carries the token file into a new worktree, or says plainly that commits will fail closed.
scan-tokens.local.txt.example States it's a supported contributor setup, that it's labelled on every run, and that its counts are below CI's floor — so passing locally doesn't predict CI.

Four tests, mutation-verified; the negative controls matter because "no source" and "the example" are different failures with different fixes.

C.3's ordering hazard is separately resolved and I've marked it done in the runbook: install-git-hooks.ps1 no longer writes .git/hooks/pre-commit at all (PR #4 moved the ledger gate into .pre-commit-config.yaml), so it can't collide with pre-commit install and order no longer matters.


Not fixed here, deliberately

The pre-commit ruff/bandit hooks scan directories CI does not — scripts/ (1 ruff finding), harness/ (74), tee/ (12), plus pre-existing bandit findings. Touching any file there blocks your commit on errors you didn't cause, invisibly to CI. I started on the one-line UP017 in vuln_metrics.py and reverted it: editing that file pulled in four unrelated bandit findings to annotate. That needs one decision — clean the dirs and add them to CI's scope, or narrow the hooks to match CI — not a drive-by under a release fix.

🤖 Generated with Claude Code

…synthetic token set announce itself

The forbidden-content hook passes --require-tokens and fails closed, which is correct: without it the
gate loads zero detectors in a fresh checkout and passes every commit green. But the token list is
git-ignored, so it does NOT travel with a clone or a `git worktree add`, and the setup step was
documented only inside a file you had to already know to open. Every new worktree therefore hit a hard
commit block with no documented way out -- reported from a parallel session that could not commit a
one-line docs edit. The enforcement was never the defect; the missing bootstrap was.

CONTRIBUTING.md said nothing about pre-commit or the token file at all. It does now, including the
part that matters: an OUTSIDE CONTRIBUTOR cannot ever have the real list, so the hook as shipped
blocked them permanently. That was stricter than both CI (which branches to structural-only for fork
PRs) and the scanner's own documented default. The supported answer is to copy the synthetic template.

Which creates the real hazard this change exists to close. The synthetic example POPULATES every
section, so it clears the count floor while matching nothing real -- a maintainer who copied it
instead of installing the real list would get a green gate that is blind, and no count could tell.
The scanner now compares the loaded set against the committed example (on PARSED content, not bytes,
so a reformatted copy still reads synthetic) and labels the run:

    loaded names=7, estate=13, estate_file_scanned=12, site_prefixes=1
    loaded names=5, estate=3, ...  [SYNTHETIC EXAMPLE TOKENS - blind to real customer tokens; CI is authoritative]
    loaded names=0, estate=0, ...  [STRUCTURAL-ONLY: no token source configured]

Three states that exit 0 collapses into one. Only the first is evidence.

  * scripts/dev/setup-leak-gate.ps1 -- -From <path> (maintainers) or -Synthetic (contributors). It
    always finishes by RUNNING the scanner and printing per-section counts, and exits non-zero if
    nothing loaded: a green gate is evidence only once you have confirmed it can see. It also verifies
    the destination is git-ignored and deletes the file rather than risk committing the token list.
  * scripts/worktree/new.ps1 -- carries the token file into a new worktree, or says plainly that
    commits will fail closed. The symptom otherwise never mentions worktrees.
  * scan-tokens.local.txt.example -- states that using it as-is is a supported contributor setup, that
    it is labelled on every run, and that its counts are BELOW CI's per-section floor, so passing
    locally with it does not predict CI.

Four tests, mutation-verified: forcing the predicate False fails exactly the two positive cases while
the negative controls (a real-shaped set, and no source at all) still hold -- "absent" and "synthetic"
are different failures with different fixes and must not be conflated.

Not changed, deliberately: --require-tokens stays. pre-commit has no per-hook env, so the flag is the
only mechanism, and relaxing it restores the blind-green failure it was added to stop.
…ra guard disabled releases entirely

Both jobs in release.yml were gated `if: github.repository != 'MEFORORG/MessageFoundry'`. That was
right when this repo was the published MIRROR: the mirror had to never release, and publish.ps1
rewrote the private slug to the public one across *.yml, so `!= public-slug` was the only rewrite-proof
form.

Both premises died at the cutover. publish.ps1 is retired (nothing rewrites anything), and MEFORORG is
now the SOURCE repo. Left inverted, the guard skips the release on the ONLY repo that can publish --
PyPI Trusted Publishing is bound to MEFORORG/MessageFoundry + release.yml (release.yml header, and the
runbook's F.1 prereq). The job guard and the OIDC binding pointed in opposite directions, so a pushed
`v*` tag would have silently SKIPPED and shipped nothing. Nobody had noticed because no tag has been
cut since the cutover -- F.1 ("validate the release under the new OIDC binding") is exactly the step
that would have found it, and it is still open.

Every other slug guard in .github/workflows -- ci.yml, codeql.yml, quality-advisory.yml, scorecard.yml
-- was already flipped to `== 'MEFORORG/MessageFoundry'` at the cutover. release.yml was missed. `==`
is also the safe form now: a fork, or the retired private vault, still cannot release.

THE TEST WAS PINNING THE BUG. tests/test_release_pipeline.py asserted the inverted guard appeared at
least twice, with a comment explaining the publish.ps1 rewrite. It was correct for the old topology and
survived it, so the broken state looked deliberate and enforced. Rewritten to assert the post-cutover
invariant, to reject the old form explicitly, and to say plainly that it used to assert the opposite --
a test can outlive the premise it encodes, and this one did. Mutation-verified: restoring the inversion
fails it.

NOT fixed here, deliberately: the pre-commit ruff/bandit hooks scan directories CI does not
(`scripts/` 1 ruff finding, `harness/` 74, `tee/` 12; plus pre-existing bandit findings in scripts/).
So touching any file there blocks your commit on errors you did not cause, invisibly to CI. I started
on the one-line UP017 in scripts/security/vuln_metrics.py and reverted it: editing that file pulled in
four unrelated bandit findings to annotate, which is scope creep on a release fix and would bury it.
It needs one decision -- clean the dirs and add them to CI's ruff/bandit scope, or narrow the hooks to
match CI -- not a drive-by.
@wshallwshall wshallwshall changed the title fix(security): document the leak-gate bootstrap; make a synthetic token set announce itself fix(release): releases were disabled by the mirror-era guard; plus the leak-gate bootstrap (F.1, C.3) Jul 27, 2026
@wshallwshall
wshallwshall enabled auto-merge (squash) July 27, 2026 14:35
wshallwshall added a commit that referenced this pull request Jul 27, 2026
…code before matching (#8)

tests/test_store_aad_binding.py asserted `"DOE" not in on_disk` against an mfenc cell. The cell's
payload is base64 of a random nonce + AES-GCM ciphertext, so matching a 3-character needle against
that TEXT is a coin flip: measured ~0.12% of runs at this payload size, about 1 in 850. It red-X'd
PR #7, whose diff touched release.yml and the leak-gate docs and nothing within reach of the store.

The flake is the smaller half. The same check is also BLIND to the thing it exists to catch. Handed a
cell that base64-encodes the plaintext verbatim, `"DOE" not in on_disk` evaluates TRUE -- "no
plaintext here" -- because base64 of "DOE^JANE" contains no literal "DOE". So it fired at random on
safe data and would have passed a real PHI-at-rest leak. Demonstrated both directions in the commit's
test run.

And the obvious fix is a trap. Reaching for a longer, more distinctive needle -- "DOE^JANE" -- makes
it VACUOUS: '^' is not in the base64 alphabet, so the assertion could never match, would pass
unconditionally, and would look stronger than what it replaced.

So: decode first, then match the FULL plaintext against the at-rest BYTES. A false positive now needs
a collision across the whole message rather than three characters, and a cell carrying its plaintext
fails loudly.

`test_the_plaintext_probe_can_actually_fail` pins the non-vacuity directly: it feeds the probe a
plaintext-bearing cell and asserts the probe says so. Without it the round-trip test would pass no
matter what the store wrote at rest -- which is exactly the state this repo keeps rediscovering.

NOT changed, because not every short needle is wrong:
  * tests/test_support_bundle.py:81 (`"2575" not in blob`) matches PLAINTEXT json.dumps output. It is a
    deterministic redaction check and a short needle is exactly right there.
  * tests/test_uploads.py:52 (`"MRN123" not in blob`) is the same ciphertext shape as this one, but a
    6-character needle is ~400,000x safer (~3e-7 %). Left alone rather than churn it; the pattern to
    avoid is a SHORT needle against ciphertext, not the length itself.
@wshallwshall
wshallwshall merged commit 5ecaf0c into main Jul 27, 2026
32 checks passed
wshallwshall added a commit that referenced this pull request Jul 27, 2026
…olation (#10)

Publishing docs/BACKLOG.md (#6) broke the ledger gate on every branch cut before that merge, including
PR #7 -- whose diff touched release.yml and leak-gate docs and nothing near a ledger:

    OSError: git show HEAD:docs/BACKLOG.md failed (128): fatal: path 'docs/BACKLOG.md' does not exist
    in 'HEAD'

CI's change set is `diff base HEAD`. Once origin/main gained the file, a branch that predates it lists
docs/BACKLOG.md as CHANGED -- as a deletion relative to base -- despite never touching it. check_backlog
then read HEAD for a copy that was never there.

`base_has` (added when the file was imported) covers the opposite end: absent on BASE, being added.
This is the mirror case -- present on base, absent on HEAD -- and it is the one that fires repeatedly,
because it hits every open branch at once rather than the single commit that publishes the file. Both
probes now share `_obj_exists`, which asks `git cat-file -e` directly rather than inferring absence
from an exception: `git()` must keep raising on real failures, since an error swallowed as "empty
ledger" reads as "no numbers taken" -- the false-clean this gate exists to prevent. "The path is not on
that ref" is the one non-failure, so it gets its own probe instead of a broad `except`.

`head_has` mirrors `head_text`'s ref exactly (HEAD in CI, the INDEX locally), so the guard cannot
disagree with the read it guards.

Mutation-verified: neutralise the guard and the new test fails with the same exit-128 shape seen in CI.

Worth noting for whoever meets this next: the failure looked like an unrelated flake on somebody else's
PR, and the first re-run "fixed" nothing -- the run before it had failed for a genuinely different,
genuinely random reason (a 3-char needle colliding with base64 ciphertext, fixed separately). Two
different faults on consecutive attempts of the same leg is exactly the shape that gets misread as one
flaky test.
wshallwshall added a commit that referenced this pull request Jul 27, 2026
…ewrite, compare versions not strings (#12)

Two defects, either of which fails a tag push on its own. Runbook F.1 ("validate the release under the
new OIDC binding") could not be performed until both were fixed.

1. THE README SLUG REWRITE FAILED UNCONDITIONALLY. publish.ps1 used to rewrite the private source slug
   to the public mirror's before `build` embedded README.md as the PyPI long_description. That rewrite
   had been applied to this workflow itself, so at the cutover both sides of the substitution collapsed
   to the same string: the sed replaced the public slug with itself (a no-op) and the guard after it
   failed if that slug appeared in the README -- which it does, 19 times. Every tag push died there.
   That is not hypothetical: the v0.3.0 tag (2026-07-13) failed at this exact step, which is why the
   repo has ZERO published releases. Removed outright -- there is one repo now and README.md already
   names it, so there is nothing to rewrite. Same root cause as the release job's inverted `if:` guard
   fixed in #7: mirror-era logic the slug rewrite mangled and the cutover left behind.

2. TAG-VS-BUILT COMPARED AS STRINGS, IN BOTH JOBS. The trigger only fires on `vX.Y.Z` / `vX.Y.Z-*`, so
   a pre-release tag must be spelled with a hyphen -- while hatchling and PyPI normalise `0.3.0-rc1` to
   `0.3.0rc1`. A raw compare therefore forced __version__ to carry the non-canonical "0.3.0-rc1" purely
   to satisfy the gate, and the tag, the module attribute and the wheel filename could not all be
   canonical at once. In the HARNESS job it was worse than awkward: `built` is parsed out of the
   already-normalised wheel FILENAME, so the compare could NEVER match a pre-release tag -- and with
   PUBLISH_HARNESS=true that job runs AFTER the engine has uploaded, so it would fail half-published.
   Both now compare packaging.Version objects; a genuine mismatch still fails loudly.

Verified by running the real comparison: 0.3.0rc1 vs tag v0.3.0-rc1 matches; 0.3.0-rc1 matches; 0.3.0
vs v0.3.0-rc1 does not; 0.3.1 vs v0.3.0 does not.

Tests pin both, mutation-verified: restoring the slug step fails, and restoring the string compare
fails. The existing "version==tag comparison" canary tracked the raw shell test by its exact spelling,
so it is retargeted at the CHECK rather than its wording.

STILL OWNER-ONLY for F.1, unchanged by this commit:
  * a `messagefoundry-harness` PyPI Trusted Publisher (PUBLISH_HARNESS is already true, so the harness
    job WILL attempt an upload on the next tag);
  * setting __version__ to the rc being cut -- canonical `0.3.0rc1` now works, tagged `v0.3.0-rc1`;
  * the tag itself, which publishes to PRODUCTION PyPI (there is no TestPyPI route).
@wshallwshall
wshallwshall deleted the leak-gate-bootstrap branch July 28, 2026 22:57
wshallwshall added a commit that referenced this pull request Jul 31, 2026
`-List` printed "[STALE ~Nh -- release it if that session is gone]" once a
claim passed 12h. That measures how long the WORK has run and says
nothing about whether anyone is still doing it, while recommending the
destructive action and withholding the fact you would need to decide.

Measured on this repo today: claim #7 rendered "STALE ~21h -- release it
if that session is gone" while its holder had committed TWO MINUTES
earlier. A session was told by its owner to clean up stale claims, read
that line, and checked the holder before acting. Had it followed the
advice, #7 would have been freed for a second session to start building
the ADR 0023 deferred tail while someone was mid-flight on it -- the
exact duplicate-build this registry exists to prevent, reached by
following the tool's own recommendation.

Reports liveness instead: worktree missing (the only state safe to act on
unasked), or hours since the holder's last commit. A long claim is the
normal shape of long work, so 21h with a commit an hour ago now reads as
active rather than abandoned, and only a genuinely quiet holder is
flagged -- as "confirm before releasing", not "release it".

The catch block sets an explicit "liveness check FAILED" annotation
rather than leaving it blank. An empty annotation reads as "nothing
notable about this claim", which is the same silent-instrument failure
the age signal had: accurate about what it measured, mute about what it
could not.

Verified: live holder, missing worktree, and non-git-directory paths all
render correctly; -Take and -Release unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
wshallwshall added a commit that referenced this pull request Aug 2, 2026
…ure was deferred after it shipped (#137)

* docs: intake auth shipped in increment A, but three docs still called it deferred

ADR 0154 increment A landed this morning (f2ef0ea, PR #109) and shipped
intake_auth on the inbound HTTP listen socket. PR #110 corrected the ADR's own
status line; nothing corrected the docs the ADR itself names at line 165 as
"docs to update on build".

CONNECTIONS.md contradicted itself as a result. Line 534 documents intake_auth
as a shipped setting with its full schema, while line 569 thirty lines below
said request authentication "is shaped but not shipped -- so an exposed
listener belongs behind the reverse proxy that terminates auth". A reader
following the second sentence stands up a reverse proxy to buy a control the
connector already has.

Fixed at the three sites where increment A is now false, and left the
synchronous-reply deferral alone -- that one is still true until #119 lands:

- CONNECTIONS.md dagger paragraph (REST-IN/SOAP-IN) -- the ADR names this
  paragraph specifically as the one the build retires.
- CONNECTIONS.md "Not built (first slice)" -- the false sentence is DELETED
  rather than reworded. The settings table above it already owns this fact, and
  restating status in a second place is what produced the contradiction.
- CONNECTIONS.md competitor-parity table, REST row.
- FEATURE-MAP.md capability catalog -- public-facing, so a stale "deferred"
  here understates shipped capability to anyone reading the mirror.

docs/BACKLOG.md carries the same staleness in two more places (the #7 summary
row and item #7's banner). It is deliberately NOT in this commit: two live
sessions are appending to that file right now and collision_gate.ps1 blocked
the edit. Coordinated with both; it follows once they land.

Verified: test_feature_map_claims, test_backlog_status_check, and the four
other suites that read these two files -- 238 passed, 3 skipped.

* docs: retire increment B's deferral, and document reply_from where an operator looks

ADR 0154 increment B (PR #119) makes the inbound HTTP listener a proxy: naming
reply_from blocks the HTTP turn until the named outbound's reply is captured and
COMMITTED, then returns it. Six sites still described that as unbuilt, and one
shipped setting group was documented nowhere outside the ADR.

DO NOT MERGE THIS BEFORE #119. Every claim here is true only once increment B is
on main.

The ADR's own status block (lines 3, 12-18) said "Increment B remains
unauthorised" and "Building increment B requires a further owner decision" --
the same defect 5dab6a0 fixed for increment A. Corrected, keeping the rev-4
split as the decision record rather than deleting it, since an ADR is a record of
what was decided and when.

Two sites were worse than stale. The fixed-JSON 502 on a partner rejection was
justified BY the deferral -- "with no committed customer this is a documented
limitation rather than a blocker ... and increment B is deferred until one
exists". Increment B shipped without capture_error_responses, so that premise is
gone while the gap remains. Per CLAUDE.md section 11, a compensating control must
not rest on a false premise: both sites now say it is a live limitation of
shipped code, accepted explicitly by the owner, rather than a consequence of
deferral.

CONNECTIONS.md had no reply_from at all -- six shipped settings with no operator
documentation, while its increment-A sibling intake_auth was fully documented.
Added the settings rows, what the mode does and why the committed row is the sole
authority, and the check-time refusals (FIFO ordering and finite max_attempts on
the named outbound, both of which would queue N callers behind one lane). The
capture_error_responses gap is stated here too, because "correct only when the
partner succeeds" is something you need before you point a partner at it, not
something to find in an ADR.

FEATURE-MAP.md bundled the SOAP-IN reply with the FHIR-IN facade under one
deferred mark. Split: the reply ships, the facade does not. The file's legend has
no partial mark, so one row could not honestly carry both.

Also applied section 11's "state a load-bearing fact ONCE" to the dagger
paragraph, which now points at the Http() section instead of keeping a second
copy of that status -- duplication is what let these drift apart twice.

NOT included, deliberately:
- docs/BACKLOG.md carries the same staleness in two places (#7 summary row, item
  #7 banner). Two live sessions are appending to that file; collision_gate.ps1
  blocked the edit and I coordinated with both rather than overriding it.
- messagefoundry/config/wiring.py ships a self-contradicting Http() docstring in
  #119 itself: "The synchronous downstream-reply (SOAP-envelope) path is a
  defined ADR 0013 follow-on, not built here" sits ~30 lines above the new
  "Synchronous captured-downstream reply" section documenting it. Not fixable
  from this branch -- that code is not on main yet.

Verified: 124 doc-gate tests pass; a markdown table-structure check (which caught
a separator I dropped in the ADR, and was proven against a deliberately broken
copy first) reports 0 mismatches across all three files.

* backlog: item #7's deferred tail still listed two things that shipped today

BACKLOG #7 is the ledger entry ADR 0154 was written against, and ADR 0154's own
item 13 flags it as a doc the build must retire. It named intake-auth and the
SOAP sync-reply as deferred in two places -- the summary row (401) and item #7's
banner (884). Increment A shipped intake auth this morning (f2ef0ea); increment
B ships the sync reply in PR #119.

Both sites corrected, and the banner now also carries the capture_error_responses
gap, since "the SOAP reply shipped" without "a partner 4xx still returns a
fixed-JSON 502" is the half-truth that would let someone plan a feed around it.

DO NOT MERGE BEFORE #119 -- the sync-reply half of this is true only once
increment B is on main.

ON OVERRIDING collision_gate.ps1, deliberately and with the reasoning recorded:

The gate blocked this edit because two live sessions had docs/BACKLOG.md in their
branch diff. It is file-granular and cannot compare hunks. Before overriding I
established, and did not assume:

- Both blocking sessions are PURE EOF APPENDS at line 8242 with zero deletions
  (verified from their worktrees: @@ -8242,3 +8242,78 @@ and @@ -8242,3 +8242,69 @@).
- This commit touches lines 401 and 884 -- roughly 7,400 lines away. Confirmed
  after the fact: git diff -U0 reports exactly those two hunks.
- BOTH sessions gave explicit written clearance, unprompted, and one confirmed it
  will not touch item #7 at all.
- The gate's own remedy is "coordinate first"; that was done first, not after.

The gate cannot re-evaluate any of this, and its predicate keys on a live
session's branch diff -- so a session whose work is committed and final blocks
this file for its entire remaining lifetime. Waiting would not have cleared it.

Its docstring says it "must never be the reason a session cannot work" and that
"a gate that cries wolf gets uninstalled". This is the third false denial today
on provably disjoint hunks (the HA re-check session hit it on wiring_runner.py
with ~1700 lines of separation). A hunk-offset proposal has gone to the session
owning the coordination hooks, because the durable fix is to compare ranges
rather than filenames -- the Bash escape used here is trivially available to
anyone, which is precisely why the gate needs to be right rather than loud.

Verified: test_backlog_status_check 15 passed (the banner invariant, which this
edits inside of), table structure 0 mismatches, CRLF preserved byte-for-byte.

* docs(adr): ADR 0023's Status line still called its deferred tail deferred

A repo-wide sweep for the same defect turned up a seventh site, and it is the
parent ADR of the work itself:

  Status: Accepted (2026-06-27, built - first slice in 0.2.10;
          SOAP-reply/auth/routing-metadata deferred)

Two of those three shipped. Intake auth landed this morning as ADR 0154
increment A (f2ef0ea); the SOAP-envelope synchronous reply is increment B
(PR #119). Only routing-metadata is still genuinely deferred.

This is the same defect PR #110 fixed on ADR 0154's own status line, in the
document one level up that nobody looked at. ADR 0023's BODY was already fine -
it links forward to ADR 0154 at lines 319 and 327 - so a reader who got that far
was told the truth. Only the header, which is what most readers actually read,
was wrong.

The replacement links to ADR 0154 as "the authority on their current state"
rather than restating what shipped, because restating the build state in a
seventh place is what produced the first six.

DO NOT MERGE BEFORE #119 - the SOAP-reply half is true only once increment B is
on main. The intake-auth half is already true on main today.

Deliberately NOT changed, having checked both:

- CHANGELOG.md:658 says the sync-reply and intake auth "are deferred follow-ons".
  It sits under "## [0.2.10] - 2026-06-27" / "### Added", where that was true.
  A changelog records what a release contained; editing it would falsify the
  release record. The new capability belongs in the entry for the release that
  ships it, which is a release-time task, not this one.
- ADR 0023's Decision paragraph (line 93) still says the synchronous reply "is a
  defined follow-on". That is a true record of what ADR 0023 DECIDED, and an ADR
  body is a decision record, not a status board. The header now carries the
  current state, which is the right division.

Verified: link target resolves, 24 doc-gate tests pass.

* fix(http): the listener's module docstring denied the feature the module implements

ADR 0154 increment B merged as 002be18 and made this module a proxy: naming
reply_from blocks the HTTP turn until the named outbound's reply is captured and
committed, then returns it as the body. The module docstring at the top of the
file it landed in still said:

  **First slice (ADR 0023 D3).** Only the cheap, correct 202-respond-with-receipt
  path is built. A synchronous downstream-reply (the SOAP-envelope
  block-on-captured-downstream-reply seam) is a defined ADR 0013 follow-on and is
  **not** built here.

"Not built here" in the docstring of the file that builds it. Anyone reading
http_listener.py top-down is told the feature is absent before reaching the code
that implements it.

Replaced with the two response modes and a pointer to Http() for the settings and
their check-time refusals, rather than a second copy of that surface here. That
is the same rule the rest of this cleanup applies (CLAUDE.md section 11): the
status was restated in a place that could not be maintained, which is why it
drifted the moment the feature shipped.

The sibling site is Http()'s own docstring in config/wiring.py, which carries the
identical claim ~30 lines above its own "Synchronous captured-downstream reply"
section. It is NOT in this commit: collision_gate.ps1 reports wiring.py held by
the live session landing PR #132, whose changes are confined to the Email() and
Direct() factories ~646 lines away. I have their written clearance and verified
the separation, but they merge next by agreement, so waiting means my branch is
based on theirs and the conflict does not need resolving at all.

Verified: ruff format --check clean, ruff check clean, module parses.

* fix(config): Http()'s docstring denied the reply path documented 30 lines below it

The sibling of f95ddb2. Http()'s docstring described respond-with-receipt and
closed with:

  The synchronous downstream-reply (SOAP-envelope) path is a defined ADR 0013
  follow-on, not built here.

Roughly thirty lines below, in the same docstring, sits the section
"Synchronous captured-downstream reply (ADR 0154 D4)" describing the feature in
detail. Both shipped in 002be18. A reader of the API reference for the factory
that CONFIGURES the reply path was told it does not exist, then shown how to
configure it.

Rewritten so the 202 paragraph says what it actually is -- the behaviour of an
inbound WITHOUT reply_from -- and hands off to the section below rather than
denying it. No second copy of that surface, per CLAUDE.md section 11.

That completes the sweep: eight sites across five files (ADR 0154 x6, ADR 0023,
BACKLOG #7 x2, CONNECTIONS.md x5, FEATURE-MAP.md, and these two docstrings), all
the same defect -- a build-status restated where nothing linked it back to the
thing that changed underneath it.

ON OVERRIDING collision_gate.ps1 A SECOND TIME, with the reasoning recorded:

The gate blocked this because the live session landing PR #132 has wiring.py in
its branch diff. Established before overriding:

- Their tree is CLEAN -- the change is committed and pushed, not in progress.
- Their hunks are at 1785-1890 (the Email() and Direct() factories); this edit is
  at ~1132 (Http()). ~650 lines apart, verified by reading both diffs.
- They gave written clearance twice, unprompted, and explicitly offered to "drop
  out of the way entirely if it drags".
- #132 is currently blocked on an unrelated SQL Server container failure
  (pyodbc HYT00 on the MERGE cipher_meta concurrent upsert; the 2025 leg passes
  the same commit), so waiting had no bounded end.

The decisive point is narrower than any of those: committed-with-a-clean-tree is
EXACTLY the case f55d6c6 fixes. That commit narrows the deny predicate to
UNCOMMITTED edits, and its author verified the fixed gate ALLOWS this scenario
against this very file. So the denial here is a known defect with a written,
tested, merge-pending fix -- not a judgment the gate is entitled to make. It
cannot reach us yet only because the gate resolves from the primary checkout,
which needs the fix merged AND the primary advanced.

Verified: ruff format --check clean, ruff check clean, mypy --strict 260 files.
wshallwshall added a commit that referenced this pull request Aug 2, 2026
`-List` printed "[STALE ~Nh -- release it if that session is gone]" once a
claim passed 12h. That measures how long the WORK has run and says
nothing about whether anyone is still doing it, while recommending the
destructive action and withholding the fact you would need to decide.

Measured on this repo today: claim #7 rendered "STALE ~21h -- release it
if that session is gone" while its holder had committed TWO MINUTES
earlier. A session was told by its owner to clean up stale claims, read
that line, and checked the holder before acting. Had it followed the
advice, #7 would have been freed for a second session to start building
the ADR 0023 deferred tail while someone was mid-flight on it -- the
exact duplicate-build this registry exists to prevent, reached by
following the tool's own recommendation.

Reports liveness instead: worktree missing (the only state safe to act on
unasked), or hours since the holder's last commit. A long claim is the
normal shape of long work, so 21h with a commit an hour ago now reads as
active rather than abandoned, and only a genuinely quiet holder is
flagged -- as "confirm before releasing", not "release it".

The catch block sets an explicit "liveness check FAILED" annotation
rather than leaving it blank. An empty annotation reads as "nothing
notable about this claim", which is the same silent-instrument failure
the age signal had: accurate about what it measured, mute about what it
could not.

Verified: live holder, missing worktree, and non-git-directory paths all
render correctly; -Take and -Release unchanged.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
wshallwshall added a commit that referenced this pull request Aug 5, 2026
…sambiguate PR citations that already mis-resolve (#209)

* fix(quality): the two advisory gates wrote a status glyph into their own summaries

liveness.py and c901_delta.py each emitted a check mark on their clean-result
line, and c901_delta rendered its complexity table with a non-ASCII arrow.

Both write via sys.stderr.write when no summary file is given, and stderr
defaults to backslashreplace, so a stock Windows cp1252 console silently
mangled the text at exit 0 rather than raising. Verified by running the
pre-edit scripts from HEAD under PYTHONIOENCODING=cp1252: neither crashed,
both corrupted. That is the worse failure -- a crash is loud and self-
reporting; silent corruption at exit 0 is not. CI was never affected, because
the step summary is opened utf-8, so this was a local-run defect only.

Say the word instead (CLAUDE.md section 11). PASS trails each sentence because
that is where the glyph sat -- the minimal edit -- and it leaves the substring
tests/test_c901_delta.py:291 asserts ("No function was introduced over the
threshold") byte-identical. That assertion is a containment check rather than
a prefix check, so it does not by itself forbid a leading token; nothing in
the repo pins the placement either way.

The three arrows become ASCII ->, matching lines 227 and 313 of the same file,
which already wrote the identical relation that way.

Both files now carry zero characters cp1252 cannot encode.

* docs(quality): document /simplify, and take the status glyphs out of the rubric

Code_Quality_Standards.md carried 40 check marks and one red circle as status
markers, which CLAUDE.md section 11 forbids outside the two machine-parsed
backlog files. All 41 are gone. Most sat beside the word they decorated and
were simply deleted, that word carrying the meaning on its own; only two were
genuine rewrites -- the red circle in Appendix A.2 became Failing, and the
Appendix A.3 legend, where the glyph was the subject, became prose.

Adds section 5.1 as the single home for /simplify: a local, human-invoked
review that APPLIES its fixes rather than reporting them, which is why it runs
before the local quartet rather than after -- running it after would mutate a
tree the quartet just certified. It is not one of the five measurement gates,
sits outside the AI companion section 6.5 local gate, and carries no Built
status, because it ships with Claude Code rather than with this project and so
leaves no artifact here to score. CLAUDE.md gains a "Before you verify"
heading so the instruction is not governed by a pass-gate it cannot satisfy:
/simplify applies edits and returns no verdict.

Also corrects a PRE-EXISTING Appendix A.3 error that removing the glyphs
surfaced rather than introduced: the legend glossed its status marker as
advisory under two PR numbers, across a five-item list whose fifth item is
blocking and shipped under a third. That legend is byte-identical in every
commit this file has existed in.

HANDOFF-mutation-coverage.md prescribed typing a check mark back into the
rubric, which would have undone the pass -- so the removal was not durable.
Its instruction is corrected, along with its stale signal numbering and column
name, and it now says to write the word.

* docs(quality): the handoff cited the PR that built the gates, not the one that restatused them

The parenthetical said signals 7 and 8 were "restatused in v0.8 (#1040)". Two
different pull requests, verified by subject:

  7540f260  ci(quality): mutation (#7) + diff-coverage (#8) advisory gates (#1040)
  46714159  docs(rubric): v0.8 -- restatus signals 7 + 8 to Built (#1044)

PR #1040 built the gates; PR #1044 did the v0.8 restatus the sentence is
about. Caught by the session sweeping citation ambiguity in these docs.

Written in the settled "PR #NNN" form rather than a bare number, which is what
that sweep is standardising. Bare and bolded #N stays for backlog items, and
the distinction is load-bearing in code: backlog_status_check.py defines
_CL_EXPLICIT as BACKLOG\s+#(\d+) to pick out item citations, so writing
"BACKLOG #1040" for a pull request would make that regex misread it as an item.

* docs(quality): the rubric cited pull requests as bare "#N", which already resolves to the wrong item

#1020, #1028, #1040 and #1047 are PULL REQUEST numbers (97b79fef, 42756bdb,
7540f260, 3d6c8adf -- subject-anchored search). A bare "#N" in this corpus
reads as a backlog item, so these send a reader to the wrong document.

#1020 is not a future risk. Backlog item 1020 exists on main, filed
2026-08-04, about a first-run bootstrap Administrator with no email address --
while the rubric uses #1020 four times to mean the PyPI sdist private-doc leak
fix. Those four citations already land on the wrong item. 1028 is next; 1040
and 1047 are still unallocated.

"PR #N" is the settled repo form rather than a new one, and the repo encodes
the distinction in code: backlog_status_check.py defines _CL_EXPLICIT to match
the word BACKLOG followed by whitespace and a number, under the comment
"Unambiguous CHANGELOG citations of a *backlog item* (not a PR number)". That
regex is also why the inverse rewrite is unavailable -- prefixing a pull
request number with that word would make the parser read it as an item.

39 markers inserted: 38 in the rubric, 1 in the handoff. The rubric's 40
four-digit citations carry 38 markers because one slash-joined run takes a
single "PRs " across its three tokens.

Deliberately left alone: the 11 rubric-signal citations (#3, #6, #7, #8, #9,
#10, #11). Six of those numbers are also real backlog items, so the ambiguity
is genuine, but resolving it is prose surgery ("signal 7", not "PR #7") and
sits with the owner as a separate decision.

Analysis and the marking script are the work of the session on
claude/sleepy-villani-df328d. Verified here as markers-only: stripping every
marker from both the committed and the working text yields identical files.

* backlog: file 1029 -- the /simplify placement decision had no number to cite

Filed closed: the documentation is the whole deliverable, and it shipped in the
three commits below this one.

REWRITTEN BEFORE FILING. The draft item, written when the change was first
made, described a structure that the remediation then reverted -- it claimed a
sixth row in the section 5 gate table, a Built status, and a CLAUDE.md bullet.
None of those is what shipped: section 5's table is unchanged at five rows,
Built is a claim the document explicitly declines to make, and CLAUDE.md
carries a "Before you verify" heading placed ahead of the verification list
rather than a bullet inside it. Every claim in the filed item was read from the
working tree at 17c5212 rather than recalled.

APPENDED, NOT INSERTED, and the instruction to insert was checked rather than
followed. It was handed over on the grounds that 1028, 1030 and 1031 are being
filed concurrently. No such rule exists and the file does not follow one:

  108 items carry 10 descending adjacent pairs in file order, the tail running
  1019, 1018, 1024, 1026, 1025, 1027;
  backlog_status_check.py enforces no ordering -- its only sort is a citation
  report at line 190;
  this file's own header states only that the numbered items are intentionally
  deferred, and docs/README.md calls them "ranked", which is not numeric order.

So nothing states an ordering rule for the numbered items, and imposing numeric
order would discard whatever the existing arrangement encodes. Appending is
where every recent item sits. The only real interaction with the concurrent
items is a textual end-of-file conflict that resolves by keeping both.

Validated with the canonical parser rather than a hand-rolled scan
(CLAUDE.md section 11): parse_items reports item 1029 with closed=['<check>'],
open=[], is_open=False -- exactly one banner, no OPEN/CLOSED contradiction. The
hygiene gate at its ci.yml invocation reports OK, 304 items, each declaring
exactly one status.

Number allocated via scripts/coord/alloc.ps1, never by grepping for the next
free one.

* docs: correct the CRLF rationale in this branch's earlier merge commit

The merge commit 95cd856 states that docs/BACKLOG.md "is 100 percent CRLF" and
that "a resolver that normalises to LF produces a clean-looking merge that
churns every line". That is FALSE about the stored file, and this commit exists
so the correction travels with the claim -- this repository composes its squash
body from the concatenated commit messages, so both land on main together.

Every committed revision of that file is pure LF. Measured on the blobs rather
than the working tree:

    origin/main   CRLF=0  bareLF=5045
    453c95f      CRLF=0  bareLF=4994
    95cd856      CRLF=0  bareLF=5065
    working tree  CRLF=5065  bareLF=0
    core.autocrlf = true

CRLF exists only as the checkout materialisation. The original measurement read
bytes on disk and reported them as the stored form -- the working tree answered
a question about the object store.

THE RESOLUTION ITSELF WAS CORRECT; only the stated reason was wrong. The
resolver read and wrote with newline="", so it preserved the on-disk form
byte-for-byte and let autocrlf normalise on the way in -- the same outcome a
resolver that ignored line endings entirely would have produced.

THE INSTRUMENT THAT SETTLES THIS IS CHURN, NOT A LINE-ENDING COUNT:

    git diff --numstat 453c95f 95cd856 -- docs/BACKLOG.md
    71      0      docs/BACKLOG.md

71 added, zero removed -- items 1030, 1031 and 1032 plus one seam blank line.
A resolver that had normalised would show thousands of lines on BOTH sides.
That reconciles to the line against the independent resolution of the same
collision on another branch, which came back 70/0 and needed no seam line.

Anyone resolving the next end-of-file collision in this file should run the
numstat check and should not chase line endings that are not there.
wshallwshall added a commit that referenced this pull request Aug 5, 2026
… surfaced

1033. The rubric cites its own eleven signals as bare #N, and six of those
numbers are real backlog items -- #3 is OPEN today, and #6/#7/#8/#10/#11 are
closed items. Ten citations on four lines, re-measured against 780ee1d. Owner
ruled on 2026-08-05 that they get disambiguated. The four-digit PR citations in
the same file were fixed in PR #209; this is the short-number half that was
deliberately left out of that scope.

Two traps are recorded because each has already caught a reader. The #3 at L120
is a markdown ANCHOR FRAGMENT inside a link target, not a citation -- converting
it silently breaks the link, and a prior census listed it as a signal because it
counted tokens without printing context. And L299/L319 use backslash-escaped
forms: a grep attempt during this triage returned ZERO matches on a file that
demonstrably contains them, and the empty result was believed until a
self-tested pattern contradicted it. The item says to prove the pattern fires
before trusting a count from it.

1034. The pre-push shim exits 0 with "THE PUSH GUARD IS OFF for this push" when
python is not on PATH. With enforce_admins OFF, push_guard.py is the only thing
refusing an admin's direct push to main, and since the cutover that push is
publication -- so the one control has a silent off switch that depends on an
environment variable. As of today the shim switches off three guards rather
than one, the two added alongside it being the namespace allowlist and the
tip-tree check.

Filed with the adjacent gaps in the same class rather than separately: a fresh
clone or new worktree has no hook at all until install-git-hooks.ps1 runs, and
--no-verify and MEFOR_ALLOW_DIRECT_PUSH=1 skip everything by design. The item
states plainly that a client-side hook cannot be the sole control and that the
durable answer is server-side, with the shim as defence in depth.

Numbers allocated via scripts/coord/alloc.ps1, never by grepping for the next
free one. Validated with parse_items rather than a hand-rolled scan: 114 items,
zero duplicate numbers, 1033 and 1034 each carrying exactly one open banner.
Hygiene gate OK at 309 across both ledger files.
wshallwshall added a commit that referenced this pull request Aug 5, 2026
…d what it CARRIES (#213)

* fix(hooks): the push guard asked where a push LANDS, and nothing asked what it CARRIES

Two guards, both for paths the existing PROTECTED check waves through.

GUARD A -- namespace allowlist. Refuse any push whose remote ref is outside
refs/heads/ or refs/tags/. That is the shape of git push --mirror, which offers
every ref in the clone including remote-tracking namespaces. A mirror push was
refused before only INCIDENTALLY: it also offers local main as an update of
refs/heads/main, so PROTECTED happened to fire. That is a property of one
branch's state, not a rule, and it evaporates the moment main is up to date.

GUARD B -- content check. Refuse a push whose ref's tip tree carries
docs/security. That directory is gitignored, and an ignore rule governs only
UNTRACKED paths, so it does nothing about a ref whose history already tracks
those files. The path this closes is the likeliest of the set and is not a
mirror at all: branch off a ref of that lineage and push it as an ordinary
branch, which every other check here permits by design.

PROVEN, not assumed. Both guards exercised via crafted pre-push stdin against a
throwaway repo, with the fixture self-checked in both directions first (a
fixture whose add -f lost to the ignore rule would make every assertion pass
vacuously):

  case                                        new  old(HEAD)
  remote-tracking ref, mirror shape             1      0
  ordinary branch, clean tip                    0      0
  ordinary branch, tip carries docs/security    1      0
  tag push, clean tip                           0      0
  delete an unprotected branch                  0      0
  direct push to main                           1      1

The old-guard column is the negative control: it returned 0 for exactly the two
cases these guards add, so this is new coverage rather than restated behaviour.
Refusals were checked to name the right reason, not merely to exit 1.

WHAT THESE ARE NOT, stated in the code because the difference decides what a
green run entitles anyone to conclude. Guard B reads the TIP TREE only -- a
branch that added and then removed the files passes with a dirty history, so it
is not a history check. It matches paths, not content. Every check here is
skipped by --no-verify, by MEFOR_ALLOW_DIRECT_PUSH=1, and by the installed
shim's own fail-open, which prints "THE PUSH GUARD IS OFF for this push" and
exits 0 when python does not resolve. A fresh clone or new worktree has no hook
at all until install-git-hooks.ps1 runs. A client-side hook cannot be the sole
control and the docstring says so.

Also fixes a false docstring in the test file, which asserted that git push
--all sends every ref. It does not -- --all is refs/heads only, while bundle
create --all and rev-list --all mean every ref. That belief is what makes
someone treat --all and --mirror as interchangeable.

109 tests pass; ruff and mypy clean.

* docs(ledger): record the vault-ref cleanup, and retire a warning that was true when written

489 refs carrying docs/security content were deleted from this clone on
2026-08-05 with git update-ref -d, across THREE namespaces: refs/remotes/vault
(20), refs/remotes/vaultall (466), and refs/vault (3). That third sits outside
refs/remotes entirely and held the newest, densest content, so a cleanup scoped
to refs/remotes would have missed it.

THE STANDING WARNING AGAINST THIS IS NOW STALE, NOT WRONG. LEDGER-GATE.md and
alloc.ps1 both named "deleting its refs" as the hazard the allocator ratchet
defends against. Re-measured directly: BACKLOG max is 1032 and sub-floor max
353 both with and without the refs, ADR max 0161 either way, the allocator
emits max+1 and never fills gaps, and the ratchets already persist 1031 / 1000
/ 160. The warning was accurate when written, in the era when the floor did
depend on the ref sweep; the ratchet and the public-boundary split made it
independent since. It is updated rather than deleted, because the principle it
teaches still holds.

THE MULTISESSION PLAN GAVE A COMMAND THAT NO LONGER WORKS, and its description
of the ref was wrong when written. It called vault/main a remote-tracking ref;
git rev-parse --symbolic-full-name resolved it to refs/vault/main, and
refs/remotes/vault/main never existed. Nor was a remote named vault ever
configured -- only origin. The refs were orphaned namespaces from two
direct-URL fetches on 2026-07-28, 45 seconds apart. Sessions should read the
vault ledger from the separate MessageFoundry-vault clone instead.

REVERSIBILITY, since deleting refs is only safe if it is undoable. A manifest
of 489 refname/SHA pairs (464 unique commits -- 25 refs share a tip) is held
outside this repo, durably, inside the vault clone's own .git. The objects
remain addressable here, and every tip is REACHABLE from the vault clone's own
refs, so they are gc-safe there rather than merely undeleted. gc.auto is set to
0 in this clone: it was unset with 7060 loose objects against a default
threshold of 6700, already over, so a routine command could have fired an
auto-gc and converted a reversible ref deletion into permanent loss.

NOTHING WAS EVER PUBLISHED FROM THESE REFS. origin/main, all 30 origin refs and
all 195 local branches carry zero docs/security files at tip and in history,
confirmed three independent ways; the two graphs share no merge base.

* backlog: file 1033 and 1034 -- the two follow-ups the push-guard work surfaced

1033. The rubric cites its own eleven signals as bare #N, and six of those
numbers are real backlog items -- #3 is OPEN today, and #6/#7/#8/#10/#11 are
closed items. Ten citations on four lines, re-measured against 780ee1d. Owner
ruled on 2026-08-05 that they get disambiguated. The four-digit PR citations in
the same file were fixed in PR #209; this is the short-number half that was
deliberately left out of that scope.

Two traps are recorded because each has already caught a reader. The #3 at L120
is a markdown ANCHOR FRAGMENT inside a link target, not a citation -- converting
it silently breaks the link, and a prior census listed it as a signal because it
counted tokens without printing context. And L299/L319 use backslash-escaped
forms: a grep attempt during this triage returned ZERO matches on a file that
demonstrably contains them, and the empty result was believed until a
self-tested pattern contradicted it. The item says to prove the pattern fires
before trusting a count from it.

1034. The pre-push shim exits 0 with "THE PUSH GUARD IS OFF for this push" when
python is not on PATH. With enforce_admins OFF, push_guard.py is the only thing
refusing an admin's direct push to main, and since the cutover that push is
publication -- so the one control has a silent off switch that depends on an
environment variable. As of today the shim switches off three guards rather
than one, the two added alongside it being the namespace allowlist and the
tip-tree check.

Filed with the adjacent gaps in the same class rather than separately: a fresh
clone or new worktree has no hook at all until install-git-hooks.ps1 runs, and
--no-verify and MEFOR_ALLOW_DIRECT_PUSH=1 skip everything by design. The item
states plainly that a client-side hook cannot be the sole control and that the
durable answer is server-side, with the shim as defence in depth.

Numbers allocated via scripts/coord/alloc.ps1, never by grepping for the next
free one. Validated with parse_items rather than a hand-rolled scan: 114 items,
zero duplicate numbers, 1033 and 1034 each carrying exactly one open banner.
Hygiene gate OK at 309 across both ledger files.
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.
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