{"version":3,"file":"checkpoint.mjs","names":[],"sources":["../../../../../../../ai/src/orchestrator/checkpoint.ts"],"sourcesContent":["import type { CheckpointRecord } from \"../contracts/orchestrator/checkpoint-store.contract\";\nimport type { Next } from \"../contracts/supervisor/next.type\";\nimport type { OrchestratorEngineContext } from \"./engine-context.type\";\n\n/** Framework default for snapshot retention per session (§4 Phase 6). */\nexport const DEFAULT_KEEP_SNAPSHOTS = 100;\n\n/** Fields the engine supplies to build a fresh checkpoint row. */\nexport type PersistParams<TState> = {\n  ctx: OrchestratorEngineContext<unknown, TState>;\n  sessionId: string;\n  turnIndex: number;\n  state: unknown;\n  /** Dispatch decision summary — a single intent, fan-out list, or null. */\n  lastRoute: string | string[] | null;\n  /** Carried forward from the loaded checkpoint (compaction progress). */\n  summarizedThrough: number | null;\n};\n\n/**\n * Summarize a supervisor dispatch decision into the `last_route`\n * checkpoint column (§4 Phase 6). A bare string passes through; a\n * fan-out array passes through; the `END` sentinel and anything else\n * collapse to `null` (the turn routed nowhere worth recording).\n */\nexport function summarizeRoute(next: Next | undefined): string | string[] | null {\n  if (typeof next === \"string\") {\n    return next === \"__warlock:end__\" ? null : next;\n  }\n\n  if (Array.isArray(next)) {\n    return next;\n  }\n\n  return null;\n}\n\n/**\n * Phase 6 — persist checkpoint (orchestrator.md §3 / §4 Phase 6).\n * Writes a fresh append-only row at `turn_index = N` carrying the\n * post-merge `state`, the current `signature` (read by the next call's\n * Phase 2), the informational `version`, the dispatch `last_route`, and\n * the carried-forward `summarized_through`. Lock columns are written\n * `null` here — they are populated only by an in-flight Phase 7\n * compaction.\n *\n * Emits `orchestrator.checkpoint.persisted` after the row lands, then\n * prunes to `keepSnapshots` (default 100; `\"all\"` opts out) — §4 Phase\n * 6 pruning (Q20). Pruning runs only when the store exposes the\n * optional `prune` hook (memory/pg/redis own their own pruning); when\n * absent the write still succeeds.\n *\n * Returns the persisted record so the engine can fold it into the\n * turn's report.\n */\nexport async function persistCheckpoint<TState>(\n  params: PersistParams<TState>,\n): Promise<CheckpointRecord> {\n  const { ctx, sessionId, turnIndex, state, lastRoute, summarizedThrough } = params;\n\n  const record: CheckpointRecord = {\n    orchestrator_name: ctx.config.name,\n    session_id: sessionId,\n    turn_index: turnIndex,\n    state,\n    last_route: lastRoute,\n    signature: ctx.signature,\n    version: ctx.config.version ?? null,\n    summarized_through: summarizedThrough,\n    lock_acquired_at: null,\n    lock_expires_at: null,\n    saved_at: new Date().toISOString(),\n  };\n\n  await ctx.checkpointStore.save(record);\n\n  ctx.emitter.emit(\"orchestrator.checkpoint.persisted\", {\n    sessionId,\n    turnIndex,\n  });\n\n  await prune(ctx, sessionId, turnIndex);\n\n  return record;\n}\n\n/**\n * Prune session rows older than `max_turn_index - keepSnapshots` (§4\n * Phase 6). Synchronous-after-save; opted out with\n * `keepSnapshots: \"all\"`. Delegated to an optional store-side `prune`\n * hook so each driver implements deletion in its own dialect — the\n * engine never reaches into store internals.\n */\nasync function prune<TState>(\n  ctx: OrchestratorEngineContext<unknown, TState>,\n  sessionId: string,\n  latestTurnIndex: number,\n): Promise<void> {\n  const keep = ctx.config.keepSnapshots ?? DEFAULT_KEEP_SNAPSHOTS;\n\n  if (keep === \"all\") {\n    return;\n  }\n\n  const store = ctx.checkpointStore as {\n    prune?: (\n      orchestratorName: string,\n      sessionId: string,\n      keepBeforeTurnIndex: number,\n    ) => Promise<void>;\n  };\n\n  if (typeof store.prune !== \"function\") {\n    return;\n  }\n\n  const keepBeforeTurnIndex = latestTurnIndex - keep + 1;\n\n  if (keepBeforeTurnIndex <= 0) {\n    return;\n  }\n\n  await store.prune(ctx.config.name, sessionId, keepBeforeTurnIndex);\n}\n"],"mappings":";;AAKA,MAAa,yBAAyB;;;;;;;AAoBtC,SAAgB,eAAe,MAAkD;CAC/E,IAAI,OAAO,SAAS,UAClB,OAAO,SAAS,oBAAoB,OAAO;CAG7C,IAAI,MAAM,QAAQ,IAAI,GACpB,OAAO;CAGT,OAAO;AACT;;;;;;;;;;;;;;;;;;;AAoBA,eAAsB,kBACpB,QAC2B;CAC3B,MAAM,EAAE,KAAK,WAAW,WAAW,OAAO,WAAW,sBAAsB;CAE3E,MAAM,SAA2B;EAC/B,mBAAmB,IAAI,OAAO;EAC9B,YAAY;EACZ,YAAY;EACZ;EACA,YAAY;EACZ,WAAW,IAAI;EACf,SAAS,IAAI,OAAO,WAAW;EAC/B,oBAAoB;EACpB,kBAAkB;EAClB,iBAAiB;EACjB,2BAAU,IAAI,KAAK,EAAC,CAAC,YAAY;CACnC;CAEA,MAAM,IAAI,gBAAgB,KAAK,MAAM;CAErC,IAAI,QAAQ,KAAK,qCAAqC;EACpD;EACA;CACF,CAAC;CAED,MAAM,MAAM,KAAK,WAAW,SAAS;CAErC,OAAO;AACT;;;;;;;;AASA,eAAe,MACb,KACA,WACA,iBACe;CACf,MAAM,OAAO,IAAI,OAAO;CAExB,IAAI,SAAS,OACX;CAGF,MAAM,QAAQ,IAAI;CAQlB,IAAI,OAAO,MAAM,UAAU,YACzB;CAGF,MAAM,sBAAsB,kBAAkB,OAAO;CAErD,IAAI,uBAAuB,GACzB;CAGF,MAAM,MAAM,MAAM,IAAI,OAAO,MAAM,WAAW,mBAAmB;AACnE"}