Skip to content

backlog: drain the dispatcher's ledger -- four items read OPEN on main that are DONE - #386

Merged
wshallwshall merged 24 commits into
mainfrom
claude/dispatcher-ledger-drain
Aug 14, 2026
Merged

backlog: drain the dispatcher's ledger -- four items read OPEN on main that are DONE#386
wshallwshall merged 24 commits into
mainfrom
claude/dispatcher-ledger-drain

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

22 commits, 21 of them ledger. Docs-only: docs/BACKLOG.md, ADR 0165 and its index row, and nothing else.

Why this was urgent, and it is a coordination defect rather than a backlog one

Four items were DONE on this branch and still read OPEN on origin/main -- #1204, #1237, #1238, #1239. Every lane reading origin/main was re-offered four phantom items every pass.

It had a measured price. A builder claimed #1238, discovered it was already merged, released it -- then measured its lane empty and self-selected other work rather than idle.

Ledger authorship and push sit in DIFFERENT SEATS. A banner the Dispatcher writes cannot reach a queue reader until the Lander lands it. The Dispatcher saw them closed; the builder saw them open; both readings were correct against the tree each was reading, and nothing printed the difference.

The delay was the Lander's, not the Dispatcher's: an agreed landing order put this branch first, but landing was always the Lander's hand -- so "first" meant "waiting on me", and the Dispatcher had nothing left to do but wait.

Gated on the RESULTING TREE, not the branch

merge-tree --write-tree      rc=0, tree 52137571
origin/main                  281 items / 206 open / 75 closed
MERGED RESULT                284 items / 205 open / 79 closed
delta                        +3 items / -1 open / +4 closed
duplicate item numbers       NONE

the four phantoms, in the merged result:
  #1204  open on main -> CLOSED     #1238  open on main -> CLOSED
  #1237  open on main -> CLOSED     #1239  open on main -> CLOSED

The arithmetic is self-consistent: three new items arrive open, four close, so net open falls by one.

Counts read with parse_items via .is_open -- the only boolean on an Item. .open and .closed are lists of banner characters, and a truth test on them silently answers a different question.

Provenance

The Dispatcher re-verified every ledger claim against the resulting merged tree rather than their own branch -- six skeptics run, four held, two were falsified and are corrected in 714fccc1.

Re-verified independently by the Lander before push: three-dot touches nothing outside docs/, the merge simulates clean, and the four closures land as claimed.

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.
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.
#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.
…ng 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.
 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.
…ther 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.
…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.
…nk-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.
…s 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.
… 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.
… 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.
…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.
…sure 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.
…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.
… 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.
…atched 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.
…alsified 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.
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.
…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.
@wshallwshall
wshallwshall enabled auto-merge (squash) August 14, 2026 08:57
… 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
wshallwshall disabled auto-merge August 14, 2026 09:00
@wshallwshall
wshallwshall enabled auto-merge (squash) August 14, 2026 09:01
@wshallwshall
wshallwshall merged commit 86ed6c3 into main Aug 14, 2026
33 of 34 checks passed
@wshallwshall
wshallwshall deleted the claude/dispatcher-ledger-drain branch August 14, 2026 09:08
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