@prisma-next/migration-tools 0.8.0-dev.3 → 0.8.0-dev.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +6 -6
  2. package/dist/{errors-EPL_9p9f.mjs → errors-DqMhmTPQ.mjs} +2 -14
  3. package/dist/errors-DqMhmTPQ.mjs.map +1 -0
  4. package/dist/exports/aggregate.mjs +3 -3
  5. package/dist/exports/errors.d.mts.map +1 -1
  6. package/dist/exports/errors.mjs +1 -1
  7. package/dist/exports/hash.d.mts +7 -5
  8. package/dist/exports/hash.d.mts.map +1 -1
  9. package/dist/exports/hash.mjs +1 -1
  10. package/dist/exports/invariants.mjs +1 -1
  11. package/dist/exports/io.d.mts +12 -16
  12. package/dist/exports/io.d.mts.map +1 -1
  13. package/dist/exports/io.mjs +1 -1
  14. package/dist/exports/migration-graph.mjs +1 -1
  15. package/dist/exports/migration.d.mts.map +1 -1
  16. package/dist/exports/migration.mjs +3 -41
  17. package/dist/exports/migration.mjs.map +1 -1
  18. package/dist/exports/refs.mjs +1 -1
  19. package/dist/exports/spaces.mjs +4 -4
  20. package/dist/{hash-C6bpZczT.mjs → hash-Cr4WIr4Z.mjs} +10 -8
  21. package/dist/hash-Cr4WIr4Z.mjs.map +1 -0
  22. package/dist/{invariants-qgQGlsrV.mjs → invariants-DItbPp4w.mjs} +2 -2
  23. package/dist/{invariants-qgQGlsrV.mjs.map → invariants-DItbPp4w.mjs.map} +1 -1
  24. package/dist/{io-Dw620b51.mjs → io-Zxj9kQwp.mjs} +16 -24
  25. package/dist/io-Zxj9kQwp.mjs.map +1 -0
  26. package/dist/{migration-graph-DGNnKDY5.mjs → migration-graph-NGymYL9J.mjs} +2 -2
  27. package/dist/{migration-graph-DGNnKDY5.mjs.map → migration-graph-NGymYL9J.mjs.map} +1 -1
  28. package/dist/{read-contract-space-contract-COyz4tZn.mjs → read-contract-space-contract-nZRrktnx.mjs} +3 -3
  29. package/dist/{read-contract-space-contract-COyz4tZn.mjs.map → read-contract-space-contract-nZRrktnx.mjs.map} +1 -1
  30. package/package.json +6 -6
  31. package/src/errors.ts +0 -21
  32. package/src/hash.ts +8 -12
  33. package/src/io.ts +12 -22
  34. package/src/migration-base.ts +1 -54
  35. package/dist/errors-EPL_9p9f.mjs.map +0 -1
  36. package/dist/hash-C6bpZczT.mjs.map +0 -1
  37. package/dist/io-Dw620b51.mjs.map +0 -1
package/README.md CHANGED
@@ -17,17 +17,17 @@ On-disk migration persistence, hash verification, and history reconstruction for
17
17
 
18
18
  `computeMigrationHash` in `hash.ts` uses explicit framing:
19
19
 
20
- 1. Strip identity-affecting fields (`migrationHash`, `fromContract`,
21
- `toContract`, `hints`) from the metadata envelope, then canonicalize the
22
- stripped envelope and the ops array.
20
+ 1. Strip non-identity fields (`migrationHash`, `hints`) from
21
+ the metadata envelope, then canonicalize the stripped envelope and the
22
+ ops array.
23
23
  2. SHA-256 each canonical part independently.
24
24
  3. SHA-256 the canonical JSON tuple `[hash(metadata), hash(ops)]`.
25
25
 
26
26
  This avoids delimiter-ambiguity and pins `migrationHash` to a 2-tuple over
27
27
  the on-disk storage shape. Per [ADR 199 — Storage-only migration identity],
28
- contracts are anchored separately via storage-hash bookends inside the
29
- metadata envelope, and planner hints are advisory and must not affect
30
- identity.
28
+ contracts are anchored by the storage-hash bookends (`from`, `to`) inside
29
+ the metadata envelope the full contract IRs themselves are not part of
30
+ the manifest. Planner hints are advisory and must not affect identity.
31
31
 
32
32
  [ADR 199 — Storage-only migration identity]: ../../../docs/architecture%20docs/adrs/ADR%20199%20-%20Storage-only%20migration%20identity.md
33
33
 
@@ -98,18 +98,6 @@ function errorInvalidOperationEntry(index, reason) {
98
98
  }
99
99
  });
100
100
  }
