{"version":3,"file":"platform-manifest.mjs","names":[],"sources":["../../../src/services/deploy/platform-manifest.ts"],"sourcesContent":["/**\n * The deploy-side half of the platform release manifest (ADR 0034 pt 1/2):\n * wires the framework repo's release inputs into the pure composer and shapes\n * the registry entry that rides the train's atomic FastKV write. Framework-\n * rooted deploys only — a child deploy publishes no release manifest.\n */\nimport { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { satisfiesConstraint } from \"../../utils/semver\";\nimport { walkConfigSlots } from \"../config-slots\";\nimport {\n  composePlatformReleaseManifest,\n  computeTemplateInventory,\n  getPlatformManifestKey,\n  getPlatformVersionSlotKey,\n  type PlatformReleaseManifest,\n  type ReleaseSlotRecord,\n  resolvePlatformManifest,\n  serializePlatformReleaseManifest,\n} from \"../platform/manifest\";\nimport { readWorkspaceCatalog } from \"../workspace/catalog\";\nimport { readFrameworkVersion } from \"./image\";\nimport { computeSriHash } from \"./integrity\";\n\nexport { getPlatformManifestKey, getPlatformVersionSlotKey };\n\n/** The universal image (ADR 0020/0021) — the release manifest's image ref. */\nexport const PLATFORM_IMAGE_REF = \"ghcr.io/nearbuilders/everything-dev\";\n\nexport interface ReleaseSlotRef extends ReleaseSlotRecord {\n  slot: string;\n}\n\n/**\n * The release's pinned slots: every config slot (dotted config path, keyed by\n * the same path shape the release-slot stamping writes back — `app.auth`,\n * `app.auth.ui`, `plugins.votes.ui`, …) with a production URL and an\n * immutable version-manifest pin in the release's publish payload. The\n * release leg records them in the release manifest so pinned children can\n * re-point inherited slots at these bytes.\n */\nexport function collectReleaseSlotPins(config: Record<string, unknown>): ReleaseSlotRef[] {\n  const slots: ReleaseSlotRef[] = [];\n  walkConfigSlots(config, ({ slot, entry }) => {\n    if (typeof entry !== \"object\" || entry === null) return;\n    const base = typeof entry.production === \"string\" ? entry.production : undefined;\n    const pin = entry.pin as { manifest?: unknown; integrity?: unknown } | undefined;\n    const manifest = typeof pin?.manifest === \"string\" ? pin.manifest : undefined;\n    const integrity = typeof pin?.integrity === \"string\" ? pin.integrity : undefined;\n    if (base && manifest && integrity) {\n      slots.push({ slot, base, pin: { manifest, integrity } });\n    }\n  });\n  return slots;\n}\n\n/** The `Record<slot, ReleaseSlotRecord>` shape the manifest schema stores. */\nexport function releaseSlotMap(config: Record<string, unknown>): Record<string, ReleaseSlotRecord> {\n  return Object.fromEntries(\n    collectReleaseSlotPins(config).map(({ slot, base, pin }) => [slot, { base, pin }]),\n  );\n}\n\nfunction readWorkspacePackageVersion(configDir: string, name: string): string | undefined {\n  try {\n    const raw = readFileSync(join(configDir, \"packages\", name, \"package.json\"), \"utf-8\");\n    const pkg = JSON.parse(raw) as { version?: unknown };\n    return typeof pkg.version === \"string\" ? pkg.version : undefined;\n  } catch {\n    return undefined;\n  }\n}\n\nexport function readEveryPluginVersion(configDir: string): string | undefined {\n  return readWorkspacePackageVersion(configDir, \"every-plugin\");\n}\n\nexport interface PlatformManifestPublishEntry {\n  key: string;\n  value: string;\n  integrity: string;\n  manifest: PlatformReleaseManifest;\n}\n\nexport function composePlatformManifestPublishEntry(opts: {\n  configDir: string;\n  account: string;\n  gateway: string;\n  /** The previous release's framework version — the compat floor it implies. */\n  previousFrameworkVersion?: string;\n  /** The deploy's repo HEAD — the serving build's identity on the wire. */\n  gitSha?: string;\n  imageRef?: string;\n  /** The release's own publish payload — its pinned slots ride the manifest\n   * (ADR 0034 amendment: release-scoped extends inheritance). */\n  publishPayload?: Record<string, unknown>;\n}): PlatformManifestPublishEntry | null {\n  const frameworkVersion = readFrameworkVersion(opts.configDir);\n  const everyPluginVersion = readEveryPluginVersion(opts.configDir);\n  if (!frameworkVersion || !everyPluginVersion) return null;\n\n  const manifest = composePlatformReleaseManifest({\n    frameworkVersion,\n    everyPluginVersion,\n    ...(opts.gitSha ? { gitSha: opts.gitSha } : {}),\n    imageRef: opts.imageRef ?? PLATFORM_IMAGE_REF,\n    catalog: readWorkspaceCatalog(opts.configDir),\n    templateInventory: computeTemplateInventory(opts.configDir),\n    compatMinChild: opts.previousFrameworkVersion ?? frameworkVersion,\n    ...(opts.publishPayload ? { slots: releaseSlotMap(opts.publishPayload) } : {}),\n  });\n  const value = serializePlatformReleaseManifest(manifest);\n  return {\n    key: getPlatformManifestKey(opts.account, opts.gateway),\n    value,\n    integrity: computeSriHash(value),\n    manifest,\n  };\n}\n\nexport interface DerivedImageRef {\n  image: string;\n  tag: string;\n  digest?: string;\n}\n\n/**\n * The truth gate (#546): every uploaded workspace's version manifest declares\n * the shared-singleton versions its build embedded; the release manifest pins\n * the same names from the release catalog. A version that appears in both but\n * disagrees means the workspace's install drifted from the catalog — the\n * manifest would lie about the uploaded bytes and pinned children would boot\n * a skewed composition. Pin shapes mirror `checkPlatformCompat`'s semantics\n * (via `satisfiesConstraint`): an exact pin must match exactly, a range pin\n * must be satisfied by the built version — the catalog pinning every-plugin\n * as `^X` while bundles record the exact resolved version is not a violation.\n * Returns human-readable violations; empty = clean.\n */\nexport function platformTruthViolations(\n  manifest: PlatformReleaseManifest,\n  sharedByWorkspace: Map<string, Record<string, string> | undefined>,\n): string[] {\n  const violations: string[] = [];\n  for (const [workspace, shared] of sharedByWorkspace) {\n    if (!shared) continue;\n    for (const [name, version] of Object.entries(shared)) {\n      const pin = manifest.sharedDeps[name];\n      if (pin && !satisfiesConstraint(version, pin.version)) {\n        violations.push(\n          `${workspace} was built against ${name}@${version}, the release manifest pins ${pin.version} — ` +\n            `a stale install publishes a manifest that lies about the uploaded bytes; ` +\n            `run pnpm install and redeploy`,\n        );\n      }\n    }\n  }\n  return violations;\n}\n\n/**\n * The derived-pin seam for image refs (ADR 0034 pt 2): when the platform\n * manifest resolves, a runtime's image ref derives as `ref@v<version>` —\n * never hand-pinned. Digest resolution rides the registry and arrives as\n * `digest` when the caller can fetch it; the deploy preflight consumer is\n * `resolveChildImagePin` (`services/deploy/legs.ts`), which resolves the\n * tag's digest and repoints the Railway service source.\n */\nexport async function deriveImageRefFromPlatformManifest(opts: {\n  configDir: string;\n  localConfig: Record<string, unknown>;\n  fetchManifest?: (account: string, gateway: string) => Promise<PlatformReleaseManifest | null>;\n  fetchSlot?: (account: string, gateway: string, version: string) => Promise<unknown>;\n  fetchParent?: (account: string, gateway: string) => Promise<Record<string, unknown> | null>;\n}): Promise<DerivedImageRef | null> {\n  const resolved = await resolvePlatformManifest({\n    configDir: opts.configDir,\n    localConfig: opts.localConfig,\n    fetchManifest: opts.fetchManifest,\n    ...(opts.fetchSlot ? { fetchSlot: opts.fetchSlot } : {}),\n    ...(opts.fetchParent ? { fetchParent: opts.fetchParent } : {}),\n  });\n  if (!resolved) return null;\n  const tag = resolved.manifest.image.tags[0];\n  if (!tag) return null;\n  return { image: resolved.manifest.image.ref, tag };\n}\n"],"mappings":"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"}