diff --git a/packages/core/src/state/state-reducer.ts b/packages/core/src/state/state-reducer.ts index 3370b0ee..d73c3c23 100644 --- a/packages/core/src/state/state-reducer.ts +++ b/packages/core/src/state/state-reducer.ts @@ -67,8 +67,14 @@ export function applyRuntimeStateDelta(params: { }; const issues = validateRuntimeState(next); - if (issues.length > 0) { - throw new Error(issues.map((issue) => `${issue.code}: ${issue.message}`).join("; ")); + const fatalIssues = issues.filter( + (issue) => + !["missing_state_change", "unsupported_change", "temporal_impossibility", "validator_crash"].includes( + issue.code, + ), + ); + if (fatalIssues.length > 0) { + throw new Error(fatalIssues.map((issue) => `${issue.code}: ${issue.message}`).join("; ")); } return next; diff --git a/packages/core/src/state/state-validator.ts b/packages/core/src/state/state-validator.ts index 81846e4f..4391b8d4 100644 --- a/packages/core/src/state/state-validator.ts +++ b/packages/core/src/state/state-validator.ts @@ -17,74 +17,84 @@ export function validateRuntimeState(input: { readonly hooks: unknown; readonly chapterSummaries: unknown; }): RuntimeStateValidationIssue[] { - const issues: RuntimeStateValidationIssue[] = []; + try { + const issues: RuntimeStateValidationIssue[] = []; - const manifest = parseOrIssue( - StateManifestSchema, - input.manifest, - issues, - "invalid_manifest", - "manifest", - ); - const currentState = parseOrIssue( - CurrentStateStateSchema, - input.currentState, - issues, - "invalid_current_state", - "currentState", - ); - const hooks = parseOrIssue( - HooksStateSchema, - input.hooks, - issues, - "invalid_hooks_state", - "hooks", - ); - const chapterSummaries = parseOrIssue( - ChapterSummariesStateSchema, - input.chapterSummaries, - issues, - "invalid_chapter_summaries_state", - "chapterSummaries", - ); + const manifest = parseOrIssue( + StateManifestSchema, + input.manifest, + issues, + "invalid_manifest", + "manifest", + ); + const currentState = parseOrIssue( + CurrentStateStateSchema, + input.currentState, + issues, + "invalid_current_state", + "currentState", + ); + const hooks = parseOrIssue( + HooksStateSchema, + input.hooks, + issues, + "invalid_hooks_state", + "hooks", + ); + const chapterSummaries = parseOrIssue( + ChapterSummariesStateSchema, + input.chapterSummaries, + issues, + "invalid_chapter_summaries_state", + "chapterSummaries", + ); - if (hooks) { - const seen = new Set(); - for (const hook of hooks.hooks) { - if (seen.has(hook.hookId)) { - issues.push({ - code: "duplicate_hook_id", - message: `duplicate hook id: ${hook.hookId}`, - path: `hooks.${hook.hookId}`, - }); + if (hooks) { + const seen = new Set(); + for (const hook of hooks.hooks) { + if (seen.has(hook.hookId)) { + issues.push({ + code: "duplicate_hook_id", + message: `duplicate hook id: ${hook.hookId}`, + path: `hooks.${hook.hookId}`, + }); + } + seen.add(hook.hookId); } - seen.add(hook.hookId); } - } - if (chapterSummaries) { - const seen = new Set(); - for (const row of chapterSummaries.rows) { - if (seen.has(row.chapter)) { - issues.push({ - code: "duplicate_summary_chapter", - message: `duplicate summary chapter: ${row.chapter}`, - path: `chapterSummaries.${row.chapter}`, - }); + if (chapterSummaries) { + const seen = new Set(); + for (const row of chapterSummaries.rows) { + if (seen.has(row.chapter)) { + issues.push({ + code: "duplicate_summary_chapter", + message: `duplicate summary chapter: ${row.chapter}`, + path: `chapterSummaries.${row.chapter}`, + }); + } + seen.add(row.chapter); } - seen.add(row.chapter); } - } - if (manifest && currentState && currentState.chapter > manifest.lastAppliedChapter) { - issues.push({ - code: "current_state_ahead_of_manifest", - message: `current state chapter ${currentState.chapter} exceeds manifest ${manifest.lastAppliedChapter}`, - path: "currentState.chapter", - }); - } + if (manifest && currentState && currentState.chapter > manifest.lastAppliedChapter) { + issues.push({ + code: "current_state_ahead_of_manifest", + message: `current state chapter ${currentState.chapter} exceeds manifest ${manifest.lastAppliedChapter}`, + path: "currentState.chapter", + }); + } - return issues; + return issues; + } catch (error) { + return [ + { + code: "validator_crash", + message: String(error), + path: "", + }, + ]; + } } function parseOrIssue(