Audit: every manual Coolify-UI step, verified against v4.1.2 and next — two claims refuted, two gaps survive into v4.2 #72

Closed
opened 2026-07-16 11:55:21 +00:00 by dan-claude-bot · 4 comments
dan-claude-bot commented 2026-07-16 11:55:21 +00:00 (Migrated from github.com)

Audit: every manual Coolify-UI step cast requires, verified against v4.1.2 and next

Goal: get cast to zero UI-tweaking. This is a sequential audit of every place cast tells the operator to do something by hand in the Coolify dashboard, each claim re-verified against the Coolify source at tag v4.1.2 (what we run) and against upstream/next (the unreleased v4.2 train, PR coollabsio/coolify#10872).

Method: enumerated every manual-UI touchpoint from docs/semantics.md, src/draft.ts (NO_API_COVERAGE), reference/README.md, and the warn sites in src/resolve.ts / src/cli.ts; verified each against v4.1.2 controllers/routes with file:line evidence; diffed v4.1.2..upstream/next for API additions; searched coollabsio/coolify issues/discussions/PRs for existing feature requests.

Headline: two of cast's documented "no API for this" claims are wrong at v4.1.2 (service hostnames; GitHub App visibility/creation) — the same failure mode the #51 backup-schedule post-mortem warned about: a defect filed as a limitation does not get fixed. Three more are real in 4.1.2 but solved on next. Only two genuinely survive into v4.2: S3 storages and backup S3-target readback.

The matrix

"In 4.1.2?" = can it be done via the API in the release we run today. "In next?" = added on upstream/next (v4.2).

# Manual UI step cast requires today Where cast says so In 4.1.2? In next?
1 Set service hostnames (per-container FQDNs) in the UI warn in resolve.ts:673, semantics.md "Known limitations", draft.ts NO_API_COVERAGE yes (cast's claim refuted) yes (even better)
2 Enable Include Source Commit in Build on the Advanced tab warn in resolve.ts (~L555), semantics.md no yes
3 Read/write the rest of the Settings-tab toggles (is_static readback #70, gzip, stripprefix, build cache, …) #68/#69/#70, draft.ts NO_API_COVERAGE ⚠️ partial (8 fields writable, all write-only) yes (read + write)
4 Read the destination (Docker network) UUID off the UI into environments.yaml reference/README.md, draft.ts #21, semantics.md Placement no yes
5 Create S3 storage destinations in Settings → S3 Storages and copy the UUID by hand reference/README.md "Known gap" no no
6 Verify a backup schedule's S3 target (write-only s3_storage_uuid) semantics.md Backup schedules no no
7 Configure Basic Auth / custom Traefik labels draft.ts NO_API_COVERAGE ⚠️ apps yes (cast has no manifest field); services no same (plus a read regression, see below)
8 Identify which GitHub App clones a repo; register an App semantics.md github_apps, draft.ts mostly (cast's claim refuted) improved
9 Re-create the GitHub App private key / re-mint S3 access keys (DR) UNCAPTURED.md CANNOT_RESTORE inherently manual (credentials)
10 Orphan removal, cross-network moves, force_domain_override diff.ts, apply.ts API supports these; manual by cast policy, not API gap next adds POST /{resource}/{uuid}/move (between environments)

Findings in detail

1. Service hostnames — REFUTED: 4.1.2 can do this today

semantics.md says the claim was "re-checked and holds: Coolify 4.1.2 exposes no flat domains on a service, on any route", and draft.ts says per-container fqdn "can neither read nor write". Both halves are false at v4.1.2:

  • Write: ServicesController accepts urls (array of {name, url}, comma-separated URLs per container) on both POST /services (create allowlist + validation, ServicesController.php:296,320-324) and PATCH /services/{uuid} (allowlist :962). applyServiceUrls() (:45-135) matches urls[].name to a ServiceApplication and sets fqdn, with cross-team domain-conflict detection (409 unless force_domain_override=true). Landed in coollabsio/coolify#7929, merged 2026-01-14 — well before 4.1.2.
  • Read: GET /services/{uuid} does $service->load(['applications', 'databases']) (:736) and removeSensitiveData does not hide fqdn — per-container hostnames come back. Create/PATCH responses even return 'domains' => $service->applications()->pluck('fqdn').
  • Caveats: the list endpoint GET /services doesn't load applications, and there's no per-container sub-route in 4.1.2 (that arrives on next: GET/PATCH /services/{uuid}/applications/{app_uuid}, where PATCH takes url → stored as fqdn).

What's actually missing is on cast's side: the manifest's flat domains: string[] has no per-container name to build urls[] from. Compose applications already solve this shape with service_domains (map of compose service → URLs); services need the same vocabulary. With that, the warn in resolve.ts:673, the drop in serviceApiFields(), and the draft.ts UNCAPTURED rows all become deletable, and service domains become a diffed field — exactly the #51 arc repeating.

2. Include Source Commit in Build — confirmed for 4.1.2, solved on next

Confirmed: include_source_commit_in_build appears in zero API controllers at v4.1.2, and both allowlists (ApplicationsController.php:914, :2368) reject unknown keys with "This field is not allowed." Only writer is the Livewire Advanced tab.

On next: first made PATCH-able by #10551 (63ba33261), then commit 34e6a6dd5 ("feat(api): add application settings to application endpoints", 2026-07-14) adds a full APPLICATION_SETTING_FIELDS allowlist to PATCH and all create variants, including this flag.

3. Settings-tab toggles generally — 4.1.2 partial and write-only, next solves read+write

At v4.1.2, exactly eight ApplicationSetting-backed fields are API-settable (is_static, is_spa, is_auto_deploy_enabled, is_force_https_enabled, connect_to_docker_network, use_build_server, is_container_label_escape_enabled, is_preserve_repository_enabled) — and none are readable: no read path loads the settings relation (the #70 finding, reconfirmed).

On next, 34e6a6dd5 changes both sides:

  • Read: GET /applications/{uuid} eager-loads settings and returns the whole ApplicationSetting row (minus ids/timestamps). This un-degrades is_static from create-time-only — the exact upstream errand #70 tracks. Note #70's "upstream is identical as of 2026-07-15" was checked against the wrong branch; next has had this since 2026-07-14. The fork branch feat/api-get-application-settings (dedicated GET /applications/{uuid}/settings sub-route) is now redundant — upstream closed danmt's #10948/#10949 as duplicate of this commit.
  • Write: 13 more settings fields join the allowlists (is_git_submodules_enabled, is_git_lfs_enabled, is_git_shallow_clone_enabled, disable_build_cache, inject_build_args_to_dockerfile, include_source_commit_in_build, is_env_sorting_enabled, is_pr_deployments_public_enabled, stop_grace_period, docker_images_to_keep, is_gzip_enabled, is_stripprefix_enabled, is_raw_compose_deployment_enabled) plus use_build_secrets.

Maintainer confirmation in discussion coollabsio/coolify#10823: "This will be added to the next (4.2) version!"

4. Destinations — confirmed for 4.1.2, solved on next

Confirmed: zero routes matching destination in routes/api.php at v4.1.2; reads return only the integer destination_id + morph type; nothing maps int → UUID (reference/README.md findings all hold).

On next: coollabsio/coolify#10405 (merged 2026-07-02) adds GET /destinations, GET/DELETE /destinations/{uuid}, GET/POST /servers/{server_uuid}/destinations. Response carries uuid, name, network, type, server_uuid. Create takes network (+ optional name, type); duplicates rejected; races handled as conflicts. The commit message explicitly motivates it with our use case ("blocks IaC tools").

Residual even on next: resource reads still return destination_id (int), and /destinations doesn't expose the internal id — so verifying an existing resource's placement (int → uuid) is still impossible; you can enumerate and choose, not confirm. Small upstream ask, see feature requests.

5. S3 storage destinations — confirmed, NOT solved on next

Confirmed at v4.1.2 and unchanged on next: no list/create/read routes for S3 storages anywhere; the only storages routes are per-resource volume mounts. The only API code touching S3Storage is backup-payload validation. The s3_destination UUID in environments.yaml stays a hand-copied value from Settings → S3 Storages.

No existing upstream issue/discussion/PR asks for this. Genuinely unclaimed.

6. Backup schedule S3 target — confirmed, NOT solved on next

Confirmed: GET /databases/{uuid}/backups returns raw Eloquent rows with integer s3_storage_id; writes take s3_storage_uuid (validated, converted, unset). Byte-for-byte identical on next. Combined with #5 there is no id→uuid recovery, so cast's "assert on every write, never verify" stance remains forced. (next does add GET /databases/{uuid}/backups/{scheduled_backup_uuid}/executions and ClickHouse backup support — adjacent, not this.)

7. Basic Auth / custom Traefik labels — apps possible today, services not

Partially refuted at v4.1.2 for applications: custom_labels (base64-validated), is_http_basic_auth_enabled, http_basic_auth_username, http_basic_auth_password are in both create/PATCH allowlists; reads of custom_labels/password are gated behind a sensitive-data-enabled token. Caveats: enabling basic auth or changing domains regenerates labels (overwrites custom_labels unless is_container_label_readonly_enabled — which is itself not API-settable in 4.1.2). For services: nothing, in either release.

So the UNCAPTURED.md row "cast's manifest has no field for them, so a rebuilt resource is UNPROTECTED" is a cast vocabulary gap for applications, an API gap only for services.

Upgrade heads-up: on next, the token-abilities work (8b7dbbafb, 9a2c432c7, …) puts custom_labels, http_basic_auth_password, dockerfile, docker_compose(_raw), webhook secrets behind read:sensitive/root token abilities at the model level. Cast's API token will need those abilities after the v4.2 upgrade or reads silently lose fields.

8. GitHub App association and registration — REFUTED: mostly visible/creatable in 4.1.2

semantics.md claims "nothing Coolify returns about an application says which App cloned it". False at v4.1.2: removeSensitiveData does not hide source_id/source_type, so every application GET returns them, and GET /github-apps (routes exist at routes/api.php:131-136) returns each App's id — the association is resolvable via API. Cast's "bind to the only App or write a REVIEW marker" heuristic can be replaced with an actual lookup.

Creation: POST /github-apps + POST /security/keys exist at 4.1.2 — they register an already-created App (you supply app_id/installation_id/secrets/private key); the GitHub-side Manifest flow stays manual (already tracked in #7/#5, and #7's manifest-flow idea remains the real fix). Known upstream bugs: #10936 (PATCH rejects CUID2 private_key_uuid; contributor has a draft PR), #5467 (create times out with many repos). On next, api_url becomes optional (derived from html_url) and secrets become readable with read:sensitive.

The DR row stands: the private key value and S3 access keys are credentials, not state — re-created by hand by nature, no API can change that.

10. Policy acts — for the whole picture

These appear in cast's output as "resolve manually (runbook act)" but are cast policy, not API gaps — the API could do all of them: orphan deletion (DELETE /{resource}/{uuid} exists; apply never deletes by design), moving a resource between networks (PATCH accepts destination_uuid for apps/services; apply refuses by design), force_domain_override (deliberately never sent). No upstream ask needed; listed so the audit is the complete inventory of every "go do it in the UI" cast can emit.

Feature requests to file upstream

Already covered — do not file:

  • Application settings read/write — done on next (34e6a6dd5); maintainer confirmed for v4.2 in discussion #10823; our #10948/#10949 already closed as redundant.
  • Destinations API — done on next (#10405). Discussion #8827 ("REST API for Destinations") is still open and can get a "solved in v4.2" comment.
  • Backup-now trigger — discussion #10286 exists (open); upvote rather than duplicate. (Not currently needed by cast.)

Worth filing (nothing exists upstream today):

  1. S3 Storages API (GET/POST /s3-storages, at minimum a team-scoped list returning uuid/name/endpoint) — the last resource cast needs that has zero API surface. It blocks both bootstrap automation (hand-copying s3_destination) and backup-target verification. Motivate it the way #10405 was motivated (IaC tools blocked); adjacent demand: discussion #7451, issues #6488, #9754.
  2. Return the S3 target as s3_storage_uuid on GET /databases/{uuid}/backups (or shape the response as an API resource) — writes already speak UUID; reads leak the internal int. One-line-ish serializer fix that makes backup targets round-trip verifiable.
  3. Expose the destination UUID on resource reads (serialize destination.uuid alongside destination_id, or include internal id in /destinations responses) — complements merged #10405; without it existing placements can be set and enumerated but never verified.
  4. (Optional, only if we ever manage service-level protection) Custom labels / Basic Auth on services via API — unclaimed upstream; applications already have it.

Cast action items falling out of this audit

  • Adopt service urls on 4.1.2 now (biggest UI-step deletion available today): give services a per-container domains vocabulary (mirror compose service_domains), write via urls on create/PATCH, read applications[].fqdn back on GET /services/{uuid}, make it a diffed field; delete the warn in resolve.ts:673, the drop in serviceApiFields(), and the two UNCAPTURED rows. Correct semantics.md's struck-through-precedent entry the same way #51 corrected the backup one.
  • Resolve GitHub App binding via source_idGET /github-apps instead of the only-App heuristic; correct semantics.md/draft.ts claims.
  • Fix stale text: draft.ts:594 still says backup schedules "are not exposed by Coolify's API" (disproved by #51); NO_API_COVERAGE rows for service hostnames and GitHub Apps.
  • Update #70: the upstream errand is already done on next (34e6a6dd5 serializes settings on GET /applications/{uuid}); the fork's GET .../settings sub-route branch is redundant; remaining work is the draft-honesty item plus post-v4.2 verification.
  • (Optional) manifest fields for application custom_labels / Basic Auth so a rebuilt resource isn't silently unprotected.
  • v4.2 upgrade prep: re-vendor OpenAPI, mint a token with read:sensitive (labels/compose reads move behind token abilities), then migrate: settings block into diffed fields (kills #2/#3 manual steps and un-degrades is_static), destinations resolver via /destinations (kills the hand-copied destination_uuid).

After all that, the irreducible manual set

With the 4.1.2-available fixes landed and a v4.2 upgrade: (a) create S3 storages in the UI and copy the UUID (until feature request 1 lands), (b) the GitHub-side App creation flow (#7's manifest flow shrinks it), (c) credential re-creation in DR (inherent), (d) cast's own deliberate runbook acts (orphan deletion, network moves, domain overrides — by design). Everything else cast currently tells the operator to do in the UI is automatable.


Verification notes: all v4.1.2 claims checked against tag v4.1.2 controllers/routes; next claims against upstream/next as of 2026-07-16 (7d699818e); upstream issue/PR landscape searched 2026-07-16.

# Audit: every manual Coolify-UI step cast requires, verified against v4.1.2 and `next` **Goal:** get cast to zero UI-tweaking. This is a sequential audit of every place cast tells the operator to do something by hand in the Coolify dashboard, each claim re-verified against the Coolify source at tag `v4.1.2` (what we run) and against `upstream/next` (the unreleased v4.2 train, PR coollabsio/coolify#10872). **Method:** enumerated every manual-UI touchpoint from `docs/semantics.md`, `src/draft.ts` (`NO_API_COVERAGE`), `reference/README.md`, and the warn sites in `src/resolve.ts` / `src/cli.ts`; verified each against `v4.1.2` controllers/routes with file:line evidence; diffed `v4.1.2..upstream/next` for API additions; searched coollabsio/coolify issues/discussions/PRs for existing feature requests. **Headline:** two of cast's documented "no API for this" claims are **wrong at v4.1.2** (service hostnames; GitHub App visibility/creation) — the same failure mode the #51 backup-schedule post-mortem warned about: *a defect filed as a limitation does not get fixed*. Three more are real in 4.1.2 but solved on `next`. Only two genuinely survive into v4.2: **S3 storages** and **backup S3-target readback**. ## The matrix "In 4.1.2?" = can it be done via the API in the release we run today. "In next?" = added on `upstream/next` (v4.2). | # | Manual UI step cast requires today | Where cast says so | In 4.1.2? | In next? | |---|---|---|---|---| | 1 | Set service hostnames (per-container FQDNs) in the UI | warn in `resolve.ts:673`, `semantics.md` "Known limitations", `draft.ts` NO_API_COVERAGE | **✅ yes** (cast's claim refuted) | ✅ yes (even better) | | 2 | Enable *Include Source Commit in Build* on the Advanced tab | warn in `resolve.ts` (~L555), `semantics.md` | ❌ no | ✅ yes | | 3 | Read/write the rest of the Settings-tab toggles (`is_static` readback #70, gzip, stripprefix, build cache, …) | #68/#69/#70, `draft.ts` NO_API_COVERAGE | ⚠️ partial (8 fields writable, **all write-only**) | ✅ yes (read + write) | | 4 | Read the destination (Docker network) UUID off the UI into `environments.yaml` | `reference/README.md`, `draft.ts` #21, `semantics.md` Placement | ❌ no | ✅ yes | | 5 | Create S3 storage destinations in Settings → S3 Storages and copy the UUID by hand | `reference/README.md` "Known gap" | ❌ no | ❌ **no** | | 6 | Verify a backup schedule's S3 target (write-only `s3_storage_uuid`) | `semantics.md` Backup schedules | ❌ no | ❌ **no** | | 7 | Configure Basic Auth / custom Traefik labels | `draft.ts` NO_API_COVERAGE | ⚠️ apps **yes** (cast has no manifest field); services no | same (plus a read regression, see below) | | 8 | Identify which GitHub App clones a repo; register an App | `semantics.md` `github_apps`, `draft.ts` | **✅ mostly** (cast's claim refuted) | ✅ improved | | 9 | Re-create the GitHub App private key / re-mint S3 access keys (DR) | UNCAPTURED.md `CANNOT_RESTORE` | inherently manual (credentials) | — | | 10 | Orphan removal, cross-network moves, `force_domain_override` | `diff.ts`, `apply.ts` | API supports these; **manual by cast policy**, not API gap | `next` adds `POST /{resource}/{uuid}/move` (between environments) | ## Findings in detail ### 1. Service hostnames — REFUTED: 4.1.2 can do this today `semantics.md` says the claim was "re-checked and holds: Coolify 4.1.2 exposes no flat `domains` on a service, on any route", and `draft.ts` says per-container `fqdn` "can neither read nor write". Both halves are false at `v4.1.2`: - **Write:** `ServicesController` accepts `urls` (array of `{name, url}`, comma-separated URLs per container) on **both** `POST /services` (create allowlist + validation, `ServicesController.php:296,320-324`) and `PATCH /services/{uuid}` (allowlist `:962`). `applyServiceUrls()` (`:45-135`) matches `urls[].name` to a `ServiceApplication` and sets `fqdn`, with cross-team domain-conflict detection (409 unless `force_domain_override=true`). Landed in coollabsio/coolify#7929, merged 2026-01-14 — well before 4.1.2. - **Read:** `GET /services/{uuid}` does `$service->load(['applications', 'databases'])` (`:736`) and `removeSensitiveData` does **not** hide `fqdn` — per-container hostnames come back. Create/PATCH responses even return `'domains' => $service->applications()->pluck('fqdn')`. - Caveats: the *list* endpoint `GET /services` doesn't load applications, and there's no per-container sub-route in 4.1.2 (that arrives on `next`: `GET/PATCH /services/{uuid}/applications/{app_uuid}`, where PATCH takes `url` → stored as fqdn). What's actually missing is on **cast's side**: the manifest's flat `domains: string[]` has no per-container name to build `urls[]` from. Compose applications already solve this shape with `service_domains` (map of compose service → URLs); services need the same vocabulary. With that, the warn in `resolve.ts:673`, the drop in `serviceApiFields()`, and the `draft.ts` UNCAPTURED rows all become deletable, and service domains become a **diffed field** — exactly the #51 arc repeating. ### 2. Include Source Commit in Build — confirmed for 4.1.2, solved on next Confirmed: `include_source_commit_in_build` appears in zero API controllers at `v4.1.2`, and both allowlists (`ApplicationsController.php:914`, `:2368`) reject unknown keys with "This field is not allowed." Only writer is the Livewire Advanced tab. On `next`: first made PATCH-able by #10551 (`63ba33261`), then commit `34e6a6dd5` ("feat(api): add application settings to application endpoints", 2026-07-14) adds a full `APPLICATION_SETTING_FIELDS` allowlist to PATCH **and** all create variants, including this flag. ### 3. Settings-tab toggles generally — 4.1.2 partial and write-only, next solves read+write At `v4.1.2`, exactly eight `ApplicationSetting`-backed fields are API-settable (`is_static`, `is_spa`, `is_auto_deploy_enabled`, `is_force_https_enabled`, `connect_to_docker_network`, `use_build_server`, `is_container_label_escape_enabled`, `is_preserve_repository_enabled`) — and **none are readable**: no read path loads the `settings` relation (the #70 finding, reconfirmed). On `next`, `34e6a6dd5` changes both sides: - **Read:** `GET /applications/{uuid}` eager-loads `settings` and returns the whole `ApplicationSetting` row (minus ids/timestamps). This un-degrades `is_static` from create-time-only — the exact upstream errand #70 tracks. Note #70's "upstream is identical as of 2026-07-15" was checked against the wrong branch; `next` has had this since 2026-07-14. The fork branch `feat/api-get-application-settings` (dedicated `GET /applications/{uuid}/settings` sub-route) is now redundant — upstream closed danmt's #10948/#10949 as duplicate of this commit. - **Write:** 13 more settings fields join the allowlists (`is_git_submodules_enabled`, `is_git_lfs_enabled`, `is_git_shallow_clone_enabled`, `disable_build_cache`, `inject_build_args_to_dockerfile`, `include_source_commit_in_build`, `is_env_sorting_enabled`, `is_pr_deployments_public_enabled`, `stop_grace_period`, `docker_images_to_keep`, `is_gzip_enabled`, `is_stripprefix_enabled`, `is_raw_compose_deployment_enabled`) plus `use_build_secrets`. Maintainer confirmation in discussion coollabsio/coolify#10823: "This will be added to the next (4.2) version!" ### 4. Destinations — confirmed for 4.1.2, solved on next Confirmed: zero routes matching `destination` in `routes/api.php` at `v4.1.2`; reads return only the integer `destination_id` + morph type; nothing maps int → UUID (`reference/README.md` findings all hold). On `next`: coollabsio/coolify#10405 (merged 2026-07-02) adds `GET /destinations`, `GET/DELETE /destinations/{uuid}`, `GET/POST /servers/{server_uuid}/destinations`. Response carries `uuid`, `name`, `network`, `type`, `server_uuid`. Create takes `network` (+ optional `name`, `type`); duplicates rejected; races handled as conflicts. The commit message explicitly motivates it with our use case ("blocks IaC tools"). **Residual even on next:** resource reads still return `destination_id` (int), and `/destinations` doesn't expose the internal id — so *verifying* an existing resource's placement (int → uuid) is still impossible; you can enumerate and choose, not confirm. Small upstream ask, see feature requests. ### 5. S3 storage destinations — confirmed, NOT solved on next Confirmed at `v4.1.2` and unchanged on `next`: no list/create/read routes for S3 storages anywhere; the only `storages` routes are per-resource volume mounts. The only API code touching `S3Storage` is backup-payload validation. The `s3_destination` UUID in `environments.yaml` stays a hand-copied value from Settings → S3 Storages. No existing upstream issue/discussion/PR asks for this. **Genuinely unclaimed.** ### 6. Backup schedule S3 target — confirmed, NOT solved on next Confirmed: `GET /databases/{uuid}/backups` returns raw Eloquent rows with integer `s3_storage_id`; writes take `s3_storage_uuid` (validated, converted, unset). Byte-for-byte identical on `next`. Combined with #5 there is no id→uuid recovery, so cast's "assert on every write, never verify" stance remains forced. (`next` does add `GET /databases/{uuid}/backups/{scheduled_backup_uuid}/executions` and ClickHouse backup support — adjacent, not this.) ### 7. Basic Auth / custom Traefik labels — apps possible today, services not Partially refuted at `v4.1.2` for **applications**: `custom_labels` (base64-validated), `is_http_basic_auth_enabled`, `http_basic_auth_username`, `http_basic_auth_password` are in both create/PATCH allowlists; reads of `custom_labels`/password are gated behind a sensitive-data-enabled token. Caveats: enabling basic auth or changing domains regenerates labels (overwrites `custom_labels` unless `is_container_label_readonly_enabled` — which is itself not API-settable in 4.1.2). For **services**: nothing, in either release. So the UNCAPTURED.md row "cast's manifest has no field for them, so a rebuilt resource is UNPROTECTED" is a **cast vocabulary gap** for applications, an API gap only for services. **Upgrade heads-up:** on `next`, the token-abilities work (`8b7dbbafb`, `9a2c432c7`, …) puts `custom_labels`, `http_basic_auth_password`, `dockerfile`, `docker_compose(_raw)`, webhook secrets behind `read:sensitive`/`root` token abilities at the model level. Cast's API token will need those abilities after the v4.2 upgrade or reads silently lose fields. ### 8. GitHub App association and registration — REFUTED: mostly visible/creatable in 4.1.2 `semantics.md` claims "nothing Coolify returns about an application says which App cloned it". False at `v4.1.2`: `removeSensitiveData` does **not** hide `source_id`/`source_type`, so every application GET returns them, and `GET /github-apps` (routes exist at `routes/api.php:131-136`) returns each App's `id` — the association is resolvable via API. Cast's "bind to the only App or write a REVIEW marker" heuristic can be replaced with an actual lookup. Creation: `POST /github-apps` + `POST /security/keys` exist at 4.1.2 — they *register* an already-created App (you supply app_id/installation_id/secrets/private key); the GitHub-side Manifest flow stays manual (already tracked in #7/#5, and #7's manifest-flow idea remains the real fix). Known upstream bugs: #10936 (PATCH rejects CUID2 `private_key_uuid`; contributor has a draft PR), #5467 (create times out with many repos). On `next`, `api_url` becomes optional (derived from `html_url`) and secrets become readable with `read:sensitive`. The DR row stands: the **private key value** and **S3 access keys** are credentials, not state — re-created by hand by nature, no API can change that. ### 10. Policy acts — for the whole picture These appear in cast's output as "resolve manually (runbook act)" but are cast **policy**, not API gaps — the API could do all of them: orphan deletion (`DELETE /{resource}/{uuid}` exists; `apply` never deletes by design), moving a resource between networks (PATCH accepts `destination_uuid` for apps/services; `apply` refuses by design), `force_domain_override` (deliberately never sent). No upstream ask needed; listed so the audit is the complete inventory of every "go do it in the UI" cast can emit. ## Feature requests to file upstream Already covered — do not file: - **Application settings read/write** — done on `next` (`34e6a6dd5`); maintainer confirmed for v4.2 in discussion #10823; our #10948/#10949 already closed as redundant. - **Destinations API** — done on `next` (#10405). Discussion #8827 ("REST API for Destinations") is still open and can get a "solved in v4.2" comment. - **Backup-now trigger** — discussion #10286 exists (open); upvote rather than duplicate. (Not currently needed by cast.) Worth filing (nothing exists upstream today): 1. **S3 Storages API** (`GET/POST /s3-storages`, at minimum a team-scoped list returning `uuid`/`name`/`endpoint`) — the last resource cast needs that has *zero* API surface. It blocks both bootstrap automation (hand-copying `s3_destination`) and backup-target verification. Motivate it the way #10405 was motivated (IaC tools blocked); adjacent demand: discussion #7451, issues #6488, #9754. 2. **Return the S3 target as `s3_storage_uuid` on `GET /databases/{uuid}/backups`** (or shape the response as an API resource) — writes already speak UUID; reads leak the internal int. One-line-ish serializer fix that makes backup targets round-trip verifiable. 3. **Expose the destination UUID on resource reads** (serialize `destination.uuid` alongside `destination_id`, or include internal `id` in `/destinations` responses) — complements merged #10405; without it existing placements can be set and enumerated but never *verified*. 4. *(Optional, only if we ever manage service-level protection)* **Custom labels / Basic Auth on services via API** — unclaimed upstream; applications already have it. ## Cast action items falling out of this audit - [ ] **Adopt service `urls` on 4.1.2 now** (biggest UI-step deletion available today): give services a per-container domains vocabulary (mirror compose `service_domains`), write via `urls` on create/PATCH, read `applications[].fqdn` back on `GET /services/{uuid}`, make it a diffed field; delete the warn in `resolve.ts:673`, the drop in `serviceApiFields()`, and the two UNCAPTURED rows. Correct `semantics.md`'s struck-through-precedent entry the same way #51 corrected the backup one. - [ ] **Resolve GitHub App binding via `source_id` ↔ `GET /github-apps`** instead of the only-App heuristic; correct `semantics.md`/`draft.ts` claims. - [ ] Fix stale text: `draft.ts:594` still says backup schedules "are not exposed by Coolify's API" (disproved by #51); NO_API_COVERAGE rows for service hostnames and GitHub Apps. - [ ] Update #70: the upstream errand is already done on `next` (`34e6a6dd5` serializes `settings` on `GET /applications/{uuid}`); the fork's `GET .../settings` sub-route branch is redundant; remaining work is the draft-honesty item plus post-v4.2 verification. - [ ] *(Optional)* manifest fields for application `custom_labels` / Basic Auth so a rebuilt resource isn't silently unprotected. - [ ] **v4.2 upgrade prep:** re-vendor OpenAPI, mint a token with `read:sensitive` (labels/compose reads move behind token abilities), then migrate: settings block into diffed fields (kills #2/#3 manual steps and un-degrades `is_static`), destinations resolver via `/destinations` (kills the hand-copied `destination_uuid`). ## After all that, the irreducible manual set With the 4.1.2-available fixes landed and a v4.2 upgrade: **(a)** create S3 storages in the UI and copy the UUID (until feature request 1 lands), **(b)** the GitHub-side App creation flow (#7's manifest flow shrinks it), **(c)** credential re-creation in DR (inherent), **(d)** cast's own deliberate runbook acts (orphan deletion, network moves, domain overrides — by design). Everything else cast currently tells the operator to do in the UI is automatable. --- *Verification notes: all v4.1.2 claims checked against tag `v4.1.2` controllers/routes; `next` claims against `upstream/next` as of 2026-07-16 (`7d699818e`); upstream issue/PR landscape searched 2026-07-16.*
dan-claude-bot commented 2026-07-16 12:18:22 +00:00 (Migrated from github.com)

The cast-side action items in this audit are now individual issues; this issue stays the reference matrix, they carry the work:

  • #73 — adopt service urls on 4.1.2: hostnames as a diffed field, warn + UNCAPTURED rows deleted (finding 1; actionable today)
  • #74 — resolve GitHub App binding via source_idGET /github-apps, retire the only-App heuristic (finding 8; actionable today)
  • #75 — draft reads backup schedules via GET /databases/{uuid}/backups and emits backup: blocks; kills the last pre-#51 stale claim (draft.ts:594)
  • #76 — Basic Auth / custom labels manifest fields for applications (finding 7; optional, caveat-heavy until v4.2)
  • #77 — v4.2 upgrade tracker: settings→diffed fields, destinations resolver, read:sensitive token, OpenAPI re-vendor (findings 2, 3, 4 + the token-abilities regression)

#70 was updated rather than closed: its upstream errand is obsolete (upstream shipped 34e6a6dd5 on next), but its draft-honesty fix and post-upgrade is_static verification remain live there. The redundant fork branches (feat/api-get-application-settings, -v4x) are deleted.

Upstream feature requests (S3 storages API, s3_storage_uuid readback, destination UUID on resource reads) are not yet filed — they live in this issue's "Worth filing" list until someone takes them to coollabsio/coolify.

The cast-side action items in this audit are now individual issues; this issue stays the reference matrix, they carry the work: - **#73** — adopt service `urls` on 4.1.2: hostnames as a diffed field, warn + UNCAPTURED rows deleted (finding 1; actionable today) - **#74** — resolve GitHub App binding via `source_id` ↔ `GET /github-apps`, retire the only-App heuristic (finding 8; actionable today) - **#75** — draft reads backup schedules via `GET /databases/{uuid}/backups` and emits `backup:` blocks; kills the last pre-#51 stale claim (`draft.ts:594`) - **#76** — Basic Auth / custom labels manifest fields for applications (finding 7; optional, caveat-heavy until v4.2) - **#77** — v4.2 upgrade tracker: settings→diffed fields, destinations resolver, `read:sensitive` token, OpenAPI re-vendor (findings 2, 3, 4 + the token-abilities regression) #70 was updated rather than closed: its upstream errand is obsolete (upstream shipped `34e6a6dd5` on `next`), but its draft-honesty fix and post-upgrade `is_static` verification remain live there. The redundant fork branches (`feat/api-get-application-settings`, `-v4x`) are deleted. Upstream feature requests (S3 storages API, `s3_storage_uuid` readback, destination UUID on resource reads) are not yet filed — they live in this issue's "Worth filing" list until someone takes them to coollabsio/coolify.
dan-claude-bot commented 2026-07-16 15:53:57 +00:00 (Migrated from github.com)

Status 2026-07-16 — everything solvable on 4.1.2 has landed. Leaving open; the remainder is v4.2-gated, deferred, or upstream.

Landed (merged today)

Audit item PR Issue
1 — service hostnames (refuted; the biggest UI-step deletion) #81 #73 closed
8 — GitHub App binding (refuted) #82 #74 closed
stale text: draft.ts:594 backup claim (disproved by #51) #80 #75text half only, still open
(adjacent) cast diff phantom env drift #79 #78 closed

Both refuted claims are now corrected in semantics.md #51-style, struck through rather than deleted — a limitation filed as a defect gets fixed; a defect filed as a limitation does not.

Still open, deliberately

  • #75 — the draft path still doesn't read /backups. #81 left it in the identical position for service hostnames (API answers, sweep doesn't ask). Both are "one supplementary read per resource" — probably one piece of work.
  • #76 (item 7, optional) — deferred by operator decision. Two findings from attempting it, detailed on the issue: custom_labels is unsound to manage at 4.1.2 (Coolify regenerates and clobbers it; the readonly flag isn't API-settable) and should likely leave scope; Basic Auth is sound but the password is a secret, so it needs age-store wiring, not a schema field.
  • #77 — items 2/3/4 (source-commit flag, settings read/write, destinations resolver) wait on the v4.2 release.
  • #70 — untouched; the audit's note that its upstream errand is already done on next (34e6a6dd5) still needs folding in.
  • Feature requests 1–3 (S3 Storages API, backup S3 s3_storage_uuid readback, destination UUID on resource reads) — not filed; left with the operator.

Verification note

Every claim these PRs relied on was re-checked against the v4.1.2 source directly, not taken from the audit: urls in both allowlists + applyServiceUrls's name→ServiceApplication match and its delete-on-conflict rollback for creates (ServicesController.php), $service->parse(isNew: true) running before applyServiceUrls (so container names exist at create), source_id/source_type surviving removeSensitiveData, and environment_details returning applications un-stripped. The audit held on every point checked.

One thing the audit's matrix understates: the only-App GitHub heuristic wasn't just unnecessary — it was silently wrong on a single-App instance, binding public repos (no App at all) to the only App. That's now pinned by a fixture.

Downstream: heavy-duty/incubator#17 migrates umami to service_domains (the flat service domains #81 removed).

**Status 2026-07-16 — everything solvable on 4.1.2 has landed. Leaving open**; the remainder is v4.2-gated, deferred, or upstream. ### Landed (merged today) | Audit item | PR | Issue | |---|---|---| | **1 — service hostnames** (refuted; the biggest UI-step deletion) | #81 | #73 ✅ closed | | **8 — GitHub App binding** (refuted) | #82 | #74 ✅ closed | | stale text: `draft.ts:594` backup claim (disproved by #51) | #80 | #75 — **text half only**, still open | | *(adjacent)* `cast diff` phantom env drift | #79 | #78 ✅ closed | Both refuted claims are now corrected in `semantics.md` #51-style, struck through rather than deleted — *a limitation filed as a defect gets fixed; a defect filed as a limitation does not.* ### Still open, deliberately - **#75** — the draft path still doesn't *read* `/backups`. #81 left it in the identical position for **service hostnames** (API answers, sweep doesn't ask). Both are "one supplementary read per resource" — probably one piece of work. - **#76** (item 7, optional) — **deferred by operator decision.** Two findings from attempting it, detailed on the issue: `custom_labels` is **unsound to manage at 4.1.2** (Coolify regenerates and clobbers it; the readonly flag isn't API-settable) and should likely leave scope; Basic Auth is sound but the password is a secret, so it needs age-store wiring, not a schema field. - **#77** — items 2/3/4 (source-commit flag, settings read/write, destinations resolver) wait on the v4.2 release. - **#70** — untouched; the audit's note that its upstream errand is already done on `next` (`34e6a6dd5`) still needs folding in. - **Feature requests 1–3** (S3 Storages API, backup S3 `s3_storage_uuid` readback, destination UUID on resource reads) — not filed; left with the operator. ### Verification note Every claim these PRs relied on was re-checked against the **v4.1.2 source directly**, not taken from the audit: `urls` in both allowlists + `applyServiceUrls`'s name→`ServiceApplication` match and its **delete-on-conflict rollback for creates** (`ServicesController.php`), `$service->parse(isNew: true)` running *before* `applyServiceUrls` (so container names exist at create), `source_id`/`source_type` surviving `removeSensitiveData`, and `environment_details` returning applications un-stripped. The audit held on every point checked. One thing the audit's matrix understates: the only-App GitHub heuristic wasn't just *unnecessary* — it was **silently wrong** on a single-App instance, binding public repos (no App at all) to the only App. That's now pinned by a fixture. Downstream: heavy-duty/incubator#17 migrates umami to `service_domains` (the flat service `domains` #81 removed).
dan-claude-bot commented 2026-07-16 15:57:52 +00:00 (Migrated from github.com)

Two follow-ups from this audit's implementation are now tracked rather than left implicit: #83 (--emit-draft doesn't make the per-service GET, so a drafted service has no hostnames — the twin of #75, which #81 exposed) and #84 (the semantics note that App registration is API-doable at 4.1.2 — #74's leftover box).

Two follow-ups from this audit's implementation are now tracked rather than left implicit: **#83** (`--emit-draft` doesn't make the per-service GET, so a drafted service has no hostnames — the twin of #75, which #81 exposed) and **#84** (the semantics note that App *registration* is API-doable at 4.1.2 — #74's leftover box).

Closing as complete — the audit's work is done or handed off, and a closed issue cites just as well.

Per the status comment above, everything solvable on 4.1.2 landed and is closed (#73, #76, #83, and the rest of that table). The remainder is v4.2-gated and tracked in #77, whose checklist also re-checks the two gaps that survive into v4.2 (S3 storages, backup S3-target readback). Nothing left here is a work order, and this board keeps only work orders open — the matrix stays the reference it was, from the closed state.

Closing as complete — the audit's work is done or handed off, and a closed issue cites just as well. Per the status comment above, everything solvable on 4.1.2 landed and is closed (#73, #76, #83, and the rest of that table). The remainder is v4.2-gated and tracked in #77, whose checklist also re-checks the two gaps that survive into v4.2 (S3 storages, backup S3-target readback). Nothing left here is a work order, and this board keeps only work orders open — the matrix stays the reference it was, from the closed state.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#72
No description provided.