is_static stays create-time-only until Coolify serializes settings on a read — track the upstream fix, and make draft admit what it couldn't see #70

Closed
opened 2026-07-15 17:23:15 +00:00 by dan-claude-bot · 2 comments
dan-claude-bot commented 2026-07-15 17:23:15 +00:00 (Migrated from github.com)

Summary

#68 / PR #69 settle cast's side of the is_static story: Coolify 4.1.2 never returns the field on any read, so cast degrades it to create-time-only (staticNotCompared, warn-and-skip). This issue tracks the two things that outlive that fix:

  1. the upstream errand — a small PR to coollabsio/coolify exposing application settings on a read, which is the only thing that can un-degrade the field, and
  2. a cast-side honesty gap that stands regardlessinventory --emit-draft silently omits static: true and UNCAPTURED.md never says so.

Why the field is unreadable (source-confirmed, v4.1.2 tag)

is_static is not an applications column — it lives on application_settings, reached via the settings hasOne relation. Every API read serializes the Application model bare:

  • GET /applications/{uuid}removeSensitiveDataserializeApiResponse = collect($model) — emits only loaded relations, and nothing on any read path loads settings (no $with, no ->load('settings'); $appends carries only server_status).
  • PATCH responds {"uuid": ...} even though its handler lazy-loads settings to write it.
  • No /applications/{uuid}/settings route exists in routes/api.php.
  • The shipped openapi.json agrees: the Application response schema has no is_static property.
  • The only code that serializes settings (ConfigurationGenerator::getApplicationSettings, which the "Download config" button uses) is Livewire-only — the dashboard reads the relation directly through Eloquent, never through the API.

Upstream main is identical as of 2026-07-15 — this is not fixed-and-awaiting-release.

The upstream errand

Open a PR on coollabsio/coolify. Preferred framing: an additive GET /applications/{uuid}/settings sub-route (mirrors the existing /envs pattern; zero risk to existing consumers), over widening the main GET response (changes response shape for everyone).

