No reviewers
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#20
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/inventory-and-read-side-coordinates"
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?
Closes #17, closes #18, closes #19.
Three fixes at one seam: cast could not read a box it did not build. All
three came out of one attended session pointing cast at a live, hand-built
Coolify for the first time — and each one failed differently, one at a time,
after the previous had been worked around.
#17 — the environment had no read-side coordinate
--projectexists because a hand-built project is called whatever someonetyped. The environment has the identical problem and had no flag. So reading
a legacy box forced a choice between mutating that box's UI and renaming our
environment to match it.
The second is what happened.
prodbecameproductionacross the manifest andenvironments.yaml, because that is what the box called it — a box that isbeing deleted next week, naming the environment of the box that replaces it,
permanently (
applycreates the Coolify environment from--env). It alsosilently moved the store to
secrets/incubator.production.env.ageandinvalidated every
--env prodin three repos' runbooks. It was reverted, andthis is the flag that means nobody has to make that trade again.
The split the fix rests on:
--envenvironments.yamlbinding, the age key, the store path, the team to assert--environmentAbsent, it defaults to
--env, so nothing changes for boxes cast built. Theabsent-environment refusal now names
--environmentrather than telling you to"re-run with
--envnaming the environment as it exists here" — which was thesentence that invited the rename.
#18 — an absent RESOURCE reported as N missing secrets
The #12/D-237 lie, one level deeper in the tree, and it fails the same way: a
resource that is absent reads back exactly like one that is present with no
env vars set.
So
capture, pointed at the right project and the right environment, reportedall 15 required names as individually MISSING — from a box that was serving
production and sending mail through Mailgun at that moment — and offered
--overrideas the remedy.Taking that offer would have "worked". The operator hand-supplies fifteen
secrets through
$CAST_CAPTURE_*, capture writes a perfectly valid store, andthe actual finding — the manifest and the box disagree about what the app is
called — is never discovered. The tool would have talked the operator into
hand-carrying the exact data it exists to stop them hand-carrying, and buried the
real problem underneath a clean result.
capturenow refuses on the resource, names what does exist, and reportsper-name
MISSINGonly for resources it actually found — where it means what itsays.
#19 —
cast inventory: see the box before you adopt itThe missing first step. cast could describe a box it built (
diff),change one (
apply), and take values off one for names a manifest alreadydeclares (
capture) — but it could not tell you what is on a box you did notbuild, which is the first thing adoption needs.
That blindness is why the three failures above arrived as a sequence of surprises
rather than as one document, and why the tempting fix for two of them was to bend
the manifest toward the legacy box.
That output is the finding #18 refuses on, stated in a form a human can act on:
coreandincubator-stackcarry the same keys. It's a rename — plus twobox-only leftovers to decide about.
It needs no store, no age key, and no recipient: it runs before adoption
exists, which is the point of it. A read token is enough.
The boundary that keeps
capturestrictinventorymay read everything, because a person reads its output.capturemay only ever write what the manifest declares, because
applyreads itsoutput. Same box, two consumers, two contracts.
That is also why there is no manifest-draft emitter, though it was the
obvious next feature and was explicitly asked about. A YAML draft that Coolify's
own live state generated is one
cpaway from becoming desired state — and a boxthat nobody declared becoming the spec is the precise failure this whole design
exists to prevent. The report is prose on purpose.
Last detail, in the same spirit as the existing sanity floor: zero drift against
a hand-built box is reported as suspicious, not as a pass.
For the reviewer
--environmentalso applies toapply, not just the read verbs. This isdeliberate: it is what lets an apply adopt an existing hand-named
environment rather than create a second one beside it. It changes only the
name on the wire.
capture, notdiff. Fordiff, a resource the boxlacks is legitimate drift and reporting it as a create is correct. For
captureit is a lookup failure wearing the costume of a content finding.resolveCheckout's--path/--env prodban applies toinventorytoo. Itreads a manifest, and a feature branch must not get to decide what prod's
adoption sees.
Checks
npm run check(biome),npm run build(tsc),npm test— 164 passing, 18files (was 151/16). The new e2e suite runs the real CLI against a stub Coolify
shaped like the actual box: project
Incubator, environmentproduction,application
incubator-stack.🤖 Generated with Claude Code