diff --git a/AGENTS.md b/AGENTS.md index de3b4b0..83ff63d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -146,8 +146,9 @@ into `templates/factory/code/pyproject.toml.tmpl` yourself. (`RECIPE_INVENTORY_SCOPE_TREE`), the Dockerfile-only one claims just what it emitted. Never rewrite `pyproject.toml` or `uv.lock` to make a path resolve. - **The runtime image is pinned.** `docker-image` rejects a bare name and - `:latest`. The default `codeflydev/python` companion is built by core and - repinned on release — prefer it. + `:latest`. The default `ghcr.io/codefly-dev/python` companion is built from + core and pinned by the digest of its multi-platform index — prefer it. Its tag + must equal the linked core's companion version (test-enforced). - **A user's `flake.nix` is never overwritten.** The embedded flake is written only when the source dir has none. - **Release needs cgo.** The binary links core's tree-sitter grammars, so diff --git a/main.go b/main.go index bcf76e0..97ec008 100644 --- a/main.go +++ b/main.go @@ -79,20 +79,39 @@ type Settings struct { // RuntimeImage overrides the default codefly-built runtime image. // Format: "name:tag". Plain "name" and ":latest" are rejected — - // pinning is enforced. Leave empty to use codeflydev/python: + // pinning is enforced. Leave empty to use ghcr.io/codefly-dev/python: // (recommended; companion is rebuilt + pinned on every codefly // release). Field named RuntimeImage (not DockerImage) to avoid // colliding with services.Base.DockerImage(req). RuntimeImage string `yaml:"docker-image"` } -// runtimeImage is the codefly-built Python runtime companion — -// python:3.13.1-alpine3.21 + codefly CLI + uv 0.5.29. Built from -// core/companions/python/. Users can override via the Python settings -// (DockerImage field) but NOT recommended — the companion image is -// the mode-consistent default and gets the same tool set as every -// other codefly-built image. -var runtimeImage = &resources.DockerImage{Name: "codeflydev/python", Tag: "0.0.1"} +// runtimeImage is the codefly-built Python runtime companion, built from +// core/companions/python/ and published by the CLI to ghcr.io/codefly-dev as +// a linux/amd64 + linux/arm64 image index. Users can override it through the +// Python settings (docker-image) but should not: the companion is the +// mode-consistent default and carries the same tool set as every other +// codefly-built image. +// +// It is pinned by the digest of the multi-platform INDEX, never of one +// platform's manifest: the build recipe is rendered for every target platform +// and each resolves its own entry from the index. The legacy +// docker.io/codeflydev/python:0.0.1 this replaces was a single amd64 manifest, +// so every linux/arm64 image build ran amd64 binaries under emulation and died +// on its first RUN ("Invalid ELF image for this architecture"). +// +// Tag is the companion version of the core this agent links +// (core/companions/python/info.codefly.yaml); a test holds the two together, +// so bumping core without re-pinning here fails the build. Re-pin the digest +// from the registry when the tag moves: +// +// docker buildx imagetools inspect ghcr.io/codefly-dev/python: +var runtimeImage = &resources.DockerImage{ + Repository: resources.ImageRegistry, + Name: "python", + Tag: "0.0.5", + Digest: "sha256:67d6fe825765fc30f94131ee551e3800f5b6fa4e07094b47d036904387cb75a7", +} // uvVersion is the uv the emitted build recipe resolves its lockfile with. // diff --git a/runtime_image_test.go b/runtime_image_test.go new file mode 100644 index 0000000..abae237 --- /dev/null +++ b/runtime_image_test.go @@ -0,0 +1,33 @@ +package main + +import ( + "context" + "regexp" + "testing" + + "github.com/codefly-dev/core/companions/python" + "github.com/codefly-dev/core/resources" + "github.com/stretchr/testify/require" +) + +// The default runtime image is the Python companion of the core this agent +// links, from the registry codefly publishes to. A core bump that moves the +// companion version fails here until the pin, and its digest, move with it. +func TestRuntimeImageIsTheLinkedCoreCompanion(t *testing.T) { + companion, err := python.CompanionImage(context.Background()) + require.NoError(t, err) + require.Equal(t, companion.Repository, runtimeImage.Repository) + require.Equal(t, companion.Name, runtimeImage.Name) + require.Equal(t, companion.Tag, runtimeImage.Tag, + "re-pin runtimeImage (tag and index digest) to core's python companion") +} + +// A tag alone lets the registry move the image under a built recipe, and a +// platform manifest digest would pin every target platform to one +// architecture. The pin is a digest; that it names the multi-platform index is +// what the comment on runtimeImage records and a reviewer checks. +func TestRuntimeImageIsDigestPinned(t *testing.T) { + require.Regexp(t, regexp.MustCompile(`^sha256:[0-9a-f]{64}$`), runtimeImage.Digest) + require.Equal(t, resources.ImageRegistry+"/python@"+runtimeImage.Digest, runtimeImage.FullName(), + "the recipe's FROM must resolve by digest") +}