@prisma-next/cli 0.14.0-dev.49 → 0.14.0-dev.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/cli.mjs +13 -13
  2. package/dist/{client-CWTNqJdj.mjs → client-DL6KE_c0.mjs} +22 -37
  3. package/dist/client-DL6KE_c0.mjs.map +1 -0
  4. package/dist/{command-helpers-6cNJZ863.mjs → command-helpers-DMG0RsR1.mjs} +3 -3
  5. package/dist/command-helpers-DMG0RsR1.mjs.map +1 -0
  6. package/dist/commands/contract-emit.mjs +1 -1
  7. package/dist/commands/contract-infer.mjs +1 -1
  8. package/dist/commands/db-init.mjs +3 -3
  9. package/dist/commands/db-schema.mjs +3 -3
  10. package/dist/commands/db-sign.mjs +4 -4
  11. package/dist/commands/db-update.mjs +4 -4
  12. package/dist/commands/db-verify.mjs +1 -1
  13. package/dist/commands/migrate.d.mts +1 -1
  14. package/dist/commands/migrate.mjs +6 -6
  15. package/dist/commands/migrate.mjs.map +1 -1
  16. package/dist/commands/migration-check.d.mts +1 -1
  17. package/dist/commands/migration-check.mjs +1 -1
  18. package/dist/commands/migration-graph.d.mts +1 -1
  19. package/dist/commands/migration-graph.mjs +3 -3
  20. package/dist/commands/migration-list.d.mts +1 -1
  21. package/dist/commands/migration-list.mjs +1 -1
  22. package/dist/commands/migration-log.d.mts +1 -1
  23. package/dist/commands/migration-log.mjs +1 -1
  24. package/dist/commands/migration-new.mjs +3 -3
  25. package/dist/commands/migration-plan.d.mts.map +1 -1
  26. package/dist/commands/migration-plan.mjs +1 -1
  27. package/dist/commands/migration-show.mjs +3 -3
  28. package/dist/commands/migration-status.d.mts +1 -1
  29. package/dist/commands/migration-status.mjs +1 -1
  30. package/dist/commands/ref.d.mts +1 -1
  31. package/dist/commands/ref.mjs +2 -2
  32. package/dist/commands/telemetry/index.mjs +1 -1
  33. package/dist/{contract-at-errors-DdxWRjMc.mjs → contract-at-errors-bF4CGDl0.mjs} +2 -2
  34. package/dist/{contract-at-errors-DdxWRjMc.mjs.map → contract-at-errors-bF4CGDl0.mjs.map} +1 -1
  35. package/dist/{contract-emit-CMwCj7_m.mjs → contract-emit-BM7oxYIG.mjs} +3 -3
  36. package/dist/{contract-emit-CMwCj7_m.mjs.map → contract-emit-BM7oxYIG.mjs.map} +1 -1
  37. package/dist/{contract-emit-klH7zzaV.mjs → contract-emit-DnCG2JaQ.mjs} +3 -3
  38. package/dist/{contract-emit-klH7zzaV.mjs.map → contract-emit-DnCG2JaQ.mjs.map} +1 -1
  39. package/dist/{contract-infer-B__56PGb.mjs → contract-infer-Dq9XNQCx.mjs} +3 -3
  40. package/dist/{contract-infer-B__56PGb.mjs.map → contract-infer-Dq9XNQCx.mjs.map} +1 -1
  41. package/dist/{contract-space-aggregate-loader-Bu2fjSWV.mjs → contract-space-aggregate-loader-DAa8u6bi.mjs} +2 -2
  42. package/dist/{contract-space-aggregate-loader-Bu2fjSWV.mjs.map → contract-space-aggregate-loader-DAa8u6bi.mjs.map} +1 -1
  43. package/dist/{db-verify-DlqHvkkA.mjs → db-verify-BpfJvC8O.mjs} +14 -38
  44. package/dist/db-verify-BpfJvC8O.mjs.map +1 -0
  45. package/dist/exports/control-api.d.mts +1 -1
  46. package/dist/exports/control-api.mjs +2 -2
  47. package/dist/exports/index.mjs +2 -2
  48. package/dist/{format-C3XdaDuA.mjs → format-DrUSkdqR.mjs} +2 -2
  49. package/dist/{format-C3XdaDuA.mjs.map → format-DrUSkdqR.mjs.map} +1 -1
  50. package/dist/{framework-components-5RljWT7Q.mjs → framework-components-BdmVlRSS.mjs} +2 -2
  51. package/dist/{framework-components-5RljWT7Q.mjs.map → framework-components-BdmVlRSS.mjs.map} +1 -1
  52. package/dist/{init-BF3s9NcB.mjs → init-C0YyVoGj.mjs} +3 -3
  53. package/dist/{init-BF3s9NcB.mjs.map → init-C0YyVoGj.mjs.map} +1 -1
  54. package/dist/{inspect-live-schema-Dd1Ft0dw.mjs → inspect-live-schema-Bs_0M6z8.mjs} +3 -3
  55. package/dist/{inspect-live-schema-Dd1Ft0dw.mjs.map → inspect-live-schema-Bs_0M6z8.mjs.map} +1 -1
  56. package/dist/{migration-check-C9F4RopI.mjs → migration-check-DYxrNfK-.mjs} +4 -5
  57. package/dist/{migration-check-C9F4RopI.mjs.map → migration-check-DYxrNfK-.mjs.map} +1 -1
  58. package/dist/{migration-command-scaffold-DW2BpjZW.mjs → migration-command-scaffold-DB5DQLvF.mjs} +3 -3
  59. package/dist/{migration-command-scaffold-DW2BpjZW.mjs.map → migration-command-scaffold-DB5DQLvF.mjs.map} +1 -1
  60. package/dist/{migration-list-BOzQzJK4.mjs → migration-list-BX5w9HAn.mjs} +3 -3
  61. package/dist/{migration-list-BOzQzJK4.mjs.map → migration-list-BX5w9HAn.mjs.map} +1 -1
  62. package/dist/{migration-log-q50rqMiU.mjs → migration-log-CB1KzVOG.mjs} +3 -3
  63. package/dist/{migration-log-q50rqMiU.mjs.map → migration-log-CB1KzVOG.mjs.map} +1 -1
  64. package/dist/{migration-path-target-zK6RG6JY.mjs → migration-path-target-CEFRK2Q7.mjs} +2 -2
  65. package/dist/{migration-path-target-zK6RG6JY.mjs.map → migration-path-target-CEFRK2Q7.mjs.map} +1 -1
  66. package/dist/{migration-plan-BwJLPkwV.mjs → migration-plan-DMKl_HnV.mjs} +11 -10
  67. package/dist/migration-plan-DMKl_HnV.mjs.map +1 -0
  68. package/dist/{migration-status-RrGUf39y.mjs → migration-status-BeNAmEju.mjs} +5 -6
  69. package/dist/{migration-status-RrGUf39y.mjs.map → migration-status-BeNAmEju.mjs.map} +1 -1
  70. package/dist/{telemetry-BckOSUWG.mjs → telemetry-_19qtfXp.mjs} +2 -2
  71. package/dist/{telemetry-BckOSUWG.mjs.map → telemetry-_19qtfXp.mjs.map} +1 -1
  72. package/dist/{types-CKgJmH02.d.mts → types-DFoqNIcQ.d.mts} +2 -2
  73. package/dist/{types-CKgJmH02.d.mts.map → types-DFoqNIcQ.d.mts.map} +1 -1
  74. package/dist/{verify-DC14_nPu.mjs → verify-Djo-Jep_.mjs} +36 -161
  75. package/dist/verify-Djo-Jep_.mjs.map +1 -0
  76. package/package.json +21 -21
  77. package/src/commands/db-verify.ts +1 -1
  78. package/src/commands/migrate.ts +4 -4
  79. package/src/commands/migration-plan.ts +11 -0
  80. package/src/control-api/operations/db-run.ts +13 -12
  81. package/src/control-api/operations/db-verify.ts +16 -13
  82. package/src/control-api/operations/migrate.ts +8 -8
  83. package/src/control-api/operations/run-migration.ts +5 -4
  84. package/src/control-api/types.ts +1 -1
  85. package/src/utils/combine-verify-results.ts +8 -27
  86. package/src/utils/formatters/errors.ts +12 -4
  87. package/src/utils/formatters/verify.ts +50 -230
  88. package/dist/client-CWTNqJdj.mjs.map +0 -1
  89. package/dist/command-helpers-6cNJZ863.mjs.map +0 -1
  90. package/dist/db-verify-DlqHvkkA.mjs.map +0 -1
  91. package/dist/migration-plan-BwJLPkwV.mjs.map +0 -1
  92. package/dist/verify-DC14_nPu.mjs.map +0 -1
