Skip to content

fix(phi-scan): a target enumerated and never read refuses the scan - #113

Merged
NSchatz merged 7 commits into
mainfrom
phi-scan-completeness-rule
Aug 11, 2026
Merged

fix(phi-scan): a target enumerated and never read refuses the scan#113
NSchatz merged 7 commits into
mainfrom
phi-scan-completeness-rule

Conversation

@NSchatz

@NSchatz NSchatz commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

What was wrong

--allow-fixture withdrew a path from the target list and the run reported on whatever remained, so the withdrawal never showed up in the verdict. Four argv shapes printed [phi-scan] OK, no hits at exit 0 over a corpus holding a live, detectable violator:

argv why it was clean
phi-scan <clean> --allow-fixture <violator> the seed read paths.length > 0 ? paths : [...allowFixtures], so with a positional present the flag was a silent no-op and the violator was never ADMITTED to the run rather than withdrawn from it
phi-scan <violator> --allow-fixture <violator> a floor of one at whole-run scope: the entire target list was withdrawn and the empty result reported clean
phi-scan --allow-fixture <violator> the same floor with no positional at all, which reads to a caller like a full-corpus sweep
phi-scan --staged --allow-fixture <violator> the identical floor on the route a commit is actually blocked on

In every one of them the file was validated, checked against the override log, and opened never. CI could print a clean verdict over a corpus it never opened.

What changed

A target this run enumerated and never read refuses (exit 2), in every mode, naming the paths. The comparison is a set difference, never a size: a count counts the targets that did get read, which is exactly the arithmetic that hides which ones did not.

A second tier refuses a bypass naming a path the run does not enumerate, under its own message, because such a flag subtracts nothing. --allow-fixture no longer selects the mode, and in paths mode it is unconditionally unioned into the target list (deduped by repo-relative path) so it means the same thing in every argv.

Hits are printed before the post-sweep refusals, so a run that is both incomplete and carrying hits prints both.

What it costs, stated rather than left to be discovered

--allow-fixture can no longer reach exit 0 in any mode. The flag, phi-scan-overrides.md and the log gate all stay, so an attempt is recorded and then REFUSED rather than silently honored, and scripts/phi-allow-list.txt is now the only mechanism that reaches a clean run. The hit footer no longer advertises the flag: a printed remedy that walks a developer from exit 1 into exit 2 is the same defect as one that reaches a false green, with the sign flipped.

The vanish tolerance (Target.tolerateVanish) is the one accounted-for non-read and is subtracted from the difference, or the ccda@0.0.5 publish-time refusal comes straight back. It cannot launder a bypass: a bypassed path is skipped before scanTarget is ever called, so it can never enter the vanished set.

The null result

There was no real unread corpus here. Measured at 1978bf7, all-mode enumerates 141 paths and reads all 141, nothing tolerated, nothing unread. The figures are dated to that commit rather than asserted as current, because the .changeset/ entry shipping this raises the tracked figure by one until a release consumes it. That 141 is not the tracked count wearing a different hat: 141 tracked, minus the one declared literal exclusion, is 140 in-scope tracked paths, plus the .git gitfile a submodule checkout carries in place of a directory (a pre-existing, already-filed observation). The four states were reproduced with planted fixtures, not found. ncpdp reached the same null result on the union half.

The positive control

Nine new cases. The last one runs the same argv against a copy of the scanner with the rule's one line mutated out, asserts the mutation landed (so it cannot go vacuous when the rule is reworded), and asserts the mutant reproduces both pre-fix results exactly: the HITS code where the corpus still holds a readable violator, and OK, no hits at exit 0 where it does not. Run against the pre-change scanner, seven of the nine red; the two that pass are the anti-vacuity premise and the no-false-refusal case, which must hold on both trees.

Every assertion pins an exact exit code and the message naming which of the three exit-2 tiers fired. This repo shipped expect(merged.status).not.toBe(0) passing on a git merge that died at exit 128 on a committer identity before it touched the index (#111): a premise assertion that accepts any non-zero exit accepts a crash.

Verification

  • scripts/verify.sh ccda: green.
  • cd config && pnpm drift: the phi-scan completeness probe drift for ccda is gone. Before: phi-scan reported only its HITS code (1) over a run that withdrew test/fixtures/phi-scan-probe-decoy.txt after enumerating it. After: no completeness line.

No published API, warning code or parser behaviour changes; this is the commit gate only.

…runs

The headline case's comment claimed `config`'s capability probe measured its
drift on that argv. It does not: the probe's graded run is
`[violator, decoy, --allow-fixture decoy]`, whose pre-rule result is the HITS
code (1), and the headline case's pre-rule result is exit 0. One run cannot
have produced both, so the comment marked the wrong test as the externally
graded one.

Comment only; no assertion, fixture or scanner behaviour changes.
@NSchatz
NSchatz merged commit c361b4a into main Aug 11, 2026
8 checks passed
@NSchatz
NSchatz deleted the phi-scan-completeness-rule branch August 11, 2026 16:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant