docs(rpc): document service keys; deny zero-consumer providers; close the project - #100
Merged
Merged
Conversation
…ents RPC bindings became authenticated (ADR-0030/0031) without the guides or the skill saying so — and its two visible consequences read as bugs when they surprise you: a hand-rolled curl of a deployed /rpc/<method> gets 401, and nothing is enforced locally or in tests because no key is provisioned there. - guides/building-an-app: the canonical explanation under Contracts — what is minted, why curl 401s, why local passes through, per-binding isolation, rotation, and that it authorizes at the service level. - guides/getting-started: the tutorial now has the reader curl the quotes service and meet the 401 where they would hit it anyway. - guides/deploying: unwired callers are rejected, and the COMPOSER_* vars are the deploy's to rewrite, not yours to hand-edit. - guides/testing: auth is inert under mockService/bootstrapService. - skills/prisma-composer: the agent-facing rules — do not invent your own service-to-service auth, and do not tell a user to curl an RPC endpoint to check a deploy (per the skill README, a surface change lands in both). - design/10-domains/connection-contracts: the key rides the binding, not the contract — which is why one Contract still serves network, in-memory and mock adapters unchanged. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DoD met: per-binding keys provision at deploy, the wired round trip returns ok, an anonymous caller gets 401, and a redeploy is a no-op with keys stable — proven live against Prisma Cloud and by CI's storefront deploy on every run. Both slices merged (#89, #93). The long-lived output already lives in docs/: ADR-0030 (the per-binding key), ADR-0031 (the opaque need + target provisioner registry), and the RPC auth story in the guides, the skill, and connection-contracts.md. What remains under .drive/projects/rpc-service-key/ is spec/plan/slices — coordination artefacts that exist to run the project, not to be read after it — so they go. Records what we deliberately did not do, in .drive/deferred.md: cross-extension provisioned edges and multi-need connections (both fail closed rather than guess), rotation UX, per-method authz, publishing the provisioning SPI (equally internal to secrets today), and the constraint the mock-bindings slice inherits — an unprovisioned service passes through by design, so design against that distinction rather than relying on it. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes the last place the service key failed open. An RPC provider with zero wired consumers got no accepted-keys var, which serve() read as the pass-through state — so a provider nobody was wired to accepted everyone. The fix needs three states where there were two, because "no keys" is ambiguous: absent var means the deploy never provisioned one (local dev, unit tests, bootstrapService) and must pass through; a provisioned "[]" means deployed with zero peers and must deny. So acceptedKeys() returns undefined vs [], the dispatch check becomes `accepted !== undefined`, and an empty set then denies naturally — nothing matches it. A malformed value now denies too (only the trusted target writes it, so unreadable means something is wrong). The target correspondingly writes the var for any service that exposes something, not only one with inbound edges; a pure consumer still writes none. Local and test behaviour is unchanged — no key is ever provisioned there. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
commit: |
wmadden-electric
enabled auto-merge (squash)
July 16, 2026 10:50
wmadden-electric
disabled auto-merge
July 16, 2026 11:07
"RPC auth is inert here" was a word carried over from internal slice framing, in the bold lead of a bullet people scan. It names nothing a reader knows. Say the fact instead: only a deploy creates keys, so in a test nothing checks and there is nothing to put in inputs. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
wmadden
approved these changes
Jul 16, 2026
Re-read as a user without our context, the service-key section had three problems. It led with the threat model — a security preamble before the thing a reader building an app needs first, which is that they do nothing. Now it opens with that and earns the rest. It was six topics in a pile (mechanism, two gotchas, per-binding isolation, rotation, capability-token philosophy, service-level scope) rather than a narrative. Now it builds: you do nothing -> here is what happens -> the one thing that looks like a bug -> local and tests -> two limits. It explained our reasoning to them. "A capability token, not a secret" exists because of an argument we had about where values live; a reader takes no action on it. Cut. The ADR link moves inline-to-"Digging deeper", the section this guide already keeps for why-the-model-is-shaped-this-way. Also drops our vocabulary: edge (they declared a dependency), mints, wired peers, no-op. And deploying.md no longer repeats the local/test rule that building-an-app and testing.md already make. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
What
Three things that finish the RPC service-key topic: document it for users and agents, close the last fail-open hole, and close the project.
Live-verified by CI:
Deploy, verify, destroydeploysstorefront-authto Prisma Cloud, proves the wired round trip, and tears it down — green on this branch. The code change is additionally unit-tested and mutation-checked; the docs and close-out carry no runtime risk.1. Docs — the surface changed and nothing said so
RPC bindings became authenticated (ADR-0030/ADR-0031) without the guides or the skill mentioning it. Two consequences read as bugs when they surprise you: a hand-rolled
curlof a deployed/rpc/<method>gets401, and nothing is enforced locally or in tests.guides/building-an-app— canonical explanation under Contracts: what's minted, whycurl401s, why local passes through, per-binding isolation, rotation, service-level scope.guides/getting-started— the tutorial now walks the reader into the401where they'd hit it anyway (curling the quotes service), and ties it back to why the local run needed no key.guides/deploying— unwired callers rejected; theCOMPOSER_*vars are the deploy's to rewrite, not yours to hand-edit.guides/testing— auth is inert undermockService/bootstrapService.skills/prisma-composer— the agent-facing rules: don't build your own service-to-service auth, and don't tell a user to curl an RPC endpoint to check a deploy. (Per the skill README, a surface change lands in both the guides and the skill.)design/10-domains/connection-contracts— the design point: the key rides the binding, not the contract, which is why one Contract still serves network, in-memory, and mock adapters unchanged.2. A provider with no wired consumers now denies
The last place this failed open. A provider with zero consumers got no accepted-keys var, which
serve()read as pass-through — so a provider nobody was wired to accepted everyone.It needs three states where there were two, because "no keys" was ambiguous:
bootstrapService"[]"["k1",…]acceptedKeys()returnsundefinedvs[]; the check becomesaccepted !== undefined, so an empty set denies naturally — no special case. Malformed now denies too (only the trusted target writes it). The target correspondingly writes the var for any service that exposes something, not only one with inbound edges; a pure consumer still writes none. Local and test behaviour is unchanged.Both files were mutation-checked: reverting each makes the new tests fail, restoring makes them pass.
3. Project closed
DoD verified (both slices merged — #89, #93 — round trip, 401, and stable no-op redeploy all proven live before the workspace broke). The long-lived output already lives in
docs/; the remainingspec/plan/sliceswere coordination artefacts and are deleted. Deferrals recorded in.drive/deferred.md: cross-extension provisioned edges and multi-need connections (both fail closed rather than guess), rotation UX, per-method authz, publishing the provisioning SPI (equally internal to secrets today), and the constraint the mock-bindings slice inherits.Tests
@internal/rpcserve.test.ts13 pass; targetcontrol-lowering.test.ts27 pass; typecheck +test:typesclean for both;lint:depsclean.Two operational findings (not blocking this PR)
destroyleaves the Project behind.prisma-composer destroytears down a stack's resources, but the Prisma Cloud Project shell survives, and CI's sweeper only matches-ci-<runid>names — so hand-run verification stacks accumulate. Three of mine (storefront-auth-s2v,-s3v,-fc) helped push the dev workspace intoplanLimitReached. I deleted my three and touched nothing else. Worth closing properly: eitherdestroyremoves the project, or the sweeper learns to catch hand-run names.The dev workspace's state database is unhealthy (separate from CI's, which is fine — hence the green deploy checks here). A freshly minted connection to
prisma-composer-state's database times out while the API reports it"ready", so it isn't a stale credential. It blocks localprisma-composer deployfrom that workspace and needs an account-level look. I did not touch that project — it's control-plane, and deleting it would destroy the deploy state for everything live in the workspace.🤖 Generated with Claude Code