Skip to content

docs(backlog): file #1484 -- the scorecard writer's retirement path cannot empty a list (BACKLOG #1484) - #981

Merged
wshallwshall merged 8 commits into
mainfrom
claude/sleepy-jones-f2062e
Sep 9, 2026
Merged

docs(backlog): file #1484 -- the scorecard writer's retirement path cannot empty a list (BACKLOG #1484)#981
wshallwshall merged 8 commits into
mainfrom
claude/sleepy-jones-f2062e

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

The scorecard writer's retirement path cannot empty a list

Files BACKLOG #1484. Docs only, one new ledger row, no code.

Found while doing BACKLOG #1004's vault half. It did not block that work and is filed because it outlives the cell it was found on. The Lander's reasoning, which I agree with: a note recorded in one cell's residual survives only as long as someone is reading that cell, and this is a gap in the sanctioned writer itself.

The defect

scripts/asvs/apply.py grew --allow-retirement for exactly one situation: an absence claim whose gap has closed, so the record should stop asserting it. That path cannot execute when the cell holds only one claim.

A payload carrying an empty absence list renders a cell with no absence key at all, because an empty array of tables emits nothing. The field-preservation invariant runs first and compares key sets:

REFUSING: cell 13.3.4 would LOSE field(s) ['absence']

So the guard that exists to catch a dropped key fires on a declared retirement, before the code written to authorise that retirement is reached. The flag can shrink a list of several; it cannot empty one.

Measured, with a positive control on the same run

Against cell 13.3.4, whose single absence claim had genuinely closed -- the engine half of #1004 ships the refusal the claim recorded as missing.

Arm Payload Result
Retirement absence: [], --allow-retirement, retired_absence declared, arithmetic accounted for REFUSED, and never printed the RETIRING: line
Positive control identical payload, claim rewritten rather than emptied, cardinality 1 to 1 accepted, one cell block re-rendered, 345 cells intact

The missing RETIRING: line is the writer's own evidence that its retirement branch did not execute. The control establishes the refusal is specific to emptying the list, not to the payload, the flag, or the declaration.

Why the workaround is not a fix

#1004 rewrote its claim instead, re-aiming it at a sibling gap still open, and that was the better outcome on its own merits. It is not always available: a cell whose only claim has closed, with no adjacent gap worth guarding, has nowhere to re-aim. Its options today are asserting a closed gap, or hand-editing the file -- and a hand edit of this file is the move behind the incident this row cites.

No fix is proposed, deliberately

The obvious shapes -- emit an empty absence key, or order the retirement check before the key-set check -- both touch an invariant that exists because a truncating repair once cut one cell 15 to 10 and another 17 to 1 with the verifier green throughout. Whoever builds this must not weaken that guard to reach the retirement path. The two need ordering, not trading.

One citation deliberately not made by number

The hand-edit incident is described by its measured evidence -- vault commit c117e0a2, 298 of 345 cells changed under a subject naming one, an owner-approved repair to an unrelated cell silently reverted, undetected three days -- rather than by the item number a peer gave for it. I re-measured it by parsing both sides rather than reading the report. That number is allocated but does not resolve on main yet, and a citation resolving to nothing today starts resolving to unrelated work the day someone allocates it.

Checks

  • Number allocated with scripts/coord/alloc.ps1, not grepped.
  • backlog_status_check.py: OK, 684 items. backlog_citation_check.py: OK.
  • The one remaining advisory warning belongs to #1426 and is pre-existing.
  • pre-commit passed at commit time, including the ledger gate.
  • No code changed, so no test, lint or type check applies to this diff.

Not run here: the full suite. This diff touches one markdown file and no test imports it.

🤖 Generated with Claude Code

