@prisma-next/family-sql 0.12.0 → 0.13.0-dev.1

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 (58) hide show
  1. package/dist/{authoring-type-constructors-F4JpCJl7.mjs → authoring-type-constructors-D4lQ-qpj.mjs} +1 -1
  2. package/dist/{authoring-type-constructors-F4JpCJl7.mjs.map → authoring-type-constructors-D4lQ-qpj.mjs.map} +1 -1
  3. package/dist/control-adapter-CgIL9Vtx.d.mts +182 -0
  4. package/dist/control-adapter-CgIL9Vtx.d.mts.map +1 -0
  5. package/dist/control-adapter.d.mts +2 -109
  6. package/dist/control.d.mts +132 -4
  7. package/dist/control.d.mts.map +1 -1
  8. package/dist/control.mjs +277 -215
  9. package/dist/control.mjs.map +1 -1
  10. package/dist/ir.d.mts +4 -5
  11. package/dist/ir.d.mts.map +1 -1
  12. package/dist/ir.mjs +1 -1
  13. package/dist/migration.d.mts +1 -1
  14. package/dist/migration.d.mts.map +1 -1
  15. package/dist/pack.mjs +1 -1
  16. package/dist/runtime.d.mts +4 -2
  17. package/dist/runtime.d.mts.map +1 -1
  18. package/dist/runtime.mjs +4 -2
  19. package/dist/runtime.mjs.map +1 -1
  20. package/dist/schema-verify.d.mts +2 -1
  21. package/dist/schema-verify.d.mts.map +1 -1
  22. package/dist/schema-verify.mjs +1 -1
  23. package/dist/{sql-contract-serializer-8axtK4lg.mjs → sql-contract-serializer-CY7qnms7.mjs} +18 -36
  24. package/dist/sql-contract-serializer-CY7qnms7.mjs.map +1 -0
  25. package/dist/{timestamp-now-generator-r7BP5n3l.mjs → timestamp-now-generator-CloimujU.mjs} +2 -1
  26. package/dist/{timestamp-now-generator-r7BP5n3l.mjs.map → timestamp-now-generator-CloimujU.mjs.map} +1 -1
  27. package/dist/{types-CeeCStqw.d.mts → types-CbwQCzXY.d.mts} +70 -16
  28. package/dist/types-CbwQCzXY.d.mts.map +1 -0
  29. package/dist/{verify-Crewz6hG.mjs → verify-C-G0obRm.mjs} +1 -1
  30. package/dist/{verify-Crewz6hG.mjs.map → verify-C-G0obRm.mjs.map} +1 -1
  31. package/dist/{verify-sql-schema-CN7pPoTC.d.mts → verify-sql-schema-DcMaT5Zj.d.mts} +1 -1
  32. package/dist/{verify-sql-schema-CN7pPoTC.d.mts.map → verify-sql-schema-DcMaT5Zj.d.mts.map} +1 -1
  33. package/dist/{verify-sql-schema-CYLsGCFO.mjs → verify-sql-schema-DlAgBiT_.mjs} +756 -319
  34. package/dist/verify-sql-schema-DlAgBiT_.mjs.map +1 -0
  35. package/dist/verify.mjs +1 -1
  36. package/package.json +23 -23
  37. package/src/core/control-adapter.ts +116 -7
  38. package/src/core/control-instance.ts +269 -66
  39. package/src/core/default-namespace.ts +9 -0
  40. package/src/core/ir/sql-contract-serializer-base.ts +72 -56
  41. package/src/core/migrations/contract-to-schema-ir.ts +75 -9
  42. package/src/core/migrations/control-policy.ts +322 -0
  43. package/src/core/migrations/field-event-planner.ts +2 -2
  44. package/src/core/migrations/plan-helpers.ts +16 -0
  45. package/src/core/migrations/types.ts +17 -7
  46. package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +8 -6
  47. package/src/core/schema-verify/control-verify-emit.ts +46 -0
  48. package/src/core/schema-verify/verifier-disposition.ts +58 -0
  49. package/src/core/schema-verify/verify-helpers.ts +310 -111
  50. package/src/core/schema-verify/verify-sql-schema.ts +309 -178
  51. package/src/core/timestamp-now-generator.ts +1 -0
  52. package/src/exports/control-adapter.ts +5 -1
  53. package/src/exports/control.ts +7 -0
  54. package/src/exports/runtime.ts +7 -0
  55. package/dist/control-adapter.d.mts.map +0 -1
  56. package/dist/sql-contract-serializer-8axtK4lg.mjs.map +0 -1
  57. package/dist/types-CeeCStqw.d.mts.map +0 -1
  58. package/dist/verify-sql-schema-CYLsGCFO.mjs.map +0 -1
@@ -1,7 +1,10 @@
1
- import { assertUniqueCodecOwner } from "@prisma-next/framework-components/control";
1
+ import { assertUniqueCodecOwner, dispositionForCategory } from "@prisma-next/framework-components/control";
2
+ import { defaultIndexName } from "@prisma-next/sql-schema-ir/naming";
2
3
  import { ifDefined } from "@prisma-next/utils/defined";
3
4
  import { UNBOUND_NAMESPACE_ID } from "@prisma-next/framework-components/ir";
4
- import { StorageTable, isPostgresEnumStorageEntry, isStorageTypeInstance } from "@prisma-next/sql-contract/types";
5
+ import { StorageTable, isPostgresEnumStorageEntry, isStorageTypeInstance, toStorageTypeInstance } from "@prisma-next/sql-contract/types";
6
+ import { blindCast } from "@prisma-next/utils/casts";
7
+ import { effectiveControlPolicy } from "@prisma-next/contract/types";
5
8
  import { canonicalStringify } from "@prisma-next/utils/canonical-stringify";
6
9
  //#region src/core/assembly.ts
