sandbox: resolve the image the publisher actually wrote, and let a digest pin it
CI/CD / gate (push) Canceled after 0s
CI/CD / containment (push) Canceled after 0s
CI/CD / image (push) Canceled after 0s
CI/CD / rollout (push) Canceled after 0s
CI/CD / reach (push) Canceled after 0s
CI/CD / fanout (push) Canceled after 0s
CI/CD / receipt (push) Canceled after 0s

TWO defects, both silent, both found by trying to start a real sandbox.

THE TAG ORDER WAS BACKWARDS. imageFor composed <class>-<version> and asked for
`dev-2026.6.7`; the registry holds `2026.6.7-dev`. So the default path 404'd on
an image sitting right there, and the only reason anything ever ran was a test
that overrode the image entirely. The publisher wins this argument: hanzoai/ci
appends a per-image tag-suffix to the version, so the whole fleet is
<version>-<suffix> and a consumer spelling it the other way is simply wrong.

A VERSION TAG IS NOT A PIN HERE. The sandbox image ships from hanzoai/bot under
BOT's package.json version, last bumped 2026-06-07. A rebuild today — commit
d1904514f4, "box: uv ships at the root" — republished `2026.6.7-dev` from source
two months newer. A tag that gets rewritten is not a pin, and one that LOOKS
pinned is worse than `latest`, which at least admits what it is.

So SANDBOX_IMAGE_DIGEST_<CLASS> is honoured ahead of any tag. `repo@sha256:…`
names bytes, and bytes do not change under a running fleet.

PER CLASS, not one variable. A single SANDBOX_IMAGE_DIGEST would have handed
every class the exec image while every log line still read correctly — the same
shape as the tag bug above. image_test.go pins that case specifically, along with
the order, because both of these are invisible until a pod fails to pull.
This commit is contained in:
zeekay
2026-08-06 13:51:40 -07:00
committed by zeekay
parent 7881feacfc
commit dbcb9040a0
2 changed files with 80 additions and 1 deletions
+50
View File
@@ -0,0 +1,50 @@
package sandbox
// What image a class resolves to, pinned — because getting this wrong is silent.
//
// Two real defects motivate every case here. The tag ORDER was reversed: cloud
// asked for `dev-2026.6.7` while the registry held `2026.6.7-dev`, so the
// default path 404'd on an image sitting right there. And a single
// SANDBOX_IMAGE_DIGEST would have handed every class the same image, which
// looks correct in every log line it produces.
import "testing"
func TestImageForResolvesTheTagThePublisherWrote(t *testing.T) {
for _, c := range []struct {
name, repo, tag, class, want string
digest map[string]string
}{{
name: "version first, class second — the order CI publishes",
repo: "oci.hanzo.ai/hanzoai/sandbox", tag: "2026.6.7", class: "dev",
want: "oci.hanzo.ai/hanzoai/sandbox:2026.6.7-dev",
}, {
name: "no tag is the bare class, unchanged",
repo: "oci.hanzo.ai/hanzoai/sandbox", class: "exec",
want: "oci.hanzo.ai/hanzoai/sandbox:exec",
}, {
// A digest names BYTES. A version tag can be republished — hanzoai/bot
// ships this image under bot's own package.json version, so a rebuild
// today re-wrote 2026.6.7-dev from a commit two months newer.
name: "a digest wins over any tag",
repo: "oci.hanzo.ai/hanzoai/sandbox", tag: "2026.6.7", class: "dev",
digest: map[string]string{"DEV": "sha256:2baf7ede"},
want: "oci.hanzo.ai/hanzoai/sandbox@sha256:2baf7ede",
}, {
// The bug this file caught before it shipped.
name: "each class takes ITS OWN digest, never a neighbour's",
repo: "oci.hanzo.ai/hanzoai/sandbox", tag: "2026.6.7", class: "exec",
digest: map[string]string{"DEV": "sha256:2baf7ede"},
want: "oci.hanzo.ai/hanzoai/sandbox:2026.6.7-exec",
}} {
t.Run(c.name, func(t *testing.T) {
for k, v := range c.digest {
t.Setenv("SANDBOX_IMAGE_DIGEST_"+k, v)
}
r := &runtime{image: c.repo, tag: c.tag}
if got := r.imageFor(c.class); got != c.want {
t.Fatalf("imageFor(%q) = %q, want %q", c.class, got, c.want)
}
})
}
}
+30 -1
View File
@@ -175,6 +175,9 @@ func (r *runtime) ready() error {
// version; nothing here resolves `latest`, because an image decided by WHEN the
// pod started rather than by what was shipped is not a deployment.
func (r *runtime) imageFor(class string) string {
if d := r.digestFor(class); d != "" {
return r.image + "@" + d
}
tag := r.tag
if tag == "" {
tag = envOr("SANDBOX_IMAGE_TAG_"+strings.ToUpper(class), "")
@@ -182,7 +185,33 @@ func (r *runtime) imageFor(class string) string {
if tag == "" {
return r.image + ":" + class
}
return r.image + ":" + class + "-" + tag
// <version>-<class>, which is the order CI PUBLISHES. This read
// `class + "-" + tag` and asked for `dev-2026.6.7` while the registry held
// `2026.6.7-dev`, so the default path 404'd on an image that was sitting
// right there. The publisher wins that argument: hanzoai/ci appends its
// per-image `tag-suffix` to the version, so every image in the fleet is
// <version>-<suffix> and a consumer that spells it the other way is simply
// wrong.
return r.image + ":" + tag + "-" + class
}
// DigestFor pins by CONTENT when the deployment names a digest, and it is the
// only pin that cannot move.
//
// A version tag looks immutable and is not: hanzoai/bot ships the sandbox image
// under bot's own package.json version, which was last bumped 2026-06-07, so a
// rebuild TODAY republished `2026.6.7-dev` from a commit two months newer. A tag
// that gets rewritten is not a pin, and one that LOOKS like a pin is worse than
// `latest`, which at least admits what it is.
//
// SANDBOX_IMAGE_DIGEST is therefore honoured ahead of any tag: `repo@sha256:…`
// names bytes, and bytes do not change under a running fleet.
// It is PER CLASS, because the three classes are three different images and one
// digest names one of them. A single SANDBOX_IMAGE_DIGEST would have quietly
// given every class the exec image — the same shape of bug as a tag that looks
// pinned and is not, which is what this function exists to end.
func (r *runtime) digestFor(class string) string {
return strings.TrimSpace(os.Getenv("SANDBOX_IMAGE_DIGEST_" + strings.ToUpper(class)))
}
func (r *runtime) pods() dynamic.ResourceInterface {