@prisma-next/family-sql 0.12.0-dev.2 → 0.12.0-dev.20

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 (37) hide show
  1. package/dist/control-adapter-8tV9WgWK.d.mts +134 -0
  2. package/dist/control-adapter-8tV9WgWK.d.mts.map +1 -0
  3. package/dist/control-adapter.d.mts +2 -109
  4. package/dist/control.d.mts +31 -3
  5. package/dist/control.d.mts.map +1 -1
  6. package/dist/control.mjs +62 -14
  7. package/dist/control.mjs.map +1 -1
  8. package/dist/migration.d.mts +1 -1
  9. package/dist/runtime.d.mts +3 -1
  10. package/dist/runtime.d.mts.map +1 -1
  11. package/dist/runtime.mjs +3 -1
  12. package/dist/runtime.mjs.map +1 -1
  13. package/dist/schema-verify.d.mts +2 -1
  14. package/dist/schema-verify.d.mts.map +1 -1
  15. package/dist/schema-verify.mjs +1 -1
  16. package/dist/{types-CeeCStqw.d.mts → types-BQiqw6kR.d.mts} +14 -5
  17. package/dist/types-BQiqw6kR.d.mts.map +1 -0
  18. package/dist/{verify-sql-schema-CYLsGCFO.mjs → verify-sql-schema-CXW_D9dW.mjs} +403 -310
  19. package/dist/verify-sql-schema-CXW_D9dW.mjs.map +1 -0
  20. package/dist/{verify-sql-schema-CN7pPoTC.d.mts → verify-sql-schema-Cj3c2t5Z.d.mts} +8 -2
  21. package/dist/verify-sql-schema-Cj3c2t5Z.d.mts.map +1 -0
  22. package/package.json +21 -21
  23. package/src/core/control-adapter.ts +44 -3
  24. package/src/core/control-instance.ts +40 -41
  25. package/src/core/default-namespace.ts +9 -0
  26. package/src/core/migrations/control-policy.ts +89 -0
  27. package/src/core/migrations/types.ts +8 -1
  28. package/src/core/schema-verify/control-verify-emit.ts +46 -0
  29. package/src/core/schema-verify/verifier-disposition.ts +53 -0
  30. package/src/core/schema-verify/verify-helpers.ts +151 -110
  31. package/src/core/schema-verify/verify-sql-schema.ts +291 -155
  32. package/src/exports/control.ts +2 -0
  33. package/src/exports/runtime.ts +7 -0
  34. package/dist/control-adapter.d.mts.map +0 -1
  35. package/dist/types-CeeCStqw.d.mts.map +0 -1
  36. package/dist/verify-sql-schema-CN7pPoTC.d.mts.map +0 -1
  37. package/dist/verify-sql-schema-CYLsGCFO.mjs.map +0 -1
@@ -1,7 +1,8 @@
1
- import { assertUniqueCodecOwner } from "@prisma-next/framework-components/control";
1
+ import { assertUniqueCodecOwner, dispositionForCategory } from "@prisma-next/framework-components/control";
2
2
  import { ifDefined } from "@prisma-next/utils/defined";
3
3
  import { UNBOUND_NAMESPACE_ID } from "@prisma-next/framework-components/ir";
4
4
  import { StorageTable, isPostgresEnumStorageEntry, isStorageTypeInstance } from "@prisma-next/sql-contract/types";
5
+ import { effectiveControlPolicy } from "@prisma-next/contract/types";
5
6
  import { canonicalStringify } from "@prisma-next/utils/canonical-stringify";
6
7
  //#region src/core/assembly.ts
