Skip to content

feat: environment-sourced config params — envParam()#94

Open
wmadden-electric wants to merge 4 commits into
mainfrom
claude/env-param-config
Open

feat: environment-sourced config params — envParam()#94
wmadden-electric wants to merge 4 commits into
mainfrom
claude/env-param-config

Conversation

@wmadden-electric

Copy link
Copy Markdown
Contributor

Params can now be bound at provision time — to a literal value or to a platform environment variable via envParam("NAME") — instead of only carrying an authoring-time default. Env-sourced params are the non-secret sibling of ADR-0029 secrets: the same need/source split, the same per-stage platform variable and preflight, but read through config(), schema-validated, and never redacted.

Why

An app needs plain, per-stage, non-secret configuration — the motivating case is a service that must know its own public origin (APP_ORIGIN for auth trusted-origins and payment redirect URLs), which differs between production and every preview stage and is only known after first deploy. Until now the only per-stage, post-deploy-settable channel was a secret slot, which forces config through redaction machinery it does not want. Provision-time literals also fix a smaller gap: params were previously default-only.

What

  • Core (@prisma/composer): ProvisionArgs gains optional params — per param, a schema-typed literal or an opaque ParamSource. Binding beats default; defaults remain the fallback; a param that is neither optional, defaulted, nor bound now fails loudly at lowering instead of silently vanishing from config. Module boundaries forward param bindings on the same rail as secrets (paramNeed()). New paramManifest(graph) seam for targets.
  • Prisma Cloud target (@prisma/composer-prisma-cloud): envParam("NAME") (validation parity with envSecret). An env-sourced param serializes a pointer row (@composer-param-pointer:NAME) — the platform injects the per-stage value; boot resolves pointer → platform var → the param's own schema. Values reach the schema as raw strings (ADR-0031 records this v1 decision). Preflight covers env-sourced param names exactly like secret names: existence check per stage, shell-fill via the write-only path, early failure listing missing names.
  • Docs: ADR-0031 + docs/design/10-domains/config-params.md § "Binding a param at provision".
  • Proof harness: examples/env-param — deploy/smoke/destroy scripts in the shape of examples/streams.

Live proof (run against real Prisma Cloud, then destroyed)

  • Missing platform var + empty shell → deploy fails at preflight: - ENV_PARAM_GREETING (required by service "echo").
  • Production deploy served {"greeting":"hello-from-production"} via config().
  • --stage env-param-proof simultaneously served "hello-from-preview" from its preview-scoped var while production kept its own value.
  • Both stages destroyed; no resources left behind.

Follow-up (separate PR)

Add examples/streams and examples/env-param to the e2e-deploy CI workflow — they share the smoke-script shape; neither is wired in today.

🤖 Generated with Claude Code

Params can now be bound at provision() time, not just carry an
authoring-time default. ProvisionArgs gains an optional params map:
each declared param accepts a schema-validated literal or an opaque
ParamSource, mirroring how ADR-0029 splits a secret into a need and a
source. A literal beats the param's default; a ParamSource flows
through untouched for the target to resolve at boot; a param that is
neither optional, defaulted, nor bound now fails loudly at buildConfig
with a message naming the param, the service, and the fix.

A module boundary can also declare a nameless ParamNeed and forward a
bound ParamSource into a child's real param, the same rail secrets
already ride on (declare, bind, forward, validate-on-Load).

This is core-only groundwork for the env-param slice; envParam() itself,
its wire serialization, and preflight are a separate dispatch that
builds on this seam (paramManifest mirrors provisionManifest).

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Adds the target-side half of env-sourced config params, the non-secret
sibling of ADR-0029's secret rail, on top of D1's core provision-time
param binding (aac23db).

- envParam('NAME') in @prisma/composer-prisma-cloud (param.ts), a
  term-for-term mirror of envSecret: same name validation (rejects
  COMPOSER_-prefixed and poisoned DATABASE_URL(_POOLED) names), same
  brand-and-reject-foreign-source shape via paramName().
- Pointer serialization: a service's own param that buildConfig
  resolved to an opaque ParamSource writes a pointer row (the bound
  platform NAME) instead of a JSON-encoded value. The two are told
  apart on the wire by a value-shape marker (serializer.ts) rather
  than a new channel, since JSON.stringify output can never start
  with the marker's leading character - literal rows are byte-for-byte
  unchanged.
- Runtime read: config() does the two-step lookup (pointer -> platform
  var) then runs the param's own schema on the raw string - no
  SecretBox, no redaction. An unset platform var is a boot error
  naming the param and the platform var; an empty string reaches the
  schema like any other value (passes iff the schema accepts it -
  deliberately unlike a secret's non-empty rule).
- Schema-restriction decision: NOT restricted to string-output schemas
  at authoring/lowering time. The platform var's raw string is simply
  handed to the param's schema; a non-string schema fails naturally at
  boot with the existing "invalid value" error. Simplest defensible
  v1 - no static schema-shape inspection machinery needed.
- Preflight: runPreflight now also walks paramManifest, checking only
  env-sourced bindings (literal-bound params never touch the
  platform), deduped into the same existence-check map as secrets.

Also fixes the D1-review carry-over nit: lowering.test.ts's test
helper default now uses blindCast instead of a bare `as any`.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ple (D3)

ADR-0031 records the env-param slice's decisions: params bind at
provision (literal beats default; unbound-required fails loudly at
lowering); env-sourcing is a target-owned source mirroring ADR-0029's
need/source split; the read stays config(), unredacted; the stored row
discriminates pointer from literal by the @composer-param-pointer:
value prefix (JSON output can never start with @); the platform var's
raw string goes straight to the param's schema (no authoring-time
string-schema restriction - a number() param bound to envParam fails
at boot, deliberately, for v1); empty string is a value, valid iff the
schema accepts it; value changes need a redeploy, like secret
rotation. Index + config-params domain doc updated with a "Binding a
param at provision" section.

examples/env-param is the live proof harness (streams-shape deploy
scripts): one compute service with a required string param bound to
envParam('ENV_PARAM_GREETING'), served back through config(), plus a
Management-API smoke script that resolves the target stage's service
by branch and asserts the greeting. Proven on real Prisma Cloud:
production and a preview stage served different values from their
respective platform-var scopes; a deploy with the var absent from
both platform and shell failed preflight early, naming the var and
the service; both stages destroyed after.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…s restricted

The last Consequences bullet claimed a literal string value starting
with @composer-param-pointer: cannot be stored. Wrong: every
service-owned literal is JSON-encoded, so such a value stores with a
leading quote, never matches the pointer check, and round-trips
intact. The prefix is reserved only in raw stored-row space, which
literals never occupy. (F-D3-1)

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@pkg-pr-new

pkg-pr-new Bot commented Jul 15, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@prisma/composer@94
npm i https://pkg.pr.new/@prisma/composer-prisma-cloud@94

commit: edcdcdb

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant