fix(diff): converge live projection on compose domains and is_static #69
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#69
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/live-projection-compose-static"
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?
Against a live Coolify 4.1.2,
cast diff/applynever converged on two application fields — they re-diffed and redeployed on every apply. A live probe pinned two distinct root causes; both are fixed here.Fix 1 —
docker_compose_domainsparse bugCoolify 4.1.2 returns this field as a JSON-encoded, service-keyed object (
{"api":{"domain":"https://…"},…}), not the[{name,domain}]array cast expected. The array-only parser bailed toundefined, so cast diffed the desired map against nothing on every run.parseDockerComposeDomainsnow decodes the real object shape into the internal{service: string[]}map, while still tolerating the legacy array shape (the write-side round-trip). Malformed / scalar / empty still collapse toundefined.Fix 2 —
is_staticunreadableCoolify 4.1.2 returns
is_static: nullon the read path even for a genuinely-static app, so projectingfalsediffedfalse → trueforever.projectLiveFieldsnow omitsis_staticwhen the live value isnull/absent;fetchLiveflags the applicationstaticNotCompared;computeDiffskips the comparison (emitting a once-per-run warn), degradingis_staticto a create-time-only setting — the create path still sends it. A real live boolean is still projected and diffed normally, preserving #63's intent as far as the live API allows.Tests
parseDockerComposeDomains: real object shape → correct map; legacy array shape still parses; malformed → undefined.docker_compose_domainsis the object-shape string matching the manifest yields zero field diff.is_static: livenull+ manifeststatic: true→ no diff + warn fires once; a real live boolean still diffs normally.Docs
docs/semantics.mdupdated for the real read shape ofdocker_compose_domainsand theis_staticcreate-time-only degradation.Gates:
npm run build,npm test(508 passing),npm run checkall green.Closes #68
🤖 Generated with Claude Code
Verify-on-a-live-instance probe
A snippet to double-check the assumptions this PR encodes against a real Coolify 4.1.2 — specifically whether
is_static/docker_compose_domainsare actually present on the resource (and in what shape), not just what a filteredjqshows.nullfromjqis ambiguous (key-absent vs present-and-null), so this useshas()to disambiguate and dumps the full object:(Adjust
incubator/prod/core|landingfor another project.)Reading it:
has_is_static: true+is_static: null→ the column exists but the read path returns null → the warn-and-skip in this PR is correct (nothing readable to compare).has_is_static: false→ the top-level key is absent; check parts 3 & 4 for a nestedis_static(e.g. under asettingsrelation). If one exists, the better fix is to read it from there rather than skip — worth revisiting before merge.has_dcd: true+docker_compose_domains: "{\"api\":{\"domain\":…}}"→ confirms the service-keyed object read shape this PR now parses (vs the array the OpenAPI implies).Follow-up:
is_staticis absent top-level — it's on theApplicationSettingrelation (.settings.is_static)The live probe above returned
has_is_static: falseon both routes for both apps — i.e.is_staticisn't a top-level key at all; Coolify'sis_*toggles hang off.settings. So this PR's Fix 2 (raw.is_static == null→ warn-and-skip) is reading the wrong path. If.settings.is_staticis present on the routefetchLivereads, the better fix is to read it there — a real reconcile (and it'd detect a UI flip), not a degradation.Probe to confirm — the deciding question is whether the
environment_detailsembed (whatfetchLivereads), not just the flat/applications, carries.settings:Decides the fix:
settings.is_staticpresent and part 3 (the embed) carries.settings→ change Fix 2 from warn-and-skip to readraw.settings.is_static(real reconcile, detects UI flips)./applicationshas.settings, the embed does not →fetchLivecan't see it without an extra per-app GET; warn-and-skip may stay the pragmatic call.is_*toggles (is_spa,connect_to_docker_network,include_source_commit_in_build) — same top-level-vs-settingstrap if cast ever manages them.Updated the branch: the
is_staticcomments, thecomputeDiffwarn, anddocs/semantics.mdnow cite the coolify v4.1.2 source provingis_staticis on the never-serializedApplicationSettingrelation (full trace in cast#68). So the warn-and-skip half is source-confirmed correct — not a guess. Behavior unchanged; 508 tests green. Ready for review.