Odds look decent — external API PRs land there: feat(api): add REST endpoints for destinations (pksorensen), fix(api): allow source commit build setting (vaguul — same shape as this), fix(parsers): populate docker_compose_domains for API-created Docker Compose apps (creazy231 — the sibling field from #68). Open-PR backlog ~30. And coollabsio/coolify#6044 shows is_static API behavior is already on their radar.

What changes in cast when it ships

Nothing, by design — PR #69's projection already handles a future Coolify that returns the field: a real boolean flows through projectLiveFields, staticNotCompared stays unset, and the diff compares normally again. The work here is verification, not code: after the box's Coolify upgrades past the fix, a diff against a static app must show the field compared (no once-per-run warn) and still converge.

The cast-side gap that stands regardless

draft.ts (~L506) emits static: true only when raw.is_static === true — on 4.1.2 the key is simply absent, so a drafted manifest of a live static app silently omits the flag, and UNCAPTURED.md does not list it. That breaks the draft's own contract ("an honest list of what it could not capture", #27). A reviewer approving the draft has no cue that the field even exists to lose; the #63 failure mode (static site rebuilt and run as a plain app) re-enters through the draft door.

Fix: when the live read cannot see is_static (the staticNotCompared condition) and the app is plausibly static (nixpacks/static pack with a publish_directory), UNCAPTURED.md must name is_static as unreadable-on-this-Coolify — check the box in the UI, not the draft.

Checklist

  • --emit-draft: list is_static in UNCAPTURED.md for apps whose live read couldn't see it (cast code, independent of upstream)
  • Open the upstream PR (coollabsio/coolify, sub-route framing) and link it here
  • When it merges: note the first Coolify release carrying it
  • After the box upgrades past that release: verify diff compares is_static again (warn gone, converges) and close

Refs

  • #68 (probe + verdict) · PR #69 (warn-and-skip fix) · #63 (what silent loss of is_static does) · #27 (UNCAPTURED.md contract)
  • incubator brain: docs/brain/STATE.md Task 8 residuals (D-272 era)
## Summary #68 / PR #69 settle cast's side of the `is_static` story: Coolify 4.1.2 never returns the field on any read, so cast degrades it to **create-time-only** (`staticNotCompared`, warn-and-skip). This issue tracks the two things that outlive that fix: 1. **the upstream errand** — a small PR to coollabsio/coolify exposing application settings on a read, which is the only thing that can un-degrade the field, and 2. **a cast-side honesty gap that stands regardless** — `inventory --emit-draft` silently omits `static: true` and UNCAPTURED.md never says so. ## Why the field is unreadable (source-confirmed, v4.1.2 tag) `is_static` is not an `applications` column — it lives on `application_settings`, reached via the `settings` hasOne relation. Every API read serializes the `Application` model bare: - `GET /applications/{uuid}` → `removeSensitiveData` → `serializeApiResponse` = `collect($model)` — emits only **loaded** relations, and nothing on any read path loads `settings` (no `$with`, no `->load('settings')`; `$appends` carries only `server_status`). - `PATCH` responds `{"uuid": ...}` even though its handler lazy-loads `settings` to write it. - No `/applications/{uuid}/settings` route exists in `routes/api.php`. - The shipped `openapi.json` agrees: the `Application` response schema has no `is_static` property. - The only code that serializes settings (`ConfigurationGenerator::getApplicationSettings`, which the "Download config" button uses) is Livewire-only — the dashboard reads the relation directly through Eloquent, never through the API. **Upstream `main` is identical as of 2026-07-15** — this is not fixed-and-awaiting-release. ## The upstream errand Open a PR on coollabsio/coolify. Preferred framing: an additive `GET /applications/{uuid}/settings` sub-route (mirrors the existing `/envs` pattern; zero risk to existing consumers), over widening the main GET response (changes response shape for everyone). Odds look decent — external API PRs land there: `feat(api): add REST endpoints for destinations` (pksorensen), `fix(api): allow source commit build setting` (vaguul — same shape as this), `fix(parsers): populate docker_compose_domains for API-created Docker Compose apps` (creazy231 — the sibling field from #68). Open-PR backlog ~30. And coollabsio/coolify#6044 shows `is_static` API behavior is already on their radar. ## What changes in cast when it ships **Nothing, by design** — PR #69's projection already handles a future Coolify that returns the field: a real boolean flows through `projectLiveFields`, `staticNotCompared` stays unset, and the diff compares normally again. The work here is verification, not code: after the box's Coolify upgrades past the fix, a `diff` against a static app must show the field compared (no once-per-run warn) and still converge. ## The cast-side gap that stands regardless `draft.ts` (~L506) emits `static: true` only when `raw.is_static === true` — on 4.1.2 the key is simply absent, so a drafted manifest of a live static app **silently omits** the flag, and UNCAPTURED.md does not list it. That breaks the draft's own contract ("an honest list of what it could not capture", #27). A reviewer approving the draft has no cue that the field even exists to lose; the #63 failure mode (static site rebuilt and run as a plain app) re-enters through the draft door. Fix: when the live read cannot see `is_static` (the `staticNotCompared` condition) and the app is plausibly static (nixpacks/static pack with a `publish_directory`), UNCAPTURED.md must name `is_static` as unreadable-on-this-Coolify — check the box in the UI, not the draft. ## Checklist - [ ] `--emit-draft`: list `is_static` in UNCAPTURED.md for apps whose live read couldn't see it (cast code, independent of upstream) - [ ] Open the upstream PR (coollabsio/coolify, sub-route framing) and link it here - [ ] When it merges: note the first Coolify release carrying it - [ ] After the box upgrades past that release: verify `diff` compares `is_static` again (warn gone, converges) and close ## Refs - #68 (probe + verdict) · PR #69 (warn-and-skip fix) · #63 (what silent loss of `is_static` does) · #27 (UNCAPTURED.md contract) - incubator brain: `docs/brain/STATE.md` Task 8 residuals (D-272 era)
dan-claude-bot commented 2026-07-16 12:16:20 +00:00 (Migrated from github.com)

Status update from the #72 audit — the upstream errand is dead, and not because we ran it: upstream did it themselves.

Commit 34e6a6dd5 ("feat(api): add application settings to application endpoints", 2026-07-14) on coollabsio/coolify next makes GET /applications/{uuid} eager-load settings and return the whole ApplicationSetting row (minus ids/timestamps), plus adds 13 more settings fields to the create/PATCH allowlists. That is a superset of the sub-route this issue asked for. Maintainer confirmed the v4.2 targeting in coollabsio/coolify discussion #10823 ("This will be added to the next (4.2) version!"); our own duplicate attempts (coollabsio/coolify#10948 / #10949) were closed as redundant. The fork branches carrying the GET /applications/{uuid}/settings sub-route have been deleted.

One correction to this issue's body: "Upstream main is identical as of 2026-07-15" was checked against the wrong branch — the fix had been sitting on next since 2026-07-14. next is where v4.2 is assembled (coollabsio/coolify#10872).

Updated checklist:

  • --emit-draft: list is_static in UNCAPTURED.md for apps whose live read couldn't see it (cast code, still needed for as long as we run 4.1.2)
  • Open the upstream PR — obsolete: upstream shipped it on next as 34e6a6dd5, a superset of the planned sub-route
  • First Coolify release carrying it: v4.2 (unreleased; next branch, release train coollabsio/coolify#10872)
  • After the box upgrades to v4.2: verify diff compares is_static again (warn gone, converges) — note the real response shape is a nested settings object with ids/timestamps hidden, not a top-level field; projectLiveFields was written before this shape existed and must be checked against it, not assumed compatible

Remaining scope is the two unchecked items. Broader v4.2 upgrade prep (token abilities, OpenAPI re-vendor, settings as diffed fields) is tracked separately — see #72.

Status update from the #72 audit — the upstream errand is **dead, and not because we ran it**: upstream did it themselves. Commit `34e6a6dd5` ("feat(api): add application settings to application endpoints", 2026-07-14) on coollabsio/coolify `next` makes `GET /applications/{uuid}` eager-load `settings` and return the whole `ApplicationSetting` row (minus ids/timestamps), plus adds 13 more settings fields to the create/PATCH allowlists. That is a superset of the sub-route this issue asked for. Maintainer confirmed the v4.2 targeting in coollabsio/coolify discussion #10823 ("This will be added to the next (4.2) version!"); our own duplicate attempts (coollabsio/coolify#10948 / #10949) were closed as redundant. The fork branches carrying the `GET /applications/{uuid}/settings` sub-route have been deleted. One correction to this issue's body: "Upstream `main` is identical as of 2026-07-15" was checked against the wrong branch — the fix had been sitting on `next` since 2026-07-14. `next` is where v4.2 is assembled (coollabsio/coolify#10872). Updated checklist: - [ ] `--emit-draft`: list `is_static` in UNCAPTURED.md for apps whose live read couldn't see it (cast code, still needed for as long as we run 4.1.2) - [x] ~~Open the upstream PR~~ — obsolete: upstream shipped it on `next` as `34e6a6dd5`, a superset of the planned sub-route - [x] First Coolify release carrying it: **v4.2** (unreleased; `next` branch, release train coollabsio/coolify#10872) - [ ] After the box upgrades to v4.2: verify `diff` compares `is_static` again (warn gone, converges) — note the real response shape is a nested `settings` object with ids/timestamps hidden, not a top-level field; `projectLiveFields` was written before this shape existed and must be checked against it, not assumed compatible Remaining scope is the two unchecked items. Broader v4.2 upgrade prep (token abilities, OpenAPI re-vendor, settings as diffed fields) is tracked separately — see #72.
dan-claude-bot commented 2026-07-19 19:37:06 +00:00 (Migrated from github.com)

Closing — the cast-side work is done and the only remaining item is tracked in #77.

Checklist status:

  • --emit-draft names is_static in UNCAPTURED.md when the live read can't see it — landed as 50b0d2d, src/draft.ts:466-475
  • Upstream errand — obsolete, upstream shipped 34e6a6dd5 on next (a superset of the sub-route this asked for); our duplicates were closed as redundant
  • First release carrying it — v4.2
  • Post-upgrade verification — now tracked in #77

#77's settings bullet carries it verbatim, including the sharp part: "is_static un-degrades (verify per #70's remaining checklist — the response shape is a nested settings object, projectLiveFields predates it)". Nothing is lost by closing here.

Also worth recording that this issue's body is now misleading in one respect: "Upstream main is identical as of 2026-07-15" was checked against the wrong branch — the fix had been on next since 2026-07-14. That's already corrected in the comment above, but leaving it open with a stale premise and a done checklist reads as unfinished work when it isn't.

Reopen if the v4.2 verification turns up something #77 doesn't cover.

Closing — the cast-side work is done and the only remaining item is tracked in #77. Checklist status: - [x] `--emit-draft` names `is_static` in UNCAPTURED.md when the live read can't see it — landed as `50b0d2d`, `src/draft.ts:466-475` - [x] Upstream errand — obsolete, upstream shipped `34e6a6dd5` on `next` (a superset of the sub-route this asked for); our duplicates were closed as redundant - [x] First release carrying it — v4.2 - [ ] Post-upgrade verification — **now tracked in #77** #77's settings bullet carries it verbatim, including the sharp part: "`is_static` un-degrades (verify per #70's remaining checklist — the response shape is a *nested* `settings` object, `projectLiveFields` predates it)". Nothing is lost by closing here. Also worth recording that this issue's body is now misleading in one respect: "Upstream `main` is identical as of 2026-07-15" was checked against the wrong branch — the fix had been on `next` since 2026-07-14. That's already corrected in the comment above, but leaving it open with a stale premise and a done checklist reads as unfinished work when it isn't. Reopen if the v4.2 verification turns up something #77 doesn't cover.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: heavy-duty/cast#70
No description provided.