Skip to content

feat(cli): programmatic control API — @prisma/composer/control (TML-3174) - #205

Merged
wmadden merged 41 commits into
mainfrom
tml-3174-composer-programmatic-deploy-api
Aug 7, 2026
Merged

feat(cli): programmatic control API — @prisma/composer/control (TML-3174)#205
wmadden merged 41 commits into
mainfrom
tml-3174-composer-programmatic-deploy-api

Conversation

@wmadden-electric

@wmadden-electric wmadden-electric commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Composer's deploy pipeline becomes drivable in-process: this PR adds @prisma/composer/control, four typed operations — deploy, destroy, dev, log — with structured inputs and results, and re-points the prisma-composer CLI to be a thin renderer over them.

import { deploy } from '@prisma/composer/control';

const result = await deploy({ entry: 'module.ts', stage: 'feat-auth' });

if (result.outcome === 'deployed') {
  for (const node of result.summary?.nodes ?? []) console.log(node.address, node.entities);
} else {
  report(result.failure.message);   // discriminated: invalid-input | pipeline | execution | unsupported-platform
}

No argv, no console output from the operations, no process.exit — failures are values a host can branch on. The full design rationale is ADR-0043 (in this diff).

How it's built up

  • One implementation, two surfaces. The operations are the CLI's own orchestration, extracted; the CLI commands now parse → call → render. The proof is src/__tests__/run.test.ts: byte-identical to main and green, so the command-line behavior demonstrably did not change.
  • One module per operation. operations/{deploy,destroy,dev,log}.ts each hold their input types, result types, and the operation function; shared failure/deps types live in shared.ts. Each module is import-light and lazily imports its executor — importing ./control executes nothing (pinned by a poisoned-import test), and a dependency tree that can't load the deploy stack surfaces as a structured pipeline failure, never an import-time crash.
  • The mechanism stays out of the contract. Deploys still execute via the spawned alchemy child and a generated stack file; that's a current mechanism, not a promise. Spawn-shaped details (exit code, stack-file path, reproduce command) ride in an optional diagnostics object; the child⇄parent summary protocol lives whole in deployment-summary.ts with a per-run unique result file; nothing mechanism-shaped is exported.
  • No test seams in the public API. Published input types carry no dependency-injection field; the CLI adapters and tests use in-package *WithDeps variants that the shims don't re-export.
  • dev returns a session handle (endpoints, stop(), closed, events) and never touches process signal handlers — the host owns signals. log returns an AsyncIterable that terminates promptly on break/abort, with a bounded drop-oldest queue that reports drops as an event.

Review history

Two full rounds are already folded in: an independent design+code review plus @wmadden's comments and the CodeRabbit triage (see the round-2 comment for the thread→commit map). Highlights fixed along the way: deploy()/dev() rejection paths became failure results, the log() early-break hang, result-file collisions between concurrent runs, and the per-operation effect preflight was deliberately removed — broken trees now diagnose through the ordinary failure path, and only the CLI's own bin.ts start-up check remains.

Alternatives considered (short form; ADR-0043 has the full reasoning)

  • Hosts shell out to prisma-composer: couples callers to output formatting and turns errors into string parsing.
  • A new workspace package: everything the operations need lives in @internal/cli; only packages/9-public/ publishes — a boundary with nothing on the other side.
  • Scraping the deploy summary from child stdout: stdout is the user's live view; the result file is the narrowest channel that survives it.

Known issues (pre-existing, not this PR)

  • check-npm-effect-resolution is red on main: alchemy's floating ranges resolve effect@4.0.0-beta.104 against the repo's beta.103 pin. Needs a pin bump in a separate change.
  • Cloud e2e and the local-dev integration scripts run in CI only.

Refs: TML-3174

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Summary by CodeRabbit

  • New Features

    • Added a typed programmatic control API for deploy, destroy, dev, and log operations.
    • Added structured results, deployment summaries, lifecycle events, service endpoints, and streamed logs.
    • Added development session management, cancellation, retries, and file-watching error reporting.
    • Added consistent structured error codes, remediation details, and improved CLI exit handling.
  • Documentation

    • Added guides and architecture decisions covering programmatic control operations and error handling.
  • Tests

    • Expanded coverage for operations, deployment summaries, imports, failures, streaming, and lifecycle behavior.

Walkthrough

