{"version":3,"file":"rollback-flow.cjs","names":["fetchConfigHistory","fetchBosConfigFromFastKv","verifyRollbackSnapshot","verifyBootPayload","buildRollbackPayload","publishToFastKv","getPlatformManifestKey"],"sources":["../../../src/services/deploy/rollback-flow.ts"],"sourcesContent":["import type { BosConfigInput, RuntimeConfig } from \"../../types\";\nimport { getPlatformManifestKey } from \"../platform/manifest\";\nimport {\n  type ConfigHistoryEntry,\n  fetchBosConfigFromFastKv,\n  fetchConfigHistory,\n} from \"../registry/fastkv\";\nimport { verifyBootPayload } from \"../resolution/boot-check\";\nimport { buildRollbackPayload, verifyRollbackSnapshot } from \"./rollback\";\nimport { type DeployTrainInput, publishToFastKv } from \"./train\";\n\n/**\n * The shared rollback flow (ticket #422): the post-publish smoke failure\n * protocol and the `bos rollback` command both stamp `rolledBackFrom` onto\n * the target snapshot and republish it verbatim (`exactPayload`) — one\n * implementation here; the callers differ only in how they pick and vet the\n * target (the command's two-stage verification with --force; the smoke\n * protocol's simple refusal).\n */\n\nexport interface RollbackSnapshotInput {\n  account: string;\n  domain: string;\n  env: \"production\" | \"staging\";\n  runtimeConfig: RuntimeConfig | null;\n  configDir: string;\n  registry?: string;\n  network?: \"mainnet\" | \"testnet\";\n  privateKey?: string;\n  wallet?: boolean;\n  verbose?: boolean;\n  dryRun?: boolean;\n}\n\nexport interface RollbackSnapshotOutcome {\n  ok: boolean;\n  registryUrl: string;\n  txHash?: string;\n  /** the caller passed dryRun — the snapshot was validated but not published */\n  dryRun?: boolean;\n  error?: string;\n}\n\nexport async function fetchRollbackHistory(input: {\n  account: string;\n  domain: string;\n  registry?: string;\n  limit: number;\n}): Promise<ConfigHistoryEntry[]> {\n  return fetchConfigHistory({\n    accountId: input.account,\n    gatewayId: input.domain,\n    registry: input.registry,\n    limit: input.limit,\n  });\n}\n\n/** The live config, or undefined when the registry read fails (an unknown\n * live state never blocks a rollback — the snapshot publish is idempotent). */\nexport async function fetchLiveConfig(input: {\n  account: string;\n  domain: string;\n  registry?: string;\n}): Promise<unknown> {\n  try {\n    return await fetchBosConfigFromFastKv<unknown>(\n      `bos://${input.account}/${input.domain}`,\n      input.registry,\n    );\n  } catch {\n    return undefined;\n  }\n}\n\nexport function isSnapshotLive(targetValue: unknown, liveValue: unknown): boolean {\n  return JSON.stringify(liveValue) === JSON.stringify(targetValue);\n}\n\n/** Stamp + republish the target snapshot verbatim. Callers own verification:\n * the smoke protocol refuses unverified targets via\n * `rollbackToPreviousSnapshot`; the command runs its two-stage check and\n * passes `skipVerify` once it has vetted (or --forced) the snapshot. */\nexport async function publishRollbackSnapshot(\n  targetValue: unknown,\n  input: RollbackSnapshotInput,\n  opts?: { skipVerify?: boolean; restorePlatformPointer?: { key: string; value: string } },\n): Promise<RollbackSnapshotOutcome> {\n  if (!opts?.skipVerify) {\n    const verification = await verifyRollbackSnapshot(targetValue as BosConfigInput);\n    if (!verification.ok && verification.verifiable) {\n      return {\n        ok: false,\n        registryUrl: \"\",\n        error: \"rollback refused — a pinned slot's bytes are gone or no longer match their SRI\",\n      };\n    }\n  }\n\n  // The boot verdict is never skippable — skipVerify only stands down the\n  // pin/byte verification (the rollback command vets bytes itself before\n  // passing it), and no other vetting covers boot-ability.\n  const bootCheck = await verifyBootPayload(\n    targetValue,\n    { account: input.account, gateway: input.domain },\n    { ...(input.registry ? { registry: input.registry } : {}) },\n  );\n  if (!bootCheck.ok) {\n    const detail = bootCheck.issues\n      .map((issue) => `${issue.path ? `${issue.path}: ` : \"\"}${issue.message}`)\n      .join(\"; \");\n    const chainNote =\n      bootCheck.chain.length > 0 ? ` (extends chain: ${bootCheck.chain.join(\" -> \")})` : \"\";\n    return {\n      ok: false,\n      registryUrl: \"\",\n      error: `rollback refused — the target snapshot would not boot${chainNote}: ${detail}`,\n    };\n  }\n\n  // Stamp the raw snapshot verbatim — the historical parse here was both a\n  // crash (host-less child snapshots have no app.host of their own) and\n  // lossy (Zod's strip mode dropped keys the schema didn't know); a\n  // rollback's whole value proposition is byte-faithful republication.\n  const payload = buildRollbackPayload(targetValue as BosConfigInput, new Date().toISOString());\n  const result = await publishToFastKv({\n    // The declared input type is the parsed BosConfig; the runtime payload is\n    // the raw snapshot — the same declared-vs-runtime split the train's\n    // exactPayload branch documents (train.ts exactPayload).\n    bosConfig: payload as unknown as DeployTrainInput[\"bosConfig\"],\n    runtimeConfig: input.runtimeConfig,\n    configDir: input.configDir,\n    env: input.env,\n    build: false,\n    dryRun: input.dryRun ?? false,\n    verbose: input.verbose ?? false,\n    packages: \"all\",\n    network: input.network,\n    privateKey: input.privateKey,\n    wallet: input.wallet ?? false,\n    registry: input.registry,\n    exactPayload: true,\n    ...(opts?.restorePlatformPointer\n      ? { restorePlatformPointer: opts.restorePlatformPointer }\n      : {}),\n  });\n  if (result.status === \"dry-run\") {\n    return { ok: true, registryUrl: result.registryUrl, dryRun: true };\n  }\n  if (result.status === \"error\") {\n    return {\n      ok: false,\n      registryUrl: result.registryUrl,\n      error: `rollback publish failed: ${result.error ?? result.status}`,\n    };\n  }\n  return { ok: true, registryUrl: result.registryUrl, txHash: result.txHash };\n}\n\n/** The platform-pointer restore for a rollback (#573): the pointer's own\n * history[1] is the pre-release manifest — the same \"previous entry\" logic\n * the config rollback uses. Best-effort: a failed lookup returns undefined\n * and the drift alarm (#571's cross-check) catches any residue loudly. */\nexport async function resolvePointerRestore(\n  account: string,\n  domain: string,\n  registry?: string,\n): Promise<{ key: string; value: string } | undefined> {\n  try {\n    const pointerHistory = await fetchConfigHistory({\n      accountId: account,\n      gatewayId: domain,\n      registry,\n      limit: 5,\n      keyPath: \"platform.json\",\n    });\n    const previousPointer = pointerHistory[1];\n    if (!previousPointer) return undefined;\n    return {\n      key: getPlatformManifestKey(account, domain),\n      value: JSON.stringify(previousPointer.value),\n    };\n  } catch {\n    console.warn(\"  platform pointer restore unavailable — the drift alarm will catch any residue\");\n    return undefined;\n  }\n}\n\n/** The post-publish smoke failure protocol (ADR 0021): roll the registry\n * config back to the previous snapshot — a red boot gate must never leave a\n * new config live against an old image. #573: the platform pointer rolls\n * back in the same atomic write. */\nexport async function rollbackToPreviousSnapshot(input: RollbackSnapshotInput): Promise<{\n  ok: boolean;\n  txHash?: string;\n  blockHeight?: string;\n  registryUrl: string;\n  error?: string;\n}> {\n  let history: ConfigHistoryEntry[];\n  try {\n    history = await fetchRollbackHistory({\n      account: input.account,\n      domain: input.domain,\n      registry: input.registry,\n      limit: 20,\n    });\n  } catch (error) {\n    return {\n      ok: false,\n      registryUrl: \"\",\n      error: `fetching publish history failed: ${error instanceof Error ? error.message : String(error)}`,\n    };\n  }\n\n  const previous = history[1];\n  if (!previous) {\n    return {\n      ok: false,\n      registryUrl: \"\",\n      error: \"no previous snapshot in the publish history — nothing to roll back to\",\n    };\n  }\n\n  // The pointer restore mirrors the config rollback: the pointer's history[1]\n  // is the pre-release manifest. Best-effort — a failed restore leaves the\n  // drift alarm to catch the residue loudly.\n  const restorePlatformPointer = await resolvePointerRestore(\n    input.account,\n    input.domain,\n    input.registry,\n  );\n\n  const result = await publishRollbackSnapshot(previous.value, input, {\n    restorePlatformPointer,\n  });\n  if (result.ok) {\n    return {\n      ok: true,\n      blockHeight: String(previous.blockHeight),\n      registryUrl: result.registryUrl,\n      txHash: result.txHash,\n    };\n  }\n  return result;\n}\n"],"mappings":"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"}