Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Status: **Accepted**

**Amended by [ADR 279](ADR%20279%20-%20Convenience%20types%20choose%20an%20explicit%20type%20from%20their%20arguments.md) (2026-10-22):** where a stored type has several codecs, every one of them carries a suffix naming its kind of value, not only the second (`TimestamptzTemporal`, `TimestamptzJsDate`, `TimestamptzString`). The unsuffixed name of the concept, such as `DateTime`, `Date`, `Time` and `Duration` on PostgreSQL, belongs to a convenience type, a type choice that becomes one of the suffixed types.
**Amended by [ADR 279](ADR%20279%20-%20Convenience%20types%20choose%20an%20explicit%20type%20from%20their%20arguments.md) (2026-10-22):** where a stored type has several codecs, every one of them carries a suffix naming its kind of value, not only the second (`TimestamptzTemporal`, `TimestamptzJsDate`, `TimestamptzString`). The unsuffixed name of the concept, such as `DateTime`, `Date`, `Time` and `Duration` on PostgreSQL, belongs to a convenience type, a type choice that becomes one of the suffixed types. Those renames were made before the release candidate, so the old names were removed at once, with no deprecated alias for a release line; the upgrade instructions give each rewrite. A type named before this amendment with no suffix beside a suffixed one, such as MongoDB's `Int64` beside `Int64Number` below, keeps its name until it is renamed under the alias rule.

## Decision

Expand Down Expand Up @@ -86,13 +86,13 @@ On PostgreSQL and SQLite, `Json` columns store JSON text, so what is stored is a

## Renaming a surface name

A renamed PSL name stays available as a deprecated alias of the new one for one release line, then is removed. The alias resolves to the same codec, so the contract does not change. A type constructor marks itself with `deprecated: { replacement }` on its `AuthoringTypeConstructorDescriptor`, and the PSL interpreter reports `PSL_DEPRECATED_SCALAR_NAME` at the type's span through the contract source's warning channel: a `ContractSourceDiagnostic` with `severity: 'warning'`, delivered to `ContractSourceContext.reportWarning`. `contract emit` prints it and still emits; the language server shows it as a warning and lists the deprecated names last in completion, struck through.
A renamed PSL name stays available as a deprecated alias of the new one for one release line, then is removed. The one exception is the PostgreSQL date and time renames of ADR 279, made before the release candidate, which removed the old names at once. The alias resolves to the same codec, so the contract does not change. A type constructor marks itself with `deprecated: { replacement }` on its `AuthoringTypeConstructorDescriptor`, and the PSL interpreter reports `PSL_DEPRECATED_SCALAR_NAME` at the type's span through the contract source's warning channel: a `ContractSourceDiagnostic` with `severity: 'warning'`, delivered to `ContractSourceContext.reportWarning`. `contract emit` prints it and still emits; the language server shows it as a warning and lists the deprecated names last in completion, struck through.

A codec id is never renamed under this decision. If one ever has to change, the reserved mechanism is: the codec registry registers the same codec under both ids, the emitter always writes the new id, the contract validator accepts both, `CodecTypes` carries both keys mapped to the same type, and an upgrade instruction tells users to re-emit and re-sign; after one release line the old id is removed with an error naming the new one.

## Consequences

- Every target follows the same rule and the same deprecation path for a renamed PSL name. The current per-target names and the cross-target concept table are listed in [Scalar types](../../reference/scalar-types.md); other docs link there instead of repeating them.
- Every target follows the same rule and the same deprecation path for a renamed PSL name, apart from the pre-release exception above. The current per-target names and the cross-target concept table are listed in [Scalar types](../../reference/scalar-types.md); other docs link there instead of repeating them.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
- Readers of Prisma 6 and Prisma 7 schemas map the old names to codec ids through their target bindings and are unaffected by PSL names.
- An extension that contributes a codec names its PSL type and TypeScript helper after the codec id's token, and declares every BSON type its codec stores in the `bsonTypes` of the codec's data type; the MongoDB validator derivation reads the whole list.

