Skip to content

backlog: file #1244 -- a broken vault anchor changes no colour, so nobody sees it - #367

Merged
wshallwshall merged 2 commits into
mainfrom
claude/file-crossrepo-anchor-gap
Aug 13, 2026
Merged

backlog: file #1244 -- a broken vault anchor changes no colour, so nobody sees it#367
wshallwshall merged 2 commits into
mainfrom
claude/file-crossrepo-anchor-gap

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

Files one backlog item. Docs-only: docs/BACKLOG.md, +31 lines, 0 deletions, pure append.

#1244 -- an engine change that breaks a vault ASVS anchor produces no state change and no
attribution.

The coupling

The scorecard lives in the vault (private) and cites code in the engine (public). All 1,998
evidence anchors name engine paths, and 192 cite engine prose under docs/ -- the kind of line an
ordinary documentation edit rewrites without anyone thinking about security evidence.

Nothing engine-side references the scorecard, and nothing can: the engine tree ships the verifier
(scripts/asvs/scorecard.py) and zero scorecard data files, deliberately, because the data is what
stays private.

Filed narrower than proposed -- and the narrowing IS the finding

The premise I started from, "nothing checks that the two repos agree", is false. The vault gate's
own trigger block documents this hazard, records a live instance where an engine PR moved eleven
anchors while the gate stayed green, and a daily run was added to close it. The detector exists and
works, and a fix must start from it rather than rebuild it.

What remains:

gap consequence
not pre-merge found on the next daily run; the engine PR was where it was still reviewable
not attributed reports a broken anchor, not which change broke it -- bisect by hand
no state change the gate is already red at 11 failures and verify is not a required context

Gap 3 is the real defect. A detector whose output is a count inside an already-failing,
non-blocking job is indistinguishable from yesterday's run. That is why the live instance -- PR #361
deleting a sentence an anchor pins verbatim -- was caught by a person reading a diff, not by the
instrument built to catch it.

A constraint that makes the obvious build wrong

An engine-side check runs in a public repository with public logs. It must report the engine file
and line
that stopped matching and must not name the cell, the requirement id, or the count of
affected cells. A failing public log enumerating those hands out coverage by subtraction
(CLAUDE.md section 12). Report the line, never the cell. The natural implementation leaks, which is
what makes this non-trivial.

Prove-a-fix

Delete a line an anchor pins verbatim in an engine PR; assert the PR goes red before merge, naming
file and line and no cell id. Then the negative control: a PR touching an unanchored line stays
green, so the check discriminates rather than being always-red. A fix that only shortens the schedule
addresses gap 1 alone; making verify required without first clearing the standing 11 converts a
silent problem into a permanently blocked repository.

Verification

  • docs-only, +31 -0, exactly one new heading
  • counts asserted either side of the edit: 274 to 275 items, open 200 to 201, closed 74 to 74 --
    one new OPEN item, no status moved. That check is what catches a glyph being read as a status banner
    rather than as emphasis; a sibling session hit exactly that today and an item silently flipped to
    CLOSED.
  • added text carries only the OPEN banner and the amendment marker; no other glyph from the closed
    alphabet
  • the item records its own framing error rather than presenting the narrow version as the original
    intent

…body sees it

The scorecard lives in the vault and cites engine code: 1,998 evidence anchors name engine paths and
192 of them cite engine prose under docs/, the kind of line an ordinary documentation edit rewrites
without anyone thinking about security evidence. Nothing engine-side references the scorecard and
nothing can, because the data is what must stay private.

Filed narrower than proposed, and the narrowing is the finding. The premise "nothing checks that the
two repos agree" is FALSE: the vault gate's own trigger block documents this hazard, records a live
instance where an engine PR moved eleven anchors while the gate stayed green, and a daily run was
added to close it. The detector exists and works.

What remains is that it cannot block pre-merge, cannot attribute a break to an engine change, and --
the real defect -- lands in a gate that is ALREADY RED at eleven failures and is not a required
context. A twelfth failure moves no colour, no mergeability and no notification, so it is
indistinguishable from yesterday's run. Detection that cannot change state is not detection anybody
sees, which is why the live instance was caught by a person reading a diff.

The fix has a constraint that makes the obvious build wrong: an engine-side check runs in a PUBLIC
repository with public logs, so it must report the file and line that stopped matching and must not
name the cell or the requirement id. A failing public log that enumerates those hands out coverage by
subtraction. Report the line, never the cell.

The item records its own framing error rather than presenting the narrow version as the original
intent. It was first proposed as one item covering a whole class, generalised from three sightings in
one evening. A class has no prove-a-fix so it could never be closed, and bundling limbs with different
fixes is what splitting #1236 out of #1131 existed to prevent. Two of the three sightings did not
survive: one had resolved itself, and the third was carried second-hand, never opened, and on
re-reading was a different mechanism entirely.

Counts asserted either side of the edit: 274 -> 275 items, open 200 -> 201, closed 74 -> 74. Exactly
one new OPEN item and no status moved, which is the check that catches a glyph being read as a banner.
@wshallwshall
wshallwshall enabled auto-merge (squash) August 13, 2026 14:01
Pure tail collision: main gained #1246 and #1245's amendment while this branch appended #1244.
Both sides only add. Resolved as a union, #1244 then #1246.

The union is safe here and that was measured rather than assumed, because a union is exactly the
resolution that silently reinstates a line one side deleted and the other still carries as
context. Neither side deletes: main since the merge base is +29 -0, this branch is +31 -0.

Verified after resolving:
  delta vs main        items +1, open +1, closed +0   -- adds exactly one OPEN item, #1244
  deletions vs main    0
  duplicate headings   none
  conflict markers     0
  files differing      docs/BACKLOG.md only

Item numbers are left in merge order rather than re-sorted. The tail now reads 1242, 1245, 1244,
1246, which looks wrong and is not: this file has never been strictly ascending -- main carries
eight descending steps of its own (344/343, 1011/1002, 1022/1019 among them) -- and nothing reads
or enforces the order. Re-sorting would move lines this branch does not own to satisfy a rule that
does not exist.
@wshallwshall
wshallwshall merged commit 66db8ca into main Aug 13, 2026
32 of 34 checks passed
@wshallwshall
wshallwshall deleted the claude/file-crossrepo-anchor-gap branch August 13, 2026 14:34
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