101
- function errorStaleContractBookends(args) {
102
- const { side, metaHash, contractHash } = args;
103
- return new MigrationToolsError("MIGRATION.STALE_CONTRACT_BOOKENDS", "Migration manifest contract bookends disagree with describe()", {
104
- why: `migration.json stores ${side}Contract.storage.storageHash "${contractHash}", but describe() returned meta.${side} = ${metaHash === null ? "null (baseline)" : `"${metaHash}"`}. The bookend is stale — most likely the migration's describe() was edited after the package was scaffolded by \`migration plan\`.`,
105
- fix: "Re-run `migration plan` to regenerate the package with fresh contract bookends, or restore the directory from version control.",
106
- details: {
107
- side,
108
- metaHash,
109
- contractHash
110
- }
111
- });
112
- }
113
101
  function errorInvalidSlug(slug) {
114
102
  return new MigrationToolsError("MIGRATION.INVALID_NAME", "Invalid migration name", {
115
103
  why: `The slug "${slug}" contains no valid characters after sanitization (only a-z, 0-9 are kept).`,
@@ -292,6 +280,6 @@ function errorMigrationHashMismatch(dir, storedHash, computedHash) {
292
280
  });
293
281
  }
294
282
  //#endregion
295
- export { errorProvidedInvariantsMismatch as C, errorUnknownInvariant as E, errorNoTarget as S, errorStaleContractBookends as T, errorInvalidSpaceId as _, errorDuplicateInvariantInEdge as a, errorNoInitialMigration as b, errorInvalidDestName as c, errorInvalidManifest as d, errorInvalidOperationEntry as f, errorInvalidSlug as g, errorInvalidRefValue as h, errorDirectoryExists as i, errorInvalidInvariantId as l, errorInvalidRefName as m, errorAmbiguousTarget as n, errorDuplicateMigrationHash as o, errorInvalidRefFile as p, errorDescriptorHeadHashMismatch as r, errorDuplicateSpaceId as s, MigrationToolsError as t, errorInvalidJson as u, errorMigrationHashMismatch as v, errorSameSourceAndTarget as w, errorNoInvariantPath as x, errorMissingFile as y };
283
+ export { errorProvidedInvariantsMismatch as C, errorNoTarget as S, errorUnknownInvariant as T, errorInvalidSpaceId as _, errorDuplicateInvariantInEdge as a, errorNoInitialMigration as b, errorInvalidDestName as c, errorInvalidManifest as d, errorInvalidOperationEntry as f, errorInvalidSlug as g, errorInvalidRefValue as h, errorDirectoryExists as i, errorInvalidInvariantId as l, errorInvalidRefName as m, errorAmbiguousTarget as n, errorDuplicateMigrationHash as o, errorInvalidRefFile as p, errorDescriptorHeadHashMismatch as r, errorDuplicateSpaceId as s, MigrationToolsError as t, errorInvalidJson as u, errorMigrationHashMismatch as v, errorSameSourceAndTarget as w, errorNoInvariantPath as x, errorMissingFile as y };
296
284
 
297
- //# sourceMappingURL=errors-EPL_9p9f.mjs.map
285
+ //# sourceMappingURL=errors-DqMhmTPQ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors-DqMhmTPQ.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["import { ifDefined } from '@prisma-next/utils/defined';\nimport { basename, dirname, relative } from 'pathe';\n\n/**\n * Build the canonical \"re-emit this package\" remediation hint.\n *\n * Every on-disk migration package ships its own `migration.ts` author-time\n * file. Running it regenerates `migration.json` and `ops.json` with the\n * correct hash + metadata, so it is the right primitive whenever a single\n * package's on-disk artifacts are missing, malformed, or otherwise corrupt.\n * Pointing users at `migration plan` would emit a *new* package rather than\n * heal the broken one.\n */\nfunction reemitHint(dir: string, fallback?: string): string {\n const relativeDir = relative(process.cwd(), dir);\n const reemit = `Re-emit the package by running \\`node \"${relativeDir}/migration.ts\"\\``;\n return fallback ? `${reemit}, ${fallback}` : `${reemit}.`;\n}\n\n/**\n * Structured error for migration tooling operations.\n *\n * Follows the NAMESPACE.SUBCODE convention from ADR 027. All codes live under\n * the MIGRATION namespace. These are tooling-time errors (file I/O, hash\n * verification, migration history reconstruction), distinct from the runtime\n * MIGRATION.* codes for apply-time failures (PRECHECK_FAILED, POSTCHECK_FAILED,\n * etc.).\n *\n * Fields:\n * - code: Stable machine-readable code (MIGRATION.SUBCODE)\n * - category: Always 'MIGRATION'\n * - why: Explains the cause in plain language\n * - fix: Actionable remediation step\n * - details: Machine-readable structured data for agents\n */\nexport class MigrationToolsError extends Error {\n readonly code: string;\n readonly category = 'MIGRATION' as const;\n readonly why: string;\n readonly fix: string;\n readonly details: Record<string, unknown> | undefined;\n\n constructor(\n code: string,\n summary: string,\n options: {\n readonly why: string;\n readonly fix: string;\n readonly details?: Record<string, unknown>;\n },\n ) {\n super(summary);\n this.name = 'MigrationToolsError';\n this.code = code;\n this.why = options.why;\n this.fix = options.fix;\n this.details = options.details;\n }\n\n static is(error: unknown): error is MigrationToolsError {\n if (!(error instanceof Error)) return false;\n const candidate = error as MigrationToolsError;\n return candidate.name === 'MigrationToolsError' && typeof candidate.code === 'string';\n }\n}\n\nexport function errorDirectoryExists(dir: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.DIR_EXISTS', 'Migration directory already exists', {\n why: `The directory \"${dir}\" already exists. Each migration must have a unique directory.`,\n fix: 'Use --name to pick a different name, or delete the existing directory and re-run.',\n details: { dir },\n });\n}\n\nexport function errorMissingFile(file: string, dir: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.FILE_MISSING', `Missing ${file}`, {\n why: `Expected \"${file}\" in \"${dir}\" but the file does not exist.`,\n fix: reemitHint(\n dir,\n 'or delete the directory if the migration is unwanted and the source TypeScript is gone.',\n ),\n details: { file, dir },\n });\n}\n\nexport function errorInvalidJson(filePath: string, parseError: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_JSON', 'Invalid JSON in migration file', {\n why: `Failed to parse \"${filePath}\": ${parseError}`,\n fix: reemitHint(dirname(filePath), 'or restore the directory from version control.'),\n details: { filePath, parseError },\n });\n}\n\nexport function errorInvalidManifest(filePath: string, reason: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_MANIFEST', 'Invalid migration manifest', {\n why: `Migration manifest at \"${filePath}\" is invalid: ${reason}`,\n fix: reemitHint(dirname(filePath), 'or restore the directory from version control.'),\n details: { filePath, reason },\n });\n}\n\nexport function errorInvalidOperationEntry(index: number, reason: string): MigrationToolsError {\n return new MigrationToolsError(\n 'MIGRATION.INVALID_OPERATION_ENTRY',\n 'Migration operation entry is malformed',\n {\n why: `Operation at index ${index} returned by the migration class failed schema validation: ${reason}.`,\n fix: \"Update the migration class so each entry of `operations` carries `id` (string), `label` (string), and `operationClass` (one of 'additive' | 'widening' | 'destructive' | 'data').\",\n details: { index, reason },\n },\n );\n}\n\nexport function errorInvalidSlug(slug: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_NAME', 'Invalid migration name', {\n why: `The slug \"${slug}\" contains no valid characters after sanitization (only a-z, 0-9 are kept).`,\n fix: 'Provide a name with at least one alphanumeric character, e.g. --name add_users.',\n details: { slug },\n });\n}\n\nexport function errorInvalidDestName(destName: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_DEST_NAME', 'Invalid copy destination name', {\n why: `The destination name \"${destName}\" must be a single path segment (no \"..\" or directory separators).`,\n fix: 'Use a simple file name such as \"contract.json\" for each destination in the copy list.',\n details: { destName },\n });\n}\n\nexport function errorInvalidSpaceId(spaceId: string): MigrationToolsError {\n return new MigrationToolsError(\n 'MIGRATION.INVALID_SPACE_ID',\n 'Invalid contract space identifier',\n {\n why: `The space id \"${spaceId}\" does not match the required pattern /^[a-z][a-z0-9_-]{0,63}$/. Space ids are used as filesystem directory names under \\`migrations/\\`, so the pattern is conservative on purpose.`,\n fix: 'Pick a lowercase identifier that begins with a letter and contains only lowercase letters, digits, hyphens, or underscores; max 64 characters total.',\n details: { spaceId },\n },\n );\n}\n\nexport function errorDescriptorHeadHashMismatch(args: {\n readonly extensionId: string;\n readonly recomputedHash: string;\n readonly headRefHash: string;\n}): MigrationToolsError {\n const { extensionId, recomputedHash, headRefHash } = args;\n return new MigrationToolsError(\n 'MIGRATION.DESCRIPTOR_HEAD_HASH_MISMATCH',\n \"Extension descriptor's headRef.hash does not match its contractJson\",\n {\n why: `Extension \"${extensionId}\" publishes a \\`contractSpace\\` whose \\`headRef.hash\\` (${headRefHash}) does not match the canonical hash recomputed from \\`contractSpace.contractJson\\` (${recomputedHash}). This means the extension descriptor was published with stale \\`headRef.hash\\` — typically because the contract was bumped without rerunning the extension's emit pipeline.`,\n fix: 'Re-run the extension authoring pipeline so `contractJson.storage.storageHash` and `headRef.hash` agree, then republish the extension. If you are the extension author and you intentionally bumped `contractJson`, recompute and update `headRef.hash` (and refresh any on-disk migration metadata that derives from it).',\n details: { extensionId, recomputedHash, headRefHash },\n },\n );\n}\n\nexport function errorDuplicateSpaceId(spaceId: string): MigrationToolsError {\n return new MigrationToolsError(\n 'MIGRATION.DUPLICATE_SPACE_ID',\n 'Duplicate contract space identifier',\n {\n why: `The space id \"${spaceId}\" appears more than once in the per-space planner input. Each space id must be unique across the inputs (the per-space planner emits one output entry per id).`,\n fix: 'Deduplicate the inputs before passing them to `planAllSpaces` — typically by checking your `extensionPacks` declaration for repeated entries.',\n details: { spaceId },\n },\n );\n}\n\nexport function errorSameSourceAndTarget(dir: string, hash: string): MigrationToolsError {\n const dirName = basename(dir);\n return new MigrationToolsError(\n 'MIGRATION.SAME_SOURCE_AND_TARGET',\n 'Migration without data-transform operations has same source and target',\n {\n why: `Migration \"${dirName}\" has from === to === \"${hash}\" and declares no data-transform operations. Self-edges are only allowed when the migration runs at least one dataTransform — otherwise the migration is a no-op.`,\n fix: reemitHint(\n dir,\n 'and either change the contract so from ≠ to, add a dataTransform op, or delete the directory if the migration is unwanted.',\n ),\n details: { dirName, hash },\n },\n );\n}\n\nexport function errorAmbiguousTarget(\n branchTips: readonly string[],\n context?: {\n divergencePoint: string;\n branches: readonly {\n tip: string;\n edges: readonly { dirName: string; from: string; to: string }[];\n }[];\n },\n): MigrationToolsError {\n const divergenceInfo = context\n ? `\\nDivergence point: ${context.divergencePoint}\\nBranches:\\n${context.branches.map((b) => ` → ${b.tip} (${b.edges.length} edge(s): ${b.edges.map((e) => e.dirName).join(' → ') || 'direct'})`).join('\\n')}`\n : '';\n return new MigrationToolsError('MIGRATION.AMBIGUOUS_TARGET', 'Ambiguous migration target', {\n why: `The migration history has diverged into multiple branches: ${branchTips.join(', ')}. This typically happens when two developers plan migrations from the same starting point.${divergenceInfo}`,\n fix: 'Use `migration ref set <name> <hash>` to target a specific branch, delete one of the conflicting migration directories and re-run `migration plan`, or use --from <hash> to explicitly select a starting point.',\n details: {\n branchTips,\n ...(context ? { divergencePoint: context.divergencePoint, branches: context.branches } : {}),\n },\n });\n}\n\nexport function errorNoInitialMigration(nodes: readonly string[]): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.NO_INITIAL_MIGRATION', 'No initial migration found', {\n why: `No migration starts from the empty contract state (known hashes: ${nodes.join(', ')}). At least one migration must originate from the empty state.`,\n fix: 'Inspect the migrations directory for corrupted migration.json files. At least one migration must start from the empty contract hash.',\n details: { nodes },\n });\n}\n\nexport function errorInvalidRefs(refsPath: string, reason: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REFS', 'Invalid refs.json', {\n why: `refs.json at \"${refsPath}\" is invalid: ${reason}`,\n fix: 'Ensure refs.json is a flat object mapping valid ref names to contract hash strings.',\n details: { path: refsPath, reason },\n });\n}\n\nexport function errorInvalidRefFile(filePath: string, reason: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REF_FILE', 'Invalid ref file', {\n why: `Ref file at \"${filePath}\" is invalid: ${reason}`,\n fix: 'Ensure the ref file contains valid JSON with { \"hash\": \"sha256:<64 hex chars>\", \"invariants\": [\"...\"] }.',\n details: { path: filePath, reason },\n });\n}\n\nexport function errorInvalidRefName(refName: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REF_NAME', 'Invalid ref name', {\n why: `Ref name \"${refName}\" is invalid. Names must be lowercase alphanumeric with hyphens or forward slashes (no \".\" or \"..\" segments).`,\n fix: `Use a valid ref name (e.g., \"staging\", \"envs/production\").`,\n details: { refName },\n });\n}\n\nexport function errorNoTarget(reachableHashes: readonly string[]): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.NO_TARGET', 'No migration target could be resolved', {\n why: `The migration history contains cycles and no target can be resolved automatically (reachable hashes: ${reachableHashes.join(', ')}). This typically happens after rollback migrations (e.g., C1→C2→C1).`,\n fix: 'Use --from <hash> to specify the planning origin explicitly.',\n details: { reachableHashes },\n });\n}\n\nexport function errorInvalidRefValue(value: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REF_VALUE', 'Invalid ref value', {\n why: `Ref value \"${value}\" is not a valid contract hash. Values must be in the format \"sha256:<64 hex chars>\" or \"sha256:empty\".`,\n fix: 'Use a valid storage hash from `prisma-next contract emit` output or an existing migration.',\n details: { value },\n });\n}\n\nexport function errorDuplicateMigrationHash(migrationHash: string): MigrationToolsError {\n return new MigrationToolsError(\n 'MIGRATION.DUPLICATE_MIGRATION_HASH',\n 'Duplicate migrationHash in migration graph',\n {\n why: `Multiple migrations share migrationHash \"${migrationHash}\". Each migration must have a unique content-addressed identity.`,\n fix: 'Regenerate one of the conflicting migrations so each migrationHash is unique, then re-run migration commands.',\n details: { migrationHash },\n },\n );\n}\n\nexport function errorInvalidInvariantId(invariantId: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_INVARIANT_ID', 'Invalid invariantId', {\n why: `invariantId ${JSON.stringify(invariantId)} is invalid. Ids must be non-empty and contain no whitespace or control characters (including Unicode whitespace like NBSP); other content (kebab-case, camelCase, namespaced, Unicode letters) is allowed.`,\n fix: 'Pick an invariantId without spaces, tabs, newlines, or control characters — e.g. \"backfill-user-phone\", \"users/backfill-phone\", or \"BackfillUserPhone\".',\n details: { invariantId },\n });\n}\n\nexport function errorDuplicateInvariantInEdge(invariantId: string): MigrationToolsError {\n return new MigrationToolsError(\n 'MIGRATION.DUPLICATE_INVARIANT_IN_EDGE',\n 'Duplicate invariantId on a single migration',\n {\n why: `invariantId \"${invariantId}\" is declared by more than one dataTransform on the same migration. The marker stores invariants as a set and the routing layer treats them as edge-level, so two ops cannot share a routing identity.`,\n fix: 'Rename one of the conflicting dataTransform invariantIds, or drop invariantId on the op that does not need to be routing-visible.',\n details: { invariantId },\n },\n );\n}\n\nexport function errorProvidedInvariantsMismatch(\n filePath: string,\n stored: readonly string[],\n derived: readonly string[],\n): MigrationToolsError {\n const storedSet = new Set(stored);\n const derivedSet = new Set(derived);\n const missing = [...derivedSet].filter((id) => !storedSet.has(id));\n const extra = [...storedSet].filter((id) => !derivedSet.has(id));\n // When sets agree but arrays don't, the only difference is ordering — call\n // it out so the reader doesn't stare at two visually-identical arrays.\n // Canonical providedInvariants is sorted ascending; a manifest with the\n // same ids in a different order is still a mismatch (the hash check would\n // also fail), but the human-readable diagnostic is otherwise unhelpful.\n const orderingOnly = missing.length === 0 && extra.length === 0;\n const why = orderingOnly\n ? `migration.json at \"${filePath}\" stores providedInvariants ${JSON.stringify(stored)}, but the canonical value derived from ops.json is ${JSON.stringify(derived)} — same ids, different order. Canonical providedInvariants is sorted ascending.`\n : `migration.json at \"${filePath}\" stores providedInvariants ${JSON.stringify(stored)}, but the value derived from ops.json is ${JSON.stringify(derived)}. The manifest copy was likely hand-edited without re-emitting.`;\n return new MigrationToolsError(\n 'MIGRATION.PROVIDED_INVARIANTS_MISMATCH',\n 'providedInvariants on migration.json disagrees with ops.json',\n {\n why,\n fix: reemitHint(dirname(filePath), 'or restore the directory from version control.'),\n details: { filePath, stored, derived, difference: { missing, extra } },\n },\n );\n}\n\n/**\n * Wire-shape edge surfaced through the JSON envelope's\n * `meta.structuralPath` of `MIGRATION.NO_INVARIANT_PATH`. Slim by design —\n * authoring metadata (`createdAt`, `labels`) lives on `MigrationEdge` but\n * is intentionally dropped here so the envelope stays stable across\n * graph-internal refactors.\n *\n * Stability: any field added here is part of the public CLI JSON contract.\n * Callers (CLI consumers, agents) must be able to treat\n * `(dirName, migrationHash, from, to, invariants)` as the canonical shape.\n */\nexport interface NoInvariantPathStructuralEdge {\n readonly dirName: string;\n readonly migrationHash: string;\n readonly from: string;\n readonly to: string;\n readonly invariants: readonly string[];\n}\n\nexport function errorNoInvariantPath(args: {\n readonly refName?: string;\n readonly required: readonly string[];\n readonly missing: readonly string[];\n readonly structuralPath: readonly NoInvariantPathStructuralEdge[];\n}): MigrationToolsError {\n const { refName, required, missing, structuralPath } = args;\n const refClause = refName ? `Ref \"${refName}\"` : 'Target';\n const missingList = missing.map((id) => JSON.stringify(id)).join(', ');\n const requiredList = required.map((id) => JSON.stringify(id)).join(', ');\n return new MigrationToolsError(\n 'MIGRATION.NO_INVARIANT_PATH',\n 'No path covers the required invariants',\n {\n why: `${refClause} requires invariants the reachable path doesn't cover. required=[${requiredList}], missing=[${missingList}].`,\n fix: 'Add a migration on the path that runs `dataTransform({ invariantId: \"<id>\", … })` for each missing invariant, or retarget the ref to a hash whose path already provides them.',\n details: {\n required,\n missing,\n structuralPath,\n ...ifDefined('refName', refName),\n },\n },\n );\n}\n\nexport function errorUnknownInvariant(args: {\n readonly refName?: string;\n readonly unknown: readonly string[];\n readonly declared: readonly string[];\n}): MigrationToolsError {\n const { refName, unknown, declared } = args;\n const refClause = refName ? `Ref \"${refName}\" declares` : 'Declares';\n const unknownList = unknown.map((id) => JSON.stringify(id)).join(', ');\n return new MigrationToolsError(\n 'MIGRATION.UNKNOWN_INVARIANT',\n 'Ref declares invariants no migration in the graph provides',\n {\n why: `${refClause} invariants no migration in the graph provides. unknown=[${unknownList}].`,\n fix: 'Either the ref has a typo, or the declaring migration has not been authored/attested yet. Re-check the ref file and the migrations directory.',\n details: {\n unknown,\n declared,\n ...ifDefined('refName', refName),\n },\n },\n );\n}\n\nexport function errorMigrationHashMismatch(\n dir: string,\n storedHash: string,\n computedHash: string,\n): MigrationToolsError {\n // Render a cwd-relative path in the human-readable diagnostic so users\n // running CLI commands from the project root see a familiar short path.\n // Keep the absolute path in `details.dir` for machine consumers.\n const relativeDir = relative(process.cwd(), dir);\n return new MigrationToolsError('MIGRATION.HASH_MISMATCH', 'Migration package is corrupt', {\n why: `Stored migrationHash \"${storedHash}\" does not match the recomputed hash \"${computedHash}\" for \"${relativeDir}\". The migration.json or ops.json has been edited or partially written since emit.`,\n fix: reemitHint(dir, 'or restore the directory from version control.'),\n details: { dir, storedHash, computedHash },\n });\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import { s as readMigrationsDir } from "../io-Dw620b51.mjs";
1
+ import { s as readMigrationsDir } from "../io-Zxj9kQwp.mjs";
2
2
  import "../constants-DWV9_o2Z.mjs";
3
- import { l as reconstructGraph, o as findPathWithDecision } from "../migration-graph-DGNnKDY5.mjs";
4
- import { a as APP_SPACE_ID, c as spaceMigrationDirectory, i as readContractSpaceHeadRef, n as listContractSpaceDirectories, t as readContractSpaceContract } from "../read-contract-space-contract-COyz4tZn.mjs";
3
+ import { l as reconstructGraph, o as findPathWithDecision } from "../migration-graph-NGymYL9J.mjs";
4
+ import { a as APP_SPACE_ID, c as spaceMigrationDirectory, i as readContractSpaceHeadRef, n as listContractSpaceDirectories, t as readContractSpaceContract } from "../read-contract-space-contract-nZRrktnx.mjs";
5
5
  import { notOk, ok } from "@prisma-next/utils/result";
6
6
  //#region src/aggregate/extract-storage-element-names.ts
7
7
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.mts","names":[],"sources":["../../src/errors.ts"],"mappings":";;AAmCA;;;;;;;;;;;;;;;cAAa,mBAAA,SAA4B,KAAA;EAAA,SAC9B,IAAA;EAAA,SACA,QAAA;EAAA,SACA,GAAA;EAAA,SACA,GAAA;EAAA,SACA,OAAA,EAAS,MAAA;cAGhB,IAAA,UACA,OAAA,UACA,OAAA;IAAA,SACW,GAAA;IAAA,SACA,GAAA;IAAA,SACA,OAAA,GAAU,MAAA;EAAA;EAAA,OAWhB,EAAA,CAAG,KAAA,YAAiB,KAAA,IAAS,mBAAA;AAAA;AAAA,iBA0BtB,gBAAA,CAAiB,QAAA,UAAkB,UAAA,WAAqB,mBAAA;AAAA,iBA6ExD,+BAAA,CAAgC,IAAA;EAAA,SACrC,WAAA;EAAA,SACA,cAAA;EAAA,SACA,WAAA;AAAA,IACP,mBAAA;;;;;;AA0NJ;;;;;;UAlCiB,6BAAA;EAAA,SACN,OAAA;EAAA,SACA,aAAA;EAAA,SACA,IAAA;EAAA,SACA,EAAA;EAAA,SACA,UAAA;AAAA;AAAA,iBAGK,oBAAA,CAAqB,IAAA;EAAA,SAC1B,OAAA;EAAA,SACA,QAAA;EAAA,SACA,OAAA;EAAA,SACA,cAAA,WAAyB,6BAAA;AAAA,IAChC,mBAAA;AAAA,iBAqBY,qBAAA,CAAsB,IAAA;EAAA,SAC3B,OAAA;EAAA,SACA,OAAA;EAAA,SACA,QAAA;AAAA,IACP,mBAAA"}
1
+ {"version":3,"file":"errors.d.mts","names":[],"sources":["../../src/errors.ts"],"mappings":";;AAmCA;;;;;;;;;;;;;;;cAAa,mBAAA,SAA4B,KAAA;EAAA,SAC9B,IAAA;EAAA,SACA,QAAA;EAAA,SACA,GAAA;EAAA,SACA,GAAA;EAAA,SACA,OAAA,EAAS,MAAA;cAGhB,IAAA,UACA,OAAA,UACA,OAAA;IAAA,SACW,GAAA;IAAA,SACA,GAAA;IAAA,SACA,OAAA,GAAU,MAAA;EAAA;EAAA,OAWhB,EAAA,CAAG,KAAA,YAAiB,KAAA,IAAS,mBAAA;AAAA;AAAA,iBA0BtB,gBAAA,CAAiB,QAAA,UAAkB,UAAA,WAAqB,mBAAA;AAAA,iBAwDxD,+BAAA,CAAgC,IAAA;EAAA,SACrC,WAAA;EAAA,SACA,cAAA;EAAA,SACA,WAAA;AAAA,IACP,mBAAA;;;;;;;;;;;AA0NJ;UAlCiB,6BAAA;EAAA,SACN,OAAA;EAAA,SACA,aAAA;EAAA,SACA,IAAA;EAAA,SACA,EAAA;EAAA,SACA,UAAA;AAAA;AAAA,iBAGK,oBAAA,CAAqB,IAAA;EAAA,SAC1B,OAAA;EAAA,SACA,QAAA;EAAA,SACA,OAAA;EAAA,SACA,cAAA,WAAyB,6BAAA;AAAA,IAChC,mBAAA;AAAA,iBAqBY,qBAAA,CAAsB,IAAA;EAAA,SAC3B,OAAA;EAAA,SACA,OAAA;EAAA,SACA,QAAA;AAAA,IACP,mBAAA"}
@@ -1,2 +1,2 @@
1
- import { E as errorUnknownInvariant, r as errorDescriptorHeadHashMismatch, t as MigrationToolsError, u as errorInvalidJson, x as errorNoInvariantPath } from "../errors-EPL_9p9f.mjs";
1
+ import { T as errorUnknownInvariant, r as errorDescriptorHeadHashMismatch, t as MigrationToolsError, u as errorInvalidJson, x as errorNoInvariantPath } from "../errors-DqMhmTPQ.mjs";
2
2
  export { MigrationToolsError, errorDescriptorHeadHashMismatch, errorInvalidJson, errorNoInvariantPath, errorUnknownInvariant };
@@ -9,11 +9,13 @@ interface VerifyResult {
9
9
  readonly computedHash: string;
10
10
  }
11
11
  /**
12
- * Content-addressed migration hash over (metadata envelope sans
13
- * contracts/hints, ops). See ADR 199 — Storage-only migration identity
14
- * for the rationale: contracts are anchored separately by the
15
- * storage-hash bookends inside the envelope; planner hints are advisory
16
- * and must not affect identity.
12
+ * Content-addressed migration hash over (metadata envelope sans hints,
13
+ * ops). See ADR 199 — Storage-only migration identity for the
14
+ * rationale: the storage-hash bookends (`from`, `to`) inside the
15
+ * envelope anchor the contract identity by hash, and planner hints are
16
+ * advisory and must not affect identity. The full contract IRs are not
17
+ * part of the manifest — they live in sibling `*-contract.json` files
18
+ * authored alongside the migration, never inlined here.
17
19
  *
18
20
  * The integrity check is purely structural, not semantic. The function
19
21
  * canonicalizes its inputs via `sortKeys` (recursive) + `JSON.stringify`
@@ -1 +1 @@
1
- {"version":3,"file":"hash.d.mts","names":[],"sources":["../../src/hash.ts"],"mappings":";;;;UAKiB,YAAA;EAAA,SACN,EAAA;EAAA,SACA,MAAA;EAAA,SACA,UAAA;EAAA,SACA,YAAA;AAAA;;;;;;;AAiCX;;;;;;;;;;;;;;;;AA8BA;;;;iBA9BgB,oBAAA,CACd,QAAA,EAAU,IAAA,CAAK,iBAAA;EAAA,SAAiD,aAAA;AAAA,GAChE,GAAA,EAAK,YAAA;;;;;;;;;;iBA4BS,mBAAA,CAAoB,GAAA,EAAK,sBAAA,GAAyB,YAAA"}
1
+ {"version":3,"file":"hash.d.mts","names":[],"sources":["../../src/hash.ts"],"mappings":";;;;UAKiB,YAAA;EAAA,SACN,EAAA;EAAA,SACA,MAAA;EAAA,SACA,UAAA;EAAA,SACA,YAAA;AAAA;;;;;;;AAmCX;;;;;;;;;;;;;;;;AAwBA;;;;;;iBAxBgB,oBAAA,CACd,QAAA,EAAU,IAAA,CAAK,iBAAA;EAAA,SAAiD,aAAA;AAAA,GAChE,GAAA,EAAK,YAAA;;;;;;;;;;iBAsBS,mBAAA,CAAoB,GAAA,EAAK,sBAAA,GAAyB,YAAA"}
@@ -1,2 +1,2 @@
1
- import { n as verifyMigrationHash, t as computeMigrationHash } from "../hash-C6bpZczT.mjs";
1
+ import { n as verifyMigrationHash, t as computeMigrationHash } from "../hash-Cr4WIr4Z.mjs";
2
2
  export { computeMigrationHash, verifyMigrationHash };
@@ -1,2 +1,2 @@
1
- import { n as validateInvariantId, t as deriveProvidedInvariants } from "../invariants-qgQGlsrV.mjs";
1
+ import { n as validateInvariantId, t as deriveProvidedInvariants } from "../invariants-DItbPp4w.mjs";
2
2
  export { deriveProvidedInvariants, validateInvariantId };
@@ -7,35 +7,31 @@ declare function writeMigrationPackage(dir: string, metadata: MigrationMetadata,
7
7
  * Materialise an in-memory {@link MigrationPackage} to a per-space
8
8
  * directory on disk.
9
9
  *
10
- * Writes three files under `<targetDir>/<pkg.dirName>/`:
10
+ * Writes two files under `<targetDir>/<pkg.dirName>/`:
11
11
  *
12
12
  * - `migration.json` — the manifest (pretty-printed, matches
13
13
  * {@link writeMigrationPackage}'s output for byte-for-byte parity with
14
14
  * app-space migrations).
15
15
  * - `ops.json` — the operation list (pretty-printed).
16
- * - `contract.json` — the canonical-JSON serialisation of
17
- * `metadata.toContract`. This is the per-package post-state contract
18
- * snapshot; the canonicalisation pass guarantees byte-determinism so
19
- * re-emitting the same package across machines / runs produces an
20
- * identical file.
21
16
  *
22
17
  * Distinct verb from the lower-level {@link writeMigrationPackage}
23
18
  * (which takes constituent `(metadata, ops)`): callers reading
24
- * `materialise…` know they are persisting a struct-typed package
25
- * including its contract-snapshot side car.
19
+ * `materialise…` know they are persisting a struct-typed package.
26
20
  *
27
21
  * Overwrite-idempotent: the per-package directory is cleared before
28
22
  * each emit, so re-running against the same `targetDir` produces
29
23
  * byte-identical contents and never leaves stale files behind. The
30
- * spec's "re-emitting the same package across runs / machines produces
31
- * byte-identical files" guarantee (§ 3) covers both same-dir and
32
- * fresh-dir re-emits. The lower-level {@link writeMigrationPackage}
33
- * stays strict because the CLI authoring path (`migration plan` /
34
- * `migration new`) deliberately refuses to clobber an existing
35
- * authored migration; this helper is the re-emit path that is
36
- * supposed to converge on a single canonical on-disk shape.
24
+ * lower-level {@link writeMigrationPackage} stays strict because the
25
+ * CLI authoring path (`migration plan` / `migration new`) deliberately
26
+ * refuses to clobber an existing authored migration; this helper is
27
+ * the re-emit path that is supposed to converge on a single canonical
28
+ * on-disk shape.
37
29
  *
38
- * @see specs/framework-mechanism.spec.md § 3 Emission helper (T1.7).
30
+ * The per-space head contract lives at
31
+ * `<projectMigrationsDir>/<spaceId>/contract.json` (written by
32
+ * {@link import('./emit-contract-space-artefacts').emitContractSpaceArtefacts}),
33
+ * not inside the per-package directory. The runner reads only
34
+ * `migration.json` + `ops.json` from each package.
39
35
  */
40
36
  declare function materialiseMigrationPackage(targetDir: string, pkg: MigrationPackage): Promise<void>;
41
37
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"io.d.mts","names":[],"sources":["../../src/io.ts"],"mappings":";;;;iBAkDsB,qBAAA,CACpB,GAAA,UACA,QAAA,EAAU,iBAAA,EACV,GAAA,EAAK,YAAA,GACJ,OAAA;AAJH;;;;;;;;;;;;;;;;AAwDA;;;;;;;;;;AAiCA;;;;;;;;AAzFA,iBAwDsB,2BAAA,CACpB,SAAA,UACA,GAAA,EAAK,gBAAA,GACJ,OAAA;;;AA8DH;;;;;;;;;;;AAaA;;;;;;;;iBA7CsB,6CAAA,CACpB,SAAA,UACA,GAAA,EAAK,gBAAA,GACJ,OAAA;EAAA,SAAmB,OAAA;AAAA;;;;;;;;;;AAqDtB;;iBAxBsB,mBAAA,CACpB,OAAA,UACA,KAAA;EAAA,SAA2B,UAAA;EAAA,SAA6B,QAAA;AAAA,MACvD,OAAA;AAAA,iBAUmB,sBAAA,CACpB,GAAA,UACA,QAAA,EAAU,iBAAA,GACT,OAAA;AAAA,iBAImB,iBAAA,CAAkB,GAAA,UAAa,GAAA,EAAK,YAAA,GAAe,OAAA;AAAA,iBAInD,oBAAA,CAAqB,GAAA,WAAc,OAAA,CAAQ,sBAAA;AAAA,iBAiG3C,iBAAA,CACpB,cAAA,WACC,OAAA,UAAiB,sBAAA;AAAA,iBA+BJ,sBAAA,CAAuB,SAAA,EAAW,IAAA,EAAM,IAAA"}
1
+ {"version":3,"file":"io.d.mts","names":[],"sources":["../../src/io.ts"],"mappings":";;;;iBA+CsB,qBAAA,CACpB,GAAA,UACA,QAAA,EAAU,iBAAA,EACV,GAAA,EAAK,YAAA,GACJ,OAAA;AAJH;;;;;;;;;;;;;;;;AAoDA;;;;;;;;;;AA8BA;;;;AAlFA,iBAoDsB,2BAAA,CACpB,SAAA,UACA,GAAA,EAAK,gBAAA,GACJ,OAAA;;;;;;;AA2DH;;;;;;;;;;;AAaA;;;;iBA7CsB,6CAAA,CACpB,SAAA,UACA,GAAA,EAAK,gBAAA,GACJ,OAAA;EAAA,SAAmB,OAAA;AAAA;;;;AAiDtB;;;;;;;;iBApBsB,mBAAA,CACpB,OAAA,UACA,KAAA;EAAA,SAA2B,UAAA;EAAA,SAA6B,QAAA;AAAA,MACvD,OAAA;AAAA,iBAUmB,sBAAA,CACpB,GAAA,UACA,QAAA,EAAU,iBAAA,GACT,OAAA;AAAA,iBAImB,iBAAA,CAAkB,GAAA,UAAa,GAAA,EAAK,YAAA,GAAe,OAAA;AAAA,iBAInD,oBAAA,CAAqB,GAAA,WAAc,OAAA,CAAQ,sBAAA;AAAA,iBAiG3C,iBAAA,CACpB,cAAA,WACC,OAAA,UAAiB,sBAAA;AAAA,iBA+BJ,sBAAA,CAAuB,SAAA,EAAW,IAAA,EAAM,IAAA"}
@@ -1,2 +1,2 @@
1
- import { a as materialiseMigrationPackage, c as writeMigrationMetadata, i as materialiseExtensionMigrationPackageIfMissing, l as writeMigrationOps, n as copyFilesWithRename, o as readMigrationPackage, r as formatMigrationDirName, s as readMigrationsDir, u as writeMigrationPackage } from "../io-Dw620b51.mjs";
1
+ import { a as materialiseMigrationPackage, c as writeMigrationMetadata, i as materialiseExtensionMigrationPackageIfMissing, l as writeMigrationOps, n as copyFilesWithRename, o as readMigrationPackage, r as formatMigrationDirName, s as readMigrationsDir, u as writeMigrationPackage } from "../io-Zxj9kQwp.mjs";
2
2
  export { copyFilesWithRename, formatMigrationDirName, materialiseExtensionMigrationPackageIfMissing, materialiseMigrationPackage, readMigrationPackage, readMigrationsDir, writeMigrationMetadata, writeMigrationOps, writeMigrationPackage };