Expand Down
2 changes: 2 additions & 0 deletions drive/calibration/dod.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ In addition to the canonical slice DoD:
- [ ] No `projects/` references in long-lived files added by the slice (per the doc-maintenance rule; grep gate in [`grep-library.md`](./grep-library.md)).
- [ ] Before enabling auto-merge, after the last merge of `main`: run the checks that only the merge queue or CI's Lint job runs. That is the PSL print sweeps (`test/integration/test/psl-print/every-postgres-contract-roundtrip.integration.test.ts` and the other `psl-print` files) when the slice changes contracts, columns or printing; every journey that runs a changed command ([`failure-modes.md § F44`](./failure-modes.md#f44-a-change-in-what-a-command-does-passes-pr-ci-and-fails-in-the-merge-queue-in-integration-tests-the-slice-never-touched)); and `pnpm lint:ratchets` (the cast, throw and framework vocabulary ratchets) and `pnpm check:upgrade-coverage --mode pr --prev <PR base sha> --head HEAD`, which `lint:agent` skips. `grep -rhoE "run: pnpm [a-z:-]+" .github/workflows/*.yml | sort -u` lists the single-line `pnpm` steps in the GitHub workflows; read the workflows for steps it misses, such as multi-line `run` blocks. (Added 2026-10-21, raw-columns: the queue dropped one slice for an unrun print sweep and another for an unrun journey and the vocabulary lint.)

