Skip to content

feat(stack): pin slim artifacts by revision and make the catalog the single version table - #6883

Merged
jgoux merged 47 commits into
developfrom
juliengoux/cli-2512-slim-services-unify-integrity-hotfix-model-for-native
Sep 30, 2026
Merged

jgoux merged 47 commits into
developfrom
juliengoux/cli-2512-slim-services-unify-integrity-hotfix-model-for-native

Conversation

@jgoux

@jgoux jgoux commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

slim-services now publishes immutable <upstream>-r<N> releases (supabase/slim-services#326, #328). This PR makes the CLI consume them safely and from one place:

  • Pinning. Every slim artifact is pinned by content: the image digest, plus an archive and manifest sha256 per target. Nothing is looked up at runtime.
  • One version table. The stack catalog (packages/stack/src/Artifacts.ts) is the only place slim-capable service versions are written down. The new stack, legacy supabase start, and legacy slim mode (SUPABASE_USE_SLIM_IMAGES) all derive from it.
  • Updates. Upgrades and packaging hotfixes arrive as automated PRs from slim-services releases.

Pinning

  • Catalog entries. They are now ArtifactPins:
    • upstreamVersion
    • revision
    • image: …:<U>-r<N>@sha256:…
    • upstreamImage: the upstream image slim-services built from or mirrored, as recorded in the release
    • natives: sha256s per target
  • Native downloads.
    • The runtime SHA256SUMS and GHCR checksum lookups are gone. The GitHub release and S3 serve bytes only.
    • The manifest is hash-checked before it is parsed.
    • A mirror that serves the wrong bytes falls through to the next one.
    • The cache key is slim-services/<svc>/<R>/<target>, so a hotfix revision gets its own cache entry.

One version table

  • Generated Dockerfile lines. The slim-capable FROM lines of apps/cli/src/shared/services/Dockerfile, and its Go copy, are generated from the catalog by apps/cli/scripts/render-service-dockerfile.ts.
    • The generator rewrites only those 14 lines, in place, and takes each repository from the existing line. imgproxy stays on darthsim/imgproxy.
    • Kong, pg14, and the job images (migra, pg_prove, pgadmin-schema-diff) are still maintained by hand or by Dependabot.
    • A drift test in the required apps/cli unit project fails when the Dockerfile and the catalog disagree.
  • Postgres 13/15 and 14. New pg15 (generated) and pg14 (hand-pinned) stages replace the hardcoded constants, in both the TS and the Go CLI.
    • Non-slim PG13/15 moves from 15.8.1.085 to the pinned 15 line.
    • Slim mode translates pg15 through the catalog, like every other alias.
  • Slim mode. It now always finds its pin for the default versions. A linked project's hosted version that the catalog doesn't pin still uses the upstream image.

Updates

  • slim-release-published.yml reconciles the dispatched service against every committed slim-services release. For each release line it opens, or rewrites in place:
    • a hotfix PR (slim-hotfix/<svc>[-<line>]), when the pinned upstream has a newer revision;
    • an upgrade PR (slim-bump/<svc>[-<line>]), when a newer upstream exists on that line.
  • Each PR:
    • pins exactly the planned release;
    • regenerates both Dockerfiles;
    • is assigned to jgoux.
  • Mirror timing. Before planning, the run waits (bounded) for the dispatched release to appear in the release list. Before pinning, it waits (bounded) for the S3 mirror of a new release, and a digest mismatch fails immediately.
  • Trust. Plan and apply run from one checkout of the default branch, so a re-run recomputes the plan and never applies a stale one. Every ::error ::/::warning :: line uses workflow-command data encoding. The app token reaches only git push and gh: it is never written to .git/config, and every bun/pnpm command (the formatter runs from the locked install) runs without it. The PR lookup ignores fork PRs. Payload and release-tag values are validated against anchored patterns, and recorded image references are validated and escaped before they are written into the catalog.
  • Dependabot. It now ignores every slim-capable image, and the Dependabot-to-catalog sync (sync-artifacts-catalog.yml) is deleted.
  • Tests. They derive versions from the catalog, so a bump PR touches only Artifacts.ts and the two Dockerfiles.

Other changes needed by the new versions

  • Vector 0.58 no longer expands ${VAR} in its config.
    • The recipe writes real values into its own config instead.
    • In caller-supplied pipelines it fills in only LOGFLARE_URL and LOGFLARE_PRIVATE_ACCESS_TOKEN.
    • It never enables --dangerously-allow-env-var-interpolation.
  • Vector on legacy supabase start. Vector 0.58's images don't ship /etc/vector, so the entrypoint creates it before writing the config. A start.lifecycle e2e scenario starts Vector against a real Logflare.
  • pgdelta.seam.layer.ts compares slim images by revision or digest, so a container on -r0 is reported stale once the catalog moves to -r1.
  • Three integration tests that failed under load now have coherent time budgets:
    • upload-release-assets: the retry that must succeed had to fit in the same 500ms as the attempt that hangs on purpose.
    • stack-shadow: a real-Docker test ran under vitest's 5s default timeout.
    • sweep-live-projects: a 10s hard kill inside the test's budget.

Docs

ADR 0026 is rewritten for this model, including the tradeoffs:

  • slim-capable upstream and security fixes arrive only via slim-services releases;
  • Dependabot's cooldown no longer applies to them;
  • kong and pg14 are bumped by hand;
  • the Deno 1 edge-runtime override stays pinned separately.

infra/cli-artifacts/README.md is updated to match.

Release notes

  • Unlinked local projects on Postgres 13 or 15 now start supabase/postgres:15.19.0.002 instead of 15.8.1.085. Later catalog upgrades on the 15 line move them within the same major.
  • supabase start runs the catalog's upstream versions (table below), and Storage is pinned to the v1.79.28-r1 hotfix.

Versions

Every catalog entry pins its newest committed revision. Legacy supabase start now runs the same upstream versions:

Service Catalog before → after Legacy Dockerfile before → after
postgres 17 17.6.1.173 → 17.11.0.002-r0 17.6.1.171 → 17.11.0.002
postgres 15 (PG13/15) 15.14.1.173 → 15.19.0.002-r0 15.8.1.085 (constant) → 15.19.0.002
postgrest v16.2 → v16.4-r0 v16.3 → v16.4
auth v2.196.0 → v2.197.0-r0 v2.197.0 (unchanged)
realtime v2.134.5 → v2.140.3-r0 v2.135.3 → v2.140.3
storage v1.73.0 → v1.79.28-r1 v1.77.0 → v1.79.28
imgproxy v3.8.0 → v3.26.0-r0 v3.8.0 → v3.26.0
edge-runtime v1.77.1 → v1.77.1-r0 v1.77.1 (unchanged)
studio 2026.09.04-sha-5a67366 → 2026.09.28-sha-5e59b60-r0 2026.09.14-sha-4dd8a95 → 2026.09.28-sha-5e59b60
pgmeta v0.99.0 → v0.99.0-r0 v0.99.0 (unchanged)
mailpit v1.30.2 → v1.31.3-r0 v1.30.2 → v1.31.3
analytics v1.50.9 → v1.50.15-r0 1.50.12 → 1.50.15
vector 0.53.0 → 0.58.0-r0 0.53.0-alpine → 0.58.0-alpine
pooler v2.9.12 → v2.9.13-r0 2.9.13 (unchanged)

@jgoux jgoux self-assigned this Sep 29, 2026
@jgoux
jgoux force-pushed the juliengoux/cli-2512-slim-services-unify-integrity-hotfix-model-for-native branch from 3d68cf9 to 1f8af88 Compare September 30, 2026 11:22
@jgoux
jgoux marked this pull request as ready for review September 30, 2026 11:23
@jgoux
jgoux requested a review from a team as a code owner September 30, 2026 11:23
Manual sync mode located an additional (release-line-keyed) pin's span
by content, but only ever rewrote the pin literal's own upstreamVersion
field, leaving the entry's string key behind on the old upstream
version. That desyncs resolveArtifact/postgresVersion lookups, which
key off the object's literal keys. Track each additional pin's key
span and rewrite it alongside the pin when the upstream version moves.
…isions

slim-services has published immutable -r0 releases for every service.
Replace every placeholder pin in Artifacts.ts with the real content
pin (image digest, archive and manifest sha256 per native target) at
the newest -r0, using the sync script's manual mode so each pin's S3
staleness check actually ran. Drop PLACEHOLDER_PINS/placeholderPin now
that nothing needs them, and turn the "catalog has no placeholder
pins" test from it.fails into a normal passing assertion.
Vector 0.58 (up from 0.53) disabled \${VAR} interpolation in config
files by default; the API config's address, sourced from our own
VECTOR_API_ADDRESS env var, was left as a literal unexpanded string,
so vector rejected it with "api.address: invalid socket address
syntax" and exited before it was ready. Pass
--dangerously-allow-env-var-interpolation: the interpolated value is
stack-controlled, not user-supplied config.
Several stack and CLI integration tests hardcoded upstream versions
(or, for auth, derived the real catalog's pinned version rather than
hardcoding it) that matched the old placeholder catalog. Now that the
catalog resolves real -r0 pins at newer upstream versions, update
those fixtures so the tests exercise real artifact resolution again
instead of failing on "Unsupported <service> artifact version" or a
stale saved-stack-binding/readiness-marker mismatch.
dockerImageTag used lastIndexOf(":") directly on the image string, so a
slim ref's @sha256:<digest> suffix made it return the raw digest hex
instead of the tag; a slim ref's own <upstream>-r<N> revision suffix
then also kept a docker.io tag at the bare upstream version from ever
comparing equal to it. Both defeated the "tags match but the image
family doesn't" staleness check. Strip the digest unconditionally and
the -r<N> suffix for a slim ref before comparing.

Confirmed against origin/develop with a throwaway worktree: this
specific pgdelta.seam.integration.test.ts case passes there, so this
branch caused the regression. Root-caused per dockerImageTag's own
digest-suffix bug, plus the catalog's real (non-placeholder) postgres
pin now differing from the Dockerfile's own pinned version, which the
test's fixture catalog mock (mirroring slim-images.unit.test.ts's
pattern) now accounts for.
…terpolation