7
10
  function hasCodecControlHooks(descriptor) {
@@ -31,6 +34,310 @@ function extractCodecControlHooks(descriptors) {
31
34
  return hooks;
32
35
  }
33
36
  //#endregion
37
+ //#region src/core/migrations/contract-to-schema-ir.ts
38
+ function convertColumn(name, column, storageTypes, expandNativeType, renderDefault) {
39
+ const resolved = resolveColumnTypeMetadata(column, storageTypes);
40
+ return {
41
+ name,
42
+ nativeType: expandNativeType ? expandNativeType({
43
+ nativeType: resolved.nativeType,
44
+ codecId: resolved.codecId,
45
+ ...ifDefined("typeParams", resolved.typeParams)
46
+ }) : resolved.nativeType,
47
+ nullable: column.nullable,
48
+ ...ifDefined("default", column.default != null && renderDefault ? renderDefault(column.default, column) : void 0)
49
+ };
50
+ }
51
+ function resolveColumnTypeMetadata(column, storageTypes) {
52
+ if (!column.typeRef) return column;
53
+ const referenced = storageTypes[column.typeRef];
54
+ if (!referenced) throw new Error(`Column references storage type "${column.typeRef}" but it is not defined in storage.types.`);
55
+ if (isPostgresEnumStorageEntry(referenced)) return {
56
+ codecId: referenced.codecId,
57
+ nativeType: referenced.nativeType,
58
+ typeParams: { values: referenced.values }
59
+ };
60
+ if (isStorageTypeInstance(referenced)) return {
61
+ codecId: referenced.codecId,
62
+ nativeType: referenced.nativeType,
63
+ typeParams: referenced.typeParams
64
+ };
65
+ throw new Error(`Storage type "${column.typeRef}" has an unknown polymorphic kind; expected codec-instance or postgres-enum.`);
66
+ }
67
+ /**
68
+ * Resolves a `ValueSetRef` to its permitted values from the contract storage.
69
+ *
70
+ * Throws when the referenced namespace or value-set is absent — this indicates
71
+ * the contract was built incorrectly (the check and the value-set must be
72
+ * co-emitted by the lowering step). Used by `convertCheck` (schema-IR
73
+ * projection), `verifyCheckConstraints` (verification), and
74
+ * `checkConstraintPlanCallStrategy` (migration planning) so all three agree on
75
+ * the resolved values and the error behavior on a missing reference.
76
+ */
77
+ function resolveValueSetValues(ref, storage, contextLabel) {
78
+ const ns = storage.namespaces[ref.namespaceId];
79
+ if (!ns) throw new Error(`resolveValueSetValues: namespace "${ref.namespaceId}" not found in storage (${contextLabel})`);
80
+ const valueSet = ns.entries.valueSet?.[ref.name];
81
+ if (!valueSet) throw new Error(`resolveValueSetValues: value-set "${ref.name}" not found in namespace "${ref.namespaceId}" (${contextLabel})`);
82
+ return valueSet.values;
83
+ }
84
+ /**
85
+ * Projects a `CheckConstraint` IR into an `SqlCheckConstraintIRInput` by
86
+ * resolving the permitted values from the storage value-set it references.
87
+ *
88
+ * The `CheckConstraint.valueSet` ref points to
89
+ * `storage.namespaces[namespaceId].entries.valueSet[name]`. The resolved
90
+ * values are lifted directly from `StorageValueSet.values` so verification
91
+ * compares value sets, not SQL predicate strings.
92
+ *
93
+ * Throws if the referenced namespace or value-set is absent — this
94
+ * indicates the contract was built incorrectly (the check and the
95
+ * value-set must be co-emitted by the lowering step).
96
+ */
97
+ function convertCheck(check, storage) {
98
+ const permittedValues = resolveValueSetValues(check.valueSet, storage, `check "${check.name}"`);
99
+ return {
100
+ name: check.name,
101
+ column: check.column,
102
+ permittedValues
103
+ };
104
+ }
105
+ function convertUnique(unique) {
106
+ return {
107
+ columns: unique.columns,
108
+ ...ifDefined("name", unique.name)
109
+ };
110
+ }
111
+ function convertIndex(index) {
112
+ return {
113
+ columns: index.columns,
114
+ unique: false,
115
+ ...ifDefined("name", index.name)
116
+ };
117
+ }
118
+ function convertForeignKey(fk) {
119
+ return {
120
+ columns: fk.source.columns,
121
+ referencedTable: fk.target.tableName,
122
+ referencedSchema: fk.target.namespaceId,
123
+ referencedColumns: fk.target.columns,
124
+ ...ifDefined("name", fk.name),
125
+ ...ifDefined("onDelete", fk.onDelete),
126
+ ...ifDefined("onUpdate", fk.onUpdate)
127
+ };
128
+ }
129
+ function convertTable(name, table, storageTypes, expandNativeType, renderDefault, storage) {
130
+ const columns = {};
131
+ for (const [colName, colDef] of Object.entries(table.columns)) columns[colName] = convertColumn(colName, colDef, storageTypes, expandNativeType, renderDefault);
132
+ const satisfiedIndexColumns = new Set([
133
+ ...table.indexes.map((idx) => idx.columns.join(",")),
134
+ ...table.uniques.map((unique) => unique.columns.join(",")),
135
+ ...table.primaryKey ? [table.primaryKey.columns.join(",")] : []
136
+ ]);
137
+ const fkBackingIndexes = [];
138
+ for (const fk of table.foreignKeys) {
139
+ if (fk.index === false) continue;
140
+ const key = fk.source.columns.join(",");
141
+ if (satisfiedIndexColumns.has(key)) continue;
142
+ fkBackingIndexes.push({
143
+ columns: fk.source.columns,
144
+ unique: false,
145
+ name: defaultIndexName(name, fk.source.columns)
146
+ });
147
+ satisfiedIndexColumns.add(key);
148
+ }
149
+ const checks = table.checks && table.checks.length > 0 ? table.checks.map((c) => convertCheck(c, storage)) : void 0;
150
+ return {
151
+ name,
152
+ columns,
153
+ ...ifDefined("primaryKey", table.primaryKey),
154
+ foreignKeys: table.foreignKeys.filter((fk) => fk.constraint !== false).map(convertForeignKey),
155
+ uniques: table.uniques.map(convertUnique),
156
+ indexes: [...table.indexes.map(convertIndex), ...fkBackingIndexes],
157
+ ...ifDefined("checks", checks)
158
+ };
159
+ }
160
+ /**
161
+ * Detects destructive changes between two contract storages.
162
+ *
163
+ * The additive-only planner silently ignores removals (tables, columns).
164
+ * This function detects those removals so callers can report them as conflicts
165
+ * rather than silently producing an empty plan.
166
+ *
167
+ * Returns an empty array if no destructive changes are found.
168
+ */
169
+ function detectDestructiveChanges(from, to) {
170
+ if (!from) return [];
171
+ const hasOwn = (value, key) => Object.hasOwn(value, key);
172
+ const conflicts = [];
173
+ const namespaceIds = [...new Set([...Object.keys(from.namespaces), ...Object.keys(to.namespaces)])].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
174
+ for (const namespaceId of namespaceIds) {
175
+ const fromNs = from.namespaces[namespaceId];
176
+ const toNs = to.namespaces[namespaceId];
177
+ const fromTables = fromNs?.entries.table;
178
+ if (!fromTables) continue;
179
+ for (const tableName of Object.keys(fromTables)) {
180
+ const toTableRaw = toNs?.entries.table[tableName];
181
+ if (!(toTableRaw instanceof StorageTable)) {
182
+ conflicts.push({
183
+ kind: "tableRemoved",
184
+ summary: `Table "${tableName}" was removed`
185
+ });
186
+ continue;
187
+ }
188
+ const toTable = toTableRaw;
189
+ const fromTableRaw = fromTables[tableName];
190
+ if (!(fromTableRaw instanceof StorageTable)) continue;
191
+ const fromTable = fromTableRaw;
192
+ for (const columnName of Object.keys(fromTable.columns)) if (!hasOwn(toTable.columns, columnName)) conflicts.push({
193
+ kind: "columnRemoved",
194
+ summary: `Column "${tableName}"."${columnName}" was removed`
195
+ });
196
+ }
197
+ }
198
+ return conflicts;
199
+ }
200
+ /**
201
+ * Converts a `Contract` to `SqlSchemaIR`.
202
+ *
203
+ * Reads `contract.storage` for tables and `contract.storage.types` for type
204
+ * annotations. Storage-type annotations are written under
205
+ * `options.annotationNamespace`.
206
+ *
207
+ * Drops codec metadata (`codecId`, `typeRef`) since the schema IR only represents
208
+ * structural information. When `expandNativeType` is provided, parameterized types
209
+ * are expanded (e.g. `character` + `{ length: 36 }` → `character(36)`) so the
210
+ * resulting IR compares correctly against the "to" contract during planning.
211
+ *
212
+ * Returns an empty schema IR when `contract` is `null` (new project).
213
+ */
214
+ function contractToSchemaIR(contract, options) {
215
+ if (options.annotationNamespace.length === 0) throw new Error("annotationNamespace must be a non-empty string");
216
+ if (!contract) return { tables: {} };
217
+ const storage = contract.storage;
218
+ const allTypes = { ...storage.types ?? {} };
219
+ for (const ns of Object.values(storage.namespaces)) {
220
+ const nsEnums = ns.entries["type"];
221
+ if (nsEnums) for (const [k, v] of Object.entries(nsEnums)) allTypes[k] = blindCast(v);
222
+ }
223
+ const storageTypes = allTypes;
224
+ const tables = {};
225
+ for (const ns of Object.values(storage.namespaces)) for (const [tableName, tableDefRaw] of Object.entries(ns.entries.table)) {
226
+ if (!(tableDefRaw instanceof StorageTable)) throw new Error(`contractToSchemaIR: expected StorageTable at namespaces.${ns.id}.entries.table.${tableName}`);
227
+ const tableDef = tableDefRaw;
228
+ if (tables[tableName] !== void 0) throw new Error(`contractToSchemaIR: duplicate SQL table name "${tableName}" across namespaces (ambiguous for flat SqlSchemaIR.tables).`);
229
+ tables[tableName] = convertTable(tableName, tableDef, storageTypes, options.expandNativeType, options.renderDefault, storage);
230
+ }
231
+ return {
232
+ tables,
233
+ ...ifDefined("annotations", deriveAnnotations(storage, options.annotationNamespace, options.resolveEnumStorageKey))
234
+ };
235
+ }
236
+ /**
237
+ * Normalises a native enum storage entry to the codec-typed annotation shape
238
+ * `{codecId, nativeType, typeParams}` the introspector writes and
239
+ * `readExistingEnumValues` reads (`existing.codecId` + `existing.typeParams.values`).
240
+ * Without this the projector would emit the raw `PostgresEnumStorageEntry`
241
+ * shape (top-level `values`, no `typeParams`) and the enum would read as new.
242
+ */
243
+ function normalizeEnumAnnotation(entry) {
244
+ return toStorageTypeInstance({
245
+ codecId: entry.codecId,
246
+ nativeType: entry.nativeType,
247
+ typeParams: { values: entry.values }
248
+ });
249
+ }
250
+ function deriveAnnotations(storage, annotationNamespace, resolveEnumStorageKey) {
251
+ const storageTypes = {};
252
+ for (const typeInstance of Object.values(storage.types ?? {})) {
253
+ if (isPostgresEnumStorageEntry(typeInstance)) {
254
+ const key = resolveEnumStorageKey ? resolveEnumStorageKey(storage, UNBOUND_NAMESPACE_ID, typeInstance.nativeType) : typeInstance.nativeType;
255
+ storageTypes[key] = normalizeEnumAnnotation(typeInstance);
256
+ continue;
257
+ }
258
+ if (isStorageTypeInstance(typeInstance)) storageTypes[typeInstance.nativeType] = typeInstance;
259
+ }
260
+ for (const [namespaceId, ns] of Object.entries(storage.namespaces)) {
261
+ const nsEnums = ns.entries["type"];
262
+ if (!nsEnums) continue;
263
+ for (const entry of Object.values(nsEnums)) {
264
+ if (!isPostgresEnumStorageEntry(entry)) continue;
265
+ const key = resolveEnumStorageKey ? resolveEnumStorageKey(storage, namespaceId, entry.nativeType) : entry.nativeType;
266
+ storageTypes[key] = normalizeEnumAnnotation(entry);
267
+ }
268
+ }
269
+ if (Object.keys(storageTypes).length === 0) return void 0;
270
+ return { [annotationNamespace]: { storageTypes } };
271
+ }
272
+ //#endregion
273
+ //#region src/core/schema-verify/verifier-disposition.ts
274
+ /**
275
+ * Classifies the relational verifier issue kinds the SQL family emits (tables,
276
+ * columns, constraints, indexes, defaults, enum types) into the target-neutral
277
+ * categories the framework grades. The relational vocabulary lives here, in the
278
+ * SQL domain — the framework never switches over `extra_foreign_key` and friends.
279
+ */
280
+ function classifySqlVerifierIssueKind(kind) {
281
+ switch (kind) {
282
+ case "extra_column": return "extraNestedElement";
283
+ case "extra_primary_key":
284
+ case "extra_foreign_key":
285
+ case "extra_unique_constraint":
286
+ case "extra_index":
287
+ case "extra_validator":
288
+ case "extra_default": return "extraAuxiliary";
289
+ case "extra_table": return "extraTopLevelObject";
290
+ case "missing_schema":
291
+ case "missing_table":
292
+ case "missing_column":
293
+ case "type_missing":
294
+ case "default_missing": return "declaredMissing";
295
+ case "type_values_mismatch":
296
+ case "enum_values_changed":
297
+ case "check_mismatch": return "valueDrift";
298
+ case "type_mismatch":
299
+ case "nullability_mismatch":
300
+ case "primary_key_mismatch":
301
+ case "foreign_key_mismatch":
302
+ case "unique_constraint_mismatch":
303
+ case "index_mismatch":
304
+ case "default_mismatch": return "declaredIncompatible";
305
+ case "check_missing": return "declaredMissing";
306
+ case "check_removed": return "extraAuxiliary";
307
+ }
308
+ }
309
+ function verifierDisposition(controlPolicy, issueKind) {
310
+ return dispositionForCategory(controlPolicy, classifySqlVerifierIssueKind(issueKind));
311
+ }
312
+ //#endregion
313
+ //#region src/core/schema-verify/control-verify-emit.ts
314
+ /**
315
+ * Grades `issue` under `controlPolicy` and, unless suppressed, pushes both the
316
+ * issue and a status-stamped verification node. Returns the resolved outcome so
317
+ * the caller never re-grades the same issue.
318
+ */
319
+ function emitIssueAndNodeUnderControlPolicy(controlPolicy, issue, node, issues, nodes) {
320
+ const disposition = verifierDisposition(controlPolicy, issue.kind);
321
+ if (disposition === "suppress") return disposition;
322
+ issues.push(issue);
323
+ nodes.push({
324
+ ...node,
325
+ status: disposition
326
+ });
327
+ return disposition;
328
+ }
329
+ /**
330
+ * Grades `issue` under `controlPolicy` and, unless suppressed, pushes the issue
331
+ * (no verification node). Returns the resolved outcome so the caller maps it to
332
+ * a node status itself without re-grading.
333
+ */
334
+ function emitIssueUnderControlPolicy(controlPolicy, issue, issues) {
335
+ const disposition = verifierDisposition(controlPolicy, issue.kind);
336
+ if (disposition === "suppress") return disposition;
337
+ issues.push(issue);
338
+ return disposition;
339
+ }
340
+ //#endregion
34
341
  //#region src/core/schema-verify/verify-helpers.ts
35
342
  function indexOptionsLooselyEqual(a, b) {
36
343
  const aKeys = a ? Object.keys(a).sort() : [];
@@ -92,27 +399,27 @@ function isIndexSatisfied(indexes, uniques, columns) {
92
399
  * Uses semantic satisfaction: identity is based on (table + kind + columns).
93
400
  * Name differences are ignored by default (names are for DDL/diagnostics, not identity).
94
401
  */
95
- function verifyPrimaryKey(contractPK, schemaPK, tableName, namespaceId, issues) {
402
+ function verifyPrimaryKey(contractPK, schemaPK, tableName, namespaceId, tableControlPolicy, issues) {
96
403
  if (!schemaPK) {
97
- issues.push({
404
+ const outcome = emitIssueUnderControlPolicy(tableControlPolicy, {
98
405
  kind: "primary_key_mismatch",
99
406
  table: tableName,
100
407
  namespaceId,
101
408
  expected: contractPK.columns.join(", "),
102
409
  message: `Table "${tableName}" is missing primary key`
103
- });
104
- return "fail";
410
+ }, issues);
411
+ return outcome === "suppress" ? "pass" : outcome;
105
412
  }
106
413
  if (!arraysEqual(contractPK.columns, schemaPK.columns)) {
107
- issues.push({
414
+ const outcome = emitIssueUnderControlPolicy(tableControlPolicy, {
108
415
  kind: "primary_key_mismatch",
109
416
  table: tableName,
110
417
  namespaceId,
111
418
  expected: contractPK.columns.join(", "),
112
419
  actual: schemaPK.columns.join(", "),
113
420
  message: `Table "${tableName}" has primary key mismatch: expected columns [${contractPK.columns.join(", ")}], got [${schemaPK.columns.join(", ")}]`
114
- });
115
- return "fail";
421
+ }, issues);
422
+ return outcome === "suppress" ? "pass" : outcome;
116
423
  }
117
424
  return "pass";
118
425
  }
@@ -123,7 +430,7 @@ function verifyPrimaryKey(contractPK, schemaPK, tableName, namespaceId, issues)
123
430
  * Uses semantic satisfaction: identity is based on (table + columns + referenced table + referenced columns).
124
431
  * Name differences are ignored by default (names are for DDL/diagnostics, not identity).
125
432
  */
126
- function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, tablePath, issues, strict) {
433
+ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict) {
127
434
  const nodes = [];
128
435
  for (const contractFK of contractFKs) {
129
436
  const fkPath = `${tablePath}.foreignKeys[${contractFK.source.columns.join(",")}]`;
@@ -131,32 +438,30 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
131
438
  const tablesMatch = fk.referencedSchema !== void 0 && contractFK.target.namespaceId !== UNBOUND_NAMESPACE_ID ? fk.referencedSchema === contractFK.target.namespaceId && fk.referencedTable === contractFK.target.tableName : fk.referencedTable === contractFK.target.tableName;
132
439
  return arraysEqual(fk.columns, contractFK.source.columns) && tablesMatch && arraysEqual(fk.referencedColumns, contractFK.target.columns);
133
440
  });
134
- if (!matchingFK) {
135
- issues.push({
136
- kind: "foreign_key_mismatch",
137
- table: tableName,
138
- namespaceId,
139
- expected: `${contractFK.source.columns.join(", ")} -> ${contractFK.target.tableName}(${contractFK.target.columns.join(", ")})`,
140
- message: `Table "${tableName}" is missing foreign key: ${contractFK.source.columns.join(", ")} -> ${contractFK.target.tableName}(${contractFK.target.columns.join(", ")})`
141
- });
142
- nodes.push({
143
- status: "fail",
144
- kind: "foreignKey",
145
- name: `foreignKey(${contractFK.source.columns.join(", ")})`,
146
- contractPath: fkPath,
147
- code: "foreign_key_mismatch",
148
- message: "Foreign key missing",
149
- expected: contractFK,
150
- actual: void 0,
151
- children: []
152
- });
153
- } else {
441
+ if (!matchingFK) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
442
+ kind: "foreign_key_mismatch",
443
+ table: tableName,
444
+ namespaceId,
445
+ expected: `${contractFK.source.columns.join(", ")} -> ${contractFK.target.tableName}(${contractFK.target.columns.join(", ")})`,
446
+ message: `Table "${tableName}" is missing foreign key: ${contractFK.source.columns.join(", ")} -> ${contractFK.target.tableName}(${contractFK.target.columns.join(", ")})`
447
+ }, {
448
+ status: "fail",
449
+ kind: "foreignKey",
450
+ name: `foreignKey(${contractFK.source.columns.join(", ")})`,
451
+ contractPath: fkPath,
452
+ code: "foreign_key_mismatch",
453
+ message: "Foreign key missing",
454
+ expected: contractFK,
455
+ actual: void 0,
456
+ children: []
457
+ }, issues, nodes);
458
+ else {
154
459
  const actionMismatches = getReferentialActionMismatches(contractFK, matchingFK);
155
460
  if (actionMismatches.length > 0) {
156
461
  const combinedMessage = actionMismatches.map((m) => m.message).join("; ");
157
462
  const combinedExpected = actionMismatches.map((m) => m.expected).join(", ");
158
463
  const combinedActual = actionMismatches.map((m) => m.actual).join(", ");
159
- issues.push({
464
+ emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
160
465
  kind: "foreign_key_mismatch",
161
466
  table: tableName,
162
467
  namespaceId,
@@ -164,8 +469,7 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
164
469
  expected: combinedExpected,
165
470
  actual: combinedActual,
166
471
  message: `Table "${tableName}" foreign key ${contractFK.source.columns.join(", ")} -> ${contractFK.target.tableName}: ${combinedMessage}`
167
- });
168
- nodes.push({
472
+ }, {
169
473
  status: "fail",
170
474
  kind: "foreignKey",
171
475
  name: `foreignKey(${contractFK.source.columns.join(", ")})`,
@@ -175,7 +479,7 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
175
479
  expected: contractFK,
176
480
  actual: matchingFK,
177
481
  children: []
178
- });
482
+ }, issues, nodes);
179
483
  } else nodes.push({
180
484
  status: "pass",
181
485
  kind: "foreignKey",
@@ -193,26 +497,23 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
193
497
  for (const schemaFK of schemaFKs) if (!contractFKs.find((fk) => {
194
498
  const tablesMatch = schemaFK.referencedSchema !== void 0 && fk.target.namespaceId !== UNBOUND_NAMESPACE_ID ? schemaFK.referencedSchema === fk.target.namespaceId && schemaFK.referencedTable === fk.target.tableName : schemaFK.referencedTable === fk.target.tableName;
195
499
  return arraysEqual(fk.source.columns, schemaFK.columns) && tablesMatch && arraysEqual(fk.target.columns, schemaFK.referencedColumns);
196
- })) {
197
- issues.push({
198
- kind: "extra_foreign_key",
199
- table: tableName,
200
- namespaceId,
201
- indexOrConstraint: schemaFK.name ?? `fk(${schemaFK.columns.join(",")})`,
202
- message: `Extra foreign key found in database (not in contract): ${schemaFK.columns.join(", ")} -> ${schemaFK.referencedTable}(${schemaFK.referencedColumns.join(", ")})`
203
- });
204
- nodes.push({
205
- status: "fail",
206
- kind: "foreignKey",
207
- name: `foreignKey(${schemaFK.columns.join(", ")})`,
208
- contractPath: `${tablePath}.foreignKeys[${schemaFK.columns.join(",")}]`,
209
- code: "extra_foreign_key",
210
- message: "Extra foreign key found",
211
- expected: void 0,
212
- actual: schemaFK,
213
- children: []
214
- });
215
- }
500
+ })) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
501
+ kind: "extra_foreign_key",
502
+ table: tableName,
503
+ namespaceId,
504
+ indexOrConstraint: schemaFK.name ?? `fk(${schemaFK.columns.join(",")})`,
505
+ message: `Extra foreign key found in database (not in contract): ${schemaFK.columns.join(", ")} -> ${schemaFK.referencedTable}(${schemaFK.referencedColumns.join(", ")})`
506
+ }, {
507
+ status: "fail",
508
+ kind: "foreignKey",
509
+ name: `foreignKey(${schemaFK.columns.join(", ")})`,
510
+ contractPath: `${tablePath}.foreignKeys[${schemaFK.columns.join(",")}]`,
511
+ code: "extra_foreign_key",
512
+ message: "Extra foreign key found",
513
+ expected: void 0,
514
+ actual: schemaFK,
515
+ children: []
516
+ }, issues, nodes);
216
517
  }
