A manifest that declares `generated_secrets:` bootstraps in two passes by
construction: pass 1 `capture` placeholds those names (their values do not
exist yet), `apply` creates the database and Coolify generates the real URL —
and nothing then taught the store that value. The operator did it by hand:
decrypt a fourteen-name store, edit two lines, re-encrypt to the environment's
age recipient, against production, holding the prod key.
`capture --generated-only` inverts capture's disposition rule and changes
nothing else — it fills the generated names and leaves every other name in the
store exactly as it is, byte for byte. Same verb, same ceremony, same
store-writing code path.
- reads the value from the resource that OWNS it (`internal_db_url` on the
database), never from a consuming app's env, where a generated URL never
appears — the app's env holds the placeholder itself at this point.
- resolves the database inside the project+environment via
GET /projects/{uuid}/{env}, never the instance-wide GET /databases (which
lists other projects' databases and umami's bundled Postgres — #29's bug in
another hat). The scoping is structural, not a filter.
- refuses to guess which database a name comes from: nothing in the manifest,
the templates or the box carries that edge, so it infers only when it cannot
be wrong (one name, one database) and otherwise hands back `--from`.
- refuses to overwrite a generated name holding a real value without --force
(a silent credential rotation), a name absent from the store, and a
placeholder nothing fills.
- asserts the postcondition against the ciphertext on disk: zero
pending-coolify-generated remain, and the name count is unchanged. That
assertion was a line in a human runbook.
`apply` deliberately does NOT do this after a create — it would make the verb
that mutates Coolify also mutate the encrypted store, and hence the git repo.
Closes#48.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>