@prisma-next/family-sql 0.14.0-dev.39 → 0.14.0-dev.40

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/contract-to-schema-ir-S-evq8E6.mjs +264 -0
  2. package/dist/contract-to-schema-ir-S-evq8E6.mjs.map +1 -0
  3. package/dist/{control-adapter-B2q9WN1V.d.mts → control-adapter-DHYFuOBy.d.mts} +14 -17
  4. package/dist/{control-adapter-B2q9WN1V.d.mts.map → control-adapter-DHYFuOBy.d.mts.map} +1 -1
  5. package/dist/control-adapter.d.mts +1 -1
  6. package/dist/control.d.mts +17 -2
  7. package/dist/control.d.mts.map +1 -1
  8. package/dist/control.mjs +42 -943
  9. package/dist/control.mjs.map +1 -1
  10. package/dist/{schema-verify.d.mts → diff.d.mts} +4 -4
  11. package/dist/diff.d.mts.map +1 -0
  12. package/dist/{verify-sql-schema-CEY7b78t.mjs → diff.mjs} +154 -244
  13. package/dist/diff.mjs.map +1 -0
  14. package/dist/migration.d.mts +1 -1
  15. package/dist/psl-infer.d.mts +80 -0
  16. package/dist/psl-infer.d.mts.map +1 -0
  17. package/dist/psl-infer.mjs +334 -0
  18. package/dist/psl-infer.mjs.map +1 -0
  19. package/dist/{verify-sql-schema-B8uEjw2s.d.mts → sql-schema-diff-6z36dZt6.d.mts} +43 -4
  20. package/dist/sql-schema-diff-6z36dZt6.d.mts.map +1 -0
  21. package/dist/{types-BxsFkHYv.d.mts → types-CkOIJXxU.d.mts} +83 -16
  22. package/dist/types-CkOIJXxU.d.mts.map +1 -0
  23. package/package.json +23 -22
  24. package/src/core/control-adapter.ts +12 -24
  25. package/src/core/control-instance.ts +113 -45
  26. package/src/core/{schema-verify/verify-sql-schema.ts → diff/sql-schema-diff.ts} +196 -8
  27. package/src/core/{schema-verify → diff}/verify-helpers.ts +12 -0
  28. package/src/core/migrations/contract-to-schema-ir.ts +48 -13
  29. package/src/core/migrations/field-event-planner.ts +3 -8
  30. package/src/core/migrations/schema-differ.ts +40 -0
  31. package/src/core/migrations/types.ts +43 -2
  32. package/src/core/psl-contract-infer/printer-config.ts +0 -6
  33. package/src/exports/control.ts +1 -0
  34. package/src/exports/diff.ts +18 -0
  35. package/src/exports/psl-infer.ts +36 -0
  36. package/dist/schema-verify.d.mts.map +0 -1
  37. package/dist/schema-verify.mjs +0 -2
  38. package/dist/types-BxsFkHYv.d.mts.map +0 -1
  39. package/dist/verify-sql-schema-B8uEjw2s.d.mts.map +0 -1
  40. package/dist/verify-sql-schema-CEY7b78t.mjs.map +0 -1
  41. package/src/core/psl-contract-infer/postgres-default-mapping.ts +0 -16
  42. package/src/core/psl-contract-infer/postgres-type-map.ts +0 -157
  43. package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +0 -795
  44. package/src/exports/schema-verify.ts +0 -18
  45. /package/src/core/{schema-verify → diff}/control-verify-emit.ts +0 -0
  46. /package/src/core/{schema-verify → diff}/verifier-disposition.ts +0 -0
