Skip to content
58 changes: 58 additions & 0 deletions .drive/projects/state-under-branch/assets/pdp-asks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Two questions from the Composer team about the Management API

Context in three sentences: **Prisma Composer** is the TypeScript framework
that deploys multi-service apps onto Prisma Cloud through the public
Management API — one app becomes one Project, each environment (production,
staging, per-PR preview) is a Branch, and the app's services and databases are
that Branch's Apps and Databases. We're changing where Composer keeps its own
deploy state (the record of what it has provisioned, used to diff deploys and
drive destroys): instead of a separate workspace-level project, each
environment will keep its state in one small, framework-owned Prisma Postgres
database named `prisma-composer-state`, attached to that environment's Branch
and created through the ordinary database-create endpoint. The point is
lifecycle containment: deleting a Branch (or the Project) then cleans up our
state automatically, with no special handling on your side.

Two things we'd like to confirm before we ship it:

## 1. Quota / billing for one extra small database per Branch

Every environment gets one additional database (tiny: a handful of rows of
JSON state). For apps using per-PR preview environments, these come and go
with the PR — created on first deploy of the branch, deleted when the branch
is destroyed.

- Does this count against any per-project or per-workspace database quota we
should design around?
- Is there a billing floor per database that would make "one small extra DB
per PR preview" surprising for customers, or is a mostly idle database
effectively free?

## 2. The dependency contract of `DELETE /v1/projects/{id}`

When Composer destroys an app's production environment, it deletes the
resources it created, then makes a best-effort `DELETE /v1/projects/{id}` so
empty projects don't accumulate toward the workspace plan limit. We rely on
the API's own 400 ("still has dependencies") to keep the project when other
environments still exist.

What we observe: the delete succeeds when the project holds only its implicit
default Branch and the auto-provisioned default database, and 400s when more
exists. We'd like to confirm that's the contract, not incidental behavior:

- Which children count as blocking dependencies? Specifically: does an
**empty non-default Branch** block deletion? Does a **non-default
database** block it (we assume yes — we delete our state database before
calling project-delete)?
- Is "implicit default Branch + default database don't block deletion"
guaranteed, or should we delete anything else first?

## FYI, not asks (yet)

Two things we'll likely bring to you later, flagged now so they're not a
surprise: a way to mark a database as framework-owned/protected (so users
don't hand-delete their environment's state DB from Console), and — for the
future GitHub App integration of Composer repos — short-lived,
project-scoped service tokens. Nothing needed on either today.

— Composer team (Will)
600 changes: 600 additions & 0 deletions .drive/projects/state-under-branch/design-notes.md

Large diffs are not rendered by default.

125 changes: 125 additions & 0 deletions .drive/projects/state-under-branch/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# State Under Branch — Project Plan

## Summary