@@ -1,7 +1,22 @@
1
- import { M as createColorFormatter, N as formatDim, P as isVerbose } from "./command-helpers-6cNJZ863.mjs";
1
+ import { M as createColorFormatter, N as formatDim, P as isVerbose } from "./command-helpers-DMG0RsR1.mjs";
2
2
  import { ifDefined } from "@prisma-next/utils/defined";
3
3
  import { bold, cyan, dim, green, magenta, red, yellow } from "colorette";
4
4
  //#region src/utils/formatters/verify.ts
5
+ /** Human-readable label for each failure reason, prefixed onto an issue's message for display. */
6
+ const REASON_LABEL = {
7
+ "not-found": "missing",
8
+ "not-expected": "extra",
9
+ "not-equal": "mismatch"
10
+ };
11
+ /**
12
+ * The issue's display text: its own message (the differ's `path`), prefixed
13
+ * with a human label for why it's flagged. Turning `reason` into a label is
14
+ * this formatter's job, not the differ's — the differ's issue is data
15
+ * (`path` + `reason` + nodes), not prose.
16
+ */
17
+ function formatIssueMessage(issue) {
18
+ return `${REASON_LABEL[issue.reason]}: ${issue.message}`;
19
+ }
5
20
  /**
6
21
  * Formats human-readable output for database verify.
7
22
  */
@@ -17,7 +32,11 @@ function formatVerifyOutput(result, flags) {
17
32
  lines.push(`${formatDimText(` verification: ${verificationMode}`)}`);
18
33
  lines.push(`${formatDimText(` storageHash: ${result.contract.storageHash}`)}`);
19
34
  if (result.contract.profileHash) lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);