package/package.json CHANGED
@@ -1,34 +1,34 @@
1
1
  {
2
2
  "name": "@prisma-next/family-sql",
3
- "version": "0.14.0-dev.39",
3
+ "version": "0.14.0-dev.40",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "description": "SQL family descriptor for Prisma Next",
8
8
  "dependencies": {
9
- "@prisma-next/contract": "0.14.0-dev.39",
10
- "@prisma-next/emitter": "0.14.0-dev.39",
11
- "@prisma-next/framework-components": "0.14.0-dev.39",
12
- "@prisma-next/migration-tools": "0.14.0-dev.39",
13
- "@prisma-next/operations": "0.14.0-dev.39",
14
- "@prisma-next/sql-contract": "0.14.0-dev.39",
15
- "@prisma-next/sql-contract-emitter": "0.14.0-dev.39",
16
- "@prisma-next/sql-contract-ts": "0.14.0-dev.39",
17
- "@prisma-next/sql-operations": "0.14.0-dev.39",
18
- "@prisma-next/sql-relational-core": "0.14.0-dev.39",
19
- "@prisma-next/sql-runtime": "0.14.0-dev.39",
20
- "@prisma-next/sql-schema-ir": "0.14.0-dev.39",
21
- "@prisma-next/utils": "0.14.0-dev.39",
9
+ "@prisma-next/contract": "0.14.0-dev.40",
10
+ "@prisma-next/emitter": "0.14.0-dev.40",
11
+ "@prisma-next/framework-components": "0.14.0-dev.40",
12
+ "@prisma-next/migration-tools": "0.14.0-dev.40",
13
+ "@prisma-next/operations": "0.14.0-dev.40",
14
+ "@prisma-next/sql-contract": "0.14.0-dev.40",
15
+ "@prisma-next/sql-contract-emitter": "0.14.0-dev.40",
16
+ "@prisma-next/sql-contract-ts": "0.14.0-dev.40",
17
+ "@prisma-next/sql-operations": "0.14.0-dev.40",
18
+ "@prisma-next/sql-relational-core": "0.14.0-dev.40",
19
+ "@prisma-next/sql-runtime": "0.14.0-dev.40",
20
+ "@prisma-next/sql-schema-ir": "0.14.0-dev.40",
21
+ "@prisma-next/utils": "0.14.0-dev.40",
22
22
  "arktype": "^2.2.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@prisma-next/driver-postgres": "0.14.0-dev.39",
26
- "@prisma-next/psl-parser": "0.14.0-dev.39",
27
- "@prisma-next/psl-printer": "0.14.0-dev.39",
28
- "@prisma-next/sql-contract-psl": "0.14.0-dev.39",
29
- "@prisma-next/test-utils": "0.14.0-dev.39",
30
- "@prisma-next/tsconfig": "0.14.0-dev.39",
31
- "@prisma-next/tsdown": "0.14.0-dev.39",
25
+ "@prisma-next/driver-postgres": "0.14.0-dev.40",
26
+ "@prisma-next/psl-parser": "0.14.0-dev.40",
27
+ "@prisma-next/psl-printer": "0.14.0-dev.40",
28
+ "@prisma-next/sql-contract-psl": "0.14.0-dev.40",
29
+ "@prisma-next/test-utils": "0.14.0-dev.40",
30
+ "@prisma-next/tsconfig": "0.14.0-dev.40",
31
+ "@prisma-next/tsdown": "0.14.0-dev.40",
32
32
  "tsdown": "0.22.1",
33
33
  "typescript": "5.9.3",
34
34
  "vitest": "4.1.8"
@@ -48,11 +48,12 @@
48
48
  "exports": {
49
49
  "./control": "./dist/control.mjs",
50
50
  "./control-adapter": "./dist/control-adapter.mjs",
51
+ "./diff": "./dist/diff.mjs",
51
52
  "./ir": "./dist/ir.mjs",
52
53
  "./migration": "./dist/migration.mjs",
53
54
  "./pack": "./dist/pack.mjs",
55
+ "./psl-infer": "./dist/psl-infer.mjs",
54
56
  "./runtime": "./dist/runtime.mjs",
55
- "./schema-verify": "./dist/schema-verify.mjs",
56
57
  "./verify": "./dist/verify.mjs",
57
58
  "./package.json": "./package.json"
58
59
  },
@@ -1,14 +1,9 @@
1
- import type {
2
- Contract,
3
- ContractMarkerRecord,
4
- LedgerEntryRecord,
5
- } from '@prisma-next/contract/types';
1
+ import type { ContractMarkerRecord, LedgerEntryRecord } from '@prisma-next/contract/types';
6
2
  import type {
7
3
  ControlAdapterInstance,
8
4
  ControlStack,
9
- SchemaDiffIssue,
10
5
  } from '@prisma-next/framework-components/control';
11
- import type { SqlControlDriverInstance, SqlStorage } from '@prisma-next/sql-contract/types';
6
+ import type { SqlControlDriverInstance } from '@prisma-next/sql-contract/types';
12
7
  import type {
13
8
  AnyQueryAst,
14
9
  DdlNode,
@@ -16,8 +11,8 @@ import type {
16
11
  LowererContext,
17
12
  SqlExecuteRequest,
18
13
  } from '@prisma-next/sql-relational-core/ast';
19
- import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
20
- import type { DefaultNormalizer, NativeTypeNormalizer } from './schema-verify/verify-sql-schema';
14
+ import type { SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
15
+ import type { DefaultNormalizer, NativeTypeNormalizer } from './diff/sql-schema-diff';
21
16
 
22
17
  /**
23
18
  * Structural interface for anything that can lower a SQL/DDL AST node to a
@@ -162,22 +157,27 @@ export interface SqlControlAdapter<TTarget extends string = string>
162
157
  ): Promise<void>;
163
158
 
164
159
  /**
165
- * Introspects a database schema and returns a raw SqlSchemaIR.
160
+ * Introspects a database schema and returns the target's schema-IR node.
166
161
  *
167
162
  * This is a pure schema discovery operation that queries the database catalog
168
163
  * and returns the schema structure without type mapping or contract enrichment.
169
164
  * Type mapping and enrichment are handled separately by enrichment helpers.
170
165
  *
166
+ * The return type is the family-base `SqlSchemaIRNode` so each target returns
167
+ * its own node shape: SQLite returns a flat `SqlSchemaIR`, Postgres returns a
168
+ * `PostgresDatabaseSchemaNode` tree root. Consumers `ensure` the concrete
169
+ * target type before walking it.
170
+ *
171
171
  * @param driver - ControlDriverInstance instance for executing queries (target-specific)
172
172
  * @param contract - Optional contract for contract-guided introspection (filtering, optimization)
173
173
  * @param schema - Schema name to introspect (defaults to 'public')
174
- * @returns Promise resolving to SqlSchemaIR representing the live database schema
174
+ * @returns Promise resolving to the live database schema node
175
175
  */
176
176
  introspect(
177
177
  driver: SqlControlDriverInstance<TTarget>,
178
178
  contract?: unknown,
179
179
  schema?: string,
180
- ): Promise<SqlSchemaIR>;
180
+ ): Promise<SqlSchemaIRNode>;
181
181
 
182
182
  /**
183
183
  * Optional target-specific normalizer for raw database default expressions.
@@ -193,18 +193,6 @@ export interface SqlControlAdapter<TTarget extends string = string>
193
193
  */
194
194
  readonly normalizeNativeType?: NativeTypeNormalizer;
195
195
 
196
- /**
197
- * Optional hook for collecting target-specific diff issues during schema
198
- * verification. Called after the relational SQL verification pass; returns
199
- * generic `SchemaDiffIssue[]` (coordinate + outcome + message — no target
200
- * naming) that are folded into `VerifyDatabaseSchemaResult.schema.schemaDiffIssues`
201
- * and counted as failures when non-empty.
202
- */
203
- collectSchemaDiffIssues?(
204
- contract: Contract<SqlStorage>,
205
- schema: SqlSchemaIR,
206
- ): readonly SchemaDiffIssue[];
207
-
208
196
  /**
209
197
  * Ordered DDL queries that bootstrap marker/ledger control tables for migration
210
198
  * runners. Postgres includes `CREATE SCHEMA`; SQLite does not.
@@ -44,18 +44,17 @@ import type {
44
44
  SqlExecuteRequest,
45
45
  } from '@prisma-next/sql-relational-core/ast';
46
46
  import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
47
- import type { SqlSchemaIR, SqlTableIR } from '@prisma-next/sql-schema-ir/types';
47
+ import type { SqlSchemaIRNode, SqlTableIR } from '@prisma-next/sql-schema-ir/types';
48
48
  import { blindCast } from '@prisma-next/utils/casts';
49
49
  import { ifDefined } from '@prisma-next/utils/defined';
50
50
  import type { SqlControlAdapter } from './control-adapter';
51
51
  import { SqlContractSerializer } from './ir/sql-contract-serializer';
52
+ import type { DiffDatabaseSchemaInput } from './migrations/schema-differ';
52
53
  import type {
53
54
  SqlControlAdapterDescriptor,
54
55
  SqlControlExtensionDescriptor,
55
56
  } from './migrations/types';
56
57
  import { sqlOperationsToPreview } from './operation-preview';
57
- import { sqlSchemaIrToPslAst } from './psl-contract-infer/sql-schema-ir-to-psl-ast';
58
- import { verifySqlSchema } from './schema-verify/verify-sql-schema';
59
58
  import { collectSupportedCodecTypeIds } from './verify';
60
59
 
61
60
  function extractCodecTypeIdsFromContract(contract: unknown): readonly string[] {
@@ -192,9 +191,9 @@ interface SqlFamilyInstanceState {
192
191
  }
193
192
 
194
193
  export interface SqlControlFamilyInstance
195
- extends ControlFamilyInstance<'sql', SqlSchemaIR>,
196
- SchemaViewCapable<SqlSchemaIR>,
197
- PslContractInferCapable<SqlSchemaIR>,
194
+ extends ControlFamilyInstance<'sql', SqlSchemaIRNode>,
195
+ SchemaViewCapable<SqlSchemaIRNode>,
196
+ PslContractInferCapable<SqlSchemaIRNode>,
198
197
  OperationPreviewCapable,
199
198
  SqlFamilyInstanceState {
200
199
  /**
@@ -216,18 +215,17 @@ export interface SqlControlFamilyInstance
216
215
  }): Promise<VerifyDatabaseResult>;
217
216
 
218
217
  /**
219
- * Verify a contract against an already-introspected schema slice.
218
+ * Verify a contract against an already-introspected schema.
220
219
  *
221
220
  * Callers that need to verify against the live database compose
222
221
  * `introspect({ driver })` + `verifySchema({ contract, schema, ... })`.
223
- * The aggregate verifier projects each member's claimed slice via
224
- * `projectSchemaToSpace` and hands the projected slice inthis
225
- * keeps per-member verification from surfacing sibling-space tables
226
- * as `extras`.
222
+ * The aggregate verifier hands in the full introspected schema and scopes
223
+ * the returned result to each member's contract space afterwards so
224
+ * sibling-space tables never survive as `extras`.
227
225
  */
228
226
  verifySchema(options: {
229
227
  readonly contract: unknown;
230
- readonly schema: SqlSchemaIR;
228
+ readonly schema: SqlSchemaIRNode;
231
229
  readonly strict: boolean;
232
230
  readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
233
231
  }): VerifyDatabaseSchemaResult;
@@ -242,9 +240,9 @@ export interface SqlControlFamilyInstance
242
240
  introspect(options: {
243
241
  readonly driver: SqlControlDriverInstance<string>;
244
242
  readonly contract?: unknown;
245
- }): Promise<SqlSchemaIR>;
243
+ }): Promise<SqlSchemaIRNode>;
246
244
 
247
- inferPslContract(schemaIR: SqlSchemaIR): PslDocumentAst;
245
+ inferPslContract(schemaIR: SqlSchemaIRNode): PslDocumentAst;
248
246
 
249
247
  lowerAst(
250
248
  ast: AnyQueryAst | DdlNode,
@@ -300,8 +298,6 @@ export interface SqlControlFamilyInstance
300
298
 
301
299
  bootstrapControlTableQueries(): readonly DdlNode[];
302
300
 
303
- bootstrapSignMarkerQueries(): readonly DdlNode[];
304
-
305
301
  toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview;
306
302
  }
307
303
 
@@ -535,6 +531,31 @@ export function createSqlFamilyInstance<TTargetId extends string>(
535
531
  };
536
532
  }
