Skip to content

Proof: an existing Composer app runs locally with zero cloud credentials#166

Closed
wmadden-electric wants to merge 3 commits into
mainfrom
claude/local-dev-s6-proof
Closed

Proof: an existing Composer app runs locally with zero cloud credentials#166
wmadden-electric wants to merge 3 commits into
mainfrom
claude/local-dev-s6-proof

Conversation

@wmadden-electric

@wmadden-electric wmadden-electric commented Jul 23, 2026

Copy link
Copy Markdown
Contributor
$ cd open-chat            # a real, pre-existing Composer app: chat service, Postgres,
$ bun run build           # streams + storage modules. Its own build, untouched.
$ APP_ORIGIN=http://localhost:3000 bunx prisma-composer dev module.ts
[dev] ready:
[dev] chat  http://localhost:3000

Against that URL, with no cloud credentials of any kind in the shell: sign-in worked, chat history loaded, and the live-tail stream delivered events. Chat generation failed — at exactly one place: the outbound OpenRouter call, because the local run minted a placeholder for the OPENROUTER_API_KEY secret instead of demanding a real one.

This PR records the evidence that the local-dev project's founding claim holds: an existing Composer app runs locally with zero cloud credentials, through the same pipeline a deploy uses, failing only where the app itself reaches for a real external service. It contains no production code — the implementation is already on main (#158, #159, #160, #162, #164). What lands here is the proof material and the design-doc reconciliation that closes the project.

The exercise

prisma-composer dev (ADR-0041, on main) runs the deploy pipeline — load the app's graph, assemble each service from the user's own built output, lower to resources, converge — but substitutes local providers that talk to on-machine emulator daemons instead of the platform. The claim worth proving was never "a fixture app works"; it was that an app written before local dev existed, in its own repo, comes up with one command.

open-chat is that app. Porting it took one commit, summarized file-by-file in assets/open-chat-port/PORT.md: point its framework dependency at the locally built packages, switch its service to node()'s directory build form, fix a launcher path that only worked un-moved, catch up with two API changes that had landed since the app was written — and delete its 169-line hand-rolled scripts/dev.ts, which is the point: the framework command replaces the app's own process-juggling script. Everything that hurt along the way is in assets/open-chat-port/FRICTION-S6.md (six findings; the one blocker is below).

Two measurements ride along:

  • Restart latency: median 3.24s (5 runs, 3.09–3.35s, Apple M3 Max) per edit → rebuild → reconverge cycle on examples/store; method and script in assets/latency.md / assets/latency-probe.ts.
  • The failure boundary: a secret absent from the shell becomes a local placeholder, so the app boots and serves, and only the code path that spends the credential fails — at the real external service, with the platform's own error. That is the designed behavior, observed end to end.

What the proof found and what happened to it

The port surfaced one real bug: after Ctrl-C, a second dev run could print the app ready while every service stayed stopped — stopping changes no resource properties, so the converge engine saw nothing to do and never restarted anything. That fix landed in #164 (the dev command now explicitly starts stopped services after every converge, before printing the ready lines). The finding stays in the friction log with a fixed-in note rather than being deleted — the log is a record of what the proof hit, not a list of open issues.

Because the proof ran before the stack's final naming pass, this PR also reconciles the design docs with what shipped: ADR-0041, the ADR index, local-dev.md, and deploy-cli.md now describe the localTarget seam (the extension field and descriptor actually in the code) instead of the earlier dev-prefixed names, and note that both build adapters now populate the watch paths the dev loop uses. Every such claim was checked against main's source before being written down.

Files

  • assets/open-chat-port/PORT.md — the port commit, summarized by file.
  • assets/open-chat-port/FRICTION-S6.md — the six findings, with outcomes.
  • assets/latency.md, assets/latency-probe.ts — the latency numbers and how they were taken.
  • docs/design/… — the reconciliation described above.

Alternatives considered

  • Shipping the port's raw git format-patch output as an asset — the first cut did, and an 1,866-line patch blob dominated the diff while being unreviewable; replaced with the file-by-file summary. The raw diff stays with the port working copy.
  • Running the proof in CI — the app lives in a separate repo with a different owner; the store-app integration suite already exercises the same machinery as a required check, so this stays a recorded manual proof.
  • Merging any port code into this repo — there is nothing to merge; the port's changes belong to the app, and the evidence is the deliverable here.

This repo is proof-of-concept stage; the proof shows the claim holds for this app on this machine, nothing broader.

🤖 Generated with Claude Code

