Skip to content

fix: rewrite config rootfs.diff_ids when injecting layers (#5)#6

Merged
cnuss merged 2 commits into
mainfrom
fix/issue-5-config-diffids
Jun 18, 2026
Merged

fix: rewrite config rootfs.diff_ids when injecting layers (#5)#6
cnuss merged 2 commits into
mainfrom
fix/issue-5-config-diffids

Conversation

@cnuss

@cnuss cnuss commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Fixes #5.

Problem

Deploying an image with layersFrom (rowdy runtime injection) produced an invalid OCI image: the injected layers were appended to the manifest but the image config's rootfs.diff_ids was left unchanged, so len(manifest.layers) != len(config.rootfs.diff_ids). Registries/runtimes drop the app layer and the function crashes with ENOENT on its entrypoint (spawn /usr/local/bin/score ENOENT).

Per the issue's follow-up, this affected both multi-arch and single-arch images.

A second, related issue: a bare image manifest (a single-platform build pushed with provenance: false, i.e. no buildx attestation index) was rejected in collect() with Unsupported mediaType on index: application/vnd.docker.distribution.manifest.v2+json.

Fix

In Transfer.collect():

  1. Config rewrite on injection — when a layersFrom platform match is found, fetch both the app and the runtime image configs, append the runtime config's rootfs.diff_ids (the uncompressed layer digests — only derivable from that config, not from the compressed layer descriptors) and history to the app config, re-serialize to a new digest/size, re-point data.config, and push the rewritten config blob instead of streaming the stale original. Assert layers.length === diff_ids.length before push.

  2. Bare manifest support — a top-level oci.image.manifest.v1+json / docker.distribution.manifest.v2+json is now treated as a one-entry index, with platform read from the image config, so the rest of the pipeline stays platform-agnostic and provenance: false builds deploy.

Upload now uploads pre-supplied config bytes when present (rather than always re-streaming the original from the source registry).

Also added types: ["node", "jest"] to tsconfig.json so editors resolve node globals (crypto, Buffer, …).

Tests

New non-AWS regression test in tests/api/internal/transfer.test.ts — collects busybox with layersFrom: ghcr.io/scaffoldly/rowdy:beta and asserts, for each injected platform, that the rewritten config's digest/size match data.config and manifest.layers.length === config.rootfs.diff_ids.length.

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

cnuss and others added 2 commits June 18, 2026 05:53
Deploying an image with layersFrom (rowdy runtime injection) produced an
invalid OCI image: the injected layers were appended to the manifest but the
image config's rootfs.diff_ids was left unchanged, so
len(manifest.layers) != len(config.rootfs.diff_ids). Registries/runtimes then
drop the app layer and the function crashes with ENOENT on its entrypoint.
This affected both multi-arch and single-arch images.

When a layersFrom match is found, fetch both the app and runtime image configs,
append the runtime config's diff_ids (the uncompressed digests, only derivable
from that config) and history to the app config, re-serialize it to a new
digest/size, re-point data.config, and push the rewritten config blob instead
of streaming the stale original. Assert layers.length === diff_ids.length
before push.

Also handle a bare image manifest (e.g. a single-platform build pushed without
buildx attestations) by treating it as a one-entry index, reading the platform
from its config, so `provenance: false` builds can be deployed.

Add types: ["node", "jest"] to tsconfig so editors resolve node globals.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Editor surfaced rootDir + deprecation errors on tests/tsconfig.json:
- noEmit + rootDir ".." resolve the "not under rootDir" / common-source-dir
  errors caused by including ../src/**/*.ts without an explicit rootDir.
- ignoreDeprecations silences the node10 moduleResolution / baseUrl
  deprecation notices pending a future TS migration.

This config is type-check/editor only; emit goes through tsup (root tsconfig)
and tests run via jest, both unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cnuss cnuss merged commit 2b9b3ab into main Jun 18, 2026
8 checks passed
@cnuss cnuss deleted the fix/issue-5-config-diffids branch June 18, 2026 10:04
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.

Multi-arch image: injected rowdy layer not added to config.rootfs.diff_ids → invalid image, app layer dropped (ENOENT)

1 participant