Просмотр исходного кода

refactor(pipeline): extract chapter state recovery

Ma 7 месяцев назад
Родитель
Сommit
616f807d6f

+ 232 - 0
packages/core/src/__tests__/chapter-state-recovery.test.ts

@@ -0,0 +1,232 @@
+import { describe, expect, it, vi } from "vitest";
+import type { AuditIssue } from "../agents/continuity.js";
+import type {
+  ValidationResult,
+  ValidationWarning,
+} from "../agents/state-validator.js";
+import type { WriteChapterOutput } from "../agents/writer.js";
+import type { BookConfig } from "../models/book.js";
+import type { ChapterMeta } from "../models/chapter.js";
+import {
+  buildStateDegradedPersistenceOutput,
+  buildStateDegradedReviewNote,
+  parseStateDegradedReviewNote,
+  resolveStateDegradedBaseStatus,
+  retrySettlementAfterValidationFailure,
+} from "../pipeline/chapter-state-recovery.js";
+
+function createBook(): BookConfig {
+  return {
+    id: "test-book",
+    title: "Test Book",
+    platform: "tomato",
+    genre: "xuanhuan",
+    status: "active",
+    targetChapters: 10,
+    chapterWordCount: 3000,
+    createdAt: "2026-04-01T00:00:00.000Z",
+    updatedAt: "2026-04-01T00:00:00.000Z",
+  };
+}
+
+function createValidationWarning(
+  overrides: Partial<ValidationWarning> = {},
+): ValidationWarning {
+  return {
+    category: overrides.category ?? "current-state",
+    description: overrides.description ?? "铜牌位置与正文矛盾",
+  };
+}
+
+function createValidationResult(
+  overrides: Partial<ValidationResult> = {},
+): ValidationResult {
+  return {
+    passed: overrides.passed ?? false,
+    warnings: overrides.warnings ?? [createValidationWarning()],
+  };
+}
+
+function createWriteChapterOutput(
+  overrides: Partial<WriteChapterOutput> = {},
+): WriteChapterOutput {
+  return {
+    chapterNumber: 3,
+    title: "第三章",
+    content: "铜牌贴在胸口。",
+    wordCount: "铜牌贴在胸口。".length,
+    preWriteCheck: "ok",
+    postSettlement: "ok",
+    updatedState: "new state",
+    updatedLedger: "new ledger",
+    updatedHooks: "new hooks",
+    chapterSummary: "| 3 | 第三章 |",
+    updatedSubplots: "new subplots",
+    updatedEmotionalArcs: "new emotional arcs",
+    updatedCharacterMatrix: "new character matrix",
+    postWriteErrors: [],
+    postWriteWarnings: [],
+    ...overrides,
+  };
+}
+
+function createChapterMeta(
+  overrides: Partial<ChapterMeta> = {},
+): ChapterMeta {
+  return {
+    number: 3,
+    title: "第三章",
+    status: "state-degraded",
+    wordCount: 1200,
+    createdAt: "2026-04-01T00:00:00.000Z",
+    updatedAt: "2026-04-01T00:00:00.000Z",
+    auditIssues: [],
+    lengthWarnings: [],
+    ...overrides,
+  };
+}
+
+describe("chapter-state-recovery", () => {
+  it("retries settlement with localized validation feedback and recovers on a clean retry", async () => {
+    let capturedFeedback = "";
+    const writer = {
+      settleChapterState: vi.fn(async (input: { validationFeedback?: string }) => {
+        capturedFeedback = input.validationFeedback ?? "";
+        return createWriteChapterOutput({
+          updatedState: "fixed state",
+          updatedHooks: "fixed hooks",
+        });
+      }),
+    };
+    const validator = {
+      validate: vi.fn(async () => createValidationResult({
+        passed: true,
+        warnings: [],
+      })),
+    };
+    const logWarn = vi.fn();
+    const warn = vi.fn();
+
+    const result = await retrySettlementAfterValidationFailure({
+      writer: writer as never,
+      validator: validator as never,
+      book: createBook(),
+      bookDir: "/tmp/test-book",
+      chapterNumber: 3,
+      title: "第三章",
+      content: "铜牌贴在胸口。",
+      oldState: "old state",
+      oldHooks: "old hooks",
+      originalValidation: createValidationResult(),
+      language: "zh",
+      logWarn,
+      logger: { warn } as never,
+    });
+
+    expect(result.kind).toBe("recovered");
+    expect(capturedFeedback).toContain("上一次状态结算未通过校验");
+    expect(capturedFeedback).toContain("铜牌位置与正文矛盾");
+    expect(logWarn).toHaveBeenCalledWith(expect.objectContaining({
+      zh: expect.stringContaining("仅重试结算层"),
+    }));
+    expect(warn).not.toHaveBeenCalled();
+  });
+
+  it("returns localized degraded issues when settlement retry still fails", async () => {
+    const validatorWarning = createValidationWarning({
+      description: "挂坠状态仍与正文冲突",
+    });
+    const result = await retrySettlementAfterValidationFailure({
+      writer: {
+        settleChapterState: vi.fn(async () => createWriteChapterOutput()),
+      } as never,
+      validator: {
+        validate: vi.fn(async () => createValidationResult({
+          passed: false,
+          warnings: [validatorWarning],
+        })),
+      } as never,
+      book: createBook(),
+      bookDir: "/tmp/test-book",
+      chapterNumber: 3,
+      title: "第三章",
+      content: "铜牌贴在胸口。",
+      oldState: "old state",
+      oldHooks: "old hooks",
+      originalValidation: createValidationResult({
+        warnings: [validatorWarning],
+      }),
+      language: "zh",
+      logWarn: vi.fn(),
+      logger: { warn: vi.fn() } as never,
+    });
+
+    expect(result.kind).toBe("degraded");
+    if (result.kind === "degraded") {
+      expect(result.issues).toEqual([
+        expect.objectContaining({
+          category: "state-validation",
+          description: "挂坠状态仍与正文冲突",
+          suggestion: "请先基于已保存正文修复本章 state,再继续后续章节。",
+        }),
+      ]);
+    }
+  });
+
+  it("freezes truth outputs when degrading persisted settlement", () => {
+    const output = createWriteChapterOutput({
+      runtimeStateDelta: { chapter: 3 } as never,
+      runtimeStateSnapshot: {
+        chapter: 3,
+        facts: [],
+        hooks: [],
+        chapterSummary: undefined,
+      } as never,
+      updatedChapterSummaries: "| 3 | 新摘要 |",
+    });
+
+    const degraded = buildStateDegradedPersistenceOutput({
+      output,
+      oldState: "stable state",
+      oldHooks: "stable hooks",
+      oldLedger: "stable ledger",
+    });
+
+    expect(degraded.updatedState).toBe("stable state");
+    expect(degraded.updatedHooks).toBe("stable hooks");
+    expect(degraded.updatedLedger).toBe("stable ledger");
+    expect(degraded.runtimeStateDelta).toBeUndefined();
+    expect(degraded.runtimeStateSnapshot).toBeUndefined();
+    expect(degraded.updatedChapterSummaries).toBeUndefined();
+  });
+
+  it("round-trips degraded review metadata and resolves fallback base status", () => {
+    const issues: AuditIssue[] = [{
+      severity: "warning",
+      category: "state-validation",
+      description: "状态结算重试后仍未通过校验。",
+      suggestion: "请先基于已保存正文修复本章 state,再继续后续章节。",
+    }];
+    const note = buildStateDegradedReviewNote("audit-failed", issues);
+
+    expect(parseStateDegradedReviewNote(note)).toEqual({
+      kind: "state-degraded",
+      baseStatus: "audit-failed",
+      injectedIssues: ["[warning] 状态结算重试后仍未通过校验。"],
+    });
+
+    expect(resolveStateDegradedBaseStatus(createChapterMeta({
+      reviewNote: note,
+    }))).toBe("audit-failed");
+
+    expect(resolveStateDegradedBaseStatus(createChapterMeta({
+      reviewNote: "{bad json",
+      auditIssues: ["[critical] still broken"],
+    }))).toBe("audit-failed");
+
+    expect(resolveStateDegradedBaseStatus(createChapterMeta({
+      reviewNote: "{bad json",
+      auditIssues: ["[warning] needs review"],
+    }))).toBe("ready-for-review");
+  });
+});