--dangerously-allow-env-var-interpolation applies process-wide, covering
both --config files vector loads: the recipe's own api config and a
caller-suppliable pipeline config (Config.configPath). The prior fix
only needed it for the API address, so it was broader than justified.

Template the real address into the stack-owned api config directly at
write time (now done in `args`, once the launch's real endpoint is
known, instead of `prepare`, before it is). The default pipeline config
has no `${VAR}` usage, so it needs no rendering. `configPath` is not
reachable from user CLI config today (grepped both apps/cli and the
stack; `stack-config.ts` never sets it), but the stack's own
`tests/whole-stack` fixture does supply one that references
`${LOGFLARE_URL}`/`${LOGFLARE_PRIVATE_ACCESS_TOKEN}`, relying on
Vector's interpolation. Rather than pass the flag for that case (which
would expose the whole process environment, the exact over-broad
behavior this fix removes), render only the recipe's own closed set of
placeholders into a recipe-owned copy of a caller's pipeline before
Vector loads it. The recipe never passes the interpolation flag now,
for either config.

Supersedes 8014870, which framed this as "the address is our own env
var, so opting back in is safe" — that justification covered only the
api config, not the caller-suppliable pipeline config the same flag
also applied to.
…se image

Two issues in ensureLocalPostgresImageCurrent's dockerImageTag:

