import { readFile } from "node:fs/promises"; import { join } from "node:path"; import { writeJsonAtomic } from "../config/loader.js"; import type { ChecklistItem, TaskLedger } from "../types.js"; function record(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } function strings(value: unknown): value is string[] { return Array.isArray(value) && value.every((item) => typeof item === "string"); } function finite(value: unknown): value is number { return typeof value === "number" && Number.isFinite(value) && value >= 0; } export const CHECKLIST_MAX_ITEMS = 32; const CHECKLIST_STATES = ["pending", "active", "done", "blocked"]; function isChecklist(value: unknown): boolean { if (value === undefined) return true; return Array.isArray(value) && value.length <= CHECKLIST_MAX_ITEMS && value.every((item) => record(item) && typeof item.id === "string" && item.id.trim().length > 0 && item.id.length <= 128 && typeof item.title === "string" && item.title.trim().length > 0 && item.title.length <= 512 && typeof item.owner === "string" && item.owner.trim().length > 0 && item.owner.length <= 128 && CHECKLIST_STATES.includes(String(item.state))); } const TOPOLOGY_CHECKLIST: Record> = { direct: [["PREFLIGHT", "Validate the request and the checkout", "root"], ["ROUTED", "Select the smallest safe topology", "root"], ["VERIFYING", "Run the declared acceptance check", "root"], ["COMPLETED", "Settle the run", "root"]], scout: [["PREFLIGHT", "Validate the request and the checkout", "root"], ["ROUTED", "Select the smallest safe topology", "root"], ["SCOUTING", "Gather bounded evidence", "scout"], ["IMPLEMENTING", "Fit accepted facts into the root envelope", "root"], ["VERIFYING", "Run the declared acceptance check", "root"], ["COMPLETED", "Settle the run", "root"]], swarm: [["PREFLIGHT", "Validate the request and the checkout", "root"], ["ROUTED", "Select the smallest safe topology", "root"], ["SWARMING", "Gather bounded evidence", "scout"], ["IMPLEMENTING", "Apply the scoped change", "writer"], ["VERIFYING", "Run the declared acceptance check", "root"], ["COMPLETED", "Settle the run", "root"]], deep: [["PREFLIGHT", "Validate the request and the checkout", "root"], ["ROUTED", "Select the smallest safe topology", "root"], ["DEEP_RUNNING", "Apply the scoped change", "writer"], ["VERIFYING", "Run the declared acceptance check", "root"], ["COMPLETED", "Settle the run", "root"]], warroom: [["PREFLIGHT", "Validate the request and the checkout", "root"], ["ROUTED", "Select the smallest safe topology", "root"], ["WAR_ROOM", "Converge the bounded blackboard", "warroom"], ["IMPLEMENTING", "Apply the scoped change", "writer"], ["VERIFYING", "Run the declared acceptance check", "root"], ["COMPLETED", "Settle the run", "root"]], }; export function plannedChecklist(topology: string): ChecklistItem[] { return (TOPOLOGY_CHECKLIST[topology] ?? TOPOLOGY_CHECKLIST.direct!).map(([id, title, owner]) => ({ id, title, owner, state: "pending" as const })); } export function checklistProgress(ledger: TaskLedger | undefined): { done: number; total: number } { const items = ledger?.checklist ?? []; return { done: items.filter((item) => item.state === "done").length, total: items.length }; } export function isTaskLedger(value: unknown): value is TaskLedger { if (!record(value) || typeof value.goal !== "string" || !value.goal.trim() || typeof value.configVersion !== "string" || !value.configVersion.trim()) return false; if (!strings(value.changedFiles) || !strings(value.activeRisks) || !strings(value.rejectedHypotheses) || !["pending", "passed", "failed"].includes(String(value.verificationState))) return false; if (!isChecklist(value.checklist)) return false; if (!Array.isArray(value.decisions) || !value.decisions.every((item) => record(item) && typeof item.id === "string" && typeof item.description === "string")) return false; if (!Array.isArray(value.acceptedFacts) || !value.acceptedFacts.every((fact) => record(fact) && typeof fact.id === "string" && typeof fact.claim === "string" && finite(fact.confidence) && fact.confidence <= 1 && Array.isArray(fact.evidence) && fact.evidence.every((evidence) => record(evidence) && typeof evidence.observation === "string"))) return false; const budget = value.remainingBudget; return record(budget) && finite(budget.spentCredits) && finite(budget.hardCap) && finite(budget.softCap) && finite(budget.internalStopTarget) && (budget.dailyCreditBudget === undefined || finite(budget.dailyCreditBudget)) && (budget.weeklyCreditBudget === undefined || finite(budget.weeklyCreditBudget)); } export function ledgerPath(ledgersDir: string, runId: string): string { return join(ledgersDir, `${runId}.json`); } export async function saveLedger(ledgersDir: string, runId: string, ledger: TaskLedger): Promise { if (JSON.stringify(ledger).includes("transcript")) throw new Error("Task Ledger must not contain raw transcripts"); await writeJsonAtomic(ledgerPath(ledgersDir, runId), ledger); } export async function loadLedger(ledgersDir: string, runId: string): Promise { try { const value: unknown = JSON.parse(await readFile(ledgerPath(ledgersDir, runId), "utf8")); if (!isTaskLedger(value)) throw new Error(`Invalid Task Ledger ${runId}`); return value; } catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined; throw error; } }