Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
de5684a
test(sql-contract-prisma7): a printed Prisma 7 contract reads back as…
wmadden-electric Sep 16, 2026
ce027e9
feat(target-postgres): print a Postgres contract as the Prisma 8 PSL …
wmadden-electric Sep 16, 2026
d8ce511
docs(target-postgres): describe the contract-to-PSL printer and its e…
wmadden-electric Sep 16, 2026
db82351
feat(psl-printer): the caller names the header the printed file opens…
wmadden-electric Sep 16, 2026
f74ef00
feat(config): a contract source can declare the Prisma 7 format
wmadden-electric Sep 16, 2026
50f2ae8
refactor(cli): one place builds the stack and loads the contract source
wmadden-electric Sep 16, 2026
bbc67b4
feat(cli): prisma contract convert writes a Prisma 7 schema as Prisma…
wmadden-electric Sep 16, 2026
730521b
test(integration): a converted Prisma 7 project emits the same contra…
wmadden-electric Sep 16, 2026
de19301
docs: contract convert and the cutover that follows it
wmadden-electric Sep 16, 2026
9fb8b35
fix(target-postgres): the contract-to-PSL printer keeps the checks, u…
wmadden-electric Sep 16, 2026
937aa58
test(sql-contract-prisma7): the printed PSL reads back with its const…
wmadden-electric Sep 16, 2026
1e6d74c
docs(sql-contract-psl): the uniqueness rule names the unique index th…
wmadden-electric Sep 16, 2026
22b145e
refactor: the converter's messages and tests say what cannot be writt…
wmadden-electric Sep 16, 2026
c325761
docs(project): the spec records what contract convert cannot write ye…
wmadden-electric Sep 16, 2026
c1e26ed
fix(cli): a refusal the target raises reaches the user with its own code
wmadden-electric Sep 16, 2026
cba58a2
fix(cli): the cutover steps start with pointing the config at the wri…
wmadden-electric Sep 16, 2026
71bc9ff
fix(cli): contract convert honours a cancellation while it reads the …
wmadden-electric Sep 16, 2026
94a8159
test(journeys): the convert journey asserts the refusal a JSON object…
wmadden-electric Sep 16, 2026
4edc3a3
docs: contract convert documents the refusal most conversions meet first
wmadden-electric Sep 16, 2026
2e7e73f
docs(project): a mapped enum no column uses converts under its databa…
wmadden-electric Sep 16, 2026
166a48f
fix(target-postgres): a native enum takes its block name from the con…
wmadden-electric Sep 16, 2026
80afd4b
test(journeys): the convert journey runs over the relations fixture
wmadden-electric Sep 16, 2026
9664246
test(target-postgres): the enum block test reads a block attribute's …
wmadden-electric Sep 16, 2026
961f282
docs(upgrading): contract convert declares no consumer action for app…
wmadden-electric Sep 16, 2026
4e8f69d
docs(cli): the convert refusal names a target or a shape, and a colum…
wmadden-electric Sep 16, 2026
390046b
fix(cli): a contract source that succeeds with no contract fails at t…
wmadden-electric Sep 16, 2026
220b7e5
fix(target-postgres): a printed relation reads the foreign key that r…
wmadden-electric Sep 16, 2026
9b558ff
fix(target-postgres): a column mixing the now generator with another …
wmadden-electric Sep 16, 2026
2418264
fix(target-postgres): an enum block name no value set supplies is der…
wmadden-electric Sep 16, 2026
598f236
fix(cli): a cancelled contract convert writes no file
wmadden-electric Sep 16, 2026
342fa14
feat(cli): contract convert refuses to write over the schema it reads
wmadden-electric Sep 16, 2026
1017699
test(prisma7): an enum default on an unmapped enum type round-trips
wmadden-electric Sep 16, 2026
79d72d5
fix(target-postgres): a list column the printed file cannot carry is …
wmadden-electric Sep 16, 2026
35a70fd
test(journeys): the convert refusal journey names the first column th…
wmadden-electric Sep 16, 2026
4fbd3d5
Merge main into prisma7-convert
wmadden-electric Sep 21, 2026
53a3317
feat(sql): contract convert writes every list column
wmadden-electric Sep 21, 2026
a37fc50
feat(cli): contract print writes any contract as Prisma 8 PSL, and re…
wmadden-electric Sep 22, 2026
d08457a
Merge main into prisma7-convert
wmadden-electric Sep 22, 2026
ad1e819
feat(sql): contract print writes defaults through the data types of A…
wmadden-electric Sep 22, 2026
49d9b1c
docs(cli): contract print writes a PSL contract, not a schema
wmadden-electric Sep 22, 2026
e1c929d
refactor: the framework no longer knows Prisma 7
wmadden-electric Sep 22, 2026
366c603
fix(mongo): the Mongo PSL reader reports an attribute on an enum member
wmadden-electric Sep 22, 2026
af00251
refactor(config): a contract source is PSL or TypeScript, and a Prism…
wmadden-electric Sep 24, 2026
f6b5270
Merge main into prisma7-convert
wmadden-electric Sep 24, 2026
6689da9
fix: contract print writes what it used to drop, and every claim abou…
wmadden-electric Sep 24, 2026
dfdbbc4
fix(psl): one grammar for every PSL document, and the formatter keeps…
wmadden-electric Sep 24, 2026
ebacca4
fix(cli): contract print loads, prints and renders against one stack,…
wmadden-electric Sep 24, 2026
ec590b0
fix(postgres): contract print writes every part of a contract or refu…
wmadden-electric Sep 24, 2026
ccad36f
Merge main into prisma7-convert
wmadden-electric Sep 24, 2026
b3e2427
fix(lint): the CLI may name a Prisma version before 8
wmadden-electric Sep 24, 2026
b1e4f3f
test(cli): the PSL source a journey prints carries the prisma-8 marker
wmadden-electric Sep 24, 2026
d26333b
Merge main into prisma7-convert
wmadden-electric Sep 25, 2026
2b47673
fix(postgres): contract print never writes a __proto__ name the PSL s…
wmadden-electric Sep 25, 2026
c262ba6
test(cli): the printed-contract journey config imports definePrismaCo…
wmadden-electric Sep 25, 2026
787294f
feat(cli): contract print prints the PSL unless --output names a file
wmadden-electric Sep 25, 2026
7eb8fb6
Merge main into prisma7-convert
wmadden-electric Sep 25, 2026
4220f16
Merge main into prisma7-convert
wmadden-electric Sep 27, 2026
475aba4
Merge main into prisma7-convert
wmadden-electric Sep 27, 2026
116c3ae
fix(scripts): check-upgrade-coverage reads a repository tree larger t…
wmadden-electric Sep 27, 2026
b5417d6
fix(scripts): check-upgrade-coverage lists only the instruction direc…
wmadden-electric Sep 27, 2026
eb5cfb3
fix: second review round for contract print
wmadden-electric Sep 27, 2026
64ca015
fix(cli): contract format formats every .prisma file under a director…
wmadden-electric Sep 27, 2026
0a442bc
docs(rules): the vocabulary rule card states the CLI exemption in few…
wmadden-electric Sep 27, 2026
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
2 changes: 1 addition & 1 deletion .agents/rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Rules below are listed by bare filename; the canonical file is `.agents/rules/<n

## Imports & Layering
- `import-validation.mdc` — Layering rules and exceptions
- `no-family-vocabulary-in-framework.mdc` — No family/target vocabulary in packages/1-framework; enforced by the `no-family-vocabulary` Biome plugin + the `lint:framework-vocabulary` ratchet
- `no-family-vocabulary-in-framework.mdc` — No family/target vocabulary in packages/1-framework, and no name of a Prisma version before 8 outside its CLI; enforced by the `no-family-vocabulary` Biome plugin + the `lint:framework-vocabulary` ratchet
- `no-inline-imports.mdc` — Prohibit inline type imports in source files
- `shared-plane-packages.mdc` — Pattern for creating shared plane packages
- `multi-plane-packages.mdc` — Packages that span multiple planes (shared, migration, runtime)
Expand Down
8 changes: 6 additions & 2 deletions .agents/rules/no-family-vocabulary-in-framework.mdc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: The framework domain (packages/1-framework) carries no family- or target-specific vocabulary — types, fields, hooks, or strategy values. Enforced by the no-family-vocabulary Biome plugin plus lint:framework-vocabulary.
description: The framework domain (packages/1-framework) carries no family- or target-specific vocabulary — types, fields, hooks, or strategy values — and, outside the CLI, no name of a Prisma version before 8. Enforced by the no-family-vocabulary Biome plugin plus lint:framework-vocabulary.
globs: ["packages/1-framework/**"]
alwaysApply: false
---
Expand All @@ -10,12 +10,16 @@ alwaysApply: false

**The test for every new framework export, field, hook, or member:** *would Mongo populate or consume this meaningfully?* If not, it belongs in the family layer (e.g. `@internal/sql-contract`), behind a structural interface + type predicate at the boundary.

The framework also must not know which Prisma version wrote a document: code for an earlier version's schema belongs in the package that reads it (e.g. `@internal/sql-contract-prisma7`). The CLI (`packages/1-framework/3-tooling/cli`) may name such a version, because it talks to users about their projects.

## How it is enforced

The [`no-family-vocabulary` Biome plugin](../../biome-plugins/no-family-vocabulary.grit) matches syntax nodes — identifiers, string literals, template chunks, import paths — carrying family/target terms (`table`, `column`, `postgres`, `nativeType`, `primaryKey`, …). It emits `info` diagnostics; `pnpm lint:framework-vocabulary` is the ratchet that counts them, deduplicated by file and line, against the `threshold` in `scripts/lint-framework-vocabulary.config.json`. The count may only shrink, and the threshold must be lowered whenever it does.
The [`no-family-vocabulary` Biome plugin](../../biome-plugins/no-family-vocabulary.grit) matches syntax nodes — identifiers, string literals, template chunks, import paths — carrying family/target terms (`table`, `column`, `postgres`, `nativeType`, `primaryKey`, …) or, outside the CLI, the name of a Prisma version before 8 (`prisma7`, `Prisma 6`, `prisma@5.22.0`, …). `Prisma 8` is never flagged. It emits `info` diagnostics; `pnpm lint:framework-vocabulary` is the ratchet that counts them, deduplicated by file and line, against the `threshold` in `scripts/lint-framework-vocabulary.config.json`. The count may only shrink, and the threshold must be lowered whenever it does.

**Comments and JSDoc are deliberately not counted.** Prose that documents family-blind code correctly ("family-parameterized (SQL, Mongo, …)") is good documentation, and the previous line-scanning checker taxed it — the majority of what it flagged was prose, not surface. A leak that matters is a real type, field, identifier, string, or import path, and the plugin only sees those.

Test files and `test/` directories are not scanned. The CLI's allowance for version names is the only path exemption; do not add another. There are two other exemptions, and no others: a framework-neutral compound listed in the plugin's `not` guards (`SymbolTable`, `MigrationLogTable`, the formatter's alignment columns), and a line suppression.

Suppress a genuinely family-blind site at the line, with a reason:

```ts
Expand Down
18 changes: 14 additions & 4 deletions biome-plugins/no-family-vocabulary.grit
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
// Recognise SQL/Mongo/target vocabulary (`table`, `column`, `postgres`,
// `nativeType`, …) reaching the family-blind `packages/1-framework` domain.
// Outside the CLI package it also flags the name of any Prisma version before
// 8 (`prisma7`, `Prisma 6`, `prisma@5.22.0`, …), because the framework reads
// every PSL document the same way and must not know which product version
// wrote it. The CLI talks to users about their projects, including a Prisma 7
// project `orm init` sets Prisma 8 up beside, so it may name the version.
// `Prisma 8` is never flagged.
//
// The scope is enforced by the `$filename` guard below, not by a biome
// `overrides` entry. Biome 2.5.6 does accept `plugins` inside `overrides` (and
Expand Down Expand Up @@ -44,8 +50,8 @@
// identifier splits on non-alphanumerics and on a lowercase/digit → uppercase
// hump, then each token is compared case-insensitively, allowing a trailing
// plural `s`. Reproducing that means the term's own casing decides which
// boundaries are legal, which is why there are three alternatives instead of
// one case-insensitive pattern:
// boundaries are legal, which is why there are three term alternatives instead
// of one case-insensitive pattern:
//
// 1. lowercase head, lowercase tail (`table`) — a lowercase head cannot open
// a hump, so the prefix must be empty or end in a non-alphanumeric. This
Expand All @@ -59,7 +65,7 @@
// stops `SQL` matching inside `SQLQueryPlan`, which the tokenizer reads as
// the single token `sqlquery`.
//
// Each alternative lists every forbidden term with a literal head character, a
// Each term alternative lists every forbidden term with a literal head character, a
// case-insensitive middle, and a literal tail character. The three multi-word
// terms (`nativeType`, `primaryKey`, `foreignKey`) additionally accept either a
// separator run or a hump between their two words, so `native-type`,
Expand Down Expand Up @@ -101,6 +107,10 @@ or {
not $filename <: r".*\.(?:test|test-d)\.ts",
not $filename <: r".*/test/.*\.ts",
or {
and {
not $filename <: r".*packages/1-framework/3-tooling/cli/.*",
$tok <: r"(?s)(?:(?:|.*[^A-Za-z0-9])p|(?:|.*[^A-Za-z0-9]|.*[a-z0-9])P)(?i:risma)[^A-Za-z0-9]*[1-7](?:|[^A-Za-z0-9].*|[A-Z].*)"
},
$tok <: r"(?s)(?:|.*[^A-Za-z0-9])(?:p(?i:ostgre)s|p(?i:ostgresq)l|s(?i:qlit)e|m(?i:ysq)l|m(?i:ariad)b|m(?i:ssq)l|s(?i:qlserve)r|c(?i:ockroac)h|p(?i:glit)e|m(?i:ong)o|m(?i:ongod)b|c(?i:ollectio)n|c(?i:olum)n|t(?i:abl)e|c(?i:onstrain)t|s(?i:q)l|v(?i:archa)r|j(?i:son)b|d(?i:ialec)t|r(?i:l)s|n(?i:ativ)(?:[eE][^A-Za-z0-9]+[tT]|eT)(?i:yp)e|p(?i:rimar)(?:[yY][^A-Za-z0-9]+[kK]|yK)(?i:e)y|f(?i:oreig)(?:[nN][^A-Za-z0-9]+[kK]|nK)(?i:e)y)[sS]?(?:|[^A-Za-z0-9].*|[A-Z].*)",
$tok <: r"(?s)(?:|.*[^A-Za-z0-9]|.*[a-z0-9])(?:P(?i:ostgre)s|P(?i:ostgresq)l|S(?i:qlit)e|M(?i:ysq)l|M(?i:ariad)b|M(?i:ssq)l|S(?i:qlserve)r|C(?i:ockroac)h|P(?i:glit)e|M(?i:ong)o|M(?i:ongod)b|C(?i:ollectio)n|C(?i:olum)n|T(?i:abl)e|C(?i:onstrain)t|S(?i:q)l|V(?i:archa)r|J(?i:son)b|D(?i:ialec)t|R(?i:l)s|N(?i:ativ)(?:[eE][^A-Za-z0-9]+[tT]|eT)(?i:yp)e|P(?i:rimar)(?:[yY][^A-Za-z0-9]+[kK]|yK)(?i:e)y|F(?i:oreig)(?:[nN][^A-Za-z0-9]+[kK]|nK)(?i:e)y)[sS]?(?:|[^A-Za-z0-9].*|[A-Z].*)",
$tok <: r"(?s)(?:|.*[^A-Za-z0-9]|.*[a-z0-9])(?:P(?i:ostgre)S|P(?i:ostgresq)L|S(?i:qlit)E|M(?i:ysq)L|M(?i:ariad)B|M(?i:ssq)L|S(?i:qlserve)R|C(?i:ockroac)H|P(?i:glit)E|M(?i:ong)O|M(?i:ongod)B|C(?i:ollectio)N|C(?i:olum)N|T(?i:abl)E|C(?i:onstrain)T|S(?i:q)L|V(?i:archa)R|J(?i:son)B|D(?i:ialec)T|R(?i:l)S|N(?i:ativ)(?:[eE][^A-Za-z0-9]+[tT]|eT)(?i:yp)E|P(?i:rimar)(?:[yY][^A-Za-z0-9]+[kK]|yK)(?i:e)Y|F(?i:oreig)(?:[nN][^A-Za-z0-9]+[kK]|nK)(?i:e)Y)[sS]?(?:|[^A-Za-z0-9].*)"
Expand All @@ -110,5 +120,5 @@ or {
not $tok <: r"(?s).*(?i:alignment)[^A-Za-z0-9]*(?i:columns).*",
not $tok <: r"(?s).*(?i:type)[^A-Za-z0-9]*(?i:column).*",
not $tok <: r"(?s).*(?i:attribute)[^A-Za-z0-9]*(?i:column).*",
register_diagnostic(span=$tok, message="no-family-vocabulary: family/target vocabulary in the family-blind framework domain; move the concept into the owning family layer, or suppress with `// biome-ignore lint/plugin/no-family-vocabulary: <why this is family-blind>`", severity="info")
register_diagnostic(span=$tok, message="no-family-vocabulary: family, target or product vocabulary in the family-blind framework domain; move the concept into the layer that owns it, or suppress with `// biome-ignore lint/plugin/no-family-vocabulary: <why this is family-blind>`", severity="info")
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,15 @@ Every lowering rule is checked against SQL that the earlier version's own toolch

```mermaid
flowchart LR
schema["schema.prisma<br/>(Prisma 7)"] --> parser["@internal/psl-parser<br/>grammar: 'prisma7'"]
schema["schema.prisma<br/>(Prisma 7)"] --> parser["@internal/psl-parser<br/>one grammar for every PSL document"]
parser --> interpreter["@internal/sql-contract-prisma7<br/>rules of the Prisma 7 language for the SQL family"]
binding["@internal/target-postgres<br/>prisma7PostgresBinding: what Postgres creates"] --> interpreter
interpreter --> contract["Contract"]
facade["@prisma/orm-postgres/config<br/>prisma7Schema(path)"] -. wires .-> interpreter
facade -. wires .-> binding
```

**The parser is shared, and the earlier grammar is opt-in.** `@internal/psl-parser` reads both languages. The two additions the earlier language needs, attributes on enum members and field lines inside a `view` block, are read only under the `grammar: 'prisma7'` parse option. The default grammar is unchanged, so a Prisma 8 schema keeps rejecting exactly what it rejected before, and nothing here reaches a user who never adopts the reader.
**The parser is shared, and it has one grammar.** `@internal/psl-parser` parses every PSL document the same way, whichever Prisma version wrote it. The two constructs the earlier language needs, attributes on enum members and field lines inside a `view` block, parse in every document. Each reader decides what it accepts. The Prisma 8 readers report an attribute on an enum member, and report a `view` block like any other block whose keyword no composed pack claims, so a Prisma 8 PSL contract that uses either fails. The Prisma 7 reader reads enum member attributes and refuses a `view` with the error shown above.

**Rules of the language live in the family authoring package.** `@internal/sql-contract-prisma7` holds everything that is true of the Prisma 7 language for the SQL family: blocks and attributes, relation pairing, junction tables, defaults, and the diagnostics. It knows nothing about a particular database and depends on no Prisma 7 package.

Expand Down
Loading
Loading