1. It re-implemented digest stripping (split("@")[0]) and revision
   stripping (its own REVISION_SUFFIX), one commit after a139af1
   unified the digest-aware split into slim-images.ts's imageTag and
   the revision split into services.shared.ts's upstreamVersionFromTag.
   Compose those exported helpers instead of a third parser.

2. Revision stripping was unconditional, so a container on
   postgres:17.11.0.002-r0 compared equal to a catalog pin on
   -r1 — masking exactly the hotfix-revision drift this staleness
   check exists to catch. Only strip the revision across families
   (docker.io vs slim, where a `-r<N>` has no docker.io equivalent);
   within the same family, compare the digest when both refs carry
   one, or the full release version otherwise.

Added tests for same-family-different-revision (stale, generic
remediation), same-family-same-revision (current), alongside the
existing docker.io-vs-slim-matching-upstream case (family-mismatch
remediation, unchanged).
…ution

prepareNativeArtifact's only remaining call site in this test was
replaced by the develop flake fix's nativeRestArtifact helper during
the rebase 3 conflict resolution, leaving the import unused.
@jgoux
jgoux force-pushed the juliengoux/cli-2512-slim-services-unify-integrity-hotfix-model-for-native branch from 1f8af88 to 130d060 Compare September 30, 2026 11:37
With SUPABASE_USE_SLIM_IMAGES, the older slim path only uses a slim
image when the Dockerfile tag matches a catalog pin; otherwise it
keeps the upstream image with the upstream healthcheck. Today that's
auth, pgmeta, edge-runtime and pooler — not every service, as the
tests assumed. realtime is on Dockerfile v2.135.3 against catalog
v2.140.3, so its upstream (Elixir, no wget) container failed the
BusyBox wget probe the old test always expected.

