Skip to content

Persist OCI manifest content model per image digest - #455

Draft
chruffins wants to merge 20 commits into
hypeship/image-tag-apifrom
hypeship/manifest-layer-model
Draft

Persist OCI manifest content model per image digest#455
chruffins wants to merge 20 commits into
hypeship/image-tag-apifrom
hypeship/manifest-layer-model

Conversation

@chruffins

@chruffins chruffins commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

summary

Stacked stage of the image-storage project. Persists one content model document per image digest at images/content/<digest>/manifest.json:

  • manifest digest (immutable identity) and media type
  • resolved platform (os/arch[/variant])
  • config blob digest with its ordered rootfs.diff_ids
  • ordered layer descriptors (compressed digest, size, media type) paired with diff ids by position
  • a blobReferences() accessor listing config + layer digests for future GC

why

Stages 4/5 need the ordered layer list and diff ids to materialize per-layer artifacts and recompose rootfs without re-reading registries; GC needs to know which OCI cache blobs each ready image still references. Extracted from the existing OCI layout cache during the pull's metadata phase — no second blob downloader.

details

  • extractManifestModel reads manifest + config from the shared layout cache (system/oci-cache), reusing already-downloaded blobs.
  • Written atomically in finalizeImage beside the shared content; legacy images simply have no model (read returns nil).
  • Metadata writes now share a single atomic temp-file-then-rename helper (writeJSONAtomic).

validation

  • go test ./lib/images ./lib/paths -count=1 — new tests cover synthetic two-layer layouts (ordering, digest/diff-id pairing, platform), write/read roundtrip atomicity, missing-model reads, blob references, and an end-to-end import that lands a ready image with a correct model.
  • Docker Hub-backed pull tests are rate-limited intermittently in this environment; they passed in the unthrottled window earlier in this work.

Note

Low Risk
Additive on-disk metadata on the image finalize path; no auth or API behavior changes, and missing models are handled for legacy images.

Overview
Adds a persisted OCI manifest content model at images/content/<digest>/manifest.json for each image that finishes the pull/build pipeline. The document captures manifest identity, platform, config blob digest with ordered diff_ids, and ordered layer descriptors (compressed digest, size, media type) so later work can rebuild rootfs from per-layer artifacts and GC can see which OCI cache blobs are still referenced via blobReferences().

During pull, extractManifestModel builds this structure from the shared OCI layout cache in the same metadata phase as existing inspection—no extra registry downloads. finalizeImage writes the model atomically next to shared content and overwrites platform with the resolved manifest platform. Images converted before this change simply have no file; readManifestModel returns nil without error.

Metadata JSON writes now share writeJSONAtomic (temp file + rename), including manifest persistence.

Reviewed by Cursor Bugbot for commit 752a6d8. Configure here.

@chruffins
chruffins force-pushed the hypeship/manifest-layer-model branch from 8255d11 to 1f79ed8 Compare August 26, 2026 18:45
@github-actions

Copy link
Copy Markdown
-->

✱ stlc build

go code · compare

Your SDK build was successful.

generate ✅bootstrap ✅format ✅

116 files generated at 3905c47 (pushed)

go get github.com/kernel/hypeman-go-staging@3905c476d8956f8a94aa6f323e6eb47206492f43
python code · compare

Your SDK build was successful.

generate ✅bootstrap ✅format ✅

231 files generated at 12a370e (pushed)

typescript code · compare

Your SDK build was successful.

generate ✅bootstrap ✅format ✅

138 files generated at eda2300 (pushed)

Diagnostics: ❗ 0 new / 1 total error, 💡 0 new / 5 total note
LevelCodeMessageTargets
Build metadata
Buildbd_76DJMxGw-keen-kelp
Timestamp2026-08-26T18:47:50.999Z
stlc8413509
Spec hash0fc2bc5606d2
Config hash659c3687c3f0

This comment is auto-generated by stlc and is kept up to date as you push.
If you push new commits, re-run this workflow to update this comment.
Last updated: 2026-08-26 18:48:24 UTC

@chruffins
chruffins force-pushed the hypeship/manifest-layer-model branch 2 times, most recently from 1d3b38c to e6fdc4c Compare August 26, 2026 18:53
@chruffins
chruffins force-pushed the hypeship/manifest-layer-model branch from 345e8e0 to 07373a3 Compare August 26, 2026 18:55
@chruffins
chruffins force-pushed the hypeship/manifest-layer-model branch from 07373a3 to e8971b4 Compare August 26, 2026 18:58
@chruffins
chruffins force-pushed the hypeship/manifest-layer-model branch from e8971b4 to 4639430 Compare August 26, 2026 19:26
@chruffins
chruffins force-pushed the hypeship/manifest-layer-model branch from 4639430 to 273909b Compare August 26, 2026 19:30
@chruffins
chruffins force-pushed the hypeship/manifest-layer-model branch from 31c4161 to c6dcc2c Compare August 26, 2026 19:47
promoteImageToContent was implemented but never invoked, so ready
per-repository images were never migrated into the digest-keyed content
layout. Promote them at manager startup: hardlink the rootfs into shared
content, repoint repository tags, and retire the legacy tree. Non-ready
images are left untouched and failures only warn.
Tag symlinks now point relatively into the shared content directory, so
assert on the resolved digest instead of the raw link target. Deleting a
digest whose build is still in flight keeps the shared content and a
re-import joins that build, so the recreate-race test must expect the
same build id. Recovery writes metadata through the layout resolver, so
the credentials scrub check reads the content metadata path.

Reuse metadataStatus for content metadata instead of duplicating the
parse.
New builds write rootfs and metadata under the shared content directory,
so EnsureImageReady must copy from the resolved disk location instead of
the legacy per-repository digest directory and create its tag symlink in
the repository references layout.
POST /images/{name}/tag creates or updates a repository tag pointing at
an existing ready image without pulling or reconverting it. Sources may
be a tag or digest; targets must carry a tag. Cross-repository targets
promote the digest into shared content first so every alias resolves to
one rootfs copy, and deletion keeps content alive while any repository
tag references it.

The tag symlink is staged and renamed atomically, so retagging an
existing name never leaves a dangling reference.
Ported from the earlier tag draft: verify the generated tag route decodes
URL-encoded source names, and keep the builds test mock satisfying the
extended Manager interface.
Record the manifest's ordered layer descriptors, config blob with its
diff ids, and resolved platform beside the shared content
(images/content/<digest>/manifest.json). The model is extracted from the
existing OCI layout cache during the metadata phase of a pull, so no
second blob downloader is introduced.

Layer digests pair with config diff ids by position, giving later stages
everything needed to materialize per-layer artifacts and recompose the
rootfs, and GC the set of OCI blobs each ready image still references.
Metadata writes now share one atomic temp-file-then-rename helper.
@chruffins
chruffins force-pushed the hypeship/manifest-layer-model branch from c6dcc2c to cc7944c Compare August 26, 2026 22:22
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