Skip to content

Design: a deploy finds its Project by logicalId only, and every node gets a logicalId and a name - #417

Open
wmadden-electric wants to merge 2 commits into
mainfrom
docs/deployment-identity-design
Open

wmadden-electric wants to merge 2 commits into
mainfrom
docs/deployment-identity-design

Conversation

@wmadden-electric

Copy link
Copy Markdown
Contributor

Today, renaming the root module in a connected repository can send the next deploy into a different Project:

// repo A, before
export default module('store', …)
// repo A, after a rename that looks cosmetic
export default module('billing', …)   // 'billing' is repo B's Project

Composer selects the Project by matching the root module's name against each Project's logicalId, and falls back to matching the display name. Nothing ties the deploy to the Project the repository was connected to, so the next push from repo A reconciles into repo B's production stage. A rename to an unused name creates a stray, empty Project instead.

This pull request adds two Drive projects that fix this. It replaces #355, which checked the GitHub repository id inside Composer.

The decision

A deploy finds its Project by logicalId only, and never creates one unless asked. The display-name fallback is removed. A deploy whose identifier matches nothing stops with "this would create a new project, not update one; pass --create". Console creates the Project when a repository is connected, so CI never needs to create one. A one-time platform script gives older Projects a logicalId. This is the deployment-identity project, which we are executing now.

Every node gets a required logicalId and an optional name. One string per node is today at once its identity, its label and its address, which is why a cosmetic edit can change identity. With two fields, editing name only changes the Console label, and editing logicalId is a visible, deliberate identity change. A deploy that would delete or re-identify a database or bucket asks for consent the way the ORM's migration commands do: --delete <subject> or --rename <old>:<new>, --yes never consents, and CI fails closed. CI tokens scoped to one Project and Console approval of consent follow on the platform. This is the node-identity-and-consent project, written for handoff.

Composer stays unaware of GitHub throughout.

What to read

  • .drive/projects/deployment-identity/spec.md and plan.md: the immediate fix, in three slices.
  • .drive/projects/node-identity-and-consent/design-notes.md: every decision with its reasoning and the alternatives rejected.
  • .drive/projects/node-identity-and-consent/assets/user-scenarios.md: the user scenarios the design covers.

