chore(devstack): C4-gate — compose completion gate + garage-init one-shot + CI seed fail-loud
Summary
Deferred sibling of C4-floor (#1011 (closed)): deliver the declarative seed fail-loud + attachment-ordering that
C4-floor scoped out because CI is currently unreliable. C4-floor made the seed idempotent + fail-loud for
local xtask paths (verify_seed on every bring_up path + auto_refresh); this adds the compose-level
guarantee that also covers raw docker compose up (the 3 CI jobs) and keeps attachments seeding once the
seed is gated.
Scope
- Compose completion gate:
craig-web depends_on craig-seed: { condition: service_completed_successfully }so anydocker compose up(dev + the 3 raw-CI jobs at.gitlab-ci.yml:489/548/596) fails on a bad seed. - garage-init compose one-shot (dev-only): a small
devstack/garage-initimage (alpine + the/garagebinary over RPC, or the Admin API on:3903) thatdepends_on garage: service_healthyand that craig-seeddepends_on … service_completed_successfully. Needed because the gate makesupblock on the seed BEFORE the post-upinit_garage, so the seed's attachment upload would race an uninitialized bucket. Retires the duplicated xtaskinit_garage(devstack.rs) + CIgarage-init.sh(single source of truth, all paths). - CI: the gate makes
up -dfail automatically on seed failure; add--wait --wait-timeout; drop the now redundantgarage-init.shstep.
Out of scope
Everything in C4-floor (done, #1011 (closed)). This is purely the declarative/CI half.
Acceptance criteria
- A deliberately-broken seed makes
docker compose up -d(no xtask) exit non-zero. - Fresh
dev clean && dev start: attachments seed on the FIRST start (bucket ready before the gated seed). - xtask
init_garage+ CIgarage-init.shremoved; garage-init runs in-graph on every path. - The 3 CI jobs fail on a bad seed.
Context
ADR-048 §Status amendment (2026-07-12) + §D6/gate; program plan C4-gate bullet. Deferred because CI is currently unreliable (user-directed 2026-07-12).