From 9feae727a2ed1dda5192846320072845aa955beb Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 13 Aug 2026 13:28:29 -0500 Subject: [PATCH 01/20] backlog: correct #1114's Severity line, which contradicted its own body The Severity line read "could submit unbounded messages". Four bounds ship ON and were re-verified at origin/main 96c9a860: DEFAULT_MAX_FRAME_BYTES 16 MiB (transports/mllp.py:105), DEFAULT_MAX_CONNECTIONS 256 (:106), DEFAULT_RECEIVE_TIMEOUT 60.0s (:107), and max_file_bytes (transports/file.py:384, remotefile.py:808). They bound SIZE and CONCURRENCY, not RATE. The item's own "What holds it short today" paragraph already said exactly that, two paragraphs above, so the Severity line was contradicting its own item rather than describing the engine. Corrected to "at an unbounded RATE"; the finding is unchanged and the item stays open. The correction runs in the direction that makes the engine look better, which is why the amendment states it explicitly: a Severity line is the sentence most often quoted onward without its body. Amendment only, no heading added, so ledger ownership is not consulted. parse_items before and after: 277 items / 203 open, unchanged. --- docs/BACKLOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index a1f832c1..8f48ccaa 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -6036,7 +6036,9 @@ filing. > 🔢 **Filed 2026-08-08 - not started. RESEARCH item: the goal is an HONEST pass, and "cannot honestly reach pass" is a valid finding.** Value **8/10** · Difficulty **7/10**. ASVS **2.4.1** (L2) currently scores **partial**. The pinned verb asks for anti-automation controls against excessive calls leading to exfiltration, garbage data, quota exhaustion, denial of service, or overuse of costly resources. What holds it short is that ~~no message-rate or volume control exists on the data plane in any configuration~~ **[FALSIFIED 2026-08-12 by `e0e979d5`/`b3b278e6` -- struck, not deleted. A pacer EXISTS and ships OFF by default; `transports/mllp.py:109` calls that *"a DELIBERATE DEVIATION from this module's key-absent-means-secure-default convention, ruled 2026-08-11 (ASVS 2.4.1 / 15.2.2)"* -- naming this very cell, so the module knew about this item while this item did not know about the module. THE ITEM STAYS OPEN AND ITS QUESTION HARDENS:** not *does a control exist* (answered, yes) but **can a control that ships OFF be an honest pass.** The `docs/SECURITY.md:1582` citation below also needs re-pointing -- the sentence it quotes was rewritten when the pacer landed, in the same edit that broke ASVS cell 2.1.3's anchor.]** -- the product says so in its own documentation at `docs/SECURITY.md:1582`. **Cluster:** Security / ASVS remediation research. **Priority:** P1. **Verdict:** research. -**Severity:** on a first deployment, any sender able to reach the NIC-bound data plane could submit unbounded messages, each durably persisted before the ACK -- an unbounded store-growth and denial-of-service exposure. MLLP has neither intake authentication nor rate limiting, so there is no actor to charge a budget against. +**Severity:** on a first deployment, any sender able to reach the NIC-bound data plane could submit messages at an unbounded RATE, each durably persisted before the ACK -- a cumulative store-growth and denial-of-service exposure. MLLP has neither intake authentication nor rate limiting, so there is no actor to charge a budget against. + +> ⚠️ **AMENDED 2026-08-13 (dispatcher) -- the Severity line said "unbounded messages", which CONTRADICTED THIS ITEM'S OWN BODY and overstated the exposure. Corrected to "at an unbounded RATE"; the finding is unchanged.** Four bounds ship **on** and were re-verified at `origin/main` `96c9a860`: `DEFAULT_MAX_FRAME_BYTES` 16 MiB (`transports/mllp.py:105`), `DEFAULT_MAX_CONNECTIONS` 256 (`:106`), `DEFAULT_RECEIVE_TIMEOUT` 60.0 s (`:107`), and `max_file_bytes` (`transports/file.py:384`, `remotefile.py:808`). They bound **SIZE and CONCURRENCY, not RATE** -- which is exactly what *"What holds it short today"* already said two paragraphs above, so the Severity line was contradicting its own item rather than describing the engine. **The correction runs in the direction that makes the engine look BETTER**, which is why it is stated explicitly rather than left for a reader to notice: a severity line is the sentence most often quoted onward without its body. **The pinned verb.** "Verify that anti-automation controls are in place to protect against excessive calls to application functions that could lead to data exfiltration, garbage-data creation, quota exhaustion, rate-limit breaches, denial-of-service, or overuse of costly resources." From 222b5b11cb6f84b434b812d9a770c67c3057d3c0 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 13 Aug 2026 13:31:46 -0500 Subject: [PATCH 02/20] backlog: flip #1237 to shipped -- its fix landed without a ledger edit PR #372 merged the code as 96c9a860 and did not touch docs/BACKLOG.md, so the item read "not started" while its fix was on main. Banner repair, not a change of plan. Both stated limbs verified at origin/main 96c9a860 rather than inferred from the PR title: Signature -- an AST probe located all three functions, so it was not blind: gzip_decompress :101, deflate_decompress :138, zip_decompress :195 each carry max_output_bytes keyword-only with NO DEFAULT. That is the construct the item asked for, a gate that refuses when the precondition is absent, rather than a changed default value, which the parent item #1129 explicitly rules out. Tests -- tests/test_compression.py pins it: "Calling a decompressor without max_output_bytes is a TypeError, not an unbounded read", with a pytest.raises(TypeError) assertion. The re-exported public surface still resolves in messagefoundry/__init__.py and parsing/__init__.py. This closes NO ASVS cell and the amendment says so in the item. The verdict is the assessor's and the vault scorecard is the record of record; the "before uncompressing" reading question is unresolved without the pre-pass #1237 deliberately excluded, which remains unfiled and is an owner call. Controls: parse_items 277 items / 203 open / 74 closed before, 277 / 202 / 75 after -- 0 / -1 / +1, the expected delta for one close. backlog_status_check green, every item declaring exactly one status. Banner invariant checked on the item body: one closed-alphabet character, zero open-alphabet characters. --- docs/BACKLOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 8f48ccaa..792ba170 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -8301,7 +8301,11 @@ every worker session's handoff, which is the sentence the next session bases its ## 1237. require an explicit decompression ceiling on the Handler-facing primitives -> 🔢 **Filed 2026-08-12 - not started.** Value **6/10** · Difficulty **2/10**. Split out of **#1129** (ASVS 5.2.3, which stays `partial`) as the BUILD half, so the research item can close on its research. All three `*_decompress` primitives in `messagefoundry/parsing/compression.py` default `max_output_bytes=None`, so a Handler author who does not think about a ceiling silently gets none. +> ✅ **SHIPPED 2026-08-13 in `96c9a860` (PR #372). Banner flipped by the dispatcher on 2026-08-13: the code landed WITHOUT a ledger edit, so the item read `not started` while its fix was on `main`.** Filed 2026-08-12. Value **6/10** · Difficulty **2/10**. Split out of **#1129** (ASVS 5.2.3, which stays `partial`) as the BUILD half, so the research item can close on its research. The defect as filed: all three `*_decompress` primitives in `messagefoundry/parsing/compression.py` defaulted `max_output_bytes=None`, so a Handler author who did not think about a ceiling silently got none. +> +> **BOTH STATED LIMBS VERIFIED AT `origin/main` `96c9a860`, not inferred from the PR title.** Signature limb, read with an AST probe that located all three functions (so it was not blind): `gzip_decompress` `:101`, `deflate_decompress` `:138`, `zip_decompress` `:195` -- each carries `max_output_bytes` **keyword-only with NO DEFAULT**, which is the construct the item asked for rather than a changed default value. Test limb: `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 MUST NOT BE CITED AS ONE.** The item says so itself: the verdict is the assessor's, the vault scorecard is the record of record, and the *"before uncompressing"* reading question is unresolved without the pre-pass this item deliberately excluded. The excluded pre-pass remains unfiled and is an owner call. **Cluster:** Security / hardening. **Priority:** P2. **Verdict:** build. **Severity:** Conditional -- there are zero deployments (§0). On a first deployment, a Handler calling `zip_decompress(body)` or `deflate_decompress(body)` without a ceiling would get no total-size bound. This is **not** bounded by the transports, contrary to #1129's severity sentence: the File connector decompresses single-stream gzip only (`transports/file.py:90`), so no transport ceiling ever sees a zip expansion, and `transports/file.py:73` bounds the **compressed** input at 16 MiB. Measured on `9d98f339` by loading the module directly with default arguments: 65,982 bytes in, 67,108,864 out across 8 members (1017:1), nothing raised, tracemalloc peak 76,081,609 bytes. At that ratio an admitted 16 MiB of compressed input would admit roughly 16 GiB of expansion in the transform worker. From 05eaff1c1695bd172f6a498eb31eb595d9034692 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 13 Aug 2026 13:57:24 -0500 Subject: [PATCH 03/20] backlog: strike #1245's false delete clause, and narrow two overclaims #1245's SCOPE paragraph said the bootstrap account "cannot be renamed (update_user does not rename) or deleted, so it persists as a permanently disabled row". The rename half is correct. The delete half is FALSE. Reproduced end to end by a second session: DELETE /users/ 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. --- docs/BACKLOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 952036f4..1cac6047 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -8509,7 +8509,13 @@ _FHIR_ID_RE.fullmatch("abc\n") -> False the fix > **THE FAILURE IS SILENT AT BOTH ENDS, which is the part that makes it worth a P2.** The administrator sees a successful reset and hands over a temporary password. The account holder sees `invalid credentials` -- the generic refusal at `:653-663`, deliberately indistinguishable from a wrong password so that it does not leak account state. Neither party is told the account was retired. The audit log records `auth.bootstrap_admin_retired` at `:593`, so the evidence exists, but nothing surfaces it at the moment of failure and no operator would think to look there for what presents as a mistyped password. -> **SCOPE, stated precisely so severity is not inflated.** This is **not** a whole-system lockout: the trigger *requires* another enabled administrator, and that administrator still works. What is destroyed is the account named `admin` specifically, and per BACKLOG #1236's neighbourhood it cannot be renamed (`update_user` does not rename) or deleted, so it persists as a permanently disabled row. The exposure is availability, not credential disclosure. **No deployment axis -- zero instances**; this is written in the conditional because nothing is running it. +> **SCOPE, stated precisely so severity is not inflated.** This is **not** a whole-system lockout: the trigger *requires* another enabled administrator, and that administrator still works. What is destroyed is the account named `admin` specifically, and per BACKLOG #1236's neighbourhood it cannot be renamed (`update_user` does not rename) ~~or deleted, so it persists as a permanently disabled row~~ **[see the amendment below -- the delete half is FALSE]**. The exposure is availability, not credential disclosure. **No deployment axis -- zero instances**; this is written in the conditional because nothing is running it. + +> ⚠️ **AMENDED 2026-08-13 (dispatcher) -- THE SCOPE PARAGRAPH'S DELETE CLAUSE IS FALSE AND IS STRUCK. The rename half stands.** The row CAN be deleted: `DELETE /users/` returns **200** `{'detail': 'deleted'}`, reproduced end to end by a second session, and confirmed here from the code -- `BOOTSTRAP_USERNAME` appears **0 times** in `messagefoundry/api/auth_routes.py` (positive control: **7** occurrences in `auth/service.py`, so the probe discriminates), meaning **no delete-time guard names the bootstrap account at all**. 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: delete the row. **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 this item claims.** *"The failure is 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. And **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. > **INTERACTION WITH #1136 / ADR 0163, so this is not fixed twice or fixed away by accident.** ADR 0163 proposes removing the bootstrap admin entirely, which would delete this defect along with its subject. That is a much larger change gated on four owner decisions and is not scheduled. **This item should be fixed on its own terms in the meantime**, and the fix is small: `admin_reset_password` should not re-raise the "unclaimed" signal for an account that has already been claimed, or the retirement gate should test claimed-ness by something a reset cannot forge. Whoever fixes it must also correct the docstring at `:580-581`, because that sentence is the reason nobody looked. From 05e71ad5a7deec5e9b86c52aa44a3d250a9e4143 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 13 Aug 2026 14:00:13 -0500 Subject: [PATCH 04/20] backlog: retract my own #1245 narrowing -- it was measured on the wrong route Forty minutes ago I amended #1245 to say "silent at both ends" was half wrong, on the ground that a 201 response body carries disabled:true. That measurement is TRUE and it is about a DIFFERENT ROUTE. auth_routes.py:656-658 and _user_summary:195 are POST /users -- a create, and the stacked-admin-name defect's route. #1245 is a RESET defect. The reset route at auth_routes.py:753 ends at :776 with return PasswordResetResponse(temp_password=temp) no re-read, no _user_summary, no disabled field. And the stronger reason, which makes "silent at both ends" true in principle rather than by omission: admin_reset_password (auth/service.py:2717) does not call _retire_superseded_bootstrap at all. Its only three call sites are :518, :651 and :2551 -- positive control, the probe resolves real sites. So the re-arm is LATENT: at the moment the reset returns nothing has happened yet, there is no disabled state to report, and a re-read there would correctly say disabled:false. "Silent at both ends" therefore STANDS for this item. The create-path fact is real and belongs in the stacked-name item instead. Caught by the builder holding #1245, which re-measured rather than accepting a correction from the dispatcher. That is the second time today the two of us have hit the same shape in opposite directions: an instrument answering truthfully about the neighbouring question. Kept struck rather than deleted, because the two routes are adjacent in one file and the wrong version is what a later reader would re-derive. One correction from that pass DOES stand and is retained: login is not the sole retirement trigger (:518 on service start, :2551 on create), so a test assuming :651 is the only path is narrower than the defect. Recorded as already handled. SECOND DEFECT IN THIS SAME EDIT, caught by the before/after control and fixed before commit: the retraction was first written with a closed-alphabet character opening a blockquote in the item body. parse_items read it as a status banner and #1245 flipped to CLOSED -- 280/204/76 against an expected 280/205/75. A live item under active build, removed from the queue by a prose edit. The rule is absolute for exactly this reason: no banner-alphabet character in an item body, any position. Say the word. Controls after the fix: 280 items / 205 open / 75 closed, #1245 is_open True, zero closed-alphabet characters in the body, backlog_status_check green with every item declaring exactly one status. --- docs/BACKLOG.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 1cac6047..a0bd8730 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -8515,7 +8515,18 @@ _FHIR_ID_RE.fullmatch("abc\n") -> False the fix > > **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: delete the row. **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 this item claims.** *"The failure is 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. And **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. +> **RETRACTED WITHIN THE HOUR, BY THE SAME AMENDER. The paragraph struck immediately below was WRONG, and it was wrong in this item's own ledger entry for about forty minutes.** It claimed *"silent at both ends"* was half wrong because a 201 response body carries `disabled:true`. **That measurement is TRUE and it is about a DIFFERENT ROUTE.** +> +> ~~The 201 response body does carry `disabled:true` (`auth_routes.py:656-658` re-reads after retirement; `_user_summary` sets it at `:195`), so the failure is silent at the login end only.~~ +> +> **WHY IT IS WRONG, measured at `origin/main` `68797768`:** those lines are `POST /users` -- a **create**, which is the stacked-admin-name defect's route, not this one. **This item is a RESET defect.** `POST /users/{user_id}/reset-password` (`auth_routes.py:753`) ends at `:776` with `return PasswordResetResponse(temp_password=temp)` -- **no re-read, no `_user_summary`, no `disabled` field, just the temp password.** +> **And the stronger reason, which makes it 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 and there is no disabled state to report.** A re-read there would correctly say `disabled: false`. +> +> **SO `"SILENT AT BOTH ENDS"` STANDS FOR THIS ITEM.** The administrator sees a temp password and success; the account holder later sees the generic 401. **The create-path fact is real and belongs in the stacked-name item, not here.** +> +> **Kept rather than deleted because the wrong version is the one a later reader would re-derive** -- the two routes are adjacent in one file, one is a create and one is a reset, and an instrument pointed at the first answers truthfully about the wrong question. Caught by the builder holding the item, who re-measured instead of accepting a correction from the dispatcher. + +> **ONE CORRECTION THAT DOES STAND: 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 `:651` is the only path is narrower than the defect. *(Recorded as already handled -- the builder's suite covers all three.)* > **INTERACTION WITH #1136 / ADR 0163, so this is not fixed twice or fixed away by accident.** ADR 0163 proposes removing the bootstrap admin entirely, which would delete this defect along with its subject. That is a much larger change gated on four owner decisions and is not scheduled. **This item should be fixed on its own terms in the meantime**, and the fix is small: `admin_reset_password` should not re-raise the "unclaimed" signal for an account that has already been claimed, or the retirement gate should test claimed-ness by something a reset cannot forge. Whoever fixes it must also correct the docstring at `:580-581`, because that sentence is the reason nobody looked. From b00380ab3c53d9b31d82a6a40c36815d98641bc5 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 13 Aug 2026 17:44:25 -0500 Subject: [PATCH 05/20] backlog: close #1240, record #1241 as partial -- the ledger edit PR #379 cannot make itself PR #379 is red on a required check that says a PR implementing BACKLOG #N must update BACKLOG.md. The owner's 2026-08-13 ruling says a builder may resolve merge conflicts but may not author ledger content. Those two are mutually unsatisfiable for a compliant builder PR, so the builder correctly withheld the banner and the PR correctly went red. Authoring is dispatcher and lander only; this supplies the edit. Neither a bug nor anyone's error -- two correct rules meeting. #1240 CLOSED. Verified before signing by printing the operands on both refs rather than counting them, after a count instrument returned 0 on a string the printed lines visibly contained: origin/main _FHIR_TYPE_RE = re.compile(r"^[A-Za-z]+$") PR #379 head _FHIR_TYPE_RE = re.compile(r"^[A-Za-z]+\Z") $ -> \Z on the two pattern definitions, call sites unchanged. That is the durable form: it covers all three call sites at once and cannot be re-broken by a future caller, where converting the calls to .fullmatch would fix three and leave a fourth free to reintroduce it. The read-path _reject_control_chars limb was deliberately not added -- redundant once the gates are strict, and it would reintroduce duplication that #1239 records as retired. The item also records that the obvious regression test cannot discriminate: _resolve_read_url strips, so "Patient/123\n" yields an identical URL before and after the fix and only "Patient\n/123" flips. Measured by executing the shipped and patched sources, not argued. #1241 STAYS OPEN, amended to record partial progress. #379 fixed construction-time screening plus a wrong-exception-class defect worse than the filed finding -- http.client.InvalidURL derives from HTTPException, not ValueError and not OSError, so it escaped every except arm in _post including the backstop written for that case. Still outstanding: transports/dicomweb.py, which the item names, and a second unscreened url-construction site in FhirLookupExecutor in the same file. The item's subject is the ASYMMETRY, so one sink screened while a sibling is not reproduces the very defect being reported. A partial close would be wrong. Two corrections to #1241's filed text, neither reducing severity: its comparison clause INVERTS rather than going stale, because the neighbouring path it called "weaker but at least screening" was removed outright, leaving :431 the only unencoded interpolation in the file; and its enum rationale is right advice for the wrong reason, since containment comes from the !r conversion rather than the enum's closedness. Controls: parse_items 281 items / 206 open / 75 closed before, 281 / 205 / 76 after -- 0 / -1 / +1, the expected delta for exactly one close and one amendment. backlog_status_check green, every item declaring exactly one status. Banner invariant checked per item: #1240 one closed-alphabet character and zero open, #1241 zero closed and one open. --- docs/BACKLOG.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 421eb5bb..f45775a9 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -8382,7 +8382,17 @@ Both compute `any(ord(ch) < 0x20 or ord(ch) == 0x7F for ch in ...)`. ## 1240. the FHIR grammar gates use `match` with a `$` anchor, so a trailing newline passes -> 🔢 **Filed 2026-08-13 - not started. NOT EXPLOITABLE TODAY -- the reachability analysis is in the item and it is honest about that.** Value **5/10** · Difficulty **1/10**. Both FHIR grammar gates accept a value with a trailing newline, so on the `fhir_lookup` read path the gate does not enforce the grammar it advertises. Found during #1107 (ASVS 1.2.2). +> ✅ **SHIPPED -- fixed on PR #379, banner authored by the dispatcher because a builder may not author ledger content (owner ruling 2026-08-13).** Filed 2026-08-13. **NOT EXPLOITABLE TODAY -- the reachability analysis is in the item and it is honest about that.** Value **5/10** · Difficulty **1/10**. The defect as filed: both FHIR grammar gates accepted a value with a trailing newline, so on the `fhir_lookup` read path the gate did not enforce the grammar it advertises. Found during #1107 (ASVS 1.2.2). +> +> **VERIFIED BY THE DISPATCHER BEFORE SIGNING THE CLOSURE, by printing the operands on both refs rather than counting them:** +> ``` +> origin/main _FHIR_TYPE_RE = re.compile(r"^[A-Za-z]+$") _FHIR_ID_RE = ...{1,64}$") +> PR #379 head _FHIR_TYPE_RE = re.compile(r"^[A-Za-z]+\Z") _FHIR_ID_RE = ...{1,64}\Z") +> call sites _FHIR_TYPE_RE.match(...) / _FHIR_ID_RE.match(...) UNCHANGED, correctly +> ``` +> **`$` -> `\Z` on the two pattern definitions, which 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 sites and leave the fourth caller 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 the duplication #1239 records as retired. +> +> ⚠️ **The obvious regression test CANNOT DISCRIMINATE and must not be written.** `_resolve_read_url:689` does `raw = query.strip()`, so `"Patient/123\n"` returns an **identical URL before and after the fix**. Only `"Patient\n/123"` flips admitted-to-refused. A test using the stripped form would ship green and prove nothing -- measured by executing the shipped and patched sources, not argued. **Cluster:** Security / input validation. **Priority:** P2. **Verdict:** build (small). **Severity:** Conditional and currently **none** -- see reachability below. The defect is that a control does not do what it claims, which matters independently of whether another control happens to cover for it. @@ -8410,7 +8420,15 @@ _FHIR_ID_RE.fullmatch("abc\n") -> False the fix ## 1241. operator-config values reach URL and header sinks with no construction-time screen -> 🔢 **Filed 2026-08-13 - not started.** Value **5/10** · Difficulty **3/10**. Several operator-configured values are interpolated into URL paths, query strings and an HTTP header with weaker treatment than the message-derived values beside them -- in one case with no screen at all. Found during #1107 (ASVS 1.2.2). **The subject is the asymmetry**, so fixing one site without the others misses the point. +> 🔢 **Filed 2026-08-13. PARTIALLY FIXED on PR #379 and DELIBERATELY STILL OPEN -- see the amendment below. DO NOT CLOSE THIS ON #379.** Value **5/10** · Difficulty **3/10**. Several operator-configured values are interpolated into URL paths, query strings and an HTTP header with weaker treatment than the message-derived values beside them -- in one case with no screen at all. Found during #1107 (ASVS 1.2.2). **The subject is the asymmetry**, so fixing one site without the others misses the point. +> +> ⚠️ **AMENDED 2026-08-13 (dispatcher) -- PARTIAL PROGRESS RECORDED, ITEM STAYS OPEN.** Banner authored by the dispatcher rather than the builder, per the owner's 2026-08-13 ruling that a builder may resolve conflicts but may not author ledger content. **The builder flagged the partiality in its own commit body; this records it in the ledger so a reader of `main` cannot mistake #379 for a closure.** +> +> **WHAT #379 FIXED:** construction-time screening of `url` and `conditional_query`, plus a wrong-exception-class defect that was 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 exactly that case. The result was an unhandled exception out of `send()` rather than the classified dead-letter the file intends. +> +> **WHAT REMAINS, and it is why this stays open:** **`transports/dicomweb.py`**, which this item names and #379 does not touch; and a **SECOND unscreened url-construction site in `FhirLookupExecutor`** in the same file, discovered only because an edit matched two locations. **The item's own framing is the reason a partial close would be wrong -- the subject is the ASYMMETRY, and one sink screened while a sibling is not reproduces exactly the defect being reported.** +> +> **TWO CORRECTIONS TO THE FILED TEXT, neither reducing severity.** The comparison clause does not merely go stale, it **INVERTS**: the neighbouring flat-search path it called "weaker but at least screening" was removed outright by `039757ff`, so `:431` became the **only** unencoded interpolation left in the file -- which **strengthens** this item rather than weakening it. And the enum rationale is **right advice for the wrong reason**: containment comes from the `!r` conversion escaping control characters, not from the enum's closedness. **Replace the reason or the next reader copies the enum argument to a site with no `!r`.** > ⚠️ **Amendment 2026-08-13 -- the item's JUSTIFICATION was the weaker of the two available, and the stronger one is a measured fact. Severity is UNCHANGED and deliberately not upgraded.** > From 9e87f1048c251be09299e84acf78a8625cdfef19 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 13 Aug 2026 17:55:34 -0500 Subject: [PATCH 06/20] backlog: flip #1204 to shipped, and record that #1203 is abandoned rather than free #1204's banner read OPEN while its own body said "FIXED in the same change" and its Verdict line said "build (done)". Banner repair, not a change of plan. Verified at origin/main before signing, with a discriminating control. All four artifacts ship: scripts/docs/asvs_tally_lint.py, scripts/docs/asvs_tally_baseline.txt, .github/workflows/asvs-tally-lint.yml, tests/test_asvs_tally_lint.py. A deliberately impossible path under the same probe returned ABSENT, so the four PRESENTs are evidence rather than a probe that answers yes to everything. One defect found while verifying, and it is not what it first looks like. asvs-tally-lint.yml:3 cites BACKLOG #1203; the item it implements is #1204. The obvious reading is a typo pointing at an unissued number, and that reading is wrong in the direction that causes harm: it would send someone to file #1203 as free. Measured: "## 1203." appears in neither docs/BACKLOG.md nor docs/archive/backlog/BACKLOG-CLOSED.md, with "## 1204." resolving in the live ledger as the positive control. But the allocator record mefor-coord/alloc/backlog/1203.json EXISTS, titled "Decide how the public engine repo obtains the private ASVS scorecard for --prove-absences". So #1203 is ALLOCATED AND NEVER FILED -- abandoned, not free. Numbers are never reclaimed and holes are free, so the hole costs nothing. What costs something is that nothing reports an allocated-but-unfiled number, and a live citation makes it look issued. The :3 correction to #1204 is a one-word fix and rides with whatever next touches that workflow. Controls: parse_items 281 items / 205 open / 76 closed before, 281 / 204 / 77 after -- 0 / -1 / +1, the expected delta for one close. backlog_status_check green, every item declaring exactly one status. Banner invariant on the item body: one closed-alphabet character, zero open-alphabet. --- docs/BACKLOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index f45775a9..3e6e6e28 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -7589,7 +7589,12 @@ credential-free cross-repo trigger exists for it; the vault's daily cron remains ## 1204. a forward-only lint refusing a NEW hard-coded ASVS tally, with the idiom set rebuilt from the corpus -> 🔢 **Filed 2026-08-09 - FIXED in the same change. Every idiom is driven from both sides, and the eight documents that motivated it were confirmed RED before it landed.** Value **7/10** · Difficulty **3/10**. ADR 0156 made the ASVS count computed. It did not stop anyone writing one down: 44 documents assert a whole-corpus tally, roughly fifty distinct tallies exist, and approximately one is correct. +> ✅ **SHIPPED -- banner flipped by the dispatcher 2026-08-13. The item said "FIXED in the same change" and "Verdict: build (done)" while its banner read OPEN, so the banner contradicted its own body.** Filed 2026-08-09. Every idiom is driven from both sides, and the eight documents that motivated it were confirmed RED before it landed. Value **7/10** · Difficulty **3/10**. +> +> **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.** `.github/workflows/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" -- **that reading is wrong and would send someone to file #1203 as free.** Measured: `## 1203.` appears in **neither** `docs/BACKLOG.md` **nor** `docs/archive/backlog/BACKLOG-CLOSED.md` (positive control: `## 1204.` resolves in the live ledger), **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 -- an abandoned number, not a free one.** Numbers are never reclaimed and holes are free, so the hole itself costs nothing; what costs something is that **nothing reports an allocated-but-unfiled number**, and the citation makes it look issued. The `:3` correction to `#1204` is a one-word fix and rides with whatever next touches that workflow. ADR 0156 made the ASVS count computed. It did not stop anyone writing one down: 44 documents assert a whole-corpus tally, roughly fifty distinct tallies exist, and approximately one is correct. **Cluster:** Documentation correctness / gate blindness. **Priority:** P2. **Verdict:** build (done). **Severity:** no product effect and no PHI effect. The cost is a security record whose published From 97de725c4ca02add28f9add6e01155baecd10e72 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 13 Aug 2026 18:05:36 -0500 Subject: [PATCH 07/20] docs(adr): ADR 0165 -- a builder PR satisfies the ledger gate with a paired commit Records a coordination decision that until now existed only in session messages and a queue file, which is the exact shape this project keeps being bitten by -- a ruling with no artifact behind it. THE COLLISION. The required check "a PR that implements BACKLOG #N must update BACKLOG.md" demands a ledger edit in the PR's own diff. The owner's 2026-08-13 authoring ruling forbids a BUILDER to author ledger content, on the property that a mechanical union cannot invent a disposition but authoring a banner can, and a seat that can author its own item's banner can turn its own PR green. Composed, a compliant builder PR cannot pass a required check. Measured live: PR #379 went red for obeying the ruling. THE DECISION. The Dispatcher or Lander authors the disposition and the commit rides on the PR branch. Owner-ruled a1. THE PART THAT INVERTED ON MEASUREMENT. Reading the gate rather than reasoning about it: backlog-hygiene.yml:64-98 computes a three-dot diff and passes if the changed set touches docs/BACKLOG.md or docs/archive/backlog/. It never inspects authorship. Evaluated against the real cherry-picked head for #379 -- touches_code 1, ledger 1, PASS. So the pattern was in force before it was named, no gate change was required, and none is pending. The ledger gate permits the cherry-pick for a non-obvious reason: it iterates headings added relative to base, and a banner flip or amendment on an item already on main adds no "## N." heading, so ownership is never consulted and the committing seat is irrelevant. Holds only for landed items; a PR that FILES an item is a different shape. REJECTED, with reasons rather than preferences. (a2) separately-landed plus cross-branch correlation would undo a deliberate control -- the gate uses three-dot on purpose and its own comment says two-dot "would pass while enforcing nothing". (b) a builder carve-out to flip its own banner reopens the self-approval hazard. (c) as a distinct interim dissolved: it is the same mechanism, so there is no transition. RECORDED NEAR-MISS, kept rather than deleted because the wrong version is what a later reader re-derives: the ruling was briefly written as "(c) is fine until (a) lands" -- an expiry whose trigger had ALREADY FIRED. It looks like the safe construction and behaves like the unsafe one, becoming permanent by default while appearing bounded. Provenance is split three ways in the ADR because each half is only checkable if attributed: the collision found by the Lander on #379's red check, the self-approval property by Builder 2, the gate measurement and the no-build finding by the Dispatcher, the ruling by the owner. ADR number allocated atomically to this worktree; index row added in the same commit, as the ledger gate requires. No engine behaviour changes. Note for whoever integrates: docs/adr/README.md is an APPEND/APPEND conflict with claude/builder-seat-playbook-bf2ead, which appends ADR 0164's row at the same tail. Both rows are additive and disjoint -- take both sides. --- ...it-authored-by-the-dispatcher-or-lander.md | 150 ++++++++++++++++++ docs/adr/README.md | 1 + 2 files changed, 151 insertions(+) create mode 100644 docs/adr/0165-a-builder-pr-satisfies-the-ledger-gate-with-a-paired-commit-authored-by-the-dispatcher-or-lander.md diff --git a/docs/adr/0165-a-builder-pr-satisfies-the-ledger-gate-with-a-paired-commit-authored-by-the-dispatcher-or-lander.md b/docs/adr/0165-a-builder-pr-satisfies-the-ledger-gate-with-a-paired-commit-authored-by-the-dispatcher-or-lander.md new file mode 100644 index 00000000..e3815550 --- /dev/null +++ b/docs/adr/0165-a-builder-pr-satisfies-the-ledger-gate-with-a-paired-commit-authored-by-the-dispatcher-or-lander.md @@ -0,0 +1,150 @@ + + + +# ADR 0165 — A builder PR satisfies the ledger gate with a paired commit authored by the Dispatcher or Lander + +- **Status:** Accepted (2026-08-13) — **already in force; no code change was required.** See §"The decision needed no build" +- **Date:** 2026-08-13 +- **Related:** [backlog-hygiene.yml](../../.github/workflows/backlog-hygiene.yml) (the gate) · [BACKLOG #1240, #1241](../BACKLOG.md) (the PR that surfaced it) · [ADR 0158](0158-silent-controls-green-signals-that-mean-nothing-and-shape-over-detection.md) (a control that cannot observe its own failure) · [CLAUDE.md](../../CLAUDE.md) §5 (git discipline), §11 (state a load-bearing fact once) + +--- + +## Context + +### Two correct rules met and produced an unsatisfiable state + +Neither rule is defective. Neither party erred. That is the whole reason this needed a decision rather +than a fix. + +- **The gate.** `.github/workflows/backlog-hygiene.yml` publishes a required status check named *"a PR + that implements BACKLOG #N must update BACKLOG.md"*. If a PR's title or body carries an explicit + `BACKLOG #N` token **and** the PR touches engine or IDE code, the PR's own diff must also touch + `docs/BACKLOG.md` or `docs/archive/backlog/`. It exists because a fix once landed on `main` with its + item still reading *not started*. +- **The authoring ruling (owner, 2026-08-13).** A builder **may** resolve a merge conflict in + `docs/BACKLOG.md`; a builder **may not author** ledger content — bodies, banners, closures. The + property being protected is narrow and load-bearing: **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.** + +Compose them and **a compliant builder PR cannot pass a required check.** Measured live: PR #379 +implemented two numbered items, withheld its banner exactly as instructed, and went red **for +obeying**. + +### The failure mode is not the red check + +A red check is loud and self-announcing. The cost is what a builder does next: conclude the ruling is +unworkable and flip the banner anyway. **The ruling would then be defeated by the gate that has +nothing to do with it**, silently, on every future PR — and the check would still be green, so +nothing would report it. + +## Decision + +**A builder PR satisfies the ledger gate with a paired ledger commit AUTHORED by the Dispatcher or the +Lander and carried ON THE PR BRANCH.** The builder never authors ledger content; the check passes on +the PR's own diff. + +Concretely, as executed on PR #379: the Dispatcher authored both dispositions (one close, one +partial-progress amendment), committed them on its own branch, and the Lander cherry-picked that commit +onto the PR head. + +### The decision needed no build + +This is the part that inverted the expected answer, and it was established by reading the gate rather +than by reasoning about it. `backlog-hygiene.yml:64-98` does exactly two things: + +``` +changed = git diff --name-only BASE_SHA...HEAD_SHA (three-dot, deliberately) +pass if changed touches docs/BACKLOG.md or docs/archive/backlog/*.md +``` + +**It never inspects authorship.** It tests the PR's changed *file set*. A commit authored by the +Dispatcher and cherry-picked onto the PR branch is, to the gate, indistinguishable from one the builder +wrote. + +Evaluated against the real cherry-picked head for #379: + +| predicate | value | +|---|---| +| `changed` | `docs/BACKLOG.md`, `transports/fhir.py`, `tests/test_fhir_lookup.py`, `tests/test_fhir_transport.py` | +| `touches_code` | 1 | +| touches ledger namespace | 1 | +| **verdict** | **PASS** | + +So the pattern was in force before it was named. **No gate change was needed, and none is pending.** + +### The ledger gate permits the cherry-pick, which is not obvious + +`scripts/hooks/ledger_check.py` keys ledger-number ownership on the worktree that ran `alloc.ps1` and +refuses a number allocated elsewhere. That would appear to block a Lander from committing a +Dispatcher's ledger edit. It does not: the gate iterates **headings added relative to base**, and a +banner flip or body amendment on an item already on `origin/main` **adds no `## N.` heading**. The +heading set is unchanged, ownership is never consulted, and the committing seat is irrelevant. +Confirmed live — the pre-commit hooks ran clean on the cherry-pick. + +**This holds only for items already on `main`.** A PR that *files* a new item is a different shape and +is out of scope here. + +## Alternatives considered and rejected + +**(a2) The ledger commit lands separately and the gate correlates it.** Rejected: it would undo a +deliberate control. The gate uses a **three-dot** diff on purpose, and its own comment states why — +two-dot *"includes everything main gained since this PR branched"*, so any main-side ledger change +would be credited to every open PR with an older base and *"this gate would pass while enforcing +nothing."* Building cross-branch correlation re-opens exactly that. + +**(b) A narrow carve-out letting a builder flip only the banner of the item it was dispatched.** +Rejected: it reopens the self-approval hazard the authoring ruling had just closed. *(Property +identified by the Builder 2 seat before any ruling existed.)* + +**(c) Dispatcher/Lander supplies the edit as standing process, as an interim before (a2).** **Dissolved +rather than rejected** — (c) and the accepted decision are the *same mechanism*. There is no interim +and no transition. + +### A recorded near-miss: an expiry condition whose trigger had already fired + +The decision was briefly recorded as *"(c) is fine until (a) lands"*. That clause **looks like the safe +construction** — a standing rule carrying its own expiry, which is the discipline this project applies +precisely so prohibitions do not become permanent by default. + +**It behaves like the unsafe one.** (a) had already landed, so the trigger could never be observed, and +the rule would have become permanent by default anyway while appearing bounded. A successor would wait +indefinitely for a gate change nobody was building. + +**Recorded rather than deleted, because the wrong version is the one a later reader re-derives: an +expiry tied to a state that is already true is not an expiry.** + +## Consequences + +- **One manual step per builder PR that implements a numbered item, indefinitely.** Accepted with open + eyes; it is the price of keeping the self-approval property. +- **The builder MUST declare the withheld banner in its PR body.** A missing banner flip is visually + identical to the defect the gate exists to catch — a fix on `main` with its item still reading *not + started*, which is exactly what happened on BACKLOG #1237. Same shape, opposite cause; **only a + declaration separates them.** +- **The Dispatcher or Lander becomes a serialisation point** on every such PR. If that seat is absent + or out of budget, the PR waits. It cannot be worked around by the builder without defeating the + ruling. +- **This ADR changes no engine behaviour.** It records a coordination decision and the measurement that + showed it needed no build. + +## What this does NOT decide + +- **Whether a builder may author ledger content in any other circumstance.** It may not; that ruling + stands unchanged and this ADR neither widens nor narrows it. +- **How a PR that FILES a new item should behave.** Filing adds a heading, so ledger ownership *is* + consulted, and that routes to whoever allocated the number. Out of scope. +- **Whether the gate should ever learn authorship.** Not proposed, not desirable on present evidence, + and it would not help — the constraint is who *writes* the content, not who *carries* the commit. + +## Provenance + +Stated because three seats contributed different halves and the reasoning is only checkable if each is +attributable. + +| part | seat | +|---|---| +| The collision, found on PR #379's red check | Lander | +| The self-approval property that rejects (b) | Builder 2 | +| The gate measurement, and the split that showed no build was needed | Dispatcher | +| The ruling | Owner | diff --git a/docs/adr/README.md b/docs/adr/README.md index 834c8ec3..3e54441a 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -188,3 +188,4 @@ what is withheld and what you can request. | [0159](0159-cancellation-safe-pooled-connection-release-mid-txn-discard-at-the-acquire-chokepoint.md) | **Cancellation-safe pooled-connection release — quarantine at the `_acquire` chokepoint** (BACKLOG #348) — `SqlServerStore`'s write idiom is `except Exception: await conn.rollback(); raise`, used at **90 of the 91** `self._acquire()` sites. `CancelledError` derives from `BaseException`, so on a cancellation **no rollback runs**, and aioodbc does not compensate: `Pool.release()` appends a non-closed connection straight back onto the free deque with no rollback, reset or transaction check (0.5.0 `pool.py:196-205`), and `_ContextManager.__aexit__` uses the *same* `release` on the exception path (`utils.py:60-62`). The next borrower inherits an open transaction still holding X locks. **Measured on a live SQL Server**: cancelling `release_claimed` left **7** X locks on `queue`, `reschedule_claimed` 7, `mark_done` 9, `enqueue_ingress` 11 (the pre-ACK ingress commit), against **0** for the `claim_fifo_heads` control; the connection returned to the free list, a raw writer got **1222**, and a real second claim yielded **EMPTY-all** — which ADR 0066 §9 sanctions, so the symptom is *silence*, not an error. Fix: at `_acquire`, on a non-`Exception` `BaseException` only, **synchronously** drop the driver handle (`conn._conn = None` — aioodbc derives `closed` from it and re-adds only `if not conn.closed`) **with no await in front of it**, then close the raw handle off-loop under a 5s bound. The ordering *is* the guarantee: a cleanup that awaits first is defeated by the **second** cancellation that shutdown's cancel-then-gather delivers. A plain `await conn.rollback()` was built and **rejected on measurement** — it runs in the default executor bounded only by `command_timeout` (30s) with no upstream timeout, and a cancel measured **1.005s** against a 1.0s rollback, stalling exactly the **demotion** path (`_stop_graph` cancels but does **not** close the store, so the poisoned connection is re-borrowed there). Corrects the lead that found it on two points: it is **not** a two-method asymmetry, and `claim_fifo_heads` does **not** shield against it — its guard is a `SET LOCK_TIMEOUT` *reset* guard and ADR 0114 §2 plus a frozen test record that **no rollback** runs on its cancellation path; it ends clean because the guard **commits**. Ordinary errors keep today's rollback-and-recycle behaviour, pinned by controls that pass before and after. SQL Server only — Postgres is safe twice over (asyncpg rolls back on any `BaseException` and its pool resets under `shield`), SQLite has no pool | Accepted (2026-08-02) — built and verified the same day; gate failed 6/12 pre-fix, passes 12/12 post-fix, live repro 7→0 locks | | [0160](0160-public-repo-content-policy-operator-and-security-review-material-only.md) | **Public-repo content policy -- operator and security-review material only** -- the owner stated twice on 2026-08-04 that the public repo should carry only what someone RUNNING MessageFoundry needs plus what a SECURITY REVIEW needs (ADRs named explicitly as the second), and that material about how the project is built with Claude Code comes out. **It was recorded NOWHERE** -- a session grepped every local and remote ref and found nothing -- while being cited as a constraint on four other sessions' work, which is exactly the standing the coordinating session had REFUSED from a peer the same day over a relayed owner ruling. That asymmetry is why this exists: an unrecorded policy enforced across isolated sessions is indistinguishable, to the session receiving it, from an invented one. The question is new because the 2026-07-27 cutover retired the publish-time deny-list that used to answer it, so it is now answered per file, at authoring time, with no gate and no written rule. Measured at `c90dcb5f`: `docs/` 580 tracked, `docs/adr/` **156 (stays)**, `docs/releases/` **101** (mostly `*-MULTISESSION-PLAN.md`, the largest concentration of process material), `scripts/` 61. Decision: a two-part test (operator need OR reviewer need), applied in three reversible phases -- `docs/releases/` first, then the individually-tracked process docs, then in-file prose -- with **no enforcement gate proposed and the reason stated**, because a path-based gate is evaded or wrong and a keyword gate fires on every legitimate use of "session". ⭐ **`CLAUDE.md` is a genuine policy-versus-tooling conflict and stays tracked as a named exception**: by the test it should go, but `git worktree add` CANNOT deliver an untracked file, so while it was untracked every new worktree came up with **zero project conventions loaded** (verified on two live worktrees; it only looked right in the primary because an untracked copy happened to sit on disk). Rejected: keep everything (a public `docs/` that is majority process material misrepresents the project to its intended reader), reinstate a publish deny-list (retired deliberately; re-adds a sync problem and the slug-rewrite damage class), and move the process DOCS to `claude-multisession` (the tooling move is owner-authorised but recorded in no ruling -- context, not constraint). ⚠️ Explicitly **NOT a confidentiality control** -- `git log` retains everything removed, nothing here was ever treated as secret, and anything genuinely sensitive belongs in the vault and always did | **Accepted (2026-08-06). Phase 1 EXECUTED; Phase 2 DECLINED; Phase 3 Proposed.** Phase 1 removed **103 files** -- `docs/releases/` (101) plus two session handoffs found tracked in the repository ROOT, now covered by a `/HANDOFF-*.md` pattern that fails closed on the next one. Custody moved to the vault and was PUSHED BEFORE removal, because gitignoring alone leaves unversioned single copies that `git clean -xdf` erases -- the two mechanisms are complements, not alternatives. Owner rulings recorded: the vault is the destination (open question 3), `claude-multisession` is REJECTED for this material, and the governing rule is **do not break anything**, applied per item as *prove the mechanism or leave the item alone*. ⛔ **D5 DECLINES the process tooling move** (`scripts/coord\|worktree\|hooks`, ~28 files + 26 tests, and the four docs describing them) on measured cost rather than blocking it on anyone: the tempting cheap gate does not work, because the 26 test files split **exactly 13/13** between `which("pwsh")`-only skips (which run on hosted ubuntu) and `or os.name != "nt"` skips (which **silently skip** there) -- so a Linux-only vault leg covers half the suite and reports GREEN, the same silent-control defect relocated. A correct gate needs ubuntu AND windows, the vault is private so Windows bills at 2x, and the vault's CI is `disabled_manually` on every workflow but one (`ci.yml` last ran 2026-07-27, failed). Benefit is cosmetic -- unlike Phase 1's plans, nobody mistakes dev tooling for the product -- so it is DECLINED, not deferred, because a permanently-blocked item reads as work someone still owes. ⚠️ Records two findings that outlive it: `tests/test_feature_map_claims.py` resolves links against the FILESYSTEM, so ignored-but-present files make it **blind in any working tree** (measured: a re-introduced dangling link passed locally, failed against a `git archive` export) -- verify link changes against a tracked-files-only export; and `git rm --cached` spares only the tree it runs in, CONFIRMED when the Phase 1 rebase deleted the files from the working tree, which is what will happen to the primary and all active worktrees on merge. Corrects the draft's "cannot be docs-only" reasoning: two of its three cited non-doc files are comments and the third's entry is an EXCLUSION list, so the real cause is that the mechanism IS a `.gitignore` edit, which ci.yml classifies as code by construction. Followed by a 104th file: `docs/releases/HANDOFF-232-router-steps.md` entered main via PR #225 WHILE the Phase 1 PR was open, and since a removal that enumerates paths cannot cover a file created after its commit was built -- and gitignore does not untrack -- the directory came back holding one file. **D6 records three sets considered and LEFT** so a later sweep does not re-derive them: 11 prose citations (provenance, not links; 0160's own must never be "cleaned"), 22 handoff documents outside `docs/releases/` (a name sweep reports 155, but 133 are benchmark DATA under `HANDBACK_*` dirs, and the rest carry the measurement narrative for the data beside them), and unanchoring `/HANDOFF-*.md` (rejected -- both locations that matter are already covered, and it would fail closed on `docs/benchmarks/` where handoffs are tracked on purpose). Open: **does `docs/BACKLOG.md` stay?** | | [0161](0161-async-session-mail-for-unreachable-peers.md) | **Async session mail for unreachable peers** (BACKLOG #1028) — the realtime session channel cannot address two peer classes, **structurally rather than by a filter**: a session launched by the VS Code extension is never entered into the Desktop app's in-memory map of sessions it spawned, and a session under a different login sits behind an independent config root (measured 2026-08-05: one repo, one moment, Desktop sessions on one root and VS Code sessions on a second, neither side visible to the other). The client's compiled peer-to-peer protocol is inert — the registry field carrying a peer's socket address is written by no code path and the lookup **fails silently green**, an empty peer list rather than an error. Decision: an **async file-drop lane** under `/mefor-coord/mail/`, keyed by the recipient's normalised **worktree path** (not session id, which `/clear` re-mints; not worktree name, which is a creation-time label — one worktree was observed on four branches under four sessions in a day), with the key function held in **one definition** dot-sourced by both ends because a drifted second copy fails silently on both sides. Location is the leak control: nothing under `.git` can enter a commit, and `mefor-coord` is not a ref namespace, so `push --mirror` cannot carry it — which is why worktree paths are plain text in the queue deliberately. Explicitly **does not replace** the realtime channel for desktop-to-desktop. **The claim primitive is the load-bearing part, and it was forced by measurement:** `[System.IO.File]::Move` **returns success without moving** for losers under contention — 16 racers x 500 rounds on .NET 10.0.9 / Windows 10.0.26200, instrumented in C# because PowerShell scriptblock closures do not capture loop variables reliably; **every** round had more than one racer return with no exception, in **375 of 500** rounds **all sixteen** did, and the obvious fix — verify `File.Exists(dst) && !File.Exists(src)` — was **true for all 16 racers in all 500 rounds**, because the winner's move makes it true for everybody. A destination unique per claimer yielded exactly one winner in **500 of 500**. Controls confirm the no-op is contention-only (a `Move` of a nonexistent or already-moved source raises). `scripts/coord/claim.ps1` is **not** affected — exclusive `CreateNew` plus a per-PID-unique target — do not "fix" it. Also decided: the **on-disk filename is authoritative and the JSON `id` is discarded** (sanitising it would be a weaker control that looks identical), the drain **never emits a runnable command**, one body sanitiser at the single injection point so a body cannot forge the frame, receiver-side caps (a send-time cap is bypassed by the write that *is* the transport), and a content rule with the force of the secrets rule. Bad consequences are stated rather than softened: the **write-side trust boundary cannot be enforced** by this design (any process running as the user can drop into any inbox, so every `from.*` field is a self-assertion), **delivery duplicates the body into a transcript no prune reaches**, and the urgent `asyncRewake` tier is **one-shot** because the rewake belongs to the process Claude Code spawned and a self-respawned grandchild's exit code is heard by nobody | **Proposed (2026-08-05)** — the code is a **PROTOTYPE and is deliberately NOT WIRED**; the installer rows exist but no config root has been installed from them. Wiring is a separate owner-approved step gated on BACKLOG #1028, and the ten EARS criteria link to functions that exist in `tests/test_session_mail.py`. **Arm B of the claim measurement was later corrected:** it was taken with 16 threads in ONE process, and re-measuring with 16 separate processes showed `File.Exists(own destination)` reporting a win to more than one racer in 46 of 800 rounds, so the shipped verdict is an exclusive open, not an existence check | +| [0165](0165-a-builder-pr-satisfies-the-ledger-gate-with-a-paired-commit-authored-by-the-dispatcher-or-lander.md) | **A builder PR satisfies the ledger gate with a paired commit authored by the Dispatcher or Lander** — **two correct rules met and produced an unsatisfiable state**, which is why it needed a decision and not a fix. 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**. Measured live: **PR #379 went red for OBEYING the ruling.** Decision: the Dispatcher or Lander **authors** the disposition and the commit rides **on the PR branch**. **The expected answer inverted on reading the gate rather than reasoning about it** — `backlog-hygiene.yml:64-98` computes `git diff --name-only BASE...HEAD` and passes if the changed set touches `docs/BACKLOG.md` or `docs/archive/backlog/`; it **never inspects authorship**, so a Dispatcher-authored commit cherry-picked onto the head is indistinguishable from a builder's. Evaluated against the real cherry-picked head: `touches_code` 1, ledger 1, **PASS** — so **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 (confirmed — pre-commit hooks ran clean on the cherry-pick); **this holds only for landed items, a PR that FILES an item is a different shape**. **(a2)** — land the ledger commit separately and correlate it — **rejected because it 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 only its own item's banner — rejected, reopens the self-approval hazard (property identified by the Builder 2 seat before any ruling existed). **(c)** — the same pattern as an interim — **dissolved rather than rejected: (c) and the decision are one mechanism, so there is no transition.** Carries a recorded near-miss: 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, and would have become permanent by default while appearing bounded. Consequence stated rather than softened: **one manual step per builder PR indefinitely**, a Dispatcher/Lander serialisation point, and the builder **MUST declare the withheld banner in its PR body** because a missing flip is visually identical to the BACKLOG #1237 defect — a fix on `main` with its item still reading *not started*, same shape, opposite cause. No engine behaviour changes | **Accepted (2026-08-13)** — **already in force; no code change was required or is pending.** Executed on PR #379 before the ADR was written. Provenance split three ways 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** | From 150613f4e463b8dbe430d7f2c46b5a72960cb443 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 13 Aug 2026 18:38:58 -0500 Subject: [PATCH 08/20] backlog: measure one limb of #1143's research question, and fix a blank-line citation #1143 asks what identification keyed on the IdP-namespaced subject would actually require across all three store backends. One limb of that is now measured rather than left to be re-derived by whoever picks the research up. MEASURED at origin/main, with a discriminating control: UNIQUE index or index naming oidc_issuer / oidc_subject: store.py 0 postgres.py 0 sqlserver.py 0 positive control: "UNIQUE" appears 13 times in store.py, so the probe discriminates and the three zeroes are real absences column types today: postgres.py:531-532 oidc_issuer TEXT, oidc_subject TEXT sqlserver.py:1357 oidc_issuer NVARCHAR(MAX) NULL, oidc_subject NVARCHAR(MAX) NULL So the federated columns exist and carry no uniqueness constraint of any kind. An (issuer, subject) identity key is therefore not a code-only change: it needs a unique index on all three backends, and on SQL Server NVARCHAR(MAX) cannot be an index key column at all, so both columns must first be re-typed to a bounded NVARCHAR(n). That is a second migration on that backend. That cost is an INPUT to choosing between the candidate designs rather than a consequence of having chosen one, which is why it belongs in the item before the research runs rather than after. CITATION FIX in the same pass: the banner cites store.py:1590 for users.username. :1590 is a BLANK LINE; the declaration "username TEXT NOT NULL UNIQUE" is at :1593. Found independently by two seats, so it is recorded rather than quietly patched. Explicitly NOT settled, and stated in the item: the ceremony for the first federated login of an account that predates federation. That remains the item's hard question and nothing above touches it. A migration cost informs that decision; it does not answer it. Amendment only, no heading added, so ledger ownership is not consulted. parse_items before and after: 281 items / 204 open / 77 closed, unchanged. --- docs/BACKLOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 3e6e6e28..689bfa50 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -6569,6 +6569,22 @@ filing. **The research question.** What would identification keyed on the IdP-namespaced subject actually require here, across all three store backends, and what is the correct ceremony for the first federated login of an account that predates federation -- that account has no (iss, sub) yet, so every candidate binding is a trust-on-first-use decision wearing a different hat, and the research must say which of them is defensible rather than assuming one. Named unknowns: whether splitting `AuthProvider` is a real improvement or merely relocates the collision; whether the allow-list can constrain the local part without rejecting legitimate UPNs; whether a single pinned issuer fronting upstream guest principals changes the answer. +> ⚠️ **AMENDED 2026-08-13 (dispatcher) -- ONE LIMB OF THE RESEARCH QUESTION IS NOW MEASURED, so nobody re-derives it: keying identity on the IdP-namespaced subject REQUIRES A SCHEMA CHANGE ON ALL THREE BACKENDS, and on SQL Server it requires a COLUMN RE-TYPE, not just an index.** Measured at `origin/main`, with a discriminating control: +> ``` +> UNIQUE index / index naming oidc_issuer or oidc_subject: +> store.py 0 postgres.py 0 sqlserver.py 0 <- none, on any backend +> POSITIVE CONTROL: 'UNIQUE' appears 13 times in store.py, so the probe discriminates +> +> 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 +> ``` +> **The columns exist and carry no uniqueness constraint of any kind.** So an `(issuer, subject)` identity key is not a code-only change: it needs a unique index on all three, **and on SQL Server `NVARCHAR(MAX)` cannot be an index KEY column at all** (max key size 900/1700 bytes), so those two columns must be re-typed to a bounded `NVARCHAR(n)` first. **That is a second migration on that backend**, and it is the kind of cost that decides between the candidate designs rather than following from one. +> +> **CITATION FIX in the same pass:** the banner cites `messagefoundry/store/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, which is why it is recorded rather than quietly patched. +> +> **What this does NOT settle:** the ceremony for the first federated login of a pre-federation account, which remains the item's hard question and is untouched by any of the above. **A migration cost is an input to that decision, not an answer to it.** + **What would NOT be an honest pass.** Grounding the cell on the (issuer, sub) continuity pair. The scorecard forbids exactly this and explains why: that is 10.5.2's verb ("for the scope of an identity provider" -- within one IdP), and the pair cannot discriminate here at all, because `auth/oidc/claims.py:227` rejects any token whose `iss` differs from the single pinned issuer before the guard runs. Equally not a pass: rescoring `na` because `oidc_enabled` ships off -- the method's 3.7.3 worked example holds that a disabled feature removes the trigger, not the control. **Source:** filed 2026-08-08 from the ASVS ledger-coverage sweep, which found 80 of the 108 partial and fail cells carried no item naming them in `docs/BACKLOG.md`. The scorecard is the record of record for the verdict; this item tracks the research toward changing it. From 398221db52340f95cb5b0c9b849c753a9a466dc7 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 13 Aug 2026 18:44:14 -0500 Subject: [PATCH 09/20] backlog: audit #1020's directory path -- it widens, and it invalidates one candidate fix #1020 said "the AD/OIDC-provisioned path in auth/reconcile.py was not audited, so the finding may narrow to local accounts". Audited now. Wrong file, and it widens rather than narrows. Every link opened individually, because a chain of separately-verified links is not a verified chain: authenticate_oidc auth/service.py:942 -> returns _complete_ad_login :1056 _complete_ad_login auth/service.py:1081 -> calls _upsert_ad_user :1107 _upsert_ad_user auth/service.py:1209 -> update_user_profile(email=principal.email) update_user_profile store/store.py:7742 -> UPDATE users SET display_name=?, email=?, ... Both the AD and the OIDC login paths provision through the SAME function, _upsert_ad_user -- not auth/reconcile.py, which the struck sentence names. authenticate_oidc returns _complete_ad_login directly, so one provisioning path serves two providers. The sharp end is the unconditional write. update_user_profile issues UPDATE users SET display_name=?, email=? with no conditional and no coalesce, and _upsert_ad_user calls it on every directory login with whatever the directory asserted. So a directory-sourced account cannot retain a hand-set address: an operator who sets one via PATCH /users/{id} has it overwritten at the account holder's next login. LDAP mail is optional at every layer, so where the directory asserts nothing the address returns to NULL. That invalidates one of the item's three candidate fixes. "Add a self-service email field" does not reach directory-provisioned accounts at all -- whatever the user sets is overwritten on their next login by the same unconditional write. Any fix gating on "a privileged account must have a deliverable address" needs a separate answer for the directory-sourced population, which moves it into the owner's decision rather than leaving it an implementation detail underneath. Deliberately NOT re-litigated: "Difficulty 3, no schema change, no migration cost" may still hold for the local-account half, and nobody has measured it for the directory half. The amendment says so rather than quietly widening the estimate. Also removed a pre-existing closed-alphabet character from this item's BODY at the old :4111. It was not flipping the item -- the counts were identical before and after -- but the rule is absolute for exactly that reason: position decides whether it parses as a status banner, and four items were mis-parsed by this class today. Replaced with the word. Provenance: the widening was measured by the Builder 2 seat during a blind re-verification pass; the chain above was re-read link by link here before being written into the ledger. Amendment only, no heading added, so ledger ownership is not consulted. parse_items before and after: 281 items / 204 open / 77 closed, unchanged. --- docs/BACKLOG.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 689bfa50..50f0c33e 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -4108,13 +4108,28 @@ Retiring the tree costs the engine nothing operationally: **`tests/test_ech_egre **The gate-blindness angle is the part with teeth.** On a PHI instance under `enforcement=enforce`, `serve` refuses to start without a security-notification channel (`__main__.py:2259-2280`), but `security_channel_ready` is computed purely from `notify_security_events` + `email_smtp_host` + `email_from`. At the moment that gate passes on a first run, the only account that exists has no address — so the gate proves the transport exists, not that any notice is deliverable. -⛔ **Do not write that the notices are lost, and do not rebuild these.** The email push is dropped; every event is also an audit row surfaced by `GET /me/security-events` (`api/auth_routes.py:453`, `auth/service.py:2458`), which `auth/notifications.py:15-17` documents as the companion "so a user with no deliverable mailbox can still review their security history". Also already present: last-admin guards on disable/delete/role-removal (`api/auth_routes.py:681-688`, `:714-715`, `:746-749`), and a settable address on any account (`PATCH /users/{id}` at `:696`, console form at `messagefoundry_webconsole/pages/admin.py:171`). +**DO NOT write that the notices are lost, and do not rebuild these.** The email push is dropped; every event is also an audit row surfaced by `GET /me/security-events` (`api/auth_routes.py:453`, `auth/service.py:2458`), which `auth/notifications.py:15-17` documents as the companion "so a user with no deliverable mailbox can still review their security history". Also already present: last-admin guards on disable/delete/role-removal (`api/auth_routes.py:681-688`, `:714-715`, `:746-749`), and a settable address on any account (`PATCH /users/{id}` at `:696`, console form at `messagefoundry_webconsole/pages/admin.py:171`). **Scope the fix wider than `admin`.** `email` is optional in `UserCreateRequest` (`api/auth_models.py:86`) and is not required for the Administrator role, so any hand-created privileged account has the same hole. A fix that hardcodes an address for the bootstrap account only would leave that open. **Difficulty 3, no schema change, no migration cost.** Candidate fixes are each a handful of lines plus a test: warn at `_emit_bootstrap_admin` or on the forced change-password page; extend the `__main__.py:2259` gate to require a deliverable address on at least one enabled Administrator when notifications are required; or add a self-service email field. Which one the owner wants is the only judgment needed — `auth/notifications.py:55` and `security_notify.py:128-131` explicitly contemplate "no mailbox on file", so "warn/gate for privileged accounts" may be preferable to "require an email". -**Mentioned, deliberately not folded in:** a *sole* claimed Administrator that loses its password genuinely has no reset path, because reset needs a second `users:manage` holder and there is no `users` CLI subcommand. That is independent of email and would be unchanged by adding one. The AD/OIDC-provisioned path in `auth/reconcile.py` was not audited, so the finding may narrow to local accounts. +**Mentioned, deliberately not folded in:** a *sole* claimed Administrator that loses its password genuinely has no reset path, because reset needs a second `users:manage` holder and there is no `users` CLI subcommand. That is independent of email and would be unchanged by adding one. ~~The AD/OIDC-provisioned path in `auth/reconcile.py` was not audited, so the finding may narrow to local accounts.~~ **[AUDITED 2026-08-13 -- see the amendment below. Wrong file, and it WIDENS rather than narrows.]** + +> ⚠️ **AMENDED 2026-08-13 (dispatcher) -- THE DIRECTORY PATH IS AUDITED AND THE FINDING WIDENS. It does not narrow to local accounts, and the file named above is not the one that provisions.** 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 at :1056 +> _complete_ad_login auth/service.py:1081 -> calls _upsert_ad_user at :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 there is one provisioning path serving 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. +> +> ⚠️ **THIS INVALIDATES ONE OF THE THREE CANDIDATE FIXES LISTED ABOVE.** *"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**, and that is now part of the owner's decision rather than an implementation detail under it. +> +> **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 by the dispatcher before being written here. **"Difficulty 3, no schema change, no migration cost" above is NOT re-litigated by this amendment** -- it may still hold for the local-account half, and nobody has measured it for the directory half. **Source:** found during the ASVS V6 re-verification, 2026-08-04. The conclusion is correct and the mechanism was **misstated**: the unlock-path half is false and is corrected above, verified against the code at `6e481c14`. From 30d5358997092907d6f1ba5e4ceace6445800c37 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 13 Aug 2026 21:59:23 -0500 Subject: [PATCH 10/20] backlog: record #1020's owner ruling, and correct the fix location it points at Owner ruled option (b): gate startup on a deliverable channel. Recorded in the item because until now the ruling existed only in session messages, which is the failure mode this session has been correcting all evening -- a decision with no artifact behind it. The deciding argument is recorded as the reason rather than only the choice: (b) is the only option that does not rest on an operator action. That is decisive because update_user_profile issues UPDATE users SET display_name=?, email=? with no conditional and no coalesce, on every directory login (store.py:7742), so any address a human sets on an AD or OIDC account is overwritten at the account holder's next login. A fix depending on someone setting an address cannot cover that population. The item's stated fix location is wrong and a builder would walk into it. The text points at __main__.py:2259, but _serve is synchronous and opens no store -- probing its full range for open_store|AuthService|list_users|count_users returns one hit and it is a comment, and uvicorn.run is at :2827 so the lifespan bootstrap has not run. The only place the store and the fresh bootstrap admin are both in hand is the ASGI lifespan at api/app.py:~5852. Option (c) is recorded as population-limited, NOT defective, and the amendment says the ruling must not be cited as a finding that it was broken. A self-service email field works for local accounts and is silently overwritten for directory ones. The incompleteness was invisible to the operator, which is more useful than "it was wrong" -- an earlier framing of mine that I withdrew. Recorded as unpriced rather than carried forward: whether "Difficulty 3, no schema change, no migration cost" still holds for the directory half. It may hold for the local half; nobody has measured the directory half. A stale difficulty estimate silently sets a lane's expectations. The build is NOT dispatched. The pool is at HOLD NEW WORK / PROTECT AND WRAP, and a builder taking this would be starting a new item and a new claim, which that state prohibits. Recording the ruling is work in hand; building it is not. Amendment only, no heading added, so ledger ownership is not consulted. parse_items before and after: 281 items / 204 open / 77 closed, unchanged. --- docs/BACKLOG.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 50f0c33e..9b53b083 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -4112,7 +4112,17 @@ Retiring the tree costs the engine nothing operationally: **`tests/test_ech_egre **Scope the fix wider than `admin`.** `email` is optional in `UserCreateRequest` (`api/auth_models.py:86`) and is not required for the Administrator role, so any hand-created privileged account has the same hole. A fix that hardcodes an address for the bootstrap account only would leave that open. -**Difficulty 3, no schema change, no migration cost.** Candidate fixes are each a handful of lines plus a test: warn at `_emit_bootstrap_admin` or on the forced change-password page; extend the `__main__.py:2259` gate to require a deliverable address on at least one enabled Administrator when notifications are required; or add a self-service email field. Which one the owner wants is the only judgment needed — `auth/notifications.py:55` and `security_notify.py:128-131` explicitly contemplate "no mailbox on file", so "warn/gate for privileged accounts" may be preferable to "require an email". +**Difficulty 3, no schema change, no migration cost.** Candidate fixes are each a handful of lines plus a test: warn at `_emit_bootstrap_admin` or on the forced change-password page; extend the `__main__.py:2259` gate to require a deliverable address on at least one enabled Administrator when notifications are required; or add a self-service email field. Which one the owner wants is the only judgment needed — `auth/notifications.py:55` and `security_notify.py:128-131` explicitly contemplate "no mailbox on file", so "warn/gate for privileged accounts" may be preferable to "require an email". **[ANSWERED 2026-08-13 — see the ruling below.]** + +> ⚠️ **OWNER RULING 2026-08-13 (recorded by the dispatcher): OPTION (b) — GATE STARTUP ON A DELIVERABLE CHANNEL.** +> +> **THE DECIDING ARGUMENT, recorded as the reason and not merely 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/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 WILL WALK INTO IT.** The text above points at `__main__.py:2259`. **`_serve` is SYNCHRONOUS and opens no store** — probing its full range for `open_store|AuthService|list_users|count_users` returns a single hit and it is a **comment**; `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`.** Build there. +> +> **OPTION (c) WAS POPULATION-LIMITED, NOT DEFECTIVE, and this 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 the more useful thing to carry than "it was wrong" — an earlier framing of mine that I withdrew. +> +> **NOT PRICED BY ANYONE:** whether *"Difficulty 3, no schema change, no migration cost"* still holds for the **directory** half. It may hold for the local half. **Recorded as unpriced rather than carried forward**, because a stale difficulty estimate silently sets a lane's expectations. **Mentioned, deliberately not folded in:** a *sole* claimed Administrator that loses its password genuinely has no reset path, because reset needs a second `users:manage` holder and there is no `users` CLI subcommand. That is independent of email and would be unchanged by adding one. ~~The AD/OIDC-provisioned path in `auth/reconcile.py` was not audited, so the finding may narrow to local accounts.~~ **[AUDITED 2026-08-13 -- see the amendment below. Wrong file, and it WIDENS rather than narrows.]** From 82deac7dfb5d2b7df6e06c90f2254a62c50a59c2 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 13 Aug 2026 22:16:10 -0500 Subject: [PATCH 11/20] backlog: record #1217 half 1 as built, half 2 outstanding -- the item stays open PR #383 is red on "a PR that implements BACKLOG #N must update BACKLOG.md". The builder withheld the banner correctly under the owner's authoring ruling, so the ledger edit is the dispatcher's. This supplies it. Same shape as #1241 on #379, and ADR 0165 records why that is the standing pattern. Half 1, the >=1 floor, is BUILT. Verified on the branch against origin/main rather than taken from the report: origin/main retry_max_attempts: int | None = 100 PR #383 retry_max_attempts: int | None = Field(default=100, ge=1) A configured 0 or negative is now refused at load rather than loading clean and dead-lettering on the FIRST failure -- the delivery check is item.attempts >= max_attempts against a post-increment count (pipeline/wiring_runner.py:5040), so 0 meant give-up-now while reading like "no limit". The floor is on the OPERATOR-FACING setting only, and that is deliberate. RetryPolicy(max_attempts=0) remains a live internal idiom for a permanent no-retry failure: measured across 5 files, including store.mark_failed call sites and asserted by tests at tests/test_batch_completion.py:206-208 and tests/test_postgres_store.py:3109. Constraining the dataclass instead would have deleted a used mechanism while claiming to add a guard -- the reads-as-hardening-but-removes-a-control shape. The item's stated reason for deferring the floor is answered rather than ignored. It said the floor was documented and not fixed "because a floor changes the accepted-configuration set". Under section 0 there are zero deployments, so there is no accepted configuration to break and no migration cost to protect. STILL OPEN, and it is why the item does not close: whether the retry-forever posture needs a TOML or env spelling. "", none and null all raise ValidationError, so that posture is reachable in code-first configuration only. It is a product question, it was handed back rather than decided, and the item itself says it should be decided alongside the floor. A closure on #383 would answer it by omission. Amendment only, no heading added, so ledger ownership is not consulted. parse_items before and after: 281 items / 204 open / 77 closed, unchanged, and #1217 verified still OPEN after the edit. --- docs/BACKLOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 9b53b083..cb12c955 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -7980,6 +7980,21 @@ gate is the wrong shape, validation of the walk is the right one. > Both are conditional on a first deployment: an operator who wrote `retry_max_attempts = 0` intending "unlimited" would get single-attempt dead-lettering, silently. Nothing is misconfigured today; there are zero deployments. Note the interaction with **#1051**, which set the finite default to 100 -- a floor should be decided alongside whether retry-forever needs a config spelling at all. +> ⚠️ **AMENDED 2026-08-13 (dispatcher) -- HALF 1 BUILT on PR #383, HALF 2 OUTSTANDING, ITEM STAYS OPEN. Do not close this on #383.** Banner authored by the dispatcher rather than the builder, per the owner's 2026-08-13 ruling that a builder may not author ledger content. +> +> **HALF 1, THE `>=1` FLOOR: BUILT.** Verified on the branch against `origin/main`: +> ``` +> 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, it is used across **5 files** including `store.mark_failed` call sites and asserted by tests (`tests/test_batch_completion.py:206-208`, `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, not ignored.** It said the floor was documented rather than 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** -- the simple correct end state wins outright. +> +> **STILL OPEN, AND IT IS WHY THIS 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**. **That 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 silently answer it by omission. + **Cluster:** Connections and Transports. **Priority:** P3. **Verdict:** build. **Severity:** minor. From 82c6af1f45b9c079eb2d5aed386d85cf75f3a82a Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 13 Aug 2026 22:54:44 -0500 Subject: [PATCH 12/20] backlog: #1242's loss is irreversible, not merely expensive -- and a relay's framing corrected The ASVS Tracker surfaced this defect via the Liaison as a candidate for a NEW item. It is not new: it is #1242, filed 2026-08-13, and Builder 2 is building it. No number was allocated. A duplicate ledger row is not a harmless extra line -- it splits the work and the second number looks unbuilt forever, because the fix lands under the first. Two things in the relay were genuinely new. One is recorded here; the other is deliberately not. RECORDED: the loss is not recoverable by re-running the derivation. The anchors most at risk are the D3 backfill's, and their warrant was that two independent derivations AGREED, measured at two different refs. Those refs have moved. So a fresh derivation reproduces values without reproducing the agreement that justified writing them, and that agreement is the whole evidentiary content. The loss is therefore irreversible rather than expensive, which is why this item outranks other writer defects rather than being one among them. Nothing in the item said this. NOT RECORDED, deliberately: the supporting anchor counts. docs/BACKLOG.md is public, and a tally over a closed public requirement set is the shape that hands out coverage by subtraction. The mechanism is fully stated without them -- a reader with vault access can price it, and a reader without one still knows what to fix and why. Verified my added lines carry no such figure, with a positive control proving the scan discriminates. AND THE RELAY'S FRAMING IS CORRECTED BY THE ITEM'S OWN TEXT. It described the defect as dropping sym/ctx. #1242 already forbids fixing it that way: the defect is the handling of UNKNOWN keys, and a fix special-casing those two by name rebuilds the same trap for the next field added. The item was ahead of the relay and a builder must follow the item. The Tracker's core claim was verified here rather than taken: sym and ctx each return 0 occurrences in scripts/asvs/apply.py at origin/main, positive control expect returns 2. Amendment only, no heading added, so ledger ownership is not consulted. parse_items before and after: 281 items / 204 open / 77 closed, unchanged, and #1242 verified still OPEN. --- docs/BACKLOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index cb12c955..178daac4 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -8581,6 +8581,12 @@ _FHIR_ID_RE.fullmatch("abc\n") -> False the fix > **How to prove a fix:** put a key the writer has never heard of inside an evidence entry, re-render, and assert **both** that it survives **and** that the guard REFUSES when it is deliberately dropped -- the second half is the real test, since a green from a guard that cannot see the failure class is worth nothing (`docs/Secure_Development_Standards.md` SDS-3.8). Cover all four rows: a payload-only new field, a live top-level table, and an absence-entry key, not just `sym`/`ctx`. A fix that special-cases `sym` and `ctx` by name **fails** -- the defect is the handling of *unknown* keys, and naming these two rebuilds the same trap for the next field added. Match entries by `(path, expect)` when comparing, not by index or count. +> ⚠️ **AMENDED 2026-08-14 (dispatcher) -- THE LOSS IS NOT RECOVERABLE BY RE-RUNNING THE DERIVATION, and nothing in this item said so.** 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 since moved. **So "re-derive them" is not a remedy** -- a fresh derivation reproduces values without reproducing the AGREEMENT that justified writing them, and that agreement is the whole evidentiary content. **This makes the loss irreversible rather than merely expensive**, which is the reason this item outranks other writer defects rather than being one among them. +> +> **The supporting counts live in the vault and are DELIBERATELY NOT REPEATED HERE.** `docs/BACKLOG.md` is public, and an anchor 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. +> +> **RELAYED FROM THE ASVS TRACKER, WITH ITS FRAMING CORRECTED BY THIS ITEM'S OWN TEXT.** Its core claim verified here independently -- `sym` and `ctx` each return **0** occurrences in `scripts/asvs/apply.py` at `origin/main`, positive control `expect` returns **2**. **But it described the defect as dropping `sym`/`ctx`, and the paragraph above already forbids fixing it that way:** the defect is the handling of **unknown** keys, and naming those two rebuilds the trap for the next field added. **The item was ahead of the relay; a builder must follow the item.** + > ⚠️ **Amendment, same day, recording what the adversarial pass corrected.** The item was first written as "latent, and a clean scalar/sub-table asymmetry." Both halves were wrong. It is **active**, because the measurement was taken one commit before the backfill landed and the parent revision genuinely has zero `sym` keys -- a stale checkout reproduces the wrong answer perfectly and reads as confirmation. And the asymmetry is four-way, not two-way; the clean framing concealed the table-mangling and the payload-only drop entirely. A fifth limb about line-ending rewriting was **investigated and rejected**: the tool does rewrite the working tree to LF, but `core.autocrlf=true` normalizes it back in the index, so the blob is unchanged and the claimed review-diff consequence does not occur. **Cluster:** Security tooling / evidence integrity. **Priority:** P1. **Verdict:** build. **Severity:** no deployment axis -- vault tooling, ships to nobody. P1 rather than P2 because the loss is **pending on the next routine operation**, is **silent in both directions** (the writer reports success, the verifier reports green having checked less), and destroys evidence that cost a dedicated backfill to produce. From 4da87a7f79ded2017e84ed935bdac51fdc39dbb2 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 13 Aug 2026 23:15:48 -0500 Subject: [PATCH 13/20] backlog: close #1238 and #1239 on the code, and file #1253 so the closure does not retire the hazard Builder 2 reported four banners owed. Two of the four -- #1237 and #1204 -- were already authored on this branch and are invisible to that lane because ledger authorship and push are held by different seats. That is recorded as a finding in the episode note; it is not fixed here. The two genuinely owed are written now. Both closures verified against origin/main rather than against the build report, because a banner that closes on a report inherits the report's errors. #1238: _is_contained_name is defined at transports/remotefile.py:97, wired at :954, and asserted in BOTH polarities at tests/test_remotefile_transport.py:1185 and :1195. The one-polarity case is called out because such a test passes against a function that refuses everything. posixpath.basename() was not used, per the owner ruling. #1239: _has_control_char returns 0 occurrences across messagefoundry/ at origin/main, so the pair it named is a single. The item's own condition is met. #1253 exists because closing #1239 there would have been true of the item as written and false of the hazard it describes. The reporting lane amended its own closure recommendation to say so -- the predicate is copied more widely today than when #1239 was filed, partly by the work that resolved it. A repo-wide re-measure widened that further: the amendment scanned transports/ and found five sites across four files; across messagefoundry/ it is seven across six, the two extra being config/codeset_edit.py:305 and config/impact.py:631. Two exclusions are recorded in the item so a later scan does not re-add them: rest.py:109 matches a naive grep but is prose in a docstring, and sniff.py:179 tests the same code points through a genuinely different byte-wise predicate that subtracts an allowlist, so folding it in would change its behaviour. rest.py:111 strips where the others reject. That is recorded as defensible and NOT as a second instance of the pattern the owner ruled against in #1238, so the next reader does not inherit a false lead: stripping CR/LF from a header value cannot redirect a request, whereas basename() mutates a path into a real and different target. #1253 allocated with alloc.ps1, never grepped. parse_items before and after: 281/204/77 -> 282/203/79, matching the predicted delta for two closures plus one filing, with a control confirming no item carries a stray banner. --- docs/BACKLOG.md | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 178daac4..bf60f049 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -8389,7 +8389,9 @@ every worker session's handoff, which is the sentence the next session bases its ## 1238. contain the server-supplied remote listing name by rejecting it, never by rewriting it -> 🔢 **Filed 2026-08-13 - not started. OWNER RULING RECORDED 2026-08-13: reject, never mutate.** Value **7/10** · Difficulty **3/10**. The BUILD half of **#1130** (ASVS 5.3.2, which holds at `partial`). `RemoteFileSource` uses a filename chosen by a remote SFTP/FTP server with no containment check. **The obvious fix -- `posixpath.basename()` -- is actively harmful and must not be used;** the reasons are recorded below because a later reader will otherwise re-propose it. +> ✅ **CLOSED 2026-08-14 - BUILT. OWNER RULING HELD: reject, never mutate.** Value **7/10** · Difficulty **3/10**. The BUILD half of **#1130** (ASVS 5.3.2, which holds at `partial`). `RemoteFileSource` used a filename chosen by a remote SFTP/FTP server with no containment check. **The obvious fix -- `posixpath.basename()` -- is actively harmful and was not used;** the reasons are retained below because a later reader will otherwise re-propose it. +> +> **Verified against `origin/main` rather than against the build report,** because a banner that closes on a report inherits the report's errors: `_is_contained_name` is defined at `transports/remotefile.py:97`, **wired** at `:954` (`if not _is_contained_name(name)`), and asserted in **both** polarities at `tests/test_remotefile_transport.py:1185` (rejects) and `:1195` (accepts). A one-polarity test would pass against a function that refuses everything. **Cluster:** Security / hardening. **Priority:** P1. **Verdict:** build. **Severity:** Conditional -- there are zero deployments (§0). On a first deployment, a malicious or compromised partner file server could return a listing entry that escapes the configured directory. It needs a hostile server rather than mere drop-directory write access, which bounds it without removing it. @@ -8416,7 +8418,9 @@ every worker session's handoff, which is the sentence the next session bases its ## 1239. `fhir.py` carries two identical control-char predicates behind different wrappers -- a latent drift, not a current divergence -> 🔢 **Filed 2026-08-13 - not started. READ THE FRAMING BEFORE ACTING: the two predicates are byte-identical TODAY. This item records a LATENT hazard, not a live defect, and it must not be cited as evidence of a current gap.** Value **3/10** · Difficulty **2/10**. Noticed during #1107's ASVS 1.2.2 surface enumeration. +> ✅ **CLOSED 2026-08-14 - the item's stated resolution condition is met.** `_has_control_char` returns **0** occurrences across `messagefoundry/` at `origin/main`, so the `fhir.py` pair this item named is now a single. Value **3/10** · Difficulty **2/10**. Noticed during #1107's ASVS 1.2.2 surface enumeration. The framing it was filed under still holds and still applies: the predicates were byte-identical, this recorded a LATENT hazard, and it must not be cited as evidence of a current gap. +> +> **THIS CLOSURE DOES NOT RETIRE THE DRIFT HAZARD, and reading it that way would invert the item.** Closing on *"the pair is now a single"* is true of the item **as written** and false of the hazard it **describes**: the same predicate is copied more widely today than when this was filed, partly by the work that resolved it. The remaining sites are measured and carried forward as **#1253**. Closing here without that item would have retired a finding whose subject had grown -- which is why the successor is named in the banner rather than left to a reader to notice. **Cluster:** Code quality / drift hazard. **Priority:** P3. **Verdict:** build (small). **Severity:** none today. There is no behavioural difference to exploit and nothing is mis-screened. The cost is future-tense and conditional: a later hardening applied to one predicate would silently not apply to the other. @@ -8817,3 +8821,32 @@ _FHIR_ID_RE.fullmatch("abc\n") -> False the fix > records a decision I acted against rather than a conclusion I verified. **Cluster:** Security record / repository topology. **Priority:** P2. **Verdict:** build. **Severity:** no deployment axis -- nothing shipped changes; the exposure is that a ranked map of unmet security requirements **is** public today and cannot be made unpublic, only stopped from growing. + +## 1253. one control-char predicate written out seven times across six files + +> 🔢 **Filed 2026-08-14 - not started. SUCCESSOR to #1239, which closed on its own narrower condition.** Value **4/10** · Difficulty **3/10**. `#1239` recorded two byte-identical control-char predicates in `fhir.py` and was resolved by removing one of that pair. The predicate is written out **seven times across six files**, so resolving the pair addressed two of seven sites and the drift surface is **wider today than when #1239 was filed** -- partly because the work that resolved it added sites. + +> **THIS IS A LATENT HAZARD, NOT A LIVE DEFECT, and it must not be cited as evidence of a current gap.** All six rejecting copies are consistent today; nothing is mis-screened and there is no behavioural difference to exploit. The cost is future-tense and conditional, and it is the same one #1239 named: a later hardening applied to one copy silently does not apply to the other six, and nothing reports the omission. + +**Cluster:** Code quality / drift hazard. **Priority:** P3. **Verdict:** build (small). +**Severity:** none today, and no deployment axis (§0). + +**What is there.** Measured at `origin/main` across `messagefoundry/`, the expression `ord(ch) < 0x20 or ord(ch) == 0x7F`: + +| site | action | +| --- | --- | +| `config/codeset_edit.py:305` | reject | +| `config/impact.py:631` | reject | +| `transports/dicomweb.py:96` | reject | +| `transports/fhir.py:178` | reject | +| `transports/fhir.py:201` | reject | +| `transports/remotefile.py:121` | reject (#1238) | +| `transports/rest.py:111` | strip | + +**Two exclusions, recorded so a later scan does not re-add them as sites.** `transports/rest.py:109` matches a naive grep but is **prose inside a docstring**, not a site. `parsing/sniff.py:179` tests the same code points but is a **genuinely different predicate** -- it is byte-wise rather than character-wise and subtracts an allowlist (`_TEXT_ALLOWED_CONTROLS`, `:151`) because a text sniffer must tolerate tab, CR and LF. Folding it into a shared helper would change its behaviour, so it is deliberately out of scope. + +**`rest.py:111` STRIPS rather than rejects, and that is defensible rather than a second instance of the pattern the owner ruled against in #1238.** It is recorded here because the difference looks like a defect at a glance and would otherwise be re-reported. #1238's argument against mutation is that `basename()` turns a path into a valid-but-**different** target, handing an attacker a real file. A header **value** has no such property: stripping CR/LF cannot redirect a request anywhere. `rest.py` already **rejects** a header *name* failing its RFC 7230 token check, so the split is name-rejected / value-stripped, which is principled. A fix that unifies the seven sites must therefore preserve two distinct actions and must not "simplify" the strip into a reject. + +**Why the count is stated as measured rather than as an enumeration.** The seven-site figure is a `git grep` result at a named ref, not a claim that no eighth spelling exists; a differently-spelled equivalent (a regex, a `str.translate`, a range test) would not match and is not counted. The item's subject is the *copying practice*, so a fix should make the shared helper the obvious thing to reach for, not merely rewrite the seven known lines. + +**Provenance.** The `transports/` half was measured and reported by the lane that had just widened it, as an amendment **against its own** closure recommendation for #1239. The `config/` sites and the two exclusions were added by a repo-wide re-measure during that closure. From 2d68dabab530a29cfe2a1ba53c012c79be5af33e Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 13 Aug 2026 23:20:33 -0500 Subject: [PATCH 14/20] backlog: record that #1234 is not startable from main -- its subject has not landed Builder 2 refused a restock offer of #1234 by correcting its OWN earlier recommendation, and verified before claiming rather than after. Re-measured here rather than taken: at origin/main, require_least_privilege returns 0 hits in any .py and appears only in this ledger's own prose. Positive control on the same instrument, require_managed_identity, returns hits across four .py files, so the scan sees Python fine -- the zero is a fact about the tree, not a broken needle. The probe this item reports a defect in exists only on the w3-store-privilege-preflight branch, dormant at that reading. The amendment is careful not to overrule the item's own "independent of #1008" paragraph, because that paragraph is right about a different thing. Both halves hold: the item is not hostage to #1008's POLICY ruling, and it is nonetheless unbuildable by any lane working from main until that BRANCH lands. Collapsing the two would either re-gate a code defect behind a demand gate or keep offering work whose subject does not exist. This is the same wasted-claim cost as #1253's provenance, one layer deeper: there, an item was unstartable because the FIX had already landed; here, because the SUBJECT has not. A banner-driven queue cannot distinguish either case from startable work, which is why both are now written down where the next dispatcher reads rather than left in session mail. Amendment only, no heading added, so ledger ownership is not consulted. parse_items before and after: 282 items / 203 open / 79 closed, unchanged, and #1234 verified still OPEN. --- docs/BACKLOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index bf60f049..fd42f243 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -8317,6 +8317,8 @@ every worker session's handoff, which is the sentence the next session bases its > **Independent of BACKLOG #1008 and deliberately filed separately.** #1008 is DEMAND-GATE on whether the engine should perform a startup privilege preflight **at all**; this item is that a written probe mis-reports. A code defect should not be hostage to a policy decision, and if #1008 is never lifted this still wants fixing before that branch is reused for anything. +> **NOT STARTABLE FROM `main`, and this does NOT contradict the paragraph above.** Measured 2026-08-14 at `origin/main`: `require_least_privilege` returns **0** hits in **any** `.py`, appearing 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 probe this item reports a defect in therefore **has not landed** -- it exists only on the `w3-store-privilege-preflight` worktree's branch, which was DORMANT at that reading. The distinction matters and both halves are true: 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. A dispatcher offering this from a banner-driven queue hands a builder work whose subject does not exist, which is the same wasted-claim cost recorded at #1253's provenance, one layer deeper -- unstartable because the SUBJECT has not landed rather than because the FIX already did. + **Cluster:** Security / observability. **Priority:** P2. **Verdict:** build. **Severity:** conditional per CLAUDE.md section 0 -- on a first deployment an over-granted store principal would be reported as observed-and-clean; **zero deployments, so nothing is mis-reported today.** ## 1235. a citation to an unallocated backlog number is a trap that arms itself the day the number is issued From 383c16894d978409941331e02c83653df08f107b Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 13 Aug 2026 23:33:58 -0500 Subject: [PATCH 15/20] backlog: file #1254 -- a required check is named for its subject, not its assertion Handed to me by the Liaison to number if I judged it worth one, in the Lander's framing. It is, because the naive fix is dangerous and nothing currently records that. MEASURED INSTANCE: the Windows leg went red under the label "test (windows-2025, py3.14)", which reads as "the tests failed on Windows". The tests passed. What failed was the wall-clock gate "Step margin -- both gated steps" (ci.yml:675). The check answered its own question truthfully and the NAME described a different one -- the reverse of the shape this project keeps hitting, where the label is honest and the instrument is not. The job name is built at ci.yml:42 from the matrix, so all three legs are named for WHERE they ran and never for WHAT they assert, while holding at least three independent assertions. Stated as at least three rather than enumerated. WHY THIS IS NOT A ONE-LINE RENAME, which is the whole reason it needed writing down: those three strings ARE required contexts. They are listed in .github/required-contexts.txt, asserted against branch protection by tests/test_required_contexts.py, and matched BY NAME on the GitHub side. A required-but-absent context blocks every PR forever, so a rename is one atomic change across the workflow, the contexts file, that test's pinned count, and the branch-protection setting, in the order that file's header prescribes. So the item deliberately does NOT recommend the rename. It prices three options and names the cheapest first: make the margin gate's failure output say in its first line that the suite passed and a timing gate fired. That costs nothing and cannot wedge the repo. The rename is listed third. Severity carries no deployment axis, but the near-miss is recorded: the misreading pointed at the wall-clock cap, and #1096's banner already says the actual fix is #320 and that re-deriving the caps is itself the failure mode. #1254 allocated with alloc.ps1, never grepped. parse_items before and after: 282/203/79 -> 283/204/79, matching the predicted delta for one filing, with a control confirming no item carries a stray banner. --- docs/BACKLOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index fd42f243..56acdc55 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -8852,3 +8852,20 @@ _FHIR_ID_RE.fullmatch("abc\n") -> False the fix **Why the count is stated as measured rather than as an enumeration.** The seven-site figure is a `git grep` result at a named ref, not a claim that no eighth spelling exists; a differently-spelled equivalent (a regex, a `str.translate`, a range test) would not match and is not counted. The item's subject is the *copying practice*, so a fix should make the shared helper the obvious thing to reach for, not merely rewrite the seven known lines. **Provenance.** The `transports/` half was measured and reported by the lane that had just widened it, as an amendment **against its own** closure recommendation for #1239. The `config/` sites and the two exclusions were added by a repo-wide re-measure during that closure. + +## 1254. a required check is named for its subject, not its assertion, so a passing suite reds under a label that says the tests failed + +> 🔢 **Filed 2026-08-14 - not started. THE INSTRUMENT IS CORRECT AND THE LABEL LIES; this is the reverse of the usual shape here.** Value **5/10** · Difficulty **4/10**. `ci.yml:42` names the job `test (${{ matrix.os }}, py${{ matrix.python-version }})`, so the required contexts render as `test (ubuntu-latest, py3.14)`, `test (windows-2022, py3.14)` and `test (windows-2025, py3.14)` -- **named for WHERE they ran, never for WHAT they assert.** + +> **MEASURED INSTANCE, 2026-08-13.** The Windows leg went red. The label said `test (windows-2025, py3.14)`, which reads as *the tests failed on Windows*. **The tests passed.** What failed was `Step margin -- both gated steps` (`ci.yml:675`, `scripts/ci/step_margin.py`), a wall-clock gate reporting a step at 1.262x against a 1.30x floor. A careful seat read the label, formed the wrong hypothesis, and corrected itself only by opening the log. The check answered its own question truthfully; the name described a different question. + +> **THE JOB HOLDS AT LEAST THREE INDEPENDENT ASSERTIONS** -- install/lint/type, the pytest run itself, and the margin gate -- and **all of them red under the one word `test`.** The failure mode is not that the name is vague: it is that the name asserts something specific and WRONG, so it is believed. A vague label invites a look at the log; a confident wrong one closes the question. Stated as *at least* three rather than as an enumeration. + +**Cluster:** CI / diagnostics. **Priority:** P3. **Verdict:** build. +**Severity:** no deployment axis (§0) -- nothing shipped changes. The cost is diagnostic time and, in the measured instance, a near-miss on a wrong remedy: the misreading pointed at the wall-clock cap, and #1096's banner already records that *"the actual fix is #320, not a larger integer, and re-deriving weekly is the failure mode to avoid."* + +**THE OBVIOUS FIX IS DANGEROUS AND MUST NOT BE APPLIED CASUALLY.** Renaming the job looks like a one-line edit and is not. 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**, and the rename is therefore a single atomic change across the workflow, the contexts file, the pinned count in that test, and the branch-protection setting -- in the order that file's own header prescribes. A rename that lands in the wrong order wedges the repo. + +**Scope, and a cheaper alternative to price first.** The name may not be the right lever at all, precisely because it is load-bearing for branch protection. The step names are already honest (`Step margin -- start the clock`, `Tests (pytest)`, `Step margin -- boundary between the two gated steps`), so the information exists and is one click away. Options worth comparing before any rename: (a) leave the context name alone and make the margin gate's **failure output** state in its first line that the suite passed and a timing gate fired; (b) split the margin gate into its own non-required check so it reds under its own name; (c) the atomic rename above. **(a) is the only one that costs nothing and cannot wedge the repo,** and it should be measured against the others rather than assumed inferior. + +**Provenance.** Surfaced by the seat that misread the label and caught itself, relayed with the framing *a check named for its SUBJECT rather than its ASSERTION*, and filed here rather than left in session mail so the next reader of a red Windows leg is not sent to the cap. From fd23d5572f9fb14c4e173bebe3bb5d4098c861d1 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 13 Aug 2026 23:37:16 -0500 Subject: [PATCH 16/20] backlog: amend #1235 -- its two named instances are inert, and I dispatched the opposite Builder 2 refused the starting fact I gave it and measured instead. It was right and I was backwards. I told it to start #1235 from #1203 as a CONFIRMED LIVE TRAP, reasoning that an allocation record with no ledger entry meant the number was free. The record is what makes the number permanently UNAVAILABLE. Verified here rather than taken: 1203.json and 1231.json both exist in .git/mefor-coord/alloc/backlog/ (claimed 2026-08-09 and 2026-08-12), and alloc.ps1 has no release at all -- :41 "a one-way door -- claims are never released", :24 "numbers are never reclaimed ... holes are free, collisions are not". So the item's own text is wrong where it says #1231 was "allocated and released without being filed", and wrong that the pair is "defused only by an accident of timing". They are defused by construction. ONE CORRECTION AGAINST THE REPORT AS WELL, because its reason is weaker than its conclusion. The argument as relayed rests on the allocation RECORDS existing. Those live under .git: uncommittable, machine-local, losable without trace. The reason that survives their loss is structural -- alloc.ps1 issues $observed + 1 (:392, and :389 under the public floor clamp) and NEVER fills a hole, so a number below the floor is unreachable whether or not its record still exists. Recording the registry as the protection would make a sound property look fragile and invite a guard nothing needs. The live shape is the other one and the item now says so: a citation to a number NEVER allocated sits above the floor and will be issued in the normal course. A detector reading only the ledgers rates the two states identically, which over docs/ in this repo mis-scores 26 reserved citations as live; of the 6 genuinely never-allocated tokens there, all six are foreign references, so this repo holds zero genuine instances. The private-repo population the item was filed against is not re-measured here and is stated as separate. The remedy is unchanged and still correct. Only the account of WHY the two named instances are harmless is corrected. Amendment only, no heading added, so ledger ownership is not consulted. parse_items before and after: 283 items / 204 open / 79 closed, unchanged, and #1235 verified still OPEN. --- docs/BACKLOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 56acdc55..ea656b42 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -8333,6 +8333,14 @@ every worker session's handoff, which is the sentence the next session bases its > **Scope:** a rule, not a sweep. **Either allocate the number before citing it, or write the reference so it CANNOT resolve** -- naming the subject instead of a number (*"the retention runbook step, unallocated"*) is enough, and costs nothing. The sweep of existing instances is a separate, owner-present task in that repository, deliberately not folded in here. +> **AMENDED 2026-08-14 -- THE TWO NAMED INSTANCES ARE NOT LIVE, AND THE ITEM'S MECHANISM WAS WRONG ABOUT WHY.** Both `#1203` and `#1231` hold **permanent allocation records** (`.git/mefor-coord/alloc/backlog/`, claimed 2026-08-09 and 2026-08-12). The claim above that `#1231` was *"allocated and released without being filed"* is incorrect: **`alloc.ps1` has no release.** `:41` states allocation is *"a one-way door -- claims are never released"*, and `:24` that *"numbers are never reclaimed ... holes are free, collisions are not"*. So neither number can be re-issued, neither citation can ever begin resolving to unrelated work, and both resolve to nothing **permanently** -- the state this item itself calls honest and harmless. **They are defused by construction, not "by an accident of timing".** + +> **THE DURABLE REASON IS STRUCTURAL, NOT THE REGISTRY -- and this distinction is the load-bearing part.** It would be natural to conclude the allocation *records* are what protect these numbers. They are not: the registry lives under `.git`, is uncommittable and machine-local, and could be lost without trace. The protection that survives that is **`alloc.ps1` only ever issues above the floor and never fills a hole** -- `$start = $observed + 1` (`:392`, and `:389` under the public floor clamp). The floor is already far above both numbers, so a hole below it is unreachable **whether or not its record still exists.** Reasoning from the registry would make this look fragile and invite a guard nothing needs. + +> **THE LIVE SHAPE IS THE OTHER ONE, and the item should be read as being about that:** a citation to a number **never allocated at all**. That sits *above* the floor and will be issued in the normal course, which is exactly when a stale citation starts resolving to unrelated work. A reserved-but-unfiled number is permanently inert; an unreserved one is the trap. **A detector that checks only the ledgers rates the two states identically** -- measured over `docs/` in this repository, that mis-scores **26** reserved citations as live. Of the **6** genuinely never-allocated tokens found there, all six are **foreign** references (an upstream driver issue, a vendor forum thread, another project's issue), so this repository currently holds **zero** genuine instances. The private-repo instances the item was filed against are a separate population and are not re-measured here. + +> **PROVENANCE, including a wrong dispatch of mine.** I sent a builder to start this item from `#1203` **as a confirmed live trap**, on my own earlier reading that an allocation record with no ledger entry meant the number was free. That reading was backwards -- the record is what makes it permanently *unavailable*. The builder verified before building, refused the premise, and measured the above. **The item's remedy is unchanged and still correct**; only its account of why the two named instances were harmless is corrected. + > **What makes this filable rather than a style note:** there is **no gate on either side**. The allocator answers *"is this number free"*; nothing asks *"is anything already pointing at it."* And a citation in another repository is invisible to every check this one runs. The two halves are each individually correct and the gap between them is the defect. **Cluster:** Process / ledger integrity. **Priority:** P3. **Verdict:** build (the rule). **Severity:** minor and self-inflicted -- it corrupts cross-references between maintainer documents, touches no shipped code, and no deployment can observe it. From 714fccc105edb285a7cb8e2d183d147f75ea67cf Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 13 Aug 2026 23:59:55 -0500 Subject: [PATCH 17/20] backlog: correct #1235 and #1254 -- two of my own committed claims, falsified under adversarial check I ran six independent skeptics over every claim I committed tonight, against the SIMULATED POST-MERGE TREE rather than my branch, because my branch was six behind origin/main and the merge is CLEAN -- git conflicts on concurrent edits, never on invalidated claims. Four claims held. Two did not. Both failures are my authoring, not merge drift: all cited files are byte-identical across the merge tree, origin/main and HEAD. #1235. The conclusion survives, the reason did not. I had written that the allocation registry is irrelevant because the mechanism is structural. That is FALSE for the newest number: the high-water ratchet persists $floor, the maximum of the OBSERVED set (:205, :214, :215), NOT the number being issued, so after issuing N it holds N-1. alloc.ps1 only PRINTS the heading, so until it is committed the sole durable record of N is its own untracked, never-pushed .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. --- docs/BACKLOG.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index ea656b42..02ce2ea7 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -8335,7 +8335,11 @@ every worker session's handoff, which is the sentence the next session bases its > **AMENDED 2026-08-14 -- THE TWO NAMED INSTANCES ARE NOT LIVE, AND THE ITEM'S MECHANISM WAS WRONG ABOUT WHY.** Both `#1203` and `#1231` hold **permanent allocation records** (`.git/mefor-coord/alloc/backlog/`, claimed 2026-08-09 and 2026-08-12). The claim above that `#1231` was *"allocated and released without being filed"* is incorrect: **`alloc.ps1` has no release.** `:41` states allocation is *"a one-way door -- claims are never released"*, and `:24` that *"numbers are never reclaimed ... holes are free, collisions are not"*. So neither number can be re-issued, neither citation can ever begin resolving to unrelated work, and both resolve to nothing **permanently** -- the state this item itself calls honest and harmless. **They are defused by construction, not "by an accident of timing".** -> **THE DURABLE REASON IS STRUCTURAL, NOT THE REGISTRY -- and this distinction is the load-bearing part.** It would be natural to conclude the allocation *records* are what protect these numbers. They are not: the registry lives under `.git`, is uncommittable and machine-local, and could be lost without trace. The protection that survives that is **`alloc.ps1` only ever issues above the floor and never fills a hole** -- `$start = $observed + 1` (`:392`, and `:389` under the public floor clamp). The floor is already far above both numbers, so a hole below it is unreachable **whether or not its record still exists.** Reasoning from the registry would make this look fragile and invite a guard nothing needs. +> **WHY THEY ARE UNREACHABLE, stated as the conjunction it actually is.** Two independent facts are both required, and an earlier version of this amendment asserted only the first and over-claimed from it. **(1)** `alloc.ps1` never searches downward: `$start = $observed + 1` (`:392`, `:389` under the public-floor clamp) and the issuing loop only increments (`:394`), so no run fills a hole. **(2)** The floor is computed from the **committed ledger headings** -- measured non-destructively with `alloc.ps1 -ShowFloor`, which reports `floor: 1254` swept from `docs/BACKLOG.md` and `docs/archive/backlog/BACKLOG-CLOSED.md`. Those headings are **tracked content on refs**, so they survive a fresh clone, and both `#1203` and `#1231` sit far below them. That is what makes those two permanently unreachable, and it holds whether or not their allocation records survive. + +> **THE GUARANTEE IS WEAKEST AT THE TOP OF THE RANGE, and that correction is recorded because a previous version of this text denied it.** I wrote that the 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 rather than writing it, so until that heading is committed, the sole durable record of N is its own `.json` under the **untracked, never-pushed** `mefor-coord/alloc/`. Lose that file and the next run recomputes a floor of N-1 and 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 they were allocated. They are safe *now* only because the ledger has since moved far past them. + +> **The clamp is a separate guarantee and should not be conflated with either.** `:389` forces every backlog number to at least the public floor, and `:317-319` refuses to allocate at all if that constant cannot be parsed out of `scripts/hooks/ledger_check.py`. That bounds the output *range*; it says nothing about re-issue. Noted as a live caveat: the ratchet guarding that constant against being lowered keeps its state in `.boundary-highwater` **in the same untracked directory** as the claim records, so a registry-absent clone reports a boundary high-water of zero and that particular refusal is disarmed. > **THE LIVE SHAPE IS THE OTHER ONE, and the item should be read as being about that:** a citation to a number **never allocated at all**. That sits *above* the floor and will be issued in the normal course, which is exactly when a stale citation starts resolving to unrelated work. A reserved-but-unfiled number is permanently inert; an unreserved one is the trap. **A detector that checks only the ledgers rates the two states identically** -- measured over `docs/` in this repository, that mis-scores **26** reserved citations as live. Of the **6** genuinely never-allocated tokens found there, all six are **foreign** references (an upstream driver issue, a vendor forum thread, another project's issue), so this repository currently holds **zero** genuine instances. The private-repo instances the item was filed against are a separate population and are not re-measured here. @@ -8865,15 +8869,17 @@ _FHIR_ID_RE.fullmatch("abc\n") -> False the fix > 🔢 **Filed 2026-08-14 - not started. THE INSTRUMENT IS CORRECT AND THE LABEL LIES; this is the reverse of the usual shape here.** Value **5/10** · Difficulty **4/10**. `ci.yml:42` names the job `test (${{ matrix.os }}, py${{ matrix.python-version }})`, so the required contexts render as `test (ubuntu-latest, py3.14)`, `test (windows-2022, py3.14)` and `test (windows-2025, py3.14)` -- **named for WHERE they ran, never for WHAT they assert.** -> **MEASURED INSTANCE, 2026-08-13.** The Windows leg went red. The label said `test (windows-2025, py3.14)`, which reads as *the tests failed on Windows*. **The tests passed.** What failed was `Step margin -- both gated steps` (`ci.yml:675`, `scripts/ci/step_margin.py`), a wall-clock gate reporting a step at 1.262x against a 1.30x floor. A careful seat read the label, formed the wrong hypothesis, and corrected itself only by opening the log. The check answered its own question truthfully; the name described a different question. +> **MEASURED INSTANCE, 2026-08-13.** The Windows leg went red. The label said `test (windows-2025, py3.14)`, which reads as *the tests failed on Windows*. **The tests passed.** What failed was `Step margin -- both gated steps` (**`ci.yml:765`**, `if: always()`, invoking `scripts/ci/step_margin.py` at `:778` and `:781`), a wall-clock gate reporting a step at 1.262x against a 1.30x floor. A careful seat read the label, formed the wrong hypothesis, and corrected itself only by opening the log. The check answered its own question truthfully; the name described a different question. > **THE JOB HOLDS AT LEAST THREE INDEPENDENT ASSERTIONS** -- install/lint/type, the pytest run itself, and the margin gate -- and **all of them red under the one word `test`.** The failure mode is not that the name is vague: it is that the name asserts something specific and WRONG, so it is believed. A vague label invites a look at the log; a confident wrong one closes the question. Stated as *at least* three rather than as an enumeration. **Cluster:** CI / diagnostics. **Priority:** P3. **Verdict:** build. **Severity:** no deployment axis (§0) -- nothing shipped changes. The cost is diagnostic time and, in the measured instance, a near-miss on a wrong remedy: the misreading pointed at the wall-clock cap, and #1096's banner already records that *"the actual fix is #320, not a larger integer, and re-deriving weekly is the failure mode to avoid."* -**THE OBVIOUS FIX IS DANGEROUS AND MUST NOT BE APPLIED CASUALLY.** Renaming the job looks like a one-line edit and is not. 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**, and the rename is therefore a single atomic change across the workflow, the contexts file, the pinned count in that test, and the branch-protection setting -- in the order that file's own header prescribes. A rename that lands in the wrong order wedges the repo. +**THE OBVIOUS FIX IS DANGEROUS AND MUST NOT BE APPLIED CASUALLY.** Renaming the job looks like a one-line edit and is not. Those three strings **are required contexts**: they are listed in `.github/required-contexts.txt`, pinned by count and resolved against real workflow job names by `tests/test_required_contexts.py`, and matched **by name** on the GitHub side. A required-but-absent context blocks every PR **forever**, and the rename is therefore a single atomic change across the workflow, the contexts file, the pinned count in that test, and the branch-protection setting -- in the order that file's own header prescribes. A rename that lands in the wrong order wedges the repo. + +> **CORRECTION 2026-08-14, recorded rather than silently fixed, because the mechanism is one this ledger keeps hitting.** This item first cited the gate as `ci.yml:675`. **`:675` is not the gate** -- it is one of **two** clock MARKS (`step_margin.py --mark between` at `:677`; the other is `--mark before-tests` at `:646`). The gate is at `:765`. The error came from opening `:675`, finding a step whose name *nearly* matched (`Step margin -- boundary between the two gated steps`), and adopting it instead of treating the near-match as the signal that the line was wrong. **A near-miss name is more dangerous than no match**, because it terminates the search. Likewise `tests/test_required_contexts.py` does **not** call the GitHub API: it pins the count at `:101` and resolves every context against real workflow job names at `:107`; the branch-protection comparison is a **human** step recorded in the comment at `:100`. The item's central argument is unaffected -- the three strings are still required contexts and a rename would still resolve them to no job -- but the evidence is now stated as what the test actually does. -**Scope, and a cheaper alternative to price first.** The name may not be the right lever at all, precisely because it is load-bearing for branch protection. The step names are already honest (`Step margin -- start the clock`, `Tests (pytest)`, `Step margin -- boundary between the two gated steps`), so the information exists and is one click away. Options worth comparing before any rename: (a) leave the context name alone and make the margin gate's **failure output** state in its first line that the suite passed and a timing gate fired; (b) split the margin gate into its own non-required check so it reds under its own name; (c) the atomic rename above. **(a) is the only one that costs nothing and cannot wedge the repo,** and it should be measured against the others rather than assumed inferior. +**Scope, and a cheaper alternative to price first.** The name may not be the right lever at all, precisely because it is load-bearing for branch protection. The step names are already honest (`Step margin -- start the clock`, `Tests (pytest)`, `Step margin -- both gated steps`), so the information exists and is one click away. Options worth comparing before any rename: (a) leave the context name alone and make the margin gate's **failure output** state in its first line that the suite passed and a timing gate fired; (b) split the margin gate into its own non-required check so it reds under its own name; (c) the atomic rename above. **(a) is the only one that costs nothing and cannot wedge the repo,** and it should be measured against the others rather than assumed inferior. **Provenance.** Surfaced by the seat that misread the label and caught itself, relayed with the framing *a check named for its SUBJECT rather than its ASSERTION*, and filed here rather than left in session mail so the next reader of a red Windows leg is not sent to the cap. From e06e0996b0557b3adc72323309c8131912a6a3f7 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Fri, 14 Aug 2026 00:10:44 -0500 Subject: [PATCH 18/20] backlog: file #1255 -- two testpaths ship a top-level conftest each Diagnosed by the lane whose own commit tripped it, and filed here because the collision outlives that commit. Seven tests in one file failed in a full run and passed in isolation, twice. Cause: pyproject sets two testpaths, both directories contain a conftest.py, neither contains an __init__.py, so both claim the top-level module name and a bare `import conftest` binds to whichever loaded first. Verified at origin/main rather than taken from the report: both conftest.py files present, both __init__.py absent, and a scan for `import conftest` / `from conftest import` across both trees returns ZERO hits. That zero is why this is filed as LATENT rather than live -- the collision is real and currently untripped, so nothing is failing today and the item must not be cited as a current gap. The signature is recorded because it mis-attributes itself: the mis-bound import surfaces as an AttributeError naming a module path from the WRONG package, not as an ImportError, so it reads as a missing attribute rather than a bad import. Two things the item forbids, both because a plausible fix is worse than the defect. Do not import conftest BY PATH -- its body claims a per-process test slot and registers an atexit unlink, so a second import under another name has side effects. And do not prove a fix in isolation: isolation is precisely the condition under which this defect reports success. The proof has to run both testpaths together and then restore the bare import to confirm the same command fails again. The house idiom already solves it -- tests/_workflow_contexts.py is imported package-qualified at tests/_negative_controls.py:35 -- so the scope is to make the name unambiguous, not to invent a mechanism. #1255 allocated with alloc.ps1, never grepped. parse_items before and after: 283/204/79 -> 284/205/79, matching the predicted delta for one filing, with a control confirming no item carries a stray banner. --- docs/BACKLOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 02ce2ea7..f7b2d753 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -8883,3 +8883,20 @@ _FHIR_ID_RE.fullmatch("abc\n") -> False the fix **Scope, and a cheaper alternative to price first.** The name may not be the right lever at all, precisely because it is load-bearing for branch protection. The step names are already honest (`Step margin -- start the clock`, `Tests (pytest)`, `Step margin -- both gated steps`), so the information exists and is one click away. Options worth comparing before any rename: (a) leave the context name alone and make the margin gate's **failure output** state in its first line that the suite passed and a timing gate fired; (b) split the margin gate into its own non-required check so it reds under its own name; (c) the atomic rename above. **(a) is the only one that costs nothing and cannot wedge the repo,** and it should be measured against the others rather than assumed inferior. **Provenance.** Surfaced by the seat that misread the label and caught itself, relayed with the framing *a check named for its SUBJECT rather than its ASSERTION*, and filed here rather than left in session mail so the next reader of a red Windows leg is not sent to the cap. + +## 1255. two testpaths ship a top-level conftest each, so a bare import conftest binds to whichever loaded first + +> 🔢 **Filed 2026-08-14 - not started. LATENT, not live: no caller trips it at `origin/main` today.** Value **6/10** · Difficulty **2/10** · _quick win_. `pyproject.toml` sets `testpaths = ["tests", "packaging/messagefoundry-webconsole/tests"]`. **Both directories contain a `conftest.py` and NEITHER contains an `__init__.py`**, so both claim the same top-level module name `conftest`. In a full run only one wins `sys.modules`, and a bare `import conftest` in either tree silently binds to it. + +> **THE FAILURE IS INVISIBLE IN ISOLATION, WHICH IS THE WHOLE DEFECT.** Run either tree alone and the import resolves to that tree's own `conftest` and passes. Run both -- which is what `pytest` does by default, and what CI does -- and one tree's import silently resolves to the OTHER tree's module. The observed signature is an `AttributeError` naming a module path from the *wrong* package, not an `ImportError`, so it reads as a missing attribute rather than a mis-bound import. + +**Cluster:** Test infrastructure / latent trap. **Priority:** P2. **Verdict:** build. +**Severity:** no deployment axis (§0) -- test-time only, nothing shipped changes. The cost is a red suite whose cause is mis-attributed, and it is paid by whoever next writes the import rather than by whoever created the collision. + +**Measured at `origin/main`.** `tests/conftest.py` and `packaging/messagefoundry-webconsole/tests/conftest.py` both exist; `tests/__init__.py` and `packaging/messagefoundry-webconsole/tests/__init__.py` both **absent**. A scan for `import conftest` / `from conftest import` across both trees returns **zero** hits, which is why this is filed as latent -- the collision is real and currently untripped. + +**Scope.** Make the module name unambiguous rather than relying on nobody writing the import. Options to price: give one or both test roots an `__init__.py` so the module becomes package-qualified; or move any shared helper out of `conftest.py` into a named module imported package-qualified. **The second is already the house idiom** -- `tests/_workflow_contexts.py` is imported as `from tests._workflow_contexts import ...` (`tests/_negative_controls.py:35`), so the pattern exists and is proven. **Do not "fix" this by importing `conftest` by path:** its body claims a per-process test slot and registers an `atexit` unlink, so importing it a second time under another name has side effects. + +**How to prove a fix.** It must be demonstrated against the FAILURE MODE, not in isolation -- run **both** testpaths together, then restore the bare import and confirm the same command fails again. Isolation is exactly the condition under which this defect reports success. + +**Provenance.** Diagnosed by the lane whose own commit tripped it: seven tests in one file failed in a full run and **passed in isolation, twice**. It reported the negative control alongside the fix -- restoring the bare import reproduced exactly those seven failures -- which is what makes the green meaningful. Recorded here rather than left in session mail because the collision outlives the commit that revealed it. It is the same shape as the rest of this cluster: **the name resolved to the neighbouring module, and nothing said so.** From b63d076c7c995d09921ded380aa93034661cfca5 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Fri, 14 Aug 2026 03:48:30 -0500 Subject: [PATCH 19/20] backlog: record that PR #382 closed ONE of #1242's four limbs -- the item stays open I told a builder its engine fix closed this item's mechanism and to claim it. That was wrong, and this records the correction where the next reader will hit it rather than in session mail. #382 merged 2026-08-13 with this item's number in its title and a title that faithfully describes what it fixed: the payload-only TOP-LEVEL key limb. Verified at origin/main -- apply.py:97 now walks {**(live or {}), **cell}.items() rather than the live cell alone. That limb is genuinely closed. The limb carrying the item's severity is untouched, and the same revision shows why the union cannot reach it: :98 skips _ORDERED and _SUBTABLES BEFORE the union at :97 is consulted for them, and evidence entries are re-emitted at :101-105 by enumerating exactly path, line and expect (absence at :106-110 by exactly pattern, positive_control, mutation). A key inside a [[cell.evidence]] entry is still dropped -- which is exactly where the backfill put the affected keys, on evidence ENTRIES and not on top-level keys. The top-level table-mangling limb also appears untouched. I had additionally told that builder to stop measuring the two affected key names because the item forbids fixing by naming them. The prohibition is real but I applied it to the wrong activity: the item forbids naming them in a FIX, not measuring their absence as a SYMPTOM, and their absence from the writer is exactly the evidence that the sub-table limb still bites. Recorded as the PARTIAL-MOVE shape, which is the reusable part: a merged PR bearing an item's number, whose title truthfully describes what it fixed, is the strongest available signal the item is done. Verify-before-closing is not enough on its own here -- the verification has to ask WHICH HALF. The item's own proof condition is the discriminator and is unchanged: put an unknown key INSIDE an evidence entry, re-render, assert both that it survives and that the guard refuses when it is deliberately dropped. #382 does not satisfy it, and no test asserting only top-level carry-through will. Amendment only, no heading added, so ledger ownership is not consulted. parse_items before and after: 284 items / 205 open / 79 closed, unchanged, and #1242 verified still OPEN. --- docs/BACKLOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index f7b2d753..12625e1e 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -8607,6 +8607,10 @@ _FHIR_ID_RE.fullmatch("abc\n") -> False the fix > ⚠️ **Amendment, same day, recording what the adversarial pass corrected.** The item was first written as "latent, and a clean scalar/sub-table asymmetry." Both halves were wrong. It is **active**, because the measurement was taken one commit before the backfill landed and the parent revision genuinely has zero `sym` keys -- a stale checkout reproduces the wrong answer perfectly and reads as confirmation. And the asymmetry is four-way, not two-way; the clean framing concealed the table-mangling and the payload-only drop entirely. A fifth limb about line-ending rewriting was **investigated and rejected**: the tool does rewrite the working tree to LF, but `core.autocrlf=true` normalizes it back in the index, so the blob is unchanged and the claimed review-diff consequence does not occur. +> **ONE LIMB HAS LANDED AND THE ITEM STAYS OPEN -- read this before treating PR #382 as a closure.** `#382` ("carry unknown scalars from BOTH sources, not just the live cell (BACKLOG #1242)") merged on 2026-08-13 and fixes the **payload-only top-level key** limb: `scripts/asvs/apply.py:97` now walks `{**(live or {}), **cell}.items()` instead of the live cell alone. That limb is genuinely closed. **The limb carrying this item's severity is untouched.** At the same revision, `:98` skips `_ORDERED` and `_SUBTABLES` *before* that union is ever consulted for them, and evidence entries are re-emitted at `:101-105` by enumerating exactly `path`, `line`, `expect` -- absence entries at `:106-110` by exactly `pattern`, `positive_control`, `mutation`. **A key inside a `[[cell.evidence]]` entry is therefore still dropped**, which is precisely where the backfill put the affected keys. The top-level table-mangling limb also appears untouched. + +> **WHY THIS IS RECORDED RATHER THAN LEFT TO A READER TO NOTICE.** A merged PR carrying this item's number, whose title is a faithful description of what it fixed, is the strongest possible signal that the item is done -- and it is wrong. That is the **partial-move** shape: *"it was built"* is true of half, so verifying-before-closing is not enough on its own; **the verification has to ask WHICH HALF.** A closure here would retire a P1 whose stated trigger has never been addressed. **The item's proof condition is unchanged and is the one that discriminates:** put a key the writer has never heard of **inside an evidence entry**, re-render, and 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. + **Cluster:** Security tooling / evidence integrity. **Priority:** P1. **Verdict:** build. **Severity:** no deployment axis -- vault tooling, ships to nobody. P1 rather than P2 because the loss is **pending on the next routine operation**, is **silent in both directions** (the writer reports success, the verifier reports green having checked less), and destroys evidence that cost a dedicated backfill to produce. ## 1245. an administrator password reset re-arms bootstrap retirement, permanently disabling the account it was meant to recover From 08a3229acc2bdb6e6af9a7c50b31a8acd0a45181 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Fri, 14 Aug 2026 03:58:24 -0500 Subject: [PATCH 20/20] backlog: record #1242's BASE REQUIREMENT -- the obvious base for limb 4 reverts limb 3 Builder 2 confirmed limb 4 as I described it, then refused to build it and handed it back with a reason better than the instruction I gave. Recording the reason, because it is not discoverable from the item and git will not raise it. Its branch still carried scripts/asvs/apply.py:88 as if key in _ORDERED or key in _SUBTABLES or key in cell: Verified here against that ref rather than taken: the clause is present there and ABSENT at origin/main. `or key in cell` is precisely what #382 deleted to fix limb 3. So a limb-4 fix authored on that base and merged would carry limb 3's REVERSAL in the same diff -- no conflict, no marker, every check green, and the item's own landed fix undone by the commit claiming to extend it. Git raises nothing here because git conflicts on concurrent edits to the same lines, never on a stale base re-asserting a clause that was deleted elsewhere. That is the same family as the clean-merge hazard this session has been working under all night, arriving from the direction nobody watches: not a doc invalidated by a merge, but a FIX reverted by an extension of itself. The item now states the base requirement and a pre-PR check that discriminates: branch fresh from current origin/main, and confirm `or key in cell` returns zero hits in the diff's own version of the file before opening a PR. Also corrected upstream of this, in my own dispatch rather than the ledger: I had told that lane to stop measuring the two affected key names. Measuring their absence as a SYMPTOM was always legitimate; only fixing by naming them is forbidden. It withdrew its acceptance of my earlier "bound lifted" on the grounds that it had taken it from me without measuring -- correctly. Amendment only, no heading added, so ledger ownership is not consulted. parse_items before and after: 284 items / 205 open / 79 closed, unchanged, and #1242 verified still OPEN. --- docs/BACKLOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 12625e1e..6a4f3baf 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -8609,6 +8609,8 @@ _FHIR_ID_RE.fullmatch("abc\n") -> False the fix > **ONE LIMB HAS LANDED AND THE ITEM STAYS OPEN -- read this before treating PR #382 as a closure.** `#382` ("carry unknown scalars from BOTH sources, not just the live cell (BACKLOG #1242)") merged on 2026-08-13 and fixes the **payload-only top-level key** limb: `scripts/asvs/apply.py:97` now walks `{**(live or {}), **cell}.items()` instead of the live cell alone. That limb is genuinely closed. **The limb carrying this item's severity is untouched.** At the same revision, `:98` skips `_ORDERED` and `_SUBTABLES` *before* that union is ever consulted for them, and evidence entries are re-emitted at `:101-105` by enumerating exactly `path`, `line`, `expect` -- absence entries at `:106-110` by exactly `pattern`, `positive_control`, `mutation`. **A key inside a `[[cell.evidence]]` entry is therefore still dropped**, which is precisely where the backfill put the affected keys. The top-level table-mangling limb also appears untouched. +> **BASE REQUIREMENT -- BUILD LIMB 4 ONLY ON A BASE THAT ALREADY CONTAINS `#382`. This is not hygiene; the obvious base silently reverts the limb that already shipped.** Measured 2026-08-14: the branch a lane was holding this on still carried `scripts/asvs/apply.py:88` as `if key in _ORDERED or key in _SUBTABLES or key in cell:` -- **`or key in cell` is exactly the clause `#382` deleted**, and it is absent at `origin/main`. A limb-4 fix authored there 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 quietly undone by the commit claiming to extend it. Git raises nothing, because git conflicts on concurrent edits to the same lines, never on a stale base re-asserting a deleted clause. **Branch fresh from current `origin/main`, and before opening a PR, confirm `or key in cell` returns zero hits in the diff's own version of the file.** + > **WHY THIS IS RECORDED RATHER THAN LEFT TO A READER TO NOTICE.** A merged PR carrying this item's number, whose title is a faithful description of what it fixed, is the strongest possible signal that the item is done -- and it is wrong. That is the **partial-move** shape: *"it was built"* is true of half, so verifying-before-closing is not enough on its own; **the verification has to ask WHICH HALF.** A closure here would retire a P1 whose stated trigger has never been addressed. **The item's proof condition is unchanged and is the one that discriminates:** put a key the writer has never heard of **inside an evidence entry**, re-render, and 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. **Cluster:** Security tooling / evidence integrity. **Priority:** P1. **Verdict:** build. **Severity:** no deployment axis -- vault tooling, ships to nobody. P1 rather than P2 because the loss is **pending on the next routine operation**, is **silent in both directions** (the writer reports success, the verifier reports green having checked less), and destroys evidence that cost a dedicated backfill to produce.