Repository navigation
Design: nodes declare an id, a deploy finds its Project by logicalId only, and asks before deleting data #417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
2aafdcf
878f1b5
4e4a43f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Design notes — deployment-identity | ||
|
|
||
| ## Principles | ||
|
|
||
| - A deploy's target is chosen by an identifier, never by a label. | ||
| - Nothing is created on the platform unless a human asked for it by name. | ||
| - Composer knows nothing about GitHub. Repo-to-Project knowledge lives on the platform's link record. | ||
|
|
||
| ## 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`. | ||
|
|
||
| ``` | ||
| prisma deploy module.ts # finds Project by logicalId, or refuses | ||
| prisma deploy module.ts --create # finds, or creates with that logicalId | ||
| ``` | ||
|
|
||
| The refusal message is the whole point: "No Project with logicalId `storefront` in this workspace. This deploy would create a new project rather than update one. Pass `--create` to do that. If this repository is connected in Console, the Project should already exist; check the connection." | ||
|
|
||
| ## Alternatives considered | ||
|
|
||
| - **PR 355: Composer checks `GITHUB_REPOSITORY_ID` against the source-repository listing.** Rejected. Makes Composer GitHub-aware, re-verifies at deploy time what the platform decided at connect time, and is only a safeguard by its own admission. | ||
| - **The OIDC exchange returns the app name and the deploy action passes `--name`.** Rejected. Locks CI but hides the identity from the repo, so a developer cannot tell which Project a module file deploys to, and a local and a CI deploy of the same file can target different Projects. | ||
| - **CI passes `--create` on first deploy.** Rejected. Makes the flag meaningless in CI and couples the generated workflow to a Composer version. Creating at connect time needs no workflow change and no version coupling. | ||
| - **Keep the display-name fallback with a warning.** Rejected. The fallback is the mechanism that lets a name adopt an unrelated Project. The backfill removes the need for it. | ||
|
|
||
| ## Open questions | ||
|
|
||
| - None at project level. | ||
|
|
||
| ## References | ||
|
|
||
| - Full design, including the phases this project does not deliver: `.drive/projects/node-identity-and-consent/design-notes.md`. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # Project Plan — deployment-identity | ||
|
|
||
| ## Summary | ||
|
|
||
| Three slices: two on the control plane that can run in parallel, then one in Composer that ships only after both are in production. Linear project: [Deployment identity](https://linear.app/prisma-company/project/deployment-identity-find-the-project-by-identifier-create-only-when-3f43eb77b990) (P-TML-1156). | ||
|
|
||
| **Spec:** `.drive/projects/deployment-identity/spec.md` | ||
|
|
||
| The control plane work runs from the clone at `wip/pdp-control-plane` (bot identity, draft PRs per that repo's rules). The Composer work runs on this branch. | ||
|
|
||
| ## Slices | ||
|
|
||
| ### Slice 1 — Console creates the Project at connect time (TML-3579, control plane) | ||
|
|
||
| The setup-PR path in `connectRepositoryForComposer` creates the Project with the reserved module name as `logicalId` and the region the user chose (already stored on the setup attempt), then creates the link with `projectId` set, the way the "connect without a setup PR" path in `project.create/action.ts` already does. `reflectProjectOnLink` becomes dead and is removed. "Remove setup" (`releasePendingLinkUnlessAdopted`) and the unmerged-PR archive path delete the Project the connect created, when it still holds nothing. The pending-link name-conflict query stops assuming `projectId` is null. | ||
|
|
||
| - **Builds on:** nothing. | ||
| - **Hands to:** slice 3 — every repo connected from now on has its Project before its first deploy, so Composer can refuse to create. | ||
|
|
||
| ### Slice 2 — Backfill `logicalId` on linked Projects (TML-3580, control plane) | ||
|
|
||
| A script under the control plane's scripts convention: for every live `ProjectScmRepo` with a `moduleName` whose Project has no `logicalId`, set it. Dry-run by default, refuses a value that is not unique in the workspace, prints the remaining count. Run against production once reviewed. | ||
|
|
||
| - **Builds on:** nothing. | ||
| - **Hands to:** slice 3 — zero linked Projects without a `logicalId` in production. | ||
|
|
||
| ### Slice 3 — Composer finds by `logicalId` only and creates only with `--create` (TML-3581, Composer) | ||
|
|
||
| `resolveProject` drops the display-name match. `ResolveContainerOptions.ensure` becomes a mode with three values: a plain deploy finds the Project or refuses, `deploy --create` finds it or creates it, and destroy finds it or reports that nothing is deployed. The mode is threaded from a new `create` option on the `deploy` operation and `prisma deploy --create` through `LocateContainerInput` and the extension's `ensureContainer`. A miss without `--create` is a structured `DEPLOY.PROJECT_NOT_FOUND` error with the message from the design notes. The decision records on stage resolution and node addresses amended, deploying guide and core-concepts skill updated, PR 355 closed. | ||
|
|
||
| - **Builds on:** slices 1 and 2 in production. | ||
| - **Hands to:** close-out. | ||
|
|
||
| ## Sequencing | ||
|
|
||
| - Parallel: slices 1 and 2. | ||
| - Stack: (1, 2) → 3. Slice 3 may be built and reviewed alongside, but is not released until the backfill has run and slice 1 is deployed. | ||
|
|
||
| ## Close-out (required) | ||
|
|
||
| - [ ] Verify all acceptance criteria in `.drive/projects/deployment-identity/spec.md` | ||
| - [ ] Migrate long-lived docs into `docs/` (ADR amendments land with slice 3; nothing else is long-lived) | ||
| - [ ] Strip repo-wide references to `.drive/projects/deployment-identity/**` | ||
| - [ ] Delete `.drive/projects/deployment-identity/` |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,63 @@ | ||||||
| # Purpose | ||||||
|
|
||||||
| Make it impossible for an edit that looks cosmetic to redirect a deploy. Today the root module's name is the only thing that chooses the Project a deploy lands in, and Composer falls back to matching display names, so renaming the root in a connected repo can deploy into another repository's Project or create a stray one. After this project, a deploy finds its Project by identifier only, never creates one unless told to, and the platform has given every Project an identifier. The rename still fails, but it fails before anything is created or overwritten, with a message that says what would have happened. | ||||||
|
|
||||||
| This is phase 1 of a larger design (see `node-identity-and-consent`). It solves Søren's reported problem and closes prisma/composer#355 without changing how modules are written. | ||||||
|
|
||||||
| # At a glance | ||||||
|
|
||||||
| Four changes, two in Composer and two on the platform: | ||||||
|
|
||||||
| 1. **Find by identifier only.** `resolveProject` in `packages/1-prisma-cloud/0-lowering/lowering/src/container.ts` matches a Project's `logicalId` against the app name and nothing else. The display-name fallback, oldest first, is deleted. | ||||||
| 2. **Create only when told.** A deploy whose app name matches no Project stops with a structured error: this would create a new project, not update one; pass `--create`. `prisma deploy` gains that flag and the control API gains the matching option. Destroy is already find-only. | ||||||
| 3. **Console creates the Project at connect.** The setup-PR path in the control plane's connect flow creates the Project with the reserved module name as its `logicalId` and links it, as the "connect without a setup PR" path already does. CI never creates a Project. Releasing or abandoning the setup deletes the empty Project it created. | ||||||
| 4. **Backfill.** A one-time platform script sets `logicalId` on every linked Project that has none, from the link's reserved module name. | ||||||
|
|
||||||
| # Non-goals | ||||||
|
|
||||||
| - Changing the node API. `logicalId` and `name` as separate fields, and refusing the single-string form, are the follow-on project. | ||||||
| - Consent for data-losing deploys. Follow-on project. | ||||||
| - Reading anything GitHub-specific in Composer. The deploy action and the generated workflow are unchanged. | ||||||
| - Preventing a deliberate deploy into another Project with a workspace-wide token. That is scoped CI tokens, a platform follow-up. | ||||||
| - A deprecation period for the display-name fallback. Composer is pre-1.0 and the backfill makes it unnecessary. | ||||||
|
|
||||||
| # Place in the larger world | ||||||
|
|
||||||
| - Composer: container resolution, which finds or creates the Project and Branch before Alchemy runs (decision record "a stage is a deploy-time environment resolved to Project and Branch", amended by "a node's address is its logical ID on the platform"), and the `prisma deploy` command. | ||||||
| - Control plane: `connectRepositoryForComposer` in `services/console-compute/lib/composerConnect.server.ts`, `connectWorkspaceToScmRepo`, `createProject` and its `reflectProjectOnLink` step, which becomes unnecessary once the Project exists before the link. The GitHub Actions token exchange and `cloud-deploy-action` are untouched. | ||||||
| - The control plane's decision record "project slug as the deploy-target identity" made the module name the Project's `logicalId`. This project finishes that work by removing the display-name fallback it kept for older Projects. | ||||||
| - prisma/composer#355 is closed in favour of this project. The decision record it proposed, "GitHub project selection checks active repository connections", is not adopted. | ||||||
|
|
||||||
| # Cross-cutting requirements | ||||||
|
|
||||||
| - A deploy that cannot find its Project never creates, mutates or reconciles anything before it stops. | ||||||
| - Every repo connected through Console today keeps deploying across the change. The backfill runs before the Composer release that removes the fallback. | ||||||
| - The `--create` refusal is a structured CLI error with a `fix` that names the flag and, for a connected repo, says the Project should already exist and to check the Console connection. | ||||||
| - Composer stays unaware of GitHub. | ||||||
|
|
||||||
| # Transitional-shape constraints | ||||||
|
|
||||||
| - Order of landing: the platform changes (create at connect, backfill) ship first. The Composer release that removes the fallback and adds `--create` ships after the backfill has run, or the setup PR's first deploy breaks. | ||||||
| - Until the Composer release ships, a Project created at connect time is found by the old resolver through its `logicalId` match, so the platform change is safe to ship alone. | ||||||
|
|
||||||
| # Project DoD | ||||||
|
|
||||||
| - [ ] A renamed root in a connected repo fails its CI deploy with the "would create a new project" error and creates nothing. Proven against a real workspace. | ||||||
| - [ ] A fresh Console connect creates the Project before the setup PR's first deploy, and that deploy lands in it. | ||||||
| - [ ] 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. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||||||
| - [ ] prisma/composer#355 closed with a comment pointing at this project. | ||||||
|
|
||||||
| # Open questions | ||||||
|
|
||||||
| - None blocking. Whether Console should also allow editing a Project's `logicalId` by hand for Projects with no link (so a user can fix one the backfill cannot) is answered in the follow-on project if it comes up. | ||||||
|
|
||||||
| # References | ||||||
|
|
||||||
| - Design synthesis and scenarios: `.drive/projects/node-identity-and-consent/design-notes.md` and `assets/`. | ||||||
| - Slack thread with Søren, 2026-10-14, and prisma/composer#355. | ||||||
| - Composer decision records, in `docs/design/90-decisions/`: ADR-0006 "every node is named", ADR-0024 "a stage is a deploy-time environment resolved to Project and Branch", ADR-0050 "Composer runs as prisma deploy and prisma dev", ADR-0051 "a node's address is its logical ID on the platform". | ||||||
| - Control plane, prisma/pdp-control-plane: ADR-010 "a connected repository is the trust boundary for CI deploys" (it mints a workspace-wide token), ADR-011 "project slug as the deploy-target identity", `docs/CiOidcTokenExchange.md`, and `docs/compute/GitHubImport.md` for the repository link lifecycle. | ||||||
| - Linear: Deployment identity (Terminal team). | ||||||
There was a problem hiding this comment.
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.mdand its design-notes D2.Recommended fix