Files
InfoRSI/任务书/03-ledger.md
T
iomgaa 493ea6d89d docs: 写明"边界"指什么;唯一约束改为有编号、只发生一次的八种记录
用户 2026-10-02 同意:CLAUDE.md"罕见情况怎么办"第 2 条写明边界是模型的提交、用户的配置、外部服务的返回,模块之间的调用不算边界;哪些记录加唯一约束属于账本设计,写在结构.md 第四部分(阶段开关、运行登记、确认启动、运行结果、模型调用开始结束、消融结算),前后顺序由 flow 的同一入口保证。任务书 03 同步。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 06:25:16 -04:00

183 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 任务书 03:ledger(账本)
写 ledger 模块:研究的全部记录怎么存、怎么保证不能改、写入时拒收什么、花钱的动作怎么先登记。
本任务书已由用户和监督者在 2026-10-02 一起确定,同一天改过一版(见"这一版改了什么")。下面说的"监督者",是在参考项目目录里和用户讨论设计的另一个会话。
按 CLAUDE.md,这一块是"账本"标准,写完要另派一个全新上下文的子 Agent 审阅。
## 这一版改了什么
第一版要求 ledger 做三件事:判断"谁在什么时候能看到什么"(快照加截止序号),给每条记录配唯一键,同样内容重写时当作已经写过。用户指出这三件都是过度设计:
- "这个任务能看到哪些材料"由 materials 按任务定。第一条消息里直接给,还是等模型用查询工具去读,是同一份材料的两种给法。ledger 不判断谁能看什么,所以也没有截止序号。
- 恢复时只核对、不重做写入(CLAUDE.md"中断后怎么恢复"),所以记录照写就是,不需要通用的去重。只给有自己编号、按定义只发生一次的记录加唯一约束。
- Jev 是一个判别器,和程序检查地位相同,不单独成一种记录。
已经写好的代码里,存储、禁改触发器、提交内容检查、作者自评拆成封存,都还用得上。
### 先清理第一版留下的东西
第一版的设计已经作废,按它写出来的代码、测试、注释、文档都要清掉,不留"以后也许用得上"的部分。改完要读起来像一开始就按这一版写的(CLAUDE.md"怎么改一份文件"):
- `src/ledger/visibility.ts` 和 `tests/ledger-visibility.test.ts`:整个删掉。
- `snapshot`、`Viewer`、截止序号:从代码、导出和测试里删掉。
- 通用唯一键(`keyOf`、`joinKey` 一类)、"同样内容重写当作已经写过"的处理和它的测试:删掉,只留第一部分列出的唯一约束。
- `jev` 这种记录:删掉,改为写进"退回"和"决定"的内容里。
- 第一版之后加出来、不在第三部分四种里的写入前提检查:删掉,测试一并删掉。
- 注释里提到快照、截止序号、可见性、"同内容重写"的地方:按这一版改写或删掉。
- `docs/结构.md` 第八部分你写的 ledger 那一段:按这一版重写。
清完以后,在 `src/ledger/`、`tests/`、`docs/` 里搜 `snapshot`、`cutoff`、`Viewer`、`visib`、`keyOf`、"快照"、"截止"、"当作已经写过",除了这份任务书和结构.md 开头的来源说明,应该一处都搜不到。把搜索命令和结果写进报告。
## 开工前先读
- `CLAUDE.md`:全部,尤其是"罕见情况怎么办"和"中断后怎么恢复"。
- `docs/结构.md`:
- 第二部分(ledger 和 materials 的边界);
- **第四部分全文,这是本任务的主要依据**(提交 0b0cbb0 改过);
- 第六部分(只有 ledger 能用 SQLite);
- 第八部分(vocab、rules 已导出的东西)。
- `docs/方法.md`:
- 第五部分开头的总原则(替换提交,实现和核验阶段不能替换);
- 4.3 ⑭(解释冻结早于任何一格消融启动)、⑱(本轮全部结果在同一个事务里算完并公开);
- 第七部分"什么时候扣次数";
- 第九部分"中断与恢复时的记账含义"。
- `WORK_LOG.md`、`src/vocab/`。
- 参考项目(只读,不要改):`/home/iomgaa/Projects/RivalBits/src/board/`
- `schema.ts`:触发器禁改禁删的写法;
- `ledger.ts`:`node:sqlite` 的用法;
- `board.ts` 里运行(`runLedger` 一带)和模型调用(`openModelCalls` 一带)分步登记的顺序。
## 一、怎么存
- 用 Node 自带的 `node:sqlite`。一份研究一个数据库文件。
- **只有一张表**,每一行是:
- 序号:自动递增,就是时间先后;
- 种类;
- 内容:JSON;
- 内容的哈希:用 vocab 的 `canonical` 和 `hash`。
- **整张表禁止修改、禁止删除**,用两条触发器实现。
- **唯一约束**:有自己编号、按定义只发生一次的记录,按编号加数据库唯一约束,重复了就报错,不当作已经写过(结构.md 第四部分):阶段开、阶段关、运行登记、确认启动、运行结果、模型调用开始、模型调用结束、消融结算。
其余记录照写,不设唯一键。前后顺序(启动前要先登记、结束前要先开始)由 flow 的同一个入口保证,ledger 不查。
- 打开数据库时:
- 新文件就建表;
- 旧文件核对一个表结构版本号,对不上就报错。
## 二、记录的种类
按结构.md 第四部分的 11 种记录细分。下面是参考,具体名字由你来定。
| 结构.md 的记录 | 细分种类 | 内容 |
|---|---|---|
| 1 研究配置 | 配置 | 先存 Json;config 模块写好后再收紧 |
| 2 阶段 | 开、关 | 开:阶段编号、阶段名(vocab `STAGES`)、轮次、应交名单;关:阶段编号 |
| 3 提交 | 提交的每一版 | 阶段、作者、针对谁、版本号、提交种类(vocab `SubmissionKind`)、内容(用 vocab `SUBMISSION_SCHEMAS` 检查) |
| 3 提交 | 退回 | 被退回的那份内容、退回理由;理由来自 Jev 的,带原始概率和结论 |
| 3 提交 | 私人笔记的每一版 | 作者、版本号、正文 |
| 4 冻结协议 | 冻结协议 | 候选编号,其余先存 Json |
| 5 作者自评 | 封存、解封 | 封存:哪一份提案、三个数;解封:放出哪几条封存(列明) |
| 6 冻结代码 | 冻结代码 | 候选或消融格编号、版本号、因子位置表 |
| 7 运行 | 登记要跑、确认启动、结果 | 运行编号(由 flow 起);登记还带用途(基线 / 正向 / 消融)、轮次、对象、运行定义(vocab `RunDefinition`);结果带 vocab `ProcessResult`,有的话加 `JudgedResult` |
| 8 决定 | 决定 | 决定种类、轮次、规则名、规则版本、方法参数、种子、输入、输出(输入输出存 Json;判重合并的输入里带 Jev 的原始概率) |
| 9 结算 | 结算 | 消融编号,其余存 Json |
| 10 用量 | 模型调用开始、每次回复的用量、结束 | 调用编号;开始时附这次任务的**全部材料**(以后能重现模型当时看到了什么);用量用 vocab `ModelUsage` |
| 11 事件 | 事件 | 事件种类、原因、相关对象 |
说明:
- **作者自评**:写一版提案时,ledger 在同一次写入里把作者自评拆出来,存成一条封存,提案记录本身不带它。以后 materials 给别人看提案时,不用去掉什么。
- **内容由还没写的模块产生的**(配置、冻结协议、决定、结算),先存 Json 加最少的标识字段,不替那些模块猜字段。
- **运行重试**:确认没启动、退还名额以后要重试,由 flow 起一个新运行编号,ledger 不需要知道这是重试。
## 三、写入时拒绝的情况
只有下面四种,在写入函数里、同一个事务中查:
1. 阶段已经关了,还要往这个阶段写提交。
2. 替换提交时,要替换的那一份不是本人在这个阶段、对同一个对象的最新一版。实现、修复、核验、消融实现、消融核验这几种提交不能替换(方法.md 第五部分)。
3. 运行结果写在"确认启动"之前。"确认没启动"这种结果例外,它本来就不需要启动记录。
4. 某一轮消融已经有格确认启动了,才来关这一轮的解释阶段(方法.md 4.3 ⑭)。
不在这四种里的检查不写。比如:阶段有没有开过、提交种类和阶段对不对得上、解封列出的封存存不存在、用量是不是在调用开始之后。这些都是 flow 自己写错才会发生的。按 CLAUDE.md"罕见情况怎么办",不写的代价是出错时报错停下,或者马上就能看出来。
## 四、不归 ledger 的
- **谁能看到什么**:归 materials。ledger 只把记录交给程序,不提供按查看者过滤的快照,也没有截止序号。
- **当前进行到哪一步、余额、信念本**:从记录现算,分别归 flow、rules、materials。
- **文件锁**:归 plugin。
- **代码文件、运行产物**:分别归 workspace、runner 和 tasks,账本只存版本号、路径和指纹。
## 五、对外提供什么
经 `src/ledger/index.ts` 导出:
1. **打开**一份研究的账本:新建或续用;表结构版本不对就报错。
2. **追加**一条记录:按种类检查内容(TypeBox),做第三部分的拒收检查。
3. **事务**:几次追加要么全部写进去,要么一条都不写(方法.md 4.3 ⑱)。
4. **读取**:
- 全部记录,按序号排,读出时按种类检查内容;
- 第三部分拒收检查本来就要用的几个查询,比如某个阶段关了没有、某份提交的最新一版。
不预先写一大套查询,以后哪个模块需要,再由那个模块的任务书加。
5. **未完成的动作**:登记了但没有结果的运行(带着有没有确认启动),开始了但没有结束的模型调用。恢复时 flow 用它逐个核对,怎么核对不归 ledger。
## 六、参考项目的保留与删除
| 处理 | 内容 |
|---|---|
| 借鉴 | 触发器禁改禁删的写法;运行和模型调用分步登记的顺序 |
| 删除 | 34 张表,改为一张表 |
| 删除 | 审计表 `audit`、请求表 `requests`(重复写入去重)、公开表 `publications`、通用事实表 `facts` |
| 删除 | `visibility.ts`、`stage-view.ts`:可见性归 materials,而且按任务定材料,不按记录逐条判断 |
| 删除 | 租约列和"研究是否在进行中"的判断(同一时刻一个进程靠文件锁,归 plugin) |
| 删除 | 本轮重来、模板替换、撤回提交、命令审查、违规暂停、收尾补测、最后一轮预留、消融份额和照测欠测 |
| 删除 | `blindProtocol`、`run_kills` |
| 删除 | 打开时逐字核对全部建表语句 |
| 挪走 | `code-store.ts` 归 workspace;`artifacts.ts` 归 runner 和 tasks;阶段视图里顺带算的剩余预算归 rules |
## 七、测试
用真实的数据库文件,放在临时目录里,每个测试自己建、自己删。重点:
- 用 SQL 直接执行 UPDATE、DELETE,被触发器拒绝。
- 八处唯一约束各有一条测试:同一个编号写第二次就报错。
- 第三部分的四种拒收各有一条测试;同时测对应的正常情况能写进去。
- 写提案时作者自评拆成封存,提案记录里不带它。
- 事务中途出错,一条都没写进去。
- 关掉再打开,读出的记录不变;表结构版本不对时打开报错。
- 未完成的动作:有登记没结果的运行、有开始没结束的调用能被找出来,写了结果之后就不再出现。
预期值就是"应该写进去什么、应该被拒绝什么",照方法.md 和结构.md 的文字写,不从实现里推。
## 八、独立审阅
写完、测试通过后,另派一个全新上下文的子 Agent 审阅。它只拿到结构.md 第四部分、方法.md 第七、九部分和 `src/ledger/`、相关测试,不给它看你的思路。
- 让它找:会让记录被改掉、被重复扣次或重复结算、恢复后对不上的具体场景。
- 它提出的每条问题,先问"不写会怎样":
- 答案是"会报错停下"或"出错后马上就能看出来",就不加代码,在报告里列出来;
- 会悄悄写错账的,才修,修了附测试。
- 它不写 md 文件,结论直接回复给你。
## 九、规模与文档
- 预期 500–900 行(不含测试)。超过 900 行就停下来检查:是需求本身复杂,还是专门处理罕见情况的分支在变多。
- 完成后更新:
- `docs/结构.md` 第八部分:补 ledger 对外的函数和类型,每项一句话。第一版写的那一段按这一版重写;
- `WORK_LOG.md`。
- 不新建其他 md 文件。
## 十、验收与报告
做完后本地提交,然后在回复里交一份报告,监督者会对着实际文件逐项核对:
1. 提交号,新增文件列表,每个文件的行数,ledger 的总行数。
2. `npm run check` 和 `npm test` 的完整输出。
3. 最终的记录种类表:每种存什么,谁会写它。
4. 按这一版删掉了第一版里的哪些东西,以及"先清理第一版留下的东西"那一节最后的搜索命令和结果。
5. 独立审阅提出的问题,以及每条怎么处理的(修了的附测试名,没修的写"不写会怎样"的答案)。
6. 你和这份任务书、结构.md、方法.md 不一致的地方,以及为什么。
7. **规则不清楚的地方**:读 CLAUDE.md、结构.md、方法.md 时,哪里让你犹豫、哪里两份文件说法不一致、哪里你是靠猜的。没有就写"没有"。