Skip to content

feat(deploy): report what each node became (ADR-0032)#101

Open
wmadden-electric wants to merge 2 commits into
mainfrom
claude/adr-deployment-report
Open

feat(deploy): report what each node became (ADR-0032)#101
wmadden-electric wants to merge 2 commits into
mainfrom
claude/adr-deployment-report

Conversation

@wmadden-electric

@wmadden-electric wmadden-electric commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Proposes that prisma-composer deploy report 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 holding lowered — node id → that node's descriptor.deploy(...) result — and ends with a hardcoded return { outputs: {} }. The print path exists too: the generated stack's default export is lower(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.ts exists solely to reconstruct this: an SDK dependency, a project-name lookup, a poll loop, and a guard — because the API reports branchId: null for every compute service, so a production site and a staged site are 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. url is a public HTTP endpoint on a compute node and a Postgres connection string on a prisma-next node. Same name, opposite sensitivity. And the S3 credentials resource keeps its secrets in accessKeyId/secretAccessKey, not in anything called url — so a rule phrased over field names would print them untouched. Core can't tell these apart, and shouldn't: LoweredNode.outputs is 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 (s3Credentials mints 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's Redacted) rather than scrubbing text at the edge.

What each descriptor now publishes

Node Reports Withholds
compute id, url, projectId the artifact hash and the service environment — the environment is where the secrets are
postgres / prisma-next id url — here it's the connection string
s3-credentials {} — listed by identity alone the minted SigV4 pair, which is its entire output
s3-store compute's report + bucket the SigV4 pair its deploy spreads into outputs

Verification

  • The tests were checked against the failure they claim to catch. Mutating the collector to spread outputs into the report fails two of the three new cases (including the secrets one); reverting passes 37/37. They don't pass vacuously.
  • Full sweep green: 47 test tasks, 56 typecheck tasks, 27 builds, lint:deps, biome.
  • lint:framework-vocabulary caught a real defect in my first cut — a core comment naming postgres, which core must never do. Fixed.

Two notes for the reviewer

  • Descriptors deliberately don't read ctx.node. My first cut had each one take node.name for its report; that broke 18 existing partial-context fakes in control-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

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>
@wmadden-electric wmadden-electric changed the title docs(adr): ADR-0032 — the deploy reports what each node became feat(deploy): report what each node became (ADR-0032) Jul 16, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jul 16, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@prisma/composer@101
npm i https://pkg.pr.new/@prisma/composer-prisma-cloud@101

commit: 3031730

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant