@prisma-next/migration-tools 0.12.0-dev.5 → 0.12.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/exports/aggregate.d.mts +13 -5
- package/dist/exports/aggregate.d.mts.map +1 -1
- package/dist/exports/aggregate.mjs +33 -12
- package/dist/exports/aggregate.mjs.map +1 -1
- package/dist/exports/ledger-origin.d.mts +5 -0
- package/dist/exports/ledger-origin.d.mts.map +1 -0
- package/dist/exports/ledger-origin.mjs +10 -0
- package/dist/exports/ledger-origin.mjs.map +1 -0
- package/dist/exports/spaces.d.mts.map +1 -1
- package/dist/exports/spaces.mjs +1 -15
- package/dist/exports/spaces.mjs.map +1 -1
- package/package.json +11 -7
- package/src/aggregate/planner-types.ts +5 -4
- package/src/aggregate/planner.ts +2 -0
- package/src/aggregate/strategies/synth.ts +14 -0
- package/src/aggregate/synth-migration-edge.ts +15 -0
- package/src/assert-descriptor-self-consistency.ts +1 -24
- package/src/exports/aggregate.ts +1 -0
- package/src/exports/ledger-origin.ts +1 -0
- package/src/ledger-origin.ts +8 -0
|
@@ -456,11 +456,12 @@ interface PerSpacePlan {
|
|
|
456
456
|
readonly displayOps: readonly MigrationPlanOperation[];
|
|
457
457
|
readonly destinationContract: Contract;
|
|
458
458
|
readonly strategy: 'graph-walk' | 'synth';
|
|
459
|
+
readonly warnings?: readonly MigrationPlannerConflict[];
|
|
459
460
|
/**
|
|
460
|
-
* Per-edge breakdown of the chain.
|
|
461
|
-
*
|
|
461
|
+
* Per-edge breakdown of the chain. Graph-walk plans carry one entry per
|
|
462
|
+
* authored edge; synth and at-head plans carry a single synthesised edge.
|
|
462
463
|
*/
|
|
463
|
-
readonly migrationEdges
|
|
464
|
+
readonly migrationEdges: readonly AggregateMigrationEdgeRef[];
|
|
464
465
|
/**
|
|
465
466
|
* Path decision data the strategy used to select the chain
|
|
466
467
|
* (alternative count, tie-break reasons, required/satisfied
|
|
@@ -468,7 +469,7 @@ interface PerSpacePlan {
|
|
|
468
469
|
* strategy; absent for synth-produced plans.
|
|
469
470
|
*
|
|
470
471
|
* `migrate` surfaces this for the app member as
|
|
471
|
-
* `
|
|
472
|
+
* `MigrateSuccess.pathDecision` (back-compat with single-
|
|
472
473
|
* space callers).
|
|
473
474
|
*/
|
|
474
475
|
readonly pathDecision?: PathDecision;
|
|
@@ -638,6 +639,13 @@ interface GraphWalkStrategyInputs {
|
|
|
638
639
|
*/
|
|
639
640
|
declare function graphWalkStrategy(input: GraphWalkStrategyInputs): GraphWalkOutcome;
|
|
640
641
|
//#endregion
|
|
642
|
+
//#region src/aggregate/synth-migration-edge.d.ts
|
|
643
|
+
declare function buildSynthMigrationEdge(args: {
|
|
644
|
+
readonly currentMarkerStorageHash: string | null | undefined;
|
|
645
|
+
readonly destinationStorageHash: string;
|
|
646
|
+
readonly operationCount: number;
|
|
647
|
+
}): AggregateMigrationEdgeRef;
|
|
648
|
+
//#endregion
|
|
641
649
|
//#region src/aggregate/verifier.d.ts
|
|
642
650
|
/**
|
|
643
651
|
* Caller policy for the verifier. Today's only knob is
|
|
@@ -744,5 +752,5 @@ type VerifierOutput<TSchemaResult> = Result<VerifierSuccess<TSchemaResult>, Veri
|
|
|
744
752
|
*/
|
|
745
753
|
declare function verifyMigration<TSchemaResult>(input: VerifierInput<TSchemaResult>): VerifierOutput<TSchemaResult>;
|
|
746
754
|
//#endregion
|
|
747
|
-
export { type AggregateCurrentDBState, type AggregateMigrationEdgeRef, type CallerPolicy, type ContractAtOptions, type ContractAtResult, type ContractMarkerRecordLike, type ContractSpaceAggregate, type ContractSpaceMember, type DeclaredExtensionEntry, type GraphWalkOutcome, type GraphWalkStrategyInputs, type IntegrityComputationInput, type IntegrityQueryOptions, type IntegritySpaceState, type IntegrityViolation, type LoadAggregateInput, type MarkerCheckResult, type MarkerCheckSection, type OrphanElement, type PerSpacePlan, type PlannerError, type PlannerInput, type PlannerOutput, type PlannerSuccess, type SchemaCheckSection, type VerifierError, type VerifierInput, type VerifierOutput, type VerifierSuccess, computeIntegrityViolations, createContractSpaceAggregate, createContractSpaceMember, graphWalkStrategy, loadContractSpaceAggregate, loadProblemToViolation, planMigration, projectSchemaToSpace, requireHeadRef, verifyMigration };
|
|
755
|
+
export { type AggregateCurrentDBState, type AggregateMigrationEdgeRef, type CallerPolicy, type ContractAtOptions, type ContractAtResult, type ContractMarkerRecordLike, type ContractSpaceAggregate, type ContractSpaceMember, type DeclaredExtensionEntry, type GraphWalkOutcome, type GraphWalkStrategyInputs, type IntegrityComputationInput, type IntegrityQueryOptions, type IntegritySpaceState, type IntegrityViolation, type LoadAggregateInput, type MarkerCheckResult, type MarkerCheckSection, type OrphanElement, type PerSpacePlan, type PlannerError, type PlannerInput, type PlannerOutput, type PlannerSuccess, type SchemaCheckSection, type VerifierError, type VerifierInput, type VerifierOutput, type VerifierSuccess, buildSynthMigrationEdge, computeIntegrityViolations, createContractSpaceAggregate, createContractSpaceMember, graphWalkStrategy, loadContractSpaceAggregate, loadProblemToViolation, planMigration, projectSchemaToSpace, requireHeadRef, verifyMigration };
|
|
748
756
|
//# sourceMappingURL=aggregate.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregate.d.mts","names":[],"sources":["../../src/integrity-violation.ts","../../src/aggregate/types.ts","../../src/aggregate/aggregate.ts","../../src/aggregate/check-integrity.ts","../../src/aggregate/loader.ts","../../src/aggregate/marker-types.ts","../../src/aggregate/planner-types.ts","../../src/aggregate/planner.ts","../../src/aggregate/project-schema-to-space.ts","../../src/aggregate/strategies/graph-walk.ts","../../src/aggregate/verifier.ts"],"mappings":"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
|
|
1
|
+
{"version":3,"file":"aggregate.d.mts","names":[],"sources":["../../src/integrity-violation.ts","../../src/aggregate/types.ts","../../src/aggregate/aggregate.ts","../../src/aggregate/check-integrity.ts","../../src/aggregate/loader.ts","../../src/aggregate/marker-types.ts","../../src/aggregate/planner-types.ts","../../src/aggregate/planner.ts","../../src/aggregate/project-schema-to-space.ts","../../src/aggregate/strategies/graph-walk.ts","../../src/aggregate/synth-migration-edge.ts","../../src/aggregate/verifier.ts"],"mappings":"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"}
|
|
@@ -655,6 +655,17 @@ function pruneCollectionsArray(schemaObj, ownedByOthers) {
|
|
|
655
655
|
};
|
|
656
656
|
}
|
|
657
657
|
//#endregion
|
|
658
|
+
//#region src/aggregate/synth-migration-edge.ts
|
|
659
|
+
function buildSynthMigrationEdge(args) {
|
|
660
|
+
return {
|
|
661
|
+
dirName: "",
|
|
662
|
+
migrationHash: args.destinationStorageHash,
|
|
663
|
+
from: args.currentMarkerStorageHash ?? "",
|
|
664
|
+
to: args.destinationStorageHash,
|
|
665
|
+
operationCount: args.operationCount
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
//#endregion
|
|
658
669
|
//#region src/aggregate/strategies/synth.ts
|
|
659
670
|
/**
|
|
660
671
|
* Synthesise a migration plan for a single member by projecting the
|
|
@@ -694,22 +705,30 @@ async function synthStrategy(input) {
|
|
|
694
705
|
conflicts: plannerResult.conflicts
|
|
695
706
|
};
|
|
696
707
|
const synthedPlan = plannerResult.plan;
|
|
708
|
+
const plan = new Proxy(synthedPlan, {
|
|
709
|
+
get(target, prop) {
|
|
710
|
+
if (prop === "targetId") return input.aggregateTargetId;
|
|
711
|
+
return Reflect.get(target, prop, target);
|
|
712
|
+
},
|
|
713
|
+
has(target, prop) {
|
|
714
|
+
if (prop === "targetId") return true;
|
|
715
|
+
return Reflect.has(target, prop);
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
const destinationStorageHash = synthedPlan.destination.storageHash;
|
|
697
719
|
return {
|
|
698
720
|
kind: "ok",
|
|
699
721
|
result: {
|
|
700
|
-
plan
|
|
701
|
-
get(target, prop) {
|
|
702
|
-
if (prop === "targetId") return input.aggregateTargetId;
|
|
703
|
-
return Reflect.get(target, prop, target);
|
|
704
|
-
},
|
|
705
|
-
has(target, prop) {
|
|
706
|
-
if (prop === "targetId") return true;
|
|
707
|
-
return Reflect.has(target, prop);
|
|
708
|
-
}
|
|
709
|
-
}),
|
|
722
|
+
plan,
|
|
710
723
|
displayOps: synthedPlan.operations,
|
|
711
724
|
destinationContract: input.member.contract(),
|
|
712
|
-
strategy: "synth"
|
|
725
|
+
strategy: "synth",
|
|
726
|
+
...plannerResult.warnings && plannerResult.warnings.length > 0 ? { warnings: plannerResult.warnings } : {},
|
|
727
|
+
migrationEdges: [buildSynthMigrationEdge({
|
|
728
|
+
currentMarkerStorageHash: input.currentMarker?.storageHash,
|
|
729
|
+
destinationStorageHash,
|
|
730
|
+
operationCount: synthedPlan.operations.length
|
|
731
|
+
})]
|
|
713
732
|
}
|
|
714
733
|
};
|
|
715
734
|
}
|
|
@@ -756,6 +775,7 @@ async function planMigration(input) {
|
|
|
756
775
|
if (ignoreGraph) {
|
|
757
776
|
const synthOutcome = await synthStrategy({
|
|
758
777
|
aggregateTargetId: aggregate.targetId,
|
|
778
|
+
currentMarker,
|
|
759
779
|
member,
|
|
760
780
|
otherMembers,
|
|
761
781
|
schemaIntrospection: currentDBState.schemaIntrospection,
|
|
@@ -800,6 +820,7 @@ async function planMigration(input) {
|
|
|
800
820
|
});
|
|
801
821
|
const synthOutcome = await synthStrategy({
|
|
802
822
|
aggregateTargetId: aggregate.targetId,
|
|
823
|
+
currentMarker,
|
|
803
824
|
member,
|
|
804
825
|
otherMembers,
|
|
805
826
|
schemaIntrospection: currentDBState.schemaIntrospection,
|
|
@@ -931,6 +952,6 @@ function detectOrphanElements(schemaIntrospection, members) {
|
|
|
931
952
|
return orphans;
|
|
932
953
|
}
|
|
933
954
|
//#endregion
|
|
934
|
-
export { computeIntegrityViolations, createContractSpaceAggregate, createContractSpaceMember, graphWalkStrategy, loadContractSpaceAggregate, loadProblemToViolation, planMigration, projectSchemaToSpace, requireHeadRef, verifyMigration };
|
|
955
|
+
export { buildSynthMigrationEdge, computeIntegrityViolations, createContractSpaceAggregate, createContractSpaceMember, graphWalkStrategy, loadContractSpaceAggregate, loadProblemToViolation, planMigration, projectSchemaToSpace, requireHeadRef, verifyMigration };
|
|
935
956
|
|
|
936
957
|
//# sourceMappingURL=aggregate.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregate.mjs","names":["detailOf"],"sources":["../../src/aggregate/aggregate.ts","../../src/aggregate/check-integrity.ts","../../src/aggregate/loader.ts","../../src/aggregate/strategies/graph-walk.ts","../../src/aggregate/project-schema-to-space.ts","../../src/aggregate/strategies/synth.ts","../../src/aggregate/planner.ts","../../src/aggregate/verifier.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport type { Contract } from '@prisma-next/contract/types';\nimport { join } from 'pathe';\nimport {\n errorBundleNotFoundForGraphNode,\n errorContractDeserializationFailed,\n errorHashNotInGraph,\n errorInvalidJson,\n errorMissingFile,\n errorSnapshotMissing,\n MigrationToolsError,\n} from '../errors';\nimport type { MigrationGraph } from '../graph';\nimport { isGraphNode } from '../graph-membership';\nimport type { IntegrityQueryOptions, IntegrityViolation } from '../integrity-violation';\nimport { reconstructGraph } from '../migration-graph';\nimport type { OnDiskMigrationPackage } from '../package';\nimport type { Refs } from '../refs';\nimport { readRefSnapshot } from '../refs/snapshot';\nimport type { ContractSpaceHeadRecord } from '../verify-contract-spaces';\nimport type {\n ContractAtOptions,\n ContractAtResult,\n ContractSpaceAggregate,\n ContractSpaceMember,\n} from './types';\n\nfunction hasErrnoCode(error: unknown, code: string): boolean {\n return error instanceof Error && (error as { code?: string }).code === code;\n}\n\nfunction contractAtMemoKey(hash: string, refName: string | undefined): string {\n return `${hash}\\0${refName ?? ''}`;\n}\n\nfunction deserializeContractAtPath(\n filePath: string,\n contractJson: unknown,\n deserializeContract: (raw: unknown) => Contract,\n): Contract {\n try {\n return deserializeContract(contractJson);\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n throw errorContractDeserializationFailed(filePath, message);\n }\n}\n\nasync function readGraphNodeEndContract(\n packageDir: string,\n deserializeContract: (raw: unknown) => Contract,\n): Promise<{ contractJson: unknown; contractDts: string; contract: Contract }> {\n const jsonPath = join(packageDir, 'end-contract.json');\n const dtsPath = join(packageDir, 'end-contract.d.ts');\n\n let rawJson: string;\n try {\n rawJson = await readFile(jsonPath, 'utf-8');\n } catch (error) {\n if (hasErrnoCode(error, 'ENOENT')) {\n throw errorMissingFile('end-contract.json', packageDir);\n }\n throw error;\n }\n\n let contractJson: unknown;\n try {\n contractJson = JSON.parse(rawJson);\n } catch (error) {\n throw errorInvalidJson(jsonPath, error instanceof Error ? error.message : String(error));\n }\n\n let contractDts: string;\n try {\n contractDts = await readFile(dtsPath, 'utf-8');\n } catch (error) {\n if (hasErrnoCode(error, 'ENOENT')) {\n throw errorMissingFile('end-contract.d.ts', packageDir);\n }\n throw error;\n }\n\n const contract = deserializeContractAtPath(jsonPath, contractJson, deserializeContract);\n return { contractJson, contractDts, contract };\n}\n\nasync function resolveContractAt(args: {\n readonly hash: string;\n readonly opts: ContractAtOptions | undefined;\n readonly refsDir: string;\n readonly packages: readonly OnDiskMigrationPackage[];\n readonly graph: MigrationGraph;\n readonly deserializeContract: (raw: unknown) => Contract;\n}): Promise<ContractAtResult> {\n const { hash, opts, refsDir, packages, graph, deserializeContract } = args;\n const refName = opts?.refName;\n\n if (refName !== undefined) {\n const snapshot = await readRefSnapshot(refsDir, refName);\n if (snapshot) {\n const jsonPath = join(refsDir, `${refName}.contract.json`);\n return {\n hash,\n contractJson: snapshot.contract,\n contractDts: snapshot.contractDts,\n contract: deserializeContractAtPath(jsonPath, snapshot.contract, deserializeContract),\n provenance: 'snapshot',\n };\n }\n\n if (isGraphNode(hash, graph)) {\n return resolveGraphNodeContractAt({\n hash,\n packages,\n deserializeContract,\n explicitLabel: refName,\n });\n }\n\n throw errorSnapshotMissing(refName);\n }\n\n if (isGraphNode(hash, graph)) {\n return resolveGraphNodeContractAt({ hash, packages, deserializeContract });\n }\n\n throw errorHashNotInGraph(hash, graph);\n}\n\nasync function resolveGraphNodeContractAt(args: {\n readonly hash: string;\n readonly packages: readonly OnDiskMigrationPackage[];\n readonly deserializeContract: (raw: unknown) => Contract;\n readonly explicitLabel?: string;\n}): Promise<ContractAtResult> {\n const { hash, packages, deserializeContract, explicitLabel } = args;\n const matchingBundle = packages.find((pkg) => pkg.metadata.to === hash);\n if (!matchingBundle) {\n throw errorBundleNotFoundForGraphNode(hash, explicitLabel);\n }\n\n const { contractJson, contractDts, contract } = await readGraphNodeEndContract(\n matchingBundle.dirPath,\n deserializeContract,\n );\n return {\n hash,\n contractJson,\n contractDts,\n contract,\n provenance: 'graph-node',\n sourceDir: matchingBundle.dirPath,\n };\n}\n\n/**\n * Resolve a member's head ref, asserting it is present. The apply/verify\n * engine only runs after `checkIntegrity` has refused on `headRefMissing`,\n * so a member reaching the planner / verifier without a head ref is a\n * programming error (the integrity gate was skipped), not a user-facing\n * state. The app member's head ref is always synthesised, so this only\n * ever guards an ungated extension space.\n */\nexport function requireHeadRef(member: ContractSpaceMember): ContractSpaceHeadRecord {\n if (member.headRef === null) {\n throw new Error(\n `Contract space \"${member.spaceId}\" has no head ref; the integrity gate must refuse a missing head ref before planning or verifying.`,\n );\n }\n return member.headRef;\n}\n\n/**\n * Build a {@link ContractSpaceMember} with lazily-memoised `graph()`,\n * `contract()`, and `contractAt()` facets.\n *\n * `graph()` reconstructs the migration graph from `packages` on first\n * call and caches it. `contract()` calls `resolveContract` on first call\n * and caches the result; a throwing `resolveContract` (e.g. a missing or\n * undeserializable on-disk contract) re-throws on each call rather than\n * caching a value — `checkIntegrity` surfaces that as `contractUnreadable`.\n * `contractAt()` materializes the contract at an arbitrary graph node with\n * the same resolution order as plan-time ref resolution: ref snapshot first\n * (when `opts.refName` is set), else the matching package's `end-contract.*`.\n */\nexport function createContractSpaceMember(args: {\n readonly spaceId: string;\n readonly packages: readonly OnDiskMigrationPackage[];\n readonly refs: Refs;\n readonly headRef: ContractSpaceHeadRecord | null;\n readonly refsDir: string;\n readonly resolveContract: () => Contract;\n readonly deserializeContract: (raw: unknown) => Contract;\n}): ContractSpaceMember {\n const { spaceId, packages, refs, headRef, refsDir, resolveContract, deserializeContract } = args;\n let graphMemo: MigrationGraph | undefined;\n let contractMemo: Contract | undefined;\n const contractAtMemo = new Map<string, ContractAtResult>();\n\n function memberGraph(): MigrationGraph {\n graphMemo ??= reconstructGraph(packages);\n return graphMemo;\n }\n\n return {\n spaceId,\n packages,\n refs,\n headRef,\n graph: memberGraph,\n contract() {\n contractMemo ??= resolveContract();\n return contractMemo;\n },\n async contractAt(hash, opts) {\n const key = contractAtMemoKey(hash, opts?.refName);\n const cached = contractAtMemo.get(key);\n if (cached) {\n return cached;\n }\n\n const result = await resolveContractAt({\n hash,\n opts,\n refsDir,\n packages,\n graph: memberGraph(),\n deserializeContract,\n });\n contractAtMemo.set(key, result);\n return result;\n },\n };\n}\n\n/**\n * Assemble a {@link ContractSpaceAggregate} value from its members and a\n * `checkIntegrity` implementation. The query methods (`listSpaces` /\n * `hasSpace` / `space` / `spaces`) are derived here so every aggregate —\n * loader-built or test-built — shares one query surface: `app` first,\n * then `extensions` in the order supplied (the loader sorts them\n * lex-ascending by `spaceId`).\n */\nexport function createContractSpaceAggregate(args: {\n readonly targetId: string;\n readonly app: ContractSpaceMember;\n readonly extensions: readonly ContractSpaceMember[];\n readonly checkIntegrity: (opts?: IntegrityQueryOptions) => readonly IntegrityViolation[];\n}): ContractSpaceAggregate {\n const { targetId, app, extensions, checkIntegrity } = args;\n const ordered: readonly ContractSpaceMember[] = [app, ...extensions];\n const byId = new Map(ordered.map((m) => [m.spaceId, m]));\n return {\n targetId,\n app,\n extensions,\n listSpaces: () => ordered.map((m) => m.spaceId),\n hasSpace: (id) => byId.has(id),\n space: (id) => byId.get(id),\n spaces: () => ordered,\n checkIntegrity,\n };\n}\n","import { elementCoordinates } from '@prisma-next/framework-components/ir';\nimport { EMPTY_CONTRACT_HASH } from '../constants';\nimport { MigrationToolsError } from '../errors';\nimport type {\n DeclaredExtensionEntry,\n IntegrityQueryOptions,\n IntegrityViolation,\n} from '../integrity-violation';\nimport type { PackageLoadProblem } from '../io';\nimport type { OnDiskMigrationPackage } from '../package';\nimport type { RefLoadProblem } from '../refs';\nimport type { ContractSpaceMember } from './types';\n\n/**\n * One space's load-time facts that `checkIntegrity` judges: the loaded\n * member, the load-time problems `readMigrationsDir` surfaced for it, and\n * whether it is the app space (the app head ref is synthesised, so the\n * head-ref checks are skipped for it).\n */\nexport interface IntegritySpaceState {\n readonly member: ContractSpaceMember;\n readonly problems: readonly PackageLoadProblem[];\n /** Per-ref problems: a user ref `*.json` that exists but is unparseable. */\n readonly refProblems: readonly RefLoadProblem[];\n /**\n * The space's `refs/head.json` problem when it exists but is unparseable.\n * `null` means the head ref was read cleanly or is genuinely absent —\n * the absent case is judged `headRefMissing`, the corrupt case here is\n * judged `refUnreadable` (and suppresses `headRefMissing`).\n */\n readonly headRefProblem: RefLoadProblem | null;\n readonly isApp: boolean;\n}\n\nexport interface IntegrityComputationInput {\n readonly targetId: string;\n readonly spaces: readonly IntegritySpaceState[];\n}\n\n/**\n * Walk the loaded model and return **every** integrity violation — never\n * bailing at the first. Structurally-derivable violations (load-time\n * problems, self-edges, missing / unreachable head refs) are always\n * produced; layout-drift checks require `declaredExtensions`, and\n * contract / target / disjointness checks require `checkContracts`.\n */\nexport function computeIntegrityViolations(\n input: IntegrityComputationInput,\n opts?: IntegrityQueryOptions,\n): readonly IntegrityViolation[] {\n const violations: IntegrityViolation[] = [];\n\n for (const { member, problems, refProblems, headRefProblem, isApp } of input.spaces) {\n const { spaceId } = member;\n\n for (const problem of problems) {\n violations.push(loadProblemToViolation(spaceId, problem));\n }\n\n for (const refProblem of refProblems) {\n violations.push({\n kind: 'refUnreadable',\n spaceId,\n refName: refProblem.refName,\n detail: refProblem.detail,\n });\n }\n if (headRefProblem !== null) {\n violations.push({\n kind: 'refUnreadable',\n spaceId,\n refName: headRefProblem.refName,\n detail: headRefProblem.detail,\n });\n }\n\n for (const pkg of member.packages) {\n const from = pkg.metadata.from ?? EMPTY_CONTRACT_HASH;\n const isSelfEdge = from === pkg.metadata.to;\n const hasDataOp = pkg.ops.some((op) => op.operationClass === 'data');\n if (isSelfEdge && !hasDataOp) {\n violations.push({ kind: 'sameSourceAndTarget', spaceId, dirName: pkg.dirName, hash: from });\n }\n }\n\n violations.push(...duplicateMigrationHashViolations(spaceId, member.packages));\n\n // The app head ref is synthesised from the live contract, so it is\n // always present and reachable; only extension spaces read their head\n // ref from disk and can be missing or point outside the graph. A head\n // ref that exists but is unparseable is already surfaced above as\n // `refUnreadable`, so it is not also reported as `headRefMissing`.\n if (!isApp && headRefProblem === null) {\n if (member.headRef === null) {\n violations.push({ kind: 'headRefMissing', spaceId });\n } else if (!headRefPresentInGraph(member, member.headRef.hash)) {\n violations.push({ kind: 'headRefNotInGraph', spaceId, hash: member.headRef.hash });\n }\n }\n }\n\n if (opts?.declaredExtensions !== undefined) {\n violations.push(...layoutViolations(input.spaces, opts.declaredExtensions));\n }\n\n if (opts?.checkContracts === true) {\n violations.push(...contractViolations(input));\n }\n\n return violations;\n}\n\nexport function loadProblemToViolation(\n spaceId: string,\n problem: PackageLoadProblem,\n): IntegrityViolation {\n switch (problem.kind) {\n case 'hashMismatch':\n return {\n kind: 'hashMismatch',\n spaceId,\n dirName: problem.dirName,\n stored: problem.stored,\n computed: problem.computed,\n };\n case 'providedInvariantsMismatch':\n return { kind: 'providedInvariantsMismatch', spaceId, dirName: problem.dirName };\n case 'packageUnloadable':\n return {\n kind: 'packageUnloadable',\n spaceId,\n dirName: problem.dirName,\n detail: problem.detail,\n };\n }\n}\n\nfunction duplicateMigrationHashViolations(\n spaceId: string,\n packages: readonly OnDiskMigrationPackage[],\n): readonly IntegrityViolation[] {\n const dirNamesByHash = new Map<string, string[]>();\n for (const pkg of packages) {\n const hash = pkg.metadata.migrationHash;\n const dirNames = dirNamesByHash.get(hash);\n if (dirNames) dirNames.push(pkg.dirName);\n else dirNamesByHash.set(hash, [pkg.dirName]);\n }\n\n const out: IntegrityViolation[] = [];\n for (const [migrationHash, dirNames] of dirNamesByHash) {\n if (dirNames.length > 1) {\n out.push({\n kind: 'duplicateMigrationHash',\n spaceId,\n migrationHash,\n dirNames: [...dirNames].sort(),\n });\n }\n }\n return out;\n}\n\n/**\n * Whether a space's head-ref hash is present in its reconstructed graph.\n * An empty graph is reachable only by the empty-contract sentinel.\n */\nfunction headRefPresentInGraph(member: ContractSpaceMember, headHash: string): boolean {\n const graph = member.graph();\n if (graph.nodes.size === 0) {\n return headHash === EMPTY_CONTRACT_HASH;\n }\n return graph.nodes.has(headHash);\n}\n\nfunction layoutViolations(\n spaces: readonly IntegritySpaceState[],\n declaredExtensions: readonly DeclaredExtensionEntry[],\n): readonly IntegrityViolation[] {\n const out: IntegrityViolation[] = [];\n const extensionSpaceIds = new Set(spaces.filter((s) => !s.isApp).map((s) => s.member.spaceId));\n const declaredIds = new Set(declaredExtensions.map((d) => d.id));\n\n for (const id of [...extensionSpaceIds].sort()) {\n if (!declaredIds.has(id)) {\n out.push({ kind: 'orphanSpaceDir', spaceId: id });\n }\n }\n for (const id of [...declaredIds].sort()) {\n if (!extensionSpaceIds.has(id)) {\n out.push({ kind: 'declaredButUnmigrated', spaceId: id });\n }\n }\n return out;\n}\n\nfunction contractViolations(input: IntegrityComputationInput): readonly IntegrityViolation[] {\n const out: IntegrityViolation[] = [];\n const elementClaimedBy = new Map<string, string[]>();\n\n for (const { member } of input.spaces) {\n let contract: ReturnType<ContractSpaceMember['contract']>;\n try {\n contract = member.contract();\n } catch (error) {\n out.push({ kind: 'contractUnreadable', spaceId: member.spaceId, detail: detailOf(error) });\n continue;\n }\n\n if (contract.target !== input.targetId) {\n out.push({\n kind: 'targetMismatch',\n spaceId: member.spaceId,\n expected: input.targetId,\n actual: contract.target,\n });\n }\n\n for (const { entityName: elementName } of elementCoordinates(contract.storage)) {\n const claimers = elementClaimedBy.get(elementName);\n if (claimers) claimers.push(member.spaceId);\n else elementClaimedBy.set(elementName, [member.spaceId]);\n }\n }\n\n const disjointness: IntegrityViolation[] = [];\n for (const [element, claimedBy] of elementClaimedBy) {\n if (claimedBy.length > 1) {\n disjointness.push({ kind: 'disjointness', element, claimedBy: [...claimedBy].sort() });\n }\n }\n disjointness.sort((a, b) =>\n a.kind === 'disjointness' && b.kind === 'disjointness' ? a.element.localeCompare(b.element) : 0,\n );\n out.push(...disjointness);\n return out;\n}\n\nfunction detailOf(error: unknown): string {\n if (MigrationToolsError.is(error)) return error.why;\n if (error instanceof Error) return error.message;\n return String(error);\n}\n","import type { Contract } from '@prisma-next/contract/types';\nimport { MigrationToolsError } from '../errors';\nimport { readMigrationsDir } from '../io';\nimport { readContractSpaceContract } from '../read-contract-space-contract';\nimport { readContractSpaceHeadRef } from '../read-contract-space-head-ref';\nimport { HEAD_REF_NAME, type RefLoadProblem, readRefsTolerant } from '../refs';\nimport {\n APP_SPACE_ID,\n isValidSpaceId,\n RESERVED_SPACE_SUBDIR_NAMES,\n spaceMigrationDirectory,\n spaceRefsDirectory,\n} from '../space-layout';\nimport { listContractSpaceDirectories } from '../verify-contract-spaces';\nimport { createContractSpaceAggregate, createContractSpaceMember } from './aggregate';\nimport { computeIntegrityViolations, type IntegritySpaceState } from './check-integrity';\nimport type { ContractSpaceAggregate } from './types';\n\nexport type { DeclaredExtensionEntry } from '../integrity-violation';\n\n/**\n * Inputs for {@link loadContractSpaceAggregate}.\n *\n * Construction reads migration **state** from disk (`migrations/<space>/`\n * packages + refs + head refs). The app's *live* contract is not a disk\n * artefact — in Prisma Next it is always compiled from the project's\n * central contract, so the caller always has it and threads it in as\n * `appContract`. `deserializeContract` is held and called lazily only for\n * the on-disk extension contracts (`migrations/<ext>/contract.json`).\n */\nexport interface LoadAggregateInput {\n readonly migrationsDir: string;\n readonly deserializeContract: (raw: unknown) => Contract;\n readonly appContract: Contract;\n}\n\n/**\n * Build a tolerant, queryable {@link ContractSpaceAggregate} from on-disk\n * migration state plus the caller's live app contract.\n *\n * Building **never throws on disk content**: a hash- or\n * invariants-mismatched package is retained, an unparseable package is\n * omitted, a missing extension head ref leaves `headRef: null`, and an\n * unreadable on-disk contract defers its failure to `member.contract()`.\n * Every such problem is judged by {@link ContractSpaceAggregate.checkIntegrity}\n * rather than aborting the load. The only rejections are catastrophic I/O\n * (a `migrations/` that exists but is unreadable for reasons other than\n * absence).\n *\n * The app space's head ref is synthesised from the live contract's\n * storage hash (the app contract is authored independently of the\n * migration graph), and `app.contract()` returns the supplied contract.\n * Extension spaces read their contract, refs, and head ref from disk.\n */\nexport async function loadContractSpaceAggregate(\n input: LoadAggregateInput,\n): Promise<ContractSpaceAggregate> {\n const { migrationsDir, deserializeContract, appContract } = input;\n const targetId = appContract.target;\n\n const appState = await loadAppSpace(migrationsDir, appContract, deserializeContract);\n const extensionStates = await loadExtensionSpaces(migrationsDir, deserializeContract);\n\n const spaces: readonly IntegritySpaceState[] = [appState, ...extensionStates];\n\n return createContractSpaceAggregate({\n targetId,\n app: appState.member,\n extensions: extensionStates.map((state) => state.member),\n checkIntegrity: (opts) => computeIntegrityViolations({ targetId, spaces }, opts),\n });\n}\n\nasync function loadAppSpace(\n migrationsDir: string,\n appContract: Contract,\n deserializeContract: (raw: unknown) => Contract,\n): Promise<IntegritySpaceState> {\n const spaceDir = spaceMigrationDirectory(migrationsDir, APP_SPACE_ID);\n const { packages, problems } = await readMigrationsDir(spaceDir);\n const { refs, problems: refProblems } = await readRefsTolerant(spaceRefsDirectory(spaceDir));\n\n const member = createContractSpaceMember({\n spaceId: APP_SPACE_ID,\n packages,\n refs,\n headRef: { hash: appContract.storage.storageHash, invariants: [] },\n refsDir: spaceRefsDirectory(spaceDir),\n resolveContract: () => appContract,\n deserializeContract,\n });\n\n // The app head ref is synthesised from the live contract, so there is\n // no on-disk head.json to be missing or corrupt for it.\n return { member, problems, refProblems, headRefProblem: null, isApp: true };\n}\n\nasync function loadExtensionSpaces(\n migrationsDir: string,\n deserializeContract: (raw: unknown) => Contract,\n): Promise<readonly IntegritySpaceState[]> {\n const candidateDirs = await listContractSpaceDirectories(migrationsDir);\n const extensionIds = candidateDirs\n .filter((name) => name !== APP_SPACE_ID)\n .filter((name) => !RESERVED_SPACE_SUBDIR_NAMES.has(name))\n .filter(isValidSpaceId)\n .sort();\n\n const states: IntegritySpaceState[] = [];\n for (const spaceId of extensionIds) {\n states.push(await loadExtensionSpace(migrationsDir, spaceId, deserializeContract));\n }\n return states;\n}\n\nasync function loadExtensionSpace(\n migrationsDir: string,\n spaceId: string,\n deserializeContract: (raw: unknown) => Contract,\n): Promise<IntegritySpaceState> {\n const spaceDir = spaceMigrationDirectory(migrationsDir, spaceId);\n const { packages, problems } = await readMigrationsDir(spaceDir);\n const { refs, problems: refProblems } = await readRefsTolerant(spaceRefsDirectory(spaceDir));\n const { headRef, problem: headRefProblem } = await readHeadRefTolerant(migrationsDir, spaceId);\n const rawContract = await readRawContractDeferred(migrationsDir, spaceId);\n\n const member = createContractSpaceMember({\n spaceId,\n packages,\n refs,\n headRef,\n refsDir: spaceRefsDirectory(spaceDir),\n resolveContract: () => deserializeContract(rawContract()),\n deserializeContract,\n });\n\n return { member, problems, refProblems, headRefProblem, isApp: false };\n}\n\n/**\n * The result of resolving an extension's `refs/head.json`: the parsed\n * head ref (or `null` when the file is absent or corrupt) plus a problem\n * when the file exists but cannot be parsed.\n */\ninterface HeadRefReadResult {\n readonly headRef: Awaited<ReturnType<typeof readContractSpaceHeadRef>>;\n readonly problem: RefLoadProblem | null;\n}\n\n/**\n * Read an extension's head ref, distinguishing a *genuinely absent*\n * `head.json` (`headRef: null`, no problem — judged `headRefMissing`)\n * from one that *exists but cannot be parsed* (`headRef: null` plus a\n * problem — judged `refUnreadable`, not `headRefMissing`).\n * `readContractSpaceHeadRef` already returns `null` only for ENOENT and\n * throws for unparseable / schema-invalid content, so the throw is the\n * corruption signal. Construction never throws on disk content.\n */\nfunction isToleratedRefHeadReadError(error: unknown): boolean {\n if (MigrationToolsError.is(error)) return true;\n if (!(error instanceof Error)) return false;\n const code = (error as NodeJS.ErrnoException).code;\n return code === 'ENOENT' || code === 'EISDIR';\n}\n\nasync function readHeadRefTolerant(\n migrationsDir: string,\n spaceId: string,\n): Promise<HeadRefReadResult> {\n try {\n const headRef = await readContractSpaceHeadRef(migrationsDir, spaceId);\n return { headRef, problem: null };\n } catch (error) {\n if (!isToleratedRefHeadReadError(error)) {\n throw error;\n }\n return { headRef: null, problem: { refName: HEAD_REF_NAME, detail: detailOf(error) } };\n }\n}\n\nfunction detailOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/**\n * Read the raw on-disk contract eagerly (cheap I/O) but defer its\n * (throwing) failure to call time, so a missing or unparseable\n * `contract.json` becomes a `contract()` throw — surfaced as\n * `contractUnreadable` — rather than a construction failure.\n */\nasync function readRawContractDeferred(\n migrationsDir: string,\n spaceId: string,\n): Promise<() => unknown> {\n try {\n const raw = await readContractSpaceContract(migrationsDir, spaceId);\n return () => raw;\n } catch (error) {\n return () => {\n throw error;\n };\n }\n}\n","import type { MigrationPlan } from '@prisma-next/framework-components/control';\nimport { EMPTY_CONTRACT_HASH } from '../../constants';\nimport { findPathWithDecision } from '../../migration-graph';\nimport type { MigrationOps, OnDiskMigrationPackage } from '../../package';\nimport { requireHeadRef } from '../aggregate';\nimport type { ContractMarkerRecordLike } from '../marker-types';\nimport type { PerSpacePlan } from '../planner-types';\nimport type { ContractSpaceMember } from '../types';\n\n/**\n * Outcome variants for the graph-walk strategy. Mirrors\n * {@link import('../../compute-extension-space-apply-path').ExtensionSpaceApplyPathOutcome}\n * but operates against the member's lazily-reconstructed `graph()`\n * instead of re-reading from disk. The aggregate planner converts\n * these into {@link import('../planner-types').PlannerError}\n * variants.\n */\nexport type GraphWalkOutcome =\n | { readonly kind: 'ok'; readonly result: PerSpacePlan }\n | { readonly kind: 'unreachable' }\n | { readonly kind: 'unsatisfiable'; readonly missing: readonly string[] };\n\nexport interface GraphWalkStrategyInputs {\n readonly aggregateTargetId: string;\n readonly member: ContractSpaceMember;\n readonly currentMarker: ContractMarkerRecordLike | null;\n /**\n * Optional ref name to decorate the resulting `PathDecision`. Used by\n * `migrate` to surface the user-supplied `--to <name>` in\n * structured-progress events and invariant-path error envelopes. The\n * strategy itself does not interpret it.\n */\n readonly refName?: string;\n}\n\n/**\n * Walk a member's hydrated migration graph from the live marker to\n * `member.headRef.hash`, covering every required invariant.\n *\n * Pure synchronous function — no I/O. The aggregate's loader has\n * already integrity-checked every package and reconstructed the graph;\n * this strategy just looks up ops by `migrationHash` and assembles a\n * `MigrationPlan` with `targetId` set from the aggregate (no\n * placeholder cast).\n *\n * Required invariants are computed as `headRef.invariants \\ marker.invariants`\n * — the marker already declares some invariants satisfied; the path\n * only needs to provide the remainder. Mirrors today's\n * `computeExtensionSpaceApplyPath` semantics.\n */\nexport function graphWalkStrategy(input: GraphWalkStrategyInputs): GraphWalkOutcome {\n const { aggregateTargetId, member, currentMarker, refName } = input;\n const headRef = requireHeadRef(member);\n const graph = member.graph();\n const packagesByMigrationHash = new Map<string, OnDiskMigrationPackage>(\n member.packages.map((pkg) => [pkg.metadata.migrationHash, pkg]),\n );\n\n const fromHash = currentMarker?.storageHash ?? EMPTY_CONTRACT_HASH;\n const markerInvariants = new Set(currentMarker?.invariants ?? []);\n const required = new Set(headRef.invariants.filter((id) => !markerInvariants.has(id)));\n\n const outcome = findPathWithDecision(graph, fromHash, headRef.hash, {\n required,\n ...(refName !== undefined ? { refName } : {}),\n });\n\n if (outcome.kind === 'unreachable') {\n return { kind: 'unreachable' };\n }\n if (outcome.kind === 'unsatisfiable') {\n return { kind: 'unsatisfiable', missing: outcome.missing };\n }\n\n const pathOps: MigrationOps[number][] = [];\n const providedInvariantsSet = new Set<string>();\n const edgeRefs: Array<{\n migrationHash: string;\n dirName: string;\n from: string;\n to: string;\n operationCount: number;\n }> = [];\n for (const edge of outcome.decision.selectedPath) {\n const pkg = packagesByMigrationHash.get(edge.migrationHash);\n if (!pkg) {\n throw new Error(\n `Migration package missing for edge ${edge.migrationHash} in space \"${member.spaceId}\". The hydrated migration graph and packagesByMigrationHash map are out of sync — this should be unreachable; report.`,\n );\n }\n for (const op of pkg.ops) pathOps.push(op);\n for (const invariant of pkg.metadata.providedInvariants) providedInvariantsSet.add(invariant);\n edgeRefs.push({\n migrationHash: edge.migrationHash,\n dirName: edge.dirName,\n from: edge.from,\n to: edge.to,\n operationCount: pkg.ops.length,\n });\n }\n\n const plan: MigrationPlan = {\n targetId: aggregateTargetId,\n spaceId: member.spaceId,\n origin: currentMarker === null ? null : { storageHash: currentMarker.storageHash },\n destination: { storageHash: headRef.hash },\n operations: pathOps,\n providedInvariants: [...providedInvariantsSet].sort(),\n };\n\n return {\n kind: 'ok',\n result: {\n plan,\n displayOps: pathOps,\n destinationContract: member.contract(),\n strategy: 'graph-walk',\n migrationEdges: edgeRefs,\n pathDecision: outcome.decision,\n },\n };\n}\n","import { elementCoordinates } from '@prisma-next/framework-components/ir';\nimport type { ContractSpaceMember } from './types';\n\n/**\n * Project the **introspected live schema** to the slice claimed by a\n * single contract-space member.\n *\n * \"Schema\" here means the live introspected database state — the\n * planner / verifier sees this object as a `MongoSchemaIR` (Mongo) or\n * `SqlSchemaIR` (SQL). It is **not** a database schema in the SQL\n * `CREATE SCHEMA` sense, nor a contract-space namespace. The\n * function's job is to filter that introspected state down to the\n * elements claimed by one space, so a per-space verify pass doesn't\n * see another space's storage as \"extras\".\n *\n * Returns the same `schema` value with every top-level storage element\n * (table or collection) claimed by **other** members of the aggregate\n * removed. Elements not claimed by any member flow through unchanged —\n * the planner / verifier sees them as orphans (extras in strict mode).\n *\n * Used by:\n *\n * - The aggregate planner's **synth strategy**: when synthesising a\n * plan against a member's contract, the live schema must be projected\n * to that member's slice so the planner doesn't treat elements claimed\n * by other members as \"extras\" and emit destructive ops to drop them.\n * - The aggregate verifier's **schemaCheck**: projects per member so the\n * single-contract verify only sees the slice claimed by the member it\n * is checking. Closes the architectural concern that a multi-member\n * deployment makes each member's elements look like extras to every\n * other member's verify pass.\n *\n * **Duck-typing semantics**: the helper operates on `unknown` for the\n * schema and falls through structurally if the shape doesn't match.\n * Two storage shapes are recognised today:\n *\n * - SQL families expose `storage.tables: Record<string, ...>` on\n * contracts and the introspected schema mirrors the same record shape.\n * Pruning iterates the record entries.\n * - Mongo exposes `storage.collections: Record<string, ...>` on\n * contracts; the introspected `MongoSchemaIR` exposes\n * `collections: ReadonlyArray<{name: string, ...}>`. Pruning iterates\n * the array on the schema side and the record's keys on the\n * other-member side.\n *\n * Schemas of unrecognised shape are returned unchanged. The function\n * never imports family classes (`SqlSchemaIR`, `MongoSchemaIR`); the\n * projected schema is a plain object — `{...schema, tables: pruned}` or\n * `{...schema, collections: pruned}` — that downstream consumers\n * duck-type. A future family with a different storage shape gets the\n * schema returned unchanged rather than blowing up the aggregate\n * planner.\n *\n * Record-shape detection guards against arrays (`!Array.isArray`) so\n * an unrecognised array-shaped value falls through unchanged rather\n * than being pruned by numeric keys.\n */\nexport function projectSchemaToSpace(\n schema: unknown,\n member: ContractSpaceMember,\n otherMembers: ReadonlyArray<ContractSpaceMember>,\n): unknown {\n if (typeof schema !== 'object' || schema === null) return schema;\n\n const ownedByOthers = collectOwnedNames(member, otherMembers);\n if (ownedByOthers.size === 0) return schema;\n\n const schemaObj = schema as { readonly tables?: unknown; readonly collections?: unknown };\n\n if (\n typeof schemaObj.tables === 'object' &&\n schemaObj.tables !== null &&\n !Array.isArray(schemaObj.tables)\n ) {\n return pruneRecord(schemaObj, 'tables', ownedByOthers);\n }\n\n if (Array.isArray(schemaObj.collections)) {\n return pruneCollectionsArray(schemaObj, ownedByOthers);\n }\n\n if (\n typeof schemaObj.collections === 'object' &&\n schemaObj.collections !== null &&\n !Array.isArray(schemaObj.collections)\n ) {\n return pruneRecord(schemaObj, 'collections', ownedByOthers);\n }\n\n return schema;\n}\n\nfunction collectOwnedNames(\n member: ContractSpaceMember,\n otherMembers: ReadonlyArray<ContractSpaceMember>,\n): Set<string> {\n const owned = new Set<string>();\n for (const other of otherMembers) {\n if (other.spaceId === member.spaceId) continue;\n for (const { entityName } of elementCoordinates(other.contract().storage)) {\n owned.add(entityName);\n }\n }\n return owned;\n}\n\nfunction pruneRecord(\n schemaObj: { readonly tables?: unknown; readonly collections?: unknown },\n field: 'tables' | 'collections',\n ownedByOthers: ReadonlySet<string>,\n): unknown {\n const source = schemaObj[field] as Record<string, unknown>;\n let removed = false;\n const pruned: Record<string, unknown> = {};\n for (const [name, value] of Object.entries(source)) {\n if (ownedByOthers.has(name)) {\n removed = true;\n } else {\n pruned[name] = value;\n }\n }\n if (!removed) return schemaObj;\n return { ...schemaObj, [field]: pruned };\n}\n\nfunction pruneCollectionsArray(\n schemaObj: { readonly collections?: unknown },\n ownedByOthers: ReadonlySet<string>,\n): unknown {\n const source = schemaObj.collections as ReadonlyArray<unknown>;\n let removed = false;\n const pruned: unknown[] = [];\n for (const entry of source) {\n if (typeof entry === 'object' && entry !== null) {\n const name = (entry as { readonly name?: unknown }).name;\n if (typeof name === 'string' && ownedByOthers.has(name)) {\n removed = true;\n continue;\n }\n }\n pruned.push(entry);\n }\n if (!removed) return schemaObj;\n return { ...schemaObj, collections: pruned };\n}\n","import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';\nimport type {\n ControlFamilyInstance,\n MigrationOperationPolicy,\n MigrationPlan,\n MigrationPlannerConflict,\n MigrationPlannerResult,\n TargetMigrationsCapability,\n} from '@prisma-next/framework-components/control';\nimport type { PerSpacePlan } from '../planner-types';\nimport { projectSchemaToSpace } from '../project-schema-to-space';\nimport type { ContractSpaceMember } from '../types';\n\nexport interface SynthStrategyInputs<TFamilyId extends string, TTargetId extends string> {\n readonly aggregateTargetId: string;\n readonly member: ContractSpaceMember;\n readonly otherMembers: ReadonlyArray<ContractSpaceMember>;\n readonly schemaIntrospection: unknown;\n readonly familyInstance: ControlFamilyInstance<TFamilyId, unknown>;\n readonly migrations: TargetMigrationsCapability<\n TFamilyId,\n TTargetId,\n ControlFamilyInstance<TFamilyId, unknown>\n >;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;\n readonly operationPolicy: MigrationOperationPolicy;\n}\n\nexport type SynthStrategyOutcome =\n | { readonly kind: 'ok'; readonly result: PerSpacePlan }\n | { readonly kind: 'failure'; readonly conflicts: readonly MigrationPlannerConflict[] };\n\n/**\n * The {@link MigrationPlanner.plan} interface is declared as synchronous,\n * but historical and test fixture call sites have always invoked it\n * with `await` (see prior `db-apply-per-space.ts`). Tolerating a\n * Promise here keeps existing test mocks working without changing the\n * declared family SPI.\n */\ntype MaybeAsyncPlannerResult = MigrationPlannerResult | Promise<MigrationPlannerResult>;\n\n/**\n * Synthesise a migration plan for a single member by projecting the\n * live schema down to that member's claimed slice and delegating to\n * the family's `createPlanner(...).plan(...)`.\n *\n * Pre-projection (via {@link projectSchemaToSpace}) closes the F23\n * concern: without it, the family's planner sees other members'\n * tables as \"extras\" and emits destructive ops to drop them. With it,\n * the planner only sees the slice this member claims.\n *\n * The synthesised plan's `targetId` is set from `aggregateTargetId`\n * (the aggregate's ambient target). The family's planner does not\n * stamp `targetId` on the produced plan; the aggregate planner is\n * the single point that knows the target.\n *\n * Used by:\n *\n * - The app member by default (CLI policy\n * `ignoreGraphFor: { app.spaceId }`).\n * - Any extension member whose `headRef.invariants` is empty (the\n * strategy selector falls back to synth when graph-walk isn't\n * required).\n */\nexport async function synthStrategy<TFamilyId extends string, TTargetId extends string>(\n input: SynthStrategyInputs<TFamilyId, TTargetId>,\n): Promise<SynthStrategyOutcome> {\n const projectedSchema = projectSchemaToSpace(\n input.schemaIntrospection,\n input.member,\n input.otherMembers,\n );\n\n const planner = input.migrations.createPlanner(input.familyInstance);\n const plannerResult: MigrationPlannerResult = await (planner.plan({\n contract: input.member.contract(),\n schema: projectedSchema,\n policy: input.operationPolicy,\n fromContract: null,\n frameworkComponents: input.frameworkComponents,\n spaceId: input.member.spaceId,\n }) as MaybeAsyncPlannerResult);\n\n if (plannerResult.kind === 'failure') {\n return { kind: 'failure', conflicts: plannerResult.conflicts };\n }\n\n const synthedPlan = plannerResult.plan;\n // The family planner returns a class-instance-shaped plan whose\n // `destination` / `operations` are accessors on the prototype, often\n // backed by private fields. A naive spread (`{ ...synthedPlan }`)\n // would lose those accessors and produce a plan with\n // `destination: undefined`; rebinding the prototype on a plain\n // object would break private-field access. We instead wrap the plan\n // in a Proxy that forwards every read except `targetId`, which is\n // stamped from the aggregate's ambient target. This preserves the\n // planner's class semantics while keeping the aggregate the single\n // source of truth for `targetId`.\n const plan: MigrationPlan = new Proxy(synthedPlan, {\n get(target, prop) {\n if (prop === 'targetId') return input.aggregateTargetId;\n // Forward `this` as the original target so prototype-bound\n // private fields (#destination, #operations, …) resolve.\n return Reflect.get(target, prop, target);\n },\n has(target, prop) {\n if (prop === 'targetId') return true;\n return Reflect.has(target, prop);\n },\n });\n\n return {\n kind: 'ok',\n result: {\n plan,\n displayOps: synthedPlan.operations,\n destinationContract: input.member.contract(),\n strategy: 'synth',\n },\n };\n}\n","import { notOk, ok } from '@prisma-next/utils/result';\nimport { requireHeadRef } from './aggregate';\nimport type { PerSpacePlan, PlannerError, PlannerInput, PlannerOutput } from './planner-types';\nimport { graphWalkStrategy } from './strategies/graph-walk';\nimport { synthStrategy } from './strategies/synth';\nimport type { ContractSpaceMember } from './types';\n\nexport type {\n AggregateCurrentDBState,\n AggregateMigrationEdgeRef,\n CallerPolicy,\n PerSpacePlan,\n PlannerError,\n PlannerInput,\n PlannerOutput,\n PlannerSuccess,\n} from './planner-types';\n\n/**\n * Plan a migration across every member of a {@link ContractSpaceAggregate}.\n *\n * Strategy selection per member, in order; first match wins:\n *\n * 1. If `callerPolicy.ignoreGraphFor.has(member.spaceId)`:\n * - If `member.headRef.invariants` is empty → synth.\n * - Else → `policyConflict` (synth cannot satisfy authored invariants).\n * 2. Else if `member.graph()` is non-empty AND graph-walk\n * succeeds → graph-walk.\n * 3. Else if `member.headRef.invariants` is empty → synth.\n * 4. Else → graph-walk failure → `extensionPathUnreachable` /\n * `extensionPathUnsatisfiable`.\n *\n * Output `applyOrder` is `[...aggregate.extensions.map(spaceId), aggregate.app.spaceId]`\n * — extensions alphabetical, then app — matching today's\n * `concatenateSpaceApplyInputs` ordering. This preserves\n * `MigrationRunnerFailure.failingSpace` attribution byte-for-byte.\n *\n * Every emitted `MigrationPlan` has `targetId = aggregate.targetId`.\n * No placeholder cast; no patch step.\n */\nexport async function planMigration<TFamilyId extends string, TTargetId extends string>(\n input: PlannerInput<TFamilyId, TTargetId>,\n): Promise<PlannerOutput> {\n const { aggregate, currentDBState, callerPolicy } = input;\n const allMembers: ReadonlyArray<ContractSpaceMember> = [aggregate.app, ...aggregate.extensions];\n\n const perSpace = new Map<string, PerSpacePlan>();\n\n // Iterate in apply order so a per-member error short-circuits the\n // walk in the same order the runner would walk inputs.\n const orderedMembers: ReadonlyArray<ContractSpaceMember> = [\n ...aggregate.extensions,\n aggregate.app,\n ];\n\n for (const member of orderedMembers) {\n const otherMembers = allMembers.filter((m) => m.spaceId !== member.spaceId);\n const currentMarker = currentDBState.markersBySpaceId.get(member.spaceId) ?? null;\n const headRef = requireHeadRef(member);\n\n const ignoreGraph = callerPolicy.ignoreGraphFor.has(member.spaceId);\n const invariantsRequired = headRef.invariants.length > 0;\n\n if (ignoreGraph && invariantsRequired) {\n const conflict: PlannerError = {\n kind: 'policyConflict',\n spaceId: member.spaceId,\n detail: `\\`callerPolicy.ignoreGraphFor\\` requested for space \"${member.spaceId}\", but the member declares non-empty head-ref invariants (${headRef.invariants.join(', ')}). Synthesising a plan from the contract IR cannot satisfy authored invariants — the graph must be walked. Either remove \"${member.spaceId}\" from \\`ignoreGraphFor\\` or amend the on-disk head ref to declare zero invariants.`,\n };\n return notOk(conflict);\n }\n\n if (ignoreGraph) {\n const synthOutcome = await synthStrategy({\n aggregateTargetId: aggregate.targetId,\n member,\n otherMembers,\n schemaIntrospection: currentDBState.schemaIntrospection,\n familyInstance: input.familyInstance,\n migrations: input.migrations,\n frameworkComponents: input.frameworkComponents,\n operationPolicy: input.operationPolicy,\n });\n if (synthOutcome.kind === 'failure') {\n return notOk({\n kind: 'appSynthFailure',\n spaceId: member.spaceId,\n conflicts: synthOutcome.conflicts,\n });\n }\n perSpace.set(member.spaceId, synthOutcome.result);\n continue;\n }\n\n // Try graph-walk first when the graph has nodes; fall back to synth\n // when the graph is empty AND no invariants are required.\n if (member.graph().nodes.size > 0) {\n const walked = graphWalkStrategy({\n aggregateTargetId: aggregate.targetId,\n member,\n currentMarker,\n });\n if (walked.kind === 'ok') {\n perSpace.set(member.spaceId, walked.result);\n continue;\n }\n if (walked.kind === 'unreachable') {\n return notOk({\n kind: 'extensionPathUnreachable',\n spaceId: member.spaceId,\n target: headRef.hash,\n });\n }\n // unsatisfiable — surface\n return notOk({\n kind: 'extensionPathUnsatisfiable',\n spaceId: member.spaceId,\n missingInvariants: walked.missing,\n });\n }\n\n // Empty graph: synth is the only option, and it can only satisfy\n // empty-invariant members.\n if (invariantsRequired) {\n return notOk({\n kind: 'extensionPathUnsatisfiable',\n spaceId: member.spaceId,\n missingInvariants: [...headRef.invariants].sort(),\n });\n }\n\n const synthOutcome = await synthStrategy({\n aggregateTargetId: aggregate.targetId,\n member,\n otherMembers,\n schemaIntrospection: currentDBState.schemaIntrospection,\n familyInstance: input.familyInstance,\n migrations: input.migrations,\n frameworkComponents: input.frameworkComponents,\n operationPolicy: input.operationPolicy,\n });\n if (synthOutcome.kind === 'failure') {\n return notOk({\n kind: 'appSynthFailure',\n spaceId: member.spaceId,\n conflicts: synthOutcome.conflicts,\n });\n }\n perSpace.set(member.spaceId, synthOutcome.result);\n }\n\n return ok({\n perSpace,\n applyOrder: [...aggregate.extensions.map((m) => m.spaceId), aggregate.app.spaceId],\n });\n}\n","import { elementCoordinates } from '@prisma-next/framework-components/ir';\nimport type { Result } from '@prisma-next/utils/result';\nimport { notOk, ok } from '@prisma-next/utils/result';\nimport { requireHeadRef } from './aggregate';\nimport type { ContractMarkerRecordLike } from './marker-types';\nimport { projectSchemaToSpace } from './project-schema-to-space';\nimport type { ContractSpaceAggregate, ContractSpaceMember } from './types';\n\n/**\n * Caller policy for the verifier. Today's only knob is\n * `mode`: `strict` treats orphan elements (live tables not claimed by\n * any aggregate member) as errors; `lenient` treats them as\n * informational. Maps directly to `db verify --strict`.\n */\nexport interface VerifierInput<TSchemaResult> {\n readonly aggregate: ContractSpaceAggregate;\n readonly markersBySpaceId: ReadonlyMap<string, ContractMarkerRecordLike | null>;\n readonly schemaIntrospection: unknown;\n readonly mode: 'strict' | 'lenient';\n /**\n * Caller-supplied per-space schema verifier. The CLI wires this to\n * the family's `verifySqlSchema` (SQL) / equivalent (other\n * families). The verifier projects the schema to the\n * member's slice via {@link projectSchemaToSpace} before invoking\n * the callback, so single-contract semantics are preserved.\n *\n * Typed structurally with a generic `TSchemaResult` so the\n * migration-tools layer doesn't depend on the SQL family's\n * `VerifySqlSchemaResult`. CLI callers pass the family's type\n * through unchanged.\n */\n readonly verifySchemaForMember: (\n projectedSchema: unknown,\n member: ContractSpaceMember,\n mode: 'strict' | 'lenient',\n ) => TSchemaResult;\n}\n\n/**\n * Marker-check result per member. Mirrors the four cases the\n * `verifyContractSpaces` primitive surfaces today, plus an `'absent'`\n * case for greenfield spaces (no marker row written yet — `db init`\n * not run).\n */\nexport type MarkerCheckResult =\n | { readonly kind: 'ok' }\n | { readonly kind: 'absent' }\n | {\n readonly kind: 'hashMismatch';\n readonly markerHash: string;\n readonly expected: string;\n }\n | { readonly kind: 'missingInvariants'; readonly missing: readonly string[] };\n\nexport interface MarkerCheckSection {\n readonly perSpace: ReadonlyMap<string, MarkerCheckResult>;\n readonly orphanMarkers: readonly {\n readonly spaceId: string;\n readonly row: ContractMarkerRecordLike;\n }[];\n}\n\n/**\n * A live storage element (today: a top-level table) not claimed by any\n * member of the aggregate. The verifier always reports these;\n * the caller decides what to do — `db verify --strict` treats them as\n * errors, the lenient default treats them as informational.\n *\n * Today only `kind: 'table'` exists. The discriminated shape leaves\n * room for orphan columns / indexes / sequences in the future without\n * breaking the type contract.\n */\nexport type OrphanElement = { readonly kind: 'table'; readonly name: string };\n\nexport interface SchemaCheckSection<TSchemaResult> {\n readonly perSpace: ReadonlyMap<string, TSchemaResult>;\n /**\n * Live elements present in the introspected schema that are not\n * claimed by **any** aggregate member. Sorted alphabetically by name.\n */\n readonly orphanElements: readonly OrphanElement[];\n}\n\nexport interface VerifierSuccess<TSchemaResult> {\n readonly markerCheck: MarkerCheckSection;\n readonly schemaCheck: SchemaCheckSection<TSchemaResult>;\n}\n\nexport type VerifierError = {\n readonly kind: 'introspectionFailure';\n readonly detail: string;\n};\n\nexport type VerifierOutput<TSchemaResult> = Result<VerifierSuccess<TSchemaResult>, VerifierError>;\n\n/**\n * Verify a {@link ContractSpaceAggregate} against the live database\n * state. Bundles two checks:\n *\n * - `markerCheck` per member: compare the live marker row against the\n * member's `headRef.hash` + `headRef.invariants`. Absence is a\n * distinct kind, not an error (callers — `db verify` strict vs\n * `db init` precondition — choose how to interpret it).\n * - `schemaCheck` per member: project the live schema to the slice\n * the member claims via {@link projectSchemaToSpace}, then delegate\n * to the caller-supplied `verifySchemaForMember`. The pre-projection\n * means the family's single-contract verifier no longer sees other\n * members' tables as `extras`, so a multi-member deployment never\n * surfaces cross-member tables as orphaned schema elements.\n *\n * `markerCheck.orphanMarkers` lists every marker row whose `space` is\n * not a member of the aggregate. `db verify` callers reject orphans;\n * future tooling may not.\n *\n * Pure synchronous function; no I/O. The caller (CLI) gathers\n * `markersBySpaceId` and `schemaIntrospection` ahead of the call.\n */\nexport function verifyMigration<TSchemaResult>(\n input: VerifierInput<TSchemaResult>,\n): VerifierOutput<TSchemaResult> {\n try {\n return runVerifyMigration(input);\n } catch (error) {\n return notOk({\n kind: 'introspectionFailure',\n detail: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\nfunction runVerifyMigration<TSchemaResult>(\n input: VerifierInput<TSchemaResult>,\n): VerifierOutput<TSchemaResult> {\n const { aggregate, markersBySpaceId, schemaIntrospection, mode, verifySchemaForMember } = input;\n const allMembers: ReadonlyArray<ContractSpaceMember> = [aggregate.app, ...aggregate.extensions];\n const memberSpaceIds = new Set(allMembers.map((m) => m.spaceId));\n\n // Marker check per member.\n const markerPerSpace = new Map<string, MarkerCheckResult>();\n for (const member of allMembers) {\n const marker = markersBySpaceId.get(member.spaceId) ?? null;\n if (marker === null) {\n markerPerSpace.set(member.spaceId, { kind: 'absent' });\n continue;\n }\n const headRef = requireHeadRef(member);\n if (marker.storageHash !== headRef.hash) {\n markerPerSpace.set(member.spaceId, {\n kind: 'hashMismatch',\n markerHash: marker.storageHash,\n expected: headRef.hash,\n });\n continue;\n }\n const markerInvariants = new Set(marker.invariants);\n const missing = headRef.invariants.filter((id) => !markerInvariants.has(id));\n if (missing.length > 0) {\n markerPerSpace.set(member.spaceId, {\n kind: 'missingInvariants',\n missing: [...missing].sort(),\n });\n continue;\n }\n markerPerSpace.set(member.spaceId, { kind: 'ok' });\n }\n\n // Orphan markers: entries in markersBySpaceId whose spaceId is not a\n // member of the aggregate.\n const orphanMarkers: { spaceId: string; row: ContractMarkerRecordLike }[] = [];\n for (const [spaceId, row] of markersBySpaceId) {\n if (row !== null && !memberSpaceIds.has(spaceId)) {\n orphanMarkers.push({ spaceId, row });\n }\n }\n orphanMarkers.sort((a, b) => a.spaceId.localeCompare(b.spaceId));\n\n // Schema check per member (with per-space pre-projection).\n const schemaPerSpace = new Map<string, TSchemaResult>();\n for (const member of allMembers) {\n const others = allMembers.filter((m) => m.spaceId !== member.spaceId);\n const projected = projectSchemaToSpace(schemaIntrospection, member, others);\n schemaPerSpace.set(member.spaceId, verifySchemaForMember(projected, member, mode));\n }\n\n return ok({\n markerCheck: {\n perSpace: markerPerSpace,\n orphanMarkers,\n },\n schemaCheck: {\n perSpace: schemaPerSpace,\n orphanElements: detectOrphanElements(schemaIntrospection, allMembers),\n },\n });\n}\n\n/**\n * Live tables not claimed by any aggregate member. Duck-typed against\n * the introspected schema's `tables` map; schemas whose shape doesn't\n * match return an empty list (consistent with\n * {@link projectSchemaToSpace}'s fall-through).\n */\nfunction detectOrphanElements(\n schemaIntrospection: unknown,\n members: ReadonlyArray<ContractSpaceMember>,\n): readonly OrphanElement[] {\n if (typeof schemaIntrospection !== 'object' || schemaIntrospection === null) return [];\n const liveTables = (schemaIntrospection as { readonly tables?: unknown }).tables;\n if (typeof liveTables !== 'object' || liveTables === null) return [];\n\n const claimedTables = new Set<string>();\n for (const member of members) {\n const contract = member.contract();\n for (const { entityName } of elementCoordinates(contract.storage)) {\n claimedTables.add(entityName);\n }\n }\n\n const orphans: OrphanElement[] = [];\n for (const tableName of Object.keys(liveTables as Record<string, unknown>)) {\n if (!claimedTables.has(tableName)) {\n orphans.push({ kind: 'table', name: tableName });\n }\n }\n orphans.sort((a, b) => a.name.localeCompare(b.name));\n return orphans;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"aggregate.mjs","names":["detailOf"],"sources":["../../src/aggregate/aggregate.ts","../../src/aggregate/check-integrity.ts","../../src/aggregate/loader.ts","../../src/aggregate/strategies/graph-walk.ts","../../src/aggregate/project-schema-to-space.ts","../../src/aggregate/synth-migration-edge.ts","../../src/aggregate/strategies/synth.ts","../../src/aggregate/planner.ts","../../src/aggregate/verifier.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport type { Contract } from '@prisma-next/contract/types';\nimport { join } from 'pathe';\nimport {\n errorBundleNotFoundForGraphNode,\n errorContractDeserializationFailed,\n errorHashNotInGraph,\n errorInvalidJson,\n errorMissingFile,\n errorSnapshotMissing,\n MigrationToolsError,\n} from '../errors';\nimport type { MigrationGraph } from '../graph';\nimport { isGraphNode } from '../graph-membership';\nimport type { IntegrityQueryOptions, IntegrityViolation } from '../integrity-violation';\nimport { reconstructGraph } from '../migration-graph';\nimport type { OnDiskMigrationPackage } from '../package';\nimport type { Refs } from '../refs';\nimport { readRefSnapshot } from '../refs/snapshot';\nimport type { ContractSpaceHeadRecord } from '../verify-contract-spaces';\nimport type {\n ContractAtOptions,\n ContractAtResult,\n ContractSpaceAggregate,\n ContractSpaceMember,\n} from './types';\n\nfunction hasErrnoCode(error: unknown, code: string): boolean {\n return error instanceof Error && (error as { code?: string }).code === code;\n}\n\nfunction contractAtMemoKey(hash: string, refName: string | undefined): string {\n return `${hash}\\0${refName ?? ''}`;\n}\n\nfunction deserializeContractAtPath(\n filePath: string,\n contractJson: unknown,\n deserializeContract: (raw: unknown) => Contract,\n): Contract {\n try {\n return deserializeContract(contractJson);\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n throw errorContractDeserializationFailed(filePath, message);\n }\n}\n\nasync function readGraphNodeEndContract(\n packageDir: string,\n deserializeContract: (raw: unknown) => Contract,\n): Promise<{ contractJson: unknown; contractDts: string; contract: Contract }> {\n const jsonPath = join(packageDir, 'end-contract.json');\n const dtsPath = join(packageDir, 'end-contract.d.ts');\n\n let rawJson: string;\n try {\n rawJson = await readFile(jsonPath, 'utf-8');\n } catch (error) {\n if (hasErrnoCode(error, 'ENOENT')) {\n throw errorMissingFile('end-contract.json', packageDir);\n }\n throw error;\n }\n\n let contractJson: unknown;\n try {\n contractJson = JSON.parse(rawJson);\n } catch (error) {\n throw errorInvalidJson(jsonPath, error instanceof Error ? error.message : String(error));\n }\n\n let contractDts: string;\n try {\n contractDts = await readFile(dtsPath, 'utf-8');\n } catch (error) {\n if (hasErrnoCode(error, 'ENOENT')) {\n throw errorMissingFile('end-contract.d.ts', packageDir);\n }\n throw error;\n }\n\n const contract = deserializeContractAtPath(jsonPath, contractJson, deserializeContract);\n return { contractJson, contractDts, contract };\n}\n\nasync function resolveContractAt(args: {\n readonly hash: string;\n readonly opts: ContractAtOptions | undefined;\n readonly refsDir: string;\n readonly packages: readonly OnDiskMigrationPackage[];\n readonly graph: MigrationGraph;\n readonly deserializeContract: (raw: unknown) => Contract;\n}): Promise<ContractAtResult> {\n const { hash, opts, refsDir, packages, graph, deserializeContract } = args;\n const refName = opts?.refName;\n\n if (refName !== undefined) {\n const snapshot = await readRefSnapshot(refsDir, refName);\n if (snapshot) {\n const jsonPath = join(refsDir, `${refName}.contract.json`);\n return {\n hash,\n contractJson: snapshot.contract,\n contractDts: snapshot.contractDts,\n contract: deserializeContractAtPath(jsonPath, snapshot.contract, deserializeContract),\n provenance: 'snapshot',\n };\n }\n\n if (isGraphNode(hash, graph)) {\n return resolveGraphNodeContractAt({\n hash,\n packages,\n deserializeContract,\n explicitLabel: refName,\n });\n }\n\n throw errorSnapshotMissing(refName);\n }\n\n if (isGraphNode(hash, graph)) {\n return resolveGraphNodeContractAt({ hash, packages, deserializeContract });\n }\n\n throw errorHashNotInGraph(hash, graph);\n}\n\nasync function resolveGraphNodeContractAt(args: {\n readonly hash: string;\n readonly packages: readonly OnDiskMigrationPackage[];\n readonly deserializeContract: (raw: unknown) => Contract;\n readonly explicitLabel?: string;\n}): Promise<ContractAtResult> {\n const { hash, packages, deserializeContract, explicitLabel } = args;\n const matchingBundle = packages.find((pkg) => pkg.metadata.to === hash);\n if (!matchingBundle) {\n throw errorBundleNotFoundForGraphNode(hash, explicitLabel);\n }\n\n const { contractJson, contractDts, contract } = await readGraphNodeEndContract(\n matchingBundle.dirPath,\n deserializeContract,\n );\n return {\n hash,\n contractJson,\n contractDts,\n contract,\n provenance: 'graph-node',\n sourceDir: matchingBundle.dirPath,\n };\n}\n\n/**\n * Resolve a member's head ref, asserting it is present. The apply/verify\n * engine only runs after `checkIntegrity` has refused on `headRefMissing`,\n * so a member reaching the planner / verifier without a head ref is a\n * programming error (the integrity gate was skipped), not a user-facing\n * state. The app member's head ref is always synthesised, so this only\n * ever guards an ungated extension space.\n */\nexport function requireHeadRef(member: ContractSpaceMember): ContractSpaceHeadRecord {\n if (member.headRef === null) {\n throw new Error(\n `Contract space \"${member.spaceId}\" has no head ref; the integrity gate must refuse a missing head ref before planning or verifying.`,\n );\n }\n return member.headRef;\n}\n\n/**\n * Build a {@link ContractSpaceMember} with lazily-memoised `graph()`,\n * `contract()`, and `contractAt()` facets.\n *\n * `graph()` reconstructs the migration graph from `packages` on first\n * call and caches it. `contract()` calls `resolveContract` on first call\n * and caches the result; a throwing `resolveContract` (e.g. a missing or\n * undeserializable on-disk contract) re-throws on each call rather than\n * caching a value — `checkIntegrity` surfaces that as `contractUnreadable`.\n * `contractAt()` materializes the contract at an arbitrary graph node with\n * the same resolution order as plan-time ref resolution: ref snapshot first\n * (when `opts.refName` is set), else the matching package's `end-contract.*`.\n */\nexport function createContractSpaceMember(args: {\n readonly spaceId: string;\n readonly packages: readonly OnDiskMigrationPackage[];\n readonly refs: Refs;\n readonly headRef: ContractSpaceHeadRecord | null;\n readonly refsDir: string;\n readonly resolveContract: () => Contract;\n readonly deserializeContract: (raw: unknown) => Contract;\n}): ContractSpaceMember {\n const { spaceId, packages, refs, headRef, refsDir, resolveContract, deserializeContract } = args;\n let graphMemo: MigrationGraph | undefined;\n let contractMemo: Contract | undefined;\n const contractAtMemo = new Map<string, ContractAtResult>();\n\n function memberGraph(): MigrationGraph {\n graphMemo ??= reconstructGraph(packages);\n return graphMemo;\n }\n\n return {\n spaceId,\n packages,\n refs,\n headRef,\n graph: memberGraph,\n contract() {\n contractMemo ??= resolveContract();\n return contractMemo;\n },\n async contractAt(hash, opts) {\n const key = contractAtMemoKey(hash, opts?.refName);\n const cached = contractAtMemo.get(key);\n if (cached) {\n return cached;\n }\n\n const result = await resolveContractAt({\n hash,\n opts,\n refsDir,\n packages,\n graph: memberGraph(),\n deserializeContract,\n });\n contractAtMemo.set(key, result);\n return result;\n },\n };\n}\n\n/**\n * Assemble a {@link ContractSpaceAggregate} value from its members and a\n * `checkIntegrity` implementation. The query methods (`listSpaces` /\n * `hasSpace` / `space` / `spaces`) are derived here so every aggregate —\n * loader-built or test-built — shares one query surface: `app` first,\n * then `extensions` in the order supplied (the loader sorts them\n * lex-ascending by `spaceId`).\n */\nexport function createContractSpaceAggregate(args: {\n readonly targetId: string;\n readonly app: ContractSpaceMember;\n readonly extensions: readonly ContractSpaceMember[];\n readonly checkIntegrity: (opts?: IntegrityQueryOptions) => readonly IntegrityViolation[];\n}): ContractSpaceAggregate {\n const { targetId, app, extensions, checkIntegrity } = args;\n const ordered: readonly ContractSpaceMember[] = [app, ...extensions];\n const byId = new Map(ordered.map((m) => [m.spaceId, m]));\n return {\n targetId,\n app,\n extensions,\n listSpaces: () => ordered.map((m) => m.spaceId),\n hasSpace: (id) => byId.has(id),\n space: (id) => byId.get(id),\n spaces: () => ordered,\n checkIntegrity,\n };\n}\n","import { elementCoordinates } from '@prisma-next/framework-components/ir';\nimport { EMPTY_CONTRACT_HASH } from '../constants';\nimport { MigrationToolsError } from '../errors';\nimport type {\n DeclaredExtensionEntry,\n IntegrityQueryOptions,\n IntegrityViolation,\n} from '../integrity-violation';\nimport type { PackageLoadProblem } from '../io';\nimport type { OnDiskMigrationPackage } from '../package';\nimport type { RefLoadProblem } from '../refs';\nimport type { ContractSpaceMember } from './types';\n\n/**\n * One space's load-time facts that `checkIntegrity` judges: the loaded\n * member, the load-time problems `readMigrationsDir` surfaced for it, and\n * whether it is the app space (the app head ref is synthesised, so the\n * head-ref checks are skipped for it).\n */\nexport interface IntegritySpaceState {\n readonly member: ContractSpaceMember;\n readonly problems: readonly PackageLoadProblem[];\n /** Per-ref problems: a user ref `*.json` that exists but is unparseable. */\n readonly refProblems: readonly RefLoadProblem[];\n /**\n * The space's `refs/head.json` problem when it exists but is unparseable.\n * `null` means the head ref was read cleanly or is genuinely absent —\n * the absent case is judged `headRefMissing`, the corrupt case here is\n * judged `refUnreadable` (and suppresses `headRefMissing`).\n */\n readonly headRefProblem: RefLoadProblem | null;\n readonly isApp: boolean;\n}\n\nexport interface IntegrityComputationInput {\n readonly targetId: string;\n readonly spaces: readonly IntegritySpaceState[];\n}\n\n/**\n * Walk the loaded model and return **every** integrity violation — never\n * bailing at the first. Structurally-derivable violations (load-time\n * problems, self-edges, missing / unreachable head refs) are always\n * produced; layout-drift checks require `declaredExtensions`, and\n * contract / target / disjointness checks require `checkContracts`.\n */\nexport function computeIntegrityViolations(\n input: IntegrityComputationInput,\n opts?: IntegrityQueryOptions,\n): readonly IntegrityViolation[] {\n const violations: IntegrityViolation[] = [];\n\n for (const { member, problems, refProblems, headRefProblem, isApp } of input.spaces) {\n const { spaceId } = member;\n\n for (const problem of problems) {\n violations.push(loadProblemToViolation(spaceId, problem));\n }\n\n for (const refProblem of refProblems) {\n violations.push({\n kind: 'refUnreadable',\n spaceId,\n refName: refProblem.refName,\n detail: refProblem.detail,\n });\n }\n if (headRefProblem !== null) {\n violations.push({\n kind: 'refUnreadable',\n spaceId,\n refName: headRefProblem.refName,\n detail: headRefProblem.detail,\n });\n }\n\n for (const pkg of member.packages) {\n const from = pkg.metadata.from ?? EMPTY_CONTRACT_HASH;\n const isSelfEdge = from === pkg.metadata.to;\n const hasDataOp = pkg.ops.some((op) => op.operationClass === 'data');\n if (isSelfEdge && !hasDataOp) {\n violations.push({ kind: 'sameSourceAndTarget', spaceId, dirName: pkg.dirName, hash: from });\n }\n }\n\n violations.push(...duplicateMigrationHashViolations(spaceId, member.packages));\n\n // The app head ref is synthesised from the live contract, so it is\n // always present and reachable; only extension spaces read their head\n // ref from disk and can be missing or point outside the graph. A head\n // ref that exists but is unparseable is already surfaced above as\n // `refUnreadable`, so it is not also reported as `headRefMissing`.\n if (!isApp && headRefProblem === null) {\n if (member.headRef === null) {\n violations.push({ kind: 'headRefMissing', spaceId });\n } else if (!headRefPresentInGraph(member, member.headRef.hash)) {\n violations.push({ kind: 'headRefNotInGraph', spaceId, hash: member.headRef.hash });\n }\n }\n }\n\n if (opts?.declaredExtensions !== undefined) {\n violations.push(...layoutViolations(input.spaces, opts.declaredExtensions));\n }\n\n if (opts?.checkContracts === true) {\n violations.push(...contractViolations(input));\n }\n\n return violations;\n}\n\nexport function loadProblemToViolation(\n spaceId: string,\n problem: PackageLoadProblem,\n): IntegrityViolation {\n switch (problem.kind) {\n case 'hashMismatch':\n return {\n kind: 'hashMismatch',\n spaceId,\n dirName: problem.dirName,\n stored: problem.stored,\n computed: problem.computed,\n };\n case 'providedInvariantsMismatch':\n return { kind: 'providedInvariantsMismatch', spaceId, dirName: problem.dirName };\n case 'packageUnloadable':\n return {\n kind: 'packageUnloadable',\n spaceId,\n dirName: problem.dirName,\n detail: problem.detail,\n };\n }\n}\n\nfunction duplicateMigrationHashViolations(\n spaceId: string,\n packages: readonly OnDiskMigrationPackage[],\n): readonly IntegrityViolation[] {\n const dirNamesByHash = new Map<string, string[]>();\n for (const pkg of packages) {\n const hash = pkg.metadata.migrationHash;\n const dirNames = dirNamesByHash.get(hash);\n if (dirNames) dirNames.push(pkg.dirName);\n else dirNamesByHash.set(hash, [pkg.dirName]);\n }\n\n const out: IntegrityViolation[] = [];\n for (const [migrationHash, dirNames] of dirNamesByHash) {\n if (dirNames.length > 1) {\n out.push({\n kind: 'duplicateMigrationHash',\n spaceId,\n migrationHash,\n dirNames: [...dirNames].sort(),\n });\n }\n }\n return out;\n}\n\n/**\n * Whether a space's head-ref hash is present in its reconstructed graph.\n * An empty graph is reachable only by the empty-contract sentinel.\n */\nfunction headRefPresentInGraph(member: ContractSpaceMember, headHash: string): boolean {\n const graph = member.graph();\n if (graph.nodes.size === 0) {\n return headHash === EMPTY_CONTRACT_HASH;\n }\n return graph.nodes.has(headHash);\n}\n\nfunction layoutViolations(\n spaces: readonly IntegritySpaceState[],\n declaredExtensions: readonly DeclaredExtensionEntry[],\n): readonly IntegrityViolation[] {\n const out: IntegrityViolation[] = [];\n const extensionSpaceIds = new Set(spaces.filter((s) => !s.isApp).map((s) => s.member.spaceId));\n const declaredIds = new Set(declaredExtensions.map((d) => d.id));\n\n for (const id of [...extensionSpaceIds].sort()) {\n if (!declaredIds.has(id)) {\n out.push({ kind: 'orphanSpaceDir', spaceId: id });\n }\n }\n for (const id of [...declaredIds].sort()) {\n if (!extensionSpaceIds.has(id)) {\n out.push({ kind: 'declaredButUnmigrated', spaceId: id });\n }\n }\n return out;\n}\n\nfunction contractViolations(input: IntegrityComputationInput): readonly IntegrityViolation[] {\n const out: IntegrityViolation[] = [];\n const elementClaimedBy = new Map<string, string[]>();\n\n for (const { member } of input.spaces) {\n let contract: ReturnType<ContractSpaceMember['contract']>;\n try {\n contract = member.contract();\n } catch (error) {\n out.push({ kind: 'contractUnreadable', spaceId: member.spaceId, detail: detailOf(error) });\n continue;\n }\n\n if (contract.target !== input.targetId) {\n out.push({\n kind: 'targetMismatch',\n spaceId: member.spaceId,\n expected: input.targetId,\n actual: contract.target,\n });\n }\n\n for (const { entityName: elementName } of elementCoordinates(contract.storage)) {\n const claimers = elementClaimedBy.get(elementName);\n if (claimers) claimers.push(member.spaceId);\n else elementClaimedBy.set(elementName, [member.spaceId]);\n }\n }\n\n const disjointness: IntegrityViolation[] = [];\n for (const [element, claimedBy] of elementClaimedBy) {\n if (claimedBy.length > 1) {\n disjointness.push({ kind: 'disjointness', element, claimedBy: [...claimedBy].sort() });\n }\n }\n disjointness.sort((a, b) =>\n a.kind === 'disjointness' && b.kind === 'disjointness' ? a.element.localeCompare(b.element) : 0,\n );\n out.push(...disjointness);\n return out;\n}\n\nfunction detailOf(error: unknown): string {\n if (MigrationToolsError.is(error)) return error.why;\n if (error instanceof Error) return error.message;\n return String(error);\n}\n","import type { Contract } from '@prisma-next/contract/types';\nimport { MigrationToolsError } from '../errors';\nimport { readMigrationsDir } from '../io';\nimport { readContractSpaceContract } from '../read-contract-space-contract';\nimport { readContractSpaceHeadRef } from '../read-contract-space-head-ref';\nimport { HEAD_REF_NAME, type RefLoadProblem, readRefsTolerant } from '../refs';\nimport {\n APP_SPACE_ID,\n isValidSpaceId,\n RESERVED_SPACE_SUBDIR_NAMES,\n spaceMigrationDirectory,\n spaceRefsDirectory,\n} from '../space-layout';\nimport { listContractSpaceDirectories } from '../verify-contract-spaces';\nimport { createContractSpaceAggregate, createContractSpaceMember } from './aggregate';\nimport { computeIntegrityViolations, type IntegritySpaceState } from './check-integrity';\nimport type { ContractSpaceAggregate } from './types';\n\nexport type { DeclaredExtensionEntry } from '../integrity-violation';\n\n/**\n * Inputs for {@link loadContractSpaceAggregate}.\n *\n * Construction reads migration **state** from disk (`migrations/<space>/`\n * packages + refs + head refs). The app's *live* contract is not a disk\n * artefact — in Prisma Next it is always compiled from the project's\n * central contract, so the caller always has it and threads it in as\n * `appContract`. `deserializeContract` is held and called lazily only for\n * the on-disk extension contracts (`migrations/<ext>/contract.json`).\n */\nexport interface LoadAggregateInput {\n readonly migrationsDir: string;\n readonly deserializeContract: (raw: unknown) => Contract;\n readonly appContract: Contract;\n}\n\n/**\n * Build a tolerant, queryable {@link ContractSpaceAggregate} from on-disk\n * migration state plus the caller's live app contract.\n *\n * Building **never throws on disk content**: a hash- or\n * invariants-mismatched package is retained, an unparseable package is\n * omitted, a missing extension head ref leaves `headRef: null`, and an\n * unreadable on-disk contract defers its failure to `member.contract()`.\n * Every such problem is judged by {@link ContractSpaceAggregate.checkIntegrity}\n * rather than aborting the load. The only rejections are catastrophic I/O\n * (a `migrations/` that exists but is unreadable for reasons other than\n * absence).\n *\n * The app space's head ref is synthesised from the live contract's\n * storage hash (the app contract is authored independently of the\n * migration graph), and `app.contract()` returns the supplied contract.\n * Extension spaces read their contract, refs, and head ref from disk.\n */\nexport async function loadContractSpaceAggregate(\n input: LoadAggregateInput,\n): Promise<ContractSpaceAggregate> {\n const { migrationsDir, deserializeContract, appContract } = input;\n const targetId = appContract.target;\n\n const appState = await loadAppSpace(migrationsDir, appContract, deserializeContract);\n const extensionStates = await loadExtensionSpaces(migrationsDir, deserializeContract);\n\n const spaces: readonly IntegritySpaceState[] = [appState, ...extensionStates];\n\n return createContractSpaceAggregate({\n targetId,\n app: appState.member,\n extensions: extensionStates.map((state) => state.member),\n checkIntegrity: (opts) => computeIntegrityViolations({ targetId, spaces }, opts),\n });\n}\n\nasync function loadAppSpace(\n migrationsDir: string,\n appContract: Contract,\n deserializeContract: (raw: unknown) => Contract,\n): Promise<IntegritySpaceState> {\n const spaceDir = spaceMigrationDirectory(migrationsDir, APP_SPACE_ID);\n const { packages, problems } = await readMigrationsDir(spaceDir);\n const { refs, problems: refProblems } = await readRefsTolerant(spaceRefsDirectory(spaceDir));\n\n const member = createContractSpaceMember({\n spaceId: APP_SPACE_ID,\n packages,\n refs,\n headRef: { hash: appContract.storage.storageHash, invariants: [] },\n refsDir: spaceRefsDirectory(spaceDir),\n resolveContract: () => appContract,\n deserializeContract,\n });\n\n // The app head ref is synthesised from the live contract, so there is\n // no on-disk head.json to be missing or corrupt for it.\n return { member, problems, refProblems, headRefProblem: null, isApp: true };\n}\n\nasync function loadExtensionSpaces(\n migrationsDir: string,\n deserializeContract: (raw: unknown) => Contract,\n): Promise<readonly IntegritySpaceState[]> {\n const candidateDirs = await listContractSpaceDirectories(migrationsDir);\n const extensionIds = candidateDirs\n .filter((name) => name !== APP_SPACE_ID)\n .filter((name) => !RESERVED_SPACE_SUBDIR_NAMES.has(name))\n .filter(isValidSpaceId)\n .sort();\n\n const states: IntegritySpaceState[] = [];\n for (const spaceId of extensionIds) {\n states.push(await loadExtensionSpace(migrationsDir, spaceId, deserializeContract));\n }\n return states;\n}\n\nasync function loadExtensionSpace(\n migrationsDir: string,\n spaceId: string,\n deserializeContract: (raw: unknown) => Contract,\n): Promise<IntegritySpaceState> {\n const spaceDir = spaceMigrationDirectory(migrationsDir, spaceId);\n const { packages, problems } = await readMigrationsDir(spaceDir);\n const { refs, problems: refProblems } = await readRefsTolerant(spaceRefsDirectory(spaceDir));\n const { headRef, problem: headRefProblem } = await readHeadRefTolerant(migrationsDir, spaceId);\n const rawContract = await readRawContractDeferred(migrationsDir, spaceId);\n\n const member = createContractSpaceMember({\n spaceId,\n packages,\n refs,\n headRef,\n refsDir: spaceRefsDirectory(spaceDir),\n resolveContract: () => deserializeContract(rawContract()),\n deserializeContract,\n });\n\n return { member, problems, refProblems, headRefProblem, isApp: false };\n}\n\n/**\n * The result of resolving an extension's `refs/head.json`: the parsed\n * head ref (or `null` when the file is absent or corrupt) plus a problem\n * when the file exists but cannot be parsed.\n */\ninterface HeadRefReadResult {\n readonly headRef: Awaited<ReturnType<typeof readContractSpaceHeadRef>>;\n readonly problem: RefLoadProblem | null;\n}\n\n/**\n * Read an extension's head ref, distinguishing a *genuinely absent*\n * `head.json` (`headRef: null`, no problem — judged `headRefMissing`)\n * from one that *exists but cannot be parsed* (`headRef: null` plus a\n * problem — judged `refUnreadable`, not `headRefMissing`).\n * `readContractSpaceHeadRef` already returns `null` only for ENOENT and\n * throws for unparseable / schema-invalid content, so the throw is the\n * corruption signal. Construction never throws on disk content.\n */\nfunction isToleratedRefHeadReadError(error: unknown): boolean {\n if (MigrationToolsError.is(error)) return true;\n if (!(error instanceof Error)) return false;\n const code = (error as NodeJS.ErrnoException).code;\n return code === 'ENOENT' || code === 'EISDIR';\n}\n\nasync function readHeadRefTolerant(\n migrationsDir: string,\n spaceId: string,\n): Promise<HeadRefReadResult> {\n try {\n const headRef = await readContractSpaceHeadRef(migrationsDir, spaceId);\n return { headRef, problem: null };\n } catch (error) {\n if (!isToleratedRefHeadReadError(error)) {\n throw error;\n }\n return { headRef: null, problem: { refName: HEAD_REF_NAME, detail: detailOf(error) } };\n }\n}\n\nfunction detailOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/**\n * Read the raw on-disk contract eagerly (cheap I/O) but defer its\n * (throwing) failure to call time, so a missing or unparseable\n * `contract.json` becomes a `contract()` throw — surfaced as\n * `contractUnreadable` — rather than a construction failure.\n */\nasync function readRawContractDeferred(\n migrationsDir: string,\n spaceId: string,\n): Promise<() => unknown> {\n try {\n const raw = await readContractSpaceContract(migrationsDir, spaceId);\n return () => raw;\n } catch (error) {\n return () => {\n throw error;\n };\n }\n}\n","import type { MigrationPlan } from '@prisma-next/framework-components/control';\nimport { EMPTY_CONTRACT_HASH } from '../../constants';\nimport { findPathWithDecision } from '../../migration-graph';\nimport type { MigrationOps, OnDiskMigrationPackage } from '../../package';\nimport { requireHeadRef } from '../aggregate';\nimport type { ContractMarkerRecordLike } from '../marker-types';\nimport type { PerSpacePlan } from '../planner-types';\nimport type { ContractSpaceMember } from '../types';\n\n/**\n * Outcome variants for the graph-walk strategy. Mirrors\n * {@link import('../../compute-extension-space-apply-path').ExtensionSpaceApplyPathOutcome}\n * but operates against the member's lazily-reconstructed `graph()`\n * instead of re-reading from disk. The aggregate planner converts\n * these into {@link import('../planner-types').PlannerError}\n * variants.\n */\nexport type GraphWalkOutcome =\n | { readonly kind: 'ok'; readonly result: PerSpacePlan }\n | { readonly kind: 'unreachable' }\n | { readonly kind: 'unsatisfiable'; readonly missing: readonly string[] };\n\nexport interface GraphWalkStrategyInputs {\n readonly aggregateTargetId: string;\n readonly member: ContractSpaceMember;\n readonly currentMarker: ContractMarkerRecordLike | null;\n /**\n * Optional ref name to decorate the resulting `PathDecision`. Used by\n * `migrate` to surface the user-supplied `--to <name>` in\n * structured-progress events and invariant-path error envelopes. The\n * strategy itself does not interpret it.\n */\n readonly refName?: string;\n}\n\n/**\n * Walk a member's hydrated migration graph from the live marker to\n * `member.headRef.hash`, covering every required invariant.\n *\n * Pure synchronous function — no I/O. The aggregate's loader has\n * already integrity-checked every package and reconstructed the graph;\n * this strategy just looks up ops by `migrationHash` and assembles a\n * `MigrationPlan` with `targetId` set from the aggregate (no\n * placeholder cast).\n *\n * Required invariants are computed as `headRef.invariants \\ marker.invariants`\n * — the marker already declares some invariants satisfied; the path\n * only needs to provide the remainder. Mirrors today's\n * `computeExtensionSpaceApplyPath` semantics.\n */\nexport function graphWalkStrategy(input: GraphWalkStrategyInputs): GraphWalkOutcome {\n const { aggregateTargetId, member, currentMarker, refName } = input;\n const headRef = requireHeadRef(member);\n const graph = member.graph();\n const packagesByMigrationHash = new Map<string, OnDiskMigrationPackage>(\n member.packages.map((pkg) => [pkg.metadata.migrationHash, pkg]),\n );\n\n const fromHash = currentMarker?.storageHash ?? EMPTY_CONTRACT_HASH;\n const markerInvariants = new Set(currentMarker?.invariants ?? []);\n const required = new Set(headRef.invariants.filter((id) => !markerInvariants.has(id)));\n\n const outcome = findPathWithDecision(graph, fromHash, headRef.hash, {\n required,\n ...(refName !== undefined ? { refName } : {}),\n });\n\n if (outcome.kind === 'unreachable') {\n return { kind: 'unreachable' };\n }\n if (outcome.kind === 'unsatisfiable') {\n return { kind: 'unsatisfiable', missing: outcome.missing };\n }\n\n const pathOps: MigrationOps[number][] = [];\n const providedInvariantsSet = new Set<string>();\n const edgeRefs: Array<{\n migrationHash: string;\n dirName: string;\n from: string;\n to: string;\n operationCount: number;\n }> = [];\n for (const edge of outcome.decision.selectedPath) {\n const pkg = packagesByMigrationHash.get(edge.migrationHash);\n if (!pkg) {\n throw new Error(\n `Migration package missing for edge ${edge.migrationHash} in space \"${member.spaceId}\". The hydrated migration graph and packagesByMigrationHash map are out of sync — this should be unreachable; report.`,\n );\n }\n for (const op of pkg.ops) pathOps.push(op);\n for (const invariant of pkg.metadata.providedInvariants) providedInvariantsSet.add(invariant);\n edgeRefs.push({\n migrationHash: edge.migrationHash,\n dirName: edge.dirName,\n from: edge.from,\n to: edge.to,\n operationCount: pkg.ops.length,\n });\n }\n\n const plan: MigrationPlan = {\n targetId: aggregateTargetId,\n spaceId: member.spaceId,\n origin: currentMarker === null ? null : { storageHash: currentMarker.storageHash },\n destination: { storageHash: headRef.hash },\n operations: pathOps,\n providedInvariants: [...providedInvariantsSet].sort(),\n };\n\n return {\n kind: 'ok',\n result: {\n plan,\n displayOps: pathOps,\n destinationContract: member.contract(),\n strategy: 'graph-walk',\n migrationEdges: edgeRefs,\n pathDecision: outcome.decision,\n },\n };\n}\n","import { elementCoordinates } from '@prisma-next/framework-components/ir';\nimport type { ContractSpaceMember } from './types';\n\n/**\n * Project the **introspected live schema** to the slice claimed by a\n * single contract-space member.\n *\n * \"Schema\" here means the live introspected database state — the\n * planner / verifier sees this object as a `MongoSchemaIR` (Mongo) or\n * `SqlSchemaIR` (SQL). It is **not** a database schema in the SQL\n * `CREATE SCHEMA` sense, nor a contract-space namespace. The\n * function's job is to filter that introspected state down to the\n * elements claimed by one space, so a per-space verify pass doesn't\n * see another space's storage as \"extras\".\n *\n * Returns the same `schema` value with every top-level storage element\n * (table or collection) claimed by **other** members of the aggregate\n * removed. Elements not claimed by any member flow through unchanged —\n * the planner / verifier sees them as orphans (extras in strict mode).\n *\n * Used by:\n *\n * - The aggregate planner's **synth strategy**: when synthesising a\n * plan against a member's contract, the live schema must be projected\n * to that member's slice so the planner doesn't treat elements claimed\n * by other members as \"extras\" and emit destructive ops to drop them.\n * - The aggregate verifier's **schemaCheck**: projects per member so the\n * single-contract verify only sees the slice claimed by the member it\n * is checking. Closes the architectural concern that a multi-member\n * deployment makes each member's elements look like extras to every\n * other member's verify pass.\n *\n * **Duck-typing semantics**: the helper operates on `unknown` for the\n * schema and falls through structurally if the shape doesn't match.\n * Two storage shapes are recognised today:\n *\n * - SQL families expose `storage.tables: Record<string, ...>` on\n * contracts and the introspected schema mirrors the same record shape.\n * Pruning iterates the record entries.\n * - Mongo exposes `storage.collections: Record<string, ...>` on\n * contracts; the introspected `MongoSchemaIR` exposes\n * `collections: ReadonlyArray<{name: string, ...}>`. Pruning iterates\n * the array on the schema side and the record's keys on the\n * other-member side.\n *\n * Schemas of unrecognised shape are returned unchanged. The function\n * never imports family classes (`SqlSchemaIR`, `MongoSchemaIR`); the\n * projected schema is a plain object — `{...schema, tables: pruned}` or\n * `{...schema, collections: pruned}` — that downstream consumers\n * duck-type. A future family with a different storage shape gets the\n * schema returned unchanged rather than blowing up the aggregate\n * planner.\n *\n * Record-shape detection guards against arrays (`!Array.isArray`) so\n * an unrecognised array-shaped value falls through unchanged rather\n * than being pruned by numeric keys.\n */\nexport function projectSchemaToSpace(\n schema: unknown,\n member: ContractSpaceMember,\n otherMembers: ReadonlyArray<ContractSpaceMember>,\n): unknown {\n if (typeof schema !== 'object' || schema === null) return schema;\n\n const ownedByOthers = collectOwnedNames(member, otherMembers);\n if (ownedByOthers.size === 0) return schema;\n\n const schemaObj = schema as { readonly tables?: unknown; readonly collections?: unknown };\n\n if (\n typeof schemaObj.tables === 'object' &&\n schemaObj.tables !== null &&\n !Array.isArray(schemaObj.tables)\n ) {\n return pruneRecord(schemaObj, 'tables', ownedByOthers);\n }\n\n if (Array.isArray(schemaObj.collections)) {\n return pruneCollectionsArray(schemaObj, ownedByOthers);\n }\n\n if (\n typeof schemaObj.collections === 'object' &&\n schemaObj.collections !== null &&\n !Array.isArray(schemaObj.collections)\n ) {\n return pruneRecord(schemaObj, 'collections', ownedByOthers);\n }\n\n return schema;\n}\n\nfunction collectOwnedNames(\n member: ContractSpaceMember,\n otherMembers: ReadonlyArray<ContractSpaceMember>,\n): Set<string> {\n const owned = new Set<string>();\n for (const other of otherMembers) {\n if (other.spaceId === member.spaceId) continue;\n for (const { entityName } of elementCoordinates(other.contract().storage)) {\n owned.add(entityName);\n }\n }\n return owned;\n}\n\nfunction pruneRecord(\n schemaObj: { readonly tables?: unknown; readonly collections?: unknown },\n field: 'tables' | 'collections',\n ownedByOthers: ReadonlySet<string>,\n): unknown {\n const source = schemaObj[field] as Record<string, unknown>;\n let removed = false;\n const pruned: Record<string, unknown> = {};\n for (const [name, value] of Object.entries(source)) {\n if (ownedByOthers.has(name)) {\n removed = true;\n } else {\n pruned[name] = value;\n }\n }\n if (!removed) return schemaObj;\n return { ...schemaObj, [field]: pruned };\n}\n\nfunction pruneCollectionsArray(\n schemaObj: { readonly collections?: unknown },\n ownedByOthers: ReadonlySet<string>,\n): unknown {\n const source = schemaObj.collections as ReadonlyArray<unknown>;\n let removed = false;\n const pruned: unknown[] = [];\n for (const entry of source) {\n if (typeof entry === 'object' && entry !== null) {\n const name = (entry as { readonly name?: unknown }).name;\n if (typeof name === 'string' && ownedByOthers.has(name)) {\n removed = true;\n continue;\n }\n }\n pruned.push(entry);\n }\n if (!removed) return schemaObj;\n return { ...schemaObj, collections: pruned };\n}\n","import type { AggregateMigrationEdgeRef } from './planner-types';\n\nexport function buildSynthMigrationEdge(args: {\n readonly currentMarkerStorageHash: string | null | undefined;\n readonly destinationStorageHash: string;\n readonly operationCount: number;\n}): AggregateMigrationEdgeRef {\n return {\n dirName: '',\n migrationHash: args.destinationStorageHash,\n from: args.currentMarkerStorageHash ?? '',\n to: args.destinationStorageHash,\n operationCount: args.operationCount,\n };\n}\n","import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';\nimport type {\n ControlFamilyInstance,\n MigrationOperationPolicy,\n MigrationPlan,\n MigrationPlannerConflict,\n MigrationPlannerResult,\n TargetMigrationsCapability,\n} from '@prisma-next/framework-components/control';\nimport type { ContractMarkerRecordLike } from '../marker-types';\nimport type { PerSpacePlan } from '../planner-types';\nimport { projectSchemaToSpace } from '../project-schema-to-space';\nimport { buildSynthMigrationEdge } from '../synth-migration-edge';\nimport type { ContractSpaceMember } from '../types';\n\nexport interface SynthStrategyInputs<TFamilyId extends string, TTargetId extends string> {\n readonly aggregateTargetId: string;\n readonly currentMarker: ContractMarkerRecordLike | null;\n readonly member: ContractSpaceMember;\n readonly otherMembers: ReadonlyArray<ContractSpaceMember>;\n readonly schemaIntrospection: unknown;\n readonly familyInstance: ControlFamilyInstance<TFamilyId, unknown>;\n readonly migrations: TargetMigrationsCapability<\n TFamilyId,\n TTargetId,\n ControlFamilyInstance<TFamilyId, unknown>\n >;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;\n readonly operationPolicy: MigrationOperationPolicy;\n}\n\nexport type SynthStrategyOutcome =\n | { readonly kind: 'ok'; readonly result: PerSpacePlan }\n | { readonly kind: 'failure'; readonly conflicts: readonly MigrationPlannerConflict[] };\n\n/**\n * The {@link MigrationPlanner.plan} interface is declared as synchronous,\n * but historical and test fixture call sites have always invoked it\n * with `await` (see prior `db-apply-per-space.ts`). Tolerating a\n * Promise here keeps existing test mocks working without changing the\n * declared family SPI.\n */\ntype MaybeAsyncPlannerResult = MigrationPlannerResult | Promise<MigrationPlannerResult>;\n\n/**\n * Synthesise a migration plan for a single member by projecting the\n * live schema down to that member's claimed slice and delegating to\n * the family's `createPlanner(...).plan(...)`.\n *\n * Pre-projection (via {@link projectSchemaToSpace}) closes the F23\n * concern: without it, the family's planner sees other members'\n * tables as \"extras\" and emits destructive ops to drop them. With it,\n * the planner only sees the slice this member claims.\n *\n * The synthesised plan's `targetId` is set from `aggregateTargetId`\n * (the aggregate's ambient target). The family's planner does not\n * stamp `targetId` on the produced plan; the aggregate planner is\n * the single point that knows the target.\n *\n * Used by:\n *\n * - The app member by default (CLI policy\n * `ignoreGraphFor: { app.spaceId }`).\n * - Any extension member whose `headRef.invariants` is empty (the\n * strategy selector falls back to synth when graph-walk isn't\n * required).\n */\nexport async function synthStrategy<TFamilyId extends string, TTargetId extends string>(\n input: SynthStrategyInputs<TFamilyId, TTargetId>,\n): Promise<SynthStrategyOutcome> {\n const projectedSchema = projectSchemaToSpace(\n input.schemaIntrospection,\n input.member,\n input.otherMembers,\n );\n\n const planner = input.migrations.createPlanner(input.familyInstance);\n const plannerResult: MigrationPlannerResult = await (planner.plan({\n contract: input.member.contract(),\n schema: projectedSchema,\n policy: input.operationPolicy,\n fromContract: null,\n frameworkComponents: input.frameworkComponents,\n spaceId: input.member.spaceId,\n }) as MaybeAsyncPlannerResult);\n\n if (plannerResult.kind === 'failure') {\n return { kind: 'failure', conflicts: plannerResult.conflicts };\n }\n\n const synthedPlan = plannerResult.plan;\n // The family planner returns a class-instance-shaped plan whose\n // `destination` / `operations` are accessors on the prototype, often\n // backed by private fields. A naive spread (`{ ...synthedPlan }`)\n // would lose those accessors and produce a plan with\n // `destination: undefined`; rebinding the prototype on a plain\n // object would break private-field access. We instead wrap the plan\n // in a Proxy that forwards every read except `targetId`, which is\n // stamped from the aggregate's ambient target. This preserves the\n // planner's class semantics while keeping the aggregate the single\n // source of truth for `targetId`.\n const plan: MigrationPlan = new Proxy(synthedPlan, {\n get(target, prop) {\n if (prop === 'targetId') return input.aggregateTargetId;\n // Forward `this` as the original target so prototype-bound\n // private fields (#destination, #operations, …) resolve.\n return Reflect.get(target, prop, target);\n },\n has(target, prop) {\n if (prop === 'targetId') return true;\n return Reflect.has(target, prop);\n },\n });\n\n const destinationStorageHash = synthedPlan.destination.storageHash;\n return {\n kind: 'ok',\n result: {\n plan,\n displayOps: synthedPlan.operations,\n destinationContract: input.member.contract(),\n strategy: 'synth',\n ...(plannerResult.warnings && plannerResult.warnings.length > 0\n ? { warnings: plannerResult.warnings }\n : {}),\n migrationEdges: [\n buildSynthMigrationEdge({\n currentMarkerStorageHash: input.currentMarker?.storageHash,\n destinationStorageHash,\n operationCount: synthedPlan.operations.length,\n }),\n ],\n },\n };\n}\n","import { notOk, ok } from '@prisma-next/utils/result';\nimport { requireHeadRef } from './aggregate';\nimport type { PerSpacePlan, PlannerError, PlannerInput, PlannerOutput } from './planner-types';\nimport { graphWalkStrategy } from './strategies/graph-walk';\nimport { synthStrategy } from './strategies/synth';\nimport type { ContractSpaceMember } from './types';\n\nexport type {\n AggregateCurrentDBState,\n AggregateMigrationEdgeRef,\n CallerPolicy,\n PerSpacePlan,\n PlannerError,\n PlannerInput,\n PlannerOutput,\n PlannerSuccess,\n} from './planner-types';\n\n/**\n * Plan a migration across every member of a {@link ContractSpaceAggregate}.\n *\n * Strategy selection per member, in order; first match wins:\n *\n * 1. If `callerPolicy.ignoreGraphFor.has(member.spaceId)`:\n * - If `member.headRef.invariants` is empty → synth.\n * - Else → `policyConflict` (synth cannot satisfy authored invariants).\n * 2. Else if `member.graph()` is non-empty AND graph-walk\n * succeeds → graph-walk.\n * 3. Else if `member.headRef.invariants` is empty → synth.\n * 4. Else → graph-walk failure → `extensionPathUnreachable` /\n * `extensionPathUnsatisfiable`.\n *\n * Output `applyOrder` is `[...aggregate.extensions.map(spaceId), aggregate.app.spaceId]`\n * — extensions alphabetical, then app — matching today's\n * `concatenateSpaceApplyInputs` ordering. This preserves\n * `MigrationRunnerFailure.failingSpace` attribution byte-for-byte.\n *\n * Every emitted `MigrationPlan` has `targetId = aggregate.targetId`.\n * No placeholder cast; no patch step.\n */\nexport async function planMigration<TFamilyId extends string, TTargetId extends string>(\n input: PlannerInput<TFamilyId, TTargetId>,\n): Promise<PlannerOutput> {\n const { aggregate, currentDBState, callerPolicy } = input;\n const allMembers: ReadonlyArray<ContractSpaceMember> = [aggregate.app, ...aggregate.extensions];\n\n const perSpace = new Map<string, PerSpacePlan>();\n\n // Iterate in apply order so a per-member error short-circuits the\n // walk in the same order the runner would walk inputs.\n const orderedMembers: ReadonlyArray<ContractSpaceMember> = [\n ...aggregate.extensions,\n aggregate.app,\n ];\n\n for (const member of orderedMembers) {\n const otherMembers = allMembers.filter((m) => m.spaceId !== member.spaceId);\n const currentMarker = currentDBState.markersBySpaceId.get(member.spaceId) ?? null;\n const headRef = requireHeadRef(member);\n\n const ignoreGraph = callerPolicy.ignoreGraphFor.has(member.spaceId);\n const invariantsRequired = headRef.invariants.length > 0;\n\n if (ignoreGraph && invariantsRequired) {\n const conflict: PlannerError = {\n kind: 'policyConflict',\n spaceId: member.spaceId,\n detail: `\\`callerPolicy.ignoreGraphFor\\` requested for space \"${member.spaceId}\", but the member declares non-empty head-ref invariants (${headRef.invariants.join(', ')}). Synthesising a plan from the contract IR cannot satisfy authored invariants — the graph must be walked. Either remove \"${member.spaceId}\" from \\`ignoreGraphFor\\` or amend the on-disk head ref to declare zero invariants.`,\n };\n return notOk(conflict);\n }\n\n if (ignoreGraph) {\n const synthOutcome = await synthStrategy({\n aggregateTargetId: aggregate.targetId,\n currentMarker,\n member,\n otherMembers,\n schemaIntrospection: currentDBState.schemaIntrospection,\n familyInstance: input.familyInstance,\n migrations: input.migrations,\n frameworkComponents: input.frameworkComponents,\n operationPolicy: input.operationPolicy,\n });\n if (synthOutcome.kind === 'failure') {\n return notOk({\n kind: 'appSynthFailure',\n spaceId: member.spaceId,\n conflicts: synthOutcome.conflicts,\n });\n }\n perSpace.set(member.spaceId, synthOutcome.result);\n continue;\n }\n\n // Try graph-walk first when the graph has nodes; fall back to synth\n // when the graph is empty AND no invariants are required.\n if (member.graph().nodes.size > 0) {\n const walked = graphWalkStrategy({\n aggregateTargetId: aggregate.targetId,\n member,\n currentMarker,\n });\n if (walked.kind === 'ok') {\n perSpace.set(member.spaceId, walked.result);\n continue;\n }\n if (walked.kind === 'unreachable') {\n return notOk({\n kind: 'extensionPathUnreachable',\n spaceId: member.spaceId,\n target: headRef.hash,\n });\n }\n // unsatisfiable — surface\n return notOk({\n kind: 'extensionPathUnsatisfiable',\n spaceId: member.spaceId,\n missingInvariants: walked.missing,\n });\n }\n\n // Empty graph: synth is the only option, and it can only satisfy\n // empty-invariant members.\n if (invariantsRequired) {\n return notOk({\n kind: 'extensionPathUnsatisfiable',\n spaceId: member.spaceId,\n missingInvariants: [...headRef.invariants].sort(),\n });\n }\n\n const synthOutcome = await synthStrategy({\n aggregateTargetId: aggregate.targetId,\n currentMarker,\n member,\n otherMembers,\n schemaIntrospection: currentDBState.schemaIntrospection,\n familyInstance: input.familyInstance,\n migrations: input.migrations,\n frameworkComponents: input.frameworkComponents,\n operationPolicy: input.operationPolicy,\n });\n if (synthOutcome.kind === 'failure') {\n return notOk({\n kind: 'appSynthFailure',\n spaceId: member.spaceId,\n conflicts: synthOutcome.conflicts,\n });\n }\n perSpace.set(member.spaceId, synthOutcome.result);\n }\n\n return ok({\n perSpace,\n applyOrder: [...aggregate.extensions.map((m) => m.spaceId), aggregate.app.spaceId],\n });\n}\n","import { elementCoordinates } from '@prisma-next/framework-components/ir';\nimport type { Result } from '@prisma-next/utils/result';\nimport { notOk, ok } from '@prisma-next/utils/result';\nimport { requireHeadRef } from './aggregate';\nimport type { ContractMarkerRecordLike } from './marker-types';\nimport { projectSchemaToSpace } from './project-schema-to-space';\nimport type { ContractSpaceAggregate, ContractSpaceMember } from './types';\n\n/**\n * Caller policy for the verifier. Today's only knob is\n * `mode`: `strict` treats orphan elements (live tables not claimed by\n * any aggregate member) as errors; `lenient` treats them as\n * informational. Maps directly to `db verify --strict`.\n */\nexport interface VerifierInput<TSchemaResult> {\n readonly aggregate: ContractSpaceAggregate;\n readonly markersBySpaceId: ReadonlyMap<string, ContractMarkerRecordLike | null>;\n readonly schemaIntrospection: unknown;\n readonly mode: 'strict' | 'lenient';\n /**\n * Caller-supplied per-space schema verifier. The CLI wires this to\n * the family's `verifySqlSchema` (SQL) / equivalent (other\n * families). The verifier projects the schema to the\n * member's slice via {@link projectSchemaToSpace} before invoking\n * the callback, so single-contract semantics are preserved.\n *\n * Typed structurally with a generic `TSchemaResult` so the\n * migration-tools layer doesn't depend on the SQL family's\n * `VerifySqlSchemaResult`. CLI callers pass the family's type\n * through unchanged.\n */\n readonly verifySchemaForMember: (\n projectedSchema: unknown,\n member: ContractSpaceMember,\n mode: 'strict' | 'lenient',\n ) => TSchemaResult;\n}\n\n/**\n * Marker-check result per member. Mirrors the four cases the\n * `verifyContractSpaces` primitive surfaces today, plus an `'absent'`\n * case for greenfield spaces (no marker row written yet — `db init`\n * not run).\n */\nexport type MarkerCheckResult =\n | { readonly kind: 'ok' }\n | { readonly kind: 'absent' }\n | {\n readonly kind: 'hashMismatch';\n readonly markerHash: string;\n readonly expected: string;\n }\n | { readonly kind: 'missingInvariants'; readonly missing: readonly string[] };\n\nexport interface MarkerCheckSection {\n readonly perSpace: ReadonlyMap<string, MarkerCheckResult>;\n readonly orphanMarkers: readonly {\n readonly spaceId: string;\n readonly row: ContractMarkerRecordLike;\n }[];\n}\n\n/**\n * A live storage element (today: a top-level table) not claimed by any\n * member of the aggregate. The verifier always reports these;\n * the caller decides what to do — `db verify --strict` treats them as\n * errors, the lenient default treats them as informational.\n *\n * Today only `kind: 'table'` exists. The discriminated shape leaves\n * room for orphan columns / indexes / sequences in the future without\n * breaking the type contract.\n */\nexport type OrphanElement = { readonly kind: 'table'; readonly name: string };\n\nexport interface SchemaCheckSection<TSchemaResult> {\n readonly perSpace: ReadonlyMap<string, TSchemaResult>;\n /**\n * Live elements present in the introspected schema that are not\n * claimed by **any** aggregate member. Sorted alphabetically by name.\n */\n readonly orphanElements: readonly OrphanElement[];\n}\n\nexport interface VerifierSuccess<TSchemaResult> {\n readonly markerCheck: MarkerCheckSection;\n readonly schemaCheck: SchemaCheckSection<TSchemaResult>;\n}\n\nexport type VerifierError = {\n readonly kind: 'introspectionFailure';\n readonly detail: string;\n};\n\nexport type VerifierOutput<TSchemaResult> = Result<VerifierSuccess<TSchemaResult>, VerifierError>;\n\n/**\n * Verify a {@link ContractSpaceAggregate} against the live database\n * state. Bundles two checks:\n *\n * - `markerCheck` per member: compare the live marker row against the\n * member's `headRef.hash` + `headRef.invariants`. Absence is a\n * distinct kind, not an error (callers — `db verify` strict vs\n * `db init` precondition — choose how to interpret it).\n * - `schemaCheck` per member: project the live schema to the slice\n * the member claims via {@link projectSchemaToSpace}, then delegate\n * to the caller-supplied `verifySchemaForMember`. The pre-projection\n * means the family's single-contract verifier no longer sees other\n * members' tables as `extras`, so a multi-member deployment never\n * surfaces cross-member tables as orphaned schema elements.\n *\n * `markerCheck.orphanMarkers` lists every marker row whose `space` is\n * not a member of the aggregate. `db verify` callers reject orphans;\n * future tooling may not.\n *\n * Pure synchronous function; no I/O. The caller (CLI) gathers\n * `markersBySpaceId` and `schemaIntrospection` ahead of the call.\n */\nexport function verifyMigration<TSchemaResult>(\n input: VerifierInput<TSchemaResult>,\n): VerifierOutput<TSchemaResult> {\n try {\n return runVerifyMigration(input);\n } catch (error) {\n return notOk({\n kind: 'introspectionFailure',\n detail: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\nfunction runVerifyMigration<TSchemaResult>(\n input: VerifierInput<TSchemaResult>,\n): VerifierOutput<TSchemaResult> {\n const { aggregate, markersBySpaceId, schemaIntrospection, mode, verifySchemaForMember } = input;\n const allMembers: ReadonlyArray<ContractSpaceMember> = [aggregate.app, ...aggregate.extensions];\n const memberSpaceIds = new Set(allMembers.map((m) => m.spaceId));\n\n // Marker check per member.\n const markerPerSpace = new Map<string, MarkerCheckResult>();\n for (const member of allMembers) {\n const marker = markersBySpaceId.get(member.spaceId) ?? null;\n if (marker === null) {\n markerPerSpace.set(member.spaceId, { kind: 'absent' });\n continue;\n }\n const headRef = requireHeadRef(member);\n if (marker.storageHash !== headRef.hash) {\n markerPerSpace.set(member.spaceId, {\n kind: 'hashMismatch',\n markerHash: marker.storageHash,\n expected: headRef.hash,\n });\n continue;\n }\n const markerInvariants = new Set(marker.invariants);\n const missing = headRef.invariants.filter((id) => !markerInvariants.has(id));\n if (missing.length > 0) {\n markerPerSpace.set(member.spaceId, {\n kind: 'missingInvariants',\n missing: [...missing].sort(),\n });\n continue;\n }\n markerPerSpace.set(member.spaceId, { kind: 'ok' });\n }\n\n // Orphan markers: entries in markersBySpaceId whose spaceId is not a\n // member of the aggregate.\n const orphanMarkers: { spaceId: string; row: ContractMarkerRecordLike }[] = [];\n for (const [spaceId, row] of markersBySpaceId) {\n if (row !== null && !memberSpaceIds.has(spaceId)) {\n orphanMarkers.push({ spaceId, row });\n }\n }\n orphanMarkers.sort((a, b) => a.spaceId.localeCompare(b.spaceId));\n\n // Schema check per member (with per-space pre-projection).\n const schemaPerSpace = new Map<string, TSchemaResult>();\n for (const member of allMembers) {\n const others = allMembers.filter((m) => m.spaceId !== member.spaceId);\n const projected = projectSchemaToSpace(schemaIntrospection, member, others);\n schemaPerSpace.set(member.spaceId, verifySchemaForMember(projected, member, mode));\n }\n\n return ok({\n markerCheck: {\n perSpace: markerPerSpace,\n orphanMarkers,\n },\n schemaCheck: {\n perSpace: schemaPerSpace,\n orphanElements: detectOrphanElements(schemaIntrospection, allMembers),\n },\n });\n}\n\n/**\n * Live tables not claimed by any aggregate member. Duck-typed against\n * the introspected schema's `tables` map; schemas whose shape doesn't\n * match return an empty list (consistent with\n * {@link projectSchemaToSpace}'s fall-through).\n */\nfunction detectOrphanElements(\n schemaIntrospection: unknown,\n members: ReadonlyArray<ContractSpaceMember>,\n): readonly OrphanElement[] {\n if (typeof schemaIntrospection !== 'object' || schemaIntrospection === null) return [];\n const liveTables = (schemaIntrospection as { readonly tables?: unknown }).tables;\n if (typeof liveTables !== 'object' || liveTables === null) return [];\n\n const claimedTables = new Set<string>();\n for (const member of members) {\n const contract = member.contract();\n for (const { entityName } of elementCoordinates(contract.storage)) {\n claimedTables.add(entityName);\n }\n }\n\n const orphans: OrphanElement[] = [];\n for (const tableName of Object.keys(liveTables as Record<string, unknown>)) {\n if (!claimedTables.has(tableName)) {\n orphans.push({ kind: 'table', name: tableName });\n }\n }\n orphans.sort((a, b) => a.name.localeCompare(b.name));\n return orphans;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger-origin.d.mts","names":[],"sources":["../../src/ledger-origin.ts"],"mappings":";iBAEgB,sBAAA,CAAuB,cAA6B"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import "../constants-DWV9_o2Z.mjs";
|
|
2
|
+
//#region src/ledger-origin.ts
|
|
3
|
+
function ledgerOriginFromStored(originCoreHash) {
|
|
4
|
+
if (originCoreHash === null || originCoreHash === "" || originCoreHash === "sha256:empty") return null;
|
|
5
|
+
return originCoreHash;
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
8
|
+
export { ledgerOriginFromStored };
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=ledger-origin.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger-origin.mjs","names":[],"sources":["../../src/ledger-origin.ts"],"sourcesContent":["import { EMPTY_CONTRACT_HASH } from './constants';\n\nexport function ledgerOriginFromStored(originCoreHash: string | null): string | null {\n if (originCoreHash === null || originCoreHash === '' || originCoreHash === EMPTY_CONTRACT_HASH) {\n return null;\n }\n return originCoreHash;\n}\n"],"mappings":";;AAEA,SAAgB,uBAAuB,gBAA8C;CACnF,IAAI,mBAAmB,QAAQ,mBAAmB,MAAM,mBAAA,gBACtD,OAAO;CAET,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spaces.d.mts","names":[],"sources":["../../src/assert-descriptor-self-consistency.ts","../../src/read-contract-space-head-ref.ts","../../src/compute-extension-space-apply-path.ts","../../src/concatenate-space-apply-inputs.ts","../../src/contract-space-from-json.ts","../../src/emit-contract-space-artefacts.ts","../../src/gather-disk-contract-space-state.ts","../../src/plan-all-spaces.ts","../../src/read-contract-space-contract.ts","../../src/space-layout.ts"],"mappings":";;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"spaces.d.mts","names":[],"sources":["../../src/assert-descriptor-self-consistency.ts","../../src/read-contract-space-head-ref.ts","../../src/compute-extension-space-apply-path.ts","../../src/concatenate-space-apply-inputs.ts","../../src/contract-space-from-json.ts","../../src/emit-contract-space-artefacts.ts","../../src/gather-disk-contract-space-state.ts","../../src/plan-all-spaces.ts","../../src/read-contract-space-contract.ts","../../src/space-layout.ts"],"mappings":"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"}
|
package/dist/exports/spaces.mjs
CHANGED
|
@@ -9,19 +9,6 @@ import { mkdir, writeFile } from "node:fs/promises";
|
|
|
9
9
|
import { canonicalizeJson } from "@prisma-next/framework-components/utils";
|
|
10
10
|
import { computeStorageHash } from "@prisma-next/contract/hashing";
|
|
11
11
|
//#region src/assert-descriptor-self-consistency.ts
|
|
12
|
-
function stripNamespaceKinds(storage) {
|
|
13
|
-
const namespaces = storage["namespaces"];
|
|
14
|
-
if (!namespaces) return storage;
|
|
15
|
-
const stripped = {};
|
|
16
|
-
for (const [nsId, ns] of Object.entries(namespaces)) if (ns && typeof ns === "object") {
|
|
17
|
-
const { kind: _kind, ...rest } = ns;
|
|
18
|
-
stripped[nsId] = rest;
|
|
19
|
-
} else stripped[nsId] = ns;
|
|
20
|
-
return {
|
|
21
|
-
...storage,
|
|
22
|
-
namespaces: stripped
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
12
|
/**
|
|
26
13
|
* Assert that an extension descriptor is self-consistent: the
|
|
27
14
|
* `headRef.hash` it publishes must match the canonical hash recomputed
|
|
@@ -43,11 +30,10 @@ function stripNamespaceKinds(storage) {
|
|
|
43
30
|
*/
|
|
44
31
|
function assertDescriptorSelfConsistency(inputs) {
|
|
45
32
|
const { storageHash: _stripped, ...storageWithoutHash } = inputs.storage;
|
|
46
|
-
const normalizedStorage = stripNamespaceKinds(storageWithoutHash);
|
|
47
33
|
const recomputed = computeStorageHash({
|
|
48
34
|
target: inputs.target,
|
|
49
35
|
targetFamily: inputs.targetFamily,
|
|
50
|
-
storage:
|
|
36
|
+
storage: storageWithoutHash,
|
|
51
37
|
...ifDefined("shouldPreserveEmpty", inputs.shouldPreserveEmpty),
|
|
52
38
|
...ifDefined("sortStorage", inputs.sortStorage)
|
|
53
39
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spaces.mjs","names":[],"sources":["../../src/assert-descriptor-self-consistency.ts","../../src/compute-extension-space-apply-path.ts","../../src/contract-space-from-json.ts","../../src/emit-contract-space-artefacts.ts","../../src/gather-disk-contract-space-state.ts","../../src/plan-all-spaces.ts"],"sourcesContent":["import type { PreserveEmptyPredicate, StorageSort } from '@prisma-next/contract/hashing';\nimport { computeStorageHash } from '@prisma-next/contract/hashing';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { errorDescriptorHeadHashMismatch } from './errors';\n\nfunction stripNamespaceKinds(storage: Record<string, unknown>): Record<string, unknown> {\n const namespaces = storage['namespaces'] as Record<string, Record<string, unknown>> | undefined;\n if (!namespaces) return storage;\n const stripped: Record<string, Record<string, unknown>> = {};\n for (const [nsId, ns] of Object.entries(namespaces)) {\n if (ns && typeof ns === 'object') {\n const { kind: _kind, ...rest } = ns as Record<string, unknown>;\n stripped[nsId] = rest as Record<string, unknown>;\n } else {\n stripped[nsId] = ns;\n }\n }\n return { ...storage, namespaces: stripped };\n}\n\n/**\n * Inputs the helper needs to recompute the descriptor's storage hash and\n * compare it to the published `headRef.hash`. Kept structural so the SQL\n * family (and any future target family) can compose the check without\n * coupling to its own descriptor types.\n */\nexport interface DescriptorSelfConsistencyInputs {\n readonly extensionId: string;\n readonly target: string;\n readonly targetFamily: string;\n /**\n * Family-specific storage object. Typed as `unknown` so callers can\n * pass their own narrow storage shape (e.g. `SqlStorage`) without an\n * inline cast — the helper canonicalises through `JSON.stringify`\n * inside {@link computeStorageHash} and only requires a plain\n * record-shaped value at runtime.\n */\n readonly storage: unknown;\n readonly headRefHash: string;\n readonly shouldPreserveEmpty?: PreserveEmptyPredicate;\n readonly sortStorage?: StorageSort;\n}\n\n/**\n * Assert that an extension descriptor is self-consistent: the\n * `headRef.hash` it publishes must match the canonical hash recomputed\n * from its `contractSpace.contractJson`.\n *\n * Recomputes via {@link computeStorageHash} — the same canonical-JSON\n * pipeline the descriptor's own emit pipeline produced the hash with —\n * over `(target, targetFamily, storage)`. Mismatch indicates the\n * extension author bumped `contractJson` without rerunning emit, leaving\n * the descriptor's `headRef.hash` stale; the consumer-side helpers\n * (drift detection, on-disk artefact emission, runner marker writes) all\n * trust `headRef.hash` as the canonical identity, so a stale value would\n * silently corrupt every downstream boundary.\n *\n * Synchronous, pure, no I/O. Throws\n * `MIGRATION.DESCRIPTOR_HEAD_HASH_MISMATCH` on failure with both the\n * recomputed and published hashes in `details` so callers can surface a\n * clear remediation hint without re-deriving them.\n */\nexport function assertDescriptorSelfConsistency(inputs: DescriptorSelfConsistencyInputs): void {\n // The published `storage.storageHash` is the *output* of the production\n // emit pipeline's `computeStorageHash` call, computed over a storage\n // object that did not yet carry `storageHash`. Recomputing against the\n // published storage as-is would feed the result back into its own input\n // and produce a different digest. Strip `storageHash` before\n // recomputing so the helper sees the same canonical shape the\n // descriptor's authoring pipeline saw.\n // The helper requires only a plain record-shaped storage value at\n // runtime; a single cast here keeps the public input type\n // family-agnostic (`unknown`) while still letting us strip the\n // descriptor-published `storageHash` before re-canonicalising.\n const storageRecord = inputs.storage as Record<string, unknown>;\n const { storageHash: _stripped, ...storageWithoutHash } = storageRecord;\n // Target serializers (e.g. PostgresContractSerializer) inject a `kind`\n // discriminator into each namespace entry when writing contract.json (e.g.\n // `\"postgres-unbound-schema\"`). The authoring pipeline computes\n // `storageHash` from IR class instances whose `kind` property is\n // non-enumerable, so `kind` is absent from the hash input. Strip `kind`\n // from namespace entries before recomputing so this check always operates\n // on the same canonical shape the authoring pipeline saw.\n const normalizedStorage = stripNamespaceKinds(storageWithoutHash);\n const recomputed = computeStorageHash({\n target: inputs.target,\n targetFamily: inputs.targetFamily,\n storage: normalizedStorage,\n ...ifDefined('shouldPreserveEmpty', inputs.shouldPreserveEmpty),\n ...ifDefined('sortStorage', inputs.sortStorage),\n });\n if (recomputed !== inputs.headRefHash) {\n throw errorDescriptorHeadHashMismatch({\n extensionId: inputs.extensionId,\n recomputedHash: recomputed,\n headRefHash: inputs.headRefHash,\n });\n }\n}\n","import { EMPTY_CONTRACT_HASH } from './constants';\nimport { readMigrationsDir } from './io';\nimport { findPathWithDecision, reconstructGraph } from './migration-graph';\nimport type { MigrationOps } from './package';\nimport {\n type ContractSpaceHeadRef,\n readContractSpaceHeadRef,\n} from './read-contract-space-head-ref';\nimport { spaceMigrationDirectory } from './space-layout';\n\n/**\n * Outcome of {@link computeExtensionSpaceApplyPath} — a discriminated union\n * mirroring {@link import('./migration-graph').FindPathOutcome} so callers\n * can map structural / invariant failures to their preferred CLI envelope\n * without re-running pathfinding.\n */\nexport type ExtensionSpaceApplyPathOutcome =\n | {\n readonly kind: 'ok';\n readonly contractSpaceHeadRef: ContractSpaceHeadRef;\n /**\n * Sorted, deduplicated invariant ids covered by the walked path.\n * Mirrors the on-disk `providedInvariants` summed across edges and\n * canonicalised — what the runner stamps on the marker after apply.\n */\n readonly providedInvariants: readonly string[];\n /**\n * Path operations in apply order. Empty when the marker is already\n * at the recorded head (no-op).\n */\n readonly pathOps: MigrationOps;\n /**\n * Migration directory names walked, in order. Mirrors `pathOps`'s\n * structure but at the package granularity — useful for surfacing\n * \"applied N migration(s)\" messages.\n */\n readonly walkedMigrationDirs: readonly string[];\n }\n | { readonly kind: 'unreachable'; readonly contractSpaceHeadRef: ContractSpaceHeadRef }\n | {\n readonly kind: 'unsatisfiable';\n readonly contractSpaceHeadRef: ContractSpaceHeadRef;\n readonly missing: readonly string[];\n readonly structuralPath: readonly { readonly dirName: string; readonly to: string }[];\n }\n | { readonly kind: 'contractSpaceHeadRefMissing' };\n\n/**\n * Inputs to {@link computeExtensionSpaceApplyPath}. The helper is\n * deliberately framework-neutral and consumes only on-disk state:\n *\n * - `projectMigrationsDir` is the project's top-level `migrations/` dir.\n * - `spaceId` selects the per-space subdirectory under it.\n * - `currentMarkerHash` / `currentMarkerInvariants` come from the live\n * marker row keyed by `space = <spaceId>`. `null` hash = no marker yet\n * (the pathfinder treats this as the empty-contract sentinel per ADR\n * 208).\n */\nexport interface ComputeExtensionSpaceApplyPathInputs {\n readonly projectMigrationsDir: string;\n readonly spaceId: string;\n readonly currentMarkerHash: string | null;\n readonly currentMarkerInvariants: readonly string[];\n}\n\n/**\n * Compute the apply path for an extension contract space — the shortest\n * sequence of on-disk migration packages that walks the live marker\n * forward to the on-disk head ref hash, covering every required\n * invariant.\n *\n * Reads only on-disk artefacts (`migrations/<spaceId>/refs/head.json`\n * and the per-space migration packages). **Does not import any\n * extension descriptor module** — `db init` / `db update` must remain\n * runnable without the descriptor source on disk.\n *\n * Behaviour:\n * - Returns `{ kind: 'ok', pathOps: [], … }` when the marker is already\n * at the recorded head and no required invariants are missing.\n * - Returns `{ kind: 'unreachable' }` when the marker hash is not\n * structurally connected to the recorded head in the graph.\n * - Returns `{ kind: 'unsatisfiable', missing, … }` when the marker is\n * reachable but no path covers the required invariants.\n * - Returns `{ kind: 'contractSpaceHeadRefMissing' }` when the per-space\n * `refs/head.json` is absent — the precheck verifier should already\n * have rejected this case, but the helper is defensive so callers can\n * surface a coherent error rather than throw.\n */\nexport async function computeExtensionSpaceApplyPath(\n inputs: ComputeExtensionSpaceApplyPathInputs,\n): Promise<ExtensionSpaceApplyPathOutcome> {\n const { projectMigrationsDir, spaceId, currentMarkerHash, currentMarkerInvariants } = inputs;\n\n const contractSpaceHeadRef = await readContractSpaceHeadRef(projectMigrationsDir, spaceId);\n if (contractSpaceHeadRef === null) {\n return { kind: 'contractSpaceHeadRefMissing' };\n }\n\n const spaceDir = spaceMigrationDirectory(projectMigrationsDir, spaceId);\n const { packages } = await readMigrationsDir(spaceDir);\n const graph = reconstructGraph(packages);\n\n // Live-marker layer encodes \"no prior state\" as EMPTY_CONTRACT_HASH;\n // mirror the `migrate` flow so a fresh-marker initial walk\n // hits the baseline migration whose `from` is EMPTY_CONTRACT_HASH.\n const fromHash = currentMarkerHash ?? EMPTY_CONTRACT_HASH;\n const required = new Set(\n contractSpaceHeadRef.invariants.filter((id) => !currentMarkerInvariants.includes(id)),\n );\n\n const outcome = findPathWithDecision(graph, fromHash, contractSpaceHeadRef.hash, { required });\n\n if (outcome.kind === 'unreachable') {\n return { kind: 'unreachable', contractSpaceHeadRef };\n }\n if (outcome.kind === 'unsatisfiable') {\n return {\n kind: 'unsatisfiable',\n contractSpaceHeadRef,\n missing: outcome.missing,\n structuralPath: outcome.structuralPath.map(({ dirName, to }) => ({ dirName, to })),\n };\n }\n\n const packagesByHash = new Map(packages.map((pkg) => [pkg.metadata.migrationHash, pkg]));\n\n const pathOps: MigrationOps[number][] = [];\n const walkedMigrationDirs: string[] = [];\n const providedInvariantsSet = new Set<string>();\n for (const edge of outcome.decision.selectedPath) {\n const pkg = packagesByHash.get(edge.migrationHash);\n if (!pkg) {\n // Path edges always come from the same `packages` array, so this\n // is only reachable when the graph is internally inconsistent —\n // surface it loudly rather than silently truncating the path.\n throw new Error(\n `Migration package missing for edge ${edge.migrationHash} in space \"${spaceId}\"`,\n );\n }\n walkedMigrationDirs.push(pkg.dirName);\n for (const op of pkg.ops) pathOps.push(op);\n for (const invariant of pkg.metadata.providedInvariants) providedInvariantsSet.add(invariant);\n }\n\n return {\n kind: 'ok',\n contractSpaceHeadRef,\n providedInvariants: [...providedInvariantsSet].sort(),\n pathOps,\n walkedMigrationDirs,\n };\n}\n","import type { Contract } from '@prisma-next/contract/types';\nimport type {\n ContractSpace,\n ContractSpaceHeadRef,\n MigrationPackage,\n MigrationPlanOperation,\n} from '@prisma-next/framework-components/control';\nimport type { MigrationMetadata } from './metadata';\n\n/**\n * Materialise a typed {@link ContractSpace} from the JSON artefacts a\n * contract-space extension package emits to disk.\n *\n * Extension descriptors wire `contract.json`, per-migration\n * `migration.json` / `ops.json`, and `refs/head.json` to the framework's\n * typed surfaces. TypeScript widens JSON imports to a structural record\n * that does not preserve readonly modifiers or branded scalars (e.g.\n * `StorageHashBase<'sha256:...'>`), so authoring the descriptor inline\n * forces every wiring site to cast through `unknown`. This helper\n * encapsulates the single narrowing point: descriptor sources stay\n * cast-free, and the (necessary) coercion is colocated with the\n * documentation explaining why it is safe.\n *\n * Safety: the JSON files passed here are produced by the framework's own\n * emit pipeline (`prisma-next contract emit` and `MigrationCLI.run`)\n * and re-validated downstream by the runner / verifier. The descriptor\n * is a pass-through wiring layer — no descriptor consumer treats the\n * narrowed types as a stronger guarantee than \"these came from the\n * canonical emit pipeline\".\n *\n * The helper does not introspect or schema-validate the inputs; runtime\n * validation is the responsibility of `family.deserializeContract`\n * (codec-aware, invoked at control-stack construction) and the\n * per-migration `readMigrationPackage` reader used when loading\n * from disk. JSON-imported packages flow through the descriptor without\n * a disk read, so the equivalent runtime guarantee comes from the emit\n * pipeline that produced the JSON in the first place.\n */\nexport function contractSpaceFromJson<TContract extends Contract = Contract>(inputs: {\n readonly contractJson: unknown;\n readonly migrations: ReadonlyArray<{\n readonly dirName: string;\n readonly metadata: unknown;\n readonly ops: unknown;\n }>;\n readonly headRef: ContractSpaceHeadRef;\n}): ContractSpace<TContract> {\n // The narrowing happens once, here. Casting via `unknown` rather than a\n // direct cast preserves TS's structural soundness checks for the\n // inputs (they must be assignable to `unknown`, which is trivial); the\n // resulting type is the family-specific Contract / MigrationPackage\n // surface descriptors publish.\n const migrations: readonly MigrationPackage[] = inputs.migrations.map((m) => ({\n dirName: m.dirName,\n metadata: m.metadata as MigrationMetadata,\n ops: m.ops as readonly MigrationPlanOperation[],\n }));\n return {\n contractJson: inputs.contractJson as TContract,\n migrations,\n headRef: inputs.headRef,\n };\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { canonicalizeJson } from '@prisma-next/framework-components/utils';\nimport { join } from 'pathe';\nimport type { ContractSpaceHeadRef } from './read-contract-space-head-ref';\nimport { assertValidSpaceId, spaceRefsDirectory } from './space-layout';\n\n/**\n * Inputs for {@link emitContractSpaceArtefacts}.\n *\n * - `contract` is the canonical contract value the framework just emitted\n * for the space; it is serialised through {@link canonicalizeJson}, so\n * it must be a JSON-compatible value (objects / arrays / primitives).\n * Typed as `unknown` rather than the SQL-family `Contract<SqlStorage>`\n * to keep `migration-tools` framework-neutral; SQL-family callers pass\n * their typed value through unchanged.\n *\n * - `contractDts` is the pre-rendered `.d.ts` text. Rendering happens in\n * the SQL family (which owns the codec / typemap input the renderer\n * needs), so this helper accepts the text verbatim and writes it out\n * without further transformation.\n *\n * - `headRef` is the head reference for the space.\n * `invariants` are sorted alphabetically before serialisation so two\n * callers passing the same set in different orders produce\n * byte-identical `refs/head.json`.\n */\nexport interface ContractSpaceArtefactInputs {\n readonly contract: unknown;\n readonly contractDts: string;\n readonly headRef: ContractSpaceHeadRef;\n}\n\n/**\n * Emit the per-space artefacts (`contract.json`, `contract.d.ts`,\n * `refs/head.json`) under `<projectMigrationsDir>/<spaceId>/`.\n *\n * Always-overwrite: the framework owns these files; running `migrate`\n * twice with the same inputs is a no-op observably (idempotent), but the\n * helper does not check pre-existing contents — re-emit always wins.\n *\n * Path layout matches the convention in\n * [`spaceMigrationDirectory`](./space-layout.ts). The space id is\n * validated against `[a-z][a-z0-9_-]{0,63}` via\n * {@link assertValidSpaceId} for filesystem-safety reasons; the helper\n * accepts every space uniformly (including the app space, default\n * `'app'`).\n *\n * The migrations directory and space subdirectory are created if they\n * do not yet exist (`mkdir { recursive: true }`).\n */\nexport async function emitContractSpaceArtefacts(\n projectMigrationsDir: string,\n spaceId: string,\n inputs: ContractSpaceArtefactInputs,\n): Promise<void> {\n assertValidSpaceId(spaceId);\n\n const dir = join(projectMigrationsDir, spaceId);\n const refsDir = spaceRefsDirectory(dir);\n await mkdir(refsDir, { recursive: true });\n\n await writeFile(join(dir, 'contract.json'), `${canonicalizeJson(inputs.contract)}\\n`);\n await writeFile(join(dir, 'contract.d.ts'), inputs.contractDts);\n\n const sortedInvariants = [...inputs.headRef.invariants].sort();\n const headJson = canonicalizeJson({\n hash: inputs.headRef.hash,\n invariants: sortedInvariants,\n });\n await writeFile(join(refsDir, 'head.json'), `${headJson}\\n`);\n}\n","import { readContractSpaceHeadRef } from './read-contract-space-head-ref';\nimport { APP_SPACE_ID } from './space-layout';\nimport {\n type ContractSpaceHeadRecord,\n listContractSpaceDirectories,\n} from './verify-contract-spaces';\n\n/**\n * Disk-side inputs to {@link import('./verify-contract-spaces').verifyContractSpaces}\n * — gathered without touching the live database. The caller composes\n * this with the marker rows it reads from the runtime to invoke the\n * verifier.\n */\nexport interface DiskContractSpaceState {\n /** Contract-space directory names observed under `<projectMigrationsDir>/`. */\n readonly spaceDirsOnDisk: readonly string[];\n /** Head-ref `(hash, invariants)` per extension space. */\n readonly headRefsBySpace: ReadonlyMap<string, ContractSpaceHeadRecord>;\n}\n\n/**\n * Read the on-disk state the per-space verifier needs:\n *\n * - The list of contract-space directories under\n * `<projectMigrationsDir>/` (via\n * {@link import('./verify-contract-spaces').listContractSpaceDirectories}).\n * - The on-disk head ref `(hash, invariants)` for each declared extension space\n * (via {@link readContractSpaceHeadRef}; missing on-disk artefacts are simply\n * omitted — the verifier reports them as `declaredButUnmigrated`).\n *\n * Synchronous in spirit but async due to filesystem reads. Reads only\n * the user's repo. **Does not import any extension descriptor module.**\n *\n * Composition convention: pure target-agnostic primitive in\n * `1-framework`; the SQL family (and any future target family) wires\n * it into its `dbInit` / `verify` flows alongside its own marker-row\n * read before invoking `verifyContractSpaces`.\n */\nexport async function gatherDiskContractSpaceState(args: {\n readonly projectMigrationsDir: string;\n /**\n * Set of space ids the project declares: `'app'` plus each entry in\n * `extensionPacks` whose descriptor exposes a `contractSpace`. The\n * helper reads on-disk head data only for the extension members.\n */\n readonly loadedSpaceIds: ReadonlySet<string>;\n}): Promise<DiskContractSpaceState> {\n const { projectMigrationsDir, loadedSpaceIds } = args;\n\n const spaceDirsOnDisk = await listContractSpaceDirectories(projectMigrationsDir);\n\n const headRefsBySpace = new Map<string, ContractSpaceHeadRecord>();\n for (const spaceId of loadedSpaceIds) {\n if (spaceId === APP_SPACE_ID) continue;\n const head = await readContractSpaceHeadRef(projectMigrationsDir, spaceId);\n if (head !== null) {\n headRefsBySpace.set(spaceId, head);\n }\n }\n\n return { spaceDirsOnDisk, headRefsBySpace };\n}\n","import { errorDuplicateSpaceId } from './errors';\n\n/**\n * Per-space input for {@link planAllSpaces}. One entry per loaded\n * contract space (the application's `'app'` plus each extension that\n * exposes a `contractSpace`).\n *\n * - `priorContract` is `null` for a space that has never been emitted\n * (no `migrations/<space-id>/contract.json` on disk yet); otherwise it\n * is the canonical contract value emitted for that space.\n * - `newContract` is the canonical contract value the planner is about\n * to emit for that space — for app-space, the just-emitted root\n * `contract.json`; for an extension space, the descriptor's\n * `contractSpace.contractJson`.\n */\nexport interface SpacePlanInput<TContract> {\n readonly spaceId: string;\n readonly priorContract: TContract | null;\n readonly newContract: TContract;\n}\n\nexport interface SpacePlanOutput<TPackage> {\n readonly spaceId: string;\n readonly migrationPackages: readonly TPackage[];\n}\n\n/**\n * Iterate the per-space planner across a set of loaded contract spaces\n * and return a deterministic shape regardless of declaration order.\n *\n * Behaviour:\n *\n * - The output is sorted alphabetically by `spaceId`. Two callers\n * passing the same set of inputs in different orders observe\n * byte-identical outputs.\n * - The per-space planner (`planSpace`) is called exactly once per\n * input, in alphabetical-by-spaceId order. Its return value is\n * attached to the corresponding output entry verbatim.\n * - Duplicate `spaceId`s in the input array throw\n * `MIGRATION.DUPLICATE_SPACE_ID` before any `planSpace` call runs,\n * keeping the planner pure when the input is malformed.\n *\n * The signature is generic over `TContract` and `TPackage` because the\n * shape is framework-neutral (SQL family today, Mongo family\n * eventually). Callers wire in whatever contract value and migration\n * package shape their family already speaks.\n *\n * Synchronous: the underlying per-space planner (target's\n * `MigrationPlanner.plan(...)`) is synchronous; callers that need to\n * resolve async I/O (e.g. reading on-disk `contract.json` from disk)\n * resolve it before calling `planAllSpaces` and pass the materialised\n * inputs through.\n */\nexport function planAllSpaces<TContract, TPackage>(\n inputs: readonly SpacePlanInput<TContract>[],\n planSpace: (input: SpacePlanInput<TContract>) => readonly TPackage[],\n): readonly SpacePlanOutput<TPackage>[] {\n const seen = new Set<string>();\n for (const input of inputs) {\n if (seen.has(input.spaceId)) {\n throw errorDuplicateSpaceId(input.spaceId);\n }\n seen.add(input.spaceId);\n }\n\n const sorted = [...inputs].sort((a, b) => {\n if (a.spaceId < b.spaceId) return -1;\n if (a.spaceId > b.spaceId) return 1;\n return 0;\n });\n\n return sorted.map((input) => ({\n spaceId: input.spaceId,\n migrationPackages: planSpace(input),\n }));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"spaces.mjs","names":[],"sources":["../../src/assert-descriptor-self-consistency.ts","../../src/compute-extension-space-apply-path.ts","../../src/contract-space-from-json.ts","../../src/emit-contract-space-artefacts.ts","../../src/gather-disk-contract-space-state.ts","../../src/plan-all-spaces.ts"],"sourcesContent":["import type { PreserveEmptyPredicate, StorageSort } from '@prisma-next/contract/hashing';\nimport { computeStorageHash } from '@prisma-next/contract/hashing';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { errorDescriptorHeadHashMismatch } from './errors';\n\n/**\n * Inputs the helper needs to recompute the descriptor's storage hash and\n * compare it to the published `headRef.hash`. Kept structural so the SQL\n * family (and any future target family) can compose the check without\n * coupling to its own descriptor types.\n */\nexport interface DescriptorSelfConsistencyInputs {\n readonly extensionId: string;\n readonly target: string;\n readonly targetFamily: string;\n /**\n * Family-specific storage object. Typed as `unknown` so callers can\n * pass their own narrow storage shape (e.g. `SqlStorage`) without an\n * inline cast — the helper canonicalises through `JSON.stringify`\n * inside {@link computeStorageHash} and only requires a plain\n * record-shaped value at runtime.\n */\n readonly storage: unknown;\n readonly headRefHash: string;\n readonly shouldPreserveEmpty?: PreserveEmptyPredicate;\n readonly sortStorage?: StorageSort;\n}\n\n/**\n * Assert that an extension descriptor is self-consistent: the\n * `headRef.hash` it publishes must match the canonical hash recomputed\n * from its `contractSpace.contractJson`.\n *\n * Recomputes via {@link computeStorageHash} — the same canonical-JSON\n * pipeline the descriptor's own emit pipeline produced the hash with —\n * over `(target, targetFamily, storage)`. Mismatch indicates the\n * extension author bumped `contractJson` without rerunning emit, leaving\n * the descriptor's `headRef.hash` stale; the consumer-side helpers\n * (drift detection, on-disk artefact emission, runner marker writes) all\n * trust `headRef.hash` as the canonical identity, so a stale value would\n * silently corrupt every downstream boundary.\n *\n * Synchronous, pure, no I/O. Throws\n * `MIGRATION.DESCRIPTOR_HEAD_HASH_MISMATCH` on failure with both the\n * recomputed and published hashes in `details` so callers can surface a\n * clear remediation hint without re-deriving them.\n */\nexport function assertDescriptorSelfConsistency(inputs: DescriptorSelfConsistencyInputs): void {\n // The published `storage.storageHash` is the *output* of the production\n // emit pipeline's `computeStorageHash` call, computed over a storage\n // object that did not yet carry `storageHash`. Recomputing against the\n // published storage as-is would feed the result back into its own input\n // and produce a different digest. Strip `storageHash` before\n // recomputing so the helper sees the same canonical shape the\n // descriptor's authoring pipeline saw.\n // The helper requires only a plain record-shaped storage value at\n // runtime; a single cast here keeps the public input type\n // family-agnostic (`unknown`) while still letting us strip the\n // descriptor-published `storageHash` before re-canonicalising.\n const storageRecord = inputs.storage as Record<string, unknown>;\n const { storageHash: _stripped, ...storageWithoutHash } = storageRecord;\n const recomputed = computeStorageHash({\n target: inputs.target,\n targetFamily: inputs.targetFamily,\n storage: storageWithoutHash,\n ...ifDefined('shouldPreserveEmpty', inputs.shouldPreserveEmpty),\n ...ifDefined('sortStorage', inputs.sortStorage),\n });\n if (recomputed !== inputs.headRefHash) {\n throw errorDescriptorHeadHashMismatch({\n extensionId: inputs.extensionId,\n recomputedHash: recomputed,\n headRefHash: inputs.headRefHash,\n });\n }\n}\n","import { EMPTY_CONTRACT_HASH } from './constants';\nimport { readMigrationsDir } from './io';\nimport { findPathWithDecision, reconstructGraph } from './migration-graph';\nimport type { MigrationOps } from './package';\nimport {\n type ContractSpaceHeadRef,\n readContractSpaceHeadRef,\n} from './read-contract-space-head-ref';\nimport { spaceMigrationDirectory } from './space-layout';\n\n/**\n * Outcome of {@link computeExtensionSpaceApplyPath} — a discriminated union\n * mirroring {@link import('./migration-graph').FindPathOutcome} so callers\n * can map structural / invariant failures to their preferred CLI envelope\n * without re-running pathfinding.\n */\nexport type ExtensionSpaceApplyPathOutcome =\n | {\n readonly kind: 'ok';\n readonly contractSpaceHeadRef: ContractSpaceHeadRef;\n /**\n * Sorted, deduplicated invariant ids covered by the walked path.\n * Mirrors the on-disk `providedInvariants` summed across edges and\n * canonicalised — what the runner stamps on the marker after apply.\n */\n readonly providedInvariants: readonly string[];\n /**\n * Path operations in apply order. Empty when the marker is already\n * at the recorded head (no-op).\n */\n readonly pathOps: MigrationOps;\n /**\n * Migration directory names walked, in order. Mirrors `pathOps`'s\n * structure but at the package granularity — useful for surfacing\n * \"applied N migration(s)\" messages.\n */\n readonly walkedMigrationDirs: readonly string[];\n }\n | { readonly kind: 'unreachable'; readonly contractSpaceHeadRef: ContractSpaceHeadRef }\n | {\n readonly kind: 'unsatisfiable';\n readonly contractSpaceHeadRef: ContractSpaceHeadRef;\n readonly missing: readonly string[];\n readonly structuralPath: readonly { readonly dirName: string; readonly to: string }[];\n }\n | { readonly kind: 'contractSpaceHeadRefMissing' };\n\n/**\n * Inputs to {@link computeExtensionSpaceApplyPath}. The helper is\n * deliberately framework-neutral and consumes only on-disk state:\n *\n * - `projectMigrationsDir` is the project's top-level `migrations/` dir.\n * - `spaceId` selects the per-space subdirectory under it.\n * - `currentMarkerHash` / `currentMarkerInvariants` come from the live\n * marker row keyed by `space = <spaceId>`. `null` hash = no marker yet\n * (the pathfinder treats this as the empty-contract sentinel per ADR\n * 208).\n */\nexport interface ComputeExtensionSpaceApplyPathInputs {\n readonly projectMigrationsDir: string;\n readonly spaceId: string;\n readonly currentMarkerHash: string | null;\n readonly currentMarkerInvariants: readonly string[];\n}\n\n/**\n * Compute the apply path for an extension contract space — the shortest\n * sequence of on-disk migration packages that walks the live marker\n * forward to the on-disk head ref hash, covering every required\n * invariant.\n *\n * Reads only on-disk artefacts (`migrations/<spaceId>/refs/head.json`\n * and the per-space migration packages). **Does not import any\n * extension descriptor module** — `db init` / `db update` must remain\n * runnable without the descriptor source on disk.\n *\n * Behaviour:\n * - Returns `{ kind: 'ok', pathOps: [], … }` when the marker is already\n * at the recorded head and no required invariants are missing.\n * - Returns `{ kind: 'unreachable' }` when the marker hash is not\n * structurally connected to the recorded head in the graph.\n * - Returns `{ kind: 'unsatisfiable', missing, … }` when the marker is\n * reachable but no path covers the required invariants.\n * - Returns `{ kind: 'contractSpaceHeadRefMissing' }` when the per-space\n * `refs/head.json` is absent — the precheck verifier should already\n * have rejected this case, but the helper is defensive so callers can\n * surface a coherent error rather than throw.\n */\nexport async function computeExtensionSpaceApplyPath(\n inputs: ComputeExtensionSpaceApplyPathInputs,\n): Promise<ExtensionSpaceApplyPathOutcome> {\n const { projectMigrationsDir, spaceId, currentMarkerHash, currentMarkerInvariants } = inputs;\n\n const contractSpaceHeadRef = await readContractSpaceHeadRef(projectMigrationsDir, spaceId);\n if (contractSpaceHeadRef === null) {\n return { kind: 'contractSpaceHeadRefMissing' };\n }\n\n const spaceDir = spaceMigrationDirectory(projectMigrationsDir, spaceId);\n const { packages } = await readMigrationsDir(spaceDir);\n const graph = reconstructGraph(packages);\n\n // Live-marker layer encodes \"no prior state\" as EMPTY_CONTRACT_HASH;\n // mirror the `migrate` flow so a fresh-marker initial walk\n // hits the baseline migration whose `from` is EMPTY_CONTRACT_HASH.\n const fromHash = currentMarkerHash ?? EMPTY_CONTRACT_HASH;\n const required = new Set(\n contractSpaceHeadRef.invariants.filter((id) => !currentMarkerInvariants.includes(id)),\n );\n\n const outcome = findPathWithDecision(graph, fromHash, contractSpaceHeadRef.hash, { required });\n\n if (outcome.kind === 'unreachable') {\n return { kind: 'unreachable', contractSpaceHeadRef };\n }\n if (outcome.kind === 'unsatisfiable') {\n return {\n kind: 'unsatisfiable',\n contractSpaceHeadRef,\n missing: outcome.missing,\n structuralPath: outcome.structuralPath.map(({ dirName, to }) => ({ dirName, to })),\n };\n }\n\n const packagesByHash = new Map(packages.map((pkg) => [pkg.metadata.migrationHash, pkg]));\n\n const pathOps: MigrationOps[number][] = [];\n const walkedMigrationDirs: string[] = [];\n const providedInvariantsSet = new Set<string>();\n for (const edge of outcome.decision.selectedPath) {\n const pkg = packagesByHash.get(edge.migrationHash);\n if (!pkg) {\n // Path edges always come from the same `packages` array, so this\n // is only reachable when the graph is internally inconsistent —\n // surface it loudly rather than silently truncating the path.\n throw new Error(\n `Migration package missing for edge ${edge.migrationHash} in space \"${spaceId}\"`,\n );\n }\n walkedMigrationDirs.push(pkg.dirName);\n for (const op of pkg.ops) pathOps.push(op);\n for (const invariant of pkg.metadata.providedInvariants) providedInvariantsSet.add(invariant);\n }\n\n return {\n kind: 'ok',\n contractSpaceHeadRef,\n providedInvariants: [...providedInvariantsSet].sort(),\n pathOps,\n walkedMigrationDirs,\n };\n}\n","import type { Contract } from '@prisma-next/contract/types';\nimport type {\n ContractSpace,\n ContractSpaceHeadRef,\n MigrationPackage,\n MigrationPlanOperation,\n} from '@prisma-next/framework-components/control';\nimport type { MigrationMetadata } from './metadata';\n\n/**\n * Materialise a typed {@link ContractSpace} from the JSON artefacts a\n * contract-space extension package emits to disk.\n *\n * Extension descriptors wire `contract.json`, per-migration\n * `migration.json` / `ops.json`, and `refs/head.json` to the framework's\n * typed surfaces. TypeScript widens JSON imports to a structural record\n * that does not preserve readonly modifiers or branded scalars (e.g.\n * `StorageHashBase<'sha256:...'>`), so authoring the descriptor inline\n * forces every wiring site to cast through `unknown`. This helper\n * encapsulates the single narrowing point: descriptor sources stay\n * cast-free, and the (necessary) coercion is colocated with the\n * documentation explaining why it is safe.\n *\n * Safety: the JSON files passed here are produced by the framework's own\n * emit pipeline (`prisma-next contract emit` and `MigrationCLI.run`)\n * and re-validated downstream by the runner / verifier. The descriptor\n * is a pass-through wiring layer — no descriptor consumer treats the\n * narrowed types as a stronger guarantee than \"these came from the\n * canonical emit pipeline\".\n *\n * The helper does not introspect or schema-validate the inputs; runtime\n * validation is the responsibility of `family.deserializeContract`\n * (codec-aware, invoked at control-stack construction) and the\n * per-migration `readMigrationPackage` reader used when loading\n * from disk. JSON-imported packages flow through the descriptor without\n * a disk read, so the equivalent runtime guarantee comes from the emit\n * pipeline that produced the JSON in the first place.\n */\nexport function contractSpaceFromJson<TContract extends Contract = Contract>(inputs: {\n readonly contractJson: unknown;\n readonly migrations: ReadonlyArray<{\n readonly dirName: string;\n readonly metadata: unknown;\n readonly ops: unknown;\n }>;\n readonly headRef: ContractSpaceHeadRef;\n}): ContractSpace<TContract> {\n // The narrowing happens once, here. Casting via `unknown` rather than a\n // direct cast preserves TS's structural soundness checks for the\n // inputs (they must be assignable to `unknown`, which is trivial); the\n // resulting type is the family-specific Contract / MigrationPackage\n // surface descriptors publish.\n const migrations: readonly MigrationPackage[] = inputs.migrations.map((m) => ({\n dirName: m.dirName,\n metadata: m.metadata as MigrationMetadata,\n ops: m.ops as readonly MigrationPlanOperation[],\n }));\n return {\n contractJson: inputs.contractJson as TContract,\n migrations,\n headRef: inputs.headRef,\n };\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { canonicalizeJson } from '@prisma-next/framework-components/utils';\nimport { join } from 'pathe';\nimport type { ContractSpaceHeadRef } from './read-contract-space-head-ref';\nimport { assertValidSpaceId, spaceRefsDirectory } from './space-layout';\n\n/**\n * Inputs for {@link emitContractSpaceArtefacts}.\n *\n * - `contract` is the canonical contract value the framework just emitted\n * for the space; it is serialised through {@link canonicalizeJson}, so\n * it must be a JSON-compatible value (objects / arrays / primitives).\n * Typed as `unknown` rather than the SQL-family `Contract<SqlStorage>`\n * to keep `migration-tools` framework-neutral; SQL-family callers pass\n * their typed value through unchanged.\n *\n * - `contractDts` is the pre-rendered `.d.ts` text. Rendering happens in\n * the SQL family (which owns the codec / typemap input the renderer\n * needs), so this helper accepts the text verbatim and writes it out\n * without further transformation.\n *\n * - `headRef` is the head reference for the space.\n * `invariants` are sorted alphabetically before serialisation so two\n * callers passing the same set in different orders produce\n * byte-identical `refs/head.json`.\n */\nexport interface ContractSpaceArtefactInputs {\n readonly contract: unknown;\n readonly contractDts: string;\n readonly headRef: ContractSpaceHeadRef;\n}\n\n/**\n * Emit the per-space artefacts (`contract.json`, `contract.d.ts`,\n * `refs/head.json`) under `<projectMigrationsDir>/<spaceId>/`.\n *\n * Always-overwrite: the framework owns these files; running `migrate`\n * twice with the same inputs is a no-op observably (idempotent), but the\n * helper does not check pre-existing contents — re-emit always wins.\n *\n * Path layout matches the convention in\n * [`spaceMigrationDirectory`](./space-layout.ts). The space id is\n * validated against `[a-z][a-z0-9_-]{0,63}` via\n * {@link assertValidSpaceId} for filesystem-safety reasons; the helper\n * accepts every space uniformly (including the app space, default\n * `'app'`).\n *\n * The migrations directory and space subdirectory are created if they\n * do not yet exist (`mkdir { recursive: true }`).\n */\nexport async function emitContractSpaceArtefacts(\n projectMigrationsDir: string,\n spaceId: string,\n inputs: ContractSpaceArtefactInputs,\n): Promise<void> {\n assertValidSpaceId(spaceId);\n\n const dir = join(projectMigrationsDir, spaceId);\n const refsDir = spaceRefsDirectory(dir);\n await mkdir(refsDir, { recursive: true });\n\n await writeFile(join(dir, 'contract.json'), `${canonicalizeJson(inputs.contract)}\\n`);\n await writeFile(join(dir, 'contract.d.ts'), inputs.contractDts);\n\n const sortedInvariants = [...inputs.headRef.invariants].sort();\n const headJson = canonicalizeJson({\n hash: inputs.headRef.hash,\n invariants: sortedInvariants,\n });\n await writeFile(join(refsDir, 'head.json'), `${headJson}\\n`);\n}\n","import { readContractSpaceHeadRef } from './read-contract-space-head-ref';\nimport { APP_SPACE_ID } from './space-layout';\nimport {\n type ContractSpaceHeadRecord,\n listContractSpaceDirectories,\n} from './verify-contract-spaces';\n\n/**\n * Disk-side inputs to {@link import('./verify-contract-spaces').verifyContractSpaces}\n * — gathered without touching the live database. The caller composes\n * this with the marker rows it reads from the runtime to invoke the\n * verifier.\n */\nexport interface DiskContractSpaceState {\n /** Contract-space directory names observed under `<projectMigrationsDir>/`. */\n readonly spaceDirsOnDisk: readonly string[];\n /** Head-ref `(hash, invariants)` per extension space. */\n readonly headRefsBySpace: ReadonlyMap<string, ContractSpaceHeadRecord>;\n}\n\n/**\n * Read the on-disk state the per-space verifier needs:\n *\n * - The list of contract-space directories under\n * `<projectMigrationsDir>/` (via\n * {@link import('./verify-contract-spaces').listContractSpaceDirectories}).\n * - The on-disk head ref `(hash, invariants)` for each declared extension space\n * (via {@link readContractSpaceHeadRef}; missing on-disk artefacts are simply\n * omitted — the verifier reports them as `declaredButUnmigrated`).\n *\n * Synchronous in spirit but async due to filesystem reads. Reads only\n * the user's repo. **Does not import any extension descriptor module.**\n *\n * Composition convention: pure target-agnostic primitive in\n * `1-framework`; the SQL family (and any future target family) wires\n * it into its `dbInit` / `verify` flows alongside its own marker-row\n * read before invoking `verifyContractSpaces`.\n */\nexport async function gatherDiskContractSpaceState(args: {\n readonly projectMigrationsDir: string;\n /**\n * Set of space ids the project declares: `'app'` plus each entry in\n * `extensionPacks` whose descriptor exposes a `contractSpace`. The\n * helper reads on-disk head data only for the extension members.\n */\n readonly loadedSpaceIds: ReadonlySet<string>;\n}): Promise<DiskContractSpaceState> {\n const { projectMigrationsDir, loadedSpaceIds } = args;\n\n const spaceDirsOnDisk = await listContractSpaceDirectories(projectMigrationsDir);\n\n const headRefsBySpace = new Map<string, ContractSpaceHeadRecord>();\n for (const spaceId of loadedSpaceIds) {\n if (spaceId === APP_SPACE_ID) continue;\n const head = await readContractSpaceHeadRef(projectMigrationsDir, spaceId);\n if (head !== null) {\n headRefsBySpace.set(spaceId, head);\n }\n }\n\n return { spaceDirsOnDisk, headRefsBySpace };\n}\n","import { errorDuplicateSpaceId } from './errors';\n\n/**\n * Per-space input for {@link planAllSpaces}. One entry per loaded\n * contract space (the application's `'app'` plus each extension that\n * exposes a `contractSpace`).\n *\n * - `priorContract` is `null` for a space that has never been emitted\n * (no `migrations/<space-id>/contract.json` on disk yet); otherwise it\n * is the canonical contract value emitted for that space.\n * - `newContract` is the canonical contract value the planner is about\n * to emit for that space — for app-space, the just-emitted root\n * `contract.json`; for an extension space, the descriptor's\n * `contractSpace.contractJson`.\n */\nexport interface SpacePlanInput<TContract> {\n readonly spaceId: string;\n readonly priorContract: TContract | null;\n readonly newContract: TContract;\n}\n\nexport interface SpacePlanOutput<TPackage> {\n readonly spaceId: string;\n readonly migrationPackages: readonly TPackage[];\n}\n\n/**\n * Iterate the per-space planner across a set of loaded contract spaces\n * and return a deterministic shape regardless of declaration order.\n *\n * Behaviour:\n *\n * - The output is sorted alphabetically by `spaceId`. Two callers\n * passing the same set of inputs in different orders observe\n * byte-identical outputs.\n * - The per-space planner (`planSpace`) is called exactly once per\n * input, in alphabetical-by-spaceId order. Its return value is\n * attached to the corresponding output entry verbatim.\n * - Duplicate `spaceId`s in the input array throw\n * `MIGRATION.DUPLICATE_SPACE_ID` before any `planSpace` call runs,\n * keeping the planner pure when the input is malformed.\n *\n * The signature is generic over `TContract` and `TPackage` because the\n * shape is framework-neutral (SQL family today, Mongo family\n * eventually). Callers wire in whatever contract value and migration\n * package shape their family already speaks.\n *\n * Synchronous: the underlying per-space planner (target's\n * `MigrationPlanner.plan(...)`) is synchronous; callers that need to\n * resolve async I/O (e.g. reading on-disk `contract.json` from disk)\n * resolve it before calling `planAllSpaces` and pass the materialised\n * inputs through.\n */\nexport function planAllSpaces<TContract, TPackage>(\n inputs: readonly SpacePlanInput<TContract>[],\n planSpace: (input: SpacePlanInput<TContract>) => readonly TPackage[],\n): readonly SpacePlanOutput<TPackage>[] {\n const seen = new Set<string>();\n for (const input of inputs) {\n if (seen.has(input.spaceId)) {\n throw errorDuplicateSpaceId(input.spaceId);\n }\n seen.add(input.spaceId);\n }\n\n const sorted = [...inputs].sort((a, b) => {\n if (a.spaceId < b.spaceId) return -1;\n if (a.spaceId > b.spaceId) return 1;\n return 0;\n });\n\n return sorted.map((input) => ({\n spaceId: input.spaceId,\n migrationPackages: planSpace(input),\n }));\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/migration-tools",
|
|
3
|
-
"version": "0.12.0-dev.
|
|
3
|
+
"version": "0.12.0-dev.50",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"description": "On-disk migration persistence, hash verification, and chain reconstruction for Prisma Next",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@prisma-next/contract": "0.12.0-dev.
|
|
10
|
-
"@prisma-next/framework-components": "0.12.0-dev.
|
|
11
|
-
"@prisma-next/utils": "0.12.0-dev.
|
|
9
|
+
"@prisma-next/contract": "0.12.0-dev.50",
|
|
10
|
+
"@prisma-next/framework-components": "0.12.0-dev.50",
|
|
11
|
+
"@prisma-next/utils": "0.12.0-dev.50",
|
|
12
12
|
"arktype": "^2.2.0",
|
|
13
13
|
"pathe": "^2.0.3",
|
|
14
14
|
"prettier": "^3.8.3"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@prisma-next/test-utils": "0.12.0-dev.
|
|
18
|
-
"@prisma-next/tsconfig": "0.12.0-dev.
|
|
19
|
-
"@prisma-next/tsdown": "0.12.0-dev.
|
|
17
|
+
"@prisma-next/test-utils": "0.12.0-dev.50",
|
|
18
|
+
"@prisma-next/tsconfig": "0.12.0-dev.50",
|
|
19
|
+
"@prisma-next/tsdown": "0.12.0-dev.50",
|
|
20
20
|
"tsdown": "0.22.0",
|
|
21
21
|
"typescript": "5.9.3",
|
|
22
22
|
"vitest": "4.1.6"
|
|
@@ -78,6 +78,10 @@
|
|
|
78
78
|
"types": "./dist/exports/constants.d.mts",
|
|
79
79
|
"import": "./dist/exports/constants.mjs"
|
|
80
80
|
},
|
|
81
|
+
"./ledger-origin": {
|
|
82
|
+
"types": "./dist/exports/ledger-origin.d.mts",
|
|
83
|
+
"import": "./dist/exports/ledger-origin.mjs"
|
|
84
|
+
},
|
|
81
85
|
"./migration-ts": {
|
|
82
86
|
"types": "./dist/exports/migration-ts.d.mts",
|
|
83
87
|
"import": "./dist/exports/migration-ts.mjs"
|
|
@@ -118,11 +118,12 @@ export interface PerSpacePlan {
|
|
|
118
118
|
readonly displayOps: readonly MigrationPlanOperation[];
|
|
119
119
|
readonly destinationContract: Contract;
|
|
120
120
|
readonly strategy: 'graph-walk' | 'synth';
|
|
121
|
+
readonly warnings?: readonly MigrationPlannerConflict[];
|
|
121
122
|
/**
|
|
122
|
-
* Per-edge breakdown of the chain.
|
|
123
|
-
*
|
|
123
|
+
* Per-edge breakdown of the chain. Graph-walk plans carry one entry per
|
|
124
|
+
* authored edge; synth and at-head plans carry a single synthesised edge.
|
|
124
125
|
*/
|
|
125
|
-
readonly migrationEdges
|
|
126
|
+
readonly migrationEdges: readonly AggregateMigrationEdgeRef[];
|
|
126
127
|
/**
|
|
127
128
|
* Path decision data the strategy used to select the chain
|
|
128
129
|
* (alternative count, tie-break reasons, required/satisfied
|
|
@@ -130,7 +131,7 @@ export interface PerSpacePlan {
|
|
|
130
131
|
* strategy; absent for synth-produced plans.
|
|
131
132
|
*
|
|
132
133
|
* `migrate` surfaces this for the app member as
|
|
133
|
-
* `
|
|
134
|
+
* `MigrateSuccess.pathDecision` (back-compat with single-
|
|
134
135
|
* space callers).
|
|
135
136
|
*/
|
|
136
137
|
readonly pathDecision?: PathDecision;
|
package/src/aggregate/planner.ts
CHANGED
|
@@ -73,6 +73,7 @@ export async function planMigration<TFamilyId extends string, TTargetId extends
|
|
|
73
73
|
if (ignoreGraph) {
|
|
74
74
|
const synthOutcome = await synthStrategy({
|
|
75
75
|
aggregateTargetId: aggregate.targetId,
|
|
76
|
+
currentMarker,
|
|
76
77
|
member,
|
|
77
78
|
otherMembers,
|
|
78
79
|
schemaIntrospection: currentDBState.schemaIntrospection,
|
|
@@ -131,6 +132,7 @@ export async function planMigration<TFamilyId extends string, TTargetId extends
|
|
|
131
132
|
|
|
132
133
|
const synthOutcome = await synthStrategy({
|
|
133
134
|
aggregateTargetId: aggregate.targetId,
|
|
135
|
+
currentMarker,
|
|
134
136
|
member,
|
|
135
137
|
otherMembers,
|
|
136
138
|
schemaIntrospection: currentDBState.schemaIntrospection,
|
|
@@ -7,12 +7,15 @@ import type {
|
|
|
7
7
|
MigrationPlannerResult,
|
|
8
8
|
TargetMigrationsCapability,
|
|
9
9
|
} from '@prisma-next/framework-components/control';
|
|
10
|
+
import type { ContractMarkerRecordLike } from '../marker-types';
|
|
10
11
|
import type { PerSpacePlan } from '../planner-types';
|
|
11
12
|
import { projectSchemaToSpace } from '../project-schema-to-space';
|
|
13
|
+
import { buildSynthMigrationEdge } from '../synth-migration-edge';
|
|
12
14
|
import type { ContractSpaceMember } from '../types';
|
|
13
15
|
|
|
14
16
|
export interface SynthStrategyInputs<TFamilyId extends string, TTargetId extends string> {
|
|
15
17
|
readonly aggregateTargetId: string;
|
|
18
|
+
readonly currentMarker: ContractMarkerRecordLike | null;
|
|
16
19
|
readonly member: ContractSpaceMember;
|
|
17
20
|
readonly otherMembers: ReadonlyArray<ContractSpaceMember>;
|
|
18
21
|
readonly schemaIntrospection: unknown;
|
|
@@ -109,6 +112,7 @@ export async function synthStrategy<TFamilyId extends string, TTargetId extends
|
|
|
109
112
|
},
|
|
110
113
|
});
|
|
111
114
|
|
|
115
|
+
const destinationStorageHash = synthedPlan.destination.storageHash;
|
|
112
116
|
return {
|
|
113
117
|
kind: 'ok',
|
|
114
118
|
result: {
|
|
@@ -116,6 +120,16 @@ export async function synthStrategy<TFamilyId extends string, TTargetId extends
|
|
|
116
120
|
displayOps: synthedPlan.operations,
|
|
117
121
|
destinationContract: input.member.contract(),
|
|
118
122
|
strategy: 'synth',
|
|
123
|
+
...(plannerResult.warnings && plannerResult.warnings.length > 0
|
|
124
|
+
? { warnings: plannerResult.warnings }
|
|
125
|
+
: {}),
|
|
126
|
+
migrationEdges: [
|
|
127
|
+
buildSynthMigrationEdge({
|
|
128
|
+
currentMarkerStorageHash: input.currentMarker?.storageHash,
|
|
129
|
+
destinationStorageHash,
|
|
130
|
+
operationCount: synthedPlan.operations.length,
|
|
131
|
+
}),
|
|
132
|
+
],
|
|
119
133
|
},
|
|
120
134
|
};
|
|
121
135
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AggregateMigrationEdgeRef } from './planner-types';
|
|
2
|
+
|
|
3
|
+
export function buildSynthMigrationEdge(args: {
|
|
4
|
+
readonly currentMarkerStorageHash: string | null | undefined;
|
|
5
|
+
readonly destinationStorageHash: string;
|
|
6
|
+
readonly operationCount: number;
|
|
7
|
+
}): AggregateMigrationEdgeRef {
|
|
8
|
+
return {
|
|
9
|
+
dirName: '',
|
|
10
|
+
migrationHash: args.destinationStorageHash,
|
|
11
|
+
from: args.currentMarkerStorageHash ?? '',
|
|
12
|
+
to: args.destinationStorageHash,
|
|
13
|
+
operationCount: args.operationCount,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -3,21 +3,6 @@ import { computeStorageHash } from '@prisma-next/contract/hashing';
|
|
|
3
3
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
4
4
|
import { errorDescriptorHeadHashMismatch } from './errors';
|
|
5
5
|
|
|
6
|
-
function stripNamespaceKinds(storage: Record<string, unknown>): Record<string, unknown> {
|
|
7
|
-
const namespaces = storage['namespaces'] as Record<string, Record<string, unknown>> | undefined;
|
|
8
|
-
if (!namespaces) return storage;
|
|
9
|
-
const stripped: Record<string, Record<string, unknown>> = {};
|
|
10
|
-
for (const [nsId, ns] of Object.entries(namespaces)) {
|
|
11
|
-
if (ns && typeof ns === 'object') {
|
|
12
|
-
const { kind: _kind, ...rest } = ns as Record<string, unknown>;
|
|
13
|
-
stripped[nsId] = rest as Record<string, unknown>;
|
|
14
|
-
} else {
|
|
15
|
-
stripped[nsId] = ns;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return { ...storage, namespaces: stripped };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
6
|
/**
|
|
22
7
|
* Inputs the helper needs to recompute the descriptor's storage hash and
|
|
23
8
|
* compare it to the published `headRef.hash`. Kept structural so the SQL
|
|
@@ -74,18 +59,10 @@ export function assertDescriptorSelfConsistency(inputs: DescriptorSelfConsistenc
|
|
|
74
59
|
// descriptor-published `storageHash` before re-canonicalising.
|
|
75
60
|
const storageRecord = inputs.storage as Record<string, unknown>;
|
|
76
61
|
const { storageHash: _stripped, ...storageWithoutHash } = storageRecord;
|
|
77
|
-
// Target serializers (e.g. PostgresContractSerializer) inject a `kind`
|
|
78
|
-
// discriminator into each namespace entry when writing contract.json (e.g.
|
|
79
|
-
// `"postgres-unbound-schema"`). The authoring pipeline computes
|
|
80
|
-
// `storageHash` from IR class instances whose `kind` property is
|
|
81
|
-
// non-enumerable, so `kind` is absent from the hash input. Strip `kind`
|
|
82
|
-
// from namespace entries before recomputing so this check always operates
|
|
83
|
-
// on the same canonical shape the authoring pipeline saw.
|
|
84
|
-
const normalizedStorage = stripNamespaceKinds(storageWithoutHash);
|
|
85
62
|
const recomputed = computeStorageHash({
|
|
86
63
|
target: inputs.target,
|
|
87
64
|
targetFamily: inputs.targetFamily,
|
|
88
|
-
storage:
|
|
65
|
+
storage: storageWithoutHash,
|
|
89
66
|
...ifDefined('shouldPreserveEmpty', inputs.shouldPreserveEmpty),
|
|
90
67
|
...ifDefined('sortStorage', inputs.sortStorage),
|
|
91
68
|
});
|
package/src/exports/aggregate.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ledgerOriginFromStored } from '../ledger-origin';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { EMPTY_CONTRACT_HASH } from './constants';
|
|
2
|
+
|
|
3
|
+
export function ledgerOriginFromStored(originCoreHash: string | null): string | null {
|
|
4
|
+
if (originCoreHash === null || originCoreHash === '' || originCoreHash === EMPTY_CONTRACT_HASH) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
return originCoreHash;
|
|
8
|
+
}
|