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 any docker 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-init image (alpine + the /garage binary over RPC, or the Admin API on :3903) that depends_on garage: service_healthy and that craig-seed depends_on … service_completed_successfully. Needed because the gate makes up block on the seed BEFORE the post-up init_garage, so the seed's attachment upload would race an uninitialized bucket. Retires the duplicated xtask init_garage (devstack.rs) + CI garage-init.sh (single source of truth, all paths).
  • CI: the gate makes up -d fail automatically on seed failure; add --wait --wait-timeout; drop the now redundant garage-init.sh step.

Out of scope

Everything in C4-floor (done, #1011 (closed)). This is purely the declarative/CI half.

Acceptance criteria

  1. A deliberately-broken seed makes docker compose up -d (no xtask) exit non-zero.
  2. Fresh dev clean && dev start: attachments seed on the FIRST start (bucket ready before the gated seed).
  3. xtask init_garage + CI garage-init.sh removed; garage-init runs in-graph on every path.
  4. 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).