Alternatives considered

  • Check the GitHub repository id in Composer (fix(cloud): reject GitHub deploys to a different connected project #355). It makes Composer GitHub-aware and re-checks at deploy time what the platform already decided at connect time.
  • Have the token exchange return the app name and the deploy action pass --name. It hides the identity from the repository, so a local deploy and a CI deploy of the same file can reach different Projects.
  • Generate the root logicalId on the platform. An opaque id breaks on fork or delete; a readable slug with a suffix invites people to "tidy" it, which creates a new Project.
  • Record consent in code (renamedFrom, Terraform-style moved blocks). The ORM tried and rejected this: the code would keep describing a system that no longer exists, when only the command doing the change needs the information.

Agent: parmenides-88

🤖 Generated with Claude Code

…nsent

Two Drive projects that replace #355.

deployment-identity: a deploy finds its Project by logicalId only, refuses to create one without --create, Console creates the Project at connect, and a platform script backfills logicalId on old Projects.

node-identity-and-consent: written for handoff. Every node gets a required logicalId and an optional name, data-losing deploys use the ORM's consent model, and the platform scopes CI tokens to one Project.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@prisma-gizmo

prisma-gizmo Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

✅ Gizmo reviewed 878f1b5 — posted 0 inline comment(s) this pass.

Open findings: 🟠 1 major · 🟡 6 minor

Change walkthrough

This pull request is documentation-only design work in .drive/projects/, covering two Drive projects that together stop a cosmetic-looking edit from redirecting a deploy into another Project: deployment-identity (a deploy finds its Project by logicalId only and never creates one without --create) and node-identity-and-consent (every node gets a required logicalId and an optional name, with ORM-style consent for data-losing identity changes). The incremental delta since the last reviewed pass reconciles both projects' docs with two decision records that landed since: ADR-0057 ("a node's address is its full path from the app name", which supersedes ADR-0051) and ADR-0059 ("a place the app runs is called an environment, not a stage or a branch").

Deployment-identity docs (design-notes.md, plan.md, spec.md): the inline ADR-0051 citation is replaced with the spelled-out record name, and the amendment/DoD bullets now name ADR-0024 and ADR-0057 as the records that still carry the display-name fallback (verified against the actual ADR text) and must be amended when it is removed. The reference list adds ADR-0057 and ADR-0059 with correct titles.

Node-identity-and-consent docs (design-notes.md, plan.md, spec.md): the compatibility-path citation is re-pointed to ADR-0057, D10 now states that a Project with neither a logicalId nor a link has no automatic fix (leaving a Console hand-edit open), "stage" becomes "environment" throughout, and the delivery table names the future decision record by the three records it amends (node naming, environment resolution, full-path addresses).

Assets (logicalid-walkthrough.md, user-scenarios.md): addresses throughout are rewritten as full paths from the app name (store.catalog) per ADR-0057, the two fields move onto node definitions with the provision() id override kept for re-addressing, previously open items are recorded as settled (root takeover needs no prompt; child identity changes are --rename/--delete consent questions; --name is removed), and the walkthrough is explicitly subordinated to the design notes. The spec gains a well-reasoned open question on what the identity field on a child's definition should be called, since ADR-0057 makes the full address the platform logicalId while a child's field holds only one segment.

@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 3c9550e1-1e15-41da-a2c9-97f7add06bfb
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Tip

🎯 Customize your risk assessments

Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.

✅ Automatic approvals, with your rules

Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New findings: 🟠 1 major · 🟡 6 minor · trace

- [ ] Releasing a setup ("Remove setup") or closing the setup PR unmerged deletes the Project the connect created, and only that one.
- [ ] Every linked Project in production has a `logicalId`; the backfill reports zero remaining.
- [ ] `prisma deploy --create` creates a Project from a laptop; without the flag the same deploy refuses.
- [ ] The two Composer decision records named above are amended. The deploying guide and the core-concepts skill say that a deploy finds its Project by `logicalId` and needs `--create` to make one. The guide's `prisma deploy module.ts --name demo-42` example still works, with `--create` on its first run.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Major · consistency — DoD amends the superseded ADR-0051 and never names ADR-0057, the live record for Project resolution

"The two Composer decision records named above" are ADR-0024 and ADR-0051 ("a node's address is its logical ID on the platform", referenced at line 26 and in the references at line 61). ADR-0057 supersedes ADR-0051, and it is ADR-0057's Consequences that record the behavior this project deletes: "container resolution falls back to matching a Project by display name … a compatibility path" (and ADR-0057 also rejects "Match on display names"). If slice 3 amends only the records named here, the repository's binding decision record keeps prescribing the fallback the new resolver no longer implements. The same omission repeats in plan.md slice 3 ("the decision records on stage resolution and node addresses amended") and in the node-identity-and-consent spec and plan.

Recommended fix

Suggested change
- [ ] The two Composer decision records named above are amended. The deploying guide and the core-concepts skill say that a deploy finds its Project by `logicalId` and needs `--create` to make one. The guide's `prisma deploy module.ts --name demo-42` example still works, with `--create` on its first run.
- [ ] The two Composer decision records named above are amended, together with ADR-0057 ("a node's address is its full path from the app name"), which supersedes ADR-0051 and is the live record that carries the display-name fallback. The deploying guide and the core-concepts skill say that a deploy finds its Project by `logicalId` and needs `--create` to make one. The guide's `prisma deploy module.ts --name demo-42` example still works, with `--create` on its first run.


## The model

The Project's `logicalId` is the app name (ADR-0051). Container resolution matches on it and on nothing else. If no Project matches, the deploy stops unless `--create` was passed. The platform creates the Project when a repo is connected, so a connected repo's CI run always finds its Project and never needs `--create`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Minor · consistency — Model statement cites ADR-0051, the superseded record, instead of ADR-0057

ADR-0051 was superseded by ADR-0057, which is the live record for "the address is the node's logicalId" (for the root, the address is the app name, so the claim here still holds). Citing only the superseded ADR — here, and in the references lists of both projects' specs — sends the reader to a dead record. The same stale citation appears in .drive/projects/deployment-identity/spec.md (references) and .drive/projects/node-identity-and-consent/spec.md and its design-notes D2.

Recommended fix

Suggested change
The Project's `logicalId` is the app name (ADR-0051). Container resolution matches on it and on nothing else. If no Project matches, the deploy stops unless `--create` was passed. The platform creates the Project when a repo is connected, so a connected repo's CI run always finds its Project and never needs `--create`.
The Project's `logicalId` is the app name (ADR-0051, superseded by ADR-0057, which keeps this: the root's address is the app name, and the address is the node's `logicalId`).

Comment on lines +53 to +55
const auth = provision(authModule, { logicalId: 'auth', secrets: { signingKey: envSecret('AUTH_SIGNING_SECRET') } });
provision(web, { logicalId: 'web', name: 'Web', deps: { db: catalog, auth: auth.rpc } });
provision(api, { logicalId: 'api', deps: { db: catalog } });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Minor · consistency — Walkthrough's proposed API puts logicalId/name in provision() options, contradicting settled decision D1

The example passes logicalId: 'web', logicalId: 'api' and logicalId: 'auth' inside the provision() options object, but design-notes D1 settles that "logicalId lives on the node definition (postgres({ logicalId }), module({ logicalId, name }, …)), not on the provision() call" — the provision-time override stays id, for provisioning the same node twice or a packaged module. The glossary likewise defines the provision-time override as id. A handoff implementer reading this asset as the worked example could build the wrong constructor surface.

Recommended fix

Suggested change
const auth = provision(authModule, { logicalId: 'auth', secrets: { signingKey: envSecret('AUTH_SIGNING_SECRET') } });
provision(web, { logicalId: 'web', name: 'Web', deps: { db: catalog, auth: auth.rpc } });
provision(api, { logicalId: 'api', deps: { db: catalog } });
const auth = provision(authModule, { secrets: { signingKey: envSecret('AUTH_SIGNING_SECRET') } });
provision(web({ logicalId: 'web', name: 'Web' }), { deps: { db: catalog, auth: auth.rpc } });
provision(api({ logicalId: 'api' }), { deps: { db: catalog } });


```
Project "billing" (proj_…) exists and holds a production deploy from
another source. Continue? (--yes)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Minor · consistency — Takeover prompt in 5b offers (--yes), which the design's consent model forbids

The scenario shows Continue? (--yes) for deploying into a Project that holds another source's production deploy. D8 and the spec settle that --yes never consents to a data-losing action — the consent flags are per-subject --delete <subject> / --rename <old>:<new>. Setting logicalId: 'billing' over store is exactly a root re-identification, so the consistent answer mechanism is --rename store:billing. The surrounding text hedges ("Whether Composer asks, or just logs, is a separate decision"), but the illustrated mechanism contradicts the consent model the design hands off.

Recommended fix

Suggested change
another source. Continue? (--yes)
another source. Continue? (--rename store:billing)


| # | Scenario | Expected | Covered by |
| --- | --- | --- | --- |
| B1 | Author changes the root `name` from "Store" to "Storefront". | Console label changes. Same Project, same stages, same data. | D1, D3 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Minor · consistency — Scenarios use the retired word "stage" for what ADR-0059 renames to "environment"

B1 says "same stages" and D5 (line 57) "User destroys a stage, or production". ADR-0059 replaces "stage" with "environment" on every Composer surface (guides, errors, agent skill), and these handoff docs seed exactly those surfaces. The same slip appears in the walkthrough's §2 table ("overwrites its production stage") and in node-identity design-notes ("The hash differs per stage"). Quoting ADR-0024's title is fine; describing behavior is not.

Recommended fix

Suggested change
| B1 | Author changes the root `name` from "Store" to "Storefront". | Console label changes. Same Project, same stages, same data. | D1, D3 |
| B1 | Author changes the root `name` from "Store" to "Storefront". | Console label changes. Same Project, same environments, same data. | D1, D3 |

2. A6: migration rule for existing single-string modules.
3. A7: backfill of `logicalId` on old Projects, or a one-time Console edit.
4. C3 and B5: plan output for identity changes below the root, and whether it needs consent.
5. E3: the fate of `--name`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Minor · consistency — E3 and open item 5 still list the fate of --name as unsettled, but the design settles it

Row E3 (line 66) marks --name as "Open" and item 5 here repeats "the fate of --name" as an open item, but the settled design removes it: design-notes "Smaller consequences" ("--name on prisma deploy goes away"), the spec's phase 2 ("--name on prisma deploy goes away") and plan phase 2 ("--name is removed from prisma deploy"). A reader reconciling the asset against the spec gets two different answers.

Recommended fix

Delete open item 5 and change the E3 row's "Open" to the settled answer: --name is removed in phase 2 and re-identifying the root is --rename.

Comment on lines +118 to +121
### D10. Backfill `logicalId` on old Projects once

A platform script sets `logicalId` from the link's reserved module name on
every linked Project that has none. A Project with neither is set by hand in

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Minor · correctness — D10 claims leftover Projects are "set by hand in Console", which deployment-identity's spec defers as an open question

D2's safe-removal story assumes D10 covers old Projects, but D10 only backfills linked Projects from the link's reserved module name. For the remaining population — Projects old enough to predate logicalId that also have no repo link — D10 says they are "set by hand in Console", yet the deployment-identity spec's open questions (line 55) defer "whether Console should also allow editing a Project's logicalId by hand for Projects with no link" to the follow-on project "if it comes up". Both cannot hold: either the Console capability exists (then the spec's open question is already answered) or it does not (then this population loses display-name resolution with no remediation, and --create would build them a second, empty Project). The cross-cutting requirement "every repo connected through Console today keeps deploying" is unaffected — linked Projects are backfilled — but the docs disagree about the unlinked tail.

Recommended fix

Reconcile the two documents: either state in D10 that Console editing of a Project's logicalId ships with the backfill slice, or scope D10 to linked Projects and record that unlinked Projects without a logicalId have no automatic remediation and their deploys stop with the --create message.

…nd environments

Cite the decision record that makes a node's address its full path from the app name, which superseded the one the designs cited and is the record that keeps the display-name fallback. Use full-path addresses and the word environment in examples. Record how the scenarios' open items were settled, and put logicalId on the node definition in the worked example. Add the open question this raises: on a child, the value an author writes is one segment of the logicalId.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New findings: none · trace

Still open from previous reviews: 🟠 1 major · 🟡 6 minor

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