Repository navigation
The emulator port-retry test exercises the retry and waits for the daemon; PRISMA_COMPOSER_EMULATORS_DIR picks the registry - #333
Conversation
|
✅ Gizmo reviewed 60973f1 — posted 0 inline comment(s) this pass. Open findings: 🟡 2 minor Change walkthroughThis PR fixes a flaky dev-emulators port-retry test that never actually exercised the retry (the old test squatted a port that Test reliability (daemon.test.ts, helpers.ts, port-taken-on-first-start.ts). The retry test now starts the daemon through a fixture that binds its allocated port itself on first start, so Registry root (daemon.ts, local-target.ts). Coverage (dev-emulators-dir.test.ts). A new CLI-level test proves the structured error reaches the Docs (design doc, guide, skill). All three state the same caveat: the variable isolates compute/buckets daemons, registry, and data, but not local Postgres — two checkouts of one app still share a server, with |
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
Comment |
commit: |
| - **`PRISMA_COMPOSER_EMULATORS_DIR` gives a checkout or a CI job its own | ||
| emulators.** By default every `dev` on the machine shares one set of | ||
| emulator daemons, registered under `~/.prisma-composer/emulators`. Set the | ||
| variable to an absolute directory and `dev` starts and finds its own | ||
| emulators there instead, with their own data, so two checkouts or two CI | ||
| jobs never share a daemon by accident. Both pick free ports, so they can | ||
| run side by side. A relative path is refused with `DEV.EMULATORS_DIR_INVALID`. | ||
| `emulatorRegistryRoot()` from `@prisma/composer-prisma-cloud/local-target` | ||
| returns the directory in effect. |
There was a problem hiding this comment.
🟡 Minor · correctness — Guide overstates isolation: Postgres data is not scoped by PRISMA_COMPOSER_EMULATORS_DIR
The new bullet says setting PRISMA_COMPOSER_EMULATORS_DIR gives a checkout or CI job its own emulators "with their own data, so two checkouts or two CI jobs never share a daemon by accident". That is true for the compute/buckets/postgres daemons and their registries, but — as the PR description itself notes — @prisma/dev keeps its own Postgres server records outside this directory, so two checkouts with different PRISMA_COMPOSER_EMULATORS_DIR values still share the local Postgres data and the machine-wide Postgres session. A reader isolating a CI job for data-separation reasons (not just daemon-port reasons) will not get the isolation the bullet implies. The existing "local Postgres is one shared session" bullet below covers the session, not the server records.
Recommended fix
Add a caveat sentence to the new bullet, e.g. "The @prisma/dev Postgres server records stay machine-wide regardless of this variable, so two registry roots still share the local Postgres data."
| /** | ||
| * The daemon's listening lines, once at least one is in its log. Under bun, | ||
| * the daemon can answer /health before its `listen` callback writes the line. | ||
| */ | ||
| async function awaitListeningLines(logPath: string): Promise<string[]> { | ||
| const listeningLines = () => | ||
| fs | ||
| .readFileSync(logPath, 'utf8') | ||
| .split('\n') | ||
| .filter((line) => line.includes('listening on 127.0.0.1:')); | ||
| await waitFor(() => listeningLines().length > 0, 5000); | ||
| return listeningLines(); | ||
| } | ||
|
|
There was a problem hiding this comment.
🟡 Minor · tests — awaitListeningLines stops at the first line, so the concurrent-ensure count can still race a duplicate line
awaitListeningLines resolves as soon as the first listening on line appears, and the concurrent-ensure test immediately asserts toHaveLength(1). A losing racing spawn's listening line — the exact failure mode this test exists to catch — that lands after the first successful poll is never seen, so the assertion can pass while the protocol violation is still in flight. This is not a regression (the old code read the log immediately, a strictly worse window), but since this PR reworks this line to stop racing the daemon, waiting only for the first line still leaves the assertion able to outrun the evidence. The retry-test caller is unaffected: its failed start provably cannot log a listening line.
Recommended fix
After the first listening line appears, let the helper settle before returning — e.g. sleep ~500 ms and re-read the log — so a second spawn's line has time to show up before the count is asserted.
fdfcddc to
27562e6
Compare
There was a problem hiding this comment.
New findings: none · trace
Still open from previous reviews: 🟡 2 minor
Both findings addressed in the follow-up commits; the bot's re-review reports no new findings.
There was a problem hiding this comment.
New findings: none · trace
Still open from previous reviews: 🟡 2 minor
…s for the listening line The test squatted a free port and expected ensureDaemon to fail on it and retry. It never did: get-port probes each candidate and locks the ports it returns in-process, so the allocation skipped the squatted port and the first spawn succeeded. The retry path went untested. It then read the daemon log as soon as ensureDaemon returned. Under bun the daemon answers /health before its listen callback writes the listening line, so the log can still be empty. That fails CI's Linux Test job with "Expected length: 1, Received length: 0". A fixture entry now binds its own allocated port on the first start, so the daemon's bind fails for real and ensureDaemon retries the next free port. The test and the concurrent-ensure test wait for the listening line before counting it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…registry root
Every dev session on a machine shares one set of emulator daemons under
~/.prisma-composer/emulators. Two checkouts, or a CI job and a test suite,
could only use separate daemons through the test-only { registryRoot }
option.
defaultRegistryRoot() now returns $PRISMA_COMPOSER_EMULATORS_DIR when it is
set. A relative value throws DEV.EMULATORS_DIR_INVALID naming the variable,
because each process that resolves it would otherwise get a different
directory. ADR-0044's DEV subcode list gains that code.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…nd docs use it @prisma/composer-prisma-cloud/local-target exports emulatorRegistryRoot(), the same function the daemons resolve their registry with. The local-dev integration proofs read their registry root from it instead of hardcoding ~/.prisma-composer/emulators, so with PRISMA_COMPOSER_EMULATORS_DIR set they check the daemons they actually started. The running-locally guide and the core-concepts skill say the variable gives a checkout or a CI job its own emulators. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…data @prisma/dev keeps its server records machine-wide, so two registry roots still share local Postgres data. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…re asserting one spawn The helper returned on the first listening line, so a losing spawn that logged later could slip past the concurrent-ensure assertion. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…t it as EMULATORS_DIR_ENV The variable's name is now the exported EMULATORS_DIR_ENV constant, used in the code, the error and the tests, and re-exported from the local-target entry next to emulatorRegistryRoot(). An absolute value is normalised with path.resolve, so trailing separators and dot segments no longer give two spellings of one directory. The error's fix says that `~` is not expanded. A CLI test runs `dev` through the engine with an emulators hook that throws DEV.EMULATORS_DIR_INVALID and checks the envelope carries its code, why, meta and fix unchanged. The listening-line wait now needs two consecutive quiet 500 ms windows, fails if the count never settles, and treats a missing log as no lines. The skipContendedDaemonPorts comment says the daemon starts above the port it finds, because get-port holds that port. The local-target entry's comment and its typecheck shadow list the new exports. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
… local Postgres Local Postgres servers are named after the app and database, so two checkouts of one app under different registry roots share a server: the second adopts the first's, and --fresh or teardown in either stops it and deletes the database for both. The guide and the skill now say so, and that the variable isolates the compute and buckets daemons and their registry. The local-dev design doc's machine-scoped emulators item describes the registry root and the variable, and notes that two Composer versions on one root keep replacing each other's daemons. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
4fb6584 to
60973f1
Compare
There was a problem hiding this comment.
New findings: none · trace
Still open from previous reviews: 🟡 2 minor
``` $ git diff --stat main...HEAD | tail -1 25 files changed, 1 insertion(+), 2833 deletions(-) ``` This closes the one-config-file project. It deletes the project's working folder, `projects/one-config-file/`, and fixes one duplicate number in the failure-mode catalogue. Linear: TML-3340. The project moved Prisma Composer's configuration out of its own `prisma-composer.config.ts` into the `composer` section of `prisma.config.ts`, and deleted Composer's standalone `prisma-composer` binary. Prisma 8 now has one CLI and one config file. The rest of this description is the close-out record the Drive process asks for: what was checked, where each decision now lives, and where each unfinished item is tracked. ## What was delivered | PR | What it did | | --- | --- | | prisma/composer#328 | Composer's configuration is the `composer` section of `prisma.config.ts`. The old file and `configPath` are refused. | | prisma/composer#331 | The `prisma-composer` binary is gone. Docs, examples and the shipped skill say `prisma deploy` and `prisma dev`. Released as Composer 0.26.0. | | prisma/prisma-cli#330 | The `prisma` host runs Composer 0.26.0. Released as `prisma@8.0.0-rc.20`. | | prisma/web#8387 | The public Composer docs describe the `composer` section. | | prisma/composer#332 | Found during testing: `prisma dev` and `prisma deploy` failed in pnpm projects. Merged, not yet released (TML-3520). | | prisma/composer#333 | An emulator test race that made CI flaky, plus `PRISMA_COMPOSER_EMULATORS_DIR`. | Three more PRs came out of this close-out and are open: - prisma/composer#347 writes ADR-0050, which records the binary's retirement. Without it the close-out failed the ADR audit, and four older ADRs still described `prisma-composer` as the entry point. - prisma/web#8415 fixes a tutorial page that still told readers to write `prisma-composer.config.ts`. - prisma/pdp-control-plane#5608 makes the platform's Compute import flow write the `composer` section into `prisma.config.ts`, and recognise repositories that already have it. Until now it wrote `prisma-composer.config.mjs` and pinned Composer 0.25.0, so imported repositories broke on upgrading to 0.26.0. ## Definition of Done | Item | Verdict | Evidence | | --- | --- | --- | | orm-demo has one config file, and `prisma deploy` and `prisma dev` run against it from the host | Met, with deviations | `examples/orm-demo` has only `prisma.config.ts`. `prisma dev` from the host build reached ready (slice 3 QA). A real `prisma deploy` of orm-demo succeeds in Composer's e2e workflow on `main`, using the published host with the workspace family. No deploy ran from the host build itself, because no service token was available. | | The old file and `configPath` get their diagnostics | Met, with a deviation | `CONFIG.FILE_RETIRED` and `CONFIG.FIELD_RETIRED`, exit 2, from the host binary. Shown with `prisma dev`, because `deploy` checks credentials before reading the config. Both commands use the same validator. | | A broken `effect` install fails with `CLI.CONFIG_UNREADABLE`, and `prisma --version` still works | Met, with the same deviation | Slice 3 QA, step 5. | | Published packages have no `bin` and no stale name | Met | `@prisma/composer-cli` and `@prisma/composer` 0.28.0 declare no `bin`. Their unpacked tarballs name the old file only in the messages that refuse it. | | A CI check keeps the old name out | Met | `pnpm lint:retired-binary-name` runs in CI. Its test plants a mention and expects a failure. | | TML-3340 Done, web pages updated | Met | TML-3340 is Done with a closing comment. One page missed by #8387 is fixed in prisma/web#8415. | | The consolidation plan says `deploy` and `dev` stay bare | Met | `projects/consolidate-clis/cli-consolidation-plan.md`, and now ADR-0050 in prisma/composer. | | Retro run, ADR merged, folder deleted | Met once #347 and this PR merge | The retro's lesson is failure mode F42. ADR-0049 is merged. ADR-0050 is in #347. | | Repository references to the folder removed | Met | Nothing outside the folder links to it. | | Manual QA for each user-facing slice | Met, with a deviation | Slice 3 has a QA transcript. Slices 1 and 2 recorded their manual QA in the Verification sections of #328 and #331. | ## Where each decision lives now Every decision recorded in the deleted spec and design notes has a home outside the folder: - **Composer's configuration is the `composer` section, validated by the section, with the old file and field refused:** ADR-0049, and the `CONFIG` code list in ADR-0044. - **The `effect` version pre-flight is deleted; a broken tree fails with the engine's error:** ADR-0049. #347 adds the four rejected alternatives, which until now were only in #328's description. - **The binary is retired, and `destroy` and `log` stay programmatic:** ADR-0050 in #347. - **`deploy` and `dev` stay bare commands; `destroy` is not mounted:** ADR-0050, and the consolidation plan. - **The examples' `destroy` scripts keep `--production` and `--stage`:** ADR-0050 records this as a repository-internal script grammar, not a public command. - **Examples and CI run the published host with a workspace override:** `gotchas.md` and `scripts/check-cli-engine-pin*.mjs` in prisma/composer, which enforce it. - **Composer runs the `alchemy` installed beside `@prisma/composer`:** ADR-0007's amendment and `docs/design/10-domains/deploy-cli.md` in prisma/composer. ## Deferred items and their tickets | Ticket | Item | | --- | --- | | TML-3520 | Release Composer 0.29.0 and pin it in the host, so `prisma@latest` gets the pnpm fix. | | TML-3521 | Teardown and logs have no `prisma` command. | | TML-3522 | Two checkouts of one app still share a local Postgres server. | | TML-3523 | A compute emulator test is too tight on time and flakes under load. | | TML-3524 | Composer's examples pin an older `prisma` host than `latest`. | | TML-3525 | Drop the exact `effect` pin once `effect` 4 is stable or Alchemy pins its peer. | | TML-3526 | dependency-cruiser skips the examples' `prisma.config.ts`. | | TML-3527 | Edge cases in how Composer starts Alchemy. | | TML-3528 | prisma/asks and prisma/streams still use the retired config or command. | Two smaller review notes are accepted without tickets. The prisma-cli conformance check needs a new exception on each joint engine release, which is visible when it happens. Windows edge cases are out of scope, because Windows is documented as unsupported for local tooling. ## What this PR deletes Every file under `projects/one-config-file/` is transient under `drive/project/README.md`: the spec, plan, design notes, retro log, README, and each slice's spec, plan, grounding notes, reviews and QA transcript. None is methodology to migrate. The decisions are mapped above. The full files stay readable in the history of prisma/orm#30536. ## The failure-mode number The retro added its lesson to `drive/calibration/failure-modes.md` as F39. prisma/orm#30613 had already used F39 two days earlier. This PR renumbers ours to F42, the next free number. Agent: saruman-38 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Found while landing the one-config-file project (prisma/orm#30536): the dev-emulators suite failed the port-retry test on four of six CI runs of two branches, while
mainstayed green.What was wrong
Two things, both in the test.
It raced the daemon's log line.
ensureDaemonreturns on the first good health check. Under Bun the compute daemon answers/healthbefore itslistencallback has written the "listening on" line, and the test read the log straight away. Measured by starting the built daemon the wayensureDaemondoes and polling health: the line was missing at the first good check in roughly one start in six, and present 300 ms later every time. Writing the line synchronously does not help; the callback itself runs late. Which branch hits it is timing:main's CI run of this test took 2.1 s and the failing branch run 1.26 s.It never exercised the retry. The test "squatted" a port it got from
get-port, butget-portlocks every port it hands out inside the process, so the squatted port was never allocated to the daemon and the first bind succeeded. The retry path the test is named for had no coverage.The fix
The retry test starts the daemon through a fixture that, on its first start, binds its own allocated port and then runs the real daemon, so the bind fails for real and
ensureDaemonretries. It asserts the daemon ends on a port above the taken one, and waits for the listening line instead of reading the log immediately. The concurrent-ensure test waits for the line the same way. With retries disabled the new test fails with "compute emulator failed to start on port 4307".Emulator copies per checkout or CI job
The daemons are machine-wide by design, registered under
~/.prisma-composer/emulators, and the daemon API already takes aregistryRootso copies can coexist. Nothing let a user or CI choose one.PRISMA_COMPOSER_EMULATORS_DIRnow sets the registry root; a relative value is refused withDEV.EMULATORS_DIR_INVALID. The three local-dev integration proofs read their root from the newemulatorRegistryRoot()export on@prisma/composer-prisma-cloud/local-targetinstead of hardcoding the home path. The running-locally guide and the skill describe the variable. Not covered:@prisma/devkeeps its own Postgres server records outside this directory, so two checkouts still share those.Review outcome
The architect and principal-engineer passes (in prisma/orm#30536,
slices/04-emulator-port-retry/review/) found one thing the first version understated: the variable isolates the compute and buckets daemons, their registry and their data, but not local Postgres. Two checkouts of one app get the same local Postgres server names, so the second checkout's emulator adopts the first's running server, and--freshor teardown in either stops that server and deletes its database for both. The guide, the skill and the local-dev design doc now say exactly that. Smaller items from the review: the settle wait requires two quiet windows and has a deadline; the value is normalised and the error says~is not expanded; the variable name is an exported constant; the error reaching the CLI is tested through the engine harness.Follow-ups, not in this PR
Verification
lint:depsandcheck:skill-packagingpass.test/integration'sbun testpasses.Agent: columbo-17
🤖 Generated with Claude Code