From 09603cce7b2e6ccbfbda2e01581be441b1f4f584 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Tue, 11 Aug 2026 10:00:02 -0500 Subject: [PATCH 1/4] fix(test): the DAST closure guard read a NEGATION as an AFFIRMATION and reddened main origin/main is RED on test_dast_claims.py::test_no_file_claims_dast_closes_the_independent_gap. The trigger is prose I wrote in the #1008 amendment: docs/BACKLOG.md:3739 'independent of the gate:** the original WIP was **INCOMPLETE' That sentence asserts the OPPOSITE of a closure. The guard read it as a claim that the independent-verification gap is closed. MECHANISM, measured rather than inferred. The pattern was (pen ?test|penetration test|independen\w*).{0,60}(complete|satisfied|closed|covered|discharg) with NO word boundary before the verb, so 'complete' matches INSIDE 'INCOMPLETE'. The same hole accepts 'uncovered', 'unsatisfied' and 'undisclosed' -- every one a word whose meaning is the NEGATION of the verb being hunted. Adding \b loses no true positive, because a real closure claim spells the verb as its own word. Verified both directions against the live sentence before the change landed. I FIXED THE GUARD RATHER THAN THE PROSE, DELIBERATELY. Rewording my sentence would have turned main green while leaving a guard that fires on the next person who writes 'independent ... incomplete' -- and it would have destroyed the only live specimen of the fault. The sentence is correct English and correct on the facts; the pattern was wrong. THE PRE-EXISTING NEGATIVE CONTROL COULD NOT HAVE CAUGHT THIS. The file already asserted that 'the independent engagement has not been performed' does not match -- but that passes because 'performed' is not in the verb list at all, not because the pattern understands negation. A guard needs a negative control PER FAILURE MODE, not one per test. The new test pins the negation mode with the REAL failing sentence kept verbatim, because a fixture invented to match the fix is not evidence that the fix covers the fault. VERIFIED, and the control had to be run twice because the first attempt was vacuous. Arming it through a bash heredoc collapsed the backslash in the word-boundary token, so the mutation never applied and the tests 'passed' over unmutated code -- indistinguishable from a surviving mutation. My occurrence assert refused the no-op rather than reporting a pass, which is exactly why that assert exists. Re-armed from a written file: boundary REMOVED -> test_the_closure_pattern_does_not_read_a_negation_as_a_claim FAILS boundary RESTORED -> 18 passed This branch carries main's docs/BACKLOG.md unchanged, so 18/18 green here IS the proof that main goes green. Test-only change; no product code touched. --- tests/test_dast_claims.py | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/tests/test_dast_claims.py b/tests/test_dast_claims.py index 2470afbe..76adc52b 100644 --- a/tests/test_dast_claims.py +++ b/tests/test_dast_claims.py @@ -266,8 +266,18 @@ def test_sds_independent_review_row_is_unchanged(row: str) -> None: # No file this change introduces may claim the gap is closed # ===================================================================================================== +# The `\b` before the closure verb is load-bearing and was added after this guard REPORTED A NEGATION +# AS AN AFFIRMATION. Without it, `complete` matches inside `INCOMPLETE`, so the sentence +# "...independent of the gate: the original WIP was INCOMPLETE" -- which asserts the OPPOSITE of a +# closure -- reddened `main` as though it claimed the independence gap was closed. The same hole +# accepts "uncovered", "unsatisfied" and "undisclosed": every one of them a word whose meaning is the +# negation of the verb being hunted. +# +# The boundary loses no true positive, because a real closure claim spells the verb as its own word. +# Verified both directions before the change landed, and pinned by +# `test_the_closure_pattern_does_not_read_a_negation_as_a_claim` below. _CLOSURE_CLAIM = re.compile( - r"(pen ?test|penetration test|independen\w*).{0,60}(complete|satisfied|closed|covered|discharg)", + r"(pen ?test|penetration test|independen\w*).{0,60}\b(complete|satisfied|closed|covered|discharg)", re.IGNORECASE, ) @@ -307,6 +317,29 @@ def test_the_closure_sweep_can_actually_see_a_claim() -> None: assert not _CLOSURE_CLAIM.search("the independent engagement has not been performed") +def test_the_closure_pattern_does_not_read_a_negation_as_a_claim() -> None: + """Regression: this guard once reported a NEGATION as an AFFIRMATION and reddened ``main``. + + Without a word boundary, ``complete`` matches inside ``INCOMPLETE``, so a sentence asserting the + exact OPPOSITE of a closure tripped the sweep. The live case is the first fixture below -- it is + the real ``docs/BACKLOG.md`` sentence that failed, kept verbatim rather than paraphrased, because a + fixture invented to match the fix is not evidence that the fix covers the fault. + + The pre-existing negative above (``has not been performed``) could not have caught this: it passes + because ``performed`` is not in the verb list at all, not because the pattern understands negation. + A guard needs a negative control per FAILURE MODE, not one per test. + """ + assert not _CLOSURE_CLAIM.search( + "independent of the gate:** the original WIP was **INCOMPLETE, not merely unverified" + ) + # The same hole accepted every negating prefix of the hunted verbs. + assert not _CLOSURE_CLAIM.search("the independent review left the finding uncovered") + assert not _CLOSURE_CLAIM.search("independent testing left the requirement unsatisfied") + # ...while the affirmations they negate must still bite. + assert _CLOSURE_CLAIM.search("the independent review left the finding covered") + assert _CLOSURE_CLAIM.search("independent testing left the requirement satisfied") + + # ===================================================================================================== # Prove the FEATURE-MAP guard catches the rewrite it exists for # ===================================================================================================== From 47d257cb5c95b78eacffd2b8ab9ee8d31bb6128f Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Tue, 11 Aug 2026 10:03:54 -0500 Subject: [PATCH 2/4] docs(ledger): reword #1008's 'independent of the gate' -- ambiguous next to a term of art Belt-and-braces, NOT the load-bearing fix. The guard fix in the previous commit already turns main green on its own: 29 passed across test_dast_claims.py and test_link_resolution.py with this wording still in place, and the fixed pattern does not match the original sentence while still matching a real closure claim. Both measured just now. The reword stands on a DIFFERENT reason, and it is the coordinator's and it is fair: in a document where 'independent verification' is a TERM OF ART, writing 'independent of the gate' to mean 'separately from the gate question' is ambiguous to a human reader, not merely to a regex. A reader who knows the term will parse it the wrong way for a beat. So two independent defects, two independent fixes: - the guard read a NEGATION as an AFFIRMATION -> fixed in the pattern - the prose collided with a term of art in context -> fixed here Doing only the reword would have left a guard that fires on the next person who writes 'independent ... incomplete'. Doing only the guard fix would have left a sentence that reads wrong to a careful human. Neither alone is the whole answer, which is why the earlier framing -- 'fix the guard, not the prose' -- was half right rather than right. Verified locally on this branch BEFORE handover, which is the practice this incident exists to establish: test_dast_claims.py and test_link_resolution.py both green, ledger gates clean, 484 items each declaring exactly one status. --- docs/BACKLOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 533c56e2..a2e6954d 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -3736,7 +3736,7 @@ and scoped this sweep out of itself. Every count above was measured against the > **AND THE SQL SERVER PROBE HAS NEVER EXECUTED ANYWHERE.** Its T-SQL (`IS_SRVROLEMEMBER` / `IS_ROLEMEMBER` / `HAS_PERMS_BY_NAME`, 17 bound parameters) was never run: no reachable local instance, and the lane correctly declined to go looking for an `sa` password. PostgreSQL **was** exercised live both directions against a real 16.14, including a purpose-made least-privilege negative arm. CI is the SQL Server probe's only coverage. The first live run will settle whether `pyodbc` binds parameters into `IS_SRVROLEMEMBER(?)` in a `FROM`-less `SELECT` at all. -> **What the branch DID legitimately establish, independent of the gate:** the original WIP was **INCOMPLETE, not merely unverified**. It broke `tests/test_webconsole_seam_snapshot.py` (measured RED), its four live DB legs were wired into no workflow step and so executed nowhere while reporting as skips — making the file's own docstring claim that CI was *"a standing positive control"* **false when written** — and `postgres_excess` read four of five role attributes off the principal's own row rather than across assumable roles, contradicting `DEPLOY-SERVER-DB.md` §1.3's shipped promise. Those are real findings and they survive whatever the owner rules. +> **What the branch DID legitimately establish, separately from the gate question:** the original WIP was **INCOMPLETE, not merely unverified**. It broke `tests/test_webconsole_seam_snapshot.py` (measured RED), its four live DB legs were wired into no workflow step and so executed nowhere while reporting as skips — making the file's own docstring claim that CI was *"a standing positive control"* **false when written** — and `postgres_excess` read four of five role attributes off the principal's own row rather than across assumable roles, contradicting `DEPLOY-SERVER-DB.md` §1.3's shipped promise. Those are real findings and they survive whatever the owner rules. **Cluster:** Security & Compliance. **Priority:** DEMAND-GATE (would be **P2** on score alone). **Verdict:** **the owner's ruling of record is *"build the runbook fix only; defer the startup From 834f4233696234ba4318a37a05d0bf786aea37a6 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Tue, 11 Aug 2026 10:26:38 -0500 Subject: [PATCH 3/4] ci: the docs-only doc-guard list omitted test_dast_claims, and was written twice The docs-only blind mode recurred on 2026-08-11. THE STEP WAS ALREADY THERE; its list was simply incomplete. tests/test_dast_claims.py scans documents for prose reading as though the independent-verification gap were closed. It was never in the list, so a docs-only PR (#322) merged green and RED MAIN on the push afterwards. THE PROOF IS UNPLANTED, WHICH IS THE BEST KIND. This branch is cut from pristine origin/main, which still carries the real defect. Running the new list here reproduces it: FAILED tests/test_dast_claims.py::test_no_file_claims_dast_closes_the_independent_gap 1 failed, 209 passed, 89 skipped So had this module been listed, #322 would have failed PRE-MERGE instead of reddening main. I did not have to invent a fixture; the fault was sitting on main and the step catches it. (The 89 skips are the documented structural vault-absence class, not a gap.) SECOND DEFECT, found while fixing the first: THE LIST WAS WRITTEN TWICE -- once for the printf and once for pytest -- so the step's own 'print what you scanned' defence COULD LIE. The two copies could drift and then it prints a module it does not run, or runs one it does not print. A defence implemented by duplication defeats itself. Now one variable used twice, plus an existence check that fails loudly on a path typo and a printed module count. WHY THE BLIND MODE IS WORSE THAN A PLAIN GAP, recorded in the step: the failure surfaces on the next PR that touches code, so it is MISATTRIBUTED to whoever opens it. That happened -- the next code PR inherited a red its author had no part in. AND THE LESSON IS ABOUT THE LIST, NOT THE GATING. A curated allowlist silently omits, and nothing in a green run says 'a doc guard exists that I did not run.' The step now says: when you add a doc-scanning module anywhere in tests/, add it HERE in the same commit, and confirm it needs no extras -- a module that cannot run on [dev] alone would red every docs-only PR, the failure the minimal-install note exists to avoid. I checked test_dast_claims against that rule before adding it: its only imports are re, pathlib, pytest and scripts.security.dast_auth_sweep, whose own top-level imports are httpx and messagefoundry.auth -- all base install, no extras. SCOPE STATED HONESTLY: a loose scan suggests other doc-reading modules may also be missing, but that scan matches any test mentioning 'docs' and over-counts badly (it flags test_push_guard and test_required_contexts, which are not doc guards). I am NOT claiming the list is now complete -- only that the one module with a demonstrated live failure is in it, and that the next addition has a stated rule to follow. Triaging the rest needs reading, not a grep. Carries the guard fix from w3-fix-dast-guard so this branch is green in either landing order. Verified: 29 passed across test_dast_claims.py and test_link_resolution.py; ci.yml parses and the doc-guard list now appears exactly once. --- .github/workflows/ci.yml | 46 ++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa27aa27..edd5ffac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -160,6 +160,19 @@ jobs: # relative `(releases/...`, and neither grep form alone finds them all), and a `docs/SECURITY.md` # route-table row asserted a refusal `DELETE /me/mfa` does not make. # + # IT RECURRED ON 2026-08-11, AND THIS STEP WAS ALREADY HERE — the list was simply incomplete. + # `tests/test_dast_claims.py` scans documents for prose reading as though the independence gap + # were closed, but was never added, so a docs-only PR (#322) merged green and RED MAIN on the push + # afterwards. The blind mode is worse than a plain gap because of WHO PAYS: the failure surfaces + # on the next PR that touches code, so it is misattributed to whoever opens it. That happened — + # the next code PR inherited a red its author had no part in. + # + # ⇒ THE LESSON IS ABOUT THE LIST, NOT THE GATING. A curated allowlist silently omits; nothing in + # a green run says "a doc guard exists that I did not run." When adding a doc-scanning test + # module anywhere in `tests/`, add it HERE in the same commit, and confirm it needs no extras — + # a module that cannot run on `[dev]` alone would red every docs-only PR, which is the failure + # mode the minimal-install note below exists to avoid. + # # WHY A SEPARATE MINIMAL INSTALL instead of ungating the install below. Unlike the status # invariant, these modules import the package (`config.settings`, `config.models`, # `parsing.binary`, `auth.service`) and one imports `fastapi.routing`, so they cannot run on @@ -190,20 +203,27 @@ jobs: run: | # PRINT WHAT IS SCANNED BEFORE RUNNING IT. A list that silently shrinks is how this class of # guard goes quiet, and `-rs` names every skip rather than letting one read as a pass. + # + # ONE LIST, USED TWICE. It used to be written out twice — once for the printf and once for + # pytest — which made the "print what you scanned" defence able to LIE: the two copies could + # drift, and then the step prints a module it does not run, or runs one it does not print. + # A defence implemented by duplication defeats itself. The variable is the fix, and the + # cross-check below turns a drift into a hard failure rather than a quiet one. + DOC_GUARDS="tests/test_asvs_file_surface_doc_drift.py tests/test_cloud_phi_hipaa_doc_drift.py + tests/test_crit2_inline_doc_drift.py tests/test_doc_ref_handle.py tests/test_docs_db_grants.py + tests/test_docs_runbooks.py tests/test_docs_security_pathways.py tests/test_security_doc_drift.py + tests/test_security_doc_rate_limits.py tests/test_threat_model_doc_drift.py + tests/test_backlog_status_check.py tests/test_sds_rule_ids_are_stable.py + tests/test_link_resolution.py tests/test_dast_claims.py" + # Every named module must EXIST. A path typo would otherwise make pytest error on an unknown + # file, or — worse under a future -k/--ignore form — silently scan nothing and read as a pass. + for m in $DOC_GUARDS; do + test -f "$m" || { echo "doc-guard list names a missing module: $m"; exit 1; } + done echo "doc guards, docs-only PR — scanning these modules:" - printf ' %s\n' tests/test_asvs_file_surface_doc_drift.py tests/test_cloud_phi_hipaa_doc_drift.py \ - tests/test_crit2_inline_doc_drift.py tests/test_doc_ref_handle.py tests/test_docs_db_grants.py \ - tests/test_docs_runbooks.py tests/test_docs_security_pathways.py tests/test_security_doc_drift.py \ - tests/test_security_doc_rate_limits.py tests/test_threat_model_doc_drift.py \ - tests/test_backlog_status_check.py tests/test_sds_rule_ids_are_stable.py \ - tests/test_link_resolution.py - pytest -q -rs \ - tests/test_asvs_file_surface_doc_drift.py tests/test_cloud_phi_hipaa_doc_drift.py \ - tests/test_crit2_inline_doc_drift.py tests/test_doc_ref_handle.py tests/test_docs_db_grants.py \ - tests/test_docs_runbooks.py tests/test_docs_security_pathways.py tests/test_security_doc_drift.py \ - tests/test_security_doc_rate_limits.py tests/test_threat_model_doc_drift.py \ - tests/test_backlog_status_check.py tests/test_sds_rule_ids_are_stable.py \ - tests/test_link_resolution.py + printf ' %s\n' $DOC_GUARDS + echo " ($(printf '%s\n' $DOC_GUARDS | wc -l) modules)" + pytest -q -rs $DOC_GUARDS # PySide6's offscreen platform plugin needs a few system libraries even # headless. Linux-only; Windows runners need no equivalent. From c2b85eebe5ddd689c40ea8ac0dde101afbb679a7 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Tue, 11 Aug 2026 10:30:02 -0500 Subject: [PATCH 4/4] ci: drop the cp1252-unsafe arrow from the doc-guard comment U+21D2 RIGHTWARDS DOUBLE ARROW raises UnicodeEncodeError on a stock Windows cp1252 console, which is the stated reason in CLAUDE.md section 11. It is a CI comment people read from terminals. Replaced with '=>'. MEASURED BEFORE CHANGING ANYTHING ELSE, because the report also named the em dashes and the section sign and those are a different case: U+2014 EM DASH encodes FINE in cp1252 U+00A7 SECTION SIGN encodes FINE in cp1252 U+21D2 ARROW raises UnicodeEncodeError <- the only real one And on origin/main this file ALREADY carries 134 non-ASCII characters, 127 of them em dashes. Stripping 7 more from my added lines while leaving 127 in place would change nothing a terminal can observe and would make the file internally inconsistent. Section 11's subject is GLYPHS AND EMOJI -- the banner alphabet and pictographs -- and its own text uses 'section' markers throughout; an em dash is punctuation, not a glyph. So: the arrow goes, the em dashes stay, and the reason for each is the same single test rather than a blanket rule applied by eye. Added lines now carry ZERO cp1252-unsafe characters, verified by encoding every non-ASCII character in the diff's added lines rather than by reading them. SEPARATELY, AND NOT MINE TO FIX HERE: origin/main's ci.yml already carries THREE U+2192 arrows, which are cp1252-unsafe by the same test that condemned mine. Pre-existing, unrelated to this change, and reported rather than silently swept in -- a drive-by edit to unrelated lines would make this diff harder to review for no gain. Verified after the change: yaml parses, 29 passed across test_dast_claims.py and test_link_resolution.py. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index edd5ffac..71f82e83 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -167,7 +167,7 @@ jobs: # on the next PR that touches code, so it is misattributed to whoever opens it. That happened — # the next code PR inherited a red its author had no part in. # - # ⇒ THE LESSON IS ABOUT THE LIST, NOT THE GATING. A curated allowlist silently omits; nothing in + # => THE LESSON IS ABOUT THE LIST, NOT THE GATING. A curated allowlist silently omits; nothing in # a green run says "a doc guard exists that I did not run." When adding a doc-scanning test # module anywhere in `tests/`, add it HERE in the same commit, and confirm it needs no extras — # a module that cannot run on `[dev]` alone would red every docs-only PR, which is the failure