Skip to content

finding(gate): check-shell-escape-residue's content/docs row has no coverage tree, so narrowing or deleting it is still silent — the one root #7413 deliberately left unruled #8011

Description

@baozhoutao

Filed out-of-scope from #7413 (PR #8010). Unassigned. Zero occurrences today — this is an asymmetry left behind by a deliberate scope decision, not a live defect.

What

#7413 added COVERAGE_TREES to scripts/check-shell-escape-residue.mjs: trees in which every .md/.mdx must be reached by some SCAN_ROOTS row, red by file name otherwise. Two trees were declared:

export const COVERAGE_TREES = Object.freeze([
  { spec: '.claude', kind: 'dir' },
  { spec: 'skills', kind: 'dir' },
]);

content/docs was deliberately not declared, and the gate's docblock says so and why:

content/docs is deliberately NOT a coverage tree. Same mechanics, but a different claim: this gate's roots are the agent-facing surface (objectui#5151 ruled it, objectui#7403 widened it), and content/docs is published prose that happens to share the scan. Declaring coverage over it would assert a completeness promise about the docs tree that no card has ruled.

The asymmetry that remains

The skills coverage tree was included even though it is a tautology today (its row is the whole tree, 16 of 16), because a tautology is exactly what it stops being under the one edit nothing else catches: narrowing the row to a subtree, or deleting the row. Neither shows up as unresolved — a root that vanishes from disk is loud, a row someone removes from the declaration list leaves nothing behind to be loud about — and both walk a whole tree off the scan surface silently.

content/docs carries that exposure identically and now has no guard against it. Measured on e1545cf: 184 files, 1064 fenced blocks, i.e. 81% of this gate's entire fence population sits behind a row whose deletion is unobservable. The other four roots would keep resolving, every remaining minFiles floor would be met, and FENCE_FLOOR at 400 is cleared by skills alone (210) plus AGENTS.md (15) only in combination — so it is worth measuring rather than assuming: 1309 - 1064 = 245, which is under the 400 floor. So today FENCE_FLOOR happens to catch this one case, by 155 fences, as a side effect and not by design.

That accident is the interesting part. FENCE_FLOOR is documented as a collapse detector for a broken fence walker, explicitly not a coverage floor:

the floor is set far under that on purpose -- it catches a fence walker that stopped matching, not a day when the docs got shorter.

So the protection is incidental, undocumented, and disappears the moment either tree grows: once skills plus AGENTS.md plus .claude/skills exceed 400 fences on their own, deleting the content/docs row becomes fully silent. It is one root away from being the same class as #7403 and #7413.

The decision to make

Not mechanical — it is the same choice #7413 framed and declined to make unilaterally:

  1. Declare content/docs a coverage tree. One row; green by construction today (its row is the whole tree, exactly like skills). Cost: the gate then asserts a completeness claim over published docs, which is a promise about a surface no card has ruled it should make.
  2. Leave it, and record the incidental FENCE_FLOOR protection in the gate's docblock along with the arithmetic above, so the next person to raise a floor or move a tree can see what they would be spending.
  3. Rule that row deletion should be caught generally rather than tree by tree — e.g. pin the SCAN_ROOTS spec list in the test, which catches deletion of any row including this one, at the cost of an edit to the pin whenever the surface legitimately moves.

Option 3 is arguably the general form of the general form, and would make the skills coverage tree redundant. Worth ruling once rather than per root.

Not a duplicate of

Measured by the dev seat on #7413 while writing the exclusion into the docblock, unclaimed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions