Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions drive/calibration/dod.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pnpm --filter <pkg> lint # biome check --error-on-warnings, per touched p
pnpm lint:deps # when imports/exports/architectural structure changes
pnpm test:packages # when source or test code changes (almost always)
pnpm test:integration # when changes affect PGlite / PG / mongo paths
# when a command's behaviour changes: every integration and e2e file that runs that command, before queueing (F44)
pnpm test:e2e # when changes affect emit / migrate / run cycle
pnpm fixtures:check # when IR / emitter / serialiser changes
pnpm check:upgrade-coverage --mode pr --prev <PR base sha> --head HEAD # when packages/3-extensions/** or examples/** change
Expand Down
45 changes: 45 additions & 0 deletions drive/calibration/failure-modes.md
Original file line number Diff line number Diff line change
Expand Up @@ -803,3 +803,48 @@ Per-repo stop conditions beyond the canonical ones:
**Mitigation.** When a dispatch changes an API with many callers, the brief names the helper or entry point the callers move onto, or makes deciding it the first step. The implementer applies the decided shape itself or hands helpers a reference implementation to copy exactly. Sizing pattern: "mechanical fan-out + design judgment in one dispatch".

**Reference incident.** 2026-09-30, lsp-go-to-definition slice 1 dispatch 4: four parallel helpers migrated ~33 test files to build a binder; the diff grew by ~2,500 lines and a second round introduced one shared function.

### F43. A change to a dependency in another package or repository is specified from the dependency's side, and the consumer finds the gaps only when it integrates

**Symptom.** A slice needs a new API in a shared package or another repository. The dependency gets its own spec, implementer and reviewer, passes review, and then the consumer's first dispatch halts: the API cannot do something the consumer needs. Each halt sends the dependency back for another round.

**Root cause.** The dependency's spec described the API in the dependency's own terms. Nobody wrote the code the consumer would call, so nothing checked that the consumer could read what it needed, or that every value the consumer passes is accepted. The dependency's reviewer checked the dependency against its own spec.

**Detection signal.** The dependency spec has API signatures but no consumer code. The consumer slice's plan says "dispatch N adopts the new API" with no sketch of the calls.

**Mitigation.**

- The dependency spec contains the consumer's call sites as code: every call the consumer slice will write, including how the consumer reads results and the full range of values it passes (for example, names that contain the separator the API parses).
- The dependency's reviewer checks each consumer call against the dependency before it ships, and the consumer's orchestrator reads the dependency's public surface before writing the consumer's briefs.

**Reference incident.** 2026-10-07, migration statements slice 2 and prisma/prisma-cli#337 (statement prompts in the CLI engine). Three reworks after review: verbs registered for the whole command family collided with the ORM's own `--rename` flag and put ORM verbs on every command (the operator caught it); the engine kept statement values private, so the ORM could not read its `--rename` values (consumer dispatch 3 halted); and the engine refused `:` in a question's subject, which a storage name can contain (same halt).

### F44. A change in what a command does passes PR CI and fails in the merge queue, in integration tests the slice never touched

**Symptom.** A slice changes how a command behaves (a new refusal, a new required flag). Its own journeys pass, PR CI passes, and the merge queue ejects the PR: older integration tests that run the same command now fail.

**Root cause.** PR CI skips integration tests on purpose, because they are slow and costly; only the merge queue runs them. The dispatch gate ran "the touched integration files", but a behaviour change breaks every test that runs the command, touched or not.

**Mitigation.** When a slice changes what a command does, the gate before queueing includes every integration and e2e test file that runs that command, and runs those files locally (`pnpm test <file>`). Most journeys do not name the command: they call a helper in `test/integration/test/utils/journey-test-helpers.ts` (for example `planMigrationAndSelfEmit` runs `migration plan`). So find the helpers that run the command first, then grep `test/integration` and `test/e2e` for the command name and for each of those helpers. Never run the full suites locally.

**Reference incident.** 2026-10-08, prisma/orm#30648 (refuse data loss without `--delete`): ejected from the merge queue by `migration-apply-edge-cases` and `rollback-cycle`, which planned column drops with no `--delete`.

### F45. A safety check is moved to a new mechanism, and one path that fed the old check does not feed the new one

**Symptom.** A check that guards every operation (consent, a policy, a refusal) is reimplemented on a new data source. Review finds that one way operations reach the runner never fills the new source, so those operations would run unchecked.

**Root cause.** The spec and brief named the new mechanism but not the list of paths the old check covered. The implementer covered the paths it knew.

**Mitigation.** A brief that replaces a check lists every path that feeds the old check, with file references, and the done conditions name a test per path. A path that no shipped fixture exercises gets a test built for it; "no shipped extension has one" is not a reason to skip the probe.