7
8
  function hasCodecControlHooks(descriptor) {
@@ -31,6 +32,71 @@ function extractCodecControlHooks(descriptors) {
31
32
  return hooks;
32
33
  }
33
34
  //#endregion
35
+ //#region src/core/schema-verify/verifier-disposition.ts
36
+ /**
37
+ * Classifies the relational verifier issue kinds the SQL family emits (tables,
38
+ * columns, constraints, indexes, defaults, enum types) into the target-neutral
39
+ * categories the framework grades. The relational vocabulary lives here, in the
40
+ * SQL domain — the framework never switches over `extra_foreign_key` and friends.
41
+ */
42
+ function classifySqlVerifierIssueKind(kind) {
43
+ switch (kind) {
44
+ case "extra_column": return "extraNestedElement";
45
+ case "extra_primary_key":
46
+ case "extra_foreign_key":
47
+ case "extra_unique_constraint":
48
+ case "extra_index":
49
+ case "extra_validator":
50
+ case "extra_default": return "extraAuxiliary";
51
+ case "extra_table": return "extraTopLevelObject";
52
+ case "missing_schema":
53
+ case "missing_table":
54
+ case "missing_column":
55
+ case "type_missing":
56
+ case "default_missing": return "declaredMissing";
57
+ case "type_values_mismatch":
58
+ case "enum_values_changed": return "valueDrift";
59
+ case "type_mismatch":
60
+ case "nullability_mismatch":
61
+ case "primary_key_mismatch":
62
+ case "foreign_key_mismatch":
63
+ case "unique_constraint_mismatch":
64
+ case "index_mismatch":
65
+ case "default_mismatch": return "declaredIncompatible";
66
+ }
67
+ }
68
+ function verifierDisposition(controlPolicy, issueKind) {
69
+ return dispositionForCategory(controlPolicy, classifySqlVerifierIssueKind(issueKind));
70
+ }
71
+ //#endregion
72
+ //#region src/core/schema-verify/control-verify-emit.ts
73
+ /**
74
+ * Grades `issue` under `controlPolicy` and, unless suppressed, pushes both the
75
+ * issue and a status-stamped verification node. Returns the resolved outcome so
76
+ * the caller never re-grades the same issue.
77
+ */
78
+ function emitIssueAndNodeUnderControlPolicy(controlPolicy, issue, node, issues, nodes) {
79
+ const disposition = verifierDisposition(controlPolicy, issue.kind);
80
+ if (disposition === "suppress") return disposition;
81
+ issues.push(issue);
82
+ nodes.push({
83
+ ...node,
84
+ status: disposition
85
+ });
86
+ return disposition;
87
+ }
88
+ /**
89
+ * Grades `issue` under `controlPolicy` and, unless suppressed, pushes the issue
90
+ * (no verification node). Returns the resolved outcome so the caller maps it to
91
+ * a node status itself without re-grading.
92
+ */
93
+ function emitIssueUnderControlPolicy(controlPolicy, issue, issues) {
94
+ const disposition = verifierDisposition(controlPolicy, issue.kind);
95
+ if (disposition === "suppress") return disposition;
96
+ issues.push(issue);
97
+ return disposition;
98
+ }
99
+ //#endregion
34
100
  //#region src/core/schema-verify/verify-helpers.ts
35
101
  function indexOptionsLooselyEqual(a, b) {
36
102
  const aKeys = a ? Object.keys(a).sort() : [];
@@ -92,27 +158,27 @@ function isIndexSatisfied(indexes, uniques, columns) {
92
158
  * Uses semantic satisfaction: identity is based on (table + kind + columns).
93
159
  * Name differences are ignored by default (names are for DDL/diagnostics, not identity).
94
160
  */
95
- function verifyPrimaryKey(contractPK, schemaPK, tableName, namespaceId, issues) {
161
+ function verifyPrimaryKey(contractPK, schemaPK, tableName, namespaceId, tableControlPolicy, issues) {
96
162
  if (!schemaPK) {
97
- issues.push({
163
+ const outcome = emitIssueUnderControlPolicy(tableControlPolicy, {
98
164
  kind: "primary_key_mismatch",
99
165
  table: tableName,
100
166
  namespaceId,
101
167
  expected: contractPK.columns.join(", "),
102
168
  message: `Table "${tableName}" is missing primary key`
103
- });
104
- return "fail";
169
+ }, issues);
170
+ return outcome === "suppress" ? "pass" : outcome;
105
171
  }
106
172
  if (!arraysEqual(contractPK.columns, schemaPK.columns)) {
107
- issues.push({
173
+ const outcome = emitIssueUnderControlPolicy(tableControlPolicy, {
108
174
  kind: "primary_key_mismatch",
109
175
  table: tableName,
110
176
  namespaceId,
111
177
  expected: contractPK.columns.join(", "),
112
178
  actual: schemaPK.columns.join(", "),
113
179
  message: `Table "${tableName}" has primary key mismatch: expected columns [${contractPK.columns.join(", ")}], got [${schemaPK.columns.join(", ")}]`
114
- });
115
- return "fail";
180
+ }, issues);
181
+ return outcome === "suppress" ? "pass" : outcome;
116
182
  }
117
183
  return "pass";
118
184
  }
@@ -123,7 +189,7 @@ function verifyPrimaryKey(contractPK, schemaPK, tableName, namespaceId, issues)
123
189
  * Uses semantic satisfaction: identity is based on (table + columns + referenced table + referenced columns).
124
190
  * Name differences are ignored by default (names are for DDL/diagnostics, not identity).
125
191
  */
126
- function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, tablePath, issues, strict) {
192
+ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict) {
127
193
  const nodes = [];
128
194
  for (const contractFK of contractFKs) {
129
195
  const fkPath = `${tablePath}.foreignKeys[${contractFK.source.columns.join(",")}]`;
@@ -131,32 +197,30 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
131
197
  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
198
  return arraysEqual(fk.columns, contractFK.source.columns) && tablesMatch && arraysEqual(fk.referencedColumns, contractFK.target.columns);
133
199
  });
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 {
200
+ if (!matchingFK) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
201
+ kind: "foreign_key_mismatch",
202
+ table: tableName,
203
+ namespaceId,
204
+ expected: `${contractFK.source.columns.join(", ")} -> ${contractFK.target.tableName}(${contractFK.target.columns.join(", ")})`,
205
+ message: `Table "${tableName}" is missing foreign key: ${contractFK.source.columns.join(", ")} -> ${contractFK.target.tableName}(${contractFK.target.columns.join(", ")})`
206
+ }, {
207
+ status: "fail",
208
+ kind: "foreignKey",
209
+ name: `foreignKey(${contractFK.source.columns.join(", ")})`,
210
+ contractPath: fkPath,
211
+ code: "foreign_key_mismatch",
212
+ message: "Foreign key missing",
213
+ expected: contractFK,
214
+ actual: void 0,
215
+ children: []
216
+ }, issues, nodes);
217
+ else {
154
218
  const actionMismatches = getReferentialActionMismatches(contractFK, matchingFK);
155
219
  if (actionMismatches.length > 0) {
156
220
  const combinedMessage = actionMismatches.map((m) => m.message).join("; ");
157
221
  const combinedExpected = actionMismatches.map((m) => m.expected).join(", ");
158
222
  const combinedActual = actionMismatches.map((m) => m.actual).join(", ");
159
- issues.push({
223
+ emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
160
224
  kind: "foreign_key_mismatch",
161
225
  table: tableName,
162
226
  namespaceId,
@@ -164,8 +228,7 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
164
228
  expected: combinedExpected,
165
229
  actual: combinedActual,
166
230
  message: `Table "${tableName}" foreign key ${contractFK.source.columns.join(", ")} -> ${contractFK.target.tableName}: ${combinedMessage}`
167
- });
168
- nodes.push({
231
+ }, {
169
232
  status: "fail",
170
233
  kind: "foreignKey",
171
234
  name: `foreignKey(${contractFK.source.columns.join(", ")})`,
@@ -175,7 +238,7 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
175
238
  expected: contractFK,
176
239
  actual: matchingFK,
177
240
  children: []
178
- });
241
+ }, issues, nodes);
179
242
  } else nodes.push({
180
243
  status: "pass",
181
244
  kind: "foreignKey",
@@ -193,26 +256,23 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
193
256
  for (const schemaFK of schemaFKs) if (!contractFKs.find((fk) => {
194
257
  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
258
  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
- }
259
+ })) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
260
+ kind: "extra_foreign_key",
261
+ table: tableName,
262
+ namespaceId,
263
+ indexOrConstraint: schemaFK.name ?? `fk(${schemaFK.columns.join(",")})`,
264
+ message: `Extra foreign key found in database (not in contract): ${schemaFK.columns.join(", ")} -> ${schemaFK.referencedTable}(${schemaFK.referencedColumns.join(", ")})`
265
+ }, {
266
+ status: "fail",
267
+ kind: "foreignKey",
268
+ name: `foreignKey(${schemaFK.columns.join(", ")})`,
269
+ contractPath: `${tablePath}.foreignKeys[${schemaFK.columns.join(",")}]`,
270
+ code: "extra_foreign_key",
271
+ message: "Extra foreign key found",
272
+ expected: void 0,
273
+ actual: schemaFK,
274
+ children: []
275
+ }, issues, nodes);
216
276
  }
