feat(guest-image): publish the guest triple as a non-runnable OCI artifact - #1268
Open
rigel-mintaka wants to merge 4 commits into
Open
rigel-mintaka wants to merge 4 commits into
rigel-mintaka wants to merge 4 commits into
Conversation
This was referenced Sep 17, 2026
|
Compass engineering docs preview: https://compass-managed-rig-3788-gue.compass-eng-docs.pages.dev Deployed from |
An error occurred while trying to automatically change base from
compass-managed/rig-3787-derive-rootfs
to
compass-managed/rig-3786-agent-image-pin
September 17, 2026 02:29
…ifact The three guest assets (kernel, erofs rootfs, initrd) publish as one OCI artifact to ghcr.io/rigelbuild/compass-guest-image, reusing the runner-image lane's shape: a pure unit-tested core does every mapping and fail-closed check, the push is digest-asserted, and the deployable reference is `repo@sha256:...` because GHCR has no server-side tag immutability. Nothing can run an erofs blob, so the manifest says so structurally: an image manifest carrying artifactType application/vnd.compass.guest-image.v1, the OCI 1.1 empty config, and three layers with their own media types. A runtime handed this reference refuses at the config media type. Two properties a local layout cannot demonstrate, so they are enforced in code and covered by tests rather than assumed: - Each layer descriptor carries the asset's real byte length. A registry validates the declared size against the blob it receives, and the local `skopeo copy` path reads the blob without ever comparing, so a placeholder size passes every local check and is rejected only at push. - The manifest carries schemaVersion 2, which is required and likewise unenforced locally. Per-asset annotations key on the filename a materialiser writes, not the nix store basename: `verifyImages` looks its sha256sum manifest up by filepath.Base, and a store basename carries a content hash that would never match. The annotation block is therefore directly usable as the Runner's --microvm-image-manifest. Publishing needs network and GHCR credentials, so it runs in a release workflow job gated on GUEST_IMAGE_CLOSURE_PATHS and never in the moon gate, whose cost stays the existing guest-image build. GHCR's acceptance of the empty-config artifact form remains unverified: it cannot be proven without a real push, and the design record expects a scratch-tag push to settle it before the production job first runs. Refs RIG-3788
… auth The publish job resolved skopeo as `nixpkgs#skopeo`, which reads the mutable flake registry: a privileged `packages:write` job would run whatever upstream build the registry pointed at that day. Resolve it from the shared pinned helper instead, the same loop the sibling publish jobs use — and the helper is what the rest of the repo already depends on, because these lanes need the fork's patched skopeo, not any skopeo. Auth moves to the house pattern at the same time. A hand-written docker config carried the credential as base64 in a file this job assembled itself; `skopeo login --password-stdin` against a pinned `REGISTRY_AUTH_FILE` keeps the token off argv without that step. The pin is load-bearing: the login and the lane's own skopeo calls are separate processes, and skopeo's default creds location is environment-dependent on hosted runners, so a mismatch greens the login and then 401s the push. The lane threads `--authfile` through all three of its calls when the variable is set, and omits the flag entirely when it is not, so a local dry run still needs no credentials. Refs RIG-3788
…h shell Two design findings on the publish lane. The shell carried its own copy of the nix invocation, so the command that realises the published assets sat beside the gate's `build` task with nothing keeping the two in step: a flag or attr change on either side would publish a triple the gate never built. It now runs that task, leaving one realization boundary. moon frames task output with its own banner and summary lines, so the store paths are selected rather than read by position. The tests covered the pure core's return values and nothing the shell observably does, so a regression in path assembly, stdout, or the digest-assertion wiring would have left the core suite green. publish.test.ts drives the CLI against stub nix/git/skopeo tools and asserts the contract: stdout is exactly one digest reference, every declared descriptor has a blob of that size, a stale layout is cleared before the digest assertion can read it, an ambiguous registry probe never reaches `copy`, a registry resolving a different manifest exits digestMismatch, and --authfile reaches every call when the login pinned one. Writing that suite caught a real defect in the first version of the realization change: the store-path filter matched a literal `/nix/store/` prefix, which no stub can produce, and would also have failed anywhere the store lives elsewhere. Refs RIG-3788
…he scan Three defects that made the publish lane either dead or fail-open. The job could not succeed at all: routing realization through the gate's build task made moon a hard dependency, but the workflow provisioned only bun, so the lane died at exit 6 before publishing anything. Both toolchains now come from the gate-tools pin. A test cannot catch a missing workflow dependency, so the reproduction was a CI-shaped PATH carrying bun, skopeo and git but no moon. The tag-absence classifier read any "not found" as a free tag, so `creds was not found in the docker config file` meant publish — a local credentials fault would have overwritten a published artifact. It now matches only a registry's own absence answers; everything else aborts. The sibling lane's looser glob is safe there because its fallback merely continues a walk, where this one's would overwrite. The annotation scan was inert against every key this lane emits. Its `(^|_)…($|_)` boundary comes from environment-variable land, but OCI annotation keys are dot-and-dash separated, so `org.compass.guest.password` and `.registry-token` both scanned clean. Annotations are the only credential-leak defense here, and its one passing test used `GITHUB_TOKEN`, a shape the lane never produces. Also, each smaller than the above but each a real fault: - A missing or malformed agent-oci.lock threw instead of exiting badLayout. - An asset path that stats but is not a regular file escaped as an uncaught EISDIR; stat and the hashing read now share one guard. - An empty --repo passed the usage gate and failed after the whole multi-GiB realization. - The caller-supplied --layout was deleted recursively with no check; it must now carry an oci-layout marker. - Hashing read each asset whole into the heap, and the blob write read it again. The hash streams in chunks and the blob is copied by the filesystem. Verified the streamed digests equal coreutils sha256sum on all three. - The default layout directory was untracked but not ignored, leaving a multi-GiB tree in an auto-snapshotting working copy. Rejected one style finding: `triple` is not invented terminology, it is the frozen design's own wording for the three assets. Refs RIG-3788
rigel-mintaka
force-pushed
the
compass-managed/rig-3788-guest-artifact-publish
branch
from
September 17, 2026 03:09
d6ecc8b to
b2c0666
Compare
rigel-mintaka
changed the base branch from
compass-managed/rig-3787-derive-rootfs
to
main
September 17, 2026 03:09
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
rigel-mintaka
marked this pull request as ready for review
September 17, 2026 03:10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The three guest assets (kernel, erofs rootfs, initrd) publish as one OCI
artifact to ghcr.io/rigelbuild/compass-guest-image, reusing the runner-image
lane's shape: a pure unit-tested core does every mapping and fail-closed
check, the push is digest-asserted, and the deployable reference is
repo@sha256:...because GHCR has no server-side tag immutability.Nothing can run an erofs blob, so the manifest says so structurally: an
image manifest carrying artifactType application/vnd.compass.guest-image.v1,
the OCI 1.1 empty config, and three layers with their own media types. A
runtime handed this reference refuses at the config media type.
Two properties a local layout cannot demonstrate, so they are enforced in
code and covered by tests rather than assumed:
validates the declared size against the blob it receives, and the local
skopeo copypath reads the blob without ever comparing, so a placeholdersize passes every local check and is rejected only at push.
unenforced locally.
Per-asset annotations key on the filename a materialiser writes, not the nix
store basename:
verifyImageslooks its sha256sum manifest up byfilepath.Base, and a store basename carries a content hash that would never
match. The annotation block is therefore directly usable as the Runner's
--microvm-image-manifest.
Publishing needs network and GHCR credentials, so it runs in a release
workflow job gated on GUEST_IMAGE_CLOSURE_PATHS and never in the moon gate,
whose cost stays the existing guest-image build.
GHCR's acceptance of the empty-config artifact form remains unverified: it
cannot be proven without a real push, and the design record expects a
scratch-tag push to settle it before the production job first runs.
Refs RIG-3788