@prisma-next/family-sql 0.14.0-dev.49 → 0.14.0-dev.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/{control-adapter-DHYFuOBy.d.mts → control-adapter-BVOj4gXI.d.mts} +34 -5
  2. package/dist/control-adapter-BVOj4gXI.d.mts.map +1 -0
  3. package/dist/control-adapter.d.mts +1 -1
  4. package/dist/control.d.mts +65 -50
  5. package/dist/control.d.mts.map +1 -1
  6. package/dist/control.mjs +308 -56
  7. package/dist/control.mjs.map +1 -1
  8. package/dist/diff.d.mts +141 -30
  9. package/dist/diff.d.mts.map +1 -1
  10. package/dist/diff.mjs +155 -1384
  11. package/dist/diff.mjs.map +1 -1
  12. package/dist/ir.d.mts +5 -5
  13. package/dist/ir.d.mts.map +1 -1
  14. package/dist/ir.mjs +2 -2
  15. package/dist/ir.mjs.map +1 -1
  16. package/dist/migration.d.mts +1 -1
  17. package/dist/schema-differ-DnoopSXm.d.mts +45 -0
  18. package/dist/schema-differ-DnoopSXm.d.mts.map +1 -0
  19. package/dist/schema-verify-W3r631Jh.mjs +226 -0
  20. package/dist/schema-verify-W3r631Jh.mjs.map +1 -0
  21. package/dist/{sql-contract-serializer-DwUSJ4PO.mjs → sql-contract-serializer-C75cfMSS.mjs} +2 -2
  22. package/dist/{sql-contract-serializer-DwUSJ4PO.mjs.map → sql-contract-serializer-C75cfMSS.mjs.map} +1 -1
  23. package/dist/{types-DBxrwN1d.d.mts → types-BW7pzb2j.d.mts} +24 -15
  24. package/dist/types-BW7pzb2j.d.mts.map +1 -0
  25. package/package.json +21 -21
  26. package/src/core/control-instance.ts +99 -80
  27. package/src/core/control-target-descriptor.ts +29 -14
  28. package/src/core/diff/diff-tree-normalization.ts +211 -0
  29. package/src/core/diff/schema-verify.ts +324 -0
  30. package/src/core/diff/sql-schema-diff.ts +40 -1466
  31. package/src/core/diff/verifier-disposition.ts +14 -42
  32. package/src/core/ir/sql-schema-verifier-base.ts +5 -5
  33. package/src/core/migrations/contract-to-schema-ir.ts +63 -20
  34. package/src/core/migrations/native-type-expander.ts +28 -0
  35. package/src/core/migrations/schema-differ.ts +30 -29
  36. package/src/core/migrations/types.ts +23 -14
  37. package/src/exports/control.ts +6 -0
  38. package/src/exports/diff.ts +28 -8
  39. package/dist/contract-to-schema-ir-S-evq8E6.mjs +0 -264
  40. package/dist/contract-to-schema-ir-S-evq8E6.mjs.map +0 -1
  41. package/dist/control-adapter-DHYFuOBy.d.mts.map +0 -1
  42. package/dist/sql-schema-diff-6z36dZt6.d.mts +0 -105
  43. package/dist/sql-schema-diff-6z36dZt6.d.mts.map +0 -1
  44. package/dist/types-DBxrwN1d.d.mts.map +0 -1
  45. package/src/core/diff/control-verify-emit.ts +0 -46
  46. package/src/core/diff/verify-helpers.ts +0 -832
@@ -1,58 +1,30 @@
1
1
  import type { ControlPolicy } from '@prisma-next/contract/types';
2
2
  import type {
3
- SchemaIssue,
3
+ SchemaDiffIssue,
4
4
  VerifierIssueCategory,
5
5
  VerifierOutcome,
6
6
  } from '@prisma-next/framework-components/control';
7
7
  import { dispositionForCategory } from '@prisma-next/framework-components/control';
8
8
 
9
9
  /**
10
- * Classifies the relational verifier issue kinds the SQL family emits (tables,
11
- * columns, constraints, indexes, defaults, enum types) into the target-neutral
12
- * categories the framework grades. The relational vocabulary lives here, in the
13
- * SQL domain the framework never switches over `extra_foreign_key` and friends.
10
+ * Classifies a codec `verifyType` hook finding into the target-neutral
11
+ * categories the framework grades. A storage type is a named type instance
12
+ * (e.g. a native enum); the only shape divergence it can carry is a change
13
+ * to its value set, so a paired mismatch always classifies as `valueDrift`.
14
14
  */