217
277
  return nodes;
218
278
  }
@@ -227,32 +287,30 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
227
287
  *
228
288
  * Name differences are ignored by default (names are for DDL/diagnostics, not identity).
229
289
  */
230
- function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes, tableName, namespaceId, tablePath, issues, strict) {
290
+ function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict) {
231
291
  const nodes = [];
232
292
  for (const contractUnique of contractUniques) {
233
293
  const uniquePath = `${tablePath}.uniques[${contractUnique.columns.join(",")}]`;
234
294
  const matchingUnique = schemaUniques.find((u) => arraysEqual(u.columns, contractUnique.columns));
235
295
  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({
296
+ if (!matchingUnique && !matchingUniqueIndex) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
297
+ kind: "unique_constraint_mismatch",
298
+ table: tableName,
299
+ namespaceId,
300
+ expected: contractUnique.columns.join(", "),
301
+ message: `Table "${tableName}" is missing unique constraint: ${contractUnique.columns.join(", ")}`
302
+ }, {
303
+ status: "fail",
304
+ kind: "unique",
305
+ name: `unique(${contractUnique.columns.join(", ")})`,
306
+ contractPath: uniquePath,
307
+ code: "unique_constraint_mismatch",
308
+ message: "Unique constraint missing",
309
+ expected: contractUnique,
310
+ actual: void 0,
311
+ children: []
312
+ }, issues, nodes);
313
+ else nodes.push({
256
314
  status: "pass",
257
315
  kind: "unique",
258
316
  name: `unique(${contractUnique.columns.join(", ")})`,
@@ -265,26 +323,23 @@ function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes,
265
323
  });
266
324
  }
267
325
  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
- }
326
+ for (const schemaUnique of schemaUniques) if (!contractUniques.find((u) => arraysEqual(u.columns, schemaUnique.columns))) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
327
+ kind: "extra_unique_constraint",
328
+ table: tableName,
329
+ namespaceId,
330
+ indexOrConstraint: schemaUnique.name ?? `unique(${schemaUnique.columns.join(",")})`,
331
+ message: `Extra unique constraint found in database (not in contract): ${schemaUnique.columns.join(", ")}`
332
+ }, {
333
+ status: "fail",
334
+ kind: "unique",
335
+ name: `unique(${schemaUnique.columns.join(", ")})`,
336
+ contractPath: `${tablePath}.uniques[${schemaUnique.columns.join(",")}]`,
337
+ code: "extra_unique_constraint",
338
+ message: "Extra unique constraint found",
339
+ expected: void 0,
340
+ actual: schemaUnique,
341
+ children: []
342
+ }, issues, nodes);
288
343
  }
289
344
  return nodes;
290
345
  }
@@ -299,32 +354,30 @@ function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes,
299
354
  *
300
355
  * Name differences are ignored by default (names are for DDL/diagnostics, not identity).