Added the @prisma/composer/control API with typed deploy, destroy, dev, and log operations. Added shared executors, structured failures, deployment-summary file transfer, development sessions, and merged log streaming. Updated the CLI to delegate to these operations. Added package exports, build entries, path aliases, integration and operation tests, architecture metadata, and documentation.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding the programmatic control API under @prisma/composer/control.
Description check ✅ Passed The description directly explains the new typed control operations, CLI delegation, structured results, and supporting design changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-3174-composer-programmatic-deploy-api
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch tml-3174-composer-programmatic-deploy-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/0-framework/3-tooling/cli/src/dev/run-dev.ts`:
- Around line 70-72: Update the `converge-failed` handler in the dev watch-loop
switch to read the emitted `stackFilePath`, `reproduceCommand`, and `cwd`
payload and print the stack path and reproduce command, matching the initial
converge-failure output. Preserve the existing message that the running app is
untouched and still watching, and use `cwd` when formatting or executing the
reproduction command as established by the startup failure path.
- Around line 109-128: Update runDev around the signal registration and
session.closed wait to use try/finally, removing the finish listener for both
SIGINT and SIGTERM in the finally block after session.closed settles or exits
early. Preserve the existing finish behavior while ensuring each runDev
invocation does not leave stale listeners behind.

In `@packages/0-framework/3-tooling/cli/src/main.ts`:
- Line 229: Guard the rethrow in the OperationFailure handling path the same way
as the existing line-231 logic: only throw failure.cause when it is an Error,
otherwise create or propagate an Error using failure.message so the formatter
always receives an Error. Keep the surrounding failure handling unchanged.

In `@packages/0-framework/3-tooling/cli/src/operations/execute-deploy-destroy.ts`:
- Around line 254-257: Update the deployment flow around resultFilePath and
readDeploymentSummary to use a unique per-run result filename, preventing
concurrent deploy() calls sharing or deleting each other’s reports. Wrap the
execution and summary-read lifecycle in a try/finally so the unique result file
is removed on success and every execution failure path, including returns before
readDeploymentSummary.

In `@packages/0-framework/3-tooling/cli/src/operations/execute-dev.ts`:
- Around line 266-271: Update the executeDev startup error handler to clean up
all partially started resources before returning the failed outcome. Declare a
watchHandle before the try, assign it from startWatch, then in the catch stop
the watcher when present and call stopServices() for every attachment,
preserving the existing failure response afterward.
- Around line 138-172: Update the converge helper to accept a pipeline argument
and use that argument for writeDevStackFile instead of the outer pipeline. Pass
the initial pipeline to the first converge call, and replace the watch-loop’s
duplicated stack-writing and runAlchemy logic with converge(rePipeline),
preserving the existing return and failure handling.
- Around line 206-240: Update the rebuild callback in startWatch to maintain a
single in-flight rebuild promise, preventing overlapping runPipeline,
writeDevStackFile, and deploy operations; queue or skip subsequent triggers
until the current rebuild completes. Track the dev session’s stopping/stopped
state and cancel or skip callbacks that begin after shutdown starts, including
already scheduled work. Before each writeDevStackFile, deploy/converge action,
and onEvent call, recheck that state so no stack writes, deploys, or events
occur after stopping begins.
- Around line 248-262: Update the shutdown logic in stop so host-supplied
onEvent callbacks cannot interrupt cleanup or leave closed unresolved: isolate
both stopping and stopped event calls from thrown exceptions, and place
resolveClosed() in a finally block that always executes after
attachment.stopServices() processing. Preserve the existing stopping guard and
closed return behavior.

In `@packages/0-framework/3-tooling/cli/src/operations/execute-log.ts`:
- Around line 38-56: Bound the shared queue used by the attachment pumps so it
cannot grow without limit when the consumer is slow. Update the queue production
and consumption flow around the attachment `logs` pumps and `notify` to apply
backpressure once the configured capacity is reached, ensuring producers resume
as items are consumed; alternatively, explicitly drop oldest entries and report
each drop through `onEvent`.
- Line 98: Update the local services declaration in the execute-log operation to
use readonly DevEndpoint[] instead of an inline object shape, reusing the
existing DevEndpoint symbol so changes to that type are enforced here.
- Around line 16-24: Remove toCliError and update the execute operation’s inner
catch blocks to rethrow the original error instead of wrapping it, so the outer
failure retains the original value as failure.cause. Keep failureMessage for
deriving the failure message only, preserving the existing message behavior.
- Around line 67-83: Update the async log generator’s cleanup around the
`finally` block to derive an internal abort signal from `input.signal`,
preserving propagation of the caller’s abort state. Abort the internal signal
before awaiting `Promise.all(pumps)` so early termination through `break`,
`return`, or `lines.return()` causes all pumps to exit and cleanup to resolve.
- Around line 113-148: Ensure every attachment created in executeLog is cleaned
up on pipeline failures, empty-service early returns, and invalid input returns;
retain them through normal log consumption. Update the attachment lifecycle
around target.attach and the services validation branches to invoke the existing
cleanup API, or add a close/dispose/detach mechanism (alternatively propagate
input.signal when creating subscriptions) so abandoned LocalTargetAttachment
followers cannot remain active.

In `@packages/0-framework/3-tooling/cli/src/render-deployment.ts`:
- Around line 149-152: Update deploymentReport’s summary-writing block to create
the parent directory for DEPLOYMENT_RESULT_FILE_ENV before writing, using
node:path to derive it, and wrap directory creation and fs.writeFileSync in
error handling that swallows failures. Preserve successful summary serialization
while ensuring report-hook write errors never escape.

In `@skills/prisma-composer/SKILL.md`:
- Around line 693-697: Update the dev and log result documentation in SKILL.md
to describe their outcome discriminants, matching the existing deploy
documentation. Identify the success and failure branches of DevStartResult and
LogResult, and state that session/services/lines are available only for the
successful outcome so callers handle failures before destructuring.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 435a7704-0c0d-4927-9cc6-6cbaeb5474fa

📥 Commits

Reviewing files that changed from the base of the PR and between dae51eb and f8243e7.

📒 Files selected for processing (26)
  • architecture.config.json
  • docs/design/10-domains/deploy-cli.md
  • docs/design/90-decisions/ADR-0043-the-control-subpath-is-the-programmatic-deploy-surface.md
  • docs/design/90-decisions/README.md
  • docs/guides/deploying.md
  • packages/0-framework/3-tooling/cli/package.json
  • packages/0-framework/3-tooling/cli/src/__tests__/render-deployment.test.ts
  • packages/0-framework/3-tooling/cli/src/dev/run-dev.ts
  • packages/0-framework/3-tooling/cli/src/exports/control.ts
  • packages/0-framework/3-tooling/cli/src/log/run-log.ts
  • packages/0-framework/3-tooling/cli/src/main.ts
  • packages/0-framework/3-tooling/cli/src/operations/__tests__/operations.test.ts
  • packages/0-framework/3-tooling/cli/src/operations/execute-deploy-destroy.ts
  • packages/0-framework/3-tooling/cli/src/operations/execute-dev.ts
  • packages/0-framework/3-tooling/cli/src/operations/execute-log.ts
  • packages/0-framework/3-tooling/cli/src/operations/operations.ts
  • packages/0-framework/3-tooling/cli/src/operations/results.ts
  • packages/0-framework/3-tooling/cli/src/render-deployment.ts
  • packages/0-framework/3-tooling/cli/tsdown.config.ts
  • packages/9-public/composer/package.json
  • packages/9-public/composer/src/exports/control.ts
  • packages/9-public/composer/tsdown.config.ts
  • scripts/check-npm-effect-resolution.mjs
  • skills/prisma-composer/SKILL.md
  • test/integration/test/control.deploy.test.ts
  • tsconfig.depcruise.json

Comment thread packages/0-framework/3-tooling/cli/src/dev/run-dev.ts Outdated
Comment thread packages/0-framework/3-tooling/cli/src/dev/run-dev.ts
Comment thread packages/0-framework/3-tooling/cli/src/main.ts Outdated
Comment thread packages/0-framework/3-tooling/cli/src/operations/execute-deploy-destroy.ts Outdated
Comment thread packages/0-framework/3-tooling/cli/src/operations/execute-dev.ts
Comment thread packages/0-framework/3-tooling/cli/src/operations/execute-log.ts
Comment thread packages/0-framework/3-tooling/cli/src/operations/execute-log.ts
Comment thread packages/0-framework/3-tooling/cli/src/operations/execute-log.ts
Comment thread packages/0-framework/3-tooling/cli/src/render-deployment.ts Outdated
Comment thread skills/prisma-composer/SKILL.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@docs/design/90-decisions/ADR-0043-the-control-subpath-is-the-programmatic-deploy-surface.md`:
- Line 5: Update the control subpath contract near the description of the four
operations to distinguish CLI rendering from inherited Alchemy output: state
that operations do not render CLI output themselves but may expose Alchemy
output when configured with stdio: 'inherit'. Keep the existing
structured-input/output and no-argv/process.exit guarantees unchanged.
- Line 44: Update ADR-0043 to explicitly define the programmatic surface’s cwd
contract: deploy(), destroy(), and dev() must use the target app directory,
including when cwd is omitted, before calling checkEffectResolution(cwd). Align
the examples and result-type description with this target-directory behavior,
including where .prisma-composer/ and .alchemy state are located.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: efd36683-40fa-44dc-902e-e6c94d398a78