**Reference incident.** 2026-10-07, migration statements slice 2 dispatch 2: the old `db update` consent counted the destructive operations of every contract space; the new questions came from the planner's `dataLoss`, which spaces that apply recorded migrations always left empty. The reviewer found it (D2-1, high); without the fix an extension's destructive migration would have run without consent. The executed probe for that path was still never run end to end.

### F46. Advice that a refusal or error prints is checked by reading it, and following it loses data or does not work

**Symptom.** A command refuses and prints what to do next. Tests assert the text is present. A user who follows it literally drops the table, moves half the documents, changes the wrong database, or types a flag the next command rejects.

**Root cause.** Advice is product behaviour, but reviewers and tests treat it as copy. Nobody runs the steps it gives.

**Mitigation.** Every printed next step (error advice, refusal flags, dry-run suggestions, recovery steps) is followed literally in an executed probe or a manual QA scenario, on each database the command supports, and the end state is checked: rows and documents kept, the right database changed, the suggested command accepted. The manual QA script lists each advice text as its own scenario.

**Reference incidents.** Migration statements slice 1 (2026-10-06 and 07): the first `@@map` refusal advice dropped the table when followed; the MongoDB field-rename advice left documents half moved (QA F3). Slice 2 (2026-10-08): the dry run printed flags the apply rejected; the no-snapshot recovery steps omitted `--db` and could change the wrong database; the MongoDB refusal offered only `--delete` to a user who had renamed a model (QA F1). All were found by executed review probes or manual QA, none by the in-loop review.
4 changes: 4 additions & 0 deletions drive/spec/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ In addition to the canonical project-spec / slice-spec templates, this repo expe
- **ADR pointer** for any architectural shift. Either link an existing ADR or commit to authoring one as part of the project's close-out.
- **Failure-state section** for any spec of a command that edits files it did not write. For each way the command can fail after it starts, name the state the user's project is left in, and require that a failure the command can predict is reported before the first edit. (Added 2026-09-24, orm-init-prisma7-detection final retro: `orm init` renamed the Prisma 7 config and rewrote scripts before finding out Prisma 8 could not read the schema, and the problem surfaced only in manual review after five dispatches.)

## Read the architecture before writing a spec

Before writing a slice or project spec, read the architecture overview, the subsystem docs for every area the work touches, and the ADRs they cite. A spec written from the code alone misses project-wide rules the code does not show. (Added 2026-10-08, migration statements slice 2 retro: the operator stopped slice 2's spec until the docs were read, and reading them turned up the CLI Style Guide's consent rule, which changed the design to a CLI engine change. The operator asked for the same reading again before the remaining slices were discussed.)

## Grounding illustrative snippets before execution

Specs legitimately carry illustrative code (PSL grammar, IR type shapes) while shaping. Before a slice executor treats any snippet as fact, they must re-verify it against shipped code — not re-read the spec sketch.
Expand Down
15 changes: 15 additions & 0 deletions projects/migration-statements/retros.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Retros — Migration statements

## 2026-10-08 — Slice 2 (TML-3476, prisma/orm#30648) and the engine change (prisma/prisma-cli#337)

Run after the merge by gauss-84, from beowulf-40's transcript and the committed dispatch briefs and QA report. Slice 1 had no retro either; its lessons that recur here are folded into F46.

| Trigger | What happened | Root cause | Landed in |
| --- | --- | --- | --- |
| Dispatch failure | Three engine reworks after review: verbs registered for the whole family, statement values the ORM could not read, `:` refused in subjects. | The engine spec described the API from the engine's side, with no ORM call sites. | `drive/calibration/failure-modes.md` F43 |
| Scope-shift escapee | The merge queue ejected #30648 on two integration tests it never touched. | The gate ran only touched journeys; PR CI skips integration tests by design. | F44; `drive/calibration/dod.md` conditional gate |
| Dispatch failure (caught in review) | Recorded extension migrations left `dataLoss` empty, so their destructive operations would have run without consent. | The brief named the new mechanism but not every path the old consent check covered. | F45 |
| Operator-flagged surprise (recurring since slice 1) | Advice texts that lost data or failed when followed; found only by executed probes and manual QA. | Advice was reviewed as copy, not run. | F46 |
| Operator-flagged surprise | The operator stopped the slice 2 spec until the architecture docs and ADRs were read; the reading changed the design. | Spec authoring grounded in code only. | `drive/spec/README.md` § Read the architecture before writing a spec |

What worked: per-command statement declarations (the operator's correction) gave one consent mechanism for flags and prompts; executed probes against PGlite, SQLite and MongoDB found every advice defect before users did; halting dispatches on named conditions surfaced the engine gaps before code was written around them.
Loading