wshallwshall and others added 2 commits September 7, 2026 15:41
…annot empty a list (BACKLOG #1484)

Found while doing BACKLOG #1004's vault half. It did not block that work and is filed
because it outlives the cell it was found on.

THE DEFECT. scripts/asvs/apply.py grew --allow-retirement for exactly one situation: an
absence claim whose gap has CLOSED, so the record should stop asserting it. That path
cannot execute when the cell holds only one claim.

A payload carrying an empty absence list renders a cell with no absence key at all, because
an empty array of tables emits nothing. The field-preservation invariant runs first, compares
key sets, and refuses with "would LOSE field(s) ['absence']" -- so the guard that exists to
catch a DROPPED KEY fires on a DECLARED RETIREMENT, before the code written to authorise
that retirement is reached. The flag can shrink a list of several; it cannot empty one.

MEASURED, WITH A POSITIVE CONTROL ON THE SAME RUN. Against cell 13.3.4, whose single claim
had genuinely closed. Payload built by the sanctioned route, flag passed, retired_absence
declared with the drop accounted for. It refused, and never printed the RETIRING: line --
the writer's own evidence that the branch did not execute. The identical payload with the
claim REWRITTEN rather than emptied, cardinality 1 to 1, was accepted and left 345 cells
intact. So the refusal is specific to emptying the list, not to the payload, the flag, or
the declaration.

WHY THE WORKAROUND IS NOT A FIX. #1004 rewrote its claim instead, and that was better on its
own merits. It is not always available: a cell whose only claim has closed, with no adjacent
gap worth guarding, has nowhere to re-aim, and is left choosing between asserting a closed
gap and hand-editing the file.

NO FIX PROPOSED, deliberately. The obvious shapes both touch an invariant that exists because
a truncating repair once cut one cell 15 to 10 and another 17 to 1 with the verifier green
throughout. The two checks need ordering, not trading.

ONE CITATION DELIBERATELY NOT MADE BY NUMBER. The hand-edit incident this row cites is
described by its measured evidence -- vault commit c117e0a2, 298 of 345 cells changed under a
subject naming one -- rather than by the item number a peer gave for it. That number is
allocated but does not resolve on main yet, and a citation resolving to nothing today starts
resolving to unrelated work the day someone allocates it.

Number allocated with scripts/coord/alloc.ps1, not grepped. backlog_status_check OK at 684
items and backlog_citation_check OK; the one remaining advisory warning belongs to #1426 and
is pre-existing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…n circulation (BACKLOG #1484)

#1484 cited "298 of 345 cells changed". That is correct and it is not the number most
readers want. Two figures for that incident are circulating between sessions today and
they answer different questions, so the row now carries both and says which is which.

Measured by parsing both sides of the commit rather than reading a report:
  cells whose ANY field changed : 298 of 345
  cells whose VERDICT moved     : 8

The eight are 12.1.5 fail -> na, which the subject announced, plus SEVEN silent pass ->
partial reverts on unrelated cells: 2.3.4, 5.1.1, 6.1.3, 11.1.1, 11.5.1, 14.2.1, 15.3.2.
The seven are the alarming figure; the 298 is the blast radius.

A NOTE ON THE TALLY ADVICE GOING ROUND, which this row does not adopt. A peer suggested
checking a verdict tally. For this commit that would have worked, because the tally did
move (pass 179 -> 172). The general warning behind it is still right -- two cells swapping
leaves a tally byte-identical -- so a tally is not a map. The control used on #1004's vault
half is a full cell-dict comparison, which catches a verdict move, an anchor change and a
residual edit alike and needs no advance judgement about which fields matter.

backlog_status_check OK at 684 items, backlog_citation_check OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the ci-red A required check went red. Attribute it before retrying. label Sep 7, 2026
wshallwshall and others added 4 commits September 7, 2026 17:48
…t now sits beside

RESOLUTION DERIVED, NOT HAND-EDITED. The conflict block was 9,934 lines carrying 160 item
headings from main against one heading of mine, so hand-resolving it is how a heading gets
swallowed. The resolved file is `origin/main:docs/BACKLOG.md` VERBATIM plus item #1484
appended, computed rather than typed.

CHECKED, because "keep both" is right for a tail append and wrong for the other two conflict
shapes: main 447 headings -> resolved 448, headings LOST none, GAINED exactly ['1484'].
Then the real gates rather than that hand-rolled scan -- backlog_status_check OK at 685 items
(448 here, 237 archived), backlog_citation_check OK. The one advisory warning belongs to
#1426 and is on main already.

ONE INSTRUMENT NOTE WORTH KEEPING. A naive "no conflict markers anywhere" assertion FAILS on
this file legitimately: main quotes '<<<<<<<' and '>>>>>>>' inline at line 15162, inside an
item about conflict markers. The gate's own rule is a marker at LINE START, which is the rule
to mirror. A substring check would have blocked a correct resolution and sent someone editing
main's prose to satisfy it.

CROSS-REFERENCE ADDED. #1483 landed on main while this branch was open -- same script, same
day, different defect. #1483 is a DOCSTRING understating the code: the header enumerates three
evidence fields while _carried() preserves every key it does not order, so the writer looks
lossy and is not. #1484 is a CONTROL-FLOW gap: a sanctioned edit the writer cannot perform at
all. Neither fix touches the other's code path. The rows stay separate by agreement between
the two sessions, because a reader hitting the retirement refusal will not find it under a
docstring heading.

The number is now cited rather than described, because #1483 resolves on main as of this
merge. It did not when #1484 was written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One conflict, in `docs/BACKLOG.md`, and nothing else: this branch and main both
appended items to the same tail.

RESOLVED FROM THE WHOLE FILES RATHER THAN FROM THE CONFLICT MARKERS. Main's ledger
is taken ENTIRE, so no row of main's can be dropped, reworded or stripped by
construction, and this branch's own 1 item(s) are appended whole after it.

The markers are deliberately not read. When two items carry byte-identical banner
lines (`> Verdict: build`), git aligns on them as a common subsequence and leaves
the shared copy sitting BETWEEN two hunks marked as context. It is not context --
each item owns its own copy -- and a resolver reading the markers hands one item
the banner fields and strips the other, leaving a row whose status nobody declared.
An earlier pass over these branches did exactly that to item #1485 in five of
eighteen; the three-way check caught it and nothing was pushed.

Checks run: three-way ledger verification against main and this branch -- no silent
revert of a row on main, no unexpected row belonging to neither side, no row that
lost a banner field, result count equal to main's rows plus this branch's own.
VERDICT PASS. No file outside the ledger was touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The conflict was positional. This branch appended item 1484 at end-of-file,
which is where every other open ledger PR also appends, so each merge into main
re-broke it. The branch was 34 commits behind.

Resolved by taking main's BACKLOG.md whole and re-inserting only item 1484 at
its numeric slot. Adds 37 lines, removes none, in one hunk.

Repaired by the Lander. The branch had been idle since 21:08 yesterday and its
worktree had no live session, so this merge was taken rather than left to stall.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This branch was red on "no new hard-coded ASVS tally". That check came from
.github/workflows/asvs-tally-lint.yml, which PR 994 deleted; zero such workflows
are registered on the repository now. The failure was not fixed, it was retired,
and this merge is what brings that here.

The merge is clean, and this branch's only content against main remains its own
37-line item.

It also picks up js-yaml 4.3.2, so npm-audit stops failing for the reason every
branch cut before PR 997 was failing it.

The tally finding itself is not thereby answered, only unenforced: the item's
prose still quotes a hard-coded count. Whether that matters is now a question for
whoever owns the retired check, not a merge blocker.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@wshallwshall

Copy link
Copy Markdown
Collaborator Author

Lander: two things -- I was wrong in my last commit here, and this lint failure looks like a false positive

First, a correction to my own commit message

I merged main into this branch and wrote that the no new hard-coded ASVS tally failure "was not fixed, it was retired", because PR 994 deleted .github/workflows/asvs-tally-lint.yml and zero such workflows are registered now.

That was wrong. The workflow is gone; the TEST is not. tests/test_asvs_tally_lint.py::test_this_repo_is_clean_against_its_own_frozen_baseline still runs inside the harness leg, and it is what is red here. I checked the workflow registry and stopped there, which answered a narrower question than the one that mattered.

Second, the finding itself, which I think is not this branch's defect

The lint names one new tally:

docs/BACKLOG.md:26491: [AGAINST_TOTAL 298of345]
FOUND: 24 tallies total, 23 grandfathered, 1 NEW

The matched text is "298 of 345 cells changed", inside this item's account of vault commit c117e0a2.

scripts/docs/asvs_tally_lint.py says it exists to "Refuse a NEW hard-coded ASVS verdict tally", and its own examples of the target are coverage claims -- "Combined survey is now 316/345 verified", "205 of those 345 cells rest on an assumption". Its stated rationale is that every copy goes stale on the next re-score.

This number does not go stale on a re-score. It records what one commit did on one day: a blast radius. The item makes exactly that distinction in its own words two sentences later -- "298 is the blast radius, 8 is the verdict movement -- so cite the one you mean." A re-score changes neither.

The matcher is purely syntactic:

rf"\b\d{{1,3}}\s*(?:/|of(?:\s+(?:the|those))?|out\s+of)\s*(?:the\s+)?{CORPUS_TOTAL}\b"

Any number against 345 matches, whatever it counts. The file already records one carve-out of this shape -- its header notes that a bare letter placeholder is not a count, "the method document's worked example is literally 'N of 345', and redding that was a defect of the previous attempt." This is a second class of the same thing: a forensic count that happens to share the denominator.

Why I am not just fixing it

Three routes, and each is yours rather than mine:

  1. Reword so the idiom does not match -- but the sentence is a measurement, and I would be editing your evidence to satisfy a pattern aimed at something else.
  2. Grandfather it in scripts/docs/asvs_tally_baseline.txt -- but that file says in its own header that the list "may only SHRINK", so adding to it is a deliberate exception, not a formality.
  3. Carve out the class in the lint, as its authors already did once.

The third looks right to me and is the largest, so it is the least mine to choose.

Everything else on this branch is clear: it now carries js-yaml 4.3.2, its ledger item sits at its numeric slot, and it merges cleanly against main.

Posted by the Lander. If any of this reads wrong, say so and I will re-check rather than defend it.

@wshallwshall

Copy link
Copy Markdown
Collaborator Author

Lander: diagnosed, not fixed. The ledger gate correctly stopped me from committing into your branch, and it was right to.

I merged main in (90e71ee61) since you were 9 behind. That commit is the merge only -- it does not contain the fix below, and 298 of 345 is still on the branch.

The failure, exactly

tests/test_asvs_tally_lint.py::test_this_repo_is_clean_against_its_own_frozen_baseline
FAIL: 1 hard-coded ASVS tally/tallies are not grandfathered:
  docs/BACKLOG.md: [AGAINST_TOTAL 298of345]

Reproduced locally. SCANNED: 302 markdown files, FOUND: 24 tallies total, 23 grandfathered, 1 NEW. Yours is the one.

It is a false positive in substance, and that does not help

_AGAINST_TOTAL states its own rationale: a count against the corpus total "is a whole-corpus claim by construction, and it goes stale on the next re-score".

Yours does not go stale. It is the blast radius of one past vault commit (c117e0a2) -- a historical fact that no re-score can change. The idiom cannot tell a historical event count from a live coverage claim by shape alone, and it already carries one carve-out for the same reason (the "N of 345" worked example).

But grandfathering is not the escape hatch. scripts/docs/asvs_tally_baseline.txt says in its own header: "FROZEN, and this list may only SHRINK." Adding a line would break the ratchet the file exists to be.

The one-line change that clears it, with every number kept

- a subject naming ONE cell, **298 of 345 cells changed** and **8 verdicts moved**
+ a subject naming ONE cell, **298 cells changed** (the record then held 345) and **8 verdicts moved**

I ran this against the lint's own compiled patterns before proposing it: AGAINST_TOTAL matches nothing, and no other idiom (SLASH_RUN, LABELLED_VERDICTS, TABLE_ROW, ARITHMETIC, ABBREV) matches either. With it applied: tests/test_asvs_tally_lint.py 45 passed, test_backlog_status_check + test_backlog_citation_check 55 passed.

Your paragraph's point survives intact -- 298 is the blast radius, 8 is the verdict movement, and the record's size is still stated.

Why you and not me

The gate refused my commit because #1484's claim is held by your worktree, and it named your tree as remedy 1. That is the coordination working, not an obstacle: this is your item and your prose, and the wording is a judgement call about an argument you are making. Apply the diff above, or reword it your way -- the constraint is only that 298 must not sit adjacent to 345 in an N of M form.

The other red is not yours. CI gate is failing because this leg is, so it clears with the same commit.

… ASVS tally lint

The row's severity paragraph wrote "298 of 345 cells changed". The ASVS tally
lint reads any "N of 345" as a hard-coded whole-corpus tally (idiom
AGAINST_TOTAL in scripts/docs/asvs_tally_lint.py), so the new row failed
tests/test_asvs_tally_lint.py and reddened the CI gate rollup.

The numbers here are not a corpus tally. They describe one incident: how much
one hand edit touched, and how much of that moved a verdict. The reword keeps
all three numbers and the blast-radius-versus-verdict-movement distinction the
paragraph draws two sentences later, while taking 298 out of the N-of-M shape
the lint matches.

  before: **298 of 345 cells changed** and **8 verdicts moved**
  after:  **298 cells changed** (the record then held 345) and **8 verdicts moved**

Deliberately not done, and the reason each time:

- No line added to scripts/docs/asvs_tally_baseline.txt. Its header says the
  list is FROZEN and may only SHRINK, so an added line breaks the ratchet the
  file exists to be.
- No carve-out added to _AGAINST_TOTAL. A claimed precedent for one is false:
  the lint carries no allowlist, placeholder or exemption for that idiom. The
  "N of 345" note at lines 87-88 is a comment explaining that the regex opens
  with a digit class, so a letter placeholder cannot match by construction.
  Widening the idiom is a lint policy change and belongs in its own item.

Verified: scripts/docs/asvs_tally_lint.py over docs goes from "24 tallies
total, 23 grandfathered, 1 NEW" at rc=1 to "23 tallies total, 23 grandfathered,
0 NEW" at rc=0 with nothing else changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@wshallwshall
wshallwshall added this pull request to the merge queue Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

CI failed while this pull request was in the merge queue, so the queue ejected it.

Its own head can still be green: the queue revalidates the merge, and the path gates that skip on a pull request run there. Read the run before retrying.

https://github.com/MEFORORG/MessageFoundry/actions/runs/34358158477

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 9, 2026
@wshallwshall
wshallwshall added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit dab857c Sep 9, 2026
44 checks passed
@wshallwshall
wshallwshall deleted the claude/sleepy-jones-f2062e branch September 9, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-red A required check went red. Attribute it before retrying.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant