feat(deploy): report what each node became (ADR-0032)#101
Open
wmadden-electric wants to merge 2 commits into
Open
feat(deploy): report what each node became (ADR-0032)#101wmadden-electric wants to merge 2 commits into
wmadden-electric wants to merge 2 commits into
Conversation
Proposes that `prisma-composer deploy` report a map from each graph node
to the entity it became, keyed by the node address, with each entry
projected by the descriptor that owns the entity.
The map already exists and is discarded: lowering() builds `lowered`
(node id -> deploy result) and then returns a hardcoded `{ outputs: {} }`,
which is what every deploy log prints. The compute descriptor already
computes `url: deployment.deployedUrl` — the one value a deploy most
needs to report — and drops it.
The projection is an allowlist, not a redaction pass, because field names
do not carry sensitivity: `url` is a public endpoint on a compute node and
a Postgres connection string on a prisma-next node, and the S3 credentials
resource keeps its secrets in accessKeyId/secretAccessKey. Core cannot
tell those apart and should not try — LoweredNode.outputs is
extension-defined and opaque to it.
Redacting secrets from log output is recorded as a separate follow-on in
.drive/deferred.md: worth having over the strings we do not author
(progress lines, errors echoing a DSN), but it cannot be what makes the
report safe — some credentials are minted mid-run, and encoded forms
defeat literal matching.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
lowering() already built the map — node id to that node deploy result —
and then returned a hardcoded `{ outputs: {} }`, which is the empty
object every deploy log ends in. The stack outputs are now that report,
so a deploy says where it published instead of saying nothing.
The split: core stamps the identity it owns from the graph (kind,
extension, name); the descriptor that owns the entity adds what only it
knows, through a new optional `report` beside a LoweredNode outputs.
It is an allowlist, not a filter, because no rule available to core is
safe. `url` is a public endpoint on a compute node and a connection
string on a prisma-next one. Node kind does not help either: s3-store is
a SERVICE whose deploy spreads accessKeyId/secretAccessKey into outputs,
and s3-credentials is a resource whose outputs are only a minted key
pair — neither named `url`. So compute reports its id/url/projectId,
the databases report their id and never their connection, s3-credentials
reports `{}` and is listed by identity alone, and s3-store reports
compute report plus the bucket.
Tests: three cases in lowering.test.ts, including one asserting a
secret in a node outputs never reaches the report. Verified they catch
the failure — mutating the collector to spread `outputs` fails two of
them, and reverting passes 37/37.
Descriptors deliberately do not read ctx.node: an earlier cut had each
one taking node.name, which broke 18 existing partial-context fakes.
Core stamping the graph identity removes that coupling, and the whole
prisma-cloud suite (159) passes untouched.
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.
Proposes that
prisma-composer deployreport what it did: a map from each graph node to the entity it became, keyed by the node's address (site→ its compute service + public URL;catalog.database→ its database, with no connection information).ADR + implementation.
lowering()already built the map and returned a hardcoded{ outputs: {} }— the empty object every deploy log ends in. The stack's outputs are now that report.Why this is more than a nicety
The map already exists and we throw it away.
lowering()walks the graph holdinglowered— node id → that node'sdescriptor.deploy(...)result — and ends with a hardcodedreturn { outputs: {} }. The print path exists too: the generated stack's default export islower(app, config, opts), and Alchemy prints a stack's outputs. The{ outputs: {} }at the end of every deploy log is that mechanism working correctly with nothing in it.The interesting value is already computed. The compute descriptor already returns
outputs: { url: deployment.deployedUrl, projectId }. The deployed URL — the thing a person or CI job most wants after a deploy — is derived, used for wiring, then discarded.Recovering it afterwards is archaeology, and it's ambiguous.
website/scripts/verify-deployed.tsexists solely to reconstruct this: an SDK dependency, a project-name lookup, a poll loop, and a guard — because the API reportsbranchId: nullfor every compute service, so a productionsiteand a stagedsiteare indistinguishable. The deploy never has to ask; it's holding the id.The load-bearing decision
Each entry is projected by the descriptor that owns the entity — an allowlist — rather than core filtering a dump.
The reason is concrete: field names don't carry sensitivity.
urlis a public HTTP endpoint on a compute node and a Postgres connection string on aprisma-nextnode. Same name, opposite sensitivity. And the S3 credentials resource keeps its secrets inaccessKeyId/secretAccessKey, not in anything calledurl— so a rule phrased over field names would print them untouched. Core can't tell these apart, and shouldn't:LoweredNode.outputsis extension-defined and opaque to it.Because the report is constructed rather than filtered, a mistake omits a field instead of exposing one.
On redaction
Redacting known secrets from log output is recorded as a separate follow-on (
.drive/deferred.md), not folded in here. It's genuinely worth having over the strings we don't author — Alchemy progress lines, errors echoing a DSN, stack traces. But it can't be what makes this report safe: some credentials are minted mid-run (s3Credentialsmints a SigV4 pair, so anything printed before registration is unmasked), and values are transformed before printing (a password in a DSN is percent-encoded, JSON escapes it again), which defeats literal matching. Defense-in-depth, not the wall — and it matches how this codebase already carries sensitivity in the type (SecretBox, Effect'sRedacted) rather than scrubbing text at the edge.What each descriptor now publishes
computeid,url,projectIdpostgres/prisma-nextidurl— here it's the connection strings3-credentials{}— listed by identity alones3-storebucketdeployspreads into outputsVerification
outputsinto the report fails two of the three new cases (including the secrets one); reverting passes 37/37. They don't pass vacuously.lint:deps, biome.lint:framework-vocabularycaught a real defect in my first cut — a core comment namingpostgres, which core must never do. Fixed.Two notes for the reviewer
ctx.node. My first cut had each one takenode.namefor its report; that broke 18 existing partial-context fakes incontrol-lowering.test.ts. Having core stamp the graph identity instead removes the coupling entirely — the whole prisma-cloud suite (159 tests) passes untouched. The test churn was the design telling me the seam was in the wrong place.lower()'s return value is published surface via@prisma/composer/deploy, so this changes a public type and the stack's printed outputs.🤖 Generated with Claude Code