// ScratchpadSummary — structured normalization of a tree-split or // long-running scratchpad. Distinct from EvidenceRecord because a // scratchpad accumulates across many calls; this schema captures the // state at a checkpoint moment. import { ValidationResult, requireString, requireIsoTimestamp, requireProvenance, requireStringArray, } from "./types"; export const SCRATCHPAD_SCHEMA_VERSION = 1; export interface ScratchpadSummary { schema_version: number; run_id: string; current_objective: string; completed_steps: string[]; failed_steps: string[]; pending_steps: string[]; important_paths: string[]; // file paths the scratchpad references decisions: string[]; // architectural/scope decisions made unresolved_questions: string[]; validation_status: "pass" | "fail" | "partial" | "pending"; next_command?: string; // recommendation for next action source_scratchpad_hash: string; // sha256 of the full source scratchpad text — diff detection summarized_at: string; // ISO 8601 provenance: { source_file: string; line_offset?: number; sig_hash: string; recorded_at: string }; } const STATUS = ["pass", "fail", "partial", "pending"]; export function validateScratchpadSummary(input: unknown): ValidationResult { const errors: string[] = []; if (typeof input !== "object" || input === null) return { valid: false, errors: ["expected object"] }; const r = input as Record; let ok = true; if (r.schema_version !== SCRATCHPAD_SCHEMA_VERSION) { errors.push(`schema_version: expected ${SCRATCHPAD_SCHEMA_VERSION}, got ${JSON.stringify(r.schema_version)}`); ok = false; } ok = requireString(r.run_id, "run_id", errors) && ok; ok = requireString(r.current_objective, "current_objective", errors) && ok; ok = requireIsoTimestamp(r.summarized_at, "summarized_at", errors) && ok; if (typeof r.source_scratchpad_hash !== "string" || !/^[0-9a-f]{64}$/.test(r.source_scratchpad_hash as string)) { errors.push("source_scratchpad_hash: must be hex sha256"); ok = false; } ok = requireStringArray(r.completed_steps, "completed_steps", errors) && ok; ok = requireStringArray(r.failed_steps, "failed_steps", errors) && ok; ok = requireStringArray(r.pending_steps, "pending_steps", errors) && ok; ok = requireStringArray(r.important_paths, "important_paths", errors) && ok; ok = requireStringArray(r.decisions, "decisions", errors) && ok; ok = requireStringArray(r.unresolved_questions, "unresolved_questions", errors) && ok; if (!STATUS.includes(r.validation_status as string)) { errors.push(`validation_status: must be one of ${STATUS.join("|")}`); ok = false; } if (r.next_command !== undefined && typeof r.next_command !== "string") { errors.push("next_command: expected string when present"); ok = false; } ok = requireProvenance(r.provenance, "provenance", errors) && ok; if (!ok) return { valid: false, errors }; return { valid: true, value: r as unknown as ScratchpadSummary }; }