{"version":3,"file":"manifest.mjs","names":[],"sources":["../../../src/services/platform/manifest.ts"],"sourcesContent":["/**\n * The platform release manifest (ADR 0034) — one module, the whole release\n * contract: schema, composer, inventory, registry slot, resolution, and the\n * compat check. One composer feeds both publish homes (FastKV pointer slot +\n * in-image staging); the image digest is deliberately absent — it derives from\n * the tags at deploy preflight. The homes' one known divergence is the compat\n * floor: publisher-side (previous release) in FastKV, own version — failing\n * closed — in the image, which cannot read the registry inside the build.\n */\nimport { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join, relative } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Context, Layer } from \"effect\";\nimport {\n  SHARED_DEP_SPECS,\n  type SharedDependencyConfig,\n  shareConfigFor,\n} from \"every-plugin/shared-deps-spec\";\nimport { glob } from \"glob\";\nimport { z } from \"zod\";\nimport { getExtendsRef, parseBosRef } from \"../../utils/bos-ref\";\nimport { compareSemver, satisfiesConstraint } from \"../../utils/semver\";\nimport { fetchRegistryKey, fetchRegistryKeyOrNull } from \"../registry/fastkv\";\nimport { fetchParentConfig } from \"../resolution/init-source\";\nimport { AGENT_SYNC_GLOBS, FRAMEWORK_OWNED_SYNC_FILES } from \"../scaffold/ownership\";\n\nexport const PlatformReleaseManifestSchema = z.object({\n  schemaVersion: z.literal(1),\n  kind: z.literal(\"everything-dev/platform-manifest\"),\n  frameworkVersion: z.string().min(1),\n  everyPluginVersion: z.string().min(1),\n  /** The serving build's repo HEAD (full sha) — omitted when the compose\n   * context has no git (regression image builds, build-arg-less builds). */\n  gitSha: z.string().min(7).optional(),\n  image: z.object({\n    ref: z.string().min(1),\n    tags: z.array(z.string().min(1)),\n  }),\n  catalog: z.record(z.string(), z.string()),\n  sharedDeps: z.record(\n    z.string(),\n    z.object({\n      version: z.string(),\n      requiredVersion: z.union([z.string(), z.boolean()]),\n      singleton: z.boolean(),\n      eager: z.boolean(),\n      strictVersion: z.boolean(),\n      shareScope: z.string(),\n    }),\n  ),\n  templateInventory: z.array(\n    z.object({\n      path: z.string(),\n      sha256: z.string().regex(/^[0-9a-f]{64}$/),\n    }),\n  ),\n  compat: z.object({ minChild: z.string().min(1) }),\n  /**\n   * The release's own slot-pin map (ADR 0034 amendment — release-scoped\n   * extends inheritance): each entry records the immutable version-manifest\n   * pointer the release published for a config slot (dotted config path, e.g.\n   * `app.auth`, `app.auth.ui`, `plugins.votes.ui`) — the son of the store, so\n   * children pinned to this release can re-point inherited slots at THESE\n   * bytes instead of the parent's live config whenever the parent publishes.\n   * Optional: releases composed before the field carry no slots, and old\n   * readers strip unknown keys.\n   */\n  slots: z\n    .record(\n      z.string(),\n      z.object({\n        base: z.string().min(1),\n        pin: z.object({\n          manifest: z.string().min(1),\n          integrity: z.string().min(1),\n        }),\n      }),\n    )\n    .optional(),\n});\nexport type PlatformReleaseManifest = z.infer<typeof PlatformReleaseManifestSchema>;\n\n/** One recorded release slot (see {@link PlatformReleaseManifestSchema.slots}). */\nexport interface ReleaseSlotRecord {\n  base: string;\n  pin: { manifest: string; integrity: string };\n}\n\nexport interface TemplateInventoryEntry {\n  path: string;\n  sha256: string;\n}\n\nexport function getPlatformManifestKey(accountId: string, gatewayId: string): string {\n  return `apps/${accountId}/${gatewayId}/platform.json`;\n}\n\n/** The immutable per-release slot (#573): one key per released framework\n * version, written once, never mutated — a divergent republish is a burned\n * version and fails loud. `platform.json` remains the moving pointer; slot-\n * first resolution (#574) reads slots directly. */\nexport function getPlatformVersionSlotKey(\n  accountId: string,\n  gatewayId: string,\n  version: string,\n): string {\n  return `apps/${accountId}/${gatewayId}/platform-versions/${version}.json`;\n}\n\n/**\n * The template-source file set a release inventory hashes: the ownership\n * inventory's static paths, the agent-sync globs, and the per-plugin template\n * scaffolding (`plugins/_template/*` — the sources of the per-plugin sync\n * patterns). Files absent from the release source are skipped, mirroring\n * sync's transitional tolerance.\n */\nconst TEMPLATE_SCAFFOLD_SOURCES = [\n  \"plugins/_template/rspack.config.js\",\n  \"plugins/_template/tsconfig.json\",\n  \"plugins/_template/tests/types.d.ts\",\n];\n\nfunction templateSourcePaths(sourceDir: string): string[] {\n  const paths = new Set<string>([...FRAMEWORK_OWNED_SYNC_FILES, ...TEMPLATE_SCAFFOLD_SOURCES]);\n  for (const match of glob.sync(AGENT_SYNC_GLOBS, {\n    cwd: sourceDir,\n    nodir: true,\n    dot: true,\n    ignore: [\"**/node_modules/**\", \"**/.git/**\", \"**/dist/**\", \"**/.bos/**\"],\n  })) {\n    paths.add(relative(sourceDir, join(sourceDir, match)));\n  }\n  return [...paths].sort();\n}\n\nexport function computeTemplateInventory(sourceDir: string): TemplateInventoryEntry[] {\n  const inventory: TemplateInventoryEntry[] = [];\n  for (const relativeFilePath of templateSourcePaths(sourceDir)) {\n    const fullPath = join(sourceDir, relativeFilePath);\n    if (!existsSync(fullPath)) continue;\n    try {\n      const content = readFileSync(fullPath);\n      inventory.push({\n        path: relativeFilePath,\n        sha256: createHash(\"sha256\").update(content).digest(\"hex\"),\n      });\n    } catch {\n      // unreadable at compose time → not part of the release inventory\n    }\n  }\n  return inventory.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n}\n\nexport function composeSharedDepsFromCatalog(\n  catalog: Record<string, string>,\n): Record<string, SharedDependencyConfig> {\n  const deps: Record<string, SharedDependencyConfig> = {};\n  for (const spec of SHARED_DEP_SPECS) {\n    const version = catalog[spec.name];\n    if (!version) continue;\n    deps[spec.name] = { version, ...shareConfigFor(spec, version) };\n  }\n  return deps;\n}\n\nexport interface ComposePlatformReleaseManifestInputs {\n  frameworkVersion: string;\n  everyPluginVersion: string;\n  gitSha?: string;\n  imageRef: string;\n  catalog: Record<string, string>;\n  templateInventory: TemplateInventoryEntry[];\n  compatMinChild: string;\n  /** The release's own slot-pin map — sorted here so identical payloads\n   * compose identical manifest bytes across machines (the burned-version\n   * guard compares serialized bytes). */\n  slots?: Record<string, ReleaseSlotRecord>;\n}\n\nexport function composePlatformReleaseManifest(\n  inputs: ComposePlatformReleaseManifestInputs,\n): PlatformReleaseManifest {\n  // The manifest carries only the derivable tag — the exact version tag the\n  // tag scheme pushes for semver releases (ADR 0021). Sha-tags are git facts\n  // an image build cannot see; derivation (`ref@v<version>` → digest) keys off\n  // the version tag so both publish homes stay byte-identical without any\n  // build-context plumbing.\n  const semver = parseSemver(inputs.frameworkVersion.trim());\n  const tags = semver\n    ? [\n        `v${semver.major}.${semver.minor}.${semver.patch}${semver.prerelease ? `-${semver.prerelease}` : \"\"}`,\n      ]\n    : [];\n  const slots = inputs.slots\n    ? Object.fromEntries(Object.entries(inputs.slots).sort(([a], [b]) => (a < b ? -1 : 1)))\n    : undefined;\n  return PlatformReleaseManifestSchema.parse({\n    schemaVersion: 1,\n    kind: \"everything-dev/platform-manifest\",\n    frameworkVersion: inputs.frameworkVersion,\n    everyPluginVersion: inputs.everyPluginVersion,\n    ...(inputs.gitSha ? { gitSha: inputs.gitSha } : {}),\n    image: { ref: inputs.imageRef, tags },\n    catalog: inputs.catalog,\n    sharedDeps: composeSharedDepsFromCatalog(inputs.catalog),\n    templateInventory: inputs.templateInventory,\n    compat: { minChild: inputs.compatMinChild },\n    ...(slots ? { slots } : {}),\n  });\n}\n\n/** The one serializer for both publish homes — byte-identical by construction. */\nexport function serializePlatformReleaseManifest(manifest: PlatformReleaseManifest): string {\n  return JSON.stringify(manifest);\n}\n\n/**\n * The in-image copy (ADR 0034 pt 1): generated inside the docker build at\n * `packages/everything-dev/dist/platform-manifest.json`. Resolved relative to\n * this module — the compiled layout is `<pkg>/dist/services/platform/manifest.mjs`\n * (two directory levels up = `<pkg>/dist/`), the source layout is\n * `<pkg>/src/services/platform/manifest.ts` (four levels up = the pnpm parent\n * dir, then `<pkg>/dist/`). Both candidates are tried; absent or unparsable →\n * null.\n */\nexport function readInImagePlatformManifest(): PlatformReleaseManifest | null {\n  const moduleFile = fileURLToPath(import.meta.url);\n  const candidates = [\n    join(moduleFile, \"..\", \"..\", \"platform-manifest.json\"),\n    join(moduleFile, \"..\", \"..\", \"..\", \"..\", \"dist\", \"platform-manifest.json\"),\n  ];\n  for (const path of candidates) {\n    try {\n      return PlatformReleaseManifestSchema.parse(JSON.parse(readFileSync(path, \"utf-8\")));\n    } catch {\n      // try the next layout candidate\n    }\n  }\n  return null;\n}\n\nfunction parseSemver(\n  version: string,\n): { major: number; minor: number; patch: number; prerelease?: string } | null {\n  const match = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(version.trim());\n  if (!match) return null;\n  return {\n    major: Number(match[1]),\n    minor: Number(match[2]),\n    patch: Number(match[3]),\n    ...(match[4] ? { prerelease: match[4] } : {}),\n  };\n}\n\nexport interface PlatformCompatSubject {\n  /** The child's recorded platform pin / installed framework version. */\n  platformVersion?: string;\n  catalog: Record<string, string>;\n}\n\nexport interface PlatformCompatViolation {\n  kind: \"minChild\" | \"shared-dep\";\n  name?: string;\n  message: string;\n}\n\n/**\n * The additive compat gate (ADR 0034 pt 5): the child's framework version must\n * sit inside the release's compat window, and strict singletons must agree\n * exactly with the release's pins. A sibling of the federation check — never\n * a relaxation of it.\n */\nexport function checkPlatformCompat(\n  manifest: PlatformReleaseManifest,\n  subject: PlatformCompatSubject,\n): PlatformCompatViolation[] {\n  const violations: PlatformCompatViolation[] = [];\n\n  if (subject.platformVersion) {\n    if (compareSemver(subject.platformVersion, manifest.compat.minChild) < 0) {\n      violations.push({\n        kind: \"minChild\",\n        message: `platform ${subject.platformVersion} is older than this release's compat floor ${manifest.compat.minChild}`,\n      });\n    }\n    if (compareSemver(subject.platformVersion, manifest.frameworkVersion) > 0) {\n      violations.push({\n        kind: \"minChild\",\n        message: `platform ${subject.platformVersion} is newer than this release ${manifest.frameworkVersion}`,\n      });\n    }\n  }\n\n  for (const [name, pin] of Object.entries(manifest.sharedDeps)) {\n    const childVersion = subject.catalog[name];\n    if (pin.strictVersion) {\n      if (!childVersion) {\n        violations.push({\n          kind: \"shared-dep\",\n          name,\n          message: `strict singleton \"${name}\" is missing from the child catalog (release pins ${pin.version})`,\n        });\n        continue;\n      }\n      if (childVersion !== pin.version) {\n        violations.push({\n          kind: \"shared-dep\",\n          name,\n          message: `strict singleton \"${name}\" is ${childVersion}, the release pins ${pin.version}`,\n        });\n      }\n      continue;\n    }\n    if (typeof pin.requiredVersion === \"string\" && childVersion) {\n      if (!satisfiesConstraint(childVersion, pin.requiredVersion)) {\n        violations.push({\n          kind: \"shared-dep\",\n          name,\n          message: `\"${name}\" is ${childVersion}, which does not satisfy the release range ${pin.requiredVersion}`,\n        });\n      }\n    }\n  }\n\n  return violations;\n}\n\n/**\n * Resolution (ADR 0034 pt 3): one authored `platform` pin in, one resolved\n * manifest out. Absent pin = latest via extends (nearest chain manifest);\n * explicit pins match exactly or fail loud. The `.bos/platform-manifest.json`\n * cache carries offline dev; network failure with no usable cache → null\n * (today's behavior). All I/O is injectable — the test seam.\n */\nexport interface ResolvedPlatformManifest {\n  manifest: PlatformReleaseManifest;\n  source: { account: string; gateway: string };\n  fromCache: boolean;\n  /** Where the resolved bytes came from (#574): an immutable version slot\n   * (an exact pin's target) or the moving `platform.json` pointer (unpinned\n   * \"latest\" resolution). */\n  provenance: \"slot\" | \"pointer\";\n}\n\nexport interface PlatformManifestCache {\n  schemaVersion: 1;\n  kind: \"everything-dev/platform-manifest-cache\";\n  resolvedAt: string;\n  source: { account: string; gateway: string };\n  manifest: PlatformReleaseManifest;\n  provenance?: \"slot\" | \"pointer\";\n}\n\n/**\n * How long a cached resolution may stand in for a live fetch. The cache\n * exists for offline dev ONLY (#574) — never stale-pin grace: slot-first\n * resolution makes old pins resolvable forever from immutable slots, so a\n * cache can no longer be the reason a pin resolves. Without a freshness\n * bound a registry outage (or a cache written before the parent's latest\n * publish) silently serves a months-old release manifest — pins derive\n * from it and the mismatch can take an hour to diagnose. Past the TTL the\n * cache is refused: pin-less resolution degrades to \"no manifest\"; pinned\n * resolution fails loud with the slot key named.\n */\nexport const PLATFORM_MANIFEST_CACHE_TTL_MS = 24 * 60 * 60_000;\n\nexport interface ResolvePlatformManifestOptions {\n  configDir: string;\n  localConfig: Record<string, unknown>;\n  fetchManifest?: (account: string, gateway: string) => Promise<PlatformReleaseManifest | null>;\n  /** The immutable version-slot read (#574) — an exact pin's target. Throws\n   * on read failure (distinct from a clean absent-slot null). */\n  fetchSlot?: (account: string, gateway: string, version: string) => Promise<unknown>;\n  fetchParent?: (account: string, gateway: string) => Promise<Record<string, unknown> | null>;\n  cacheRead?: () => PlatformManifestCache | null;\n  cacheWrite?: (cached: PlatformManifestCache) => void;\n  /** injectable clock for the cache-freshness check (test seam) */\n  now?: () => number;\n  /** injectable freshness bound (test seam); defaults to {@link PLATFORM_MANIFEST_CACHE_TTL_MS} */\n  cacheTtlMs?: number;\n}\n\nconst CACHE_DIR = \".bos\";\nconst CACHE_FILE = \"platform-manifest.json\";\n\n/**\n * A cached resolution stands in for a live fetch only while it is fresh:\n * `resolvedAt` inside the TTL, and parseable (a cache with an unparseable\n * timestamp is treated as stale — never trusted).\n */\nfunction isFresh(cached: PlatformManifestCache, opts: ResolvePlatformManifestOptions): boolean {\n  const now = (opts.now ?? Date.now)();\n  const ttl = opts.cacheTtlMs ?? PLATFORM_MANIFEST_CACHE_TTL_MS;\n  const resolvedAt = Date.parse(cached.resolvedAt);\n  if (Number.isNaN(resolvedAt)) return false;\n  return now - resolvedAt <= ttl;\n}\n\nfunction defaultCachePath(configDir: string): string {\n  return join(configDir, CACHE_DIR, CACHE_FILE);\n}\n\nfunction defaultCacheRead(configDir: string): PlatformManifestCache | null {\n  try {\n    const raw = JSON.parse(\n      readFileSync(defaultCachePath(configDir), \"utf-8\"),\n    ) as PlatformManifestCache;\n    return raw?.kind === \"everything-dev/platform-manifest-cache\" ? raw : null;\n  } catch {\n    return null;\n  }\n}\n\nfunction defaultCacheWrite(configDir: string, cached: PlatformManifestCache): void {\n  try {\n    mkdirSync(join(configDir, CACHE_DIR), { recursive: true });\n    writeFileSync(defaultCachePath(configDir), `${JSON.stringify(cached, null, 2)}\\n`);\n  } catch {\n    // cache writes never fail the resolution\n  }\n}\n\nfunction defaultFetchManifest(\n  account: string,\n  gateway: string,\n): Promise<PlatformReleaseManifest | null> {\n  return (async () => {\n    const raw = await fetchRegistryKeyOrNull<unknown>(account, gateway, \"platform.json\");\n    if (!raw) return null;\n    return PlatformReleaseManifestSchema.parse(raw);\n  })();\n}\n\nfunction defaultFetchSlot(account: string, gateway: string, version: string): Promise<unknown> {\n  // NOT the OrNull variant: a read failure must stay distinguishable from a\n  // clean absent-slot — the resolver fails loud with the slot key on a\n  // failure and treats null as absence.\n  return fetchRegistryKey<unknown>(account, gateway, `platform-versions/${version}.json`);\n}\n\nfunction defaultFetchParent(\n  account: string,\n  gateway: string,\n): Promise<Record<string, unknown> | null> {\n  return fetchParentConfig(account, gateway)\n    .then((config) => config as Record<string, unknown>)\n    .catch(() => null);\n}\n\nexport interface ExtendsChainNode {\n  account: string;\n  gateway: string;\n}\n\nexport interface ExtendsChainWalk {\n  /** child → root, nearest first — `chain[0]` is the local extends target */\n  chain: ExtendsChainNode[];\n  /** the most rootward `repository` seen — parent configs override the child's */\n  repository?: string;\n  /** set when the walk revisits a node — a pathological extends chain */\n  circularAt?: string;\n}\n\n/**\n * The ONE extends-chain walk (child → root via FastKV parent configs): both\n * `resolvePlatformManifest` and the upgrade source resolution used to carry\n * their own `fetchParentConfig` loops; this is the shared implementation.\n * A fetch failure (throw or null) stops the walk — the chain gathered so far\n * is still returned.\n */\nexport async function walkExtendsChain(input: {\n  localConfig: Record<string, unknown>;\n  localRepository?: string;\n  fetchParent?: (account: string, gateway: string) => Promise<Record<string, unknown> | null>;\n}): Promise<ExtendsChainWalk> {\n  const fetchParent = input.fetchParent ?? defaultFetchParent;\n  const chain: ExtendsChainNode[] = [];\n  let repository = input.localRepository;\n\n  const startsRef = getExtendsRef(input.localConfig);\n  let cursor: ExtendsChainNode | null =\n    typeof startsRef === \"string\" && startsRef.startsWith(\"bos://\")\n      ? (parseBosRef(startsRef) ?? null)\n      : null;\n  const visited = new Set<string>();\n  let circularAt: string | undefined;\n\n  while (cursor) {\n    const ref = `bos://${cursor.account}/${cursor.gateway}`;\n    if (visited.has(ref)) {\n      circularAt = ref;\n      break;\n    }\n    visited.add(ref);\n    chain.push(cursor);\n\n    const parentConfig = await fetchParent(cursor.account, cursor.gateway);\n    if (!parentConfig) break;\n    if (typeof parentConfig.repository === \"string\" && parentConfig.repository) {\n      repository = parentConfig.repository;\n    }\n    const nextRef = getExtendsRef(parentConfig);\n    cursor =\n      typeof nextRef === \"string\" && nextRef.startsWith(\"bos://\")\n        ? (parseBosRef(nextRef) ?? null)\n        : null;\n  }\n\n  return {\n    chain,\n    ...(repository !== undefined ? { repository } : {}),\n    ...(circularAt ? { circularAt } : {}),\n  };\n}\n\nexport async function resolvePlatformManifest(\n  opts: ResolvePlatformManifestOptions,\n): Promise<ResolvedPlatformManifest | null> {\n  const fetchManifest =\n    opts.fetchManifest ?? ((a: string, g: string) => defaultFetchManifest(a, g));\n  const fetchSlot =\n    opts.fetchSlot ?? ((a: string, g: string, v: string) => defaultFetchSlot(a, g, v));\n  const cacheRead = opts.cacheRead ?? (() => defaultCacheRead(opts.configDir));\n  const cacheWrite = opts.cacheWrite ?? ((cached) => defaultCacheWrite(opts.configDir, cached));\n\n  const { chain } = await walkExtendsChain({\n    localConfig: opts.localConfig,\n    ...(opts.fetchParent ? { fetchParent: opts.fetchParent } : {}),\n  });\n\n  const writeCache = (\n    manifest: PlatformReleaseManifest,\n    source: { account: string; gateway: string },\n    provenance: \"slot\" | \"pointer\",\n  ) => {\n    const cached: PlatformManifestCache = {\n      schemaVersion: 1,\n      kind: \"everything-dev/platform-manifest-cache\",\n      resolvedAt: new Date().toISOString(),\n      source,\n      manifest,\n      provenance,\n    };\n    cacheWrite(cached);\n    return { manifest, source, fromCache: false, provenance };\n  };\n\n  const pin = typeof opts.localConfig.platform === \"string\" ? opts.localConfig.platform : undefined;\n\n  // Slot-first pin resolution (#574): an exact pin targets the immutable\n  // `platform-versions/<pin>.json` slot, scanning the extends chain — a root\n  // release never strands a child pin, because slots are immutable and the\n  // pointer only moves forward (the #546 stale-pin class is dead). The\n  // pointer serves only unpinned (\"latest\") resolution.\n  if (pin) {\n    for (const node of chain) {\n      const slotKey = getPlatformVersionSlotKey(node.account, node.gateway, pin);\n      let raw: unknown;\n      try {\n        raw = await fetchSlot(node.account, node.gateway, pin);\n      } catch (error) {\n        // Slot read failure: the offline cache may stand in (an offline-dev\n        // aid, not stale-pin grace — same pin match required) — otherwise\n        // fail with the slot key named.\n        const cached = cacheRead();\n        if (cached && cached.manifest.frameworkVersion === pin && isFresh(cached, opts)) {\n          return {\n            manifest: cached.manifest,\n            source: cached.source,\n            fromCache: true,\n            provenance: cached.provenance ?? \"slot\",\n          };\n        }\n        throw new Error(\n          `platform pin ${pin}: the version slot read failed (${slotKey}): ${error instanceof Error ? error.message : String(error)}`,\n        );\n      }\n      if (raw) {\n        const manifest = PlatformReleaseManifestSchema.parse(raw);\n        return writeCache(manifest, node, \"slot\");\n      }\n    }\n    // Slot miss across the chain — the pin names a release whose manifest\n    // leg never published (the npm-version-exists / manifest-not-published\n    // race, #568). The old \"pin not found, publish the release or clear the\n    // pin\" dead path is gone: slots are immutable, so this state means the\n    // release deploy must be re-run.\n    throw new Error(\n      `platform pin ${pin}: no version slot in the extends chain` +\n        `${chain.length > 0 ? ` (${chain.map((n) => `bos://${n.account}/${n.gateway}`).join(\" → \")})` : \"\"} — ` +\n        `the release's manifest leg never published it; re-run the release deploy (or \\`bos upgrade\\` to re-pin)`,\n    );\n  }\n\n  for (const node of chain) {\n    const manifest = await fetchManifest(node.account, node.gateway);\n    if (!manifest) continue;\n    return writeCache(manifest, node, \"pointer\");\n  }\n\n  const cached = cacheRead();\n  if (cached && isFresh(cached, opts)) {\n    return {\n      manifest: cached.manifest,\n      source: cached.source,\n      fromCache: true,\n      provenance: cached.provenance ?? \"pointer\",\n    };\n  }\n  return null;\n}\n\n/**\n * The platform release manifest as a Context.Service: the same \"module is\n * the implementation, the tag is the seam\" shape as FastKVRegistryService —\n * CLI handlers `yield*` the tag; pure and standalone callers keep using the\n * plain functions. Tests provide `Layer.succeed` fakes instead of module\n * mocks.\n */\nexport class PlatformManifestService extends Context.Service<\n  PlatformManifestService,\n  {\n    resolvePlatformManifest: typeof resolvePlatformManifest;\n    composePlatformReleaseManifest: typeof composePlatformReleaseManifest;\n    checkPlatformCompat: typeof checkPlatformCompat;\n    readInImagePlatformManifest: typeof readInImagePlatformManifest;\n  }\n>()(\"everything-dev/platform/manifest\") {\n  static readonly Live = Layer.succeed(\n    this,\n    this.of({\n      resolvePlatformManifest,\n      composePlatformReleaseManifest,\n      checkPlatformCompat,\n      readInImagePlatformManifest,\n    }),\n  );\n}\n"],"mappings":"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"}