Skip to content

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
mainfrom
compass-managed/rig-3786-agent-image-pin
Open

feat(guest-image): pin the agent OCI image the guest rootfs derives from (RIG-3786)#1215
rigel-mintaka wants to merge 2 commits into
mainfrom
compass-managed/rig-3786-agent-image-pin

Conversation

@rigel-mintaka

Copy link
Copy Markdown
Contributor

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 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

…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>
@linear-code

linear-code Bot commented Sep 13, 2026

Copy link
Copy Markdown

RIG-3786

@trunk-io

trunk-io Bot commented Sep 13, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

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

@github-actions

github-actions Bot commented Sep 13, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-managed-rig-3786-age.compass-eng-docs.pages.dev

Deployed from compass-managed/rig-3786-agent-image-pin at 6cabd50.

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
rigel-mintaka marked this pull request as ready for review September 13, 2026 22: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