{"version":3,"file":"hash-BNCgfhir.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, ops). See\n * ADR 199 — Storage-only migration identity for the rationale: the\n * storage-hash bookends (`from`, `to`) inside the envelope anchor the\n * contract identity by hash. The full contract IRs are not part of the\n * manifest — they live in sibling `*-contract.json` files authored\n * 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, ...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,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK;AACxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,qBACd,UACA,KACQ;CACR,MAAM,EAAE,eAAe,gBAAgB,GAAG,iBAAiB;CAQ3D,OAAO,UAFM,UAAU,iBADJ,CAHO,iBAAiB,YAGP,GAFf,iBAAiB,GAEY,CAAC,CAAC,CAAC,IAAI,SACR,CAAC,CAE9B;AACtB;;;;;;;;;;AAWA,SAAgB,oBAAoB,KAA2C;CAC7E,MAAM,WAAW,qBAAqB,IAAI,UAAU,IAAI,GAAG;CAE3D,IAAI,IAAI,SAAS,kBAAkB,UACjC,OAAO;EACL,IAAI;EACJ,YAAY,IAAI,SAAS;EACzB,cAAc;CAChB;CAGF,OAAO;EACL,IAAI;EACJ,QAAQ;EACR,YAAY,IAAI,SAAS;EACzB,cAAc;CAChB;AACF"}