Greenfield manifest-first bootstrap is a chicken-and-egg: apply demands a store, capture refuses an absent project #104

Closed
opened 2026-07-19 12:10:20 +00:00 by dan-claude-bot · 0 comments
dan-claude-bot commented 2026-07-19 12:10:20 +00:00 (Migrated from github.com)

Found by the 2026-07-19 release drill (two live Coolify 4.1.2 instances).

What happened

Fresh instance, registered project, manifest with databases only and zero ${…} refs. There is no path to the first apply:

  • cast applyno secret store for dan-claude-bot/drill-widget in drill (src/cli.ts:1160-1172 — unconditional existsSync(store) check)
  • cast capture → refuses correctly: the project is absent on the box, and capture "may only ever describe one that is already there"

The documented two-pass bootstrap (README The bootstrap is two-pass) starts from capture — which presumes live resources to describe, i.e. the adoption path. A greenfield environment (nothing on the box, everything declared in the manifest) satisfies neither verb's precondition.

The drill unblocked by hand-writing an empty store:

printf '' | age -r <recipient> -o secrets/drill-widget.drill.env.age

…after which apply → diff → re-apply all behaved perfectly. But that line appears in no doc and no error message.

Why it matters

This is the first command a manifest-first adopter runs, and it is also the DR shape ("rebuild the control plane from state" with a store that never existed for a new project). The refusal message says cast capture writes the store — which then refuses in turn, one message deep from the answer.

Options (in rough order of smallness)

  1. Message-only: the no secret store refusal gains the empty-store one-liner for the zero-refs case.
  2. Semantic: when the manifest resolves zero ${…} refs, treat an absent store as empty (say so loudly) and proceed — the fleet-scale silent-skip concern doesn't apply, because nothing needed resolving.
  3. A verb: cast init-store <org>/<repo> --env <env> writing the empty store to the bound recipient.

Option 2 keeps the file requirement meaningful exactly when it protects something. Happy to take direction.

🤖 Filed from the release-drill session.

Found by the 2026-07-19 release drill (two live Coolify 4.1.2 instances). ## What happened Fresh instance, registered project, manifest with databases only and **zero `${…}` refs**. There is no path to the first apply: - `cast apply` → `no secret store for dan-claude-bot/drill-widget in drill` (`src/cli.ts:1160-1172` — unconditional `existsSync(store)` check) - `cast capture` → refuses correctly: the project is absent on the box, and capture "may only ever describe one that is already there" The documented two-pass bootstrap (README *The bootstrap is two-pass*) starts from `capture` — which presumes live resources to describe, i.e. the **adoption** path. A greenfield environment (nothing on the box, everything declared in the manifest) satisfies neither verb's precondition. The drill unblocked by hand-writing an empty store: ```sh printf '' | age -r <recipient> -o secrets/drill-widget.drill.env.age ``` …after which apply → diff → re-apply all behaved perfectly. But that line appears in no doc and no error message. ## Why it matters This is the first command a manifest-first adopter runs, and it is also the DR shape ("rebuild the control plane from state" with a store that never existed for a new project). The refusal message says `cast capture` writes the store — which then refuses in turn, one message deep from the answer. ## Options (in rough order of smallness) 1. **Message-only**: the `no secret store` refusal gains the empty-store one-liner for the zero-refs case. 2. **Semantic**: when the manifest resolves **zero** `${…}` refs, treat an absent store as empty (say so loudly) and proceed — the fleet-scale silent-skip concern doesn't apply, because nothing needed resolving. 3. **A verb**: `cast init-store <org>/<repo> --env <env>` writing the empty store to the bound recipient. Option 2 keeps the file requirement meaningful exactly when it protects something. Happy to take direction. 🤖 Filed from the release-drill session.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: heavy-duty/cast#104
No description provided.