20
- if (result.mode === "full" && result.schema && isVerbose(flags, 1)) lines.push(`${formatDimText(` schema: pass=${result.schema.counts.pass} warn=${result.schema.counts.warn} fail=${result.schema.counts.fail}`)}`);
35
+ if (result.schema?.warnings && result.schema.warnings.length > 0) {
36
+ lines.push("");
37
+ lines.push(formatYellow("Schema warnings:"));
38
+ for (const message of result.schema.warnings) lines.push(` ${formatYellow("⚠")} ${message}`);
39
+ }
21
40
  if (result.unclaimed && result.unclaimed.length > 0) {
22
41
  lines.push("");
23
42
  lines.push(formatYellow("Unclaimed elements (declared by no contract):"));
@@ -62,7 +81,7 @@ function formatIntrospectJson(result) {
62
81
  }
63
82
  /**
64
83
  * Renders a schema tree structure from CoreSchemaView.
65
- * Matches the style of renderSchemaVerificationTree for consistency.
84
+ * Status-glyph tree styling shared with the retired verification-tree renderer.
66
85
  */
67
86
  function renderSchemaTree(node, flags, options) {
68
87
  const { isLast, prefix, useColor, formatDimText, isRoot = false } = options;
@@ -181,145 +200,6 @@ function formatIntrospectOutput(result, schemaView, flags) {
181
200
  return lines.join("\n");
182
201
  }
183
202
  /**
184
- * Renders a schema verification tree structure from SchemaVerificationNode.
185
- * Similar to renderSchemaTree but for verification nodes with status-based colors and glyphs.
186
- */
187
- function renderSchemaVerificationTree(node, flags, options) {
188
- const { isLast, prefix, useColor, formatDimText, isRoot = false } = options;
189
- const lines = [];
190
- let statusGlyph = "";
191
- let statusColor = (text) => text;
192
- if (useColor) switch (node.status) {
193
- case "pass":
194
- statusGlyph = "✔";
195
- statusColor = green;
196
- break;
197
- case "warn":
198
- statusGlyph = "⚠";
199
- statusColor = (text) => useColor ? yellow(text) : text;
200
- break;
201
- case "fail":
202
- statusGlyph = "✖";
203
- statusColor = red;
204
- break;
205
- }
206
- else switch (node.status) {
207
- case "pass":
208
- statusGlyph = "✔";
209
- break;
210
- case "warn":
211
- statusGlyph = "⚠";
212
- break;
213
- case "fail":
214
- statusGlyph = "✖";
215
- break;
216
- }
217
- let labelColor = (text) => text;
218
- let formattedLabel = node.name;
219
- if (useColor) switch (node.kind) {
220
- case "contract":
221
- case "schema":
222
- labelColor = bold;
223
- formattedLabel = labelColor(node.name);
224
- break;
225
- case "table": {
226
- const tableMatch = node.name.match(/^table\s+(.+)$/);
227
- if (tableMatch?.[1]) {
228
- const tableName = tableMatch[1];
229
- formattedLabel = `${dim("table")} ${cyan(tableName)}`;
230
- } else formattedLabel = dim(node.name);
231
- break;
232
- }
233
- case "columns":
234
- labelColor = dim;
235
- formattedLabel = labelColor(node.name);
236
- break;
237
- case "column": {
238
- const columnMatch = node.name.match(/^([^:]+):\s*(.+)$/);
239
- if (columnMatch?.[1] && columnMatch[2]) {
240
- const columnName = columnMatch[1];
241
- const rest = columnMatch[2];
242
- const typeMatch = rest.match(/^([^\s→]+)\s*→\s*([^\s(]+)\s*(\([^)]+\))$/);
243
- if (typeMatch?.[1] && typeMatch[2] && typeMatch[3]) {
244
- const contractType = typeMatch[1];
245
- const nativeType = typeMatch[2];
246
- const nullability = typeMatch[3];
247
- formattedLabel = `${cyan(columnName)}: ${contractType} → ${dim(nativeType)} ${dim(nullability)}`;
248
- } else formattedLabel = `${cyan(columnName)}: ${rest}`;
249
- } else formattedLabel = node.name;
250
- break;
251
- }
252
- case "type":
253
- case "nullability":
254
- labelColor = (text) => text;
255
- formattedLabel = labelColor(node.name);
256
- break;
257
- case "primaryKey": {
258
- const pkMatch = node.name.match(/^primary key:\s*(.+)$/);
259
- if (pkMatch?.[1]) {
260
- const columnNames = pkMatch[1];
261
- formattedLabel = `${dim("primary key")}: ${cyan(columnNames)}`;
262
- } else formattedLabel = dim(node.name);
263
- break;
264
- }
265
- case "foreignKey":
266
- case "unique":
267
- case "index":
268
- labelColor = dim;
269
- formattedLabel = labelColor(node.name);
270
- break;
271
- case "dependency": {
272
- const dbMatch = node.name.match(/^database is\s+(.+)$/);
273
- if (dbMatch?.[1]) {
274
- const dbName = dbMatch[1];
275
- formattedLabel = `${dim("database is")} ${cyan(dbName)}`;
276
- } else {
277
- const extMatch = node.name.match(/^([^\s]+)\s+(extension is enabled)$/);
278
- if (extMatch?.[1] && extMatch[2]) {
279
- const extName = extMatch[1];
280
- const rest = extMatch[2];
281
- formattedLabel = `${cyan(extName)} ${dim(rest)}`;
282
- } else {
283
- labelColor = magenta;
284
- formattedLabel = labelColor(node.name);
285
- }
286
- }
287
- break;
288
- }
289
- default:
290
- formattedLabel = node.name;
291
- break;
292
- }
293
- else formattedLabel = node.name;
294
- const statusGlyphColored = statusColor(statusGlyph);
295
- let nodeLabel = formattedLabel;
296
- if ((node.status === "fail" || node.status === "warn") && node.message && node.message.length > 0) {
297
- const messageText = formatDimText(`(${node.message})`);
298
- nodeLabel = `${formattedLabel} ${messageText}`;
299
- }
300
- if (isRoot) lines.push(`${statusGlyphColored} ${nodeLabel}`);
301
- else {
302
- const treePrefix = `${formatDimText(isLast ? "└" : "├")}─ `;
303
- lines.push(`${prefix}${treePrefix}${statusGlyphColored} ${nodeLabel}`);
304
- }
305
- if (node.children && node.children.length > 0) {
306
- const childPrefix = isRoot ? "" : `${prefix}${isLast ? " " : `${formatDimText("│")} `}`;
307
- for (let i = 0; i < node.children.length; i++) {
308
- const child = node.children[i];
309
- if (!child) continue;
310
- const childLines = renderSchemaVerificationTree(child, flags, {
311
- isLast: i === node.children.length - 1,
312
- prefix: childPrefix,
313
- useColor,
314
- formatDimText,
315
- isRoot: false
316
- });
317
- lines.push(...childLines);
318
- }
319
- }
320
- return lines;
321
- }
322
- /**
323
203
  * Formats human-readable output for database schema verification.
324
204
  */
325
205
  function formatSchemaVerifyOutput(result, flags, unclaimed = []) {
@@ -330,30 +210,25 @@ function formatSchemaVerifyOutput(result, flags, unclaimed = []) {
330
210
  const formatRed = createColorFormatter(useColor, red);
331
211
  const formatYellow = createColorFormatter(useColor, yellow);
332
212
  const formatDimText = (text) => formatDim(useColor, text);
333
- const treeLines = renderSchemaVerificationTree(result.schema.root, flags, {
334
- isLast: true,
335
- prefix: "",
336
- useColor,
337
- formatDimText,
338
- isRoot: true
339
- });
340
- lines.push(...treeLines);
341
- if (result.schema.schemaDiffIssues.length > 0) {
342
- lines.push("");
343
- lines.push(formatRed("Schema drift:"));
344
- for (const issue of result.schema.schemaDiffIssues) lines.push(` ${formatRed("✖")} ${issue.message}`);
213
+ const issueMessages = result.schema.issues.map(formatIssueMessage);
214
+ if (issueMessages.length > 0) {
215
+ lines.push(formatRed("Schema issues:"));
216
+ for (const message of issueMessages) lines.push(` ${formatRed("✖")} ${message}`);
217
+ }
218
+ const warningMessages = (result.schema.warnings?.issues ?? []).map(formatIssueMessage);
219
+ if (warningMessages.length > 0) {
220
+ if (lines.length > 0) lines.push("");
221
+ lines.push(formatYellow("Schema warnings:"));
222
+ for (const message of warningMessages) lines.push(` ${formatYellow("")} ${message}`);
345
223
  }
346
224
  if (unclaimed.length > 0) {
347
225
  const strict = result.meta?.strict ?? false;
348
- lines.push("");
226
+ if (lines.length > 0) lines.push("");
349
227
  lines.push((strict ? formatRed : formatYellow)("Unclaimed elements (declared by no contract):"));
350
228
  for (const name of unclaimed) lines.push(` ${(strict ? formatRed : formatYellow)(strict ? "✖" : "⚠")} ${name}`);
351
229
  }
352
- if (isVerbose(flags, 1)) {
353
- lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);
354
- lines.push(`${formatDimText(` pass=${result.schema.counts.pass} warn=${result.schema.counts.warn} fail=${result.schema.counts.fail}`)}`);
355
- }
356
- lines.push("");
230
+ if (isVerbose(flags, 1)) lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);
231
+ if (lines.length > 0) lines.push("");
357
232
  if (result.ok) lines.push(`${formatGreen("✔")} ${result.summary}`);
358
233
  else {
359
234
  const codeText = result.code ? ` (${result.code})` : "";
@@ -404,4 +279,4 @@ function formatSignJson(result) {
404
279
  //#endregion
405
280
  export { formatSignJson as a, formatVerifyOutput as c, formatSchemaVerifyOutput as i, formatIntrospectOutput as n, formatSignOutput as o, formatSchemaVerifyJson as r, formatVerifyJson as s, formatIntrospectJson as t };
406
281
 
407
- //# sourceMappingURL=verify-DC14_nPu.mjs.map
282
+ //# sourceMappingURL=verify-Djo-Jep_.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-Djo-Jep_.mjs","names":[],"sources":["../src/utils/formatters/verify.ts"],"sourcesContent":["import type {\n CoreSchemaView,\n ExpectationFailureReason,\n IntrospectSchemaResult,\n SchemaDiffIssue,\n SchemaTreeNode,\n SignDatabaseResult,\n VerifyDatabaseResult,\n VerifyDatabaseSchemaResult,\n} from '@prisma-next/framework-components/control';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { bold, cyan, dim, green, magenta, red, yellow } from 'colorette';\nimport type { GlobalFlags } from '../global-flags';\nimport { createColorFormatter, formatDim, isVerbose } from './helpers';\n\n/** Human-readable label for each failure reason, prefixed onto an issue's message for display. */\nconst REASON_LABEL: Record<ExpectationFailureReason, string> = {\n 'not-found': 'missing',\n 'not-expected': 'extra',\n 'not-equal': 'mismatch',\n};\n\n/**\n * The issue's display text: its own message (the differ's `path`), prefixed\n * with a human label for why it's flagged. Turning `reason` into a label is\n * this formatter's job, not the differ's — the differ's issue is data\n * (`path` + `reason` + nodes), not prose.\n */\nfunction formatIssueMessage(issue: SchemaDiffIssue): string {\n return `${REASON_LABEL[issue.reason]}: ${issue.message}`;\n}\n\n// ============================================================================\n// Verify Output Formatters\n// ============================================================================\n\nexport interface DbVerifyCommandSuccessResult {\n readonly ok: true;\n readonly mode: 'full' | 'marker-only';\n readonly summary: string;\n readonly contract: VerifyDatabaseResult['contract'];\n readonly marker?: VerifyDatabaseResult['marker'];\n readonly target: VerifyDatabaseResult['target'];\n readonly missingCodecs?: VerifyDatabaseResult['missingCodecs'];\n readonly codecCoverageSkipped?: VerifyDatabaseResult['codecCoverageSkipped'];\n readonly schema?: {\n readonly summary: string;\n readonly strict: boolean;\n /**\n * Warn-graded finding messages (observed-policy drift). Informational —\n * present on a passing verify; the full-mode result summarizes them as a\n * flat message list.\n */\n readonly warnings?: readonly string[];\n };\n /**\n * Live element names no contract space declares. In full success this is\n * only ever non-empty in lenient mode — strict mode fails on it — and is\n * rendered informationally.\n */\n readonly unclaimed?: readonly string[];\n readonly warning?: string;\n readonly meta?:\n | (NonNullable<VerifyDatabaseResult['meta']> & {\n readonly schemaVerification: 'performed' | 'skipped';\n })\n | {\n readonly schemaVerification: 'performed' | 'skipped';\n };\n readonly timings: {\n readonly total: number;\n };\n}\n\n/**\n * Formats human-readable output for database verify.\n */\nexport function formatVerifyOutput(\n result: DbVerifyCommandSuccessResult,\n flags: GlobalFlags,\n): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatYellow = createColorFormatter(useColor, yellow);\n const formatDimText = (text: string) => formatDim(useColor, text);\n const verificationMode =\n result.mode === 'full'\n ? `marker + schema${result.schema?.strict ? ' (strict)' : ' (tolerant)'}`\n : 'marker only (--marker-only)';\n\n lines.push(`${formatGreen('✔')} ${result.summary}`);\n lines.push(`${formatDimText(` verification: ${verificationMode}`)}`);\n lines.push(`${formatDimText(` storageHash: ${result.contract.storageHash}`)}`);\n if (result.contract.profileHash) {\n lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);\n }\n if (result.schema?.warnings && result.schema.warnings.length > 0) {\n lines.push('');\n lines.push(formatYellow('Schema warnings:'));\n for (const message of result.schema.warnings) {\n lines.push(` ${formatYellow('⚠')} ${message}`);\n }\n }\n\n if (result.unclaimed && result.unclaimed.length > 0) {\n lines.push('');\n lines.push(formatYellow('Unclaimed elements (declared by no contract):'));\n for (const name of result.unclaimed) {\n lines.push(` ${formatYellow('⚠')} ${name}`);\n }\n }\n\n if (result.warning) {\n lines.push('');\n lines.push(`${formatYellow('⚠')} ${result.warning}`);\n }\n\n if (isVerbose(flags, 1)) {\n if (result.codecCoverageSkipped) {\n lines.push(\n `${formatDimText(' Codec coverage check skipped (helper returned no supported types)')}`,\n );\n }\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats JSON output for database verify.\n */\nexport function formatVerifyJson(result: DbVerifyCommandSuccessResult): string {\n const output = {\n ok: result.ok,\n summary: result.summary,\n mode: result.mode,\n contract: result.contract,\n ...ifDefined('marker', result.marker),\n target: result.target,\n ...ifDefined('missingCodecs', result.missingCodecs),\n ...ifDefined('codecCoverageSkipped', result.codecCoverageSkipped),\n ...ifDefined('schema', result.schema),\n unclaimed: result.unclaimed ?? [],\n ...ifDefined('warning', result.warning),\n ...ifDefined('meta', result.meta),\n timings: result.timings,\n };\n\n return JSON.stringify(output, null, 2);\n}\n\n/**\n * Formats JSON output for database introspection.\n */\nexport function formatIntrospectJson(result: IntrospectSchemaResult<unknown>): string {\n return JSON.stringify(result, null, 2);\n}\n\n/**\n * Renders a schema tree structure from CoreSchemaView.\n * Status-glyph tree styling shared with the retired verification-tree renderer.\n */\nfunction renderSchemaTree(\n node: SchemaTreeNode,\n flags: GlobalFlags,\n options: {\n readonly isLast: boolean;\n readonly prefix: string;\n readonly useColor: boolean;\n readonly formatDimText: (text: string) => string;\n readonly isRoot?: boolean;\n },\n): string[] {\n const { isLast, prefix, useColor, formatDimText, isRoot = false } = options;\n const lines: string[] = [];\n\n // Format node label with color based on kind (matching schema-verify style)\n let formattedLabel: string = node.label;\n\n if (useColor) {\n switch (node.kind) {\n case 'root':\n formattedLabel = bold(node.label);\n break;\n case 'entity': {\n // Parse \"table tableName\" format - color \"table\" dim, tableName cyan\n const tableMatch = node.label.match(/^table\\s+(.+)$/);\n if (tableMatch?.[1]) {\n const tableName = tableMatch[1];\n formattedLabel = `${dim('table')} ${cyan(tableName)}`;\n } else {\n // Fallback: color entire label with cyan\n formattedLabel = cyan(node.label);\n }\n break;\n }\n case 'collection': {\n // \"columns\" grouping node - dim the label\n formattedLabel = dim(node.label);\n break;\n }\n case 'field': {\n // Parse column name format: \"columnName: typeDisplay (nullability)\"\n // Color code: column name (cyan), type (default), nullability (dim)\n const columnMatch = node.label.match(/^([^:]+):\\s*(.+)$/);\n if (columnMatch?.[1] && columnMatch[2]) {\n const columnName = columnMatch[1];\n const rest = columnMatch[2];\n // Parse rest: \"typeDisplay (nullability)\"\n const typeMatch = rest.match(/^([^\\s(]+)\\s*(\\([^)]+\\))$/);\n if (typeMatch?.[1] && typeMatch[2]) {\n const typeDisplay = typeMatch[1];\n const nullability = typeMatch[2];\n formattedLabel = `${cyan(columnName)}: ${typeDisplay} ${dim(nullability)}`;\n } else {\n // Fallback if format doesn't match\n formattedLabel = `${cyan(columnName)}: ${rest}`;\n }\n } else {\n formattedLabel = node.label;\n }\n break;\n }\n case 'index': {\n // Parse index/unique constraint/primary key formats\n // \"primary key: columnName\" -> dim \"primary key\", cyan columnName\n const pkMatch = node.label.match(/^primary key:\\s*(.+)$/);\n if (pkMatch?.[1]) {\n const columnNames = pkMatch[1];\n formattedLabel = `${dim('primary key')}: ${cyan(columnNames)}`;\n } else {\n // \"unique name\" -> dim \"unique\", cyan \"name\"\n const uniqueMatch = node.label.match(/^unique\\s+(.+)$/);\n if (uniqueMatch?.[1]) {\n const name = uniqueMatch[1];\n formattedLabel = `${dim('unique')} ${cyan(name)}`;\n } else {\n // \"index name\" or \"unique index name\" -> dim label prefix, cyan name\n const indexMatch = node.label.match(/^(unique\\s+)?index\\s+(.+)$/);\n if (indexMatch?.[2]) {\n const indexPrefix = indexMatch[1] ? `${dim('unique')} ` : '';\n const name = indexMatch[2];\n formattedLabel = `${indexPrefix}${dim('index')} ${cyan(name)}`;\n } else {\n formattedLabel = dim(node.label);\n }\n }\n }\n break;\n }\n case 'dependency': {\n // Parse extension message formats similar to schema-verify\n // \"extensionName extension is enabled\" -> cyan extensionName, dim rest\n const extMatch = node.label.match(/^([^\\s]+)\\s+(extension is enabled)$/);\n if (extMatch?.[1] && extMatch[2]) {\n const extName = extMatch[1];\n const rest = extMatch[2];\n formattedLabel = `${cyan(extName)} ${dim(rest)}`;\n } else {\n // Fallback: color entire label with magenta\n formattedLabel = magenta(node.label);\n }\n break;\n }\n default:\n formattedLabel = node.label;\n break;\n }\n }\n\n // Root node renders without tree characters or prefix\n if (isRoot) {\n lines.push(formattedLabel);\n } else {\n const treeChar = isLast ? '└' : '├';\n const treePrefix = `${formatDimText(treeChar)}─ `;\n lines.push(`${prefix}${treePrefix}${formattedLabel}`);\n }\n\n // Render children if present\n if (node.children && node.children.length > 0) {\n const childPrefix = isRoot ? '' : `${prefix}${isLast ? ' ' : `${formatDimText('│')} `}`;\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i];\n if (!child) continue;\n const isLastChild = i === node.children.length - 1;\n const childLines = renderSchemaTree(child, flags, {\n isLast: isLastChild,\n prefix: childPrefix,\n useColor,\n formatDimText,\n isRoot: false,\n });\n lines.push(...childLines);\n }\n }\n\n return lines;\n}\n\n/**\n * Formats human-readable output for database introspection.\n */\nexport function formatIntrospectOutput(\n result: IntrospectSchemaResult<unknown>,\n schemaView: CoreSchemaView | undefined,\n flags: GlobalFlags,\n): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n if (schemaView) {\n // Render tree structure - root node is special (no tree characters)\n const treeLines = renderSchemaTree(schemaView.root, flags, {\n isLast: true,\n prefix: '',\n useColor,\n formatDimText,\n isRoot: true,\n });\n lines.push(...treeLines);\n } else {\n // Fallback: print summary when toSchemaView is not available\n lines.push(`✔ ${result.summary}`);\n if (isVerbose(flags, 1)) {\n lines.push(` Target: ${result.target.familyId}/${result.target.id}`);\n if (result.meta?.dbUrl) {\n lines.push(` Database: ${result.meta.dbUrl}`);\n }\n }\n }\n\n // Add timings in verbose mode\n if (isVerbose(flags, 1)) {\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats human-readable output for database schema verification.\n */\nexport function formatSchemaVerifyOutput(\n result: VerifyDatabaseSchemaResult,\n flags: GlobalFlags,\n unclaimed: readonly string[] = [],\n): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatRed = createColorFormatter(useColor, red);\n const formatYellow = createColorFormatter(useColor, yellow);\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n const issueMessages = result.schema.issues.map(formatIssueMessage);\n if (issueMessages.length > 0) {\n lines.push(formatRed('Schema issues:'));\n for (const message of issueMessages) {\n lines.push(` ${formatRed('✖')} ${message}`);\n }\n }\n\n const warningMessages = (result.schema.warnings?.issues ?? []).map(formatIssueMessage);\n if (warningMessages.length > 0) {\n if (lines.length > 0) lines.push('');\n lines.push(formatYellow('Schema warnings:'));\n for (const message of warningMessages) {\n lines.push(` ${formatYellow('⚠')} ${message}`);\n }\n }\n\n if (unclaimed.length > 0) {\n const strict = result.meta?.strict ?? false;\n if (lines.length > 0) lines.push('');\n lines.push(\n (strict ? formatRed : formatYellow)('Unclaimed elements (declared by no contract):'),\n );\n for (const name of unclaimed) {\n lines.push(` ${(strict ? formatRed : formatYellow)(strict ? '✖' : '⚠')} ${name}`);\n }\n }\n\n if (isVerbose(flags, 1)) {\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n\n // Blank line before summary\n if (lines.length > 0) lines.push('');\n\n // Summary line at the end: verdict with status glyph\n if (result.ok) {\n lines.push(`${formatGreen('✔')} ${result.summary}`);\n } else {\n const codeText = result.code ? ` (${result.code})` : '';\n lines.push(`${formatRed('✖')} ${result.summary}${codeText}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats JSON output for database schema verification. The unclaimed-elements\n * list is a top-level field alongside the combined result, reported once for\n * the whole database.\n */\nexport function formatSchemaVerifyJson(\n result: VerifyDatabaseSchemaResult,\n unclaimed: readonly string[] = [],\n): string {\n return JSON.stringify({ ...result, unclaimed }, null, 2);\n}\n\n// ============================================================================\n// Sign Output Formatters\n// ============================================================================\n\n/**\n * Formats human-readable output for database sign.\n */\nexport function formatSignOutput(result: SignDatabaseResult, flags: GlobalFlags): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n if (result.ok) {\n // Main success message in white (not dimmed)\n lines.push(`${formatGreen('✔')} Database signed`);\n\n // Show from -> to hashes with clear labels\n const previousHash = result.marker.previous?.storageHash ?? 'none';\n const currentHash = result.contract.storageHash;\n\n lines.push(`${formatDimText(` from: ${previousHash}`)}`);\n lines.push(`${formatDimText(` to: ${currentHash}`)}`);\n\n if (isVerbose(flags, 1)) {\n if (result.contract.profileHash) {\n lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);\n }\n if (result.marker.previous?.profileHash) {\n lines.push(\n `${formatDimText(` previous profileHash: ${result.marker.previous.profileHash}`)}`,\n );\n }\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats JSON output for database sign.\n */\nexport function formatSignJson(result: SignDatabaseResult): string {\n return JSON.stringify(result, null, 2);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma-next/cli",
3
- "version": "0.14.0-dev.49",
3
+ "version": "0.14.0-dev.50",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -9,18 +9,18 @@
9
9
  },
10
10
  "dependencies": {
11
11
  "@clack/prompts": "^1.4.0",
12
- "@prisma-next/config": "0.14.0-dev.49",
13
- "@prisma-next/config-loader": "0.14.0-dev.49",
14
- "@prisma-next/contract": "0.14.0-dev.49",
15
- "@prisma-next/emitter": "0.14.0-dev.49",
16
- "@prisma-next/errors": "0.14.0-dev.49",
17
- "@prisma-next/framework-components": "0.14.0-dev.49",
18
- "@prisma-next/language-server": "0.14.0-dev.49",
19
- "@prisma-next/migration-tools": "0.14.0-dev.49",
20
- "@prisma-next/psl-parser": "0.14.0-dev.49",
21
- "@prisma-next/psl-printer": "0.14.0-dev.49",
22
- "@prisma-next/cli-telemetry": "0.14.0-dev.49",
23
- "@prisma-next/utils": "0.14.0-dev.49",
12
+ "@prisma-next/config": "0.14.0-dev.50",
13
+ "@prisma-next/config-loader": "0.14.0-dev.50",
14
+ "@prisma-next/contract": "0.14.0-dev.50",
15
+ "@prisma-next/emitter": "0.14.0-dev.50",
16
+ "@prisma-next/errors": "0.14.0-dev.50",
17
+ "@prisma-next/framework-components": "0.14.0-dev.50",
18
+ "@prisma-next/language-server": "0.14.0-dev.50",
19
+ "@prisma-next/migration-tools": "0.14.0-dev.50",
20
+ "@prisma-next/psl-parser": "0.14.0-dev.50",
21
+ "@prisma-next/psl-printer": "0.14.0-dev.50",
22
+ "@prisma-next/cli-telemetry": "0.14.0-dev.50",
23
+ "@prisma-next/utils": "0.14.0-dev.50",
24
24
  "arktype": "^2.2.0",
25
25
  "ci-info": "^4.3.1",
26
26
  "clipanion": "4.0.0-rc.4",
@@ -36,14 +36,14 @@
36
36
  "wrap-ansi": "^10.0.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@prisma-next/sql-contract": "0.14.0-dev.49",
40
- "@prisma-next/sql-contract-emitter": "0.14.0-dev.49",
41
- "@prisma-next/sql-contract-ts": "0.14.0-dev.49",
42
- "@prisma-next/sql-operations": "0.14.0-dev.49",
43
- "@prisma-next/sql-runtime": "0.14.0-dev.49",
44
- "@prisma-next/test-utils": "0.14.0-dev.49",
45
- "@prisma-next/tsconfig": "0.14.0-dev.49",
46
- "@prisma-next/tsdown": "0.14.0-dev.49",
39
+ "@prisma-next/sql-contract": "0.14.0-dev.50",
40
+ "@prisma-next/sql-contract-emitter": "0.14.0-dev.50",
41
+ "@prisma-next/sql-contract-ts": "0.14.0-dev.50",
42
+ "@prisma-next/sql-operations": "0.14.0-dev.50",
43
+ "@prisma-next/sql-runtime": "0.14.0-dev.50",
44
+ "@prisma-next/test-utils": "0.14.0-dev.50",
45
+ "@prisma-next/tsconfig": "0.14.0-dev.50",
46
+ "@prisma-next/tsdown": "0.14.0-dev.50",
47
47
  "@types/node": "25.9.1",
48
48
  "tsdown": "0.22.1",
49
49
  "typescript": "5.9.3",
@@ -437,8 +437,8 @@ async function executeDbVerifyCommand(
437
437
  ...ifDefined('codecCoverageSkipped', verifyResult.codecCoverageSkipped),
438
438
  schema: {
439
439
  summary: combined.result.summary,
440
- counts: combined.result.schema.counts,
441
440
  strict: combined.result.meta?.strict ?? false,
441
+ warnings: (combined.result.schema.warnings?.issues ?? []).map((issue) => issue.message),
442
442
  },
443
443
  unclaimed: combined.unclaimed,
444
444
  meta: {
@@ -153,7 +153,7 @@ export interface MigrateResult {
153
153
  * - `readAllMarkers()` for the from-state (when no `--from` is given), preserving
154
154
  * the full marker including `invariants` (not just `storageHash`).
155
155
  * - `planSpacePath()` (shared with `executeMigrate`) for per-space path selection,
156
- * which feeds `graphWalkStrategy()` with the same target hash, target invariants,
156
+ * which feeds `resolveRecordedPath()` with the same target hash, target invariants,
157
157
  * and current marker as the real apply path uses.
158
158
  *
159
159
  * Returns BEFORE any write boundary (`runMigration` / marker / DDL). No
@@ -206,7 +206,7 @@ async function executeMigrateShowCommand(
206
206
  const appGraph = aggregate.app.graph();
207
207
 
208
208
  // Resolve the --to target (defaults to the on-disk contract, same as migrate).
209
- // Also capture the ref's invariants so planSpacePath feeds graphWalkStrategy the
209
+ // Also capture the ref's invariants so planSpacePath feeds resolveRecordedPath the
210
210
  // same target invariants that real migrate would use (refInvariants ?? headRef.invariants).
211
211
  let targetHash: string = contractHash;
212
212
  let refInvariants: readonly string[] | undefined;
@@ -262,7 +262,7 @@ async function executeMigrateShowCommand(
262
262
  // - Omitted: read the live DB marker via readAllMarkers() — the same source migrate uses.
263
263
  //
264
264
  // Full marker records (storageHash + invariants) are preserved so planSpacePath
265
- // can feed graphWalkStrategy the complete currentMarker — exactly as executeMigrate
265
+ // can feed resolveRecordedPath the complete currentMarker — exactly as executeMigrate
266
266
  // does via familyInstance.readAllMarkers(). A stripped { storageHash, invariants: [] }
267
267
  // marker would produce a different `required` set and a different (incorrect) path.
268
268
  type LiveMarker = { readonly storageHash: string; readonly invariants: readonly string[] };
@@ -359,7 +359,7 @@ async function executeMigrateShowCommand(
359
359
  }
360
360
 
361
361
  // Walk the path via planSpacePath — the same helper executeMigrate uses.
362
- // planSpacePath feeds graphWalkStrategy identical inputs (targetHash, targetInvariants,
362
+ // planSpacePath feeds resolveRecordedPath identical inputs (targetHash, targetInvariants,
363
363
  // currentMarker with full invariants), so the preview path is always the path migrate runs.
364
364
  //
365
365
  // Canonical schedule order: extensions alphabetically first, then app — mirroring the
@@ -7,6 +7,7 @@ import {
7
7
  hasOperationPreview,
8
8
  type MigrationPlanOperation,
9
9
  type OperationPreview,
10
+ type SchemaOwnership,
10
11
  } from '@prisma-next/framework-components/control';
11
12
  import { canonicalizeJson } from '@prisma-next/framework-components/utils';
12
13
  import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
@@ -97,6 +98,7 @@ async function runPlannerLeg(
97
98
  contract: Contract,
98
99
  fromContract: Contract | null,
99
100
  spaceId: string,
101
+ ownership: SchemaOwnership,
100
102
  ): Promise<Result<PlannerSuccess, CliStructuredError>> {
101
103
  const fromSchema = migrations.contractToSchema(fromContract, frameworkComponents);
102
104
  const plannerResult = planner.plan({
@@ -106,6 +108,12 @@ async function runPlannerLeg(
106
108
  fromContract,
107
109
  frameworkComponents,
108
110
  spaceId,
111
+ // Offline `migration plan` is the aggregate-of-(possibly one) degenerate
112
+ // case: the same ownership consultation the live aggregate flow uses. A
113
+ // from→to extra (a table removed from the contract) is not declared by any
114
+ // space in the aggregate, so it stays a genuine drop; a table another
115
+ // space owns is never dropped.
116
+ ownership,
109
117
  });
110
118
  if (plannerResult.kind === 'failure') {
111
119
  return notOk(
@@ -498,6 +506,7 @@ async function executeMigrationPlanCommand(
498
506
  fromContract,
499
507
  null,
500
508
  aggregate.app.spaceId,
509
+ aggregate,
501
510
  );
502
511
  if (!baselineLeg.ok) {
503
512
  return notOk(baselineLeg.failure);
@@ -567,6 +576,7 @@ async function executeMigrationPlanCommand(
567
576
  aggregate.app.contract(),
568
577
  fromContract,
569
578
  aggregate.app.spaceId,
579
+ aggregate,
570
580
  );
571
581
  if (!deltaLeg.ok) {
572
582
  return notOk(deltaLeg.failure);
@@ -640,6 +650,7 @@ async function executeMigrationPlanCommand(
640
650
  aggregate.app.contract(),
641
651
  fromContract,
642
652
  aggregate.app.spaceId,
653
+ aggregate,
643
654
  );
644
655
  if (!deltaLeg.ok) {
645
656
  return notOk(deltaLeg.failure);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Backs `db init` / `db update`. Strategy: introspect → planMigration; synth-for-app + graph-walk-extensions; plan-mode + orphan-marker preflight.
2
+ * Backs `db init` / `db update`. Strategy: introspect → planMigration; planFromDiff-for-app + resolveRecordedPath-extensions; plan-mode + orphan-marker preflight.
3
3
  */
4
4
 
5
5
  import type { Contract } from '@prisma-next/contract/types';
@@ -97,10 +97,11 @@ export interface ExecuteRunOptions<TFamilyId extends string, TTargetId extends s
97
97
  * integrity violation short-circuits with a structured error.
98
98
  * 2. **Read DB state**: marker rows (`familyInstance.readAllMarkers`)
99
99
  * + introspected schema (`familyInstance.introspect`).
100
- * 3. **Plan**: {@link planMigration} chooses graph-walk vs synth per
101
- * space according to `callerPolicy.ignoreGraphFor`. The app space
102
- * is forced through synth (today's daily-driver behaviour); every
103
- * extension space walks its on-disk graph.
100
+ * 3. **Plan**: {@link planMigration} chooses `resolveRecordedPath` vs
101
+ * `planFromDiff` per space according to `callerPolicy.ignoreGraphFor`.
102
+ * The app space is forced through `planFromDiff` (today's daily-driver
103
+ * behaviour); every extension space walks its on-disk graph via
104
+ * `resolveRecordedPath`.
104
105
  * 4. **Apply** (when `mode === 'apply'`): every per-space `MigrationPlan`
105
106
  * feeds into the runner's `execute` — one outer
106
107
  * transaction across every space; failure on any space rolls back
@@ -168,9 +169,9 @@ export async function executeRun<TFamilyId extends string, TTargetId extends str
168
169
  });
169
170
  onProgress?.({ action, kind: 'spanEnd', spanId: SPAN_IDS.introspect, outcome: 'ok' });
170
171
 
171
- // 3. Plan via aggregate planner. App is forced through synth (today's
172
- // `db init` / `db update` daily-driver behaviour); extensions walk
173
- // their on-disk migration graphs.
172
+ // 3. Plan via aggregate planner. App is forced through planFromDiff
173
+ // (today's `db init` / `db update` daily-driver behaviour); extensions
174
+ // walk their on-disk migration graphs via resolveRecordedPath.
174
175
  onProgress?.({
175
176
  action,
176
177
  kind: 'spanStart',
@@ -228,9 +229,9 @@ export async function executeRun<TFamilyId extends string, TTargetId extends str
228
229
  // 5. Run mode: hand off to the shared `runMigration` primitive.
229
230
  // The runner-driving tail is identical for `db init` / `db update` /
230
231
  // `migrate` — only how each caller produces `perSpacePlans`
231
- // differs (synth + graph-walk via planMigration here; graph-walk
232
- // only for migrate). Each caller produces perSpacePlans differently;
233
- // this helper handles the shared run tail.
232
+ // differs (planFromDiff + resolveRecordedPath via planMigration here;
233
+ // resolveRecordedPath only for migrate). Each caller produces
234
+ // perSpacePlans differently; this helper handles the shared run tail.
234
235
  const applied = await runMigration({
235
236
  aggregate,
236
237
  perSpacePlans: planResult.value.perSpace,
@@ -328,7 +329,7 @@ function detectOrphanMarkers(
328
329
  }
329
330
 
330
331
  function mapPlannerError(error: PlannerError): DbInitResult | DbUpdateResult {
331
- if (error.kind === 'appSynthFailure') {
332
+ if (error.kind === 'planFromDiffFailed') {
332
333
  const failure: DbInitFailure | DbUpdateFailure = {
333
334
  code: 'PLANNING_FAILED',
334
335
  summary: 'Migration planning failed due to conflicts',
@@ -4,8 +4,10 @@ import type {
4
4
  ControlDriverInstance,
5
5
  ControlExtensionDescriptor,
6
6
  ControlFamilyInstance,
7
+ SchemaDiffIssue,
7
8
  VerifyDatabaseSchemaResult,
8
9
  } from '@prisma-next/framework-components/control';
10
+ import { hasSchemaSubjectClassifier } from '@prisma-next/framework-components/control';
9
11
  import {
10
12
  type AggregateContractSpace,
11
13
  collectAggregateNamespaces,
@@ -14,6 +16,7 @@ import {
14
16
  verifyMigration,
15
17
  } from '@prisma-next/migration-tools/aggregate';
16
18
  import { castAs } from '@prisma-next/utils/casts';
19
+ import { ifDefined } from '@prisma-next/utils/defined';
17
20
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
18
21
  import { CliStructuredError } from '../../utils/cli-errors';
19
22
  import {
@@ -115,6 +118,17 @@ export async function executeDbVerify<TFamilyId extends string, TTargetId extend
115
118
  contract: collectAggregateNamespaces(aggregate),
116
119
  });
117
120
 
121
+ // The subject-granularity + entity-kind classifiers are an injected
122
+ // capability — not every family provides one — detected the same way the
123
+ // other optional per-family capabilities are (`hasSchemaView`, …), never
124
+ // assumed.
125
+ const classifySubjectGranularity = hasSchemaSubjectClassifier(familyInstance)
126
+ ? (issue: SchemaDiffIssue) => familyInstance.classifySubjectGranularity(issue)
127
+ : undefined;
128
+ const classifyEntityKind = hasSchemaSubjectClassifier(familyInstance)
129
+ ? (issue: SchemaDiffIssue) => familyInstance.classifyEntityKind(issue)
130
+ : undefined;
131
+
118
132
  emitVerifySpan(onProgress, 'spanStart');
119
133
  const verifyResult = verifyMigration({
120
134
  aggregate,
@@ -122,6 +136,8 @@ export async function executeDbVerify<TFamilyId extends string, TTargetId extend
122
136
  schemaIntrospection,
123
137
  mode: options.mode,
124
138
  verifySchemaForSpace: createPerSpaceVerifier(options),
139
+ ...ifDefined('classifySubjectGranularity', classifySubjectGranularity),
140
+ ...ifDefined('classifyEntityKind', classifyEntityKind),
125
141
  });
126
142
  return finaliseVerifyResult({ verifyResult, aggregate, skipMarker, onProgress });
127
143
  }
@@ -276,19 +292,6 @@ function buildSkippedSchemaResult(space: AggregateContractSpace): VerifyDatabase
276
292
  target: { expected: contract.target },
277
293
  schema: {
278
294
  issues: [],
279
- schemaDiffIssues: [],
280
- root: {
281
- status: 'pass',
282
- kind: 'skipped',
283
- name: space.spaceId,
284
- contractPath: '',
285
- code: 'SKIPPED',
286
- message: 'Schema verification skipped',
287
- expected: undefined,
288
- actual: undefined,
289
- children: [],
290
- },
291
- counts: { pass: 0, warn: 0, fail: 0, totalNodes: 0 },
292
295
  },
293
296
  timings: { total: 0 },
294
297
  };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Backs the `migrate` command. Strategy: graph-walk-all-spaces, replay-only (no introspect/synth/planner).
2
+ * Backs the `migrate` command. Resolves the recorded path for every space, replay-only (no introspect/diff/planner).
3
3
  */
4
4
 
5
5
  import type { Contract } from '@prisma-next/contract/types';
@@ -12,12 +12,12 @@ import type {
12
12
  } from '@prisma-next/framework-components/control';
13
13
  import {
14
14
  type AggregateContractSpace,
15
- buildSynthMigrationEdge,
15
+ buildFabricatedMigrationEdge,
16
16
  type ContractMarkerRecordLike,
17
17
  type ContractSpaceAggregate,
18
- graphWalkStrategy,
19
18
  type PerSpacePlan,
20
19
  requireHeadRef,
20
+ resolveRecordedPath,
21
21
  } from '@prisma-next/migration-tools/aggregate';
22
22
  import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
23
23
  import { errorNoInvariantPath } from '@prisma-next/migration-tools/errors';
@@ -96,7 +96,7 @@ export interface ExecuteMigrateOptions<TFamilyId extends string, TTargetId exten
96
96
  * 1. Load aggregate from disk (loader hydrates extension graphs;
97
97
  * caller provides app-space packages).
98
98
  * 2. Read live marker rows per space (`familyInstance.readAllMarkers`).
99
- * 3. Per space: `graphWalkStrategy` plots the path from the live
99
+ * 3. Per space: `resolveRecordedPath` plots the path from the live
100
100
  * marker to `space.headRef.hash` (or `refHash` for the app
101
101
  * space when provided). Empty-graph spaces fail loudly — a
102
102
  * "never planned" space is a user-error condition for replay.
@@ -326,7 +326,7 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
326
326
  * Callers switch on `kind` and map to their own error representation:
327
327
  * `executeMigrate` throws / returns `notOk`; `executeMigrateShowCommand`
328
328
  * returns a CLI structured error. The shared discriminant guarantees both
329
- * paths feed `graphWalkStrategy` the same inputs.
329
+ * paths feed `resolveRecordedPath` the same inputs.
330
330
  *
331
331
  * @internal Exported for `executeMigrateShowCommand` to call.
332
332
  */
@@ -407,7 +407,7 @@ export function planSpacePath({
407
407
  ? space
408
408
  : { ...space, headRef: { hash: targetHash, invariants: targetInvariants } };
409
409
 
410
- const walked = graphWalkStrategy({
410
+ const walked = resolveRecordedPath({
411
411
  aggregateTargetId: aggregate.targetId,
412
412
  space: targetSpace,
413
413
  currentMarker: liveMarker,
@@ -458,9 +458,9 @@ function buildAtHeadResolution(args: {
458
458
  },
459
459
  displayOps: [],
460
460
  destinationContract: space.contract(),
461
- strategy: 'graph-walk',
461
+ strategy: 'declared-state',
462
462
  migrationEdges: [
463
- buildSynthMigrationEdge({
463
+ buildFabricatedMigrationEdge({
464
464
  currentMarkerStorageHash: liveMarker?.storageHash,
465
465
  destinationStorageHash: targetHash,
466
466
  operationCount: 0,