cast/src
claude-hdb 965541bbc1 fix: never write an env var whose name Coolify injects itself (#50)
Coolify injects SOURCE_COMMIT and the COOLIFY_* family into an application's
runtime environment itself, and SKIPS its own injection of a name the resource
already carries a var of (ApplicationDeploymentJob.php v4.1.2, line 2994 —
`->where('key', 'SOURCE_COMMIT')->isEmpty()`). A resource-level var of that
name therefore SUPPRESSES the platform's value. An empty one suppresses it
just as completely: presence, not value.

And it fails green — the deploy succeeds, health checks pass, and the only
symptom is /version reporting "unknown", the endpoint a production cutover is
gated on (D-266).

The rule is now a property of cast, not of one code path. A new src/reserved.ts
owns it, and every place cast touches an env var honors it:

- resolve — every manifest read (desiredFromManifest, requiredSecrets,
  manifestResources) refuses a template declaring a reserved name, before any
  write. So apply, diff, capture and inventory all refuse identically.
- draft — a reserved name read off a live box gets its own provenance,
  `suppressed`: out of the template, out of the age store, its live value read
  into no artifact, and named in UNCAPTURED.md with the consequence.
- diff — promoted out of the remove-candidate orphan list ("apply never removes
  these; read them by eye") and printed as a FINDING with its consequence. Not
  clean. apply still never deletes: cast reports, the human removes it.
- capture (classify) and cli (syncEnv) carry the same assertion at the file and
  at the wire — unreachable through the CLI today, and kept because the
  invariant is "cast never writes one", not "the CLI happens to check first".
- smoke writes an env var too; its probe names are asserted outside the space.

The rule lives in cast's code, NOT beside forbidden_var_patterns in private
state: that one is policy an environment may set for itself, this one is a fact
about Coolify, true on every box — nothing a manifest change could lower.

19 tests in test/reserved.test.ts, one per path.

Closes #50.
2026-07-14 22:29:23 +00:00
..
apply.ts feat: cast — the Coolify executor, extracted from the infra state repo 2026-07-11 12:25:44 +00:00
bindings.ts Merge pull request #32 from claude-hdb/feat/fleet-all 2026-07-13 21:45:55 +01:00
capture.ts fix: never write an env var whose name Coolify injects itself (#50) 2026-07-14 22:29:23 +00:00
cli.ts fix: never write an env var whose name Coolify injects itself (#50) 2026-07-14 22:29:23 +00:00
config.ts feat: select the Coolify instance by name instead of editing .coolify.env (#14) 2026-07-13 16:42:45 +00:00
coolify.ts fix: the first apply against a fresh multi-destination box (#40, #41) 2026-07-14 17:25:29 +00:00
diff.ts fix: never write an env var whose name Coolify injects itself (#50) 2026-07-14 22:29:23 +00:00
draft.ts fix: never write an env var whose name Coolify injects itself (#50) 2026-07-14 22:29:23 +00:00
envtemplate.ts feat: read-side coordinates (#17, #18) + cast inventory (#19) 2026-07-13 18:20:53 +00:00
fleet.ts feat: --all — every project in an environment, and a report that says so (#26) 2026-07-13 20:23:23 +00:00
inventory.ts fix(inventory): biome lint — a bare template literal and a string concat 2026-07-13 19:22:07 +00:00
manifest.ts feat: cast capture — adopt a hand-built Coolify into the age secret store (#15) 2026-07-13 16:51:43 +00:00
reserved.ts fix: never write an env var whose name Coolify injects itself (#50) 2026-07-14 22:29:23 +00:00
resolve.ts fix: never write an env var whose name Coolify injects itself (#50) 2026-07-14 22:29:23 +00:00
secrets.ts fix: hand the age identity to age on stdin — fd paths resolve only in cast's process 2026-07-13 22:43:00 +00:00
server.ts feat: cast — the Coolify executor, extracted from the infra state repo 2026-07-11 12:25:44 +00:00
smoke.ts fix: never write an env var whose name Coolify injects itself (#50) 2026-07-14 22:29:23 +00:00
team.ts feat: assert the token's team before touching Coolify (fail-closed) 2026-07-12 20:55:04 +00:00