- The BusyBox wget test now iterates catalogPins() directly (the
  pinned digest refs) for the services that use the wget healthcheck,
  independent of whether the Dockerfile currently matches. It no
  longer depends on the Dockerfile at all.
- The start test derives each service's expected image the same way
  slim mode does (toSlimImage(alias, dockerfileTag) ?? upstream), then
  asserts the container runs that image and its healthcheck matches
  that image's family (slim BusyBox wget vs. each service's own
  upstream argv) — never assuming a fixed family. auth naturally
  exercises the slim branch today; realtime and storage exercise
  whichever branch the Dockerfile/catalog alignment puts them in.
- Renamed both tests: "latest slim images" no longer describes a path
  that depends on Dockerfile/catalog alignment.

@avallete avallete left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The runtime integrity path looks solid: expected hashes come only from the catalog pin, the manifest is verified before it is parsed, a mirror serving the wrong bytes falls through to the next one, and the per-revision cache key is right. Tag and version parsing handles digest-suffixed refs and Studio-style versions.

One blocker and one real workflow bug, plus some cleanup (inline).

Blocker: slim-images.ts now imports @supabase/stack/internal/artifacts, and .github/scripts/sync-artifacts-catalog.ts imports slim-images.ts. Neither slim-release-published.yml nor sync-artifacts-catalog.yml installs dependencies, so both would fail to load the script after merge. Reproduced on a clean git archive HEAD (no node_modules): bun .github/scripts/sync-artifacts-catalog.ts validate-payload ... exits 1 with Cannot find module '@supabase/stack/internal/artifacts'. GitHub Scripts CI misses it because it installs the whole workspace.

Non-blocking: because the catalog is ahead of the Dockerfile, SUPABASE_USE_SLIM_IMAGES starts currently fall back to docker.io for most services. That matches the description, just flagging that the opt-in path is mostly inert until Dependabot catches up.

Comment thread apps/cli/src/shared/services/slim-images.ts
Comment thread .github/workflows/slim-release-published.yml Outdated
Comment thread .github/workflows/slim-release-published.yml
Comment thread .github/scripts/sync-artifacts-catalog.ts Outdated
Comment thread apps/cli/src/command-internal/db-bootstrap/pinned-image.unit.test.ts Outdated
Comment thread apps/cli/src/shared/services/services.shared.ts Outdated
Comment thread docs/adr/0026-slim-artifact-mirrors.md Outdated
Adds `upstreamImage` to each catalog `ArtifactPin` (the exact upstream
image slim-services built or mirrored each pin from), backfilled through
the sync script's manual mode. A new generator
(apps/cli/scripts/render-service-dockerfile.ts) rewrites only the
slim-capable `FROM ... AS <alias>` lines of the Dockerfile in place from
that field, asserting the repository stays the one the Dockerfile already
uses (imgproxy is the one documented exception); everything else (kong,
Dependabot-managed job images) passes through untouched. Its drift test
runs as part of the required apps/cli unit suite.

