feat(auth): migrate to the ADR-0042 input model — a generated value is a config param, not a secret#173
Conversation
|
Important Review skippedToo many files! This PR contains 154 files, which is 54 over the limit of 100. To get a review, narrow the scope: Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (154)
You can disable this status message by setting the ✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
commit: |
ed65be9 to
73457d2
Compare
…branch Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
… deploy walk
Adds the target-generated param mechanism on the ADR-0041 input walk:
- generatedParam() param source (param.ts): branded GeneratedParamPayload,
isGeneratedParamSource, bytes 16..1024 (default 32), redacted (default true).
- GeneratedParam Alchemy resource (generated-param-resource.ts): Web-Crypto
generate, reconcile keeps persisted output stable across redeploys; provider
registered in control/extension.ts.
- Serializer walk: recognizes a generated leaf (before the param/secret
branches), emits a {"$generated":VAR} pointer (VAR =
generatedParamVarName(address,path) = COMPOSER_<addr>_<KEY>_GENERATED), sentinel
is SecretBox("") when redacted else "", records a ResolvedInputBinding.generated
list, extends the $secret escaping table to cover $generated, and boots by
hydrating $generated from process.env[VAR].
- General collision guard (assertDistinctPointerNames): all $secret AND
$generated var names must be distinct after uppercasing; duplicate names deploy-error.
- Descriptor: provisions a GeneratedParam + EnvironmentVariable per generated leaf.
- Preflight: collectSecretLeafNames skips generated leaves (nothing to seed).
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…k, and collision guard generated-param.test.ts: reconcile generate/stability (bytes-length, redeploy no-op, bytes-change keeps value, randomness), construction validation (16..1024, defaults), isGeneratedParamSource discrimination (env/secret NOT generated, raw paramSource NOT). input-serializer.test.ts: $generated pointer + sentinel-by-redaction + generated list, address/path-scoped var name, boot round-trip + missing-var error, $generated escaping, and the pointer-name collision guard (secret/secret, generated/generated, secret-vs-generated, case-collision). preflight.test.ts: a generated leaf makes zero platform calls. control-lowering.test.ts: serialize yields a GeneratedParam resource + env row per leaf. invariants.test.ts: serializer.ts process.env count 11 -> 12 (boot generated-pointer lookup). Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Boot is schema-blind (a Standard Schema exposes only validate, never its field
types), so the redacted facet now rides the pointer: the document leaf is
{"$generated":"<VAR>","redacted":<bool>}. hydrateInputDocument boxes iff the
pointers redacted is true, plain string otherwise — replacing D1s uniform
boxing, which would have failed a non-redacted leaf paired with a plain string
schema field. isGeneratedPointer recognizes the 2-key shape; substitution emits
the facet from the leaf. Adds the redacted:false -> plain-string boot round-trip
test that uniform boxing would have broken.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…point: typecheck-green) Halfway checkpoint per the dispatch. Brings the auth project (auth package, auth example, service-rpc composeServiceFetch, PN pack-requirement machinery) from s2-rebase onto #161+D1 and migrates it to the input model: - §6.1-6.3 auth-service/module/entrypoint: one input document (baseUrl + secretString), secret bound to generatedParam(), service.input() + service.port() + secret.expose(). - §6.6 example servers -> service.port(); §6.7 module.test.ts -> graph.inputBindings. - Shared target files kept at #161+D1 shape (secret/serializer/preflight/compute); PN machinery (prisma-next/descriptors/exports/preflight) 3-way reconciled to keep #161 ADR-0040 binding + auth PN renames + runPackPreflight. - §6.11 runPackPreflight inert head-comparison branch DELETED (pack-not-listed kept). - @prisma-next 0.15.0 -> 0.16.0 across auth pkg + example to match #161 (emit format stable, no contract regen). Full graph typecheck 74/74. Tests + §6.11 survival checks + lint/build validation land in the follow-up commit. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…ted per §6.11) Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…Secret the only secret; retire "minted" Glossary + config-params source model; rewrite ADR-0029 (a secret is environment-sourced, never stored) and ADR-0030 (a service key is a generated param, not a secret); ADR-0042 gains a Generated sources section. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…ram-roll gap note Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…hand-declared ApiDeps/OpsDeps
createApiApp/createOpsApp now type their deps parameter as ReturnType<typeof {api,ops}Service.load> — the HydratedDeps the framework already infers from compute({ deps }). The hand-declared ApiDeps/OpsDeps interfaces duplicated that inferred shape, so they are removed (addresses the review comments on api/app.ts and ops/app.ts).
The local integration test now drives the services through the framework testing seam: bootstrapService boots each real built entry (dist/{api,ops}/server.mjs) with its inputs pointed at the running startLocalAuthServer/startLocalEmailServer, mirroring examples/cron and examples/storefront-auth, instead of hand-building a deps object and calling the factories directly.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
… Node/undici Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
73457d2 to
7178b05
Compare
…oviders The ADR-0042 migration added generatedParam() and registered its GeneratedParamProvider in the deploy provider set (control/extension.ts) but not in the near-mirror local-target set (local-target/descriptor.ts), which predates generatedParam. Result: prisma-composer dev fails at converge for any module using generatedParam() (the auth module binds its instance secret to it) with "Provider not found for PrismaCloud.GeneratedParam". Register the existing pure provider in the local-target Layer.mergeAll, mirroring the deploy registration. The provider mints random bytes via Web Crypto with reconcile-keeps-output and makes no cloud calls, so it behaves identically under local dev. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Wiring the auth module onto the new input model looks like this — one schema, one binding, and the instance signing value the framework generates for you:
This PR migrates the auth module onto ADR-0042's single
inputschema and, in doing so, fixes a vocabulary mistake the earlier auth work shipped: it called the generated signing value amintedSecret. It is not a secret.Stacked on #161 (ADR-0042). Supersedes #163 and #165 — the same S1+S2 auth module, re-applied on the new model. Base retargets to
mainwhen #161 merges.The decision: a generated value is a config param, not a secret
The framework has exactly one secret concept:
envSecret— a value the deployment reads from the environment by name, whose contents never enter deploy state. That never-in-state property is only possible because an external holder (the operator, the platform) holds the value and the framework merely references it.A value the framework generates at deploy — an rpc service key, the auth instance signing value — has no external holder. It must be stored to be stable across redeploys. So it is a config param the system produces and owns, and it lives in deploy state by design. It is "secret" only in the everyday sense that knowing it lets you participate; in our model that is config, not a secret. Redaction (don't print it) is an orthogonal display facet, not what makes a value a secret.
So there is no "minted secret." The concept is a generated param, and "minted" — a coining metaphor — is retired in favour of "generated". The settled model is one param bound to a source, with four sources:
literal,envParam,envSecret(the only secret), andgeneratedParam.What's in the PR
generatedParam()(target/src/param.ts, besideenvParam— it is a param source, not a secret): the target generates the value at deploy from the wiring info and keeps it stable across redeploys.$generatedpointer carries its redaction facet —{ "$generated": VAR, "redacted": <bool> }. A generated param may be redacted or plain, and boot cannot ask an opaque Standard Schema which; so the deploy, which knows, writes the facet into the self-describing document and boot reads it from the pointer. The value never appears in the document — only the pointer — so the deploy report stays printable.inputschema; the module bindsgeneratedParam()for the signing value; the entrypoint readsservice.input()+service.port(). Everything the earlier S1/S2 work delivered is re-applied on the new base — the PN extension-pack machinery,composeServiceFetch, theensureLocalAuthSchemadbInit local bootstrap, Hono example apps, the email flows, the bundling fixes.$secretand$generatednames together (also closing the pre-existing case-collision gap a prior review flagged).config-params.mdstate the source model; ADR-0029 is rewritten (a secret is environment-sourced and never stored), ADR-0030 names the service key a generated param, and ADR-0042 gains a § Generated sources.Alternatives considered
inputleaf with a minting carve-out. Rejected: it forces the never-mints-a-resource invariant of ADR-0042's binding walk to grow an exception at every point that funnels secrets through one check — reintroducing exactly the thing the model removed, to house a value that isn't a secret.Verification
Typecheck 74/74, target 327 tests, auth 104 (incl. integration), email untouched at 96, examples/auth 2, lint/depcruise/casts clean; a fresh build confirms the deployed entrypoint carries no Prisma Next control client and the testing bundle is self-contained; zero
minted-family survivors in shipped source. Opus review: SATISFIED, zero findings.Deployed smoke: passed 13/13 on real Prisma Cloud, teardown clean (48/48 deleted, project removed, nothing left). The deploy report shows the mechanism working end to end: the
GeneratedParamresource created, its value written toCOMPOSER_AUTH_SERVICE_SECRET_GENERATED, the input document carrying{"$generated":"…","redacted":true}, and — on a second pass that correctedAUTH_BASE_URL— the auth service rolling while the generated secret no-op'd (reconcile-keeps-output, so the signing value stays stable across redeploys). The six JWT/session steps confirm Better Auth signs and verifies with the generated value.🤖 Generated with Claude Code