Skip to content

wave 1: six backlog lanes as one integration train (24 items close) - #310

Merged
wshallwshall merged 34 commits into
mainfrom
w1-integration
Aug 10, 2026
Merged

wave 1: six backlog lanes as one integration train (24 items close)#310
wshallwshall merged 34 commits into
mainfrom
w1-integration

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

Wave 1 of the backlog-clearing plan, as a single integration branch rather than six lane PRs.

BACKLOG #1018 #1024 #1037 #1042 #1043 #1044 #1045 #1046 #1047 #1048 #1049 #1054 #1055 #1077 #1078 #1087 #1098
BACKLOG #327 #1099 #1200 #1201 #1206 #1207 #1209

Why one PR and not six

backlog-hygiene fires when a PR carries a BACKLOG #N token and its three-dot diff touches
messagefoundry/, ide/ or messagefoundry_webconsole/, and it then requires docs/BACKLOG.md in
the same diff. Three of the six lanes touch engine code, and none of them may edit the ledger --
docs/BACKLOG.md had a single sole-writer lane for the wave, which is what keeps concurrent banner
edits from colliding. As independent PRs those three could not satisfy the required context. The train
carries the engine changes and the banner commit together, so the context is satisfied once, in one CI
cycle instead of six.

Contents

Six lanes, each verified contained in the train by merge-base --is-ancestor, plus a final commit
flipping 17 banners. 32 commits, 52 files.

Verification

  • Six lanes merged with zero conflicts. The instrument was checked before its result was trusted:
    merge-tree --write-tree against a known-conflicting branch returns exit 1, so a 0 here means clean
    rather than blind.
  • w1-integration merges cleanly onto main (merge-tree exit 0, re-confirmed after a fresh fetch).
  • Ledger re-derived with parse_items rather than carried forward: live 241, open 224,
    closed-in-live 17, archive 236 -- namespace 477 conserved. No duplicate numbers, and no item
    declaring more than one status.
  • The banner commit passed the full hook set (ledger gate, leak guard, secret scan) with no
    --no-verify and no hooksPath override.
  • Diff scanned for customer, site and partner tokens and for private paths before pushing: zero hits,
    with the scan's negative control confirmed firing on synthetic bad input. The one session-slug token
    in the diff is a net-zero move between the live ledger and the archive, and is already present on
    main.

Note for reviewers

#64 is reconciled by this change but deliberately stays OPEN, by owner instruction. Please do not
close it as tidy-up.

One cross-lane file overlap exists and is benign: docs/WORKTREES.md is touched by two lanes, with the
hunks roughly 500 lines apart.

`_request` measured `len(base_url) + len(path)` against MAX_REQUEST_URL_LEN and
then handed `params=` to httpx, which appends the query AFTER that check. Every
read the console/harness/tray makes goes through `_get`, so a long filter value
(a search needle, a control id) would, on first deployment, build an over-long
request line with nothing refusing it -- the residual under the ASVS 4.2.5
`partial`.

