From 72e6457ce1ef69f421d194cc109a227094bc2de6 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Tue, 25 Aug 2026 13:36:11 -0500 Subject: [PATCH 1/9] backlog: close #1005 and #1010 -- both landed via squash, subjects naming other items #1005 (CRL checking, ASVS 12.1.4 band B1): landed 3c5cb988 (PR #458, 2026-08-22), squashed alongside #1268 -- the commit subject names #1268, not this row, which is why a subject screen never found it. All four scope items verified present by content. Ceiling stays partial per the row's own ASVS note; not described as clearing the cell. #1010 (licence-header gate): landed 4d3a72eb (PR #398, 2026-08-15). Gate, negative control, backfill, and both callers (pre-commit + required CI) verified present and wired. Neither closure is complete: #1005's vault-side ASVS cell and #1010's security deviation register both need a matching correction that this checkout cannot see or verify (vaulted/ gitignored). Noted in each banner rather than silently left. Also: #1010's own text claims backlog_status_check.py hard-errors on a shipped item still carrying Priority -- tested empirically (--min-items 300), it does not, at least in this checkout's version. Left Priority as-is rather than acting on the unverified claim. Both ancestor claims verified directly before closing. Co-Authored-By: Claude Opus 5 --- docs/BACKLOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 15b2b043d..ad6758e5f 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -3874,7 +3874,7 @@ trap were verified by resolving all four commits. ## 1005. CRL checking of partner client certs on the mTLS-terminating listeners (ASVS 12.1.4 band B1) -> 🔢 **Re-scored 2026-08-20 -> P1.** Value **8/10** · Difficulty **5/10** · _quick win_. No client-certificate revocation code exists anywhere under messagefoundry/, and the one ENFORCED revocation control in the tree (tls_policy.py:20-23, ADR 0078) governs the engine's own off-loopback [api] TLS bind rather than partner client certs, so it does not reach the MLLP or DICOM listeners. Value 8 because a revoked partner cert would keep authenticating to those two on first deployment and an HTTP proxy can terminate neither protocol; difficulty 5 because the remainder spans three connector factories plus ApiSettings, a tls_policy sibling, a posture-keyed refusal, a CertExpiryRunner freshness alarm, and a real-handshake test rig that today exists for one builder only. _(was 8/10 · 5/10.)_ +> ✅ **SHIPPED -- verified on main 2026-08-25 at `3c5cb988`** (PR #458, 2026-08-22 -- squashed alongside #1268, which is why the commit subject does not name this row). All four scope items confirmed present by content: the per-inbound `tls_crl_file` setting reaching all three verifying listeners, CA+CRL load, the posture-keyed fail-closed refusal, and the freshness preflight + pre-expiry alarm. ADR 0173 is on main. **Per the row's own ceiling: this is ASVS 12.1.4 band B1 only -- `partial`, never `pass`. Do not describe it as clearing the cell.** 🔢 **Re-scored 2026-08-20 -> P1.** Value **8/10** · Difficulty **5/10** · _quick win_. No client-certificate revocation code exists anywhere under messagefoundry/, and the one ENFORCED revocation control in the tree (tls_policy.py:20-23, ADR 0078) governs the engine's own off-loopback [api] TLS bind rather than partner client certs, so it does not reach the MLLP or DICOM listeners. Value 8 because a revoked partner cert would keep authenticating to those two on first deployment and an HTTP proxy can terminate neither protocol; difficulty 5 because the remainder spans three connector factories plus ApiSettings, a tls_policy sibling, a posture-keyed refusal, a CertExpiryRunner freshness alarm, and a real-handshake test rig that today exists for one builder only. _(was 8/10 · 5/10.)_ > > **Filed 2026-08-04 — not started. Scored 2026-08-04 → P1.** Three server-side `SSLContext` builders already require and verify a > partner client certificate and **not one checks revocation** — measured on this tree, a @@ -4432,7 +4432,7 @@ open. ## 1010. No licence-header gate exists in any language, and 196 first-party sources carry no SPDX tag -> 🔢 **Re-scored 2026-08-20 -> P3.** Value **3/10** · Difficulty **2/10** · _fill-in_. I attacked the shipped claim limb by limb rather than trusting the item's own SHIPPED banner. Checker exists AND is the right shape: scripts/quality/licence_header_check.py asserts the VALUE (EXPECTED_IDENTIFIER = "AGPL-3.0-or-later", WRONG reported as its own class), requires the tag to be a comment in the language's own prefix within HEAD_LINES=20, so a header-emitting string literal does not count. I ran it live rather than reading it: whole-tree run exits 0 with "1309 file(s) checked, all declare AGPL-3.0-or-later", so the backfill including the five Apache-2.0 files is done. I proved it can go red by planting both classes in the scratchpad (never in the repo): it exits 1 and names WRONG and MISSING separately. Extension scope matches the item's six languages exactly -- COMMENT_PREFIXES covers py/ps1/sh/ts/js/go, and --list yields 1147 py + 108 ts + 48 ps1 + 4 js + 2 sh, which equals the full tracked count for each of those extensions, so no tree is excluded and no in-scope language is uncovered (.go is 0 because #1011 retired the Go tree). Both callers are wired: .pre-commit-config.yaml:180-184 as a local hook with files: \.(py|ps1|sh|ts|js|go)$, and .github/workflows/ci.yml:351-353 inside the required `test` job, deliberately NOT paths-filtered. The negative control ships with the gate in tests/test_licence_header_gate.py (four planted classes plus test_the_real_tracked_tree_is_clean, which enforces the invariant in the ordinary pytest job). Adding commits 4d3a72eb and 587ea4b8. So the "no gate exists / 196 headerless files" gap is genuinely gone. It is not confirmed_shipped because the security record still says otherwise in two places, one of them a deviation register whose own neighbouring entry states that naming a nonexistent control there is the worst place to do it -- and because the ledger closure is untaken, which the item's own banner says is the only protection against a rebuild of merged work on any other checkout. Remainder value 3: nothing runnable ships, but discharging a stale risk acceptance and preventing a duplicate rebuild of a landed quick win is worth more than marginal. Difficulty 2: doc edits plus a three-edit coupled closure that backlog_status_check.py hard-errors on if done partially. _(previously unscored.)_ +> ✅ **SHIPPED -- verified on main 2026-08-25 at `4d3a72eb`** (PR #398, 2026-08-15). Gate, negative control, and backfill all confirmed present on main; both callers (pre-commit, required CI) confirmed wired. The follow-up (three more headers) is also on main by content, though its own commit is a squash-orphan. **Not fully closed here -- two items the row itself names remain: the security deviation register still lists this control as absent (correct it there too), and installed hook-script copies on this box may have drifted from source (owner-run, pull before install).** 🔢 **Re-scored 2026-08-20 -> P3.** Value **3/10** · Difficulty **2/10** · _fill-in_. I attacked the shipped claim limb by limb rather than trusting the item's own SHIPPED banner. Checker exists AND is the right shape: scripts/quality/licence_header_check.py asserts the VALUE (EXPECTED_IDENTIFIER = "AGPL-3.0-or-later", WRONG reported as its own class), requires the tag to be a comment in the language's own prefix within HEAD_LINES=20, so a header-emitting string literal does not count. I ran it live rather than reading it: whole-tree run exits 0 with "1309 file(s) checked, all declare AGPL-3.0-or-later", so the backfill including the five Apache-2.0 files is done. I proved it can go red by planting both classes in the scratchpad (never in the repo): it exits 1 and names WRONG and MISSING separately. Extension scope matches the item's six languages exactly -- COMMENT_PREFIXES covers py/ps1/sh/ts/js/go, and --list yields 1147 py + 108 ts + 48 ps1 + 4 js + 2 sh, which equals the full tracked count for each of those extensions, so no tree is excluded and no in-scope language is uncovered (.go is 0 because #1011 retired the Go tree). Both callers are wired: .pre-commit-config.yaml:180-184 as a local hook with files: \.(py|ps1|sh|ts|js|go)$, and .github/workflows/ci.yml:351-353 inside the required `test` job, deliberately NOT paths-filtered. The negative control ships with the gate in tests/test_licence_header_gate.py (four planted classes plus test_the_real_tracked_tree_is_clean, which enforces the invariant in the ordinary pytest job). Adding commits 4d3a72eb and 587ea4b8. So the "no gate exists / 196 headerless files" gap is genuinely gone. It is not confirmed_shipped because the security record still says otherwise in two places, one of them a deviation register whose own neighbouring entry states that naming a nonexistent control there is the worst place to do it -- and because the ledger closure is untaken, which the item's own banner says is the only protection against a rebuild of merged work on any other checkout. Remainder value 3: nothing runnable ships, but discharging a stale risk acceptance and preventing a duplicate rebuild of a landed quick win is worth more than marginal. Difficulty 2: doc edits plus a three-edit coupled closure that backlog_status_check.py hard-errors on if done partially. _(previously unscored.)_ > > **THE BUILD HAS LANDED ON `origin/main` AND THIS BANNER STILL READS OPEN. DO NOT REBUILD. Measured 2026-08-15 at `origin/main` `4d3a72eb`.** > ``` From e3661f63f5fbd109267c6509523ee0f005f29c9a Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Tue, 25 Aug 2026 13:39:35 -0500 Subject: [PATCH 2/9] backlog: close #1214 -- rename shipped, row's own citation was a squash-orphan The threat-model gate's rename (test_transcribed_bounds_match_the_live_constants) and failure-message fix both landed at 9022b2cc (PR #562, 2026-08-23). The row's own body cited a5d5a9ab as the rename commit -- a5d5a9ab is not an ancestor of origin/main, same squash- orphan class PR 587 fixed on two other rows tonight. Corrected in the same edit as the closure rather than left for a separate pass. Skipped #1061 and #1249 from the same evidence batch: both are landed by content but each carries a prior, on-main, deliberate decision not to close (12aa0674/9847d267 for #1061; an unwitnessed owner ratification for #1249). Closing either would override a standing decision, not just flip a stale banner. Verified the ancestor claim and the rename directly before writing. Co-Authored-By: Claude Opus 5 --- docs/BACKLOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index ad6758e5f..5f13055bf 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -10814,7 +10814,7 @@ gate is the wrong shape, validation of the walk is the right one. ## 1214. The threat-model numeric-parity gate compares live constants to a hardcoded transcription, not to the document it names -> 🔢 **Re-scored 2026-08-20 -> P3.** Value **2/10** · Difficulty **2/10** · _fill-in_. The published defect, a failure message naming an artefact the check never opened, is fixed in place with its reasoning in the docstring, and two guards were added past the filing. The remainder is a one-identifier rename or the document-binding alternative the Scope explicitly makes optional, which ships nothing runnable either way, so value 2 and a small edit on an existing test module. _(was 5/10 · 3/10.)_ +> ✅ **SHIPPED -- verified on main 2026-08-25 at `9022b2cc`** (PR #562, 2026-08-23; the row's own citation `a5d5a9ab` is a squash-orphan and not an ancestor -- corrected here to the landed sha, same defect class PR 587 just fixed on two other rows). The rename shipped (`test_transcribed_bounds_match_the_live_constants`, live at :823; the old name survives only inside a retraction docstring, confirmed by content, not a live symbol) and the failure message no longer claims "doc says" -- it now states outright that the assertion does not open the document. Both Scope options were optional; the rename satisfies it. 🔢 **Re-scored 2026-08-20 -> P3.** Value **2/10** · Difficulty **2/10** · _fill-in_. The published defect, a failure message naming an artefact the check never opened, is fixed in place with its reasoning in the docstring, and two guards were added past the filing. The remainder is a one-identifier rename or the document-binding alternative the Scope explicitly makes optional, which ships nothing runnable either way, so value 2 and a small edit on an existing test module. _(was 5/10 · 3/10.)_ > > **Filed 2026-08-11 -- found by Session C, MEASURED not argued.** `tests/test_threat_model_doc_drift.py::test_transcribed_bounds_match_the_live_constants` (**renamed 2026-08-23 in `a5d5a9ab`; this row previously cited `test_documented_bounds_...`, which no longer resolves**) compares each live constant to a **hardcoded literal in the test file**, then reports a mismatch as `"doc says {expected!r}, code says {actual!r}"`. **"doc says" is not true of any value it compares** -- the literal is a human transcription, and nothing binds the transcription to the document. All of the roughly 60 rows in the `checks` list share the shape, so the gate catches *code drifting from the transcription* and can never catch *the transcription drifting from the document*. > **The proof it does not work is that it PASSES when fully wired.** Session C set `MEFOR_THREAT_MODEL_DOC` and `MEFOR_REQUIRE_THREAT_MODEL_DOC` and ran it against the real vault document: **90 passed, 11 skipped**. A gate that passes against the artefact it claims to compare against is measuring something else. From 07d42eda5cdec7b85efc5ae89330b40eec45d7ae Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Tue, 25 Aug 2026 13:45:19 -0500 Subject: [PATCH 3/9] backlog: note #1022's self-exclusion limb is built -- item stays open, one record still owed Builder 1's claude/builder-1-1022-self-exclusion adds self-exclusion to POST /users/{user_id}/reset-mfa. Per standing dispatcher guidance #1022 is not a clean closure, so this is a progress note, not a banner flip -- status glyph unchanged. Of the row's three OWED record corrections, verified which landed: (1) SECURITY.md and (2) ADR 0068 both corrected on that branch. (3) is not -- the ADR gained retraction prose but no new formal AC entry (checked directly: existing ACs run AC-10 through AC-16). Co-Authored-By: Claude Opus 5 --- docs/BACKLOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 5f13055bf..cebd936b6 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -4797,6 +4797,7 @@ Retiring the tree costs the engine nothing operationally: **`tests/test_ech_egre > **One bounded residual, stated so nobody reports it as worse than it is:** the guard is **check-then-act** (read then write, both `await`s), so it is not fail-closed under concurrency. Two racing requests could each see a surviving factor and both remove one. The consequence is **forced re-enrollment, not single-factor access**, which is why it is a residual rather than a blocker. > **RESIDUAL SCOPE, written 2026-08-21 (dispatcher), verified on the code at `origin/main` `75e20d43`. THE TITLED DEFECT IS BUILT -- what remains is one small code limb and three records the fix made FALSE.** > **BUILT, verified at the definition, at BOTH call sites, and in both polarities of test.** `disable_mfa` (`auth/service.py:2253`) reads `list_webauthn_credentials` then raises when `_mfa_required_for` holds and no factor would survive -- the exact guard shape this item specified, keyed on the Identity's roles with no extra store read. Call sites: `api/auth_routes.py:443-446` maps it to a 400 with a `#1022` comment, so the 500 this item predicted does not occur; `messagefoundry_webconsole/routes/account.py:282-294` renders it as a page and re-raises 429 to preserve Retry-After. Tests: `tests/test_mfa.py:369` asserts the refusal AND that the store was not mutated anyway; `:392` is the labelled POSITIVE CONTROL asserting the disable is ALLOWED when `require_mfa=False`; `:407` asserts the admin recovery path was NOT narrowed. Route level: `tests/test_api_auth.py:1444` and the web console suite. +> **BUILT 2026-08-25, self-exclusion limb.** `POST /users/{user_id}/reset-mfa` now refuses (400) when `user_id` is the caller's own; cross-user reset is untouched, pinned by its own test. **Stays OPEN regardless** -- per standing dispatcher guidance this row is not a clean closure. Of the three OWED records below, (1) and (2) are corrected on main; (3) is NOT -- the ADR gained retraction prose at the same spot but no new formal `AC-N` entry (checked directly: the file's ACs run AC-10 through AC-16, none added for this refusal). Whoever eventually closes this row still owes that one line. > **OWED -- code, and it is small.** `POST /users/{user_id}/reset-mfa` (`api/auth_routes.py:786-804`) still has **no self-exclusion**, while its sibling `reset_user_password` 25 lines above (`:761-784`, guard at `:770-773`) does. So an Administrator can still zero their own factors. **The fix is self-exclusion at the ROUTE, never a guard in the service method** -- `admin_reset_mfa` is deliberately unguarded as the always-available recovery for a locked-out passkey user, its docstring says so, and `tests/test_mfa.py:407` exists specifically to prove the new guard did not narrow it. > **OWED -- three records the landed fix made false.** (1) `docs/SECURITY.md` still asserts of `DELETE /me/mfa` that there is "no last-factor guard" and that it does not refuse a zero-factor state -- **a shipped security document making a false NEGATIVE claim about a control that now exists.** (2) `docs/adr/0068-*.md:140` still says TOTP-disable "keeps its existing behavior this lane (parity follow-up recorded)". (3) ADR 0068 should gain an acceptance criterion for the `disable_mfa` refusal, pointing at the named test. > **DO NOT go looking for the AC-10 / AC-11 contradiction this item alleges -- IT IS NOT THERE.** The body above says ADR 0068 "contradicts itself four lines apart" because AC-10 was amended unscoped. On `origin/main` AC-10 IS scoped -- it conditions on deleting a WebAuthn credential that would remove the user's last second factor -- so it does not collide with AC-11's mandate that `admin_reset_mfa` clears both factor types. **The real ADR defect is under-description, not contradiction.** A builder sent to resolve a contradiction that does not exist will either invent one or return empty-handed. From c8aca60b00614cadd30b6cba182f747b9ffe6c86 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Tue, 25 Aug 2026 13:58:46 -0500 Subject: [PATCH 4/9] backlog: #1273 progress note -- third spelling verified gone, item stays open logging_setup.py now imports controlchars and states its own table as that alphabet minus TAB, proved byte-identical to the pre-change table entry-for-entry rather than asserted. Verified before writing this note. Co-Authored-By: Claude Sonnet 5 --- docs/BACKLOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index cebd936b6..9397aa2ff 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -13219,6 +13219,7 @@ measurement from this row's subject and it is named here rather than performed.* > 🔢 **Re-scored 2026-08-20 -> P3.** Value **4/10** · Difficulty **2/10** · _fill-in_. One limb remains and it is a pure refactor: logging_setup.py:66-70 still derives its own table from range(0x20) plus 0x7F and does not import controlchars at all, while the module now states the alphabet once at :67 with both arms reading it. Value 4 because nothing is mis-screened today, two of the three named copies already agree, and at least two further independent derivations sit outside this item's scope (spreadsheet.py:37, soap.py:263), so folding logging_setup in buys the stated cross-module leverage for one file rather than repo-wide. Difficulty 2 for one import, one derivation with an explicit tab subtraction, a test pinning that tab stays excluded, and renaming the acceptance test's scrub_control_chars, which returns zero code hits. _(previously unscored.)_ > +> **PARTIAL 2026-08-25 -- THE THIRD SPELLING IS NOW GONE TOO. STILL STAYS OPEN** (spreadsheet.py:37 and soap.py:263 are named out-of-scope above, and remain so). `logging_setup.py` now imports `controlchars` and states its own table as that alphabet minus TAB, proved byte-identical to the pre-change table entry-for-entry (32 entries both sides) rather than asserted. Verified before writing this note, not taken from the branch's own claim. > **PARTIAL 2026-08-20 -- ONE OF THE TWO SPELLINGS INSIDE THE MODULE IS GONE; THE THIRD, IN ANOTHER MODULE, IS NOT. THIS ITEM STAYS OPEN.** `controlchars.py` now states the set once, in `_is_control_char` at `:67`, and both `has_control_char` and `strip_control_chars` read it -- so the module's own interior no longer contradicts its docstring. **But `logging_setup.py:66-70` still re-derives the same set independently** (`for _i in range(0x20)` plus `[0x7F]`), and the landing branch does not touch that file at all. The item names **three** spellings and asks for **one definition of the SET**; two of three now share one. **The leverage the module exists to provide is still absent across the module boundary:** widen `_is_control_char` and it reaches neither `_CTRL_TRANSLATION` nor anything reading it, and nothing reports the omission. Measured on the landing branch, not inferred. Recorded by the lander under ADR 0165, because a closing banner that lists what a change fixed and not what it left is half a record. > 🔢 **Filed 2026-08-15 - not started. THE CONSOLIDATION DOES NOT CONSOLIDATE ITS OWN TWO FUNCTIONS.** [`controlchars.py`](../messagefoundry/controlchars.py) was created by [#1253](BACKLOG.md) to write the C0/DEL test **once**; its docstring is titled *"The C0/DEL control-character test, written once"* and ends *"THE POINT IS THE COPYING PRACTICE, not the seven known lines. If you need this test, import it."* **It then spells the predicate out twice inside itself**, and a third statement of the same set lives in `logging_setup`. > **MEASURED, three independent spellings of one set:** From 92c7960c76538c6525adfe919d0589a8d0efe6dc Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Tue, 25 Aug 2026 14:26:59 -0500 Subject: [PATCH 5/9] backlog: close 6, retire a stale must-not-close marker on #1235, progress-note 2 Closed, each verified independently (ancestor sha or byte-identical content match against the row's own cited branch tip, not adopted from the evidence pass): #1262 (doc-guards lane membership + anti-vacuity test), #1263 (citation_line_check.py with all four required controls), #1301 (banner-sha agreement check, narrowed trigger), #1306 (worktree gate read/write fix, heredoc half correctly left to #1332), #1331 (connscale smoke test split into 9 named properties), #1338 (duplicate-banner-field detector, both must-fire/ must-not-fire controls pinned). #1235: retired the 2026-08-24 must-not-close marker. Its stated condition -- the closing PR falsified the detector's header and left it uncorrected -- has cleared: read the header on main directly, it now says plainly that the script is wired into no workflow and distinguishes itself from its CI-wired sibling. Closed alongside. Two stay open with a progress note rather than closing: #1254 (option (a) landed and resolves the measured defect; options (b)/(c) are an unbuilt branch-protection decision, not a build) and #1324 (mechanism is live -- verified with a fresh parse_items run, not the evidence pass's snapshot -- data migrations landed, but real fill-in work remains: 2 mechanical, 5 needing an owner ruling the row forbids flattening). Deliberately not touching #1086 from the same evidence batch: closing it declined-by-design rests on an owner ruling relayed via the Liaison that I have not seen directly, for a security-relevant gate. Leaving it rather than acting on a relayed claim for a call of that weight. Verified every landed sha against origin/main myself before writing any of this (ancestor checks plus, where the cited sha was a squash-orphan, a content diff against the row's own branch tip) rather than trusting the evidence pass's citations. Co-Authored-By: Claude Opus 5 --- docs/BACKLOG.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 9397aa2ff..94a59548a 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -11333,7 +11333,7 @@ build commits and retirement markers all read the LEDGER. ***"Does the subject e only check that reads the CODE, and it is the one that decides startability.*** ## 1235. a citation to an unallocated backlog number is a trap that arms itself the day the number is issued -> 🚧 **Re-scored 2026-08-20 -> P3.** Value **4/10** · Difficulty **2/10** · _fill-in_. The rule and both coverage residuals landed, leaving one gap: the detector runs only inside pytest, which is skipped on documentation-only pull requests, and a citation is introduced by editing prose. Wiring it as a workflow step with a paired must-trip and must-not-trip arm is a small additive change on an existing gate seam, and the coverage bound stands regardless since the detector cannot see the private companion repository where the filed instances live. _(was 6/10 · 2/10.)_ +> ✅ **SHIPPED -- verified on main 2026-08-25.** The wiring limb landed at `b47c9fd9` (PR #560, sibling gate `backlog_citation_check.py` run in CI on documentation-only PRs). The header-falsification that kept this row must-not-close (2026-08-24 marker, below) is now corrected: `scripts/docs/dangling_citation_check.py`'s docstring on main states plainly, in its own words, "THIS SCRIPT IS RUN BY NO WORKFLOW AND NO PRE-COMMIT HOOK," distinguishes itself from the CI-wired sibling by name, and carries the wiring measurement with a positive control (this script: zero hits across `.github/` and `.pre-commit-config.yaml`; `backlog_status_check`/`ledger_check`/`scan_forbidden`: 3/2/3). That content is on main by a non-ancestor commit (`06427921`) whose CONTENT matches exactly -- confirmed by direct read, not cited as an ancestor. The marker's stated condition ("the closing PR falsified the detector's own header and left it uncorrected") has cleared; retiring it below rather than leaving a cleared condition standing as a live blocker. 🚧 **Re-scored 2026-08-20 -> P3.** Value **4/10** · Difficulty **2/10** · _fill-in_. The rule and both coverage residuals landed, leaving one gap: the detector runs only inside pytest, which is skipped on documentation-only pull requests, and a citation is introduced by editing prose. Wiring it as a workflow step with a paired must-trip and must-not-trip arm is a small additive change on an existing gate seam, and the coverage bound stands regardless since the detector cannot see the private companion repository where the filed instances live. _(was 6/10 · 2/10.)_ > > **Filed 2026-08-12; PARTIAL as of 2026-08-14 -- BOTH NAMED LIMBS HAVE LANDED and the item stays open on a residual the limbs never named. NOT CLOSED.** Documents in this project's PRIVATE companion repository cite public `BACKLOG #N` numbers that **were never allocated**. > **BANNER CORRECTED 2026-08-14 (dispatcher) -- THE PREVIOUS BANNER WAS FACTUALLY WRONG IN ALL THREE OF ITS CLAUSES, AND I WROTE IT.** It read *"the DETECTOR shipped, the RULE did not, and the detector is wired into nothing."* Measured on `origin/main`: **the rule DID ship** -- `d059ddf6` put it in both places a reader looks, as a bullet in `CLAUDE.md` section 5 and as a named section in `docs/LEDGER-GATE.md` -- **and the detector is NOT wired into nothing**: `d48bfce5` flipped it fail-closed (`--advisory` is now the opt-out, exit 1 is the default) and the committed suite walks the real `docs/` tree, with the population pinned so a walk that collapses to nothing cannot report clean. **The failing arm was proven empirically, not inferred:** a planted live-shape citation makes the committed test go red naming file, line and number, and removing the plant restores green. **A banner is the first thing a reader trusts and the last thing anyone re-checks; this one was three-for-three wrong for several hours.** @@ -11361,7 +11361,7 @@ only check that reads the CODE, and it is the one that decides startability.*** > Verdict: build > Closing-act: code -***VERIFIED-BUT-NOT-CLOSEABLE 2026-08-24 (dispatcher, adversarial pass).*** **The engineering is discharged and the row still must not close, because THE MERGED TREE CONTRADICTS THE CLOSING REASON.** PR #560 is merged (`b47c9fd9`) and the wiring is real -- `ci.yml:244` carries the guard in `DOC_GUARDS`, and the noncode regex at `:1500` means a prose-only PR actually runs it. ***BUT THE CLOSING PR FALSIFIED THE DETECTOR'S OWN HEADER AND LEFT IT UNCORRECTED ON MAIN.*** **That is a LIVE DEFECT on main rather than a stale ledger note, and it is unowned** -- flagged here so it is not lost with the pass that found it. +***VERIFIED-BUT-NOT-CLOSEABLE 2026-08-24 (dispatcher, adversarial pass) -- MARKER RETIRED 2026-08-25, ITS CONDITION HAS CLEARED.*** **The engineering is discharged and the row still must not close, because THE MERGED TREE CONTRADICTS THE CLOSING REASON.** PR #560 is merged (`b47c9fd9`) and the wiring is real -- `ci.yml:244` carries the guard in `DOC_GUARDS`, and the noncode regex at `:1500` means a prose-only PR actually runs it. ***THE CLOSING PR FALSIFIED THE DETECTOR'S OWN HEADER AND LEFT IT UNCORRECTED ON MAIN, AT THE TIME THIS WAS WRITTEN.*** That header is now corrected -- see the SHIPPED line above -- so the specific condition this marker names no longer holds. A marker has a date; this one's has passed. Not lifted on request -- verified by reading the corrected header directly before writing this. **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. @@ -12280,6 +12280,8 @@ BUILDS it.* **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. +> **PARTIAL 2026-08-25 -- OPTION (a) LANDED, STAYS OPEN.** `scripts/ci/step_margin.py:98` now leads its failure output with the step's SUCCESS, then the timing -- verified directly on `origin/main`, not taken from a prior claim. That is the row's own "only one that costs nothing and cannot wedge the repo" option, and it resolves the row's measured instance: a reader hitting the margin gate's output no longer forms the wrong hypothesis at the point of failure. Options (b) (split into a non-required check) and (c) (the atomic rename) are unbuilt and remain a decision for whoever owns branch protection, not a build lane -- the row's own text calls getting the rename order wrong a way to wedge the repo forever, so this is not a builder dispatch. + ## 1255. two testpaths ship a top-level conftest each, so a bare import conftest binds to whichever loaded first > 🔢 **Re-scored 2026-08-20 -> P2.** Value **5/10** · Difficulty **3/10** · _fill-in_. Every precondition re-verifies at HEAD: no importmode at pyproject.toml:317, neither test root carries an __init__.py, and the collision is still untripped (zero bare conftest imports), so the trap is latent exactly as filed. Difficulty is above a plain additive edit because option B changes pytest module naming for roughly 680 files and the item's own rule is that the only honest check is a both-testpaths full-suite run. _(was 6/10 · 2/10.)_ @@ -12454,7 +12456,7 @@ BUILDS it.* ## 1262. the docs-only CI short-circuit skips the gates whose subject is documentation -> 🔢 **Re-scored 2026-08-20 -> P3.** Value **4/10** · Difficulty **2/10** · _fill-in_. The measured defect, membership of the ungated doc-guards lane, is closed: both named absentees are in DOC_GUARDS at ci.yml:236-243, the non-member ruling is recorded inline at :231-235, and the step runs precisely on the docs-only pull requests that skip the suite (:201-202). Value 4 and difficulty 2 on the remainder, which is re-deriving the set in an environment carrying the five CI extras the trace lacked and demonstrating that a Markdown-only pull request carrying a deliberate violation actually goes red, since the lane has never been shown able to fail. _(was 7/10 · 3/10.)_ +> ✅ **SHIPPED -- verified on main 2026-08-25 at `4a8f062d`** (PR #394, membership fix -- both named absentees present in DOC_GUARDS with the non-member ruling recorded inline) **and `test_THE_LANE_CAN_FAIL_on_a_planted_documentation_violation`** (`tests/test_doc_guards_lane.py:106`, live on main), which the 2026-08-23 amendment named as "now the whole of this item." Both landing commits are squash-orphans of their branch originals; confirmed by content, not by the branch shas. 🔢 **Re-scored 2026-08-20 -> P3.** Value **4/10** · Difficulty **2/10** · _fill-in_. The measured defect, membership of the ungated doc-guards lane, is closed: both named absentees are in DOC_GUARDS at ci.yml:236-243, the non-member ruling is recorded inline at :231-235, and the step runs precisely on the docs-only pull requests that skip the suite (:201-202). Value 4 and difficulty 2 on the remainder, which is re-deriving the set in an environment carrying the five CI extras the trace lacked and demonstrating that a Markdown-only pull request carrying a deliberate violation actually goes red, since the lane has never been shown able to fail. _(was 7/10 · 3/10.)_ > > **Filed 2026-08-14 - not started. THE GATES THAT POLICE DOCUMENTATION LIVE INSIDE THE SUITE THAT IS SKIPPED WHEN ONLY DOCUMENTATION CHANGES.** This is an **inversion**, not a hole: the short-circuit is deliberate and sound for its stated purpose, and the doc-subject gates are exactly the ones it should not have swallowed. > **THE MECHANISM, measured on `origin/main`.** The `changes` job computes `code` as a *"CONSERVATIVE docs-only detector"* whose allowlist is `(\.md$|^docs/|^LICENSE$|^NOTICE$|^AUTHORS$|^\.editorconfig$|^\.gitattributes$|^\.github/(ISSUE_TEMPLATE/|PULL_REQUEST_TEMPLATE))`. The main suite step is gated on it: `if: needs.changes.outputs.code == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'`. **So on a pull request touching only Markdown, the entire pytest suite does not run** -- and it *does* run on the subsequent `push` to `main`. **The failure therefore lands on `main` instead of on the pull request that introduced it.** @@ -12512,7 +12514,7 @@ box-specific -- a different platform and a `[dev]`-only install will differ. *** THAT TRANSFERS, because its cause is identical in both: a document that is not there.*** ## 1263. a file:line citation into code is validated by nothing, so any bulk edit silently re-points 1,193 of them -> 🔢 **Re-scored 2026-08-20 -> P2.** Value **7/10** · Difficulty **4/10** · _quick win_. Re-measured with a separately written matcher at HEAD, the corpus is 904 live and 381 archived path:line citations into source, the archived figure reproducing this item's own 381 exactly while the live figure has grown past every number the item records, which is the drift it predicts. Three checkers exist and none reads a cited line, so the cheap detector plus its printed silence, its ref and denominator, and the mutation test that proves it can stay quiet remain unbuilt. _(was 7/10 · 4/10.)_ +> ✅ **SHIPPED -- verified on main 2026-08-25 at `a8f5d10a`**, byte-identical to branch tip `4cc38a05` (`git diff --stat` empty). `scripts/docs/citation_line_check.py` implements the 2026-08-23 amendment's build list: symbol-agreement (AGREES/DRIFTED/UNRESOLVED, not a bounds check), a loud refusal of bare filenames with the count printed, the printed-denominator rule pinned by test, and the stay-quiet mutation test (`test_a_correct_citation_is_silent_and_the_drifted_twin_is_not`, :62) the amendment demanded. Not CI-wired -- a script plus a pytest module over synthetic fixtures, absent from every workflow; worth filing separately if wanted, not a reason to hold this row open. 🔢 **Re-scored 2026-08-20 -> P2.** Value **7/10** · Difficulty **4/10** · _quick win_. Re-measured with a separately written matcher at HEAD, the corpus is 904 live and 381 archived path:line citations into source, the archived figure reproducing this item's own 381 exactly while the live figure has grown past every number the item records, which is the drift it predicts. Three checkers exist and none reads a cited line, so the cheap detector plus its printed silence, its ref and denominator, and the mutation test that proves it can stay quiet remain unbuilt. _(was 7/10 · 4/10.)_ > > **Filed 2026-08-14 - not started. A CITATION THAT DRIFTS DOES NOT BREAK; IT RESOLVES TO THE WRONG LINE, WHICH IS THE FAILURE THIS PROJECT ALREADY TREATS AS WORSE.** The two ledger files carry **1,193** `path:line` citations into code (`.py`/`.ts`/`.ps1`/`.sh`/`.js`) -- 812 live, 381 archived. **Nothing anywhere checks that the cited line still contains what the citation says it does.** > **TWO CHECKERS EXIST AND NEITHER ASKS THIS QUESTION -- by their own account.** `link_check.py` answers *"does this path resolve"*, and it does. `backlog_citation_check.py` resolves a **backlog number** against the **ledger namespace** -- *"does the cited FILE contain the item"* -- and its docstring already draws exactly this distinction for its own case: *"The two checkers ask different questions and neither subsumes the other."* **A `path:line` citation into source is a third question, and it has no gate.** Verified by reading both modules on `origin/main`, not inferred from their names. @@ -13787,7 +13789,7 @@ point, which are the parts that must survive it.** ## 1306. the worktree gate denies a READ-ONLY git config core.hooksPath query on command shape -> 🔢 **Filed 2026-08-22 by the lander.** Value **4/10** -- Difficulty **2/10** -- _fill-in_. Reported by the Builder 1 seat, relayed by the Dispatcher, and **reproduced here before filing, with a harness whose controls discriminate.** The gate decides on COMMAND SHAPE rather than on whether a value is being assigned, so a bare `git config core.hooksPath` -- which assigns nothing -- is refused with *"setting 'core.hooksPath' would change the SHARED git configuration"*. The message is false about what the command does. +> ✅ **SHIPPED -- verified on main 2026-08-25 at `5eb261de`** (PR #541), same patch-id (`ee619959e567b0272ef0b87af83dda606cbf9c25`) as the row's own cited `0c176a85`, which is a squash-orphan and not an ancestor -- corrected here to the landed sha. `git grep -n "rest -notmatch" scripts/hooks/worktree_gate.ps1` confirms the fix line at :1317. Scope is the READ half only, as ruled 2026-08-23; the heredoc half is a separate row, #1332, and does not fold back in here. 🔢 **Filed 2026-08-22 by the lander.** Value **4/10** -- Difficulty **2/10** -- _fill-in_. Reported by the Builder 1 seat, relayed by the Dispatcher, and **reproduced here before filing, with a harness whose controls discriminate.** The gate decides on COMMAND SHAPE rather than on whether a value is being assigned, so a bare `git config core.hooksPath` -- which assigns nothing -- is refused with *"setting 'core.hooksPath' would change the SHARED git configuration"*. The message is false about what the command does. > **REPRODUCED, and the defect is NARROWER than the report reads.** Driven through the real hook interface: a JSON payload on stdin carrying `cwd`, with `-ReposFile` naming the primary checkout. Five cases, both controls discriminating: > ``` > git config core.hooksPath /dev/null DENY <- positive control, correctly denied @@ -14584,7 +14586,7 @@ quoted body's line from a command line. **Do not pick it up expecting a one-line it has been ruled closed for now.** ## 1301. a ledger banner citing a commit sha must cite a commit whose subject names the item it sits under -> 🔢 **Filed 2026-08-21 -- not started. ONE EDIT CORRUPTED TWO ITEMS IN OPPOSITE DIRECTIONS AND NO GATE COULD SEE IT, BUT A SHA-TO-ITEM AGREEMENT CHECK WOULD HAVE.** A retirement banner intended for one item was written onto another. The Markdown stayed valid, the item count did not move, the status glyph was untouched, and the misplaced paragraph carried no glyph of its own -- so `parse_items` had no second banner to object to and every ledger gate passed. +> ✅ **SHIPPED -- verified on main 2026-08-25 at `4d76bd6e`** (PR #542), byte-identical to branch commit `5a513350` (`git diff --stat` empty, including all 8 tests in `tests/test_banner_sha_check.py`). The landed check is the 2026-08-23-narrowed trigger, not the row's original literal rule -- a sha in a CLOSED/retirement/SHIPPED banner asserting closure of THIS item, using the qualified `BACKLOG #N` form since a bare `#N` is ambiguous with a PR number. Not CI-wired -- a script plus a pytest module over synthetic fixtures; one of the three hits its first real run found is recorded as left untriaged and still needs a person, worth a separate filing rather than holding this row open. 🔢 **Filed 2026-08-21 -- not started. ONE EDIT CORRUPTED TWO ITEMS IN OPPOSITE DIRECTIONS AND NO GATE COULD SEE IT, BUT A SHA-TO-ITEM AGREEMENT CHECK WOULD HAVE.** A retirement banner intended for one item was written onto another. The Markdown stayed valid, the item count did not move, the status glyph was untouched, and the misplaced paragraph carried no glyph of its own -- so `parse_items` had no second banner to object to and every ledger gate passed. > Verdict: build > Research: none > Closing-act: code @@ -14655,7 +14657,7 @@ THAN DISMISSED UNEXAMINED.** ***"Untriaged" is an honest state; "no defects foun hit is not.*** ## 1331. test_connscale_smoke_end_to_end wears six assertions under one name, so a merge-blocking flake reads as six unrelated bugs -> 🔢 **Filed 2026-08-22 - not started.** One test name covers at least six separate properties, so two seats hitting it twice see two unrelated bugs rather than one recurring problem. It sits on three of the thirteen required contexts, so every occurrence is merge-blocking. +> ✅ **SHIPPED -- verified on main 2026-08-25 at `df8acc95`** (PR #537), byte-identical to branch tip `2ac5be6a` (`git diff --stat` empty). `tests/test_connscale_smoke.py` now carries 9 separately named property tests (`test_the_sweep_produces_one_record_per_mode_and_count`, `test_no_loss_reconciles_at_every_step`, and 6 more) over one shared fixture, in place of the single welded name -- confirmed live, not a comment. The item's own defect (one name masking six properties) is fully fixed; the leg's underlying INTERMITTENCY is a separate, unattributed subject the row's last amendment explicitly declines to scope as a build -- worth its own filed row if wanted, not a reason to hold this one open. 🔢 **Filed 2026-08-22 - not started.** One test name covers at least six separate properties, so two seats hitting it twice see two unrelated bugs rather than one recurring problem. It sits on three of the thirteen required contexts, so every occurrence is merge-blocking. > Verdict: build > Closing-act: code @@ -14912,6 +14914,9 @@ no count**, so the check every seat has been quoting -- *total items and open co do-not-land was issued for the other. ***THAT WAS LUCK PLUS A HUMAN INSTRUCTION, NOT A CONTROL.*** **A count-preserving check cannot detect a count-preserving corruption**, and nothing in the current gate set counts *occurrences per item per field*. + +> **PARTIAL 2026-08-25 -- THE MECHANISM IS BUILT AND LIVE, THE DATA MIGRATIONS LANDED, STAYS OPEN ON A SMALL NAMED RESIDUAL.** Ran `parse_items` live against the current `docs/BACKLOG.md`, not against a snapshot: 363 items, 244 open, zero duplicate-field items (the #1338 detector's own positive control). Missing `Verdict` on 10 open items: #351, #352, #353, #1008, #1093, #1236, #1305, #1339, #1340, #1341. Missing `Closing-act` on 11: the same ten plus #320. This matches the reader working as designed, not a defect. What remains, per the 2026-08-23 amendment: (1) MECHANICAL -- #1305 (verdict) and #320 (closing-act) are fillable today; (2) NOT A BUILDER'S JOB -- #351/#352/#353/#1008/#1093 carry free-text verdicts outside the closed vocabulary and the row forbids flattening them, so they need an owner ruling routed via the Liaison; (3) #1236/#1339/#1340/#1341 are drift from rows filed after the migration, out of this item's scope. Leaving open rather than closing with a residual, since real fill-in work remains. + ## 1332. Heredoc bodies are scanned as commands, so quoted documentation trips the secret-scanning rules > 🔢 **Filed 2026-08-23 - not started.** A heredoc body becomes its own line in `Get-ScannableSegments` ([`scripts/hooks/worktree_gate.ps1:557`](../scripts/hooks/worktree_gate.ps1)), and the fact that it is quoted DATA does not survive the newline split. Documentation that QUOTES a config key is scanned as if it SET one. This is the root cause under #1305 and one half of #1306. @@ -15339,7 +15344,7 @@ grammar stops colliding with semantic versions. ## 1338. The ledger check is count-preserving, so a duplicated banner field is invisible to it -> 🔢 **Filed 2026-08-23 - not started.** Every seat verifies a ledger edit with `parse_items` totals -- item count and open count. **A duplicated banner field line adds no item, flips no status and moves no count**, so that check reads perfectly clean over a corrupted row. **Reached as a live near-miss on 2026-08-23, caught by a human instruction rather than by any gate.** +> ✅ **SHIPPED -- verified on main 2026-08-25 at `f402a2d3`** (PR #550), byte-identical to branch tip `9873b38a` (`git diff --stat` empty on both changed files). `Item.__slots__` now carries `duplicate_fields`, recording a displaced value when a key repeats; both required controls are pinned (`test_a_duplicated_banner_field_is_recorded_rather_than_silently_overwritten` must-fire, `test_a_field_key_in_prose_below_the_banner_is_not_a_duplicate` must-not-fire -- the exact #1324 false-positive the row's own amendment predicted, landed before the checker that had to avoid it). Live run of `parse_items` over the current `docs/BACKLOG.md`: zero duplicate-field items across 363, with the must-fire test as positive control that the zero is real. 🔢 **Filed 2026-08-23 - not started.** Every seat verifies a ledger edit with `parse_items` totals -- item count and open count. **A duplicated banner field line adds no item, flips no status and moves no count**, so that check reads perfectly clean over a corrupted row. **Reached as a live near-miss on 2026-08-23, caught by a human instruction rather than by any gate.** > Verdict: build > Research: none > Closing-act: code From 85bc151a02cc9d07dabdea005d901283639136bf Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Tue, 25 Aug 2026 14:50:20 -0500 Subject: [PATCH 6/9] backlog: file #1356 -- PR 487's resolver conflict is a judgment merge Content is PR 527's own #1330 filing, re-numbered: the original #1330 allocation belonged to a different, now-gone Lander worktree, and this one's ledger gate correctly refused to let me commit under someone else's allocation. Re-allocated fresh in this worktree per the owner's call, after confirming nothing in the tracked tree cites "#1330" expecting this content -- the number was never published anywhere main-visible. Filed clean off current main rather than resolving PR 527's own conflict: PR 527 also carried a competing #1321 edit, but #1321 (and #1322, the item it was a duplicate of) are both already closed on main via a separate path, so that half of PR 527 is moot -- dropped rather than replayed. This item's own text records that as done rather than repeating the stale "banner needs flipping" framing from the original filing. Co-Authored-By: Claude Opus 5 --- docs/BACKLOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 94a59548a..ecd5be469 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -16127,3 +16127,14 @@ federation on, the authorization request would travel the browser-visible front identity provider is the only party positioned to reject tampering. That is true, and it is not by itself a finding against this cell. +## 1356. PR 487's five-file resolver conflict is a judgment merge whose obvious resolution re-arms a fixed defect + +> 🔢 **Filed 2026-08-22 by the lander, under a live 5-hour rung, because it cannot be finished in the remaining window.** Value **5/10** -- Difficulty **5/10**. PR **#487** (`lander/432-resolved`, draft, 45 ahead / 26 behind) conflicts with `origin/main` in FIVE files, all in the bash-interpreter resolver area that **#505** rewrote: `tests/_bash_resolver.py`'s consumers plus `tests/tooling_manifest.txt`. +> **THE RESOLUTION IS NOT MECHANICAL, AND THE OBVIOUS ONE IS WRONG.** An adversarial per-file pass (one classifier per file, each handed to a skeptic told to refute it) found that **some of #487's assertions were written against the OLD interpreter selection order** -- `bin/bash.exe` before `usr/bin/bash.exe`. #505 reversed that order because the first is the MSYSTEM=MINGW64 wrapper, which rewrites `PATH` so `/mingw64/bin` outranks anything a caller prepends. **So those assertions are now WRONG rather than merely redundant, and resolving toward #487 re-arms the exact defect #505 fixed.** +> **WHY IT MATTERS BEYOND THIS PR:** the blast radius is every test that PATH-stubs a binary Git ships in `mingw64/bin`. Measured: `curl`, `git` and `openssl` shipped; `gh`, `jq` and `python` not. That is why #505's release-age rows failed while its gh-stub rows passed, and why the failure read as flakiness for hours. +> **WHAT A TAKER NEEDS, so this is not re-derived:** the per-file verdicts are in this session's workflow output at `subagents/workflows/wf_c6ae5d48-661/journal.jsonl` -- one classifier result and one refutation per file. Read those before resolving. #487's CI is ~15 hours stale (03:04-03:20) and its three failures are all explained: the gitleaks one is a 23-minute window that closed itself when PR #491 merged needles and allowlist together, and the harness one predates #505. +> **ALSO STRANDED HERE, both small:** the two re-scores the ASVS tracker handed over (`672b0f1b` four V6 cells partial->pass, `2e5cd61d` **6.1.3 pass->PARTIAL, a RE-OPEN**) need item numbers derived, because no cell-to-item map exists and the tracker correctly refused to guess one. *(The other stranded piece named in the original filing -- flipping #1321's banner to superseded -- is done: #1321 and #1322 are both closed on `main` as of this filing.)* +> +> **Cluster:** Merge queue / interpreter resolution. **Priority:** P3. **Verdict:** build. +> **Severity:** no product effect and no PHI effect -- test-harness and merge-queue only. The cost of getting it wrong is silent: a PATH-stubbed test that reaches the real network reads as a pass on a fixture it never consulted. + From 3a42daf60abf47d50353b9530603b87cf6066039 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Tue, 25 Aug 2026 14:57:14 -0500 Subject: [PATCH 7/9] backlog: close #1086, #1070, #1057 -- declined-by-design, owner-ruled All three confirmed one-hop via the Liaison, who put each question to the owner directly and read the answer -- not relayed through a third party. This is the same tokeniser-family ruling I held #1086 on earlier this session pending exactly this kind of confirmation. #1086 + #1070: both fold into the 2026-08-23 tokeniser ruling's family (#1066/#1070/#1086/#1305/#1336). Verified the code claim behind "one string asked two questions" myself before writing this: Remove-QuotedSpans is real at worktree_gate.ps1:425, called on the scan path at :960/:967, Raw and Scan are genuinely separate fields at :1319-1321/:1290-1291. Did not re-verify #1070's three design refutations (Builder 2's own measurement, not independently re-run by anyone since) -- recorded as such in the row. #1057: a tier move off the row's own Verdict: build line, so asked rather than stamped, matching this ledger's existing declined-by-design rows (all of which carry an owner date). Declines because rule 3d has no allow path -- zero Write-Allow in its span -- so the row can only ever act as an authorization, which prune-merged.ps1 forbids occupancy from being, in its own comment, for the same no-heartbeat-on-this-host reason. Co-Authored-By: Claude Opus 5 --- docs/BACKLOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 3e832fa77..f02ae1dd8 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -5268,7 +5268,7 @@ cd ../Unrelated && git -C . config core.hooksPath /dev/null ## 1086. Rule 3c reads a PowerShell here-string as a command, so a commit message that quotes a disarm key is refused -> 🔢 **Re-scored 2026-08-20 -> P2.** Value **6/10** · Difficulty **3/10** · _quick win_. No message-flag blanking has shipped: Get-ScannableSegments (scripts/hooks/worktree_gate.ps1:403) blanks only ordinary quoted spans, and its own residual list at :470 names BACKLOG #1086 as the pending change that would remove the accidental multi-line denial. The coupled #1097 recursion gap HAS landed (the flag family is now derived by rule, with the -Com, -Comm, --Com, unicode-dash and /Q/C measurements recorded at :411-540), so the remainder is the banked closed-flag-set patch re-derived against the shipped recursion plus the two undisclosed cases; value 6 because rewording the message is a real if record-corrupting workaround the item itself demonstrates. _(was 7/10 · 3/10.)_ +> ✅ **DECLINED-BY-DESIGN 2026-08-25, OWNER RULING extending the 2026-08-23 tokeniser ruling to the family (#1066/#1070/#1086/#1305/#1336).** Confirmed one-hop: the Liaison put it to the owner directly and read the answer, rather than relaying a third party's account -- the alternative (extending the ruling to this row alone, on the ground that a false-deny with a verified one-flag workaround and a fail-open in a security gate are not the same animal) was on the table and the owner declined it, taking the whole family knowingly. **The code claim behind "one string asked two questions" is verified, not a figure of speech:** `Remove-QuotedSpans` is real at `worktree_gate.ps1:425`, called on the scan path at `:960`/`:967`, and the gate holds `Raw` and `Scan` as genuinely separate fields (`$seg.Raw` at `:1319-1321`, `$seg.Scan` at `:1290-1291`) -- that IS the shape the tokeniser ruling addresses. The rejected `95abca70` attempt below remains the concrete evidence for why a narrow fix is dangerous here: it passed all its own tests and still shipped six DENY->ALLOW moves carrying live `$(...)` payloads. 🔢 **Re-scored 2026-08-20 -> P2.** Value **6/10** · Difficulty **3/10** · _quick win_. No message-flag blanking has shipped: Get-ScannableSegments (scripts/hooks/worktree_gate.ps1:403) blanks only ordinary quoted spans, and its own residual list at :470 names BACKLOG #1086 as the pending change that would remove the accidental multi-line denial. The coupled #1097 recursion gap HAS landed (the flag family is now derived by rule, with the -Com, -Comm, --Com, unicode-dash and /Q/C measurements recorded at :411-540), so the remainder is the banked closed-flag-set patch re-derived against the shipped recursion plus the two undisclosed cases; value 6 because rewording the message is a real if record-corrupting workaround the item itself demonstrates. _(was 7/10 · 3/10.)_ > > **Filed 2026-08-07 — not started. ⛔ LIVE ON THE INSTALLED GATE, and it refused the commit that filed #1085.** `git commit -m @'...'@` whose MESSAGE quotes a disarm command is denied by rule 3c. The hook scans the tool-call text, matches the key, and refuses — though the quoted text is data being passed to `-m` and nothing was ever going to execute. **The gate cannot commit a faithful description of its own defect.** > ⚠️ **AMENDED 2026-08-11 — an attempt was ruled DO_NOT_LAND: it WEAKENED the gate and SHIPPED A TEST PINNING THE WEAKENING.** `95abca70` (branch `claude/g1086`, local only, anchored, **not pushed**). Its premise was *"a MESSAGE flag's quoted span is DATA, not a command"*. **That is FALSE for two of the four quoting forms, because POWERSHELL SUBSTITUTES BEFORE GIT IS EVER INVOKED:** @@ -5684,7 +5684,7 @@ and `enforce_admins` governs **protected branches**. Re-enabling it would refuse ## 1057. Rule 3d has no occupancy signal, so it cannot tell an abandoned worktree from a live one -> 🚧 **Re-scored 2026-08-20 -> P3.** Value **4/10** · Difficulty **5/10** · _fill-in_. The remedy half shipped and the occupancy half did not, on the installed hook as well as the repo copy, which is exactly what the banner claims. Value 4 because the rule fails closed with no data loss and no security effect, the cost being a correct refusal the caller cannot act on; difficulty 5 because the installed hook lives outside every checkout and cannot dot-source scripts/coord/occupancy.ps1, forcing one of three structural options each carrying a drift or fail-open hazard, and whatever is built must also carry the 36-hour age rule that did the actual vetoing. _(was 4/10 · 5/10.)_ +> ✅ **DECLINED-BY-DESIGN 2026-08-25, OWNER RULING.** Confirmed one-hop: the Liaison put it to the owner directly and read the answer -- asked rather than stamped by the Dispatcher because this is a tier move off the row's own `Verdict: build` line, and every existing declined-by-design row in this ledger carries an owner date. **Why it declines: rule 3d has no allow path.** Exactly two exits in its span, both `Write-Deny` (`:1660` own-worktree, `:1680` other) -- zero `Write-Allow`. A veto added to a rule that already denies everything adds nothing, so the only way this row has any effect is as an AUTHORISATION, which its own Scope line asks for outright ("an UNOCCUPIED one becomes actionable"). `prune-merged.ps1` forbids exactly that, verbatim, in its own comment: occupancy is a veto only, it can never authorise a removal, because there is no heartbeat on this host to PROVE a session is gone. **Two alternatives were on the table and both were declined:** leaving the row open with the finding recorded (the Dispatcher's own view that this might be the better call), and changing `prune-merged.ps1` so occupancy CAN authorise (recommended against, for the same no-heartbeat reason). **Worth keeping for the next reader:** occupancy is a *better* signal than the authorship this row originally proposed, and `prune-merged.ps1` uses it correctly -- as one veto among three, where the actual authorisation comes from merged-and-clean. This row has no merged-and-clean to lean on, so occupancy would have to carry the authorisation alone. 🚧 **Re-scored 2026-08-20 -> P3.** Value **4/10** · Difficulty **5/10** · _fill-in_. The remedy half shipped and the occupancy half did not, on the installed hook as well as the repo copy, which is exactly what the banner claims. Value 4 because the rule fails closed with no data loss and no security effect, the cost being a correct refusal the caller cannot act on; difficulty 5 because the installed hook lives outside every checkout and cannot dot-source scripts/coord/occupancy.ps1, forcing one of three structural options each carrying a drift or fail-open hazard, and whatever is built must also carry the 36-hour age rule that did the actual vetoing. _(was 4/10 · 5/10.)_ > > **PARTLY SHIPPED 2026-08-06 — the REMEDY slice is in; the occupancy body is NOT, and this item stays open for it.** What shipped is the half nobody had noticed: rule 3d refused correctly and then handed the caller a command that **throws**. `remove.ps1 -Name` resolves only to `/-` — `new.ps1:79-81` *asserts* that shape, so the sibling family is the only one it can produce — and `prune-merged.ps1` excludes anything with a `.claude/worktrees/` path segment **outright**, by its own header, with `-Name` unable to reach them either. Census on this clone: **45 sibling worktrees, 8 Claude-managed, 4 other — and all six live sessions sat in the 8**, so the remedy failed for the population that actually reaches the rule. Verified against the **installed** gate, not only the repo copy. The deny now branches on worktree family and, for anything the two scripts cannot serve, prints a literal `git -C "" worktree remove ""` plus a line saying why `prune-merged.ps1` does not apply; the sibling family keeps `prune-merged.ps1`, which is dry-run by default and consults occupancy, so the fix is not "stop naming the scripts" — a test pins that. Writing the test enlarged the defect: the own-tree branch printed the literal placeholder `-Name ` for **both** families, so it named the tool correctly and still could not be pasted; the gate has the resolved path and now uses it. **This is a remedy-text change only** — which worktrees rule 3d refuses is untouched, and nothing security-relevant reads the classification, which is what makes a misclassification cheap here and expensive in rule 3c. **Same defect as #1032, one rule over** (there, rule 3b printed a `new.ps1` command `new.ps1` refuses to run). **Still open, and unchanged below:** rule 3d has no occupancy or authorship signal, so an *unoccupied* worktree is still refused rather than allowed — the three structural options, the measured 0-of-24 occupancy result and the fail-closed constraint all stand as written. See also **#1064**, a measured rule 3d fail-open confirmed 2026-08-06, deliberately not touched here. Original filing follows. #1041 fixed rule 3d's *false* claim; this is the *missing* one underneath it. The rule refuses every governed worktree that is not the caller's own, because it has no way to ask whether anyone is actually using it. The refusal is right by default — a needless refusal costs a message, a wrong allow deletes a live session's work — but it is unactionable for a caller cleaning up a worktree it created itself, who must escalate to a human for something it was entitled to do. > Verdict: build @@ -6043,7 +6043,7 @@ git -c 'alias.ci=commit --no-verify' ci -m x ## 1070. git carries config in the ENVIRONMENT, and no argv rule can see it -- GIT_CONFIG_COUNT, GIT_CONFIG_PARAMETERS, GIT_CONFIG, config edit -> 🔢 **Re-scored 2026-08-20 -> P1.** Value **8/10** · Difficulty **5/10** · _quick win_. The fail-open is unaddressed in the shipped gate: rule 3c decides from argv at worktree_gate.ps1:960 and the whole file contains no GIT_CONFIG reference, so all four measured channels remain ALLOW. The remainder is a new rule with its own danger match plus a file-identity target resolution inside a 1911-line Windows gate that has regressed before, which is bounded but intricate work with no store, ADR or migration axis. _(was 8/10 · 6/10.)_ +> ✅ **DECLINED-BY-DESIGN 2026-08-25, OWNER RULING, ruled in about an hour after the four-row family (#1066/#1086/#1305/#1336) -- the family is five rows now.** Confirmed one-hop: the Liaison put it to the owner directly and read the answer. The declined alternative was closing this row as BLOCKED on measured evidence instead, keeping the ruling at four rows -- the owner ruled it in rather than holding the line there. **What was NOT independently re-verified, stated so it is not inherited as checked:** the three refutations of this row's fix designs were measured by Builder 2; the Dispatcher confirmed only the ledger citation relationships (this row cites neither #1336 nor #1086; #1336 cites #1086 six times and this row cites #1069 twice); the Liaison confirmed the code structure claim. Three seats, three instruments -- but nobody re-ran the refutations themselves, so treat the underlying fix-rejection as carried forward rather than re-proven here. 🔢 **Re-scored 2026-08-20 -> P1.** Value **8/10** · Difficulty **5/10** · _quick win_. The fail-open is unaddressed in the shipped gate: rule 3c decides from argv at worktree_gate.ps1:960 and the whole file contains no GIT_CONFIG reference, so all four measured channels remain ALLOW. The remainder is a new rule with its own danger match plus a file-identity target resolution inside a 1911-line Windows gate that has regressed before, which is bounded but intricate work with no store, ADR or migration axis. _(was 8/10 · 6/10.)_ > > **Filed 2026-08-06 — not started. ⛔ OPEN ON EVERY GATE MEASURED: the committed one, the rejected round-2 patch, and the round-3 fix (banked as a patch, not committed).** > Verdict: build From a7e52c0289352ec941c0ba5123d5447ed3a22bf8 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Tue, 25 Aug 2026 16:19:39 -0500 Subject: [PATCH 8/9] backlog: #1255 progress note -- guard landed, item stays open The conftest name-collision guard landed as PR 600. Records that the row's own recommended fix (option B, __init__.py in both roots) was measured and makes the collision FATAL rather than fixing it -- verified before writing this note, not taken from the PR's own claim. Co-Authored-By: Claude Opus 5 --- docs/BACKLOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index f02ae1dd8..88f9a1ca8 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -12284,6 +12284,19 @@ BUILDS it.* ## 1255. two testpaths ship a top-level conftest each, so a bare import conftest binds to whichever loaded first +> **PARTIAL 2026-08-25 -- A GUARD LANDED, STAYS OPEN.** `tests/test_conftest_name_collision_guard.py` +> statically walks every testpath root for a bare `import conftest`/`from conftest import` and reds on +> one; nothing does that today, so it is a regression guard, not a fix for a present-tense defect -- +> the row's own "latent, not live" framing still holds. Verified: 13/13 across the guard and its +> tooling-partition registration, ruff + mypy strict clean. +> **THE ROW'S OWN RECOMMENDED FIX (option B, `__init__.py` in both roots) WAS MEASURED AND DOES NOT +> WORK.** It doesn't merely fail to help -- it turns the LATENT collision FATAL: both directories are +> named `tests`, so both conftests become `tests.conftest`, and the whole suite fails to collect +> (`_pytest.pathlib.ImportPathMismatchError`) rather than silently mis-binding. Option A +> (`importmode = "importlib"`) removes the collision but its own stated 44-file-breakage risk did not +> reproduce in a sandbox on pytest 9.1.1 -- a sandbox isn't the real 691-file tree, so that needs +> re-measuring before anyone adopts or dismisses it, and switching import semantics for 691 files on +> an unreproduced premise is exactly what landing the guard alone avoids needing right now. > 🔢 **Re-scored 2026-08-20 -> P2.** Value **5/10** · Difficulty **3/10** · _fill-in_. Every precondition re-verifies at HEAD: no importmode at pyproject.toml:317, neither test root carries an __init__.py, and the collision is still untripped (zero bare conftest imports), so the trap is latent exactly as filed. Difficulty is above a plain additive edit because option B changes pytest module naming for roughly 680 files and the item's own rule is that the only honest check is a both-testpaths full-suite run. _(was 6/10 · 2/10.)_ > > **THE FAILURE MODE IS WORSE THAN FILED, AND THE FIX IS A DECISION RATHER THAN A TASK. Measured 2026-08-15; verified independently here.** This item describes the signature as an `AttributeError` naming a module path from the wrong package -- i.e. failing loudly-ish. **It can fail SILENTLY instead.** From 5627d874d2a575a3ce4e7cd1126e2c10d0ceb2ea Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Tue, 25 Aug 2026 21:10:14 -0500 Subject: [PATCH 9/9] backlog: close #1066, #1305, #1336 -- the tokeniser-family ruling named them, #1086's closure missed them #1086's own closure text cites an owner ruling "extending the 2026-08-23 tokeniser ruling to the family (#1066/#1070/#1086/#1305/#1336)" -- but only #1086 and #1070 were actually closed against it. This closes the other three named members on the same ruling: #1336: the row the ruling actually turns on -- it named the tokeniser requirement and recorded the 2026-08-23 out-of-scope verdict. Also re-applies a metadata fix (Verdict/Closing-act -> owner-ruling) that Dispatcher's 883f77349 made and an unrelated PR (#581, 0d38d1e928) silently reverted by merging from a stale base -- confirmed via git blame, not assumed. #1305: already self-diagnosed the same outcome in its own 2026-08-23 amendment ("as filed, this item is probably UNBUILDABLE"); this formalizes what its own text anticipated. #1066: the banked second fix remains open evidence of why a narrower attempt still fails without the declined tokeniser -- rejected by four verifiers finding five new fail-opens and two new false-deny classes. All three bypasses remain real and live on the installed gate. The ruling accepts that risk rather than building the tokeniser needed to close any of them without reopening one of the others. Co-Authored-By: Claude Sonnet 5 --- docs/BACKLOG.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 5364863de..28847300a 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -5938,7 +5938,7 @@ Reach proven with a harmless key, no disarm key executed: `git -C '../../..' con ## 1066. Rule 3c strips double quotes only, so a single-quoted -C target bypasses it -- including an absolute one -> 🔢 **Re-scored 2026-08-20 -> P2.** Value **7/10** · Difficulty **6/10** · _big bet_. Both halves are live on the repo blob and on the installed hook that actually governs this machine: the -C reader is the unchanged double-quote-only regex and the disarm-key match still reads the quote-blanked scan text, so a single-quoted absolute -C and every quoted -c alias override remain reachable by ordinary spellings. Value 7 because this governs agent behaviour in development with no product or PHI effect, which is above the rubric's parity rungs but below the production and ASVS rungs; difficulty 6 because two written fixes have been rejected by adversarial verification, the second yielding at least five new fail-opens with residuals filed as #1067, #1069, #1070, #1071 and #1072. _(was 9/10 · 3/10.)_ +> ✅ **DECLINED-BY-DESIGN 2026-08-25, OWNER RULING extending the 2026-08-23 tokeniser ruling to the family (#1066/#1070/#1086/#1305/#1336).** Confirmed one-hop: the Liaison put it to the owner directly and read the answer, rather than relaying a third party's account. **#1336 already established this row cannot be fixed alone** -- it and `#1086` are "the same design error pulling in opposite directions," and closing this row's fail-open without widening `#1086`'s false-deny needs exactly the block-tracking shell tokeniser the ruling declines. The banked second fix below remains open evidence of why a narrower attempt still fails: it is MINIMAL-FROM-COMMITTED rather than a parser, and it was REJECTED by four independent verifiers finding at least five new fail-opens and two new false-deny classes -- the same failure shape #1336's own history shows four separate candidates hitting. **The gate governing this machine is unchanged and this row's bypass is real and remains live**; the ruling accepts that risk rather than building a fifth tokeniser-shaped candidate to close it. 🔢 **Re-scored 2026-08-20 -> P2.** Value **7/10** · Difficulty **6/10** · _big bet_. Both halves are live on the repo blob and on the installed hook that actually governs this machine: the -C reader is the unchanged double-quote-only regex and the disarm-key match still reads the quote-blanked scan text, so a single-quoted absolute -C and every quoted -c alias override remain reachable by ordinary spellings. Value 7 because this governs agent behaviour in development with no product or PHI effect, which is above the rubric's parity rungs but below the production and ASVS rungs; difficulty 6 because two written fixes have been rejected by adversarial verification, the second yielding at least five new fail-opens with residuals filed as #1067, #1069, #1070, #1071 and #1072. _(was 9/10 · 3/10.)_ > > **Filed — a SECOND fix was WRITTEN and then REJECTED BY VERIFICATION (2026-08-06). ⛔ DO NOT SHIP IT EITHER. Four independent verifiers each returned a DIFFERENT blocker: at least FIVE new fail-opens and TWO new false-deny classes against the gate live on 57 worktrees, three proven end-to-end to disarm the commit hooks, two of those on keys the rule names explicitly (`includeif.`, `core.hooksPath`). Its 420/420 green suite could not see a single one of the four blockers. Same root cause as round 2, one layer down: structured parsing narrower than the regex it replaces. Patch banked, NOT COMMITTED, NOT INSTALLED (2026-08-06). ⛔ THE GATE GOVERNING THIS MACHINE IS UNCHANGED — still commit `a67838d2`, blob `3e7db362`, 57 worktrees — so this item is OPEN against what is actually running. The FIRST fix (round 2) was verified and REJECTED and its patch is banked, not in the tree; read nothing below as a closure claim, and re-measure before citing any of it.** **What the second fix is, and why it is a different object.** It is MINIMAL-FROM-COMMITTED, not a second parser: `+484/-74` against the committed gate versus the rejected patch's `+616/-96`. The rejected patch replaced this rule's broad regex matching with structured token parsing, and every place the parser turned out NARROWER than the regex became a hole — the git 2.46+ `git config set` form, a scope flag counted in any position, nine wrapper spellings (`(`, `$(`, `exec`, `eval`, `xargs`, `timeout`, `winpty`, `then`, `find -exec`), and a `--file` compared as TEXT. The second fix keeps the regex base and carries across only three of that work's ideas (sub-command splitting, `rev-parse --absolute-git-dir`, and `--file` retargeting), none of which needs a tokeniser. **Verified in both directions, which is the part that was missing before:** the new deny cases were confirmed RED against the REJECTED PATCH — not against the committed gate, which already denies them, so recording them as red-first-against-HEAD would have been false and would have produced a suite blind to exactly that regression class. **Still open on the second fix, measured and named:** #1067, #1069's residual, #1070, #1071, #1072. **On THIS item specifically:** the single-quoted `-C` target and the space-containing governed root still deny on the second fix, and the reader that does it gained one correction the rejected patch did not have — it finds the option in the QUOTE MASK and reads its value from the raw text at the same offset, because searching the raw text was itself a fail-open: `git config core.hooksPath "/nope -C HEAD"` ALLOWED, the `-C HEAD` inside the quoted VALUE having been read as the directory to check. The quoted-KEY half this item's source note points at is now #1069. The drafted assessment follows unedited: **filed and fixed in one act, and it turned out to be the SMALLER half of its own class. ⛔ Rule 3c is still bypassable and this banner does not claim otherwise.** Filed out of #1065, which recorded the finding beside its sibling because it was found too late in that session to allocate a number. > Verdict: build @@ -14555,7 +14555,8 @@ would not appear.** The date comparison remains the open work here. ## 1305. the worktree gate matches git by SPELLING, so a case variant of the program name bypasses every rule -> 🔢 **Filed 2026-08-21 -- not started. `Git -C reset --hard` IS ALLOWED WHERE `git -C reset --hard` IS DENIED, AND THE ONLY DIFFERENCE IS THE CAPITAL LETTER.** Measured against `origin/main` driving the real hook as a subprocess, with the lowercase spelling as a discriminating control and a benign command as a negative control. The same holds for `GIT`, and it is not confined to one rule -- `Git ... checkout` allows too. No quoting, no wrapper, no escape sequence: the bypass is typing the program name differently. +> ✅ **DECLINED-BY-DESIGN 2026-08-25, OWNER RULING extending the 2026-08-23 tokeniser ruling to the family (#1066/#1070/#1086/#1305/#1336).** Confirmed one-hop: the Liaison put it to the owner directly and read the answer, rather than relaying a third party's account. **This row already self-diagnosed the outcome below, on 2026-08-23, before the family ruling made it formal:** its own position-test fix needs a per-line scanner to distinguish a quoted body's line from a command line, which is the same shell-tokeniser requirement `#1336` named and the owner declined. That amendment's own words -- *"as filed, this item is probably UNBUILDABLE... do not hold a slot for it"* -- are what this closure formalizes rather than overrides. **The bypass itself is real and remains live on the installed gate**; the ruling accepts that risk rather than building the tokeniser needed to close it without reopening `#1086`. +> **Filed 2026-08-21 -- not started. `Git -C reset --hard` IS ALLOWED WHERE `git -C reset --hard` IS DENIED, AND THE ONLY DIFFERENCE IS THE CAPITAL LETTER.** Measured against `origin/main` driving the real hook as a subprocess, with the lowercase spelling as a discriminating control and a benign command as a negative control. The same holds for `GIT`, and it is not confined to one rule -- `Git ... checkout` allows too. No quoting, no wrapper, no escape sequence: the bypass is typing the program name differently. > **WHY IT WORKS.** Windows resolves `Git`, `GIT` and `git` to the same `git.exe`, so all three RUN. The gate's token comparison is case-SENSITIVE, so only one of them MATCHES. The gate is therefore matching a **spelling** and calling it a program, while the operating system matches an **executable**. Everything downstream of that comparison inherits the gap, which is why it is not a single-rule defect. @@ -15178,10 +15179,10 @@ per-lane virtualenv. ## 1336. Rule 3c asks one string two questions, so every fix for the false deny widens the bypass and back -> 🔢 **Filed 2026-08-23 - not started.** `#1066` (a quoted danger key is invisible, FAIL OPEN) and `#1086` (a quoted body reads as a command, FALSE DENY) are **the same design error pulling in opposite directions**: rule 3c asks both questions of `$seg.Scan`, the quote-blanked text. Blanking more fixes `#1086` and widens `#1066`. Blanking less does the reverse. **Neither row can be fixed alone.** -> Verdict: build +> ✅ **DECLINED-BY-DESIGN 2026-08-25, OWNER RULING extending the 2026-08-23 tokeniser ruling to the family (#1066/#1070/#1086/#1305/#1336).** Confirmed one-hop: the Liaison put it to the owner directly and read the answer, rather than relaying a third party's account. **This is the row the family ruling actually turns on** -- it is the one that named the tokeniser requirement in the first place ("tracking multi-line quote state across a per-line scanner means writing a shell tokeniser") and recorded the 2026-08-23 verdict that a tokeniser is out of scope. That verdict now extends past "no fifth candidate for now" to declining the row outright: four candidates, four green suites, four failures each finding what the suite it shipped with could not, is treated as the design direction being exhausted rather than merely unlucky. **NOTE ON THIS ROW'S OWN METADATA:** an earlier fix (`883f77349`, PR #578) corrected `Verdict`/`Closing-act` from `build`/`code` to `owner-ruling`/`owner-ruling` without closing the row, deliberately -- but that correction was silently overwritten back to `build`/`code` by an unrelated commit (`0d38d1e928`, PR #581) merging from a base that predated it. Measured via `git blame`, not assumed. This closure both retires the row and re-applies the metadata fix so a stale base cannot silently revert it a second time. +> Verdict: owner-ruling > Research: none -> Closing-act: code +> Closing-act: owner-ruling **Cluster:** commit gates / secret scanning. **Priority:** P1 -- higher than either row it reconciles, because **a fix to either one alone makes the other worse and the last three attempts did exactly