@prisma-next/cli 0.14.0-dev.5 → 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.
- package/dist/cli.mjs +14 -14
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-EnAjSmZg.mjs → client-DL6KE_c0.mjs} +88 -99
- package/dist/client-DL6KE_c0.mjs.map +1 -0
- package/dist/{command-helpers-6cNJZ863.mjs → command-helpers-DMG0RsR1.mjs} +3 -3
- package/dist/command-helpers-DMG0RsR1.mjs.map +1 -0
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.mjs +3 -3
- package/dist/commands/db-schema.mjs +3 -3
- package/dist/commands/db-sign.mjs +4 -4
- package/dist/commands/db-update.mjs +4 -4
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.d.mts +1 -1
- package/dist/commands/migrate.d.mts.map +1 -1
- package/dist/commands/migrate.mjs +35 -35
- package/dist/commands/migrate.mjs.map +1 -1
- package/dist/commands/migration-check.d.mts +1 -1
- package/dist/commands/migration-check.mjs +1 -1
- package/dist/commands/migration-graph.d.mts +1 -1
- package/dist/commands/migration-graph.mjs +7 -7
- package/dist/commands/migration-graph.mjs.map +1 -1
- package/dist/commands/migration-list.d.mts +6 -6
- package/dist/commands/migration-list.d.mts.map +1 -1
- package/dist/commands/migration-list.mjs +1 -1
- package/dist/commands/migration-log.d.mts +1 -1
- package/dist/commands/migration-log.mjs +1 -1
- package/dist/commands/migration-new.mjs +3 -3
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.mjs +3 -3
- package/dist/commands/migration-status.d.mts +1 -1
- package/dist/commands/migration-status.mjs +1 -1
- package/dist/commands/ref.d.mts +1 -1
- package/dist/commands/ref.mjs +2 -2
- package/dist/commands/telemetry/index.mjs +1 -1
- package/dist/{contract-at-errors-DdxWRjMc.mjs → contract-at-errors-bF4CGDl0.mjs} +2 -2
- package/dist/{contract-at-errors-DdxWRjMc.mjs.map → contract-at-errors-bF4CGDl0.mjs.map} +1 -1
- package/dist/{contract-emit-fz6pDR2n.mjs → contract-emit-BM7oxYIG.mjs} +7 -5
- package/dist/{contract-emit-fz6pDR2n.mjs.map → contract-emit-BM7oxYIG.mjs.map} +1 -1
- package/dist/{contract-emit-D4xDDFkO.mjs → contract-emit-DnCG2JaQ.mjs} +3 -3
- package/dist/{contract-emit-D4xDDFkO.mjs.map → contract-emit-DnCG2JaQ.mjs.map} +1 -1
- package/dist/{contract-infer-Ars4yjBv.mjs → contract-infer-Dq9XNQCx.mjs} +3 -3
- package/dist/{contract-infer-Ars4yjBv.mjs.map → contract-infer-Dq9XNQCx.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-Bu2fjSWV.mjs → contract-space-aggregate-loader-DAa8u6bi.mjs} +2 -2
- package/dist/{contract-space-aggregate-loader-Bu2fjSWV.mjs.map → contract-space-aggregate-loader-DAa8u6bi.mjs.map} +1 -1
- package/dist/{db-verify-CZxRV_er.mjs → db-verify-BpfJvC8O.mjs} +56 -60
- package/dist/db-verify-BpfJvC8O.mjs.map +1 -0
- package/dist/exports/control-api.d.mts +3 -3
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.mjs +2 -2
- package/dist/extension-pack-inputs-1ySHqxKG.mjs.map +1 -1
- package/dist/{format-C3XdaDuA.mjs → format-DrUSkdqR.mjs} +2 -2
- package/dist/{format-C3XdaDuA.mjs.map → format-DrUSkdqR.mjs.map} +1 -1
- package/dist/{framework-components-5RljWT7Q.mjs → framework-components-BdmVlRSS.mjs} +2 -2
- package/dist/{framework-components-5RljWT7Q.mjs.map → framework-components-BdmVlRSS.mjs.map} +1 -1
- package/dist/{init-C79Yb97u.mjs → init-C0YyVoGj.mjs} +3 -3
- package/dist/{init-C79Yb97u.mjs.map → init-C0YyVoGj.mjs.map} +1 -1
- package/dist/{inspect-live-schema-DR8Eyt29.mjs → inspect-live-schema-Bs_0M6z8.mjs} +3 -3
- package/dist/{inspect-live-schema-DR8Eyt29.mjs.map → inspect-live-schema-Bs_0M6z8.mjs.map} +1 -1
- package/dist/{migration-check-CTcJfCC4.mjs → migration-check-DYxrNfK-.mjs} +9 -9
- package/dist/migration-check-DYxrNfK-.mjs.map +1 -0
- package/dist/{migration-command-scaffold-6OT_Q-3Y.mjs → migration-command-scaffold-DB5DQLvF.mjs} +3 -3
- package/dist/{migration-command-scaffold-6OT_Q-3Y.mjs.map → migration-command-scaffold-DB5DQLvF.mjs.map} +1 -1
- package/dist/{migration-list-DVHVmMrf.mjs → migration-list-BX5w9HAn.mjs} +14 -14
- package/dist/migration-list-BX5w9HAn.mjs.map +1 -0
- package/dist/{migration-log-CQpc9JoL.mjs → migration-log-CB1KzVOG.mjs} +3 -3
- package/dist/{migration-log-CQpc9JoL.mjs.map → migration-log-CB1KzVOG.mjs.map} +1 -1
- package/dist/{migration-path-target-zK6RG6JY.mjs → migration-path-target-CEFRK2Q7.mjs} +2 -2
- package/dist/{migration-path-target-zK6RG6JY.mjs.map → migration-path-target-CEFRK2Q7.mjs.map} +1 -1
- package/dist/{migration-plan-o2aqjnfd.mjs → migration-plan-DMKl_HnV.mjs} +29 -28
- package/dist/migration-plan-DMKl_HnV.mjs.map +1 -0
- package/dist/{migration-status-2aXGtYMW.mjs → migration-status-BeNAmEju.mjs} +12 -12
- package/dist/migration-status-BeNAmEju.mjs.map +1 -0
- package/dist/{telemetry-BckOSUWG.mjs → telemetry-_19qtfXp.mjs} +2 -2
- package/dist/{telemetry-BckOSUWG.mjs.map → telemetry-_19qtfXp.mjs.map} +1 -1
- package/dist/{types-qETCCNbe.d.mts → types-DFoqNIcQ.d.mts} +26 -20
- package/dist/types-DFoqNIcQ.d.mts.map +1 -0
- package/dist/{verify-DLqhL2LU.mjs → verify-Djo-Jep_.mjs} +57 -159
- package/dist/verify-Djo-Jep_.mjs.map +1 -0
- package/package.json +21 -21
- package/src/commands/db-verify.ts +31 -19
- package/src/commands/lsp.ts +3 -1
- package/src/commands/migrate.ts +46 -43
- package/src/commands/migration-check.ts +5 -5
- package/src/commands/migration-graph.ts +4 -4
- package/src/commands/migration-list.ts +12 -12
- package/src/commands/migration-plan.ts +14 -3
- package/src/commands/migration-status.ts +9 -9
- package/src/control-api/client.ts +5 -3
- package/src/control-api/operations/contract-emit.ts +7 -6
- package/src/control-api/operations/db-init.ts +2 -2
- package/src/control-api/operations/db-run.ts +18 -17
- package/src/control-api/operations/db-verify.ts +52 -44
- package/src/control-api/operations/migrate.ts +72 -72
- package/src/control-api/operations/run-migration.ts +6 -5
- package/src/control-api/types.ts +8 -8
- package/src/utils/combine-verify-results.ts +94 -0
- package/src/utils/extension-pack-inputs.ts +1 -1
- package/src/utils/formatters/errors.ts +12 -4
- package/src/utils/formatters/verify.ts +88 -227
- package/src/utils/plan-resolution.ts +18 -18
- package/dist/client-EnAjSmZg.mjs.map +0 -1
- package/dist/command-helpers-6cNJZ863.mjs.map +0 -1
- package/dist/db-verify-CZxRV_er.mjs.map +0 -1
- package/dist/migration-check-CTcJfCC4.mjs.map +0 -1
- package/dist/migration-list-DVHVmMrf.mjs.map +0 -1
- package/dist/migration-plan-o2aqjnfd.mjs.map +0 -1
- package/dist/migration-status-2aXGtYMW.mjs.map +0 -1
- package/dist/types-qETCCNbe.d.mts.map +0 -1
- package/dist/verify-DLqhL2LU.mjs.map +0 -1
- package/src/utils/combine-schema-results.ts +0 -84
|
@@ -20,7 +20,7 @@ import { executeRun } from './db-run';
|
|
|
20
20
|
* {@link import('@prisma-next/migration-tools/aggregate').loadContractSpaceAggregate}
|
|
21
21
|
* from the supplied descriptor set + on-disk on-disk artefacts.
|
|
22
22
|
* 2. The aggregate planner runs with `callerPolicy.ignoreGraphFor`
|
|
23
|
-
* locked to the app
|
|
23
|
+
* locked to the app space — synth strategy for the app, graph-walk
|
|
24
24
|
* for every extension.
|
|
25
25
|
* 3. The runner's `execute` applies the per-space plans
|
|
26
26
|
* inside one outer transaction.
|
|
@@ -53,7 +53,7 @@ export interface ExecuteDbInitOptions<TFamilyId extends string, TTargetId extend
|
|
|
53
53
|
/**
|
|
54
54
|
* Declared extension descriptors. Defaults to an empty list, which
|
|
55
55
|
* routes through the same loader → planner → runner pipeline with no
|
|
56
|
-
* extension
|
|
56
|
+
* extension spaces in the aggregate.
|
|
57
57
|
*/
|
|
58
58
|
readonly extensionPacks?: ReadonlyArray<ControlExtensionDescriptor<TFamilyId, TTargetId>>;
|
|
59
59
|
/** Optional progress callback for observing operation progress */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Backs `db init` / `db update`. Strategy: introspect → planMigration;
|
|
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
|
|
101
|
-
*
|
|
102
|
-
* is forced through
|
|
103
|
-
* extension
|
|
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
|
|
172
|
-
// `db init` / `db update` daily-driver behaviour); extensions
|
|
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',
|
|
@@ -200,7 +201,7 @@ export async function executeRun<TFamilyId extends string, TTargetId extends str
|
|
|
200
201
|
const appResolution = orderedResolutions.find((r) => r.spaceId === aggregate.app.spaceId);
|
|
201
202
|
if (!appResolution) {
|
|
202
203
|
throw new Error(
|
|
203
|
-
'Aggregate planner returned no plan for the app
|
|
204
|
+
'Aggregate planner returned no plan for the app space — the planner is supposed to always emit one.',
|
|
204
205
|
);
|
|
205
206
|
}
|
|
206
207
|
const appPlan = appResolution.entry.plan;
|
|
@@ -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 (
|
|
232
|
-
// only for migrate). Each caller produces
|
|
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,
|
|
@@ -280,14 +281,14 @@ function aggregatePlannerWarnings(
|
|
|
280
281
|
|
|
281
282
|
/**
|
|
282
283
|
* Compare the live `_prisma_marker` rows against the aggregate's
|
|
283
|
-
* declared
|
|
284
|
+
* declared contract spaces. Any marker row whose `space` is not a space of
|
|
284
285
|
* the aggregate is an "orphan" — typically a marker left behind by
|
|
285
286
|
* an extension that was removed from `extensionPacks` without first
|
|
286
287
|
* cleaning up its on-disk migrations / database tables.
|
|
287
288
|
*
|
|
288
289
|
* Returns a {@link CliStructuredError} envelope (code `5002`,
|
|
289
290
|
* `kind: 'orphanMarker'`) for the first orphan it finds, or `null`
|
|
290
|
-
* when every marker row maps to a declared
|
|
291
|
+
* when every marker row maps to a declared contract space. Mirrors the M2
|
|
291
292
|
* `runContractSpaceVerifierMarkerCheck` envelope so downstream
|
|
292
293
|
* tooling (integration tests, JSON consumers) keeps asserting on the
|
|
293
294
|
* same shape.
|
|
@@ -296,13 +297,13 @@ function detectOrphanMarkers(
|
|
|
296
297
|
aggregate: ContractSpaceAggregate,
|
|
297
298
|
markerRows: ReadonlyMap<string, unknown>,
|
|
298
299
|
): CliStructuredError | null {
|
|
299
|
-
const
|
|
300
|
+
const aggregateSpaceIds = new Set<string>([
|
|
300
301
|
aggregate.app.spaceId,
|
|
301
302
|
...aggregate.extensions.map((m) => m.spaceId),
|
|
302
303
|
]);
|
|
303
304
|
const orphans: string[] = [];
|
|
304
305
|
for (const [spaceId, row] of markerRows) {
|
|
305
|
-
if (row !== null && row !== undefined && !
|
|
306
|
+
if (row !== null && row !== undefined && !aggregateSpaceIds.has(spaceId)) {
|
|
306
307
|
orphans.push(spaceId);
|
|
307
308
|
}
|
|
308
309
|
}
|
|
@@ -328,7 +329,7 @@ function detectOrphanMarkers(
|
|
|
328
329
|
}
|
|
329
330
|
|
|
330
331
|
function mapPlannerError(error: PlannerError): DbInitResult | DbUpdateResult {
|
|
331
|
-
if (error.kind === '
|
|
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,16 +4,19 @@ 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
|
-
type
|
|
12
|
+
type AggregateContractSpace,
|
|
11
13
|
collectAggregateNamespaces,
|
|
12
14
|
requireHeadRef,
|
|
13
15
|
type VerifierOutput,
|
|
14
16
|
verifyMigration,
|
|
15
17
|
} from '@prisma-next/migration-tools/aggregate';
|
|
16
|
-
import {
|
|
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 {
|
|
@@ -37,9 +40,9 @@ const SPAN_IDS = {
|
|
|
37
40
|
*
|
|
38
41
|
* Loader → verifier pipeline. The loader (sole descriptor-import
|
|
39
42
|
* boundary) builds a {@link import('@prisma-next/migration-tools/aggregate').ContractSpaceAggregate};
|
|
40
|
-
* the aggregate verifier bundles `markerCheck` + per-space
|
|
41
|
-
*
|
|
42
|
-
* facing `--strict` flag.
|
|
43
|
+
* the aggregate verifier bundles `markerCheck` + per-space `schemaCheck`
|
|
44
|
+
* (each contract space verified against the full schema; extras stripped to its own view).
|
|
45
|
+
* `mode: 'strict' | 'lenient'` maps directly to the user facing `--strict` flag.
|
|
43
46
|
*/
|
|
44
47
|
export interface ExecuteDbVerifyOptions<TFamilyId extends string, TTargetId extends string> {
|
|
45
48
|
readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;
|
|
@@ -63,13 +66,19 @@ export interface ExecuteDbVerifyOptions<TFamilyId extends string, TTargetId exte
|
|
|
63
66
|
* emitted, so downstream tooling and integration tests assert on the
|
|
64
67
|
* same shape).
|
|
65
68
|
*
|
|
66
|
-
* On success, the per-space
|
|
69
|
+
* On success, the per-space verify results are returned for the CLI to
|
|
67
70
|
* render. When `skipSchema` is true (`--marker-only`), the schema map
|
|
68
71
|
* is empty.
|
|
69
72
|
*/
|
|
70
73
|
export interface ExecuteDbVerifySuccess {
|
|
71
74
|
readonly schemaResults: ReadonlyMap<string, VerifyDatabaseSchemaResult>;
|
|
72
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Live element names no contract space declares, deduplicated and reported
|
|
77
|
+
* once for the whole database (never per space). Strict mode fails on a
|
|
78
|
+
* non-empty list; lenient mode surfaces it informationally.
|
|
79
|
+
*/
|
|
80
|
+
readonly unclaimed: readonly string[];
|
|
81
|
+
readonly spaceOrder: readonly string[];
|
|
73
82
|
readonly appSpaceId: string;
|
|
74
83
|
}
|
|
75
84
|
|
|
@@ -85,11 +94,11 @@ export type ExecuteDbVerifyResult = Result<ExecuteDbVerifySuccess, CliStructured
|
|
|
85
94
|
* structured CLI error.
|
|
86
95
|
* 2. **Read DB state**: marker rows + (when `skipSchema` is `false`)
|
|
87
96
|
* schema introspection.
|
|
88
|
-
* 3. **Verify**: {@link verifyMigration} returns per-space
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* callers (CLI command) can render
|
|
92
|
-
* returned to the caller verbatim.
|
|
97
|
+
* 3. **Verify**: {@link verifyMigration} returns per-space `markerCheck` +
|
|
98
|
+
* per-space `schemaCheck` (each contract space verified against the full schema,
|
|
99
|
+
* then scoped to its own contract space). Marker mismatches map to
|
|
100
|
+
* `CliStructuredError` (code `5002`) so callers (CLI command) can render
|
|
101
|
+
* and exit. Verify results are returned to the caller verbatim.
|
|
93
102
|
*/
|
|
94
103
|
export async function executeDbVerify<TFamilyId extends string, TTargetId extends string>(
|
|
95
104
|
options: ExecuteDbVerifyOptions<TFamilyId, TTargetId>,
|
|
@@ -109,13 +118,26 @@ export async function executeDbVerify<TFamilyId extends string, TTargetId extend
|
|
|
109
118
|
contract: collectAggregateNamespaces(aggregate),
|
|
110
119
|
});
|
|
111
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
|
+
|
|
112
132
|
emitVerifySpan(onProgress, 'spanStart');
|
|
113
133
|
const verifyResult = verifyMigration({
|
|
114
134
|
aggregate,
|
|
115
135
|
markersBySpaceId,
|
|
116
136
|
schemaIntrospection,
|
|
117
137
|
mode: options.mode,
|
|
118
|
-
|
|
138
|
+
verifySchemaForSpace: createPerSpaceVerifier(options),
|
|
139
|
+
...ifDefined('classifySubjectGranularity', classifySubjectGranularity),
|
|
140
|
+
...ifDefined('classifyEntityKind', classifyEntityKind),
|
|
119
141
|
});
|
|
120
142
|
return finaliseVerifyResult({ verifyResult, aggregate, skipMarker, onProgress });
|
|
121
143
|
}
|
|
@@ -166,30 +188,27 @@ async function runIntrospection<TFamilyId extends string, TTargetId extends stri
|
|
|
166
188
|
}
|
|
167
189
|
|
|
168
190
|
/**
|
|
169
|
-
* Build the per-
|
|
191
|
+
* Build the per-space schema callback handed to the aggregate verifier.
|
|
170
192
|
* When `skipSchema` is true the callback short-circuits with a synthetic
|
|
171
193
|
* `ok` result so the verifier still runs the (cheap) schemaCheck loop
|
|
172
194
|
* without invoking the family's verification path.
|
|
173
195
|
*/
|
|
174
|
-
export function
|
|
196
|
+
export function createPerSpaceVerifier<TFamilyId extends string, TTargetId extends string>(
|
|
175
197
|
options: ExecuteDbVerifyOptions<TFamilyId, TTargetId>,
|
|
176
198
|
): (
|
|
177
|
-
|
|
178
|
-
|
|
199
|
+
schema: unknown,
|
|
200
|
+
space: AggregateContractSpace,
|
|
179
201
|
verifyMode: 'strict' | 'lenient',
|
|
180
202
|
) => VerifyDatabaseSchemaResult {
|
|
181
203
|
const { skipSchema, familyInstance, frameworkComponents } = options;
|
|
182
|
-
return (
|
|
183
|
-
if (skipSchema) return buildSkippedSchemaResult(
|
|
204
|
+
return (schema, space, verifyMode) => {
|
|
205
|
+
if (skipSchema) return buildSkippedSchemaResult(space);
|
|
184
206
|
return familyInstance.verifySchema({
|
|
185
|
-
contract:
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
// family
|
|
189
|
-
schema
|
|
190
|
-
never,
|
|
191
|
-
'family TSchemaIR is opaque to migration-tools; projectedSchema is passed straight through'
|
|
192
|
-
>(projectedSchema),
|
|
207
|
+
contract: space.contract(),
|
|
208
|
+
// `familyInstance` is `ControlFamilyInstance<_, unknown>`, so `verifySchema`
|
|
209
|
+
// takes its `TSchemaIR` as `unknown` — the introspected schema passes
|
|
210
|
+
// straight through; the family narrows to its own IR node internally.
|
|
211
|
+
schema,
|
|
193
212
|
strict: verifyMode === 'strict',
|
|
194
213
|
frameworkComponents,
|
|
195
214
|
});
|
|
@@ -223,7 +242,7 @@ function emitVerifySpan(
|
|
|
223
242
|
* by the CLI's `--schema-only` mode.
|
|
224
243
|
*/
|
|
225
244
|
function finaliseVerifyResult(args: {
|
|
226
|
-
verifyResult: VerifierOutput
|
|
245
|
+
verifyResult: VerifierOutput;
|
|
227
246
|
aggregate: {
|
|
228
247
|
readonly app: { readonly spaceId: string };
|
|
229
248
|
readonly extensions: ReadonlyArray<{ readonly spaceId: string }>;
|
|
@@ -253,14 +272,15 @@ function finaliseVerifyResult(args: {
|
|
|
253
272
|
emitVerifySpan(onProgress, 'spanEndOk');
|
|
254
273
|
return ok({
|
|
255
274
|
schemaResults: verifyResult.value.schemaCheck.perSpace,
|
|
256
|
-
|
|
275
|
+
unclaimed: verifyResult.value.schemaCheck.unclaimed,
|
|
276
|
+
spaceOrder: [aggregate.app.spaceId, ...aggregate.extensions.map((e) => e.spaceId)],
|
|
257
277
|
appSpaceId: aggregate.app.spaceId,
|
|
258
278
|
});
|
|
259
279
|
}
|
|
260
280
|
|
|
261
|
-
function buildSkippedSchemaResult(
|
|
262
|
-
const contract =
|
|
263
|
-
const headRef = requireHeadRef(
|
|
281
|
+
function buildSkippedSchemaResult(space: AggregateContractSpace): VerifyDatabaseSchemaResult {
|
|
282
|
+
const contract = space.contract();
|
|
283
|
+
const headRef = requireHeadRef(space);
|
|
264
284
|
const profileHash = castAs<{ profileHash?: string }>(contract).profileHash;
|
|
265
285
|
return {
|
|
266
286
|
ok: true,
|
|
@@ -272,18 +292,6 @@ function buildSkippedSchemaResult(member: ContractSpaceMember): VerifyDatabaseSc
|
|
|
272
292
|
target: { expected: contract.target },
|
|
273
293
|
schema: {
|
|
274
294
|
issues: [],
|
|
275
|
-
root: {
|
|
276
|
-
status: 'pass',
|
|
277
|
-
kind: 'skipped',
|
|
278
|
-
name: member.spaceId,
|
|
279
|
-
contractPath: '',
|
|
280
|
-
code: 'SKIPPED',
|
|
281
|
-
message: 'Schema verification skipped',
|
|
282
|
-
expected: undefined,
|
|
283
|
-
actual: undefined,
|
|
284
|
-
children: [],
|
|
285
|
-
},
|
|
286
|
-
counts: { pass: 0, warn: 0, fail: 0, totalNodes: 0 },
|
|
287
295
|
},
|
|
288
296
|
timings: { total: 0 },
|
|
289
297
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Backs the `migrate` command.
|
|
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';
|
|
@@ -11,13 +11,13 @@ import type {
|
|
|
11
11
|
TargetMigrationsCapability,
|
|
12
12
|
} from '@prisma-next/framework-components/control';
|
|
13
13
|
import {
|
|
14
|
-
|
|
14
|
+
type AggregateContractSpace,
|
|
15
|
+
buildFabricatedMigrationEdge,
|
|
15
16
|
type ContractMarkerRecordLike,
|
|
16
17
|
type ContractSpaceAggregate,
|
|
17
|
-
type ContractSpaceMember,
|
|
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';
|
|
@@ -62,8 +62,8 @@ export interface ExecuteMigrateOptions<TFamilyId extends string, TTargetId exten
|
|
|
62
62
|
readonly extensionPacks: ReadonlyArray<ControlExtensionDescriptor<TFamilyId, TTargetId>>;
|
|
63
63
|
readonly targetId: TTargetId;
|
|
64
64
|
/**
|
|
65
|
-
* Optional app-space ref override. When provided, the app
|
|
66
|
-
* graph-walk targets this hash instead of `
|
|
65
|
+
* Optional app-space ref override. When provided, the app space's
|
|
66
|
+
* graph-walk targets this hash instead of `space.headRef.hash`.
|
|
67
67
|
* Extensions are unaffected — they always walk to their own head.
|
|
68
68
|
*/
|
|
69
69
|
readonly refHash?: string;
|
|
@@ -72,7 +72,7 @@ export interface ExecuteMigrateOptions<TFamilyId extends string, TTargetId exten
|
|
|
72
72
|
* Threaded into the graph-walk's `required` calculation so the
|
|
73
73
|
* planner picks an invariant-bearing path and surfaces the
|
|
74
74
|
* required/satisfied set on the success envelope. When `refHash`
|
|
75
|
-
* is absent the file's `
|
|
75
|
+
* is absent the file's `space.headRef.invariants` are used.
|
|
76
76
|
*/
|
|
77
77
|
readonly refInvariants?: readonly string[];
|
|
78
78
|
/**
|
|
@@ -89,16 +89,16 @@ export interface ExecuteMigrateOptions<TFamilyId extends string, TTargetId exten
|
|
|
89
89
|
/**
|
|
90
90
|
* Apply pending migrations across every contract space (app +
|
|
91
91
|
* extensions). Replay-only: graph-walk against the on-disk graph for
|
|
92
|
-
* every
|
|
92
|
+
* every contract space; no synth, no introspection.
|
|
93
93
|
*
|
|
94
94
|
* Pipeline:
|
|
95
95
|
*
|
|
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
|
|
100
|
-
* marker to `
|
|
101
|
-
*
|
|
99
|
+
* 3. Per space: `resolveRecordedPath` plots the path from the live
|
|
100
|
+
* marker to `space.headRef.hash` (or `refHash` for the app
|
|
101
|
+
* space when provided). Empty-graph spaces fail loudly — a
|
|
102
102
|
* "never planned" space is a user-error condition for replay.
|
|
103
103
|
* 4. Hand off to {@link runMigration} (the runner-driving tail
|
|
104
104
|
* shared with `db init` / `db update`). Marker advancement is
|
|
@@ -140,41 +140,41 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
|
|
|
140
140
|
|
|
141
141
|
const markerRows = await familyInstance.readAllMarkers({ driver });
|
|
142
142
|
|
|
143
|
-
// Plan every
|
|
143
|
+
// Plan every space via graph-walk. App space targets `refHash`
|
|
144
144
|
// when provided, otherwise its own head; extensions always walk
|
|
145
145
|
// to their own head ref.
|
|
146
|
-
const
|
|
146
|
+
const allSpaces: ReadonlyArray<AggregateContractSpace> = [aggregate.app, ...aggregate.extensions];
|
|
147
147
|
const perSpacePlans = new Map<string, PerSpacePlan>();
|
|
148
|
-
// Already-at-head empty-graph
|
|
148
|
+
// Already-at-head empty-graph spaces (typically extensions whose
|
|
149
149
|
// head ref is the empty sentinel, or whose live marker already
|
|
150
150
|
// matches the target). Kept out of the runner schedule so we don't
|
|
151
151
|
// write spurious markers for greenfield extensions, but merged back
|
|
152
|
-
// into the success envelope so every loaded
|
|
152
|
+
// into the success envelope so every loaded space is represented.
|
|
153
153
|
const atHeadResolutions = new Map<string, PerSpacePlan>();
|
|
154
|
-
for (const
|
|
155
|
-
const
|
|
156
|
-
// The aggregate passed the integrity gate, so every
|
|
154
|
+
for (const space of allSpaces) {
|
|
155
|
+
const isAppSpace = space.spaceId === aggregate.app.spaceId;
|
|
156
|
+
// The aggregate passed the integrity gate, so every space's head ref
|
|
157
157
|
// is resolved (the app's is synthesised from the live contract).
|
|
158
|
-
const headRef = requireHeadRef(
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
const liveMarker = markerRows.get(
|
|
158
|
+
const headRef = requireHeadRef(space);
|
|
159
|
+
const spaceTargetHash = isAppSpace && refHash !== undefined ? refHash : headRef.hash;
|
|
160
|
+
const spaceRefInvariants = isAppSpace && refHash !== undefined ? refInvariants : undefined;
|
|
161
|
+
const liveMarker = markerRows.get(space.spaceId) ?? null;
|
|
162
162
|
|
|
163
|
-
const outcome =
|
|
164
|
-
|
|
163
|
+
const outcome = planSpacePath({
|
|
164
|
+
space,
|
|
165
165
|
aggregate,
|
|
166
|
-
targetHash:
|
|
167
|
-
refInvariants:
|
|
166
|
+
targetHash: spaceTargetHash,
|
|
167
|
+
refInvariants: spaceRefInvariants,
|
|
168
168
|
liveMarker,
|
|
169
|
-
...(
|
|
169
|
+
...(isAppSpace ? { refName } : {}),
|
|
170
170
|
});
|
|
171
171
|
|
|
172
172
|
if (outcome.kind === 'at-head') {
|
|
173
|
-
// Empty-graph
|
|
173
|
+
// Empty-graph space whose live marker already matches the target.
|
|
174
174
|
// Kept out of the runner schedule so we don't write spurious markers
|
|
175
175
|
// for greenfield extensions, but merged back into the success envelope
|
|
176
|
-
// so every loaded
|
|
177
|
-
atHeadResolutions.set(
|
|
176
|
+
// so every loaded space is represented.
|
|
177
|
+
atHeadResolutions.set(space.spaceId, outcome.plan);
|
|
178
178
|
continue;
|
|
179
179
|
}
|
|
180
180
|
if (outcome.kind === 'never-planned') {
|
|
@@ -197,9 +197,9 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
|
|
|
197
197
|
// is never a graph node, producing an empty `structuralPath` and
|
|
198
198
|
// a less actionable diagnostic.
|
|
199
199
|
const structural = findPathWithDecision(
|
|
200
|
-
outcome.
|
|
200
|
+
outcome.targetSpace.graph(),
|
|
201
201
|
outcome.liveHash,
|
|
202
|
-
|
|
202
|
+
spaceTargetHash,
|
|
203
203
|
{ required: new Set<string>() },
|
|
204
204
|
);
|
|
205
205
|
const structuralPath =
|
|
@@ -220,14 +220,14 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
|
|
|
220
220
|
});
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
perSpacePlans.set(
|
|
223
|
+
perSpacePlans.set(space.spaceId, outcome.plan);
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
const canonicalOrder = [...aggregate.extensions.map((m) => m.spaceId), aggregate.app.spaceId];
|
|
227
227
|
const applyOrder = canonicalOrder.filter((spaceId) => perSpacePlans.has(spaceId));
|
|
228
228
|
|
|
229
229
|
// Short-circuit: nothing pending across any space (no runner-bound
|
|
230
|
-
// plans). Surfaces every loaded
|
|
230
|
+
// plans). Surfaces every loaded space — including at-head empty-
|
|
231
231
|
// graph extensions — in `perSpace[]` so the result reflects the
|
|
232
232
|
// full aggregate, not just the spaces the runner would have touched.
|
|
233
233
|
const totalPlannedOps = sumPlannedOps(applyOrder, perSpacePlans);
|
|
@@ -285,7 +285,7 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
|
|
|
285
285
|
}
|
|
286
286
|
|
|
287
287
|
// Merge at-head zero-op resolutions back into the canonical order
|
|
288
|
-
// so the success envelope surfaces every loaded
|
|
288
|
+
// so the success envelope surfaces every loaded space, not just
|
|
289
289
|
// those the runner executed.
|
|
290
290
|
const orderedAll = canonicalOrder
|
|
291
291
|
.filter((spaceId) => perSpacePlans.has(spaceId) || atHeadResolutions.has(spaceId))
|
|
@@ -321,16 +321,16 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
|
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
/**
|
|
324
|
-
* Outcome variants for one
|
|
324
|
+
* Outcome variants for one space's path computation.
|
|
325
325
|
*
|
|
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 `
|
|
329
|
+
* paths feed `resolveRecordedPath` the same inputs.
|
|
330
330
|
*
|
|
331
331
|
* @internal Exported for `executeMigrateShowCommand` to call.
|
|
332
332
|
*/
|
|
333
|
-
export type
|
|
333
|
+
export type SpacePathOutcome =
|
|
334
334
|
| { readonly kind: 'ok'; readonly plan: PerSpacePlan }
|
|
335
335
|
| { readonly kind: 'at-head'; readonly plan: PerSpacePlan }
|
|
336
336
|
| { readonly kind: 'never-planned'; readonly spaceId: string; readonly targetHash: string }
|
|
@@ -343,16 +343,16 @@ export type MemberPathOutcome =
|
|
|
343
343
|
| {
|
|
344
344
|
readonly kind: 'unsatisfiable';
|
|
345
345
|
readonly spaceId: string;
|
|
346
|
-
readonly
|
|
346
|
+
readonly isAppSpace: boolean;
|
|
347
347
|
readonly missing: readonly string[];
|
|
348
348
|
readonly targetInvariants: readonly string[];
|
|
349
|
-
readonly
|
|
349
|
+
readonly targetSpace: AggregateContractSpace;
|
|
350
350
|
readonly liveHash: string;
|
|
351
351
|
readonly refName: string | undefined;
|
|
352
352
|
};
|
|
353
353
|
|
|
354
354
|
/**
|
|
355
|
-
* Compute the graph-walk path for one contract
|
|
355
|
+
* Compute the graph-walk path for one contract space.
|
|
356
356
|
*
|
|
357
357
|
* Encapsulates the invariant-correct input assembly that both
|
|
358
358
|
* `executeMigrate` and `executeMigrateShowCommand` must use:
|
|
@@ -361,71 +361,71 @@ export type MemberPathOutcome =
|
|
|
361
361
|
* - `targetInvariants` uses the caller-supplied `refInvariants` when a
|
|
362
362
|
* `--to` ref was resolved (not always the file head ref's invariants).
|
|
363
363
|
*
|
|
364
|
-
* Both callers map the returned `
|
|
364
|
+
* Both callers map the returned `SpacePathOutcome` to their own error
|
|
365
365
|
* representation; the path-compute logic is shared and identical.
|
|
366
366
|
*
|
|
367
367
|
* @internal Exported for `executeMigrateShowCommand`.
|
|
368
368
|
*/
|
|
369
|
-
export function
|
|
370
|
-
|
|
369
|
+
export function planSpacePath({
|
|
370
|
+
space,
|
|
371
371
|
aggregate,
|
|
372
372
|
targetHash,
|
|
373
373
|
refInvariants,
|
|
374
374
|
liveMarker,
|
|
375
375
|
refName,
|
|
376
376
|
}: {
|
|
377
|
-
readonly
|
|
377
|
+
readonly space: AggregateContractSpace;
|
|
378
378
|
readonly aggregate: Pick<ContractSpaceAggregate, 'targetId' | 'app'>;
|
|
379
379
|
readonly targetHash: string;
|
|
380
380
|
readonly refInvariants: readonly string[] | undefined;
|
|
381
381
|
readonly liveMarker: ContractMarkerRecordLike | null;
|
|
382
382
|
readonly refName?: string;
|
|
383
|
-
}):
|
|
384
|
-
const
|
|
385
|
-
const headRef = requireHeadRef(
|
|
383
|
+
}): SpacePathOutcome {
|
|
384
|
+
const isAppSpace = space.spaceId === aggregate.app.spaceId;
|
|
385
|
+
const headRef = requireHeadRef(space);
|
|
386
386
|
|
|
387
|
-
if (
|
|
387
|
+
if (space.graph().nodes.size === 0) {
|
|
388
388
|
const liveHash = liveMarker?.storageHash;
|
|
389
389
|
if (targetHash === liveHash || (liveHash === undefined && targetHash === EMPTY_CONTRACT_HASH)) {
|
|
390
390
|
return {
|
|
391
391
|
kind: 'at-head',
|
|
392
392
|
plan: buildAtHeadResolution({
|
|
393
393
|
aggregateTargetId: aggregate.targetId,
|
|
394
|
-
|
|
394
|
+
space,
|
|
395
395
|
targetHash,
|
|
396
396
|
liveMarker,
|
|
397
397
|
}),
|
|
398
398
|
};
|
|
399
399
|
}
|
|
400
|
-
return { kind: 'never-planned', spaceId:
|
|
400
|
+
return { kind: 'never-planned', spaceId: space.spaceId, targetHash };
|
|
401
401
|
}
|
|
402
402
|
|
|
403
403
|
const targetInvariants =
|
|
404
|
-
|
|
405
|
-
const
|
|
404
|
+
isAppSpace && refInvariants !== undefined ? refInvariants : headRef.invariants;
|
|
405
|
+
const targetSpace: AggregateContractSpace =
|
|
406
406
|
targetHash === headRef.hash && targetInvariants === headRef.invariants
|
|
407
|
-
?
|
|
408
|
-
: { ...
|
|
407
|
+
? space
|
|
408
|
+
: { ...space, headRef: { hash: targetHash, invariants: targetInvariants } };
|
|
409
409
|
|
|
410
|
-
const walked =
|
|
410
|
+
const walked = resolveRecordedPath({
|
|
411
411
|
aggregateTargetId: aggregate.targetId,
|
|
412
|
-
|
|
412
|
+
space: targetSpace,
|
|
413
413
|
currentMarker: liveMarker,
|
|
414
|
-
...(
|
|
414
|
+
...(isAppSpace && refName !== undefined ? { refName } : {}),
|
|
415
415
|
});
|
|
416
416
|
|
|
417
417
|
if (walked.kind === 'unreachable') {
|
|
418
|
-
return { kind: 'unreachable', spaceId:
|
|
418
|
+
return { kind: 'unreachable', spaceId: space.spaceId, liveMarker, targetHash };
|
|
419
419
|
}
|
|
420
420
|
if (walked.kind === 'unsatisfiable') {
|
|
421
421
|
const liveHash = liveMarker?.storageHash ?? EMPTY_CONTRACT_HASH;
|
|
422
422
|
return {
|
|
423
423
|
kind: 'unsatisfiable',
|
|
424
|
-
spaceId:
|
|
425
|
-
|
|
424
|
+
spaceId: space.spaceId,
|
|
425
|
+
isAppSpace,
|
|
426
426
|
missing: walked.missing,
|
|
427
427
|
targetInvariants,
|
|
428
|
-
|
|
428
|
+
targetSpace,
|
|
429
429
|
liveHash,
|
|
430
430
|
refName,
|
|
431
431
|
};
|
|
@@ -435,32 +435,32 @@ export function planMemberPath({
|
|
|
435
435
|
|
|
436
436
|
/**
|
|
437
437
|
* Build a zero-op {@link PerSpacePlan} for an empty-graph
|
|
438
|
-
*
|
|
439
|
-
* pipeline thread the
|
|
438
|
+
* space whose live marker already matches the target. Lets the apply
|
|
439
|
+
* pipeline thread the space through `perSpacePlans` -> `applyOrder`
|
|
440
440
|
* -> the success envelope's `perSpace[]` block so the result reflects
|
|
441
441
|
* every loaded space, even when there is nothing to execute.
|
|
442
442
|
*/
|
|
443
443
|
function buildAtHeadResolution(args: {
|
|
444
444
|
readonly aggregateTargetId: string;
|
|
445
|
-
readonly
|
|
445
|
+
readonly space: AggregateContractSpace;
|
|
446
446
|
readonly targetHash: string;
|
|
447
447
|
readonly liveMarker: ContractMarkerRecordLike | null;
|
|
448
448
|
}): PerSpacePlan {
|
|
449
|
-
const { aggregateTargetId,
|
|
449
|
+
const { aggregateTargetId, space, targetHash, liveMarker } = args;
|
|
450
450
|
return {
|
|
451
451
|
plan: {
|
|
452
452
|
targetId: aggregateTargetId,
|
|
453
|
-
spaceId:
|
|
453
|
+
spaceId: space.spaceId,
|
|
454
454
|
origin: liveMarker === null ? null : { storageHash: liveMarker.storageHash },
|
|
455
455
|
destination: { storageHash: targetHash },
|
|
456
456
|
operations: [],
|
|
457
457
|
providedInvariants: [],
|
|
458
458
|
},
|
|
459
459
|
displayOps: [],
|
|
460
|
-
destinationContract:
|
|
461
|
-
strategy: '
|
|
460
|
+
destinationContract: space.contract(),
|
|
461
|
+
strategy: 'declared-state',
|
|
462
462
|
migrationEdges: [
|
|
463
|
-
|
|
463
|
+
buildFabricatedMigrationEdge({
|
|
464
464
|
currentMarkerStorageHash: liveMarker?.storageHash,
|
|
465
465
|
destinationStorageHash: targetHash,
|
|
466
466
|
operationCount: 0,
|
|
@@ -494,7 +494,7 @@ interface BuildSuccessArgs {
|
|
|
494
494
|
}
|
|
495
495
|
|
|
496
496
|
function buildSuccess(args: BuildSuccessArgs): MigrateSuccess {
|
|
497
|
-
// The marker hash surfaced at the top level is the **app
|
|
497
|
+
// The marker hash surfaced at the top level is the **app space's**
|
|
498
498
|
// post-migrate marker (the top-level `markerHash` field).
|
|
499
499
|
// Per-space markers live on `perSpace[].marker.storageHash`.
|
|
500
500
|
const appResolution = args.orderedResolutions.find(
|
|
@@ -586,7 +586,7 @@ export function buildPathNotFoundFailure(
|
|
|
586
586
|
const fromHash = marker?.storageHash ?? '<empty>';
|
|
587
587
|
// The app-case phrasing names the user-visible condition (a
|
|
588
588
|
// contract has been emitted that no on-disk migration reaches) so
|
|
589
|
-
// the error reads naturally for the app
|
|
589
|
+
// the error reads naturally for the app space. Extension spaces
|
|
590
590
|
// see the same condition expressed against the offending space.
|
|
591
591
|
const summary =
|
|
592
592
|
spaceId === 'app'
|