Repository navigation
feat!: a deploy reads its configuration once, and Composer redacts every secret the graph names - #419
Conversation
…e change Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…ine transform, not a relay Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
….10.0 API Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…s, names secretsKey, refuses a name read as both, and redacts through the engine's output transform Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…tion and the app secrets Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
… the second process Loads with it Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
… adapter Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…and the second process's output Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…ors, the composer section and dev secrets; the app configuration and the app secrets Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…nown secrets Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…re's environment invariant still holds Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…ration file and the app secrets as prisma dev does Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…and the host release step 5 needs Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…iguration file and the app secrets as prisma dev hands them over Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…eld or a plain value Load refuses a root input field the body passes on when an envSecret() in the graph reads the same name. Before it writes the configuration file, the CLI refuses when any text in it equals a value under a secrets key or a value a written secrets list returned (CONFIG.SECRET_AS_PLAIN_VALUE). Both name the field, never the value. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…'s cause chain, and uses one set for every output One SecretMatcher, built once per set, finds the longest secret at each position and merges overlapping spans, in whole-text and stream redaction alike. The stream redactor holds back a match a later secret could extend. Its cost no longer grows with the number of secrets times their length squared. The redaction helpers move from deployment-summary.ts to redaction.ts. KnownSecrets starts with the credential-looking values of the process environment, so errors, events and the second process's output hide the same set. redactThrown also redacts the cause chain and an AggregateError's errors, and executeDev calls it. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
It no longer leaves out PRISMA_COMPOSER_ variables. The second process runs no adapter, so it never reads them back. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…dlers A host whose engine has no spawnOptionNames runs createDeployCommand and createDevCommand. Each refuses with DEPS.CLI_ENGINE_INCOMPATIBLE before it spawns or calls an operation. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…, which runs prisma deploy The old hint ran alchemy deploy directly, without Composer's first process. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…le, and names say what each piece holds The configuration file is now the app configuration file (PRISMA_COMPOSER_APP_CONFIGURATION_FILE, @prisma/composer/internal/app-configuration-file). It holds what Load read and the node addresses of the graph the first process built, so PRISMA_COMPOSER_NODE_ADDRESSES is gone. Under prisma dev the second process also gets the named secrets, in PRISMA_COMPOSER_NAMED_SECRETS. Names: CheckedConfiguration is AppConfigurationRead, with rootInput (RootInputField) and configurationKeyText; Graph.appConfigurationRead; lower()'s firstProcessNodeAddresses; readAppConfigurationFile and namedSecretsFromEnvironment. The local target gets configurationKeyText and namedSecrets (NamedSecrets), not the whole app configuration and app secrets. LoadOptions is a union, so the compiler refuses configuration with appConfigurationRead, and a source origin carries a configurationKey or a secretsKey by its sourceKind. One function, prepareSecondProcess, writes and removes the file, builds the environment, chooses the named secrets and refuses a secret in the file. Deploy, destroy, dev and the integration tests call it; it is the one handover export on @prisma/composer-cli/testing. nodeAddresses and secretsKeys move from @prisma/composer to @prisma/composer/deploy. Each deploy, destroy and dev run starts by deleting app configuration files earlier runs left, when their process is no longer running. The stack-import fixture's adapters throw, so the stack file tests prove the second process runs none. A deploy with the default lists and a reporter shows that a secret known only through an envSecret() name reaches no file, report, reporter, error or output. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…ixes in the decision records The glossary gains "The app configuration file" and defines the secrets key and the named secrets. ADR-0062, ADR-0049, ADR-0058, ADR-0032, the domain docs and the project design describe one app configuration file plus, under prisma dev, one variable for the named secrets; the two new refusals; the start-of-run cleanup; and environmentVariables() returning the whole process environment. ADR-0029 and ADR-0058 are renamed after their current titles, and every link follows. ADR-0061 names configurationKey and secretsKey. "Read once" now says once per deploy, and at start and at each rebuild under prisma dev. ADR-0044 states its one exception, redaction, in its Decision section. CONFIG.SECRET_AS_PLAIN_VALUE is in the failure table. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…nd two items are deferred Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
LoadOptions is a union, so TypeScript refuses both. A JavaScript caller, or one that casts, still could pass both and silently get appConfigurationRead. Load now refuses that, as it did before the union. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…a plain value, and no app configuration file outlives its run redactThrown never throws. It sets each property with Object.defineProperty, which works on a DOMException, and replaces an error it cannot change, such as a frozen one, with a new error that carries the redacted message, stack and cause chain. The caller rethrows what it returns. The check before the app configuration file is written refuses a known secret anywhere in a plain value, not only a value equal to one, such as the password in a URL or "Bearer <secret>" from a schema transform. Numbers and booleans are compared as text. The message names the field, never the value. writeFile removes the file when the process exits, and removeFile stops that. A later run deletes a leftover file only when its process is not running and the file is older than ten minutes, so a run in a container that shares the folder keeps its file. The names say which of the two kinds of value each piece holds. SecretRedaction is what one command hides: its known secrets, which Composer knows are secrets, and credential-looking values, which only look like secrets by their variable name. KnownSecrets holds the known secrets, and prepareSecondProcess takes one as knownSecrets. credentialNamedValues replaces secretNamedValues and credentialNamedValuesIn, and redactCredentialLooking replaces redactSecrets. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…d options the code has ADR-0058 says the adapters run once per deploy, and at start and each rebuild under prisma dev. ADR-0062, ADR-0044, ADR-0061, deploy-cli.md and the guide say the check refuses a known secret anywhere in a value, and that the CLI deletes the app configuration file at exit and deletes a leftover only when it is ten minutes old. ADR-0062 records two known limits: destroy cannot check against values only a secrets list returns, and the stream redactor costs the square of the longest secret. core-model.md shows CommonLoadOptions, FirstProcessLoadOptions and SecondProcessLoadOptions. The glossary gives the configuration key, the secrets key, the named secrets and the known secrets their own entries. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…pass Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…Thrown never throws A thrown string or plain object now becomes an Error whose message is its redacted text, before the engine prints it or deploy sends it to the reporters. redactThrown survives a message that is not text, getters that throw, and a revoked proxy, and falls back to a new Error with redacted text. The credential-looking heuristic has one name: credentialLookingValues and addCredentialLookingValues. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
redactThrown takes any thrown value and never throws, the heuristic has one name, and the ConfigPools row no longer lists LocalTargetProvidersInput. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
… in the root's input Settled with Will on 2026-11-05. A value the platform owns is a resource node whose value is a pointer; its parse is a Standard JSON Schema that travels with the subscription. The root's secret() fields are carried secrets read from the app secrets, and only those values are known secrets. The deploy no longer fills platform variables. S15 to S17 fold into S18. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
… dropped, seeding is shelved Will's rulings of 2026-11-05: the platform validates nothing, Composer validates a platform variable's value against its JSON Schema at boot and in the local target; the first step stops stripping a service's environment on Prisma Cloud; prisma/prisma-cli#383 is dropped because the Alchemy integration moves to the programmatic API; seeding is shelved. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…econd process keeps the terminal A value in the app secrets is a known secret only when an envSecret() in the graph names it. Before, every value a written secrets list returned counted, so the recommended secrets: [dotenv(), environmentVariables()] refused every root input field before the app configuration file was written and blanked every 1 and true in the output (review finding F16). The engine-based redaction of the second process's output goes with prisma/prisma-cli#383, which Will dropped: Composer will run Alchemy through its programmatic API soon, which removes the second process. The CLI's second process is started as before, with the terminal. DEPS.CLI_ENGINE_INCOMPATIBLE goes with it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…value only for secrets of 8 characters or more Round 4 review fixes (F19 to F25, D24 to D32). A short secret such as dev no longer refuses EMAIL_FROM=dev@example.com. The records state the limits that remain: nothing is redacted by the known secrets before Load has named them, destroy cannot refuse a value only the secrets list returns, and the CLI's second process prints Alchemy's output unredacted while a programmatic host's spawnAlchemy redacts it. The stale statements about the dropped engine change and the old known-secrets rule are gone from the skill, the guides and the records. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…the current design Six steps run on 2026-11-05: the two-list setup deploys with plain values in the configuration file and none left behind, a five-character secret inside a plain value is not refused, an adapter failure prints the credential-looking value redacted, a graph that differs in the second process is refused, prisma dev keeps the secret out of project files, and destroy runs no secrets adapter. One expectation was wrong: step labels and the Deployed line carry node addresses and are not redacted, now recorded as a known limit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…limit Found in the 2026-11-05 manual QA: the command builds them from node addresses and the app name, which the author wrote, so they do not pass through the redactor. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
|
✅ Gizmo reviewed d412ca8 — posted 1 inline comment(s) this pass. Open findings: 🟡 1 minor Change walkthroughThis delta makes the app-configuration-file tests tolerate Windows, where Node cannot observe POSIX permission bits: the The pull request's second-process handover writes the deploy's configuration record with
The behavior being tested is unchanged — this is a test-only delta, and no production code moved. The file's owner-only guarantee remains asserted on macOS and Linux, which matches where the guarantee is observable; on Windows the file's protection rests on default ACLs, which the mode API cannot express. |
commit: |
…s have POSIX modes Windows reports no owner-only mode, so the four 0600 assertions failed there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
| knownSecrets: new KnownSecrets(), | ||
| }).writeFile(); | ||
|
|
||
| if (process.platform !== 'win32') expect(fs.statSync(file).mode & 0o777).toBe(0o600); |
There was a problem hiding this comment.
🟡 Minor · consistency — Inline if (process.platform !== 'win32') guard around expect contradicts the package's test.skipIf idiom and silently drops the mode assertion on Windows
Every other platform-conditional test in this package uses test.skipIf(process.platform === 'win32')(...) or describe.skipIf(...) (e.g. control.test.ts:40, operations.test.ts:1795, run-alchemy.test.ts:413), which shows up as a skip in test output. An inline if around expect instead reports the test as passing on Windows with the owner-only-mode guarantee unverified, and both affected test titles ('…readable only by its owner', 'a file already there is replaced, and gets mode 0600') still state the 0600 claim unconditionally. Same guard pattern repeated at line 123 and echoed by the 'mode 0600' title in operations.test.ts:3962.
Recommended fix
Extract the mode check into its own test.skipIf(process.platform === 'win32')('the file is readable only by its owner', ...) matching the package idiom, or qualify the titles (e.g. 'gets mode 0600 (POSIX)') so the skipped coverage is visible in test names.
…cts checked, and the root is a type of its own The six open questions about Prisma Cloud are answered against the live API and recorded as facts in design section 3: the platform refuses empty values and non-upper-case keys, resolves a name at two scopes today (project class and branch), has no system-managed rows in a fresh project, sets a preview-wide row with `prisma project env add --role preview`, and reports a deployment whose process exits at boot as running and promotes it. The plan is written again from `main` after #419 with four numbered slices and plain titles, no letter codes. The slice 1 spec makes the app root a type of its own through `isRoot()` with literal return types, accepts a `module()` root silently, and rewrites ADR-0003, ADR-0014 and the glossary in place as current statements. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
* docs(drive): platform variables are their own project, and module-input S18 keeps the root's input Platform variables leave the module-input project. The new project records the model Will settled on 2026-11-06: a platform variable is a named request for a value the operator sets; only the root, written with appRoot(), may provision one; variable() and variable.secret() replace envSecret(); schema replaces parse; the deploy seeds nothing and refuses only a required variable with no row. No platform change is needed. Three reviews of the first draft and their decisions are applied. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(drive): platform-variables slices are S1 to S3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(drive): platform-variables S1 spec, and module() entries are deprecated rather than refused S1 ships appRoot() alone. An entry built with module() keeps deploying with a warning, because module() roots exist in create-prisma, prisma-examples, prisma/studio and prisma/web; removing it is deferred until they move. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(drive): platform-variables plan rewritten from main, platform facts checked, and the root is a type of its own The six open questions about Prisma Cloud are answered against the live API and recorded as facts in design section 3: the platform refuses empty values and non-upper-case keys, resolves a name at two scopes today (project class and branch), has no system-managed rows in a fresh project, sets a preview-wide row with `prisma project env add --role preview`, and reports a deployment whose process exits at boot as running and promotes it. The plan is written again from `main` after #419 with four numbered slices and plain titles, no letter codes. The slice 1 spec makes the app root a type of its own through `isRoot()` with literal return types, accepts a `module()` root silently, and rewrites ADR-0003, ADR-0014 and the glossary in place as current statements. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * feat(core): the app's root is written with appRoot(), and an app root is a type of its own `appRoot(name, { input? }, body)` builds the root. It takes what `module()` takes minus `deps` and `expose`. Its result is an `AppRootNode`, whose `isRoot()` answers `true` where a `ModuleNode`'s answers `false`; the literal return types make the two unassignable, so `provision()` refuses an app root in every overload and through any wrapper typed `ModuleNode`. One internal builder serves both constructors. Load, lowering and the CLI's entry accept either kind of node, and Load refuses an app root that reaches `provision()` through a cast. The entry's service check moves before Load, so every command gives the same answer. An entry built with `module()` is accepted as it is. Every root in the repository, the guides, the skill and the decision records' samples is written with `appRoot()`. ADR-0003, ADR-0014, ADR-0016, ADR-0044, ADR-0061 and the glossary are rewritten in place as current statements, and the glossary gains an "App root" entry. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * fix(core): slice 1 review findings Load's internal root flag is `isLoadRoot`, so it cannot be read as the node's `isRoot()`. Constructors check the name, then the body, then `deps` and `expose`. The glossary separates the root as a position from the app root as a type, ADR-0014 states the root as an exception to "compose, don't special-case" and why, and the spec's file list matches what moved. One operations test keeps a `module()` entry through the deploy pipeline. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * fix(core): review findings from CodeRabbit on slice 1 Load's diagnostics name appRoot() beside service() and module(). The appRoot() options overload refuses deps and expose on a non-literal options object too, with type tests. Design notes: the decoder refuses an unsafe integer by its parsed value, preflight keeps the updatedAt of the row the platform resolves and validates the whole input before blaming a variable, and the release note for slice 2 says to rename a lower-case name that only ever worked in prisma dev. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(drive): a service scope also touches prisma dev, and a leading-underscore name may be deployed Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> --------- Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
prisma deployruns in two processes. The first is the Prisma CLI: it reads the app's configuration, checks it, and runs preflight. The second runs Alchemy, which creates the resources. Before this PR, the second process ran theconfigadapters again and had to get the same answer. After it, the configuration is read once, the second process receives what the first one read in a file, and a secret's value never goes into that file.The decision
configandsecretsadapters run there and nowhere else. The second process receives exactly what Load read, in a file only the user can read, and that file is deleted when the command ends.envSecret()in the graph reads.Background: two processes, two reads
A Composer app lists its configuration sources in
prisma.config.ts.config:lists the adapters for plain values;secrets:lists the adapters for secrets.dotenv()reads.envfiles andenvironmentVariables()reads the shell. What theconfigadapters return, merged, is the app configuration. What thesecretsadapters return is the app secrets. These names replace "config pool" and "secrets pool" everywhere.Before this PR, both processes ran the
configadapters. To make the second run return what the first did,environmentVariables()passed the first run's values to the second process in an environment variable,PRISMA_COMPOSER_PARENT_VALUES. Anything else, such as a.env.stagingedited during the deploy, could still make the two runs differ. The deploy would then create resources from values nobody had checked.How the second process gets its configuration now
The first process runs the
secretsadapters, then theconfigadapters.It Loads the graph. The root module's input schema checks the app configuration. Load records what it read: each root input field the root's body passes on, with its checked value, and the text of each name an
envParam()reads.It writes that record and the graph's node addresses to
.prisma-composer/app-configuration-<pid>-<uuid>.json, readable only by the user:{"appConfigurationRead":{"rootInput":[{"path":["EMAIL_FROM"],"value":"shop@example.com"}],"configurationKeyText":{}},"nodeAddresses":["shop","shop.email"]}It starts the second process with
PRISMA_COMPOSER_APP_CONFIGURATION_FILEnaming the file. The second process runs no adapter. It Loads the graph from the record without running the root's schema again.The second process refuses to deploy a graph whose node addresses differ from the file's. That happens when a module body reads
process.env, a file or the clock, and builds a different graph the second time.The first process deletes the file when the command ends, whether it succeeds or fails, and when the process exits. A later run deletes a file left by a process that was killed.
prisma devworks the same way. Its second process also needs the secrets the local services use; it receives them in memory, inPRISMA_COMPOSER_NAMED_SECRETS, never in a file.destroyreads only theconfiglist.Which values are secrets
Load knows the name each
envSecret()reads. The value under each of those names, in the app configuration or the app secrets, is a known secret. No other value is. Withsecrets: [dotenv(), environmentVariables()], a plain.envvalue or the shell'sSHLVL=1is not a known secret, so it is neither redacted nor refused.Two checks keep secrets out of the file:
envParam()andenvSecret(), and a root input field passed on whose name anenvSecret()reads. These fail withCOMPOSE.GRAPH_INVALID.CONFIG.SECRET_AS_PLAIN_VALUE. A secret of 8 characters or more is refused anywhere inside a value, such as a password inside a database URL. A shorter one is refused only when it is the whole value, so a secretdevdoes not refusedev@example.com.Both errors name the field and the secret's name, never its value.
What Composer redacts, and what it does not yet
Composer replaces every known secret with
[redacted], at any length, in: every error a command returns, thrown defects and their causes,prisma dev's events, the run report, the deploy reporter and the deployment summary. The redaction that shipped before this PR still applies on top: values of 8 characters or more under credential-looking variable names, and token shapes.Not redacted, recorded as known limits:
spawnAlchemyalready gets redacted output.Deployed <app>line. They carry node addresses and the app name, which are not redacted.destroy. It reads no secrets, so it cannot refuse a value that only thesecretslist returns.S11:
dotenv()reads throughdotenv-flowdotenv()now reads its files throughdotenv-flow4.1.0, in the same order as before:.env,.env.local,.env.<environment>,.env.<environment>.local. It never reads.env.defaults. It now skips.env.localfor the environmenttest, asdotenv-flowdoes.Breaking changes
Renames.
LoadOptions.configPoolisconfiguration.paramSource()'spoolKeyisconfigurationKey. The typesConfigPool,SecretsPoolandConfigPoolsareAppConfigurationandAppSecrets.LoadOptionsis now a union ofFirstProcessLoadOptionsandSecondProcessLoadOptions, sointerface X extends LoadOptionsstops compiling; extend one of the two.Extension authors. A preflight step and a local attachment's
servestep receiveconfigurationandsecretsin place ofpools.localTargetProviders()takesconfigurationKeyTextandnamedSecrets.@prisma/composer/internal/poolsis gone; it was not public API.import 'dotenv/config'. Ifprisma.config.tshas it and you adoptdotenv(), remove it. Otherwise.env's values enter the process environment,environmentVariables()returns them last, and they override.env.<environment>.dotenv()skips.env.localfor the environmenttest.A module body that builds a different graph in the second process fails the deploy, naming the addresses.
A root input field passed on must hold JSON data: text, a finite number, a boolean, null, or arrays and plain objects of these. A
Datefrom a transform fails the Load.A name read by both
envParam()andenvSecret()fails the Load. So does a root input field passed on whose name anenvSecret()reads.The file check,
CONFIG.SECRET_AS_PLAIN_VALUE, as above.A non-text
envParam()value in a service's input or params fails at Load, before anything is created. It used to fail later.Adapter order. The
secretsadapters run before theconfigadapters, so a brokensecretsadapter is reported first.Redaction of the lists. Only values under names an
envSecret()reads are known secrets. Before, every value a writtensecretslist returned was redacted. A known secret is redacted at any length: a one-character secret blanks that character in the output.environmentVariables()returns the whole process environment,PRISMA_COMPOSER_variables included.Versions.
@prisma/composerand@prisma/composer-climust be the same version: the generated stack file imports@prisma/composer/internal/app-configuration-file.Testing
prisma devhands its second process.test/integration/test/local-dev.integration.tsandlocal-dev-store.integration.tsdriveprisma dev's second process with the file and the named secrets..drive/projects/module-input/slices/s11-s13-configuration-read-once/qa.md.Documentation
composerconfig section (ADR-0049), dev secrets (ADR-0058) and environment values (ADR-0062) are each rewritten as one current record.deploy-cli.md,config-params.md,local-dev.md, the building-an-app, running-locally and deploying guides, and the core-concepts agent skill match the code.Alternatives considered
.envfile edited mid-deploy, or an adapter that reads a remote store, could deploy values nobody checked.configlist, into a file. The record holds only what Load read.secretslist as a secret. With the recommendedsecrets: [dotenv(), environmentVariables()], every shell variable would be a secret, includingSHLVL=1. Redacting1everywhere makes output unreadable, and the file check would refuse ordinary values.Spec:
.drive/projects/module-input/slices/s11-s13-configuration-read-once/spec.md. Design:.drive/projects/module-input/design.md, sections 8 and 9.Agent: hipparchus-99
🤖 Generated with Claude Code