feat(runtime): prepare safe context checkpoints

This commit is contained in:
2026-07-29 08:46:23 +08:00
parent d0612b399c
commit b6600c1f06
64 changed files with 2552 additions and 187 deletions
+189
View File
@@ -0,0 +1,189 @@
# Current-Branch Context Checkpoints
Status: design frozen for the Wave 7 implementation slice.
## Purpose
Long stories must stay inside the selected LAPP model's real context window without silently
dropping the current input, triggered world-book entries, runtime facts, or recent committed
scenes. A checkpoint is a disposable narrative cache. It is never an authoritative source for
flags, relationships, promises, inventory, knowledge, checks, or branch structure.
## Prerequisites
Checkpoint persistence starts only after these contracts are represented in code:
1. The selected LAPP model's `context_window` and `max_output_tokens` reach the Runtime provider.
2. The stable prompt prefix has a deterministic resource fingerprint.
3. The narrative source path has a canonical ordered hash that covers committed public story
content, not only `RuntimeState`.
4. `Regenerate` excludes the replaced node from narrative history, constrains the replacement with
the target node's committed post-state, creates a sibling on a new branch, and reuses existing
hidden-check outcomes.
## Prompt budget
The provider computes one explicit budget before every model request:
```text
hard input budget =
model context window
- requested output tokens
- system prompt and tool schema
- message framing overhead
- reserved hidden-check continuation overhead
- safety margin
```
- Requested output is `min(model.max_output_tokens ?? 4096, 4096)`.
- A missing context window uses a conservative 16,384-token V1 fallback, but the resulting
`BudgetSource::Assumed` diagnostic must remain visible to the connection/settings layer. The
fallback must not be silent.
- Cross-provider V1 estimation treats every serialized UTF-8 byte as at most one token. This may
compact early but must not optimistically overfill a model window.
- Compression starts at 70% of the hard input budget and compacts back below a lower watermark.
- The complete dynamic tail is budgeted first. If it does not fit by itself, the request fails with
`DynamicTailTooLarge`.
- At least one recent committed node remains verbatim. A single oversized node fails with
`HistoryEntryTooLarge`; beat text is never truncated.
- Every initial and hidden-check continuation `ChatInput` must remain within budget.
## Prompt layout
Prompt schema v3 will replace the current v2 history array with one production checkpoint-aware
encoder:
```text
prompt_schema_version
stable_prefix
branch_context
checkpoint?
raw_tail[]
dynamic_tail
```
`stable_prefix` remains byte-identical while the bound resources do not change. `dynamic_tail`
always contains the current input and the complete safe state projection. A checkpoint replaces
only a continuous oldest prefix of `raw_tail`.
## Checkpoint record
SQLite schema v3 adds a cache table keyed by the immutable host node, not by branch:
```sql
CREATE TABLE context_checkpoints (
story_id TEXT NOT NULL,
at_node_id TEXT NOT NULL,
covered_through_node_id TEXT NOT NULL,
retained_from_node_id TEXT,
checkpoint_schema_version INTEGER NOT NULL,
prompt_schema_version INTEGER NOT NULL,
stable_prefix_hash TEXT NOT NULL,
summary_json TEXT NOT NULL,
source_hash TEXT NOT NULL,
PRIMARY KEY (story_id, at_node_id),
FOREIGN KEY (story_id, at_node_id)
REFERENCES nodes (story_id, node_id) ON DELETE CASCADE,
FOREIGN KEY (story_id, covered_through_node_id)
REFERENCES nodes (story_id, node_id) ON DELETE CASCADE,
FOREIGN KEY (story_id, retained_from_node_id)
REFERENCES nodes (story_id, node_id) ON DELETE CASCADE
);
```
- The nearest checkpoint is found only by walking the current node's `parent_id` chain.
- Shared ancestors naturally share a checkpoint; sibling-only descendants are unreachable.
- The summary is self-contained. A new checkpoint replaces the old summary instead of nesting a
chain of summaries in the prompt.
- Successful checkpoint generation may be persisted independently on the existing host node.
Failure of the later story-generation call may leave this harmless cache in place.
- Deleting every checkpoint must leave nodes, branch heads, materialized state, and `PlayerView`
byte-identical.
## Source and resource hashes
The model never supplies either hash.
`stable_prefix_hash` covers the exact canonical stable-prefix encoding, including character,
Persona, resource provenance, and prompt safety/version fields.
`source_hash` covers:
```text
checkpoint source schema version
prompt schema version
stable_prefix_hash
story id
ordered root-to-covered sequence of:
node id
parent id
player input
public scene
public character visual state
committed beats
```
Unselected suggestions, state delta, exact relationship values, checks, NPC private inventory,
untriggered world-book content, provider responses, credentials, and chain-of-thought never enter
the source manifest or compression request.
## Regenerate boundary
`Regenerate` is a full alternative rendering of one committed player choice:
- the replaced node is not included in the new prompt;
- model history ends at the replaced node's parent, while the target node's authoritative
post-state supplies only the already-committed facts and fixed qualitative check outcomes;
- the replacement creates a sibling on a new runtime-generated branch;
- hidden checks from the original action are supplied as qualitative fixed outcomes and are not
rolled again;
- a model cannot submit forged `RecordCheck` operations;
- old nodes, descendants, and checkpoints remain immutable and become naturally unreachable from
the new branch unless they are shared ancestors.
The sibling keeps the original player action identity, authoritative delta, terminal state, and
exact hidden-check records. Those immutable records intentionally retain their original source-node
provenance; the new branch is an alternative presentation of that same committed action, not a new
roll or a new state transition.
## Summary safety
The summary may retain public causal order, actual player choices, public NPC actions, revealed
facts with their certainty, unresolved conflicts, shared goals, and observable emotional residue.
It may not become a trigger or rules input.
The summary must not contain:
- exact dice mechanics or relationship numbers;
- private NPC knowledge, private inventory, or unrevealed item provenance;
- hidden flags, clocks, event conditions, or untriggered world-book entries;
- unselected suggestions, cancelled output, sibling-branch content, or inferred player thoughts;
- credentials, request headers, raw provider bodies, or chain-of-thought.
The current player inventory projection must not be fed to the summarizer until ownership and
acquisition fields have their own player-knowledge visibility boundary.
## Failure semantics
- No valid checkpoint and history above the high watermark returns `NeedsCompaction`; history is
never silently shortened.
- Invalid schema, source hash, stable-prefix hash, range, or summary size makes a checkpoint
unusable and rebuildable.
- Broken authoritative ancestry or state remains a hard store error and is not downgraded to a
cache miss.
- Cancelled, timed-out, rate-limited, or malformed compression produces no story node and moves no
branch head.
- A first load of a very long legacy branch uses bounded rolling chunks and keeps only the final
self-contained summary.
## Required gates
- Exact window boundary, one-token overflow, unknown-model-limit fallback, and smaller-model switch.
- Every normal and hidden-check request stays within the computed budget.
- 500-node first compaction and incremental compaction from an existing checkpoint.
- v2-to-v3 migration, rollback, restart recovery, cache deletion, and corruption rejection.
- Root, shared-ancestor, pre/post-checkpoint fork, regenerate, and sibling-canary isolation.
- Stable-prefix cache identity and one cache break only when a checkpoint rotates.
- Hidden canaries for checks, exact relationships, NPC facts/items, untriggered resources,
unselected suggestions, credentials, and provider bodies.
@@ -0,0 +1,72 @@
# M2 第七波 Windows 基线与上下文前置状态
日期:2026-07-29
## 本机 Windows 基线
- 已安装 Visual Studio 2022 Build Tools 17.14、MSVC x64 工具链与 Windows 11 SDK
10.0.26100`cl.exe``link.exe``rc.exe` 均可用。
- `scripts/windows-smoke.ps1` 现在会通过 `vswhere` 选择具备 C++ 工具链的 Visual
Studio,并自动载入 x64 开发环境。普通 PowerShell 不再需要先手工运行
`VsDevCmd.bat`
- 使用现有应用图标生成 Tauri 的 Windows ICO、macOS ICNS、Linux PNG 及后续移动端
图标集合,关闭了 Windows 资源编译缺少 `icon.ico` 的阻塞。
- release 模式的 Windows 桌面程序已构建:
`target/release/nana-story-app.exe`
## Task 5 前置契约
### 模型预算
- `OpenLappChatExecutor` 从实际选中的 LAPP 模型读取 `context_window`
`max_output_tokens`
- 缺少或无效模型元数据时显式标为 `Assumed`,使用 16,384 / 4,096 的保守 V1 回退;
被应用上限或窗口边界收紧时标为 `Capped` 并保留原始来源。
- 预算值只能通过校验构造,始终保证输出预算大于零且小于上下文窗口;上层可读取来源、
fallback 与 cap 诊断。
- 普通回合、隐藏判定初始调用和所有工具续调用使用同一个模型预算;单回合输出上限为
4,096 tokens。
### 检查点来源
- 增加稳定前缀与叙事来源的强类型 SHA-256 指纹。
- 稳定前缀显式覆盖角色、Persona、剧情模块与绑定世界书的版本来源;持久化读取只接受
规范化的 `sha256:` 小写十六进制值。
- 叙事来源只接受连续的根到目标节点路径,覆盖节点 ID、父节点、玩家实际输入、公开场景、
角色视觉状态和完整演出节拍。
- 状态 delta、精确关系、隐藏判定、NPC 私物、未选择建议和兄弟分支均不能进入来源投影。
- [上下文检查点设计](../context-checkpoint-design.md) 已冻结预算、提示布局、SQLite v3、
失效规则和安全边界。
### 重新生成
- `Regenerate` 的叙事历史截止到待替换节点的父节点;模型使用目标节点的安全 post-state
与原判定定性结果生成兄弟节点,不再接收旧演出或骰点细节。
- 原玩家行动标识、authoritative delta、隐藏判定、终局状态与物化状态原样复用;模型
只能替换演出,也不能让成功 / 失败结果反转。
- 终局节点允许重生成演出,但普通继续行动仍会被终局保护拦截。
- 新版本使用独立分支并切为活动线路,旧节点、旧线路和后代保持不可变。
- Memory 与 SQLite 均在单次原子操作中校验来源线路、节点、delta 和物化状态。
## 验证
- Rust workspace171 项测试通过。
- Runtime83 项。
- Store44 项。
- Tauri 后端:17 项。
- Domain / Engine / Contracts27 项。
- `cargo clippy --workspace --all-targets -- -D warnings`:通过。
- Rust 契约生成器 `--check`:通过。
- Web:5 个测试文件 / 29 项测试、TypeScript 检查与生产构建通过。
- 契约:25 份 Schema 与 TypeScript DTO 无漂移。
- `pnpm tauri build --no-bundle`:通过,生成 Windows release 可执行文件。
- 隔离 Demo 已启动,窗口枚举标题为《听娜娜讲故事》,进程保持响应并创建独立 SQLite
存档。为重建 release 文件现已关闭该进程;隔离存档仍保留。自动截图组件不支持该
Tauri 窗口,因此本报告不宣称视觉验收完成。
## 尚未关闭
- 当前执行环境仍会关闭 Gitea SSH 2222 连接,本地提交暂时不能推送。
- SQLite v3 检查点表、同模型摘要工具、500 节点滚动压缩与实际预算编排仍属于 Task 5
主体。
- Demo 重启恢复、终局 / 双线路人工操作和真实 LAPP 在线调用仍待后续冒烟。
+4 -3
View File
@@ -28,9 +28,10 @@ git -C .\lapp-rs checkout 5ba3c659e1536ec4bee16340faca603940a5cb17
还需预先安装 Windows 的 Tauri 2 原生开发依赖、Microsoft C++ Build Tools、WebView2、
Git、rustup、Rust 1.96.0 MSVC host(含 `rustfmt``clippy`)、Node.js 24+,以及
`package.json` 指定版本的 pnpm。脚本只检查它们,不会自动安装或升级工具链。下文使用
PowerShell 7 的 `pwsh`;脚本也只使用 Windows PowerShell 5.1 支持的语法,可将
`pwsh` 换成 `powershell.exe`
`package.json` 指定版本的 pnpm。脚本会通过 `vswhere` 自动载入 x64 C++ 开发环境并
检查 `cl.exe``link.exe``rc.exe`,但不会自动安装或升级工具链。下文使用 PowerShell
7 的 `pwsh`;脚本也只使用 Windows PowerShell 5.1 支持的语法,可将 `pwsh` 换成
`powershell.exe`
## 2. 跑机械门禁