#394·inkos

状态修复缺少非破坏性出口:基线快照缺失时 resync/repair-state/delete 全部拒绝,且报错不指路(附可用绕过途径)

Author: pengyzCreated Sep 15, 2026Updated Sep 15, 2026

环境

  • @actalk/inkos 1.8.0,Node v24.14.0,Linux
  • 故障态:chapters/index.json 为 1..15,但 story/snapshots/ 只有 0..13(第14章的结算被跳过,第15章已有正文)

⚠️ 先说清楚:这不是"无法修复"。 我最初的结论是"无解死锁",复核后被自己推翻——存在至少 3 条官方途径(见下方第 3 节,其中 write rewrite 已实测跑通)。本 issue 真正要报的是:缺少非破坏性的修复出口,且报错完全不指路。

1. 现象:三个"状态修复"工具在该状态下全部拒绝

bash
$ inkos write repair-state 南渡北辰 14
Chapter 14 is not state-degraded.

$ inkos chapter delete 南渡北辰 --chapter 15
Cannot delete chapter 15: the state snapshot for chapter 14 is missing
(story/snapshots/14/current_state.md). Nothing was changed.

$ inkos chapter delete 南渡北辰 --chapter 14
Only the latest chapter (15) can be deleted, but chapter 14 was requested.

2. 前置条件(已逐条核实,均属实)

@actalk/inkos-core/dist/pipeline/runner.js

javascript
// :1882-1885  resync:必须是最新章
const latestChapter = Math.max(...index.map((chapter) => chapter.number));
if (targetChapter !== latestChapter) {
    throw new Error(`Only the latest persisted chapter can be synced safely (latest is ${latestChapter}).`);
}

// :1891-1897  resync 还要求基线快照 N−1 —— 所以连最新的 15 也救不了
const baselineChapter = targetChapter - 1;
const baselineStoryDir = join(bookDir, "story", "snapshots", String(baselineChapter));
const [oldState, oldHooks] = await Promise.all([...]).catch((error) => {
    throw new Error(`Cannot sync chapter ${targetChapter} safely: baseline snapshot ${baselineChapter} is unavailable (${String(error)})`);
});

// :1777-1783  repair-state:status 必须为 state-degraded 且必须是最新章
if (targetMeta.status !== "state-degraded") {
    throw new Error(`Chapter ${targetChapter} is not state-degraded.`);
}
if (targetChapter !== latestChapter) {
    throw new Error(`Only the latest state-degraded chapter can be repaired safely (latest is ${latestChapter}).`);
}

@actalk/inkos-core/dist/state/chapter-delete.js

javascript
// :18-23  中章禁令
// :25-37  回滚目标快照必须可用,否则"Nothing was changed"
const rollbackTarget = latest - 1;
for (const required of ["current_state.md", "pending_hooks.md"]) {
    const snapshotFile = join(bookDir, "story", "snapshots", String(rollbackTarget), required);
    try { await stat(snapshotFile); }
    catch {
        throw new Error(`Cannot delete chapter ${latest}: the state snapshot for chapter ${rollbackTarget} is missing `
            + `(story/snapshots/${rollbackTarget}/${required}). Nothing was changed.`);
    }
}

三条闸门在"最新章缺基线快照"这个状态下形成合围resync/repair-state 处理不了非最新章,delete 又因为要读同一个缺失快照而动不了。

实测:在 /tmp 副本里重建该故障态,上述三条报错逐字复现

3. 但存在绕过途径(这部分是我最初漏掉的,请以此为准)

途径 是否可用 代价
inkos write rewrite <book> 14 --force 实测跑通 write.js:162-166 只校验 snapshot 13(存在)→ 171-193 删 14/15 并裁剪索引 → 195 restoreState(13) → 201 next=14。正文被 unlink 硬删,不进 .trash
inkos import chapters --resume-from 14 --from <源> ✅ 可用 runner.js:2340 起从14、跳过真相文件重置;2389-2442 逐章 replay 并无条件 snapshotState非破坏,但要重放章节
inkos review reject <book> 14 ✅ 可用 review.js:201-202 + manager.js:626-630 回滚到13;丢弃第15章
Studio UI 第14章 reject ✅ 等价上一条 server.js:2752-2762rollbackToChapter(13)

所以准确表述是:在"最新章缺基线快照"时,唯一能用的官方途径都带破坏性或很重——要么丢正文、要么丢后续章、要么整章重放。没有一条"只把缺失快照补出来、其它什么都不动"的路。

4. 请求(按价值排序)

4.1 【主要】提供非破坏性的"重建缺失快照"途径

现在的合围之所以成立,是因为所有修复动作都以"回滚到 N−1"为前提。但代码里其实已经具备所需能力——_repairChapterStateLocked 会调 writer.settleChapterState({ baselineChapter, chapterNumber, content, ... }) 重新结算任意一章,只是被两道闸门挡住:

  • 它要求 status === "state-degraded"
  • 它要求 targetChapter === latestChapter

建议:

  • 新增显式命令(如 inkos chapter rebuild-snapshot <book> <n>)或在 repair-state 上加开关,允许在基线快照缺失时用"更早的可用快照 + 该章正文"重建,并写入 snapshots/<n>/
  • 或者至少放宽 repair-state 的 latest-only 限制("修复历史章"本身是安全操作,前提是不改正文——它确实不改)。

4.2 【次要】报错应该指路

现在三条报错都只陈述"不行",不提示任何可用出口。建议在错误信息里直接给出下一步,例如:

Cannot delete chapter 15: the state snapshot for chapter 14 is missing (…). Nothing was changed.
Hint: you can rebuild state from an earlier snapshot with `inkos write rewrite <book> 14`
      (this removes chapter 14 and all later chapters), or replay with
      `inkos import chapters <book> --resume-from 14 --from <dir>`.

4.3 【次要】允许删除中间章

chapter-delete.js:18-23 目前完全禁止("Only the latest chapter can be deleted")。当章节质量问题出在中段(本文即如此:第14章质量可疑、想删 14 和 15),用户没有官方途径。 (注:Studio 前端 bundle 里 删除本章 / canDelete / deleteChapter,但后端走同一个 deleteLatestChapter,点第14章必然失败——UI 与后端能力不一致,容易让人以为"没有按钮"。)

5. 不确定项(如实标注)

  • 我用 --api-key-env INKOS_FAKE_MISSING 让 LLM 步骤必然 401,因此 write rewrite / import闸门之后的生成与结算阶段未端到端跑通;该部分由代码(runner.js:780 / 1717 / 2442snapshotState)与本项目已有的 15 个快照佐证。
  • 未实测 write next 在该状态的最终失败点(它先到 LLM 阶段;基线校验在 settleChapterState 内部),"也被堵"属推断,故未在本 issue 中主张。
  • 结论基于已安装的 v1.8.0 编译产物(即实际运行的代码),未克隆 TS 源码;三条报错与真实项目中观察到的逐字一致。