Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .drive/projects/deployment-identity/design-notes.md
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: the decision record "a node's address is its full path from the app name" makes the root's address the app name, and every address is its node's `logicalId`. 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`.
44 changes: 44 additions & 0 deletions .drive/projects/deployment-identity/plan.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 environment resolution and on full-path node addresses are 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/`
63 changes: 63 additions & 0 deletions .drive/projects/deployment-identity/spec.md
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"), and the `prisma deploy` command. The decision record "a node's address is its full path from the app name" makes the root's address, the app name, the Project's `logicalId`, and it is the record that still describes the display-name fallback as a compatibility path.
- 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 Composer decision records "a stage is a deploy-time environment resolved to Project and Branch" and "a node's address is its full path from the app name" are amended: the fallback is gone, and creating a Project needs `--create`. 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.
- [ ] 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-0057 "a node's address is its full path from the app name" (it superseded ADR-0051), and ADR-0059 "a place the app runs is called an environment, not a stage or a branch".
- 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).
Loading
Loading