Uh oh!
There was an error while loading. Please reload this page.
fix(chart): move the prod ingestor pin into chart defaults so it reaches edges - #398
Conversation
…hes edges The prod ingestor digest pin reached no customer edge, for two independent reasons. 1. Standard installs never applied the overlay. `client/values-prod.yaml` was an install-time `-f` overlay, but the installer runs `helm upgrade --install … --values "$values_file"` with only its generated values file. No installer path ever passed the overlay, so a normal prod install kept the base default `images.ingestor.digest: ""` and floated on tag `0.7` with imagePullPolicy=Always — exactly like dev and staging. 2. Where it was applied by hand it could never be updated. The fleet auto-upgrade CronJob runs `helm upgrade --reset-then-reuse-values` with no `-f` and no `--set`: that resets to the new chart's `values.yaml` defaults, then re-applies the release's stored user-supplied values. An overlay value is user-supplied, so it was replayed verbatim forever — and Helm only auto-reads `values.yaml` from a chart, so the updated overlay shipped inside the new chart archive was never read. Chart defaults propagate through that upgrade; user-supplied values freeze. So the pin has to be a chart default, which is how the egress-proxy squid image has always been pinned. * `images.ingestor.prodDigest` (new, chart default) carries the pin. * `images.ingestor.prodPin` (new, default true) gates it. A new `tracebloc.ingestorDigest` helper resolves one effective digest for every consumer: explicit `images.ingestor.digest` wins in any environment, else `prodDigest` when prodPin is on and the resolved CLIENT_ENV is prod, else empty (float on `tag`). dev/staging carry `env.CLIENT_ENV: dev|stg` in user-supplied values while the template defaults it to prod, so prod pins and non-prod floats with zero per-edge action. `prodPin: false` floats a canary prod edge. * The metadata-backfill hook now shares the same helper, so it can never run a different build than the ingestions it reconciles. * The consuming contract in client-runtime `_build_image_reference` is unchanged: digest set -> repo@digest + IfNotPresent, empty -> repo:tag + Always. `client/values-prod.yaml` is removed rather than kept as a shim: passing `-f values-prod.yaml` is what created the frozen-value bug, so leaving the file would invite operators to re-create it. `MIGRATION.md` documents the move, the canary knob, and how to clear a hand-layered stored digest (which still takes precedence and would otherwise stay frozen). `resolve-ingestor-digest.sh` keeps its resolution mechanism (multi-arch index digest + guard) and now writes `prodDigest` in `client/values.yaml`, keyed on that unique name with a single-match assertion so it can never rewrite a sibling image's `digest:` leaf, and reminding the operator to bump Chart.yaml. CI's `ingestor-multiarch` guard now validates `prodDigest` too — it previously read only values.yaml and so never checked the overlay it was meant to guard — and hard-fails an empty pin, which would silently un-pin prod. Verified on a throwaway k3d cluster: installing the published 1.9.5 chart renders an empty digest (prod floats today), and `--reset-then-reuse-values` to this tree lands the chart-default pin on the already-installed edge, while the same upgrade leaves an overlay-style user-supplied pin frozen on its install-day value. `scripts/tests/e2e-auto-upgrade.sh` now asserts all of that plus the prodPin opt-out surviving the next auto-upgrade. Chart bumped 1.9.5 -> 1.9.6 — the chart only publishes on a version change, so an unbumped pin refresh reaches no edge. Fixestracebloc/backend#1245 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LukasWodka
commented
Jul 25, 2026
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit e381555. Configure here.
divyasinghds
left a comment
There was a problem hiding this comment.
Approve. The most consequential of the batch; the core reasoning (chart defaults propagate through --reset-then-reuse-values, user-supplied values freeze) is correct and the helper logic holds.
tracebloc.ingestorDigestprecedence is right: explicitdigest→ elseprodPin(default-true)&& CLIENT_ENV==prod→prodDigest→ else empty/float. Every read is nil-guarded, so a--reuse-valuesupgrade from a pre-key release still renders (and correctly does not pin — the documented path-1 limitation, asserted in e2e).- Complete consumer coverage — jobs-manager and metadata-backfill are the only two ingestor-digest consumers;
image-refresh-cronjob.yamlexplicitly excludes the ingestor, so nothing bypasses the shared helper. - No dangling
values-prod.yamlreferences after deletion (no installer/CI path ever passed it — which was the bug). - CI
ingestor-multiarchnow hard-fails an empty/single-archprodDigest;resolve-ingestor-digest.shis hardened with a single-match assertion so it can't rewrite a sibling image'sdigest:leaf. Chart bumped 1.9.5 → 1.9.6 (required to publish).
The gate rests on the invariant that dev/staging carry env.CLIENT_ENV in user-supplied values (else they'd resolve to prod and pin) — an existing installer guarantee, now locked by the new helm-unittest + e2e cases.
Reviewed with Claude Code.
Uh oh!
There was an error while loading. Please reload this page.
aptracebloc
commented
Jul 27, 2026
Post-merge review — for the record, this would have been an approve. Correct root-cause diagnosis: the prod ingestor pin reached no edge because it lived in an install-time The
Two things I especially liked: LGTM (retroactively). — drafted with Claude (Opus 4.8), sent by @aptracebloc |
…ase now carries the prod pin The path-1 assertion hardcoded the pre-#398 era: it expected NO ingestor pin after a --reuse-values upgrade because the published release's computed values predated images.ingestor.prodDigest. The #383 promotion (2026-07-27) published a release that includes the pin, so replayed computed values now carry it and the assertion fails on every chart-touching PR (first hit: #454). The expectation is now read from the baseline release itself (helm get values --all): pin absent => must not arrive (old behavior); pin present => the SAME digest must be replayed verbatim. Both eras keep asserting the actual limitation: --reuse-values never injects new chart defaults. Path-2's era-stale comment corrected, with a note on the replay-contamination signal a future pin bump will surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ge prod image (#454) * fix(chart): pin mysql-client by digest — keep fleets on the 5.7-lineage prod image (backend#723) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(e2e): era-aware --reuse-values pin assertion — the published release now carries the prod pin The path-1 assertion hardcoded the pre-#398 era: it expected NO ingestor pin after a --reuse-values upgrade because the published release's computed values predated images.ingestor.prodDigest. The #383 promotion (2026-07-27) published a release that includes the pin, so replayed computed values now carry it and the assertion fails on every chart-touching PR (first hit: #454). The expectation is now read from the baseline release itself (helm get values --all): pin absent => must not arrive (old behavior); pin present => the SAME digest must be replayed verbatim. Both eras keep asserting the actual limitation: --reuse-values never injects new chart defaults. Path-2's era-stale comment corrected, with a note on the replay-contamination signal a future pin bump will surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(e2e): fail fast when jq is missing (review: local runs, not just CI runners) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(e2e): one era boundary, stated once (the #383 promotion); track the path-2 tripwire in #459 (review) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(chart)+test: single-arch pin note + render assertion for the digest path (review) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ow carries the prod pin (#455) * fix(e2e): era-aware --reuse-values pin assertion — the published release now carries the prod pin The path-1 assertion hardcoded the pre-#398 era: it expected NO ingestor pin after a --reuse-values upgrade because the published release's computed values predated images.ingestor.prodDigest. The #383 promotion (2026-07-27) published a release that includes the pin, so replayed computed values now carry it and the assertion fails on every chart-touching PR (first hit: #454). The expectation is now read from the baseline release itself (helm get values --all): pin absent => must not arrive (old behavior); pin present => the SAME digest must be replayed verbatim. Both eras keep asserting the actual limitation: --reuse-values never injects new chart defaults. Path-2's era-stale comment corrected, with a note on the replay-contamination signal a future pin bump will surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(e2e): fail fast when jq is missing (review: local runs, not just CI runners) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(e2e): one era boundary, stated once (the #383 promotion); track the path-2 tripwire in #459 (review) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Summary
The prod ingestor digest pin reached no customer edge. This moves it from an
install-time
-foverlay into the chart's defaultvalues.yaml, gated to prod,so it propagates through the auto-upgrade the fleet already runs.
Two independent reasons the overlay never applied:
helm upgrade --install … --values "$values_file"with only its generatedvalues file; no installer path ever passed
-f client/values-prod.yaml. Anormal prod install kept the base default
images.ingestor.digest: ""andfloated on tag
0.7withimagePullPolicy: Always— exactly like dev/staging.runs
helm upgrade --reset-then-reuse-valueswith no-fand no--set.That resets to the new chart's
values.yamldefaults, then re-applies therelease's stored user-supplied values. An overlay value is user-supplied, so
it was replayed verbatim forever — and Helm only auto-reads
values.yamlfroma chart, so the updated overlay inside the new chart archive was never read.
Chart defaults propagate; user-supplied values freeze. So the pin has to be a
default — which is how the egress-proxy squid image has always been pinned, and
why that one does reach installed edges.
What changed
images.ingestor.prodDigestimages.ingestor.prodPintruefalsefloats a canary prod edge.images.ingestor.digestA new
tracebloc.ingestorDigesthelper resolves one effective digest:images.ingestor.digestnon-empty → that digest, any environment.prodPin(default true when the key is absent) and resolvedCLIENT_ENV == "prod"→prodDigest.tag.Gating mechanism — why CLIENT_ENV. dev/staging installs carry
env.CLIENT_ENV: dev|stgin their user-supplied values, while the templatedefaults
CLIENT_ENVtoprod. So a template-level gate on the resolvedCLIENT_ENVgives prod-pinned / non-prod-floating with zero per-edge actionand no new install-time flag.
prodPindefaults totruewhen the key is absentso a
--reuse-valuesupgrade from a release predating it doesn't silentlydefeat the pin.
The metadata-backfill hook now shares the same helper, so it can never run a
different ingestor build than the ingestions it reconciles.
The consuming contract in client-runtime
_build_image_referenceis unchanged:digest set →
repo@digest+IfNotPresent; digest empty →repo:tag+Always.values-prod.yaml— removed, not shimmedPassing
-f values-prod.yamlis precisely what created the frozen-value bug, sokeeping the file as a no-op shim would invite operators to re-create it. It is
deleted and every reference updated.
MIGRATION.mddocuments the move, thecanary knob, and — importantly — how to clear a hand-layered stored
images.ingestor.digest, which still takes precedence and would otherwise keepsuch an edge frozen:
Other
resolve-ingestor-digest.shkeeps its resolution mechanism (multi-arch indexdigest + guard); only the destination changes. It now writes
prodDigestinclient/values.yaml, keyed on that unique name with a single-match assertionso it can never rewrite a sibling image's
digest:leaf (values.yaml has oneper image), and it reminds the operator to bump
Chart.yaml.ingestor-multiarchguard now validatesprodDigesttoo. It previouslyread only
values.yaml, so it never checked the overlay it was meant to guard— a gap the old overlay's own comments admitted. It also hard-fails an empty
pin, which would silently un-pin prod.
on a version change and nothing in CI guards a forgotten bump.
prodDigestis set to the multi-arch index the published0.7tag currentlyresolves to, so switching the pin on is behaviour-neutral on day one: prod
keeps running exactly the image it runs today, now reproducibly. (The digest
previously committed in the overlay had already gone stale — see below.)
Test results
helm unittest ./client— 304 passed, 27 suites (was 297). Addedhelm-unittest cases asserting prod renders the pinned digest and dev/staging
render the floating tag, plus explicit-
CLIENT_ENV=prod,prodPin: false,empty-
prodDigestfallback, and explicit-digest-overrides-prod-pin.helm lint --strict ./client -f client/ci/{aks,bm,eks,oc}-values.yaml— cleanon all four;
helm lint --strict ./ingestorclean.helm templateproving the gate:CLIENT_ENV(what the standard prod installer generates) →INGESTOR_IMAGE_DIGEST: "sha256:9098b3c9…"--set env.CLIENT_ENV=dev→INGESTOR_IMAGE_DIGEST: ""prodDigestand a non-booleanprodPinare bothrejected by
values.schema.json.shellcheck --severity=errorclean on both changed shell scripts (warningsclean too);
bash -nclean acrossscripts/;scripts/check-style.shclean;scripts/gen-manifest.sh --checkreports the manifest current (neither changedscript is part of the bootstrap's integrity surface, so no manifest change).
today, confirming the bug.
--reset-then-reuse-values(the exact fleet command, no-f, no--set)lands the chart-default pin on the already-installed edge. Setting
env.CLIENT_ENV=devon the same live release floats it again.pin frozen on its install-day value, with the chart's newer pin ignored.
Clearing it with
--set images.ingestor.digest=""restores tracking.scripts/tests/e2e-auto-upgrade.shnow asserts all of this permanently,including that a
prodPin: falseopt-out survives the next auto-upgrade.Note for RFC 0005 / epic tracebloc/backend#1246
The same distribution flaw applies to the training-image digest map proposed
in RFC 0005. A digest map delivered as an install-time overlay would reach no
edge and could never be updated, for exactly the reasons above. That is why the
RFC now specifies chart defaults rather than an overlay — and any per-env
gating there should follow the resolved-
CLIENT_ENVpattern established here.One thing that contradicted the issue
The issue lists the pin as
sha256:78f21a08…onmainandsha256:a5f2650b…ondevelop. Resolving the published0.7tag against the registry today returnsneither — it is now
sha256:9098b3c9…. The0.7line moved again after theoverlay's last
# VERIFIEDrefresh, so that digest was already stale on top ofbeing undeliverable. Pinning the current head keeps activation behaviour-neutral;
pinning
a5f2650b…would have moved prod backwards from what it runs today.Fixes tracebloc/backend#1245