Skip to content

feat: adopt the image SBOM evidence contract (#108) - #113

Merged
antoinetoussaint-byte merged 2 commits into
mainfrom
issue-108-universal-sboms-cover-every-runtime-image-and-adopt
Sep 14, 2026
Merged

antoinetoussaint-byte merged 2 commits into
mainfrom
issue-108-universal-sboms-cover-every-runtime-image-and-adopt

Conversation

@antoinetoussaint-byte

@antoinetoussaint-byte antoinetoussaint-byte commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Closes #108.

Summary

  • This agent inherited a source-only Builder.SBOM, so an image-scope request was answered with a Go module graph — evidence that says nothing about the packages in the Alpine image it ships. Pinning service-go v0.0.49 adopts the shared image-scope override; the scanner stays in core and this repo adds no override of its own.
  • Coverage is measured against the recipe this specialization emits. go-grpc fully overrides Build, so its plan is its own (one app recipe, Dockerfile at the output-directory root, two shipped platforms), and image subjects are derived from that plan rather than restated beside it.
  • Subjects derived from that plan carry a tag and no digest, so handing them back must not yield coverage for whatever the tag resolves to at scan time. That is asserted on the typed outcome, never on message text.

Verification that these tests actually gate

Each assertion was re-run against the old pin (service-go v0.0.48) in a scratch copy. Both image-scope tests fail there and pass on v0.0.49, so they detect the regression they claim to:

Test v0.0.48 v0.0.49
TestImageScopeWithoutSubjectsIsAPreconditionFailure FAIL PASS
TestPlanDerivedSubjectsAreRefusedUntilTheyCarryADigest FAIL PASS
TestSourceInventoryRemainsAvailable PASS PASS (guard, by design)

An earlier revision of this branch carried two further tests that passed identically on both pins — they could not detect the adoption they advertised. They were removed rather than left as false assurance; the multi-platform assertion they contained was folded into the test that does gate.

Cross-repo action taken

service-go's override was merged (codefly-dev/service-go#70) but never released, so there was nothing here to pin. On the operator's instruction I tagged service-go v0.0.49 at 5b1f5c4 — the version its agent.codefly.yaml already declared — after confirming main was green in CI and locally. That tag is what this PR pins.

That release deliberately does not flip ValidationCapabilities().ImageSbom. Advertising it registers a required TASK_PHASE_IMAGE_SBOM phase (core ciinputs/inputs.go), so doing it before codefly-dev/cli#663 supplies subjects would give every Go service a CI phase failing PRECONDITION_FAILED by design.

Required follow-through — this PR is not self-completing

  1. Tag v0.1.39 after merge. agent.codefly.yaml now declares 0.1.39 and the newest tag is v0.1.38. This repo has no manifest-guard check, so nothing in CI catches a manifest claiming a version that was never released.
  2. Promote the CLI roster — Promote codefly.dev/go source-workspace pin to 0.0.49 (image SBOM contract) cli#677. The CLI embeds a source-workspace roster pinning codefly.dev/go at 0.0.48, validated as an exact version, so the released contract is not reachable through the CLI until that pin moves. It is filed rather than fixed directly because promote-source is a qualification gate requiring a fixture run, not a version-string edit; hand-editing the JSON would bypass that gate. This does not affect go-grpc, which is absent from that roster.

Not done, and why

No end-to-end test scans a real runtime image for OS packages. This agent emits a recipe and never runs buildx, so it holds no digest; real evidence needs a pushed image and a scanner, and syft is on neither the development machine nor the stock ubuntu-latest runner this repo's CI uses. The concrete blockers are recorded in #112 rather than papered over with a test that always skips.

Test plan

  • go build ./..., go vet ./..., gofmt, go mod tidy clean
  • go test ./... green against service-go v0.0.49
  • Both image-scope tests verified to FAIL against v0.0.48 (see table) — they gate, rather than passing on old behaviour
  • Image scope with no subjects returns ERROR / SBOM_SCOPE_IMAGE / FAILURE_CODE_PRECONDITION_FAILED through the composed gRPC agent
  • Plan-derived subjects assert one per shipped platform, then are refused for carrying no digest
  • Source inventory still answers an unset scope, asserted on the resolved module purl so an inventory that resolved nothing cannot pass
  • TestBuildRecipeOverGRPC still green after moving it onto the shared harness

🤖 Generated with Claude Code

This agent inherited a source-only Builder.SBOM, so an image-scope
request was answered with a Go module graph — evidence that says nothing
about the packages in the Alpine image it ships.

Pin service-go v0.0.49 to adopt the shared image-scope override. The
scanner stays in core and this repo adds no override of its own.

Coverage is measured against the recipe this specialization emits:
go-grpc fully overrides Build, so its plan is its own and the image
subjects are derived from that plan rather than restated beside it. The
release gate asserts only the typed outcome, so it holds whether the
agent answers with a source inventory or the precondition failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two of the tests added in 7af9395 passed unchanged against service-go
v0.0.48 — the exact pin they were meant to gate. An assertion that holds
on both sides of the change it guards is not coverage: reverting the pin
would have left the suite green, and one of the two was described in the
pull request as the release gate.

Delete both. The coverage-validation link they claimed is already
exercised by the plan-derived subject test, which does fail on v0.0.48,
and the multi-platform assertion worth keeping is folded into it.

Verified by re-running every remaining assertion against v0.0.48: both
image-scope tests fail there and pass on v0.0.49, while the source
inventory test passes on both, which is what a guard should do.

Also fixed, each at its cause rather than its symptom:

- Collect Serve's error through a channel and report it from cleanup. A
  t.Errorf from a goroutine that outlives the test panics the whole
  package instead of failing one test.
- Share one gRPC harness rather than two near-identical copies, which
  removes that same goroutine flaw from the recipe test.
- Assert the resolved module purl in the source inventory. The fixture
  has no dependencies, so a checksum alone would also be produced by an
  inventory that resolved nothing at all.
- Stop writing this agent's GoVersion into fixture go.mod files. Naming
  a patch release newer than the toolchain running the tests makes
  go list download a toolchain before it will answer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@antoinetoussaint-byte
antoinetoussaint-byte merged commit ac58a69 into main Sep 14, 2026
3 checks passed
@antoinetoussaint-byte
antoinetoussaint-byte deleted the issue-108-universal-sboms-cover-every-runtime-image-and-adopt branch October 25, 2026 17:05
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.

Universal SBOMs: cover every runtime image and adopt the fleet evidence contract

1 participant