Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
6978f42
docs(drive): module-input S11 to S13 slice spec, before the cli-engin…
wmadden-electric Nov 2, 2026
177581d
docs(drive): module-input S11 to S13 spec redacts through the cli-eng…
wmadden-electric Nov 2, 2026
75a9043
docs(drive): module-input S11 to S13 spec pins the final cli-engine 0…
wmadden-electric Nov 2, 2026
a556840
docs(drive): the module-input design says dotenv() drops .env.default…
wmadden-electric Nov 2, 2026
325c456
feat(cli)!: dotenv() reads its files through dotenv-flow
wmadden-electric Nov 2, 2026
35b7d70
refactor!: the config pool and the secrets pool are the app configura…
wmadden-electric Nov 2, 2026
89d2b24
feat(core): Load records what it read from the app configuration, and…
wmadden-electric Nov 2, 2026
cf0279d
feat(cli)!: the second process reads a configuration file and runs no…
wmadden-electric Nov 2, 2026
af3355b
feat(cli): Composer redacts every secret it knows, in errors, events …
wmadden-electric Nov 2, 2026
c1f8b79
docs(design): one current record for secrets, env-sourced params, err…
wmadden-electric Nov 2, 2026
2afab23
docs: guides and the agent skill read configuration once and redact k…
wmadden-electric Nov 2, 2026
9bb765f
docs(drive): module-input S11 to S13 manual QA
wmadden-electric Nov 2, 2026
f92e063
fix(core): the same-graph messages say the process environment, so co…
wmadden-electric Nov 2, 2026
fbb1aab
test(integration): the local dev proof hands its converge the configu…
wmadden-electric Nov 2, 2026
c2fdacd
docs(drive): module-input S11 to S13 QA records the engine's Bun fix …
wmadden-electric Nov 2, 2026
b60693d
test(integration): the store proof's direct re-converge gets the conf…
wmadden-electric Nov 2, 2026
fd573d0
fix: no secret reaches the configuration file through a root input fi…
wmadden-electric Nov 2, 2026
0c84313
fix(cli): redaction hides overlapping secrets whole, redacts a defect…
wmadden-electric Nov 2, 2026
7cbf5ee
fix(cli): environmentVariables() returns the whole process environment
wmadden-electric Nov 2, 2026
2fbf7f2
test(cli): the engine-version test drives the real deploy and dev han…
wmadden-electric Nov 2, 2026
b6de0b2
chore(scripts): setup-env points at the storefront-auth deploy script…
wmadden-electric Nov 2, 2026
ede7381
refactor(cli)!: the first process hands over one app configuration fi…
wmadden-electric Nov 2, 2026
aabd99e
docs: the app configuration file, the named secrets, and the review f…
wmadden-electric Nov 2, 2026
8be6c18
docs(drive): module-input S11 to S13 spec records the review fixes, a…
wmadden-electric Nov 2, 2026
bfe64eb
fix(core): Load refuses configuration and appConfigurationRead together
wmadden-electric Nov 2, 2026
7c3ff7f
fix(cli): no thrown defect escapes redaction, no secret hides inside …
wmadden-electric Nov 2, 2026
9d3b163
docs: the records name the known secrets, the new limits, and the Loa…
wmadden-electric Nov 2, 2026
e6702ed
docs(drive): the S11 to S13 spec describes the current design in one …
wmadden-electric Nov 2, 2026
10ed017
fix(cli): a thrown value that is not an Error is redacted, and redact…
wmadden-electric Nov 2, 2026
c626504
docs(drive): the S11 to S13 spec records the round 3 review fixes
wmadden-electric Nov 2, 2026
3c48c0b
docs(drive): the design adds platform variables as nodes, and secrets…
wmadden-electric Nov 5, 2026
c604132
docs(drive): Composer validates parse itself, the engine redaction is…
wmadden-electric Nov 5, 2026
014bc40
fix(cli): the known secrets are the values the graph names, and the s…
wmadden-electric Nov 5, 2026
d90192e
fix(cli): the file check names the matched secret and looks inside a …
wmadden-electric Nov 5, 2026
f69fc74
docs(drive): the S11 to S13 manual QA passes against Prisma Cloud on …
wmadden-electric Nov 5, 2026
116306d
docs(drive): step labels and the Deployed line are a known redaction …
wmadden-electric Nov 5, 2026
d412ca8
test(cli): the app configuration file mode is checked only where file…
wmadden-electric Nov 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .drive/projects/module-input/deferred.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@
- Later: a service wrapper generic only in `I`, with no dependencies, that does not widen the node, cannot call `provision(s, { input })`. TypeScript defers the conditional on an unbound `I` and refuses the call. Pre-existing (it failed before S9 as well). Widening the node to `ServiceNode<D, P, E>` works around it, as `module-composition.md` documents. Found in the S10 round-2 review.
- Later: an app that installs a different `@prisma/composer` version from the one `@prisma/composer-prisma-cloud` pins exactly gets a second, nested copy. `SecretBox` and the node brand then split again, and a redacted `generatedParam()` no longer fits a `secretString()` field. Pre-existing and not specific to S10's fix. Found in the S10 round-2 review.
- Later: `@prisma/composer-prisma-cloud`'s published JavaScript still inlines some foundation code: `describeKind` from `@internal/foundation/errors` (no public entry exports it), and `blindCast`, `field` and the secret and pointer helpers inside the `testing` entries and `local-target`, which come from internal packages' prebuilt `dist`. Each reads `Symbol.for` brands or is a plain function, so behavior matches, and no foundation type is inlined in the declarations. Found while fixing D16 of the S10 round-2 review.
- Later: two redaction markers reach the user. `SecretString` and `SecretBox` print `[REDACTED]`; the CLI's redactor prints `[redacted]`. Pick one. Pre-existing; found in the S11 to S13 design review.
- Later: Composer's variables use two prefixes: `COMPOSER_` on the platform variables a deployment sets (ADR-0029), and `PRISMA_COMPOSER_` on the variables Composer's processes read. Pick one, or record why both stay. Pre-existing; found in the S11 to S13 design review.
- Known limit: `destroy` runs only the `config` adapters, not the `secrets` adapters (Will's ruling: an unreachable secrets manager must not block a teardown). So on `destroy`, which reads no secrets, a secret only the `secrets` list returns is not known, and the file check cannot refuse it. Destroy still compares against a value under an `envSecret()` name in the app configuration, and Load still refuses a root field named like an `envSecret()`. A deploy of the same configuration refuses first, so the impact is small. Found in the S11 to S13 round-2 code review (F11); still open after round 4 (F22).
- Known limit: the stream redactor's cost grows with the square of the longest secret's length. Only programmatic hosts use it, through `spawnAlchemy`. It matters only for secrets of many kilobytes, such as a CA bundle in a credential-named variable: a 32,000-character secret costs about 2 seconds per megabyte of output. A trie or rolling hash would make it linear. Found in the S11 to S13 round-2 code review (F12).
- Shelved by Will, 2026-11-05: a command that seeds platform values from the app configuration and the app secrets (`.env`, the shell). From S18, `deploy` never does it; until then deploy preflight fills a missing `envSecret()` variable from the app secrets.
- Deferred to the in-process Alchemy work: redacting the second process's (Alchemy's) output. Will dropped the engine change it needed (`@prisma/cli-engine` piping a child's output, prisma/prisma-cli#383) on 2026-11-05, because the Alchemy integration moves to the programmatic API soon. That work must redact Alchemy's output itself: in Composer's own process, Alchemy writes to Composer's output, and nothing redacts it unless the integration routes it through the command's `SecretRedaction`. Until then Alchemy's output prints unredacted under the CLI, as before S13. A programmatic host's `spawnAlchemy` does redact it.
- Known limit: Nothing is redacted by the known secrets before Load has named them: an adapter that throws, and a root schema failure that echoes a field's value, are redacted only by the credential-looking rule. A fix needs the secret names before Load, which the design does not provide. Found in the S11 to S13 round-4 reviews (F20).
204 changes: 130 additions & 74 deletions .drive/projects/module-input/design.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .drive/projects/module-input/handover.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Pass `model: opus` explicitly on every agent.
- **"App configuration" and "app secrets"** name the merged values of the config adapters and the secrets adapters. "App" is Composer's word for the application, the outermost module. Prisma Cloud's "App" label never constrains Composer's terms. The code still says "config pool" until S12 renames it.
- **`envParam()` stays deploy-time.** In a service's input it is read at deploy and written into the input document. Making it a runtime pointer would break every existing app. It gets deprecated later.
- **`environmentVariables()` returns the whole process environment.** No special cases, no filtering of credentials.
- **Redaction covers every secret value Composer knows, at any length.** Under the default lists that is the values under names an `envSecret()` names. With a project's own secrets list, it is everything that list returns.
- **Redaction covers every secret value Composer knows, at any length.** Those are the values under the names an `envSecret()` in the graph names, whatever the lists. The lists are pools Composer reads only for those names, so a project's own secrets list does not make its other values secrets.
- **Inputs are always truthy `Value` objects.** Will does not want to hear about branching on them.
- **Out of scope by Will's ruling:**
- `dotenv-flow` skipping `.env.local` for `test`;
Expand Down
33 changes: 21 additions & 12 deletions .drive/projects/module-input/plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ A PR delivers one capability a user would notice, so slices whose limits the nex
- **S1** is done: [slices/s1-spike/report.md](slices/s1-spike/report.md).
- **S2** is merged: prisma/composer#401.
- **S3 to S8** are merged as one PR: prisma/composer#403.
- **S9** is built and in review as prisma/composer#412. **S10** is built in the same PR.
- **S11 to S13** are not built. [deferred.md](deferred.md) lists items S10 must cover.
- **S14 to S17** are designed with Will before they are specified.
- **S9 and S10** are built and in review as prisma/composer#412.
- **S11 to S13** are built on the git branch `claude/module-input-s11-s13`, not yet opened as a PR, through four review rounds. The two changes of 2026-11-05 are done and in review: the known secrets are the values the graph names (S13 below), and the engine-based redaction of Alchemy's output is gone, because Will dropped prisma/prisma-cli#383. The PR then waits only for #412 to merge.
- **S18**, platform variables, was designed with Will on 2026-11-05 ([design.md](design.md) sections 5 and 7). It is specified next and built after S11 to S13 merge. It absorbs S15, S16 and S17.
- **S14** is designed with Will before it is specified.

## S1. Spike: an input field as an opaque typed value

Expand All @@ -27,7 +28,7 @@ Both return values, and so do `generatedParam()` and `serviceOrigin()`. Deploy p

## S4. Config adapters and the root's input

The `config` and `secrets` lists on the `composer` section, each defaulting to `[environmentVariables()]`; `dotenv()`; the root declares `input`, bound to the app configuration and checked before anything is created; `envParam()` reads the app configuration; deploy and dev preflight fill `envSecret()` from the app secrets. Built on `claude/module-input-s4`. It names the app configuration and the app secrets "pools" in code, lists `dotenv()`'s four files itself, and has the second process run the adapters again; S11 and S12 change these.
The `config` and `secrets` lists on the `composer` section, each defaulting to `[environmentVariables()]`; `dotenv()`; the root declares `input`, bound to the app configuration and checked before anything is created; `envParam()` reads the app configuration; deploy and dev preflight fill `envSecret()` from the app secrets. Built on `claude/module-input-s4`. It gives the app configuration and the app secrets older names in code, such as `configPool`, lists `dotenv()`'s four files itself, and has the second process run the adapters again; S11 and S12 change these.

## S5. Migrate and remove the old slots

Expand Down Expand Up @@ -59,27 +60,35 @@ The body's `input` carries a brand that every binding type refuses, so passing t

## S12. The second process reads a configuration file

The first process writes a configuration file beside the generated stack file, mode 0600, holding the root's real values at the paths its body names and the values of the names `envParam()` reads. It deletes the file when the deploy ends, whether it succeeds or fails. The deploy's and `destroy`'s second processes read the file and run no adapter, and do not run the root's schema again. The first process passes the list of node addresses it checked, and the second process refuses to deploy a different graph. `prisma dev`'s second process reads the same kind of file, and receives the app secrets its local services need through its environment, never through a file. The "pool" names in code take the names "the app configuration" and "the app secrets", and the glossary's pool and adapter entries are replaced by "the app configuration", "the app secrets", "config adapter" and "secrets adapter".
The first process writes a configuration file beside the generated stack file, mode 0600, holding the root's real values at the paths its body names and the values of the names `envParam()` reads. It deletes the file when the deploy ends, whether it succeeds or fails. The deploy's and `destroy`'s second processes read the file and run no adapter, and do not run the root's schema again. The first process passes the list of node addresses it checked, and the second process refuses to deploy a different graph. `prisma dev`'s second process reads the same kind of file, and receives the app secrets its local services need through its environment, never through a file. The old names in code take the names "the app configuration" and "the app secrets", and the glossary's old entries for the two lists and the adapter are replaced by "the app configuration", "the app secrets", "config adapter" and "secrets adapter".

## S13. Composer redacts every secret it knows

Everywhere Composer prints, it redacts every value under a name an `envSecret()` in the graph names, in the app configuration or the app secrets, and every value a project's own secrets adapters return when it writes a `secrets` list, at any length. That covers errors, adapter failures, the second process's failure cause, and the second process's output, which Composer now reads through a pipe, holding back enough characters to catch a secret split across two chunks. An end-to-end test searches the configuration file, the stack file, the deploy report, the errors and the second process's output for a known secret value. Manual QA of the examples against real targets, including a root `input` from `.env.<environment>` in `prisma deploy` and `prisma dev`.
Everywhere Composer prints, it redacts every known secret, at any length. The known secrets are the values under the names the graph's `envSecret()`s pass, in the app secrets or the app configuration, and from S18 under the root's `secret()` field names too. A value in the lists that nothing names is not a known secret, so the recommended `secrets: [dotenv(), environmentVariables()]` redacts nothing it should not. `prisma dev` compares the whole app secrets between rebuilds to notice a change, which reads them without making them known secrets. (The first build counted every value a written `secrets` list returned; with the recommended setup that refused every root input field and blanked every `1` in the output, review finding F16. Will ruled on 2026-11-05 that the lists hold what Composer may carry, read only at the root's boundary.) That covers errors, adapter failures and the second process's failure cause. The second process's own output is not redacted: that needed an engine change (prisma/prisma-cli#383), dropped because the Alchemy integration moves to the programmatic API soon. That work must redact Alchemy's output itself. An end-to-end test searches the configuration file, the stack file, the deploy report, the errors and the second process's output for a known secret value. Manual QA of the examples against real targets, including a root `input` from `.env.<environment>` in `prisma deploy` and `prisma dev`.

## S14. `prisma dev` watches its inputs

Adapters may report that their values changed, and `dotenv()` watches its files. `prisma dev` applies the new values instead of pausing. Designed and specified after S13.

## S15. A developer's local secret does not reach another environment
## S15, S16 and S17 are absorbed by S18

Design a solution for this case: deploying production from a machine whose `.env.local` holds a test key fills a missing production variable with that key. Designed with Will after S13.
S15 (a developer's local secret reaching another environment) is settled because the deploy no longer fills platform variables. S16 (secrets get a home of their own) is settled because only the values the root names are known secrets. S17 (`secret(schema)`) ships inside S18, because platform secrets and carried secrets both need it.

## S16. Secrets get a home of their own
## S18. Platform variables are nodes, and the root's input may hold a secret

Give secrets a home distinct from plain values, so a `.env` file no longer mixes the two, and redaction stops covering the plain values that share a file with secrets. Designed with Will after S13.
Design: [design.md](design.md) sections 5, 7, 8 and 9, settled 2026-11-05. One PR, after S11 to S13 merge.

## S17. A secret field wraps its schema, as `field()` does
- `@prisma/composer-prisma-cloud` exports `platformVariable({ name, parse? })` and `platformSecret({ name, parse? })`, resources whose `value` is a runtime value. The Prisma Cloud target creates a subscription of each service to each variable its input points at, and sets no value. The deploy report lists the variables with no entry on the platform. The local target fills a platform variable from the app configuration or the app secrets under its `name`, and validates the value against `parse`.
- `parse` takes a Standard JSON Schema; its `jsonSchema.input()` travels in a `$platform` marker in the input document; Load refuses a transforming or unencodable schema, and two nodes for one `name` with different schemas. Boot decodes the text as JSON unless the schema's type is `string`, validates it against the JSON Schema with a validator Composer ships, then hands it to the service's schema. The platform validates nothing.
- `secretString()` becomes `secret(inner)`, in both entry points.
- A root `secret()` field is filled from the app secrets and reaches the second process in `PRISMA_COMPOSER_NAMED_SECRETS`; the deploy sets it on the service as a secret variable. A name in both the app configuration and the app secrets for a root field fails the deploy. `destroy` binds it to a stand-in box.
- Preflight stops filling platform variables on deploy and dev. The Prisma Cloud target stops stripping a service's environment at boot: a service sees the project's variables plus what Composer sets on it directly. The local target keeps giving each service only what its input points at.
- `envSecret()` and `envParam()` are deprecated as shortcuts.
- The slice decides whether the deploy writes the resolved binding instead of the schema's output into the input document, so a transforming `field()` schema works for a literal.
- Decision records rewritten in full: ADR-0029, ADR-0032, ADR-0042, ADR-0058 and ADR-0062. New glossary entries: platform variable, platform secret, subscription, carried secret, known secrets.
- Breaking: `secretString()` removed; preflight no longer fills a variable, so a project that relied on `deploy` seeding `envSecret()` values sets them on the platform instead.

`secretString()` becomes `secret(schema)`, following the `field()` pattern: `secret(type('string'))` for ArkType and `secret(z.string())` for Zod. The wrapped schema checks the secret's content at boot, when the real value exists; at deploy the field accepts a secret pointer or a secret box. The name is settled when the slice is designed. `secretString()` is removed, which is a breaking change for every schema with a secret field. Designed after S16.
Nothing in it waits for the platform. When the platform ships subscriptions, the target records each service's subscriptions and the "not set" report comes from the platform instead of the project's variable names.

## Close-out

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ An `envParam()` field whose variable is unset or empty is a key left out, and wh
- [ADR-0016](ADR-0016-a-module-has-the-same-boundary-as-a-service.md): a module has the same boundary as a service.
- [ADR-0062](ADR-0062-environment-values-come-from-resolve-and-config-adapters.md): values, runtime values and pointers, and the configuration `envParam()` reads.
- [ADR-0021](ADR-0021-params-are-read-through-config-not-load.md), superseded: `config()` is replaced by `service.input()`; dependencies still read through `load()`.
- [ADR-0029](ADR-0029-secrets-are-a-forwardable-slot.md), partly superseded: `envSecret()` survives as a binding leaf; the `secrets()` read and per-slot pointer rows are replaced.
- [ADR-0029](ADR-0029-a-secret-is-an-environment-sourced-value-the-framework-never-stores.md), partly superseded: `envSecret()` survives as a binding leaf; the `secrets()` read and per-slot pointer rows are replaced.
- [ADR-0032](ADR-0032-params-bind-at-provision-env-sourcing-is-a-target-source.md), partly superseded: binding at `provision()` and `envParam()` survive; the `config()` read and per-key pointer rows are replaced.
- [ADR-0031](ADR-0031-provisioned-param-values-are-a-need-resolved-through-a-target-registry.md): Composer's own provider params, still on per-key rows.
- [config-params.md](../10-domains/config-params.md): the sources, and where each is accepted.
Expand Down
Loading
Loading