**One slice** — ADR-0033 and its implementation in one PR
([#113](https://github.com/prisma/composer/pull/113)). The binding design it
executes is [design-notes.md](design-notes.md).

This started as two strictly-sequenced slices (ADR first, then code) on the
reasoning that principles bind until an ADR supersedes them. That was wrong:
it produces a docs-only PR, which delivers nothing on its own and costs a
review cycle to say so. Operator direction, 2026-07-17: *"Don't separate docs
and implementation. A docs PR on its own is useless."* The ADR half is already
written, reviewed, and approved on #113; the implementation lands on the same
branch before it merges.

**Spec:** [spec.md](spec.md) · **Design notes:** [design-notes.md](design-notes.md)

## Tracker

Slices are identified by their S-number here; this plan is the source of
truth. Linear issues are created per-slice when the slice starts, not during
planning. Tracker project:
[Prisma Composer: State Under Branch](https://linear.app/prisma-company/project/prisma-composer-state-under-branch-5754597a6981).

## External dependencies

- ~~**PDP team answers**~~ — both resolved from source 2026-07-17, no ask
outstanding; see design-notes § Resolved questions. Quota is a real
constraint (a state DB per stage takes one of the workspace's 50 free /
1000 paid database slots); billing is negligible (no per-database fee).
Project deletion is blocked only by active compute deployments — never by
Branches or databases.
- **`@prisma/management-api-sdk` types** — the implementation needs `branchId` +
`region: 'inherit'` admitted on database create (or falls back to the
documented create-then-PATCH; a stale region union requires an SDK update,
never a hard-coded region). Verified against the live API source; only the
generated types may lag.

## The slice — State under Branch (TML-3049, PR #113)

Two halves of one PR, in this order on the branch.

### Half 1 — ADR-0033 + documentation corrections — DONE, APPROVED

ADR-0033 ("Deploy state lives in the stage's Branch"); ADR-0009 marked
superseded; the consequence note on ADR-0010 (lock scoped within the per-stage
DB; severed-lock kill-switch property; redundant key retained) and the
correction note on ADR-0024 (resources land on the platform's implicit default
Branch; addressing model unchanged); the cutover note in
`docs/guides/deploying.md`; `docs/design/10-domains/deploy-cli.md`'s state
section and the ADR index. The staleness sweep also reached ADR-0011,
ADR-0023, ADR-0030, `layering.md`, and `alchemy-lowering.md`.

### Half 2 — Per-branch state bootstrap + destroy ordering

Implement design-notes § The design, exhaustively: bootstrap rework in
`packages/1-prisma-cloud/0-lowering/lowering/src/state/`, `prismaState()` env
contract change, `deleteStateDatabase`, and the CLI destroy-tail ordering in
`packages/0-framework/3-tooling/cli/src/main.ts`. Tests reworked/added per
[slices/per-branch-state/spec.md](slices/per-branch-state/spec.md).

- **Builds on:** half 1, already on the branch.
- **Hands to:** project close-out; the successor project's precondition
(project-scoped state) is now true.

Nothing here merges until both halves are on #113 and green.

## Successor project (recorded, not started): Compute GitHub App integration

Out of scope for this project by operator decision (2026-07-17), to be opened
as its own Drive project afterwards. Recording the settled inputs so shaping
starts warm:

**Goal.** Push-to-deploy for Composer repos through pdp-control-plane's
existing GitHub App pipeline (webhook → build-runner → E2B sandbox), with the
sandbox running the user's declared build + `prisma-composer deploy` instead
of the `BuildStrategy` pipeline.

**Already settled in discussion** (transcript
`0feb1bb2-7810-4fd6-9cbb-7d348f8c32f0` + continuation):

- The webhook service, repo↔Project link (`ProjectScmRepo`), Branch
resolution, `Build` row lifecycle, sandbox spawning, and log streaming are
reused unchanged; the runner's kickoff/finalize legs (App find-or-create,
ComputeVersion + presigned upload, Foundry start/promote) are skipped for
Composer builds — the CLI performs versions/promote itself.
- Composer detection must be deterministic (repo-link flag or
`prisma-composer.config.ts` presence), never inferred.
- `Build.appId`/`deploymentId` stay null for Composer builds;
`computeProjectId` is the scope; two pdp consumers need re-scoping
(failure-email cron, setup-activation notifications).
- Branch-delete teardown needs no Composer involvement — this project's
containment model makes the platform's existing enumerate-and-delete
correct, and deleting the state DB severs in-flight deploys' locks.

**Open design points carried forward** (the hard ones first):

1. Per-build credential: short-lived **project-scoped** service token minted
by the control plane into the sandbox — machinery and Management API
scoping support don't exist yet (PDP dependency).
2. Where the `{ entry, build command }` declaration lives (Composer-side ADR;
ADR-0017 currently forbids app settings in `prisma-composer.config.ts`).
3. Supersede-vs-lock ordering: runner must kill the old sandbox (dropping its
lock connection) before dispatching the successor, plus a short acquire
retry.
4. Pinned-id deploy mode: CLI accepts pinned Project/Branch ids and fails
rather than find-or-creates (closes the delete/push resurrection race —
GitHub webhook ordering is not guaranteed).
5. Stage seeding policy for `envParam`/`envSecret` on first webhook deploy of
a fresh stage (likely: copy production's platform variables, else fail with
a "set these in Console" build error).
6. Restrict webhook-managed apps to platform-backed extensions until a
pre-teardown destroy-run exists (external-resource limitation).

## Close-out (required)

- [ ] Verify all acceptance criteria in [spec.md](spec.md) § Project-DoD
- [ ] Migrate long-lived docs into `docs/` (S1 lands them; verify nothing
else accrued — the teardown matrix and cutover note must live in
`docs/`, not here)
- [ ] Open the successor project from § Successor project above (or
explicitly defer it with the operator)
- [ ] Strip repo-wide references to `.drive/projects/state-under-branch/**`
- [ ] Delete `.drive/projects/state-under-branch/`
80 changes: 80 additions & 0 deletions .drive/projects/state-under-branch/slices/adr-supersession/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# S1 — ADR-0033 + documentation corrections

Docs-only slice. Records the settled decision so S2's code is
principle-compliant when it lands. The content source is
[design-notes.md](../../design-notes.md) — the ADR is a transcription into the
repo's ADR voice, not a re-derivation; where design-notes and this spec are
silent, the repo's existing ADR conventions decide, nothing else.

## At a glance

One PR touching only `docs/`:

1. **New `docs/design/90-decisions/ADR-0033-deploy-state-lives-in-the-stage-branch.md`.**
Sections and required content:
- **Decision** — the containment rule (design-notes § The decision), naming:
per-stage database `prisma-composer-state` attached to the stage's
Branch; production on the implicit default Branch; workspace store
retired.
- **Rationale** — the supersession case verbatim in substance
(design-notes § Why the inputs changed): circularity dissolved by
ADR-0023/0024, fragmentation moved to the platform, two new forces
(teardown correctness, credential scoping). Must state explicitly this
supersedes ADR-0009 because its inputs changed, not because its reasoning
was wrong.
- **The ordering rules** — CLI deletes state last-among-members and
before the container; platform teardown deletes it like any child, and
the severed lock is a kill switch (design-notes § Destroy ordering,
§ teardown matrix).
- **The teardown-path matrix** (design-notes § Teardown-path matrix),
including the documented limitation: platform-side teardown covers
platform resources only.
- **Consequences** — per-stage DB cost (OQ-1 noted), state DB visible in
Console per stage (platform ask: protected flag), manual cutover for the
legacy store, ADR-0010/0011/0012 survive.
2. **ADR-0009** — status line → superseded by ADR-0033 (repo's existing
supersession convention).
3. **ADR-0010** — consequence note: lock now lives in the per-stage database;
`(stack, stage)` key retained though redundant; state-DB deletion severs
the lease (kill-switch property).
4. **ADR-0024** — correction note: "lives at the Project level" describes
Composer's addressing (no Branch ensured or named); physically the platform
attaches default-stage resources to the Project's implicit default Branch
(post-#3902 invariant, confirmed with PDP 2026-07-17).
5. **`docs/design/90-decisions/README.md`** — index entry for ADR-0033.
6. **`docs/guides/deploying.md`** — upgrade note with the 4-step manual
cutover (design-notes § Legacy workspace store).
7. **`docs/design/10-domains/deploy-cli.md`** — state section updated: where
state lives, destroy-tail ordering.

## Coherence rationale

One reviewer, one sitting: a single new ADR plus five surgical edits, all
tracing to one design record. Rollback is one revert.

## Scope

**In:** the seven document changes above. **Deliberately out:** any code;
platform-ask.md additions beyond the protected-DB flag mention if the file
convention requires an entry (implementer checks `platform-ask.md`'s existing
format and adds the protected-flag + reserved-name asks there if that is where
asks live).

## Slice-specific done conditions

- ADR-0033 contains the ordering rules and teardown matrix (S2's tests cite
them).
- No document still describes the workspace store as current except ADR-0009
itself, which is marked superseded.

## Dispatch plan

Single dispatch (designer agent per repo convention, model per operator's
global rule for implementer/reviewer subagents does not apply to the designer
persona — use the designer agent as configured):

1. **D1 — author the seven document changes.** Outcome: the PR-ready docs
diff. Builds on: design-notes.md (read-only source). Hands to: PR open.
Completed when: all seven items exist, `git grep -l "prisma-composer-state"
docs/` shows no doc presenting the workspace store as current outside
ADR-0009, and the ADR index lists 0033.
108 changes: 108 additions & 0 deletions .drive/projects/state-under-branch/slices/per-branch-state/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# S2 — Per-branch state bootstrap + destroy ordering

Implements [design-notes.md](../../design-notes.md) § The design, exhaustively.
The design doc is binding: where it specifies an algorithm step, message
string, failure semantic, or "unchanged" surface, the implementation follows
it without reinterpretation. Deviations discovered necessary during execution
go back through discussion mode, not into the diff. Requires S1 merged
(ADR-0033 exists).

## At a glance

One PR across two packages:

- `packages/1-prisma-cloud/0-lowering/lowering/src/state/bootstrap.ts` —
replace workspace-project discovery with branch-scoped database
find-or-create (design-notes § Bootstrap, steps 1–6, including exact error
and log messages and the SDK-types fallback rule).
- `.../state/layer.ts` — `prismaState()` reads `PRISMA_PROJECT_ID`
(required) + `PRISMA_BRANCH_ID` (optional); `workspaceId` option deleted;
bootstrap-error step strings updated.
- New `deleteStateDatabase({ projectId, branchId? })` exported from lowering
(design-notes § Destroy ordering, steps 1–4; ownership verification before
deletion is mandatory).
- `packages/0-framework/3-tooling/cli/src/main.ts` — destroy tail gains the
state-delete step with the asymmetric failure semantics (stage: throw
`CliError`; production: warn-and-continue).
- `schema.ts`, `lock.ts`, `service.ts`, `errors.ts`, `transient.ts`,
resource lowering, `ensure-containers` deploy path: **unchanged** (the
review should verify the absence of changes here as much as the presence
elsewhere).

## Coherence rationale

One reviewer, one sitting: the diff is one algorithm swap plus one new
operation plus two-line-order changes in the CLI, all specified in a single
design section, with the test rework tracking the same seams. Rollback is one
revert (no schema or state migration involved — the store format is
unchanged).

## Scope

**In:** the code above; test rework (below); `pnpm run deploy`-based live
verification of the DoD scenarios that don't need PDP answers.
**Deliberately out:** docs (S1); migration tooling (manual cutover is
documented); any lowering/provider change; anything in § Successor project.

## Pre-investigated edge cases

(From the design discussion — knowledge the implementer's grep would not
surface.)

- ~~The e2e noop assertion greps deploy output for bare create/update verbs.~~
**False — it no longer exists** (deleted with the `makerkit-hello` example;
nothing in `.github/`, `scripts/`, `test/`, or `examples/` greps for this).
Follow design-notes' log phrasings anyway, because they read well and match
the surrounding code — but do not repeat the claim that a check enforces it.
- PDP allows duplicate names; a user database named `prisma-composer-state`
on the same branch must be skipped on adopt and left alone on delete
(squatter verdict), never adopted by name, never deleted by name.
- A freshly provisioned database can refuse connections for minutes — the
existing 2-minute migrate retry schedule must survive the rework untouched.
- `region: 'inherit'` and body `branchId` may be missing from the generated
SDK types even though the API supports them — fallback is create-then-PATCH
(mirroring `postgres/Database.ts`); a stale region union means updating the
SDK dependency, never hard-coding a region literal.
- Destroy retry semantics: every crash window between alchemy exit 0 and
container deletion must converge on re-run (design-notes documents each
window; the tests assert the two interesting ones).

## Slice-specific done conditions

- `bootstrap.test.ts` reworked to the new discovery (branch resolution,
candidate verdicts, squatter failure, default-branch-missing failure), and
`main`/`ensure-containers` tests cover the destroy tail ordering + both
failure semantics and idempotent re-run.
- Live round-trip per Project-DoD items 2–4 (named-stage zero-residue,
production state on default branch, Console-delete-then-redeploy) executed
against the dogfood workspace and evidence recorded in the PR. The
production `deleteAppProject` leg may be waived pending OQ-2 with an
explicit note.
- `git grep STATE_PROJECT_NAME` returns nothing.

## Dispatch plan

Sequential; implementer subagents on Sonnet-4.6-mid, reviewer on Opus-4.8-mid
(operator's standing rule).

1. **D1 — bootstrap rework in lowering.** Outcome: `bootstrapStateConnection`
takes `{ projectId, branchId? }` and implements design-notes § Bootstrap
steps 1–6; `prismaState()` env contract changed; `STATE_PROJECT_NAME`
machinery deleted; `bootstrap.test.ts` reworked and green. Builds on: S1's
merged ADR. Hands to: a lowering package whose deploy path is fully
per-branch, destroy path not yet wired. Completed when: package tests
green; `git grep STATE_PROJECT_NAME` empty; no changes outside
`state/`.
2. **D2 — `deleteStateDatabase` + CLI destroy tail.** Outcome: the new
operation (design-notes § Destroy ordering steps 1–4) exported from
lowering; `main.ts` destroy tail ordered stage/production with asymmetric
failure semantics; CLI tests cover ordering, both failure modes, and
no-op-on-retry. Builds on: D1's bootstrap-shaped discovery (shared
resolution helpers). Hands to: feature-complete code. Completed when: CLI +
lowering tests green; the only `main.ts` diff is the destroy tail.
3. **D3 — live verification + review evidence.** Outcome: the three live DoD
scenarios executed against the dogfood workspace (credentials per
memory: copy `makerkit/.env`, `pnpm run deploy`), transcripts/evidence in
the PR body; full-repo checks green. Builds on: D2. Hands to: PR open.
Completed when: evidence for each scenario is recorded, or a scenario is
explicitly waived with the OQ-2 note.
Loading