Adds the `pg15` (generated) and `pg14` (hand-pinned, no slim build)
Dockerfile stages as the single version table for legacy Postgres
resolution. `postgresImageForDbMajorVersion` now reads them instead of
three hardcoded fallback constants; the Go CLI's own `pg14`/`pg15`
constants are replaced with the same Dockerfile-derived fields, closing
the second version table. One-time regeneration bumps every slim-capable
service to its currently pinned upstream version.
Replaces literals that happened to equal the current catalog/Dockerfile
pin with values derived from the catalog (`catalogPins()`/`postgresVersion`)
or the Dockerfile (`dockerfileServiceImageRaw`), so a routine version bump
no longer turns these integration tests red. Mocked, self-contained
fixture catalogs (services.shared/slim-images unit tests) are unaffected:
they only ever compare against their own literals.
sync-artifacts-catalog.ts writes upstreamImage straight into
Artifacts.ts as a TypeScript string literal. Validate the resolved
value against an anchored image-reference pattern before returning it
from resolveUpstreamImage, and serialize every field in serializePin
with JSON.stringify instead of manual "${...}" interpolation, so a
release manifest or provenance file carrying a quote or expression
can't inject code into the generated catalog.
start.integration.test.ts's stack-backend database fixture derives its
version from the real catalog (postgresVersion("17")) instead of a
hardcoded pin, so a routine Postgres bump can't desync it from the
version the code under test resolves. render-service-dockerfile's
hand-managed-line preservation test no longer freezes kong/differ/
migra/pgprove's literal tags; it now tampers them with a synthetic
marker and asserts the generator leaves them untouched, so a
Dependabot bump of any of those can't fail this required check.
slim-release-published now reconciles a service against every committed
slim-services release (planSlimUpdates) and opens or updates one PR per
hotfix and/or upgrade it finds, instead of only matching the release named
in the dispatch. Plan and apply run from the same checkout in a single job
so a re-run always recomputes from develop. Manual mode gains --release to
pin an exact committed revision.
The stack catalog is now the single version table; the Dockerfile's
slim-capable lines are generated from it and updated by
slim-release-published, not by syncing a Dependabot bump back into the
catalog. Dependabot now ignores every slim-capable image and keeps bumping
only the upstream-only ones.
The comment said the fallback "only happens for an alias with no slim
build", but toSlimImage also returns undefined for an OrioleDB tag (an
alias that does have a slim build) and for a tag the catalog simply
doesn't pin. State the general contract instead: undefined whenever no
catalog pin matches the alias and tag, with all three cases as examples.
Comment-only; no behavior change.
Guards slim-release-published against a releases API that hasn't caught up
with the release that triggered the dispatch: plan-updates now requires
--expect-release to be listed before planning, retrying a bounded number of
times first. Also drops the dead placeholderPin parsing path now that the
catalog only ever holds resolved pins, and stops embedding raw dispatch
values in error messages so a crafted payload can't inject workflow commands.
Several slim-image tests computed their expected value with toSlimImage
itself, so they passed whether or not the catalog lookup actually resolved.
Under the single version table, a default Dockerfile tag always matches a
catalog pin, so these now read the expected ghcr.io image straight from
catalogPins() and assert its shape, catching a regression to docker.io.
Also has services.shared.ts reuse slim-images.ts's imageTag/imageRepository
instead of re-implementing the @-aware tag split.
Six test files each carried a near-identical "look up the catalog's pinned
image for this alias" helper, plus three copies of the ghcr.io shape regex.
Consolidates both into slim-images.test-support.ts, next to slim-images.ts.
Routes every ::error :: and ::warning :: line this script emits through one
encoder (workflow-command data encoding: %, then \r/\n), so an external value
reaching the top-level catch or a planner warning can never inject an extra
workflow command via an embedded newline. Also validates --service in
plan-updates before any listing or wait, and makes the expect-release wait
interval reject a malformed SLIM_UPDATES_EXPECT_RELEASE_WAIT_MS instead of
silently coercing it to NaN, 0, or a negative delay.
@jgoux jgoux changed the title feat(stack): pin slim artifacts by immutable revision and content digest feat(stack): pin slim artifacts by revision and make the catalog the single version table Sep 30, 2026

@avallete avallete left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at 89fb625. The single-table redesign holds up: plan and apply run from one checkout, each branch pins exactly one --release, validation is anchored, and the rendered Dockerfiles match the Go copy. Almost everything from the first round is fixed (replies on those threads).

One small must-fix and one reliability gap (inline), plus non-blocking notes:

  • A same-date Studio build (2026.09.28-sha-a vs 2026.09.28-sha-b) compares equal, so a later same-day build never gets an automatic upgrade PR. The comment says that's intended and the manual --release path covers it; flagging it in case it's not.
  • upstreamImage and the fetchManifest/fetchProvenance fetchers are optional only so tests can skip stubbing them, but ArtifactPin.upstreamImage is required. refreshCatalogPin with such an io rewrites the pin without it, producing an Artifacts.ts that doesn't typecheck (the test near sync-artifacts-catalog.test.ts:1174 locks that in). Consider making them required.
  • A hotfix and an upgrade planned on the same line open two PRs editing the same span; after the upgrade merges, the slim-hotfix/<svc> PR is left conflicting and never revisited. Skipping the hotfix when an upgrade is planned on that line would avoid it.
  • Unlinked PG13/15 local projects move to 15.19.0.002, and every later catalog upgrade on the 15 line moves existing local volumes with it. Probably fine within a major, but worth a release-note line.
  • The load-hardening changes to upload-release-assets and sweep-live-projects tests look unrelated and would be easier to review separately.
  • Comment density is still well above the repo's quarter guideline in sync-artifacts-catalog.ts and the workflow; the mode docs could move into the ADR.