@@ -1,2 +1,2 @@
1
- import { a as findPath, c as findReachableLeaves, i as findLeaf, l as reconstructGraph, n as detectOrphans, o as findPathWithDecision, r as findLatestMigration, s as findPathWithInvariants, t as detectCycles } from "../migration-graph-DGNnKDY5.mjs";
1
+ import { a as findPath, c as findReachableLeaves, i as findLeaf, l as reconstructGraph, n as detectOrphans, o as findPathWithDecision, r as findLatestMigration, s as findPathWithInvariants, t as detectCycles } from "../migration-graph-NGymYL9J.mjs";
2
2
  export { detectCycles, detectOrphans, findLatestMigration, findLeaf, findPath, findPathWithDecision, findPathWithInvariants, findReachableLeaves, reconstructGraph };
@@ -1 +1 @@
1
- {"version":3,"file":"migration.d.mts","names":[],"sources":["../../src/migration-base.ts"],"mappings":";;;;UAgBiB,aAAA;EAAA,SACN,IAAA;EAAA,SACA,EAAA;EAAA,SACA,MAAA;AAAA;;;;;;;AAuBX;;;uBAAsB,SAAA,oBACD,sBAAA,GAAyB,sBAAA,mFAGjC,aAAA;EAAA,kBAEO,QAAA;EAWqB;;;;;;;;;EAAA,mBAApB,KAAA,EAAO,YAAA,CAAa,SAAA,EAAW,SAAA;cAEtC,KAAA,GAAQ,YAAA,CAAa,SAAA,EAAW,SAAA;EAlB5C;;;;;;EAAA,aA4Ba,UAAA,CAAA,YAAuB,UAAA;EAZjB;;;;;EAAA,SAmBV,QAAA,CAAA,GAAY,aAAA;EAAA,IAEjB,MAAA,CAAA;IAAA,SAAqB,WAAA;EAAA;EAAA,IAKrB,WAAA,CAAA;IAAA,SAA0B,WAAA;EAAA;AAAA;;;;;;;AAYhC;iBAAgB,kBAAA,CAAmB,aAAA;;;;AAsBnC;;;;;;;;UAAiB,kBAAA;EAAA,SACN,OAAA;EAAA,SACA,QAAA,EAAU,mBAAA;EAAA,SACV,YAAA;AAAA;;;;;;;;;;iBAkHK,uBAAA,CACd,QAAA,EAAU,SAAA,EACV,QAAA,EAAU,OAAA,CAAQ,mBAAA,WACjB,kBAAA"}
1
+ {"version":3,"file":"migration.d.mts","names":[],"sources":["../../src/migration-base.ts"],"mappings":";;;;UAeiB,aAAA;EAAA,SACN,IAAA;EAAA,SACA,EAAA;EAAA,SACA,MAAA;AAAA;;;;;;;AAuBX;;;uBAAsB,SAAA,oBACD,sBAAA,GAAyB,sBAAA,mFAGjC,aAAA;EAAA,kBAEO,QAAA;EAWqB;;;;;;;;;EAAA,mBAApB,KAAA,EAAO,YAAA,CAAa,SAAA,EAAW,SAAA;cAEtC,KAAA,GAAQ,YAAA,CAAa,SAAA,EAAW,SAAA;EAlB5C;;;;;;EAAA,aA4Ba,UAAA,CAAA,YAAuB,UAAA;EAZjB;;;;;EAAA,SAmBV,QAAA,CAAA,GAAY,aAAA;EAAA,IAEjB,MAAA,CAAA;IAAA,SAAqB,WAAA;EAAA;EAAA,IAKrB,WAAA,CAAA;IAAA,SAA0B,WAAA;EAAA;AAAA;;;;;;;AAYhC;iBAAgB,kBAAA,CAAmB,aAAA;;;;AAsBnC;;;;;;;;UAAiB,kBAAA;EAAA,SACN,OAAA;EAAA,SACA,QAAA,EAAU,mBAAA;EAAA,SACV,YAAA;AAAA;;;;;;;;;;iBA8DK,uBAAA,CACd,QAAA,EAAU,SAAA,EACV,QAAA,EAAU,OAAA,CAAQ,mBAAA,WACjB,kBAAA"}
@@ -1,6 +1,6 @@
1
- import { T as errorStaleContractBookends, f as errorInvalidOperationEntry } from "../errors-EPL_9p9f.mjs";
2
- import { t as computeMigrationHash } from "../hash-C6bpZczT.mjs";
3
- import { t as deriveProvidedInvariants } from "../invariants-qgQGlsrV.mjs";
1
+ import { f as errorInvalidOperationEntry } from "../errors-DqMhmTPQ.mjs";
2
+ import { t as computeMigrationHash } from "../hash-Cr4WIr4Z.mjs";
3
+ import { t as deriveProvidedInvariants } from "../invariants-DItbPp4w.mjs";
4
4
  import { t as MigrationOpSchema } from "../op-schema-D5qkXfEf.mjs";