217
518
  return nodes;
218
519
  }
@@ -227,32 +528,30 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
227
528
  *
228
529
  * Name differences are ignored by default (names are for DDL/diagnostics, not identity).
229
530
  */
230
- function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes, tableName, namespaceId, tablePath, issues, strict) {
531
+ function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict) {
231
532
  const nodes = [];
232
533
  for (const contractUnique of contractUniques) {
233
534
  const uniquePath = `${tablePath}.uniques[${contractUnique.columns.join(",")}]`;
234
535
  const matchingUnique = schemaUniques.find((u) => arraysEqual(u.columns, contractUnique.columns));
235
536
  const matchingUniqueIndex = !matchingUnique && schemaIndexes.find((idx) => idx.unique && arraysEqual(idx.columns, contractUnique.columns));
236
- if (!matchingUnique && !matchingUniqueIndex) {
237
- issues.push({
238
- kind: "unique_constraint_mismatch",
239
- table: tableName,
240
- namespaceId,
241
- expected: contractUnique.columns.join(", "),
242
- message: `Table "${tableName}" is missing unique constraint: ${contractUnique.columns.join(", ")}`
243
- });
244
- nodes.push({
245
- status: "fail",
246
- kind: "unique",
247
- name: `unique(${contractUnique.columns.join(", ")})`,
248
- contractPath: uniquePath,
249
- code: "unique_constraint_mismatch",
250
- message: "Unique constraint missing",
251
- expected: contractUnique,
252
- actual: void 0,
253
- children: []
254
- });
255
- } else nodes.push({
537
+ if (!matchingUnique && !matchingUniqueIndex) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
538
+ kind: "unique_constraint_mismatch",
539
+ table: tableName,
540
+ namespaceId,
541
+ expected: contractUnique.columns.join(", "),
542
+ message: `Table "${tableName}" is missing unique constraint: ${contractUnique.columns.join(", ")}`
543
+ }, {
544
+ status: "fail",
545
+ kind: "unique",
546
+ name: `unique(${contractUnique.columns.join(", ")})`,
547
+ contractPath: uniquePath,
548
+ code: "unique_constraint_mismatch",
549
+ message: "Unique constraint missing",
550
+ expected: contractUnique,
551
+ actual: void 0,
552
+ children: []
553
+ }, issues, nodes);
554
+ else nodes.push({
256
555
  status: "pass",
257
556
  kind: "unique",
258
557
  name: `unique(${contractUnique.columns.join(", ")})`,
@@ -265,26 +564,23 @@ function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes,
265
564
  });
266
565
  }
267
566
  if (strict) {
268
- for (const schemaUnique of schemaUniques) if (!contractUniques.find((u) => arraysEqual(u.columns, schemaUnique.columns))) {
269
- issues.push({
270
- kind: "extra_unique_constraint",
271
- table: tableName,
272
- namespaceId,
273
- indexOrConstraint: schemaUnique.name ?? `unique(${schemaUnique.columns.join(",")})`,
274
- message: `Extra unique constraint found in database (not in contract): ${schemaUnique.columns.join(", ")}`
275
- });
276
- nodes.push({
277
- status: "fail",
278
- kind: "unique",
279
- name: `unique(${schemaUnique.columns.join(", ")})`,
280
- contractPath: `${tablePath}.uniques[${schemaUnique.columns.join(",")}]`,
281
- code: "extra_unique_constraint",
282
- message: "Extra unique constraint found",
283
- expected: void 0,
284
- actual: schemaUnique,
285
- children: []
286
- });
287
- }
567
+ for (const schemaUnique of schemaUniques) if (!contractUniques.find((u) => arraysEqual(u.columns, schemaUnique.columns))) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
568
+ kind: "extra_unique_constraint",
569
+ table: tableName,
570
+ namespaceId,
571
+ indexOrConstraint: schemaUnique.name ?? `unique(${schemaUnique.columns.join(",")})`,
572
+ message: `Extra unique constraint found in database (not in contract): ${schemaUnique.columns.join(", ")}`
573
+ }, {
574
+ status: "fail",
575
+ kind: "unique",
576
+ name: `unique(${schemaUnique.columns.join(", ")})`,
577
+ contractPath: `${tablePath}.uniques[${schemaUnique.columns.join(",")}]`,
578
+ code: "extra_unique_constraint",
579
+ message: "Extra unique constraint found",
580
+ expected: void 0,
581
+ actual: schemaUnique,
582
+ children: []
583
+ }, issues, nodes);
288
584
  }