Comment thread .github/workflows/slim-release-published.yml Outdated
Comment thread .github/scripts/sync-artifacts-catalog.ts
@avallete
avallete self-requested a review September 30, 2026 17:52
Comment thread .github/workflows/slim-release-published.yml Outdated

@avallete avallete left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default supabase start fails on Vector 0.58. stack start does not.

Dogfooded d0574ea. The legacy entrypoint is unchanged on this head.

packages/stack/src/services/Vector.ts writes the pipeline on the host and bind-mounts it, so stack start --runtime docker and --runtime native come up on the new pins.

Legacy Compose does not. buildVectorEntrypointScript in apps/cli/src/commands/start/services/vector.service.ts still does:

cat <<'EOF' > /etc/vector/vector.yaml

Vector 0.58 (slim 0.58.0-r0 and timberio/vector:0.58.0-alpine) has no /etc/vector. Both slim and flag-off supabase start crash-loop Vector and the health check times out:

sh: can't create /etc/vector/vector.yaml: nonexistent directory
ERROR vector::config::loading: Config file not found in path. path="/etc/vector/vector.yaml"
HealthCheckTimeoutError: supabase_vector_* container is not ready: unhealthy

Vector starts by default (analytics.enabled). --exclude vector skips it. Catalog digests matched on GHCR and the ECR mirror, and both native mirrors matched the pinned sha256. The rest of the slim service smoke did not run, because start aborted on Vector.

This blocks the default local start. It does not block stack start.

