The ledger stays a machine-written artifact (coverage.json and the SARIF
non-failing results); the executive report goes back to being the agent's
document.
get_available_skills() now yields {name, description} records, so the
coverage gap check reads names out of them rather than iterating the
records themselves.
A real scan surfaced both. An agent carrying path_traversal_lfi_rfi
records "Path Traversal", not the skill's filename, so requiring every
token of the leaf published "treat it as unexamined" for a class that
had been tested and had a finding filed against it; 18 multi-token
skills were exposed. Each vulnerability skill now declares how it can
appear in a ledger row, with a test that a new skill must do the same.
The root agent delegates rather than tests, so it recorded nothing on
every clean run and every report carried a false Not Covered line. It is
exempt while it has children, and only when the snapshot identifies one
unambiguous root -- a root that worked alone is still held to the rule.
Promote the coverage ledger from runtime state to a deliverable:
coverage.json beside vulnerabilities.json, a Coverage section rendered
into the report from the ledger rather than transcribed by an agent, and
SARIF pass / notApplicable / open results so a consumer can tell 'tested
and clean' from 'never tested'.
Ground it in what the runtime observed rather than only what agents
claimed: a risk class an agent carried a skill for and never accounted
for is published as a gap (and surfaced back to the root agent from
finish_scan while it can still act), and a run cut short is stamped
incomplete on both the artifact and the SARIF invocation.
Also: make the ledger's duplicate check and insertion one critical
section and persist under the lock; key a checkout and the URL it was
cloned from onto one threat-model identity; render the calibration
metadata (counterevidence, confidence, severity change conditions, fix
verification) that was being stored and then dropped.