📥 Commits

Reviewing files that changed from the base of the PR and between f8243e7 and 6025239.

📒 Files selected for processing (1)
  • docs/design/90-decisions/ADR-0043-the-control-subpath-is-the-programmatic-deploy-surface.md

Comment thread packages/0-framework/3-tooling/cli/src/operations/execute-deploy-destroy.ts Outdated
Comment thread packages/0-framework/3-tooling/cli/src/operations/execute-deploy-destroy.ts Outdated
Comment thread packages/0-framework/3-tooling/cli/src/operations/results.ts Outdated
Comment thread packages/0-framework/3-tooling/cli/src/operations/results.ts Outdated
@wmadden-electric

Copy link
Copy Markdown
Contributor Author

Round 2 pushed (12 commits since the last review): a full second-round review (architect + principal-engineer passes, CodeRabbit triage, and @wmadden's comments) drove this batch.

@wmadden's review comments:

  • results.ts grouped by type / ascii-art markers → restructured to one module per operation (operations/{deploy,destroy,dev,log}.ts, each holding its input types, result types, and the operation; shared failure/deps types in shared.ts) — 0e6a112
  • executeDeployOrDestroy names its callersrunStackPipeline(action, …), now returning a proper discriminated union — 0e6a112
  • "What are these IDs?" (1–6 step comments) → the numbering referenced the deleted main.ts sequence; stripped from all executors, rationale prose kept — 0e6a112
  • (follow-on ruling) deps removed from the published API entirely — public input types carry no injection seam; in-package *WithDeps variants serve the CLI adapters and tests — 7458963

Contract neutralization (keeping the execution mechanism out of the exported types): spawn-shaped fields (exitCode, stackFilePath, reproduceCommand, cwd) moved to an optional diagnostics object; DEPLOYMENT_RESULT_FILE_ENV no longer exported; the summary protocol now lives in one deployment-summary.ts; ADR reworded to current-mechanism framing — 628428d, 7234f36

Probe-confirmed contract breaks fixed: deploy()/dev() could reject instead of returning failures (unprotected stack write / first converge) — f9a2244; log() hung forever on early break — fixed with prompt termination, a bounded drop-oldest queue with a lines-dropped event, and post-end event silencing — 218bab4

CodeRabbit items: 10 accepted and fixed across ea56d3c (dev session: host onEvent throw can't wedge closed; post-attach failures roll back services + watcher; signal listeners removed after close; converge-failed hints rendered), 218bab4 (log cause preservation, emulator retry parity), f7fec8c (unique per-run result file deleted after read; guarded best-effort report-hook write; real-child round-trip test), 628428d (main.ts rethrow guard), 7234f36 (SKILL outcome docs, ADR console wording). 3 rejected (converge-helper extraction — style; watch debounce — pre-existing behavior; DevEndpoint naming — superseded by ServiceEndpoint rename), 1 obsolete (preflight analysis — the effect-defense shrink removed that code), 1 not-applicable (attachment disposal — the contract has no disposal member).

Also: unsupportedunsupported-platform; DevSession contract now pinned by tests (stop idempotence, closed settling, zero signal-handler registration); a fresh-process import test enforces the control entry's import-lightness. Gates: 180 cli tests green, run.test.ts byte-identical to main, typecheck/build/lint/lint:deps/cast-ratchet clean.

🤖 Generated with Claude Code

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/0-framework/3-tooling/cli/src/operations/execute-deploy-destroy.ts (1)

217-249: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Use a per-run generated stack file for concurrent operations.

writeStackFile still writes only .prisma-composer/alchemy.run.ts, and stackFileRelativePath: GENERATED_STACK_RELATIVE_PATH passes that fixed path to the Alchemy child. If two deployments/destroys target the same cwd, a later run can overwrite the generated stack before an earlier child reads it, so the earlier command can act on another run’s stack program. Generate a per-run stack path as well, and pass that path as stackFileRelativePath.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/0-framework/3-tooling/cli/src/operations/execute-deploy-destroy.ts`
around lines 217 - 249, The deployment flow currently uses the shared
GENERATED_STACK_RELATIVE_PATH, allowing concurrent runs to overwrite each
other’s stack files. Update the stack-generation call around writeStackFile to
create a unique per-run relative stack path, use it when writing the stack, and
pass the same path as stackFileRelativePath in the deps.alchemy/runAlchemy
invocation; keep result-file isolation unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/0-framework/3-tooling/cli/src/dev/run-dev.ts`:
- Around line 54-87: Update the event switch in the onEvent handler to add a
stop-error case that logs event.message to the console with the existing [dev]
prefix, alongside the stopping and stopped cases. Preserve the current
stopped-event output while ensuring service stop failures are surfaced.

In `@packages/0-framework/3-tooling/cli/src/log/run-log.ts`:
- Around line 41-45: Update the onEvent callback to handle events with kind
`lines-dropped` by emitting a console.error warning, alongside the existing
`stream-failed` handling. Include the event’s available message or dropped-line
details in the warning.

In
`@packages/0-framework/3-tooling/cli/src/operations/__tests__/operations.test.ts`:
- Around line 1158-1186: Update the test around the async generator in “no event
is delivered after the merged iterable has ended” to assert that failLate is
defined before invoking it, ensuring the late-failure path is actually
exercised. If breaking after the first line prevents the generator from reaching
that assignment, advance the generator once more before breaking while
preserving the test’s expected empty events result.
- Around line 1125-1156: Update the test around logWithDeps to derive TOTAL from
the log module’s exported queue-bound constant, using a value comfortably above
that bound so the 20 ms stall reliably causes drops; preserve the existing
droppedTotal and conservation assertions. Export the bound from the log module
if it is not currently public.

In `@packages/0-framework/3-tooling/cli/src/operations/execute-deploy-destroy.ts`:
- Around line 314-323: Ensure every execution path in the operation that creates
resultFilePath removes the result file, including execution failures and
teardown failures, not only the successful deploy path. Reuse the existing
cleanup helper or centralize cleanup in a finally block spanning the result-file
assignment through all returns, while preserving the returned success and
failure values.

In `@packages/0-framework/3-tooling/cli/src/operations/execute-log.ts`:
- Around line 66-68: Update the emit callback in the log streaming operation to
catch and isolate errors thrown by input.onEvent?. Ensure handler failures do
not propagate through pump() or reject the public lines iterable, while
preserving the existing done guard and event delivery behavior.

---

Outside diff comments:
In `@packages/0-framework/3-tooling/cli/src/operations/execute-deploy-destroy.ts`:
- Around line 217-249: The deployment flow currently uses the shared
GENERATED_STACK_RELATIVE_PATH, allowing concurrent runs to overwrite each
other’s stack files. Update the stack-generation call around writeStackFile to
create a unique per-run relative stack path, use it when writing the stack, and
pass the same path as stackFileRelativePath in the deps.alchemy/runAlchemy
invocation; keep result-file isolation unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6608894c-11a7-47a9-a096-52bbee179c57

📥 Commits

Reviewing files that changed from the base of the PR and between 6025239 and 7234f36.

📒 Files selected for processing (24)
  • docs/design/90-decisions/ADR-0043-the-control-subpath-is-the-programmatic-deploy-surface.md
  • docs/design/90-decisions/README.md
  • docs/guides/deploying.md
  • packages/0-framework/3-tooling/cli/src/__tests__/render-deployment.test.ts
  • packages/0-framework/3-tooling/cli/src/cli-error.ts
  • packages/0-framework/3-tooling/cli/src/deployment-summary.ts
  • packages/0-framework/3-tooling/cli/src/dev/run-dev.ts
  • packages/0-framework/3-tooling/cli/src/dev/watch.ts
  • packages/0-framework/3-tooling/cli/src/exports/__tests__/control-import.test.ts
  • packages/0-framework/3-tooling/cli/src/exports/control.ts
  • packages/0-framework/3-tooling/cli/src/log/run-log.ts
  • packages/0-framework/3-tooling/cli/src/main.ts
  • packages/0-framework/3-tooling/cli/src/operations/__tests__/operations.test.ts
  • packages/0-framework/3-tooling/cli/src/operations/deploy.ts
  • packages/0-framework/3-tooling/cli/src/operations/destroy.ts
  • packages/0-framework/3-tooling/cli/src/operations/dev.ts
  • packages/0-framework/3-tooling/cli/src/operations/emulator-retry.ts
  • packages/0-framework/3-tooling/cli/src/operations/execute-deploy-destroy.ts
  • packages/0-framework/3-tooling/cli/src/operations/execute-dev.ts
  • packages/0-framework/3-tooling/cli/src/operations/execute-log.ts
  • packages/0-framework/3-tooling/cli/src/operations/log.ts
  • packages/0-framework/3-tooling/cli/src/operations/shared.ts
  • packages/0-framework/3-tooling/cli/src/render-deployment.ts
  • skills/prisma-composer/SKILL.md

Comment thread packages/0-framework/3-tooling/cli/src/dev/run-dev.ts
Comment thread packages/0-framework/3-tooling/cli/src/log/run-log.ts
Comment thread packages/0-framework/3-tooling/cli/src/operations/execute-deploy-destroy.ts Outdated
Comment thread packages/0-framework/3-tooling/cli/src/operations/execute-log.ts
@pkg-pr-new

pkg-pr-new Bot commented Aug 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@prisma/composer@205
npm i https://pkg.pr.new/@prisma/composer-prisma-cloud@205

commit: ba427be

@wmadden-electric

Copy link
Copy Markdown
Contributor Author

On the round-3 review body's outside-diff item (per-run generated stack file — .prisma-composer/alchemy.run.ts collides across concurrent same-cwd runs): ruling this out of scope for this PR, same as the round-2 watch-serialization ruling. The fixed stack path is pre-existing CLI behavior moved verbatim, and a per-run path would also change reproduceCommand semantics and the dev watch loop. Round 2 already isolated the result file, which removes the data-integrity half — summaries cannot cross runs.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/0-framework/3-tooling/cli/src/operations/execute-deploy-destroy.ts`:
- Around line 286-309: Update the non-CliError wrapping in the teardown and
container-removal catches within the deployment flow to construct CliError with
the original error object supplied as its cause via ErrorOptions, while
preserving existing CliError instances unchanged. Apply this consistently to
both extension.teardown and extension.container.remove failure paths so the
original error and cause chain remain accessible.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5cf30ad2-dd7e-42f3-bdc3-a010ea2f9445

📥 Commits

Reviewing files that changed from the base of the PR and between 7234f36 and 39a6655.

📒 Files selected for processing (5)
  • packages/0-framework/3-tooling/cli/src/dev/run-dev.ts
  • packages/0-framework/3-tooling/cli/src/log/run-log.ts
  • packages/0-framework/3-tooling/cli/src/operations/__tests__/operations.test.ts
  • packages/0-framework/3-tooling/cli/src/operations/execute-deploy-destroy.ts
  • packages/0-framework/3-tooling/cli/src/operations/execute-log.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
skills/prisma-composer/SKILL.md (1)

672-674: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the console-output contract.

The deploy engine can stream output to host stdio, as documented at Lines 697-698. Replace “no console output” with “no CLI rendering” and state that inherited deploy-engine output can still reach the host terminal.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/prisma-composer/SKILL.md` around lines 672 - 674, Update the
`@prisma/composer/control` description to replace “no console output” with “no CLI
rendering,” and explicitly state that inherited deploy-engine output may still
reach the host terminal while preserving the existing structured in-process
operation contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/design/10-domains/deploy-cli.md`:
- Around line 209-211: Update the documentation sentence in the CLI error
description to avoid claiming every structured error has a fix. State that the
renderer includes Why, Fix, and Where details when those fields are provided by
CliStructuredError, while preserving the existing error code and rendering
format description.

In
`@docs/design/90-decisions/ADR-0044-errors-are-structural-envelopes-with-dotted-namespace-codes.md`:
- Around line 86-91: Specify the text language on the error-layout code block by
changing its opening fence to use text, while leaving the block contents
unchanged.

In `@packages/0-framework/0-foundation/foundation/src/structured-error.ts`:
- Around line 4-5: The public StructuredError code contract must match
STRUCTURED_CODE_RE’s uppercase single-dot grammar. Tighten
StructuredError['code'] and update structuredError() and CliStructuredError to
validate or reject lowercase, empty-segment, and extra-dot values consistently
with isStructuredError(); add tests covering those invalid cases.

In `@packages/0-framework/3-tooling/cli/src/__tests__/load-graph.test.ts`:
- Around line 15-19: Update the test around Load(entry.root) so the caught error
is captured in a variable, then assert its LoadError.code outside the try/catch.
Ensure the test fails when Load() does not throw while preserving the expected
COMPOSE.GRAPH_INVALID assertion.

In `@packages/0-framework/3-tooling/cli/src/dev/run-dev.ts`:
- Around line 94-107: The child-status exception rendering is duplicated between
the failure handling in run-dev and renderDeployDestroyFailure in main.ts.
Export a shared renderer from main.ts, including both console.error messages and
the diagnostics.exitCode return behavior, then replace both local
implementations with calls to that renderer while continuing to use
executionDiagnostics.

In `@packages/0-framework/3-tooling/cli/src/operations/destroy.ts`:
- Line 49: Update the destroyWithDeps() executor-load failure path to provide
operation-specific wording instead of the deploy-specific message. Pass the
destroy operation name to executorLoadFailure(), or configure it to use neutral
“operation executor” wording while preserving the existing notOk() error flow.

In `@packages/0-framework/3-tooling/cli/src/operations/execute-dev.ts`:
- Around line 220-227: Update the attachment tracking in the executeDev startup
loop so each attachment is added to started before withEmulatorRetry invokes
attachment.startServices(). Remove the post-success insertion, preserving the
existing structured error handling and rollback behavior.

In `@packages/0-framework/3-tooling/cli/src/operations/log.ts`:
- Line 75: Update executorLoadFailure to accept an operation name and
interpolate it into the failure message and fix text, then pass the appropriate
operation name from each caller, including the log() and dev() flows, so each
reports its own executor.

In `@packages/0-framework/3-tooling/cli/src/operations/shared.ts`:
- Around line 94-115: Update executorLoadFailure to accept an operation name and
use it in both the failure summary and environment explanation. Update
devWithDeps, destroyWithDeps, and logWithDeps lazy-loader call sites to pass
their respective operation names, preserving existing error details and
diagnostic handling.

---

Outside diff comments:
In `@skills/prisma-composer/SKILL.md`:
- Around line 672-674: Update the `@prisma/composer/control` description to
replace “no console output” with “no CLI rendering,” and explicitly state that
inherited deploy-engine output may still reach the host terminal while
preserving the existing structured in-process operation contract.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 969e636b-2ef0-4277-ac80-f8c50d32b622

📥 Commits

Reviewing files that changed from the base of the PR and between 39a6655 and efa73aa.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (66)
  • docs/design/10-domains/deploy-cli.md
  • docs/design/90-decisions/ADR-0043-the-control-subpath-is-the-programmatic-deploy-surface.md
  • docs/design/90-decisions/ADR-0044-errors-are-structural-envelopes-with-dotted-namespace-codes.md
  • docs/design/90-decisions/README.md
  • packages/0-framework/0-foundation/foundation/package.json
  • packages/0-framework/0-foundation/foundation/src/cli-structured-error.test.ts
  • packages/0-framework/0-foundation/foundation/src/cli-structured-error.ts
  • packages/0-framework/0-foundation/foundation/src/defined.ts
  • packages/0-framework/0-foundation/foundation/src/exports/errors.ts
  • packages/0-framework/0-foundation/foundation/src/exports/result.ts
  • packages/0-framework/0-foundation/foundation/src/internal-error.test.ts
  • packages/0-framework/0-foundation/foundation/src/internal-error.ts
  • packages/0-framework/0-foundation/foundation/src/result.test.ts
  • packages/0-framework/0-foundation/foundation/src/result.ts
  • packages/0-framework/0-foundation/foundation/src/structured-error.test.ts
  • packages/0-framework/0-foundation/foundation/src/structured-error.ts
  • packages/0-framework/0-foundation/foundation/tsdown.config.ts
  • packages/0-framework/1-core/core/src/control/local-target.ts
  • packages/0-framework/1-core/core/src/graph-types.ts
  • packages/0-framework/1-core/core/src/toposort.ts
  • packages/0-framework/3-tooling/assemble/package.json
  • packages/0-framework/3-tooling/assemble/src/__tests__/assemble-services.test.ts
  • packages/0-framework/3-tooling/assemble/src/assemble-error.ts
  • packages/0-framework/3-tooling/assemble/src/assemble-services.ts
  • packages/0-framework/3-tooling/assemble/src/exports/index.ts
  • packages/0-framework/3-tooling/cli/package.json
  • packages/0-framework/3-tooling/cli/src/__tests__/check-effect-resolution.test.ts
  • packages/0-framework/3-tooling/cli/src/__tests__/cli.test.ts
  • packages/0-framework/3-tooling/cli/src/__tests__/fixtures/run-load-entry.ts
  • packages/0-framework/3-tooling/cli/src/__tests__/jsx-load-error.test.ts
  • packages/0-framework/3-tooling/cli/src/__tests__/load-config.test.ts
  • packages/0-framework/3-tooling/cli/src/__tests__/load-entry.test.ts
  • packages/0-framework/3-tooling/cli/src/__tests__/load-graph.test.ts
  • packages/0-framework/3-tooling/cli/src/__tests__/render-error.test.ts
  • packages/0-framework/3-tooling/cli/src/__tests__/run.test.ts
  • packages/0-framework/3-tooling/cli/src/__tests__/validate-stage.test.ts
  • packages/0-framework/3-tooling/cli/src/bin.ts
  • packages/0-framework/3-tooling/cli/src/check-effect-resolution.ts
  • packages/0-framework/3-tooling/cli/src/cli-error.ts
  • packages/0-framework/3-tooling/cli/src/cli.ts
  • packages/0-framework/3-tooling/cli/src/dev/run-dev.ts
  • packages/0-framework/3-tooling/cli/src/exports/control.ts
  • packages/0-framework/3-tooling/cli/src/exports/index.ts
  • packages/0-framework/3-tooling/cli/src/jsx-load-error.ts
  • packages/0-framework/3-tooling/cli/src/load-config.ts
  • packages/0-framework/3-tooling/cli/src/load-entry.ts
  • packages/0-framework/3-tooling/cli/src/log/__tests__/run-log.test.ts
  • packages/0-framework/3-tooling/cli/src/log/run-log.ts
  • packages/0-framework/3-tooling/cli/src/main.ts
  • packages/0-framework/3-tooling/cli/src/operations/__tests__/operations.test.ts
  • packages/0-framework/3-tooling/cli/src/operations/deploy.ts
  • packages/0-framework/3-tooling/cli/src/operations/destroy.ts
  • packages/0-framework/3-tooling/cli/src/operations/dev.ts
  • packages/0-framework/3-tooling/cli/src/operations/execute-deploy-destroy.ts
  • packages/0-framework/3-tooling/cli/src/operations/execute-dev.ts
  • packages/0-framework/3-tooling/cli/src/operations/execute-log.ts
  • packages/0-framework/3-tooling/cli/src/operations/log.ts
  • packages/0-framework/3-tooling/cli/src/operations/shared.ts
  • packages/0-framework/3-tooling/cli/src/pipeline.ts
  • packages/0-framework/3-tooling/cli/src/render-error.ts
  • packages/0-framework/3-tooling/cli/src/run-alchemy.ts
  • packages/0-framework/3-tooling/cli/src/validate-coverage.ts
  • packages/0-framework/3-tooling/cli/src/validate-stage.ts
  • skills/prisma-composer/SKILL.md
  • test/integration/test/control.deploy.test.ts
  • tsconfig.depcruise.json
💤 Files with no reviewable changes (1)
  • packages/0-framework/3-tooling/cli/src/cli-error.ts

Comment thread docs/design/10-domains/deploy-cli.md Outdated
Comment thread packages/0-framework/3-tooling/cli/src/__tests__/load-graph.test.ts Outdated
Comment thread packages/0-framework/3-tooling/cli/src/dev/run-dev.ts
Comment thread packages/0-framework/3-tooling/cli/src/operations/destroy.ts Outdated
Comment thread packages/0-framework/3-tooling/cli/src/operations/execute-dev.ts
Comment thread packages/0-framework/3-tooling/cli/src/operations/log.ts Outdated
Comment thread packages/0-framework/3-tooling/cli/src/operations/shared.ts Outdated
wmadden-electric added a commit that referenced this pull request Aug 7, 2026
… throws

Recorded in `started` before the call, so the existing catch stops an
attachment whose startServices came up partway and then threw
(stopServices failures are already swallowed there, so stopping a
never-started attachment is harmless). Review A-001 on #205.

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric added a commit that referenced this pull request Aug 7, 2026
executorLoadFailure gains an operation-name parameter used in
DEPS.EXECUTOR_UNLOADABLE's summary and why; deploy/destroy/dev/log each
pass their own. Also lands the partial-start rollback test for A-001.
Review A-002/A-003/A-004 on #205.

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric added a commit that referenced this pull request Aug 7, 2026
renderChildStatusHints in render-error.ts is the single implementation
of the ADR-0044 child-status exception; main.ts and run-dev.ts call it,
output byte-identical. Review A-005 on #205.

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric added a commit that referenced this pull request Aug 7, 2026
The captured-error pattern fails the test if Load() stops throwing,
instead of silently skipping the code assertion. Review A-006 on #205.

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric added a commit that referenced this pull request Aug 7, 2026
deploy-cli.md no longer promises a Fix line for every failure; the
ADR-0044 layout fence is tagged text (MD040); SKILL.md says the control
operations do no CLI rendering while inherited deploy-engine output can
still reach the host terminal.

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric and others added 11 commits August 7, 2026 16:27
deploymentReport now also writes a JSON DeploymentSummary — the
serializable projection of DeploymentResult (address + entities, no
in-process node) — to the file named by
PRISMA_COMPOSER_DEPLOYMENT_RESULT_FILE when that env var is set. The
printed report is unchanged, and nothing is written without the env
var, so the generated stack file stays byte-identical.

This is the writer half of the cross-process result contract the
programmatic deploy operation reads back (TML-3174 design §3.4).

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
deploy() and destroy() are now programmatic operations (operations/):
typed inputs, structured results, no argv, no console, no
process.exit. The pipeline orchestration (main.ts steps 0-9.75) moved
verbatim into execute-deploy-destroy.ts, reached only by dynamic
import after a structured effect-resolution preflight (TML-3158) so
the operations entry stays import-safe in a broken effect tree.

main.ts run() becomes a thin renderer: flag combinations validated
with the same CliError texts, the destroy no-state warning rendered
from the operation event, alchemy-failure hints and passthrough exit
codes unchanged. run.test.ts passes unmodified — the extraction proof.

A successful deploy now also reads back the DeploymentSummary the
alchemy child writes via PRISMA_COMPOSER_DEPLOYMENT_RESULT_FILE
(absent or malformed file = undefined summary, never a failure).

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
dev() returns a DevSession (endpoints, stop(), closed) and reports
lifecycle through onEvent — the operation never touches process
signal handlers; the CLI adapter keeps the removeAllListeners +
single-listener signal ownership and renders each event with the
exact console lines it always printed. log() returns the running
services plus a merged, address-filtered AsyncIterable of lines,
ended by the caller-owned AbortSignal; per-stream failures surface
as stream-failed events without ending the other streams.

run-dev.ts and run-log.ts become thin parse-shaped adapters over the
operations; their suites pass unchanged.

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
New `./control` subpath on @internal/cli (generated exports map
committed) and @prisma/composer (hand-maintained map, per the
published-package exception): the deploy/destroy/dev/log operations,
their input/result types, and the DEPLOYMENT_RESULT_FILE_ENV
cross-process contract. Depcruise aliases and the architecture
per-file entry for the 9-public shim keep every edge visible to the
cruiser; lint:deps is green.

Named `/control` after the plane the CLI sources already occupy —
the shim doc-comment distinguishes it from an extension's ADR-0017
`/control` entry.

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ublished tree

operations.test.ts drives deploy/destroy/log with the run.test.ts
fakes: the result-file round trip (env var passed, stale file removed
pre-spawn, malformed file = undefined summary), structured
invalid-input/pipeline/execution failures with the exact CLI
messages, destroy target discrimination and teardown-before-remove
order, the pre-pipeline no-local-deploy-state event, the merged/
filtered/abortable log stream with stream-failed events, and a seeded
effect-mismatch tree returning an effect-resolution result. Every
operation call runs inside silently(), which fails on any console
output.

control.deploy.test.ts is the slice done-condition: a consumer
outside the CLI imports @prisma/composer/control and runs deploy over
the real integration fixture, reaching the same missing-built-entry
terminal point the binary test pins — as a structured result.

check-npm-effect-resolution.mjs now also probes the adversarial
package-manager tree in-process: importing the control surface must
not crash, and deploy() must return the effect-resolution failure
with exit 0.

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
ADR-0043 records the ./control surface and the
PRISMA_COMPOSER_DEPLOYMENT_RESULT_FILE cross-process contract (plus
the index entry). The deploying guide gains a "Driving deploys from
code" section, mirrored tersely into skills/prisma-composer/SKILL.md
(user-facing-surface-changes: both in the same PR), and
deploy-cli.md § Contracts now names @internal/assemble's second
consumer as shipped rather than future.

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The endpoint merge and watch setup ran outside any try/catch, so a throw
there (e.g. withEmulatorRetry exhausting its attempts) rejected the dev()
promise instead of producing { outcome: "failed", kind: "pipeline" } as
design § 3.5 requires. The attach try block now extends through the
endpoint merge, watch setup, and session construction; the startServices
rollback is unchanged.

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The CLI adapter printed unwatchable notices before the `[dev] logs:` hint;
the shipped order was front door, hint, then unwatchable lines. Notices
received before the session is returned are now buffered and flushed right
after the hint; later ones print immediately. Every string is unchanged.

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Ground the decision in a usage example, build the narrative up from
motivation through import safety and the process-boundary contract,
and strip refactor-history framing and ticket references.

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…catch path

The per-operation checkEffectResolution preflight and the dedicated
effect-resolution failure kind were oversized for what is now a transient
upstream condition. Each operation instead wraps the lazy import of its
executor in a try/catch: on a load failure it diagnoses the target tree
with checkEffectResolution and returns a pipeline failure carrying the
fix-naming message (or the original error message when the tree is
healthy), with the import error as cause. The entry stays import-light as
a general no-import-side-effects property; bin.ts keeps its own start-up
check. Reverts the control-surface probe added to
scripts/check-npm-effect-resolution.mjs back to main.

Refs: TML-3174
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…l property

ADR-0043 no longer frames import-safety as an effect-specific contract
with its own failure kind: the entry is import-light with lazily loaded
executors, and a tree that cannot load the deploy stack surfaces as a
structured pipeline failure with a diagnostic message. Update the ADR
index line, the deploying guide, and the composer skill to the reduced
failure taxonomy.

Refs: TML-3174
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric and others added 24 commits August 7, 2026 16:27
…inputs

Per operator ruling: either it's public API or it isn't — we don't ship
production code just for tests. DeployInput/DestroyInput/DevInput/LogInput
no longer carry deps, and OperationDeps/LogDeps are gone from the
./control shims. Each per-operation module now pairs the clean public
function with an in-package *WithDeps variant (deployWithDeps, ...) that
takes the seam as a separate parameter; the executors take (input, deps,
cwd). The CLI adapters and unit tests thread RunDeps through the WithDeps
variants — run.test.ts unchanged — and the published integration consumer
(test/integration control.deploy.test.ts) keeps driving the real pipeline
with no seam at all.

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…nd-trip

The result file was a fixed path under cwd, so two deploys sharing a
working directory (two stages from one checkout, a host mid-deploy while
the CLI runs) silently corrupted each other: run B's stale-guard deleted
run A's summary and both children raced one file. The path now carries
pid + a UUID, the pre-spawn stale-guard is gone (nothing can be stale on a
unique path), and the file is deleted right after the operation reads it.

The summary protocol also gains its first covering test through a REAL
child process: the injected alchemy spawns a bun child that calls
writeDeploymentSummaryFile with the env var the operation set, and the
operation reads back exactly what the writer wrote — previously every
regression in the pair (env-var drift, report unwired) presented as a
normal deploy with summary: undefined.

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
… graph

Two ADR-level guarantees had no test:

- DevSession: closed settles only via stop(), stop() is idempotent
  (stopping/stopped fire once, both calls resolve), and a dev() run leaves
  process.listenerCount('SIGINT'/'SIGTERM') untouched — the operation
  never registers signal handlers, so the host can own signals.
- Import-lightness: a fresh bun process imports src/exports/control.ts
  with every heavy module poisoned (executors, pipeline, run-alchemy,
  stack generators, watch, adapters); if the entry's static graph ever
  reaches one, the import throws. Replaces the reverted CI probe with an
  in-repo structural check.

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…th round 2

- ADR heading softened to what the body defends ('Importing the subpath
  executes nothing'), and the body now cites the structural test pinning
  the entry's static graph.
- The stdio consequence stops promising a mechanism: the spawned child and
  'stdio: inherit' are how composer deploys today, not part of the
  contract — matching the demotion of the spawn-shaped failure fields into
  the optional diagnostics object, which all three documents now describe.
- The ADR names the accepted structural cost: @internal/cli now contains a
  surface that is not a CLI.
- The summary-protocol section points at deployment-summary.ts, the
  unique per-run result file, the best-effort writer, and the env var's
  removal from the public exports.
- The guide and SKILL document dev/log outcome discriminants
  ({outcome:'started', session} / {outcome:'attached', ...}), the
  lines-dropped and watch-error events, and clean early termination of the
  log stream.

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The dev operation emits a stop-error DevEvent when stopServices() throws,
but run-dev.ts had no case for it, so the failure never reached the
console — the user only saw "[dev] stopped."

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The bounded merge queue emits lines-dropped when the consumer falls
behind, but runLog ignored every event except stream-failed, so lines
vanished without warning. Branches explicitly on the kind so a future
event kind is not misrendered.

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
TOTAL was 10_150 against a bound of 10_000 — a 1.5% margin that one
consumed line plus scheduling variation could erase, flipping the
droppedTotal > 0 assertion flaky. LOG_QUEUE_LIMIT is now exported and
the flood is twice the bound.

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The no-events-after-end test assigns failLate only when the generator
resumes past its first yield; if the pump ever stopped requesting the
second item, failLate?.() would no-op and the test would pass without
exercising the late failure. Asserting failLate is defined first makes
that regression loud.

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
emit() called input.onEvent unguarded: a throw during a pump's
stream-failed emission rejected the fire-and-forget pump promise (an
unhandled rejection), and a throw for lines-dropped rejected the merged
iterable. Host events are advisory — a broken renderer must not tear
down the stream.

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The child can write deployment-result-<pid>-<uuid>.json via the report
hook before a later step fails; cleanup only ran on the successful
deploy path, so every failed run left resource ids/URLs on disk and the
files accumulated. The stack-file/alchemy/teardown suffix now funnels
through one try/finally that removes the file best-effort.

Refs: TML-3174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…181)

Copies structured-error, internal-error, result, ifDefined, and
CliStructuredError (with the cause repair baked in) from the donor,
byte-close, exposed through the new /errors and /result subpaths.
Donor tests travel (vitest -> bun:test) plus two new cause cases.

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ut (TML-3181)

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
LoadError becomes a CliStructuredError with the type-level
COMPOSE.GRAPH_INVALID code; the toposort cycle assertion is reclassified
as an InternalError (a bug, not a user failure); the no-dev-support
refusal is DEV.TARGET_UNSUPPORTED at origin. AssembleError gains the
ASSEMBLE.* codes, and a foreign build failure is wrapped as
ASSEMBLE.BUILD_FAILED where the address is known.

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
… API (TML-3181)

Every CliError raise site becomes a CliStructuredError with a dotted
NAMESPACE.SUBCODE code and a why/fix split; cli-error.ts is deleted.
OperationFailure is deleted — operation results ride the shared Result
shape with CliStructuredError as the failure, and executionDiagnostics()
reads an engine failure's meta.diagnostics. The boundary is exit-code
mapped per the shared rule: usage/structured failures exit 2, a
non-structured escape is a bug (exit 1 + report hint, no fallback
codes). The alchemy child-status passthrough stays as the documented
exception. The CLI manifest's repository slug is fixed to
prisma/composer.

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ML-3181)

ADR-0044 records the shared error convention: dotted namespace codes,
structural recognition, bugs carry no code, the exit-code split, and
the alchemy child-status exception. ADR-0043's example and consequences
move to the Result shape and the code registry as the taxonomy.

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Assigning undefined does not clear an already-set exit code under bun,
so the leaked 2 failed the whole bun test run despite 0 failing tests.

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
prisma/compose was a mistake; the repo is prisma/composer (the Amb 5
ruling on TML-3181, extended to every manifest).

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…..> directly

DeployResult, DestroyResult, DevStartResult, and LogResult are deleted
(semver 0, no consumers) — operations and the ./control shim name the
Result<Success, CliStructuredError> shapes directly.

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
… throws

Recorded in `started` before the call, so the existing catch stops an
attachment whose startServices came up partway and then threw
(stopServices failures are already swallowed there, so stopping a
never-started attachment is harmless). Review A-001 on #205.

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
executorLoadFailure gains an operation-name parameter used in
DEPS.EXECUTOR_UNLOADABLE's summary and why; deploy/destroy/dev/log each
pass their own. Also lands the partial-start rollback test for A-001.
Review A-002/A-003/A-004 on #205.

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
renderChildStatusHints in render-error.ts is the single implementation
of the ADR-0044 child-status exception; main.ts and run-dev.ts call it,
output byte-identical. Review A-005 on #205.

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The captured-error pattern fails the test if Load() stops throwing,
instead of silently skipping the code assertion. Review A-006 on #205.

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
deploy-cli.md no longer promises a Fix line for every failure; the
ADR-0044 layout fence is tagged text (MD040); SKILL.md says the control
operations do no CLI rendering while inherited deploy-engine output can
still reach the host terminal.

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…sumer tree

Workaround for the upstream alchemy bug (the TaggedErrorClass drift):
alchemy's effect-family dependency and peer ranges float past the
versions its shipped code supports, so a fresh npm install resolves the
floaters (@effect/sql-d1, @effect/sql-pg, @effect/vitest,
@effect/platform-*) to the newest beta, whose peer floors reject
composer's effect pin and drag in a second effect — the
check-npm-effect-resolution failure.

Every consumer-shaped tree now pins the whole constellation to
composer's exact pin via an npm overrides block: the examples/*
manifests, the getting-started and deploying guides, and the check
script's healthy shapes (which now install the scaffolded consumer
shape, proving it genuinely dedupes to one effect@4.0.0-beta.103 with
alchemy working). The adversarial WRONG_EFFECT shape still proves the
CLI fails loudly on a bad tree. Delete the block everywhere when
alchemy fixes its ranges; the upgrade-alchemy-effect skill records the
lockstep rule.

Refs: TML-3181

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden
wmadden enabled auto-merge (squash) August 7, 2026 14:27
@wmadden
wmadden force-pushed the tml-3174-composer-programmatic-deploy-api branch from dd37a91 to ba427be Compare August 7, 2026 14:27
@wmadden
wmadden merged commit 014b0a4 into main Aug 7, 2026
17 checks passed
@wmadden
wmadden deleted the tml-3174-composer-programmatic-deploy-api branch August 7, 2026 14:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants