Skip to content

fix(fhir): anchor path-segment patterns, catch InvalidURL, and screen operator config at construction (#1240, #1241) - #379

Merged
wshallwshall merged 11 commits into
mainfrom
claude/b2-1240-fhir-anchor
Aug 14, 2026
Merged

fix(fhir): anchor path-segment patterns, catch InvalidURL, and screen operator config at construction (#1240, #1241)#379
wshallwshall merged 11 commits into
mainfrom
claude/b2-1240-fhir-anchor

Conversation

@wshallwshall

@wshallwshall wshallwshall commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Anchors the two FHIR path-segment patterns with \Z instead of $ (BACKLOG #1240).

Python's $ also matches immediately before a final newline, so ^[A-Za-z]+$ accepted
"Patient\n" and the gate did not enforce the grammar it advertises.

Fixed on the patterns, not the call sites -- deliberately

The item as filed prescribed "two one-line changes: match to fullmatch on both regexes". That is
not executable: match versus fullmatch is a property of the call, and there are three call
sites (:189, :698, :704), so it would be three edits and a future caller could re-introduce it.

$ to \Z is genuinely two lines, covers all three call sites, and cannot be re-broken by a new caller.

Deliberately not done

The item's read-path _reject_control_chars limb. Redundant once the gates are strict -- both charsets
exclude every C0 and DEL character, and every character of the query reaches a gate -- and adding it
would re-introduce the duplication that #1239 records as retired by #1243.

Test shape, because the obvious test proves nothing

"Patient/123\n" is normalised away upstream and builds a URL byte-identical to the clean input,
measured before and after the fix -- so the obvious test passes either way. Only "Patient\n/123", an
LF ending a segment followed by more path, reaches a gate carrying the newline.

Both shapes are pinned: the discriminating case asserts the refusal, and a second test pins the
normalisation, so if that ever starts raising, the first test is known to need re-deriving rather than
deleting.

Red-first: both parametrized cases failed with DID NOT RAISE ValueError against the unfixed
patterns; the normalisation pin passed before and after, as it must.

No banner flip, and that is compliance rather than an oversight

This branch touches exactly two files and nothing under docs/. The ledger disposition is withheld
deliberately and routes to the Dispatcher.

Flagging it explicitly because the opposite cause produces the same visible shape: earlier today PR #372
landed a fix with no ledger edit, and its item sat reading not-started while the fix was already on main.

Verification scope -- stated, not implied

Re-run against the rebased commit, not the originally tested one:

ruff format --check, ruff check   clean on both files
mypy (strict)                     clean on transports/fhir.py
pytest test_fhir_lookup.py + test_egress_allowlist.py + test_transports.py
                                  165 passed, 1 skipped

The full suite was NOT run locally and neither test path was collected in full. CI is the gate for
that. All commit hooks passed, including ruff and bandit.


ALSO CARRIES #1241 (added after this PR was opened)

http.client.InvalidURL is not a ValueError and not an OSError -- its MRO is
InvalidURL -> HTTPException -> Exception -- so it matched none of _post's arms, including the
ValueError backstop at :638 whose own comment says it exists for "a CRLF in a header/URL that slipped
past the control-char guard"
. The arm written for this exception could not catch it.

Now caught and named explicitly, with the MRO in the comment so nobody folds it back into a bare
ValueError.

#1241 IS A PARTIAL FIX -- DO NOT CLOSE THE ITEM ON IT

The item's filed claim -- operator-config values reaching the URL and header sinks with no
construction-time screen
-- still HOLDS for both sinks and is not addressed here. What is fixed is
the narrower defect found while re-verifying: when the URL sink fails, it failed in the wrong class.

Scope bound worth carrying: the URL limb has two incidental neutralisations the header limb does not
-- urllib.parse.unwrap strips a trailing CRLF, and Request.full_url splits at # client-side.
The header sink has neither, which is why the construction-time screen is still needed.

Red-first, with a shipped negative control

Red was the defect itself, not a proxy: the test failed with a raw http.client.InvalidURL escaping
_post.

A second test ships as the negative control -- a URLError must STILL raise a retryable
DeliveryError and NOT a NegativeAckError -- so this cannot pass by the method having been widened to
sweep everything into the permanent class. It passed before and after.

Why both fixes are on one branch

Both write transports/fhir.py, and the write serialisation on that file was explicit. Splitting them
into concurrent branches is the thing that was ruled out, and stacking a second PR on this one would
create a pre-squash base the moment either merged.

Verification for the combined branch

ruff format --check, ruff check   clean on all changed files
mypy (strict)                     clean on transports/fhir.py
pytest test_fhir_transport + test_fhir_lookup + test_egress_allowlist
       + test_transports + test_smart_backend
                                  259 passed, 1 skipped

The full suite was NOT run locally, neither test path was collected in full, and the webconsole suite
was not run at all. CI is the gate for that.

Both diff instruments agree on the tip: three-dot and two-dot are byte-identical at 3 files, 77
insertions, 3 deletions
, and docs/ is untouched -- so no banner flip in either commit, deliberately.


AND #1241's ACTUAL FILED DEFECT (third commit, f7bd300)

The earlier commit fixed the wrong-exception-class defect. This one fixes the claim as filed.

conditional_query was taken verbatim from operator settings and reached two sinks with no screen --
an unencoded URL interpolation and the If-None-Exist header value. url was equally unscreened.

Screened at construction, and the disposition is the point

_reject_config_control_chars raises ValueError, deliberately distinct from the existing
_reject_control_chars, which screens message-derived values and raises a permanent
NegativeAckError.

a bad MESSAGE  -> dead-letters one message
a bad SETTING  -> is wrong for EVERY message the connection will ever send

So a bad setting fails the connection at load rather than dead-lettering an unbounded stream of
messages that were never at fault.

The header sink is why the send path was not enough. The URL limb has two incidental neutralisations
it does not -- unwrap strips a trailing CRLF, full_url splits at # client-side -- and neither
touches a header value
.

Still incomplete -- #1241 must NOT be closed on this either

  • transports/dicomweb.py, which the item also names, is untouched.
  • FhirLookupExecutor has a SECOND unscreened url construction site in the same file, found only
    because an edit matched two locations instead of one. Outside what was dispatched, so reported rather
    than fixed.

Red-first, including one that had to be rebuilt

All five new cases red with DID NOT RAISE ValueError. One first failed with a TypeError -- the
test passed url= through a helper that already supplies it -- and a test failing for the wrong reason
is not a red-first proof
, so it was rebuilt to construct the Destination directly and re-confirmed red.

Positive control shipped: a clean conditional_query carrying |, : and / still constructs and
is preserved verbatim, so the screen cannot pass by rejecting everything.

Verification

ruff format --check, ruff check   clean
mypy (strict)                     clean on transports/fhir.py
pytest over six suites            302 passed, 1 skipped

The full suite was NOT run and the webconsole suite was not collected at all. CI is the gate.

A diff disagreement that was chased, not assumed

Two-dot and three-dot disagreed after this commit (3 files vs 5, 131 deletions) -- the same signature as a
genuine squash-revert caught earlier today. The discriminator was run rather than assumed:

'\Z' anchors on origin/main        0
http.client import on origin/main  0     -> #379 has NOT merged
positive control (patterns exist)  1

So this branch is merely behind, not reverting: main gained #376 during the build, and two-dot renders
main's newer work as deletions. Intersection test, the decisive one: files changed here
(fhir.py + tests) versus files main changed since the merge-base (remotefile.py + its test) --
empty intersection, so a merge cannot lose anything. Re-verified independently before this push.

…refused (BACKLOG #1240)

Python's `$` also matches immediately before a final newline, so `^[A-Za-z]+$` accepted
"Patient\n" and the gate did not enforce the grammar it advertises.

Fixed on the PATTERNS, not the call sites. `match` versus `fullmatch` is a property of
the CALL and there are three call sites (fhir.py:189, :698, :704), so a per-call fix
covers whichever two you happen to notice and leaves the third to re-introduce the hole.
Anchoring the pattern fixes all three at once and cannot be re-broken by a future caller.

The item as filed prescribed "two one-line changes: match to fullmatch on both regexes".
That is not executable -- it is three call-site edits, not two. `$` to `\Z` is genuinely
two lines and strictly stronger. Re-verified against the code before building; the
amendment content is with the dispatcher.

NOT DONE, deliberately: the item's read-path `_reject_control_chars` limb. Once the gates
are strict it is redundant -- both charsets exclude every C0 and DEL character, and every
character of the query reaches a gate ('?' refused at :713, more than two segments raised
at :696). Adding it would also re-introduce the second control-char treatment that #1239
records as retired by #1243.

TEST SHAPE IS LOAD-BEARING. A trailing LF on the whole query ("Patient/123\n") is
normalised away upstream and builds a URL byte-identical to the clean input -- measured
both before and after this change -- so the obvious test passes either way and proves
nothing. Only an LF ending a segment followed by more path ("Patient\n/123") reaches a
gate carrying the newline. Both shapes are pinned: the discriminating one asserts the
refusal, and a second test pins the normalisation so that if it ever starts raising, the
first test is known to need re-deriving rather than deleting.

Red-first: both parametrized cases failed with "DID NOT RAISE ValueError" against the
unfixed patterns, and the normalisation pin passed before and after, as it should.

Verified, with scope stated: ruff format --check and ruff check clean on both changed
files; mypy strict clean on transports/fhir.py; pytest over tests/test_fhir_lookup.py,
tests/test_egress_allowlist.py and tests/test_transports.py = 165 passed, 1 skipped, in
the lane venv built against constraints.lock (ruff 0.15.22, matching the pin). The FULL
suite was NOT run and neither test path was collected in full.
@wshallwshall
wshallwshall enabled auto-merge (squash) August 13, 2026 19:25
…nstead of escaping (BACKLOG #1241)

InvalidURL is not a ValueError and not an OSError. Its MRO is

    InvalidURL -> HTTPException -> Exception

so it matched NONE of _post's except arms: not HTTPError (:616), not URLError (:634),
not (TimeoutError, OSError) (:647), and not the ValueError backstop at :638 -- whose own
comment says it exists for "a CRLF in a header/URL that slipped past the control-char
guard", which is precisely the condition urllib raises InvalidURL for.

So the arm written for this exception could not catch it. On first deployment the URL
limb would surface as an unhandled internal error out of send() rather than the
classified permanent dead-letter the file intends, which is a different disposition and
a different operator experience: an escaping exception instead of a dead-lettered
message with a reason.

This is a PARTIAL fix for #1241 and I am not claiming otherwise. The item's filed claim
-- that operator-config values reach the URL and header sinks with no construction-time
screen -- still HOLDS for both sinks and is NOT addressed here. This commit closes the
narrower defect found while re-verifying the item: that when the URL sink does fail, it
fails in the wrong class.

Scope note carried from the re-verification, because it bounds how far this goes: the
URL limb has two incidental neutralisations the header limb does not -- urllib.parse.unwrap
strips a trailing CRLF, and Request.full_url splits at '#' client-side. The header sink
has neither, which is why the construction-time screen is still needed and why a fix
cannot stop here.

Red-first: the test failed with a raw `http.client.InvalidURL: URL can't contain control
characters` escaping _post, which is the defect itself rather than a proxy for it.

NEGATIVE CONTROL SHIPPED ALONGSIDE. A second test asserts a URLError still raises a
retryable DeliveryError and NOT a NegativeAckError, so this cannot pass by the method
having been widened to swallow everything into the permanent class. It passed before this
change and after it.

Verified, with scope stated: ruff format --check and ruff check clean on both changed
files; mypy strict clean on transports/fhir.py; pytest over test_fhir_transport.py,
test_fhir_lookup.py, test_egress_allowlist.py, test_transports.py and test_smart_backend.py
= 259 passed, 1 skipped, in the lane venv built against constraints.lock (ruff 0.15.22,
matching the pin). THE FULL SUITE WAS NOT RUN and neither test path was collected in full.

No ledger edit: the banner flip is withheld deliberately and the disposition routes to the
dispatcher.
@wshallwshall
wshallwshall disabled auto-merge August 13, 2026 19:28
@wshallwshall wshallwshall changed the title fix(fhir): anchor the path-segment patterns with \Z so a trailing newline cannot pass (#1240) fix(fhir): anchor the path-segment patterns, and catch InvalidURL in _post (#1240, #1241 partial) Aug 13, 2026
@wshallwshall
wshallwshall enabled auto-merge (squash) August 13, 2026 19:29
…nstruction (BACKLOG #1241)

This is the item's FILED defect, which the previous commit did not touch: operator-config
values reached the URL and header sinks with no construction-time screen.

conditional_query was taken verbatim from settings and reached TWO sinks:
  - an unencoded URL interpolation, f"{base}/{type_seg}?{self.conditional_query}"
  - the If-None-Exist HEADER value

The header sink is why this could not be left to the send path. The URL limb has two
incidental neutralisations it does not: urllib.parse.unwrap strips a trailing CRLF, and
Request.full_url splits at '#' client-side. Neither touches a header value, so a CRLF in
conditional_query is a header injection with nothing in front of it.

SCREENED AT CONSTRUCTION, NOT PER MESSAGE, AND THE DISPOSITION IS THE REASON.
_reject_config_control_chars raises ValueError and is deliberately distinct from the
existing _reject_control_chars, which screens message-derived values and raises a
permanent NegativeAckError. A bad MESSAGE dead-letters one message. A bad SETTING is
wrong for every message the connection will ever send, so it must fail the connection at
load rather than dead-letter an unbounded stream of messages that were never at fault.

Applied to both `url` and `conditional_query`.

Red-first: all five new cases failed with "DID NOT RAISE ValueError". One of them first
failed with a TypeError instead -- the test passed url= through a helper that already
supplies it -- and a test failing for the wrong reason is not a red-first proof, so it was
rebuilt to construct the Destination directly and re-confirmed.

POSITIVE CONTROL SHIPPED: a clean conditional_query carrying '|' and ':' and '/' still
constructs and is preserved verbatim, so the screen cannot pass by rejecting everything.

STILL NOT COMPLETE, and #1241 must not be closed on this either. Not addressed here:
  - transports/dicomweb.py, which the item also names. Untouched.
  - FhirLookupExecutor has a SECOND url construction site in this same file with the same
    unscreened shape. Found only because an edit matched two locations rather than one.
    Not fixed here because it is outside what was dispatched; reported as content.

Verified, with scope stated: ruff format --check and ruff check clean on both changed
files; mypy strict clean on transports/fhir.py; pytest over test_fhir_transport,
test_fhir_lookup, test_egress_allowlist, test_transports, test_smart_backend and
test_connection_api = 302 passed, 1 skipped, in the lane venv built against
constraints.lock (ruff 0.15.22, matching the pin). THE FULL SUITE WAS NOT RUN and the
webconsole suite was not collected at all.

No ledger edit; the banner flip is withheld and disposition routes to the dispatcher.
@wshallwshall
wshallwshall disabled auto-merge August 13, 2026 20:05
@wshallwshall wshallwshall changed the title fix(fhir): anchor the path-segment patterns, and catch InvalidURL in _post (#1240, #1241 partial) fix(fhir): anchor path-segment patterns, catch InvalidURL, and screen operator config at construction (#1240, #1241) Aug 13, 2026
@wshallwshall
wshallwshall enabled auto-merge (squash) August 13, 2026 20:06
@wshallwshall
wshallwshall disabled auto-merge August 13, 2026 22:45
@wshallwshall
wshallwshall enabled auto-merge (squash) August 13, 2026 22:46
@wshallwshall
wshallwshall disabled auto-merge August 13, 2026 22:46
 cannot make itself

PR #379 is red on a required check that says a PR implementing BACKLOG #N must
update BACKLOG.md. The owner's 2026-08-13 ruling says a builder may resolve merge
conflicts but may not author ledger content. Those two are mutually unsatisfiable
for a compliant builder PR, so the builder correctly withheld the banner and the
PR correctly went red. Authoring is dispatcher and lander only; this supplies the
edit. Neither a bug nor anyone's error -- two correct rules meeting.

#1240 CLOSED. Verified before signing by printing the operands on both refs
rather than counting them, after a count instrument returned 0 on a string the
printed lines visibly contained:

  origin/main   _FHIR_TYPE_RE = re.compile(r"^[A-Za-z]+$")
  PR #379 head  _FHIR_TYPE_RE = re.compile(r"^[A-Za-z]+\Z")

$ -> \Z on the two pattern definitions, call sites unchanged. That is the durable
form: it covers all three call sites at once and cannot be re-broken by a future
caller, where converting the calls to .fullmatch would fix three and leave a
fourth free to reintroduce it. The read-path _reject_control_chars limb was
deliberately not added -- redundant once the gates are strict, and it would
reintroduce duplication that #1239 records as retired.

The item also records that the obvious regression test cannot discriminate:
_resolve_read_url strips, so "Patient/123\n" yields an identical URL before and
after the fix and only "Patient\n/123" flips. Measured by executing the shipped
and patched sources, not argued.

#1241 STAYS OPEN, amended to record partial progress. #379 fixed construction-time
screening plus a wrong-exception-class defect worse than the filed finding --
http.client.InvalidURL derives from HTTPException, not ValueError and not OSError,
so it escaped every except arm in _post including the backstop written for that
case. Still outstanding: transports/dicomweb.py, which the item names, and a
second unscreened url-construction site in FhirLookupExecutor in the same file.
The item's subject is the ASYMMETRY, so one sink screened while a sibling is not
reproduces the very defect being reported. A partial close would be wrong.

Two corrections to #1241's filed text, neither reducing severity: its comparison
clause INVERTS rather than going stale, because the neighbouring path it called
"weaker but at least screening" was removed outright, leaving :431 the only
unencoded interpolation in the file; and its enum rationale is right advice for
the wrong reason, since containment comes from the !r conversion rather than the
enum's closedness.

Controls: parse_items 281 items / 206 open / 75 closed before, 281 / 205 / 76
after -- 0 / -1 / +1, the expected delta for exactly one close and one amendment.
backlog_status_check green, every item declaring exactly one status. Banner
invariant checked per item: #1240 one closed-alphabet character and zero open,
#1241 zero closed and one open.
@wshallwshall
wshallwshall enabled auto-merge (squash) August 13, 2026 22:49
@wshallwshall
wshallwshall disabled auto-merge August 13, 2026 23:59
…ruction (BACKLOG #1241)

Completes #1241's second named file. dicomweb.py already had the right helper and the
right contract -- _reject_url_control_chars raises ValueError at construction -- and
applied it to exactly ONE operator setting, study_uid. Three others reached the same wire
unscreened:

  url            scheme-checked only
  headers        merged into the request headers verbatim, NAMES as well as values
  bearer_token   interpolated into Authorization verbatim

Header NAMES are screened as well as values because both halves land on the wire, so a
CRLF in either splits the request. The header sink is the one that needs this most: a URL
has incidental neutralisation downstream (urllib.parse.unwrap strips a trailing CRLF,
Request.full_url splits at '#' client-side) and a header value has none -- nothing strips
or re-encodes it.

Screened at CONSTRUCTION, matching the existing study_uid treatment and the fhir.py
sibling in this same item: a bad MESSAGE dead-letters one message, a bad SETTING is wrong
for every message the connection will ever send, so it fails the connection at load
rather than dead-lettering an unbounded stream of messages that were never at fault.

The inconsistency is the interesting part and worth recording: the file was not missing
the concept, the helper, or the contract. It had all three and applied them to one of
four settings. A reader auditing "does dicomweb screen its config?" finds study_uid
screened and can reasonably stop.

Red-first: all six new cases failed with "DID NOT RAISE ValueError".

POSITIVE CONTROL SHIPPED: clean operator headers still construct and are preserved
verbatim on the destination, so the screen cannot pass by rejecting everything.

Verified, with scope stated: ruff format and ruff check clean on both changed files; mypy
strict clean on transports/dicomweb.py; pytest over test_dicomweb, test_dicom_wiring,
test_fhir_transport, test_fhir_lookup and test_transports = 271 passed, 1 skipped, in the
lane venv built against constraints.lock (ruff 0.15.22, matching the pin). THE FULL SUITE
WAS NOT RUN and the webconsole suite was not collected at all.

Still open on #1241 and NOT closed by this: FhirLookupExecutor has a second unscreened url
construction site in fhir.py, reported to the dispatcher as content rather than fixed here
because it is outside what was dispatched.

No ledger edit; banner flip withheld, disposition routes to the dispatcher.
@wshallwshall
wshallwshall enabled auto-merge (squash) August 14, 2026 00:00
@wshallwshall
wshallwshall disabled auto-merge August 14, 2026 00:25
…CKLOG #1241)

The SECOND url construction site in this module. FhirDestination screens its own url and
conditional_query; FhirLookupExecutor took a base url from the same operator config and
checked only that it was a non-empty string with an http(s) scheme.

THE ASYMMETRY IS THE ITEM'S SUBJECT, which is why this is not a separate concern.
#1241 reports operator-config values reaching sinks with no construction-time screen.
Screening one sink and leaving its sibling unscreened reproduces the defect being
reported, in the same file, on the same setting name.

Found only because an earlier edit to the destination matched TWO locations instead of
one. It was reported to the dispatcher as content rather than fixed at the time, because
it was outside what had been dispatched.

The helper gained a `where` parameter so the message names WHICH construction site
raised. It defaults to "destination", so the two existing call sites are unchanged in
behaviour and the tests that match on "control character" are unaffected. That parameter
exists because there are two sites and the reader of a load-time failure needs to know
which one.

Red-first: all three control-char cases failed with DID NOT RAISE ValueError against the
unscreened constructor.

POSITIVE CONTROL SHIPPED: a clean https url still constructs and the connection is
registered, so the screen cannot pass by rejecting everything.

Verified, with scope stated: ruff format --check and ruff check clean on both changed
files; mypy strict clean on transports/fhir.py; pytest over test_fhir_lookup,
test_fhir_transport, test_dicomweb, test_egress_allowlist and test_transports = 270
passed, 1 skipped, in the lane venv built against constraints.lock (ruff 0.15.22,
matching the pin). THE FULL SUITE WAS NOT RUN and the webconsole suite was not collected.

WHAT REMAINS OPEN ON #1241, so this commit is not read as closing it: nothing in this
module that I have found. dicomweb.py was screened in 45293154, which is committed and
anchored but did NOT reach PR #379 -- content-tested against the PR head, not inferred.
Whether the item closes depends on that commit landing alongside these.

No ledger edit; the banner flip is withheld and disposition routes to the dispatcher.
@wshallwshall
wshallwshall enabled auto-merge (squash) August 14, 2026 00:26
@wshallwshall
wshallwshall merged commit 0424bfa into main Aug 14, 2026
35 of 36 checks passed
@wshallwshall
wshallwshall deleted the claude/b2-1240-fhir-anchor branch August 14, 2026 01:54
wshallwshall added a commit that referenced this pull request Aug 14, 2026
…n that are DONE (#386)

* backlog: correct #1114's Severity line, which contradicted its own body

The Severity line read "could submit unbounded messages". Four bounds ship ON and
were re-verified at origin/main 96c9a86: DEFAULT_MAX_FRAME_BYTES 16 MiB
(transports/mllp.py:105), DEFAULT_MAX_CONNECTIONS 256 (:106),
DEFAULT_RECEIVE_TIMEOUT 60.0s (:107), and max_file_bytes (transports/file.py:384,
remotefile.py:808). They bound SIZE and CONCURRENCY, not RATE.

The item's own "What holds it short today" paragraph already said exactly that, two
paragraphs above, so the Severity line was contradicting its own item rather than
describing the engine. Corrected to "at an unbounded RATE"; the finding is unchanged
and the item stays open.

The correction runs in the direction that makes the engine look better, which is why
the amendment states it explicitly: a Severity line is the sentence most often quoted
onward without its body.

Amendment only, no heading added, so ledger ownership is not consulted. parse_items
before and after: 277 items / 203 open, unchanged.

* backlog: flip #1237 to shipped -- its fix landed without a ledger edit

PR #372 merged the code as 96c9a86 and did not touch docs/BACKLOG.md, so the
item read "not started" while its fix was on main. Banner repair, not a change
of plan.

Both stated limbs verified at origin/main 96c9a86 rather than inferred from the
PR title:

  Signature -- an AST probe located all three functions, so it was not blind:
  gzip_decompress :101, deflate_decompress :138, zip_decompress :195 each carry
  max_output_bytes keyword-only with NO DEFAULT. That is the construct the item
  asked for, a gate that refuses when the precondition is absent, rather than a
  changed default value, which the parent item #1129 explicitly rules out.

  Tests -- tests/test_compression.py pins it: "Calling a decompressor without
  max_output_bytes is a TypeError, not an unbounded read", with a
  pytest.raises(TypeError) assertion. The re-exported public surface still
  resolves in messagefoundry/__init__.py and parsing/__init__.py.

This closes NO ASVS cell and the amendment says so in the item. The verdict is
the assessor's and the vault scorecard is the record of record; the "before
uncompressing" reading question is unresolved without the pre-pass #1237
deliberately excluded, which remains unfiled and is an owner call.

Controls: parse_items 277 items / 203 open / 74 closed before, 277 / 202 / 75
after -- 0 / -1 / +1, the expected delta for one close. backlog_status_check
green, every item declaring exactly one status. Banner invariant checked on the
item body: one closed-alphabet character, zero open-alphabet characters.

* backlog: strike #1245's false delete clause, and narrow two overclaims

#1245's SCOPE paragraph said the bootstrap account "cannot be renamed
(update_user does not rename) or deleted, so it persists as a permanently
disabled row". The rename half is correct. The delete half is FALSE.

Reproduced end to end by a second session: DELETE /users/<bootstrap admin>
returns 200 {'detail': 'deleted'}. Confirmed here independently from the code --
BOOTSTRAP_USERNAME appears 0 times in messagefoundry/api/auth_routes.py, with a
positive control of 7 occurrences in auth/service.py so the probe discriminates.
No delete-time guard names the bootstrap account. The only guard on that route
is is_last_enabled_admin (auth_routes.py:714), which skips disabled users, and
retirement is what disables this one.

Why the correction matters more than the fact: "persists as a permanently
disabled row" is what makes this read as an availability defect with no exit.
There is an exit and it is destructive. A fix must not rest on the row being
undeletable, and a reader checking this paragraph would otherwise re-conclude a
related defect is unreachable and close it as impossible.

Two narrower corrections in the same pass, both REDUCING what the item claims:

  "Silent at both ends" is half wrong. The 201 response body does carry
  disabled:true (auth_routes.py:656-658 re-reads after retirement, _user_summary
  sets it at :195). It is silent at the login end only, via the generic 401 that
  is deliberately indistinguishable from a wrong password.

  Login is not the sole retirement trigger: auth/service.py:518 fires on every
  service start and :2551 on create, so a regression test assuming :650 is the
  only path is narrower than the defect.

Struck rather than deleted, so the wrong version stays visible to the next
reader. The item stays open and its severity is unchanged.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 280 items / 205 open / 75 closed, unchanged.

* backlog: retract my own #1245 narrowing -- it was measured on the wrong route

Forty minutes ago I amended #1245 to say "silent at both ends" was half wrong,
on the ground that a 201 response body carries disabled:true. That measurement
is TRUE and it is about a DIFFERENT ROUTE.

auth_routes.py:656-658 and _user_summary:195 are POST /users -- a create, and
the stacked-admin-name defect's route. #1245 is a RESET defect. The reset route
at auth_routes.py:753 ends at :776 with

    return PasswordResetResponse(temp_password=temp)

no re-read, no _user_summary, no disabled field.

And the stronger reason, which makes "silent at both ends" true in principle
rather than by omission: admin_reset_password (auth/service.py:2717) does not
call _retire_superseded_bootstrap at all. Its only three call sites are :518,
:651 and :2551 -- positive control, the probe resolves real sites. So the re-arm
is LATENT: at the moment the reset returns nothing has happened yet, there is no
disabled state to report, and a re-read there would correctly say disabled:false.

"Silent at both ends" therefore STANDS for this item. The create-path fact is
real and belongs in the stacked-name item instead.

Caught by the builder holding #1245, which re-measured rather than accepting a
correction from the dispatcher. That is the second time today the two of us have
hit the same shape in opposite directions: an instrument answering truthfully
about the neighbouring question. Kept struck rather than deleted, because the
two routes are adjacent in one file and the wrong version is what a later reader
would re-derive.

One correction from that pass DOES stand and is retained: login is not the sole
retirement trigger (:518 on service start, :2551 on create), so a test assuming
:651 is the only path is narrower than the defect. Recorded as already handled.

SECOND DEFECT IN THIS SAME EDIT, caught by the before/after control and fixed
before commit: the retraction was first written with a closed-alphabet character
opening a blockquote in the item body. parse_items read it as a status banner
and #1245 flipped to CLOSED -- 280/204/76 against an expected 280/205/75. A live
item under active build, removed from the queue by a prose edit. The rule is
absolute for exactly this reason: no banner-alphabet character in an item body,
any position. Say the word.

Controls after the fix: 280 items / 205 open / 75 closed, #1245 is_open True,
zero closed-alphabet characters in the body, backlog_status_check green with
every item declaring exactly one status.

* backlog: close #1240, record #1241 as partial -- the ledger edit PR #379 cannot make itself

PR #379 is red on a required check that says a PR implementing BACKLOG #N must
update BACKLOG.md. The owner's 2026-08-13 ruling says a builder may resolve merge
conflicts but may not author ledger content. Those two are mutually unsatisfiable
for a compliant builder PR, so the builder correctly withheld the banner and the
PR correctly went red. Authoring is dispatcher and lander only; this supplies the
edit. Neither a bug nor anyone's error -- two correct rules meeting.

#1240 CLOSED. Verified before signing by printing the operands on both refs
rather than counting them, after a count instrument returned 0 on a string the
printed lines visibly contained:

  origin/main   _FHIR_TYPE_RE = re.compile(r"^[A-Za-z]+$")
  PR #379 head  _FHIR_TYPE_RE = re.compile(r"^[A-Za-z]+\Z")

$ -> \Z on the two pattern definitions, call sites unchanged. That is the durable
form: it covers all three call sites at once and cannot be re-broken by a future
caller, where converting the calls to .fullmatch would fix three and leave a
fourth free to reintroduce it. The read-path _reject_control_chars limb was
deliberately not added -- redundant once the gates are strict, and it would
reintroduce duplication that #1239 records as retired.

The item also records that the obvious regression test cannot discriminate:
_resolve_read_url strips, so "Patient/123\n" yields an identical URL before and
after the fix and only "Patient\n/123" flips. Measured by executing the shipped
and patched sources, not argued.

#1241 STAYS OPEN, amended to record partial progress. #379 fixed construction-time
screening plus a wrong-exception-class defect worse than the filed finding --
http.client.InvalidURL derives from HTTPException, not ValueError and not OSError,
so it escaped every except arm in _post including the backstop written for that
case. Still outstanding: transports/dicomweb.py, which the item names, and a
second unscreened url-construction site in FhirLookupExecutor in the same file.
The item's subject is the ASYMMETRY, so one sink screened while a sibling is not
reproduces the very defect being reported. A partial close would be wrong.

Two corrections to #1241's filed text, neither reducing severity: its comparison
clause INVERTS rather than going stale, because the neighbouring path it called
"weaker but at least screening" was removed outright, leaving :431 the only
unencoded interpolation in the file; and its enum rationale is right advice for
the wrong reason, since containment comes from the !r conversion rather than the
enum's closedness.

Controls: parse_items 281 items / 206 open / 75 closed before, 281 / 205 / 76
after -- 0 / -1 / +1, the expected delta for exactly one close and one amendment.
backlog_status_check green, every item declaring exactly one status. Banner
invariant checked per item: #1240 one closed-alphabet character and zero open,
#1241 zero closed and one open.

* backlog: flip #1204 to shipped, and record that #1203 is abandoned rather than free

#1204's banner read OPEN while its own body said "FIXED in the same change" and
its Verdict line said "build (done)". Banner repair, not a change of plan.

Verified at origin/main before signing, with a discriminating control. All four
artifacts ship: scripts/docs/asvs_tally_lint.py, scripts/docs/asvs_tally_baseline.txt,
.github/workflows/asvs-tally-lint.yml, tests/test_asvs_tally_lint.py. A deliberately
impossible path under the same probe returned ABSENT, so the four PRESENTs are
evidence rather than a probe that answers yes to everything.

One defect found while verifying, and it is not what it first looks like.
asvs-tally-lint.yml:3 cites BACKLOG #1203; the item it implements is #1204. The
obvious reading is a typo pointing at an unissued number, and that reading is
wrong in the direction that causes harm: it would send someone to file #1203 as
free.

Measured: "## 1203." appears in neither docs/BACKLOG.md nor
docs/archive/backlog/BACKLOG-CLOSED.md, with "## 1204." resolving in the live
ledger as the positive control. But the allocator record
mefor-coord/alloc/backlog/1203.json EXISTS, titled "Decide how the public engine
repo obtains the private ASVS scorecard for --prove-absences".

So #1203 is ALLOCATED AND NEVER FILED -- abandoned, not free. Numbers are never
reclaimed and holes are free, so the hole costs nothing. What costs something is
that nothing reports an allocated-but-unfiled number, and a live citation makes
it look issued. The :3 correction to #1204 is a one-word fix and rides with
whatever next touches that workflow.

Controls: parse_items 281 items / 205 open / 76 closed before, 281 / 204 / 77
after -- 0 / -1 / +1, the expected delta for one close. backlog_status_check
green, every item declaring exactly one status. Banner invariant on the item
body: one closed-alphabet character, zero open-alphabet.

* docs(adr): ADR 0165 -- a builder PR satisfies the ledger gate with a paired commit

Records a coordination decision that until now existed only in session messages
and a queue file, which is the exact shape this project keeps being bitten by --
a ruling with no artifact behind it.

THE COLLISION. The required check "a PR that implements BACKLOG #N must update
BACKLOG.md" demands a ledger edit in the PR's own diff. The owner's 2026-08-13
authoring ruling forbids a BUILDER to author ledger content, on the property that
a mechanical union cannot invent a disposition but authoring a banner can, and a
seat that can author its own item's banner can turn its own PR green. Composed,
a compliant builder PR cannot pass a required check. Measured live: PR #379 went
red for obeying the ruling.

THE DECISION. The Dispatcher or Lander authors the disposition and the commit
rides on the PR branch. Owner-ruled a1.

THE PART THAT INVERTED ON MEASUREMENT. Reading the gate rather than reasoning
about it: backlog-hygiene.yml:64-98 computes a three-dot diff and passes if the
changed set touches docs/BACKLOG.md or docs/archive/backlog/. It never inspects
authorship. Evaluated against the real cherry-picked head for #379 -- touches_code
1, ledger 1, PASS. So the pattern was in force before it was named, no gate change
was required, and none is pending.

The ledger gate permits the cherry-pick for a non-obvious reason: it iterates
headings added relative to base, and a banner flip or amendment on an item already
on main adds no "## N." heading, so ownership is never consulted and the committing
seat is irrelevant. Holds only for landed items; a PR that FILES an item is a
different shape.

REJECTED, with reasons rather than preferences. (a2) separately-landed plus
cross-branch correlation would undo a deliberate control -- the gate uses three-dot
on purpose and its own comment says two-dot "would pass while enforcing nothing".
(b) a builder carve-out to flip its own banner reopens the self-approval hazard.
(c) as a distinct interim dissolved: it is the same mechanism, so there is no
transition.

RECORDED NEAR-MISS, kept rather than deleted because the wrong version is what a
later reader re-derives: the ruling was briefly written as "(c) is fine until (a)
lands" -- an expiry whose trigger had ALREADY FIRED. It looks like the safe
construction and behaves like the unsafe one, becoming permanent by default while
appearing bounded.

Provenance is split three ways in the ADR because each half is only checkable if
attributed: the collision found by the Lander on #379's red check, the
self-approval property by Builder 2, the gate measurement and the no-build finding
by the Dispatcher, the ruling by the owner.

ADR number allocated atomically to this worktree; index row added in the same
commit, as the ledger gate requires. No engine behaviour changes.

Note for whoever integrates: docs/adr/README.md is an APPEND/APPEND conflict with
claude/builder-seat-playbook-bf2ead, which appends ADR 0164's row at the same tail.
Both rows are additive and disjoint -- take both sides.

* backlog: measure one limb of #1143's research question, and fix a blank-line citation

#1143 asks what identification keyed on the IdP-namespaced subject would actually
require across all three store backends. One limb of that is now measured rather
than left to be re-derived by whoever picks the research up.

MEASURED at origin/main, with a discriminating control:

  UNIQUE index or index naming oidc_issuer / oidc_subject:
    store.py 0    postgres.py 0    sqlserver.py 0
    positive control: "UNIQUE" appears 13 times in store.py, so the probe
    discriminates and the three zeroes are real absences

  column types today:
    postgres.py:531-532   oidc_issuer TEXT, oidc_subject TEXT
    sqlserver.py:1357     oidc_issuer NVARCHAR(MAX) NULL,
                          oidc_subject NVARCHAR(MAX) NULL

So the federated columns exist and carry no uniqueness constraint of any kind. An
(issuer, subject) identity key is therefore not a code-only change: it needs a
unique index on all three backends, and on SQL Server NVARCHAR(MAX) cannot be an
index key column at all, so both columns must first be re-typed to a bounded
NVARCHAR(n). That is a second migration on that backend.

That cost is an INPUT to choosing between the candidate designs rather than a
consequence of having chosen one, which is why it belongs in the item before the
research runs rather than after.

CITATION FIX in the same pass: the banner cites store.py:1590 for users.username.
:1590 is a BLANK LINE; the declaration "username TEXT NOT NULL UNIQUE" is at
:1593. Found independently by two seats, so it is recorded rather than quietly
patched.

Explicitly NOT settled, and stated in the item: the ceremony for the first
federated login of an account that predates federation. That remains the item's
hard question and nothing above touches it. A migration cost informs that
decision; it does not answer it.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 281 items / 204 open / 77 closed, unchanged.

* backlog: audit #1020's directory path -- it widens, and it invalidates one candidate fix

#1020 said "the AD/OIDC-provisioned path in auth/reconcile.py was not audited, so
the finding may narrow to local accounts". Audited now. Wrong file, and it widens
rather than narrows.

Every link opened individually, because a chain of separately-verified links is
not a verified chain:

  authenticate_oidc      auth/service.py:942   -> returns _complete_ad_login :1056
  _complete_ad_login     auth/service.py:1081  -> calls _upsert_ad_user :1107
  _upsert_ad_user        auth/service.py:1209  -> update_user_profile(email=principal.email)
  update_user_profile    store/store.py:7742   -> UPDATE users SET display_name=?, email=?, ...

Both the AD and the OIDC login paths provision through the SAME function,
_upsert_ad_user -- not auth/reconcile.py, which the struck sentence names.
authenticate_oidc returns _complete_ad_login directly, so one provisioning path
serves two providers.

The sharp end is the unconditional write. update_user_profile issues
UPDATE users SET display_name=?, email=? with no conditional and no coalesce, and
_upsert_ad_user calls it on every directory login with whatever the directory
asserted. So a directory-sourced account cannot retain a hand-set address: an
operator who sets one via PATCH /users/{id} has it overwritten at the account
holder's next login. LDAP mail is optional at every layer, so where the directory
asserts nothing the address returns to NULL.

That invalidates one of the item's three candidate fixes. "Add a self-service
email field" does not reach directory-provisioned accounts at all -- whatever the
user sets is overwritten on their next login by the same unconditional write. Any
fix gating on "a privileged account must have a deliverable address" needs a
separate answer for the directory-sourced population, which moves it into the
owner's decision rather than leaving it an implementation detail underneath.

Deliberately NOT re-litigated: "Difficulty 3, no schema change, no migration cost"
may still hold for the local-account half, and nobody has measured it for the
directory half. The amendment says so rather than quietly widening the estimate.

Also removed a pre-existing closed-alphabet character from this item's BODY at the
old :4111. It was not flipping the item -- the counts were identical before and
after -- but the rule is absolute for exactly that reason: position decides
whether it parses as a status banner, and four items were mis-parsed by this class
today. Replaced with the word.

Provenance: the widening was measured by the Builder 2 seat during a blind
re-verification pass; the chain above was re-read link by link here before being
written into the ledger.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 281 items / 204 open / 77 closed, unchanged.

* backlog: record #1020's owner ruling, and correct the fix location it points at

Owner ruled option (b): gate startup on a deliverable channel. Recorded in the
item because until now the ruling existed only in session messages, which is the
failure mode this session has been correcting all evening -- a decision with no
artifact behind it.

The deciding argument is recorded as the reason rather than only the choice:
(b) is the only option that does not rest on an operator action. That is
decisive because update_user_profile issues

    UPDATE users SET display_name=?, email=?

with no conditional and no coalesce, on every directory login (store.py:7742), so
any address a human sets on an AD or OIDC account is overwritten at the account
holder's next login. A fix depending on someone setting an address cannot cover
that population.

The item's stated fix location is wrong and a builder would walk into it. The
text points at __main__.py:2259, but _serve is synchronous and opens no store --
probing its full range for open_store|AuthService|list_users|count_users returns
one hit and it is a comment, and uvicorn.run is at :2827 so the lifespan
bootstrap has not run. The only place the store and the fresh bootstrap admin are
both in hand is the ASGI lifespan at api/app.py:~5852.

Option (c) is recorded as population-limited, NOT defective, and the amendment
says the ruling must not be cited as a finding that it was broken. A self-service
email field works for local accounts and is silently overwritten for directory
ones. The incompleteness was invisible to the operator, which is more useful than
"it was wrong" -- an earlier framing of mine that I withdrew.

Recorded as unpriced rather than carried forward: whether "Difficulty 3, no
schema change, no migration cost" still holds for the directory half. It may hold
for the local half; nobody has measured the directory half. A stale difficulty
estimate silently sets a lane's expectations.

The build is NOT dispatched. The pool is at HOLD NEW WORK / PROTECT AND WRAP, and
a builder taking this would be starting a new item and a new claim, which that
state prohibits. Recording the ruling is work in hand; building it is not.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 281 items / 204 open / 77 closed, unchanged.

* backlog: record #1217 half 1 as built, half 2 outstanding -- the item stays open

PR #383 is red on "a PR that implements BACKLOG #N must update BACKLOG.md". The
builder withheld the banner correctly under the owner's authoring ruling, so the
ledger edit is the dispatcher's. This supplies it. Same shape as #1241 on #379,
and ADR 0165 records why that is the standing pattern.

Half 1, the >=1 floor, is BUILT. Verified on the branch against origin/main
rather than taken from the report:

  origin/main   retry_max_attempts: int | None = 100
  PR #383       retry_max_attempts: int | None = Field(default=100, ge=1)

A configured 0 or negative is now refused at load rather than loading clean and
dead-lettering on the FIRST failure -- the delivery check is
item.attempts >= max_attempts against a post-increment count
(pipeline/wiring_runner.py:5040), so 0 meant give-up-now while reading like
"no limit".

The floor is on the OPERATOR-FACING setting only, and that is deliberate.
RetryPolicy(max_attempts=0) remains a live internal idiom for a permanent
no-retry failure: measured across 5 files, including store.mark_failed call sites
and asserted by tests at tests/test_batch_completion.py:206-208 and
tests/test_postgres_store.py:3109. Constraining the dataclass instead would have
deleted a used mechanism while claiming to add a guard -- the
reads-as-hardening-but-removes-a-control shape.

The item's stated reason for deferring the floor is answered rather than ignored.
It said the floor was documented and not fixed "because a floor changes the
accepted-configuration set". Under section 0 there are zero deployments, so there
is no accepted configuration to break and no migration cost to protect.

STILL OPEN, and it is why the item does not close: whether the retry-forever
posture needs a TOML or env spelling. "", none and null all raise ValidationError,
so that posture is reachable in code-first configuration only. It is a product
question, it was handed back rather than decided, and the item itself says it
should be decided alongside the floor. A closure on #383 would answer it by
omission.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 281 items / 204 open / 77 closed, unchanged, and
#1217 verified still OPEN after the edit.

* backlog: #1242's loss is irreversible, not merely expensive -- and a relay's framing corrected

The ASVS Tracker surfaced this defect via the Liaison as a candidate for a NEW
item. It is not new: it is #1242, filed 2026-08-13, and Builder 2 is building it.
No number was allocated. A duplicate ledger row is not a harmless extra line --
it splits the work and the second number looks unbuilt forever, because the fix
lands under the first.

Two things in the relay were genuinely new. One is recorded here; the other is
deliberately not.

RECORDED: the loss is not recoverable by re-running the derivation. The anchors
most at risk are the D3 backfill's, and their warrant was that two independent
derivations AGREED, measured at two different refs. Those refs have moved. So a
fresh derivation reproduces values without reproducing the agreement that
justified writing them, and that agreement is the whole evidentiary content. The
loss is therefore irreversible rather than expensive, which is why this item
outranks other writer defects rather than being one among them. Nothing in the
item said this.

NOT RECORDED, deliberately: the supporting anchor counts. docs/BACKLOG.md is
public, and a tally over a closed public requirement set is the shape that hands
out coverage by subtraction. The mechanism is fully stated without them -- a
reader with vault access can price it, and a reader without one still knows what
to fix and why. Verified my added lines carry no such figure, with a positive
control proving the scan discriminates.

AND THE RELAY'S FRAMING IS CORRECTED BY THE ITEM'S OWN TEXT. It described the
defect as dropping sym/ctx. #1242 already forbids fixing it that way: the defect
is the handling of UNKNOWN keys, and a fix special-casing those two by name
rebuilds the same trap for the next field added. The item was ahead of the relay
and a builder must follow the item.

The Tracker's core claim was verified here rather than taken: sym and ctx each
return 0 occurrences in scripts/asvs/apply.py at origin/main, positive control
expect returns 2.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 281 items / 204 open / 77 closed, unchanged, and
#1242 verified still OPEN.

* backlog: close #1238 and #1239 on the code, and file #1253 so the closure does not retire the hazard

Builder 2 reported four banners owed. Two of the four -- #1237 and #1204 -- were
already authored on this branch and are invisible to that lane because ledger
authorship and push are held by different seats. That is recorded as a finding in
the episode note; it is not fixed here. The two genuinely owed are written now.

Both closures verified against origin/main rather than against the build report,
because a banner that closes on a report inherits the report's errors.

#1238: _is_contained_name is defined at transports/remotefile.py:97, wired at
:954, and asserted in BOTH polarities at tests/test_remotefile_transport.py:1185
and :1195. The one-polarity case is called out because such a test passes against
a function that refuses everything. posixpath.basename() was not used, per the
owner ruling.

#1239: _has_control_char returns 0 occurrences across messagefoundry/ at
origin/main, so the pair it named is a single. The item's own condition is met.

#1253 exists because closing #1239 there would have been true of the item as
written and false of the hazard it describes. The reporting lane amended its own
closure recommendation to say so -- the predicate is copied more widely today
than when #1239 was filed, partly by the work that resolved it. A repo-wide
re-measure widened that further: the amendment scanned transports/ and found five
sites across four files; across messagefoundry/ it is seven across six, the two
extra being config/codeset_edit.py:305 and config/impact.py:631.

Two exclusions are recorded in the item so a later scan does not re-add them:
rest.py:109 matches a naive grep but is prose in a docstring, and sniff.py:179
tests the same code points through a genuinely different byte-wise predicate that
subtracts an allowlist, so folding it in would change its behaviour.

rest.py:111 strips where the others reject. That is recorded as defensible and
NOT as a second instance of the pattern the owner ruled against in #1238, so the
next reader does not inherit a false lead: stripping CR/LF from a header value
cannot redirect a request, whereas basename() mutates a path into a real and
different target.

#1253 allocated with alloc.ps1, never grepped. parse_items before and after:
281/204/77 -> 282/203/79, matching the predicted delta for two closures plus one
filing, with a control confirming no item carries a stray banner.

* backlog: record that #1234 is not startable from main -- its subject has not landed

Builder 2 refused a restock offer of #1234 by correcting its OWN earlier
recommendation, and verified before claiming rather than after. Re-measured here
rather than taken: at origin/main, require_least_privilege returns 0 hits in any
.py and appears only in this ledger's own prose. Positive control on the same
instrument, require_managed_identity, returns hits across four .py files, so the
scan sees Python fine -- the zero is a fact about the tree, not a broken needle.

The probe this item reports a defect in exists only on the
w3-store-privilege-preflight branch, dormant at that reading.

The amendment is careful not to overrule the item's own "independent of #1008"
paragraph, because that paragraph is right about a different thing. Both halves
hold: the item is not hostage to #1008's POLICY ruling, and it is nonetheless
unbuildable by any lane working from main until that BRANCH lands. Collapsing the
two would either re-gate a code defect behind a demand gate or keep offering work
whose subject does not exist.

This is the same wasted-claim cost as #1253's provenance, one layer deeper: there,
an item was unstartable because the FIX had already landed; here, because the
SUBJECT has not. A banner-driven queue cannot distinguish either case from
startable work, which is why both are now written down where the next dispatcher
reads rather than left in session mail.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 282 items / 203 open / 79 closed, unchanged, and
#1234 verified still OPEN.

* backlog: file #1254 -- a required check is named for its subject, not its assertion

Handed to me by the Liaison to number if I judged it worth one, in the Lander's
framing. It is, because the naive fix is dangerous and nothing currently records
that.

MEASURED INSTANCE: the Windows leg went red under the label
"test (windows-2025, py3.14)", which reads as "the tests failed on Windows". The
tests passed. What failed was the wall-clock gate "Step margin -- both gated
steps" (ci.yml:675). The check answered its own question truthfully and the NAME
described a different one -- the reverse of the shape this project keeps hitting,
where the label is honest and the instrument is not.

The job name is built at ci.yml:42 from the matrix, so all three legs are named
for WHERE they ran and never for WHAT they assert, while holding at least three
independent assertions. Stated as at least three rather than enumerated.

WHY THIS IS NOT A ONE-LINE RENAME, which is the whole reason it needed writing
down: those three strings ARE required contexts. They are listed in
.github/required-contexts.txt, asserted against branch protection by
tests/test_required_contexts.py, and matched BY NAME on the GitHub side. A
required-but-absent context blocks every PR forever, so a rename is one atomic
change across the workflow, the contexts file, that test's pinned count, and the
branch-protection setting, in the order that file's header prescribes.

So the item deliberately does NOT recommend the rename. It prices three options
and names the cheapest first: make the margin gate's failure output say in its
first line that the suite passed and a timing gate fired. That costs nothing and
cannot wedge the repo. The rename is listed third.

Severity carries no deployment axis, but the near-miss is recorded: the
misreading pointed at the wall-clock cap, and #1096's banner already says the
actual fix is #320 and that re-deriving the caps is itself the failure mode.

#1254 allocated with alloc.ps1, never grepped. parse_items before and after:
282/203/79 -> 283/204/79, matching the predicted delta for one filing, with a
control confirming no item carries a stray banner.

* backlog: amend #1235 -- its two named instances are inert, and I dispatched the opposite

Builder 2 refused the starting fact I gave it and measured instead. It was right
and I was backwards.

I told it to start #1235 from #1203 as a CONFIRMED LIVE TRAP, reasoning that an
allocation record with no ledger entry meant the number was free. The record is
what makes the number permanently UNAVAILABLE. Verified here rather than taken:
1203.json and 1231.json both exist in .git/mefor-coord/alloc/backlog/ (claimed
2026-08-09 and 2026-08-12), and alloc.ps1 has no release at all -- :41 "a one-way
door -- claims are never released", :24 "numbers are never reclaimed ... holes are
free, collisions are not". So the item's own text is wrong where it says #1231 was
"allocated and released without being filed", and wrong that the pair is "defused
only by an accident of timing". They are defused by construction.

ONE CORRECTION AGAINST THE REPORT AS WELL, because its reason is weaker than its
conclusion. The argument as relayed rests on the allocation RECORDS existing. Those
live under .git: uncommittable, machine-local, losable without trace. The reason
that survives their loss is structural -- alloc.ps1 issues $observed + 1 (:392,
and :389 under the public floor clamp) and NEVER fills a hole, so a number below
the floor is unreachable whether or not its record still exists. Recording the
registry as the protection would make a sound property look fragile and invite a
guard nothing needs.

The live shape is the other one and the item now says so: a citation to a number
NEVER allocated sits above the floor and will be issued in the normal course. A
detector reading only the ledgers rates the two states identically, which over
docs/ in this repo mis-scores 26 reserved citations as live; of the 6 genuinely
never-allocated tokens there, all six are foreign references, so this repo holds
zero genuine instances. The private-repo population the item was filed against is
not re-measured here and is stated as separate.

The remedy is unchanged and still correct. Only the account of WHY the two named
instances are harmless is corrected.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 283 items / 204 open / 79 closed, unchanged, and
#1235 verified still OPEN.

* backlog: correct #1235 and #1254 -- two of my own committed claims, falsified under adversarial check

I ran six independent skeptics over every claim I committed tonight, against the
SIMULATED POST-MERGE TREE rather than my branch, because my branch was six behind
origin/main and the merge is CLEAN -- git conflicts on concurrent edits, never on
invalidated claims. Four claims held. Two did not. Both failures are my authoring,
not merge drift: all cited files are byte-identical across the merge tree,
origin/main and HEAD.

#1235. The conclusion survives, the reason did not. I had written that the
allocation registry is irrelevant because the mechanism is structural. That is
FALSE for the newest number: the high-water ratchet persists $floor, the maximum
of the OBSERVED set (:205, :214, :215), NOT the number being issued, so after
issuing N it holds N-1. alloc.ps1 only PRINTS the heading, so until it is
committed the sole durable record of N is its own untracked, never-pushed
<n>.json. Lose that and the next run re-issues N. So the registry is exactly what
protects a just-allocated-but-unfiled number -- the state #1203 and #1231 were both
in when allocated.

What actually makes those two unreachable is a CONJUNCTION, now stated as one: the
loop never searches downward (:392, :389, :394), AND the floor is computed from
COMMITTED LEDGER HEADINGS. Measured non-destructively with -ShowFloor: floor 1254,
swept from docs/BACKLOG.md and the closed archive. Those are tracked content on
refs, they survive a fresh clone, and both numbers sit far below them. Recorded the
public-floor clamp as a THIRD, separate guarantee about the output range, with the
caveat that its own anti-lowering ratchet lives in the same untracked directory and
is disarmed on a registry-absent clone.

#1254 cited the margin gate as ci.yml:675. That is a clock MARK
(step_margin.py --mark between, :677). The gate is :765, if: always(), invoking at
:778 and :781. The error is worth recording rather than silently fixing: I opened
:675, found a step whose name NEARLY matched, and adopted it instead of treating
the near-match as the signal the line was wrong. A near-miss terminates the search;
no match would have continued it.

Also corrected in #1254: tests/test_required_contexts.py does NOT call the GitHub
API. It pins the count at :101 and resolves contexts against real workflow job
names at :107; the branch-protection comparison is a HUMAN step in the comment at
:100. The item's central argument is unaffected -- the strings are still required
contexts and a rename still resolves them to no job -- but the evidence now says
what the test does.

The four that held: #1253's seven-sites-across-six-files with both exclusions and
every line number, #1239's zero occurrences, #1238's defined-wired-and-both-test-
polarities, and #1234's zero .py hits with its positive control.

Amendments only, no heading added, so ledger ownership is not consulted.
parse_items unchanged at 283 items / 204 open / 79 closed.

* backlog: file #1255 -- two testpaths ship a top-level conftest each

Diagnosed by the lane whose own commit tripped it, and filed here because the
collision outlives that commit. Seven tests in one file failed in a full run and
passed in isolation, twice. Cause: pyproject sets two testpaths, both directories
contain a conftest.py, neither contains an __init__.py, so both claim the top-level
module name and a bare `import conftest` binds to whichever loaded first.

Verified at origin/main rather than taken from the report: both conftest.py files
present, both __init__.py absent, and a scan for `import conftest` /
`from conftest import` across both trees returns ZERO hits. That zero is why this
is filed as LATENT rather than live -- the collision is real and currently
untripped, so nothing is failing today and the item must not be cited as a current
gap.

The signature is recorded because it mis-attributes itself: the mis-bound import
surfaces as an AttributeError naming a module path from the WRONG package, not as
an ImportError, so it reads as a missing attribute rather than a bad import.

Two things the item forbids, both because a plausible fix is worse than the defect.
Do not import conftest BY PATH -- its body claims a per-process test slot and
registers an atexit unlink, so a second import under another name has side effects.
And do not prove a fix in isolation: isolation is precisely the condition under
which this defect reports success. The proof has to run both testpaths together and
then restore the bare import to confirm the same command fails again.

The house idiom already solves it -- tests/_workflow_contexts.py is imported
package-qualified at tests/_negative_controls.py:35 -- so the scope is to make the
name unambiguous, not to invent a mechanism.

#1255 allocated with alloc.ps1, never grepped. parse_items before and after:
283/204/79 -> 284/205/79, matching the predicted delta for one filing, with a
control confirming no item carries a stray banner.

* backlog: record that PR #382 closed ONE of #1242's four limbs -- the item stays open

I told a builder its engine fix closed this item's mechanism and to claim it. That
was wrong, and this records the correction where the next reader will hit it rather
than in session mail.

#382 merged 2026-08-13 with this item's number in its title and a title that
faithfully describes what it fixed: the payload-only TOP-LEVEL key limb. Verified
at origin/main -- apply.py:97 now walks {**(live or {}), **cell}.items() rather
than the live cell alone. That limb is genuinely closed.

The limb carrying the item's severity is untouched, and the same revision shows
why the union cannot reach it: :98 skips _ORDERED and _SUBTABLES BEFORE the union
at :97 is consulted for them, and evidence entries are re-emitted at :101-105 by
enumerating exactly path, line and expect (absence at :106-110 by exactly pattern,
positive_control, mutation). A key inside a [[cell.evidence]] entry is still
dropped -- which is exactly where the backfill put the affected keys, on evidence
ENTRIES and not on top-level keys. The top-level table-mangling limb also appears
untouched.

I had additionally told that builder to stop measuring the two affected key names
because the item forbids fixing by naming them. The prohibition is real but I
applied it to the wrong activity: the item forbids naming them in a FIX, not
measuring their absence as a SYMPTOM, and their absence from the writer is exactly
the evidence that the sub-table limb still bites.

Recorded as the PARTIAL-MOVE shape, which is the reusable part: a merged PR bearing
an item's number, whose title truthfully describes what it fixed, is the strongest
available signal the item is done. Verify-before-closing is not enough on its own
here -- the verification has to ask WHICH HALF. The item's own proof condition is
the discriminator and is unchanged: put an unknown key INSIDE an evidence entry,
re-render, assert both that it survives and that the guard refuses when it is
deliberately dropped. #382 does not satisfy it, and no test asserting only
top-level carry-through will.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 284 items / 205 open / 79 closed, unchanged, and
#1242 verified still OPEN.

* backlog: record #1242's BASE REQUIREMENT -- the obvious base for limb 4 reverts limb 3

Builder 2 confirmed limb 4 as I described it, then refused to build it and handed
it back with a reason better than the instruction I gave. Recording the reason,
because it is not discoverable from the item and git will not raise it.

Its branch still carried scripts/asvs/apply.py:88 as

    if key in _ORDERED or key in _SUBTABLES or key in cell:

Verified here against that ref rather than taken: the clause is present there and
ABSENT at origin/main. `or key in cell` is precisely what #382 deleted to fix limb
3. So a limb-4 fix authored on that base and merged would carry limb 3's REVERSAL
in the same diff -- no conflict, no marker, every check green, and the item's own
landed fix undone by the commit claiming to extend it.

Git raises nothing here because git conflicts on concurrent edits to the same
lines, never on a stale base re-asserting a clause that was deleted elsewhere. That
is the same family as the clean-merge hazard this session has been working under
all night, arriving from the direction nobody watches: not a doc invalidated by a
merge, but a FIX reverted by an extension of itself.

The item now states the base requirement and a pre-PR check that discriminates:
branch fresh from current origin/main, and confirm `or key in cell` returns zero
hits in the diff's own version of the file before opening a PR.

Also corrected upstream of this, in my own dispatch rather than the ledger: I had
told that lane to stop measuring the two affected key names. Measuring their
absence as a SYMPTOM was always legitimate; only fixing by naming them is
forbidden. It withdrew its acceptance of my earlier "bound lifted" on the grounds
that it had taken it from me without measuring -- correctly.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 284 items / 205 open / 79 closed, unchanged, and
#1242 verified still OPEN.
wshallwshall added a commit that referenced this pull request Aug 14, 2026
…30 on its ruled half (#388)

* backlog: correct #1114's Severity line, which contradicted its own body

The Severity line read "could submit unbounded messages". Four bounds ship ON and
were re-verified at origin/main 96c9a860: DEFAULT_MAX_FRAME_BYTES 16 MiB
(transports/mllp.py:105), DEFAULT_MAX_CONNECTIONS 256 (:106),
DEFAULT_RECEIVE_TIMEOUT 60.0s (:107), and max_file_bytes (transports/file.py:384,
remotefile.py:808). They bound SIZE and CONCURRENCY, not RATE.

The item's own "What holds it short today" paragraph already said exactly that, two
paragraphs above, so the Severity line was contradicting its own item rather than
describing the engine. Corrected to "at an unbounded RATE"; the finding is unchanged
and the item stays open.

The correction runs in the direction that makes the engine look better, which is why
the amendment states it explicitly: a Severity line is the sentence most often quoted
onward without its body.

Amendment only, no heading added, so ledger ownership is not consulted. parse_items
before and after: 277 items / 203 open, unchanged.

* backlog: flip #1237 to shipped -- its fix landed without a ledger edit

PR #372 merged the code as 96c9a860 and did not touch docs/BACKLOG.md, so the
item read "not started" while its fix was on main. Banner repair, not a change
of plan.

Both stated limbs verified at origin/main 96c9a860 rather than inferred from the
PR title:

  Signature -- an AST probe located all three functions, so it was not blind:
  gzip_decompress :101, deflate_decompress :138, zip_decompress :195 each carry
  max_output_bytes keyword-only with NO DEFAULT. That is the construct the item
  asked for, a gate that refuses when the precondition is absent, rather than a
  changed default value, which the parent item #1129 explicitly rules out.

  Tests -- tests/test_compression.py pins it: "Calling a decompressor without
  max_output_bytes is a TypeError, not an unbounded read", with a
  pytest.raises(TypeError) assertion. The re-exported public surface still
  resolves in messagefoundry/__init__.py and parsing/__init__.py.

This closes NO ASVS cell and the amendment says so in the item. The verdict is
the assessor's and the vault scorecard is the record of record; the "before
uncompressing" reading question is unresolved without the pre-pass #1237
deliberately excluded, which remains unfiled and is an owner call.

Controls: parse_items 277 items / 203 open / 74 closed before, 277 / 202 / 75
after -- 0 / -1 / +1, the expected delta for one close. backlog_status_check
green, every item declaring exactly one status. Banner invariant checked on the
item body: one closed-alphabet character, zero open-alphabet characters.

* backlog: strike #1245's false delete clause, and narrow two overclaims

#1245's SCOPE paragraph said the bootstrap account "cannot be renamed
(update_user does not rename) or deleted, so it persists as a permanently
disabled row". The rename half is correct. The delete half is FALSE.

Reproduced end to end by a second session: DELETE /users/<bootstrap admin>
returns 200 {'detail': 'deleted'}. Confirmed here independently from the code --
BOOTSTRAP_USERNAME appears 0 times in messagefoundry/api/auth_routes.py, with a
positive control of 7 occurrences in auth/service.py so the probe discriminates.
No delete-time guard names the bootstrap account. The only guard on that route
is is_last_enabled_admin (auth_routes.py:714), which skips disabled users, and
retirement is what disables this one.

Why the correction matters more than the fact: "persists as a permanently
disabled row" is what makes this read as an availability defect with no exit.
There is an exit and it is destructive. A fix must not rest on the row being
undeletable, and a reader checking this paragraph would otherwise re-conclude a
related defect is unreachable and close it as impossible.

Two narrower corrections in the same pass, both REDUCING what the item claims:

  "Silent at both ends" is half wrong. The 201 response body does carry
  disabled:true (auth_routes.py:656-658 re-reads after retirement, _user_summary
  sets it at :195). It is silent at the login end only, via the generic 401 that
  is deliberately indistinguishable from a wrong password.

  Login is not the sole retirement trigger: auth/service.py:518 fires on every
  service start and :2551 on create, so a regression test assuming :650 is the
  only path is narrower than the defect.

Struck rather than deleted, so the wrong version stays visible to the next
reader. The item stays open and its severity is unchanged.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 280 items / 205 open / 75 closed, unchanged.

* backlog: retract my own #1245 narrowing -- it was measured on the wrong route

Forty minutes ago I amended #1245 to say "silent at both ends" was half wrong,
on the ground that a 201 response body carries disabled:true. That measurement
is TRUE and it is about a DIFFERENT ROUTE.

auth_routes.py:656-658 and _user_summary:195 are POST /users -- a create, and
the stacked-admin-name defect's route. #1245 is a RESET defect. The reset route
at auth_routes.py:753 ends at :776 with

    return PasswordResetResponse(temp_password=temp)

no re-read, no _user_summary, no disabled field.

And the stronger reason, which makes "silent at both ends" true in principle
rather than by omission: admin_reset_password (auth/service.py:2717) does not
call _retire_superseded_bootstrap at all. Its only three call sites are :518,
:651 and :2551 -- positive control, the probe resolves real sites. So the re-arm
is LATENT: at the moment the reset returns nothing has happened yet, there is no
disabled state to report, and a re-read there would correctly say disabled:false.

"Silent at both ends" therefore STANDS for this item. The create-path fact is
real and belongs in the stacked-name item instead.

Caught by the builder holding #1245, which re-measured rather than accepting a
correction from the dispatcher. That is the second time today the two of us have
hit the same shape in opposite directions: an instrument answering truthfully
about the neighbouring question. Kept struck rather than deleted, because the
two routes are adjacent in one file and the wrong version is what a later reader
would re-derive.

One correction from that pass DOES stand and is retained: login is not the sole
retirement trigger (:518 on service start, :2551 on create), so a test assuming
:651 is the only path is narrower than the defect. Recorded as already handled.

SECOND DEFECT IN THIS SAME EDIT, caught by the before/after control and fixed
before commit: the retraction was first written with a closed-alphabet character
opening a blockquote in the item body. parse_items read it as a status banner
and #1245 flipped to CLOSED -- 280/204/76 against an expected 280/205/75. A live
item under active build, removed from the queue by a prose edit. The rule is
absolute for exactly this reason: no banner-alphabet character in an item body,
any position. Say the word.

Controls after the fix: 280 items / 205 open / 75 closed, #1245 is_open True,
zero closed-alphabet characters in the body, backlog_status_check green with
every item declaring exactly one status.

* backlog: close #1240, record #1241 as partial -- the ledger edit PR #379 cannot make itself

PR #379 is red on a required check that says a PR implementing BACKLOG #N must
update BACKLOG.md. The owner's 2026-08-13 ruling says a builder may resolve merge
conflicts but may not author ledger content. Those two are mutually unsatisfiable
for a compliant builder PR, so the builder correctly withheld the banner and the
PR correctly went red. Authoring is dispatcher and lander only; this supplies the
edit. Neither a bug nor anyone's error -- two correct rules meeting.

#1240 CLOSED. Verified before signing by printing the operands on both refs
rather than counting them, after a count instrument returned 0 on a string the
printed lines visibly contained:

  origin/main   _FHIR_TYPE_RE = re.compile(r"^[A-Za-z]+$")
  PR #379 head  _FHIR_TYPE_RE = re.compile(r"^[A-Za-z]+\Z")

$ -> \Z on the two pattern definitions, call sites unchanged. That is the durable
form: it covers all three call sites at once and cannot be re-broken by a future
caller, where converting the calls to .fullmatch would fix three and leave a
fourth free to reintroduce it. The read-path _reject_control_chars limb was
deliberately not added -- redundant once the gates are strict, and it would
reintroduce duplication that #1239 records as retired.

The item also records that the obvious regression test cannot discriminate:
_resolve_read_url strips, so "Patient/123\n" yields an identical URL before and
after the fix and only "Patient\n/123" flips. Measured by executing the shipped
and patched sources, not argued.

#1241 STAYS OPEN, amended to record partial progress. #379 fixed construction-time
screening plus a wrong-exception-class defect worse than the filed finding --
http.client.InvalidURL derives from HTTPException, not ValueError and not OSError,
so it escaped every except arm in _post including the backstop written for that
case. Still outstanding: transports/dicomweb.py, which the item names, and a
second unscreened url-construction site in FhirLookupExecutor in the same file.
The item's subject is the ASYMMETRY, so one sink screened while a sibling is not
reproduces the very defect being reported. A partial close would be wrong.

Two corrections to #1241's filed text, neither reducing severity: its comparison
clause INVERTS rather than going stale, because the neighbouring path it called
"weaker but at least screening" was removed outright, leaving :431 the only
unencoded interpolation in the file; and its enum rationale is right advice for
the wrong reason, since containment comes from the !r conversion rather than the
enum's closedness.

Controls: parse_items 281 items / 206 open / 75 closed before, 281 / 205 / 76
after -- 0 / -1 / +1, the expected delta for exactly one close and one amendment.
backlog_status_check green, every item declaring exactly one status. Banner
invariant checked per item: #1240 one closed-alphabet character and zero open,
#1241 zero closed and one open.

* backlog: flip #1204 to shipped, and record that #1203 is abandoned rather than free

#1204's banner read OPEN while its own body said "FIXED in the same change" and
its Verdict line said "build (done)". Banner repair, not a change of plan.

Verified at origin/main before signing, with a discriminating control. All four
artifacts ship: scripts/docs/asvs_tally_lint.py, scripts/docs/asvs_tally_baseline.txt,
.github/workflows/asvs-tally-lint.yml, tests/test_asvs_tally_lint.py. A deliberately
impossible path under the same probe returned ABSENT, so the four PRESENTs are
evidence rather than a probe that answers yes to everything.

One defect found while verifying, and it is not what it first looks like.
asvs-tally-lint.yml:3 cites BACKLOG #1203; the item it implements is #1204. The
obvious reading is a typo pointing at an unissued number, and that reading is
wrong in the direction that causes harm: it would send someone to file #1203 as
free.

Measured: "## 1203." appears in neither docs/BACKLOG.md nor
docs/archive/backlog/BACKLOG-CLOSED.md, with "## 1204." resolving in the live
ledger as the positive control. But the allocator record
mefor-coord/alloc/backlog/1203.json EXISTS, titled "Decide how the public engine
repo obtains the private ASVS scorecard for --prove-absences".

So #1203 is ALLOCATED AND NEVER FILED -- abandoned, not free. Numbers are never
reclaimed and holes are free, so the hole costs nothing. What costs something is
that nothing reports an allocated-but-unfiled number, and a live citation makes
it look issued. The :3 correction to #1204 is a one-word fix and rides with
whatever next touches that workflow.

Controls: parse_items 281 items / 205 open / 76 closed before, 281 / 204 / 77
after -- 0 / -1 / +1, the expected delta for one close. backlog_status_check
green, every item declaring exactly one status. Banner invariant on the item
body: one closed-alphabet character, zero open-alphabet.

* docs(adr): ADR 0165 -- a builder PR satisfies the ledger gate with a paired commit

Records a coordination decision that until now existed only in session messages
and a queue file, which is the exact shape this project keeps being bitten by --
a ruling with no artifact behind it.

THE COLLISION. The required check "a PR that implements BACKLOG #N must update
BACKLOG.md" demands a ledger edit in the PR's own diff. The owner's 2026-08-13
authoring ruling forbids a BUILDER to author ledger content, on the property that
a mechanical union cannot invent a disposition but authoring a banner can, and a
seat that can author its own item's banner can turn its own PR green. Composed,
a compliant builder PR cannot pass a required check. Measured live: PR #379 went
red for obeying the ruling.

THE DECISION. The Dispatcher or Lander authors the disposition and the commit
rides on the PR branch. Owner-ruled a1.

THE PART THAT INVERTED ON MEASUREMENT. Reading the gate rather than reasoning
about it: backlog-hygiene.yml:64-98 computes a three-dot diff and passes if the
changed set touches docs/BACKLOG.md or docs/archive/backlog/. It never inspects
authorship. Evaluated against the real cherry-picked head for #379 -- touches_code
1, ledger 1, PASS. So the pattern was in force before it was named, no gate change
was required, and none is pending.

The ledger gate permits the cherry-pick for a non-obvious reason: it iterates
headings added relative to base, and a banner flip or amendment on an item already
on main adds no "## N." heading, so ownership is never consulted and the committing
seat is irrelevant. Holds only for landed items; a PR that FILES an item is a
different shape.

REJECTED, with reasons rather than preferences. (a2) separately-landed plus
cross-branch correlation would undo a deliberate control -- the gate uses three-dot
on purpose and its own comment says two-dot "would pass while enforcing nothing".
(b) a builder carve-out to flip its own banner reopens the self-approval hazard.
(c) as a distinct interim dissolved: it is the same mechanism, so there is no
transition.

RECORDED NEAR-MISS, kept rather than deleted because the wrong version is what a
later reader re-derives: the ruling was briefly written as "(c) is fine until (a)
lands" -- an expiry whose trigger had ALREADY FIRED. It looks like the safe
construction and behaves like the unsafe one, becoming permanent by default while
appearing bounded.

Provenance is split three ways in the ADR because each half is only checkable if
attributed: the collision found by the Lander on #379's red check, the
self-approval property by Builder 2, the gate measurement and the no-build finding
by the Dispatcher, the ruling by the owner.

ADR number allocated atomically to this worktree; index row added in the same
commit, as the ledger gate requires. No engine behaviour changes.

Note for whoever integrates: docs/adr/README.md is an APPEND/APPEND conflict with
claude/builder-seat-playbook-bf2ead, which appends ADR 0164's row at the same tail.
Both rows are additive and disjoint -- take both sides.

* backlog: measure one limb of #1143's research question, and fix a blank-line citation

#1143 asks what identification keyed on the IdP-namespaced subject would actually
require across all three store backends. One limb of that is now measured rather
than left to be re-derived by whoever picks the research up.

MEASURED at origin/main, with a discriminating control:

  UNIQUE index or index naming oidc_issuer / oidc_subject:
    store.py 0    postgres.py 0    sqlserver.py 0
    positive control: "UNIQUE" appears 13 times in store.py, so the probe
    discriminates and the three zeroes are real absences

  column types today:
    postgres.py:531-532   oidc_issuer TEXT, oidc_subject TEXT
    sqlserver.py:1357     oidc_issuer NVARCHAR(MAX) NULL,
                          oidc_subject NVARCHAR(MAX) NULL

So the federated columns exist and carry no uniqueness constraint of any kind. An
(issuer, subject) identity key is therefore not a code-only change: it needs a
unique index on all three backends, and on SQL Server NVARCHAR(MAX) cannot be an
index key column at all, so both columns must first be re-typed to a bounded
NVARCHAR(n). That is a second migration on that backend.

That cost is an INPUT to choosing between the candidate designs rather than a
consequence of having chosen one, which is why it belongs in the item before the
research runs rather than after.

CITATION FIX in the same pass: the banner cites store.py:1590 for users.username.
:1590 is a BLANK LINE; the declaration "username TEXT NOT NULL UNIQUE" is at
:1593. Found independently by two seats, so it is recorded rather than quietly
patched.

Explicitly NOT settled, and stated in the item: the ceremony for the first
federated login of an account that predates federation. That remains the item's
hard question and nothing above touches it. A migration cost informs that
decision; it does not answer it.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 281 items / 204 open / 77 closed, unchanged.

* backlog: audit #1020's directory path -- it widens, and it invalidates one candidate fix

#1020 said "the AD/OIDC-provisioned path in auth/reconcile.py was not audited, so
the finding may narrow to local accounts". Audited now. Wrong file, and it widens
rather than narrows.

Every link opened individually, because a chain of separately-verified links is
not a verified chain:

  authenticate_oidc      auth/service.py:942   -> returns _complete_ad_login :1056
  _complete_ad_login     auth/service.py:1081  -> calls _upsert_ad_user :1107
  _upsert_ad_user        auth/service.py:1209  -> update_user_profile(email=principal.email)
  update_user_profile    store/store.py:7742   -> UPDATE users SET display_name=?, email=?, ...

Both the AD and the OIDC login paths provision through the SAME function,
_upsert_ad_user -- not auth/reconcile.py, which the struck sentence names.
authenticate_oidc returns _complete_ad_login directly, so one provisioning path
serves two providers.

The sharp end is the unconditional write. update_user_profile issues
UPDATE users SET display_name=?, email=? with no conditional and no coalesce, and
_upsert_ad_user calls it on every directory login with whatever the directory
asserted. So a directory-sourced account cannot retain a hand-set address: an
operator who sets one via PATCH /users/{id} has it overwritten at the account
holder's next login. LDAP mail is optional at every layer, so where the directory
asserts nothing the address returns to NULL.

That invalidates one of the item's three candidate fixes. "Add a self-service
email field" does not reach directory-provisioned accounts at all -- whatever the
user sets is overwritten on their next login by the same unconditional write. Any
fix gating on "a privileged account must have a deliverable address" needs a
separate answer for the directory-sourced population, which moves it into the
owner's decision rather than leaving it an implementation detail underneath.

Deliberately NOT re-litigated: "Difficulty 3, no schema change, no migration cost"
may still hold for the local-account half, and nobody has measured it for the
directory half. The amendment says so rather than quietly widening the estimate.

Also removed a pre-existing closed-alphabet character from this item's BODY at the
old :4111. It was not flipping the item -- the counts were identical before and
after -- but the rule is absolute for exactly that reason: position decides
whether it parses as a status banner, and four items were mis-parsed by this class
today. Replaced with the word.

Provenance: the widening was measured by the Builder 2 seat during a blind
re-verification pass; the chain above was re-read link by link here before being
written into the ledger.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 281 items / 204 open / 77 closed, unchanged.

* backlog: record #1020's owner ruling, and correct the fix location it points at

Owner ruled option (b): gate startup on a deliverable channel. Recorded in the
item because until now the ruling existed only in session messages, which is the
failure mode this session has been correcting all evening -- a decision with no
artifact behind it.

The deciding argument is recorded as the reason rather than only the choice:
(b) is the only option that does not rest on an operator action. That is
decisive because update_user_profile issues

    UPDATE users SET display_name=?, email=?

with no conditional and no coalesce, on every directory login (store.py:7742), so
any address a human sets on an AD or OIDC account is overwritten at the account
holder's next login. A fix depending on someone setting an address cannot cover
that population.

The item's stated fix location is wrong and a builder would walk into it. The
text points at __main__.py:2259, but _serve is synchronous and opens no store --
probing its full range for open_store|AuthService|list_users|count_users returns
one hit and it is a comment, and uvicorn.run is at :2827 so the lifespan
bootstrap has not run. The only place the store and the fresh bootstrap admin are
both in hand is the ASGI lifespan at api/app.py:~5852.

Option (c) is recorded as population-limited, NOT defective, and the amendment
says the ruling must not be cited as a finding that it was broken. A self-service
email field works for local accounts and is silently overwritten for directory
ones. The incompleteness was invisible to the operator, which is more useful than
"it was wrong" -- an earlier framing of mine that I withdrew.

Recorded as unpriced rather than carried forward: whether "Difficulty 3, no
schema change, no migration cost" still holds for the directory half. It may hold
for the local half; nobody has measured the directory half. A stale difficulty
estimate silently sets a lane's expectations.

The build is NOT dispatched. The pool is at HOLD NEW WORK / PROTECT AND WRAP, and
a builder taking this would be starting a new item and a new claim, which that
state prohibits. Recording the ruling is work in hand; building it is not.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 281 items / 204 open / 77 closed, unchanged.

* backlog: record #1217 half 1 as built, half 2 outstanding -- the item stays open

PR #383 is red on "a PR that implements BACKLOG #N must update BACKLOG.md". The
builder withheld the banner correctly under the owner's authoring ruling, so the
ledger edit is the dispatcher's. This supplies it. Same shape as #1241 on #379,
and ADR 0165 records why that is the standing pattern.

Half 1, the >=1 floor, is BUILT. Verified on the branch against origin/main
rather than taken from the report:

  origin/main   retry_max_attempts: int | None = 100
  PR #383       retry_max_attempts: int | None = Field(default=100, ge=1)

A configured 0 or negative is now refused at load rather than loading clean and
dead-lettering on the FIRST failure -- the delivery check is
item.attempts >= max_attempts against a post-increment count
(pipeline/wiring_runner.py:5040), so 0 meant give-up-now while reading like
"no limit".

The floor is on the OPERATOR-FACING setting only, and that is deliberate.
RetryPolicy(max_attempts=0) remains a live internal idiom for a permanent
no-retry failure: measured across 5 files, including store.mark_failed call sites
and asserted by tests at tests/test_batch_completion.py:206-208 and
tests/test_postgres_store.py:3109. Constraining the dataclass instead would have
deleted a used mechanism while claiming to add a guard -- the
reads-as-hardening-but-removes-a-control shape.

The item's stated reason for deferring the floor is answered rather than ignored.
It said the floor was documented and not fixed "because a floor changes the
accepted-configuration set". Under section 0 there are zero deployments, so there
is no accepted configuration to break and no migration cost to protect.

STILL OPEN, and it is why the item does not close: whether the retry-forever
posture needs a TOML or env spelling. "", none and null all raise ValidationError,
so that posture is reachable in code-first configuration only. It is a product
question, it was handed back rather than decided, and the item itself says it
should be decided alongside the floor. A closure on #383 would answer it by
omission.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 281 items / 204 open / 77 closed, unchanged, and
#1217 verified still OPEN after the edit.

* backlog: #1242's loss is irreversible, not merely expensive -- and a relay's framing corrected

The ASVS Tracker surfaced this defect via the Liaison as a candidate for a NEW
item. It is not new: it is #1242, filed 2026-08-13, and Builder 2 is building it.
No number was allocated. A duplicate ledger row is not a harmless extra line --
it splits the work and the second number looks unbuilt forever, because the fix
lands under the first.

Two things in the relay were genuinely new. One is recorded here; the other is
deliberately not.

RECORDED: the loss is not recoverable by re-running the derivation. The anchors
most at risk are the D3 backfill's, and their warrant was that two independent
derivations AGREED, measured at two different refs. Those refs have moved. So a
fresh derivation reproduces values without reproducing the agreement that
justified writing them, and that agreement is the whole evidentiary content. The
loss is therefore irreversible rather than expensive, which is why this item
outranks other writer defects rather than being one among them. Nothing in the
item said this.

NOT RECORDED, deliberately: the supporting anchor counts. docs/BACKLOG.md is
public, and a tally over a closed public requirement set is the shape that hands
out coverage by subtraction. The mechanism is fully stated without them -- a
reader with vault access can price it, and a reader without one still knows what
to fix and why. Verified my added lines carry no such figure, with a positive
control proving the scan discriminates.

AND THE RELAY'S FRAMING IS CORRECTED BY THE ITEM'S OWN TEXT. It described the
defect as dropping sym/ctx. #1242 already forbids fixing it that way: the defect
is the handling of UNKNOWN keys, and a fix special-casing those two by name
rebuilds the same trap for the next field added. The item was ahead of the relay
and a builder must follow the item.

The Tracker's core claim was verified here rather than taken: sym and ctx each
return 0 occurrences in scripts/asvs/apply.py at origin/main, positive control
expect returns 2.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 281 items / 204 open / 77 closed, unchanged, and
#1242 verified still OPEN.

* backlog: close #1238 and #1239 on the code, and file #1253 so the closure does not retire the hazard

Builder 2 reported four banners owed. Two of the four -- #1237 and #1204 -- were
already authored on this branch and are invisible to that lane because ledger
authorship and push are held by different seats. That is recorded as a finding in
the episode note; it is not fixed here. The two genuinely owed are written now.

Both closures verified against origin/main rather than against the build report,
because a banner that closes on a report inherits the report's errors.

#1238: _is_contained_name is defined at transports/remotefile.py:97, wired at
:954, and asserted in BOTH polarities at tests/test_remotefile_transport.py:1185
and :1195. The one-polarity case is called out because such a test passes against
a function that refuses everything. posixpath.basename() was not used, per the
owner ruling.

#1239: _has_control_char returns 0 occurrences across messagefoundry/ at
origin/main, so the pair it named is a single. The item's own condition is met.

#1253 exists because closing #1239 there would have been true of the item as
written and false of the hazard it describes. The reporting lane amended its own
closure recommendation to say so -- the predicate is copied more widely today
than when #1239 was filed, partly by the work that resolved it. A repo-wide
re-measure widened that further: the amendment scanned transports/ and found five
sites across four files; across messagefoundry/ it is seven across six, the two
extra being config/codeset_edit.py:305 and config/impact.py:631.

Two exclusions are recorded in the item so a later scan does not re-add them:
rest.py:109 matches a naive grep but is prose in a docstring, and sniff.py:179
tests the same code points through a genuinely different byte-wise predicate that
subtracts an allowlist, so folding it in would change its behaviour.

rest.py:111 strips where the others reject. That is recorded as defensible and
NOT as a second instance of the pattern the owner ruled against in #1238, so the
next reader does not inherit a false lead: stripping CR/LF from a header value
cannot redirect a request, whereas basename() mutates a path into a real and
different target.

#1253 allocated with alloc.ps1, never grepped. parse_items before and after:
281/204/77 -> 282/203/79, matching the predicted delta for two closures plus one
filing, with a control confirming no item carries a stray banner.

* backlog: record that #1234 is not startable from main -- its subject has not landed

Builder 2 refused a restock offer of #1234 by correcting its OWN earlier
recommendation, and verified before claiming rather than after. Re-measured here
rather than taken: at origin/main, require_least_privilege returns 0 hits in any
.py and appears only in this ledger's own prose. Positive control on the same
instrument, require_managed_identity, returns hits across four .py files, so the
scan sees Python fine -- the zero is a fact about the tree, not a broken needle.

The probe this item reports a defect in exists only on the
w3-store-privilege-preflight branch, dormant at that reading.

The amendment is careful not to overrule the item's own "independent of #1008"
paragraph, because that paragraph is right about a different thing. Both halves
hold: the item is not hostage to #1008's POLICY ruling, and it is nonetheless
unbuildable by any lane working from main until that BRANCH lands. Collapsing the
two would either re-gate a code defect behind a demand gate or keep offering work
whose subject does not exist.

This is the same wasted-claim cost as #1253's provenance, one layer deeper: there,
an item was unstartable because the FIX had already landed; here, because the
SUBJECT has not. A banner-driven queue cannot distinguish either case from
startable work, which is why both are now written down where the next dispatcher
reads rather than left in session mail.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 282 items / 203 open / 79 closed, unchanged, and
#1234 verified still OPEN.

* backlog: file #1254 -- a required check is named for its subject, not its assertion

Handed to me by the Liaison to number if I judged it worth one, in the Lander's
framing. It is, because the naive fix is dangerous and nothing currently records
that.

MEASURED INSTANCE: the Windows leg went red under the label
"test (windows-2025, py3.14)", which reads as "the tests failed on Windows". The
tests passed. What failed was the wall-clock gate "Step margin -- both gated
steps" (ci.yml:675). The check answered its own question truthfully and the NAME
described a different one -- the reverse of the shape this project keeps hitting,
where the label is honest and the instrument is not.

The job name is built at ci.yml:42 from the matrix, so all three legs are named
for WHERE they ran and never for WHAT they assert, while holding at least three
independent assertions. Stated as at least three rather than enumerated.

WHY THIS IS NOT A ONE-LINE RENAME, which is the whole reason it needed writing
down: those three strings ARE required contexts. They are listed in
.github/required-contexts.txt, asserted against branch protection by
tests/test_required_contexts.py, and matched BY NAME on the GitHub side. A
required-but-absent context blocks every PR forever, so a rename is one atomic
change across the workflow, the contexts file, that test's pinned count, and the
branch-protection setting, in the order that file's header prescribes.

So the item deliberately does NOT recommend the rename. It prices three options
and names the cheapest first: make the margin gate's failure output say in its
first line that the suite passed and a timing gate fired. That costs nothing and
cannot wedge the repo. The rename is listed third.

Severity carries no deployment axis, but the near-miss is recorded: the
misreading pointed at the wall-clock cap, and #1096's banner already says the
actual fix is #320 and that re-deriving the caps is itself the failure mode.

#1254 allocated with alloc.ps1, never grepped. parse_items before and after:
282/203/79 -> 283/204/79, matching the predicted delta for one filing, with a
control confirming no item carries a stray banner.

* backlog: amend #1235 -- its two named instances are inert, and I dispatched the opposite

Builder 2 refused the starting fact I gave it and measured instead. It was right
and I was backwards.

I told it to start #1235 from #1203 as a CONFIRMED LIVE TRAP, reasoning that an
allocation record with no ledger entry meant the number was free. The record is
what makes the number permanently UNAVAILABLE. Verified here rather than taken:
1203.json and 1231.json both exist in .git/mefor-coord/alloc/backlog/ (claimed
2026-08-09 and 2026-08-12), and alloc.ps1 has no release at all -- :41 "a one-way
door -- claims are never released", :24 "numbers are never reclaimed ... holes are
free, collisions are not". So the item's own text is wrong where it says #1231 was
"allocated and released without being filed", and wrong that the pair is "defused
only by an accident of timing". They are defused by construction.

ONE CORRECTION AGAINST THE REPORT AS WELL, because its reason is weaker than its
conclusion. The argument as relayed rests on the allocation RECORDS existing. Those
live under .git: uncommittable, machine-local, losable without trace. The reason
that survives their loss is structural -- alloc.ps1 issues $observed + 1 (:392,
and :389 under the public floor clamp) and NEVER fills a hole, so a number below
the floor is unreachable whether or not its record still exists. Recording the
registry as the protection would make a sound property look fragile and invite a
guard nothing needs.

The live shape is the other one and the item now says so: a citation to a number
NEVER allocated sits above the floor and will be issued in the normal course. A
detector reading only the ledgers rates the two states identically, which over
docs/ in this repo mis-scores 26 reserved citations as live; of the 6 genuinely
never-allocated tokens there, all six are foreign references, so this repo holds
zero genuine instances. The private-repo population the item was filed against is
not re-measured here and is stated as separate.

The remedy is unchanged and still correct. Only the account of WHY the two named
instances are harmless is corrected.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 283 items / 204 open / 79 closed, unchanged, and
#1235 verified still OPEN.

* backlog: correct #1235 and #1254 -- two of my own committed claims, falsified under adversarial check

I ran six independent skeptics over every claim I committed tonight, against the
SIMULATED POST-MERGE TREE rather than my branch, because my branch was six behind
origin/main and the merge is CLEAN -- git conflicts on concurrent edits, never on
invalidated claims. Four claims held. Two did not. Both failures are my authoring,
not merge drift: all cited files are byte-identical across the merge tree,
origin/main and HEAD.

#1235. The conclusion survives, the reason did not. I had written that the
allocation registry is irrelevant because the mechanism is structural. That is
FALSE for the newest number: the high-water ratchet persists $floor, the maximum
of the OBSERVED set (:205, :214, :215), NOT the number being issued, so after
issuing N it holds N-1. alloc.ps1 only PRINTS the heading, so until it is
committed the sole durable record of N is its own untracked, never-pushed
<n>.json. Lose that and the next run re-issues N. So the registry is exactly what
protects a just-allocated-but-unfiled number -- the state #1203 and #1231 were both
in when allocated.

What actually makes those two unreachable is a CONJUNCTION, now stated as one: the
loop never searches downward (:392, :389, :394), AND the floor is computed from
COMMITTED LEDGER HEADINGS. Measured non-destructively with -ShowFloor: floor 1254,
swept from docs/BACKLOG.md and the closed archive. Those are tracked content on
refs, they survive a fresh clone, and both numbers sit far below them. Recorded the
public-floor clamp as a THIRD, separate guarantee about the output range, with the
caveat that its own anti-lowering ratchet lives in the same untracked directory and
is disarmed on a registry-absent clone.

#1254 cited the margin gate as ci.yml:675. That is a clock MARK
(step_margin.py --mark between, :677). The gate is :765, if: always(), invoking at
:778 and :781. The error is worth recording rather than silently fixing: I opened
:675, found a step whose name NEARLY matched, and adopted it instead of treating
the near-match as the signal the line was wrong. A near-miss terminates the search;
no match would have continued it.

Also corrected in #1254: tests/test_required_contexts.py does NOT call the GitHub
API. It pins the count at :101 and resolves contexts against real workflow job
names at :107; the branch-protection comparison is a HUMAN step in the comment at
:100. The item's central argument is unaffected -- the strings are still required
contexts and a rename still resolves them to no job -- but the evidence now says
what the test does.

The four that held: #1253's seven-sites-across-six-files with both exclusions and
every line number, #1239's zero occurrences, #1238's defined-wired-and-both-test-
polarities, and #1234's zero .py hits with its positive control.

Amendments only, no heading added, so ledger ownership is not consulted.
parse_items unchanged at 283 items / 204 open / 79 closed.

* backlog: file #1255 -- two testpaths ship a top-level conftest each

Diagnosed by the lane whose own commit tripped it, and filed here because the
collision outlives that commit. Seven tests in one file failed in a full run and
passed in isolation, twice. Cause: pyproject sets two testpaths, both directories
contain a conftest.py, neither contains an __init__.py, so both claim the top-level
module name and a bare `import conftest` binds to whichever loaded first.

Verified at origin/main rather than taken from the report: both conftest.py files
present, both __init__.py absent, and a scan for `import conftest` /
`from conftest import` across both trees returns ZERO hits. That zero is why this
is filed as LATENT rather than live -- the collision is real and currently
untripped, so nothing is failing today and the item must not be cited as a current
gap.

The signature is recorded because it mis-attributes itself: the mis-bound import
surfaces as an AttributeError naming a module path from the WRONG package, not as
an ImportError, so it reads as a missing attribute rather than a bad import.

Two things the item forbids, both because a plausible fix is worse than the defect.
Do not import conftest BY PATH -- its body claims a per-process test slot and
registers an atexit unlink, so a second import under another name has side effects.
And do not prove a fix in isolation: isolation is precisely the condition under
which this defect reports success. The proof has to run both testpaths together and
then restore the bare import to confirm the same command fails again.

The house idiom already solves it -- tests/_workflow_contexts.py is imported
package-qualified at tests/_negative_controls.py:35 -- so the scope is to make the
name unambiguous, not to invent a mechanism.

#1255 allocated with alloc.ps1, never grepped. parse_items before and after:
283/204/79 -> 284/205/79, matching the predicted delta for one filing, with a
control confirming no item carries a stray banner.

* backlog: record that PR #382 closed ONE of #1242's four limbs -- the item stays open

I told a builder its engine fix closed this item's mechanism and to claim it. That
was wrong, and this records the correction where the next reader will hit it rather
than in session mail.

#382 merged 2026-08-13 with this item's number in its title and a title that
faithfully describes what it fixed: the payload-only TOP-LEVEL key limb. Verified
at origin/main -- apply.py:97 now walks {**(live or {}), **cell}.items() rather
than the live cell alone. That limb is genuinely closed.

The limb carrying the item's severity is untouched, and the same revision shows
why the union cannot reach it: :98 skips _ORDERED and _SUBTABLES BEFORE the union
at :97 is consulted for them, and evidence entries are re-emitted at :101-105 by
enumerating exactly path, line and expect (absence at :106-110 by exactly pattern,
positive_control, mutation). A key inside a [[cell.evidence]] entry is still
dropped -- which is exactly where the backfill put the affected keys, on evidence
ENTRIES and not on top-level keys. The top-level table-mangling limb also appears
untouched.

I had additionally told that builder to stop measuring the two affected key names
because the item forbids fixing by naming them. The prohibition is real but I
applied it to the wrong activity: the item forbids naming them in a FIX, not
measuring their absence as a SYMPTOM, and their absence from the writer is exactly
the evidence that the sub-table limb still bites.

Recorded as the PARTIAL-MOVE shape, which is the reusable part: a merged PR bearing
an item's number, whose title truthfully describes what it fixed, is the strongest
available signal the item is done. Verify-before-closing is not enough on its own
here -- the verification has to ask WHICH HALF. The item's own proof condition is
the discriminator and is unchanged: put an unknown key INSIDE an evidence entry,
re-render, assert both that it survives and that the guard refuses when it is
deliberately dropped. #382 does not satisfy it, and no test asserting only
top-level carry-through will.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 284 items / 205 open / 79 closed, unchanged, and
#1242 verified still OPEN.

* backlog: record #1242's BASE REQUIREMENT -- the obvious base for limb 4 reverts limb 3

Builder 2 confirmed limb 4 as I described it, then refused to build it and handed
it back with a reason better than the instruction I gave. Recording the reason,
because it is not discoverable from the item and git will not raise it.

Its branch still carried scripts/asvs/apply.py:88 as

    if key in _ORDERED or key in _SUBTABLES or key in cell:

Verified here against that ref rather than taken: the clause is present there and
ABSENT at origin/main. `or key in cell` is precisely what #382 deleted to fix limb
3. So a limb-4 fix authored on that base and merged would carry limb 3's REVERSAL
in the same diff -- no conflict, no marker, every check green, and the item's own
landed fix undone by the commit claiming to extend it.

Git raises nothing here because git conflicts on concurrent edits to the same
lines, never on a stale base re-asserting a clause that was deleted elsewhere. That
is the same family as the clean-merge hazard this session has been working under
all night, arriving from the direction nobody watches: not a doc invalidated by a
merge, but a FIX reverted by an extension of itself.

The item now states the base requirement and a pre-PR check that discriminates:
branch fresh from current origin/main, and confirm `or key in cell` returns zero
hits in the diff's own version of the file before opening a PR.

Also corrected upstream of this, in my own dispatch rather than the ledger: I had
told that lane to stop measuring the two affected key names. Measuring their
absence as a SYMPTOM was always legitimate; only fixing by naming them is
forbidden. It withdrew its acceptance of my earlier "bound lifted" on the grounds
that it had taken it from me without measuring -- correctly.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 284 items / 205 open / 79 closed, unchanged, and
#1242 verified still OPEN.

* backlog: file #1256 -- the federated binding never checks subject exclusivity

Builder 1 concluded #1143's research and handed over the finding rather than a
commit: the defensible-ceremony question dissolves, because every candidate
collapses to trust-on-first-use when no out-of-band proof exists at first
federated login. What it surfaced instead is a separable gap, and that gap is
this item. Content theirs, number mine, per the authoring split.

Verified at origin/main rather than relayed. auth/service.py:1109-1114 compares
user.oidc_subject against the presented subject and refuses on mismatch, then
binds at :1119-1120. The comparison is keyed on the USER, so it is structurally
incapable of noticing a second account carrying the same (issuer, subject). A scan
for a UNIQUE constraint naming the federated columns returns 0 on ALL THREE
backends, so nothing below it closes the gap either.

The item credits the three shipped controls rather than implying an absence: the
hybrid-only refusal, the subject-continuity guard, and the UPN suffix allow-list.
All three constrain which subject may bind to a GIVEN account. None constrains how
many accounts one SUBJECT may bind to. Stating that explicitly is what should stop
this being re-closed as a duplicate of #1015 or #1143.

The difficulty is recorded where it actually lives. SQL Server types the federated
columns NVARCHAR(MAX), and a MAX column cannot be an index key, so a unique index
there needs a RE-TYPE and not merely a constraint -- a cost SQLite and Postgres do
not share. The proof condition requires demonstrating the refusal on every backend
in CI, because the two server store suites SKIP in a local run and a green on
SQLite alone would certify nothing.

#1143 is NOT closed by this and the item says so: whether TOFU is the defensible
ceremony is separable and still open.

#1256 allocated with alloc.ps1, never grepped. parse_items: 285 items / 206 open /
79 closed after, matching the predicted +1/+1/0 for a single filing, with a control
confirming no item carries a stray banner.

* backlog: record that #1020's refusal path hangs in-harness and is unverified under uvicorn

Reported by the lane that built the gate, against its own work, and recorded here
because it changes what "built" means for this item.

The gate refuses by raising during ASGI lifespan STARTUP, and in-harness that
HANGS rather than exiting. Their control is what makes it a measurement rather
than an impression: the sibling non-raising lifespan test passes in 1.13s, raising
from the lifespan BODY exits cleanly, and raising during STARTUP hangs with zero
output. It sits after engine.start() and before the task handles teardown expects,
so the condition is PRE-EXISTING -- the gate is simply the first thing to raise in
that window, which is why this is not filed as a defect in their fix.

What was NOT measured is what uvicorn does there, and uvicorn is the runner that
ships. They said so explicitly rather than letting the harness result stand for
the product, which is the reason this is worth recording at all.

The consequence is ordered plainly in the item: a startup refusal that hangs a
service is STRICTLY WORSE than the mis-report #1020 exists to correct. An operator
can see a wrong readiness answer; they cannot see a process that never finishes
starting. So the banner now carries an explicit bar on closing this on the gate
landing until the refusal is shown to TERMINATE under uvicorn rather than under
the test harness.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items unchanged at 285 items / 206 open / 79 closed, and #1020 verified
still OPEN.

* backlog: #1235 is PARTIAL, not closed -- the detector shipped, the rule did not

I was about to close this on PR #385 landing. An adversarial pass over my own
proposed banner refuted it, and the refutation is right.

What I would have relied on -- do the two files exist at origin/main -- passes
identically whether or not the rule landed and whether or not anything invokes the
detector. It measures PRESENCE, NOT ENFORCEMENT, and it is the same
cannot-fail shape as running a CLI's --help and reading exit 0, which I had
already caught once today in my own #1030 check.

Three measurements, each independently sufficient to block closure:

  the RULE is unwritten. #1235's Scope names its deliverable as "a rule, not a
  sweep", and that guidance appears at origin/main only in the item's own prose --
  zero hits for "unallocated" across CLAUDE.md, docs/LEDGER-GATE.md, CONTRIBUTING,
  scripts/, .github/ and .claude/.

  the DETECTOR is wired into nothing. Repo-wide it is referenced by exactly two
  lines, both inside its own unit test. Not in any workflow, not in
  .pre-commit-config.yaml, not in .mefor-hooks/pre-commit, not in pyproject.toml.

  it EXITS 0 even when it fires. main() ends `return 1 if args.fail else 0` and
  --fail is opt-in and passed by nothing. A planted live-shape citation was
  reported correctly and the process still exited 0. No test runs
  unresolved_citations over the real docs/ tree, so a new dangling citation turns
  nothing red.

The controls are what make those zeros trustworthy: the same wiring grep DOES
resolve three sibling scripts/docs tools that are wired into CI, and the detector
itself discriminates correctly when --fail is supplied. So the absences are facts
about the tree, not a broken pattern or a blind scan.

Banner moves to in-progress rather than closed, and the item now records what
shipped, the two residual limbs (write the rule where authors read it; wire the
detector with --fail or test it against the real tree), and a coverage bound that
survives both -- by its own docstring the detector cannot see the private
companion repository, which is where this item's filed instances live.

Closing on the detector's existence would have recorded an enforced rule where
nothing enforces it. The builder declined to close it for the same reason and left
the ledger half to me.

Amendment only, no heading added, so ledger ownership is not consulted. Both
glyphs are OPEN, so parse_items is unchanged at 285 items / 206 open / 79 closed,
with #1235 verified still OPEN and carrying exactly one banner.

* backlog: close #1230 on the owner-ruled half, with the wiring's own regression gap named

Four proposed banner verdicts went through an adversarial pass before any was
written. Three survived; one did not and was corrected separately (#1235). This is
the survivor, and it survived on evidence stronger than I would have gathered.

#1230 CLOSES because the loud-omission half is built AND WIRED, not merely
present. tests/_extras_probe.py supplies the emitters and tests/conftest.py binds
them to pytest's own hooks at :366 and :370. Verified by RUNNING rather than
reading, in both directions on two real interpreters: extras-complete venv, banner
ABSENT; the venv scripts/worktree/new.ps1 actually builds, banner FIRES in both
surfaces -- including on a bare full-suite run collecting 13,187 tests. The
silent direction was observed rather than assumed, which is what makes the loud
direction mean something.

Scope is exactly the half the owner ruled. new.ps1:232 is unchanged, so options
(a) and (b) -- installing the five extras into every worktree venv -- were
correctly not taken.

CLOSED WITH A NAMED RESIDUAL, because folding it into the closure would reproduce
the defect. Deleting BOTH hook functions from tests/conftest.py leaves
tests/test_incomplete_run_banner.py reporting 7 passed, while a real run goes from
banner-present to banner-gone. The tests drive the probe against a fake reporter;
nothing asserts that pytest invokes it. Repo-wide, pytest_report_header and
pytest_terminal_summary appear ONLY at conftest.py:366 and :370, guarded by no
test, lint or gate. So a conftest refactor can silently delete the mechanism with
every check green -- which is this item's own failure shape, one layer up, inside
the fix for it.

The item closes on the owner-ruled half working. The residual is recorded in the
banner rather than discovered later by whoever the silence next costs.

Amendment plus banner flip, no heading added, so ledger ownership is not
consulted. parse_items 285 items / 206 open / 79 closed -> 285 / 205 / 80,
matching the predicted 0/-1/+1 for a single closure, with a control confirming no
item carries a stray banner and #1235, #1249 and #1030 all verified still OPEN.
wshallwshall added a commit that referenced this pull request Aug 14, 2026
…1250 (#389)

* backlog: correct #1114's Severity line, which contradicted its own body

The Severity line read "could submit unbounded messages". Four bounds ship ON and
were re-verified at origin/main 96c9a860: DEFAULT_MAX_FRAME_BYTES 16 MiB
(transports/mllp.py:105), DEFAULT_MAX_CONNECTIONS 256 (:106),
DEFAULT_RECEIVE_TIMEOUT 60.0s (:107), and max_file_bytes (transports/file.py:384,
remotefile.py:808). They bound SIZE and CONCURRENCY, not RATE.

The item's own "What holds it short today" paragraph already said exactly that, two
paragraphs above, so the Severity line was contradicting its own item rather than
describing the engine. Corrected to "at an unbounded RATE"; the finding is unchanged
and the item stays open.

The correction runs in the direction that makes the engine look better, which is why
the amendment states it explicitly: a Severity line is the sentence most often quoted
onward without its body.

Amendment only, no heading added, so ledger ownership is not consulted. parse_items
before and after: 277 items / 203 open, unchanged.

* backlog: flip #1237 to shipped -- its fix landed without a ledger edit

PR #372 merged the code as 96c9a860 and did not touch docs/BACKLOG.md, so the
item read "not started" while its fix was on main. Banner repair, not a change
of plan.

Both stated limbs verified at origin/main 96c9a860 rather than inferred from the
PR title:

  Signature -- an AST probe located all three functions, so it was not blind:
  gzip_decompress :101, deflate_decompress :138, zip_decompress :195 each carry
  max_output_bytes keyword-only with NO DEFAULT. That is the construct the item
  asked for, a gate that refuses when the precondition is absent, rather than a
  changed default value, which the parent item #1129 explicitly rules out.

  Tests -- tests/test_compression.py pins it: "Calling a decompressor without
  max_output_bytes is a TypeError, not an unbounded read", with a
  pytest.raises(TypeError) assertion. The re-exported public surface still
  resolves in messagefoundry/__init__.py and parsing/__init__.py.

This closes NO ASVS cell and the amendment says so in the item. The verdict is
the assessor's and the vault scorecard is the record of record; the "before
uncompressing" reading question is unresolved without the pre-pass #1237
deliberately excluded, which remains unfiled and is an owner call.

Controls: parse_items 277 items / 203 open / 74 closed before, 277 / 202 / 75
after -- 0 / -1 / +1, the expected delta for one close. backlog_status_check
green, every item declaring exactly one status. Banner invariant checked on the
item body: one closed-alphabet character, zero open-alphabet characters.

* backlog: strike #1245's false delete clause, and narrow two overclaims

#1245's SCOPE paragraph said the bootstrap account "cannot be renamed
(update_user does not rename) or deleted, so it persists as a permanently
disabled row". The rename half is correct. The delete half is FALSE.

Reproduced end to end by a second session: DELETE /users/<bootstrap admin>
returns 200 {'detail': 'deleted'}. Confirmed here independently from the code --
BOOTSTRAP_USERNAME appears 0 times in messagefoundry/api/auth_routes.py, with a
positive control of 7 occurrences in auth/service.py so the probe discriminates.
No delete-time guard names the bootstrap account. The only guard on that route
is is_last_enabled_admin (auth_routes.py:714), which skips disabled users, and
retirement is what disables this one.

Why the correction matters more than the fact: "persists as a permanently
disabled row" is what makes this read as an availability defect with no exit.
There is an exit and it is destructive. A fix must not rest on the row being
undeletable, and a reader checking this paragraph would otherwise re-conclude a
related defect is unreachable and close it as impossible.

Two narrower corrections in the same pass, both REDUCING what the item claims:

  "Silent at both ends" is half wrong. The 201 response body does carry
  disabled:true (auth_routes.py:656-658 re-reads after retirement, _user_summary
  sets it at :195). It is silent at the login end only, via the generic 401 that
  is deliberately indistinguishable from a wrong password.

  Login is not the sole retirement trigger: auth/service.py:518 fires on every
  service start and :2551 on create, so a regression test assuming :650 is the
  only path is narrower than the defect.

Struck rather than deleted, so the wrong version stays visible to the next
reader. The item stays open and its severity is unchanged.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 280 items / 205 open / 75 closed, unchanged.

* backlog: retract my own #1245 narrowing -- it was measured on the wrong route

Forty minutes ago I amended #1245 to say "silent at both ends" was half wrong,
on the ground that a 201 response body carries disabled:true. That measurement
is TRUE and it is about a DIFFERENT ROUTE.

auth_routes.py:656-658 and _user_summary:195 are POST /users -- a create, and
the stacked-admin-name defect's route. #1245 is a RESET defect. The reset route
at auth_routes.py:753 ends at :776 with

    return PasswordResetResponse(temp_password=temp)

no re-read, no _user_summary, no disabled field.

And the stronger reason, which makes "silent at both ends" true in principle
rather than by omission: admin_reset_password (auth/service.py:2717) does not
call _retire_superseded_bootstrap at all. Its only three call sites are :518,
:651 and :2551 -- positive control, the probe resolves real sites. So the re-arm
is LATENT: at the moment the reset returns nothing has happened yet, there is no
disabled state to report, and a re-read there would correctly say disabled:false.

"Silent at both ends" therefore STANDS for this item. The create-path fact is
real and belongs in the stacked-name item instead.

Caught by the builder holding #1245, which re-measured rather than accepting a
correction from the dispatcher. That is the second time today the two of us have
hit the same shape in opposite directions: an instrument answering truthfully
about the neighbouring question. Kept struck rather than deleted, because the
two routes are adjacent in one file and the wrong version is what a later reader
would re-derive.

One correction from that pass DOES stand and is retained: login is not the sole
retirement trigger (:518 on service start, :2551 on create), so a test assuming
:651 is the only path is narrower than the defect. Recorded as already handled.

SECOND DEFECT IN THIS SAME EDIT, caught by the before/after control and fixed
before commit: the retraction was first written with a closed-alphabet character
opening a blockquote in the item body. parse_items read it as a status banner
and #1245 flipped to CLOSED -- 280/204/76 against an expected 280/205/75. A live
item under active build, removed from the queue by a prose edit. The rule is
absolute for exactly this reason: no banner-alphabet character in an item body,
any position. Say the word.

Controls after the fix: 280 items / 205 open / 75 closed, #1245 is_open True,
zero closed-alphabet characters in the body, backlog_status_check green with
every item declaring exactly one status.

* backlog: close #1240, record #1241 as partial -- the ledger edit PR #379 cannot make itself

PR #379 is red on a required check that says a PR implementing BACKLOG #N must
update BACKLOG.md. The owner's 2026-08-13 ruling says a builder may resolve merge
conflicts but may not author ledger content. Those two are mutually unsatisfiable
for a compliant builder PR, so the builder correctly withheld the banner and the
PR correctly went red. Authoring is dispatcher and lander only; this supplies the
edit. Neither a bug nor anyone's error -- two correct rules meeting.

#1240 CLOSED. Verified before signing by printing the operands on both refs
rather than counting them, after a count instrument returned 0 on a string the
printed lines visibly contained:

  origin/main   _FHIR_TYPE_RE = re.compile(r"^[A-Za-z]+$")
  PR #379 head  _FHIR_TYPE_RE = re.compile(r"^[A-Za-z]+\Z")

$ -> \Z on the two pattern definitions, call sites unchanged. That is the durable
form: it covers all three call sites at once and cannot be re-broken by a future
caller, where converting the calls to .fullmatch would fix three and leave a
fourth free to reintroduce it. The read-path _reject_control_chars limb was
deliberately not added -- redundant once the gates are strict, and it would
reintroduce duplication that #1239 records as retired.

The item also records that the obvious regression test cannot discriminate:
_resolve_read_url strips, so "Patient/123\n" yields an identical URL before and
after the fix and only "Patient\n/123" flips. Measured by executing the shipped
and patched sources, not argued.

#1241 STAYS OPEN, amended to record partial progress. #379 fixed construction-time
screening plus a wrong-exception-class defect worse than the filed finding --
http.client.InvalidURL derives from HTTPException, not ValueError and not OSError,
so it escaped every except arm in _post including the backstop written for that
case. Still outstanding: transports/dicomweb.py, which the item names, and a
second unscreened url-construction site in FhirLookupExecutor in the same file.
The item's subject is the ASYMMETRY, so one sink screened while a sibling is not
reproduces the very defect being reported. A partial close would be wrong.

Two corrections to #1241's filed text, neither reducing severity: its comparison
clause INVERTS rather than going stale, because the neighbouring path it called
"weaker but at least screening" was removed outright, leaving :431 the only
unencoded interpolation in the file; and its enum rationale is right advice for
the wrong reason, since containment comes from the !r conversion rather than the
enum's closedness.

Controls: parse_items 281 items / 206 open / 75 closed before, 281 / 205 / 76
after -- 0 / -1 / +1, the expected delta for exactly one close and one amendment.
backlog_status_check green, every item declaring exactly one status. Banner
invariant checked per item: #1240 one closed-alphabet character and zero open,
#1241 zero closed and one open.

* backlog: flip #1204 to shipped, and record that #1203 is abandoned rather than free

#1204's banner read OPEN while its own body said "FIXED in the same change" and
its Verdict line said "build (done)". Banner repair, not a change of plan.

Verified at origin/main before signing, with a discriminating control. All four
artifacts ship: scripts/docs/asvs_tally_lint.py, scripts/docs/asvs_tally_baseline.txt,
.github/workflows/asvs-tally-lint.yml, tests/test_asvs_tally_lint.py. A deliberately
impossible path under the same probe returned ABSENT, so the four PRESENTs are
evidence rather than a probe that answers yes to everything.

One defect found while verifying, and it is not what it first looks like.
asvs-tally-lint.yml:3 cites BACKLOG #1203; the item it implements is #1204. The
obvious reading is a typo pointing at an unissued number, and that reading is
wrong in the direction that causes harm: it would send someone to file #1203 as
free.

Measured: "## 1203." appears in neither docs/BACKLOG.md nor
docs/archive/backlog/BACKLOG-CLOSED.md, with "## 1204." resolving in the live
ledger as the positive control. But the allocator record
mefor-coord/alloc/backlog/1203.json EXISTS, titled "Decide how the public engine
repo obtains the private ASVS scorecard for --prove-absences".

So #1203 is ALLOCATED AND NEVER FILED -- abandoned, not free. Numbers are never
reclaimed and holes are free, so the hole costs nothing. What costs something is
that nothing reports an allocated-but-unfiled number, and a live citation makes
it look issued. The :3 correction to #1204 is a one-word fix and rides with
whatever next touches that workflow.

Controls: parse_items 281 items / 205 open / 76 closed before, 281 / 204 / 77
after -- 0 / -1 / +1, the expected delta for one close. backlog_status_check
green, every item declaring exactly one status. Banner invariant on the item
body: one closed-alphabet character, zero open-alphabet.

* docs(adr): ADR 0165 -- a builder PR satisfies the ledger gate with a paired commit

Records a coordination decision that until now existed only in session messages
and a queue file, which is the exact shape this project keeps being bitten by --
a ruling with no artifact behind it.

THE COLLISION. The required check "a PR that implements BACKLOG #N must update
BACKLOG.md" demands a ledger edit in the PR's own diff. The owner's 2026-08-13
authoring ruling forbids a BUILDER to author ledger content, on the property that
a mechanical union cannot invent a disposition but authoring a banner can, and a
seat that can author its own item's banner can turn its own PR green. Composed,
a compliant builder PR cannot pass a required check. Measured live: PR #379 went
red for obeying the ruling.

THE DECISION. The Dispatcher or Lander authors the disposition and the commit
rides on the PR branch. Owner-ruled a1.

THE PART THAT INVERTED ON MEASUREMENT. Reading the gate rather than reasoning
about it: backlog-hygiene.yml:64-98 computes a three-dot diff and passes if the
changed set touches docs/BACKLOG.md or docs/archive/backlog/. It never inspects
authorship. Evaluated against the real cherry-picked head for #379 -- touches_code
1, ledger 1, PASS. So the pattern was in force before it was named, no gate change
was required, and none is pending.

The ledger gate permits the cherry-pick for a non-obvious reason: it iterates
headings added relative to base, and a banner flip or amendment on an item already
on main adds no "## N." heading, so ownership is never consulted and the committing
seat is irrelevant. Holds only for landed items; a PR that FILES an item is a
different shape.

REJECTED, with reasons rather than preferences. (a2) separately-landed plus
cross-branch correlation would undo a deliberate control -- the gate uses three-dot
on purpose and its own comment says two-dot "would pass while enforcing nothing".
(b) a builder carve-out to flip its own banner reopens the self-approval hazard.
(c) as a distinct interim dissolved: it is the same mechanism, so there is no
transition.

RECORDED NEAR-MISS, kept rather than deleted because the wrong version is what a
later reader re-derives: the ruling was briefly written as "(c) is fine until (a)
lands" -- an expiry whose trigger had ALREADY FIRED. It looks like the safe
construction and behaves like the unsafe one, becoming permanent by default while
appearing bounded.

Provenance is split three ways in the ADR because each half is only checkable if
attributed: the collision found by the Lander on #379's red check, the
self-approval property by Builder 2, the gate measurement and the no-build finding
by the Dispatcher, the ruling by the owner.

ADR number allocated atomically to this worktree; index row added in the same
commit, as the ledger gate requires. No engine behaviour changes.

Note for whoever integrates: docs/adr/README.md is an APPEND/APPEND conflict with
claude/builder-seat-playbook-bf2ead, which appends ADR 0164's row at the same tail.
Both rows are additive and disjoint -- take both sides.

* backlog: measure one limb of #1143's research question, and fix a blank-line citation

#1143 asks what identification keyed on the IdP-namespaced subject would actually
require across all three store backends. One limb of that is now measured rather
than left to be re-derived by whoever picks the research up.

MEASURED at origin/main, with a discriminating control:

  UNIQUE index or index naming oidc_issuer / oidc_subject:
    store.py 0    postgres.py 0    sqlserver.py 0
    positive control: "UNIQUE" appears 13 times in store.py, so the probe
    discriminates and the three zeroes are real absences

  column types today:
    postgres.py:531-532   oidc_issuer TEXT, oidc_subject TEXT
    sqlserver.py:1357     oidc_issuer NVARCHAR(MAX) NULL,
                          oidc_subject NVARCHAR(MAX) NULL

So the federated columns exist and carry no uniqueness constraint of any kind. An
(issuer, subject) identity key is therefore not a code-only change: it needs a
unique index on all three backends, and on SQL Server NVARCHAR(MAX) cannot be an
index key column at all, so both columns must first be re-typed to a bounded
NVARCHAR(n). That is a second migration on that backend.

That cost is an INPUT to choosing between the candidate designs rather than a
consequence of having chosen one, which is why it belongs in the item before the
research runs rather than after.

CITATION FIX in the same pass: the banner cites store.py:1590 for users.username.
:1590 is a BLANK LINE; the declaration "username TEXT NOT NULL UNIQUE" is at
:1593. Found independently by two seats, so it is recorded rather than quietly
patched.

Explicitly NOT settled, and stated in the item: the ceremony for the first
federated login of an account that predates federation. That remains the item's
hard question and nothing above touches it. A migration cost informs that
decision; it does not answer it.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 281 items / 204 open / 77 closed, unchanged.

* backlog: audit #1020's directory path -- it widens, and it invalidates one candidate fix

#1020 said "the AD/OIDC-provisioned path in auth/reconcile.py was not audited, so
the finding may narrow to local accounts". Audited now. Wrong file, and it widens
rather than narrows.

Every link opened individually, because a chain of separately-verified links is
not a verified chain:

  authenticate_oidc      auth/service.py:942   -> returns _complete_ad_login :1056
  _complete_ad_login     auth/service.py:1081  -> calls _upsert_ad_user :1107
  _upsert_ad_user        auth/service.py:1209  -> update_user_profile(email=principal.email)
  update_user_profile    store/store.py:7742   -> UPDATE users SET display_name=?, email=?, ...

Both the AD and the OIDC login paths provision through the SAME function,
_upsert_ad_user -- not auth/reconcile.py, which the struck sentence names.
authenticate_oidc returns _complete_ad_login directly, so one provisioning path
serves two providers.

The sharp end is the unconditional write. update_user_profile issues
UPDATE users SET display_name=?, email=? with no conditional and no coalesce, and
_upsert_ad_user calls it on every directory login with whatever the directory
asserted. So a directory-sourced account cannot retain a hand-set address: an
operator who sets one via PATCH /users/{id} has it overwritten at the account
holder's next login. LDAP mail is optional at every layer, so where the directory
asserts nothing the address returns to NULL.

That invalidates one of the item's three candidate fixes. "Add a self-service
email field" does not reach directory-provisioned accounts at all -- whatever the
user sets is overwritten on their next login by the same unconditional write. Any
fix gating on "a privileged account must have a deliverable address" needs a
separate answer for the directory-sourced population, which moves it into the
owner's decision rather than leaving it an implementation detail underneath.

Deliberately NOT re-litigated: "Difficulty 3, no schema change, no migration cost"
may still hold for the local-account half, and nobody has measured it for the
directory half. The amendment says so rather than quietly widening the estimate.

Also removed a pre-existing closed-alphabet character from this item's BODY at the
old :4111. It was not flipping the item -- the counts were identical before and
after -- but the rule is absolute for exactly that reason: position decides
whether it parses as a status banner, and four items were mis-parsed by this class
today. Replaced with the word.

Provenance: the widening was measured by the Builder 2 seat during a blind
re-verification pass; the chain above was re-read link by link here before being
written into the ledger.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 281 items / 204 open / 77 closed, unchanged.

* backlog: record #1020's owner ruling, and correct the fix location it points at

Owner ruled option (b): gate startup on a deliverable channel. Recorded in the
item because until now the ruling existed only in session messages, which is the
failure mode this session has been correcting all evening -- a decision with no
artifact behind it.

The deciding argument is recorded as the reason rather than only the choice:
(b) is the only option that does not rest on an operator action. That is
decisive because update_user_profile issues

    UPDATE users SET display_name=?, email=?

with no conditional and no coalesce, on every directory login (store.py:7742), so
any address a human sets on an AD or OIDC account is overwritten at the account
holder's next login. A fix depending on someone setting an address cannot cover
that population.

The item's stated fix location is wrong and a builder would walk into it. The
text points at __main__.py:2259, but _serve is synchronous and opens no store --
probing its full range for open_store|AuthService|list_users|count_users returns
one hit and it is a comment, and uvicorn.run is at :2827 so the lifespan
bootstrap has not run. The only place the store and the fresh bootstrap admin are
both in hand is the ASGI lifespan at api/app.py:~5852.

Option (c) is recorded as population-limited, NOT defective, and the amendment
says the ruling must not be cited as a finding that it was broken. A self-service
email field works for local accounts and is silently overwritten for directory
ones. The incompleteness was invisible to the operator, which is more useful than
"it was wrong" -- an earlier framing of mine that I withdrew.

Recorded as unpriced rather than carried forward: whether "Difficulty 3, no
schema change, no migration cost" still holds for the directory half. It may hold
for the local half; nobody has measured the directory half. A stale difficulty
estimate silently sets a lane's expectations.

The build is NOT dispatched. The pool is at HOLD NEW WORK / PROTECT AND WRAP, and
a builder taking this would be starting a new item and a new claim, which that
state prohibits. Recording the ruling is work in hand; building it is not.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 281 items / 204 open / 77 closed, unchanged.

* backlog: record #1217 half 1 as built, half 2 outstanding -- the item stays open

PR #383 is red on "a PR that implements BACKLOG #N must update BACKLOG.md". The
builder withheld the banner correctly under the owner's authoring ruling, so the
ledger edit is the dispatcher's. This supplies it. Same shape as #1241 on #379,
and ADR 0165 records why that is the standing pattern.

Half 1, the >=1 floor, is BUILT. Verified on the branch against origin/main
rather than taken from the report:

  origin/main   retry_max_attempts: int | None = 100
  PR #383       retry_max_attempts: int | None = Field(default=100, ge=1)

A configured 0 or negative is now refused at load rather than loading clean and
dead-lettering on the FIRST failure -- the delivery check is
item.attempts >= max_attempts against a post-increment count
(pipeline/wiring_runner.py:5040), so 0 meant give-up-now while reading like
"no limit".

The floor is on the OPERATOR-FACING setting only, and that is deliberate.
RetryPolicy(max_attempts=0) remains a live internal idiom for a permanent
no-retry failure: measured across 5 files, including store.mark_failed call sites
and asserted by tests at tests/test_batch_completion.py:206-208 and
tests/test_postgres_store.py:3109. Constraining the dataclass instead would have
deleted a used mechanism while claiming to add a guard -- the
reads-as-hardening-but-removes-a-control shape.

The item's stated reason for deferring the floor is answered rather than ignored.
It said the floor was documented and not fixed "because a floor changes the
accepted-configuration set". Under section 0 there are zero deployments, so there
is no accepted configuration to break and no migration cost to protect.

STILL OPEN, and it is why the item does not close: whether the retry-forever
posture needs a TOML or env spelling. "", none and null all raise ValidationError,
so that posture is reachable in code-first configuration only. It is a product
question, it was handed back rather than decided, and the item itself says it
should be decided alongside the floor. A closure on #383 would answer it by
omission.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 281 items / 204 open / 77 closed, unchanged, and
#1217 verified still OPEN after the edit.

* backlog: #1242's loss is irreversible, not merely expensive -- and a relay's framing corrected

The ASVS Tracker surfaced this defect via the Liaison as a candidate for a NEW
item. It is not new: it is #1242, filed 2026-08-13, and Builder 2 is building it.
No number was allocated. A duplicate ledger row is not a harmless extra line --
it splits the work and the second number looks unbuilt forever, because the fix
lands under the first.

Two things in the relay were genuinely new. One is recorded here; the other is
deliberately not.

RECORDED: the loss is not recoverable by re-running the derivation. The anchors
most at risk are the D3 backfill's, and their warrant was that two independent
derivations AGREED, measured at two different refs. Those refs have moved. So a
fresh derivation reproduces values without reproducing the agreement that
justified writing them, and that agreement is the whole evidentiary content. The
loss is therefore irreversible rather than expensive, which is why this item
outranks other writer defects rather than being one among them. Nothing in the
item said this.

NOT RECORDED, deliberately: the supporting anchor counts. docs/BACKLOG.md is
public, and a tally over a closed public requirement set is the shape that hands
out coverage by subtraction. The mechanism is fully stated without them -- a
reader with vault access can price it, and a reader without one still knows what
to fix and why. Verified my added lines carry no such figure, with a positive
control proving the scan discriminates.

AND THE RELAY'S FRAMING IS CORRECTED BY THE ITEM'S OWN TEXT. It described the
defect as dropping sym/ctx. #1242 already forbids fixing it that way: the defect
is the handling of UNKNOWN keys, and a fix special-casing those two by name
rebuilds the same trap for the next field added. The item was ahead of the relay
and a builder must follow the item.

The Tracker's core claim was verified here rather than taken: sym and ctx each
return 0 occurrences in scripts/asvs/apply.py at origin/main, positive control
expect returns 2.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 281 items / 204 open / 77 closed, unchanged, and
#1242 verified still OPEN.

* backlog: close #1238 and #1239 on the code, and file #1253 so the closure does not retire the hazard

Builder 2 reported four banners owed. Two of the four -- #1237 and #1204 -- were
already authored on this branch and are invisible to that lane because ledger
authorship and push are held by different seats. That is recorded as a finding in
the episode note; it is not fixed here. The two genuinely owed are written now.

Both closures verified against origin/main rather than against the build report,
because a banner that closes on a report inherits the report's errors.

#1238: _is_contained_name is defined at transports/remotefile.py:97, wired at
:954, and asserted in BOTH polarities at tests/test_remotefile_transport.py:1185
and :1195. The one-polarity case is called out because such a test passes against
a function that refuses everything. posixpath.basename() was not used, per the
owner ruling.

#1239: _has_control_char returns 0 occurrences across messagefoundry/ at
origin/main, so the pair it named is a single. The item's own condition is met.

#1253 exists because closing #1239 there would have been true of the item as
written and false of the hazard it describes. The reporting lane amended its own
closure recommendation to say so -- the predicate is copied more widely today
than when #1239 was filed, partly by the work that resolved it. A repo-wide
re-measure widened that further: the amendment scanned transports/ and found five
sites across four files; across messagefoundry/ it is seven across six, the two
extra being config/codeset_edit.py:305 and config/impact.py:631.

Two exclusions are recorded in the item so a later scan does not re-add them:
rest.py:109 matches a naive grep but is prose in a docstring, and sniff.py:179
tests the same code points through a genuinely different byte-wise predicate that
subtracts an allowlist, so folding it in would change its behaviour.

rest.py:111 strips where the others reject. That is recorded as defensible and
NOT as a second instance of the pattern the owner ruled against in #1238, so the
next reader does not inherit a false lead: stripping CR/LF from a header value
cannot redirect a request, whereas basename() mutates a path into a real and
different target.

#1253 allocated with alloc.ps1, never grepped. parse_items before and after:
281/204/77 -> 282/203/79, matching the predicted delta for two closures plus one
filing, with a control confirming no item carries a stray banner.

* backlog: record that #1234 is not startable from main -- its subject has not landed

Builder 2 refused a restock offer of #1234 by correcting its OWN earlier
recommendation, and verified before claiming rather than after. Re-measured here
rather than taken: at origin/main, require_least_privilege returns 0 hits in any
.py and appears only in this ledger's own prose. Positive control on the same
instrument, require_managed_identity, returns hits across four .py files, so the
scan sees Python fine -- the zero is a fact about the tree, not a broken needle.

The probe this item reports a defect in exists only on the
w3-store-privilege-preflight branch, dormant at that reading.

The amendment is careful not to overrule the item's own "independent of #1008"
paragraph, because that paragraph is right about a different thing. Both halves
hold: the item is not hostage to #1008's POLICY ruling, and it is nonetheless
unbuildable by any lane working from main until that BRANCH lands. Collapsing the
two would either re-gate a code defect behind a demand gate or keep offering work
whose subject does not exist.

This is the same wasted-claim cost as #1253's provenance, one layer deeper: there,
an item was unstartable because the FIX had already landed; here, because the
SUBJECT has not. A banner-driven queue cannot distinguish either case from
startable work, which is why both are now written down where the next dispatcher
reads rather than left in session mail.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 282 items / 203 open / 79 closed, unchanged, and
#1234 verified still OPEN.

* backlog: file #1254 -- a required check is named for its subject, not its assertion

Handed to me by the Liaison to number if I judged it worth one, in the Lander's
framing. It is, because the naive fix is dangerous and nothing currently records
that.

MEASURED INSTANCE: the Windows leg went red under the label
"test (windows-2025, py3.14)", which reads as "the tests failed on Windows". The
tests passed. What failed was the wall-clock gate "Step margin -- both gated
steps" (ci.yml:675). The check answered its own question truthfully and the NAME
described a different one -- the reverse of the shape this project keeps hitting,
where the label is honest and the instrument is not.

The job name is built at ci.yml:42 from the matrix, so all three legs are named
for WHERE they ran and never for WHAT they assert, while holding at least three
independent assertions. Stated as at least three rather than enumerated.

WHY THIS IS NOT A ONE-LINE RENAME, which is the whole reason it needed writing
down: those three strings ARE required contexts. They are listed in
.github/required-contexts.txt, asserted against branch protection by
tests/test_required_contexts.py, and matched BY NAME on the GitHub side. A
required-but-absent context blocks every PR forever, so a rename is one atomic
change across the workflow, the contexts file, that test's pinned count, and the
branch-protection setting, in the order that file's header prescribes.

So the item deliberately does NOT recommend the rename. It prices three options
and names the cheapest first: make the margin gate's failure output say in its
first line that the suite passed and a timing gate fired. That costs nothing and
cannot wedge the repo. The rename is listed third.

Severity carries no deployment axis, but the near-miss is recorded: the
misreading pointed at the wall-clock cap, and #1096's banner already says the
actual fix is #320 and that re-deriving the caps is itself the failure mode.

#1254 allocated with alloc.ps1, never grepped. parse_items before and after:
282/203/79 -> 283/204/79, matching the predicted delta for one filing, with a
control confirming no item carries a stray banner.

* backlog: amend #1235 -- its two named instances are inert, and I dispatched the opposite

Builder 2 refused the starting fact I gave it and measured instead. It was right
and I was backwards.

I told it to start #1235 from #1203 as a CONFIRMED LIVE TRAP, reasoning that an
allocation record with no ledger entry meant the number was free. The record is
what makes the number permanently UNAVAILABLE. Verified here rather than taken:
1203.json and 1231.json both exist in .git/mefor-coord/alloc/backlog/ (claimed
2026-08-09 and 2026-08-12), and alloc.ps1 has no release at all -- :41 "a one-way
door -- claims are never released", :24 "numbers are never reclaimed ... holes are
free, collisions are not". So the item's own text is wrong where it says #1231 was
"allocated and released without being filed", and wrong that the pair is "defused
only by an accident of timing". They are defused by construction.

ONE CORRECTION AGAINST THE REPORT AS WELL, because its reason is weaker than its
conclusion. The argument as relayed rests on the allocation RECORDS existing. Those
live under .git: uncommittable, machine-local, losable without trace. The reason
that survives their loss is structural -- alloc.ps1 issues $observed + 1 (:392,
and :389 under the public floor clamp) and NEVER fills a hole, so a number below
the floor is unreachable whether or not its record still exists. Recording the
registry as the protection would make a sound property look fragile and invite a
guard nothing needs.

The live shape is the other one and the item now says so: a citation to a number
NEVER allocated sits above the floor and will be issued in the normal course. A
detector reading only the ledgers rates the two states identically, which over
docs/ in this repo mis-scores 26 reserved citations as live; of the 6 genuinely
never-allocated tokens there, all six are foreign references, so this repo holds
zero genuine instances. The private-repo population the item was filed against is
not re-measured here and is stated as separate.

The remedy is unchanged and still correct. Only the account of WHY the two named
instances are harmless is corrected.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 283 items / 204 open / 79 closed, unchanged, and
#1235 verified still OPEN.

* backlog: correct #1235 and #1254 -- two of my own committed claims, falsified under adversarial check

I ran six independent skeptics over every claim I committed tonight, against the
SIMULATED POST-MERGE TREE rather than my branch, because my branch was six behind
origin/main and the merge is CLEAN -- git conflicts on concurrent edits, never on
invalidated claims. Four claims held. Two did not. Both failures are my authoring,
not merge drift: all cited files are byte-identical across the merge tree,
origin/main and HEAD.

#1235. The conclusion survives, the reason did not. I had written that the
allocation registry is irrelevant because the mechanism is structural. That is
FALSE for the newest number: the high-water ratchet persists $floor, the maximum
of the OBSERVED set (:205, :214, :215), NOT the number being issued, so after
issuing N it holds N-1. alloc.ps1 only PRINTS the heading, so until it is
committed the sole durable record of N is its own untracked, never-pushed
<n>.json. Lose that and the next run re-issues N. So the registry is exactly what
protects a just-allocated-but-unfiled number -- the state #1203 and #1231 were both
in when allocated.

What actually makes those two unreachable is a CONJUNCTION, now stated as one: the
loop never searches downward (:392, :389, :394), AND the floor is computed from
COMMITTED LEDGER HEADINGS. Measured non-destructively with -ShowFloor: floor 1254,
swept from docs/BACKLOG.md and the closed archive. Those are tracked content on
refs, they survive a fresh clone, and both numbers sit far below them. Recorded the
public-floor clamp as a THIRD, separate guarantee about the output range, with the
caveat that its own anti-lowering ratchet lives in the same untracked directory and
is disarmed on a registry-absent clone.

#1254 cited the margin gate as ci.yml:675. That is a clock MARK
(step_margin.py --mark between, :677). The gate is :765, if: always(), invoking at
:778 and :781. The error is worth recording rather than silently fixing: I opened
:675, found a step whose name NEARLY matched, and adopted it instead of treating
the near-match as the signal the line was wrong. A near-miss terminates the search;
no match would have continued it.

Also corrected in #1254: tests/test_required_contexts.py does NOT call the GitHub
API. It pins the count at :101 and resolves contexts against real workflow job
names at :107; the branch-protection comparison is a HUMAN step in the comment at
:100. The item's central argument is unaffected -- the strings are still required
contexts and a rename still resolves them to no job -- but the evidence now says
what the test does.

The four that held: #1253's seven-sites-across-six-files with both exclusions and
every line number, #1239's zero occurrences, #1238's defined-wired-and-both-test-
polarities, and #1234's zero .py hits with its positive control.

Amendments only, no heading added, so ledger ownership is not consulted.
parse_items unchanged at 283 items / 204 open / 79 closed.

* backlog: file #1255 -- two testpaths ship a top-level conftest each

Diagnosed by the lane whose own commit tripped it, and filed here because the
collision outlives that commit. Seven tests in one file failed in a full run and
passed in isolation, twice. Cause: pyproject sets two testpaths, both directories
contain a conftest.py, neither contains an __init__.py, so both claim the top-level
module name and a bare `import conftest` binds to whichever loaded first.

Verified at origin/main rather than taken from the report: both conftest.py files
present, both __init__.py absent, and a scan for `import conftest` /
`from conftest import` across both trees returns ZERO hits. That zero is why this
is filed as LATENT rather than live -- the collision is real and currently
untripped, so nothing is failing today and the item must not be cited as a current
gap.

The signature is recorded because it mis-attributes itself: the mis-bound import
surfaces as an AttributeError naming a module path from the WRONG package, not as
an ImportError, so it reads as a missing attribute rather than a bad import.

Two things the item forbids, both because a plausible fix is worse than the defect.
Do not import conftest BY PATH -- its body claims a per-process test slot and
registers an atexit unlink, so a second import under another name has side effects.
And do not prove a fix in isolation: isolation is precisely the condition under
which this defect reports success. The proof has to run both testpaths together and
then restore the bare import to confirm the same command fails again.

The house idiom already solves it -- tests/_workflow_contexts.py is imported
package-qualified at tests/_negative_controls.py:35 -- so the scope is to make the
name unambiguous, not to invent a mechanism.

#1255 allocated with alloc.ps1, never grepped. parse_items before and after:
283/204/79 -> 284/205/79, matching the predicted delta for one filing, with a
control confirming no item carries a stray banner.

* backlog: record that PR #382 closed ONE of #1242's four limbs -- the item stays open

I told a builder its engine fix closed this item's mechanism and to claim it. That
was wrong, and this records the correction where the next reader will hit it rather
than in session mail.

#382 merged 2026-08-13 with this item's number in its title and a title that
faithfully describes what it fixed: the payload-only TOP-LEVEL key limb. Verified
at origin/main -- apply.py:97 now walks {**(live or {}), **cell}.items() rather
than the live cell alone. That limb is genuinely closed.

The limb carrying the item's severity is untouched, and the same revision shows
why the union cannot reach it: :98 skips _ORDERED and _SUBTABLES BEFORE the union
at :97 is consulted for them, and evidence entries are re-emitted at :101-105 by
enumerating exactly path, line and expect (absence at :106-110 by exactly pattern,
positive_control, mutation). A key inside a [[cell.evidence]] entry is still
dropped -- which is exactly where the backfill put the affected keys, on evidence
ENTRIES and not on top-level keys. The top-level table-mangling limb also appears
untouched.

I had additionally told that builder to stop measuring the two affected key names
because the item forbids fixing by naming them. The prohibition is real but I
applied it to the wrong activity: the item forbids naming them in a FIX, not
measuring their absence as a SYMPTOM, and their absence from the writer is exactly
the evidence that the sub-table limb still bites.

Recorded as the PARTIAL-MOVE shape, which is the reusable part: a merged PR bearing
an item's number, whose title truthfully describes what it fixed, is the strongest
available signal the item is done. Verify-before-closing is not enough on its own
here -- the verification has to ask WHICH HALF. The item's own proof condition is
the discriminator and is unchanged: put an unknown key INSIDE an evidence entry,
re-render, assert both that it survives and that the guard refuses when it is
deliberately dropped. #382 does not satisfy it, and no test asserting only
top-level carry-through will.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 284 items / 205 open / 79 closed, unchanged, and
#1242 verified still OPEN.

* backlog: record #1242's BASE REQUIREMENT -- the obvious base for limb 4 reverts limb 3

Builder 2 confirmed limb 4 as I described it, then refused to build it and handed
it back with a reason better than the instruction I gave. Recording the reason,
because it is not discoverable from the item and git will not raise it.

Its branch still carried scripts/asvs/apply.py:88 as

    if key in _ORDERED or key in _SUBTABLES or key in cell:

Verified here against that ref rather than taken: the clause is present there and
ABSENT at origin/main. `or key in cell` is precisely what #382 deleted to fix limb
3. So a limb-4 fix authored on that base and merged would carry limb 3's REVERSAL
in the same diff -- no conflict, no marker, every check green, and the item's own
landed fix undone by the commit claiming to extend it.

Git raises nothing here because git conflicts on concurrent edits to the same
lines, never on a stale base re-asserting a clause that was deleted elsewhere. That
is the same family as the clean-merge hazard this session has been working under
all night, arriving from the direction nobody watches: not a doc invalidated by a
merge, but a FIX reverted by an extension of itself.

The item now states the base requirement and a pre-PR check that discriminates:
branch fresh from current origin/main, and confirm `or key in cell` returns zero
hits in the diff's own version of the file before opening a PR.

Also corrected upstream of this, in my own dispatch rather than the ledger: I had
told that lane to stop measuring the two affected key names. Measuring their
absence as a SYMPTOM was always legitimate; only fixing by naming them is
forbidden. It withdrew its acceptance of my earlier "bound lifted" on the grounds
that it had taken it from me without measuring -- correctly.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items before and after: 284 items / 205 open / 79 closed, unchanged, and
#1242 verified still OPEN.

* backlog: file #1256 -- the federated binding never checks subject exclusivity

Builder 1 concluded #1143's research and handed over the finding rather than a
commit: the defensible-ceremony question dissolves, because every candidate
collapses to trust-on-first-use when no out-of-band proof exists at first
federated login. What it surfaced instead is a separable gap, and that gap is
this item. Content theirs, number mine, per the authoring split.

Verified at origin/main rather than relayed. auth/service.py:1109-1114 compares
user.oidc_subject against the presented subject and refuses on mismatch, then
binds at :1119-1120. The comparison is keyed on the USER, so it is structurally
incapable of noticing a second account carrying the same (issuer, subject). A scan
for a UNIQUE constraint naming the federated columns returns 0 on ALL THREE
backends, so nothing below it closes the gap either.

The item credits the three shipped controls rather than implying an absence: the
hybrid-only refusal, the subject-continuity guard, and the UPN suffix allow-list.
All three constrain which subject may bind to a GIVEN account. None constrains how
many accounts one SUBJECT may bind to. Stating that explicitly is what should stop
this being re-closed as a duplicate of #1015 or #1143.

The difficulty is recorded where it actually lives. SQL Server types the federated
columns NVARCHAR(MAX), and a MAX column cannot be an index key, so a unique index
there needs a RE-TYPE and not merely a constraint -- a cost SQLite and Postgres do
not share. The proof condition requires demonstrating the refusal on every backend
in CI, because the two server store suites SKIP in a local run and a green on
SQLite alone would certify nothing.

#1143 is NOT closed by this and the item says so: whether TOFU is the defensible
ceremony is separable and still open.

#1256 allocated with alloc.ps1, never grepped. parse_items: 285 items / 206 open /
79 closed after, matching the predicted +1/+1/0 for a single filing, with a control
confirming no item carries a stray banner.

* backlog: record that #1020's refusal path hangs in-harness and is unverified under uvicorn

Reported by the lane that built the gate, against its own work, and recorded here
because it changes what "built" means for this item.

The gate refuses by raising during ASGI lifespan STARTUP, and in-harness that
HANGS rather than exiting. Their control is what makes it a measurement rather
than an impression: the sibling non-raising lifespan test passes in 1.13s, raising
from the lifespan BODY exits cleanly, and raising during STARTUP hangs with zero
output. It sits after engine.start() and before the task handles teardown expects,
so the condition is PRE-EXISTING -- the gate is simply the first thing to raise in
that window, which is why this is not filed as a defect in their fix.

What was NOT measured is what uvicorn does there, and uvicorn is the runner that
ships. They said so explicitly rather than letting the harness result stand for
the product, which is the reason this is worth recording at all.

The consequence is ordered plainly in the item: a startup refusal that hangs a
service is STRICTLY WORSE than the mis-report #1020 exists to correct. An operator
can see a wrong readiness answer; they cannot see a process that never finishes
starting. So the banner now carries an explicit bar on closing this on the gate
landing until the refusal is shown to TERMINATE under uvicorn rather than under
the test harness.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items unchanged at 285 items / 206 open / 79 closed, and #1020 verified
still OPEN.

* backlog: #1235 is PARTIAL, not closed -- the detector shipped, the rule did not

I was about to close this on PR #385 landing. An adversarial pass over my own
proposed banner refuted it, and the refutation is right.

What I would have relied on -- do the two files exist at origin/main -- passes
identically whether or not the rule landed and whether or not anything invokes the
detector. It measures PRESENCE, NOT ENFORCEMENT, and it is the same
cannot-fail shape as running a CLI's --help and reading exit 0, which I had
already caught once today in my own #1030 check.

Three measurements, each independently sufficient to block closure:

  the RULE is unwritten. #1235's Scope names its deliverable as "a rule, not a
  sweep", and that guidance appears at origin/main only in the item's own prose --
  zero hits for "unallocated" across CLAUDE.md, docs/LEDGER-GATE.md, CONTRIBUTING,
  scripts/, .github/ and .claude/.

  the DETECTOR is wired into nothing. Repo-wide it is referenced by exactly two
  lines, both inside its own unit test. Not in any workflow, not in
  .pre-commit-config.yaml, not in .mefor-hooks/pre-commit, not in pyproject.toml.

  it EXITS 0 even when it fires. main() ends `return 1 if args.fail else 0` and
  --fail is opt-in and passed by nothing. A planted live-shape citation was
  reported correctly and the process still exited 0. No test runs
  unresolved_citations over the real docs/ tree, so a new dangling citation turns
  nothing red.

The controls are what make those zeros trustworthy: the same wiring grep DOES
resolve three sibling scripts/docs tools that are wired into CI, and the detector
itself discriminates correctly when --fail is supplied. So the absences are facts
about the tree, not a broken pattern or a blind scan.

Banner moves to in-progress rather than closed, and the item now records what
shipped, the two residual limbs (write the rule where authors read it; wire the
detector with --fail or test it against the real tree), and a coverage bound that
survives both -- by its own docstring the detector cannot see the private
companion repository, which is where this item's filed instances live.

Closing on the detector's existence would have recorded an enforced rule where
nothing enforces it. The builder declined to close it for the same reason and left
the ledger half to me.

Amendment only, no heading added, so ledger ownership is not consulted. Both
glyphs are OPEN, so parse_items is unchanged at 285 items / 206 open / 79 closed,
with #1235 verified still OPEN and carrying exactly one banner.

* backlog: close #1230 on the owner-ruled half, with the wiring's own regression gap named

Four proposed banner verdicts went through an adversarial pass before any was
written. Three survived; one did not and was corrected separately (#1235). This is
the survivor, and it survived on evidence stronger than I would have gathered.

#1230 CLOSES because the loud-omission half is built AND WIRED, not merely
present. tests/_extras_probe.py supplies the emitters and tests/conftest.py binds
them to pytest's own hooks at :366 and :370. Verified by RUNNING rather than
reading, in both directions on two real interpreters: extras-complete venv, banner
ABSENT; the venv scripts/worktree/new.ps1 actually builds, banner FIRES in both
surfaces -- including on a bare full-suite run collecting 13,187 tests. The
silent direction was observed rather than assumed, which is what makes the loud
direction mean something.

Scope is exactly the half the owner ruled. new.ps1:232 is unchanged, so options
(a) and (b) -- installing the five extras into every worktree venv -- were
correctly not taken.

CLOSED WITH A NAMED RESIDUAL, because folding it into the closure would reproduce
the defect. Deleting BOTH hook functions from tests/conftest.py leaves
tests/test_incomplete_run_banner.py reporting 7 passed, while a real run goes from
banner-present to banner-gone. The tests drive the probe against a fake reporter;
nothing asserts that pytest invokes it. Repo-wide, pytest_report_header and
pytest_terminal_summary appear ONLY at conftest.py:366 and :370, guarded by no
test, lint or gate. So a conftest refactor can silently delete the mechanism with
every check green -- which is this item's own failure shape, one layer up, inside
the fix for it.

The item closes on the owner-ruled half working. The residual is recorded in the
banner rather than discovered later by whoever the silence next costs.

Amendment plus banner flip, no heading added, so ledger ownership is not
consulted. parse_items 285 items / 206 open / 79 closed -> 285 / 205 / 80,
matching the predicted 0/-1/+1 for a single closure, with a control confirming no
item carries a stray banner and #1235, #1249 and #1030 all verified still OPEN.

* backlog: file #1257 -- a startup refusal after engine.start() hangs instead of exiting

Found by the lane building #1020, against its own work, when it closed that item's
own open question: does the refusal terminate under uvicorn rather than under the
harness. It does not, and the answer blocks #1020.

The distinction is the finding. uvicorn behaves correctly -- full error printed,
"Application startup failed. Exiting.", SystemExit(3), no socket bound. The PROCESS
then never exits; measured alive 90 seconds later and killed. So this is a HUNG
refusal, not a silent one: an operator at a console sees exactly the right error,
and a SUPERVISOR sees a process that started and never stopped. The engine ships
under NSSM, and systemd and container runtimes decide the same way -- by process
liveness. Running-and-dead is the one state a restart policy cannot detect, which
is why this outranks the mis-report #1020 exists to fix.

Structural half re-verified here at HEAD rather than taken. Every pre-engine step
carries its own unwinding: api/app.py:5608 for startup attestation and :5628 for
the trust-anchor preflight each wrap in try/except BaseException, close the
notifier and store, and re-raise. From await engine.start() at :5731 to the yield
there is no try, no except and no finally at all, and auth_settings handling sits
at :5816-5818, inside that unprotected span.

Filed separately from #1020 deliberately. The cause is pre-existing and the gate is
merely the first thing that has ever raised in that window; a lifespan-teardown
change has its own blast radius in code every deployment runs and should not ride
in on an auth item.

The rejected option is recorded with its reason so it is not re-proposed: moving
the check before engine.start() fails on measurement, because the bootstrap admin
does not exist until auth.initialize(), which runs later.

The proof condition is written to exclude the checks that already pass: raise
deliberately in the post-engine span and assert the PROCESS EXITS -- not that the
error is printed and not that SystemExit is raised, both of which are true today
and neither of which discriminates.

#1257 allocated with alloc.ps1, never grepped. parse_items 285/206/79 -> 286/206/80
... note the closed count moves because the prior commit closed #1230; this filing
itself is +1 item / +1 open. Control confirms no item carries a stray banner.

* backlog: record that #1250 is now load-bearing, and keep the retroactive question separate

Owner ruled 2026-08-14 that an item whose substance is a weakness in the
coordination tooling or the seat topology is a DEFICIT and may not go in the public
ledger. With no private ledger existing, such an item PARKS pending #1250.

That changes what #1250 is. It was a proposal; it is now the thing a real,
measured, reproduced defect is waiting on, and the ruling applies to every future
item of this class rather than to the one instance. The cost of not having it is
therefore per-item and accumulating: each such finding lands in a coordination
handoff file, discoverable only by whoever thinks to look there.

I did NOT re-score the priority. The P2 line predates the ruling and is left
untouched deliberately -- re-scoring is the owner's call, and recording why an item
now matters is not the same act as deciding it matters more. Someone reading this
should be able to see the new fact and make that decision, rather than inherit a
number I moved quietly.

The parked item itself is not described here, only that one exists. Describing it
in this file is precisely what the ruling forbids, so the amendment records the
GATING relationship and nothing about the gated content.

The retroactive question is recorded as explicitly NOT settled by this. The ruling
answered where a NEW item goes; several items already in this ledger appear to be
the same class, and they have a different cost structure because unfiling from a
public repository does not unpublish anything. A boundary never applied
retroactively is not a boundary ignored, and merging the two questions would let
the cheaper answer decide the harder one.

Amendment only, no heading added, so ledger ownership is not consulted.
parse_items unchanged at 286 items / 206 open / 80 closed, #1250 verified still
OPEN, no stray banners.

* backlog: file #1258 -- classify existing ledger items against the deficits ruling

Owner-authorised. I raised this observation earlier and deliberately did not act on
it, on the grounds that reclassifying existing public items is not a dispatcher's
unilateral call. It was escalated, the owner said file it, so it is filed.

FILED AS BLOCKED BEHIND #1250, and that is a judgment I made before dispatching
rather than one discovered when the result lands. The sweep's product is a list of
public items that disclose tooling weaknesses. That list is an index to exactly
what the rule protects -- the same aggregation argument that kept the 2026-08-14
item out of this file. So the output cannot be written here, and with no private
ledger existing it has nowhere to go. Running the sweep first would manufacture a
finding that must then be parked in a handoff file, adding to the cost #1250 exists
to end.

WORDING CONSTRAINTS ARE THE SUBSTANCE OF THIS ITEM, not decoration on it. The item
names the RULE and the SCOPE and no candidates. It records no count either, because
a count over a bounded set is the same disclosure one subtraction later. Verified
before committing: the only backlog number appearing in the item's body is #1250,
its blocker, with a positive control showing the same pattern finds 543 references
across the file -- so the single hit is a true reading rather than a broken scan.
An item that enumerated its candidates would perform the defect it was filed to
find.

The remedy is explicitly left open. Unfiling from a public repository does not
unpublish anything, so what to do about anything found is a separate question with
a different cost structure, and this item must not answer it by implication. A
reclassification that moves text without reducing exposure is churn wearing the
shape of a fix.

The framing is a constraint too. The ruling is dated 2026-08-13 and the items most
likely to match were filed around the same time. A boundary never applied
retroactively is not a boundary ignored. This is hygiene, not an audit of anyone's
judgement, and if the output reads as an accusation it has been written wrongly.

#1258 allocated with alloc.ps1, never grepped. parse_items 286/206/80 -> 287/207/80,
matching the predicted +1/+1/0, no stray banners.

* docs(ledger): write the citation rule #1235 asked for, in both places a reader looks

#1235's stated deliverable was "a rule, not a sweep", and an adversarial pass
established that the rule existed nowhere at origin/main except the item's own
prose. A builder shipped the detector and correctly declined this half as
authorship rather than build work. This writes it.

TWO PLACES, because "where authors read it" is the whole point and they are
different audiences. CLAUDE.md section 5 gets it as a bullet directly beneath the
never-grep-for-a-number rule it mirrors -- an author reading the allocation rule
now meets the citation rule in the same breath. docs/LEDGER-GATE.md gets the full
treatment under its own section, next to the machinery.

THE RULE: either allocate the number before citing it, or write a reference that
CANNOT resolve. Naming the subject instead of a number costs nothing and cannot
arm.

WHAT THE LONG FORM ADDS, because a bare rule invites the wrong remediation:
- Only a citation ABOVE the allocation floor can ever arm. A reserved-but-never-
  filed number is inert PERMANENTLY -- alloc.ps1 issues $observed + 1, never fills
  a hole, and computes its floor from committed ledger headings that survive a
  fresh clone. Without this, a reader would treat every unresolved citation as a
  live trap and sweep the harmless majority.
- Foreign #N references are not citations of this ledger at all and are not the
  rule's subject.
- Enforcement does not replace the rule. A checker finds only what has already
  been written; the rule is what stops it being written.

THE CLAUDE.MD EDIT IS APPEND-ONLY inside an existing bullet list, and I verified
section numbering is unchanged -- 13 numbered headings before and after -- because
renumbering that file silently breaks citations across the tree and nothing
validates one. The new cross-reference was checked to resolve: the LEDGER-GATE.md
anchor it names exists.

#1235 stays PARTIAL. Limb (i) is discharged and recorded as such; the wiring and
fail-closed halves are a builder's work on an unlanded branch, so the item does not
close on this.

parse_items unchanged at 287 items / 207 open / 80 closed, #1235 verified still
OPEN and carrying exactly one banner.

* backlog: file #1259 -- parse_items censuses a conflicted ledger without error

The landing seat ran parse_items on a merge-tree output BEFORE checking the merge's
exit code and got a census matching my incoming batch's prediction exactly: right
item count, right open count, both new numbers present, no duplicates. The exit
code was rc=1 with six conflict markers in the blob. It reported the near-miss
against its own process rather than quietly reordering its checks, which is why
there is an item.

Reproduced here with a control rather than taken: the live ledger and a copy
poisoned with conflict markers BOTH parse to 287 items / 207 open, and nothing
raises. The counts being IDENTICAL is the finding -- the census cannot be used to
detect the condition, because the number is right and the file is unusable.

I nearly recorded that a gate already covers this. A grep for "conflict" across the
ledger gates returned a hit in ledger_check.py, and reading the context showed both
matches are PROSE -- a comment about clean merges and an error string reading "not
as a conflict". Neither detects a marker. backlog_status_check.py and
backlog_citation_check.py contain no detection either, and .pre-commit-config.yaml
runs nothing that inspects Markdown for markers. So a conflicted docs/BACKLOG.md
can be committed and every ledger gate passes over it.

Scope prices two options and says which one matters. The standard
check-merge-conflict pre-commit hook is the cheap general fix and covers every
file. Making the READER refuse a conflicted source is the one that protects
PROGRAMMATIC callers, and that is where this bit -- a hook does not help a gate
handed a tree in memory.

The proof condition excludes the check that cannot fail: poison a copy of the real
ledger and assert the reader FAILS. Asserting it parses a clean file proves
nothing; it already does that, and did so throughout the incident.

The general rule is recorded with the item because it outlives the fix: read the
EXIT CODE before the CONTENT, because a content check on a conflicted tree
certifies nothing.

#1259 allocated with alloc.ps1, never grepped. parse_items 287/207/80 -> 288/208/80,
matching the predicted +1/+1/0, no stray banners.

* backlog: reframe #1242 limb 4 as the unbuilt half of an existing spec

The ASVS Tracker supplied this from the design record and it is a better brief
than anything in the item so far. The promotion of the writer was SPECIFIED to
carry through the union of live and payload keys, so the schema could grow without
hand-editing the record. What shipped delivers that for top-level scalars and drops
it for sub-table entries.

So limb 4 is not a new requirement. It is the unbuilt half of one, and "restore the
specified behaviour" is a stronger instruction to a builder than "also handle sym
and ctx" -- the latter invites a fix keyed to the two names that happen to exist
today, which would satisfy the symptom and still drop the next field anyone adds.
The Tracker made that same point about its own earlier framing and I am recording
its version rather than mine.

The evidenc…
wshallwshall added a commit that referenced this pull request Aug 15, 2026
…ord the two generators (#409)

* docs(backlog): correct #1241 -- one remaining limb is built, and eight anchors have drifted

Measured against origin/main at ae76b9f9, reading the ref rather than a working
tree. The item stays OPEN; what changes is the size of what remains and whether
its citations resolve.

The FhirLookupExecutor limb named in "WHAT REMAINS" is built. fhir.py:797 screens
the lookup url through _reject_config_control_chars, and the comment above it cites
#1241 by number and restates the item's own asymmetry argument as the reason. So
the paragraph lists a site that no longer remains. transports/dicomweb.py is still
outstanding alone -- study_uid is control-char screened at :155 and then reaches the
URL path at :243 with no grammar gate and no percent-encode -- so the item does not
close.

Eight anchors no longer point at what they claim, recorded in a table so the next
reader re-derives none of them. Two are called out because they fail quietly:

fhir.py:428 is cited as the screened-and-encoded exemplar and is in fact a base64
Authorization header build. It resolves to a real, plausibly adjacent line in the
same file, so a reader can accept it as the exemplar and never notice the citation
came loose. That is the failure mode CLAUDE.md warns about for a wrongly-resolving
reference versus a dangling one.

fhir.py:231 inverts rather than drifts. The prose says conditional_query reaches the
URL with no screen at all, while the WHAT #379 FIXED paragraph in the same item says
#379 added exactly that screen -- and :261-263 confirms it shipped. Both sentences
live in this one item and the stale one sits where a builder reads last.

The test_fhir_transport.py anchor has now drifted twice (:220, corrected to :221,
today :222). Recorded with an instruction to pin it by content rather than correct
it a third time.

Status is unchanged and verified with parse_items from
scripts/docs/backlog_status_check.py, not a hand-rolled scan: 292 items parsed on
both sides, #1241 is_open=True with one open banner and zero closed banners, matching
origin/main. The amendment carries no status glyph and introduces no new glyph
vocabulary.

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

* docs(backlog): #1253's work has landed -- record the evidence, leave the closure act separate

The filed banner still reads "not started", which is false as of origin/main
ae76b9f9. messagefoundry/controlchars.py exists and exports has_control_char and
strip_control_chars; the predicate ord(ch) < 0x20 or ord(ch) == 0x7F now occurs in
that module alone -- twice as implementation, once in a docstring naming what it
replaced. The seven-across-six count the item was filed over is retired.

Adoption was enumerated rather than assumed, because that is the half a grep does
not answer: a new shared helper nobody imports would retire the count while leaving
all six copies in place. The importers are config/codeset_edit.py, config/impact.py,
transports/dicomweb.py, transports/fhir.py, transports/remotefile.py and
transports/rest.py -- six files, matching the six the item named -- plus a dedicated
tests/test_controlchars.py.

The status banner is deliberately NOT flipped. backlog_status_check.py treats an
item carrying both a closed and an open banner as a hard error, and separately
rejects a shipped item still carrying a Priority. Closing this is three coupled
edits (replace the banner, drop the P3 line, move the item verbatim to the archive),
so a banner-only "close" would red the hygiene gate. The evidence is recorded here
so whoever performs the closure does not re-measure it.

Verified: backlog_status_check.py passes -- 528 items, each declaring exactly one
status (292 live + 236 archived). The new banner carries no status glyph and
introduces no new glyph vocabulary.

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

* backlog: file #1265 -- the warning sign is unsanctioned decoration in 496 places

Owner-ruled 2026-08-14, relayed via the Liaison from a three-option interactive
ask: "not sanctioned; file a sweep". The remedy is a filed migration item, not an
edit -- CLAUDE.md section 11 already rules that changing a glyph population is a
migration with its own item, and the owner applied that same shape. Nobody is
authorised to start editing the 496 lines on the strength of this filing, and the
item says so in its banner.

U+26A0 is in neither _CLOSED nor _OPEN in backlog_status_check.py, so parse_items
ignores it and it carries no status semantics anywhere. That is the whole argument:
the five holdouts are tolerated because they are machine-parsed, and this one is
not. Demonstrated rather than asserted -- the #1241 and #1253 amendments committed
earlier today carry no glyph at all and the hygiene gate passed clean.

The census is positive-controlled, using the ledger counts as the control, because
the first attempt returned a FALSE ZERO for engine source off a broken shell escape.
A pattern that finds nothing anywhere is indistinguishable from a clean repo, and a
false zero on this exact question has stalled it once before.

The distribution as first told was wrong and is corrected in the item. It is not
concentrated in engine source and the web console has none: 447 of 496 are under
docs/, about 17 are in anything executable, and engine source has 3. So the honest
framing of the non-ledger remainder is documentation consistency, not code hygiene.

Two framings are refused explicitly so they are not rebuilt later. It is NOT a
cp1252 hazard -- the cp1252 gate's scope is scripts/**/*.py and scripts/ contains
zero of these, so the gate is not silently missing them. Filing it as an operational
risk would be a compensating-control argument resting on a false premise (SDS-3.7).
And the two ledger files are sequenced LAST rather than first, because they are the
only slice sitting beside an alphabet that something actually parses.

CLAUDE.md section 11 now records the measured population so the false zero cannot be
re-derived. It names the codepoint rather than pasting the character; verified that
CLAUDE.md still contains zero instances.

Verified: backlog_status_check.py passes at 529 items, each declaring exactly one
status (293 live + 236 archived); #1265 parses as open with one banner; the census
control still reports 121 in docs/BACKLOG.md. Number allocated atomically via
scripts/coord/alloc.ps1, not by grepping for the next free one.

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

* backlog: file #1263's pricing, with its arithmetic stated rather than smoothed

Measured by a builder, endorsed by the previous dispatcher seat, and never filed --
it had been living in a handoff for six hours. Filing it so it stops depending on a
seat staying alive. Authored here rather than by the builder, per the 2026-08-13
ruling that builders may not author ledger content.

The substance: 1,184 citations, of which 906 resolve under exactly one root, 28
under multiple, 250 under none. So 278 (23 percent) cannot be checked by a
path-keyed checker at all, and 11 are already past EOF -- live breakage today.

The finding that drives the recommendation is that a checker keyed on exact
repo-relative paths would silently skip that 23 percent and report green, which is
a gate-shaped hole rather than a gate. Hence: build the cheap form, and PRINT the
silence -- the run must emit "278 not checked because they do not resolve" as part
of its own output, because a detector reporting only what it checked is
indistinguishable from one that checked everything. Re-price the strong form only
after the detector runs once, since its output is the cleaning list the strong form
needs first.

Three arithmetic caveats are recorded in the item rather than smoothed away, because
filing a tidy number would launder a known defect into the ledger:

923 "of the resolved" land on a real line, but 923 exceeds the 906 resolved, so it
cannot be a subset as stated. The measuring builder flagged this themselves.

This pass counts 1,184 while the item's own heading and banner say 1,193 -- a
nine-citation gap between two passes over one corpus, unreconciled. Noted that
906 + 28 + 250 sums to exactly 1,184, so this pass is internally coherent as a
partition; that is evidence about consistency, not about which pass is correct.

The 11 past EOF is named as the load-bearing figure: it is the only present-tense
breakage claim, it is small enough to verify by hand, and it does not depend on the
disputed partition.

Verified: backlog_status_check.py passes at 529 items, each declaring exactly one
status; #1263 remains open with one banner. The item span was bounded by the next
heading rather than by eyeball -- an earlier edit this session was wrong because a
hand-picked line range silently truncated half an item.

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

* backlog: file #1266 and #1267 -- the clock's fanout, and the rubric that cannot describe it

Routed by the role-playbooks seat as an unclaimed CODE item it could not take (that
seat edits roles/ only), with an explicit request that the two be kept apart. They
have different owners and different artefacts: #1266 is the broadcast/roster code,
#1267 is prose living in the running clock's own send. Merging them would produce
one item that neither owner can close.

#1266: the clock is healthy and the fanout is not, and conflating those is why this
sat as three seats' corroboration for a day with nobody owning it. 112 firings at a
10.0 min cadence, but recipients per firing swing 1 to 11, median 2, and 53 of 112
reached exactly one seat. The dropout is per-seat-per-firing, not an outage window.

Two innocent explanations are recorded as already refuted so nobody re-runs them:
roster growth fails because the count collapses and recovers, and "the seat was
dark" fails first-hand on a skip bracketed by that seat's own sends 5 minutes before
and 93 seconds after.

Three candidate mechanisms are enumerated and explicitly NOT diagnosed. Candidate 2
(a deliberate withhold to seats carrying undrained mail) is flagged for stricter
treatment than the others: it was offered and retracted by its own author as never
measured, and from a receiving seat a correctly-withheld send and a failed one are
the same observation, so it must be excluded by reading the code. It is also the
reassuring answer, and "working as designed" ends investigations.

I recorded this seat's own tick receipts -- four ticks, three closed gaps of 9m55s,
10m04s and 9m56s, all on-grid -- with an explicit warning that they are NOT evidence
against the item. A clean receipt at one seat is precisely what a fanout defect looks
like from inside a seat that got the messages, and a later reader would otherwise be
entitled to cite it as a refutation.

#1267: the rubric asks about cadence, so a seat hitting a quantised gap is instructed
to file the wrong diagnosis. Confirmed first-hand -- four ticks received here each
elicited a cadence report, which is the only shape that text can produce. The sharper
half is that a seat took six perfect ticks, answered "nothing changed" correctly every
time, and stayed stopped for forty minutes: every answer right, the sequence
concealing the stoppage. The concealing sentence is live and verbatim in the ticks
received while filing this.

Both items carry a how-to-prove-a-fix that requires the check be shown able to FAIL --
a suppressed seat that must be named for #1266, and for #1267 the constructed case
where the old text says sleep and the new text says work. A rewrite that produces the
same instruction on that case has changed the wording, not the rubric.

Verified: backlog_status_check.py passes at 531 items, each declaring exactly one
status; both new items parse as open with one banner. The warning-sign count in
docs/BACKLOG.md is unchanged at 121 -- these four items added none, which matters
since #1265 was filed against that population.

Numbers allocated atomically via scripts/coord/alloc.ps1.

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

* backlog: retract #1267 limb A -- it was already fixed when I filed it, and I misquoted it

Checked against the primary artefact rather than the note I filed from: an
untruncated live tick delivered to this seat's own mail box at 22:21:11Z, read from
disk. Limb A is struck, limb B is confirmed live. The retraction is kept in place
rather than deleted.

Limb A is wrong twice over. The defect was fixed before I filed it -- the live rubric
already carries the divide-by-ten discriminator, quantised-versus-ragged, the
53-of-112 measurement, and two refinements no measuring seat had. And separately, the
rubric text I quoted against it does not exist: "the chain is broken and you are
awake only by luck" returns ABSENT. Filing a quotation from a note rather than from
the artefact would have made the item unfalsifiable to anyone who checked the phrase.

Limb B survives and is better evidenced than when filed. "Act only if something
actually changed" and "Waking is not a reason to do work" are both PRESENT verbatim,
and the replacement this item proposes is ABSENT, so the fix has not been applied.
It was confirmed in a tick received while filing the retraction of limb A.

One probe result is recorded as a false negative rather than as evidence: "failed
SEND" read ABSENT only because a line wrap splits the two words. The phrase is
present. Left in the item because a probe reporting a real absence and a probe
reporting a line break look identical in the output.

How it happened is recorded because two seats hit it within one hour: both this seat
and the routing seat wrote limb A from notes predating the rewrite, and the routing
seat caught itself the same way, by a live tick landing in its own box. The primary
artefact was one message away in both inboxes. Same family as this ledger's own
landed #1242 amendment and a builder's commit message that already answered a
question four seats then argued about -- the note outlived the code.

Verified: backlog_status_check.py passes at 531 items, each declaring exactly one
status; #1267 remains open with one banner.

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

* backlog: #1235 contradicts itself -- its corrected banner and its body disagree on enforcement

The item says both "the detector is NOT wired into nothing" (the corrected banner)
and "(2) The detector is wired into nothing ... committing a new dangling citation
turns nothing red" twelve lines below. Both cannot be true, and a builder reads the
lower one last. This is the same intra-item contradiction corrected in #1241 earlier
today, where a superseded paragraph sat below the amendment that superseded it --
twice in one item set, so it is recorded as a pattern: correcting a banner does not
correct the body it summarises.

What survives: there is genuinely no gate-config wiring. dangling_citation_check is
referenced by zero files under .github/, .pre-commit-config.yaml or pyproject.toml,
and the identical grep shape returns 4 for backlog_status_check, so the instrument
fires and the zero is real.

What no longer holds is the consequence the item draws from that. The committed suite
IS the enforcement path: tests/test_dangling_citation_check.py:212 walks the real
docs/ tree with the population pinned above 200 files, and :220 turns red on a
live-shape citation. So a new dangling citation does turn something red. Measurement
(2) must be read as "wired into no gate CONFIG", never as "nothing enforces it" --
which is exactly residual (3)'s point, since enforcement rides the pytest path.

Residual (1) is confirmed live with its null controlled: main()'s exit-code contract
and the --advisory escape have zero coverage in that file, confirmed against two
positive controls rather than asserted. An uncontrolled zero is the error this ledger
keeps repeating and this is not one.

Provenance is recorded in the item: the reading is a builder's, offered as a reading
rather than an edit because builders may not author ledger content. The measurements
were taken here to test that reading before acting on it rather than relaying it.

Coordination: the overlap guard blocked this edit while another live session held
uncommitted docs/BACKLOG.md changes. I did not override it. I asked that session
whether they were touching #1235, waited, and proceeded only after their worktree
went clean and their diff was measured to touch #1235 in zero lines.

Verified: backlog_status_check.py passes at 531 items, each declaring exactly one
status; #1235 remains open with one banner.

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

* backlog: #1263 -- the 1,193-vs-1,184 gap is unadjudicable, and that is the finding

Two corrections and one measurement, all arriving after the pricing was filed.

The attribution in the pricing block is wrong and the measuring seat caught it
themselves: there was no such pass by that seat. The figures were relayed verbatim
from an earlier handoff under a heading that read as first-hand measurement. A
relayed number wearing a measured number's clothes -- the same defect the 923/906
note was preserved to prevent, one line earlier.

A bare total over the live ledger is not a fact; it is a fact only paired with a ref.
Re-measured under a stated rule and independently reproduced here with a separately
written matcher, agreeing to the unit at all four refs: 809/381 at origin/main,
fb72075d and 0b6ccc47, and 807/381 at a stale checkout's main. So 1,193 and 1,184
were both recorded without a ref and have no shared denominator -- unadjudicable as
posed, rather than one right and one wrong.

The rule is validated on the stable half, which is what makes the drift trustworthy:
the archived file returns 381 at every ref, exactly the figure the item already
carries. Archived items barely move, so reproducing that to the unit is evidence the
re-measurement's rule matches the item's own.

Three independent demonstrations of the item's thesis are recorded, all measured the
day after filing: the live count moved 809 to 819 within a single session because
four filings by one seat added ten citations; a sibling item's citations moved 74
lines on a PR head before that item was a day old; and the 807/809 split is itself
the drift, caught between two refs of one branch.

One thing is left unexplained rather than smoothed: the item says 812 live and the
re-measurement returns 809 at every ref tried, including one near its own filing.
Delta 3, live-file only. 1,190 is explicitly NOT proposed as a replacement, because
publishing a third bare total would repeat the exact error being recorded.

The build recommendation sharpens in the same direction as the printed-silence rule:
the detector must print its REF and its DENOMINATOR, not only its findings. A
detector reporting "N citations need re-checking" with neither is this defect one
level up.

The 11-past-EOF figure is untouched and remains the one present-tense breakage claim.

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

* backlog: fence #1265's scope -- it does not authorise the glyph removal that has an ordering hazard

Relayed from the ASVS tracker via the Liaison, verified here against the code before
acting on it. The finding is real and it binds a population this item never measured,
which is the distinction worth writing down.

Verified: scripts/asvs/apply.py refuses to write a record entry whose prose carries a
banned glyph, and U+26A0 is the FIRST character in that class (_BANNED at :26). The
refusal runs in the validation phase at :217; the field-preservation invariant runs
later at :275. So the ordering claim holds structurally, on this repo's own code.

The hazard: for at least one entry carrying a top-level non-scalar value, that glyph
refusal is currently the only thing preventing a write that would type-mangle the
value into a quoted Python repr, which parses, so nothing goes red. The refusal was
designed to keep glyphs out of security prose and nothing records it as a control for
type-mangling. It holds by accident of ordering -- a compensating control resting on a
premise nobody wrote down, except the premise here is "a glyph happens to be present",
which is what this item proposes removing.

But it does NOT bind this item's own sweep. #1265's measured population is the engine
repository: 496 occurrences across 80 files, scripts/ and the web console at zero. The
maintainer-internal record is a separate repository this census never touched, so
nothing in the item's table says anything about it.

What is added is therefore a FENCE rather than a change of plan. The sequencing stays
as filed -- shipped operator docs first, the two ledger files last, none of it gated on
#1242. The risk being fenced is that a later reader treats this item as the mandate for
glyph removal everywhere and inherits an ordering precondition it never priced. Anyone
extending the sweep beyond the engine repo owns that precondition and must land #1242
first.

Written public-safe: no cell identifiers, no coverage figures, mechanism only. The
amendment names the codepoint rather than pasting the character -- verified that
docs/BACKLOG.md still carries 121 occurrences, unchanged from origin/main.

Verified: backlog_status_check.py passes at 531 items, each declaring exactly one
status; #1265 remains open with one banner. Checked the concurrent Lander branch first:
it touches #1265 in zero lines.

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

* backlog: cross-link #1020's hang rider to #1257, and rule the landing order

The rider on #1020 already recorded that its refusal path is unverified under the
runner that ships and that a hang would be worse than the defect the item fixes. What
it could not say, because the number did not exist when it was written, is that the
pre-existing condition is now filed as #1257 (value 8, difficulty 5, not started).
Cross-linked so the rider stops being a warning nobody can act on. The mechanism is
not restated -- SDS-3.5, state it once and link.

Ordering ruling recorded: build #1020 now, do not close it before #1257 lands. One
measured fact sharpens the rider's own reasoning -- option (b) adds a SECOND refusal
to a code path whose refusals are already known to hang, and the existing
security-channel refusal at __main__.py:2314-2322 is exactly that path. Landing the
gate alone would make the product worse than leaving the defect in, because an
operator can see a wrong readiness answer but cannot see a process that never
finishes starting.

Building it anyway is deliberate: the gate is needed on any ordering, the owner's
option (b) ruling is settled, and the two fixes do not conflict -- one adds a refusal,
the other makes refusals terminate. Parking it would idle a lane against an unclaimed
item. The constraint binds closure and landing order, not the build.

Also recorded: the four commits citing #1020 that are on no ref of origin/main look
like the duplicate-work shape that has already cost this project two rebuilt items,
and they are not it. Measured by the building lane and carried here: the branch
holding two of them touches auth/service.py only, does not touch __main__.py, and its
security_channel_ready block is byte-identical to main's SMTP-only version. The
option (b) gate is genuinely unbuilt; those commits belong to #1257's subject.

Verified: backlog_status_check.py passes at 531 items each declaring exactly one
status; #1020 and #1257 both still open with one banner; warning-sign count unchanged
at 121. Checked the concurrent Lander branch first -- it touches #1020 in zero lines.

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

* backlog: narrow #1020's ordering ruling -- the #1257 dependency is placement-conditional

My ruling of an hour ago asserted that #1020's fix adds a second refusal to a path
already known to hang, and therefore must wait on #1257 unconditionally. That is too
strong. The building lane narrowed it and I verified their measurement to the line
before amending.

Measured on origin/main: _serve spans __main__.py:1042-2833 and is a PREFLIGHT that
runs before the ASGI lifespan -- its own comment at :1541 says the Engine loads inside
the lifespan, well after. It holds 32 return-2 sites, and the existing security-channel
refusal at :2314-2322 reaches one at :2329, a clean pre-engine process exit. #1257's
hang is an exception after engine.start(), inside the lifespan, and engine.start() does
not appear in __main__.py at all. The hanging path and the existing refusal are not the
same path.

So a lifespan-placed check lands on the hanging path and genuinely gates on #1257,
while a preflight-placed check refuses with the same clean return 2 as the SMTP gate
beside it and carries no #1257 dependency.

The hazard is still real and explains the rider: the check needs to know whether an
enabled Administrator has a deliverable address, which is a store read, and the store
is not open in the preflight -- _serve has settings only. So the natural implementation
is the lifespan, which is the hanging path, and the lane that wrote the rider was right
on that assumption.

Recorded as a design fork rather than a block. Preflight costs one short-lived
read-only store open and buys independence from #1257; lifespan costs nothing extra and
inherits the dependency. The building lane recommends preflight and is writing an ADR;
if it proves unworkable the fallback is lifespan and the dependency returns, so the
cross-link stays live either way.

Also recorded: do not write this fork as "(a) versus (b)". The owner's settled ruling
on this item is already called option (b), and a second (a)/(b) pair over one item is
the exact label collision #1242 exists to record. Named by placement instead.

Verified: backlog_status_check.py passes at 531 items each declaring exactly one status;
#1020 and #1257 both open with one banner; warning-sign count unchanged at 121. The
concurrent Lander branch touches #1020 in zero lines.

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

* backlog: stop tracking #1020's live design in the ledger, and drop a withdrawn recommendation

My previous amendment recorded that the building lane "recommends PREFLIGHT and is
writing an ADR". That recommendation was withdrawn by its author within the hour, so
my committed text was carrying a stale conclusion under a fresh banner -- the exact
corrected-banner-over-stale-body defect this ledger has recorded twice today, this
time self-inflicted.

Two measurements withdrew it, and they are kept because they are stable: list_users()
is async (store/base.py:1548, all three backends), and _serve never opens a store at
all -- zero open_store calls across 1042-2833, the only hit a comment at :1240. So
preflight placement is not one extra store open; it is the first store open in a
preflight that has never had one, driven from sync code.

The open question is recorded rather than answered: whether a point exists inside the
lifespan but before engine.start() where the store is already open and a raise still
exits cleanly. #1257's hang is specifically an exception AFTER engine.start(). That
placement would need neither an extra open nor #1257 -- but it is UNVERIFIED, and the
rider's evidence is suggestive without settling the ordering. Whoever builds it
verifies the precondition first.

The substantive change is the boundary: this item now records that a placement
decision exists and what it turns on, and explicitly does not track the decision while
it moves. The design belongs in an ADR. #1020 has been amended three times in about
forty minutes by a design still in motion, and a ledger item that transcribes a live
conversation reads as settled at every intermediate state.

The #1257 cross-link stays live -- it binds under lifespan-after-start placement and
is retired only by a placement demonstrated to sit outside that window.

Verified: backlog_status_check.py passes at 531 items each declaring exactly one
status; #1020 still open; warning-sign count unchanged at 121.

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

* backlog: name #1020's third placement EARLY-LIFESPAN and pin where its precondition is measured

Vocabulary and a coordinate, not design. Both are stable facts, which is why they go
in the item while the placement decision itself stays out of it.

The third placement had no name, and its author's instinct was to call it "(c)" --
which would have been the third label in an (a)/(b)/(c) scheme over an item whose
owner ruling is already called "option (b)". That is the label collision #1242 exists
to record, arriving for the second time on one item in one hour. Named EARLY-LIFESPAN
so it can be cited without colliding.

The coordinate matters more. The item previously said the precondition was "inside the
lifespan but before engine.start()", which is correct and unlocatable. Measured:
engine.start() does not appear in __main__.py at all; lifespan is defined at
api/app.py:5512 and await engine.start() is at api/app.py:5731. So the open question is
a question about the ~219 lines between them -- is the store open there, and does
raising there exit rather than hang. That turns "measure it somewhere in the lifespan"
into a bounded read.

Verified: backlog_status_check.py passes at 531 items, each declaring exactly one
status.

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

* backlog: #1020 -- EARLY-LIFESPAN's existence half is measured and holds; only the raise half is open

This is a conclusion rather than a step, which is the bar I set for touching this item
again. One of the two open questions is answered and the item said both were
unverified, so leaving it would have been stale in the direction that matters.

Measured at origin/main in messagefoundry/api/app.py and independently confirmed:
lifespan at :5512, store = await open_store(...) at :5540, await engine.start() at
:5731, yield at :5923. Between :5540 and :5731 -- 191 lines -- the store is open and
engine.start() has not been called. So EARLY-LIFESPAN is a real placement rather than a
hypothesis, and a check placed there gets the open store as a plain await with no extra
open and no sync/async bridge.

The only engine.start text inside that span is a comment at :5640. Recorded because my
own verification nearly reported the window as narrower than claimed: a substring probe
found the comment, and a second probe printed a verdict line that contradicted its own
data because the range bound and the label disagreed by one. The data was right and the
summary was wrong -- the same shape as the truncated count corrected earlier on this
item, and the reason the rows are quoted here rather than a conclusion about them.

What remains open is the half that decides the placement: does raising between :5540
and :5731 terminate rather than hang under uvicorn. #1257's mechanism says an exception
AFTER engine.start() unwinds nothing, so raising before it should stay outside the
hanging window -- but that is an inference from the mechanism, and this item's rider
exists precisely because someone inferred. One runnable test settles it. If it exits,
EARLY-LIFESPAN is clean and #1020 carries no #1257 dependency; if it hangs, the window
is contaminated and both the LIFESPAN placement and the dependency return.

Verified: backlog_status_check.py passes at 531 items, each declaring exactly one
status.

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

* backlog: add #1245's intermittent-failure diagnostic, and stop the wrong-assertion hunt

Routed to me as content by the lane that did the read-only pass. They could not write
it themselves: #1245 is claimed by another worktree, and a commit citing the item from
their lane would either collide with that claim or drop the citation to go green. Both
are wrong, so it routes. Amending a landed item needs no claim -- the coordination gate
scopes to code-touching diffs -- so the claim is untouched and its holder is not
disturbed.

The correction leads because it saves an entire pass: the recorded failure is
must_change_password False after admin_reset_password, not the retirement assertion.
One pass was already spent on the wrong assertion, and the answer was sitting in that
seat's own episode note two files away.

Six candidate mechanisms are recorded as refuted-by-reading rather than dropped, so
nobody re-derives them: the set_password placeholder alignment, argon2 rehash-on-login,
admin_reset_password overwriting itself, change_password suppressing the flag,
_opt_float coercion, and _commit() moving a boundary. The mechanism was not found and
no seventh is offered as a story -- a plausible unverified mechanism is exactly what
this ledger keeps having to retract.

The actionable half is the diagnostic. The failure cannot distinguish three causes --
the write never landed, it landed and was reset, or the read path is wrong -- because
all three produce an identical red. A paired read at the same instant, the store record
and the raw row together, splits them on the next occurrence. Two lines, and it does not
require reproducing the failure first, which is what makes it worth adding before anyone
tries to.

Also recorded: do not close on a green re-run, which is consistent with an intermittent
rather than evidence against one. And a structural note that the only transaction-
boundary hazard in the file is a group-committer that cannot be active in this test but
should be checked first on a server backend.

Verified: backlog_status_check.py passes at 531 items each declaring exactly one status;
#1245 still open with one banner; warning-sign count unchanged at 121. The concurrent
Lander branch touches #1245 in zero lines.

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

* backlog: #1017 is ALREADY-DONE -- it was fixed under #1041 and its banner still says "not started"

A builder claimed it, found it built, released the claim and reported it rather than
quietly moving on. That report is the only reason it was not about to be claimed a
third time, so the evidence goes in the item where the next reader sees it.

All three of the item's own measurements have inverted, verified against origin/main
5e86bdfb by ref rather than a working tree: the rule-3d block does now mention cwd
where the item says a grep returns zero; the deny text it quotes survives only as a
historical comment with no live deny string; and the session_id/transcript_path gap is
still real but is now openly declared by the code's own comment rather than hidden.

The fix is better than the item asked for and its comment records why. Rule 3d once
justified having no cwd check by arguing that git refuses to remove the worktree you
are standing in; that inference was found unreachable under #1041, so the check is now
made rather than argued for. It establishes that this IS the tree you are standing in
and explicitly does not claim the converse, so the deny text asserts only what is
checked -- which is the subtlety this item was filed to get.

Closure is deliberately not performed. backlog_status_check.py hard-errors on an item
carrying both a closed and an open banner and on a shipped item still carrying a
Priority, so closing is three coupled edits including a verbatim archive move. The
banner records the evidence so whoever closes it does not re-measure.

Also recorded, because BUILDER.md section 6 leaves it open: the builder released rather
than held, on the grounds that COMMON's release condition is fix-text-on-main and for an
ALREADY-DONE item the text is on main by definition. That answers the open question in
one direction only -- ALREADY-DONE is releasable, CONCLUDED-AS-RESEARCH is not, because
there is no text.

Verified: backlog_status_check.py passes at 531 items each declaring exactly one status;
#1017 still parses as open with one banner and zero closed; warning-sign count unchanged
at 121. The concurrent Lander branch touches #1017 in zero lines.

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

* backlog: #340 is not buildable as written -- two required contexts cannot report in a merge queue

A builder took the item, read it before building, and found an unstated design fork.
Recorded here rather than left in mail, with the measurements verified independently
before I acted on them.

The blocker is structural rather than an oversight. required-contexts.txt is the
authoritative set -- 13 contexts across exactly four workflows -- and two of those four
cannot report in a merge queue at all, because their subject IS the pull request.
cla.yml triggers on issue_comment and pull_request_target only, neither of which fires
on merge_group, and its action needs a PR to act on; a merge group has no PR.
backlog-hygiene.yml has no merge_group trigger and seven references to
github.event.pull_request, all null in a merge_group event, and its question -- does
this PR update BACKLOG.md -- has no meaning for a merge group.

So adding merge_group to four files produces a queue that hangs on cla forever: the
required-but-absent trap that required-contexts.txt documents in its own header, and
which this repo has already been bitten by. Half B done naively creates the exact
failure its own source of record warns about.

The item is also two things with different owners, which is now stated: enabling the
queue is a repo-settings change and therefore the owner's, while the merge_group
triggers would be builder work. A claim on this number means half B unless it says
otherwise.

The three-way fork is recorded with the shim rejected on durability rather than on
correctness -- a green that means not-applicable on the merge path decays into a green
that reads as passed, which is the defect ADR 0158 names -- and narrowing the required
set rejected outright, since branch protection is not per-event and it would trade a
merge race for an unsigned-CLA merge. Recommendation is do-not-enable, on the grounds
that the item's own re-score dropped it from 8 to 6 because a workaround exists and is
exercised, and the shim cost was not in that score.

One thing is ruled rather than recommended: half B must not be built speculatively. A
precondition is inert only while nothing consumes it, and once the workflows carry
merge_group, enabling the queue looks like a one-click finish and the structural
exception gets built under time pressure instead of deliberately.

Verified: backlog_status_check.py passes at 531 items each declaring exactly one status;
#340 still open with one banner; warning-sign count unchanged at 121.

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

* backlog: #1216's Scope prescribes a skip; the fix ships loud failure, and the remedy predates the filing

Routed to me by a builder because authoring ledger content is not theirs and #1216 is
landed, so it is amendable from any worktree. Verified the divergence by reading the
item before amending it.

The item's Scope says "skip honestly when it cannot -- naming which interpreter was
found". The build does loud failure instead, and that is correct: ci.yml sets
defaults.run.shell bash on every OS, so a leg without a usable bash cannot run the gate
at all. A skip there is a green that proves nothing -- the silent-control shape ADR 0158
names -- and worse than a red, because a red gets investigated. The resolver tries
git-derived candidates first, so loud failure fires only when no bash on the machine can
read a file the process just wrote, at which point the box cannot run the suite anyway.

The sharper fact is that the remedy was already in the tree when this was filed.
tests/test_merge_gate_controls.py solved it on 2026-08-10, one day before this item was
filed on 2026-08-11, shipping a git-derived candidate list, a live positive control that
writes a token and requires the candidate to read it back, and a require-helper whose own
text reads "a bash that can see this process's files, or a loud failure -- NEVER A SKIP".
So the Scope contradicts a module in its own test directory that had already refused that
exact remedy with the reason written down.

That narrows the defect: it is not a missing design, it is three modules that never
adopted an existing one. Copy-versus-single-source. The fix is promotion and adoption,
keeping a 127-versus-2 discrimination as a second layer so a harness failure cannot
impersonate a syntax error.

Also recorded: the count drift from 154 to 160 blocks is corpus growth, not a
discrepancy, and the 100-percent signature that is the actual finding held at both sizes.
And #1272 is this item's duplicate, closing with a pointer here -- filed onto an unpushed
branch because neither author could see the other's tree, with this number surviving on a
mechanical asymmetry rather than on merit.

Verified: backlog_status_check.py passes at 531 items each declaring exactly one status;
#1216 still open with one banner; warning-sign count unchanged at 121. The concurrent
Lander branch touches #1216 in zero lines.

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

* backlog: the quantised-gap discriminator is three-way, and one of the three is not a fault

Relayed by the seat that supplied #1266's original write-up, correcting its own evidence.
I filed the two-way version into #1266 and wrote #1267's retraction on the same
understanding, so both items needed it.

The correction: a quantised gap has three explanations, not two. A fanout skip is a
fault and is #1266's subject. A failed send is a fault. But a RECIPIENT UNAVAILABLE --
a dark seat the clock correctly withheld from rather than spamming -- is not a fault at
all. Measured instance: a gap of 79m56s, exactly 8.0 cadences, on-grid at both ends,
quantised by the test and working as designed.

So a quantised gap is evidence of a delivery event that did not land, not evidence of a
defect, and anyone measuring fanout from their own gaps must exclude intervals in which
they were dark or correct suppressions get counted as skips.

#1266 stands and what survives is stated so nobody over-corrects: the 53-of-112 figure
came from recipient sets across firings in the mail store rather than from anyone's gap,
and the collapse-and-recover sequence, the 93-second bracket and the prove-the-fix are
all untouched. Only the cheapest supporting evidence got weaker.

#1267 gains a third limb, and it is explicitly not the retracted limb A resurrected.
Limb A was withdrawn because the rubric does name a fanout fault; this is the opposite
problem -- it names it and misclassifies it. The live text asserts quantised means every
firing happened and none reached you, a delivery fault, which is wrong in one of three
cases. The rubric therefore instructs every seat to report a correct suppression as a
delivery fault, inflating the count #1266 rests on. The fix is a third branch rather
than a reworded second one.

Recorded against myself: I have reported cadence at every tick tonight using the
two-way test. Those readings were not wrong, because this seat was never dark -- but the
method was incomplete the whole time and would have misclassified the first gap it saw.

Verified: backlog_status_check.py passes at 531 items each declaring exactly one status;
both items still open with one banner; warning-sign count unchanged at 121. The
concurrent Lander branch touches neither item.

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

* backlog: #1265's fence is right and its stated reason is false -- the glyph barrier has a sanctioned bypass

I wrote 0838d50f on a relayed premise, verified that premise from the code, and the
premise itself was wrong. The relaying seat corrected it unprompted; the measuring seat
had corrected themselves first. Verified here before amending, at apply.py:216:

    blob = "" if anchor_repair else " ".join(str(v) for v in (c.get("residual", ""),))

On the anchor-repair path the banned-glyph check searches an empty string, so it cannot
fire. An anchor repair was measured writing a corrupted table with exit 0 while the
ordinary write was refused on the same content. And the bypass is not an obscure code
path: an anchor repair is the one write that role may perform unasked, so it is the
routine, sanctioned operation.

My amendment therefore asserted that the glyph refusal "is currently the ONLY thing
preventing" the type-mangling write and "holds the line by accident of ordering". It
does not hold the line. That text describes a compensating control that does not
compensate, which is SDS-3.7 -- written into an amendment whose own subject is an
SDS-3.7 defect.

The correction is placed BEFORE the false paragraph rather than replacing it, so a
reader meets it first, and the wrong version is kept rather than deleted because the
ordering argument alone would lead a later reader to re-derive it.

What does not change: the fence and the ordering. Corruption-before-glyph still holds,
the type-mangling repair still lands first, and #1265's engine-repo sweep is still not
gated on it. What changes is the urgency, in the uncomfortable direction -- the exposure
does not wait for the sweep, it exists now through the sanctioned path.

Verified: backlog_status_check.py passes at 531 items each declaring exactly one status;
#1265 still open with one banner; warning-sign count unchanged at 121. The concurrent
Lander branch touches #1265 in zero lines.

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

* backlog: #1257 is BUILT and unlanded on PR #394, not "not started" -- and #1022/#1259 are the same trap

A builder pulling from the unclaimed pool found the class and predicted it rather than
discovering it after a rebuild. Verified here against refs/pull/394/head (0b6ccc47).

My own #1020 amendment described #1257 as "value 8, difficulty 5, not started". That is
false. PR #394 carries 0e9c104b "fix(api): unwind the lifespan when startup fails after
engine.start() (#1257)", ccfdfd90 asserting the teardown does not mask the startup
error, and a new tests/test_lifespan_startup_unwinds.py. #1022 and #1259 are on the same
PR: b680ee0d and 261ae1c9 for #1022, and #1259's parse_items conflict-marker refusal
measured directly -- 300 lines and zero conflict signals on origin/main against 326
lines and eleven on the PR head.

All three read OPEN on origin/main and are UNCLAIMED, so the pool offers them as fresh
work and the next lane to pull one rebuilds what exists. This is the #1216/#1272
duplicate four hours later from the same root cause -- unlanded work is invisible to the
pool -- with the difference that it is now predicted rather than discovered.

The consequence for #1020 relaxes rather than tightens the constraint. The hang forcing
the placement decision is fixed on that PR, and the measurement showing a post-start
raise hangs was taken against origin/main, which lacks the fix. EARLY-LIFESPAN remains
correct because it avoids the window entirely, but once #394 lands the LIFESPAN-after-
start placement stops being disqualified -- so the ADR must record that constraint as
ref-scoped rather than permanent. The ordering ruling is unchanged: do not close #1020
before #1257 lands.

Also checked and NOT a trap: #1235 is genuinely unbuilt on both origin/main and the #394
head -- 21 test definitions and zero main/SystemExit/advisory symbols on each -- so the
force-release and the "genuinely free" statement to a builder both stand.

Verified: backlog_status_check.py passes at 531 items, each declaring exactly one status.

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

* backlog: #1266 -- expired/ is a real instrument, and the suppression REASON is retained nowhere

Another seat found that the mail store moves undelivered messages to expired/ rather
than deleting them, which converts an absence into evidence and defeats this item's own
"no seat can tell from its own inbox" premise -- true of the inbox, false of the box.
That is theirs and it is a genuine improvement.

Verified independently on this seat's own 132-minute outage: last tick consumed at
00:41:10Z in seen/, the 00:51:10Z tick delivered but never consumed and now in expired/
-- proof the send was made -- then no file of any kind until resume, with seat-tick.last
reading dispatcher=STALE(no-live-session). So this seat's long gap contains zero faults,
and anyone counting it toward this item would be counting a correct suppression.

But the discriminator is two-way as offered and the truth is four-way: a file in
expired/ means the send reached an unavailable recipient; no file with the seat marked
BACKLOG-suppressed or STALE means correctly withheld; only no file with the seat live
and unsuppressed is this item's fault. And one unconsumed message causes the silence
after it, because a pending message marks the box backlogged and suppresses subsequent
sends -- so a single expired file explains an arbitrarily long following gap, and
counting those intervals overcounts without bound.

The limitation that matters is measured: the suppression reason is retained nowhere.
seat-tick.last is a single line overwritten every tick, and seat-tick.state.json holds
one timestamp per worktree and no state. So expired/ answers "did it reach me"
retrospectively and nothing answers "why not".

That upgrades this item's cheap fix rather than replacing it. Comparing recipient sets
across firings reports who was absent, never why, and a seat absent because it was
correctly withheld is indistinguishable from one that was skipped. The clock must log
its per-seat decision per firing -- sent, suppressed-backlog, suppressed-stale, skipped
-- or the comparison counts correct behaviour as a fault. One durable line per firing,
not a redesign.

Verified: backlog_status_check.py passes at 531 items each declaring exactly one status;
#1266 still open with one banner.

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

* backlog: warn on the three pool traps in the items themselves, not just in a handoff note

#1022, #1257 and #1259 read OPEN and UNCLAIMED on origin/main while their fixes sit
built on PR #394. The finding was recorded in my episode note, which is the wrong place:
a lane pulling from the pool reads the ITEM, and the playbook is explicit that a finding
living outside the ledger is rediscovered at full cost.

Each now carries a DO-NOT-BUILD banner naming the PR, the specific commits or the
measured markers, and the condition that clears it. Placed before the status banner so a
puller meets the warning first rather than after reading the item.

Re-verified before writing, because I had been dark for two hours and a landed #394
would have made all three banners false: origin/main has moved to 2b64170a, but
backlog_status_check.py is still 300 lines with zero conflict signals and
tests/test_lifespan_startup_unwinds.py is still absent. Still unlanded, banners still
correct.

#1257's banner also records why its unlanded state is load-bearing for #1020: that
item's placement decision rests on a post-engine.start() raise hanging, which was
measured against origin/main and is fixed on #394. So #1020's constraint is ref-scoped
rather than permanent, and the two items must be re-read together when #394 lands.

None of the three banners carries a status glyph, so item status is untouched.

Verified: backlog_status_check.py passes at 531 items each declaring exactly one status;
all three items still open with one banner and zero closed; warning-sign count unchanged
at 121.

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

* backlog: #1267 gets the rubric's replacement procedure, and it shares a prerequisite with #1266

Supplied by the seat the clock wakes, relayed by the seat that owns the playbooks.
Neither may author ledger content, so it routes here.

The rubric's real defect is that it STOPS: it states that a quantised gap cannot
separate a fanout skip from a failed send, and then ends -- a dead end in the exact case
it exists to diagnose. It is resolvable, just not from one seat alone.

The replacement is three-branch and the ORDER is load-bearing. Check your own per-seat
status for the missed cycles first, because suppression is the cheapest test, needs no
second seat, and is the innocent explanation. Only if not suppressed, compare a second
seat's log for the same cycle: another seat received it means a fanout skip; no seat
received it means a failed send or the scheduler, checked against the task's own
LastRunTime and LastTaskResult, which are independent of mail.

The counterweight ships with it or step 1 becomes the new defect: confirm suppression
from the clock's recorded status for those cycles, not from the plausibility of the
story and not from a current label, which is transient and describes the mail queue
rather than liveness. Test the reassuring branch with the same evidence you would demand
of the alarming one.

Recorded because it is the sharpest part: a two-branch version was proposed and withdrawn
by its own author. Under two branches, "another seat received that cycle" resolves to
fanout skip -- and a correctly-suppressed seat produces exactly that evidence, so the fix
would have manufactured phantom fanout bugs out of the clock working. That is the same
overcount #1266 records, arriving through the remedy rather than the original text.

And the procedure is currently unrunnable retrospectively, which couples the two items:
step 1 needs per-cycle status, and that status is retained nowhere -- the state line is
overwritten every tick and the state file holds one timestamp per worktree. A seat with
no evidence for step 1 is pushed straight to step 2, the branch that overcounts. So both
fixes share one prerequisite: the clock must log its per-seat decision and its reason,
per firing, durably.

Verified: backlog_status_check.py passes at 531 items each declaring exactly one status;
#1267 still open with one banner; warning-sign count unchanged at 121. Checked the
concurrent Lander branch first -- it adds #1269 through #1272 and touches #1267 in zero
lines.

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

* backlog: #1020's #1257 dependency is REAL -- my narrowing overturned, the gate is parked

The building lane overturned their own recommendation and my narrowing with it, by
writing the code rather than reading it. My original unconditional ruling was right.
Verified the ordering independently at origin/main before amending.

The lifespan runs open_store at 5540, engine.start at 5731, AuthService construction at
5837, and initialize at 5852, with _ensure_bootstrap_admin at auth/service.py:517 inside
that call. So a pre-start check faces two independent blockers, either one fatal: there
is no AuthService to ask before 5837, and on a first run there is no administrator at
all because initialize is what creates it. A check in the 5540-5731 window would refuse
every first run, before the account it is about exists -- converting a gate that reports
a wrong answer into one that prevents startup. That is worse than the defect, and it is
the identical test used to rule the post-start placement out.

So the check must sit after 5852, inside the post-engine.start window, and the #1257
dependency returns intact. The placement that has the data is the placement that carries
the hazard; there is no third option.

Recorded because it is the part none of us saw: this vindicates the original rider's
author for a reason all three parties missed while arguing. They assumed the lifespan
placement and were right -- not because the store is there, which is what we debated,
but because the data the check needs does not exist until after the engine has started.

Dispatch ruling: do not build the gate yet. #394 rewrites the exact window the check must
live in -- api/app.py grows from 5985 to 6065 lines there and its fix is that window's
unwind behaviour -- and it is draft and disarmed, so there is no landing to race.
Building against a structure about to change, in the one place the code must go, is
waste. Re-approach against the post-#394 tree.

What survives and is not parked: the deliverable-address decision, the predicate itself
built and tested, the ROLE scoping, and the exit-code findings, which were always about
the post-start path.

This entry is marked as the convergence rather than another intermediate state. The item
amended five times in forty minutes earlier tonight while a design was live, which was my
error; it stops tracking this design here.

Verified: backlog_status_check.py passes at 531 items each declaring exactly one status;
#1020 still open with one banner.

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

* backlog: #1266 -- the clock already decides out loud; only the retention is missing

A builder analysed seat-tick.ps1 rather than building against my framing, and my framing
was wrong in a useful direction. Verified line-by-line here before amending.

I wrote that the suppression reason is retained nowhere and proposed making the clock log
its per-seat decision and reason. The clock already computes exactly that: $results holds
one entry per seat per firing carrying the decision AND its reason, across seven states.
What is missing is retention -- Save-Heartbeat writes one line with Set-Content and the
previous firing's reasons are gone. So the first deliverable is "stop discarding what it
already says", which is a far smaller change.

And the overwrite is deliberate with its reason in the code at :80-81 -- a heartbeat, not
a log, and an append-only file would need rotation. Any append-only design must answer
that rather than flip it.

Three of the seven states were missing from my corrected table. THROTTLED and COLD are
further correct-withholding cases. FAILED is a fault but a DIFFERENT one: the send was
attempted and refused, and this item's title is "drops seats" -- folding a refused send
into a drop hides a mail-layer fault behind a fanout one.

A genuine silent drop the table did not cover at all: an opted-out seat produces no line,
because :343 continues before anything reaches $results. So clock:false is
indistinguishable from "never considered" and from "does not exist" -- the one case where
evidence is never produced rather than merely discarded. Emitting SUPPRESSED(opt-out)
costs one line and removes a whole branch of the ambiguity.

The severest form has already happened and its fix is one switch. At :313 a seats.json
holding one worktree path in two casings makes ConvertFrom-Json throw, every seat is
dropped for that firing, and the process exits 0. A stale artefact on this box records
exactly that. ConvertFrom-Json -AsHashtable fixes it. A total fanout failure that reports
success is this item's subject at maximum severity and it is not hypothetical.

On the decoy trap, a content discriminator beats the path rule: the decoy carries negative
elapsed times, which are structurally impossible, so asserting elapsed >= 0 rejects it on
content with no path faith. And :111-116 documents that exact negative age as a real
historical bug, so the decoy is a fossil of a fixed defect and the same check would have
caught the original. The authoritative file also identifies itself against a message id
from a different channel, which beats both "newest wins" and a pinned path.

Verified: backlog_status_check.py passes at 531 items each declaring exactly one status;
#1266 still open with one banner.

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

* backlog: #1266 and #1267 are blocked on AUTHORITY -- the clock script is under no version control

I dispatched two builders to edit a machine-global script that is versioned nowhere.
One of them checked before writing, found it, and handed the item back. Verified here:

  git ls-files | grep seat-tick     -> ZERO   (control: 1952 files tracked)
  git grep "seat-tick" origin/main  -> ZERO references anywhere in the repo
  ~/.claude/mefor-usage/.git        -> does not exist

Corroborating signature in that directory: atrisk_ledger.py.deleted-20260805, a file
deleted by renaming, which is what an unversioned directory looks like.

So any fix is an unattributable write to a live shared control every seat depends on
while the edit is made -- no review, no rollback, no record of who changed it. Same
defect class as #1247 and one level worse, because the worktree gate at least had a
committed source to compare an installed copy against.

The blast radius is documented in the file at :191-198: the tick body has a hard
2000-character cap, exceeding it kills the clock silently, and the clock was dead for
about 35 minutes on 2026-08-14 with the first symptom being the author noticing their
own ticks had stopped. A bad edit silences the fleet and exits 0 while doing it -- this
item's own subject, turned on this item's own fix. #1267 is blocked identically, because
its fix is a rewrite of the tick body: precisely the edit that caused that outage.

Therefore the first deliverable is version control, not the log. Under scripts/coord/ or
the vault, the retention change becomes an ordinary reviewable commit; until then every
improvement to this clock hits the same wall. If the owner prefers it stay out-of-repo,
the #1247 pattern transfers directly: back up the bytes, write a receipt naming who wrote
it and from where, refuse to overwrite an unrecognised copy.

Recorded as an owner decision. An AFK delegation lets a seat exercise grants it already
holds; it does not create a grant over a shared machine-global control. The building lane
reached that independently and handed the item back rather than proceeding, which is why
nothing was broken.

Verified: backlog_status_check.py passes at 531 items each declaring exactly one status;
#1266 still open with one banner.

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

* backlog: #1267's own fix nearly killed the fleet -- the near miss is the finding, not the save

The building lane drafted the replacement rubric text, was stopped on unrelated
authority grounds, then measured the draft it had already written. Verified the cap
facts and the live body length independently here before recording them.

Their first draft was 3536 characters against a hard 2000 cap -- over by 1536, and worse
than the 3395 that killed every seat's clock for 35 minutes on 2026-08-14. The current
body measures 1960, leaving forty characters of headroom. The revision measures 1981,
under cap with nineteen to spare.

And the verification they would naturally have run is exactly the pair seat-tick.ps1
records as unable to catch it. Its own comment at :201-204 says the last author checked
that the file parsed and carried no glyphs, both passed, and neither could have caught a
runtime length check in a different script. A live fire is the only check that covers it.

The near miss is the finding rather than the save. They had reached do-not-apply
independently, but on authority grounds because the file is unversioned -- an unrelated
reason. So the fleet was protected by luck while a fleet-killing text sat in a proposal
reading as ready to paste. A correct outcome reached for the wrong reason is not a
control.

What paid for the new text is the file's own standing rule at :206-207: forensic detail
belongs in the backlog item, not in a message firing every ten minutes to every seat. The
revision drops the 53-of-112 figure and the 36-minute outage and cites this item instead
-- the rule applied to the very text that states it.

Limb B's prove-the-fix fired on its own author ten minutes after they wrote it: at the
first tick after drafting, the old question said stop and the new question said work,
naming a specific item. The constructed case arrived unconstructed.

A second-order finding is recorded and routed rather than filed here: nineteen characters
is not a margin. The body is chronically at cap, 1960 before and 1981 after, so the next
edit of any size fails identically and nothing measures it. The remedy is a length
assertion that fails loudly at edit time, or a deliberate reduction restoring headroom.

Verified: backlog_status_check.py passes at 531 items each declaring exactly one status;
#1267 still open with one banner. The concurrent Lander branch touches #1267 in zero
lines.

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

* backlog: #1255 can fail SILENTLY, and its fix is a decision rather than a task

A builder took the item, measured it, and handed it back with the direction named. Their
measurements verified independently here before recording.

The item describes the signature as an AttributeError naming a module path from the wrong
package -- failing loudly-ish. It can fail silently instead. The two conftests share eight
top-level names, the logging-quiesce machinery duplicated in both trees, so a mis-bound
import conftest does not necessarily raise: it can succeed and return the wrong
implementation. That is the shape this project keeps meeting, a resolution landing on
something plausible from the wrong subject, and it is strictly worse than filed.

Preconditions confirmed at origin/main: pyproject sets no importmode, so pytest runs its
default prepend, exactly the mode where top-level module names collide; both directories
ship conftest.py and neither has __init__.py. The latent framing holds -- no bare import
conftest exists in either tree today.

The fork is recorded with the risk that decides it measured rather than asserted.
importmode=importlib is the modern documented fix and removes the collision outright, b…
wshallwshall added a commit that referenced this pull request Aug 21, 2026
…r site (#470)

parse_items 316 items / 229 open / 87 closed, from 231/85. Expected delta for two
closures: 0 items, -2 open, +2 closed. backlog_status_check rc=0, 552 across both
files (BACKLOG.md 316, BACKLOG-CLOSED.md 236).

Both verified by me at origin/main eea8784 with the CONTENT test. Reachability
predicates are useless here -- the repo squash-merges, so is-ancestor, patch-id and
cherry all report landed work as unlanded.

#1241 -- I AM TAKING THE COMPLETENESS JUDGEMENT THE LANDER DELIBERATELY LEFT.
Its 2026-08-14 amendment declined closure on seat-shaped grounds: all five limbs
appeared complete and two seats had independently verified four of them, so closing
was defensible, but it stayed "a judgement about COMPLETENESS, and the seat that
supplied a paired commit to unblock a gate is the wrong one to make it." That
reasoning was right and it names a conflict I do not have. Banner repair on a landed
item is dispatcher maintenance.

The item's original DO-NOT-CLOSE stood and was honoured. It was scoped to PR #379
specifically -- "DO NOT CLOSE THIS ON #379" -- and #379 was never the closure; four
further limbs landed after it. Reading that as a permanent bar would have been the
same narrowing error the ledger has refused elsewhere.

BOTH SINKS CHECKED SEPARATELY, because this item's whole subject is the ASYMMETRY and
checking site one is exactly how a partial reads as complete. One screen defined at
fhir.py:188, applied at three construction sites (:233 url, :263 conditional_query,
:797 lookup url). The URL sink at :464 and the If-None-Exist HEADER sink at :469 both
read the screened attribute, so the single construction-time screen covers both.

WHAT DOES NOT CLOSE WITH IT, said in the banner so it is not buried: the citation
damage this item documents in its own prose survives the code being fixed. At least
seven anchors no longer point at what they claim, and fhir.py:428 is the one that
matters -- it resolves to a real, plausibly adjacent line, an outbound header build in
the same file, so it reads as a working citation forever rather than announcing its
own brokenness. That is #1263's subject and it is filed there.

#1271 -- ALL THREE CITED SITES FIXED, checked individually. :1174 is a raw string
covering all three named escape sequences; :1221 and :1226 are both raw BYTE literals,
which is the second of the item's two corrections and the one a reader checking only
the first site would miss.

CONFIRMED WITH THE ITEM'S OWN INSTRUMENT rather than a substitute: compiling all 1147
.py blobs on origin/main with warnings captured returns 0 SyntaxWarnings and 0
SyntaxErrors. That covers the whole corpus rather than the one module, so it also
answers whether the class recurred elsewhere while this sat open. It has not.

Both are amendments to items already on origin/main, so no heading is added and the
ledger gate's ownership check does not fire.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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