5
5
  import { type } from "arktype";
6
6
  import { realpathSync } from "node:fs";
@@ -77,15 +77,12 @@ function isDirectEntrypoint(importMetaUrl) {
77
77
  * the on-disk artifacts are always fully attested.
78
78
  */
79
79
  function buildAttestedMetadata(meta, ops, existing) {
80
- assertBookendsMatchMeta(meta, existing);
81
80
  const baseMetadata = {
82
81
  from: meta.from,
83
82
  to: meta.to,
84
83
  labels: meta.labels ?? existing?.labels ?? [],
85
84
  providedInvariants: deriveProvidedInvariants(ops),
86
85
  createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
87
- fromContract: existing?.fromContract ?? null,
88
- toContract: existing?.toContract ?? { storage: { storageHash: meta.to } },
89
86
  hints: normalizeHints(existing?.hints)
90
87
  };
91
88
  const migrationHash = computeMigrationHash(baseMetadata, ops);
@@ -95,41 +92,6 @@ function buildAttestedMetadata(meta, ops, existing) {
95
92
  };
96
93
  }
97
94
  /**
98
- * Verify each preserved contract bookend in `existing` agrees with the
99
- * corresponding side of `describe()`'s output. A mismatch indicates the
100
- * migration's `describe()` was edited after `migration plan` scaffolded
101
- * the package, leaving a self-inconsistent manifest. Failing fast at
102
- * write-time turns a silent foot-gun into an actionable diagnostic.
103
- *
104
- * Skipped when a side's `existing.<side>Contract` is null/absent (the
105
- * synthesis path stays open for origin-less initial migrations and for
106
- * bare `migration.ts` runs from scratch). When a bookend is *present*
107
- * but its `storage.storageHash` is missing, that's treated as a
108
- * mismatch — a malformed bookend is not equivalent to "no bookend".
109
- *
110
- * This check is paired with TML-2274, which removes `fromContract` /
111
- * `toContract` from the manifest entirely; once that lands, this
112
- * function and its error code are deleted.
113
- */
114
- function assertBookendsMatchMeta(meta, existing) {
115
- if (existing?.fromContract != null) {
116
- const contractHash = existing.fromContract.storage?.storageHash ?? "";
117
- if (contractHash !== meta.from) throw errorStaleContractBookends({
118
- side: "from",
119
- metaHash: meta.from,
120
- contractHash
121
- });
122
- }
123
- if (existing?.toContract != null) {
124
- const contractHash = existing.toContract.storage?.storageHash ?? "";
125
- if (contractHash !== meta.to) throw errorStaleContractBookends({
126
- side: "to",
127
- metaHash: meta.to,
128
- contractHash
129
- });
130
- }
131
- }
132
- /**
133
95
  * Project `existing.hints` down to the known `MigrationHints` shape, dropping
134
96
  * any legacy keys that may linger in metadata scaffolded by older CLI
135
97
  * versions (e.g. `planningStrategy`). Picking fields explicitly instead of
@@ -1 +1 @@
1
- {"version":3,"file":"migration.mjs","names":[],"sources":["../../src/migration-base.ts"],"sourcesContent":["import { realpathSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport type { Contract } from '@prisma-next/contract/types';\nimport type {\n ControlStack,\n MigrationPlan,\n MigrationPlanOperation,\n} from '@prisma-next/framework-components/control';\nimport { type } from 'arktype';\nimport { errorInvalidOperationEntry, errorStaleContractBookends } from './errors';\nimport { computeMigrationHash } from './hash';\nimport { deriveProvidedInvariants } from './invariants';\nimport type { MigrationHints, MigrationMetadata } from './metadata';\nimport { MigrationOpSchema } from './op-schema';\nimport type { MigrationOps } from './package';\n\nexport interface MigrationMeta {\n readonly from: string | null;\n readonly to: string;\n readonly labels?: readonly string[];\n}\n\n// `from` rejects empty strings to mirror `MigrationMetadataSchema` in\n// `./io.ts`. Without this match, an authored migration could `describe()` with\n// `from: ''` and pass `buildMigrationArtifacts`'s validation, only to have\n// `readMigrationPackage` reject the resulting `migration.json` later — the\n// two validators must agree on the legal value space.\nconst MigrationMetaSchema = type({\n from: 'string > 0 | null',\n to: 'string',\n 'labels?': type('string').array(),\n});\n\n/**\n * Base class for migrations.\n *\n * A `Migration` subclass is itself a `MigrationPlan`: CLI commands and the\n * runner can consume it directly via `targetId`, `operations`, `origin`, and\n * `destination`. The metadata-shaped inputs come from `describe()`, which\n * every migration must implement — `migration.json` is required for a\n * migration to be valid.\n */\nexport abstract class Migration<\n TOperation extends MigrationPlanOperation = MigrationPlanOperation,\n TFamilyId extends string = string,\n TTargetId extends string = string,\n> implements MigrationPlan\n{\n abstract readonly targetId: string;\n\n /**\n * Assembled `ControlStack` injected by the orchestrator (`runMigration`).\n *\n * Subclasses (e.g. `PostgresMigration`) read the stack to materialize their\n * adapter once per instance. Optional at the abstract level so unit tests can\n * construct `Migration` instances purely for `operations` / `describe`\n * assertions without needing a real stack; concrete subclasses that need the\n * stack at runtime should narrow the parameter to required.\n */\n protected readonly stack: ControlStack<TFamilyId, TTargetId> | undefined;\n\n constructor(stack?: ControlStack<TFamilyId, TTargetId>) {\n this.stack = stack;\n }\n\n /**\n * Ordered list of operations this migration performs.\n *\n * Implemented as a getter so that subclasses can either precompute the list\n * in their constructor or build it lazily per access.\n */\n abstract get operations(): readonly TOperation[];\n\n /**\n * Metadata inputs used to build `migration.json` and to derive the plan's\n * origin/destination identities. Every migration must provide this —\n * omitting it would produce an invalid on-disk migration package.\n */\n abstract describe(): MigrationMeta;\n\n get origin(): { readonly storageHash: string } | null {\n const from = this.describe().from;\n return from === null ? null : { storageHash: from };\n }\n\n get destination(): { readonly storageHash: string } {\n return { storageHash: this.describe().to };\n }\n}\n\n/**\n * Returns true when `import.meta.url` resolves to the same file that was\n * invoked as the node entrypoint (`process.argv[1]`). Used by\n * `MigrationCLI.run` (in `@prisma-next/cli/migration-cli`) to no-op when\n * the migration module is being imported (e.g. by another script) rather\n * than executed directly.\n */\nexport function isDirectEntrypoint(importMetaUrl: string): boolean {\n const metaFilename = fileURLToPath(importMetaUrl);\n const argv1 = process.argv[1];\n if (!argv1) return false;\n try {\n return realpathSync(metaFilename) === realpathSync(argv1);\n } catch {\n return false;\n }\n}\n\n/**\n * In-memory artifacts produced from a `Migration` instance: the\n * serialized `ops.json` body, the `migration.json` metadata object, and\n * its serialized form. Returned by `buildMigrationArtifacts` so callers\n * (today: `MigrationCLI.run` in `@prisma-next/cli/migration-cli`) can\n * decide how to persist them — write to disk, print in dry-run, ship\n * over the wire — without coupling artifact construction to file I/O.\n *\n * `metadataJson` is `JSON.stringify(metadata, null, 2)` — the canonical\n * on-disk shape that the arktype loader-schema in `./io` validates.\n */\nexport interface MigrationArtifacts {\n readonly opsJson: string;\n readonly metadata: MigrationMetadata;\n readonly metadataJson: string;\n}\n\n/**\n * Build the attested metadata from `describe()`-derived metadata, the\n * operations list, and the previously-scaffolded metadata (if any).\n *\n * When a `migration.json` already exists for this package (the common\n * case: it was scaffolded by `migration plan`), preserve the contract\n * bookends, hints, labels, and `createdAt` set there — those fields are\n * owned by the CLI scaffolder, not the authored class. Only the\n * `describe()`-derived fields (`from`, `to`) and the operations\n * change as the author iterates. When no metadata exists yet (a bare\n * `migration.ts` run from scratch), synthesize a minimal but\n * schema-conformant record so the resulting package can still be read,\n * verified, and applied.\n *\n * The `migrationHash` is recomputed against the current metadata + ops so\n * the on-disk artifacts are always fully attested.\n */\nfunction buildAttestedMetadata(\n meta: MigrationMeta,\n ops: MigrationOps,\n existing: Partial<MigrationMetadata> | null,\n): MigrationMetadata {\n assertBookendsMatchMeta(meta, existing);\n\n const baseMetadata: Omit<MigrationMetadata, 'migrationHash'> = {\n from: meta.from,\n to: meta.to,\n labels: meta.labels ?? existing?.labels ?? [],\n providedInvariants: deriveProvidedInvariants(ops),\n createdAt: existing?.createdAt ?? new Date().toISOString(),\n fromContract: existing?.fromContract ?? null,\n // When no scaffolded metadata exists we synthesize a minimal contract\n // stub so the package is still readable end-to-end. The cast is\n // intentional: only the storage bookend matters for hash computation\n // (everything else is stripped by `computeMigrationHash`), and a real\n // contract bookend would only be available after `migration plan`.\n toContract: existing?.toContract ?? ({ storage: { storageHash: meta.to } } as Contract),\n hints: normalizeHints(existing?.hints),\n };\n\n const migrationHash = computeMigrationHash(baseMetadata, ops);\n return { ...baseMetadata, migrationHash };\n}\n\n/**\n * Verify each preserved contract bookend in `existing` agrees with the\n * corresponding side of `describe()`'s output. A mismatch indicates the\n * migration's `describe()` was edited after `migration plan` scaffolded\n * the package, leaving a self-inconsistent manifest. Failing fast at\n * write-time turns a silent foot-gun into an actionable diagnostic.\n *\n * Skipped when a side's `existing.<side>Contract` is null/absent (the\n * synthesis path stays open for origin-less initial migrations and for\n * bare `migration.ts` runs from scratch). When a bookend is *present*\n * but its `storage.storageHash` is missing, that's treated as a\n * mismatch — a malformed bookend is not equivalent to \"no bookend\".\n *\n * This check is paired with TML-2274, which removes `fromContract` /\n * `toContract` from the manifest entirely; once that lands, this\n * function and its error code are deleted.\n */\nfunction assertBookendsMatchMeta(\n meta: MigrationMeta,\n existing: Partial<MigrationMetadata> | null,\n): void {\n if (existing?.fromContract != null) {\n const contractHash = existing.fromContract.storage?.storageHash ?? '';\n if (contractHash !== meta.from) {\n throw errorStaleContractBookends({\n side: 'from',\n metaHash: meta.from,\n contractHash,\n });\n }\n }\n if (existing?.toContract != null) {\n const contractHash = existing.toContract.storage?.storageHash ?? '';\n if (contractHash !== meta.to) {\n throw errorStaleContractBookends({\n side: 'to',\n metaHash: meta.to,\n contractHash,\n });\n }\n }\n}\n\n/**\n * Project `existing.hints` down to the known `MigrationHints` shape, dropping\n * any legacy keys that may linger in metadata scaffolded by older CLI\n * versions (e.g. `planningStrategy`). Picking fields explicitly instead of\n * spreading keeps refreshed `migration.json` files schema-clean regardless\n * of what was on disk before.\n */\nfunction normalizeHints(existing: MigrationHints | undefined): MigrationHints {\n return {\n used: existing?.used ?? [],\n applied: existing?.applied ?? [],\n plannerVersion: existing?.plannerVersion ?? '2.0.0',\n };\n}\n\n/**\n * Pure conversion from a `Migration` instance (plus the previously\n * scaffolded metadata, when one exists on disk) to the in-memory\n * artifacts that downstream tooling persists. Owns metadata validation,\n * metadata synthesis/preservation, hint normalization, and the\n * content-addressed `migrationHash` computation, but performs no file I/O\n * — callers handle reads (to source `existing`) and writes (to persist\n * `opsJson` / `metadataJson`).\n */\nexport function buildMigrationArtifacts(\n instance: Migration,\n existing: Partial<MigrationMetadata> | null,\n): MigrationArtifacts {\n const ops = instance.operations;\n if (!Array.isArray(ops)) {\n throw new Error('operations must be an array');\n }\n\n for (let index = 0; index < ops.length; index++) {\n const result = MigrationOpSchema(ops[index]);\n if (result instanceof type.errors) {\n throw errorInvalidOperationEntry(index, result.summary);\n }\n }\n\n const rawMeta: unknown = instance.describe();\n const parsed = MigrationMetaSchema(rawMeta);\n if (parsed instanceof type.errors) {\n throw new Error(`describe() returned invalid metadata: ${parsed.summary}`);\n }\n\n const metadata = buildAttestedMetadata(parsed, ops, existing);\n\n return {\n opsJson: JSON.stringify(ops, null, 2),\n metadata,\n metadataJson: JSON.stringify(metadata, null, 2),\n };\n}\n"],"mappings":";;;;;;;;AA2BA,MAAM,sBAAsB,KAAK;CAC/B,MAAM;CACN,IAAI;CACJ,WAAW,KAAK,SAAS,CAAC,OAAO;CAClC,CAAC;;;;;;;;;;AAWF,IAAsB,YAAtB,MAKA;;;;;;;;;;CAYE;CAEA,YAAY,OAA4C;EACtD,KAAK,QAAQ;;CAkBf,IAAI,SAAkD;EACpD,MAAM,OAAO,KAAK,UAAU,CAAC;EAC7B,OAAO,SAAS,OAAO,OAAO,EAAE,aAAa,MAAM;;CAGrD,IAAI,cAAgD;EAClD,OAAO,EAAE,aAAa,KAAK,UAAU,CAAC,IAAI;;;;;;;;;;AAW9C,SAAgB,mBAAmB,eAAgC;CACjE,MAAM,eAAe,cAAc,cAAc;CACjD,MAAM,QAAQ,QAAQ,KAAK;CAC3B,IAAI,CAAC,OAAO,OAAO;CACnB,IAAI;EACF,OAAO,aAAa,aAAa,KAAK,aAAa,MAAM;SACnD;EACN,OAAO;;;;;;;;;;;;;;;;;;;;AAsCX,SAAS,sBACP,MACA,KACA,UACmB;CACnB,wBAAwB,MAAM,SAAS;CAEvC,MAAM,eAAyD;EAC7D,MAAM,KAAK;EACX,IAAI,KAAK;EACT,QAAQ,KAAK,UAAU,UAAU,UAAU,EAAE;EAC7C,oBAAoB,yBAAyB,IAAI;EACjD,WAAW,UAAU,8BAAa,IAAI,MAAM,EAAC,aAAa;EAC1D,cAAc,UAAU,gBAAgB;EAMxC,YAAY,UAAU,cAAe,EAAE,SAAS,EAAE,aAAa,KAAK,IAAI,EAAE;EAC1E,OAAO,eAAe,UAAU,MAAM;EACvC;CAED,MAAM,gBAAgB,qBAAqB,cAAc,IAAI;CAC7D,OAAO;EAAE,GAAG;EAAc;EAAe;;;;;;;;;;;;;;;;;;;AAoB3C,SAAS,wBACP,MACA,UACM;CACN,IAAI,UAAU,gBAAgB,MAAM;EAClC,MAAM,eAAe,SAAS,aAAa,SAAS,eAAe;EACnE,IAAI,iBAAiB,KAAK,MACxB,MAAM,2BAA2B;GAC/B,MAAM;GACN,UAAU,KAAK;GACf;GACD,CAAC;;CAGN,IAAI,UAAU,cAAc,MAAM;EAChC,MAAM,eAAe,SAAS,WAAW,SAAS,eAAe;EACjE,IAAI,iBAAiB,KAAK,IACxB,MAAM,2BAA2B;GAC/B,MAAM;GACN,UAAU,KAAK;GACf;GACD,CAAC;;;;;;;;;;AAYR,SAAS,eAAe,UAAsD;CAC5E,OAAO;EACL,MAAM,UAAU,QAAQ,EAAE;EAC1B,SAAS,UAAU,WAAW,EAAE;EAChC,gBAAgB,UAAU,kBAAkB;EAC7C;;;;;;;;;;;AAYH,SAAgB,wBACd,UACA,UACoB;CACpB,MAAM,MAAM,SAAS;CACrB,IAAI,CAAC,MAAM,QAAQ,IAAI,EACrB,MAAM,IAAI,MAAM,8BAA8B;CAGhD,KAAK,IAAI,QAAQ,GAAG,QAAQ,IAAI,QAAQ,SAAS;EAC/C,MAAM,SAAS,kBAAkB,IAAI,OAAO;EAC5C,IAAI,kBAAkB,KAAK,QACzB,MAAM,2BAA2B,OAAO,OAAO,QAAQ;;CAK3D,MAAM,SAAS,oBADU,SAAS,UACQ,CAAC;CAC3C,IAAI,kBAAkB,KAAK,QACzB,MAAM,IAAI,MAAM,yCAAyC,OAAO,UAAU;CAG5E,MAAM,WAAW,sBAAsB,QAAQ,KAAK,SAAS;CAE7D,OAAO;EACL,SAAS,KAAK,UAAU,KAAK,MAAM,EAAE;EACrC;EACA,cAAc,KAAK,UAAU,UAAU,MAAM,EAAE;EAChD"}
1
+ {"version":3,"file":"migration.mjs","names":[],"sources":["../../src/migration-base.ts"],"sourcesContent":["import { realpathSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport type {\n ControlStack,\n MigrationPlan,\n MigrationPlanOperation,\n} from '@prisma-next/framework-components/control';\nimport { type } from 'arktype';\nimport { errorInvalidOperationEntry } from './errors';\nimport { computeMigrationHash } from './hash';\nimport { deriveProvidedInvariants } from './invariants';\nimport type { MigrationHints, MigrationMetadata } from './metadata';\nimport { MigrationOpSchema } from './op-schema';\nimport type { MigrationOps } from './package';\n\nexport interface MigrationMeta {\n readonly from: string | null;\n readonly to: string;\n readonly labels?: readonly string[];\n}\n\n// `from` rejects empty strings to mirror `MigrationMetadataSchema` in\n// `./io.ts`. Without this match, an authored migration could `describe()` with\n// `from: ''` and pass `buildMigrationArtifacts`'s validation, only to have\n// `readMigrationPackage` reject the resulting `migration.json` later — the\n// two validators must agree on the legal value space.\nconst MigrationMetaSchema = type({\n from: 'string > 0 | null',\n to: 'string',\n 'labels?': type('string').array(),\n});\n\n/**\n * Base class for migrations.\n *\n * A `Migration` subclass is itself a `MigrationPlan`: CLI commands and the\n * runner can consume it directly via `targetId`, `operations`, `origin`, and\n * `destination`. The metadata-shaped inputs come from `describe()`, which\n * every migration must implement — `migration.json` is required for a\n * migration to be valid.\n */\nexport abstract class Migration<\n TOperation extends MigrationPlanOperation = MigrationPlanOperation,\n TFamilyId extends string = string,\n TTargetId extends string = string,\n> implements MigrationPlan\n{\n abstract readonly targetId: string;\n\n /**\n * Assembled `ControlStack` injected by the orchestrator (`runMigration`).\n *\n * Subclasses (e.g. `PostgresMigration`) read the stack to materialize their\n * adapter once per instance. Optional at the abstract level so unit tests can\n * construct `Migration` instances purely for `operations` / `describe`\n * assertions without needing a real stack; concrete subclasses that need the\n * stack at runtime should narrow the parameter to required.\n */\n protected readonly stack: ControlStack<TFamilyId, TTargetId> | undefined;\n\n constructor(stack?: ControlStack<TFamilyId, TTargetId>) {\n this.stack = stack;\n }\n\n /**\n * Ordered list of operations this migration performs.\n *\n * Implemented as a getter so that subclasses can either precompute the list\n * in their constructor or build it lazily per access.\n */\n abstract get operations(): readonly TOperation[];\n\n /**\n * Metadata inputs used to build `migration.json` and to derive the plan's\n * origin/destination identities. Every migration must provide this —\n * omitting it would produce an invalid on-disk migration package.\n */\n abstract describe(): MigrationMeta;\n\n get origin(): { readonly storageHash: string } | null {\n const from = this.describe().from;\n return from === null ? null : { storageHash: from };\n }\n\n get destination(): { readonly storageHash: string } {\n return { storageHash: this.describe().to };\n }\n}\n\n/**\n * Returns true when `import.meta.url` resolves to the same file that was\n * invoked as the node entrypoint (`process.argv[1]`). Used by\n * `MigrationCLI.run` (in `@prisma-next/cli/migration-cli`) to no-op when\n * the migration module is being imported (e.g. by another script) rather\n * than executed directly.\n */\nexport function isDirectEntrypoint(importMetaUrl: string): boolean {\n const metaFilename = fileURLToPath(importMetaUrl);\n const argv1 = process.argv[1];\n if (!argv1) return false;\n try {\n return realpathSync(metaFilename) === realpathSync(argv1);\n } catch {\n return false;\n }\n}\n\n/**\n * In-memory artifacts produced from a `Migration` instance: the\n * serialized `ops.json` body, the `migration.json` metadata object, and\n * its serialized form. Returned by `buildMigrationArtifacts` so callers\n * (today: `MigrationCLI.run` in `@prisma-next/cli/migration-cli`) can\n * decide how to persist them — write to disk, print in dry-run, ship\n * over the wire — without coupling artifact construction to file I/O.\n *\n * `metadataJson` is `JSON.stringify(metadata, null, 2)` — the canonical\n * on-disk shape that the arktype loader-schema in `./io` validates.\n */\nexport interface MigrationArtifacts {\n readonly opsJson: string;\n readonly metadata: MigrationMetadata;\n readonly metadataJson: string;\n}\n\n/**\n * Build the attested metadata from `describe()`-derived metadata, the\n * operations list, and the previously-scaffolded metadata (if any).\n *\n * When a `migration.json` already exists for this package (the common\n * case: it was scaffolded by `migration plan`), preserve the contract\n * bookends, hints, labels, and `createdAt` set there — those fields are\n * owned by the CLI scaffolder, not the authored class. Only the\n * `describe()`-derived fields (`from`, `to`) and the operations\n * change as the author iterates. When no metadata exists yet (a bare\n * `migration.ts` run from scratch), synthesize a minimal but\n * schema-conformant record so the resulting package can still be read,\n * verified, and applied.\n *\n * The `migrationHash` is recomputed against the current metadata + ops so\n * the on-disk artifacts are always fully attested.\n */\nfunction buildAttestedMetadata(\n meta: MigrationMeta,\n ops: MigrationOps,\n existing: Partial<MigrationMetadata> | null,\n): MigrationMetadata {\n const baseMetadata: Omit<MigrationMetadata, 'migrationHash'> = {\n from: meta.from,\n to: meta.to,\n labels: meta.labels ?? existing?.labels ?? [],\n providedInvariants: deriveProvidedInvariants(ops),\n createdAt: existing?.createdAt ?? new Date().toISOString(),\n hints: normalizeHints(existing?.hints),\n };\n\n const migrationHash = computeMigrationHash(baseMetadata, ops);\n return { ...baseMetadata, migrationHash };\n}\n\n/**\n * Project `existing.hints` down to the known `MigrationHints` shape, dropping\n * any legacy keys that may linger in metadata scaffolded by older CLI\n * versions (e.g. `planningStrategy`). Picking fields explicitly instead of\n * spreading keeps refreshed `migration.json` files schema-clean regardless\n * of what was on disk before.\n */\nfunction normalizeHints(existing: MigrationHints | undefined): MigrationHints {\n return {\n used: existing?.used ?? [],\n applied: existing?.applied ?? [],\n plannerVersion: existing?.plannerVersion ?? '2.0.0',\n };\n}\n\n/**\n * Pure conversion from a `Migration` instance (plus the previously\n * scaffolded metadata, when one exists on disk) to the in-memory\n * artifacts that downstream tooling persists. Owns metadata validation,\n * metadata synthesis/preservation, hint normalization, and the\n * content-addressed `migrationHash` computation, but performs no file I/O\n * — callers handle reads (to source `existing`) and writes (to persist\n * `opsJson` / `metadataJson`).\n */\nexport function buildMigrationArtifacts(\n instance: Migration,\n existing: Partial<MigrationMetadata> | null,\n): MigrationArtifacts {\n const ops = instance.operations;\n if (!Array.isArray(ops)) {\n throw new Error('operations must be an array');\n }\n\n for (let index = 0; index < ops.length; index++) {\n const result = MigrationOpSchema(ops[index]);\n if (result instanceof type.errors) {\n throw errorInvalidOperationEntry(index, result.summary);\n }\n }\n\n const rawMeta: unknown = instance.describe();\n const parsed = MigrationMetaSchema(rawMeta);\n if (parsed instanceof type.errors) {\n throw new Error(`describe() returned invalid metadata: ${parsed.summary}`);\n }\n\n const metadata = buildAttestedMetadata(parsed, ops, existing);\n\n return {\n opsJson: JSON.stringify(ops, null, 2),\n metadata,\n metadataJson: JSON.stringify(metadata, null, 2),\n };\n}\n"],"mappings":";;;;;;;;AA0BA,MAAM,sBAAsB,KAAK;CAC/B,MAAM;CACN,IAAI;CACJ,WAAW,KAAK,SAAS,CAAC,OAAO;CAClC,CAAC;;;;;;;;;;AAWF,IAAsB,YAAtB,MAKA;;;;;;;;;;CAYE;CAEA,YAAY,OAA4C;EACtD,KAAK,QAAQ;;CAkBf,IAAI,SAAkD;EACpD,MAAM,OAAO,KAAK,UAAU,CAAC;EAC7B,OAAO,SAAS,OAAO,OAAO,EAAE,aAAa,MAAM;;CAGrD,IAAI,cAAgD;EAClD,OAAO,EAAE,aAAa,KAAK,UAAU,CAAC,IAAI;;;;;;;;;;AAW9C,SAAgB,mBAAmB,eAAgC;CACjE,MAAM,eAAe,cAAc,cAAc;CACjD,MAAM,QAAQ,QAAQ,KAAK;CAC3B,IAAI,CAAC,OAAO,OAAO;CACnB,IAAI;EACF,OAAO,aAAa,aAAa,KAAK,aAAa,MAAM;SACnD;EACN,OAAO;;;;;;;;;;;;;;;;;;;;AAsCX,SAAS,sBACP,MACA,KACA,UACmB;CACnB,MAAM,eAAyD;EAC7D,MAAM,KAAK;EACX,IAAI,KAAK;EACT,QAAQ,KAAK,UAAU,UAAU,UAAU,EAAE;EAC7C,oBAAoB,yBAAyB,IAAI;EACjD,WAAW,UAAU,8BAAa,IAAI,MAAM,EAAC,aAAa;EAC1D,OAAO,eAAe,UAAU,MAAM;EACvC;CAED,MAAM,gBAAgB,qBAAqB,cAAc,IAAI;CAC7D,OAAO;EAAE,GAAG;EAAc;EAAe;;;;;;;;;AAU3C,SAAS,eAAe,UAAsD;CAC5E,OAAO;EACL,MAAM,UAAU,QAAQ,EAAE;EAC1B,SAAS,UAAU,WAAW,EAAE;EAChC,gBAAgB,UAAU,kBAAkB;EAC7C;;;;;;;;;;;AAYH,SAAgB,wBACd,UACA,UACoB;CACpB,MAAM,MAAM,SAAS;CACrB,IAAI,CAAC,MAAM,QAAQ,IAAI,EACrB,MAAM,IAAI,MAAM,8BAA8B;CAGhD,KAAK,IAAI,QAAQ,GAAG,QAAQ,IAAI,QAAQ,SAAS;EAC/C,MAAM,SAAS,kBAAkB,IAAI,OAAO;EAC5C,IAAI,kBAAkB,KAAK,QACzB,MAAM,2BAA2B,OAAO,OAAO,QAAQ;;CAK3D,MAAM,SAAS,oBADU,SAAS,UACQ,CAAC;CAC3C,IAAI,kBAAkB,KAAK,QACzB,MAAM,IAAI,MAAM,yCAAyC,OAAO,UAAU;CAG5E,MAAM,WAAW,sBAAsB,QAAQ,KAAK,SAAS;CAE7D,OAAO;EACL,SAAS,KAAK,UAAU,KAAK,MAAM,EAAE;EACrC;EACA,cAAc,KAAK,UAAU,UAAU,MAAM,EAAE;EAChD"}
@@ -1,4 +1,4 @@
1
- import { h as errorInvalidRefValue, m as errorInvalidRefName, p as errorInvalidRefFile, t as MigrationToolsError } from "../errors-EPL_9p9f.mjs";
1
+ import { h as errorInvalidRefValue, m as errorInvalidRefName, p as errorInvalidRefFile, t as MigrationToolsError } from "../errors-DqMhmTPQ.mjs";
2
2
  import { dirname, join, relative } from "pathe";
3
3
  import { mkdir, readFile, readdir, rename, rmdir, unlink, writeFile } from "node:fs/promises";
4
4
  import { type } from "arktype";
@@ -1,8 +1,8 @@
1
- import { r as errorDescriptorHeadHashMismatch, s as errorDuplicateSpaceId } from "../errors-EPL_9p9f.mjs";
2
- import { s as readMigrationsDir } from "../io-Dw620b51.mjs";
1
+ import { r as errorDescriptorHeadHashMismatch, s as errorDuplicateSpaceId } from "../errors-DqMhmTPQ.mjs";
2
+ import { s as readMigrationsDir } from "../io-Zxj9kQwp.mjs";
3
3
  import "../constants-DWV9_o2Z.mjs";
4
- import { l as reconstructGraph, o as findPathWithDecision } from "../migration-graph-DGNnKDY5.mjs";
5
- import { a as APP_SPACE_ID, c as spaceMigrationDirectory, i as readContractSpaceHeadRef, n as listContractSpaceDirectories, o as assertValidSpaceId, r as verifyContractSpaces, s as isValidSpaceId, t as readContractSpaceContract } from "../read-contract-space-contract-COyz4tZn.mjs";
4
+ import { l as reconstructGraph, o as findPathWithDecision } from "../migration-graph-NGymYL9J.mjs";
5
+ import { a as APP_SPACE_ID, c as spaceMigrationDirectory, i as readContractSpaceHeadRef, n as listContractSpaceDirectories, o as assertValidSpaceId, r as verifyContractSpaces, s as isValidSpaceId, t as readContractSpaceContract } from "../read-contract-space-contract-nZRrktnx.mjs";
6
6
  import { join } from "pathe";
7
7
  import { mkdir, writeFile } from "node:fs/promises";
8
8
  import { canonicalizeJson } from "@prisma-next/framework-components/utils";
@@ -1,15 +1,17 @@
1
- import { canonicalizeJson } from "@prisma-next/framework-components/utils";
2
1
  import { createHash } from "node:crypto";
2
+ import { canonicalizeJson } from "@prisma-next/framework-components/utils";
3
3
  //#region src/hash.ts
4
4
  function sha256Hex(input) {
5
5
  return createHash("sha256").update(input).digest("hex");
6
6
  }
7
7
  /**
8
- * Content-addressed migration hash over (metadata envelope sans
9
- * contracts/hints, ops). See ADR 199 — Storage-only migration identity
10
- * for the rationale: contracts are anchored separately by the
11
- * storage-hash bookends inside the envelope; planner hints are advisory
12
- * and must not affect identity.
8
+ * Content-addressed migration hash over (metadata envelope sans hints,
9
+ * ops). See ADR 199 — Storage-only migration identity for the
10
+ * rationale: the storage-hash bookends (`from`, `to`) inside the
11
+ * envelope anchor the contract identity by hash, and planner hints are
12
+ * advisory and must not affect identity. The full contract IRs are not
13
+ * part of the manifest — they live in sibling `*-contract.json` files
14
+ * authored alongside the migration, never inlined here.
13
15
  *
14
16
  * The integrity check is purely structural, not semantic. The function
15
17
  * canonicalizes its inputs via `sortKeys` (recursive) + `JSON.stringify`
@@ -31,7 +33,7 @@ function sha256Hex(input) {
31
33
  * yet) and at verify time (rehashing an already-attested record).
32
34
  */
33
35
  function computeMigrationHash(metadata, ops) {
34
- const { migrationHash: _migrationHash, fromContract: _fromContract, toContract: _toContract, hints: _hints, ...strippedMeta } = metadata;
36
+ const { migrationHash: _migrationHash, hints: _hints, ...strippedMeta } = metadata;
35
37
  return `sha256:${sha256Hex(canonicalizeJson([canonicalizeJson(strippedMeta), canonicalizeJson(ops)].map(sha256Hex)))}`;
36
38
  }
37
39
  /**
@@ -60,4 +62,4 @@ function verifyMigrationHash(pkg) {
60
62
  //#endregion
61
63
  export { verifyMigrationHash as n, computeMigrationHash as t };
62
64
 
63
- //# sourceMappingURL=hash-C6bpZczT.mjs.map
65
+ //# sourceMappingURL=hash-Cr4WIr4Z.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash-Cr4WIr4Z.mjs","names":[],"sources":["../src/hash.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { canonicalizeJson } from '@prisma-next/framework-components/utils';\nimport type { MigrationMetadata } from './metadata';\nimport type { MigrationOps, OnDiskMigrationPackage } from './package';\n\nexport interface VerifyResult {\n readonly ok: boolean;\n readonly reason?: 'mismatch';\n readonly storedHash: string;\n readonly computedHash: string;\n}\n\nfunction sha256Hex(input: string): string {\n return createHash('sha256').update(input).digest('hex');\n}\n\n/**\n * Content-addressed migration hash over (metadata envelope sans hints,\n * ops). See ADR 199 — Storage-only migration identity for the\n * rationale: the storage-hash bookends (`from`, `to`) inside the\n * envelope anchor the contract identity by hash, and planner hints are\n * advisory and must not affect identity. The full contract IRs are not\n * part of the manifest — they live in sibling `*-contract.json` files\n * authored alongside the migration, never inlined here.\n *\n * The integrity check is purely structural, not semantic. The function\n * canonicalizes its inputs via `sortKeys` (recursive) + `JSON.stringify`\n * and hashes the result. Target-specific operation payloads (`step.sql`,\n * Mongo's pipeline AST, …) are hashed verbatim — no per-target\n * normalization is required, because what's being verified is \"do the\n * on-disk bytes still produce their recorded hash\", not \"do two\n * semantically-equivalent migrations hash the same\". The latter is an\n * emit-drift concern (ADR 192 step 2).\n *\n * The symmetry across write and read holds because `JSON.parse(\n * JSON.stringify(x))` round-trips JSON-safe values losslessly and\n * `sortKeys` is idempotent and deterministic — write-time and read-time\n * canonicalization produce the same canonical bytes regardless of\n * source-side key ordering or whitespace.\n *\n * The `migrationHash` field on the metadata is stripped before hashing\n * so the function can be used both at write time (when no hash exists\n * yet) and at verify time (rehashing an already-attested record).\n */\nexport function computeMigrationHash(\n metadata: Omit<MigrationMetadata, 'migrationHash'> & { readonly migrationHash?: string },\n ops: MigrationOps,\n): string {\n const { migrationHash: _migrationHash, hints: _hints, ...strippedMeta } = metadata;\n\n const canonicalMetadata = canonicalizeJson(strippedMeta);\n const canonicalOps = canonicalizeJson(ops);\n\n const partHashes = [canonicalMetadata, canonicalOps].map(sha256Hex);\n const hash = sha256Hex(canonicalizeJson(partHashes));\n\n return `sha256:${hash}`;\n}\n\n/**\n * Re-hash an in-memory migration package and compare against the stored\n * `migrationHash`. See `computeMigrationHash` for the canonicalization rules.\n *\n * Returns `{ ok: true }` when the package is internally consistent, or\n * `{ ok: false, reason: 'mismatch', storedHash, computedHash }` when it is\n * not — typically a sign of FS corruption, partial writes, or a post-emit\n * hand edit.\n */\nexport function verifyMigrationHash(pkg: OnDiskMigrationPackage): VerifyResult {\n const computed = computeMigrationHash(pkg.metadata, pkg.ops);\n\n if (pkg.metadata.migrationHash === computed) {\n return {\n ok: true,\n storedHash: pkg.metadata.migrationHash,\n computedHash: computed,\n };\n }\n\n return {\n ok: false,\n reason: 'mismatch',\n storedHash: pkg.metadata.migrationHash,\n computedHash: computed,\n };\n}\n"],"mappings":";;;AAYA,SAAS,UAAU,OAAuB;CACxC,OAAO,WAAW,SAAS,CAAC,OAAO,MAAM,CAAC,OAAO,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BzD,SAAgB,qBACd,UACA,KACQ;CACR,MAAM,EAAE,eAAe,gBAAgB,OAAO,QAAQ,GAAG,iBAAiB;CAQ1E,OAAO,UAFM,UAAU,iBADJ,CAHO,iBAAiB,aAGN,EAFhB,iBAAiB,IAEa,CAAC,CAAC,IAAI,UACP,CAAC,CAE9B;;;;;;;;;;;AAYvB,SAAgB,oBAAoB,KAA2C;CAC7E,MAAM,WAAW,qBAAqB,IAAI,UAAU,IAAI,IAAI;CAE5D,IAAI,IAAI,SAAS,kBAAkB,UACjC,OAAO;EACL,IAAI;EACJ,YAAY,IAAI,SAAS;EACzB,cAAc;EACf;CAGH,OAAO;EACL,IAAI;EACJ,QAAQ;EACR,YAAY,IAAI,SAAS;EACzB,cAAc;EACf"}
@@ -1,4 +1,4 @@
1
- import { a as errorDuplicateInvariantInEdge, l as errorInvalidInvariantId } from "./errors-EPL_9p9f.mjs";
1
+ import { a as errorDuplicateInvariantInEdge, l as errorInvalidInvariantId } from "./errors-DqMhmTPQ.mjs";
2
2
  //#region src/invariants.ts
3
3
  /**
4
4
  * Hygiene check for `invariantId`. Rejects empty values plus any
@@ -54,4 +54,4 @@ function readInvariantId(op) {
54
54
  //#endregion
55
55
  export { validateInvariantId as n, deriveProvidedInvariants as t };
56
56
 
57
- //# sourceMappingURL=invariants-qgQGlsrV.mjs.map
57
+ //# sourceMappingURL=invariants-DItbPp4w.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"invariants-qgQGlsrV.mjs","names":[],"sources":["../src/invariants.ts"],"sourcesContent":["import type { MigrationPlanOperation } from '@prisma-next/framework-components/control';\nimport { errorDuplicateInvariantInEdge, errorInvalidInvariantId } from './errors';\nimport type { MigrationOps } from './package';\n\n/**\n * Hygiene check for `invariantId`. Rejects empty values plus any\n * whitespace or control character (including Unicode whitespace like\n * NBSP and em space, which are visually identical to ASCII space and\n * routinely sneak in via paste).\n */\nexport function validateInvariantId(invariantId: string): boolean {\n if (invariantId.length === 0) return false;\n return !/[\\p{Cc}\\p{White_Space}]/u.test(invariantId);\n}\n\n/**\n * Walk a migration's operations and produce its `providedInvariants`\n * aggregate: the sorted, deduplicated list of `invariantId`s declared\n * by ops in the migration. Ops without an `invariantId` are skipped.\n *\n * Both `data`-class ops (data-transforms, e.g. backfills) and\n * `additive`-class opaque DDL (e.g. cipherstash's vendored EQL bundle\n * via `installEqlBundleOp`) may declare invariantIds: the\n * `operationClass` axis classifies *policy gating* (which kinds of ops\n * a `db init` / `db update` policy permits), while `invariantId`\n * classifies *marker bookkeeping* (which named bundles of work a\n * future regeneration knows to skip). The two concerns are\n * intentionally orthogonal — an extension can ship additive\n * non-IR-derivable DDL (the only way the planner can know the bundle\n * is already applied is via the invariantId on the marker) without\n * needing to mis-classify it as `data`-class.\n *\n * Throws `MIGRATION.INVALID_INVARIANT_ID` on a malformed id and\n * `MIGRATION.DUPLICATE_INVARIANT_IN_EDGE` on duplicates.\n *\n * @see docs/architecture docs/adrs/ADR 212 - Contract spaces.md\n * — extension migrations carry `invariantId`s on additive ops; e.g.\n * cipherstash's `installEqlBundle` and structural `create-*` ops are\n * additive-class but carry `cipherstash:*` invariantIds.\n */\nexport function deriveProvidedInvariants(ops: MigrationOps): readonly string[] {\n const seen = new Set<string>();\n for (const op of ops) {\n const invariantId = readInvariantId(op);\n if (invariantId === undefined) continue;\n if (!validateInvariantId(invariantId)) {\n throw errorInvalidInvariantId(invariantId);\n }\n if (seen.has(invariantId)) {\n throw errorDuplicateInvariantInEdge(invariantId);\n }\n seen.add(invariantId);\n }\n return [...seen].sort();\n}\n\nfunction readInvariantId(op: MigrationPlanOperation): string | undefined {\n if (!Object.hasOwn(op, 'invariantId')) return undefined;\n const candidate = (op as { invariantId?: unknown }).invariantId;\n return typeof candidate === 'string' ? candidate : undefined;\n}\n"],"mappings":";;;;;;;;AAUA,SAAgB,oBAAoB,aAA8B;CAChE,IAAI,YAAY,WAAW,GAAG,OAAO;CACrC,OAAO,CAAC,2BAA2B,KAAK,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BtD,SAAgB,yBAAyB,KAAsC;CAC7E,MAAM,uBAAO,IAAI,KAAa;CAC9B,KAAK,MAAM,MAAM,KAAK;EACpB,MAAM,cAAc,gBAAgB,GAAG;EACvC,IAAI,gBAAgB,KAAA,GAAW;EAC/B,IAAI,CAAC,oBAAoB,YAAY,EACnC,MAAM,wBAAwB,YAAY;EAE5C,IAAI,KAAK,IAAI,YAAY,EACvB,MAAM,8BAA8B,YAAY;EAElD,KAAK,IAAI,YAAY;;CAEvB,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM;;AAGzB,SAAS,gBAAgB,IAAgD;CACvE,IAAI,CAAC,OAAO,OAAO,IAAI,cAAc,EAAE,OAAO,KAAA;CAC9C,MAAM,YAAa,GAAiC;CACpD,OAAO,OAAO,cAAc,WAAW,YAAY,KAAA"}
1
+ {"version":3,"file":"invariants-DItbPp4w.mjs","names":[],"sources":["../src/invariants.ts"],"sourcesContent":["import type { MigrationPlanOperation } from '@prisma-next/framework-components/control';\nimport { errorDuplicateInvariantInEdge, errorInvalidInvariantId } from './errors';\nimport type { MigrationOps } from './package';\n\n/**\n * Hygiene check for `invariantId`. Rejects empty values plus any\n * whitespace or control character (including Unicode whitespace like\n * NBSP and em space, which are visually identical to ASCII space and\n * routinely sneak in via paste).\n */\nexport function validateInvariantId(invariantId: string): boolean {\n if (invariantId.length === 0) return false;\n return !/[\\p{Cc}\\p{White_Space}]/u.test(invariantId);\n}\n\n/**\n * Walk a migration's operations and produce its `providedInvariants`\n * aggregate: the sorted, deduplicated list of `invariantId`s declared\n * by ops in the migration. Ops without an `invariantId` are skipped.\n *\n * Both `data`-class ops (data-transforms, e.g. backfills) and\n * `additive`-class opaque DDL (e.g. cipherstash's vendored EQL bundle\n * via `installEqlBundleOp`) may declare invariantIds: the\n * `operationClass` axis classifies *policy gating* (which kinds of ops\n * a `db init` / `db update` policy permits), while `invariantId`\n * classifies *marker bookkeeping* (which named bundles of work a\n * future regeneration knows to skip). The two concerns are\n * intentionally orthogonal — an extension can ship additive\n * non-IR-derivable DDL (the only way the planner can know the bundle\n * is already applied is via the invariantId on the marker) without\n * needing to mis-classify it as `data`-class.\n *\n * Throws `MIGRATION.INVALID_INVARIANT_ID` on a malformed id and\n * `MIGRATION.DUPLICATE_INVARIANT_IN_EDGE` on duplicates.\n *\n * @see docs/architecture docs/adrs/ADR 212 - Contract spaces.md\n * — extension migrations carry `invariantId`s on additive ops; e.g.\n * cipherstash's `installEqlBundle` and structural `create-*` ops are\n * additive-class but carry `cipherstash:*` invariantIds.\n */\nexport function deriveProvidedInvariants(ops: MigrationOps): readonly string[] {\n const seen = new Set<string>();\n for (const op of ops) {\n const invariantId = readInvariantId(op);\n if (invariantId === undefined) continue;\n if (!validateInvariantId(invariantId)) {\n throw errorInvalidInvariantId(invariantId);\n }\n if (seen.has(invariantId)) {\n throw errorDuplicateInvariantInEdge(invariantId);\n }\n seen.add(invariantId);\n }\n return [...seen].sort();\n}\n\nfunction readInvariantId(op: MigrationPlanOperation): string | undefined {\n if (!Object.hasOwn(op, 'invariantId')) return undefined;\n const candidate = (op as { invariantId?: unknown }).invariantId;\n return typeof candidate === 'string' ? candidate : undefined;\n}\n"],"mappings":";;;;;;;;AAUA,SAAgB,oBAAoB,aAA8B;CAChE,IAAI,YAAY,WAAW,GAAG,OAAO;CACrC,OAAO,CAAC,2BAA2B,KAAK,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BtD,SAAgB,yBAAyB,KAAsC;CAC7E,MAAM,uBAAO,IAAI,KAAa;CAC9B,KAAK,MAAM,MAAM,KAAK;EACpB,MAAM,cAAc,gBAAgB,GAAG;EACvC,IAAI,gBAAgB,KAAA,GAAW;EAC/B,IAAI,CAAC,oBAAoB,YAAY,EACnC,MAAM,wBAAwB,YAAY;EAE5C,IAAI,KAAK,IAAI,YAAY,EACvB,MAAM,8BAA8B,YAAY;EAElD,KAAK,IAAI,YAAY;;CAEvB,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM;;AAGzB,SAAS,gBAAgB,IAAgD;CACvE,IAAI,CAAC,OAAO,OAAO,IAAI,cAAc,EAAE,OAAO,KAAA;CAC9C,MAAM,YAAa,GAAiC;CACpD,OAAO,OAAO,cAAc,WAAW,YAAY,KAAA"}
@@ -1,10 +1,9 @@
1
- import { C as errorProvidedInvariantsMismatch, c as errorInvalidDestName, d as errorInvalidManifest, g as errorInvalidSlug, i as errorDirectoryExists, u as errorInvalidJson, v as errorMigrationHashMismatch, y as errorMissingFile } from "./errors-EPL_9p9f.mjs";
2
- import { n as verifyMigrationHash } from "./hash-C6bpZczT.mjs";
3
- import { t as deriveProvidedInvariants } from "./invariants-qgQGlsrV.mjs";
1
+ import { C as errorProvidedInvariantsMismatch, c as errorInvalidDestName, d as errorInvalidManifest, g as errorInvalidSlug, i as errorDirectoryExists, u as errorInvalidJson, v as errorMigrationHashMismatch, y as errorMissingFile } from "./errors-DqMhmTPQ.mjs";
2
+ import { n as verifyMigrationHash } from "./hash-Cr4WIr4Z.mjs";
3
+ import { t as deriveProvidedInvariants } from "./invariants-DItbPp4w.mjs";
4
4
  import { n as MigrationOpsSchema } from "./op-schema-D5qkXfEf.mjs";
5
5
  import { basename, dirname, join, resolve } from "pathe";
6
6
  import { copyFile, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
7
- import { canonicalizeJson } from "@prisma-next/framework-components/utils";
8
7
  import { type } from "arktype";
9
8
  //#region src/io.ts
10
9
  const MANIFEST_FILE = "migration.json";
@@ -18,8 +17,6 @@ const MigrationMetadataSchema = type({
18
17
  from: "string > 0 | null",
19
18
  to: "string",
20
19
  migrationHash: "string",
21
- fromContract: "object | null",
22
- toContract: "object",
23
20
  hints: type({
24
21
  used: "string[]",
25
22
  applied: "string[]",
@@ -44,35 +41,31 @@ async function writeMigrationPackage(dir, metadata, ops) {
44
41
  * Materialise an in-memory {@link MigrationPackage} to a per-space
45
42
  * directory on disk.
46
43
  *
47
- * Writes three files under `<targetDir>/<pkg.dirName>/`:
44
+ * Writes two files under `<targetDir>/<pkg.dirName>/`:
48
45
  *
49
46
  * - `migration.json` — the manifest (pretty-printed, matches
50
47
  * {@link writeMigrationPackage}'s output for byte-for-byte parity with
51
48
  * app-space migrations).
52
49
  * - `ops.json` — the operation list (pretty-printed).
53
- * - `contract.json` — the canonical-JSON serialisation of
54
- * `metadata.toContract`. This is the per-package post-state contract
55
- * snapshot; the canonicalisation pass guarantees byte-determinism so
56
- * re-emitting the same package across machines / runs produces an
57
- * identical file.
58
50
  *
59
51
  * Distinct verb from the lower-level {@link writeMigrationPackage}
60
52
  * (which takes constituent `(metadata, ops)`): callers reading
61
- * `materialise…` know they are persisting a struct-typed package
62
- * including its contract-snapshot side car.
53
+ * `materialise…` know they are persisting a struct-typed package.
63
54
  *
64
55
  * Overwrite-idempotent: the per-package directory is cleared before
65
56
  * each emit, so re-running against the same `targetDir` produces
66
57
  * byte-identical contents and never leaves stale files behind. The
67
- * spec's "re-emitting the same package across runs / machines produces
68
- * byte-identical files" guarantee (§ 3) covers both same-dir and
69
- * fresh-dir re-emits. The lower-level {@link writeMigrationPackage}
70
- * stays strict because the CLI authoring path (`migration plan` /
71
- * `migration new`) deliberately refuses to clobber an existing
72
- * authored migration; this helper is the re-emit path that is
73
- * supposed to converge on a single canonical on-disk shape.
58
+ * lower-level {@link writeMigrationPackage} stays strict because the
59
+ * CLI authoring path (`migration plan` / `migration new`) deliberately
60
+ * refuses to clobber an existing authored migration; this helper is
61
+ * the re-emit path that is supposed to converge on a single canonical
62
+ * on-disk shape.
74
63
  *
75
- * @see specs/framework-mechanism.spec.md § 3 Emission helper (T1.7).
64
+ * The per-space head contract lives at
65
+ * `<projectMigrationsDir>/<spaceId>/contract.json` (written by
66
+ * {@link import('./emit-contract-space-artefacts').emitContractSpaceArtefacts}),
67
+ * not inside the per-package directory. The runner reads only
68
+ * `migration.json` + `ops.json` from each package.
76
69
  */
77
70
  async function materialiseMigrationPackage(targetDir, pkg) {
78
71
  const dir = join(targetDir, pkg.dirName);
@@ -81,7 +74,6 @@ async function materialiseMigrationPackage(targetDir, pkg) {
81
74
  force: true
82
75
  });
83
76
  await writeMigrationPackage(dir, pkg.metadata, pkg.ops);
84
- await writeFile(join(dir, "contract.json"), `${canonicalizeJson(pkg.metadata.toContract)}\n`, { flag: "wx" });
85
77
  }
86
78
  /**
87
79
  * Idempotent variant of {@link materialiseMigrationPackage}: writes the
@@ -229,4 +221,4 @@ function formatMigrationDirName(timestamp, slug) {
229
221
  //#endregion
230
222
  export { materialiseMigrationPackage as a, writeMigrationMetadata as c, materialiseExtensionMigrationPackageIfMissing as i, writeMigrationOps as l, copyFilesWithRename as n, readMigrationPackage as o, formatMigrationDirName as r, readMigrationsDir as s, MANIFEST_FILE as t, writeMigrationPackage as u };
231
223
 
232
- //# sourceMappingURL=io-Dw620b51.mjs.map
224
+ //# sourceMappingURL=io-Zxj9kQwp.mjs.map