The S3 native-archive mirror uploads in parallel with the dispatch, best-
effort, so a freshly committed release's S3 copy can lag GitHub by the time
the sync script pins it. Waits, bounded, while an object is missing (reusing
the same retry helper --expect-release's visibility wait uses), but fails
immediately on a digest mismatch, since that's corruption rather than lag.
The apply loop ran bun x oxfmt (fetching ~46 packages from the public
registry) and every sync/generator call in the same step that held the app
token in its env, and persisted the token into .git/config via
git remote set-url. Formats with the locked pnpm install instead, pushes
with the token only in an explicit URL computed once (never written to
disk), and strips it from every bun/pnpm command with env -u. gh now also
gets the token inline per call, and filters gh pr list to same-repository
PRs (isCrossRepository) so a fork PR with a matching branch name can't be
selected and edited with a trusted-looking title and body.
Vector 0.58's images (upstream and slim alike) ship no /etc/vector, so
the legacy start entrypoint's heredoc write failed and crash-looped the
container in both flag-off and slim supabase start. Create the
directory before writing the config.
@jgoux

jgoux commented Sep 30, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the re-review and the dogfooding.

Vector on legacy supabase start: fixed in 0b6b111. The entrypoint now runs mkdir -p /etc/vector before writing the config. Vector 0.58's images, slim and upstream, don't ship that directory. Both run as root, and /var/lib/vector, the docker_logs checkpoint directory, exists in both. A start.lifecycle e2e scenario now starts with Vector and Logflare included. supabase start with the default config comes up with every container healthy, both flag-off and with SUPABASE_USE_SLIM_IMAGES=1.

The two inline items (the oxfmt install path and the S3 mirror wait) and the fork-PR lookup are fixed too; details are on those threads. On the summary notes:

  • Same-date Studio builds. Intended. Studio versions compare by date, and two builds from the same day have no order we can derive from the tag. The next day's build upgrades automatically, and a manual --release pins a specific build.
  • Optional upstreamImage fetchers. Agreed, they should be required so every written pin typechecks. Moving to a follow-up.
  • Hotfix and upgrade on the same line. Agreed: skipping the hotfix when an upgrade is planned avoids the stranded conflicting PR, and manual --release still covers a hotfix-only pin. Moving to a follow-up.
  • PG13/15 moving to 15.19. Added a release-notes section to the description.
  • The load-hardening test changes. Kept here to avoid another round on this PR. They're self-contained commits touching only those three test files.
  • Comment density and the remaining provenance comments. Agreed. Moving the mode docs into the ADR, and trimming the comments you listed, go to the same follow-up.

@jgoux
jgoux enabled auto-merge September 30, 2026 18:41
@jgoux
jgoux added this pull request to the merge queue Sep 30, 2026
Merged via the queue into develop with commit 62aac0f Sep 30, 2026
35 of 36 checks passed
@jgoux
jgoux deleted the juliengoux/cli-2512-slim-services-unify-integrity-hotfix-model-for-native branch September 30, 2026 18:54
@avallete

Copy link
Copy Markdown
Member

Dogfood — conditional go

Tested 4446e85 (darwin arm64, OrbStack). Prior pass on d0574ea5 was a no-go: Vector 0.58 images have no /etc/vector, so legacy supabase start never became healthy. That is fixed on this SHA.

flowchart LR
  pins[Catalog pins] --> slim[Legacy slim start]
  pins --> off[Flag-off start]
  pins --> native[Native cache]
  native --> tests[Retry contract tests]
  tests --> gap[Live GitHub to S3 not injected]
Loading

What ran

Check Result
GHCR and ECR digests match the catalog, including storage v1.79.28-r1 Pass
Native darwin-arm64 GitHub and S3 manifest/archive sha256 match the catalog Pass
Legacy slim full start (no excludes): vector healthy, /etc/vector/vector.yaml written, no unexpanded ${, no --dangerously-allow-env-var-interpolation Pass
R1–R10 (analytics, socket, --from-backup, role-only dump, volume restart, pooler, storage/imgproxy, no silent docker.io fallback, slim PG15, full start) Pass
Flag-off start: upstream images, vector healthy, Postgres 17.11 Pass
Slim PG15 db start: 15.19.0.002-r0, version() 15.19 Pass
basejump corpus, slim PG17, migration up Pass
Stack docker eager and stack native eager (cache hit on second start, storage -r1 cache key) Pass
slim-services retry/mirror tests (20) and container image-mirror tests (14) Pass

Kong stayed on ECR kong:2.8.1. Storage ran ghcr.io/supabase/cli/storage:v1.79.28-r1.

Gap

Live native fallthrough (GitHub release 404, then S3) was not proven on the real CLI. A bun --preload wrap of globalThis.fetch never saw the release URLs, and an HTTPS_PROXY that 404s github.com/supabase/slim-services/releases saw zero CONNECT attempts. Downloads still came from GitHub. The same fallthrough, gateway retry, truncated-archive retry, and tampered-archive fallthrough are covered by the integration tests above.

A live GHCR→ECR pull failure was not injected: that needs the daemon to fail ghcr.io globally.

Soft, not product failures

  • functions invoke with the anon key returned INVALID_API_KEY (not retested with the publishable key).
  • basejump declarative generate reported declarative mode disabled in that scratch config.
  • Vector → analytics api-key 401 is the known Logflare seed mismatch.

TFSebben pushed a commit to TFSebben/supabase_cli that referenced this pull request Oct 1, 2026
…hotfixes (supabase#6926)

Follows up the non-blocking review notes on supabase#6883.

- **Required fetchers.** `RevisionIo.fetchManifest` and
`fetchProvenance` are now required, and `refreshCatalogPin` always
resolves `upstreamImage`. The serializer only accepts a pin type where
that field is required, so a rewritten `Artifacts.ts` always typechecks.
- **One update per release line.** When a line upgrades,
`planSlimUpdates` skips that line's hotfix. It emits a warning that
`--service <svc> --release <U>-r<N>` can pin the hotfix alone. Otherwise
the `slim-hotfix/<svc>[-<line>]` PR would conflict once the upgrade
merges. Separate lines, such as postgres 17 and 15, stay independent.
- **Docs over comments.** The sync script's mode docs and the
`slim-release-published.yml` header move into ADR 0026:
  - a new "Invocations" section;
- "Hotfix and upgrade pickup" now covers payload trust, the per-line
rule, line assignment and app-token handling.

Provenance, narration and over-long comments are trimmed across the
files supabase#6883 changed.
- **Listing tests.** `services.shared.unit.test.ts` covers two `supabase
services` cases against the real catalog again:
  - the default slim listing;
  - a catalog-pinned postgres 15 override.

  The auth-only fixture stays the default for the other tests.
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.

2 participants