Skip to content
Merged
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
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.

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

## Summary

**One slice** — ADR-0034 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-0034 + documentation corrections — DONE, APPROVED

ADR-0034 ("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): decouple the CLI from Prisma Cloud

Operator-directed 2026-07-17, after PR #113's review found Prisma Cloud
semantics in the framework domain. The pragmatic call for #113 was to ship the
`teardown` hook mirroring `preflight` — pattern-consistent, no new concept —
and do the real decoupling separately. That project's scope, from the sites
that leak today:

- `core/app-config.ts`: `PreflightInput`/`TeardownInput` carry
`projectId`/`branchId` — replace with a target-owned opaque context
produced by container resolution and passed through core untyped.
- `cli/ensure-containers.ts`: Project/Branch resolution via the Management
API — the "known debt" named by `architecture.config.json`'s
cross-domain exception; ADR-0017's config-driven model absorbs it (the
target's descriptor supplies the container-resolution step).
- `cli/run-alchemy.ts`: sets `PRISMA_PROJECT_ID`/`PRISMA_BRANCH_ID` on the
child — becomes target-owned env the extension contributes.
- Exit criterion: the `crossDomainExceptions` entry for `cli → lowering` is
deleted and `lint:deps` still passes.

## 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-0034 + 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-0034-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-0034 (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-0034.
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-0034 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.
Loading
Loading