feat(prisma-cloud): resolve the deploy target by project logical id - #230
Conversation
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 39 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (4)
Comment |
commit: |
31043c8 to
146b410
Compare
The module name is the project logicalId and every node address is that node logicalId — the identity the topology submits. Project-level resolution is PR #230; the per-resource stamping waits on the platform columns. Records the two hazards: the wire-field rename (slug → logicalId) is free only while #4885 and #230 are both unmerged, and the lowering-address root-segment question must be verified against the topology addresses before stamping, or the join fails on a prefix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Kristof Siket <siket@prisma.io>
146b410 to
2916c4a
Compare
…ucture Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Kristof Siket <siket@prisma.io>
Signed-off-by: Kristof Siket <siket@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@coderabbitai review |
|
|
@coderabbitai review |
|
|
@coderabbitai review 🥺 |
|
|
Review pass (Claude Code, time-boxed)Checked against the diff on branch resolveProject — logical-id precedence and fallback parity Create path — logicalId field and 409 scoping One design note (not a blocker): e2e script — cleanup, uniqueness, secrets Workflow wiring Tests "slug" vocabulary Claude Code — time-boxed low-effort review pass |
Signed-off-by: Kristof Siket <siket@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@coderabbitai approve (it has been rate limited for hours so i did a |
|
✅ Action performedComments resolved and changes approved. |
* docs(drive): shape the build-reporting project Design review of the brief against the platform contract in pdp-control-plane PR #4855 and the Composer deploy pipeline, plus the project spec, plan and design notes. The review changed the design in four ways: the workspace comes from the token so PRISMA_WORKSPACE_ID is not part of the contract; commitSha and branchName are required and Composer reads no git today; the SDK has none of the builds endpoints yet; and end-of-run resource reporting could only ever cover three of the eight platform resource types, so reporting is intercepted at the state layer instead. A fifth finding was handed to the author of #4855: the build anchors are settable only at creation, which no column constrains, so Composer cannot attach a build to the project it resolves partway through a deploy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * feat: report every deploy to Prisma Cloud as a Build Composer is the first external reporter against the platform build API (pdp-control-plane #4855). A deploy now records that it happened, how far it got, how it ended, and which platform resources it touched, and can emit its outcome as JSON for the Prisma GitHub Action. Three parts: The run report. `--report <path>`, or PRISMA_COMPOSER_REPORT_FILE, writes a versioned JSON file carrying the app, its nodes and entities, preview URLs, and the failure cause. Written on the failure path too, where the existing internal summary does not exist. Distinct from that summary, which stays a private per-run carrier the parent deletes. The build lifecycle. A new `reporter` hook on ExtensionDescriptor, driven by the CLI: opened after the graph loads and before containers resolve, so a bootstrap failure that orphans a project (#103) is still recorded; anchored once the project and branch exist; finished on every exit path including a thrown defect and a caught signal. It joins PRISMA_BUILD_ID when the Action supplies one, and otherwise creates the build itself — `ci` with a run identity under GitHub Actions, `cli` elsewhere. Phase is always `deploy`: Composer never builds the user code (ADR-0005). Resource reporting. The state store reports each resource as it lands, mapping the seven Prisma Cloud Alchemy resources onto the platform types. The state store is the interception point because the descriptors\x27 entity vocabulary is three kinds wide against eight platform types, and `deployment` — the one that makes the platform maintain the build link — is not an entity at all. Reports fire without blocking the apply and are drained before the deploy lease is released. Reporting never fails a deploy. Every call warns and returns instead of throwing, and the CLI swallows anything a reporter still manages to throw. Two placement constraints shaped this. The framework may import nothing but external dependencies, so reporting reaches the CLI through the extension seam. The extension package may read no environment and import no node builtin (its invariants 4 and 5), so the session lives in lowering and the extension keeps a five-line adapter. Not yet verifiable end to end: the platform routes are unmerged and the SDK carries none of them, so the three calls are hand-written for now against the same token and origin, to be deleted when the SDK ships them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * refactor(prisma-cloud): move build reporting onto the shipped SDK The builds API is in production: pdp-control-plane #4853, #4850 and #4855 all merged, and @prisma/management-api-sdk 1.60.0 carries every endpoint. So the temporary hand-written client is gone and the three calls go through the same generated client as every other call in this package. Every request and response shape is now derived from the SDK\x27s generated `operations` type rather than restated. A hand-kept copy of a contract someone else owns drifts silently; a derived one breaks the build. The derivation was checked with a compiled probe in both directions, since a type that collapses to `never` or widens to `any` typechecks just as quietly as a correct one. The pin moves to ^1.60.0, which also closes a gap that predates this work: the hosted-state lease and scope endpoints were missing from the client too, so this package never typechecked cleanly. It does now. The anchor amendment landed in full — the anchors are fill-only, verified against what the build already carries, 409 on a genuine change — and it gained `deployedUrl` on the same terms. So a build now also records the app it deployed and where it can be reached, but only when the run deployed exactly one compute service: those columns hold one value each, and picking a service arbitrarily would imply it was the app\x27s address. Multi-service apps get neither and lose nothing, since every service is reported through the resources endpoint regardless. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * feat(cli): --build-id joins a deploy record CI already created PRISMA_BUILD_ID was the only way to hand Composer an existing build, which suits a runner that exports one id for a whole job but not a workflow that names a different record per step. `--build-id` adds the other channel and wins over the variable, because a flag passed for one step means that step. An empty value counts as unset on both — that is how a shell spells "no value", and mistaking it for an id would report into nothing. The framework field is `ReportBeginInput.reportId`, deliberately not `buildId`: core has no idea what a Build is, and the id is opaque to it. The flag keeps the platform\x27s own noun because that is what a user copies out of their CI, and nothing else on this CLI takes a build id — so it cannot be confused with the build adapter that produces a service bundle (ADR-0005), even though both are spelt "build". Deploy only; destroy has no record to join. Also records what the platform does and does not do about run identity, having checked it: `sourceEventIdForRun` owns the dedup key, so two reporters of one CI run converge on a single Build — but the platform reads no GitHub environment itself. Deriving the identity stays the reporter\x27s job, which means Composer and the Action must derive it identically or one run produces two builds. Passing an id down side-steps that entirely, which is the reason this flag matters. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * refactor: drop the anchor vocabulary; correct two outdated platform claims The term came from an older platform doc and is outdated (operator, 2026-08-13): a Build's required scope is its workspace, from the token; projectId/branchId/appId are ordinary optional foreign keys that narrow the Console's views. The hook is now `attach`, the extension option `refsOf`, and nothing says anchor. Two design-note claims corrected against today's pdp main: the Console lists builds at workspace level too (#4860), so a build without a project reference is visible, not lost; and the git webhook now converges with a CI run's build on the same sourceEventId (#4877), superseding the note that said the two key spaces never meet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(drive): record the settled topology-persistence design Outcome of the design discussion on the platform resource model: the platform holds the branch's full graph — nodes, ports, edges, module boundary forwarding included — as three generic record types under the Branch, submitted as one replace-set pre-apply, keyed by config address with no platform ids in the payload (the platform joins to its typed rows on branch/kind/key at read). Build Job stays a pure run journal; the two are deliberately decoupled. Rejected alternatives and the Composer-side follow-up slices are recorded with the reasoning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(drive): rewrite the project docs for a context-free implementer Fresh-eyes pass at the operator's direction: lead with the decision, state the design and name the requirements it satisfies, end with the alternatives, and stop making the reader reverse-engineer the design from functional requirements. topology-design.md is rewritten as the implementation handoff: a vocabulary table for readers new to either codebase, a precise schema (endpoint directionality, the $out convention for anonymous resource outputs, per-consumer-endpoint uniqueness), a worked example, the exact flatten algorithm, and the pipeline position of the write. The rewrite also fixes two things review surfaced: the join-by-key rule silently required typed rows to carry the full config address (now a named platform change, configKey), and the write cannot precede container resolution as previously implied, because there is no Branch row to write under until containers resolve. spec.md now opens with the shipped design and maps each requirement to what satisfies it; decision ids are unchanged, ordered logically instead of chronologically, and the stale pre-amendment contract claim is gone. design-notes.md is bannered as the chronological log it is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(drive): the canonical topology handoff moved to pdp-control-plane The implementer works in pdp-control-plane, so the spec now lives there under its projects/ convention (PR #4892); this copy is the mirror kept for the Composer-side work items. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(drive): mirror the logicalName decision into the topology spec The join column is logicalName: the declaring node's full topology address stored verbatim, joined by string equality. Identifier taxonomy: id (physical, minted), logicalName (declared identity in the logical namespace the topology maps), displayName (presentation). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(drive): collapse the topology mirror into a pointer One canonical home for the design (pdp-control-plane projects/branch-topology/); the composer copy kept drifting. Composer's own work items move to this project's plan. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(drive): record the logicalId adoption direction for Composer The module name is the project logicalId and every node address is that node logicalId — the identity the topology submits. Project-level resolution is PR #230; the per-resource stamping waits on the platform columns. Records the two hazards: the wire-field rename (slug → logicalId) is free only while #4885 and #230 are both unmerged, and the lowering-address root-segment question must be verified against the topology addresses before stamping, or the join fails on a prefix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(drive): the address form is settled — bare of the root prefix Verified in core: root-scope children get unprefixed addresses; the root node address is its own name, which is the Project logicalId. The verify-later note is replaced by the fact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(drive): the live deploy is verified — definition of done met Build bld_cqdzjjlja99nmcd4f27bn69g in the dev workspace: right source, phase, state, branch, commit; project attached through the fill-only PATCH; appId/deployedUrl correctly withheld for a two-service app; 21 resource rows including both deployments. Records the two non-blocking observations: the Project has no resource row on the hosted flow, and branchId stays null on default-stage deploys. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(drive): the status header matches the verified DoD Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * fix: address the CodeRabbit review — bounded reporting, enforced contracts Every reporting request now carries a 10s deadline (AbortSignal.timeout on each SDK call), so a hung platform connection can stall a deploy by at most that. The resource drain gains its own 15s backstop: a report that never settles is abandoned with a warning instead of holding the state-layer finalizer — and with it the deploy lease — open forever. The deadline is injectable, and the new test proves the abandon path fires rather than merely that the drain is pending. The reporter now enforces its own documented never-throws contract instead of borrowing it from the CLI wrappers: begin catches everything into a warning and no session; finish catches the terminal PATCH the same way. New coverage drives both with a rejecting BuildsApi. Docs and tests from the same review: the run-report fixture is typed `satisfies DeploymentSummary` so shape drift fails compilation; the .drive documents' repo-relative links resolve from their own directory; and the two claims superseded by the shipped amendment and the live verification are marked as such instead of contradicting the status header. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * test: the git-identity test builds its own repo instead of borrowing the checkout CI checks out pull requests at a detached HEAD, where "no branch" is the designed answer — so a test asserting the HOST checkout resolves a branch failed there while passing on any developer machine. It now creates a temp repository with a named branch and asserts against that, and a new case pins the detached-HEAD behaviour explicitly: no branch, no identity. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> --------- Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
What changes
`resolveProject` in `packages/1-prisma-cloud/0-lowering/lowering/src/container.ts` previously found a project by matching its display name against the Composer module name, then adopted the oldest match when duplicates existed.
This PR changes the resolution order:
Why display names were the wrong key
Display names are mutable and not guaranteed to be unique within a workspace. A user can rename a project after its first deploy, causing the next deploy to create a new project instead of targeting the existing one. The logical id is a module-name-format identifier (same constraints as Composer's `module()` name), set once at creation and tied to the workspace, which makes it a stable deploy identity.
Backward compatibility
The platform does not return a `logicalId` field on projects today. With the current API, every project has `logicalId: null` or the field is absent, so the logical id match finds nothing, the name fallback runs, and behavior is byte-identical to what was there before. Once the platform ships the `logicalId` field, new projects get the stable identity automatically -- no deploy-side changes needed.
Logical id on create
`POST /v1/projects` now includes `logicalId: appName` in the body. Today's API ignores unknown fields, so this is a no-op against the current platform. Once the platform ships `logicalId` support on the create endpoint, the logical id is set on the first deploy.
409 conflict surface
A `409` from project create means the requested name is already owned by a different project in the workspace. The error message tells the user to rename the Composer module or free the name. Silently adopting the conflicting project is not safe -- it would deploy into someone else's project.
Generated with Claude Code
E2E proof
scripts/logical-id-resolution.tsruns in thelogical-id-resolutionCI job (wired topull_requestine2e-deploy.yml).Scenario 1 — rename-proof adoption: creates a project with a unique logical id, renames its display name to something unrelated, then calls
resolveContainerwith the original logical id. The test asserts the resolver returns the original project id and that no new project was created. Display-name matching alone would miss the renamed project and create a duplicate, so only a successful logical-id match can pass this assertion.Scenario 2 — duplicate rejection: attempts a second
POST /v1/projectswith the same logical id. The test asserts the platform returns 409, proving uniqueness is enforced at the API level.Cleanup runs in a
finallyblock; theresolverprefix is also covered by the sweep in the last CI job so failed runs do not leak projects.