quality: re-runnable Steps-view coverage scan (BACKLOG #239) - #81
Conversation
Pre-registered decision rule (posted BEFORE the estate scan is run)The estate number is about to be measured. Recording the rule first so it can't become a post-hoc justification in either direction. Owner delegated the threshold choice; this is mine. Metrics (both on the default reading, where a recognized
Bands:
Reported but not band-driving — the cause mix of the opaque mass, in particular the share attributable to delegating calls into helper modules. If helper descent dominates, ADR 0089 Phase D is the highest-leverage fix regardless of which band we land in, because it converts opaque rows without widening the grammar. Corpus: the de-identified estate (375 config modules — 75 handlers, 75 routers, 167 connections). Note it measures a different population than ADR 0089's 486 Ledger note: the tool skips |
Result — de-identified estate, scored against the pre-registered rule388 files · 145 handlers · 1,423 rows · 0 parse refusals
A (editable share) = 42.0% · B (fully-typed handlers) = 14.5% (21/145) · median opaque rows/handler = 3 (max 50) · handlers with zero editable rows: 5/145 (3.4%) Rule verdict: 🔴 RED — but at the boundary on both triggersApplying the rule as posted, two RED conditions fire independently:
Meanwhile A = 42.0% sits mid-AMBER (30–55%). So: RED is the honest verdict under the rule I committed to, and I'm not going to relitigate a threshold after seeing the data — but nobody should read this as a decisive RED. Had I written The diagnostic changes what RED means hereThe rule declared the cause mix "reported but not band-driving." It is nonetheless the most decision-relevant output:
RED in my rule meant "the projection model is the problem." The diagnostic says otherwise. The opacity is overwhelmingly mechanical, not fundamental:
Together, comments + helper descent account for ~70% of the opaque RecommendationTake the AMBER prescription (breadth before depth) despite the RED trigger, on the strength of the diagnostic — and treat the two cheap conversions as the next work:
This is a recommendation, not a decision — the RED trigger is real and the call on whether the diagnostic overrides it is the owner's. Comparison note: ADR 0089's ~66%-opaque baseline counted a different population (486 |
400227f to
db99c98
Compare
PR #80 appended eight Steps-view items to docs/BACKLOG.md without the status banner every numbered item must carry, so tests/test_backlog_status_check.py::test_the_real_backlog_satisfies_the_invariant began failing on main itself (8 errors, first at line 6905). Because GitHub tests each PR merged into main, every open PR inherited the failure: #81, #74, #71, #66 and #60 were all blocked, three of them with auto-merge armed and unable to fire. Adds exactly one leading banner per item. Seven use the open/prioritized form; #239 uses the partial form, because its measurement ran and is recorded on PR #81 while the re-runnable scripts/quality/lens_coverage.py is still unmerged -- the number is not yet reproducible from main. The banners are deliberately minimal and do not re-litigate any item's verdict. #234 in particular is left explicitly unsettled rather than entrenched: it was filed as "revisit, not a bug" after the owner asked for a fix, and that framing is still open. Verified: scripts/docs/backlog_status_check.py exits 0 (237 items) and tests/test_backlog_status_check.py is 15 passed, was 14 passed 1 failed. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
ADR 0089 §5 promised the coverage scan was repeatable, and then nobody re-ran
it after Phase A, the ADR 0106 palette, ADR 0108 fan-out and ADR 0104 picker
shipped. This makes re-running it a one-liner.
It drives the SHIPPED `lens parse --json` rather than re-implementing the
grammar with a second ast walk, so the number describes what the Steps view
actually renders and cannot drift from it.
PHI-safe by construction: `lens parse` never imports or executes a config
module and no message enters the path, so it runs against a production estate
unmodified. --hash-names for when results leave the building.
First result, samples/config (14 files, 12 handlers, 28 rows, 0 refusals):
code 42.9% OPAQUE 57.1% (46.4% --strict-control)
send 42.9% EDITABLE 42.9%
control 10.7%
control (unrecognized) 3.6%
handlers with zero editable rows : 0/12
handlers 100% typed : 5/12 (41.7%)
median code rows per handler : 1 (max 4)
This does NOT answer #239, and the reason is worth recording: samples/config
contains ZERO action rows and ZERO lookup rows. Every editable row in it is a
`send`. ADR 0089 §4 puts the pre-Phase-A baseline at "~13% (sends only)" --
so this corpus sits at exactly that baseline shape and cannot demonstrate
Phase A's lift at all. The demo handlers delegate their transform work to
helper modules (_demo_oru_transforms.py, _pdf_mdm_transforms.py) which the
lens does not descend into, so it renders as `code`. That is ADR 0089's
Phase D helper-descent gap, reproduced in miniature.
28 rows also cannot speak for an estate ADR 0089 measured at 3,852
statements. The decision-grade number needs the production estate; its path
is owner-confirmed per #239 and deliberately not recorded here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two gaps found by running the scan against a real estate rather than samples/. 1. A real estate keeps its own .venv beside the config modules. A naive rglob walked ~1,900 site-packages files, spawned a CLI call per file, and buried the estate's own numbers. Dot-dirs, __pycache__, site-packages, node_modules, build and dist are now skipped. 2. The pre-registered decision rule (PR comment) referenced "median opaque rows per handler" and a cause mix, and the tool reported neither -- so it could not actually adjudicate its own rule. Both now reported. The cause classifier is a deliberate HEURISTIC on first-statement shape, not a parse. It exists to answer the one question the row contract cannot: how much of the opaque mass is a delegating call into a helper module, i.e. how much ADR 0089 Phase D would convert WITHOUT widening the grammar. Shapes only -- no source is ever emitted, only counts, so it stays estate-safe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The ASVS 11.1.3 crypto-inventory gate failed this branch: --hash-names used hashlib.sha256 to redact file names, and hashlib is a tracked crypto module with no inventory entry for a dev script. Adding an inventory entry would have been the wrong fix, because the hash was also the wrong PRIMITIVE. Estate modules follow a rigid naming convention (IB_<id>_<system>_<type>_handler.py), so a candidate list is cheap to generate and digest -- a truncated SHA-256 of a file name is reversible by dictionary attack. The flag is now --anonymize and assigns stable per-run opaque indices (<module-0001>.py), which carry no preimage at all. More private, and no crypto import in a quality script. Gate now reports "OK - 57 documented crypto call site(s), no drift." Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… override The item's banner said the tooling was unmerged; this PR merges it. Flips it to shipped and carries the numbers the scan produced, so the backlog states the result rather than pointing at a PR comment. Also records, in the item itself, that the pre-registered decision rule fired RED and that the RED prescription was NOT adopted. Both triggers landed exactly on their boundaries and the AMBER prescription was taken instead on a delegated judgment call that the owner never explicitly ratified. That override is load-bearing for the next several items, so it belongs on the record next to the number rather than only in a handoff. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
db99c98 to
151b7e2
Compare
Adds
scripts/quality/lens_coverage.pyand reports its first result.ADR 0089 §5 promised the coverage scan was repeatable — "re-running it after each phase measures the coverage lift and surfaces the shrinking residual" — and then nobody re-ran it after Phase A, the ADR 0106 palette, ADR 0108 fan-out and ADR 0104 picker all shipped. This makes re-running it a one-liner.
Design choice worth reviewing
It drives the shipped
lens parse --jsoninstead of re-implementing the grammar with a secondastwalk. The lens is the surface under evaluation; asking it directly means the number can't drift from what the Steps view actually renders. The cost is one subprocess per module, which is irrelevant at estate scale.PHI:
lens parsenever imports or executes a config module and no message enters the path, so this runs against a production estate unmodified.--hash-namesfor when results leave the building.First result —
samples/config14 files · 12 handlers · 28 rows · 0 parse refusals
codesendcontrolcontrol(unrecognized)OPAQUE 57.1% (46.4% under
--strict-control) · EDITABLE 42.9%handlers with zero editable rows: 0/12 · handlers 100% typed: 5/12 · median
coderows/handler: 1 (max 4)This does not answer #239, and the reason is the interesting part
samples/configcontains zeroactionrows and zerolookuprows. Every editable row in it is asend. ADR 0089 §4 puts the pre-Phase-A baseline at "~13% (sends only)" — so this corpus sits at exactly that baseline shape and cannot demonstrate Phase A's lift at all.The cause: the demo handlers delegate their transform work to helper modules (
_demo_oru_transforms.py,_pdf_mdm_transforms.py) that the lens does not descend into, so it renders ascode. That is ADR 0089's Phase D helper-descent gap, reproduced in miniature — and it suggests the sample corpus is a poor advertisement for the palette as well as a poor measurement subject.28 rows also cannot speak for an estate ADR 0089 measured at 3,852 statements. The decision-grade number needs the production estate, whose path is owner-confirmed per #239 and deliberately not recorded here.
Claimed via
scripts/coord/claim.ps1 -Take 239.🤖 Generated with Claude Code