@prisma-next/family-sql 0.12.0-dev.5 → 0.12.0-dev.51

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