状态修复缺少非破坏性出口:基线快照缺失时 resync/repair-state/delete 全部拒绝,且报错不指路(附可用绕过途径)
环境
@actalk/inkos1.8.0,Node v24.14.0,Linux- 故障态:
chapters/index.json为 1..15,但story/snapshots/只有0..13(第14章的结算被跳过,第15章已有正文)
⚠️ 先说清楚:这不是"无法修复"。 我最初的结论是"无解死锁",复核后被自己推翻——存在至少 3 条官方途径(见下方第 3 节,其中
write rewrite已实测跑通)。本 issue 真正要报的是:缺少非破坏性的修复出口,且报错完全不指路。
1. 现象:三个"状态修复"工具在该状态下全部拒绝
$ 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
// :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
// :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-2762 → rollbackToChapter(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 / 2442的snapshotState)与本项目已有的 15 个快照佐证。 - 未实测
write next在该状态的最终失败点(它先到 LLM 阶段;基线校验在settleChapterState内部),"也被堵"属推断,故未在本 issue 中主张。 - 结论基于已安装的 v1.8.0 编译产物(即实际运行的代码),未克隆 TS 源码;三条报错与真实项目中观察到的逐字一致。
Source: Narcooo/inkos