537
533
  ).contractSerializer;
534
+ // Database→PSL inference is target logic (it owns the dialect type/default
535
+ // maps and walks its own schema tree), so it is read off the descriptor like
536
+ // `contractSerializer`. Absent for targets without `contract infer` (Mongo).
537
+ const targetInferPslContract = blindCast<
538
+ { readonly inferPslContract?: (schema: SqlSchemaIRNode) => PslDocumentAst },
539
+ 'reading the optional target-descriptor inferPslContract hook'
540
+ >(target).inferPslContract;
541
+ // The combined database-schema verify is a required target-descriptor
542
+ // operation: every SQL target provides it, wrapping the same comparison
543
+ // `diffDatabaseSchema` runs in the verify envelope plus the pass/warn/fail
544
+ // tree the CLI renders. Read it off the descriptor like the other
545
+ // target-owned hooks.
546
+ const verifyDatabaseSchema = blindCast<
547
+ {
548
+ readonly verifyDatabaseSchema?: (
549
+ input: DiffDatabaseSchemaInput,
550
+ ) => VerifyDatabaseSchemaResult;
551
+ },
552
+ 'reading the required target-descriptor verifyDatabaseSchema hook'
553
+ >(target).verifyDatabaseSchema;
554
+ if (!verifyDatabaseSchema) {
555
+ throw new Error(
556
+ `SQL target "${target.targetId}" is missing the required verifyDatabaseSchema descriptor operation`,
557
+ );
558
+ }
538
559
  const deserializeWithTargetSerializer = (contractOrJson: unknown): Contract<SqlStorage> => {
539
560
  const serializer = targetSerializer ?? new SqlContractSerializer();
540
561
  const json =
@@ -687,29 +708,36 @@ export function createSqlFamilyInstance<TTargetId extends string>(
687
708
 
688
709
  verifySchema(options: {
689
710
  readonly contract: unknown;
690
- readonly schema: SqlSchemaIR;
711
+ readonly schema: SqlSchemaIRNode;
691
712
  readonly strict: boolean;
692
713
  readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
693
714
  }): VerifyDatabaseSchemaResult {
694
715
  const contract = deserializeWithTargetSerializer(options.contract) as Contract<SqlStorage>;
695
- const controlAdapter = getControlAdapter();
696
- const sqlResult = verifySqlSchema({
716
+ // Verify is a thin consumer of the target's black-box comparison: it
717
+ // introspects the actual schema (already in `options.schema`), calls the
718
+ // target's required `verifyDatabaseSchema`, and rejects when a surviving
719
+ // issue is a failure. It composes no diffing itself and is blind to how
720
+ // the comparison works.
721
+ const sqlResult = verifyDatabaseSchema({
697
722
  contract,
698
723
  schema: options.schema,
699
724
  strict: options.strict,
700
725
  typeMetadataRegistry,
701
726
  frameworkComponents: options.frameworkComponents,
702
- ...ifDefined('normalizeDefault', controlAdapter.normalizeDefault),
703
- ...ifDefined('normalizeNativeType', controlAdapter.normalizeNativeType),
704
727
  });
705
- const rawSchemaDiffIssues =
706
- controlAdapter.collectSchemaDiffIssues?.(contract, options.schema) ?? [];
728
+ // Control-policy suppression of the structural (e.g. RLS policy) diff
729
+ // issues is a verify-side post-step — the combined diff returns them
730
+ // ownership-filtered, and a suppressed control policy drops them here.
707
731
  const schemaDiffIssues = filterSchemaDiffIssues(
708
- rawSchemaDiffIssues,
732
+ sqlResult.schema.schemaDiffIssues,
709
733
  contract.defaultControlPolicy,
710
734
  );
711
- if (schemaDiffIssues.length === 0) return sqlResult;
712
- const totalFails = sqlResult.schema.counts.fail + schemaDiffIssues.length;
735
+ const relationalFails = sqlResult.schema.counts.fail;
736
+ if (schemaDiffIssues.length === 0) {
737
+ if (schemaDiffIssues === sqlResult.schema.schemaDiffIssues) return sqlResult;
738
+ return { ...sqlResult, schema: { ...sqlResult.schema, schemaDiffIssues } };
739
+ }
740
+ const totalFails = relationalFails + schemaDiffIssues.length;
713
741
  return {
714
742
  ...sqlResult,
715
743
  ok: false,
@@ -885,12 +913,17 @@ export function createSqlFamilyInstance<TTargetId extends string>(
885
913
  async introspect(options: {
886
914
  readonly driver: SqlControlDriverInstance<string>;
887
915
  readonly contract?: unknown;
888
- }): Promise<SqlSchemaIR> {
916
+ }): Promise<SqlSchemaIRNode> {
889
917
  return getControlAdapter().introspect(options.driver, options.contract);
890
918
  },
891
919
 
892
- inferPslContract(schemaIR: SqlSchemaIR): PslDocumentAst {
893
- return sqlSchemaIrToPslAst(schemaIR);
920
+ inferPslContract(schemaIR: SqlSchemaIRNode): PslDocumentAst {
921
+ if (!targetInferPslContract) {
922
+ throw new Error(
923
+ `Target "${target.targetId}" does not support contract infer (no inferPslContract on its descriptor).`,
924
+ );
925
+ }
926
+ return targetInferPslContract(schemaIR);
894
927
  },
895
928
 
896
929
  lowerAst(
@@ -904,17 +937,52 @@ export function createSqlFamilyInstance<TTargetId extends string>(
904
937
  return getControlAdapter().bootstrapControlTableQueries();
905
938
  },
906
939
 
907
- bootstrapSignMarkerQueries(): readonly DdlNode[] {
908
- return getControlAdapter().bootstrapSignMarkerQueries();
909
- },
910
-
911
940
  toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview {
912
941
  return sqlOperationsToPreview(operations);
913
942
  },
914
943
 
915
- toSchemaView(schema: SqlSchemaIR): CoreSchemaView {
916
- const tableNodes: readonly SchemaTreeNode[] = Object.entries(schema.tables).map(
917
- ([tableName, table]: [string, SqlTableIR]) => {
944
+ toSchemaView(schema: SqlSchemaIRNode): CoreSchemaView {
945
+ // Walk the schema-IR tree's own structure (root → namespaces → tables)
946
+ // into one flat list of table nodes. A root that exposes a `namespaces`
947
+ // record (Postgres) contributes each namespace's tables; a flat root
948
+ // (SQLite) is its own single namespace. The single-schema common case
949
+ // renders the same table-level view as today — no synthetic namespace
950
+ // level.
951
+ const root = blindCast<
952
+ {
953
+ readonly namespaces?: Readonly<
954
+ Record<string, { readonly tables: Record<string, SqlTableIR> }>
955
+ >;
956
+ readonly tables?: Record<string, SqlTableIR>;
957
+ },
958
+ 'structural read of the schema-IR tree own namespaces/tables records'
959
+ >(schema);
960
+ // A multi-namespace root qualifies every table's display name with its
961
+ // namespace key, so same-named tables in different namespaces (e.g.
962
+ // public.thing and auth.thing) render distinct ids and labels. The
963
+ // single-namespace case (and a flat SQLite root) keeps today's bare
964
+ // names. Synthesized constraint/index fallback names keep the bare table
965
+ // name — that is the name the database derives them from.
966
+ const namespaceEntries: ReadonlyArray<[string | undefined, Record<string, SqlTableIR>]> =
967
+ root.namespaces !== undefined
968
+ ? Object.entries(root.namespaces).map(
969
+ ([namespaceKey, namespace]): [string, Record<string, SqlTableIR>] => [
970
+ namespaceKey,
971
+ namespace.tables,
972
+ ],
973
+ )
974
+ : [[undefined, root.tables ?? {}]];
975
+ const qualify = namespaceEntries.length > 1;
976
+ const tableEntries: ReadonlyArray<[string, string, SqlTableIR]> = namespaceEntries.flatMap(
977
+ ([namespaceKey, tables]) =>
978
+ Object.entries(tables).map(([tableName, table]): [string, string, SqlTableIR] => [
979
+ qualify && namespaceKey !== undefined ? `${namespaceKey}.${tableName}` : tableName,
980
+ tableName,
981
+ table,
982
+ ]),
983
+ );
984
+ const tableNodes: readonly SchemaTreeNode[] = tableEntries.map(
985
+ ([displayName, tableName, table]: [string, string, SqlTableIR]) => {
918
986
  const children: SchemaTreeNode[] = [];
919
987
 
920
988
  const columnNodes: SchemaTreeNode[] = [];
@@ -925,7 +993,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
925
993
  columnNodes.push(
926
994
  new SchemaTreeNode({
927
995
  kind: 'field',
928
- id: `column-${tableName}-${columnName}`,
996
+ id: `column-${displayName}-${columnName}`,
929
997
  label,
930
998
  meta: {
931
999
  nativeType: column.nativeType,
@@ -940,7 +1008,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
940
1008
  children.push(
941
1009
  new SchemaTreeNode({
942
1010
  kind: 'collection',
943
- id: `columns-${tableName}`,
1011
+ id: `columns-${displayName}`,
944
1012
  label: 'columns',
945
1013
  children: columnNodes,
946
1014
  }),
@@ -952,7 +1020,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
952
1020
  children.push(
953
1021
  new SchemaTreeNode({
954
1022
  kind: 'index',
955
- id: `primary-key-${tableName}`,
1023
+ id: `primary-key-${displayName}`,
956
1024
  label: `primary key: ${pkColumns}`,
957
1025
  meta: {
958
1026
  columns: table.primaryKey.columns,
@@ -968,7 +1036,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
968
1036
  children.push(
969
1037
  new SchemaTreeNode({
970
1038
  kind: 'index',
971
- id: `unique-${tableName}-${name}`,
1039
+ id: `unique-${displayName}-${name}`,
972
1040
  label,
973
1041
  meta: {
974
1042
  columns: unique.columns,
@@ -984,7 +1052,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
984
1052
  children.push(
985
1053
  new SchemaTreeNode({
986
1054
  kind: 'index',
987
- id: `index-${tableName}-${name}`,
1055
+ id: `index-${displayName}-${name}`,
988
1056
  label,
989
1057
  meta: {
990
1058
  columns: index.columns,
@@ -1012,8 +1080,8 @@ export function createSqlFamilyInstance<TTargetId extends string>(
1012
1080
 
1013
1081
  return new SchemaTreeNode({
1014
1082
  kind: 'entity',
1015
- id: `table-${tableName}`,
1016
- label: `table ${tableName}`,
1083
+ id: `table-${displayName}`,
1084
+ label: `table ${displayName}`,
1017
1085
  ...(Object.keys(tableMeta).length > 0 ? { meta: tableMeta } : {}),
1018
1086
  ...(children.length > 0 ? { children } : {}),
1019
1087
  });
@@ -1033,8 +1101,8 @@ export function createSqlFamilyInstance<TTargetId extends string>(
1033
1101
  }
1034
1102
 
1035
1103
  /**
1036
- * Filters schema diff issues (from `collectSchemaDiffIssues`) through the
1037
- * contract's `defaultControlPolicy`. Issues whose outcome maps to a suppressed
1104
+ * Filters the structural schema-diff issues (from `diffDatabaseSchema`) through
1105
+ * the contract's `defaultControlPolicy`. Issues whose outcome maps to a suppressed
1038
1106
  * category under the effective policy are removed. This mirrors the control-policy
1039
1107
  * filtering applied by `verifySqlSchema` for table/column-level findings.
1040
1108
  *