feat(guest-image): pin the agent OCI image the guest rootfs derives from (RIG-3786) - #1215
Open
rigel-mintaka wants to merge 2 commits into
Open
feat(guest-image): pin the agent OCI image the guest rootfs derives from (RIG-3786)#1215rigel-mintaka wants to merge 2 commits into
rigel-mintaka wants to merge 2 commits into
Conversation
…rom (RIG-3786) The microVM guest rootfs is about to be derived from the PUBLISHED compass-agent image rather than re-evaluated from the agent's nix expressions. That makes the pin the whole provenance story, so this adds it first, on its own. guest-image/agent-oci.lock records the repo, an immutable per-commit tag, the manifest digest they resolve to, and every layer descriptor digest the rootfs's fixed-output fetches will key on. tools/guest-image/pin-core.ts enforces provenance rather than documenting it: the only accepted repo is ghcr.io/rigelbuild/compass-agent, the only accepted tag shape is the publish lane's immutable git-<sha12>, and a multi-platform index is refused outright because it would need a platform choice the lock cannot record. Layer order is preserved deliberately — the layers stack into the filesystem, so a reordered set fetches identical bytes and unpacks a different rootfs. The Renovate lockstep is the non-obvious half. A bare regex manager is NOT sufficient here, unlike the digest-only postgres pin where the rewrite completes the update: the lock carries fetch-key fields Renovate cannot compute, so a digest-only bump would leave the layer descriptors describing the previous manifest and redden every fixed-output fetch on every Renovate PR. That is the same stale-paired-field class refresh-devenv-lock.ts names for a rev-only rewrite, so this takes the same remedy: a customManager PLUS a branch-mode postUpgradeTask relock, solo-grouped so it owns the branch's single task slot, with the paired bot-config allowlist entry. Detection tracks the moving :latest digest, because the pinned tag is per-commit immutable and no datasource can order it. The relock resolves :latest back to whichever immutable tag shares its manifest, which is sound because the publish lane asserts the pair shares a config digest and fails closed. Verified live against the registry: git-ec4954bd9400 matches :latest on both manifest and config digest. Tag discovery walks the tag list newest-first. The registry lists tags oldest-first and the wanted tag is almost always newest, so in-order discovery cost a round trip per historical tag — measured at 121s, now 3s — and compares digests without fetching the ~120-layer manifest body. The project is registered in .moon/workspace.yml AND defines a `ci` aggregate: ci-matrix emits a target only for a project that defines one, so without it the guard suite would be registered and still silently inert. Co-authored-by: Matt Wilkinson <matt@rigel.build>
|
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 |
|
Compass engineering docs preview: https://compass-managed-rig-3786-age.compass-eng-docs.pages.dev Deployed from |
Review findings on the agent-image pin, plus one the review prompted me to measure. The digest no longer comes from a second registry read. `inspect` made two skopeo calls against a MUTABLE tag — one for the manifest body, one for the digest — so a publish landing between them would pair one manifest's layers with another's digest, and the lock would describe layers its own digest disowns. A manifest digest is by definition the sha256 of the manifest bytes, verified here against the registry's own reported digest on the live image, so one fetch hashed locally gives both and the digest provably describes the body that was pinned. The Renovate rule no longer inherits the repo-wide 5-day soak. This image is built by nix2container, which zeroes timestamps for reproducibility, so the registry reports Created: 0001-01-01T00:00:00Z and `internalChecksFilter: "strict"` over an unknown age would hold the digest permanently pending — zero PRs, the stale-pin-forever outcome the rule exists to prevent. Nulled with the git-refs siblings' rationale, NOT the postgres pin's, which keeps its soak precisely because Docker Hub carries a real timestamp. Two guards added: the cooldown is nulled, and the dep resolves enabled rather than being swallowed. Tag discovery filters candidates by the same predicate the lock validator enforces, so a malformed git-* tag sharing the digest is skipped rather than selected and then hard-failing the relock. The render test asserted renderLock against itself, which passes for any deterministic implementation; I tried three mutations before finding that canonical key order is structural in renderLock and therefore unfalsifiable. Replaced with the byte-exact output, which does fail when the indentation changes. Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka
marked this pull request as ready for review
September 13, 2026 22:46
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 microVM guest rootfs is about to be derived from the PUBLISHED
compass-agent image rather than re-evaluated from the agent's nix
expressions. That makes the pin the whole provenance story, so this adds
it first, on its own.
guest-image/agent-oci.lock records the repo, an immutable per-commit
tag, the manifest digest they resolve to, and every layer descriptor
digest the rootfs's fixed-output fetches will key on.
tools/guest-image/pin-core.ts enforces provenance rather than
documenting it: the only accepted repo is ghcr.io/rigelbuild/compass-agent,
the only accepted tag shape is the publish lane's immutable git-,
and a multi-platform index is refused outright because it would need a
platform choice the lock cannot record. Layer order is preserved
deliberately — the layers stack into the filesystem, so a reordered set
fetches identical bytes and unpacks a different rootfs.
The Renovate lockstep is the non-obvious half. A bare regex manager is
NOT sufficient here, unlike the digest-only postgres pin where the
rewrite completes the update: the lock carries fetch-key fields Renovate
cannot compute, so a digest-only bump would leave the layer descriptors
describing the previous manifest and redden every fixed-output fetch on
every Renovate PR. That is the same stale-paired-field class
refresh-devenv-lock.ts names for a rev-only rewrite, so this takes the
same remedy: a customManager PLUS a branch-mode postUpgradeTask relock,
solo-grouped so it owns the branch's single task slot, with the paired
bot-config allowlist entry.
Detection tracks the moving :latest digest, because the pinned tag is
per-commit immutable and no datasource can order it. The relock resolves
:latest back to whichever immutable tag shares its manifest, which is
sound because the publish lane asserts the pair shares a config digest
and fails closed. Verified live against the registry: git-ec4954bd9400
matches :latest on both manifest and config digest.
Tag discovery walks the tag list newest-first. The registry lists tags
oldest-first and the wanted tag is almost always newest, so in-order
discovery cost a round trip per historical tag — measured at 121s, now
3s — and compares digests without fetching the ~120-layer manifest body.
The project is registered in .moon/workspace.yml AND defines a
ciaggregate: ci-matrix emits a target only for a project that defines one,
so without it the guard suite would be registered and still silently
inert.
Co-authored-by: Matt Wilkinson matt@rigel.build