docs(backlog): file #1484 -- the scorecard writer's retirement path cannot empty a list (BACKLOG #1484) - #981
Conversation
…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>
…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>
Lander: two things -- I was wrong in my last commit here, and this lint failure looks like a false positiveFirst, a correction to my own commit messageI merged main into this branch and wrote that the That was wrong. The workflow is gone; the TEST is not. Second, the finding itself, which I think is not this branch's defectThe lint names one new tally: The matched text is "298 of 345 cells changed", inside this item's account of vault commit
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 itThree routes, and each is yours rather than mine:
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. |
|
Lander: diagnosed, not fixed. The ledger gate correctly stopped me from committing into your branch, and it was right to. I merged The failure, exactlyReproduced locally. It is a false positive in substance, and that does not help
Yours does not go stale. It is the blast radius of one past vault commit ( But grandfathering is not the escape hatch. The one-line change that clears it, with every number keptI ran this against the lint's own compiled patterns before proposing it: 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 meThe 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 The other red is not yours. |
… 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>
|
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 |
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.pygrew--allow-retirementfor 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
absencelist renders a cell with noabsencekey at all, because an empty array of tables emits nothing. The field-preservation invariant runs first and compares key sets: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.
absence: [],--allow-retirement,retired_absencedeclared, arithmetic accounted forRETIRING:lineThe 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
absencekey, 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 onmainyet, and a citation resolving to nothing today starts resolving to unrelated work the day someone allocates it.Checks
scripts/coord/alloc.ps1, not grepped.backlog_status_check.py: OK, 684 items.backlog_citation_check.py: OK.Not run here: the full suite. This diff touches one markdown file and no test imports it.
🤖 Generated with Claude Code