301
356
  */
302
- function verifyIndexes(contractIndexes, schemaIndexes, schemaUniques, tableName, namespaceId, tablePath, issues, strict) {
357
+ function verifyIndexes(contractIndexes, schemaIndexes, schemaUniques, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict) {
303
358
  const nodes = [];
304
359
  for (const contractIndex of contractIndexes) {
305
360
  const indexPath = `${tablePath}.indexes[${contractIndex.columns.join(",")}]`;
306
361
  const matchingIndex = schemaIndexes.find((idx) => arraysEqual(idx.columns, contractIndex.columns) && indexExtrasMatch(contractIndex, idx));
307
362
  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({
363
+ if (!matchingIndex && !matchingUniqueConstraint) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
364
+ kind: "index_mismatch",
365
+ table: tableName,
366
+ namespaceId,
367
+ expected: contractIndex.columns.join(", "),
368
+ message: `Table "${tableName}" is missing index: ${contractIndex.columns.join(", ")}`
369
+ }, {
370
+ status: "fail",
371
+ kind: "index",
372
+ name: `index(${contractIndex.columns.join(", ")})`,
373
+ contractPath: indexPath,
374
+ code: "index_mismatch",
375
+ message: "Index missing",
376
+ expected: contractIndex,
377
+ actual: void 0,
378
+ children: []
379
+ }, issues, nodes);
380
+ else nodes.push({
328
381
  status: "pass",
329
382
  kind: "index",
330
383
  name: `index(${contractIndex.columns.join(", ")})`,
@@ -338,26 +391,23 @@ function verifyIndexes(contractIndexes, schemaIndexes, schemaUniques, tableName,
338
391
  }
339
392
  if (strict) for (const schemaIndex of schemaIndexes) {
340
393
  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
- }
394
+ if (!contractIndexes.find((idx) => arraysEqual(idx.columns, schemaIndex.columns) && indexExtrasMatch(idx, schemaIndex))) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
395
+ kind: "extra_index",
396
+ table: tableName,
397
+ namespaceId,
398
+ indexOrConstraint: schemaIndex.name ?? `idx(${schemaIndex.columns.join(",")})`,
399
+ message: `Extra index found in database (not in contract): ${schemaIndex.columns.join(", ")}`
400
+ }, {
401
+ status: "fail",
402
+ kind: "index",
403
+ name: `index(${schemaIndex.columns.join(", ")})`,
404
+ contractPath: `${tablePath}.indexes[${schemaIndex.columns.join(",")}]`,
405
+ code: "extra_index",
406
+ message: "Extra index found",
407
+ expected: void 0,
408
+ actual: schemaIndex,
409
+ children: []
410
+ }, issues, nodes);
361
411
  }
362
412
  return nodes;
363
413
  }
@@ -429,7 +479,7 @@ function normalizeReferentialAction(action) {
429
479
  * @returns VerifyDatabaseSchemaResult with verification tree and issues
430
480
  */
431
481
  function verifySqlSchema(options) {
432
- const { contract, schema, strict, context, typeMetadataRegistry, normalizeDefault, normalizeNativeType, resolveExistingEnumValues } = options;
482
+ const { contract, schema, strict, context, typeMetadataRegistry, normalizeDefault, normalizeNativeType, resolveExistingEnumValues, columnsCompatible } = options;
433
483
  const startTime = Date.now();
434
484
  const codecHooks = extractCodecControlHooks(options.frameworkComponents);
435
485
  const { contractStorageHash, contractProfileHash, contractTarget } = extractContractMetadata(contract);
@@ -446,19 +496,31 @@ function verifySqlSchema(options) {
446
496
  codecHooks,
447
497
  storageTypes: allStorageTypesMap,
448
498
  ...ifDefined("normalizeDefault", normalizeDefault),
449
- ...ifDefined("normalizeNativeType", normalizeNativeType)
499
+ ...ifDefined("normalizeNativeType", normalizeNativeType),
500
+ ...ifDefined("columnsCompatible", columnsCompatible)
450
501
  });
451
502
  validateFrameworkComponentsForExtensions(contract, options.frameworkComponents);
452
503
  const typeNodes = [];
453
- const pushTypeNode = (typeName, contractPath, typeIssues) => {
454
- if (typeIssues.length > 0) issues.push(...typeIssues);
504
+ const pushTypeNode = (typeName, contractPath, typeIssues, controlPolicy) => {
505
+ let status = "pass";
506
+ let code = "";
507
+ let emitted = 0;
508
+ for (const issue of typeIssues) {
509
+ const disposition = verifierDisposition(controlPolicy, issue.kind);
510
+ if (disposition === "suppress") continue;
511
+ issues.push(issue);
512
+ emitted += 1;
513
+ if (code === "") code = issue.kind;
514
+ if (disposition === "fail") status = "fail";
515
+ else if (disposition === "warn" && status !== "fail") status = "warn";
516
+ }
455
517
  typeNodes.push({
456
- status: typeIssues.length > 0 ? "fail" : "pass",
518
+ status,
457
519
  kind: "storageType",
458
520
  name: `type ${typeName}`,
459
521
  contractPath,
460
- code: typeIssues.length > 0 ? typeIssues[0]?.kind ?? "" : "",
461
- message: typeIssues.length > 0 ? `${typeIssues.length} issue${typeIssues.length === 1 ? "" : "s"}` : "",
522
+ code: status === "pass" ? "" : code,
523
+ message: emitted > 0 ? `${emitted} issue${emitted === 1 ? "" : "s"}` : "",
462
524
  expected: void 0,
463
525
  actual: void 0,
464
526
  children: []
@@ -470,14 +532,14 @@ function verifySqlSchema(options) {
470
532
  schema,
471
533
  resolveExistingEnumValues,
472
534
  namespaceId: UNBOUND_NAMESPACE_ID
473
- }));
535
+ }), effectiveControlPolicy(typeInstance.control, contract.defaultControl));
474
536
  else if (isStorageTypeInstance(typeInstance)) {
475
537
  const hook = codecHooks.get(typeInstance.codecId);
476
538
  pushTypeNode(typeName, `storage.types.${typeName}`, hook?.verifyType ? hook.verifyType({
477
539
  typeName,
478
540
  typeInstance,
479
541
  schema
480
- }) : []);
542
+ }) : [], effectiveControlPolicy(void 0, contract.defaultControl));
481
543
  }
