smoke resolves its target inside the project it was declared under (#29) #31
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#31
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/smoke-project-scoped"
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 #29.
smokefound the application it writes to by name againstGET /applications— every application the token can see, across every project and every environment on the instance — and took the first name match. Sosmoke_target: coredid not name an application; it named whichever application calledcoreCoolify happened to list first.It does not take two projects for this to bite. One project with two environments on one instance is enough, which is the ordinary shape:
cast smoke --env stagingcould POST its canary vars onto prod'score, and on the failure path leave them there. The verb whose entire job is to be a write against a live application was picking its victim by a lookup that cannot distinguish prod from staging.The fix
fetchLive(client, projectName, coolifyEnv)— the same lookup every other verb uses — and filter tokind === "application".GET /applicationsis not called at all any more.smokegains the read-side coordinates it was the last verb to lack:--project/--environment, withdiff/capture/inventory's exact semantics (the D-241 split, one level up from--resource).renderAbsentTarget(…, { verb: "smoke" }). A project+environment that exists but holds no application of that name gets a new refusal listing the applications that are there — and if a resource of that name exists as a service or database, it says so, becausesmokePOSTs to/applications/<uuid>/envsand would otherwise 404 on an endpoint that does not exist for that kind. Neither refusal widens the search: an application in another project is not the same application seen from a different angle.The wrinkle, resolved as the issue proposed
The
<org>/<repo>positional is now required, and the deprecated state-file-scopedsmoke_targetis dropped — it had no project to scope to, so it could only ever be carried, and carrying it meant keeping the instance-wide lookup alive for exactly the invocation that most needed it dead (cast smoke --env prod).Because
BindingsSchemais.strict(), the key is still declared — and refused with a migration message telling the operator exactly what to write instead. Deleting the field outright would have made an unmigratedenvironments.yamlfail with a raw zod unrecognized key, andloadBindingsruns for every verb:diff,apply,captureandinventorywould all have died on a key none of them ever read.⚠️ Operator note — this is a lockstep migration
heavy-duty/infrastill carries the top-levelsmoke_target: core. Pull the infra migration and re-install cast together. Re-installing cast alone against an unmigrated state file breaks every verb, not justsmoke. The matching infra PR moves the key underenvironments.prod.projects."heavy-duty/incubator"and updates the threecast smokecall sites inrunbooks/coolify-bootstrap.md.Test
test/smoke-cli.test.tsstubs a Coolify holding three applications namedcore— incubator/prod, incubator/staging, and a second project's — and asserts from the wire that every mutation lands on the staging one, that prod's is untouched, and thatGET /applicationsis never requested.Gates:
npm run check,npm run build,npm test(222 passing) all green.🤖 Generated with Claude Code