15
- export function classifySqlVerifierIssueKind(kind: SchemaIssue['kind']): VerifierIssueCategory {
16
- switch (kind) {
17
- case 'extra_column':
18
- return 'extraNestedElement';
19
- case 'extra_primary_key':
20
- case 'extra_foreign_key':
21
- case 'extra_unique_constraint':
22
- case 'extra_index':
23
- case 'extra_validator':
24
- case 'extra_default':
25
- return 'extraAuxiliary';
26
- case 'extra_table':
27
- return 'extraTopLevelObject';
28
- case 'missing_schema':
29
- case 'missing_table':
30
- case 'missing_column':
31
- case 'type_missing':
32
- case 'default_missing':
33
- return 'declaredMissing';
34
- case 'type_values_mismatch':
35
- case 'enum_values_changed':
36
- case 'check_mismatch':
37
- return 'valueDrift';
38
- case 'type_mismatch':
39
- case 'nullability_mismatch':
40
- case 'primary_key_mismatch':
41
- case 'foreign_key_mismatch':
42
- case 'unique_constraint_mismatch':
43
- case 'index_mismatch':
44
- case 'default_mismatch':
45
- return 'declaredIncompatible';
46
- case 'check_missing':
47
- return 'declaredMissing';
48
- case 'check_removed':
49
- return 'extraAuxiliary';
15
+ export function classifyStorageTypeDiffIssue(issue: SchemaDiffIssue): VerifierIssueCategory {
16
+ if (issue.reason === 'not-found') {
17
+ return 'declaredMissing';
50
18
  }
19
+ if (issue.reason === 'not-expected') {
20
+ return 'extraAuxiliary';
21
+ }
22
+ return 'valueDrift';
51
23
  }
52
24
 
53
25
  export function verifierDisposition(
54
26
  controlPolicy: ControlPolicy,
55
- issueKind: SchemaIssue['kind'],
27
+ issue: SchemaDiffIssue,
56
28
  ): VerifierOutcome {
57
- return dispositionForCategory(controlPolicy, classifySqlVerifierIssueKind(issueKind));
29
+ return dispositionForCategory(controlPolicy, classifyStorageTypeDiffIssue(issue));
58
30
  }
@@ -1,5 +1,5 @@
1
1
  import type {
2
- SchemaIssue,
2
+ SchemaDiffIssue,
3
3
  SchemaVerifier,
4
4
  SchemaVerifyOptions,
5
5
  SchemaVerifyResult,
@@ -9,7 +9,7 @@ import type {
9
9
  * SQL family `SchemaVerifier` abstract base. Centralises the SQL-shared
10
10
  * walk (table-by-table + column-by-column matching keyed by
11
11
  * `(namespace.id, name)`, FK / unique / index comparisons via the
12
- * existing helpers in `verify-helpers.ts`) and exposes a protected hook
12
+ * shared satisfaction helpers in `sql-schema-diff.ts`) and exposes a protected hook
13
13
  * for target extensions (Postgres functions, RLS policies, future
14
14
  * target-only kinds).
15
15
  *
@@ -29,7 +29,7 @@ export abstract class SqlSchemaVerifierBase<TContract, TSchema>
29
29
  implements SchemaVerifier<TContract, TSchema>
30
30
  {
31
31
  verifySchema(options: SchemaVerifyOptions<TContract, TSchema>): SchemaVerifyResult {
32
- const issues: SchemaIssue[] = [];
32
+ const issues: SchemaDiffIssue[] = [];
33
33
  issues.push(...this.verifyCommonSqlSchema(options));
34
34
  issues.push(...this.verifyTargetExtensions(options));
35
35
  return { ok: issues.length === 0, issues };
@@ -43,7 +43,7 @@ export abstract class SqlSchemaVerifierBase<TContract, TSchema>
43
43
  */
44
44
  protected abstract verifyCommonSqlSchema(
45
45
  options: SchemaVerifyOptions<TContract, TSchema>,
46
- ): readonly SchemaIssue[];
46
+ ): readonly SchemaDiffIssue[];
47
47
 
48
48
  /**
49
49
  * Target-specific extensions — e.g. Postgres functions, future RLS
@@ -52,5 +52,5 @@ export abstract class SqlSchemaVerifierBase<TContract, TSchema>
52
52
  */
53
53
  protected abstract verifyTargetExtensions(
54
54
  options: SchemaVerifyOptions<TContract, TSchema>,
55
- ): readonly SchemaIssue[];
55
+ ): readonly SchemaDiffIssue[];
56
56
  }
@@ -1,4 +1,5 @@
1
1
  import type { ColumnDefault, Contract, JsonValue } from '@prisma-next/contract/types';
2
+ import type { CodecRef } from '@prisma-next/framework-components/codec';
2
3
  import type { MigrationPlannerConflict } from '@prisma-next/framework-components/control';
3
4
  import {
4
5
  type CheckConstraint,
@@ -12,16 +13,17 @@ import {
12
13
  type UniqueConstraint,
13
14
  } from '@prisma-next/sql-contract/types';
14
15
  import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
15
- import type {
16
- SqlAnnotations,
17
- SqlCheckConstraintIRInput,
18
- SqlColumnIR,
19
- SqlForeignKeyIR,
20
- SqlIndexIR,
16
+ import {
17
+ type SqlAnnotations,
18
+ type SqlCheckConstraintIRInput,
19
+ type SqlColumnIRInput,
20
+ type SqlForeignKeyIRInput,
21
+ type SqlIndexIRInput,
21
22
  SqlSchemaIR,
22
23
  SqlTableIR,
23
- SqlUniqueIR,
24
+ type SqlUniqueIRInput,
24
25
  } from '@prisma-next/sql-schema-ir/types';
26
+ import { blindCast } from '@prisma-next/utils/casts';
25
27
  import { ifDefined } from '@prisma-next/utils/defined';
26
28
 
27
29
  /**
@@ -72,7 +74,7 @@ function convertColumn(
72
74
  storageTypes: ResolvedStorageTypes,
73
75
  expandNativeType: NativeTypeExpander | undefined,
74
76
  renderDefault: DefaultRenderer | undefined,
75
- ): SqlColumnIR {
77
+ ): SqlColumnIRInput {
76
78
  // Resolve `typeRef` so columns that delegate their `nativeType`/`codecId`/
77
79
  // `typeParams` to a named `storage.types` entry expand the same way as
78
80
  // columns that inline those fields. Without this resolution, a
@@ -98,6 +100,43 @@ function convertColumn(
98
100
  'default',
99
101
  column.default != null && renderDefault ? renderDefault(column.default, column) : undefined,
100
102
  ),
103
+ // Contract-derived columns are resolved by construction: the computed
104
+ // full native type doubles as the resolved value, and the contract's
105
+ // structured default is the resolved default (the introspected side
106
+ // stamps its normalizer's parse of the raw expression).
107
+ resolvedNativeType: nativeType,
108
+ ...ifDefined('resolvedDefault', column.default ?? undefined),
109
+ // The column's codec identity, carried the same way the query AST
110
+ // carries `CodecRef` (TML-2456) — the migration planner's op-builders
111
+ // resolve DDL rendering from this at plan time (Decision 5), instead of
112
+ // reading a derivation-precomputed render payload.
113
+ codecRef: buildColumnCodecRef(resolved, column.many),
114
+ codecBaseNativeType: resolved.nativeType,
115
+ ...(column.typeRef !== undefined ? { codecNamedType: true } : {}),
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Builds the column's `CodecRef` from its resolved (post-`typeRef`) codec
121
+ * identity — the same construction the query AST and the migration DDL
122
+ * renderer already use (TML-2456, TML-2918).
123
+ */
124
+ function buildColumnCodecRef(
125
+ resolved: Pick<StorageColumn, 'codecId' | 'nativeType' | 'typeParams'>,
126
+ many: boolean | undefined,
127
+ ): CodecRef {
128
+ return {
129
+ codecId: resolved.codecId,
130
+ ...ifDefined(
131
+ 'typeParams',
132
+ resolved.typeParams !== undefined
133
+ ? blindCast<
134
+ JsonValue,
135
+ 'resolved.typeParams is JsonValue-shaped storage metadata; the narrowed (non-undefined) value lands in CodecRef.typeParams which is JsonValue'
136
+ >(resolved.typeParams)
137
+ : undefined,
138
+ ),
139
+ ...ifDefined('many', many),
101
140
  };
102
141
  }
103
142
 
@@ -193,22 +232,26 @@ function convertCheck(check: CheckConstraint, storage: SqlStorage): SqlCheckCons
193
232
  };
194
233
  }
195
234
 
196
- function convertUnique(unique: UniqueConstraint): SqlUniqueIR {
235
+ function convertUnique(unique: UniqueConstraint): SqlUniqueIRInput {
197
236
  return {
198
237
  columns: unique.columns,
199
238
  ...ifDefined('name', unique.name),
200
239
  };
201
240
  }
202
241
 
203
- function convertIndex(index: Index): SqlIndexIR {
242
+ function convertIndex(index: Index): SqlIndexIRInput {
204
243
  return {
205
244
  columns: index.columns,
206
245
  unique: false,
207
246
  ...ifDefined('name', index.name),
247
+ // Carried so the derived index node compares type/options against the
248
+ // introspected side (the legacy walk read them from the contract).
249
+ ...ifDefined('type', index.type),
250
+ ...ifDefined('options', index.options),
208
251
  };
209
252
  }
210
253
 
211
- function convertForeignKey(fk: ForeignKey): SqlForeignKeyIR {
254
+ function convertForeignKey(fk: ForeignKey): SqlForeignKeyIRInput {
212
255
  return {
213
256
  columns: fk.source.columns,
214
257
  referencedTable: fk.target.tableName,
@@ -228,7 +271,7 @@ function convertTable(
228
271
  renderDefault: DefaultRenderer | undefined,
229
272
  storage: SqlStorage,
230
273
  ): SqlTableIR {
231
- const columns: Record<string, SqlColumnIR> = {};
274
+ const columns: Record<string, SqlColumnIRInput> = {};
232
275
  for (const [colName, colDef] of Object.entries(table.columns)) {
233
276
  columns[colName] = convertColumn(
234
277
  colName,
@@ -244,7 +287,7 @@ function convertTable(
244
287
  ...table.uniques.map((unique) => unique.columns.join(',')),
245
288
  ...(table.primaryKey ? [table.primaryKey.columns.join(',')] : []),
246
289
  ]);
247
- const fkBackingIndexes: SqlIndexIR[] = [];
290
+ const fkBackingIndexes: SqlIndexIRInput[] = [];
248
291
  for (const fk of table.foreignKeys) {
249
292
  if (fk.index === false) continue;
250
293
  const key = fk.source.columns.join(',');
@@ -262,7 +305,7 @@ function convertTable(
262
305
  ? table.checks.map((c) => convertCheck(c, storage))
263
306
  : undefined;
264
307
 
265
- return {
308
+ return new SqlTableIR({
266
309
  name,
267
310
  columns,
268
311
  ...ifDefined('primaryKey', table.primaryKey),
@@ -270,7 +313,7 @@ function convertTable(
270
313
  uniques: table.uniques.map(convertUnique),
271
314
  indexes: [...table.indexes.map(convertIndex), ...fkBackingIndexes],
272
315
  ...ifDefined('checks', checks),
273
- };
316
+ });
274
317
  }
275
318
 
276
319
  /**
@@ -383,7 +426,7 @@ export function contractNamespaceToSchemaIR(
383
426
  }
384
427
  const namespace = storage.namespaces[namespaceId];
385
428
  if (!namespace) {
386
- return { tables: {} };
429
+ return new SqlSchemaIR({ tables: {} });
387
430
  }
388
431
  const storageTypes: ResolvedStorageTypes = { ...(storage.types ?? {}) };
389
432
  const tables: Record<string, SqlTableIR> = {};
@@ -398,7 +441,7 @@ export function contractNamespaceToSchemaIR(
398
441
  storage,
399
442
  );
400
443
  }
401
- return { tables };
444
+ return new SqlSchemaIR({ tables });
402
445
  }
403
446
 
404
447
  export function contractToSchemaIR(
@@ -410,7 +453,7 @@ export function contractToSchemaIR(
410
453
  }
411
454
 
412
455
  if (!contract) {
413
- return { tables: {} };
456
+ return new SqlSchemaIR({ tables: {} });
414
457
  }
415
458
 
416
459
  const storage = contract.storage;
@@ -442,10 +485,10 @@ export function contractToSchemaIR(
442
485
  options.resolveEnumNamespaceSchema,
443
486
  );
444
487
 
445
- return {
488
+ return new SqlSchemaIR({
446
489
  tables,
447
490
  ...ifDefined('annotations', annotations),
448
- };
491
+ });
449
492
  }
450
493
 
451
494
  function deriveAnnotations(
@@ -0,0 +1,28 @@
1
+ import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
2
+ import { extractCodecControlHooks } from '../assembly';
3
+
4
+ /**
5
+ * Builds the codec-hook-composed `expandNativeType` callback the contract→IR
6
+ * derivation uses to expand parameterized native types (e.g. `character` +
7
+ * `{ length: 36 }` → `character(36)`). Returns `undefined` when no framework
8
+ * components are supplied, so callers can omit the option entirely.
9
+ */
10
+ export function buildNativeTypeExpander(
11
+ frameworkComponents?: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>,
12
+ ) {
13
+ if (!frameworkComponents) {
14
+ return undefined;
15
+ }
16
+ const codecHooks = extractCodecControlHooks(frameworkComponents);
17
+ return (input: {
18
+ readonly nativeType: string;
19
+ readonly codecId?: string;
20
+ readonly typeParams?: Record<string, unknown>;
21
+ }) => {
22
+ if (!input.typeParams) return input.nativeType;
23
+ if (!input.codecId) return input.nativeType;
24
+ const hooks = codecHooks.get(input.codecId);
25
+ if (!hooks?.expandNativeType) return input.nativeType;
26
+ return hooks.expandNativeType(input);
27
+ };
28
+ }
@@ -1,40 +1,41 @@
1
- import type { Contract } from '@prisma-next/contract/types';
1
+ import type { Contract, ControlPolicy } from '@prisma-next/contract/types';
2
2
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
3
- import type {
4
- SchemaDiffer,
5
- VerifyDatabaseSchemaResult,
6
- } from '@prisma-next/framework-components/control';
3
+ import type { SchemaDiffIssue } from '@prisma-next/framework-components/control';
7
4
  import type { SqlStorage } from '@prisma-next/sql-contract/types';
8
5
  import type { SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
9
6
 
10
7
  /**
11
- * Inputs to a SQL target's schema-differ (`diffDatabaseSchema` /
12
- * `verifyDatabaseSchema` on the descriptor): the contract (the expected side
13
- * derives from it), the introspected actual schema node, and the resolution
14
- * context the relational diff needs.
8
+ * The full-tree node diff a SQL target produces for the family verify
9
+ * verdict: the target derives the expected tree from the contract, applies
10
+ * the pre-diff normalizations (semantic satisfaction, FK schema-segment
11
+ * resolution), runs the generic differ, and ownership-scopes the result.
12
+ * Strict gating, control-policy disposition, and the verdict itself are the
13
+ * family's post-diff filters over this output.
15
14
  */
16
- export interface DiffDatabaseSchemaInput {
15
+ export interface SqlSchemaDiffResult {
16
+ /** The full, ownership-scoped diff issue list. */
17
+ readonly issues: readonly SchemaDiffIssue[];
18
+ /**
19
+ * Resolves a diff issue's subject table's declared control policy directly
20
+ * from the contract (Decision 5's own-layer-per-concern discipline extends
21
+ * here too: control policy is a contract concern, resolved by the target
22
+ * at disposition time — never stamped on the diff node). `undefined`
23
+ * when the issue's path resolves to no contract table (a genuine orphan,
24
+ * or a non-table subject).
25
+ */
26
+ readonly resolveControlPolicy: (issue: SchemaDiffIssue) => ControlPolicy | undefined;
27
+ /**
28
+ * The expected/actual namespace-node pairs the codec `verifyType` hooks
29
+ * run over — one per contract namespace with tables, paired by DDL
30
+ * schema; a flat target repeats its sole actual root per such namespace.
31
+ */
32
+ readonly namespacePairs: ReadonlyArray<{ readonly actual: SqlSchemaIRNode | undefined }>;
33
+ }
34
+
35
+ export interface SqlSchemaDiffInput {
17
36
  readonly contract: Contract<SqlStorage>;
18
37
  readonly schema: SqlSchemaIRNode;
19
- readonly strict: boolean;
20
- readonly typeMetadataRegistry: ReadonlyMap<string, { readonly nativeType?: string }>;
21
38
  readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
22
39
  }
23
40
 
24
- /**
25
- * The `SchemaDiffer` a SQL target implements: the black-box comparison of the
26
- * contract's expected schema against the introspected actual schema, projected
27
- * to the two issue lists. How it computes them is private to the target.
28
- */
29
- export type SqlDiffDatabaseSchema = SchemaDiffer<DiffDatabaseSchemaInput>['diff'];
30
-
31
- /**
32
- * The same combined comparison as {@link SqlDiffDatabaseSchema}, wrapped in the
33
- * verify envelope (`ok`/`summary`/`code`/`target`/`timings`) plus the
34
- * pass/warn/fail tree the CLI renders. Verify calls this instead of the diff so
35
- * the relational walk that produces the tree runs once per verify, not once for
36
- * the diff and again for the tree.
37
- */
38
- export type SqlVerifyDatabaseSchema = (
39
- input: DiffDatabaseSchemaInput,
40
- ) => VerifyDatabaseSchemaResult;
41
+ export type SqlSchemaDiffFn = (input: SqlSchemaDiffInput) => SqlSchemaDiffResult;
@@ -4,7 +4,6 @@ import type {
4
4
  ContractSpace,
5
5
  ControlAdapterDescriptor,
6
6
  ControlExtensionDescriptor,
7
- DiffIssue,
8
7
  MigrationOperationPolicy,
9
8
  MigrationPlan,
10
9
  MigrationPlannerConflict,
@@ -17,7 +16,8 @@ import type {
17
16
  MigrationRunnerResult,
18
17
  OperationContext,
19
18
  OpFactoryCall,
20
- SchemaIssue,
19
+ SchemaDiffIssue,
20
+ SchemaOwnership,
21
21
  } from '@prisma-next/framework-components/control';
22
22
  import type { AggregateMigrationEdgeRef } from '@prisma-next/migration-tools/aggregate';
23
23
  import type {
@@ -28,7 +28,7 @@ import type {
28
28
  StorageTypeInstance,
29
29
  } from '@prisma-next/sql-contract/types';
30
30
  import type { SqlOperationDescriptors } from '@prisma-next/sql-operations';
31
- import type { SqlSchemaIR, SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
31
+ import type { SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
32
32
  import type { Result } from '@prisma-next/utils/result';
33
33
  import type { SqlControlAdapter } from '../control-adapter';
34
34
 
@@ -99,20 +99,29 @@ export interface FieldEventContext {
99
99
  }
100
100
 
101
101
  export interface CodecControlHooks<TTargetDetails = unknown> {
102
+ /**
103
+ * `schema` is typed as the family-level `SqlSchemaIRNode` (not the concrete
104
+ * `SqlSchemaIR` class) because the actual value handed in is whatever
105
+ * per-namespace node the calling target's tree shape produces — a flat
106
+ * `SqlSchemaIR` for SQLite, a `PostgresNamespaceSchemaNode` for Postgres —
107
+ * read structurally for its `tables`/`nativeEnumTypeNames` fields. Hooks
108
+ * that need the concrete Postgres shape narrow via
109
+ * `PostgresNamespaceSchemaNode.is(schema)`.
110
+ */
102
111
  planTypeOperations?: (options: {
103
112
  readonly typeName: string;
104
113
  readonly typeInstance: StorageTypeInstance;
105
114
  readonly contract: Contract<SqlStorage>;
106
- readonly schema: SqlSchemaIR;
115
+ readonly schema: SqlSchemaIRNode;
107
116
  readonly schemaName?: string;
108
117
  readonly policy: MigrationOperationPolicy;
109
118
  }) => StorageTypePlanResult<TTargetDetails>;
110
119
  verifyType?: (options: {
111
120
  readonly typeName: string;
112
121
  readonly typeInstance: StorageTypeInstance;
113
- readonly schema: SqlSchemaIR;
122
+ readonly schema: SqlSchemaIRNode;
114
123
  readonly schemaName?: string;
115
- }) => readonly SchemaIssue[];
124
+ }) => readonly SchemaDiffIssue[];
116
125
  introspectTypes?: (options: {
117
126
  readonly driver: SqlControlDriverInstance<string>;
118
127
  readonly schemaName?: string;
@@ -346,15 +355,15 @@ export interface SqlMigrationPlannerPlanOptions {
346
355
  */
347
356
  readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
348
357
  /**
349
- * Caller-supplied keep-predicate the planner applies to its schema diff
350
- * (via `SchemaDiff.filter`) before building operations. The orchestration
351
- * constructs it so the diff findings reaching op-building are exactly the
352
- * contract space's own e.g. dropping the `extra` findings for elements a
353
- * sibling contract space declares, so the planner never emits DROP ops
354
- * against another space's tables. The planner applies it blindly and holds
355
- * no ownership logic. Absent for single-space plans.
358
+ * Ownership oracle over the whole contract-space composition (the passive
359
+ * aggregate). The planner asks it, per live extra node, whether any space
360
+ * declares that entity: a sibling-owned node is left untouched, an unowned
361
+ * node is a genuine extra it may drop under a destructive policy. The
362
+ * planner holds no list of other spaces' names ownership lives in the
363
+ * aggregate; it only asks. Absent for a single-space plan handed no
364
+ * aggregate. See {@link SchemaOwnership}.
356
365
  */
357
- readonly keepDiffIssue?: (issue: DiffIssue) => boolean;
366
+ readonly ownership?: SchemaOwnership;
358
367
  }
359
368
 
360
369
  export interface SqlMigrationPlanner<TTargetDetails> {
@@ -39,6 +39,7 @@ export {
39
39
  } from '../core/migrations/control-policy';
40
40
  export type { PlanFieldEventOperationsOptions } from '../core/migrations/field-event-planner';
41
41
  export { planFieldEventOperations } from '../core/migrations/field-event-planner';
42
+ export { buildNativeTypeExpander } from '../core/migrations/native-type-expander';
42
43
  export {
43
44
  createMigrationPlan,
44
45
  plannerFailure,
@@ -47,6 +48,11 @@ export {
47
48
  runnerSuccess,
48
49
  } from '../core/migrations/plan-helpers';
49
50
  export { INIT_ADDITIVE_POLICY } from '../core/migrations/policies';
51
+ export type {
52
+ SqlSchemaDiffFn,
53
+ SqlSchemaDiffInput,
54
+ SqlSchemaDiffResult,
55
+ } from '../core/migrations/schema-differ';
50
56
  export type {
51
57
  CodecControlHooks,
52
58
  CreateSqlMigrationPlanOptions,
@@ -1,18 +1,38 @@
1
1
  /**
2
2
  * SQL relational schema-diff exports.
3
3
  *
4
- * The shared relational diff that each SQL target descriptor's
5
- * `diffDatabaseSchema` composes (Postgres adds its structural policy diff on
6
- * top; SQLite is relational only). Pure — no database connection required.
4
+ * The generic node differ (`buildPostgresPlanDiff` / `buildSqlitePlanDiff`)
5
+ * drives both plan and verify; this module surfaces the shared
6
+ * semantic-satisfaction predicates and verify-verdict machinery that survive
7
+ * it. Pure — no database connection required.
7
8
  */
8
9
 
9
10
  export type {
10
- NativeTypeNormalizer,
11
- VerifySqlSchemaOptions,
12
- } from '../core/diff/sql-schema-diff';
13
- export { verifySqlSchema, verifySqlSchemaTree } from '../core/diff/sql-schema-diff';
11
+ SemanticSatisfactionInput,
12
+ SemanticSatisfactionResult,
13
+ } from '../core/diff/diff-tree-normalization';
14
+ export {
15
+ neutralizeFlatExpectedFkSchemas,
16
+ normalizeFlatActualForDiff,
17
+ resolveSemanticSatisfaction,
18
+ } from '../core/diff/diff-tree-normalization';
19
+ export type {
20
+ SqlDiffVerdict,
21
+ SqlDiffVerdictInput,
22
+ StorageTypeVerdict,
23
+ StorageTypeVerdictInput,
24
+ VerifySqlSchemaByDiffInput,
25
+ } from '../core/diff/schema-verify';
26
+ export {
27
+ classifyDiffSubjectGranularity,
28
+ classifySqlDiffIssue,
29
+ computeSqlDiffVerdict,
30
+ computeStorageTypeVerdict,
31
+ verifySqlSchemaByDiff,
32
+ } from '../core/diff/schema-verify';
33
+ export type { NativeTypeNormalizer } from '../core/diff/sql-schema-diff';
14
34
  export {
15
35
  arraysEqual,
16
36
  isIndexSatisfied,
17
37
  isUniqueConstraintSatisfied,
18
- } from '../core/diff/verify-helpers';
38
+ } from '../core/diff/sql-schema-diff';