Skip to content

S2: replace the assignment parser with a pair matcher, and give the scan a stopping rule #243

Description

@Polichinel

Re-scoped 2026-08-12. #244 is merged into this issue. The first draft's acceptance criterion was "catch 29 forms"; that is the whack-a-mole this epic exists to stop. This story makes the scanner smaller and gives it a stopping rule.

Part of #241, Gate 2 — does not block PR #240. Registers C-57, C-89. Depends on #242.

Problem

The markdown half of the no-copy scan parses an assignment out of free-form prose and captures the value, then compares the capture against the registry. It has been widened four times and is still blind, and it always will be — capturing "the value" from prose is not a solvable problem, because registry values contain spaces:

  • NAME=Production Forecasts # note must capture two words
  • set NAME=production_forecasts before running must not capture three

Any terminator that fixes one breaks the other. Measured: the author proposed 13 forms and proved 13 caught; an independent review proposed 29 and 15 missed.

The scanner already knows both halves before it starts. It has the coordinate name and it has the registry value. Parsing is the wrong tool.

Work — this is a simplification, not a widening

1. Match the pair, do not parse the line. Per (name, value), with re.escape:

(?<![A-Za-z0-9_])NAME\s*=\s*["'`]?VALUE["'`]?(?![A-Za-z0-9_])

This deletes, in one move: the terminator problem, the six-character lookbehind allow-list, the once-per-line search, the quoted/unquoted alternation, and the captured value that #242 had to stop printing. There is nothing to capture, so there is nothing to leak. Cost is 17 names × ~11k lines, which is nothing.

Expect the diff to be net negative. If it is not, the design is wrong — say so and stop.

2. The secret exemption goes (was #244). scanned_sections filters _TABLE_ROLE for CONSUMED and then subtracts secret by name literal, two lines under a comment saying the scope comes from the declared partition and not from an inline list, with no reason recorded anywhere. Measured: [secret] has 7 rows, none carries a value, so the clause removes zero entries today — and the isinstance(..., str) filter already does that job. Its only effect is a future hole in the one table class whose leak matters most.

3. Exclude the package-name collisions by declaration (was #244). The registry's two shortest values are this repo's own package directory names — the exact contents of PARTNER_PACKAGES — compared by exact string equality against every non-docstring string constant under views_postprocessing/. The next logging.getLogger(<partner>) fails a security guard for code that copied nothing, and a guard that cries wolf gets deleted (ADR-014 §3). Exclude them by name with the reason written down, not by a length threshold that excluded them as a side effect. State the hole plainly: a real copy of a value that equals a package name is not caught by the AST branch.

Stopping rule — the actual deliverable

Write this into the test file:

The scan is finished when, for every configuration-shaped line in this repository's own tracked documents (git ls-files '*.md'), the matcher either understands that line's form or the form is on a declared exempt list with a reason.

The form list is derived from the corpus, never from imagination. A form no document here uses is not a gap. When a new document introduces a new form, the form is added in the same change as the document — never speculatively, never because a review invented it.

Make that rule a test: enumerate candidate lines from the corpus, assert each is understood or exempt. It goes red exactly when someone writes a new form, which is the only moment widening is warranted.

The 29 forms from the review are a one-time seed, not a standard. Seed the corpus test with the forms this repo actually uses and move on.

Acceptance criteria

  • git diff --stat for tests/test_env_declaration.py is net negative for this story.
  • The corpus stopping-rule test exists and is written above the matcher.
  • Injecting a real coordinate value in the form README uses fails the scan; the message names no value (holds S1: the no-copy scan must not print what it forbids #242's invariant structurally).
  • A [secret.*] row gaining a value is scanned; mutation-proven.
  • logging.getLogger(<a partner name>) in a package module does not fail; a real coordinate value does.
  • The residual is stated: a value in prose with no assignment syntax near it is not a copy and must not fire.
  • C-57 amended with the measured facts and the stopping rule.

Labels

story, implementation, testing

Metadata

Metadata

Assignees

No one assigned

    Labels

    implementationCode implementation workstoryA single reviewable unit of an epictestingTest/parity/validation work

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions