Skip to content

fix(coverage-ledger): verify local-check artifacts exist on disk - #25

Open
alter wants to merge 1 commit into
cloudflare:mainfrom
alter:fix/coverage-ledger-artifact-existence
Open

alter wants to merge 1 commit into
cloudflare:mainfrom
alter:fix/coverage-ledger-artifact-existence

Conversation

@alter

@alter alter commented Sep 17, 2026

Copy link
Copy Markdown

Closes #21

validateChecks in skills/security-audit/validate-coverage-ledger.cjs only checked that a
local check's artifact path was lexically owned by the checking agent (isOwnedArtifactPath:
matches agents/<agent_id>/artifacts/... and is a safe relative path). It never confirmed the
file the path names actually exists. A covered, blocked, or candidate coverage unit could
pass validation while its local check evidence was never produced, exactly the repro in the
issue: a syntactically valid artifact path with no file behind it passes with "PASS: 1
coverage units valid".

Added validateArtifactExists, called for every local method check once its path already
passes the existing ownership check. It resolves the artifact against the ledger's own
directory (the /coverage-ledger.json convention already documented in
RECONNAISSANCE.md and VALIDATION-AND-REPORTING.md, same directory the README's
~/security-audit-skill//run- default describes), and rejects when:

  1. the path doesn't resolve to anything ("local check artifact does not exist")
  2. it resolves to a symlink ("local check artifact must not be a symlink")
  3. it resolves to something other than a regular file, like a directory ("local check
    artifact must be a regular file")
  4. its real path, after resolving symlinks, escapes the owning agent's real artifacts
    directory ("local check artifact resolves outside the owning agent's artifacts
    directory"). This catches a symlinked ancestor directory smuggling the artifact outside
    its owner's tree even when the artifact itself isn't a symlink.

Diagnostic format matches the issue's own suggestion:
$[0].local_checks[0].artifact: local check artifact does not exist.

validateDocument, collectUnitErrors, validateAttempts, and validateChecks all take a new
optional baseDir parameter that only the CLI (run(file), via
path.dirname(path.resolve(file))) supplies. Direct callers of validateDocument that don't
have a real ledger directory, like the existing unit tests exercising isolated schema rules,
keep today's shape only behavior. That's deliberate, not an oversight: hundreds of existing
tests build ledgers in memory without ever writing artifact files to disk, and forcing every
one of them to also manage real fixtures would turn a focused validator correctness fix into
an unrelated test suite rewrite. The CLI path, the only one that runs for real per
RECONNAISSANCE.md's "run after seeding, after every parent update, and before Phase 6", now
always gets the real check.

Tested:

  1. Four new tests in validate-coverage-ledger.test.cjs, each driving the real CLI end to end
    via spawnSync, matching the file's existing CLI test convention: missing artifact,
    directory instead of file, symlink artifact, and the accepting case with a real file in
    place. Confirmed all three rejecting cases pass against unmodified code (the bug
    reproduces, PASS: 1 coverage units valid when it should fail) before the fix, and fail
    correctly after.
  2. A fifth new test confirms validateDocument called directly, no baseDir, matching every
    pre-existing unit test in the file, keeps its current shape only behavior. That's the
    compatibility guarantee the design above depends on.
  3. node --test skills/security-audit/*.test.cjs: all 70 tests pass, including the pre-existing
    cross check that findings and coverage ledger validator share aligned helpers.
  4. No lint or build step in this repo, plain Node.js .cjs scripts, no package.json.

validateChecks only checked that a local check's artifact path was
lexically owned by the checking agent (agents/<id>/artifacts/...); it
never confirmed the file actually exists. A covered/blocked/candidate
unit could pass validation while pointing at local evidence that was
never produced.

The CLI now resolves each local-check artifact against the ledger's
own directory (the documented <output-dir>/coverage-ledger.json
convention) and rejects a missing artifact, a non-regular file, or a
symlink, and rejects an artifact whose real path escapes the owning
agent's artifacts directory. validateDocument/validateChecks keep
today's shape-only behavior when called without a base directory, so
existing unit tests that check schema rules in isolation are
unaffected.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Coverage validator accepts nonexistent local-check artifacts

1 participant