TML-2727: move SQL/Mongo canonicalizer hooks to family packs#631
Conversation
The granular S1.D ticket (TML-2625) was canceled and archived in the 2026-05-20 ticket cleanup; recreated as TML-2727 (the single live S1.D ticket). Also: the subsumed cleanup tickets (TML-2579/2580/2582/2545/ 2563) are already Canceled, so PDoD10 needs no close-out dispatch \u2014 mark it satisfied in the coverage map. Signed-off-by: Will Madden <madden@prisma.io>
…efer structural items A 2026-05-29 inventory falsified S1.D as one deletions-only slice: three of the eight subsumed surfaces carry structural prerequisites (a contract.json- shape coordinate change, a hash-computation change, a query-builder type rewrite). Per the narrow-and-defer decision, S1.D ships the three clean deletes now as independent parallel slices (construction-discipline shims, canonicalizer family hook, migration -> elementCoordinates) and defers the three structural items to follow-ups recorded in deferred.md. Amends PDoD5/PDoD10 to scope the deferred items out; restructures the plan composition into parallel groups (no stack); adds the three lean slice specs. Signed-off-by: Will Madden <madden@prisma.io>
Move preserve-empty and storage-sort rules out of the framework canonicalizer into family-contributed hooks on CanonicalizeContractOptions, wired through ContractSerializer and the emit path. Byte-identical canonical output is preserved; computeStorageHash accepts the same hooks. Signed-off-by: Will Madden <madden@prisma.io>
|
Warning Review limit reached
More reviews will be available in 39 minutes and 8 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds path-pattern and storage-sorting utilities, exposes PreserveEmptyPredicate and StorageSort hooks, integrates those hooks into canonicalization and hashing, provides SQL/Mongo family hook implementations, wires hooks through serializer/emit/migration flows, and updates tests and exports accordingly. ChangesCanonicalization hooks & utilities
Exports, hashing, and factories
Serializer, emitter, migration wiring
Family-specific hooks and integration
Tests
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Avoid bundling @prisma-next/sql-contract into dist/test/utils.mjs; test helpers mirror production hook behaviour without a cross-layer import. Signed-off-by: Will Madden <madden@prisma.io>
…lizer-hook Signed-off-by: Will Madden <madden@prisma.io> # Conflicts: # test/integration/test/authoring/side-by-side-contracts.test.ts
@prisma-next/extension-author-tools
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/extension-arktype-json
@prisma-next/extension-cipherstash
@prisma-next/middleware-cache
@prisma-next/mongo
@prisma-next/extension-paradedb
@prisma-next/extension-pgvector
@prisma-next/extension-postgis
@prisma-next/postgres
@prisma-next/sql-orm-client
@prisma-next/sqlite
@prisma-next/target-mongo
@prisma-next/adapter-mongo
@prisma-next/driver-mongo
@prisma-next/contract
@prisma-next/utils
@prisma-next/config
@prisma-next/errors
@prisma-next/framework-components
@prisma-next/operations
@prisma-next/ts-render
@prisma-next/contract-authoring
@prisma-next/ids
@prisma-next/psl-parser
@prisma-next/psl-printer
@prisma-next/cli
@prisma-next/cli-telemetry
@prisma-next/emitter
@prisma-next/migration-tools
prisma-next
@prisma-next/vite-plugin-contract-emit
@prisma-next/mongo-codec
@prisma-next/mongo-contract
@prisma-next/mongo-value
@prisma-next/mongo-contract-psl
@prisma-next/mongo-contract-ts
@prisma-next/mongo-emitter
@prisma-next/mongo-schema-ir
@prisma-next/mongo-query-ast
@prisma-next/mongo-orm
@prisma-next/mongo-query-builder
@prisma-next/mongo-lowering
@prisma-next/mongo-wire
@prisma-next/sql-contract
@prisma-next/sql-errors
@prisma-next/sql-operations
@prisma-next/sql-schema-ir
@prisma-next/sql-contract-psl
@prisma-next/sql-contract-ts
@prisma-next/sql-contract-emitter
@prisma-next/sql-lane-query-builder
@prisma-next/sql-relational-core
@prisma-next/sql-builder
@prisma-next/target-postgres
@prisma-next/target-sqlite
@prisma-next/adapter-postgres
@prisma-next/adapter-sqlite
@prisma-next/driver-postgres
@prisma-next/driver-sqlite
commit: |
size-limit report 📦
|
…it paths Two test-helper gaps surfaced after moving preserve-empty/sort rules to family hooks: - assert-descriptor-self-consistency: the kind-stripping case computed its headRef hash with the SQL preserve-empty hook but recomputed without it, causing a spurious mismatch. Pass the hook on both sides. - psl.pgvector-dbinit: the inline emit dropped empty uniques/indexes/ foreignKeys arrays (the SQL contract validator requires them), so dbInit failed structural validation. Thread sqlContractCanonicalizationHooks through the emit, matching the side-by-side fixture test and production emit (which gets them from descriptor.contractSerializer). Signed-off-by: Will Madden <madden@prisma.io>
Records the readability/de-duplication refactor (framework-level path matcher + storage-sort helper, cast removal, kill the drifted test copy of the SQL hook) as round-2 actions, with refusal triggers guarding the foundation-layer no-family-path-knowledge invariant and byte stability. Signed-off-by: Will Madden <madden@prisma.io>
Move preserve-empty path matching and storage-array sorting into generic @prisma-next/contract/hashing-utils helpers so SQL and Mongo hooks compose declarative pattern data instead of imperative walkers. Emitter tests use the shared mechanism (lint:deps forbids importing sql-contract). Signed-off-by: wmadden-electric <286902546+wmadden-electric@users.noreply.github.com>
Explicit PreserveEmptyPredicate / StorageSort annotations avoid TS2742 when tsdown generates declarations for family hook re-exports. Signed-off-by: wmadden-electric <286902546+wmadden-electric@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (5)
packages/1-framework/3-tooling/emitter/test/hashing.test.ts (1)
11-18: ⚡ Quick winConsider using the declarative builder pattern for consistency.
Similar to the contract/test/hashing.test.ts file, this could use
createPreserveEmptyPredicatefrom@prisma-next/contract/hashing-utils:import { createPreserveEmptyPredicate } from '`@prisma-next/contract/hashing-utils`'; const sqlPreserveEmptyPatterns = [ ['storage', 'namespaces', '*', 'tables'], ] as const; const SQL_HOOKS = { shouldPreserveEmpty: createPreserveEmptyPredicate(sqlPreserveEmptyPatterns) };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/1-framework/3-tooling/emitter/test/hashing.test.ts` around lines 11 - 18, Replace the manual predicate function sqlPreserveEmpty with the declarative builder: import createPreserveEmptyPredicate from `@prisma-next/contract/hashing-utils`, define sqlPreserveEmptyPatterns = [['storage','namespaces','*','tables']] as const, then set SQL_HOOKS.shouldPreserveEmpty = createPreserveEmptyPredicate(sqlPreserveEmptyPatterns); ensure you remove the old sqlPreserveEmpty function and add the import for createPreserveEmptyPredicate.packages/1-framework/3-tooling/emitter/test/canonicalization.test.ts (1)
441-446: 💤 Low valueConsider using
pathefor consistency.While
node:pathworks here, the repo preferspathefor cross-platform path operations. Since this test constructs file paths, usingpathewould be more consistent:const { readFile } = await import('node:fs/promises'); const { fileURLToPath } = await import('node:url'); const { dirname, join } = await import('pathe');🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/1-framework/3-tooling/emitter/test/canonicalization.test.ts` around lines 441 - 446, Replace the use of node:path with pathe for cross-platform consistency: keep fileURLToPath from 'node:url' and readFile from 'node:fs/promises', but import dirname and join from 'pathe' (the code around the sourcePath constant that uses dirname(fileURLToPath(import.meta.url)) and join should remain the same); update the import statement that currently imports dirname and join from 'node:path' to import them from 'pathe' instead.packages/1-framework/3-tooling/emitter/test/utils.ts (1)
15-89: ⚡ Quick winReplace manual hook implementations with declarative builders.
The manual
sqlPreserveEmptyandsqlSortStorageimplementations could use the builder utilities from@prisma-next/contract/hashing-utilsfor better maintainability and consistency withcanonicalization.test.ts:♻️ Refactor using builders
+import { + createPreserveEmptyPredicate, + createStorageSort, + type NamedArraySortTarget, + type PathPattern, +} from '`@prisma-next/contract/hashing-utils`'; -function sqlPreserveEmpty(path: readonly string[]): boolean { - const len = path.length; - if (len < 2 || path[0] !== 'storage') return false; - if (path[1] === 'namespaces') { - if (len === 4 && path[3] === 'tables') return true; - if (len === 5 && path[3] === 'tables') return true; - if ( - len === 6 && - path[3] === 'tables' && - (path[5] === 'uniques' || path[5] === 'indexes' || path[5] === 'foreignKeys') - ) - return true; - if ( - len === 7 && - path[3] === 'tables' && - path[5] === 'foreignKeys' && - (path[6] === 'constraint' || path[6] === 'index') - ) - return true; - } - if (path[1] === 'types' && len === 4 && path[3] === 'typeParams') return true; - return false; -} +const sqlPreserveEmptyPatterns = [ + ['storage', 'namespaces', '*', 'tables'], + ['storage', 'namespaces', '*', 'tables', '*'], + ['storage', 'namespaces', '*', 'tables', '*', ['uniques', 'indexes', 'foreignKeys']], + ['storage', 'namespaces', '*', 'tables', '*', 'foreignKeys', ['constraint', 'index']], + ['storage', 'types', '*', 'typeParams'], +] as const satisfies readonly PathPattern[]; -const sqlSortStorage: StorageSort = (storage) => { - if (!storage || typeof storage !== 'object' || Array.isArray(storage)) return storage; - const s = storage as Record<string, unknown>; - const namespaces = s['namespaces']; - if (!namespaces || typeof namespaces !== 'object' || Array.isArray(namespaces)) return storage; - const ns = namespaces as Record<string, unknown>; - const sortedNs: Record<string, unknown> = {}; - for (const nsId of Object.keys(ns)) { - const nsEntry = ns[nsId]; - if (!nsEntry || typeof nsEntry !== 'object' || Array.isArray(nsEntry)) { - sortedNs[nsId] = nsEntry; - continue; - } - const tables = (nsEntry as Record<string, unknown>)['tables']; - if (!tables || typeof tables !== 'object' || Array.isArray(tables)) { - sortedNs[nsId] = nsEntry; - continue; - } - const sortedTables: Record<string, unknown> = {}; - for (const tname of Object.keys(tables as Record<string, unknown>)) { - const t = (tables as Record<string, unknown>)[tname]; - if (!t || typeof t !== 'object' || Array.isArray(t)) { - sortedTables[tname] = t; - continue; - } - const tableObj = t as Record<string, unknown>; - const sorted: Record<string, unknown> = { ...tableObj }; - const byName = (a: unknown, b: unknown): number => { - const na = - a && typeof a === 'object' && 'name' in a && typeof a.name === 'string' ? a.name : ''; - const nb = - b && typeof b === 'object' && 'name' in b && typeof b.name === 'string' ? b.name : ''; - return na.localeCompare(nb); - }; - if (Array.isArray(tableObj['indexes'])) { - sorted['indexes'] = [...tableObj['indexes']].sort(byName); - } - if (Array.isArray(tableObj['uniques'])) { - sorted['uniques'] = [...tableObj['uniques']].sort(byName); - } - sortedTables[tname] = sorted; - } - sortedNs[nsId] = { ...(nsEntry as Record<string, unknown>), tables: sortedTables }; - } - return { ...s, namespaces: sortedNs }; -}; +const sqlSortTargets = [ + { path: ['namespaces', '*', 'tables', '*'], arrayKeys: ['indexes', 'uniques'] }, +] as const satisfies readonly NamedArraySortTarget[]; const SQL_EMIT_HOOKS = { - shouldPreserveEmpty: sqlPreserveEmpty satisfies PreserveEmptyPredicate, - sortStorage: sqlSortStorage, + shouldPreserveEmpty: createPreserveEmptyPredicate(sqlPreserveEmptyPatterns), + sortStorage: createStorageSort(sqlSortTargets), } satisfies Pick<CanonicalizeContractOptions, 'shouldPreserveEmpty' | 'sortStorage'>;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/1-framework/3-tooling/emitter/test/utils.ts` around lines 15 - 89, The current file contains manual implementations sqlPreserveEmpty and sqlSortStorage and then composes SQL_EMIT_HOOKS; replace these with the declarative builder utilities from `@prisma-next/contract/hashing-utils` (as used in canonicalization.test.ts) to improve maintainability: remove the procedural logic inside sqlPreserveEmpty and sqlSortStorage and instead construct equivalent predicates and sorters via the provided builder functions, then export SQL_EMIT_HOOKS using those builders (ensuring types still satisfy PreserveEmptyPredicate and CanonicalizeContractOptions keys). Locate sqlPreserveEmpty, sqlSortStorage, and SQL_EMIT_HOOKS in the diff and swap their manual logic for the builder-based declarations, preserving behavior for storage.namespaces.*.tables, indexes, uniques, foreignKeys, and types.*.typeParams.packages/1-framework/3-tooling/migration/test/assert-descriptor-self-consistency.test.ts (1)
24-40: ⚡ Quick winConsider using the declarative builder pattern for maintainability.
The
sqlPreserveEmptypredicate could usecreatePreserveEmptyPredicatefrom@prisma-next/contract/hashing-utilsto make the path patterns more explicit and reduce duplication across test files:import { createPreserveEmptyPredicate } from '`@prisma-next/contract/hashing-utils`'; const sqlPreserveEmptyPatterns = [ ['storage', 'namespaces', '*', 'tables'], ['storage', 'namespaces', '*', 'tables', '*'], ['storage', 'namespaces', '*', 'tables', '*', ['uniques', 'indexes', 'foreignKeys']], ] as const; const SQL_HOOKS = { shouldPreserveEmpty: createPreserveEmptyPredicate(sqlPreserveEmptyPatterns) };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/1-framework/3-tooling/migration/test/assert-descriptor-self-consistency.test.ts` around lines 24 - 40, The current sqlPreserveEmpty predicate is implemented imperatively and duplicates path pattern logic; replace it by using createPreserveEmptyPredicate from `@prisma-next/contract/hashing-utils`: define an array sqlPreserveEmptyPatterns that lists the three patterns (['storage','namespaces','*','tables'], ['storage','namespaces','*','tables','*'], ['storage','namespaces','*','tables','*', ['uniques','indexes','foreignKeys']]) and then set SQL_HOOKS.shouldPreserveEmpty to createPreserveEmptyPredicate(sqlPreserveEmptyPatterns); keep the SQL_HOOKS symbol and remove the handwritten sqlPreserveEmpty function.packages/1-framework/0-foundation/contract/test/hashing.test.ts (1)
6-18: ⚡ Quick winConsider using the declarative builder pattern for maintainability.
The manual
sqlPreserveEmptyimplementation could usecreatePreserveEmptyPredicatefrom@prisma-next/contract/hashing-utils(same package). This would match the pattern incanonicalization.test.tsand make the preservation rules more declarative:import { createPreserveEmptyPredicate } from '`@prisma-next/contract/hashing-utils`'; const sqlPreserveEmptyPatterns = [ ['storage', 'namespaces', '*', 'tables'], ['storage', 'namespaces', '*', 'tables', '*'], ] as const; const sqlPreserveEmpty = createPreserveEmptyPredicate(sqlPreserveEmptyPatterns);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/1-framework/0-foundation/contract/test/hashing.test.ts` around lines 6 - 18, Replace the handwritten sqlPreserveEmpty predicate with a declarative builder: define a const sqlPreserveEmptyPatterns array containing the patterns ['storage','namespaces','*','tables'] and ['storage','namespaces','*','tables','*'], import createPreserveEmptyPredicate from '`@prisma-next/contract/hashing-utils`', then create sqlPreserveEmpty by calling createPreserveEmptyPredicate(sqlPreserveEmptyPatterns) and keep sqlSortStorage and SQL_HOOKS unchanged (ensure types PreserveEmptyPredicate and StorageSort still match).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/1-framework/0-foundation/contract/src/canonicalization-storage-sort.ts`:
- Around line 14-18: compareByNameProperty relies on localeCompare which is
host/ICU dependent; replace it with a deterministic code‑unit comparison: keep
extracting nameA/nameB as currently done in compareByNameProperty, then if nameA
=== nameB return 0, otherwise return nameA < nameB ? -1 : 1 so ordering is
purely based on Unicode code units and stable across hosts (refer to function
name compareByNameProperty for where to change).
In `@packages/1-framework/0-foundation/contract/test/canonicalization.test.ts`:
- Around line 739-743: The forbidden-token checks currently match hardcoded
quoted strings like "'tables'", "'indexes'", "'uniques'", "'foreignKeys'", and
'sortIndexesAndUniques' which misses the same tokens if double quotes or
template literals are used; update the check to be quote-agnostic by normalizing
tokens before comparison (e.g., strip surrounding single/double/backtick quotes)
or use a regex that accepts ['"`] around the literal (for example matching
/['"`]tables['"`]/), and apply the same change to the other occurrences
referenced (the entries for "tables", "indexes", "uniques", "foreignKeys", and
"sortIndexesAndUniques" and the similar checks at the other location).
In
`@packages/1-framework/3-tooling/cli/src/control-api/operations/contract-emit.ts`:
- Around line 259-267: The code uses redundant optional chaining on the required
ControlTargetDescriptor.contractSerializer: locate the emit call where
serializeContract is defined and the spreads using
ifDefined('shouldPreserveEmpty', contractSerializer?.shouldPreserveEmpty) and
ifDefined('sortStorage', contractSerializer?.sortStorage), and replace the
optional chains with direct property accesses
(contractSerializer.shouldPreserveEmpty and contractSerializer.sortStorage) so
the spreads pass the actual optional fields without the unnecessary `?.`.
---
Nitpick comments:
In `@packages/1-framework/0-foundation/contract/test/hashing.test.ts`:
- Around line 6-18: Replace the handwritten sqlPreserveEmpty predicate with a
declarative builder: define a const sqlPreserveEmptyPatterns array containing
the patterns ['storage','namespaces','*','tables'] and
['storage','namespaces','*','tables','*'], import createPreserveEmptyPredicate
from '`@prisma-next/contract/hashing-utils`', then create sqlPreserveEmpty by
calling createPreserveEmptyPredicate(sqlPreserveEmptyPatterns) and keep
sqlSortStorage and SQL_HOOKS unchanged (ensure types PreserveEmptyPredicate and
StorageSort still match).
In `@packages/1-framework/3-tooling/emitter/test/canonicalization.test.ts`:
- Around line 441-446: Replace the use of node:path with pathe for
cross-platform consistency: keep fileURLToPath from 'node:url' and readFile from
'node:fs/promises', but import dirname and join from 'pathe' (the code around
the sourcePath constant that uses dirname(fileURLToPath(import.meta.url)) and
join should remain the same); update the import statement that currently imports
dirname and join from 'node:path' to import them from 'pathe' instead.
In `@packages/1-framework/3-tooling/emitter/test/hashing.test.ts`:
- Around line 11-18: Replace the manual predicate function sqlPreserveEmpty with
the declarative builder: import createPreserveEmptyPredicate from
`@prisma-next/contract/hashing-utils`, define sqlPreserveEmptyPatterns =
[['storage','namespaces','*','tables']] as const, then set
SQL_HOOKS.shouldPreserveEmpty =
createPreserveEmptyPredicate(sqlPreserveEmptyPatterns); ensure you remove the
old sqlPreserveEmpty function and add the import for
createPreserveEmptyPredicate.
In `@packages/1-framework/3-tooling/emitter/test/utils.ts`:
- Around line 15-89: The current file contains manual implementations
sqlPreserveEmpty and sqlSortStorage and then composes SQL_EMIT_HOOKS; replace
these with the declarative builder utilities from
`@prisma-next/contract/hashing-utils` (as used in canonicalization.test.ts) to
improve maintainability: remove the procedural logic inside sqlPreserveEmpty and
sqlSortStorage and instead construct equivalent predicates and sorters via the
provided builder functions, then export SQL_EMIT_HOOKS using those builders
(ensuring types still satisfy PreserveEmptyPredicate and
CanonicalizeContractOptions keys). Locate sqlPreserveEmpty, sqlSortStorage, and
SQL_EMIT_HOOKS in the diff and swap their manual logic for the builder-based
declarations, preserving behavior for storage.namespaces.*.tables, indexes,
uniques, foreignKeys, and types.*.typeParams.
In
`@packages/1-framework/3-tooling/migration/test/assert-descriptor-self-consistency.test.ts`:
- Around line 24-40: The current sqlPreserveEmpty predicate is implemented
imperatively and duplicates path pattern logic; replace it by using
createPreserveEmptyPredicate from `@prisma-next/contract/hashing-utils`: define an
array sqlPreserveEmptyPatterns that lists the three patterns
(['storage','namespaces','*','tables'],
['storage','namespaces','*','tables','*'],
['storage','namespaces','*','tables','*', ['uniques','indexes','foreignKeys']])
and then set SQL_HOOKS.shouldPreserveEmpty to
createPreserveEmptyPredicate(sqlPreserveEmptyPatterns); keep the SQL_HOOKS
symbol and remove the handwritten sqlPreserveEmpty function.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: dbea8c9f-8110-48f2-b93e-8941da78e5a3
⛔ Files ignored due to path filters (6)
projects/contract-ir-planes/deferred.mdis excluded by!projects/**projects/contract-ir-planes/plan.mdis excluded by!projects/**projects/contract-ir-planes/slices/canonicalizer-family-hook/spec.mdis excluded by!projects/**projects/contract-ir-planes/slices/construction-discipline-shims/spec.mdis excluded by!projects/**projects/contract-ir-planes/slices/migration-element-coordinates/spec.mdis excluded by!projects/**projects/contract-ir-planes/spec.mdis excluded by!projects/**
📒 Files selected for processing (50)
packages/1-framework/0-foundation/contract/package.jsonpackages/1-framework/0-foundation/contract/src/canonicalization-path-match.tspackages/1-framework/0-foundation/contract/src/canonicalization-storage-sort.tspackages/1-framework/0-foundation/contract/src/canonicalization.tspackages/1-framework/0-foundation/contract/src/exports/hashing-utils.tspackages/1-framework/0-foundation/contract/src/exports/hashing.tspackages/1-framework/0-foundation/contract/src/hashing.tspackages/1-framework/0-foundation/contract/src/testing-factories.tspackages/1-framework/0-foundation/contract/test/canonicalization-path-match.test.tspackages/1-framework/0-foundation/contract/test/canonicalization-storage-sort.test.tspackages/1-framework/0-foundation/contract/test/canonicalization.test.tspackages/1-framework/0-foundation/contract/test/hashing.test.tspackages/1-framework/0-foundation/contract/tsdown.config.tspackages/1-framework/1-core/framework-components/src/control/contract-serializer.tspackages/1-framework/3-tooling/cli/src/control-api/operations/contract-emit.tspackages/1-framework/3-tooling/emitter/src/emit-types.tspackages/1-framework/3-tooling/emitter/src/emit.tspackages/1-framework/3-tooling/emitter/test/canonicalization.test.tspackages/1-framework/3-tooling/emitter/test/hashing.test.tspackages/1-framework/3-tooling/emitter/test/utils.tspackages/1-framework/3-tooling/migration/src/assert-descriptor-self-consistency.tspackages/1-framework/3-tooling/migration/test/assert-descriptor-self-consistency.test.tspackages/2-mongo-family/1-foundation/mongo-contract/package.jsonpackages/2-mongo-family/1-foundation/mongo-contract/src/canonicalization-hooks.tspackages/2-mongo-family/1-foundation/mongo-contract/src/exports/canonicalization-hooks.tspackages/2-mongo-family/1-foundation/mongo-contract/tsdown.config.tspackages/2-mongo-family/2-authoring/contract-psl/src/interpreter.tspackages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.tspackages/2-mongo-family/9-family/src/core/control-instance.tspackages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.tspackages/2-mongo-family/9-family/test/control-instance.descriptor-self-consistency.test.tspackages/2-sql/1-core/contract/package.jsonpackages/2-sql/1-core/contract/src/canonicalization-hooks.tspackages/2-sql/1-core/contract/src/exports/canonicalization-hooks.tspackages/2-sql/1-core/contract/tsdown.config.tspackages/2-sql/2-authoring/contract-ts/src/build-contract.tspackages/2-sql/9-family/src/core/control-instance.tspackages/2-sql/9-family/src/core/ir/sql-contract-serializer-base.tspackages/2-sql/9-family/test/control-instance.descriptor-self-consistency.test.tspackages/3-extensions/cipherstash/test/descriptor.test.tspackages/3-extensions/pgvector/test/descriptor.test.tstest/integration/test/authoring/cli.emit-parity-fixtures.test.tstest/integration/test/authoring/psl.pgvector-dbinit.test.tstest/integration/test/authoring/side-by-side-contracts.test.tstest/integration/test/cli.emit-contract.test.tstest/integration/test/contract-space-fixture-mongo/contract.tstest/integration/test/contract-space-fixture/contract.tstest/integration/test/control-api.test.tstest/integration/test/family.verify-database.basic.test.tstest/integration/test/family.verify-database.errors.test.ts
The guard only searched single-quoted path literals, so a hardcoded 'tables'/'indexes'/'uniques'/'foreignKeys' in double quotes or a template string would slip past. Match the literal regardless of quote style after stripping comments (doc-comment prose like markdown `indexes` is not a code literal). Helper-identifier checks keep their plain substring assertions. Signed-off-by: wmadden-electric <286902546+wmadden-electric@users.noreply.github.com>
…izer contractSerializer is a required field on ControlTargetDescriptor and the serializeContract closure already dereferences it non-optionally, so contractSerializer?.shouldPreserveEmpty / ?.sortStorage was redundant defensive chaining. Read the (still-optional) hook fields directly. Complete the two partial target mocks that omitted the type-required serializer. Signed-off-by: wmadden-electric <286902546+wmadden-electric@users.noreply.github.com>
Reconcile S1.D-1 (#630, now on main) with S1.D-2 (#631) hook work: - sql-contract-serializer-base.ts / build-contract.ts: keep #630's materialised-namespace naming + unbound injection, layer #631's canonicalization hook threading (shouldPreserveEmpty/sortStorage) on top. - descriptor-self-consistency test + contract-space fixture: compose the shared hooks over #630's namespace bodies. - pnpm install relinked @prisma-next/utils into sql-contract (dep added by #630). Generated fixtures regenerated via scripts; fixtures:check zero-diff. Signed-off-by: wmadden-electric <286902546+wmadden-electric@users.noreply.github.com>
biome organize-imports on the merged serializer-base import block. Signed-off-by: wmadden-electric <286902546+wmadden-electric@users.noreply.github.com>
Apply the shared hashing-utils path-matcher/sort to two more test-helper sites that still hand-roll the SQL preserve-empty/sort logic. Signed-off-by: Will Madden <madden@prisma.io>
…rs (R5-R6) Replace the remaining hand-rolled sqlPreserveEmpty / sqlSortStorage copies in the emitter test utils and the migration descriptor-self-consistency test with createPreserveEmptyPredicate / createStorageSort from @prisma-next/contract/hashing-utils, composed from local SQL pattern data. Each file's pattern set mirrors its prior predicate exactly, so recomputed hashes are byte-identical (fixtures:check zero-diff). Comparator unchanged (localeCompare; deferred to TML-2732). Signed-off-by: wmadden-electric <286902546+wmadden-electric@users.noreply.github.com>
…istory Replace the three synthetic normal-shape golden cases with cases drawn from real merged PRs, so the corpus measures Drive runs against work the team actually shipped rather than synthesised tasks: - direct-change-example-emit-outputpath (TML-2722 / #618) - slice-dedupe-generated-imports (TML-2714 / #614) - project-reap-subsumed-ir-surfaces (TML-2727 / #630, #631, #629) — a three-slice parallel fan-out that exercises planner parallelisation and scope discipline. Each real case carries the task as posed (Linear ticket, solution-scrubbed so the run still does the design/planning), a base_sha to run against, and a reference.md describing the known-good output by commit SHA (the output itself is fetchable via git diff <base_sha> <merge_sha>). case.json gains source + base_sha; the loader ignores the extra fields until the experiment-engine slice wires base_sha into a checkout. The two pathological cases (i12-halt, spike-first) stay synthetic: no clean merged PR exhibits a halted or spiked run. Update harness tests, SKILL.md examples, and the corpus README for the renamed slugs. validate-parser fixtures are left as-is — they are synthetic parser fixtures with tuned event counts, not corpus members. Signed-off-by: wmadden-electric <286902546+wmadden-electric@users.noreply.github.com>
Linked issue
Refs TML-2727 — closes TML-2579.
Spec:
projects/contract-ir-planes/slices/canonicalizer-family-hook/spec.mdParallel to S1.D-1/-3 + S1.E — disjoint files.
At a glance
Family-specific canonicalization rules now live in family packs and are threaded into the framework canonicalizer as optional hooks:
Before this change, the framework canonicalizer hardcoded SQL-shaped
storage.namespaces.*.tables.*path knowledge inline.What changed
canonicalization.ts): removed inline SQL/Mongo storage path guards andsortIndexesAndUniques; added optionalshouldPreserveEmptyandsortStorageonCanonicalizeContractOptions.computeStorageHashaccepts the same hooks so hash computation stays aligned with emit.sqlContractCanonicalizationHooksandmongoContractCanonicalizationHooksexported from@prisma-next/sql-contract/canonicalization-hooksand@prisma-next/mongo-contract/canonicalization-hooks; wired throughContractSerializer,SqlContractSerializerBase,MongoContractSerializerBase, contract-ts builders, andcontract-emit.tables/indexes/uniques/foreignKeysguards remain in the canonicalizer; SQL/Mongo asymmetry (empty collections vs tables) preserved via family hooks.Why
Completes PDoD6: the last SQL-specific framework canonicalizer path becomes a family contribution. The framework keeps only family-agnostic rules (top-level order,
_generatedstripping,noActionomission, key sort). Families pass hooks in — no framework→family import cycle.Byte-stability evidence
Testing performed
pnpm --filter @prisma-next/contract testpnpm --filter @prisma-next/emitter test test/canonicalization.test.tspnpm --filter @prisma-next/family-sql --filter @prisma-next/family-mongo testpnpm fixtures:checkpnpm lint:depspnpm typecheckChecklist
fixtures:checkzero diff)Summary by CodeRabbit
New Features
Refactor
Tests
Chores