Skip to content

fix(asvs): a scorecard cell whose own prose carries a banned glyph was unwritable (BACKLOG #1308) - #584

Merged
wshallwshall merged 13 commits into
mainfrom
claude/builder-2-1308-safe-writer
Aug 26, 2026
Merged

fix(asvs): a scorecard cell whose own prose carries a banned glyph was unwritable (BACKLOG #1308)#584
wshallwshall merged 13 commits into
mainfrom
claude/builder-2-1308-safe-writer

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

A vault scorecard cell whose existing prose already carried a banned character had become unwritable by the safe writer: every payload had to carry that residual forward, so every payload re-presented the character and got refused, however mechanical the edit -- a security record gone read-only through its own guard. docs/BACKLOG.md untouched (verified, count 0).

The fix compares the payload against the LIVE record and refuses only what the payload introduces, counted per codepoint rather than by presence -- presence would let a payload add a SECOND instance of the banned glyph to a cell that already carried one, which is new vocabulary, the exact thing the ban exists to stop.

Mutation-proved with three mutants: reverting to a whole-payload scan reds the two "still applies" arms; swapping the count for a presence check reds exactly one test (adding more of the same glyph) -- the one that shows counting is load-bearing rather than decorative; removing the guard entirely reds both refuse arms plus the pre-existing glyph-refusal test, confirming original coverage is untouched.

Deliberately not done: did not open the vault scorecard to count how many live cells become writable again -- that population is vaulted, and the fix is correct independently of the count. A number nobody can verify is worse in a commit than none.

Verification, explicitly scoped: tests/test_asvs_apply.py, 31 passed (was 26; the pre-existing 26 ran green before and after, so the change disturbs nothing). ruff/mypy strict clean. Full suite not run.

…s unwritable (BACKLOG #1308)

LEAD WITH THE UNWRITABILITY, NOT THE CHARACTERS. Read as a tidiness item this defers
forever; the actual symptom is a security record going read-only through its own guard.

`apply.py` scanned the WHOLE residual a payload carried and refused on any banned
character. But every payload must carry the residual forward, so a cell whose existing prose
already held one re-presented it on every pass and was refused every time -- however
mechanical the edit. The only route to touching such a cell was to edit prose the pass was
not about, which is a different act needing a different decision. The existing test says this
fired for real on 13.3.4.

THE FIX IS TO SCAN WHAT THE PAYLOAD INTRODUCES, NOT WHAT THE RECORD ALREADY CARRIES.
`_introduced_banned(payload, live)` returns the first banned codepoint the payload holds MORE
of than the live record does. `live` was already in scope at the check site.

COUNTED PER CODEPOINT, NOT PRESENCE, and that distinction is the whole safety margin.
Presence alone would let a payload add a SECOND warning sign to a cell that already had one
-- new vocabulary, which is precisely what the ban exists to stop. Counting refuses that
while allowing the character to be kept or MOVED, since neither introduces anything a later
reader could copy forward.

FAIL-CLOSED WHERE THERE IS NOTHING TO COMPARE AGAINST: a cell with no live counterpart has a
live count of zero for everything, so any banned character in a NEW cell is introduced and
refused. The comparison relaxes the scan only where a record exists to relax it against.

MUTATION-PROVED WITH THREE MUTANTS, each attacking a different property, each asserting its
anchor unique and the file hash CHANGED before scoring:

    A  the old whole-payload scan    2 red  exactly the two "still applies" arms
    B  PRESENCE instead of COUNT     1 red  ONLY "adding MORE of the same glyph"
    C  guard removed entirely        4 red  both refuse arms, plus the PRE-EXISTING glyph test

Three mutants, three distinct red sets, each matching its prediction exactly. B is the one
worth having: it reds a single test, which is what shows the counting is load-bearing rather
than decoration. C reding the pre-existing test_it_refuses_a_glyph_in_a_residual shows the
original guard's coverage survives untouched.

The five new tests drive the REAL main() against a record whose LIVE residual carries U+26D4,
so the reassuring arm and the alarming arm differ only in what the payload does with it -- a
suite that only ever fed it clean records could not tell the fix from the defect.

Verification, with scope: tests/test_asvs_apply.py, 31 passed (was 26, all 26 still green
including the refuse arm), on .venv/Scripts/python.exe (CPython 3.14.6 non-freethreaded,
seven CI extras). ruff 0.15.22 (== the constraints.lock pin) check and format --check clean;
mypy strict clean on apply.py. NOT the full suite.

NOT DONE, deliberately: I did not open the vault scorecard and did not count how many live
cells this makes writable again. That coverage is vaulted, and the fix is correct
independently of the population -- a number I cannot show anyone is worse in a commit than
no number.

No test was removed or weakened. The ledger row is not mine to author and is not in this
commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@wshallwshall
wshallwshall enabled auto-merge (squash) August 25, 2026 18:08
@wshallwshall
wshallwshall merged commit c654c38 into main Aug 26, 2026
40 of 41 checks passed
@wshallwshall
wshallwshall deleted the claude/builder-2-1308-safe-writer branch August 26, 2026 09:32
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