asvs(verifier): anchor form, a ref-stamped --status, sym/ctx validation, and counters that reconcile - #303
Open
wshallwshall wants to merge 6 commits into
Open
asvs(verifier): anchor form, a ref-stamped --status, sym/ctx validation, and counters that reconcile#303wshallwshall wants to merge 6 commits into
wshallwshall wants to merge 6 commits into
Conversation
…ry overstating what a run proves Measured 2026-08-09, vault origin/main (1a59e4a1) scorecard against engine tree c383eea -- quoted as a (file x ref) pair, because a count is a fact about one and this programme has already produced three wrong-base errors by dropping the qualifier: 1,980 anchors, 1,979 located, 1 GONE (15.3.1, from the JWS signing-key redaction; not this stream) form split: 1,479 code / 233 doc (173 docstring + 60 comment) / 267 foreign / 0 undetermined foreign by extension: 197 .md, 17 .ts, 16 .js, 13 .yml, 8 .toml, 16 scattered 500 of 1,979 (25.3%) resolve into prose or a non-Python file, which no structural or executable scheme reaches, ever -- and the record presented all 1,980 as code evidence 17 of 343 evidenced cells carry NO code anchor at all, derived here independently and agreeing exactly with the 2026-08-09 recut's figure WHAT CHANGED `form` is DERIVED at check time, never authored: `foreign` for a non-Python file, `doc` for a match landing inside a docstring or a `#` comment, `code` otherwise, and None -- reported as undetermined, never folded into `code` -- for Python that will not parse. Classified by DOCSTRING POSITION, not by a token mask. A docstring is the first statement of a module, class or function via `ast`; comments come from `tokenize`, so a `#` inside a string literal is not mistaken for one. A mask misfiles the Content-Security-Policy fragments in messagefoundry_webconsole/_security.py and the whole SQL Server and Postgres DDL as prose -- long, quoted, operator-free strings that read as English while being the literal subject of the control their cell cites. Position cannot make that mistake, and not because it is a better mask: it never asks the question. The token's START decides, not any overlap with its end. Measured, those two rules disagree on 57 of 1,712 located Python anchors, and every one of the 57 is a code statement whose recorded token runs past the end of the statement into a trailing comment. `doc` is a LABEL and never a demotion. It feeds no verdict, no completeness check and no exit code, and a test pins that: a cell evidenced only by prose stays green and stays complete. 17 cells rest genuinely on documentation, which for a documentation requirement is the correct ground. Only anchors that LOCATE are classified. A GONE or AMBIGUOUS token has no landing site, so giving it a form would be an invented number inside the one figure whose purpose is to stop the record overstating itself. The summary prints both populations rather than either alone. The summary line already said "resolved" rather than "verified" (8cd0d16). The split now rides beside it with its denominator, because "resolved 1,980" still reads as 1,980 pieces of code evidence. Also caches the per-file read and parse inside check_anchors: 1,980 anchors concentrate onto ~224 files. End-to-end run against the real record is 41.1s against a 40.2s baseline -- the classification is free, the cost is check_absences. PROVED BY MAKING IT FAIL Nine defects injected one at a time, each confirmed to have landed before the run: any string token treated as a docstring; undetermined defaulting to code; a `#` scan replacing tokenize; left-overlap replacing start; the form counted before the occurrence guards; the summary saying "verified" again; the split not printed; the split printed without its denominator; and doc anchors demoted to a problem. All nine were caught by a named test, and the tree returned green after each. Two negative controls: a file with no prose yields zero doc, and an empty run prints 0 of 0 (0.0%) rather than dividing by zero. ruff format --check, ruff check, mypy strict (-p scripts.asvs and the test file) and the 74 scorecard tests are clean.
BRIEF.md is the work order for this worktree. It rode along in the branch and would have landed on main. Kept on disk for the worker, excluded from the index.
…uoted without its ref Query mode step one, per ASVS-QUERY-MODE-SPEC-2026-08-09. Provenance line plus --status only: no --cell, no --cells-for, no --cells-since. THE DEFECT THIS CLOSES, DEMONSTRATED LIVE Two files on this machine, both named asvs-scorecard.toml, both real, measured 2026-08-09: vault working tree (31d4cd98) 105 partial, 3 fail, 4 needs-review, 1,978 anchors, 223 paths vault origin/main (1a59e4a1) 106 partial, 2 fail, 4 needs-review, 1,980 anchors, 224 paths Nothing in either reading says which is which. That is exactly how three wrong-base errors happened in one working thread on 2026-08-08/09 -- the number was right and the ref was unnamed. With the header the first reads BEHIND 38 upstream=origin/main remote-knowledge=36m and the second reads NO-GIT, because it is an extracted copy rather than a checkout and therefore not quotable at all. The spec's own measured case on that same vault checkout was BEHIND 37 at 23 minutes. WHAT LANDED # asvs-status scorecard=31d4cd98 engine=2e59dc6e+dirty # scorecard: freshness=BEHIND 38 upstream=origin/main remote-knowledge=36m # engine: freshness=CURRENT upstream=origin/asvs-verifier remote-knowledge=3m # generated=2026-08-09T09:40:28+00:00 freshness is one of CURRENT / BEHIND n / AHEAD n / DIVERGED / NO-UPSTREAM / NO-GIT / UNRESOLVED, and it is ALWAYS populated. NO-GIT and UNRESOLVED are extensions that exist because of the never-silent rule, not in spite of it: calling a non-repo NO-UPSTREAM implies a repo exists, and rendering a comparison git refused as CURRENT is the precise silence the field is for. ONE DELIBERATE DEVIATION from the spec's literal shape, documented beside the code so it is not "fixed" back: the freshness and remote-knowledge group is emitted PER REPO and labelled, because the scorecard and the engine are separate checkouts and a single field covering both would itself be an unnamed qualifier -- the reader could not tell which repo it described. No mandated field is renamed, dropped, or given a value outside its stated set. upstream= is likewise additive: BEHIND 37 is not a claim until you know behind what, and on a feature branch the branch's own upstream and the canonical line are different questions. ZERO NETWORK, ZERO MUTATION. Staleness comes from git rev-list --left-right --count HEAD...<upstream>, which counts against the last-fetched remote-tracking ref, and from the mtime of FETCH_HEAD, probed in both the per-worktree and common git dirs and reported as the newest. There is deliberately NO --fetch: it is permitted by the spec but not required, it is the only mutating path in a tool whose whole thesis is that a query must not mutate, and it cannot be tested without network. Refreshing is git fetch, by hand, on purpose. Say so if you want it and it will be added with its own control. --status is a pure read of the scorecard: no corpus, no engine tree, nothing cached to disk (a cached result would be document 69 in a corpus where 68 assert a tally and about one is correct). It runs in 0.69s against the real record, against 41s for verify. It reports all six verdicts including needs-review, which the verify summary line omits, and it NAMES WHAT IT DID NOT CHECK -- anchor resolution, absence truth and corpus completeness are verify's job, and a structural tally printed under a heading implying anchor health would be the same overstatement corrected in the last commit. Exit 0 on a read, 2 when the scorecard will not load, NEVER 1. A query that borrows the gate's failure code gets wired into CI as a gate. PROVED BY MAKING IT FAIL Twelve defects injected one at a time, each confirmed landed, each caught by a named test, tree green after every one: the tool fetches; dirty never reported; freshness omitted on no-upstream; a non-repo mislabelled NO-UPSTREAM; DIVERGED collapsed into BEHIND; remote-knowledge a constant instead of an age; a never-fetched repo reporting an age; one freshness field for two repos; --status no longer naming what it did not check; --status returning the gate's exit code; provenance suppressed on the error path; needs-review dropped from the counts. The no-mutation test is the negative control and it checks the OUTCOME, not only the intent: every git argv is captured and checked against a read-only allowlist, AND the remote-tracking refs and FETCH_HEAD mtime are compared across the run. Injecting a fetch reddens both halves independently. Tests use real git repositories -- a bare origin and a clone -- rather than a stub, because the failure being prevented is a fact about remote-tracking refs, fetch recency and worktree layout, and a stub would reproduce my assumptions instead of git's behaviour. None is marked skipif, and one test asserts git is present so the suite cannot silently evaporate. ruff format --check, ruff check, mypy strict (-p scripts.asvs and the test file) and 93 scorecard tests are clean.
… counters can be reconciled
Reported by Stream A and routed through the coordinator. Confirmed, and it is one worse than the
report said.
WHAT I MEASURED
`_prove_one` has EIGHT outcomes. Three increment a counter; FIVE raise a problem and increment
nothing:
counted no mutation_path -> skipped_absences
counted mutated exit 1 -> proved_absences
counted static backstop (may ALSO raise SUSPECT) -> static_screened
uncounted mutation_path escapes the tree -> PROVE-ERROR
uncounted mutation_path is not a file -> PROVE-ERROR
uncounted baseline observable not green -> PROVE-ERROR
uncounted mutated exit 0 -> UNPROVEN
uncounted mutated exit not in {0,1} -> PROVE-ERROR
The report said four uncounted; it is five -- the escaping-`mutation_path` branch was missed. The
proposed fix is unaffected, because `saw N` supplies the denominator and the remainder is then
derivable whatever the branch count is. Reporting it rather than quietly correcting it: a count is
a fact, and this one was off by one in the direction that flatters the tool.
Against the real record, vault origin/main 1a59e4a1:
prove-absences: saw 276 absence claim(s); proved 0 by mutation; 0 static-screened; 276 skipped;
0 problem(s)
All 276 are SKIPPED. Not one absence claim in the record is proven by execution, which is what the
capability exists for -- the line now says that instead of implying it.
WHAT CHANGED
`saw N absence claim(s);` leads the summary, exactly the insertion Stream A verified their parser in
scripts/asvs/prove_report.py tolerates. Nothing else on the line moved, and no second line was added,
because their tolerance was verified against that one insertion and not against a new line.
`checked_absences` is now set by BOTH passes and is incremented BEFORE any outcome branch, so it is
right whichever branch a claim takes. The closing arithmetic is documented on the field:
checked_absences - proved_absences - static_screened - skipped_absences
== claims that ended in a problem-only branch
`len(problems)` is deliberately NOT asserted equal to that remainder, and the test says why: a
SUSPECT finding rides along with a claim already counted in static_screened, so problems and claims
are different populations. Pinning that equality would pin a falsehood.
PROVED BY MAKING IT FAIL
Three injections, each confirmed landed, each caught: the saw-N term deleted; the population counted
only for claims that get past the first guard; skipped claims dropped from the arithmetic. A negative
control asserts an empty run says `saw 0` rather than printing four zeroes that read like a clean
pass over a real population.
ruff, mypy strict and 97 scorecard tests clean.
…l, additive to drift An anchor may now carry `sym` (enclosing symbol, dotted) and `ctx` (block-node chain, e.g. Try.body). Both are derived with `ast` and VALIDATED here; backfilling them is Stream D's. WHAT IT IS, AND THE SENTENCE THAT HAD TO SHIP WITH IT This is a DISPLACEMENT signal, not a defect detector; its security-relevant precision measured 0 of 1 on the only datum the corpus offers, because 10.5.4's red was a HARDENING. It sits in the docstring beside the check, because a signal described as a defect detector where people read it will be quoted as one -- and this one would then be quoted at 0% precision. Read it as "your reasoning about this cell is stale, go re-read it". That is why a MISMATCH is an ADVISORY. A gate that redded on 10.5.4 would have demanded the rollback of a security improvement. WHAT I MEASURED, AND WHERE THE BRIEF'S FIGURE CAME FROM Vault origin/main 1a59e4a1's anchors against engine tree 4667e94, over the 1,712 Python anchors that locate and parse. The region is the innermost node the (sym, ctx) PAIR pins -- both fields must match, so the pair is only as loose as its tighter half: 536 of 1,712 (31.3%) sit in a region SPANNING MORE than the 81 lines the retired +/-40 window covered. For those, sym/ctx alone is the LOOSER signal. region spans: median 33, p90 567, max 9,799 1,403 of 1,712 unnested (ctx == ""); 280 at module level (sym == "") innermost region kind: 1,008 scope, 328 If, 280 module, 50 Try, 21 For, 10 AsyncWith, 8 handler The brief's 38.4% / 639 IS REPRODUCIBLE, under a looser region definition: scoring the region as the enclosing SYMBOL only and ignoring the ctx refinement gives 634 at this ref against the brief's 639. So the figure was not wrong, it answered a slightly different question. Both definitions support the same conclusion, which is the only one that matters here: replacing drift with sym/ctx would lose detection on roughly a third of the record, so this is ADDITIVE and a test pins it -- three anchors, three outcomes (drift only, sym/ctx only, both). PROCESS NOTE, recorded because it is the failure this programme is about: the first draft of that docstring carried "39.7% of 1,467 ... 583 anchors". Those were placeholders I wrote before running anything. They were replaced with measured values before commit, but they existed, and they read exactly like measurements. ctx, NOT INDENTATION Cell 12.3.5 carries the identical 4-versus-8 indent mismatch as 10.5.4 and is a non-event -- a hand-trimming slip, ctx unchanged at both ends. A test pins that a re-indent under the same block leaves (sym, ctx) identical. Indentation flags it and costs a triage; ctx correctly does not fire. SEVERITY SPLIT MALFORMED (fatal) -- a ctx naming a node type or field the deriver cannot produce, a sym that is not a dotted identifier, or sym/ctx on a non-Python file. These are defects in the RECORD; no engine movement can cause one, and an unmatchable chain would otherwise advise forever. Validated against _BLOCK_FIELDS, the same table the deriver walks, so an accepted chain is one the deriver can actually produce. MISMATCHED (advisory) -- displacement. See above. ABSENT (silent) -- `None`, not "". The loader keeps them distinct on purpose: "" is the claim "module level / unnested", so defaulting absent to "" would turn all 1,980 un-backfilled anchors into that claim overnight. Coverage is printed -- "sym/ctx asserted on 0 of 1980 anchor(s); the rest assert neither, and absence of the field is NOT agreement" -- because a structural check reaching 0 of 1,980 while printing like a whole-corpus result is the overstatement this branch keeps correcting. AN INJECTION THAT DID NOT FAIL, AND THE DESIGN CHANGE IT FORCED Eleven defects were injected. The first pass ran ten and ONE WAS NOT CAUGHT: deleting `ExceptHandler` from the transparent set changed nothing observable. Cause: one table was answering two questions -- "can the walk descend into this node?" and "does it contribute a chain element?" -- so removing the name silently stopped DESCENT instead of starting RECORDING, and the chain came out identical by a different route. Untestable by construction. Split into `_DESCEND_ONLY` (descent) and `_TRANSPARENT` (recording), with a test asserting they do not drift apart. Both halves are now independently drivable, and both injections are caught: L5a reddens the recording test, L5b the descent test. The failed injection was worth more than the nine that passed. Re-run: 11 of 11 caught, each confirmed landed, tree green after every one. NOT TOUCHED: `_prove_one` and `_run_prove_absences` -- Stream A owns those for A2. Verified: zero lines of this diff touch either. ruff format --check, ruff check, mypy strict (-p scripts.asvs and the test file) and 120 scorecard tests are clean.
wshallwshall
marked this pull request as ready for review
August 9, 2026 14:01
wshallwshall
enabled auto-merge (squash)
August 9, 2026 14:01
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three commits on
scripts/asvs/scorecard.pyandtests/test_asvs_scorecard.py, and nothing else.Each is independently landable; they are stacked only because they share one file and one editor.
ef3ad91eform, and the summary stops overstating what a run proves725347dc--statusand a provenance line, so no answer can be quoted without its ref4667e945--prove-absencesprints the population it scanned0876a4c1sym+ctxvalidation -- a displacement signal, additive to driftAll three share one theme: a number printed without the population or the ref it came from is not a
measurement, and this record has been shipping several of those.
V1 — what does an anchor actually resolve INTO?
An evidence anchor now carries a derived
form:code,doc, orforeign. Computed at check timefrom where the token lands, never authored, and wired into nothing — not
check_completeness, notany verdict, not the exit code.
Measured against vault
origin/main(1a59e4a1) at engine treec383eeab:.md, 17.ts, 16.js, 13.yml, 8.toml, 16 scattered)A quarter of the record's anchors resolve into prose or a non-Python file that no structural or
executable scheme reaches, ever, and the record presented all of them as code evidence.
The 17-cell figure was derived here independently and matches the 2026-08-09 recut's list exactly —
two methods, same 17 cells, and the recut's number was never used as a target. That is why
docisstated as a label rather than hedged.
Why docstring position and not a token mask. A docstring is the first statement of a module, class
or function via
ast; comments come fromtokenize, so a#inside a string literal is not mistakenfor one. A mask misfiles the CSP fragments in
messagefoundry_webconsole/_security.pyand the wholeSQL Server and Postgres DDL as prose — long, quoted, operator-free strings that read as English while
being the literal subject of the control their cell cites. Position cannot make that mistake, and not
because it is a better mask: it never asks the question.
START, not overlap. The two rules disagree on 57 of the 1,712 located Python anchors, and every one
of the 57 is a code statement whose recorded token runs past the statement into a trailing comment.
The reason is in the code so it does not get "fixed" back.
The summary line already said "resolved" rather than "verified" as of
8cd0d168, which landedbefore this branch started. Rather than re-doing a done fix, it is now pinned end-to-end through
mainby a test so it cannot silently revert, with the split beside it — "resolved 1,980" still readsas 1,980 pieces of code evidence.
V2 — query mode, step one: the provenance line and
--statusPer
ASVS-QUERY-MODE-SPEC-2026-08-09. Provenance plus--statusonly: no--cell, no--cells-for,no
--cells-since.The defect, demonstrated live. Two files on this machine, both named
asvs-scorecard.toml, bothreal, measured 2026-08-09:
31d4cd98)origin/main(1a59e4a1)Nothing in either reading says which is which. That is exactly how three wrong-base errors happened in
one working thread on 2026-08-08/09 — the number was right and the ref was unnamed. With the header,
the first reads
BEHIND 38 upstream=origin/main remote-knowledge=36mand the second readsNO-GIT,because it is an extracted copy rather than a checkout and therefore not quotable at all.
freshnessis always populated:CURRENT/BEHIND n/AHEAD n/DIVERGED/NO-UPSTREAM/
NO-GIT/UNRESOLVED. The last two are extensions that exist because of the never-silentrule: calling a non-repo
NO-UPSTREAMimplies a repo exists, and rendering a comparison git refusedas
CURRENTis the precise silence the field is for.git rev-list --left-right --count HEAD...<upstream>counts againstthe last-fetched remote-tracking ref;
FETCH_HEADmtime is probed in both the per-worktree andcommon git dirs and the newest is reported.
--fetch. Permitted by the spec, not required. It is the only mutating path in a tool whosewhole thesis is that a query must not mutate, and it cannot be tested without network. Say the word
and it lands with its own control.
--statuscosts 0.69s against the real record, versus 41s for verify. No corpus, no engine tree,nothing cached to disk — a cached result would be document 69 in a corpus where 68 assert a tally
and about one is correct.
failure code gets wired into CI as a gate.
One deliberate deviation from the spec's literal shape, documented beside the code: the
freshness/remote-knowledgegroup is emitted per repo and labelled. The scorecard and theengine are separate checkouts, and a single field covering both would itself be an unnamed qualifier —
the reader could not tell which repo it described, which is the exact defect the field exists to
prevent. No mandated field is renamed, dropped, or given a value outside its stated set.
upstream=is likewise additive:
BEHIND 37is not a claim until you know behind what.V3 —
--prove-absencesprints the population it scannedReported by Stream A, routed through the coordinator. Confirmed, and one worse than the report
said:
_prove_onehas eight outcomes, three counted and five — not four — that raise a problemand increment nothing. The escaping-
mutation_pathbranch was the one missed. The proposed fix isunaffected, since
saw Nsupplies the denominator and the remainder is derivable whatever the branchcount is.
Against the real record:
All 276 are skipped. Not one absence claim in the record is proven by execution, which is what the
capability exists for. The line now says that instead of implying it.
saw N absence claim(s);is exactly the insertion Stream A verified their parser inscripts/asvs/prove_report.pytolerates. Nothing else on the line moved and no second line was added,because their tolerance was verified against that one insertion. The closing arithmetic is documented
on the field:
len(problems)is deliberately not asserted equal to that remainder, and the test says why: aSUSPECT finding rides along with a claim already counted in
static_screened, so problems and claimsare different populations. Pinning that equality would pin a falsehood.
V4 --
sym+ctx, validatedAn anchor may carry
sym(enclosing symbol, dotted) andctx(block chain,Try.body). Derived withast, validated here; backfill is Stream D's.The sentence that ships with it, in the docstring beside the check: this is a DISPLACEMENT signal,
not a defect detector; its security-relevant precision measured 0 of 1 on the only datum the corpus
offers, because 10.5.4's red was a HARDENING. That is why a mismatch is an advisory -- a gate redding
on 10.5.4 would have demanded the rollback of a security improvement.
Additive, measured. Over the 1,712 Python anchors that locate and parse at engine
4667e945,536 (31.3%) sit in a
(sym, ctx)region wider than the 81 lines the retired window covered, sosym/ctx alone is the looser signal for those. Region spans: median 33, p90 567, max 9,799. A test pins
the additivity directly: three anchors, three outcomes -- drift only, sym/ctx only, both.
The brief's 38.4% / 639 is reproducible under a looser definition -- scoring the region as the
enclosing symbol only, ignoring the
ctxrefinement, gives 634 at this ref. The figure was notwrong; it answered a slightly different question. Both support the same conclusion.
ctx, not indentation. A test pins 12.3.5's shape: a re-indent under the same block leaves(sym, ctx)identical. Indentation flags it and costs a triage;ctxcorrectly does not fire.Severity split: malformed is fatal (a chain the deriver cannot produce, or sym/ctx on a non-Python
file -- defects in the record that no engine movement can cause), mismatch is advisory, absent is
silent. Absent is
None, never"":""is the claim "module level, unnested", so defaulting wouldturn all 1,980 un-backfilled anchors into that claim overnight.
Coverage is printed --
sym/ctx asserted on 0 of 1980 anchor(s); the rest assert neither, and absence of the field is NOT agreement.Proved by making it fail
35 defects injected, one at a time, each confirmed to have landed before the run, each caught by a
named test, with the tree returning green after every one. No injection was believed on inspection.
form+ summary--statussym+ctxOne injection did NOT fail, and it was worth more than the nine that passed. Deleting
ExceptHandlerfrom the transparent set changed nothing observable, because a single table wasanswering two questions -- "can the walk descend into this node?" and "does it contribute a chain
element?" -- so removing the name silently stopped descent instead of starting recording, and the
chain came out identical by a different route. Untestable by construction. Split into
_DESCEND_ONLYand
_TRANSPARENT, with a test that they do not drift apart; both halves are now independentlydrivable and both injections are caught.
The others that carry the most weight:
checked against a read-only allowlist, and the remote-tracking refs and
FETCH_HEADmtime arecompared across the run. Injecting a fetch reddens both halves independently. Asserting on argv
alone would pass a tool that reached the network some other way.
docanchors to aproblemsentry. If someone later teaches this gatethat a doc-only cell is unevidenced, that test goes red and says why.
Negative controls throughout: a file with no prose yields zero
doc; an empty form run prints0 of 0 (0.0%)rather than dividing by zero; an empty prove run sayssaw 0; a scorecard with everyanchor broken still exits 0 under
--status, because--statusdoes not check anchors.The V2 tests use real git repositories — a bare origin and a clone — not a stub, because the
failure being prevented is a fact about remote-tracking refs, fetch recency and worktree layout, and a
stub would reproduce my assumptions instead of git's behaviour. None is marked
skipif, and one testasserts git is present so the suite cannot silently evaporate.
Checks
ruff format --check,ruff check,mypystrict (-p scripts.asvsand the test file), and 120scorecard tests are clean. CI's mypy legs run
messagefoundry messagefoundry_webconsoleand do notcover
scripts/, so the type check was run explicitly.Cost: verify is 41.1s against a 40.2s pre-change baseline on the same tree — the form classification is
free, and
check_anchorsnow caches the per-file read and parse (1,980 anchors over ~224 files).Not in scope, and noticed in passing
messagefoundry/config/wiring.py, killed bye7566082.Adjudicated by the coordinator as class (c) — the gap the anchor certified was CLOSED — and routed to
the assessment session. Reported here, never re-anchored, and no target suggested.
render_currentis deliberately untouched. It generates the vault'sASVS-CURRENT.md, which isdiffed against a committed copy, so adding the
formsplit there would red that drift gate until thevault re-renders.
docs/BACKLOG.md:180and:199citescripts/asvs/scorecard.py:387,409-413and:395. Alreadystale; these commits move them further. Residual-lint work, not this.
Generated with Claude Code.