diff --git a/drive/calibration/failure-modes.md b/drive/calibration/failure-modes.md index 77c4eeac170d..d161c01ece9b 100644 --- a/drive/calibration/failure-modes.md +++ b/drive/calibration/failure-modes.md @@ -587,7 +587,7 @@ Patterns to **catch** the F-family modes live in [`grep-library.md`](./grep-libr **Second incident.** prisma/orm#30439 (2026-09-28, Mongo `Json` and `Bson` codecs). The in-loop reviewer closed the slice SATISFIED by reading. A `drive-code-review` pass after PR-open, with execution allowed, ran the built codecs and found: both decoders threw a raw `TypeError` on a stored plain subdocument carrying a `_bsontype` key; `Bson` encode let through look-alike objects and values from another `bson` major that the driver then rejected without naming the field; the `Bson` JSON form turned bytes into `$numberInt` documents. The first fix round then introduced an `instanceof Code` check that never matched a `Code` the driver had read, because the target loads `bson`'s ESM build and `mongodb` loads the CommonJS build; only a test built from a CommonJS load of `bson` exposed it. Three fix rounds, 33 commits, before the PR could be queued. -### F39. A branch-only CI failure gets "fixed" by rescheduling CI instead of by finding the cause +### F42. A branch-only CI failure gets "fixed" by rescheduling CI instead of by finding the cause **Symptom.** A test fails on a branch and not on `main`, in a package the branch does not touch. The orchestrator forms a hypothesis from the failure's shape (ports, timing, a shared daemon), changes the CI workflow to run suites in series, and the same test fails again with the change in place. The real cause turns out to be a race inside the test and a test that never exercised the behaviour it was named for, exposed by a timing shift on the branch; the subsystem already supported the isolation the orchestrator was trying to schedule around. diff --git a/projects/one-config-file/README.md b/projects/one-config-file/README.md deleted file mode 100644 index 8b80a0eb14b1..000000000000 --- a/projects/one-config-file/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# One config file for Composer - -Prisma 8 ships one CLI, `prisma`, and one config file, `prisma.config.ts`. Composer still has a second config file and a second binary. This project removes both. - -- [`spec.md`](./spec.md) — what is true at the project level and when the project is done -- [`design-notes.md`](./design-notes.md) — the decisions, with the alternatives that were rejected and why -- [`plan.md`](./plan.md) — the slices, their order, and what each hands to the next -- `slices/` — one folder per slice, written when the slice is picked up - -Almost all code changes land in [prisma/composer](https://github.com/prisma/composer) and [prisma/prisma-cli](https://github.com/prisma/prisma-cli). The artifacts live here because this repo already holds the cross-repo planning: [`projects/consolidate-clis/`](../consolidate-clis/) and the GA plan on the `planning/prisma-8-ga` branch. - -Everything under this folder is transient and is deleted at close-out. diff --git a/projects/one-config-file/design-notes.md b/projects/one-config-file/design-notes.md deleted file mode 100644 index ecf04a870105..000000000000 --- a/projects/one-config-file/design-notes.md +++ /dev/null @@ -1,81 +0,0 @@ -# Design notes: one config file for Composer - -Decisions made with Will Madden on 2026-09-30, each with the alternatives rejected and why. Facts were read from `main` of prisma/composer, prisma/prisma-cli and this repo on that date, and from the npm registry. - -## Principles - -1. **One config file means one loader.** The engine loads `prisma.config.ts`; families own a section and its validator. A family that keeps a private loader for a private file has not consolidated. -2. **A section holds the configuration, not a pointer to it.** The reason given in Composer's section header for keeping only a path, that a validator loads at start-up and must be light, describes the engine wrongly: validators are loaded with the family but only run when a command needs config, and the ORM section already holds built descriptors with functions inside. -3. **Fail fast and loud on the old shape.** No fallback, no silent ignore, no codemod. -4. **No machinery with one consumer.** An engine extension point that only Composer would use is not worth its maintenance. - -## The model - -### The section - -```ts -composer: composer({ - extensions: [prismaCloud(), nodeBuild()], - state: prismaState(), -}) -``` - -`composer` is `defineConfig` from `@prisma/composer/config`, today an identity function over `PrismaAppConfig` in `packages/0-framework/1-core/core/src/control/app-config.ts`. It stays light: the module imports no control-plane code. The section validator, replacing the `configPath`-only one in `packages/0-framework/3-tooling/cli/src/family/section.ts`, checks exactly what Composer's loader checks today in `configShapeDiagnostics`, and wraps each descriptor with the engine's `reference(...)` so commands receive the objects the config built. This is the ORM section's pattern in `packages/1-framework/3-tooling/config-loader/src/orm-section.ts` of this repo. - -The deploy and dev pipelines, which today call Composer's loader through `resolveConfigFile` in `pipeline.ts`, instead read the validated section the engine hands the command. `deploy` and `dev` already declare a config need on the `composer` section, so the engine evaluates the file before either runs. - -### Refusing the old shape - -Two diagnostics, both under Composer's `CONFIG.` prefix: - -- The section validator rejects `configPath` as an unknown field with a message that shows the section to write. Today unknown fields are only warnings; that changes to errors, because a config the user believes is in effect and is not is worse than a rejected config. -- Each Composer command, before its pipeline starts, looks for `prisma-composer.config.{ts,mts,mjs,js}` in the directory of the loaded `prisma.config.ts` and fails naming the file. The engine's section provenance gives that directory. Only that directory is checked; the old walk-up discovery is not reproduced. - -### The `effect` version pre-flight is deleted - -Composer's `check-effect-resolution.ts` compares the `effect` version Alchemy resolves against the exact version `@prisma/composer` pins, before Composer's loader evaluates the config file. It exists because Alchemy's `effect` peer range, `>=4.0.0-rc.115 || >=4.0.0`, accepts newer release candidates that remove modules Alchemy imports, and package managers hoist over peer ranges with only a warning. Verified on 2026-09-30: `alchemy@2.0.0-beta.78` or `beta.79` with `effect@4.0.0-rc.118` fails at import with a missing `effect/dist/unstable/http/FetchHttpClient.js`; with `rc.115` both import cleanly. - -After the merge the engine evaluates the file. The Prisma Cloud control entry imports Alchemy at module load through its resource definitions in at least 18 files, so a broken tree throws during evaluation. The engine catches that and reports `CLI.CONFIG_UNREADABLE` with the first line of the error, exit code 2, for every command that reads config; commands that do not, such as `prisma --version`, are unaffected. Will's ruling: that is enough. The case is rare, catastrophic, and can only be fixed by the user and their package manager; failing hard and fast with the module error is correct. The pre-flight module and its two callers go, as does the standalone binary's start-up use of it. - -**Rejected: an engine hook that lets a family veto config evaluation.** It would have produced Composer's friendlier message on ORM commands too. Nothing else would consume it, and the message quality gain does not justify a family-level extension point. - -**Rejected: Composer's commands declaring no config need and loading the config themselves after the check.** Possible with the runtime's config loader, and what the standalone tool did, but it bypasses the engine's declarative config need for the sake of a message on a rare failure. - -**Rejected: running the check at import time inside `@prisma/composer/config`.** Depends on import order in the user's file. An import sorter puts `@prisma/composer-prisma-cloud/control` before `@prisma/composer/config`, because a hyphen sorts before a slash, so the sorted file crashes before the check runs. - -**Rejected: making the control-plane entries import Alchemy lazily.** That is a refactor of Composer's lowering layer, not something to do before GA. - -What stays: the exact pins on `effect` and its companions, and the CI script `scripts/check-npm-effect-resolution.mjs` that installs the published tarballs with the real package manager and proves a clean install resolves one `effect` that Alchemy loads. Its adversarial shape currently runs the standalone binary to assert the friendly error; it changes to assert that importing Alchemy from the broken tree throws. - -### The standalone binary - -`prisma-composer` is declared by `@prisma/composer-cli`, not `@prisma/composer` as two stale comments say. Its entry `bin.ts`, `cli.ts`, `family/engine-cli.ts`, and the pass-through `orm` section family that `engine-cli.ts` mounts so a shared config loads, are deleted. `destroy` and `log` remain as operations on `@prisma/composer/control`, where the public docs already send readers, but no command mounts them, and the family's internal operations seam (`ComposerOperations` and its test double) narrows to `deploy` and `dev`, since nothing else reads it. Inside the repository the examples and CI run the published `prisma` host with a root `pnpm.overrides` entry pointing `@prisma/composer-cli` at the workspace; the hoisted linker means example scripts call the bin by a repo-relative path and CI teardown is a repo-private script over the programmatic `destroy`. - -### Docs and the skill - -TML-3340 counted 55 command mentions across seven files at commit `c29ae43`. A full count on `main` finds 176 mentions of `prisma-composer` in prisma/composer: 138 as the command and 38 as the config file name, spread over the guides, README, skill, all ten examples' package scripts and tsconfig includes, and design docs. The design docs are out of scope, as the ticket says. Everything else changes in this project. The CI check is a script under `scripts/` that fails on `prisma-composer` as a command or config-file name in `README.md`, `docs/guides/`, `skills/`, `examples/`, and is wired into the existing lint job. - -In prisma/web, no page tells the reader to run a `prisma-composer` command; the getting-started and porting pages name the config file, and the deploying and local-development pages already say the unified CLI has no `destroy` or `log`. Two one-line fixes. - -### Commands - -Nothing new is mounted. Will's ruling on 2026-09-30: `deploy` and `dev` stay as bare commands, special cases of the noun-verb grammar in `docs/product/command-principles.md` of prisma/prisma-cli and the consolidation plan's invariants. The consolidation plan's statement that bare deploy does not survive is corrected in this project's shaping PR. - -**Rejected: mounting `destroy` in the host.** `destroy` is not a verb in the CLI's language, `delete` is, and `--production` is not a concept in Composer's model or the plan's. The plan maps stage teardown to `branch delete` with a plan and a guarded confirm. That is a command-grammar project, not a config-file one. - -**Rejected: renaming `deploy` and `dev` to `project deploy` and `project dev`.** Will ruled they stay. - -## Alternatives considered at the project level - -- **Keep the pointer section and only fix the docs.** That is TML-3340 alone. It leaves two config files, which the GA plan names as a must-fix before GA. -- **Automatic migration of `prisma-composer.config.ts`.** More code than the feature: the migration is moving one object literal, and the release-candidate audience is small. - -## Open questions - -None. Every question in the original brief was settled on 2026-09-30. - -## References - -- Consolidation plan: [`projects/consolidate-clis/cli-consolidation-plan.md`](../consolidate-clis/cli-consolidation-plan.md), § "Evaluating the config never errors" and the Composer rows of the rename table. -- prisma/prisma-cli: `docs/product/command-principles.md`; `packages/cli-engine/src/config-loader.ts` (evaluation and `CLI.CONFIG_UNREADABLE`); `packages/cli-engine/src/execution/needs.ts` (lazy loading and section validation). -- prisma/composer: `packages/0-framework/3-tooling/cli/src/check-effect-resolution.ts` (header comment records the field failure on Composer 0.6.0); `docs/design/90-decisions/ADR-0017-control-plane-loads-through-the-app-config.md`. diff --git a/projects/one-config-file/plan.md b/projects/one-config-file/plan.md deleted file mode 100644 index 9fcca0d50cbf..000000000000 --- a/projects/one-config-file/plan.md +++ /dev/null @@ -1,51 +0,0 @@ -# Plan: one config file for Composer - -**Spec:** [`spec.md`](./spec.md). **Design:** [`design-notes.md`](./design-notes.md). - -Three slices in one stack. Each is one PR in the repository named. Slice specs and plans are written under `slices/` when the slice is picked up. - -## Slice 1 — The `composer` section holds Composer's configuration (prisma/composer) - -**Outcome.** Composer's whole configuration lives in the `composer` section of `prisma.config.ts`, shaped like the ORM section. Composer's own loader, the walk-up discovery, the `configPath` field, the c12 dependency of the cli package and the `effect` pre-flight are gone. The old file and the old field are refused with diagnostics that show the section to write. All ten examples, the integration test project and the website config carry the section and have no `prisma-composer.config.ts`. The standalone binary still builds and runs on the new section so the tree is releasable at every commit. A new Composer ADR records the decision and ADR-0017 names `prisma.config.ts`. Guard tests of the control-plane boundary pass. - -**Builds on.** Nothing. - -**Hands to.** Slice 2: a family whose section is the configuration, and examples that only need their scripts changed. Slice 3: a published dev build of `@prisma/composer-cli` whose family reads the section. - -**Verified by.** Composer's unit tests for the section validator, the two legacy diagnostics, and the deploy and dev pipelines reading the section; the existing static-graph and control-import checks; the published-tarball resolution script with its adversarial shape reworked to assert that importing Alchemy from a broken tree throws. - -## Slice 2 — The `prisma-composer` binary and its name are gone (prisma/composer) - -**Outcome.** `@prisma/composer-cli` declares no `bin`. `bin.ts`, `cli.ts`, `family/engine-cli.ts`, the pass-through `orm` section family and everything only they used are deleted. The stale comments saying `@prisma/composer` ships the CLI are gone. Every guide, the README, the example READMEs and package scripts, and the shipped skill name `prisma` commands only, and only ones the released `prisma` binary has. A CI check fails on `prisma-composer` as a command or config-file name in those files. The root version advances so the merge publishes a release. - -**Builds on.** Slice 1. - -**Hands to.** Slice 3: a released `@prisma/composer-cli` with no binary and a family that reads the section. - -**Verified by.** The packed tarballs; the CI check failing on a planted mention and passing on `main`; the existing engine-pin check still passing. - -## Slice 3 — The host proves the end state (prisma/prisma-cli, prisma/web, this repo) - -**Outcome.** The `prisma` host pins the released `@prisma/composer-cli`. Its tests that exercised the `configPath` fixture now exercise a real `composer` section. Run from the host binary against the migrated orm-demo example: `prisma deploy` and `prisma dev` work; the legacy file and legacy field cases give Composer's diagnostics; the `effect@4.0.0-rc.118` case gives `CLI.CONFIG_UNREADABLE` with exit code 2 while `prisma --version` still exits 0. The two prisma/web pages that name the old config file are fixed. TML-3340 is closed with a comment linking the PRs. - -**Builds on.** Slice 2. - -**Hands to.** Close-out. - -**Verified by.** The host's test suite; the manual QA transcript recorded in the slice folder and summarised in the PR. - -## Sequencing - -One stack: 1 → 2 → 3. Slice 2 cannot start before slice 1 merges because the binary it deletes must keep working until then, and slice 3 needs slice 2's release to pin. The prisma/web fix in slice 3 could land any time after slice 1 merges, and is folded into slice 3 only to keep the slice count low. - -## Tracking - -TML-3340 is the only ticket, and it tracks the docs and skill outcome of slice 2 and the web fix of slice 3. No further Linear issues are created unless Will asks; Composer work is tracked in the "Prisma Composer" Linear project. - -## Close-out (required) - -- [ ] Verify every item of the project Definition of Done in `spec.md`. -- [ ] The Composer ADR is merged and ADR-0017 amended (slice 1). -- [ ] Final retro run; lessons landed in `drive/` calibration files where they generalise. -- [ ] Strip repo-wide references to `projects/one-config-file/**`. -- [ ] Delete `projects/one-config-file/`. diff --git a/projects/one-config-file/retros.md b/projects/one-config-file/retros.md deleted file mode 100644 index 378b2590dee8..000000000000 --- a/projects/one-config-file/retros.md +++ /dev/null @@ -1,20 +0,0 @@ -# Retros - -## 2026-10-05 · Final retro (mandatory at project close) - -**Trigger.** Project close. Three slices merged; two side fixes found by the slice 3 proof merged or open. - -**What happened against what was planned.** - -- The design settled in one session with Will on 2026-09-30 and did not change during execution. Every open question in the brief was resolved there, and the grounding passes before each slice replaced guesses with file-level facts; the one public API change (the programmatic operations taking `config`) came from grounding, not from the spec, and was raised to Will at the right time rather than discovered at review. -- The slice 3 proof from the real `prisma` binary against a project outside the workspace found a user-facing defect (`DEPLOY.ALCHEMY_BIN_MISSING` in plain pnpm projects) that no unit test or workspace run could have shown. The cross-repository proof the brief demanded earned its cost. -- The one process failure: a branch-only CI flake was "fixed" by serialising suites in `ci.yml` on a hypothesis, and the same test failed again. Root cause was a race in the test plus a test that never exercised its named behaviour, in a package the project did not touch. Lesson landed as `drive/calibration/failure-modes.md` § F39 and in agent memory. -- Guardrail-bot change requests stay "changes requested" after their findings are fixed and the bot re-reviews clean; they block merge until dismissed. Routine now: dismiss with a note once the re-review reports no new findings. -- Release ordering across three repositories worked as planned: engine untouched, Composer 0.26.0 published from the slice 2 merge, host 8.0.0-rc.20 pinned to it, web docs last. - -**Deferred, with homes.** - -- Command-line teardown and logs (`branch delete`, `service logs`): recorded in `projects/consolidate-clis/` as the grammar project's work. -- Dropping the `effect` pin once `effect` 4 is stable or Alchemy pins its peer: raised with Alchemy's maintainer on 2026-09-30. -- The dependency-cruiser exclusion of `prisma.config.ts`: recorded in ADR-0049 in prisma/composer. -- Local Postgres instance names including the registry root; the crash-supervision test's time budget: listed in prisma/composer#333's description. diff --git a/projects/one-config-file/slices/01-composer-section/grounding.md b/projects/one-config-file/slices/01-composer-section/grounding.md deleted file mode 100644 index d09df042a614..000000000000 --- a/projects/one-config-file/slices/01-composer-section/grounding.md +++ /dev/null @@ -1,231 +0,0 @@ -# Slice 1 grounding: the `composer` section holds Composer's configuration - -Read from a clone of prisma/composer `main` at `edaf7b27` (five commits after the spec's `58e858b`; the extra commits touch only the engine-failure cause in the alchemy child). Engine read from the installed `@prisma/cli-engine@0.6.2` in that clone's `node_modules`. All paths below are relative to the prisma/composer root unless they say otherwise. `CLI` below means `packages/0-framework/3-tooling/cli/src`. - -## Surprises first - -1. **The alchemy child imports the config file itself.** `CLI/generate-stack.ts:62,74` and `CLI/dev/generate-dev-stack.ts:65,74` write `import config from ""` into `.prisma-composer/alchemy.run.ts`, and pass it to `lower(app, config, …)`. The child re-evaluates the config from scratch (comment at `CLI/operations/execute-deploy-destroy.ts:302-305`; preflight payloads exist only because of this). After the slice the generated file must import `prisma.config.ts` and read `.composer` from its default export. The handler must therefore still know the path of the file that declared the section, so `configPath` does not disappear from the pipeline; it changes meaning from "the Composer config file" to "the `prisma.config.ts` that declared the section". Because the engine merges the section over a chain of files (see item 3), a section split across two files would need the generated file to import each key from its declaring file (`provenance.keys`), or the validator must refuse a split section. -2. **The public programmatic operations lose their config source.** `deploy`, `destroy`, `dev`, `log` on `@prisma/composer/control` (`CLI/exports/control.ts`; guide `docs/guides/deploying.md:316-330`: `deploy({ entry: 'module.ts', stage: 'pr-42' })`) take no config today and rely on the walk-up. `@prisma/composer` must stay engine-free (`scripts/check-cli-engine-pin.mjs` scans its packed dist), so it cannot call the engine's `loadConfig`. Either the operations gain a required `config` (plus the declaring file path for the stack import) input, or they keep a private loader, which contradicts "one loader". This is a public API change the spec does not mention, and `test/integration/test/control.deploy.test.ts` exercises exactly the no-config call. -3. **The engine discovers `prisma.config.ts` from the cwd and walks up to the repository boundary, merging the chain.** `engine-BLU1DuVo.js:1791 discoveryDirs`, `:1907 loadConfig`. The old loader walked up from the entry file. `prisma deploy apps/x/module.ts` run from a repo root now reads the root's file, not `apps/x`'s. Ancestor files are collected automatically: in `examples/store`, running any `prisma` command that needs config inside `modules/catalog` will also evaluate `examples/store/prisma.config.ts` once it carries the `composer` section, which loads Alchemy. -4. **A project that has only the old file never reaches the handler.** Eight of the ten examples, `test/integration` and `website` have `prisma-composer.config.ts` and no `prisma.config.ts`. After migration-by-hand is skipped, the engine finds no section, the validator gets `undefined`, and the engine fails with `CLI.CONFIG_SECTION_INVALID "The 'composer' section is missing: no config file was found"` (`engine-BLU1DuVo.js:3490`) before any handler runs. The handler-side legacy-file check in the design only fires when a `prisma.config.ts` with a valid section exists next to the old file. The most common legacy case needs the absence diagnostic from the validator to mention `prisma-composer.config.ts`, and the validator has no directory to look in when no file was found (`provenance.files` is empty). -5. **Section failures are headlined by the engine, not by Composer's code.** When a validator returns `ok: false`, the engine's error is `CLI.CONFIG_SECTION_INVALID` and Composer's diagnostics ride along as secondary diagnostics (`engine-BLU1DuVo.js:3450`, `needsErrored(sectionInvalidError(...), validation.diagnostics)`). So the legacy-field refusal renders as `CLI.CONFIG_SECTION_INVALID` with `CONFIG.LEGACY_FIELD` (or whatever code) underneath; only the handler-side legacy-file check can put a `CONFIG.` code in the headline, as the spec's example shows. -6. **Examples import `definePrismaConfig` from `@prisma/cli-engine`, not `prisma/config`.** `examples/auth/prisma.config.ts:1`, `examples/orm-demo/prisma.config.ts:1`, both `examples/store/modules/*/prisma.config.ts:1`. No example depends on the `prisma` package, and `test/integration` depends on `prisma@7.9.0`, whose `prisma/config` is Prisma 7's. The spec's "At a glance" import cannot work inside this repo; migrated files must use `@prisma/cli-engine`, and the eight examples without it must add `@prisma/cli-engine@0.6.2` to their dependencies (the child and the engine both resolve it from the app). -7. **The effect pre-flight has a second caller the design notes misname.** The design says "its two callers, as does the standalone binary's start-up use". On `main` the bin does not call it (`CLI/bin.ts`, `CLI/cli.ts`). The two callers are `load-config.ts:295` (`configSource`) and `CLI/operations/shared.ts:132` (`executorLoadFailure`, which turns a failed executor import into `DEPS.EFFECT_VERSION_CONFLICT`). `operations.test.ts:640-679` asserts that conversion through a real breaker preload. -8. **`check-npm-effect-resolution.mjs` healthy shapes also depend on the old flow.** They run `prisma-composer deploy app.ts` with no config and treat reaching the executor as proof Alchemy loads (`:287-312`). With a required section, the engine refuses before the handler, so the executor never loads and the healthy shapes stop proving anything. Both healthy and adversarial shapes need a direct `import('alchemy')`-style probe, not only the adversarial one. The adversarial override is `effect@4.0.0-beta.93` (`:328`); the design verified the import failure with `rc.118`, so the chosen version must be re-verified. -9. **Unknown fields need a custom validator.** Schema-declared sections (`defineConfigSection({ name, schema })`) are arktype types; nothing in the engine rejects undeclared keys (no `onUndeclaredKey`/`"+"` in the dist). To make unknown fields errors, and to give `configPath` its own migration message and keep the duplicate-extension-id check, the section needs a custom `validate` (it can call `validateSectionWithSchema` inside). -10. **User-facing messages outside the loader name the old file.** `CLI/validate-coverage.ts:12,28` (imports `CONFIG_FILENAME` from `load-config.ts`), `packages/0-framework/3-tooling/assemble/src/assemble-services.ts:34`, `packages/0-framework/1-core/core/src/control/deploy.ts:446`. Tests assert them: `assemble-services.test.ts:148`, `core/src/__tests__/lowering.test.ts:665`. -11. **`c12` is declared in three manifests, not one.** `CLI/../package.json`, `packages/9-public/composer/package.json` and `packages/9-public/composer-cli/package.json`. `load-config.ts:27` is its only importer. All three declarations can go. -12. **Dependency-cruiser stops seeing the config imports.** `dependency-cruiser.config.mjs:216-232` excludes `prisma\.config` (twice) while its comment says `prisma-composer.config.ts` files "MUST be cruised". After the move, control-plane imports in configs are no longer cruised unless that exclusion changes. -13. **The standalone binary is close to free.** `engine-cli.ts` already runs the engine loader over `prisma.config.ts` and mounts a pass-through `orm` section. Keeping it working means updating `commands/destroy.ts:77` and `commands/log.ts:80` like deploy and dev, and nothing else in the bin. -14. **The ORM loader will evaluate the shared file inside the alchemy child.** `packages/1-prisma-cloud/1-extensions/target/src/orm-config.ts:51` loads the postgres resource's `prisma.config.ts` with `@prisma/orm-toolchain/config-loader` (c12, ignores other sections). In orm-demo and auth that is the same root file that will hold the `composer` section, so evaluating it now also imports `@prisma/composer-prisma-cloud/control` (Alchemy) through c12/jiti in a process that already loaded it. Worth one real deploy of orm-demo to confirm no double-load problem. - -## 1. Call sites and how the pipelines get config today - -### Definitions - -- `CLI/load-config.ts`: `CONFIG_FILENAMES` (:37), `CONFIG_FILENAME` (:44), `CONFIG_FILENAME_PATTERN` (:46), `LoadedAppConfig { path, config }` (:48), `findConfigPathForEntry` (:55), `missingConfigError` (:68), `configShapeDiagnostics` (:108), `validateConfigShape` (:183), private `evaluateConfig` (:198, c12 with rc/global/package.json off, same-file check), `ConfigLoadRequest { entryPath, configPath?, cwd? }` (:252), private `configSource` (:291, runs `effectResolutionDiagnostic(cwd)` first, then walk-up or explicit path), `resolveConfigFile` (:349), `loadAppConfigDiagnostics` (:377), `loadAppConfig` (:392). -- `CLI/check-effect-resolution.ts`: `findAlchemyPackageDir` (:17), `resolveEffectVersionFrom` (~:32), `requiredEffectVersion` (:64), `effectMismatchError` (:93), `effectResolutionDiagnostic` (:124), `checkEffectResolution` (:140, throws). - -### Callers (production code) - -| Site | What it does with the result | -|---|---| -| `CLI/pipeline.ts:17` imports `loadAppConfig, resolveConfigFile` | | -| `CLI/pipeline.ts:43-55` `loadConfigStep(entryPath, cwd, deps)` | `resolveConfigFile({ entryPath, configPath: deps.configPath, cwd })` picks the file (effect check, then walk-up or explicit path); then `deps.config ?? (await loadAppConfig(configPath, !explicit)).config`. Returns `{ configPath, config }`. | -| `CLI/pipeline.ts:79-94` `resolveAppIdentity` | calls `loadConfigStep`, returns `{ configPath, config, name }` (used by `log`). | -| `CLI/pipeline.ts:103-149` `runPipeline` | calls `loadConfigStep` (:111), then entry load, `Load`, `validateRegistryCoverage(graph, config)` (:127), `assembleServices(graph, config, cwd, …)` (:140). Returns `{ configPath, config, entryModule, graph, name, assembled }`. | -| `CLI/validate-coverage.ts:12,28` | imports `CONFIG_FILENAME` only for a fix message. | -| `CLI/operations/shared.ts:11,132` | `executorLoadFailure` calls `checkEffectResolution(cwd)`; on throw returns the `DEPS.EFFECT_VERSION_CONFLICT` error with the import error as cause, else `DEPS.EXECUTOR_UNLOADABLE`. Called from `operations/deploy.ts:63`, `destroy.ts:47`, `dev.ts:70`, `log.ts:78` when the lazy `import()` of the executor fails. | -| `CLI/operations/shared.ts:38-40` `OperationDeps.config?`, `.configPath?` | the injection seam for deploy/destroy/dev. | -| `CLI/operations/log.ts:35` `LogDeps.config?`, `.identity?`, `.configPath?` | same for log. | - -`loadAppConfigDiagnostics` has no production caller (only `load-config.test.ts`); it was written for a family shape that never used it. - -### How each pipeline gets its config today - -- **deploy / destroy** (`CLI/operations/execute-deploy-destroy.ts`): `:311-315` builds `PipelineDeps { runAssembler, config: deps.config, configPath: deps.configPath }` and calls `runPipeline` (:328). `:467-473` passes `pipeline.configPath` to `writeStackFile`, which writes the relative import of the config into `.prisma-composer/alchemy.run.ts`. -- **dev** (`CLI/operations/execute-dev.ts`): `:65-69` same `PipelineDeps`, `runPipeline` (:70). `:131-137` `writeDevStackFile({ configPath: pipeline.configPath, … })`. The watch loop re-runs the pipeline with `watchDeps` (:259-263) and rewrites the stack file with `rePipeline.configPath` (:274-279), so the config is re-evaluated on every rebuild today. -- **log** (`CLI/operations/execute-log.ts:151-156`): `deps.identity ?? resolveAppIdentity(entry, name, cwd, { config: deps.config, configPath: deps.configPath })`, then `resolveLocalTargets(identity.config)`. -- **Family handlers** pass the section's field straight through: `commands/deploy.ts:68-73` `operationDeps({ alchemy, configPath: ctx.config.configPath, workspaceId, client: ctx.api })` (helper at `family/converge.ts:70-82`); `commands/dev.ts:211` `{ alchemy: coalescedConverge(alchemy), configPath: ctx.config.configPath }`; `commands/destroy.ts:77` and `commands/log.ts:80` the same (standalone bin only). -- **`composerSection`** is referenced at `family/family.ts:32,67` (`configSection`), `commands/deploy.ts:11,55`, `commands/dev.ts:24,199`, `commands/destroy.ts:13,51`, `commands/log.ts:17,57`, and exported publicly from `CLI/exports/family.ts:17` (so `@prisma/composer-cli/family` exposes `composerSection` and `ComposerSection`). - -Unrelated `configPath` hits (the ORM config path of a postgres resource, leave alone): `packages/1-prisma-cloud/1-extensions/target/src/orm-config.ts`, `orm-migration-resource.ts`, `descriptors/orm-postgres.ts:60`, and their tests. - -## 2. How a family command receives its section - -Command side (`family/commands/deploy.ts:55,70`): - -```ts -needs: { config: composerSection, credentials: 'child' }, -handler: async (args, ctx) => { … configPath: ctx.config.configPath … } -``` - -The handler reads the validated value as `ctx.config`. The handler does not receive provenance. It does receive the chain: - -```ts -// report-B_8n69OY.d.ts:1179-1199 -interface CommandContext { - readonly config: TConfig; // validated section value - readonly configFiles: ReadonlyArray; // chain, nearest first; empty when no file - readonly configPath: string | undefined; // the --config flag as written - … -} -interface LoadedConfigFile { readonly path: string; readonly sections: Readonly>; } -``` - -Provenance reaches only the validator, as its second argument. So "the directory of the loaded prisma.config.ts" is available to the handler as `path.dirname(ctx.configFiles[0].path)`, or the validator can copy `provenance.files[0]` / `provenance.keys` into the value it returns. A schema-declared section whose value is a plain object gets `baseDir` (directory of the nearest declaring file) added automatically; a whole-section `reference` does not. - -Engine signatures (`node_modules/@prisma/cli-engine/dist/report-B_8n69OY.d.ts`): - -```ts -interface SectionProvenance { // :759 - readonly files: readonly string[]; // declaring files, nearest first - readonly keys: Readonly>; // declaring file per top-level key -} -declare function resolveSectionPath(provenance: SectionProvenance, key: string, path: string): string; // :803, throws on unknown key -declare const configSchema: typeof configScope.type; // :830, arktype + `path` keyword -type ConfigSchema = Type; -type ConfigSchemaValue = S["infer"] & { readonly baseDir?: string }; -declare function reference(schema: S): S; // :867, command gets the file's own object; no path/default inside -declare function validateSectionWithSchema(name: string, schema: S, raw: unknown, provenance: SectionProvenance): SectionValidation>; // :870, never throws -interface ConfigSection { // :884 - readonly name: string; - readonly validate: (raw: unknown | undefined, provenance: SectionProvenance) => SectionValidation; - readonly merge?: (parent: unknown, child: unknown) => unknown; // default: per top-level key, nearer wins -} -type SectionValidation = // :902; diagnostics on ok are warnings to stderr - | { readonly ok: true; readonly value: T; readonly diagnostics: readonly Diagnostic[] } - | { readonly ok: false; readonly diagnostics: readonly Diagnostic[] }; -declare function defineConfigSection(spec: { name: string; schema: S; merge?: SectionMerge }): ConfigSection>; // :919 -declare function defineConfigSection(spec: { name: string; validate: SectionValidator; merge?: SectionMerge }): ConfigSection; // :924 -interface NeedsSpec { readonly config?: ConfigSection; … } // :1440 -declare function definePrismaConfig>(config: T): T & { readonly $prismaConfig: number }; // index.d.ts:81 -declare function loadConfig(cwd: string, configPath?: string, cliVersion?: string): Promise; // index.d.ts:105 -``` - -Engine flow (`engine-BLU1DuVo.js:3430-3462`): `checkConfiguration` calls `runtime.loadConfig(--config)`, fails first on file-level diagnostics (unreadable file: `CLI.CONFIG_UNREADABLE`, exit 2, `:1696,:1709`) and on unknown top-level keys (`CLI.CONFIG_UNKNOWN_SECTION`, `:3407`), then `resolveSectionOverChain` and `section.validate(value, provenance)`. A throwing validator is settled as a bug. `ok: false` becomes `CLI.CONFIG_SECTION_INVALID` with the validator's diagnostics attached (`:3480-3496`); the missing-section form says "no config file was found" or "no loaded config file declares it". - -The ORM section's pattern (this repo, `packages/1-framework/3-tooling/config-loader/src/orm-section.ts:74-90`): `configSchema({ family: reference(configSchema({ kind: "'family'", …, emission: 'object' })), target: reference(…), 'extensions?': [reference(configSchema({ kind: "'extension'", … })), '[]'], … })`. - -Current Composer section (`family/section.ts`): custom `validate`; absent section is `{ ok: true, value: {} }` (:56); non-object and unreadable (Proxy) sections fail with `CONFIG.FIELD_INVALID` (:58-92); `configPath` must be a non-empty string (:94-108) and is resolved with `resolveSectionPath` inside try/catch (:116-134); unknown fields are `CONFIG.FIELD_UNKNOWN` warnings (:136-153); `defineConfigSection({ name: 'composer', validate })` (:156). Its header (:4-13) states the reason the design notes call wrong. - -## 3. Types and `configShapeDiagnostics` - -`packages/0-framework/1-core/core/src/control/app-config.ts`: - -- `ExtensionDescriptor` (:46-113): `id: string`, `nodes: Record`, optional `provisions` (ReadonlyMap), `application`, `providers()`, `preflight()`, `teardown()`, `container`, `reporter`, `localTarget()`. -- `StateDescriptor` (:119-124): `extension: string`, `create(container): AlchemyStateLayer`. -- `PrismaAppConfig` (:326-329): `{ readonly extensions: ExtensionDescriptor[]; readonly state: StateDescriptor }` (mutable array type). -- `defineConfig(config: PrismaAppConfig): PrismaAppConfig` (:332), identity. Published as `@prisma/composer/config` via `packages/9-public/composer/src/exports/config.ts` (`export * from '@internal/core/config'`), which also exports `deserializeContainers`, `containerEnv`, `DEV_DIR` etc. that the generated dev stack imports. -- Header comments at :1 and :331, and `core/src/exports/app-config.ts:1`, name `prisma-composer.config.ts`. - -`configShapeDiagnostics(loaded, configPath)` (`CLI/load-config.ts:108-180`) checks, collecting rather than throwing: - -| Check | Code | Message | -|---|---|---| -| export not an object, or no keys | `CONFIG.EXPORT_INVALID` | `"" exported no config.`; fix names `defineConfig({ extensions: [...], state: ... })` from `'@prisma/composer/config'`; `where.path` | -| `extensions` not an array | `CONFIG.FIELD_INVALID` | `: \`extensions\` must be an array.`; `meta.field` | -| `extensions[i]` not an object | `CONFIG.FIELD_INVALID` | `` `extensions[i]` must be an extension descriptor object `` | -| `extensions[i].id` not a non-empty string | `CONFIG.FIELD_INVALID` | `` `extensions[i].id` must be a non-empty string (the extension package name) `` | -| duplicate `id` | `CONFIG.EXTENSION_DUPLICATE` | `: extension "" is listed more than once in \`extensions\`.` | -| `extensions[i].nodes` not an object | `CONFIG.FIELD_INVALID` | `` `extensions[i].nodes` must be an object (the node-ID → control registry) `` | -| `state` not an object with string `extension` and function `create` | `CONFIG.FIELD_INVALID` | `` `state` must be a state descriptor (e.g. prismaState()) `` | - -All `FIELD_INVALID` errors carry fix `"See defineConfig() in '@prisma/composer/config'."`. Other codes in the file: `CONFIG.FILE_MISSING` (:71, :331), `CONFIG.EVALUATION_FAILED` (:221), `CONFIG.PATH_MISMATCH` (:238), `CONFIG.PATH_NOT_ABSOLUTE` (:316). The pre-flight's code is `DEPS.EFFECT_VERSION_CONFLICT`. `isRecord` treats arrays as records, so `extensions[i]` as an array passes the object check today. - -## 4. Tests that reference the old file, the loader, `configPath`, the pre-flight or `CONFIG.` codes - -In `packages/0-framework/3-tooling/cli/src`: - -- `__tests__/load-config.test.ts` (348 lines): walk-up finds, nests, nearest wins, spelling precedence, missing-config names every spelling; `resolveConfigFile` explicit path, `FILE_MISSING`, `PATH_NOT_ABSOLUTE`, walk not explicit; real c12 load, throwing module is `EVALUATION_FAILED`; `validateConfigShape` errors per field. Delete with the loader; port the shape cases to the section validator's tests. -- `__tests__/check-effect-resolution.test.ts` (190 lines): alchemy dir walk, effect resolution from a package, pin reading, mismatch message, `checkEffectResolution` on healthy and broken trees. Delete. -- `family/__tests__/section.test.ts` (149 lines): absence ok, `configPath` resolution against declaring file, missing provenance key fails, non-object fails, unknown field warns, diagnostics carry nextActions, validator never throws (fuzz), unreadable Proxy fails, name is `composer`. Rewrite for the new shape. -- `family/__tests__/engine-cli.test.ts` (272 lines): standalone CLI composition; `:199-265` a `configPath` reaches `ctx.config` resolved against the declaring file, root-declared `configPath` from a subdirectory, non-string `configPath` refused. Rewrite those three. -- `family/__tests__/host-adapter.test.ts` (153 lines): `runComposerCli` with a real host; `:100-125` `--config` to a missing file is `CLI.CONFIG_NOT_FOUND`; `:128-152` writes `export default { $prismaConfig: 1, composer: { configPath: "custom" } }` and asserts `double.calls.deps.dev[0]?.configPath` is resolved. The second needs a real section. -- `family/__tests__/runtime.test.ts` (294 lines): `:268-292` the runtime passes `loadConfig` through and forwards the `--config` path; uses `{ composer: { configPath: 'x.ts' } }` only as opaque data. -- `family/__tests__/deploy-destroy.test.ts` (405 lines, `:76-80`) and `dev-interrupt.test.ts` (`:26-29`): `createTestCli({ commandFamilies, commands, config: {} })` with the control double. An empty config currently validates; with required fields these harnesses need a fake `composer` section. -- `family/__tests__/converge-settlement.test.ts`: uses its own probe family; no Composer section. -- `family/__tests__/translate-error.test.ts:44-72`: translates `CONFIG.FIELD_INVALID` with `where.path: '/app/prisma-composer.config.ts'` and `CONFIG.FILE_MISSING`; only fixture strings. -- `__tests__/generate-stack.test.ts` (187 lines, :12-178): asserts `import config from "../prisma-composer.config.ts";` and relative-path cases for the rendered stack file. Rewrite for the new import. -- `dev/__tests__/generate-dev-stack.test.ts` (:9-52): same for the dev stack file. -- `operations/__tests__/operations.test.ts` (1775 lines): `makeAppDir` writes a `prisma-composer.config.ts` as a discovery target (:154-166) while tests inject `deps.config`; `:409-426` missing config is `CONFIG.FILE_MISSING` naming the spellings; `:640-679` a preloaded breaker makes the executor import fail and asserts `DEPS.EFFECT_VERSION_CONFLICT` with the import error as cause; `:681-695` `executorLoadFailure` gives `DEPS.EXECUTOR_UNLOADABLE`; `:933` a fake identity `{ configPath: 'c', config, name }`. -- `__tests__/deployment-summary.test.ts:58,68`: stack-file snippets containing `import config from '../prisma-composer.config.ts'` as parse fixtures. -- `__tests__/fixtures/run-load-entry.ts:4`: comment only. - -Elsewhere: - -- `packages/0-framework/3-tooling/assemble/src/__tests__/assemble-services.test.ts:148`: `ASSEMBLE.EXTENSION_MISSING` fix contains `prisma-composer.config.ts`. -- `packages/0-framework/1-core/core/src/__tests__/lowering.test.ts:665`: `LowerError` message contains `prisma-composer.config.ts`. -- `packages/0-framework/2-authoring/node/src/__tests__/no-control-import.test.ts` and `…/nextjs/…/no-control-import.test.ts`: ADR-0017 guard; nothing reachable from the authoring entry imports a `/control` entry. Name the old file only in the header comment (:7). -- `packages/1-prisma-cloud/1-extensions/target/src/__tests__/invariants.test.ts:194-197`: the same guard for the Prisma Cloud target; comment names the old file. -- `packages/0-framework/0-foundation/foundation/src/cli-structured-error.test.ts`: uses `CONFIG.FILE_NOT_FOUND` as an arbitrary code. Unaffected. -- `test/integration/test/cli.extension-config.test.ts`: spawns `prisma-composer deploy ` from `test/integration` with a fake service token; asserts the real `/control` entries resolve and the run fails at "no built entry". Needs a `prisma.config.ts` in `test/integration`. -- `test/integration/test/control.deploy.test.ts`: `deploy({ entry, cwd })` from `@prisma/composer/control` fails with `ASSEMBLE.BUILD_FAILED`; relies on walk-up discovery (see surprise 2). -- `test/integration/test/cli.engine-shell.test.ts`: the binary names itself, lists four commands, grammar errors exit 2, signed-out deploy refused before evaluation. Unaffected by the config shape. -- `test/integration/test/local-dev*.ts`: drive `prisma-composer dev` against `test/integration` and `examples/store`; `local-dev.integration.ts:103,201` uses its own `fixtures/local-dev/dev-config.ts` and renders its own stack import. -- `test/integration/test/fixtures/extension-config/service.ts:8`, `fixtures/local-dev/module.ts:5`, `dev-config.ts:3`: comments naming the old file and the walk-up. - -## 5. Config files in the repo - -`prisma-composer.config.ts` (all default-export `defineConfig({ extensions, state: prismaState() })` from `@prisma/composer/config`, with a header comment naming the file): - -| File | extensions | Sibling `prisma.config.ts` | -|---|---|---| -| `examples/auth/` | `prismaCloud(), nodeBuild()` | yes: `definePrismaConfig` from `@prisma/cli-engine`, `orm` with `contract`, `db.connection`, `extensions: [authPack]` | -| `examples/bucket/` | `prismaCloud(), nodeBuild()` | no | -| `examples/cron/` | `prismaCloud(), nodeBuild()` | no | -| `examples/email/` | `prismaCloud(), nodeBuild()` | no | -| `examples/env-param/` | `prismaCloud(), nodeBuild()` | no | -| `examples/orm-demo/` | `prismaCloud(), nodeBuild()` | yes: `definePrismaConfig` from `@prisma/cli-engine`, `orm` with `contract`, `db.connection` | -| `examples/storage/` | `prismaCloud(), nodeBuild()` | no | -| `examples/store/` | `prismaCloud(), nodeBuild(), nextjsBuild()` | no at the root; `modules/catalog/prisma.config.ts` and `modules/orders/prisma.config.ts` hold ORM-only sections loaded by path by the postgres resources | -| `examples/storefront-auth/` | `prismaCloud(), nodeBuild(), nextjsBuild()` | no | -| `examples/streams/` | `prismaCloud(), nodeBuild()` | no | -| `test/integration/` | `prismaCloud(), nodeBuild()` | no | -| `website/` | `prismaCloud(), nodeBuild()` | no | - -Other `prisma.config.ts` files (ORM-only, not Composer projects): `packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/{gadget,packed,widget}-contract/source/prisma.config.ts`, `packages/1-prisma-cloud/2-shared-modules/auth/src/pack/prisma.config.ts`. - -tsconfig `include` lists `prisma-composer.config.ts` in `examples/{auth,bucket,email,orm-demo,storage,streams}/tsconfig.json` and `website/tsconfig.json`. - -Package scripts: every example except `bucket` (no deploy script), plus `website`, has `deploy` and `destroy` scripts running `bun node_modules/.bin/prisma-composer deploy|destroy module.ts …`; `env-param` also has `destroy:stage`. `scripts/destroy-guard.sh:20` and `.github/workflows/deploy-docs.yml:80` run the binary too. The spec puts the script changes in slice 2; slice 1 leaves them on the standalone binary, which keeps working. - -Dependencies of note: only `examples/auth` and `examples/orm-demo` declare `@prisma/cli-engine` (0.6.2). No example declares `prisma`. `test/integration` declares `prisma@7.9.0`. - -## 6. Dependencies - -- `c12` declared in `packages/0-framework/3-tooling/cli/package.json` (`^3.3.4`), `packages/9-public/composer/package.json` (`^3.3.4`), `packages/9-public/composer-cli/package.json` (`^3.3.4`). Imported only by `CLI/load-config.ts:27`. The engine carries its own exact `c12@3.3.4`. -- `@prisma/cli-engine` in the cli package: dependency `0.6.2` of `@internal/cli`; peer `0.6.2` and dev `0.6.2` of `@prisma/composer-cli`; also a dependency of `packages/1-prisma-cloud/1-extensions/target` (0.6.2). Imported in `CLI/family/{family,section,converge,workspace,target,translate-error,engine-cli,runtime}.ts` and `family/commands/*.ts`, plus family tests (`@prisma/cli-engine/testing`'s `createTestCli`, `mintTestJwt`). Nothing outside `family/` imports it, which keeps `@prisma/composer` engine-free. - -## 7. ADR conventions - -- Files: `docs/design/90-decisions/ADR-NNNN-.md`, four-digit number, slug states the decision. -- Highest number: `ADR-0048-prisma-cloud-resources-come-from-the-upstream-alchemy-provider.md`. The new ADR is `ADR-0049`. -- Index: `docs/design/90-decisions/README.md` § Index, one bullet per ADR: `- [ADR-NNNN](file.md) — .` Supersession is an italic suffix on the old entry, e.g. `*(Superseded in part by ADR-0047: …)*`. ADR-0017's entry is line 41 and names `prisma-composer.config.ts`. -- Template: `docs/design/99-process/templates/adr.md`. Sections Decision (with a code snippet directly under it), Reasoning, Consequences, Alternatives considered, Related. No Status section; no tickets, dates or project history in the body. -- ADR-0045 headings: `# ADR-0045: Deploy state lives behind the platform state API; deploys hold a server-side lease` / `## Decision` / `## Reasoning` / `## Consequences` / `## Alternatives considered` / `## Related`. (ADR-0048 adds topic subsections between Decision and Consequences and ends with `## References`.) -- ADR-0017 (161 lines) names `prisma-composer.config.ts` at :5, :10 (code-block comment), :43; headings Decision, Reasoning, Consequences, Alternatives considered, Related. -- Related ADRs a reader would expect touched: ADR-0043 (programmatic `@prisma/composer/control` surface, see surprise 2) and ADR-0003 ("there is no deploy config file"). The spec limits design-doc changes to the new ADR and ADR-0017. - -## 8. Testing conventions in the cli package - -- Runner: `bun test` (`packages/0-framework/3-tooling/cli/package.json` `"test": "bun test"`); `import { describe, expect, test } from 'bun:test'`. Tests live in `src/**/__tests__/*.test.ts` with fixtures in `__tests__/fixtures/`. CI runs `pnpm turbo run test`, `pnpm --dir test/integration exec bun test`, `pnpm test:scripts` (`node --test scripts/*.test.mjs scripts/*.test.ts`), and `node-compat.test.ts` under Node. -- Family against the engine: `createTestCli` from `@prisma/cli-engine/testing`, seeded with `config: {
: … }` or a `loadConfig` override, mounting `createComposerFamily({ operations: createControlDouble(...).operations })` (`deploy-destroy.test.ts:70-83`, `dev-interrupt.test.ts:26`). The double (`CLI/testing/control-double.ts`, public as `@prisma/composer-cli/testing`) records each call's input and its deps (`calls.deps.deploy[i].configPath`). A real-disk run goes through `runComposerCli(argv, fakeHost(dir), { version, operations })` with a real `prisma.config.ts` written to a temp dir (`host-adapter.test.ts:128-152`). `engine-cli.test.ts` builds probe commands on `composerSection` with `createCli` and a stub `loadConfig`. -- `scripts/check-family-static-graph.mjs`: packs `@prisma/composer-cli`, walks the static import graph of `dist/family.mjs`, `dist/bin.mjs` and `dist/testing.mjs`; fails on any `alchemy`, `effect` or `@effect/*` specifier and on any static `@prisma/composer/...` import; requires the `@prisma/cli-engine` specifier in family and bin (proof it stayed external) and the `createControlDouble` definition in testing; testing may contain no dynamic import. The new section validator is in this graph, so it must import nothing heavy. -- `scripts/check-cli-engine-pin.mjs`: composer-cli's engine peer, dev dependency and `@internal/cli`'s dependency are the same exact version; the engine survives as a bare import in composer-cli's packed dist, including `dist/bin.mjs` by name (fails if `bin.mjs` is missing from the tarball, `:193`); `@prisma/composer`'s packed dist has no engine import or inlined copy and its manifest declares no engine. -- No script greps for `prisma-composer` as a check. Mentions exist in comments of `check-npm-effect-resolution.mjs`, `check-family-static-graph.mjs:3`, `check-floor-imports.mjs:12`, `check-publish-deps.mjs:240`, `check-skill-packaging.mjs:2` (skill name), `set-version.ts:63`, `cold-start-canary.ts:214,251`, `ci-cleanup-utils.ts` (state project name), and `.github/workflows/ci.yml:235`. - -## 9. Public packages - -`packages/9-public/composer-cli/package.json`: `bin: { "prisma-composer": "./dist/bin.mjs" }`; exports `./family`, `./testing`, `./package.json`; deps `@prisma/composer` (workspace), `alchemy@2.0.0-beta.78`, `c12`, `cross-spawn`, `effect@4.0.0-rc.115`, `esbuild`; peer `@prisma/cli-engine@0.6.2`. `tsdown.config.ts`: config 1 entries `family: src/exports/family.ts`, `testing: src/exports/testing.ts`, external `esbuild`, `@prisma/cli-engine`, inline `@internal/*`; config 2 entry `bin: ../../0-framework/3-tooling/cli/dist/bin.mjs`, no d.ts, same externals. `src/exports/family.ts` re-exports `@internal/cli/family`, which includes `createComposerCli`, `runComposerCli`, `BINARY_NAME`, `createComposerFamily`, `realOperations`, `composerSection`, `ComposerSection`, `toEngineError`. - -`packages/9-public/composer/package.json`: exports `.`, `./config`, `./control`, `./deploy`, `./local-target`, `./report`, `./testing`, `./casts`, `./assertions`, `./arktype`, `./service-rpc`, `./node`, `./node/control`, `./nextjs`, `./nextjs/control`, `./package.json`; no bin; deps include `alchemy`, `effect@4.0.0-rc.115` and companions pinned exactly, and `c12`. `tsdown.config.ts` maps each export to `src/exports/.ts`; external `esbuild` and `/^@prisma\/cli-engine(\/|$)/` so an accidental engine import stays visible. `./control` is `export * from '@internal/cli/control'`, i.e. the programmatic operations plus the loader they call today. - -`@internal/cli` (`CLI/../tsdown.config.ts`): entries `index`, `bin` (`src/bin.ts`), `report`, `control`, `family`, `testing`. `CLI/exports/index.ts` exports `cli`, `shippedVersion`, stack-file renderers, `loadEntry`, alchemy helpers. - -`scripts/check-npm-effect-resolution.mjs`: - -- Packs `@prisma/composer`, `@prisma/composer-cli`, `@prisma/composer-prisma-cloud` with `pnpm pack`, then installs into scratch apps with real npm. -- Healthy shapes: `composer-and-cli` (current npm), `composer-and-cli-npm10` (`npx --yes npm@10`), `composer-cli-and-prisma-cloud`. Each asserts: install finishes within 5 minutes; `npm ls effect --all` shows exactly one `effect`, equal to `@prisma/composer`'s exact pin; `effect` resolved from alchemy's position is the pin; `node_modules/.bin/prisma-composer --help` lists `deploy `, `destroy `, `dev `, `log ` and shows no module crash; `prisma-composer deploy app.ts` with a fake `PRISMA_SERVICE_TOKEN` does not print the pre-flight marker `alchemy resolves effect@` and shows no `is not a function` / `Cannot find module`. -- Adversarial shape: all three tarballs with npm `overrides: { effect: '4.0.0-beta.93' }`. An `ERESOLVE`-style install refusal passes; otherwise asserts alchemy resolves a non-pinned `effect`, `prisma-composer deploy app.ts` exits non-zero with the marker `alchemy resolves effect@` and no `is not a function`, and `prisma-composer --help` still exits 0. -- Invokes the binary with `spawnSync(join(appDir, 'node_modules', '.bin', 'prisma-composer'), args, { cwd: appDir })`. No `prisma.config.ts` is written into the scratch apps. diff --git a/projects/one-config-file/slices/01-composer-section/plan.md b/projects/one-config-file/slices/01-composer-section/plan.md deleted file mode 100644 index 5274ad18e05d..000000000000 --- a/projects/one-config-file/slices/01-composer-section/plan.md +++ /dev/null @@ -1,35 +0,0 @@ -# Slice 1 plan - -Spec: [`spec.md`](./spec.md). Branch in prisma/composer: `one-config-file/composer-section`, from `main`. One implementer, resumed across dispatches. Each dispatch ends with commits on the branch and the slice's tests green; nothing is left uncommitted. - -## Dispatch 1 — The section validator holds the configuration - -**Outcome.** `family/section.ts` validates `{ extensions, state }` by identifying fields, passes descriptors through by reference, errors on unknown keys and on `configPath` with the migration message, handles the absent section with the best message the engine allows, and merges by taking the nearest file's section whole. Tests written first cover every branch. The exported section type is `PrismaAppConfig` from core. - -**Builds on.** Nothing. - -**Hands to.** Dispatch 2: a validated `PrismaAppConfig` value with provenance reaching the `deploy`, `dev`, `destroy` and `log` handlers. - -## Dispatch 2 — Pipelines and programmatic operations consume the section - -**Outcome.** The four handlers pass `{ value, path }` to the pipelines; the handler checks the section's directory for `prisma-composer.config.*` and fails with `CONFIG.LEGACY_FILE`. The programmatic `deploy`, `destroy`, `dev` and `log` on `@prisma/composer/control` take a required `config: { value, path }`. The generated deploy and dev stack files import `prisma.config.ts` and read `.composer`. `load-config.ts`, `check-effect-resolution.ts`, the `executorLoadFailure` effect diagnosis, `CONFIG_FILENAME`, the three old-file messages and the `c12` declarations are gone. Every affected test in `grounding.md` § 4 is updated or deleted with its subject. `packages/` builds. - -**Builds on.** Dispatch 1. - -**Hands to.** Dispatch 3: a cli package and public packages that read only `prisma.config.ts`. - -## Dispatch 3 — Examples, fixtures, CI script, guides, skill and ADRs - -**Outcome.** All ten examples, `test/integration` and `website` carry the section in `prisma.config.ts` and have no `prisma-composer.config.ts`; missing `@prisma/cli-engine` dependencies added at the pinned version and the lockfile updated with `pnpm install`; tsconfig includes fixed. `scripts/check-npm-effect-resolution.mjs` asserts by importing Alchemy directly. The guides and the skill describe the section and the programmatic `config` input. ADR-0049 written, ADR-0017 and ADR-0043 amended, the ADR index updated. The slice's done conditions from `spec.md` are run from `examples/orm-demo` and the transcript saved to `wip/slice-01-qa.md` in the Composer clone. - -**Builds on.** Dispatch 2. - -**Hands to.** Code review, then PR open. - -## Dispatch 4 — Review fixes - -**Outcome.** Every finding from `drive-code-review` that is a defect is fixed and the review re-run is clean; findings triaged out are listed in the PR. - -**Builds on.** Dispatch 3 and the review. - -**Hands to.** PR open. diff --git a/projects/one-config-file/slices/01-composer-section/review/code-review.md b/projects/one-config-file/slices/01-composer-section/review/code-review.md deleted file mode 100644 index 8337a1267453..000000000000 --- a/projects/one-config-file/slices/01-composer-section/review/code-review.md +++ /dev/null @@ -1,194 +0,0 @@ -# Code review: slice 1, the `composer` section holds Composer's configuration - -Repository: prisma/composer, branch `one-config-file/composer-section`, range `main...HEAD` (11 commits, 125 files). Reviewer lens: principal engineer. Expectations: [`../spec.md`](../spec.md) (slice done conditions), [`../../../spec.md`](../../../spec.md) (cross-cutting requirements 1–7), [`../../../design-notes.md`](../../../design-notes.md), [`../grounding.md`](../grounding.md). All repo paths below are relative to the Composer clone. - -## Summary - -The slice does what it set out to do. `family/section.ts` now validates Composer's whole configuration, the old loader, the effect pre-flight and `c12` are gone with no surviving references, all ten examples plus `test/integration` and `website` carry a `composer` section, and the CLI refuses the old file and the old field with clear diagnostics. The cli package suite passes (249 pass, 3 skip, 0 fail), as do `check-cli-engine-pin`, `check-family-static-graph`, both control-import guard tests and the Prisma Cloud invariants test. - -The weak points are at the edges the slice created. The new programmatic `config` input is trusted blindly, so a caller's mistake fails late, possibly after containers were created. The `dev` watch loop now keeps the startup value in the parent while each rebuild's child re-imports the file from disk, so the "restart after editing" note in the docs is not quite true. The one behaviour no test or QA step executes is the generated stack file importing `prisma.config.ts` and reading `.composer`. The QA deploy stopped at assemble, not at the plan stage the done condition names. The deploying guide contradicts itself on the result shape. - -## What looks solid - -- **Deletions are complete.** `git grep` finds no `loadAppConfig`, `resolveConfigFile`, `findConfigPathForEntry`, `checkEffectResolution`, `effectResolutionDiagnostic`, `EFFECT_VERSION_CONFLICT`, `CONFIG_FILENAME`, `ConfigLoadRequest` or `c12` import in `packages/`, `scripts/`, `examples/`, `test/` or `website/`. The remaining `configPath` hits outside the ORM resource code are the `CONFIG.LEGACY_FIELD` diagnostic and its tests. -- **Lockfile.** The diff removes `c12` from three importers and adds `@prisma/cli-engine@0.6.2` to the eight examples, `test/integration` and `website` that lacked it. Nothing else moves. -- **Provenance is sound.** I read the engine (`@prisma/cli-engine@0.6.2`, `resolveSectionOverChain`, `provenanceOf`, `loadConfig`). `provenance.files` lists the declaring files nearest first. The section's `merge` returns the child whole, so `files[0]` is the file whose value was used. A section declared only in a parent file yields that parent file, so the legacy check and the stack import both target the parent. That case is tested in `family/__tests__/composer-config.test.ts` lines 27–41 and `engine-cli.test.ts` "a section declared only at the repo root". A relative `--config` is resolved against cwd and passed through `realpath` by the engine, and `process.cwd()` is already physical, so the CLI path never mixes a logical and a physical path. -- **The legacy check reads one directory.** `composerConfigOf` joins the four names onto `path.dirname(declaringFile)` only. A test asserts that an old file in a non-declaring directory is ignored. -- **Descriptors pass by identity.** `validateSection` pushes the original entry objects, and `section.test.ts` "hands every descriptor to the command by reference" asserts `toBe`. The engine freezes only the section's own shallow copy, never the descriptors. -- **The validator never throws.** The `try` around `validateSection` turns a throwing getter or Proxy into `CONFIG.FIELD_INVALID`, and a test exercises it. -- **`DEPS.EXECUTOR_UNLOADABLE` still names the module error.** `operations/shared.ts` lines 117–131 put the import error's message in the summary and keep the error as `cause`. `operations.test.ts` lines 579–644 check both in a real subprocess. -- **The ORM loader tolerates the shared file.** I ran `resolveOrmConfig` from `packages/1-prisma-cloud/1-extensions/target/src/orm-config.ts` against the migrated `examples/orm-demo/prisma.config.ts` and `examples/auth/prisma.config.ts`. Both resolved: the migrations directory, and 0 and 1 extension packs respectively. A full deploy stays in slice 3's QA. -- **The ADR explains the decisions.** ADR-0049 records why the engine's schema-and-`reference()` path was rejected (it does not reject unknown keys, uses `CLI.` codes, and gives `configPath` no message of its own). It also records why the absent-section message cannot check the directory: the engine gives empty provenance when no file declares the section. -- **The fixture's cast is honest.** `family/__tests__/fixtures/composer-section.ts` gives `blindCast` a reason, and the reason is true: its users (`deploy-destroy.test.ts`, `dev-interrupt.test.ts`, `composer-config.test.ts`) substitute the operations and never call a descriptor. - -## Findings - -### F01: the programmatic `config` input is not checked, so a caller's mistake fails late - -- **Location:** `packages/0-framework/3-tooling/cli/src/pipeline.ts` lines 76–86; `packages/0-framework/3-tooling/cli/src/operations/deploy.ts` lines 50–71 (the same shape in `destroy.ts`, `dev.ts` and `log.ts`). -- **Issue:** Before this slice, programmatic callers went through `loadAppConfig`, which ran the shape check and failed early when the file was missing. Now `runPipeline` takes `composerConfig.value` as given and resolves `path` without checking that it exists. Three plausible mistakes now fail late: - - Passing `prismaConfig` instead of `prismaConfig.composer` surfaces as a `TypeError` from `validateRegistryCoverage` iterating `undefined`. - - A wrong `path` is found only when the Alchemy child fails to import it. That happens after `executeDeploy` has created containers and run preflight (the stack file is written at `execute-deploy-destroy.ts` line 471). - - A `path` pointing at a file that inherits the section from a parent file (for example `examples/store/modules/catalog/prisma.config.ts`) makes the child read `prismaConfig.composer` as `undefined`. - - The `CONFIG.LEGACY_FILE` check also runs only in the CLI handler. -- **Suggestion:** At the start of each operation, validate `value` with the same checks the section runs, check that `path` exists, and check that the file sits in a directory with no `prisma-composer.config.*`. `section.ts` imports the engine, which `@prisma/composer` must not depend on. So move the engine-free checks (`validateSection` and `legacyFileError`, which need only the `Diagnostic` type and `node:fs`) into a module the section and the operations both call. Put that module in the operations' `CliStructuredError` shape at the call site. - -### F02: after an edit to `prisma.config.ts`, `dev` builds the parent from the old config and the child from the new one - -- **Location:** `packages/0-framework/3-tooling/cli/src/operations/execute-dev.ts` lines 255–296; `docs/guides/running-locally.md` lines 51–53; `skills/prisma-composer-core-concepts/SKILL.md` lines 390–391; ADR-0049 Consequences. -- **Issue:** The watch loop reruns `runPipeline` with `input.config`, the value read at start. Each rebuild then spawns a fresh Alchemy child, and its generated stack file re-imports `prisma.config.ts` from disk. After an edit, the parent checks registry coverage and assembles against the old extensions, while the child lowers against the new ones. The docs say an edit "takes effect only after you stop `dev`". In fact it takes partial effect on the next source change. Before this slice the parent reloaded the config on each rebuild, so the two sides agreed. -- **Suggestion:** Add `config.path` to the watch targets. When it changes, emit an event that tells the user to restart `dev`, and skip rebuilds until they do. Keep the doc sentence. Alternatively, reword the docs to describe the actual behaviour. The first option is better because it removes the mismatch instead of documenting it. - -### F03: no test or QA step runs the generated stack's `prisma.config.ts` import - -- **Location:** `packages/0-framework/3-tooling/cli/src/generate-stack.ts` lines 59–78; `dev/generate-dev-stack.ts` lines 62–82; `test/integration/test/local-dev.integration.ts` lines 103 and 200–225; `wip/slice-01-qa.md` §3. -- **Issue:** The central runtime change is that the child now imports `prisma.config.ts` and reads `prismaConfig.composer`. Unit tests assert only the rendered text. The QA deploy moved `dist/` aside and stopped at `ASSEMBLE.BUILD_FAILED`, before any stack file was written. The spec's done condition asks for a deploy that reaches the plan stage. The local-dev integration test renders its own copy of the dev stack, still in the old shape (`import config from dev-config.ts`, used directly), so it no longer mirrors `renderDevStackFile` and would not catch a regression there. -- **Suggestion:** Run `dev` on an example locally (no network is needed) or a signed-in deploy that reaches the Alchemy plan, and record the output in the PR. Then make `local-dev.integration.ts` call the real `renderDevStackFile` with a `prisma.config.ts` fixture whose `composer` section is `dev-config`'s value. That way the rendered import is executed in CI. - -### F04: the adversarial CI shape passes on any import failure - -- **Location:** `scripts/check-npm-effect-resolution.mjs` lines 186–210 and 343–350. -- **Issue:** The adversarial check accepts any non-zero exit from `import('alchemy')…` as proof that the broken `effect` was caught. A future Alchemy bump that renames `alchemy/Output`, or a probe typo, would also pass. The healthy shapes run first and import the same subpaths, which limits the risk, but the adversarial assertion does not say what it claims. The probe also leaves out the Prisma Cloud providers, which is what a user's config actually imports. -- **Suggestion:** Require the adversarial output to point into `effect`, for example `/node_modules\/effect\/|from 'effect|effect\/dist/`. Fail with the output when it does not. If the Prisma Cloud tarball is installed in the healthy shapes, add `@prisma/composer-prisma-cloud/control` to the healthy probe, so it matches what `prisma.config.ts` evaluates. - -### F05: the deploying guide contradicts itself on the result shape - -- **Location:** `docs/guides/deploying.md` lines 373–387 and 402–415. -- **Issue:** This PR rewrote the example to `if (result.ok) … result.value.summary`. Two paragraphs later, the bullet it also edited (adding `DEPS.EXECUTOR_UNLOADABLE`) still says operations resolve to `{ outcome: 'failed', failure }` with `failure.kind` in `invalid-input | unsupported-platform | pipeline | execution`. The code returns `Result` with a `CliStructuredError` that has a dotted `code`, and the skill (lines 363–371) says exactly that. The implementer reported this bullet as wrong and left it. The doc-maintenance rule applies to lines the PR touched. -- **Suggestion:** Rewrite the bullet to `{ ok: false, failure }`, branching on `failure.code` (examples: `ASSEMBLE.BUILD_FAILED`, `DEPLOY.ENGINE_FAILED`, `DEPS.EXECUTOR_UNLOADABLE`), matching the skill. - -### F06: two new doc statements are inaccurate - -- **Location:** `docs/guides/deploying.md` lines 374–381 and 392–397; `skills/prisma-composer-core-concepts/SKILL.md` lines 364–368; `docs/guides/getting-started.md` lines 114 and 258–261. -- **Issue:** - - The programmatic example imports `./prisma.config.ts` relative to the script, but passes `path: './prisma.config.ts'`, which the operation resolves against `cwd`. The two agree only when the script runs from its own directory. - - Getting started says "Only the `prisma-composer` commands read it". Two lines later it says the `orm` section goes in the same file, and ORM commands read that file too. -- **Suggestion:** Write the example as `path: fileURLToPath(new URL('./prisma.config.ts', import.meta.url))` with `import { fileURLToPath } from 'node:url'`, and say in the bullet that `path` must be the file that declares the section. Change the getting-started sentence to "Composer's commands read the `composer` section; your app code never imports the file." - -### F07: the removed `ComposerSection` type may still be imported by the host - -- **Location:** `packages/0-framework/3-tooling/cli/src/exports/family.ts` line 16 (removed export). -- **Issue:** `@prisma/composer-cli/family` is what the `prisma` host in prisma/prisma-cli mounts. If the host names `ComposerSection` anywhere, the pin bump in slice 3 fails typecheck. I could not check this, because prisma-cli is outside the review boundary and no host is installed in the clone. The removal is documented in the guide, the skill and the ADR. -- **Suggestion:** Before merging, run `git grep ComposerSection` in prisma/prisma-cli. If it has a hit, keep the name as a type alias of `PrismaAppConfig` for this release, or fix the host in slice 3's pin bump. - -### F08: a failed second read of the section is reported as a Composer bug with a misleading message - -- **Location:** `packages/0-framework/3-tooling/cli/src/family/composer-config.ts` lines 33–39. -- **Issue:** When `resolveSectionOverChain` returns `ok: false` on the handler's second read, `declaringFile` is `undefined`. The handler then throws "no loaded config file declares it". The engine reports a handler throw as an internal error. That can only happen when user code, such as a getter, throws on the second read. The message then misstates the cause, and the report blames Composer. -- **Suggestion:** Branch on `!resolved.ok` first and return a structured `CONFIG.FIELD_INVALID` that names `resolved.file` and carries the cause. Keep the plain `throw` only for the true invariant, an ok result with no declaring file. - -### F09: the absent-section headline is wrong when there is no config file at all - -- **Location:** `packages/0-framework/3-tooling/cli/src/family/section.ts` lines 65–78. -- **Issue:** With no `prisma.config.ts` anywhere, the user is told "prisma.config.ts has no `composer` section". The ADR notes that the engine passes empty provenance in both the no-file and the no-section case. The fix text is still right. -- **Suggestion:** Word it for both cases, for example "No loaded prisma.config.ts declares a `composer` section." This matches the wording the guide's table already uses. - -### F10: examples spell the import differently from the docs - -- **Location:** `examples/*/prisma.config.ts`, `test/integration/prisma.config.ts`, `website/prisma.config.ts`. -- **Issue:** The spec's example, the guides, the skill and both ADRs write `import { defineConfig as composer }` and `composer: composer({...})`. The examples write `composer: defineConfig({...})` and `orm: orm.defineConfig({...})`. Agents copy the examples, so two spellings will spread. -- **Suggestion:** Use `defineConfig as composer` in the examples too. - -## Deferred - -| Item | Why deferred | -| --- | --- | -| `dependency-cruiser.config.mjs` excludes `prisma.config.ts`, so the examples' `/control` imports are no longer cruised. This PR changed only the comment. | Listed as out of scope and as a deferred item in the slice spec. | -| A real deploy of orm-demo proving the ORM loader is harmless inside the Alchemy child. | Slice 3's manual QA. My `resolveOrmConfig` probe on orm-demo and auth passed, which lowers the risk. | -| Package scripts and guides still name the `prisma-composer` binary. The binary and `engine-cli.ts` remain. | Slice 2, by design. | -| A programmatic caller mixing a logical symlinked `cwd` with a physical `config.path` can produce a wrong relative import. On Windows, a config on another drive gives a `./D:/…` specifier. | Both already existed for the entry path through the same `relativeImportSpecifier`. Windows is documented as unsupported. | - -## Already addressed - -| Item | Where | -| --- | --- | -| (none) | | - -## Acceptance-criteria verification - -| # | Criterion | Verdict | Evidence | -| --- | --- | --- | --- | -| S1a | `deploy --help` from orm-demo runs against `prisma.config.ts` alone | PASS | QA §1 shows exit 0. `prisma-composer.config.ts` is deleted from orm-demo. | -| S1b | A dry deploy reaches the plan stage against `prisma.config.ts` alone | WEAK (round 2; was FAIL) | The re-run QA §2 still stops at `ASSEMBLE.BUILD_FAILED`. `stack-file-import.test.ts` now runs both generated stack files against a real `prisma.config.ts`, so the child's import is proven, but no run reached the Alchemy plan. | -| S1c | With `prisma-composer.config.ts` restored: `CONFIG.LEGACY_FILE` | PASS | QA §4. `host-adapter.test.ts` "an old prisma-composer.config.ts beside the declaring file…" asserts the code, summary and path, and that the operation is never called. | -| S1d | With `composer: { configPath }`: a section error naming `configPath` | PASS | QA §5. `engine-cli.test.ts` lines 243–271 assert `CLI.CONFIG_SECTION_INVALID` wrapping `CONFIG.LEGACY_FIELD` with `meta.field: 'configPath'`. | -| S2a | `check-family-static-graph` and `check-cli-engine-pin` pass unchanged | PASS | I ran both; both exit 0. The graph shows family, bin and testing are free of alchemy and effect. | -| S2b | Control-import guard tests pass | PASS | I ran the node and nextjs `no-control-import.test.ts` (1 pass each) and the target `invariants.test.ts` (11 pass). | -| S2c | `check-npm-effect-resolution.mjs` passes in its reworked form, with output recorded in the PR | NOT VERIFIED | Needs network and tarballs. Not in the QA transcript. The adversarial assertion is loose (F04). | -| S3 | A search for `prisma-composer.config` over packages, examples, test, website, guides and skills finds only the diagnostics and ADRs | PASS | The hits are `section.ts` and `composer-config.ts` diagnostics, their tests, and the guide and skill text that documents those same diagnostics. | -| CC1 | The section is written like the ORM section: identifying fields checked, descriptors passed through, unknown fields are errors | PASS | `section.ts` lines 92–211. Tests cover identity pass-through, `FIELD_UNKNOWN` as an error, and every field rule. The examples import from `@prisma/cli-engine`, which the spec allows. | -| CC2 | `configPath` fails validation; `prisma-composer.config.{ts,mts,mjs,js}` beside the loaded file fails before anything else; both show the section | PASS | `section.ts` lines 172–174 and 80–90. `composer-config.ts` lines 9–23 run first in each of the four handlers. Tests cover all four extensions. Round 2: the programmatic path now runs the same checks (F01 fixed); on the CLI the old-file check runs after the field checks (F11). | -| CC3 | Nothing reads `prisma-composer.config.ts`: loader, walk-up, c12 and `configPath` handling deleted | PASS | `load-config.ts` is deleted. The greps and the lockfile diff above confirm the rest. | -| CC4 | A broken `effect` tree fails fast with `CLI.CONFIG_UNREADABLE`; the pre-flight is deleted; the pins and the CI proof stay | WEAK | The pre-flight and its callers are gone, and the docs describe the engine diagnostic. The engine behaviour is not exercised in this slice, and the CI script was not run (S2c, F04). Round 2: the adversarial check now requires an effect-shaped failure, with one false-positive path (F12). | -| CC5 | No published package declares the `prisma-composer` binary | NOT VERIFIED | Slice 2's scope. This slice keeps the binary working on purpose. | -| CC6 | Every example, `test/integration` and `website` has a `composer` section and no old file | PASS | Each of the 12 `prisma.config.ts` files has one `composer:`, and `git ls-files` finds no `prisma-composer.config.*`. Switching the scripts to `prisma` is slice 2. | -| CC7 | The control-plane guard tests pass and name `prisma.config.ts` | PASS | I ran them. The node, nextjs and target test comments and ADR-0017 now name `prisma.config.ts`. | - -| Verdict | Count | -| --- | --- | -| PASS | 11 | -| FAIL | 0 | -| NOT VERIFIED | 2 | -| WEAK | 2 | - -## Round 2 verification - -Range: the seven fix commits `91a72eec`..`827588a3`. I ran the cli package suite (255 pass, 3 skip, 0 fail, including the new `stack-file-import.test.ts` with no network), `node --test scripts/lint-casts.test.mjs` (9 pass), and root `pnpm typecheck`, `pnpm lint` and `pnpm lint:casts` (all exit 0; casts 21 at HEAD and at the merge base). I did not run `check-npm-effect-resolution.mjs` (needs network). - -### Per-finding verdicts - -| Id | Verdict | Evidence | -| --- | --- | --- | -| F01 | FIXED | `833c896e`. `pipeline.ts` `checkConfigSource` runs first in `runPipeline` and `resolveAppIdentity`. `operations.test.ts` "a config the CLI would refuse is refused before any container call" asserts `FIELD_UNKNOWN` with the "pass the `composer` property" fix, `FILE_RETIRED` and `FILE_MISSING`, each with `containerCalls` empty and Alchemy not run. The `log()` test asserts `FIELD_INVALID` on `extensions`. | -| F02 | FIXED | `4db7a4f7`. `execute-dev.ts` watches `configSource.file` and pauses rebuilds. The dev test edits the file, then a build output, and asserts two `config-changed` events and `converges === 1`. Residual edge cases in F13. | -| F03 | PARTIAL | `f3b39486`. `stack-file-import.test.ts` runs both real generators' output in a child against a real `definePrismaConfig` file and asserts the extension ids and state that reach `lower()`; passing the whole export would throw and fail on stderr. Remaining: `local-dev.integration.ts` still hand-renders its stack instead of calling `renderDevStackFile`, and no deploy or `dev` run reached the Alchemy plan (S1b). | -| F04 | PARTIAL | `886f6dba`. The adversarial shape now fails unless the probe blames `effect`. The healthy probe still omits the Prisma Cloud providers, with a stated reason (platform peers). The blame check has a false-positive path (F12). | -| F05 | FIXED | `827588a3`. The `deploying.md` bullet now says `{ ok: false, failure }` and "branch on `code`", matching the skill. | -| F06 | FIXED | `827588a3`. The example builds `file` with `fileURLToPath(new URL(...))` and the bullet explains why; getting-started says "Composer's commands read the `composer` section". | -| F07 | DEFERRED-BY-DECISION | Host check belongs to slice 3's pin bump. ADR-0049 records the removal. | -| F08 | FIXED | `833c896e`. `family/composer-config.ts` and its second resolve are deleted; the validator returns `{ value, file }` (`section.test.ts` "returns extensions and state together with the file that declared them"). | -| F09 | FIXED | `833c896e`. Summary is "No loaded prisma.config.ts declares a `composer` section." (`section.test.ts` line 97, `engine-cli.test.ts` line 239). | -| F10 | FIXED | `1b607959`. All ten examples, `test/integration` and `website` use `defineConfig as composer`, and `defineConfig as orm` where there is an ORM section. | -| D01 | PARTIAL | Shape, old file and missing file are checked (F01). The value/file agreement is recorded in ADR-0049 as uncheckable in-process, which is correct. Not done: the generated stack still does `const config = prismaConfig.composer` with no check, so a `file` that inherits its section from a parent file still fails inside `lower()` with a TypeError rather than a `CONFIG.` code. | -| D02 | FIXED | `833c896e`. The validator returns the file and runs the old-file check; handlers pass `ctx.config`. `host-adapter.test.ts` line 163 asserts `FILE_RETIRED` under the section headline before the operation. | -| D03 | FIXED | `827588a3`. ADR-0044's `CONFIG` list matches the emitted codes and marks three as retired by ADR-0049. | -| D04 | FIXED | `833c896e`. `FIELD_RETIRED` and `FILE_RETIRED` in code, tests, ADRs and guides; `git grep LEGACY_` finds nothing. | -| D05 | FIXED | `ComposerConfigSource { value, file }` in `src/composer-config.ts`, exported from `/control`; `log` documents that it reads only `value`. `StackFileInput.configFile` keeps its own name, which is acceptable for an internal type. | -| D06 | FIXED | Same as F10. | -| D07 | DEFERRED-BY-DECISION | ADR-0049 now names ADR-0028's lost rule. | -| D08 | FIXED | ADR-0049's consequence now says every section's imports run in the child and a failure in any of them fails the deploy. | -| D09 | FIXED | `stack-file-import.test.ts` plus the new operation refusal tests. | - -Totals: 14 FIXED, 3 PARTIAL, 0 NOT FIXED, 2 DEFERRED-BY-DECISION. - -### The Biome plugin change - -I ran the new and the old pattern over one probe file. Both flag the same ten sites: `v as T`, both halves of `v as unknown as T`, a cast as a call argument, a cast inside a `satisfies` object, `(v as T).x`, a cast after an explicit generic call, `as Array`, `(x satisfies U) as T` and a cast in a return. Both exempt `as const`. Neither flags `v`, so that is not a regression. The `.tsx` copy gave the same result. `lint-casts.test.mjs` "positive delta" already proves a real cast raises the count (`delta=+1`); the new test proves renames do not. - -### New findings - -#### F11: the CLI and the programmatic check refuse the same bad input with different codes - -- **Location:** `family/section.ts` `validate`; `pipeline.ts` `checkConfigSource`. -- **Issue:** The validator checks fields first, then the old file, and reports every finding. `checkConfigSource` checks the file exists, then the old file, then fields, and throws only the first finding. A section with a bad field and an old file beside it gives `FIELD_*` on the CLI and `FILE_RETIRED` in code. ADR-0049 says code "gets the same refusals a CLI user would". On the CLI the old file is now reported only after the fields are valid, which is weaker than CC2's "fails before anything else". A symlinked `file` also diverges: the engine gives a real path, while `checkConfigSource` looks for the old file beside the link. -- **Suggestion:** One function in `composer-config.ts`, `checkComposerConfig(raw, file)`, that returns findings in one order (old file first), used by both callers. The operations throw the first finding and keep the rest in `meta`. Call `fs.realpathSync` after the existence check. - -#### F12: the effect-blame check blames `effect` for any `a.b is not a function` - -- **Location:** `scripts/check-npm-effect-resolution.mjs` `ALCHEMY_BLAME_PROBE`. -- **Issue:** `typeof effect[X]?.[Y] !== 'function'` is true when `effect` has no export `X` at all, so any `foo.bar is not a function`, including one from a minified name or an Alchemy-internal rename, counts as effect's fault. That is the F04 false pass, narrowed to one message shape. The healthy shapes import the same entries first, which limits the impact. The opposite miss fails closed: "Cannot read properties of undefined" from a missing effect namespace is not recognised, so the script fails loudly on a genuine effect break. -- **Suggestion:** Require `typeof effect[X] === 'object'` and `typeof effect[X][Y] !== 'function'`. Also accept `Cannot read properties of undefined (reading 'Y')` when the stack names `node_modules/alchemy/`. - -#### F13: the `dev` config pause has three unhandled cases - -- **Location:** `operations/execute-dev.ts` lines 258–271; `dev/watch.ts` `startWatch`. -- **Issue:** The flag cannot deadlock, two writes within 300 ms coalesce into one notice, and both watchers close on `stop()` and on a failed start. But: - - Any write event pauses `dev` for good, including a save that leaves the content unchanged (format-on-save, `touch`, a branch switch that rewrites the file with the same bytes). - - The config watcher gets no `onError`, so a watch error on it is silent and later edits go undetected. That silently restores F02's mixed-config rebuild. - - If a bundle's watch target is a directory that contains `prisma.config.ts`, both debounced callbacks fire on one edit. The build callback can run first and converge once with mixed config. -- **Suggestion:** Hash the file at start and on each event, and ignore unchanged content. Pass `onError` so failures surface as `watch-error`. In the build callback, compare the hash before rebuilding instead of relying only on the flag set by the other watcher. - -### Verdict - -The branch can go to a PR. F11–F13 are low severity and can be fixed in this PR in a short round. Before opening it, either record a run that reaches the Alchemy plan (S1b), or state in the PR that the plan-stage proof moves to slice 3's QA. Record the `check-npm-effect-resolution.mjs` output in the PR (S2c). diff --git a/projects/one-config-file/slices/01-composer-section/review/system-design-review.md b/projects/one-config-file/slices/01-composer-section/review/system-design-review.md deleted file mode 100644 index 0ecf83b86bc7..000000000000 --- a/projects/one-config-file/slices/01-composer-section/review/system-design-review.md +++ /dev/null @@ -1,109 +0,0 @@ -# System design review: slice 1, the `composer` section - -Branch `one-config-file/composer-section` in prisma/composer, reviewed as `main...HEAD` (11 commits, 591fdc10 at the tip). Paths are repo-relative to prisma/composer. Reviewed against the slice spec, the project spec and design notes, ADR-0049 and the amendments to ADR-0017, ADR-0043 and ADR-0044. - -## Summary - -The shape is right. The private loader, its walk-up discovery, c12 and the effect pre-flight are gone rather than bypassed. The `composer` section now holds the whole configuration, the section merge takes the nearest file whole, and every command reads its config from the engine. The package boundary holds: `ComposerConfig` and the operations import nothing from `@prisma/cli-engine`, and `packages/9-public/composer/tsdown.config.ts` keeps the engine external so `check-cli-engine-pin.mjs` would catch a stray import. - -The main weakness is the programmatic `config: { value, path }` input. The engine path validates the section and refuses the old file. The programmatic path does neither, and nothing checks that `value` is the `composer` export of the file at `path`, which is what the Alchemy child imports. Two smaller structural points: the handler re-derives provenance that the validator already had, and ADR-0044's closed code registry was not updated for the new codes. - -## What changed, conceptually - -| Concept | Before | After | -| --- | --- | --- | -| Where the config lives | `prisma-composer.config.ts`, found by walking up from the entry, loaded with c12 | `composer` section of `prisma.config.ts`, loaded by the engine | -| Section value | `{ configPath }` pointer | `PrismaAppConfig`: identifying fields checked, descriptors passed by reference | -| Section merge | engine default, key by key | nearest declaring file, whole (family/section.ts:255) | -| Operation input | discovered from `entry`; `deps.config`/`deps.configPath` overrides | required `config: ComposerConfig` = `{ value, path }` | -| Old setup | n/a | `CONFIG.SECTION_MISSING`, `CONFIG.LEGACY_FIELD` (validator), `CONFIG.LEGACY_FILE` (handler) | -| Broken `effect` tree | `DEPS.EFFECT_VERSION_CONFLICT` pre-flight | engine's `CLI.CONFIG_UNREADABLE`; executor import failure is `DEPS.EXECUTOR_UNLOADABLE` | -| Stack file in the Alchemy child | imports the old file's default export | imports `prisma.config.ts` and reads `.composer` | - -## Findings - -### D01: The programmatic `config` input is unvalidated, and `value` and `path` can disagree - -Location: packages/0-framework/3-tooling/cli/src/pipeline.ts:26-31, 80-85; packages/0-framework/3-tooling/cli/src/generate-stack.ts:74-77; packages/0-framework/3-tooling/cli/src/dev/generate-dev-stack.ts:78-81; docs/guides/deploying.md:392-397. - -Issue: `ComposerConfig` carries the config twice. The in-process pipeline uses `value` for coverage and assembly. The Alchemy child imports the file at `path` and uses its `.composer` export for `lower()`. On `main`, a programmatic `deploy` ran the loader, so its config was shape-checked. Now a host can pass any `value`: it gets no shape check, no `CONFIG.LEGACY_FILE` refusal, and no check that `value` is the file's section. The guide says "`value` must be that file's `composer` export", but nothing enforces it. If the host passes the path of an old `prisma-composer.config.ts`, or a file whose section comes from a parent file, the child reads `undefined` and fails inside `lower()` with an unstructured error. ADR-0049 says the operations "take the same value", but only the CLI path makes that true. - -Suggestion: Move the shape check and the legacy-file check into one module that does not import the engine, for example `src/composer-config.ts` at package level, returning `CliStructuredError`s. The operations run it at the start of `runPipeline`. `family/section.ts` maps the results to engine `Diagnostic`s. This restores the guarantee programmatic callers had on `main`, and it keeps one definition of "a valid section" instead of two. The child should also check that `prismaConfig.composer` is defined and throw a named `CONFIG.` error if it is not, so a mismatch fails with a diagnostic rather than a TypeError. Record the value/path invariant in ADR-0049 § Consequences. - -### D02: The handler re-derives provenance the validator already had; the legacy check depends on each handler remembering it - -Location: packages/0-framework/3-tooling/cli/src/family/composer-config.ts:25-43; family/commands/deploy.ts:59, destroy.ts:57, dev.ts:203, log.ts:60; ADR-0049 line 33. - -Issue: The validator receives `provenance.files[0]` and discards it. `composerConfigOf` then runs `resolveSectionOverChain` a second time to recover the same path, and throws a plain `Error` if the two reads disagree. The engine documents the intended pattern on `resolveSectionPath`: the validator resolves paths from provenance and returns them in its validated value. Separately, `CONFIG.LEGACY_FILE` runs only because each of four handlers calls `composerConfigOf`. A fifth command that needs the section and forgets the call would silently ignore an old file, which is exactly what principle 3 of the design notes forbids. ADR-0049 records the re-resolve as the mechanism but not why the validator-returns-path option was rejected. - -Suggestion: Make the section's value type `ComposerConfig` itself: the validator returns `{ value, path: provenance.files[0] }` and runs the legacy-file check there, so `LEGACY_FILE` is reported under the same `CLI.CONFIG_SECTION_INVALID` headline as the other two refusals. That removes `composerConfigOf`, the second resolve, the unreachable throw, and the per-handler call. Handlers pass `ctx.config` straight through. If the engine forbids file-system reads in validators, say so in ADR-0049 and keep the handler check, renamed to something like `refuseLegacyFile` so the name says it can fail. - -### D03: ADR-0044's closed `CONFIG` registry was not updated - -Location: docs/design/90-decisions/ADR-0044-errors-are-structural-envelopes-with-dotted-namespace-codes.md:81-83. - -Issue: ADR-0044 says "Adding a subcode is an edit to this list." The branch edited the `DEPS` line and the namespace-owner table, but not the `CONFIG` line. `SECTION_MISSING`, `LEGACY_FIELD`, `LEGACY_FILE` and `FIELD_UNKNOWN` are missing. `FILE_MISSING`, `EXPORT_INVALID`, `PATH_MISMATCH` and `EVALUATION_FAILED` are listed but no production code emits them after the loader's deletion. - -Suggestion: Rewrite the `CONFIG` line to the codes the code emits, and mark the four retired codes as retired by ADR-0049, the same way the `DEPS` line does. - -### D04: `LEGACY_FIELD` and `LEGACY_FILE` break the subcode word order - -Location: packages/0-framework/3-tooling/cli/src/family/section.ts:70, 82, 180; family/composer-config.ts; docs/guides/deploying.md (diagnostics table). - -Issue: Every other `CONFIG` subcode is subject then state: `FIELD_INVALID`, `FIELD_UNKNOWN`, `SECTION_MISSING`, `EXTENSION_DUPLICATE`, `DESCRIPTOR_MISSING`. The two new ones reverse it. Users and hosts branch on these strings, so they are part of the public surface, and they are cheapest to fix before the GA release. - -Suggestion: `CONFIG.FIELD_RETIRED` and `CONFIG.FILE_RETIRED`, or `FIELD_LEGACY` and `FILE_LEGACY`. Apply the change to the code, the ADRs, the guide and the tests together. - -### D05: `ComposerConfig` names the wrong thing, and the path has three names - -Location: packages/0-framework/3-tooling/cli/src/pipeline.ts:26-34; exports/control.ts:34; generate-stack.ts `StackFileInput.configFile`. - -Issue: The configuration a user writes is `PrismaAppConfig`, returned by `defineConfig` from `@prisma/composer/config`. The published type called `ComposerConfig` is not that configuration. It is the configuration plus the file it came from. A reader of `@prisma/composer/control` will expect `ComposerConfig` to be what `composer({...})` returns. The file is called `path` on `ComposerConfig`, `configFile` on `PipelineResult` and `StackFileInput`, and the engine's `ctx.configPath` means something else again (the `--config` flag). The type is part of the operations' public input but lives in `pipeline.ts`, an internal module. `log` also requires `path` and never reads it. - -Suggestion: Rename it to something like `ComposerConfigSource`, or name the fields for what they are, for example `{ section, file }`. Use one word for the declaring file everywhere, for example `configFile`. Move the type next to the other operation-surface types in `operations/shared.ts`. Either document that `log` ignores `path`, or keep the uniform input deliberately and say so in the doc comment. - -### D06: The examples do not use the import form that the docs and diagnostics tell users to write - -Location: examples/*/prisma.config.ts (all ten, for example examples/bucket/prisma.config.ts:9,14 and examples/orm-demo/prisma.config.ts:2,5,16,23); test/integration/prisma.config.ts; website/prisma.config.ts; family/section.ts:14-15. - -Issue: This is a real inconsistency, not a matter of style. The slice spec's own grounding example (examples/orm-demo) uses `defineConfig as composer` and `defineConfig as orm`. So do ADR-0017, ADR-0049, the guides, the skill, and the `SECTION_FIX` text every refusal prints. The examples use bare `defineConfig`, and orm-demo falls back to a namespace import (`orm.defineConfig`) because two families' `defineConfig` collide in one file. Users copy the examples more than any other source. They will see three forms, and the error message will tell them to write a fourth. - -Suggestion: Use `import { defineConfig as composer }` and `composer({...})` in every example, the integration fixture and the website. Use `defineConfig as orm` where there is an ORM section. The example header comments that name `prisma-composer deploy` belong to slice 2. - -### D07: `prisma.config.ts` is no longer checked by `examples-import-public-only` - -Location: dependency-cruiser.config.mjs:180-186, 216-232; ADR-0049 line 49. - -Issue: The old config file was deliberately left in the cruise ("MUST be cruised"), so the rule that examples, the website and tests import only the 9-public packages (ADR-0028) covered the one file that imports `/control` entries. `prisma\.config` was already excluded on `main`, and it is listed twice. Moving the Composer config into that file removes the coverage. An example config could now import `@internal/*` without any check noticing. The spec defers this, and ADR-0049 records that the imports "are not cruised". Neither says which rule is lost. - -Suggestion: Keep the deferral if necessary, but name the lost rule (ADR-0028's examples-import-public-only) in the ADR-0049 consequence and in the project's deferred list. The likely fix is to stop excluding the file and exclude only the module that dependency-cruiser cannot resolve, `@prisma/cli-engine` or `prisma/config`. Remove the duplicate pattern. - -### D08: The Alchemy child now evaluates every family's config, not only Composer's - -Location: generate-stack.ts:74-77; dev/generate-dev-stack.ts:78-81; ADR-0049 line 42. - -Issue: Importing the declaring `prisma.config.ts` in the child is the right shape. With one config file and descriptors that contain functions, no other file could supply the section. But ADR-0049 says the file "is evaluated again in the child, as the old config file was". That understates the change. The old file imported only Composer's `/control` entries. The shared file also imports the ORM config module, the engine's `definePrismaConfig` (the examples import it from the `@prisma/cli-engine` root, the whole engine barrel), and any section a user adds later. Each of those now runs in the deploy child with the child's environment. The spec defers the real orm-demo deploy to slice 3. - -Suggestion: Make the consequence accurate: every section's imports run in the child, and a failure in one of them fails the deploy. Add the orm-demo deploy to the slice 3 QA list by name. - -### D09: No test runs the config import that the generated stack file performs - -Location: packages/0-framework/3-tooling/cli/src/__tests__/generate-stack.test.ts; dev/__tests__/generate-dev-stack.test.ts; test/integration/test/control.deploy.test.ts. - -Issue: The engine-level behaviour is tested through the engine. `engine-cli.test.ts` covers the missing section, `configPath`, the whole-section merge and the case where only the repository root declares the section, using `createTestCli`. `host-adapter.test.ts` covers `LEGACY_FILE` and the `{ value, path }` handoff through the real Runtime and a real file on disk. That coverage is good. The new behaviour in the child is only string-matched: `prismaConfig.composer` on a real frozen `definePrismaConfig` export is never evaluated. The integration deploy test fails at the missing built entry before the stack file is written. The programmatic path has no test with a bad `value`, because nothing validates it (D01). - -Suggestion: Add one integration test that writes the stack file for `test/integration` and imports its config binding, or evaluates the same two lines against `test/integration/prisma.config.ts`, and asserts that the extension ids match. After D01, add operation tests for an invalid `value`, a legacy file beside `path`, and a `path` whose file has no `composer` export. - -## ADR reasoning check - -- ADR-0049 handles the alternatives well. The schema-declared section is rejected for concrete reasons (unknown keys are not rejected, the codes would be `CLI.` rather than `CONFIG.`, and `configPath` could not get its own message). The per-key merge is rejected because the child imports exactly one file. The pre-flight retirement cites the checked versions. Missing: the validator-returns-path option (D02), the value/path invariant (D01), and the lost dependency-cruiser rule (D07). -- The ADR-0017 amendment is correct: the firewall argument is independent of the file name. The ADR-0043 amendment is correct. The ADR-0044 amendment is incomplete (D03). -- The dependency direction is correct. `family/` (engine-aware, published through `@prisma/composer-cli`) depends on `pipeline.ts` and `operations/` (engine-free, published through `@prisma/composer`), never the reverse. - -## Routed to the code-review pass - -- `family/section.ts` constants: `CONFIGURATION_HOME` is a vague name for a message. `SECTION_FIX`, `MOVE_TO_SECTION_FIX` and the `LEGACY_FIELD` fix text say the same thing three slightly different ways. -- `family/section.ts` header comment is two sentences. The spec asked for one. -- `ComposerConfig.path`'s doc comment says relative paths resolve against the operation's cwd, but `resolveAppIdentity` never resolves it. Check that the `log` and `dev` watch paths get an absolute path. -- `scripts/check-npm-effect-resolution.mjs`: check that the adversarial assertion fails for the import error it expects and not for any throw. -- Generated-file banners still say "Generated by `prisma-composer deploy`". That is slice 2 scope; confirm it is on its list. diff --git a/projects/one-config-file/slices/01-composer-section/spec.md b/projects/one-config-file/slices/01-composer-section/spec.md deleted file mode 100644 index c0955242a842..000000000000 --- a/projects/one-config-file/slices/01-composer-section/spec.md +++ /dev/null @@ -1,95 +0,0 @@ -# Slice 1: the `composer` section holds Composer's configuration - -Repository: prisma/composer. One PR. Parent: [`../../spec.md`](../../spec.md). Grounding with file paths and line numbers: [`grounding.md`](./grounding.md), read against Composer `main` at edaf7b27 on 2026-09-30. - -## At a glance - -```ts -// examples/orm-demo/prisma.config.ts — the only config file -import { definePrismaConfig } from '@prisma/cli-engine'; -import { defineConfig as composer } from '@prisma/composer/config'; -import { nodeBuild } from '@prisma/composer/node/control'; -import { prismaCloud, prismaState } from '@prisma/composer-prisma-cloud/control'; -import { defineConfig as orm } from '@prisma/orm-postgres/config'; - -export default definePrismaConfig({ - composer: composer({ - extensions: [prismaCloud(), nodeBuild()], - state: prismaState(), - }), - orm: orm({ /* unchanged */ }), -}); -``` - -`prisma-composer deploy module.ts` and the programmatic `deploy({ entry, stage, config })` both run against it. `examples/orm-demo/prisma-composer.config.ts` no longer exists. In the examples `definePrismaConfig` comes from `@prisma/cli-engine` because no example depends on the `prisma` host; the guides and skill write `prisma/config`, which is what a user's project has. - -## Chosen design - -**The section.** `family/section.ts` gets a hand-written `validate` that replaces the `configPath`-only one. It checks what `configShapeDiagnostics` in `load-config.ts` checks today: `extensions` is an array of objects with a string `id` and an object `nodes`, extension ids are unique, `state` is an object with a string `extension` and a function `create`. Descriptors pass through by reference, as the ORM section does. Unknown keys are errors, not warnings. `configPath` gets its own error that shows the section to write. The section's `merge` takes the nearest file's section whole, never key by key, so a section always has one declaring file. The header comment that explains the old split is replaced by one sentence saying the section is the configuration. - -**Where the section's file path goes.** The generated stack files import the config file in the Alchemy child process. After this slice they import `prisma.config.ts` and read its `composer` export. The path is the section's provenance, which the engine gives the handler with the validated value. The pipelines therefore take `{ value: PrismaAppConfig, path: string }` in place of the request they build today from an entry path and an optional `configPath`. - -**The programmatic operations take the config.** `deploy`, `destroy`, `dev` and `log` on `@prisma/composer/control` today find the config by walking up from the entry file. `@prisma/composer` must not import the engine, so they gain a required `config` input with the same `{ value, path }` shape, and the caller evaluates the file: - -```ts -import prismaConfig from './prisma.config.ts'; -import { deploy } from '@prisma/composer/control'; - -await deploy({ entry: './module.ts', stage: 'preview', config: { value: prismaConfig.composer, path: './prisma.config.ts' } }); -``` - -This is a public API change. It lands with the matching guide and skill updates in this PR, per the repo's user-facing-surface rule, and the ADR records it. - -**Refusing the old shape.** Three diagnostics, all under Composer's `CONFIG.` prefix and all naming `prisma-composer.config.ts` and showing the section: - -- `configPath` present: a section validation error. The engine's headline is `CLI.CONFIG_SECTION_INVALID` with Composer's message attached; that is the engine's shape and is accepted. -- Section absent: the validator already receives an absent section today. It returns an error saying the `composer` section is missing and, when the config file's directory is known from provenance and holds `prisma-composer.config.*`, that the old file is no longer read. The implementer establishes what the engine passes for an absent section and writes the best message the engine allows. -- Section present and `prisma-composer.config.*` also present in the section's directory: the handler fails before the pipeline starts, code `CONFIG.FILE_RETIRED`. Only that directory is checked. - -**Deletions.** `load-config.ts`, `check-effect-resolution.ts` and their tests; `executorLoadFailure`'s use of the effect check in `operations/shared.ts`; the `c12` dependency from the cli package, `@prisma/composer` and `@prisma/composer-cli`; `CONFIG_FILENAME` and the three user-facing messages that name the old file in `validate-coverage.ts`, `assemble-services.ts` and core `control/deploy.ts`; the header comments in `app-config.ts` that name it. - -**The standalone binary keeps working.** `commands/destroy.ts` and `commands/log.ts` get the same change as `deploy` and `dev`. Nothing else in the binary changes; its removal is slice 2. - -**Examples and fixtures.** All ten examples, `test/integration` and `website` get a `composer` section in `prisma.config.ts` and lose `prisma-composer.config.ts`. Examples that had no `prisma.config.ts` get one. Examples that do not yet depend on `@prisma/cli-engine` add it at the pinned version. `tsconfig.json` includes that name the old file are updated. Package scripts keep calling the standalone binary in this slice; slice 2 changes them. - -**CI script.** `scripts/check-npm-effect-resolution.mjs` proves Alchemy loads by running the binary with no config; that path now stops at the engine before Alchemy is reached. Both healthy shapes and the adversarial shape switch to importing Alchemy directly from the installed tree, asserting success or the import error respectively. - -**Docs in this slice.** The guides and the skill are updated only where this slice changes what a user observes: the config file, its section, and the programmatic `config` input. Command names stay as they are; slice 2 does that sweep. - -**ADR.** A new ADR-0049 records: the configuration is the `composer` section of `prisma.config.ts`; identifying fields are validated and descriptors pass through as references; the old file is refused; programmatic operations take the config; the effect pre-flight is retired in favour of the engine's fail-fast diagnostic, with the verified reason. ADR-0017 is amended so its wording names `prisma.config.ts`; ADR-0043 is amended for the programmatic API. - -## Coherence rationale - -One reviewer can hold this: every change follows from one fact, the section is the configuration. The deletions, the new input, the diagnostics and the example migration are the consequences of that fact and cannot be reviewed apart from it. The binary removal and the command-name sweep are separable and are slice 2. - -## Scope - -**In:** everything under Chosen design. - -**Deliberately out:** deleting the binary and `engine-cli.ts`; the `prisma-composer` command name in guides, README, skill and example scripts; the CI check for the name; the root version bump; the dependency-cruiser exclusion of `prisma.config` files (recorded as a deferred item); a real deploy of orm-demo to confirm that the ORM loader evaluating the shared file inside the Alchemy child is harmless (slice 3 manual QA). - -## Pre-investigated edge cases - -| Case | Disposition | -| --- | --- | -| The engine merges a section across a config chain key by key | The section's `merge` takes the nearest file's section whole, so provenance is always one file. | -| `examples/store` has module-level `prisma.config.ts` files with only an `orm` section | The root `prisma.config.ts` gains the `composer` section; modules inherit it through the chain. A `prisma-composer deploy` from the store root must still work. | -| `test/integration` pins `prisma@7.9.0` and imports `definePrismaConfig` from `@prisma/cli-engine` | Keep `@prisma/cli-engine`; do not add the host. | -| The adversarial shape of the tarball script overrides to `effect@4.0.0-beta.93` | Keep that override; the assertion becomes that importing Alchemy throws. | -| `operations.test.ts` asserts `DEPS.EFFECT_VERSION_CONFLICT` from `executorLoadFailure` | That path is deleted with the check; the test goes with it. A failed executor import is reported as the import error. | - -## Slice-specific done conditions - -- From `examples/orm-demo`, `pnpm exec prisma-composer deploy --help` and a dry deploy that reaches the plan stage run against `prisma.config.ts` alone; with `prisma-composer.config.ts` restored, `CONFIG.FILE_RETIRED`; with `composer: { configPath: './x.ts' }`, the section error naming `configPath`. -- `pnpm exec check-family-static-graph` and `check-cli-engine-pin` pass unchanged; the control-import guard tests pass; `scripts/check-npm-effect-resolution.mjs` passes in its reworked form (it needs network and built tarballs; run it once and record the output in the PR). -- A search for `prisma-composer.config` over `packages/`, `examples/`, `test/`, `website/`, `docs/guides/` and `skills/` finds only the diagnostics that name the old file and the ADRs. - -## Open questions - -None. - -## References - -- Parent spec and design notes; `grounding.md` for every call site and signature. -- Composer rules: `user-facing-surface-changes.mdc`, `exports-entrypoints.mdc`, `commit-story.mdc`, `git-staging.mdc`, `no-bare-casts.mdc`, `test-import-patterns.mdc`. -- ORM section pattern in prisma/orm: `packages/1-framework/3-tooling/config-loader/src/orm-section.ts`. diff --git a/projects/one-config-file/slices/02-remove-binary/grounding.md b/projects/one-config-file/slices/02-remove-binary/grounding.md deleted file mode 100644 index b41568ed2388..000000000000 --- a/projects/one-config-file/slices/02-remove-binary/grounding.md +++ /dev/null @@ -1,328 +0,0 @@ -# Slice 2 grounding: remove the `prisma-composer` binary and its name - -> Read-only survey of `wip/composer` on branch `one-config-file/composer-section` (HEAD `e83bab65`, 21 commits ahead of `origin/main` `edaf7b27`), plus prisma/prisma-cli `main` at `4d254ff` (2026-09-30). Paths are relative to the prisma/composer checkout unless they start with `prisma-cli:`. `CLI` below means `packages/0-framework/3-tooling/cli`. - -Slice 1 is already on this branch: `load-config.ts` and `check-effect-resolution.ts` are gone, `composer-config.ts` holds the section check, and `CONFIG.FILE_RETIRED` / `CONFIG.FIELD_RETIRED` exist. - -## 1. Files that exist for the standalone binary - -### Delete outright - -| File | What it is | Only used by | -| --- | --- | --- | -| `CLI/src/bin.ts` (1-6) | shebang entry, `process.exitCode = await cli()` | tsdown `bin` entry, `node-compat.test.ts` | -| `CLI/src/cli.ts` (1-73) | `cli()`, `shippedVersion()`, `REPORT_HINT` naming `prisma-composer` | `bin.ts`, `exports/index.ts:5` | -| `CLI/src/family/engine-cli.ts` (1-104) | `BINARY_NAME = 'prisma-composer'` (31), `mountedTree` adds `destroy`/`log` (50-56), `foreignOrmSectionFamily` (65-71), `createComposerCli` (73-83), `runComposerCli` (96-104) | `cli.ts`, `exports/family.ts:12-13`, three test files | -| `CLI/src/family/runtime.ts` | `createRuntime` (Node `process` to engine `Runtime`, env-only credentials, cross-spawn child) | only `engine-cli.ts:29`; the host has its own runtime (`prisma-cli:packages/cli/src/runtime.ts`) | -| `CLI/src/family/commands/destroy.ts` (1-94) | `destroy ` command | `engine-cli.ts:26`, `deploy-destroy.test.ts:25,79` | -| `CLI/src/family/commands/log.ts` (1-109) | `log [address]` session command | `engine-cli.ts:27` | -| `CLI/src/family/target.ts` (1-37) | `targetOf()`, source of `DEPLOY.TARGET_CONFLICT` / `DEPLOY.TARGET_MISSING` | only `commands/destroy.ts:14,54` | -| `CLI/src/__tests__/node-compat.test.ts` | spawns `node src/bin.ts`; asserts help names `prisma-composer` and lists `destroy ` | CI `node-floor` step, `ci.yml:215-216` | -| `CLI/src/family/__tests__/host-adapter.test.ts` (1-198) | drives `runComposerCli` with a fake host | | -| `CLI/src/family/__tests__/runtime.test.ts` (1-294) | tests `createRuntime`, `detectPackageManager` | | - -Test files that are only partly about the binary: - -- `CLI/src/family/__tests__/engine-cli.test.ts` (360 lines). - - `createComposerFamily()` block (61-89) stays. Its `realOperations.destroy` and `.log` asserts (76, 78) go if the seam drops them (see § 2). - - `createComposerCli()` block (91-124) goes. - - "composer's Runtime against a real engine run" (126-157) goes: it uses `createRuntime` and `name: 'prisma-composer'`. - - The section-through-the-engine block (204-360) stays. Its `probeCli` (179-190) mounts `foreignOrmSectionFamily`. The test "a shared config's orm section is recognised and ignored" (342-353) exists only for that pass-through and goes with it. - - The surviving tests belong in a file named for what they test (for example `section-engine.test.ts`). The name `engine-cli` no longer describes anything. -- `host-adapter.test.ts` 163-197 is the only end-to-end proof that a `prisma-composer.config.ts` beside the declaring file fails before the operation runs. `section.test.ts` 455-533 and `operations.test.ts` 451-500 cover the same rule in-process, so deleting the file loses no rule. Slice 3 covers the host's real loader. -- `CLI/src/family/__tests__/deploy-destroy.test.ts` (418 lines). - - The header (1-15, 62-66) and the harness line `commands: { ...family.commands, destroy: createDestroyCommand(...) }` (79) change to use the family alone. - - These destroy-only tests go: 115-122, 147-167, 194-201, the destroy row of the `--json` table (214), 248-255, the destroy signal cases around 317-331, and 395-418. - - Deploy tests stay. Rename the file to `deploy.test.ts`. - -### Shared with the family: keep, fix comments - -| File | Kept because | Stale text | -| --- | --- | --- | -| `CLI/src/family/family.ts` | it is the family | header 2-3 ("composer's own CLI or the `prisma` bin"); 20-23 ("remain first-class commands of composer's own bin, mounted on top of the family in engine-cli.ts") | -| `CLI/src/family/converge.ts` | used by `commands/deploy.ts` | 2 "What `deploy` and `destroy` share"; 98 | -| `CLI/src/family/translate-error.ts` | used by `converge.ts` | none | -| `CLI/src/family/workspace.ts` | used by `commands/deploy.ts` | none | -| `CLI/src/family/section.ts`, `commands/deploy.ts`, `commands/dev.ts` | the family | none found | -| `CLI/src/exports/family.ts` | the host imports `createComposerFamily` from it | remove lines 12-13 (`ComposerCliSpec`, `BINARY_NAME`, `createComposerCli`, `runComposerCli`); header 2-3 "and the thin CLI that mounts it" | -| `CLI/src/exports/index.ts` | nothing imports the `@internal/cli` root; only the alias at `tsconfig.depcruise.json:55` names it | line 5 exports `cli`, `shippedVersion`; header line 2 "`bin.ts` is the CLI entrypoint". The whole `.` entry could go | -| `CLI/src/family/__tests__/signal-listeners.test.ts` and `fixtures/signal-listeners.mjs` | they test an alchemy import, not the binary | none | - -### Build config and manifests - -- `CLI/tsdown.config.ts`: entry `bin: 'src/bin.ts'` (17). The `exports: { ...baseConfig.exports, bin: false }` override (23-26) and comment 3-7 ("@prisma/composer publishes the CLI", already wrong) exist only for the binary and go with it. -- `CLI/package.json:5` description "The `prisma-composer` deploy CLI." The `cross-spawn` dependency stays because `run-alchemy.ts:17` also uses it. -- `tsconfig.depcruise.json:48` alias `@internal/cli/bin` to `src/bin.ts`. -- `packages/9-public/composer-cli/package.json`: - - The description (5) ends "and the `prisma-composer` CLI." The `bin` block is lines 6-8. - - `exports` (9-13) has no bin entry already. `files` (14-17) is `["dist", "src"]` and does not involve the binary. - - The dependencies `alchemy`, `cross-spawn`, `effect` and `esbuild` stay. The family's lazily imported executor chunks are inlined into this package's dist and import them. -- `packages/9-public/composer-cli/tsdown.config.ts`: the second config object (37-56) builds `dist/bin.mjs` from `../../0-framework/3-tooling/cli/dist/bin.mjs`. Delete it and unwrap the array. Comments 4-7 ("the bin must not be importable") and 26-33 go stale. -- `packages/9-public/composer/package.json:5` description ends "The `prisma-composer` CLI lives in @prisma/composer-cli." -- `packages/9-public/composer/tsdown.config.ts:7-8` says "The command family and the `prisma-composer` bin live in @prisma/composer-cli". -- Host constraint: `prisma-cli:packages/cli/tests/fixtures/startup-probe.mjs:37` matches the path `@prisma/composer-cli/dist/family.mjs`. Keep the `family` entry name. - -### Scripts that run or reference the binary - -| Script | How it uses the binary | Rework | -| --- | --- | --- | -| `scripts/check-cli-engine-pin.mjs` | `BIN = 'bin.mjs'` (66-67); requires the packed tarball to contain `dist/bin.mjs` (191-194) and requires that file to import the engine (183-190); header 14-16, 39-41 | delete the two bin checks and the constant. The whole-dist check (175-182) still proves the engine stays external, through `family.mjs` | -| `scripts/check-family-static-graph.mjs` | `CHECKS` entry for `dist/bin.mjs` (77-88); header 2-3; OK message 241 | delete the entry; fix the header and the message | -| `scripts/check-npm-effect-resolution.mjs` | `runCli` spawns `node_modules/.bin/prisma-composer` (226-237). `assertCliStarts` requires `--help` to list `deploy`, `destroy`, `dev` and `log ` (244-263). The adversarial shape requires `--help` to survive a broken tree (380-392). Header 29-36 | replace both with a child `node` process that imports `@prisma/composer-cli/family`. In healthy trees it checks that `createComposerFamily().commands` is `deploy` and `dev`. In the broken tree it checks that the import still succeeds. That keeps the proof that start-up survives a broken tree, without a binary | -| `scripts/check-floor-imports.mjs` | comment only (7-12). It walks `exports`, which never had `bin` | fix the comment | -| `scripts/destroy-guard.sh` | `bun node_modules/.bin/prisma-composer destroy "$entry" --name "$stack_name" --production` (20) | `prisma` has no `destroy`, so this needs a programmatic replacement (see "What replaces the binary in CI") | -| `scripts/cold-start-canary.ts:214` | comment "`prisma-composer deploy --name`" | fix the comment | -| `scripts/skill-frontmatter.test.ts:12` | fixture `name: prisma-composer`, a skill name | leave it or rename it; it is not a command | - -### Workflows and actions - -- `.github/workflows/ci.yml` - - `node-floor` job: the step "CLI node-compat smoke test (spawns node at src/bin.ts)" (215-216) runs the deleted test. The step "Smoke the published CLI binary" (223-228) runs `packages/9-public/composer-cli/dist/bin.mjs --version` and `--help`. Both go. The floor job keeps `check:floor-imports` (236-237), which imports `./family` and `./testing` on Node 22.18.0. - - The comment at 232-235 names `prisma-composer deploy`. - - `platform-tests` (Windows): "Test installed CLI on Windows" (159-161) runs `pnpm --dir test/integration exec bun test`, which spawns the binary (see the next section). -- `.github/actions/deploy-verify-destroy/action.yml`: - - The Deploy step (45-51) runs `bun node_modules/.bin/prisma-composer deploy module.ts --name ...`. - - The Destroy step (60-66) calls `scripts/destroy-guard.sh`. The input description at line 15 names the binary. - - `e2e-deploy.yml` (47, 70, 93, 162) and `cron-keep-awake-canary.yml` (41) use this action. -- `.github/workflows/deploy-docs.yml`: - - Its Deploy step runs `bun node_modules/.bin/prisma-composer deploy module.ts` (80). The comment at 69 also names the binary. - - **This workflow runs on every push to main that touches `docs/guides/**`.** Slice 2 edits the guides, so the merge commit itself runs it. It must be reworked in the same PR. -- `.github/ISSUE_TEMPLATE/bug_report.yml:19` "Which package (or the `prisma-composer` CLI)". - -**What replaces the binary in CI.** - -- `prisma deploy` exists. `prisma@8.0.0-rc.19` is `latest` on npm and has `deploy` and `dev`. -- The published `prisma` pins `@prisma/composer-cli@0.25.0` from npm. Run inside this workspace, it would deploy with the last release of the family, not the code under test. The only fix on that route is a root pnpm override that points `@prisma/composer-cli` at the workspace package. -- `destroy` has no `prisma` command. `destroy-guard.sh` must become a small script over `destroy` from `@prisma/composer/control`. The bare operation falls back to `PRISMA_SERVICE_TOKEN` and `PRISMA_WORKSPACE_ID` when no credentials are injected (`operations/shared.ts:40-51`). -- A repo-private script that calls `deploy` and `destroy` from `/control` for both steps avoids the override question entirely. - -### Tests that spawn the binary (`test/integration`) - -| File | How it uses the binary | -| --- | --- | -| `test/integration/test/spawn-composer.ts` (1-19) | `spawn.sync('prisma-composer', ...)` with `node_modules/.bin` on PATH | -| `test/integration/test/cli.engine-shell.test.ts` | the whole file tests the binary: `--version`, `--help` listing four commands, the name `prisma-composer` | -| `test/integration/test/cli.extension-config.test.ts` | `spawnComposer(['deploy', fixtureEntry])`, to prove real `/control` resolution (45-60) | -| `test/integration/test/local-dev-criteria-4-5.integration.ts` | `CLI_BIN = .../node_modules/.bin/prisma-composer` (46), spawns `dev` (189), `pgrep -f 'prisma-composer dev'` (382) | -| `test/integration/test/local-dev-store.integration.ts` | `CLI_BIN = examples/store/node_modules/.bin/prisma-composer` (99), spawns `dev` (311), `pgrep` (719) | - -- `cli.extension-config.test.ts` and the two local-dev proofs test real behaviour, so they need a new driver. Options: the family mounted with `createTestCli` or `createCli` over the real operations, or the `/control` operations called directly. -- `cli.engine-shell.test.ts` has nothing left to prove and goes. -- `test/integration/package.json` already declares a `prisma` devDependency, pinned to **7.9.0** (Prisma 7). Using the `prisma` bin here means changing that pin, which may break whatever uses 7.9.0 today. - -## 2. What stays as programmatic API - -- `CLI/src/exports/control.ts` is published as `@prisma/composer/control` through `packages/9-public/composer/src/exports/control.ts:1`. These exports stay: - - `destroy` and the types `DestroyEvent`, `DestroyInput` and `DestroyTarget` (23-28). - - `log` and the types `LogAttached`, `LogEvent`, `LogInput` and `LogLine` (31-32). - - The header comment (1-15) is accurate. -- `CLI/src/operations/destroy.ts`, `log.ts`, `execute-log.ts` and `execute-deploy-destroy.ts` stay. Their stale comments: - - `operations/destroy.ts:3-4` and `operations/deploy.ts:3-4` say "The prisma-composer CLI (main.ts) is a thin renderer over it". `main.ts` does not exist. - - `operations/deploy.ts:20` says "same contract as `prisma-composer deploy `". -- `OperationName` (`operations/shared.ts:118`) keeps `'destroy' | 'log'`. -- `ComposerOperations` (`family/family.ts:42-47`) and `realOperations` (49-54) are the family's injection seam, not the programmatic API. - - Once the `destroy` and `log` commands are gone, nothing reads the `destroy` and `log` members. - - The host imports only `createComposerFamily` (`prisma-cli:packages/cli/src/cli.ts:10`). It never imports `ComposerOperations`, `realOperations` or `/testing`. - - Dropping the two members also drops `destroy` and `log` from the control double (`CLI/src/testing/control-double.ts:22-24, 31-33, 41-42, 57-67, 176-217`), which is published as `@prisma/composer-cli/testing`. - - Recommendation: drop them, because nothing but the deleted commands used them. Keeping them does no harm, but it is dead code. - -These tests cover `destroy` and `log` without going through the commands. All of them stay: - -- `CLI/src/operations/__tests__/operations.test.ts`: - - `destroy()`, 769 to about 1050: target to locate, failure cause, teardown order, the `no-local-deploy-state` event. - - The Windows refusal of `dev` and `log`, 1053-1070. - - `log()`, 1471-1797: config refusal, merging, address filter, abort, back-pressure, stream failure. - - "destroy is not reported", 2032. -- `CLI/src/exports/__tests__/control-import.test.ts`: importing `/control` loads no executor. The regex at 31 names `execute-deploy-destroy` and `execute-log`. -- `CLI/src/testing/__tests__/control-double.test.ts` 41 (destroy) and 89-130 (log), only if the double keeps those members. - -## 3. `prisma-composer` as a command or config-file name - -The search uses the pattern `(?` (55-59) and `pnpm install --lockfile-only` (61-66). - - Because it reads `HEAD`, run it after everything else is committed. -- `scripts/set-version.ts`: - - It writes the version into **every** package that `pnpm list -r --json` lists: the workspace root and every private and public package (45-61). It also rewrites internal `workspace:` specifiers (`rewriteWorkspaceDeps`). - - It stamps `metadata.library_version` into every `skills/*/SKILL.md` (63-78). - - On this branch, 41 of the 46 tracked `package.json` files carry 0.25.0. The other 5 are samples under `docs/design/04-inspirations/**`, outside the workspace. - - So a bump touches 41 manifests, `pnpm-lock.yaml` and `SKILL.md`. -- `publish.yml` runs on every push to `main` (20-22): - - `scripts/determine-version.ts` compares the root version with the one at the commit before the push (header 11-21). If it changed, the version is `` with dist-tag `latest`, plus a `devVersion` follow-up. If not, the version is `-dev.N` with tag `dev`. - - Then it runs `set-version.ts`, `pnpm build`, `check:publish-deps`, `check:cli-engine-pin`, `check:skill-packaging`, `check:family-static-graph` and `check:npm-effect-resolution` (82-115). - - Then `publish-packages.mjs` (127-131) and, for `latest`, a GitHub Release (153-173). - - Finally "Notify prisma-cli" (175-189) sends a `product-published` repository dispatch naming `@prisma/composer-cli` and the version. It is skipped when `DEPLOY_GITHUB_TOKEN` is not set. - - The publish job runs the same pin, static-graph and effect-resolution scripts that § 1 rewrites. A missed `bin.mjs` reference therefore fails the release, not only CI. -- The maintainer procedure is `skills-contrib/publish-npm-version/SKILL.md` together with `docs/oss/versioning.md`. It puts the bump in its own `chore(release): v` PR. Slice 2 folds the bump into the feature PR instead. The mechanism allows that, because it only compares root versions. - -## 6. Existing source-text checks and where a new one fits - -- The closest model is `scripts/lint-framework-vocabulary.mjs` (136 lines) with `scripts/lint-framework-vocabulary.test.mjs` (96 lines). - - The script exports a pure `findVocabularyViolations(baseDir, table)`. It walks directories with an extension allowlist and a list of excluded directories. - - It prints `file:line: term: text` for each violation and exits 1. - - It takes an optional root argument for tests, and runs `main()` only when invoked directly. - - The test uses `node:test` and a temporary fixture tree, and also spawns the script. -- Wiring: - - The root `package.json` script `lint:deps` chains `depcruise ... && node scripts/lint-architecture-coverage.mjs && node scripts/lint-publishable-location.mjs && node scripts/lint-framework-vocabulary.mjs && node scripts/lint-orm-pins.mjs && node scripts/lint-contract-snapshots.mjs`. - - CI runs it in job `lint` ("Lint"), step "Lint (dependency-cruiser — domain/layer/plane boundaries, ADR-0028)" (`ci.yml:28-29`). - - Script tests run through `test:scripts` (`node --test scripts/*.test.mjs scripts/*.test.ts`), in job `test`, step "Test scripts (cast-ratchet unit tests)" (`ci.yml:107-108`), and in `node-floor` (219-220). -- Other checks: - - `check-skill-packaging.mjs` packs `@prisma/composer` and compares the packed skill with `skills/` byte for byte. It runs in job `cli-engine-pin` (`ci.yml:304-305`) and in `publish.yml:103-104`. - - `lint-casts.mjs` runs in job `cast-ratchet`. - - `check-orm-pins.mjs` checks that ORM-family version pins agree. -- The house style fits a new `scripts/lint-no-standalone-cli.mjs` (name open) with a `.test.mjs`, appended to `lint:deps`. - - Its scope, from the spec: `README.md`, `docs/guides/**`, `skills/**` and `examples/**` (READMEs, package.json scripts, sources and comments). Arguably `website/**` too. - - It must allow the legacy-diagnostic text. `docs/guides/deploying.md` 63, 70 and 404 and `SKILL.md` 241 and 245 name `prisma-composer.config.ts` on purpose, to tell users the file is retired. - - Two options: the check allows the config-file name on specific lines or in a migration section, or those passages describe the file without its literal name. Spec requirement 8 says the check "fails on either string in those files", so this decision needs to be recorded. - -## 7. Editing the shipped skill - -- `skills/README.md:62-90` sets the rules: - - Verify every claim while drafting, against a `packages/9-public/*` export map or the CLI source. If ripgrep finds nothing, list the item under "What Composer doesn't do yet". - - Stay self-contained: no link may resolve outside `skills/prisma-composer-core-concepts/`, and repo docs are named in prose only. - - Teach concepts, not procedures. - - Leave `metadata.library` and `metadata.library_version` alone. - - The folder name must equal the frontmatter `name`. - - Maintainer skills live in `skills-contrib/`. -- `.agents/rules/user-facing-surface-changes.mdc` (alwaysApply) requires a user-facing change to update `docs/guides/` **and** the skill in the same PR. The guides are canonical and the skill mirrors them. Editing a guide redeploys the docs site through `deploy-docs.yml` (see § 1). -- `packages/9-public/composer/skills/` is a staged copy. Do not edit or commit it. - - It is **gitignored** (`.gitignore:61-64`). - - `prepack` (`node ../../../scripts/stage-skills.mjs`, package.json:31) rebuilds it from scratch on every `pnpm pack` or `pnpm publish`, taking the skills whose `metadata.library` is `@prisma/composer`. - - `check:skill-packaging` proves the packed copy matches `skills/`. - -## 8. The prisma-cli host (`main` at `4d254ff`) - -- The host's published bin is the package `prisma` (`packages/prisma/package.json`): version 8.0.0-rc.19, bin `prisma` pointing at `./dist/prisma.js`, and an `./config` export. `packages/cli` is `@prisma/cli`, whose bin is `prisma-cli`. Both depend on exactly `@prisma/composer-cli: "0.25.0"`. `packages/cli` also has `@prisma/composer: "0.25.0"` as a devDependency. -- The host source imports one thing from Composer: `import { createComposerFamily } from "@prisma/composer-cli/family"` (`packages/cli/src/cli.ts:10`). - - It becomes `composerCommandFamily` (141), is mounted at the root as `deploy` and `dev` (415-416), and is listed in `commandFamilies` (460). - - The host never imports `/testing`, `ComposerOperations`, `realOperations`, `composerSection`, `toEngineError`, `BINARY_NAME`, `createComposerCli` or `runComposerCli`. -- Other Composer references in the host: - - `packages/cli/scripts/conformance.ts:62,68,111,114` and `tests/v8-conformance.test.ts:76` require the specifier `@prisma/composer-cli/family`. - - `tests/fixtures/startup-probe.mjs:29` imports `@prisma/composer/deploy`, and line 37 matches the path `@prisma/composer-cli/dist/family.mjs`. - - `src/lib/skills/allowlist.ts:20` lists `@prisma/composer` for skill sync. -- `configPath` in the host: - - `tests/fixtures/config/composer-section.config.ts` contains `composer: { configPath: "./named-by-the-section.config.ts" }`. - - `tests/bin.test.ts` 36-42 and 506-588 ("hands the composer section of prisma.config.ts to the composer family") expect `CONFIG.FILE_MISSING`. - - Those tests break on the slice-1 release whether or not slice 2 lands. Slice 3 owns them. - - Every other `configPath` hit in `packages/cli` is the engine's `--config` path or telemetry, unrelated to Composer. -- `destroy` and `log`: no Composer-related hits. The matches are the platform's own `service delete`, `service logs` and `project delete` commands. -- `prisma-composer`: only `tests/skills-sync.test.ts:179-189, 844-854`, a fixture skill named `prisma-composer`, unrelated to the binary. -- Conclusion: slice 2 deletes nothing the host imports, as long as `./family` keeps exporting `createComposerFamily` and the built file stays `dist/family.mjs`. diff --git a/projects/one-config-file/slices/02-remove-binary/plan.md b/projects/one-config-file/slices/02-remove-binary/plan.md deleted file mode 100644 index 3632b8b67846..000000000000 --- a/projects/one-config-file/slices/02-remove-binary/plan.md +++ /dev/null @@ -1,31 +0,0 @@ -# Slice 2 plan - -Spec: [`spec.md`](./spec.md). Branch in prisma/composer: `one-config-file/remove-binary`, stacked on `one-config-file/composer-section` until slice 1 merges, then rebased onto `main`. Same implementer as slice 1, resumed. - -## Dispatch 1 — The examples and CI run the real host - -**Outcome.** Every example, `test/integration` and `website` depend on the `prisma` package at the host's current published version and import `definePrismaConfig` from `prisma/config`. A root `pnpm.overrides` entry points `@prisma/composer-cli` at the workspace. Example scripts run `bun node_modules/.bin/prisma deploy|dev`. A repo-private destroy script under `scripts/` calls the programmatic `destroy` with the example's config. `.github/actions/deploy-verify-destroy`, `scripts/destroy-guard.sh`, `deploy-docs.yml` and the cron canary use `prisma` and that script. The four integration tests that spawned the binary spawn `prisma` instead. `check-cli-engine-pin.mjs` still passes. The binary still exists at the end of this dispatch, unused by anything in the repo. - -**Builds on.** Slice 1. - -**Hands to.** Dispatch 2: nothing in the repo runs the binary. - -## Dispatch 2 — The binary is deleted - -**Outcome.** The cli package's `bin.ts`, `cli.ts`, `family/engine-cli.ts`, `family/runtime.ts`, `commands/destroy.ts`, `commands/log.ts`, `target.ts`, the pass-through `orm` section family, their tests, the tsdown `bin` entry, `ComposerOperations.destroy`/`.log` and the control double's counterparts are deleted. `@prisma/composer-cli` declares no `bin`; `check-cli-engine-pin.mjs` and `check-npm-effect-resolution.mjs` no longer expect `dist/bin.mjs` or run `--help`. The `/control` exports `destroy` and `log` keep their tests. Stale comments corrected. `packages/` builds and every check from slice 1 passes. - -**Builds on.** Dispatch 1. - -**Hands to.** Dispatch 3: a repo with no binary and no reference to it in code. - -## Dispatch 3 — The name is gone and a check keeps it out - -**Outcome.** Every `prisma-composer` command mention in `README.md`, `docs/guides/`, `skills/`, `skills-contrib/`, `examples/**`, `website/`, `test/`, `packages/**` messages and comments, and `.github/` is rewritten to the `prisma` form or to the programmatic operation, with the guide and skill passages the spec names (teardown and logs as operations with an example each, login required, the stale `[dev] logs` line removed). The staged skill copy is regenerated. The CI check script and its test exist and are wired into the lint job, with the allowlist for the migration passages. `pnpm bump-minor` is run last, taking the repo to 0.26.0. - -**Builds on.** Dispatch 2. - -**Hands to.** Code review, then PR open. - -## Dispatch 4 — Review fixes - -**Outcome.** Every in-scope finding fixed and verified. diff --git a/projects/one-config-file/slices/02-remove-binary/review/alchemy-bin-review.md b/projects/one-config-file/slices/02-remove-binary/review/alchemy-bin-review.md deleted file mode 100644 index d10ed3a7524f..000000000000 --- a/projects/one-config-file/slices/02-remove-binary/review/alchemy-bin-review.md +++ /dev/null @@ -1,155 +0,0 @@ -# Review: alchemy bin resolution (`fix/alchemy-bin-resolution`) - -Range: `one-config-file/remove-binary...fix/alchemy-bin-resolution` in the `wip/composer` clone (6 commits). Reviewed as a principal engineer: failure modes, blast radius, operability. - -## Summary - -The fix is correct for the bug it targets. Composer no longer depends on a `node_modules/.bin/alchemy` link, which pnpm creates only for direct dependencies. QA run 2 shows `prisma dev` reaching ready in a plain pnpm project with no direct `alchemy` dependency. Moving the dev stack file's `localState` import behind `@prisma/composer/local-target` is the right second half: the app cannot resolve `alchemy` under pnpm either. The deploy stack file already imports only `@prisma/composer` entries. - -The main problem is a claim, not the code. The guide, the skill and the `nodeExecutable` doc comment say Alchemy runs under Node whenever `prisma` runs under Bun. That is not true. Alchemy's own launcher (`node_modules/alchemy/bin/cli.js`) switches to Bun whenever `npm_execpath` or `npm_config_user_agent` names Bun. That covers `bun run