482
544
  for (const nsId of Object.keys(contract.storage.namespaces)) {
483
545
  const ns = contract.storage.namespaces[nsId];
@@ -492,11 +554,11 @@ function verifySqlSchema(options) {
492
554
  schema,
493
555
  resolveExistingEnumValues,
494
556
  namespaceId: nsId
495
- }));
557
+ }), effectiveControlPolicy(entry.control, contract.defaultControl));
496
558
  }
497
559
  }
498
560
  if (typeNodes.length > 0) {
499
- const typesStatus = typeNodes.some((n) => n.status === "fail") ? "fail" : "pass";
561
+ const typesStatus = typeNodes.some((n) => n.status === "fail") ? "fail" : typeNodes.some((n) => n.status === "warn") ? "warn" : "pass";
500
562
  rootChildren.push({
501
563
  status: typesStatus,
502
564
  kind: "storageTypes",
@@ -580,7 +642,8 @@ function extractContractMetadata(contract) {
580
642
  };
581
643
  }
582
644
  function verifySchemaTables(options) {
583
- const { contract, schema, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
645
+ const { contract, schema, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType, columnsCompatible } = options;
646
+ const contractDefaultControl = contract.defaultControl;
584
647
  const issues = [];
585
648
  const rootChildren = [];
586
649
  const schemaTables = schema.tables;
@@ -591,16 +654,16 @@ function verifySchemaTables(options) {
591
654
  for (const [tableName, contractTableRaw] of Object.entries(ns.tables)) {
592
655
  if (!(contractTableRaw instanceof StorageTable)) throw new Error(`verifySqlSchema: expected StorageTable at storage.namespaces.${namespaceId}.tables.${tableName}`);
593
656
  const contractTable = contractTableRaw;
657
+ const tableControlPolicy = effectiveControlPolicy(contractTable.control, contractDefaultControl);
594
658
  const schemaTable = schemaTables[tableName];
595
659
  const tablePath = `storage.namespaces.${namespaceId}.tables.${tableName}`;
596
660
  if (!schemaTable) {
597
- issues.push({
661
+ emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
598
662
  kind: "missing_table",
599
663
  table: tableName,
600
664
  namespaceId,
601
665
  message: `Table "${tableName}" is missing from database`
602
- });
603
- rootChildren.push({
666
+ }, {
604
667
  status: "fail",
605
668
  kind: "table",
606
669
  name: `table ${tableName}`,
@@ -610,7 +673,7 @@ function verifySchemaTables(options) {
610
673
  expected: void 0,
611
674
  actual: void 0,
612
675
  children: []
613
- });
676
+ }, issues, rootChildren);
614
677
  continue;
615
678
  }
616
679
  const tableChildren = verifyTableChildren({
@@ -619,36 +682,35 @@ function verifySchemaTables(options) {
619
682
  tableName,
620
683
  namespaceId,
621
684
  tablePath,
685
+ tableControlPolicy,
622
686
  issues,
623
687
  strict,
624
688
  typeMetadataRegistry,
625
689
  codecHooks,
626
690
  storageTypes,
627
691
  ...ifDefined("normalizeDefault", normalizeDefault),
628
- ...ifDefined("normalizeNativeType", normalizeNativeType)
692
+ ...ifDefined("normalizeNativeType", normalizeNativeType),
693
+ ...ifDefined("columnsCompatible", columnsCompatible)
629
694
  });
630
695
  rootChildren.push(buildTableNode(tableName, tablePath, tableChildren));
631
696
  }
632
697
  }
633
698
  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
- }
699
+ for (const tableName of Object.keys(schemaTables)) if (!namespaceIds.some((namespaceId) => contract.storage.namespaces[namespaceId]?.tables[tableName] !== void 0)) emitIssueAndNodeUnderControlPolicy(effectiveControlPolicy(void 0, contractDefaultControl), {
700
+ kind: "extra_table",
701
+ table: tableName,
702
+ message: `Extra table "${tableName}" found in database (not in contract)`
703
+ }, {
704
+ status: "fail",
705
+ kind: "table",
706
+ name: `table ${tableName}`,
707
+ contractPath: `storage.namespaces.*.tables.${tableName}`,
708
+ code: "extra_table",
709
+ message: `Extra table "${tableName}" found`,
710
+ expected: void 0,
711
+ actual: void 0,
712
+ children: []
713
+ }, issues, rootChildren);
652
714
  }
653
715
  return {
654
716
  issues,
@@ -656,7 +718,7 @@ function verifySchemaTables(options) {
656
718
  };
657
719
  }
658
720
  function verifyTableChildren(options) {
659
- const { contractTable, schemaTable, tableName, namespaceId, tablePath, issues, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
721
+ const { contractTable, schemaTable, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType, columnsCompatible } = options;
660
722
  const tableChildren = [];
661
723
  const columnNodes = collectContractColumnNodes({
662
724
  contractTable,
@@ -664,13 +726,15 @@ function verifyTableChildren(options) {
664
726
  tableName,
665
727
  namespaceId,
666
728
  tablePath,
729
+ tableControlPolicy,
667
730
  issues,
668
731
  strict,
669
732
  typeMetadataRegistry,
670
733
  codecHooks,
671
734
  storageTypes,
672
735
  ...ifDefined("normalizeDefault", normalizeDefault),
673
- ...ifDefined("normalizeNativeType", normalizeNativeType)
736
+ ...ifDefined("normalizeNativeType", normalizeNativeType),
737
+ ...ifDefined("columnsCompatible", columnsCompatible)
674
738
  });
675
739
  if (columnNodes.length > 0) tableChildren.push(buildColumnsNode(tablePath, columnNodes));
676
740
  if (strict) appendExtraColumnNodes({
@@ -679,77 +743,87 @@ function verifyTableChildren(options) {
679
743
  tableName,
680
744
  namespaceId,
681
745
  tablePath,
746
+ tableControlPolicy,
682
747
  issues,
683
748
  columnNodes
684
749
  });
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({
750
+ if (contractTable.primaryKey) {
751
+ const pkStatus = verifyPrimaryKey(contractTable.primaryKey, schemaTable.primaryKey, tableName, namespaceId, tableControlPolicy, issues);
752
+ if (pkStatus === "fail") tableChildren.push({
715
753
  status: "fail",
716
754
  kind: "primaryKey",
717
- name: `primary key: ${schemaTable.primaryKey.columns.join(", ")}`,
755
+ name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
718
756
  contractPath: `${tablePath}.primaryKey`,
719
- code: "extra_primary_key",
720
- message: "Extra primary key found",
721
- expected: void 0,
757
+ code: "primary_key_mismatch",
758
+ message: "Primary key mismatch",
759
+ expected: contractTable.primaryKey,
722
760
  actual: schemaTable.primaryKey,
723
761
  children: []
724
762
  });
725
- }
763
+ else if (pkStatus === "warn") tableChildren.push({
764
+ status: "warn",
765
+ kind: "primaryKey",
766
+ name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
767
+ contractPath: `${tablePath}.primaryKey`,
768
+ code: "primary_key_mismatch",
769
+ message: "Primary key mismatch",
770
+ expected: contractTable.primaryKey,
771
+ actual: schemaTable.primaryKey,
772
+ children: []
773
+ });
774
+ else tableChildren.push({
775
+ status: "pass",
776
+ kind: "primaryKey",
777
+ name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
778
+ contractPath: `${tablePath}.primaryKey`,
779
+ code: "",
780
+ message: "",
781
+ expected: void 0,
782
+ actual: void 0,
783
+ children: []
784
+ });
785
+ } else if (schemaTable.primaryKey && strict) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
786
+ kind: "extra_primary_key",
787
+ table: tableName,
788
+ namespaceId,
789
+ message: "Extra primary key found in database (not in contract)"
790
+ }, {
791
+ status: "fail",
792
+ kind: "primaryKey",
793
+ name: `primary key: ${schemaTable.primaryKey.columns.join(", ")}`,
794
+ contractPath: `${tablePath}.primaryKey`,
795
+ code: "extra_primary_key",
796
+ message: "Extra primary key found",
797
+ expected: void 0,
798
+ actual: schemaTable.primaryKey,
799
+ children: []
800
+ }, issues, tableChildren);
726
801
  const constraintFks = contractTable.foreignKeys.filter((fk) => fk.constraint === true);
727
802
  if (constraintFks.length > 0 || strict) {
728
- const fkStatuses = verifyForeignKeys(constraintFks, schemaTable.foreignKeys, tableName, namespaceId, tablePath, issues, strict);
803
+ const fkStatuses = verifyForeignKeys(constraintFks, schemaTable.foreignKeys, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict);
729
804
  tableChildren.push(...fkStatuses);
730
805
  }
731
- const uniqueStatuses = verifyUniqueConstraints(contractTable.uniques, schemaTable.uniques, schemaTable.indexes, tableName, namespaceId, tablePath, issues, strict);
806
+ const uniqueStatuses = verifyUniqueConstraints(contractTable.uniques, schemaTable.uniques, schemaTable.indexes, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict);
732
807
  tableChildren.push(...uniqueStatuses);
733
808
  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);
809
+ const indexStatuses = verifyIndexes([...contractTable.indexes, ...fkBackingIndexes], schemaTable.indexes, schemaTable.uniques, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict);
735
810
  tableChildren.push(...indexStatuses);
736
811
  return tableChildren;
737
812
  }
738
813
  function collectContractColumnNodes(options) {
739
- const { contractTable, schemaTable, tableName, namespaceId, tablePath, issues, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
814
+ const { contractTable, schemaTable, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType, columnsCompatible } = options;
740
815
  const columnNodes = [];
741
816
  for (const [columnName, contractColumn] of Object.entries(contractTable.columns)) {
742
817
  const schemaColumn = schemaTable.columns[columnName];
743
818
  const columnPath = `${tablePath}.columns.${columnName}`;
744
819
  if (!schemaColumn) {
745
- issues.push({
820
+ emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
746
821
  kind: "missing_column",
747
822
  table: tableName,
748
823
  namespaceId,
749
824
  column: columnName,
750
825
  message: `Column "${tableName}"."${columnName}" is missing from database`
751
- });
752
- columnNodes.push({
826
+ }, {
753
827
  status: "fail",
754
828
  kind: "column",
755
829
  name: `${columnName}: missing`,
@@ -759,7 +833,7 @@ function collectContractColumnNodes(options) {
759
833
  expected: void 0,
760
834
  actual: void 0,
761
835
  children: []
762
- });
836
+ }, issues, columnNodes);
763
837
  continue;
764
838
  }
765
839
  columnNodes.push(verifyColumn({
@@ -769,42 +843,41 @@ function collectContractColumnNodes(options) {
769
843
  contractColumn,
770
844
  schemaColumn,
771
845
  columnPath,
846
+ tableControlPolicy,
772
847
  issues,
773
848
  strict,
774
849
  typeMetadataRegistry,
775
850
  codecHooks,
776
851
  storageTypes,
777
852
  ...ifDefined("normalizeDefault", normalizeDefault),
778
- ...ifDefined("normalizeNativeType", normalizeNativeType)
853
+ ...ifDefined("normalizeNativeType", normalizeNativeType),
854
+ ...ifDefined("columnsCompatible", columnsCompatible)
779
855
  }));
780
856
  }
781
857
  return columnNodes;
782
858
  }
783
859
  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
- }
860
+ const { contractTable, schemaTable, tableName, namespaceId, tablePath, tableControlPolicy, issues, columnNodes } = options;
861
+ for (const [columnName, { nativeType }] of Object.entries(schemaTable.columns)) if (!contractTable.columns[columnName]) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
862
+ kind: "extra_column",
863
+ table: tableName,
864
+ namespaceId,
865
+ column: columnName,
866
+ message: `Extra column "${tableName}"."${columnName}" found in database (not in contract)`
867
+ }, {
868
+ status: "fail",
869
+ kind: "column",
870
+ name: `${columnName}: extra`,
871
+ contractPath: `${tablePath}.columns.${columnName}`,
872
+ code: "extra_column",
873
+ message: `Extra column "${columnName}" found`,
874
+ expected: void 0,
875
+ actual: nativeType,
876
+ children: []
877
+ }, issues, columnNodes);
805
878
  }
