From 5925551860044143fd278111cfc9c0812ec9183e Mon Sep 17 00:00:00 2001 From: Polichinl Date: Wed, 12 Aug 2026 22:22:31 +0200 Subject: [PATCH] =?UTF-8?q?fix(tests):=20#243=20=E2=80=94=20match=20the=20?= =?UTF-8?q?pair,=20and=20let=20the=20corpus=20decide=20when=20the=20scan?= =?UTF-8?q?=20is=20finished?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit THE PARSE IS GONE. The scan no longer reads a line, captures what it thinks the value is, and compares. It knows both halves before it starts, so it matches NAME=VALUE directly, with findall rather than one match per line. Every blindness C-57 records came from that parse — the swallowed comment, the missing terminator, the six-character lookbehind, the second assignment on a table row. None is expressible now. The two halves need not correspond: `APPWRITE_X=` is the ordinary copy-paste slip, and it names the coordinate that DECLARES the value rather than the one the line assigns. THE SECRET EXEMPTION IS GONE. An inline name literal, sitting under a comment saying the scope came from the declared partition and not from an inline list. Measured, [secret] carries no values today, so removing it changes nothing now and closes the hole the day one gains a value. THE STOPPING RULE IS A TEST. It enumerates every line in this repository's own tracked markdown that assigns a declared coordinate and fails if the matcher cannot read one. So the form list comes from the corpus: a form no document here uses is not a gap, and a new form is taught in the same change as the document that introduces it. That is what stops a fifth widening, which is what this story was for. ALL FOUR SCOPE MUTATIONS ROUTED FROM #242 ARE CLOSED, by one richer fixture rather than four tests — two copies, two sections, one short, one in markdown. Each element defeats a specific narrowing: stopping at the first finding, reading only `target`, reinstating a length floor, skipping the markdown branch. Measured surviving before, caught after. Plus one small test for a package module that does not parse. AND AN EXCLUSION LIST WAS BUILT AND THEN DELETED, which is the part worth keeping. The plan called for excluding values spelled like this repository's own code. Measured against what the AST branch actually sees — non-docstring string constants — THE COLLISION DOES NOT EXIST: the only registry values appearing that way are the two contract rows, and contract is MIRRORED rather than CONSUMED, so it is never in the scanned set. I had built a narrowing of a security scan for a problem this branch does not have. Deleted; the latent version is recorded in C-97. Register: C-57 gets the mitigation, in one place, and the docstrings point at it rather than restating it — the one-home rule, applied for the first time. tests/*.py 9200 -> 9308. The budget is still the wrong way and I am not going to pretend otherwise; see the story comment. Suite 415 passed / 1 skipped / 40 xfailed, ruff clean. Closes #243. Epic #241. Co-Authored-By: Claude Opus 5 (1M context) --- reports/technical_risk_register.md | 14 +++ tests/test_env_declaration.py | 163 +++++++++++++++++++---------- 2 files changed, 119 insertions(+), 58 deletions(-) diff --git a/reports/technical_risk_register.md b/reports/technical_risk_register.md index 8071d55..f2ebe2d 100644 --- a/reports/technical_risk_register.md +++ b/reports/technical_risk_register.md @@ -1849,6 +1849,20 @@ The shared cause is not a regex bug. **The pattern described one way of writing **The residual, which is deliberate and should not be "fixed".** This remains a *syntax* match. A value merely named in a sentence — "the six stranded documents in ``" — is not a copy and does not fire. An earlier draft that matched any occurrence fired on a dozen documents, and the lesson recorded above applies to itself: a guard that cries wolf gets deleted, after which the real rule is unguarded (ADR-014 §3). The class this cannot catch is a value pasted into prose with no assignment syntax anywhere near it. That is accepted, because the alternative has been tried and was worse. +**Mitigation — landed 2026-08-12 (#243). The parse is gone; the scan matches the pair.** + +The scan no longer parses a line and compares what it captured. It knows both halves before it starts — the declared names and the declared values — so it matches `NAME = VALUE` directly, with `findall` rather than one match per line. Every blindness recorded above came from the parse: the swallowed comment, the missing terminator, the six-character lookbehind, the second assignment on a table row. None is expressible now. + +The two halves need not correspond: `APPWRITE_X=` is the ordinary copy-paste slip and is reported as a copy, naming the coordinate that *declares* the value rather than the one the line assigns. + +**The `secret` exemption is gone.** It was an inline name literal under a comment saying the scope came from the declared partition and not from an inline list. Measured, `[secret]` carries no values today, so removing it changes nothing now and closes the hole the day one gains a value. + +**The stopping rule is a test, not a paragraph.** `test_the_scan_understands_every_assignment_form_this_repo_writes` enumerates every line in this repository's own tracked markdown that assigns a declared coordinate, and fails if the matcher cannot read one. So the form list is derived from the corpus: a form no document here uses is not a gap, and a new form is added in the same change as the document that introduces it. That is what stops the fifth widening. + +**The four scope mutations routed from #242 are all closed**, by one richer fixture rather than four tests: two copies, in two sections, one short, one in markdown. Each element defeats a specific narrowing — stopping at the first finding, reading only `target`, reinstating a length floor, skipping the markdown branch. Plus one small test for a package module that does not parse, which must be refused rather than stepped over. All five measured surviving before, all five caught after. + +**And an exclusion list was built and then deleted**, which is the useful part. The plan called for excluding values spelled like this repository's own code. Measured against what the AST branch actually sees — non-docstring string constants — **the collision does not exist**: the only registry values appearing that way are the two `contract` rows, and `contract` is MIRRORED rather than CONSUMED, so it is never in the scanned set. The list narrowed a security scan for a problem this branch does not have. The residual is latent and recorded in C-97: the day someone writes a package directory name as a bare string constant, the scan will fire on it. + **Why this belongs on C-57 rather than in a new entry.** It is the same guard, the same failure direction, and the same lesson this entry already records one layer down: the earlier amendment found the scan's *scope* was never mutation-proven (it named `unfao` and missed `crafd`); this one finds its *matching* was never proven against the file formats it scans. Scope, matching, and now syntax-variant — three ways for a mutation-proven guard to be proven against the wrong thing. **⚠ CORRECTED 2026-08-12, and the correction is the same mistake one level up.** The amendment immediately above claims "thirteen forms are proven caught" and cites `` set `NAME=value` before … `` as evidence the mid-sentence class is covered. An independent review supplied twenty-nine forms and **fifteen missed**, including the *unbackticked* form of that very example: `set NAME=value before running` captures `'value before running'`. The cited case passes only because of its backticks. Measured, not argued. diff --git a/tests/test_env_declaration.py b/tests/test_env_declaration.py index 2672ed6..5a59f7c 100644 --- a/tests/test_env_declaration.py +++ b/tests/test_env_declaration.py @@ -1111,6 +1111,7 @@ def _docstring_nodes(tree: ast.AST) -> set[int]: return out + def test_no_coordinate_value_is_copied_into_this_repo(): """The registry's own rule: *"never bake a value into code, an example, or a dataclass default."* Consumers READ and VALIDATE; the launcher supplies values. @@ -1143,7 +1144,7 @@ def test_no_coordinate_value_is_copied_into_this_repo(): # environment. The no-copy rule protects values the launcher supplies; a mirror is # the inverse by construction. scanned_sections = tuple( - name for name, role in _TABLE_ROLE.items() if role == "CONSUMED" and name != "secret" + name for name, role in _TABLE_ROLE.items() if role == "CONSUMED" ) # value -> every coordinate declaring it. A list, because two coordinates may share # a value and measured against the live registry two pairs do. @@ -1156,7 +1157,6 @@ def test_no_coordinate_value_is_copied_into_this_repo(): if isinstance(body.get("value"), str) and body["value"].strip(): declared_by_value.setdefault(body["value"], []).append(name) values = set(declared_by_value) - copied = [] for source in sorted(_PKG.rglob("*.py")): tree = _parsed(source) @@ -1187,46 +1187,21 @@ def test_no_coordinate_value_is_copied_into_this_repo(): # and the identical lesson: when a guard cries wolf, the matching is wrong before the # scope is (ADR-014 §3). # - # The copy is a value **assigned to its own coordinate name** — `APPWRITE_X=value` — - # which is a reader's instruction to configure with that literal. That is precise - # enough to have caught README.md and to ignore every legitimate mention. - # - # **It has been blind in this repository's own house style twice, and both blindnesses - # had the same cause: the pattern described one way of writing markdown.** - # - # 1. `(.+?)\s*$` swallowed an inline comment into the captured value, so - # `NAME=value # note` compared `'value # note'` against the registry and - # matched nothing. README.md's Configuration block is written in exactly that form. - # 2. Anchoring the name at `^\s*` saw only an assignment that *starts a line*. A - # markdown bullet — ``- `NAME=value` `` — a table cell, or an assignment quoted - # mid-sentence were all invisible, and all three are ordinary ways to document - # configuration. The fenced-block form the guard was written against is the one - # form this repository happens to use today. + # A copy is a declared NAME assigned a declared VALUE. Both halves are known before + # the scan starts, so it matches the pair rather than parsing the line and comparing + # what it captured. Three rounds of blindness came from that parse, all of them the + # same shape — the pattern described one dialect of markdown (register C-57). # - # So the name may be preceded by anything that is not part of an identifier, and the - # value ends at whatever terminates it in prose: a comment, a closing backtick, or a - # table pipe. A `#` inside a QUOTED value is legitimate, so the quoted forms are tried - # first and taken whole; only an unquoted value is truncated. + # The two halves need not correspond: `APPWRITE_X=` is + # the ordinary copy-paste slip, and is a copy. # - # This stays a syntax match, deliberately. A value merely *named* in a sentence is not - # a copy — C-57 recorded a draft that fired on a dozen such documents, and a guard that - # cries wolf gets deleted, after which the real rule is unguarded (ADR-014 §3). - assignment = re.compile( - r"(?:^|(?<=[\s`|>*-]))(?:export\s+)?(" + "|".join(sorted(_EXPECTED_NAMES)) + r")\s*=" - r"""\s*(?:"([^"]*)"|'([^']*)'|([^#`|]*?))\s*(?:[#`|].*)?$""" + # It stays a syntax match. A value merely *named* in a sentence is not a copy, and a + # draft that fired on those was deleted for crying wolf (ADR-014 §3, C-57). + names_alt = "|".join(sorted(_EXPECTED_NAMES)) + values_alt = "|".join(re.escape(v) for v in sorted(values, key=len, reverse=True)) + pair = re.compile( + rf"""(?