Build the request first (httpx's own resolution step) and measure str(request.url),
then dispatch the built request through `send` -- which is what `request()` does
internally, so the auth and follow-redirects client defaults are unchanged.

The new bound is proved by a tripwire transport rather than a stub response: the
claim is that the request never reaches the wire, and a stub 200 cannot tell that
apart from a request that went out. Watched RED against the pre-fix code (the
9046-char URL reached the transport). A positive control pins that a short query
still goes out, so a bound that refused every query-bearing GET would not pass.

test_request_maps_non_2xx_to_apierror moves its stub from `_http.request` to
`_http.send` -- the same transport seam, one call deeper.
…ution-tested (BACKLOG #1037)

remove.ps1 derived its repo root from $PSScriptRoot alone, so the only repository a
test could point it at was the real checkout -- which no test may drive, because the
script force-removes worktrees and force-deletes refs. Its branch-delete path was
therefore covered by review only, and it is the one place in scripts/worktree/ where
being wrong loses commits reachable from no ref and no reflog.

Adds -RepoRoot, the same parameter and the same reason prune-merged.ps1 already had:
default to the script own checkout, refuse a root that does not exist, otherwise
resolve it. Behaviour on the default path is unchanged.

tests/test_worktree_remove.py drives the real script as a subprocess against a
synthetic repo under tmp_path, never against this checkout. It covers both halves of
the lossless-delete discipline -- the branch that IS contained in origin/main and is
force-deleted after re-verification, and the branch holding unique commits that is
KEPT with its count reported -- plus the branch being read from git rather than from
the directory name, the detached-HEAD refusal, the untracked/.venv case, and the
$PSScriptRoot default itself (exercised against a copy inside the fixture).

Evidence, not assertion. The suite was run RED first: 9 of 10 failed with "A parameter
cannot be found that matches parameter name RepoRoot". Two mutations then proved the
assertions discriminate rather than merely survive:

  branch -d  ->  branch -D   (drop the re-verification)
      test_delete_branch_keeps_a_branch_holding_commits_not_on_origin_main FAILED
      -- the branch holding unique commits was destroyed.

  $branch = git rev-parse HEAD  ->  $branch = $Name   (name the directory, not the ref)
      test_delete_branch_reads_the_branch_from_git_not_the_directory_name FAILED
      test_detached_head_refuses_delete_branch_without_removing_anything FAILED

Both mutations were reverted; 10 passed.

Note for a later item, NOT fixed here: the git status --porcelain at the top of
remove.ps1 does not check its exit code, so an unreadable status is indistinguishable
from a clean tree and the script proceeds to --force. prune-merged.ps1 closed exactly
that fail-open in its own Test-WorktreeClean. Out of scope for #1037.
… #1048)

The `auth` package carried zero outbound length measurement, so the one HTTP
request the OIDC relying party makes -- the token exchange -- went to the opener
unmeasured. Measure the request line and header block immediately before the
POST and refuse with FlowError.

`token_endpoint` is operator-static config (validated https at load), so this is
the weaker of the two ASVS 4.2.5 limbs and the partial does not rest on it. It
earns the bound anyway: an env() value that resolved to an unexpected blob now
surfaces as a clear refusal instead of a wire-level surprise on the first
federated login.

No third copy of the limit. The guard calls transports/rest.py's
find_outbound_length_violation -- the same measurement every other egress uses --
imported lazily so the pure, socket-free auth.oidc package takes no module-scope
transports import, the containment store/keyprovider_vault.py already uses for
the same helper. The raise stays FlowError so the caller's audited
login-failure mapping still applies; only the violation class and the length are
disclosed.

Watched RED against the pre-fix code: the 9019-char request reached the tripwire
opener. Two positive controls ship with it -- a normal endpoint must still reach
the opener (so a guard that refused every exchange would not pass), and the
shared bound is asserted to be the 8192 the refusal message quotes (so the number
in the test is the shipped limit, not a coincidence).
new.ps1 anchors on $PSScriptRoot, so run from a linked worktree it creates the new
tree beside ITSELF -- under .claude/worktrees/, say -- and then told the reader to run
remove.ps1 from the primary. remove.ps1 anchors on its own location too, so the
primary copy derives <primary-parent>/<primary-leaf>-<Name>, a path that does not
exist, and throws "No such worktree". The anchoring is correct (#1060); the sentence
was not, and #1078 is explicit that the placement must not change.

new.ps1 now prints two commands, each carrying $RepoRoot explicitly so it works from
any cwd outside the worktree:

  pwsh -NoProfile -File "<root>\scripts\worktree\remove.ps1" -Name <name>
  git -C "<root>" worktree remove --force "<path>"

The first is the one to reach for -- it keeps the uncommitted-tracked-changes guard,
which the bare git call does not. --force on the second because the untracked .venv
makes git consider the worktree non-empty.

The same false claim was in remove.ps1's own header and in docs/WORKTREES.md; both are
corrected here, because fixing one site and leaving two is how a corrected fact comes
back.

ADVICE THAT IS ONLY READ IS ADVICE NOTHING CHECKS, so the test EXECUTES it.
tests/test_worktree_new_cleanup_advice.py extracts each printed command from the
script (an extraction contract new.ps1 now states on its side: "  #   " is a command,
"  # " is prose), substitutes the paths, and runs it against a synthetic repo,
requiring the worktree to be gone AND deregistered.

Red first, and the control is live. All three tests failed before the change; the
third failed only AFTER reproducing the defect -- it builds a linked checkout `inner`,
creates `inner-feature` beside it the way new.ps1 would, and asserts the retired
advice still throws today:

    assert retired.returncode != 0            PASSED
    assert "No such worktree" in stderr       PASSED
    ...naming <primary-parent>/repo-feature   PASSED
    assert not target.exists()                FAILED  <- no working advice yet

so the green it reports now is evidence the assertion can see the class. An
end-to-end run of the real new.ps1 (copied into a synthetic repo, -NoInstall, invoked
from the linked worktree) printed both commands with the linked root filled in, and
the first one removed the worktree verbatim.
…#327, #1099, #1200, #1201, #1206, #1207, #1209, #64)

Seven banners flipped to closed, each confirmed by OPENING THE SHIPPED CODE rather
than by trusting a report or a commit message, plus one prose-only reconciliation.

Confirmed in the code, not inherited:
  #1200  ci.yml carries alwayscode='\.(py|ps1|sh|ts|js|yml|yaml|toml|lock|cfg|ini)$'
         and evaluates it in the FIRST elif, ahead of alwayscodepath and noncode.
         Re-driven with the regexes read back out of ci.yml.
  #1201  _is_secret_header() ends in a substring test over
         auth|token|secret|credential|password|passphrase|key, with the five original
         names kept as a floor and a second VALUE arm for opaque vendor names.
  #1206  redacted_settings() has an odbc_params arm; _is_secret_odbc_key() is
         shape-based and case-insensitive, with the libpq PATH keywords excluded.
  #1207  _redact_header_value() opens with the EnvRef arm; _mask_url_userinfo()
         replaces only the password half. Both reach display_settings by delegation.
  #1209  the || binds the ASSIGNMENT, outside the substitution, plus a shape test.
         Re-executed under bash -e against a gh stub reproducing the stream split:
         pre-fix emits advisory_ok=true (FAIL OPEN), shipped emits false.
  #327   tests/test_private_paths_stay_ignored.py pins all six rules with a
         cardinality assertion. PROVED ABLE TO FAIL: removing /docs/security/ from
         .gitignore reds it by name; restored byte-clean.

#327's residual is fixed in this commit. docs/SESSION-DRIFT-CONTROLS.md linked
[.claude/settings.json](../.claude/settings.json) -- a path no reader outside the
maintainer's machine has -- and presented the blanket-stage hook as an active
control. The link is removed rather than repaired and the guard's real reach is
stated: the script is tracked, its PreToolUse matcher is not, so a fresh clone and
every worktree come up without it.

link_check.py could never have caught that: .claude/ is in its WITHHELD set and the
exemption continues BEFORE the counter increments. Measured by planting two hrefs --
a missing non-withheld path took the run red and the total 5359->5360; the same path
under .claude/ left it green AND the total unchanged. The href was not resolved, it
was never counted.

#1099 corrects #1094's "the archival pass generates the anchor ... derive it from
the generator". There is no archival tooling: the only tracked paths matching
"archiv" are seven documents. Two corrections to #1099's own text, both the class it
was filed about -- the sentence was in the LIVE file, not the archive, and it cited
tests/test_archive_link_resolution.py, which is on no merged ref (PR #281 squashed as
6cb34f5 and the file landed as tests/test_link_resolution.py). Found by searching
every ref. The same stale name in #1095's block was corrected with it.

#64 is PROSE ONLY and STAYS OPEN. Step 1 ran 2026-07-12; step 2 is REFUSED, not
gated (ADR 0055 withdrawn on two independent grounds, ADR 0107 closes Phase 4). The
2026-06-28 "honest verdict" figures are corrected in place: "compute unvalidated" is
falsified (C5 pins per-shard R in [2,3) against the 3.62 a cleared N=16 needs), and
"~7 commits/msg, group-commit unbuilt" is wrong in both halves (10.4746
committed_txns/msg measured; the commit tier is ~9% utilised). What survives is only
the index role over #62/#63/#47/#34. Whether that umbrella is discharged is an owner
call and was deliberately not taken.

HELD FOR THE OWNER (G28): the two redaction route-onwards found under #1201 and
#1206 -- an env() ref in a headers table, and env() resolution inside nested settings
-- are marked "pending owner ledger decision" in place. No number was allocated and
neither was quietly closed as prose.

Gates: backlog_status_check 477 items / 282 live + 195 archive, link_check 5369
links across 347 files, ledger_check clean.
…042)

Every other shipped HTTP egress routes through a no-redirect opener --
transports/rest.py's _NO_REDIRECT_OPENER, and auth/oidc_http.py's local twin for
the IdP hop. The [vault] provider clients were the exception: they built an
hvac.Client with no redirect policy, and hvac's default is allow_redirects=True.
The token rides as an X-Vault-Token header on every Transit/KV call, so on a
first deployment a 3xx from an on-path attacker (absent TLS integrity) or a
spoofed Vault would relocate a request carrying it, while every default egress
refused the same redirect.

Set allow_redirects=False at both client-construction points. The third client
named in the item, store/crypto_transit.py, already reuses keyprovider_vault's
_build_client, so it inherits the policy from one construction point rather than
growing a second.

Verified against the real library rather than assumed -- hvac 2.4.0's
Client.__init__ takes allow_redirects (default True), stores it on the adapter,
and the adapter passes allow_redirects=self.allow_redirects to
requests.Session.request. hvac is the optional [vault] extra and CI never
installs it, so the committed tests stand a recording module in for it and
assert what our code asks for; the measurement above is recorded in the test
module's docstring because no test can reach it without the extra.

Watched all three RED against the pre-fix code -- each reported the constructed
kwargs as dict_keys(['url', 'token']), no redirect policy present. The Transit
cipher is driven end to end through build_transit_cipher rather than asserted by
identity, so a future private client construction there reds this test. A fourth
test is the live positive control on the instrument: it builds a client without
the policy through the same fake and asserts the recorder reports its absence,
so a green above cannot be the fake swallowing an unrecognised kwarg.
…im (BACKLOG #327, #1099, #1200, #1201, #1206, #1207, #1209)

Every item carrying a closed banner in docs/BACKLOG.md moves into
docs/archive/backlog/BACKLOG-CLOSED.md: the 34 that were already closed-in-live
plus the 7 closed in the preceding commit.

  live    282 -> 241        archive 195 -> 236        total 477, unchanged

VERBATIM, and measured rather than asserted. Each block was SLICED, never
re-rendered, so headings are byte-identical by construction -- 41/41 confirmed
against the pre-move file, which is what keeps every #<n>-<slug> anchor resolving.
Undoing the link rewrite reproduces the pre-move body exactly for 41/41. No item
was lost (the pre-move set minus live minus archive is empty) and no number is
duplicated across the two files.

DEPTH IS +2, NOT +1. docs/BACKLOG.md -> docs/archive/backlog/BACKLOG-CLOSED.md
crosses archive/ AND backlog/, so 84 relative hrefs took a ../../ prefix. That is
what the existing archive already uses (../../adr/... for docs/'s adr/...), and the
+1 form the plan called for is provably wrong: injecting one href at +1 takes
link_check.py RED, resolving to docs/archive/adr/0004-payload-agnostic-ingress.md,
which does not exist. Restored, green again -- the gate can see this class.

The rewrite replicates link_check.py's OWN skip rules (fenced blocks; links whose
"]" falls inside an inline code span), so the set of hrefs rewritten is exactly the
set the gate checks. Rewriting more would corrupt displayed text; rewriting fewer
would leave a checked link broken.

Appended rather than inserted in numeric order, which is the archive's existing
convention -- its tail already runs 348, 349, 350, 335, 233, 326.

Gates: backlog_status_check 477 items (241 live + 236 archive), each declaring
exactly one status; link_check 5369 links across 347 files, all resolving;
ledger_check clean.
…he model

redact_unauthorized masked a property exactly where it was called, so the set of
protected surfaces was only ever the set of call sites someone remembered to write.
Coverage was pinned by an enumerated test, which by construction cannot cover the
route nobody has written yet: a new PHI-returning route that forgot the call would
have serialized summary / error / metadata / last_error / detail in full, with the
whole suite green.

The default now denies. api/phi_gate.PhiGatedModel withholds each declared PHI
property from JSON serialization until an authorization decision is recorded on the
instance; redact_unauthorized is what records one, releasing exactly the properties
the caller's permissions unlock. A forgotten call returns null - a functional defect
its author sees - instead of a leak nobody sees.

Mechanics, and why each was chosen over the obvious alternative:

- A named field serializer with a `str | None` return, not a model-level wrap
  serializer. Measured against pydantic 2.13.4 / fastapi 0.141.1: a wrap serializer
  collapses the model's whole serialization schema to
  {"type": "object", "additionalProperties": true}, and an Any-returning field
  serializer untypes its own property. The published OpenAPI must not get vaguer than
  what the code returns; a test now pins that.
- when_used="json", so the gate covers every path by which one of these models reaches
  a client (FastAPI response models, jsonable_encoder, model_dump_json) while leaving
  python-mode model_dump alone. api/app.py composes MessageDetail from a MessageSummary
  dump BEFORE any authorization decision exists; gating that dump would blank the
  detail route for an authorized caller.
- Class creation refuses a gated name outside GATEABLE_PROPERTIES or absent from the
  model's fields - the one way this gate could go quietly inert.

Policy stays in field_authz (which permission unlocks which property); the model
declares only that a property is gated. The two are pinned to each other in both
directions.

Evidence: with phi_gate present but the models unwired, the new end-to-end test went
red on exactly the item's failure mode - "a route with no redact_unauthorized call
returned PHI: {'summary': 'MRN9001 DOE^JANE', 'error': 'strict validation failed on
segment PID', 'metadata': ...}" - and green once the six models declared their gate.
Every null assertion ships with a released positive control, so none of them can pass
because serialization is simply broken. Synthetic HL7 only.
…estating it (BACKLOG #1099)

The header read "moved here verbatim on 2026-08-03 so the published backlog is the
~92 items someone can actually act on" -- a PRESENT-TENSE count that was a
measurement taken on one day and has drifted every day since. Measured 2026-08-10
the live file holds 241 open items, so the sentence a reader would have quoted was
off by a factor of two and a half.

Replaced with the derivation rather than a fresher number, because a fresher number
rots identically: run parse_items from backlog_status_check.py over BOTH files. That
is the same single-source rule the file already states two paragraphs down for
reading the status banners, and the rule CLAUDE.md 11 states for the alphabet.

Same class as #1099 -- a ledger document asserting something about itself that
nothing re-derives.
"Two of its six clauses are falsified" / "The other four clauses stand" is a pair of
counts that have to agree with each other and with a paragraph nobody will re-parse
-- and the second one then named only three things. Replaced with "at least two" and
"the remaining clauses", which is CLAUDE.md 11's rule (SDS-3.6: a completeness claim
is a liability, prefer "at least" to an enumeration) applied to a sentence I had just
written while correcting someone else's stale figures.
…s a safe path

XmlMessage.find / get / get_all / exists / set / set_attribute took only an
expression string into the sole sink self._root.xpath(...), and nothing in the tree
bound a value. XmlMessage is exported to code-first Handlers, Handlers are authored
by users, and all HL7/XML content is untrusted data - so an author filtering on a
message-derived value had no framework-provided alternative to an f-string, and an
f-string into an XPath predicate is an injection.

Every query method now takes $name bindings as keyword arguments:

    msg.get("//record[@mrn=$mrn]/note/text()", mrn=untrusted)

A bound value is compared as a value and never parsed as expression syntax. The
expression (and set's value / set_attribute's name+value) are positional-only, so a
binding may legitimately be called "expression" or "value" without colliding with the
parameter. Only str/int/float/bool bind: lxml would also accept a node set, which
would let a caller feed one expression's result back in as a sub-expression - the
shape this API exists to remove. An unbound or unbindable variable surfaces as
XmlPathError, the codec's own data error, not a bare lxml traceback out of a
transform; the message names the variable and its type, never its value.

Evidence, both halves red first:

- The threat is demonstrated, not asserted. With mrn = "nope' or @mrn!='", the
  interpolated form selects BOTH records where the author meant one -
  test_interpolating_a_tainted_value_into_an_xpath_string_is_injectable passes today
  and is the live positive control for the empty result below.
- The four binding tests failed with "XmlMessage.get_all() got an unexpected keyword
  argument 'mrn'" before the change and pass after; the same payload bound as $mrn now
  selects nothing, and the same call shape with a legitimate value still selects its
  record, so the empty result is the binding working rather than the expression being
  broken.

Invented MRNs throughout, never real PHI. This is a hardening item for the authoring
surface, not a shipped vulnerability: nothing in-tree reaches .xpath() with tainted
data.
`FileSource._move` relocated a processed file with `path.replace(_unique(dest))`
-- a check-then-act pair, where `_unique` asked exists() and `replace` then
overwrote whatever sat at the name it chose. The delivery path had already
replaced that pattern with `_claim_unique`'s os.link/O_EXCL claim (FILE-5); the
archive move was the caller left behind. Route it through the same claim.

Claim-then-unlink rather than one rename: the atomic claim cannot be expressed
as a rename, because renaming a file over its own hard link is a POSIX no-op and
the original would survive. If the unlink fails after the claim, the file is
archived AND left to be re-read -- the same duplicate-read outcome the
pre-existing failure arm already had, logged the same way.

`_claim_unique`'s cross-filesystem fallback now streams instead of read_bytes():
the archive move claims through it too, and an inbound file is only as small as
max_file_bytes, which is unset by default -- buffering one whole to claim a name
would put an arbitrarily large payload in memory on exactly the filesystems that
already cannot hard-link.

Scope, stated plainly: the default config cannot race this. One poller per source
over an engine-owned processed_dir, and the raw message is durable in the store
before the ACK regardless. It bites the non-default config the item names.

RED evidence, two threads with a per-round barrier archiving same-named files
into one processed_dir: pre-fix, "38 of 120 archived messages were lost or
overwritten by the other source" (a standalone probe of the same shape measured
61, 61, 64, 63 and 62 of 120 across five runs). Post-fix: 120 of 120, five runs
of five. Real threads rather than an injected interleaving, because the window
only exists in the pre-fix code and a hook placed inside it could not survive the
fix. Two deterministic controls ship with it -- the escalation still yields
`m-1.hl7` without touching the file already there, and the original is still
removed, so a `_move` that refused every taken name or that copied instead of
moved would not pass.

test_file_source_move_failure_leaves_file_in_place is repaired in the same
commit, and it is the more interesting half. It patched Path.replace to force a
failure and asserted the file was left in place -- but its inbox had no
.processed dir, so the move ALSO failed for that reason. Once `_move` stopped
calling Path.replace the injection went inert and the test kept passing on the
missing directory alone. It now creates the archive dir (so the injection is the
only possible cause) and patches the claim. Proved load-bearing: with the patch
line removed it reds.
…asked question (BACKLOG #1087)

new.ps1 ran `git worktree add <path> -b <Branch> <Base>` with -Base defaulting to
origin/main. Git branch.autoSetupMerge then set the new branch upstream to the
remote-tracking BASE, so @{u} resolved to origin/main rather than to the branch own
remote ref -- and @{u}..HEAD reported a fully-pushed branch own commits as UNPUSHED,
forever. That number feeds the "is anything at risk if I delete this worktree" check,
so a routine safety question got a confidently wrong answer with no error anywhere.

The fix is the one flag #1087 names. push.default is NOT touched and must not be:
with the upstream wrong, push.default=upstream makes a bare push write the feature
branch onto main, and push to main is not blocked server-side here.

Measured on a synthetic repo with a real bare origin, both legs, branch tip byte-
identical to its pushed remote:

  worktree add <path> -b feat-a origin/main
      tip == origin/feat-a ? True
      @{u}            -> exit 0   : origin/main
      @{u}..HEAD      -> exit 0   : 1          <- FALSE unpushed commit
      bare `git push` -> fatal, and git own remediation text reads
                         "git push origin HEAD:main"

  worktree add --no-track <path> -b feat-b origin/main
      tip == origin/feat-b ? True
      @{u}            -> exit 128 : no upstream configured   <- loud, not wrong
      after `git push -u origin feat-b`:
      @{u}            -> origin/feat-b
      @{u}..HEAD      -> 0

Loud beats wrong: an instrument that cannot answer is the correct failure direction.
The cost is that the first push is `git push -u origin <branch>`, which then sets the
upstream to the right value. Documented in new.ps1 and docs/WORKTREES.md.

tests/test_worktree_new_no_track.py asserts the DIVERGENCE, per #1000: it extracts the
creation command from new.ps1, runs it against a synthetic repo, pushes, and reads
@{u}. The control is the pre-fix command written out literally in the same test, so it
keeps reproducing the class whichever sanctioned fix new.ps1 later carries. Red first:
the control legs passed (upstream origin/main, count 1) and the subject leg failed on
`good_up == "origin/main"` -- the defect, in the assertion.

A third test scans every .ps1 under scripts/ (38 files, count printed on failure) and
refuses `push.default`. It was first written as a raw text scan and fired on new.ps1
own comment WARNING against push.default -- which is how the string match was shown to
work; it now skips comment lines, and a real `& git -C $RepoRoot config push.default
upstream` appended to new.ps1 was caught at line 262 before being reverted.

Two stale premises the fix created, corrected in the same commit rather than left:

* new.ps1 lock comment attributed the .git/config.lock race to the upstream write that
  --no-track removes. The measurement was taken with tracking on and nobody has
  re-measured without it, so the lock STAYS and the comment now says exactly that.
* prune-merged.ps1 signal 3 named new.ps1 as the source of the parent-upstream shape.
  It is no longer. Note also the improvement: a new.ps1 branch upstream was pinned to
  the base, which is never origin/<branch>, so signal 3 could never fire for one --
  after `push -u` it can.
… (BACKLOG #1087)

The flag only applies at creation. Every worktree made before it still carries
@{u} = origin/main and nothing corrects them retroactively, so a reading taken in one
of those is still wrong -- and a reader who saw the fix land could reasonably conclude
otherwise. Follows the rule that a compensating control must not rest on a false
premise: the control here is "the upstream is right now", which is true only of
worktrees created since.

Both remediations are measured, not inferred, against the same synthetic fixture the
item was reproduced in:

  git push -u origin feat-a       @{u} origin/main -> origin/feat-a, count 1 -> 0
  git branch --unset-upstream     @{u} origin/main -> "fatal: no upstream configured"
… not enforcing

Every doc-content assertion in tests/test_threat_model_doc_drift.py went silently
inert when docs/security/THREAT-MODEL.md was absent - which on a public checkout it
always is, since docs/security/** is deny-listed from the OSS mirror and vaulted. A
bare pytest.skip in a 12,000-test run is one 's' among thousands: the run reads clean
while ASVS 15.1.3 and 15.1.5, which are scored on that document, have no drift
enforcement at all. That is ADR 0158's class 2 - a control that cannot report its own
failure - and the fix has to add reporting without adding a false red on the tree
where the absence is legitimate.

Three changes, and deliberately not a fourth:

- The absence is ANNOUNCED, once per run, as a ThreatModelDocUnenforced warning that
  names the path it looked for, every category of assertion that stopped enforcing,
  what still enforces, and the two env vars. It lands in pytest's warnings summary,
  which prints even under -q. The skip reason points at it.
- MEFOR_THREAT_MODEL_DOC points the module at a copy elsewhere (the vault working
  tree), so the content half can be enforced from a checkout that does not carry the
  file. MEFOR_REQUIRE_THREAT_MODEL_DOC=1 makes absence a hard FAILURE, so a leg that
  is supposed to enforce fails closed rather than best-effort.
- The CHECKER MECHANISM is now verified on every tree, doc or no doc, against a
  stand-in written in the module.

Not a fourth: no public stand-in for the document's CONTENT. Copying the registries
into a tracked fixture would be a green proving only that a fixture matches a fixture
- one silent skip traded for a vacuous pass, which the item explicitly rules out. The
stand-in proves the checkers can go RED, which is the property a skipped run stops
evidencing, and nothing more.

Evidence - the guard run in all three postures, and the new self-tests broken on
purpose:

- doc ABSENT: 12 passed, 89 skipped, 1 warning. The warning text is the receipt.
- stand-in PRESENT (MEFOR_THREAT_MODEL_DOC at a scratch file): 79 failed, 21 passed,
  1 skipped - the content half is live and names what it could not find, e.g.
  "15.1.5 no longer inventories these dangerous surfaces: ['_exec_module',
  '_assert_safe_config_source', 'db_lookup', ...]". A guard that reports 89 skips in
  one posture and 79 named failures in the other is demonstrably not asleep.
- MEFOR_REQUIRE_THREAT_MODEL_DOC=1 with the doc absent: "Failed:
  MEFOR_REQUIRE_THREAT_MODEL_DOC is set, so this run is expected to enforce the threat
  model's content - but ...THREAT-MODEL.md does not exist."
- The row-scoped checker was mutated to scan the whole section instead of its table
  rows - the regression this module was actually caught by once - and the new
  self-test went red with "the row-scoped checker did not notice a deleted row - it is
  asleep". Reverted; green after.

The stand-in also caught the author: test_section_slicing_stops_at_the_next_same_or_
higher_heading was written asserting that a ### subsection is EXCLUDED from its ##
slice. It is not, by design, and that inclusion is exactly why the anchor checks are
scoped to table rows. The assertion now pins the real contract.
ASVS 15.1.3's "avoid building a response that takes longer than the consumer's
timeout" limb (properly 15.2.2) had no server-side enforcement. The only
asyncio.wait_for in api/ caps the connection-test probe; nothing bounded a
handler, so a slow one held its worker for as long as it ran and the client's own
timeout was the only thing that ever gave up -- which does not free the server.
No exposure on the shipping config (loopback, authenticated, single worker); this
is what a first deployment would need.

RequestTimeoutMiddleware, pure ASGI, refuses with 503 when the handler has not
begun responding within the deadline.

The bound is on BUILDING the response, not on sending it: the clock is cancelled
at http.response.start, so an attachment download or a large log body already
streaming is never cut mid-body over a slow link. That is also the limb ASVS
words -- the cost being bounded is the handler's, not the network's.

Registered directly inside ClientNetworkMiddleware and outside everything else,
so the deadline covers the attachment CSP re-assert, the console's own
middleware, the body cap, the security-headers middleware and every auth
dependency, while a refused address is still rejected before it can occupy a
deadline. That order is pinned by a test, not just described here. Sitting
outside _security_headers, the 503 sets the baseline headers itself rather than
being the one response in the API with none of them.

Default 120s: a runaway backstop, not a latency budget. Overridable per app via
app.state.request_timeout_seconds (<=0 disables) -- the seam a future [api] knob
would set, so a deployment with a genuinely long admin operation raises it
instead of losing the backstop everywhere. Not wired to config in this change.

Watched RED against the unregistered code: the slow route returned 200 with
{"status": "finished"} after running to completion. Positive controls ship with
it -- a fast handler under the same deadline still returns its own 200, a
disabled deadline lets the slow handler finish, and a non-numeric state value
falls back to the default rather than disabling the control.
…ut not the backlog (BACKLOG #327)

Archiving 41 closed items took test_present_tense_mirror_prose_does_not_grow from
54 to 55 and red, with ZERO new prose written. Cause: _HISTORICAL excluded
docs/BACKLOG.md but not docs/archive/backlog/BACKLOG-CLOSED.md, so the same
sentence counted or did not purely by which of the two ledger files it sat in --
and closing an item moves the block byte-identically from the excluded file into
the counted one. Any archival pass could breach this ratchet.

It is also unfixable where it lands. The archive's invariant is that each block is
byte-identical to the one that left BACKLOG.md, which is what keeps its #<n>-<slug>
anchors resolving. So an archive hit can be counted forever and never edited away.
A ratchet that can fire and can never be cleared is one that eventually gets
suppressed -- the outcome this module's own docstring warns about.

VERIFIED BEFORE EXCLUDING, because excluding a file to hide a real hit is the
failure mode here. All three archive hits are false positives of the kind already
recorded above the regex:
  :1558  "0 downloads on a private repo"      a DIFFERENT project's release channel
  :4744  "green on the mirror-nightly run"    a CI job name
  :7470  "the mirror branch does not exist"   a git branch in the scorecard repo
None claims this repository is a mirror. No genuine hit is lost.

CEILING LOWERED 54 -> 52, not left at 54 to bank the two. Honest count re-measured
at 52 across 1533 tracked files. Banking slack is what turns a ratchet into a rubber
stamp: rot would have to exceed the slack before anything reds, and nothing reports
that the gate has gone quiet.

PROVED STILL ABLE TO FIRE rather than assumed. Planting one genuine present-tense
claim ("This repository is the public mirror of the private development repo") in a
SCANNED file takes it red at 53 against the new ceiling of 52; removed, green again,
the file byte-clean. Narrower domain, tighter threshold, still fires on the first
new instance.

SCOPE NOTE: this is the only file outside the ledger lane's list that this wave
touched. It is here because the archive move surfaced the defect and the branch is
otherwise red; the alternative -- editing the moved prose -- would have broken the
verbatim invariant the whole archive rests on, and raising the ceiling is forbidden
by this module in as many words.
Routing the archive move through `_claim_unique` left `_unique` with no callers
in the module. It is not merely dead: it is the exists()-then-act helper the fix
exists to retire, so leaving it in the file invites the next caller straight back
onto the racy form.

Verified orphaned rather than assumed -- a tree-wide search for the name finds
only `harness/file_transport.py`'s own module-level function and
`transports/remotefile.py`'s own method, neither of which imports this one, plus
prose references in the #1046 docstring and test module. It was never in
`__all__`.
The #1043 commit added one line of new "OSS mirror" prose to the module docstring,
which took tests/test_cutover_slug_rot.py's present-tense ratchet from 54 to 55
against a ceiling of 54. Measured, not assumed: the same five modules run at
origin/main fail 20 tests in this environment and on the branch 21, and the one
difference is exactly test_present_tense_mirror_prose_does_not_grow. The other 20 are
pre-existing here (installed-hook parity, dependabot allowset, workflow shell syntax)
and reproduce unchanged at the base commit.

Reworded rather than ceiling-raised - raising it is the rot the ratchet exists to
stop. The warning text loses the same framing for a second reason: the repository is
developed directly in the public remote now, so "deny-listed from the OSS mirror" is
dead framing to be planting in new prose. "withheld from public checkouts" is what is
actually true, and it is what an operator reading the warning needs.
…BACKLOG #1077)

The hook told every session "No exact row, or isRunning is false -> SKIP that
peer", then had the model file the outcome under the token NOT_RUNNING. Measured
2026-08-06: an isRunning:false peer was DELIVERED to and answered within one turn,
while an isRunning:true peer QUEUED behind its in-flight turn. The field means
"executing a turn right now", so as a reachability test it reads backwards -- the
rule dropped exactly the peers most able to answer, and the receipt token recorded
a delivery that would have succeeded under a word that reads "gone".

scripts/coord/session-registry.ps1 already documented the correct reading, so the
repo contradicted itself in the one place a model acts on it.

- Drop the isRunning condition entirely. An exact cwd match is the whole test; a
  wrong id is loud, so cwd alone deciding costs nothing.
- Drop the NOT_RUNNING token from the delivery-receipt vocabulary, leaving two.
  The rationale is a source comment, not a line of stdout: naming a retired token
  in the instruction is how a model learns it is available.
- Record the measurement ONCE, in session-registry.ps1's header (the field's source
  of record). The hook and docs/WORKTREES.md now point at it instead of restating.
- Correct the WORKTREES.md paragraph that counted isRunning:true for 1 of 6
  registry-LIVE peers and read that as a reachability rate; it was a count of who
  happened to be mid-turn.

Tests assert the EMITTED STRING, because the hook's entire product is the text it
puts in front of the model. Watched both go RED against the pre-fix script first:
the failure output quoted "_LISTED | NOT_RUNNING> TAB <sent | failed>" as what it
scanned. The absence assertion is paired with a presence assertion so a hook that
emitted no instruction at all could not satisfy it -- and it immediately earned its
keep by failing an earlier draft of this change whose own explanatory line printed
the retired token.
…#1098)

The SessionStart banner printed each live peer as a bare 8-hex token before a
bracketed branch. That is a REGISTRY SESSION ID and resolves to no git object --
but the banner's own `git worktree list` block, a few lines above, prints a REAL
abbreviated SHA in exactly that shape, so the reader is taught the wrong meaning by
the output itself. A session comparing "is that worktree ahead of mine" against it
gets a git error at best, and the wrong tree if the prefix happens to resolve.

Measured in the test, not asserted: the same run carries `<path> 8ba9b65 [master]`
rows from `git worktree list`, keyed to `rev-parse HEAD` so the control is an
object and not a shape.

- session-context.ps1: the word `session` goes on the ROW, in both the live-peer
  roster and the "WHAT THEY ARE BUILDING" list. A legend would let the column mean
  one thing in one row and something else in the next, which is what the item
  forbids.
- presence.ps1: a column HEADER instead, because that output is a fixed-width
  table -- one header governs every row, which is the same property. It names all
  four columns; naming only the ambiguous one leaves the rest unstated.

Both tests assert the EMITTED TEXT, which is the only place either defect exists:
every other test of these two scripts reads `-Json`, where the field is called
`Short` and no ambiguity is possible. That is exactly how a reporting defect
survives a green suite. Watched both go RED against the pre-fix scripts first; the
presence failure printed the whole table it scanned.

No consumer parses the human table -- mail.ps1, announce-session.ps1 and
session-context.ps1 all invoke presence with `-Json`, which returns before it.
…ting its own writes (BACKLOG #1024)

Two halves of one defect.

1. The glob was unanchored. `-Filter ".claude-account-*"` matches any directory
   whose name merely BEGINS with `.claude-account-`, and `~/.claude-account-2.lock`
   IS a directory -- so the installer wrote gate wiring into a dir with no
   .claude.json, no .credentials.json and no sessions, i.e. one nothing has ever
   launched from, and re-wrote it on every run. Now anchored on `\A\.claude-account-
   \d+\z`, the .NET spelling of the predicate #199 gave the Python reader (.NET `\Z`
   also matches before a trailing newline; `\z` is the one that means Python's `\Z`).
   Writer and reader are now the same predicate.

2. -Status validated its own output. It scanned exactly $ConfigDir -- the set this
   script WRITES -- so a wrong discovery predicate made the writer manufacture the
   wiring and the reader read it back as correct. Both were wrong the same way, so
   they agreed: a validator satisfied by construction, ADR 0158's exact class.

   -Status now enumerates a DIFFERENT population: every ~/.claude* dir carrying a
   settings.json, judged by name afterwards rather than selected by name up front.
   It prints that population by name (a count that got smaller looks like an
   improvement; only the names say what stopped being looked at) and reports any dir
   outside the wire set that still carries gate wiring as ORPHAN GATE WIRING.

   Reported, never fixed -- deliberately. Anchoring the writer also puts the existing
   artifact out of -Uninstall's reach, so the remedy is a command a human runs
   (`-Uninstall -ConfigDir "<path>"`); which dir is a stale artifact versus a config
   root this box really uses is the owner's call.

Verification is by redirected HOME, not by installing: a session must NOT execute
this installer for real, because it rewrites user-scope wiring for every session on
the box. That constraint is why the item was scored difficulty 3, and it is why
-Status sits above the CLAUDECODE refusal -- auditing is not installing.

Three tests, all watched RED against the pre-fix script first; the failure output
carried the whole pre-fix -Status text, showing `.claude-account-2.lock` on a
`wiring      :` line and `scanned 3 config dir(s)`.

- The writer/reader agreement test runs the REAL installer over the same name corpus
  the reader's own negative control uses and IMPORTS the reader's pattern rather than
  restating it. A restatement would be a third predicate, and this defect was two
  predicates disagreeing. It guards its own guard: the corpus must contain both an
  accepted and a rejected name, or the comparison is vacuous.
- The orphan report is exercised on a decoy `.claude-account-2.lock`.
- And the quiet arm is its negative control: same decoy, no settings.json, so the
  loud line must not fire. A line that appears on every run is one readers skip,
  which is how a real orphan would go unnoticed.
…(BACKLOG #1018)

The two regexes that read worktree_gate.ps1 as text and extract every tool it
branches on are implemented THREE times -- test_install_gate_wiring.py,
test_gate_installed_parity.py, and Get-HandledTools in install-gate.ps1 (PowerShell)
-- with nothing tying them together. They compute the same quantity, so this is
duplication, not resemblance.

They do NOT disagree today. The hazard is a one-sided edit, and what makes it worth
a test is the FAILURE DIRECTION: an under-matching copy 2 shrinks `required` in
test_every_non_optional_rule_is_wired_in_every_config_dir, so it passes having
checked less; an under-matching copy 3 prints no UNWIRED line from -Status. Both are
false greens, in the files written because a rule once shipped dead while 85 tests
stayed green -- and neither is visible from inside the copy that changed.

NOT UNIFIED, deliberately and per the item. A shared Python helper cannot absorb the
PowerShell copy, so the honest end state is one helper plus a cross-language
agreement test; this is that second half, and it stands alone.

Each real implementation is driven as it stands over one corpus -- copy 1 through
its module constant, copy 2 by its text argument, copy 3 by lifting its function out
of the installer with the PowerShell AST and defining it in a fresh session (so the
installer, which rewrites machine-global wiring, is never executed). A regex that
carved the function body out by text would be a text scan of a text scanner.
Nothing here re-implements the regexes: a fourth copy written to test the other
three would be the same defect with better manners.

Three arms:

- The REAL gate. All three must agree, and each must return something -- three
  implementations that all return the empty set agree perfectly and measure nothing.
- Six regex shapes (-in, -notin, spacing, single quotes, a hyphenated name, two
  branches). Each asserts the EXPECTED set, not just mutual agreement: three copies
  agreeing on the wrong answer is a state this file would otherwise call healthy.
- The ONE known divergence, pinned with its reason. Copy 2 alone drops whole-line #
  comments, because the real gate quotes rule 4's condition in prose as well as in
  code. On the real gate that difference is invisible, which is exactly why it needs
  a corpus that separates it. Pinned, it becomes a stated property; any change to it
  fails here and has to be argued rather than absorbed.

RED FIRST, both directions, exactly the demonstration the item prescribes. Changing
the quote pattern to `"([A-Z][a-z]+)"` in copy 3 failed 5 of 8, printing
  1 ...tools_the_gate_handles: ['GhostTool', 'RealTool']
  2 ...handled_tools:          ['RealTool']
  3 ...Get-HandledTools:       []
and the same change in copy 2 produced the mirror image, with [] on line 2 and both
names on 1 and 3. Three of the eight stayed green under each perturbation, because
that pattern under-matches only camel-case and hyphenated names -- so the test says
which construct broke rather than only that something did.
…over an empty population (BACKLOG #1024)

Found by reading back my own output. With no ~/.claude* dir carrying a settings.json
the audit fell through to "every dir found is in the wire set above; nothing is
unjudged" -- reassurance derived from nothing having been measured, which is the
exact class the audit was added to remove. Measured, not reasoned: reverting the
guard and re-running prints that line under `audit : 0 ~/.claude* dir(s)`.

"I found nothing" and "I found things and they are all fine" print identically the
moment a scan reports only its verdict. presence.ps1 already draws this distinction
between an empty roster and an unavailable one; the audit now draws it too, saying
NOTHING EXAMINED and naming the home it looked under.
…OG #1054)

The ADR 0087 sandbox child configured logging with a bare `logging.basicConfig`,
whose handler carries no filters. Redaction here is a property of the HANDLER, not
of the logger or the call site, so the child's records reached the engine's
inherited stderr with neither PHI redaction nor CR/LF neutralization: the three
filters `_install_phi_filters` puts on the engine's own handlers were simply absent
in that process.

`logging_setup` grows a public `configure_stderr_logging()` -- the same chain
(RedactionFilter -> CredentialQueryScrubFilter -> ControlCharScrubFilter) and the
same text formatter as `configure_logging`, bound to stderr because the caller's
stdout is a binary channel. The worker calls it in place of `basicConfig`.

Nothing is leaking today: `[sandbox].mode` defaults to "off" and there are zero
deployments. On a first deployment that opted into mode="subprocess", a WARNING+
record emitted by admin-authored Router/Handler code would have carried
message-derived content onto stream 1's own sink unredacted and un-neutralized.

Measured, not reasoned. The new subprocess test imports the real worker module in a
child process and asserts on its actual stderr; against the pre-fix code it fails
with the defect visible in the assertion output -- the child emitted the synthetic
`PID|1||100^^^H^MR||DOE^JANE^Q||19800101|F` verbatim, and the raw CR/LF put the
injected text at column 0 on its own physical line.

docs/PHI.md changes in this same commit because the fix closes the weakness the doc
disclosed. `test_the_sandbox_worker_stderr_writer_is_disclosed` couples the two by
construction: once the child is filtered it demands the "outside the filter chain"
exclusion be dropped. That test is re-pinned both ways, with an added assertion that
the child uses one mechanism or the other, so removing both cannot pass green. The
sink-module inventory drops the worker for the same reason -- it no longer builds a
sink, it asks logging_setup for one.
…#1055)

`sys.excepthook` covers the main thread only. The interpreter routes an exception
that escapes a thread's run() to `threading.excepthook` and nowhere else, and that
was still the stdlib default, which prints a raw traceback straight to stderr --
past the handler filter chain, since it never builds a LogRecord at all.

`last_resort` grows `install_thread_excepthook()`, routing through `safe_exc` to the
filtered log and naming the thread, and serve() installs it beside the existing
`install_excepthook()`. SystemExit is ignored, matching the stdlib default: a thread
calling sys.exit() is a clean exit, not an error to report.

The engine thread that motivates it is the sandbox session's raw stdout reader,
whose except clause catches only OSError by design, so anything else escapes run()
and the frame bytes it was mid-read on are message-derived.

Nothing is leaking today -- zero deployments. On a first deployment an unexpected
non-OSError there would have put an unredacted traceback into the NSSM-captured
stderr.

Measured with a live positive control reproducing that shape, run against the
pre-fix and post-fix trees. Before: the stdlib hook wrote 838 bytes of traceback
ending in the full synthetic `PID|1||100^^^H^MR||DOE^JANE^Q||19800101|F`. After: 93
bytes reading `last-resort: uncaught exception in thread 'mefor-sandbox-reader':
ValueError: PID|[redacted]` -- thread name and exception type kept, body gone. The
committed test drives a real thread rather than calling the hook directly, and
asserts stderr carries no traceback at all.
The coordinator's integration commit, and the single point where the
"a PR that implements BACKLOG #N must update BACKLOG.md" required context
is satisfied for the whole train. Lanes 4, 5 and 6 touch messagefoundry/
and none of them may edit the ledger, so as independent pull requests three
of the six would have gone red on that context.

Banner text is each lane's own, carried verbatim from its report rather
than re-written here: the lane that measured the fix is the one that should
describe it.

Ledger after this commit, re-derived with parse_items rather than carried
forward: live 241, open 224, closed-in-live 17, archive 236, namespace 477
conserved. Item 64 stays OPEN by instruction -- only its index role over
62/63/47/34 survives, and discharging that umbrella is an owner call.
…ing (BACKLOG #1024)

CI caught this on the ubuntu leg and no Windows run could have. Get-ChildItem
omits hidden entries without -Force. On Windows a dot-prefixed directory carries
no hidden ATTRIBUTE, so every ~/.claude-account-N enumerates either way. On Linux
the dot prefix IS the hidden convention, so the glob returned NOTHING and the wire
set collapsed to the single explicit ~/.claude candidate on the line above --
which is a Join-Path, not a glob, and so survived.

That is exactly what the parity test reported:
  writer wires : ['.claude']
  reader judges: ['.claude', '.claude-account-1', '.claude-account-42']
and it is the defect #1024 exists to close -- two enumerations of the same
population disagreeing. Get-ConfigCandidates already passed -Force; this is the
matching half. The anchoring introduced earlier is correct and unchanged.

Also adds a cross-platform arm to the test file. The existing parity test can only
go red where dot-dirs are hidden, so on Windows it is a claim rather than a control:
the one instrument able to see this defect was a CI leg. The new test sets
FILE_ATTRIBUTE_HIDDEN explicitly via attrib +H, making the class reproducible on the
box the code is written on, and asserts loudly rather than skipping if the attribute
cannot be set -- a silent skip would restore the blindness it removes.

Red-first, on Windows, with -Force reverted:
  FAILED test_a_hidden_account_dir_is_still_wired
  "a HIDDEN ~/.claude-account-N was not wired"
  and the -Status audit printed "scanned 1 config dir(s)" with ORPHAN GATE WIRING
  in .claude-account-1 -- the same shape CI reported.
Restored: 11 passed.
@wshallwshall
wshallwshall merged commit 516f59e into main Aug 10, 2026
47 of 48 checks passed
@wshallwshall
wshallwshall deleted the w1-integration branch August 10, 2026 19:29
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