+ 235 - 0
packages/core/src/pipeline/chapter-state-recovery.ts

@@ -0,0 +1,235 @@
+import type { AuditIssue } from "../agents/continuity.js";
+import type {
+  ValidationResult,
+  ValidationWarning,
+} from "../agents/state-validator.js";
+import type { StateValidatorAgent } from "../agents/state-validator.js";
+import type { WriteChapterOutput } from "../agents/writer.js";
+import type { WriterAgent } from "../agents/writer.js";
+import type { Logger } from "../utils/logger.js";
+import type { BookConfig } from "../models/book.js";
+import type { ChapterMeta } from "../models/chapter.js";
+import type { ContextPackage, RuleStack } from "../models/input-governance.js";
+import type { LengthLanguage } from "../utils/length-metrics.js";
+
+export interface SettlementRetryParams {
+  readonly writer: Pick<WriterAgent, "settleChapterState">;
+  readonly validator: Pick<StateValidatorAgent, "validate">;
+  readonly book: BookConfig;
+  readonly bookDir: string;
+  readonly chapterNumber: number;
+  readonly title: string;
+  readonly content: string;
+  readonly reducedControlInput?: {
+    chapterIntent: string;
+    contextPackage: ContextPackage;
+    ruleStack: RuleStack;
+  };
+  readonly oldState: string;
+  readonly oldHooks: string;
+  readonly originalValidation: ValidationResult;
+  readonly language: LengthLanguage;
+  readonly logWarn?: (message: { zh: string; en: string }) => void;
+  readonly logger?: Pick<Logger, "warn">;
+}
+
+export type SettlementRetryResult =
+  | {
+    readonly kind: "recovered";
+    readonly output: WriteChapterOutput;
+    readonly validation: ValidationResult;
+  }
+  | {
+    readonly kind: "degraded";
+    readonly issues: ReadonlyArray<AuditIssue>;
+  };
+
+export async function retrySettlementAfterValidationFailure(
+  params: SettlementRetryParams,
+): Promise<SettlementRetryResult> {
+  params.logWarn?.({
+    zh: `状态校验失败,正在仅重试结算层(第${params.chapterNumber}章)`,
+    en: `State validation failed; retrying settlement only for chapter ${params.chapterNumber}`,
+  });
+
+  const retryOutput = await params.writer.settleChapterState({
+    book: params.book,
+    bookDir: params.bookDir,
+    chapterNumber: params.chapterNumber,
+    title: params.title,
+    content: params.content,
+    chapterIntent: params.reducedControlInput?.chapterIntent,
+    contextPackage: params.reducedControlInput?.contextPackage,
+    ruleStack: params.reducedControlInput?.ruleStack,
+    validationFeedback: buildStateValidationFeedback(
+      params.originalValidation.warnings,
+      params.language,
+    ),
+  });
+
+  let retryValidation: ValidationResult;
+  try {
+    retryValidation = await params.validator.validate(
+      params.content,
+      params.chapterNumber,
+      params.oldState,
+      retryOutput.updatedState,
+      params.oldHooks,
+      retryOutput.updatedHooks,
+      params.language,
+    );
+  } catch (error) {
+    throw new Error(`State validation retry failed for chapter ${params.chapterNumber}: ${String(error)}`);
+  }
+
+  if (retryValidation.warnings.length > 0) {
+    params.logWarn?.({
+      zh: `状态校验重试后,第${params.chapterNumber}章仍有 ${retryValidation.warnings.length} 条警告`,
+      en: `State validation retry still reports ${retryValidation.warnings.length} warning(s) for chapter ${params.chapterNumber}`,
+    });
+    for (const warning of retryValidation.warnings) {
+      params.logger?.warn(`  [${warning.category}] ${warning.description}`);
+    }
+  }
+
+  if (retryValidation.passed) {
+    return {
+      kind: "recovered",
+      output: retryOutput,
+      validation: retryValidation,
+    };
+  }
+
+  return {
+    kind: "degraded",
+    issues: buildStateDegradedIssues(retryValidation.warnings, params.language),
+  };
+}
+
+export function buildStateValidationFeedback(
+  warnings: ReadonlyArray<ValidationWarning>,
+  language: LengthLanguage,
+): string {
+  if (warnings.length === 0) {
+    return language === "en"
+      ? "The previous settlement contradicted the chapter text. Reconcile truth files strictly to the body."
+      : "上一次状态结算与正文矛盾。请严格以正文为准修正 truth files。";
+  }
+
+  if (language === "en") {
+    return [
+      "The previous settlement failed validation. Fix these contradictions against the chapter body:",
+      ...warnings.map((warning) => `- [${warning.category}] ${warning.description}`),
+    ].join("\n");
+  }
+
+  return [
+    "上一次状态结算未通过校验。请对照正文修正以下矛盾:",
+    ...warnings.map((warning) => `- [${warning.category}] ${warning.description}`),
+  ].join("\n");
+}
+
+export function buildStateDegradedIssues(
+  warnings: ReadonlyArray<ValidationWarning>,
+  language: LengthLanguage,
+): ReadonlyArray<AuditIssue> {
+  if (warnings.length > 0) {
+    return warnings.map((warning) => ({
+      severity: "warning" as const,
+      category: "state-validation",
+      description: warning.description,
+      suggestion: language === "en"
+        ? "Repair chapter state from the persisted body before continuing."
+        : "请先基于已保存正文修复本章 state,再继续后续章节。",
+    }));
+  }
+
+  return [{
+    severity: "warning",
+    category: "state-validation",
+    description: language === "en"
+      ? "State validation still failed after settlement retry."
+      : "状态结算重试后仍未通过校验。",
+    suggestion: language === "en"
+      ? "Repair chapter state from the persisted body before continuing."
+      : "请先基于已保存正文修复本章 state,再继续后续章节。",
+  }];
+}
+
+export function buildStateDegradedPersistenceOutput(params: {
+  readonly output: WriteChapterOutput;
+  readonly oldState: string;
+  readonly oldHooks: string;
+  readonly oldLedger: string;
+}): WriteChapterOutput {
+  return {
+    ...params.output,
+    runtimeStateDelta: undefined,
+    runtimeStateSnapshot: undefined,
+    updatedState: params.oldState,
+    updatedLedger: params.oldLedger,
+    updatedHooks: params.oldHooks,
+    updatedChapterSummaries: undefined,
+  };
+}
+
+export interface StateDegradedReviewNote {
+  readonly kind: "state-degraded";
+  readonly baseStatus: "ready-for-review" | "audit-failed";
+  readonly injectedIssues: ReadonlyArray<string>;
+}
+
+export function buildStateDegradedReviewNote(
+  baseStatus: "ready-for-review" | "audit-failed",
+  issues: ReadonlyArray<AuditIssue>,
+): string {
+  return JSON.stringify({
+    kind: "state-degraded",
+    baseStatus,
+    injectedIssues: issues.map((issue) => `[${issue.severity}] ${issue.description}`),
+  } satisfies StateDegradedReviewNote);
+}
+
+export function parseStateDegradedReviewNote(
+  reviewNote?: string,
+): StateDegradedReviewNote | null {
+  if (!reviewNote) {
+    return null;
+  }
+
+  try {
+    const parsed = JSON.parse(reviewNote) as {
+      kind?: unknown;
+      baseStatus?: unknown;
+      injectedIssues?: unknown;
+    };
+    if (
+      parsed.kind !== "state-degraded"
+      || (parsed.baseStatus !== "ready-for-review" && parsed.baseStatus !== "audit-failed")
+      || !Array.isArray(parsed.injectedIssues)
+    ) {
+      return null;
+    }
+
+    return {
+      kind: "state-degraded",
+      baseStatus: parsed.baseStatus,
+      injectedIssues: parsed.injectedIssues.filter((issue): issue is string => typeof issue === "string"),
+    };
+  } catch {
+    return null;
+  }
+}
+
+export function resolveStateDegradedBaseStatus(
+  chapter: Pick<ChapterMeta, "reviewNote" | "auditIssues">,
+): "ready-for-review" | "audit-failed" {
+  const metadata = parseStateDegradedReviewNote(chapter.reviewNote);
+  if (metadata) {
+    return metadata.baseStatus;
+  }
+
+  return chapter.auditIssues.some((issue) => issue.startsWith("[critical]"))
+    ? "audit-failed"
+    : "ready-for-review";
+}