- [ ] After merging `main`: if the merge resolved conflicts with a change `main` made to the same code, run a review round on `git diff <main commit> <merge commit>` before asking for review ([`failure-modes.md § F51`](./failure-modes.md#f51-a-merge-of-main-that-resolves-a-large-overlapping-change-goes-to-review-as-if-it-were-not-new-work)); if `main` had a release bump, `git diff <release commit> HEAD -- upgrade-instructions/releases/` is empty ([`F52`](./failure-modes.md#f52-a-release-bump-archives-pending-upgrade-instructions-and-the-branchs-later-edits-land-in-the-released-copy)). (Added 2026-10-28, datetime-convenience-types.)
- [ ] If the slice changes what an existing type name means: every PSL and TypeScript source under `test/` and `examples/` that writes the name is checked, and the integration files that emit them are run ([`F53`](./failure-modes.md#f53-a-name-changes-meaning-and-a-hand-written-source-that-fixturescheck-does-not-emit-keeps-the-old-meaning)). (Added 2026-10-28, datetime-convenience-types.)
- [ ] Before enabling auto-merge: every review thread on the PR is resolved, by a fix or by a reply that says why not. List the open ones with `gh api graphql --paginate -F number=<PR number> -f query='query($number:Int!,$endCursor:String){repository(owner:"prisma",name:"orm"){pullRequest(number:$number){reviewThreads(first:100,after:$endCursor){pageInfo{hasNextPage endCursor} nodes{id isResolved path}}}}}' --jq '.data.repository.pullRequest.reviewThreads.nodes[]|select(.isResolved==false)|"\(.id) \(.path)"'`, which prints nothing when every thread is resolved; bot reviewers count. (Added 2026-10-27, mongo-authoring-structure: slice 1 merged with four open CodeRabbit threads, found only at close-out.)
- [ ] If the slice changes code that `defineContract`, a pack descriptor or the runtime reaches: run `pnpm size:build` in `examples/bundle-size` (it builds the workspace, then bundles) on the branch and on the PR base ref (`main`, or the base branch of a stacked PR), compare the metafiles, and explain any growth in the PR description before CI's size report asks. (Added 2026-10-27, mongo-authoring-structure: both slices grew a client bundle and the cause was traced only after CI reported it.)

Expand Down
39 changes: 39 additions & 0 deletions drive/calibration/failure-modes.md
Original file line number Diff line number Diff line change
Expand Up @@ -907,3 +907,42 @@ Per-repo stop conditions beyond the canonical ones:
- The parity suite gets a case per assumption, accepted and refused, not only a case per existing rule.

**Reference incident.** One MongoDB definition and builder (prisma/orm#30846, 2026-10): the principal-engineer review found, in two rounds, that a variant field could be stored under a name its base used, and that a variant could restate a base field with another type or stored name. Both built, and MongoDB then refused the variant's documents, because the validator is written from the base. Both were on `main` before the refactor.

### F51. A merge of `main` that resolves a large overlapping change goes to review as if it were not new work

**Symptom.** A branch that review has already passed merges `main`, and `main` has a change that rewrote the same code. The merge resolves dozens of conflicts with design choices of its own, such as which side's concept survives. The branch is then handed to the operator for review with no review round on the merge, and the next round finds many defects in it.

**Root cause.** The slice DoD treats review as done once a round has passed, and a merge of `main` reads as routine upkeep. A conflict resolution that keeps one side's concept, rewrites the other side's new code to new names, or edits docs both sides changed is implementation work, and nothing in the DoD asks for it to be reviewed.

**Detection signal.**

- The merge commit touches more than a handful of conflicted files, or `git diff <main commit> <merge commit>` shows the branch editing code `main` just added.
- The resolver's report contains decisions ("kept X only for Y", "moved Z into W").

**Mitigation.** After a merge of `main` like this, run a review round on what the branch adds on top of `main` (`git diff <main commit> <merge commit>`) before asking the operator to review, and fix its findings first.

**Reference incident.** Convenience date and time types (prisma/orm#30847, 2026-10): the merge of the field-presets removal resolved 42 conflicted files and kept the `option` argument kind for type choices only. The operator had to ask whether it had been reviewed; the round that followed found twelve defects, including refusal messages that did not parse and upgrade text written into a released guide.

### F52. A release bump archives pending upgrade instructions, and the branch's later edits land in the released copy

**Symptom.** A branch edits a pending upgrade instruction that another change created. A release then moves the pending instructions into `upgrade-instructions/releases/<from>-to-<to>/sources/`. Merging `main` carries the branch's edits into the released sources, which now describe a later release and disagree with the assembled guide that shipped.

**Root cause.** Git follows the rename, so the branch's edit applies cleanly to the moved file. Nothing checks that released sources stay as they shipped.

**Detection signal.** After merging a release bump, `git diff <release commit> HEAD -- upgrade-instructions/releases/` is not empty.

**Mitigation.** After merging a release bump, run that diff. Restore any released file the branch changed (`git checkout <release commit> -- <path>`) and put the branch's guidance in its own pending instruction, with a sentence for readers who followed the released guide.

**Reference incident.** Convenience date and time types (prisma/orm#30847, 2026-10): the 8.0.0-rc.21 bump archived `remove-field-presets` and `prisma7-now-default-on-create` while the branch was rewriting them; the code review found the released sources describing rc.22 behaviour.

### F53. A name changes meaning, and a hand-written source that `fixtures:check` does not emit keeps the old meaning

**Symptom.** A change gives an existing type name a new meaning (here `DateTime`, from a Temporal value to a JavaScript `Date`) and rewrites every emitted fixture so no contract changes. `pnpm fixtures:check` passes. The merge queue then fails a test whose own PSL or TypeScript source is written inside a test fixture directory and emitted only by that test.

**Root cause.** The rewrite was driven by the emitted fixtures `fixtures:check` knows about. Sources that a test emits at run time are not in that list, and PR CI skips the integration tests that read them.

**Detection signal.** `git grep -n "<Name>" -- 'test/**/*.psl' 'test/**/*.prisma' 'test/**/*.ts' 'examples/**/*.prisma' 'examples/**/*.ts'` lists files the branch did not change.

**Mitigation.** When a name changes meaning, grep every PSL and TypeScript source under `test/` and `examples/` for the name, rewrite each one that relied on the old meaning, and run the integration file that emits it before enabling auto-merge.

**Reference incident.** Convenience date and time types (prisma/orm#30847, 2026-10): `cli.emit-command.test.ts` emits `fixtures/emit-command/schema.parity.psl`, which still wrote bare `DateTime` beside a TypeScript side written with the Temporal type; the queue dropped the PR on the hash mismatch.
Loading
Loading