fix(manifest): refuse checkout paths that are not absolute (#49) #52
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#52
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/compose-file-path"
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 problem
A first apply, planning
create application core, got a bare 422 from Coolify. The manifest saidcompose_file: docker-compose.yaml; Coolify wanted/docker-compose.yaml. By the time the 422 landed,applyhad already created the project and the environment — a deterministic, manifest-shaped error paid for with a half-applied run.cast passed the field through verbatim (
resolve.ts:361) and validated nothing about its shape (manifest.ts:25required only that it be present). Meanwhile cast's own draft writer already defaults it to/docker-compose.yaml(draft.ts:459), because that is what it reads off a live box: cast wrote the correct value and accepted the incorrect one. Anddocs/semantics.md:85taught the incorrect one — anyone hand-writing a manifest from the docs wrote a manifest that could not be applied to a fresh box.What changed
src/manifest.ts—compose_file,base_directoryandpublish_directoryare now refined to Coolify's own path patterns, with messages that name the fix (e.g. "compose_file must be an absolute path inside the repo checkout (Coolify 4.1.2 rejects the create otherwise) — write /docker-compose.yaml, not docker-compose.yaml").A refine, not a normalization — deliberately. cast does not quietly rewrite what the manifest says: the manifest is the artifact under review, so a value that would 422 gets fixed in the file, in a commit, once, rather than repaired in memory on every run. And it is a parse-time refusal, so it costs zero API calls and fires on every verb, unlike the translated errors for the multi-destination 400 (#41) and the domain 409 (#44) — this one is fully knowable before cast talks to anything.
docs/semantics.md— fixed the compose example, and added a short "Checkout paths are absolute" paragraph covering all three fields and citing the Coolify rule.test/manifest.test.ts,test/resolve.test.ts,test/capture.test.ts) — every one of them is evidence for the issue: they are what let a value Coolify rejects look like a tested one.README.mddoes not teach these fields (grepped) — untouched.Evidence for the Coolify claim
Read from the v4.1.2 source, not the vendored OpenAPI spec (which documents these fields as bare strings and is provably incomplete):
bootstrap/helpers/api.php—sharedDataApplications(), merged into the validation rules of every application-create path (ApplicationsController::create_application, lines ~1038/1241/1482/1680/1798) and of the update path:app/Support/ValidationPatterns.php: Failing either is422 Validation failed.fromValidator::make(...)->fails().Point 3 of the issue:
base_directorydoes need the same refinementSettled, and the answer is yes — with one difference that matters.
base_directory(andpublish_directory, the third field passed through the same way) is bound todirectoryPathRules(), which is also anchored on a leading/— sobase_directory: apps/core422s a create exactly likecompose_file: docker-compose.yamldoes. The difference is that the directory pattern additionally admits the bare/, where the file pattern requires at least one character after the slash. That is why this PR carries two patterns rather than one shared "absolute path" rule: a shared rule strict enough forcompose_filewould refusebase_directory: /, which is what essentially every manifest says, including all of cast's own fixtures. Both patterns are transcribed with a citation comment inmanifest.ts.Tests
299/299 green (
npm run check && npm run build && npm test), up from a 294 baseline. Five new cases intest/manifest.test.ts, covering the refusal paths and the boundary between the two patterns:compose_filewith no leading slash, and the message names the fixcompose_filethat is the bare/(a directory, not a file — the file/directory pattern boundary)base_directorywith no leading slashpublish_directorywith no leading slash/asbase_directoryand a nested absolutepublish_directory(the regression guard for that boundary)Noted, not fixed (out of scope here)
test/wire.test.tsandtest/apply.test.tsstill usedocker_compose_location: "docker-compose.yaml"in hand-built wire-shaped objects. Those never pass through the manifest schema, so they are unaffected and still pass — but they are the same stale value, and are worth a sweep.Closes #49.