289
585
  return nodes;
290
586
  }
@@ -299,32 +595,30 @@ function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes,
299
595
  *
300
596
  * Name differences are ignored by default (names are for DDL/diagnostics, not identity).
301
597
  */
302
- function verifyIndexes(contractIndexes, schemaIndexes, schemaUniques, tableName, namespaceId, tablePath, issues, strict) {
598
+ function verifyIndexes(contractIndexes, schemaIndexes, schemaUniques, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict) {
303
599
  const nodes = [];
304
600
  for (const contractIndex of contractIndexes) {
305
601
  const indexPath = `${tablePath}.indexes[${contractIndex.columns.join(",")}]`;
306
602
  const matchingIndex = schemaIndexes.find((idx) => arraysEqual(idx.columns, contractIndex.columns) && indexExtrasMatch(contractIndex, idx));
307
603
  const matchingUniqueConstraint = !matchingIndex && contractIndex.type === void 0 && contractIndex.options === void 0 && schemaUniques.find((u) => arraysEqual(u.columns, contractIndex.columns));
308
- if (!matchingIndex && !matchingUniqueConstraint) {
309
- issues.push({
310
- kind: "index_mismatch",
311
- table: tableName,
312
- namespaceId,
313
- expected: contractIndex.columns.join(", "),
314
- message: `Table "${tableName}" is missing index: ${contractIndex.columns.join(", ")}`
315
- });
316
- nodes.push({
317
- status: "fail",
318
- kind: "index",
319
- name: `index(${contractIndex.columns.join(", ")})`,
320
- contractPath: indexPath,
321
- code: "index_mismatch",
322
- message: "Index missing",
323
- expected: contractIndex,
324
- actual: void 0,
325
- children: []
326
- });
327
- } else nodes.push({
604
+ if (!matchingIndex && !matchingUniqueConstraint) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
605
+ kind: "index_mismatch",
606
+ table: tableName,
607
+ namespaceId,
608
+ expected: contractIndex.columns.join(", "),
609
+ message: `Table "${tableName}" is missing index: ${contractIndex.columns.join(", ")}`
610
+ }, {
611
+ status: "fail",
612
+ kind: "index",
613
+ name: `index(${contractIndex.columns.join(", ")})`,
614
+ contractPath: indexPath,
615
+ code: "index_mismatch",
616
+ message: "Index missing",
617
+ expected: contractIndex,
618
+ actual: void 0,
619
+ children: []
620
+ }, issues, nodes);
621
+ else nodes.push({
328
622
  status: "pass",
329
623
  kind: "index",
330
624
  name: `index(${contractIndex.columns.join(", ")})`,
@@ -338,26 +632,23 @@ function verifyIndexes(contractIndexes, schemaIndexes, schemaUniques, tableName,
338
632
  }
339
633
  if (strict) for (const schemaIndex of schemaIndexes) {
340
634
  if (schemaIndex.unique) continue;
341
- if (!contractIndexes.find((idx) => arraysEqual(idx.columns, schemaIndex.columns) && indexExtrasMatch(idx, schemaIndex))) {
342
- issues.push({
343
- kind: "extra_index",
344
- table: tableName,
345
- namespaceId,
346
- indexOrConstraint: schemaIndex.name ?? `idx(${schemaIndex.columns.join(",")})`,
347
- message: `Extra index found in database (not in contract): ${schemaIndex.columns.join(", ")}`
348
- });
349
- nodes.push({
350
- status: "fail",
351
- kind: "index",
352
- name: `index(${schemaIndex.columns.join(", ")})`,
353
- contractPath: `${tablePath}.indexes[${schemaIndex.columns.join(",")}]`,
354
- code: "extra_index",
355
- message: "Extra index found",
356
- expected: void 0,
357
- actual: schemaIndex,
358
- children: []
359
- });
360
- }
635
+ if (!contractIndexes.find((idx) => arraysEqual(idx.columns, schemaIndex.columns) && indexExtrasMatch(idx, schemaIndex))) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
636
+ kind: "extra_index",
637
+ table: tableName,
638
+ namespaceId,
639
+ indexOrConstraint: schemaIndex.name ?? `idx(${schemaIndex.columns.join(",")})`,
640
+ message: `Extra index found in database (not in contract): ${schemaIndex.columns.join(", ")}`
641
+ }, {
642
+ status: "fail",
643
+ kind: "index",
644
+ name: `index(${schemaIndex.columns.join(", ")})`,
645
+ contractPath: `${tablePath}.indexes[${schemaIndex.columns.join(",")}]`,
646
+ code: "extra_index",
647
+ message: "Extra index found",
648
+ expected: void 0,
649
+ actual: schemaIndex,
650
+ children: []
651
+ }, issues, nodes);
361
652
  }
362
653
  return nodes;
363
654
  }
@@ -416,6 +707,110 @@ function getReferentialActionMismatches(contractFK, schemaFK) {
416
707
  function normalizeReferentialAction(action) {
417
708
  return action === "noAction" ? void 0 : action;
418
709
  }
710
+ /**
711
+ * Compares two value arrays as unordered sets.
712
+ * Returns true when both sides contain exactly the same values.
713
+ */
714
+ function valueSetsEqual(a, b) {
715
+ const aSet = new Set(a);
716
+ const bSet = new Set(b);
717
+ if (aSet.size !== bSet.size) return false;
718
+ return [...aSet].every((v) => bSet.has(v));
719
+ }
720
+ /**
721
+ * Verifies check constraints match between contract-projected checks and
722
+ * introspected live checks.
723
+ *
724
+ * Comparison is value-set-based, not SQL-string-based. Postgres rewrites
725
+ * `col IN ('a','b')` as `col = ANY (ARRAY['a','b'])` in
726
+ * `pg_get_constraintdef`, so comparing the extracted value sets (after
727
+ * the introspection adapter parses the predicate) avoids false mismatches
728
+ * from the `IN`-vs-`= ANY (ARRAY…)` rendering difference.
729
+ *
730
+ * Issues emitted:
731
+ * - `check_missing` — check expected by contract but absent from live DB
732
+ * - `check_removed` — check present in live DB but not in contract
733
+ * - `check_mismatch` — check present on both sides but permitted values differ
734
+ *
735
+ * `check_removed` is emitted only when `strict` is true so non-strict
736
+ * verification (the normal path) does not complain about extra constraints.
737
+ */
738
+ function verifyCheckConstraints(contractChecks, schemaChecks, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict) {
739
+ const nodes = [];
740
+ for (const contractCheck of contractChecks) {
741
+ const checkPath = `${tablePath}.checks[${contractCheck.name}]`;
742
+ const liveCheck = schemaChecks.find((c) => c.name === contractCheck.name);
743
+ if (!liveCheck) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
744
+ kind: "check_missing",
745
+ table: tableName,
746
+ namespaceId,
747
+ indexOrConstraint: contractCheck.name,
748
+ expected: contractCheck.permittedValues.join(", "),
749
+ message: `Table "${tableName}" is missing check constraint "${contractCheck.name}" (column "${contractCheck.column}" IN (${contractCheck.permittedValues.join(", ")}))`
750
+ }, {
751
+ status: "fail",
752
+ kind: "checkConstraint",
753
+ name: `check(${contractCheck.name})`,
754
+ contractPath: checkPath,
755
+ code: "check_missing",
756
+ message: `Check constraint "${contractCheck.name}" missing`,
757
+ expected: contractCheck,
758
+ actual: void 0,
759
+ children: []
760
+ }, issues, nodes);
761
+ else if (!valueSetsEqual(contractCheck.permittedValues, liveCheck.permittedValues)) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
762
+ kind: "check_mismatch",
763
+ table: tableName,
764
+ namespaceId,
765
+ indexOrConstraint: contractCheck.name,
766
+ expected: contractCheck.permittedValues.join(", "),
767
+ actual: liveCheck.permittedValues.join(", "),
768
+ message: `Table "${tableName}" check constraint "${contractCheck.name}" has different permitted values: expected [${contractCheck.permittedValues.join(", ")}], got [${liveCheck.permittedValues.join(", ")}]`
769
+ }, {
770
+ status: "fail",
771
+ kind: "checkConstraint",
772
+ name: `check(${contractCheck.name})`,
773
+ contractPath: checkPath,
774
+ code: "check_mismatch",
775
+ message: `Check constraint "${contractCheck.name}" values mismatch`,
776
+ expected: contractCheck,
777
+ actual: liveCheck,
778
+ children: []
779
+ }, issues, nodes);
780
+ else nodes.push({
781
+ status: "pass",
782
+ kind: "checkConstraint",
783
+ name: `check(${contractCheck.name})`,
784
+ contractPath: checkPath,
785
+ code: "",
786
+ message: "",
787
+ expected: void 0,
788
+ actual: void 0,
789
+ children: []
790
+ });
791
+ }
792
+ if (strict) {
793
+ for (const liveCheck of schemaChecks) if (!contractChecks.find((c) => c.name === liveCheck.name)) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
794
+ kind: "check_removed",
795
+ table: tableName,
796
+ namespaceId,
797
+ indexOrConstraint: liveCheck.name,
798
+ actual: liveCheck.permittedValues.join(", "),
799
+ message: `Table "${tableName}" has extra check constraint "${liveCheck.name}" in database (not in contract)`
800
+ }, {
801
+ status: "fail",
802
+ kind: "checkConstraint",
803
+ name: `check(${liveCheck.name})`,
804
+ contractPath: `${tablePath}.checks[${liveCheck.name}]`,
805
+ code: "check_removed",
806
+ message: `Extra check constraint "${liveCheck.name}" found`,
807
+ expected: void 0,
808
+ actual: liveCheck,
809
+ children: []
810
+ }, issues, nodes);
811
+ }
812
+ return nodes;
813
+ }
419
814
  //#endregion