wmadden-electric and others added 2 commits July 23, 2026 21:55
…onciliation

Proves the local-dev project end to end against the open-chat port (a
separate repo, worked on as a full copy since the original is owned by a
different user): pointed the port at this worktrees locally built
@prisma/composer and @prisma/composer-prisma-cloud, switched its build
descriptor to nodes directory form, replaced its hand-rolled
scripts/dev.ts with prisma-composer dev, and proved sign-in, chat history,
live-tail SSE, and generation-fails-at-OpenRouter-with-the-placeholder all
work credential-free. Findings and the port-side patch land in
.drive/projects/local-dev/assets/open-chat-port/.

Measures restart latency on examples/store (median 3.24s over 5 runs,
method + numbers in assets/latency.md) using the S5 proving scripts own
touch-rebuild-reconverge-poll technique.

Reconciles local-dev.md and ADR-0041 against what shipped, including a
significant unresolved finding from the open-chat proof: a warm restart
after Ctrl-C can leave every service stopped because Alchemys own no-op
diffing skips the local Deployment providers reconcile when nothing in a
resources props changed (a Ctrl-C stop is invisible to that diff). The
existing store proving scripts own criterion-6 check does not catch this
because it verifies port stability and reads Postgres directly rather than
making an HTTP round-trip against the restarted service.

Adds prisma-composer dev to deploy-cli.mds Scope section (moved out of
Out of scope). Syncs spec.md/plan.md wholesale from the design branch tip.

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>
Rebase-equivalent of claude/local-dev-s6-proof onto main: the branch is
rebuilt as the single S6 assets/docs commit cherry-picked onto main (the
35 production-code commits underneath it all merged via #158-#164), plus
this refresh for two things that changed since the proof ran:

- The warm-restart bug the proof found (services stayed stopped while dev
  printed ready, because a Ctrl-C stop is invisible to Alchemy props
  diffing) was fixed on #164: LocalTargetAttachment.startServices() runs
  on every attachment after each converge, before the front door. The
  finding stays in FRICTION-S6.md/local-dev.md/ADR-0041 with a fixed
  note referencing #164 instead of being deleted.
- The seam rename: ExtensionDescriptor.localTarget (a lazy thunk) /
  LocalTargetDescriptor, subpaths @prisma/composer/local-target and
  @prisma/composer-prisma-cloud/local-target; "dev" names only the
  user-facing command, [dev] prefix and .prisma-composer/dev/. Stale
  pre-rename names (dev field, DevDescriptor, Dev*Input, the old
  @internal/lowering src/dev/ path) are updated in ADR-0041, the ADR
  index, local-dev.md and the latency probe, verified against main
  source (app-config.ts, local-target.ts, run-dev.ts, build.ts).

Also records that Bundle.watch is now populated by both build adapters,
so the latency method note about the missing watch loop is historical.

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>
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@wmadden-electric, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 39 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e2773988-56cc-46a7-afec-6fda8ea7f7ef

📥 Commits

Reviewing files that changed from the base of the PR and between 3142847 and 9f048f1.

📒 Files selected for processing (8)
  • .drive/projects/local-dev/assets/latency-probe.ts
  • .drive/projects/local-dev/assets/latency.md
  • .drive/projects/local-dev/assets/open-chat-port/FRICTION-S6.md
  • .drive/projects/local-dev/assets/open-chat-port/PORT.md
  • docs/design/10-domains/deploy-cli.md
  • docs/design/10-domains/local-dev.md
  • docs/design/90-decisions/ADR-0041-local-dev-runs-the-deploy-pipeline-against-local-providers.md
  • docs/design/90-decisions/README.md
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/local-dev-s6-proof
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/local-dev-s6-proof

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.

An 1866-line format-patch blob dominated the PR diff and is not
reviewable material; PORT.md now summarizes the port commit by file, and
the friction log points there. The raw diff stays with the port copy.

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>
@wmadden-electric wmadden-electric changed the title S6 proof: open-chat runs under prisma-composer dev credential-free, plus restart-latency numbers Proof: an existing Composer app runs locally with zero cloud credentials Jul 23, 2026
@wmadden-electric

Copy link
Copy Markdown
Contributor Author

Closing unmerged per review: the assets are transient project artifacts destined for deletion at close-out, and the doc reconciliation belongs with the close-out itself. One close-out PR replaces this — doc reconciliation + durable-evidence migration + removal of .drive/projects/local-dev/. The evidence remains on this branch.

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.

1 participant