cast inventory: see the box before you adopt it — the missing first step #19
Labels
No labels
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-triage
ready
release
scope:apply
scope:capture
scope:coolify-api
scope:fleet
scope:manifest
scope:secrets
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/cast#19
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The missing verb
cast can describe a Coolify it built (
diff), change one (apply), andtake secret values from one for names a manifest already declares
(
capture). It cannot tell you what is actually on a box you did not build.That is the first thing anyone needs when adopting an existing deployment, and
today it is done by hand:
Every input to that pipeline is something cast already has — the client, the
token, the instance, the project/environment resolution. A human is shuffling
cast's own inputs through a terminal, which is the same complaint that produced
capture(#15).What it cost us, concretely
During the prod migration (D-193) we went into the adoption blind, and paid for
it three times in one session:
capturerefused: no project namedincubator— the box calls itIncubator. An inventory would have shown that instantly. (The refusalworked; the blindness is what made it a surprise.)
capturerefused again: no environment namedprod— the box calls itproduction(Coolify's default). This one was "fixed" by renaming ourenvironment to match the legacy box — letting a machine that is being deleted
next week name the new box's environment forever. Reverted, and now #17.
capturethen reported 15 of 15 required names missing from a box that isserving production and sending mail right now — because the manifest's
resources (
core,umami) are named something else on the box. Now #18.Every one of those is the same root cause: we could not see the box. The
refusals were correct and the tool behaved as designed — but each one arrived as
a surprise, one at a time, mid-migration, and the "obvious" fix to two of them
was to bend the manifest toward the legacy box. An inventory would have surfaced
all three in a single read, before anything was decided.
Proposal
Reads the live instance and writes a review artifact — cast's own format,
locally, for a human to read and reconcile. Explicitly not desired state and
not a store:
packs, base directories, ports, domains
what the box has that the manifest doesn't know about, and what matches — the
same three buckets a human is otherwise assembling by eye
The output is the input to a manifest PR, not to
apply. The lifecycle is:inventory → human reads → manifest PR →
capture→applycast is missing the first step, which is why every mismatch currently surfaces as
a refusal from a verb running much later, when the operator is already committed
to a course of action.
Why this must NOT just become "capture everything"
Worth stating, because it is the natural next thought and it is a trap.
The store must contain exactly what the manifest declares — no more, no
fewer. If a dump's values became the store, the new prod app would boot with the
source box's
DATABASE_URL, and it would work: migrations run, userssubmit, data flows — into the old box's Postgres. You would find out the day the
old box is deleted. That is why
captureplacespending-coolify-generatedandrefuses rather than copies.
So:
inventorymay read everything, because its output is read by a person.capturemay only ever write what the manifest declares, because its output isread by
apply. Same box, two different consumers, two different contracts.Acceptance
cast inventoryagainst a hand-built instance lists every resource and everyenv key, with no secret values in the default output.
against the repo's manifest.
apply— it is a document, not state.read:sensitiveunless values areexplicitly requested).