Skip to content

Lane A end to end: anvil scan, the daemon, and the first-run contract fixes - #18

Merged
tom-snyder merged 2 commits into
mainfrom
feat/anvil-scan
Oct 3, 2026
Merged

tom-snyder merged 2 commits into
mainfrom
feat/anvil-scan

Conversation

@tom-snyder

Copy link
Copy Markdown
Member

Phase 4's vertical slice (plan nodes cli and daemon): anvil stops printing "bootstrap placeholder" and runs Lane A end to end.

What runs

  • anvil scan --repo PATH and anvil scan --host --inventory FILE|-: trigger policy → scan controller → collection → comparator → record emission → SAST seal → record assembly → masking → store (WAL, BEGIN IMMEDIATE) → SARIF out.
  • Exit statuses: 0 clean, 1 findings, 2 usage, 3 refused, 4 missing tool, 5 error. A refusal or a missing tool is never 0.
  • Regression marking keyed on the fingerprint alone: new, persisting, regressed and fixed. A finding is fixed only by a cleanly sealed, complete scan of the detectors it came from.
  • anvil daemon keeps feeds fresh through delta.Syncer and runs dispatched scans from a spool, each through the trigger policy. Scheduled full scans are anvil-full-scan@.timer one-shots.
  • anvil feeds import DIR loads an offline snapshot through the same licence gate, decoders and cache writes as live ingestion. anvil findings reads the store back.
  • anvil scan --host never collects an inventory itself. It reads what the collector writes under its unit (deploy/systemd/README.md explains the handoff and its open item).

Mismatches the first run found, fixed in their owning packages

  1. No production policy loader. policy.Load/DecodeYAML promote the test decoder, hardened for files from the repository under scan.
  2. No record assembler. record.Assemble builds and validates a SAST-only audit from the sealer's snapshot.
  3. anvil/target.provisioning was required with no truthful value for an audit that declared no target. It is now absent exactly then; emission's own envelope had been writing a false value.
  4. Validate and the sealer disagreed about a failed half. Validate now counts terminal halves, as the sealer does.
  5. The wire schema rejected null index maps and a null cveIds; the Go validator could not see it. Both fixed, and CI now validates every record against the schema.
  6. The identity seams the conformance harness reported. Distro ranges carry their release (internal/distro), host purls come from os-release, and the cache-backed source is release-scoped, so a Debian 11 advisory never decides a Debian 12 package. Repository findings Trivy decided go to emission directly.

The owner's accelerator decision (2026-10-03)

The Trivy database is off by default. Repository SCA runs only when the operator's configuration enables it, never by a flag, and every finding it produces carries tier-2 attribution (LicenseRef-Anvil-TrivyDB-Tier2). With it off, anvil scan --repo exits 3.

Evidence

  • sh test/e2e/fixture.sh passes on Debian 13: two host scans (3 new; then 1 new, 2 persisting, 1 fixed), a refused collector-less host scan, a real Trivy repository scan (7 lodash findings, all tier-2), and all three records valid against schemas/anvil-record-v1.schema.json.
  • The Lane A chain ledger proves 10 of 12 links on this machine. Still UNPROVEN: the weekly self-heal and the DB accelerator, with reasons.
  • New CI job anvil end to end (fixture) runs the script on a clean checkout with the pinned Trivy, and fails unless the repo SCA link is PROVEN there.
  • gofmt, go vet, go test -count=1 ./... (with and without Trivy on PATH), go test -race -count=1 ./... and the eval suite are green.

…icy, repo collector

Wiring anvil scan end to end surfaced mismatches no unit test could see, and
each is fixed where it lives:

- Record: anvil/target.provisioning is absent exactly when no target was
  declared. Both legal values describe a runtime target, so every core-only
  audit had to state a provisioning path that never happened, and record
  emission's own validation envelope did.
- Record: Validate counted only a sealed half toward anvil/state, while the
  sealer advances the state on any terminal half. It refused the sealer's own
  output for an audit whose SAST half failed; it now uses the sealer's rule.
- Record: Assemble, the record assembler the contract names and nothing
  implemented, builds and validates a SAST-only audit from the sealer's
  snapshot. Its index maps are empty objects, and emission's cveIds an empty
  array, because the wire schema rejects null for both.
- Store: Open applies ConnectionPragmas on every connection (foreign keys were
  otherwise off) and migrates; WriteScan records scan_run, audit_record with
  the zstd payload, and the findings, marking each new, persisting, regressed
  or fixed by fingerprint. A finding is fixed only by a cleanly sealed,
  complete scan of the detectors it came from.
- Policy: Load and DecodeYAML read .anvil/policy.yml in production; the only
  decoder was a test helper. It refuses oversized files, deep nesting, and
  anchors, aliases, tags and block scalars.
- Repo collector: Database reports the Trivy database's build time, the as_of
  of every Trivy-decided finding.
- internal/distro: the one release vocabulary (debian-12, alpine-3.19, rhel-9)
  shared by ingestion, the host scan and the cache-backed source.
…end to end

cmd/anvil replaces the bootstrap placeholder with a thin command over the
tested packages, and internal/scan is the production caller the Lane A ledger
reported missing: trigger policy, scan controller, collection, comparison,
emission, seal, assembly, masking, the store and SARIF out.

The identity seams the conformance harness found are closed in their owners:
ingestion writes distro ranges in the comparator's scheme with the release in
the purl's distro qualifier; scan.HostRecords builds each host purl from
os-release; scan.CacheSource consults only the host release's ranges, so a
Debian 11 advisory never decides a Debian 12 package; and repository findings,
which Trivy has already decided, go to emission directly under the owner's
accelerator decision of 2026-10-03: off unless the operator's configuration
enables the Trivy database, tier-2 attribution on every finding when it does.

- anvil scan --repo / --host: exit 0 clean, 1 findings, 2 usage, 3 refused,
  4 missing tool, 5 error. A host scan reads the collector's inventory and
  never collects itself.
- anvil daemon keeps feeds fresh through delta.Syncer and runs dispatched
  scans from a spool, each through the trigger policy; scheduled full scans
  are anvil-full-scan@.timer one-shots.
- anvil feeds import loads an offline snapshot through the same licence gate,
  decoders and cache writes as live ingestion. testdata/lanea-fixture is one,
  with two host inventories and a repository for the regression marks.
- The conformance harness calls the production code instead of its stand-ins
  and proves 10 of 12 links where Trivy and its database exist.
- CI's new anvil end to end job runs test/e2e/fixture.sh on a clean checkout
  and validates every record against the wire schema.
@tom-snyder
tom-snyder merged commit 1d8657e into main Oct 3, 2026
7 checks passed
@tom-snyder
tom-snyder deleted the feat/anvil-scan branch October 3, 2026 18:46
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.

1 participant