+ 17 - 215
packages/core/src/pipeline/runner.ts

@@ -34,6 +34,13 @@ import { loadNarrativeMemorySeed, loadSnapshotCurrentStateFacts } from "../state
 import { rewriteStructuredStateFromMarkdown } from "../state/state-bootstrap.js";
 import { readFile, readdir, writeFile, mkdir, rename, rm, stat } from "node:fs/promises";
 import { join, relative } from "node:path";
+import {
+  buildStateDegradedPersistenceOutput,
+  buildStateDegradedReviewNote,
+  parseStateDegradedReviewNote,
+  resolveStateDegradedBaseStatus,
+  retrySettlementAfterValidationFailure,
+} from "./chapter-state-recovery.js";
 
 export interface PipelineConfig {
   readonly client: LLMClient;
@@ -1297,7 +1304,7 @@ export class PipelineRunner {
       }
     }
     if (!validation.passed) {
-      const recovery = await this.retrySettlementAfterValidationFailure({
+      const recovery = await retrySettlementAfterValidationFailure({
         writer,
         validator,
         book,
@@ -1310,6 +1317,8 @@ export class PipelineRunner {
         oldHooks,
         originalValidation: validation,
         language: pipelineLang,
+        logWarn: (message) => this.logWarn(pipelineLang, message),
+        logger: this.config.logger,
       });
 
       if (recovery.kind === "recovered") {
@@ -1318,7 +1327,7 @@ export class PipelineRunner {
       } else {
         chapterStatus = "state-degraded";
         degradedIssues = recovery.issues;
-        persistenceOutput = this.buildStateDegradedPersistenceOutput({
+        persistenceOutput = buildStateDegradedPersistenceOutput({
           output: persistenceOutput,
           oldState,
           oldHooks,
@@ -1388,7 +1397,7 @@ export class PipelineRunner {
       ),
       lengthWarnings,
       reviewNote: chapterStatus === "state-degraded"
-        ? this.buildStateDegradedReviewNote(
+        ? buildStateDegradedReviewNote(
             auditResult.passed ? "ready-for-review" : "audit-failed",
             degradedIssues,
           )
@@ -1514,7 +1523,7 @@ export class PipelineRunner {
     );
 
     if (!validation.passed) {
-      const recovery = await this.retrySettlementAfterValidationFailure({
+      const recovery = await retrySettlementAfterValidationFailure({
         writer,
         validator,
         book,
@@ -1526,6 +1535,8 @@ export class PipelineRunner {
         oldHooks,
         originalValidation: validation,
         language: pipelineLang,
+        logWarn: (message) => this.logWarn(pipelineLang, message),
+        logger: this.config.logger,
       });
       if (recovery.kind !== "recovered") {
         throw new Error(
@@ -1548,8 +1559,8 @@ export class PipelineRunner {
     await this.state.snapshotState(bookId, targetChapter);
     await this.syncCurrentStateFactHistory(bookId, targetChapter);
 
-    const baseStatus = this.resolveStateDegradedBaseStatus(targetMeta);
-    const degradedMetadata = this.parseStateDegradedReviewNote(targetMeta.reviewNote);
+    const baseStatus = resolveStateDegradedBaseStatus(targetMeta);
+    const degradedMetadata = parseStateDegradedReviewNote(targetMeta.reviewNote);
     const injectedIssues = new Set(degradedMetadata?.injectedIssues ?? []);
     index[targetIndex] = {
       ...targetMeta,
@@ -2031,215 +2042,6 @@ ${matrix}`,
     );
   }
 
-  private async retrySettlementAfterValidationFailure(params: {
-    readonly writer: WriterAgent;
-    readonly validator: StateValidatorAgent;
-    readonly book: BookConfig;
-    readonly bookDir: string;
-    readonly chapterNumber: number;
-    readonly title: string;
-    readonly content: string;
-    readonly reducedControlInput?: {
-      chapterIntent: string;
-      contextPackage: ContextPackage;
-      ruleStack: RuleStack;
-    };
-    readonly oldState: string;
-    readonly oldHooks: string;
-    readonly originalValidation: ValidationResult;
-    readonly language: LengthLanguage;
-  }): Promise<
-    | {
-      readonly kind: "recovered";
-      readonly output: WriteChapterOutput;
-      readonly validation: ValidationResult;
-    }
-    | {
-      readonly kind: "degraded";
-      readonly issues: ReadonlyArray<AuditIssue>;
-    }
-  > {
-    this.logWarn(params.language, {
-      zh: `状态校验失败,正在仅重试结算层(第${params.chapterNumber}章)`,
-      en: `State validation failed; retrying settlement only for chapter ${params.chapterNumber}`,
-    });
-
-    const retryOutput = await params.writer.settleChapterState({
-      book: params.book,
-      bookDir: params.bookDir,
-      chapterNumber: params.chapterNumber,
-      title: params.title,
-      content: params.content,
-      chapterIntent: params.reducedControlInput?.chapterIntent,
-      contextPackage: params.reducedControlInput?.contextPackage,
-      ruleStack: params.reducedControlInput?.ruleStack,
-      validationFeedback: this.buildStateValidationFeedback(
-        params.originalValidation.warnings,
-        params.language,
-      ),
-    });
-
-    let retryValidation: ValidationResult;
-    try {
-      retryValidation = await params.validator.validate(
-        params.content,
-        params.chapterNumber,
-        params.oldState,
-        retryOutput.updatedState,
-        params.oldHooks,
-        retryOutput.updatedHooks,
-        params.language,
-      );
-    } catch (error) {
-      throw new Error(`State validation retry failed for chapter ${params.chapterNumber}: ${String(error)}`);
-    }
-
-    if (retryValidation.warnings.length > 0) {
-      this.logWarn(params.language, {
-        zh: `状态校验重试后,第${params.chapterNumber}章仍有 ${retryValidation.warnings.length} 条警告`,
-        en: `State validation retry still reports ${retryValidation.warnings.length} warning(s) for chapter ${params.chapterNumber}`,
-      });
-      for (const warning of retryValidation.warnings) {
-        this.config.logger?.warn(`  [${warning.category}] ${warning.description}`);
-      }
-    }
-
-    if (retryValidation.passed) {
-      return {
-        kind: "recovered",
-        output: retryOutput,
-        validation: retryValidation,
-      };
-    }
-
-    return {
-      kind: "degraded",
-      issues: this.buildStateDegradedIssues(retryValidation.warnings, params.language),
-    };
-  }
-
-  private buildStateValidationFeedback(
-    warnings: ReadonlyArray<ValidationWarning>,
-    language: LengthLanguage,
-  ): string {
-    if (warnings.length === 0) {
-      return language === "en"
-        ? "The previous settlement contradicted the chapter text. Reconcile truth files strictly to the body."
-        : "上一次状态结算与正文矛盾。请严格以正文为准修正 truth files。";
-    }
-
-    if (language === "en") {
-      return [
-        "The previous settlement failed validation. Fix these contradictions against the chapter body:",
-        ...warnings.map((warning) => `- [${warning.category}] ${warning.description}`),
-      ].join("\n");
-    }
-
-    return [
-      "上一次状态结算未通过校验。请对照正文修正以下矛盾:",
-      ...warnings.map((warning) => `- [${warning.category}] ${warning.description}`),
-    ].join("\n");
-  }
-
-  private buildStateDegradedIssues(
-    warnings: ReadonlyArray<ValidationWarning>,
-    language: LengthLanguage,
-  ): ReadonlyArray<AuditIssue> {
-    if (warnings.length > 0) {
-      return warnings.map((warning) => ({
-        severity: "warning" as const,
-        category: "state-validation",
-        description: warning.description,
-        suggestion: language === "en"
-          ? "Repair chapter state from the persisted body before continuing."
-          : "请先基于已保存正文修复本章 state,再继续后续章节。",
-      }));
-    }
-
-    return [{
-      severity: "warning",
-      category: "state-validation",
-      description: language === "en"
-        ? "State validation still failed after settlement retry."
-        : "状态结算重试后仍未通过校验。",
-      suggestion: language === "en"
-        ? "Repair chapter state from the persisted body before continuing."
-        : "请先基于已保存正文修复本章 state,再继续后续章节。",
-    }];
-  }
-
-  private buildStateDegradedPersistenceOutput(params: {
-    readonly output: WriteChapterOutput;
-    readonly oldState: string;
-    readonly oldHooks: string;
-    readonly oldLedger: string;
-  }): WriteChapterOutput {
-    return {
-      ...params.output,
-      runtimeStateDelta: undefined,
-      runtimeStateSnapshot: undefined,
-      updatedState: params.oldState,
-      updatedLedger: params.oldLedger,
-      updatedHooks: params.oldHooks,
-      updatedChapterSummaries: undefined,
-    };
-  }
-
-  private buildStateDegradedReviewNote(
-    baseStatus: "ready-for-review" | "audit-failed",
-    issues: ReadonlyArray<AuditIssue>,
-  ): string {
-    return JSON.stringify({
-      kind: "state-degraded",
-      baseStatus,
-      injectedIssues: issues.map((issue) => `[${issue.severity}] ${issue.description}`),
-    });
-  }
-
-  private parseStateDegradedReviewNote(reviewNote?: string): {
-    readonly kind: "state-degraded";
-    readonly baseStatus: "ready-for-review" | "audit-failed";
-    readonly injectedIssues: ReadonlyArray<string>;
-  } | null {
-    if (!reviewNote) {
-      return null;
-    }
-
-    try {
-      const parsed = JSON.parse(reviewNote) as {
-        kind?: unknown;
-        baseStatus?: unknown;
-        injectedIssues?: unknown;
-      };
-      if (
-        parsed.kind !== "state-degraded"
-        || (parsed.baseStatus !== "ready-for-review" && parsed.baseStatus !== "audit-failed")
-        || !Array.isArray(parsed.injectedIssues)
-      ) {
-        return null;
-      }
-
-      return {
-        kind: "state-degraded",
-        baseStatus: parsed.baseStatus,
-        injectedIssues: parsed.injectedIssues.filter((issue): issue is string => typeof issue === "string"),
-      };
-    } catch {
-      return null;
-    }
-  }
-
-  private resolveStateDegradedBaseStatus(chapter: ChapterMeta): "ready-for-review" | "audit-failed" {
-    const metadata = this.parseStateDegradedReviewNote(chapter.reviewNote);
-    if (metadata) {
-      return metadata.baseStatus;
-    }
-
-    return chapter.auditIssues.some((issue) => issue.startsWith("[critical]"))
-      ? "audit-failed"
-      : "ready-for-review";
-  }
-
   // ---------------------------------------------------------------------------
   // Helpers
   // ---------------------------------------------------------------------------