feat: environment-sourced config params — envParam()#94
Open
wmadden-electric wants to merge 4 commits into
Open
feat: environment-sourced config params — envParam()#94wmadden-electric wants to merge 4 commits into
wmadden-electric wants to merge 4 commits into
Conversation
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>
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-timedefault. 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 throughconfig(), 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_ORIGINfor 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
@prisma/composer):ProvisionArgsgains optionalparams— per param, a schema-typed literal or an opaqueParamSource. Binding beatsdefault; 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()). NewparamManifest(graph)seam for targets.@prisma/composer-prisma-cloud):envParam("NAME")(validation parity withenvSecret). 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/design/10-domains/config-params.md§ "Binding a param at provision".examples/env-param— deploy/smoke/destroy scripts in the shape ofexamples/streams.Live proof (run against real Prisma Cloud, then destroyed)
- ENV_PARAM_GREETING (required by service "echo").{"greeting":"hello-from-production"}viaconfig().--stage env-param-proofsimultaneously served"hello-from-preview"from its preview-scoped var while production kept its own value.Follow-up (separate PR)
Add
examples/streamsandexamples/env-paramto the e2e-deploy CI workflow — they share the smoke-script shape; neither is wired in today.🤖 Generated with Claude Code