806
879
  function verifyColumn(options) {
807
- const { tableName, namespaceId, columnName, contractColumn, schemaColumn, columnPath, issues, strict, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
880
+ const { tableName, namespaceId, columnName, contractColumn, schemaColumn, columnPath, tableControlPolicy, issues, strict, codecHooks, storageTypes, normalizeDefault, normalizeNativeType, columnsCompatible } = options;
808
881
  const columnChildren = [];
809
882
  let columnStatus = "pass";
810
883
  const resolvedContractColumn = resolveContractColumnTypeMetadata(contractColumn, storageTypes, {
@@ -816,8 +889,8 @@ function verifyColumn(options) {
816
889
  columnName
817
890
  });
818
891
  const schemaNativeType = normalizeNativeType?.(schemaColumn.nativeType) ?? schemaColumn.nativeType;
819
- if (contractNativeType !== schemaNativeType) {
820
- issues.push({
892
+ if (!(contractNativeType === schemaNativeType || tableControlPolicy === "external" && (columnsCompatible?.(contractNativeType, schemaNativeType) ?? contractNativeType === schemaNativeType))) {
893
+ const issue = {
821
894
  kind: "type_mismatch",
822
895
  table: tableName,
823
896
  namespaceId,
@@ -825,19 +898,23 @@ function verifyColumn(options) {
825
898
  expected: contractNativeType,
826
899
  actual: schemaNativeType,
827
900
  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";
901
+ };
902
+ const disposition = verifierDisposition(tableControlPolicy, issue.kind);
903
+ if (disposition !== "suppress") {
904
+ issues.push(issue);
905
+ columnChildren.push({
906
+ status: disposition,
907
+ kind: "type",
908
+ name: "type",
909
+ contractPath: `${columnPath}.nativeType`,
910
+ code: "type_mismatch",
911
+ message: `Type mismatch: expected ${contractNativeType}, got ${schemaNativeType}`,
912
+ expected: contractNativeType,
913
+ actual: schemaNativeType,
914
+ children: []
915
+ });
916
+ columnStatus = disposition;
917
+ }
841
918
  }
842
919
  if (resolvedContractColumn.codecId) {
843
920
  const typeMetadata = options.typeMetadataRegistry.get(resolvedContractColumn.codecId);
@@ -865,7 +942,7 @@ function verifyColumn(options) {
865
942
  });
866
943
  }
867
944
  if (contractColumn.nullable !== schemaColumn.nullable) {
868
- issues.push({
945
+ const issue = {
869
946
  kind: "nullability_mismatch",
870
947
  table: tableName,
871
948
  namespaceId,
@@ -873,47 +950,55 @@ function verifyColumn(options) {
873
950
  expected: String(contractColumn.nullable),
874
951
  actual: String(schemaColumn.nullable),
875
952
  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";
953
+ };
954
+ const disposition = verifierDisposition(tableControlPolicy, issue.kind);
955
+ if (disposition !== "suppress") {
956
+ issues.push(issue);
957
+ columnChildren.push({
958
+ status: disposition,
959
+ kind: "nullability",
960
+ name: "nullability",
961
+ contractPath: `${columnPath}.nullable`,
962
+ code: "nullability_mismatch",
963
+ message: `Nullability mismatch: expected ${contractColumn.nullable ? "nullable" : "not null"}, got ${schemaColumn.nullable ? "nullable" : "not null"}`,
964
+ expected: contractColumn.nullable,
965
+ actual: schemaColumn.nullable,
966
+ children: []
967
+ });
968
+ columnStatus = disposition;
969
+ }
889
970
  }
890
971
  if (contractColumn.default) {
891
972
  if (!schemaColumn.default) {
892
973
  const defaultDescription = describeColumnDefault(contractColumn.default);
893
- issues.push({
974
+ const issue = {
894
975
  kind: "default_missing",
895
976
  table: tableName,
896
977
  namespaceId,
897
978
  column: columnName,
898
979
  expected: defaultDescription,
899
980
  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";
981
+ };
982
+ const disposition = verifierDisposition(tableControlPolicy, issue.kind);
983
+ if (disposition !== "suppress") {
984
+ issues.push(issue);
985
+ columnChildren.push({
986
+ status: disposition,
987
+ kind: "default",
988
+ name: "default",
989
+ contractPath: `${columnPath}.default`,
990
+ code: "default_missing",
991
+ message: `Default missing: expected ${defaultDescription}`,
992
+ expected: defaultDescription,
993
+ actual: void 0,
994
+ children: []
995
+ });
996
+ columnStatus = disposition;
997
+ }
913
998
  } else if (!columnDefaultsEqual(contractColumn.default, schemaColumn.default, normalizeDefault, schemaNativeType)) {
914
999
  const expectedDescription = describeColumnDefault(contractColumn.default);
915
1000
  const actualDescription = schemaColumn.default;
916
- issues.push({
1001
+ const issue = {
917
1002
  kind: "default_mismatch",
918
1003
  table: tableName,
919
1004
  namespaceId,
@@ -921,41 +1006,49 @@ function verifyColumn(options) {
921
1006
  expected: expectedDescription,
922
1007
  actual: actualDescription,
923
1008
  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";
1009
+ };
1010
+ const disposition = verifierDisposition(tableControlPolicy, issue.kind);
1011
+ if (disposition !== "suppress") {
1012
+ issues.push(issue);
1013
+ columnChildren.push({
1014
+ status: disposition,
1015
+ kind: "default",
1016
+ name: "default",
1017
+ contractPath: `${columnPath}.default`,
1018
+ code: "default_mismatch",
1019
+ message: `Default mismatch: expected ${expectedDescription}, got ${actualDescription}`,
1020
+ expected: expectedDescription,
1021
+ actual: actualDescription,
1022
+ children: []
1023
+ });
1024
+ columnStatus = disposition;
1025
+ }
937
1026
  }
938
1027
  } else if (strict && schemaColumn.default) {
939
- issues.push({
1028
+ const issue = {
940
1029
  kind: "extra_default",
941
1030
  table: tableName,
942
1031
  namespaceId,
943
1032
  column: columnName,
944
1033
  actual: schemaColumn.default,
945
1034
  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";
1035
+ };
1036
+ const disposition = verifierDisposition(tableControlPolicy, issue.kind);
1037
+ if (disposition !== "suppress") {
1038
+ issues.push(issue);
1039
+ columnChildren.push({
1040
+ status: disposition,
1041
+ kind: "default",
1042
+ name: "default",
1043
+ contractPath: `${columnPath}.default`,
1044
+ code: "extra_default",
1045
+ message: `Extra default: ${schemaColumn.default}`,
1046
+ expected: void 0,
1047
+ actual: schemaColumn.default,
1048
+ children: []
1049
+ });
1050
+ columnStatus = disposition;
1051
+ }
959
1052
  }
960
1053
  const aggregated = aggregateChildState(columnChildren, columnStatus);
961
1054
  const nullableText = contractColumn.nullable ? "nullable" : "not nullable";
@@ -1154,4 +1247,4 @@ function formatLiteralValue(value) {
1154
1247
  //#endregion
1155
1248
  export { extractCodecControlHooks as a, isUniqueConstraintSatisfied as i, arraysEqual as n, isIndexSatisfied as r, verifySqlSchema as t };
1156
1249
 
1157
- //# sourceMappingURL=verify-sql-schema-CYLsGCFO.mjs.map
1250
+ //# sourceMappingURL=verify-sql-schema-CXW_D9dW.mjs.map