420
815
  //#region src/core/schema-verify/verify-sql-schema.ts
421
816
  /**
@@ -435,7 +830,7 @@ function verifySqlSchema(options) {
435
830
  const { contractStorageHash, contractProfileHash, contractTarget } = extractContractMetadata(contract);
436
831
  const allStorageTypesMap = { ...contract.storage.types ?? {} };
437
832
  for (const ns of Object.values(contract.storage.namespaces)) {
438
- const nsEnums = ns.enum;
833
+ const nsEnums = blindCast(ns.entries).type;
439
834
  if (nsEnums) for (const [k, v] of Object.entries(nsEnums)) allStorageTypesMap[k] = v;
440
835
  }
441
836
  const { issues, rootChildren } = verifySchemaTables({
@@ -450,53 +845,57 @@ function verifySqlSchema(options) {
450
845
  });
451
846
  validateFrameworkComponentsForExtensions(contract, options.frameworkComponents);
452
847
  const typeNodes = [];
453
- const pushTypeNode = (typeName, contractPath, typeIssues) => {
454
- if (typeIssues.length > 0) issues.push(...typeIssues);
848
+ const pushTypeNode = (typeName, contractPath, typeIssues, controlPolicy) => {
849
+ let status = "pass";
850
+ let code = "";
851
+ let emitted = 0;
852
+ for (const issue of typeIssues) {
853
+ const disposition = verifierDisposition(controlPolicy, issue.kind);
854
+ if (disposition === "suppress") continue;
855
+ issues.push(issue);
856
+ emitted += 1;
857
+ if (code === "") code = issue.kind;
858
+ if (disposition === "fail") status = "fail";
859
+ else if (disposition === "warn" && status !== "fail") status = "warn";
860
+ }
455
861
  typeNodes.push({
456
- status: typeIssues.length > 0 ? "fail" : "pass",
862
+ status,
457
863
  kind: "storageType",
458
864
  name: `type ${typeName}`,
459
865
  contractPath,
460
- code: typeIssues.length > 0 ? typeIssues[0]?.kind ?? "" : "",
461
- message: typeIssues.length > 0 ? `${typeIssues.length} issue${typeIssues.length === 1 ? "" : "s"}` : "",
866
+ code: status === "pass" ? "" : code,
867
+ message: emitted > 0 ? `${emitted} issue${emitted === 1 ? "" : "s"}` : "",
462
868
  expected: void 0,
463
869
  actual: void 0,
464
870
  children: []
465
871
  });
466
872
  };
467
- for (const [typeName, typeInstance] of Object.entries(contract.storage.types ?? {})) if (isPostgresEnumStorageEntry(typeInstance)) pushTypeNode(typeName, `storage.types.${typeName}`, verifyEnumType({
468
- typeName,
469
- typeInstance,
470
- schema,
471
- resolveExistingEnumValues,
472
- namespaceId: UNBOUND_NAMESPACE_ID
473
- }));
474
- else if (isStorageTypeInstance(typeInstance)) {
873
+ for (const [typeName, typeInstance] of Object.entries(contract.storage.types ?? {})) if (isStorageTypeInstance(typeInstance)) {
475
874
  const hook = codecHooks.get(typeInstance.codecId);
476
875
  pushTypeNode(typeName, `storage.types.${typeName}`, hook?.verifyType ? hook.verifyType({
477
876
  typeName,
478
877
  typeInstance,
479
878
  schema
480
- }) : []);
879
+ }) : [], effectiveControlPolicy(void 0, contract.defaultControlPolicy));
481
880
  }
482
881
  for (const nsId of Object.keys(contract.storage.namespaces)) {
483
882
  const ns = contract.storage.namespaces[nsId];
484
883
  if (!ns) continue;
485
- const nsEnums = ns.enum;
884
+ const nsEnums = ns.entries["type"];
486
885
  if (!nsEnums) continue;
487
886
  for (const [typeName, entry] of Object.entries(nsEnums)) {
488
887
  if (!isPostgresEnumStorageEntry(entry)) continue;
489
- pushTypeNode(typeName, `storage.namespaces.${nsId}.enum.${typeName}`, verifyEnumType({
888
+ pushTypeNode(typeName, `storage.namespaces.${nsId}.entries.type.${typeName}`, verifyEnumType({
490
889
  typeName,
491
890
  typeInstance: entry,
492
891
  schema,
493
892
  resolveExistingEnumValues,
494
893
  namespaceId: nsId
495
- }));
894
+ }), effectiveControlPolicy(entry.control, contract.defaultControlPolicy));
496
895
  }
497
896
  }
498
897
  if (typeNodes.length > 0) {
499
- const typesStatus = typeNodes.some((n) => n.status === "fail") ? "fail" : "pass";
898
+ const typesStatus = typeNodes.some((n) => n.status === "fail") ? "fail" : typeNodes.some((n) => n.status === "warn") ? "warn" : "pass";
500
899
  rootChildren.push({
501
900
  status: typesStatus,
502
901
  kind: "storageTypes",
@@ -581,6 +980,7 @@ function extractContractMetadata(contract) {
581
980
  }
582
981
  function verifySchemaTables(options) {
583
982
  const { contract, schema, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
983
+ const contractDefaultControl = contract.defaultControlPolicy;
584
984
  const issues = [];
585
985
  const rootChildren = [];
586
986
  const schemaTables = schema.tables;
@@ -588,19 +988,19 @@ function verifySchemaTables(options) {
588
988
  for (const namespaceId of namespaceIds) {
589
989
  const ns = contract.storage.namespaces[namespaceId];
590
990
  if (!ns) continue;
591
- for (const [tableName, contractTableRaw] of Object.entries(ns.tables)) {
592
- if (!(contractTableRaw instanceof StorageTable)) throw new Error(`verifySqlSchema: expected StorageTable at storage.namespaces.${namespaceId}.tables.${tableName}`);
991
+ for (const [tableName, contractTableRaw] of Object.entries(ns.entries.table)) {
992
+ if (!(contractTableRaw instanceof StorageTable)) throw new Error(`verifySqlSchema: expected StorageTable at storage.namespaces.${namespaceId}.entries.table.${tableName}`);
593
993
  const contractTable = contractTableRaw;
994
+ const tableControlPolicy = effectiveControlPolicy(contractTable.control, contractDefaultControl);
594
995
  const schemaTable = schemaTables[tableName];
595
- const tablePath = `storage.namespaces.${namespaceId}.tables.${tableName}`;
996
+ const tablePath = `storage.namespaces.${namespaceId}.entries.table.${tableName}`;
596
997
  if (!schemaTable) {
597
- issues.push({
998
+ emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
598
999
  kind: "missing_table",
599
1000
  table: tableName,
600
1001
  namespaceId,
601
1002
  message: `Table "${tableName}" is missing from database`
602
- });
603
- rootChildren.push({
1003
+ }, {
604
1004
  status: "fail",
605
1005
  kind: "table",
606
1006
  name: `table ${tableName}`,
@@ -610,7 +1010,7 @@ function verifySchemaTables(options) {
610
1010
  expected: void 0,
611
1011
  actual: void 0,
612
1012
  children: []
613
- });
1013
+ }, issues, rootChildren);
614
1014
  continue;
615
1015
  }
616
1016
  const tableChildren = verifyTableChildren({
@@ -619,11 +1019,13 @@ function verifySchemaTables(options) {
619
1019
  tableName,
620
1020
  namespaceId,
621
1021
  tablePath,
1022
+ tableControlPolicy,
622
1023
  issues,
623
1024
  strict,
624
1025
  typeMetadataRegistry,
625
1026
  codecHooks,
626
1027
  storageTypes,
1028
+ contractStorage: contract.storage,
627
1029
  ...ifDefined("normalizeDefault", normalizeDefault),
628
1030
  ...ifDefined("normalizeNativeType", normalizeNativeType)
629
1031
  });
@@ -631,24 +1033,21 @@ function verifySchemaTables(options) {
631
1033
  }
632
1034
  }
633
1035
  if (strict) {
634
- for (const tableName of Object.keys(schemaTables)) if (!namespaceIds.some((namespaceId) => contract.storage.namespaces[namespaceId]?.tables[tableName] !== void 0)) {
635
- issues.push({
636
- kind: "extra_table",
637
- table: tableName,
638
- message: `Extra table "${tableName}" found in database (not in contract)`
639
- });
640
- rootChildren.push({
641
- status: "fail",
642
- kind: "table",
643
- name: `table ${tableName}`,
644
- contractPath: `storage.namespaces.*.tables.${tableName}`,
645
- code: "extra_table",
646
- message: `Extra table "${tableName}" found`,
647
- expected: void 0,
648
- actual: void 0,
649
- children: []
650
- });
651
- }
1036
+ for (const tableName of Object.keys(schemaTables)) if (!namespaceIds.some((namespaceId) => contract.storage.namespaces[namespaceId]?.entries.table[tableName] !== void 0)) emitIssueAndNodeUnderControlPolicy(effectiveControlPolicy(void 0, contractDefaultControl), {
1037
+ kind: "extra_table",
1038
+ table: tableName,
1039
+ message: `Extra table "${tableName}" found in database (not in contract)`
1040
+ }, {
1041
+ status: "fail",
1042
+ kind: "table",
1043
+ name: `table ${tableName}`,
1044
+ contractPath: `storage.namespaces.*.entries.table.${tableName}`,
1045
+ code: "extra_table",
1046
+ message: `Extra table "${tableName}" found`,
1047
+ expected: void 0,
1048
+ actual: void 0,
1049
+ children: []
1050
+ }, issues, rootChildren);
652
1051
  }
653
1052
  return {
654
1053
  issues,
@@ -656,7 +1055,7 @@ function verifySchemaTables(options) {
656
1055
  };
657
1056
  }
658
1057
  function verifyTableChildren(options) {
659
- const { contractTable, schemaTable, tableName, namespaceId, tablePath, issues, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
1058
+ const { contractTable, schemaTable, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType, contractStorage } = options;
660
1059
  const tableChildren = [];
661
1060
  const columnNodes = collectContractColumnNodes({
662
1061
  contractTable,
@@ -664,6 +1063,7 @@ function verifyTableChildren(options) {
664
1063
  tableName,
665
1064
  namespaceId,
666
1065
  tablePath,
1066
+ tableControlPolicy,
667
1067
  issues,
668
1068
  strict,
669
1069
  typeMetadataRegistry,
@@ -679,77 +1079,96 @@ function verifyTableChildren(options) {
679
1079
  tableName,
680
1080
  namespaceId,
681
1081
  tablePath,
1082
+ tableControlPolicy,
682
1083
  issues,
683
1084
  columnNodes
684
1085
  });
685
- if (contractTable.primaryKey) if (verifyPrimaryKey(contractTable.primaryKey, schemaTable.primaryKey, tableName, namespaceId, issues) === "fail") tableChildren.push({
686
- status: "fail",
687
- kind: "primaryKey",
688
- name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
689
- contractPath: `${tablePath}.primaryKey`,
690
- code: "primary_key_mismatch",
691
- message: "Primary key mismatch",
692
- expected: contractTable.primaryKey,
693
- actual: schemaTable.primaryKey,
694
- children: []
695
- });
696
- else tableChildren.push({
697
- status: "pass",
698
- kind: "primaryKey",
699
- name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
700
- contractPath: `${tablePath}.primaryKey`,
701
- code: "",
702
- message: "",
703
- expected: void 0,
704
- actual: void 0,
705
- children: []
706
- });
707
- else if (schemaTable.primaryKey && strict) {
708
- issues.push({
709
- kind: "extra_primary_key",
710
- table: tableName,
711
- namespaceId,
712
- message: "Extra primary key found in database (not in contract)"
713
- });
714
- tableChildren.push({
1086
+ if (contractTable.primaryKey) {
1087
+ const pkStatus = verifyPrimaryKey(contractTable.primaryKey, schemaTable.primaryKey, tableName, namespaceId, tableControlPolicy, issues);
1088
+ if (pkStatus === "fail") tableChildren.push({
715
1089
  status: "fail",
716
1090
  kind: "primaryKey",
717
- name: `primary key: ${schemaTable.primaryKey.columns.join(", ")}`,
1091
+ name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
718
1092
  contractPath: `${tablePath}.primaryKey`,
719
- code: "extra_primary_key",
720
- message: "Extra primary key found",
721
- expected: void 0,
1093
+ code: "primary_key_mismatch",
1094
+ message: "Primary key mismatch",
1095
+ expected: contractTable.primaryKey,
722
1096
  actual: schemaTable.primaryKey,
723
1097
  children: []
724
1098
  });
725
- }
1099
+ else if (pkStatus === "warn") tableChildren.push({
1100
+ status: "warn",
1101
+ kind: "primaryKey",
1102
+ name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
1103
+ contractPath: `${tablePath}.primaryKey`,
1104
+ code: "primary_key_mismatch",
1105
+ message: "Primary key mismatch",
1106
+ expected: contractTable.primaryKey,
1107
+ actual: schemaTable.primaryKey,
1108
+ children: []
1109
+ });
1110
+ else tableChildren.push({
1111
+ status: "pass",
1112
+ kind: "primaryKey",
1113
+ name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
1114
+ contractPath: `${tablePath}.primaryKey`,
1115
+ code: "",
1116
+ message: "",
1117
+ expected: void 0,
1118
+ actual: void 0,
1119
+ children: []
1120
+ });
1121
+ } else if (schemaTable.primaryKey && strict) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
1122
+ kind: "extra_primary_key",
1123
+ table: tableName,
1124
+ namespaceId,
1125
+ message: "Extra primary key found in database (not in contract)"
1126
+ }, {
1127
+ status: "fail",
1128
+ kind: "primaryKey",
1129
+ name: `primary key: ${schemaTable.primaryKey.columns.join(", ")}`,
1130
+ contractPath: `${tablePath}.primaryKey`,
1131
+ code: "extra_primary_key",
1132
+ message: "Extra primary key found",
1133
+ expected: void 0,
1134
+ actual: schemaTable.primaryKey,
1135
+ children: []
1136
+ }, issues, tableChildren);
726
1137
  const constraintFks = contractTable.foreignKeys.filter((fk) => fk.constraint === true);
727
1138
  if (constraintFks.length > 0 || strict) {
728
- const fkStatuses = verifyForeignKeys(constraintFks, schemaTable.foreignKeys, tableName, namespaceId, tablePath, issues, strict);
1139
+ const fkStatuses = verifyForeignKeys(constraintFks, schemaTable.foreignKeys, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict);
729
1140
  tableChildren.push(...fkStatuses);
730
1141
  }
731
- const uniqueStatuses = verifyUniqueConstraints(contractTable.uniques, schemaTable.uniques, schemaTable.indexes, tableName, namespaceId, tablePath, issues, strict);
1142
+ const uniqueStatuses = verifyUniqueConstraints(contractTable.uniques, schemaTable.uniques, schemaTable.indexes, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict);
732
1143
  tableChildren.push(...uniqueStatuses);
733
1144
  const fkBackingIndexes = contractTable.foreignKeys.filter((fk) => fk.index === true && !contractTable.indexes.some((idx) => arraysEqual(idx.columns, fk.source.columns))).map((fk) => ({ columns: fk.source.columns }));
734
- const indexStatuses = verifyIndexes([...contractTable.indexes, ...fkBackingIndexes], schemaTable.indexes, schemaTable.uniques, tableName, namespaceId, tablePath, issues, strict);
1145
+ const indexStatuses = verifyIndexes([...contractTable.indexes, ...fkBackingIndexes], schemaTable.indexes, schemaTable.uniques, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict);
735
1146
  tableChildren.push(...indexStatuses);
1147
+ const contractCheckIRs = (contractTable.checks ?? []).map((c) => ({
1148
+ name: c.name,
1149
+ column: c.column,
1150
+ permittedValues: resolveValueSetValues(c.valueSet, contractStorage, `check "${c.name}"`)
1151
+ }));
1152
+ if (strict || contractCheckIRs.length > 0) {
1153
+ const checkStatuses = verifyCheckConstraints(contractCheckIRs, schemaTable.checks ?? [], tableName, namespaceId, tablePath, tableControlPolicy, issues, strict);
1154
+ tableChildren.push(...checkStatuses);
1155
+ }
736
1156
  return tableChildren;
737
1157
  }
738
1158
  function collectContractColumnNodes(options) {
739
- const { contractTable, schemaTable, tableName, namespaceId, tablePath, issues, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
1159
+ const { contractTable, schemaTable, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
740
1160
  const columnNodes = [];
741
1161
  for (const [columnName, contractColumn] of Object.entries(contractTable.columns)) {
742
1162
  const schemaColumn = schemaTable.columns[columnName];
743
1163
  const columnPath = `${tablePath}.columns.${columnName}`;
744
1164
  if (!schemaColumn) {
745
- issues.push({
1165
+ emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
746
1166
  kind: "missing_column",
747
1167
  table: tableName,
748
1168
  namespaceId,
749
1169
  column: columnName,
750
1170
  message: `Column "${tableName}"."${columnName}" is missing from database`
751
- });
752
- columnNodes.push({
1171
+ }, {
753
1172
  status: "fail",
754
1173
  kind: "column",
755
1174
  name: `${columnName}: missing`,
@@ -759,7 +1178,7 @@ function collectContractColumnNodes(options) {
759
1178
  expected: void 0,
760
1179
  actual: void 0,
761
1180
  children: []
762
- });
1181
+ }, issues, columnNodes);
763
1182
  continue;
764
1183
  }
765
1184
  columnNodes.push(verifyColumn({
@@ -769,6 +1188,7 @@ function collectContractColumnNodes(options) {
769
1188
  contractColumn,
770
1189
  schemaColumn,
771
1190
  columnPath,
1191
+ tableControlPolicy,
772
1192
  issues,
773
1193
  strict,
774
1194
  typeMetadataRegistry,
@@ -781,30 +1201,27 @@ function collectContractColumnNodes(options) {
781
1201
  return columnNodes;
782
1202
  }
783
1203
  function appendExtraColumnNodes(options) {
784
- const { contractTable, schemaTable, tableName, namespaceId, tablePath, issues, columnNodes } = options;
785
- for (const [columnName, { nativeType }] of Object.entries(schemaTable.columns)) if (!contractTable.columns[columnName]) {
786
- issues.push({
787
- kind: "extra_column",
788
- table: tableName,
789
- namespaceId,
790
- column: columnName,
791
- message: `Extra column "${tableName}"."${columnName}" found in database (not in contract)`
792
- });
793
- columnNodes.push({
794
- status: "fail",
795
- kind: "column",
796
- name: `${columnName}: extra`,
797
- contractPath: `${tablePath}.columns.${columnName}`,
798
- code: "extra_column",
799
- message: `Extra column "${columnName}" found`,
800
- expected: void 0,
801
- actual: nativeType,
802
- children: []
803
- });
804
- }
1204
+ const { contractTable, schemaTable, tableName, namespaceId, tablePath, tableControlPolicy, issues, columnNodes } = options;
1205
+ for (const [columnName, { nativeType }] of Object.entries(schemaTable.columns)) if (!contractTable.columns[columnName]) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
1206
+ kind: "extra_column",
1207
+ table: tableName,
1208
+ namespaceId,
1209
+ column: columnName,
1210
+ message: `Extra column "${tableName}"."${columnName}" found in database (not in contract)`
1211
+ }, {
1212
+ status: "fail",
1213
+ kind: "column",
1214
+ name: `${columnName}: extra`,
1215
+ contractPath: `${tablePath}.columns.${columnName}`,
1216
+ code: "extra_column",
1217
+ message: `Extra column "${columnName}" found`,
1218
+ expected: void 0,
1219
+ actual: nativeType,
1220
+ children: []
1221
+ }, issues, columnNodes);
805
1222
  }
806
1223
  function verifyColumn(options) {
807
- const { tableName, namespaceId, columnName, contractColumn, schemaColumn, columnPath, issues, strict, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
1224
+ const { tableName, namespaceId, columnName, contractColumn, schemaColumn, columnPath, tableControlPolicy, issues, strict, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
808
1225
  const columnChildren = [];
809
1226
  let columnStatus = "pass";
810
1227
  const resolvedContractColumn = resolveContractColumnTypeMetadata(contractColumn, storageTypes, {
@@ -816,8 +1233,8 @@ function verifyColumn(options) {
816
1233
  columnName
817
1234
  });
818
1235
  const schemaNativeType = normalizeNativeType?.(schemaColumn.nativeType) ?? schemaColumn.nativeType;
819
- if (contractNativeType !== schemaNativeType) {
820
- issues.push({
1236
+ if (!(contractNativeType === schemaNativeType)) {
1237
+ const issue = {
821
1238
  kind: "type_mismatch",
822
1239
  table: tableName,
823
1240
  namespaceId,
@@ -825,19 +1242,23 @@ function verifyColumn(options) {
825
1242
  expected: contractNativeType,
826
1243
  actual: schemaNativeType,
827
1244
  message: `Column "${tableName}"."${columnName}" has type mismatch: expected "${contractNativeType}", got "${schemaNativeType}"`
828
- });
829
- columnChildren.push({
830
- status: "fail",
831
- kind: "type",
832
- name: "type",
833
- contractPath: `${columnPath}.nativeType`,
834
- code: "type_mismatch",
835
- message: `Type mismatch: expected ${contractNativeType}, got ${schemaNativeType}`,
836
- expected: contractNativeType,
837
- actual: schemaNativeType,
838
- children: []
839
- });
840
- columnStatus = "fail";
1245
+ };
1246
+ const disposition = verifierDisposition(tableControlPolicy, issue.kind);
1247
+ if (disposition !== "suppress") {
1248
+ issues.push(issue);
1249
+ columnChildren.push({
1250
+ status: disposition,
1251
+ kind: "type",
1252
+ name: "type",
1253
+ contractPath: `${columnPath}.nativeType`,
1254
+ code: "type_mismatch",
1255
+ message: `Type mismatch: expected ${contractNativeType}, got ${schemaNativeType}`,
1256
+ expected: contractNativeType,
1257
+ actual: schemaNativeType,
1258
+ children: []
1259
+ });
1260
+ columnStatus = disposition;
1261
+ }
841
1262
  }
842
1263
  if (resolvedContractColumn.codecId) {
843
1264
  const typeMetadata = options.typeMetadataRegistry.get(resolvedContractColumn.codecId);
@@ -865,7 +1286,7 @@ function verifyColumn(options) {
865
1286
  });
866
1287
  }
867
1288
  if (contractColumn.nullable !== schemaColumn.nullable) {
868
- issues.push({
1289
+ const issue = {
869
1290
  kind: "nullability_mismatch",
870
1291
  table: tableName,
871
1292
  namespaceId,
@@ -873,47 +1294,55 @@ function verifyColumn(options) {
873
1294
  expected: String(contractColumn.nullable),
874
1295
  actual: String(schemaColumn.nullable),
875
1296
  message: `Column "${tableName}"."${columnName}" has nullability mismatch: expected ${contractColumn.nullable ? "nullable" : "not null"}, got ${schemaColumn.nullable ? "nullable" : "not null"}`
876
- });
877
- columnChildren.push({
878
- status: "fail",
879
- kind: "nullability",
880
- name: "nullability",
881
- contractPath: `${columnPath}.nullable`,
882
- code: "nullability_mismatch",
883
- message: `Nullability mismatch: expected ${contractColumn.nullable ? "nullable" : "not null"}, got ${schemaColumn.nullable ? "nullable" : "not null"}`,
884
- expected: contractColumn.nullable,
885
- actual: schemaColumn.nullable,
886
- children: []
887
- });
888
- columnStatus = "fail";
1297
+ };
1298
+ const disposition = verifierDisposition(tableControlPolicy, issue.kind);
1299
+ if (disposition !== "suppress") {
1300
+ issues.push(issue);
1301
+ columnChildren.push({
1302
+ status: disposition,
1303
+ kind: "nullability",
1304
+ name: "nullability",
1305
+ contractPath: `${columnPath}.nullable`,
1306
+ code: "nullability_mismatch",
1307
+ message: `Nullability mismatch: expected ${contractColumn.nullable ? "nullable" : "not null"}, got ${schemaColumn.nullable ? "nullable" : "not null"}`,
1308
+ expected: contractColumn.nullable,
1309
+ actual: schemaColumn.nullable,
1310
+ children: []
1311
+ });
1312
+ columnStatus = disposition;
1313
+ }
889
1314
  }
890
1315
  if (contractColumn.default) {
891
1316
  if (!schemaColumn.default) {
892
1317
  const defaultDescription = describeColumnDefault(contractColumn.default);
893
- issues.push({
1318
+ const issue = {
894
1319
  kind: "default_missing",
895
1320
  table: tableName,
896
1321
  namespaceId,
897
1322
  column: columnName,
898
1323
  expected: defaultDescription,
899
1324
  message: `Column "${tableName}"."${columnName}" should have default ${defaultDescription} but database has no default`
900
- });
901
- columnChildren.push({
902
- status: "fail",
903
- kind: "default",
904
- name: "default",
905
- contractPath: `${columnPath}.default`,
906
- code: "default_missing",
907
- message: `Default missing: expected ${defaultDescription}`,
908
- expected: defaultDescription,
909
- actual: void 0,
910
- children: []
911
- });
912
- columnStatus = "fail";
1325
+ };
1326
+ const disposition = verifierDisposition(tableControlPolicy, issue.kind);
1327
+ if (disposition !== "suppress") {
1328
+ issues.push(issue);
1329
+ columnChildren.push({
1330
+ status: disposition,
1331
+ kind: "default",
1332
+ name: "default",
1333
+ contractPath: `${columnPath}.default`,
1334
+ code: "default_missing",
1335
+ message: `Default missing: expected ${defaultDescription}`,
1336
+ expected: defaultDescription,
1337
+ actual: void 0,
1338
+ children: []
1339
+ });
1340
+ columnStatus = disposition;
1341
+ }
913
1342
  } else if (!columnDefaultsEqual(contractColumn.default, schemaColumn.default, normalizeDefault, schemaNativeType)) {
914
1343
  const expectedDescription = describeColumnDefault(contractColumn.default);
915
1344
  const actualDescription = schemaColumn.default;
916
- issues.push({
1345
+ const issue = {
917
1346
  kind: "default_mismatch",
918
1347
  table: tableName,
919
1348
  namespaceId,
@@ -921,41 +1350,49 @@ function verifyColumn(options) {
921
1350
  expected: expectedDescription,
922
1351
  actual: actualDescription,
923
1352
  message: `Column "${tableName}"."${columnName}" has default mismatch: expected ${expectedDescription}, got ${actualDescription}`
924
- });
925
- columnChildren.push({
926
- status: "fail",
927
- kind: "default",
928
- name: "default",
929
- contractPath: `${columnPath}.default`,
930
- code: "default_mismatch",
931
- message: `Default mismatch: expected ${expectedDescription}, got ${actualDescription}`,
932
- expected: expectedDescription,
933
- actual: actualDescription,
934
- children: []
935
- });
936
- columnStatus = "fail";
1353
+ };
1354
+ const disposition = verifierDisposition(tableControlPolicy, issue.kind);
1355
+ if (disposition !== "suppress") {
1356
+ issues.push(issue);
1357
+ columnChildren.push({
1358
+ status: disposition,
1359
+ kind: "default",
1360
+ name: "default",
1361
+ contractPath: `${columnPath}.default`,
1362
+ code: "default_mismatch",
1363
+ message: `Default mismatch: expected ${expectedDescription}, got ${actualDescription}`,
1364
+ expected: expectedDescription,
1365
+ actual: actualDescription,
1366
+ children: []
1367
+ });
1368
+ columnStatus = disposition;
1369
+ }
937
1370
  }
938
1371
  } else if (strict && schemaColumn.default) {
939
- issues.push({
1372
+ const issue = {
940
1373
  kind: "extra_default",
941
1374
  table: tableName,
942
1375
  namespaceId,
943
1376
  column: columnName,
944
1377
  actual: schemaColumn.default,
945
1378
  message: `Column "${tableName}"."${columnName}" has default ${schemaColumn.default} in database but contract specifies no default`
946
- });
947
- columnChildren.push({
948
- status: "fail",
949
- kind: "default",
950
- name: "default",
951
- contractPath: `${columnPath}.default`,
952
- code: "extra_default",
953
- message: `Extra default: ${schemaColumn.default}`,
954
- expected: void 0,
955
- actual: schemaColumn.default,
956
- children: []
957
- });
958
- columnStatus = "fail";
1379
+ };
1380
+ const disposition = verifierDisposition(tableControlPolicy, issue.kind);
1381
+ if (disposition !== "suppress") {
1382
+ issues.push(issue);
1383
+ columnChildren.push({
1384
+ status: disposition,
1385
+ kind: "default",
1386
+ name: "default",
1387
+ contractPath: `${columnPath}.default`,
1388
+ code: "extra_default",
1389
+ message: `Extra default: ${schemaColumn.default}`,
1390
+ expected: void 0,
1391
+ actual: schemaColumn.default,
1392
+ children: []
1393
+ });
1394
+ columnStatus = disposition;
1395
+ }
959
1396
  }
960
1397
  const aggregated = aggregateChildState(columnChildren, columnStatus);
961
1398
  const nullableText = contractColumn.nullable ? "nullable" : "not nullable";
@@ -1152,6 +1589,6 @@ function formatLiteralValue(value) {
1152
1589
  return JSON.stringify(value);
1153
1590
  }
1154
1591
  //#endregion
1155
- export { extractCodecControlHooks as a, isUniqueConstraintSatisfied as i, arraysEqual as n, isIndexSatisfied as r, verifySqlSchema as t };
1592
+ export { contractToSchemaIR as a, extractCodecControlHooks as c, isUniqueConstraintSatisfied as i, arraysEqual as n, detectDestructiveChanges as o, isIndexSatisfied as r, resolveValueSetValues as s, verifySqlSchema as t };
1156
1593
 
1157
- //# sourceMappingURL=verify-sql-schema-CYLsGCFO.mjs.map
1594
+ //# sourceMappingURL=verify-sql-schema-DlAgBiT_.mjs.map