Files
InfoRSI/任务书/03-ledger.md
T
2026-10-02 02:36:47 -04:00

13 KiB
Raw Blame History

任务书 03:ledger(账本)

写 ledger 模块:研究的全部记录怎么存、怎么保证不能改、写入时拒收什么、谁在什么时候能看到什么、花钱的动作怎么先登记。

本任务书的内容已由用户和监督者在 2026-10-02 一起确定。下面说的"监督者",是在参考项目目录里和用户讨论设计的另一个会话。

按 CLAUDE.md,这一块是"账本"标准,写完要另派一个全新上下文的子 Agent 审阅。

开工前先读

  • CLAUDE.md:全部。2026-10-02 改过两处:
    • "罕见情况怎么办"一节;
    • 账本全部只追加。
  • docs/结构.md:
    • 第二部分(ledger 的边界,"几条边界"里的前两条);
    • 第四部分全文,这是本任务的主要依据;
    • 第六部分(只有 ledger 能用 SQLite);
    • 第八部分(vocab、rules 已导出的东西)。
  • docs/方法.md:
    • 第三部分"谁在什么时候能看到什么";
    • 第五部分开头的总原则(替换提交、实现和核验阶段不能替换);
    • 4.3 ⑭(解释冻结早于任何一格消融启动)、⑱(本轮全部结果在同一个事务里算完并公开);
    • 第七部分"什么时候扣次数";
    • 第九部分"中断与恢复时的记账含义"。
  • WORK_LOG.md、src/vocab/。
  • 参考项目(只读,不要改):/home/iomgaa/Projects/RivalBits/src/board/
    • schema.ts:触发器禁改禁删的写法(59–70 行一带);
    • ledger.ts:node:sqlite 的用法;
    • board.ts 里运行(runLedger 一带)和模型调用(openModelCalls 一带)分步登记的顺序;
    • visibility.ts:旧的可见性判断,注意它有绕弯的地方,见下面"删除"。

一、怎么存(结构.md 第四部分)

  • 用 Node 自带的 node:sqlite,不另装依赖。一份研究一个数据库文件。

  • 只有一张表,每一行是:

    • 序号:自动递增,就是时间先后;
    • 种类;
    • 唯一键;
    • 内容:JSON;
    • 内容的哈希:用 vocab 的 canonical 和 hash。
  • 整张表禁止修改、禁止删除,用两条触发器实现。

  • "种类 + 唯一键"不能重复,用数据库的唯一约束实现。

  • 重复写入的规则:

    • 同一个种类和键,内容哈希相同,当作已经写过,返回原来那条,不报错;
    • 内容哈希不同,报错,写清种类、键和两份哈希。
  • 唯一键怎么拼,只在 ledger 里定义,调用方不自己拼键。键拼错会让去重悄悄失效,所以每种记录的键由一个函数从内容里取,别处不另写一份。

  • 打开数据库时:

    • 新文件就建表;
    • 旧文件核对一个表结构版本号,对不上就报错。

    不逐字核对建表语句。

二、记录的种类

结构.md 第四部分列了 12 种记录。ledger 按需要把它们细分成具体种类,每种有自己的内容定义(TypeBox)和唯一键。下面是监督者整理的参考,具体名字和细分由你来定;和结构.md、方法.md 对不上时以文档为准,并在报告里写出来。

结构.md 的记录 细分种类 唯一键 内容
1 研究配置 配置 固定一个 先存 Json;config 模块写好后再收紧
2 阶段 开、关 阶段编号 开:阶段名(vocab STAGES)、轮次、这个阶段的应交名单;关:无
3 提交 提交的每一版 阶段编号 + 作者 + 对象 + 版本 提交种类(vocab SubmissionKind)、作者、针对谁(候选、格,没有就空)、版本号、要替换的上一版、内容(用 vocab SUBMISSION_SCHEMAS 检查)
3 提交 私人笔记的每一版 作者 + 版本 正文
4 冻结协议 冻结协议 候选编号 先存 Json 加必要的标识
5 作者自评 封存、解封 封存:提案的那一版;解封:候选编号 封存:三个数;解封:放出哪几份封存(列明)
6 冻结代码 冻结代码 候选或消融格编号 版本号、因子位置表
7 运行 登记要跑、确认启动、结果 都是运行编号 登记:用途(基线 / 正向 / 消融)、轮次、对象、运行定义(vocab RunDefinition);结果:vocab ProcessResult,以及有的话 JudgedResult
8 决定 决定 决定种类 + 轮次 + 对象 规则名、规则版本、方法参数、种子、输入、输出(输入输出存 Json,ledger 不认识 rules)
9 结算 结算 消融编号 + 格 存 Json,同上
10 Jev 判别 Jev 判别 自动 问了什么、原始概率、结论
11 用量 模型调用开始、每次回复的用量、结束 调用编号(用量另加回复序号) 用量用 vocab ModelUsage
12 事件 事件 自动 事件种类、原因、相关对象

几点说明:

  • 作者自评怎么封存。 提案的内容里有作者自评(authorEstimate)。写一版提案时,ledger 在同一次写入里把作者自评拆出来存成一条"封存"记录,提案记录本身不带它。这样可见性只需要判断"这条封存有没有被解封",不用在给别人看之前改写提案内容。解封记录列明它放出了哪几条封存。合并而成的候选有几位作者,就有几条封存。
  • 能直接用现有定义的就用现有的:提交内容、运行定义、运行结果、模型用量,vocab 里都有。内容由还没写的模块(config、rules 的决定和结算)产生的,先存 Json 加最少的标识字段,不要替那些模块猜字段。
  • 没有"动手杀之前"那条记录:被取消也是一种结果。

三、写入时拒绝的情况

只有下面这些,写在 ledger 的写入函数里,在同一个事务中查:

  1. 阶段已经关了,还要往这个阶段写提交。
  2. 替换提交时,要替换的那一份不是本人在这个阶段、对同一个对象的最新一版。实现、修复、核验、消融实现、消融核验这几种提交不能替换(方法.md 第五部分)。
  3. 运行结果写在"确认启动"之前。例外是"没启动"这种结果,它本来就不需要启动记录(方法.md 第七部分:确认没启动的不扣次数)。
  4. 某一轮消融已经有格确认启动了,才来关这一轮的解释阶段(方法.md 4.3 ⑭:冻结必须早于任何一格的启动)。

别的不在 ledger 里查。比如提交内容的字段对不对、三个数是否严格递增,归 submissions;该不该开下一个阶段,归 flow。

四、谁能看到什么

只用一个函数回答"某位查看者在某个序号时,能不能看到这条记录"。查看者有两种:

  • 程序:看全部。
  • 某位研究者:按下表。
记录 本人 其他研究者
研究者阶段的提交 交上就能看 这个阶段关闭后能看
作者自评的封存 能看 有列明它的解封记录后能看
第一次预测 能看 永远不能看(只进导出)
私人笔记 能看 永远不能看
冻结协议、冻结代码、运行、决定、结算、阶段 写入后都能看 写入后都能看
Jev 判别、用量、事件 不能看 不能看

后三种不给研究者看,原因有两个:研究者用不到;Jev 判别可能在阶段还开着时就问到了某份提交的内容,给了别人就是提前泄露。

另外两条:

  • 截止序号:研究者在一个阶段里看到的东西,截止到这个阶段开始的那一刻(方法.md 第三部分、结构.md 第二部分)。快照函数接收一个截止序号,序号之后的记录一律不给。
  • 实验员和核验员:不单独设查看者。给他们的材料由以后的 materials 从程序的全集里挑。

只用"给不给"这一种手段,不在给出之前改写记录的内容。

五、对外提供什么

经 src/ledger/index.ts 导出:

  1. 打开一份研究的账本:新建或续用;表结构版本不对就报错。
  2. 追加一条记录:检查内容(TypeBox),取唯一键,做第三部分的拒收检查,处理重复写入。
  3. 事务:几次追加要么全部写进去,要么一条都不写(方法.md 4.3 ⑱)。
  4. 读取:
    • 快照:给定查看者和截止序号,返回能看到的记录。读出时按种类用 TypeBox 校验;
    • 程序自己要的几个常用查询,比如某个阶段开着没有、某份提交的最新一版。只写现在能说出谁会用的,不预先写一大套查询。
  5. 未完成的动作:登记了但没有结果的运行,开始了但没有结束的模型调用。恢复时 flow 用它逐个核对,怎么核对不归 ledger。

六、参考项目的保留与删除

处理 内容
借鉴 触发器禁改禁删的写法;运行和模型调用分步登记的顺序;"同样内容重写当作已写过、不同内容报错";写入先后的检查
删除 34 张表,改为一张表
删除 审计表 audit、请求表 requests、公开表 publications、通用事实表 facts:它们的作用都变成了一张表本身的性质(序号、唯一键、阶段关闭记录)
删除 studies 表里的租约列(owner、epoch、lease_until),以及每个写入函数里"研究是否在进行中"的判断。同一时刻只有一个进程,靠的是文件锁,文件锁归 plugin
删除 本轮重来、模板替换、撤回提交(round-restart、template-replacement、withdrawn-submission)以及可见性里排除它们的三处判断
删除 命令审查 shell-review、违规暂停 integrity-incident/integrity-disposition
删除 收尾补测 fillin_plans、ablation_final_settlements,最后一轮预留 reserves,消融的份额、照测、欠测
删除 blindProtocol(改写冻结协议后再给第一次预测看):第一次预测在挑刺之前、看的是原始提案,不需要它
删除 run_kills(动手杀之前那条)
删除 打开时逐字核对全部建表语句
挪走 code-store.ts 归 workspace(账本只存版本号);artifacts.ts 归 runner 和 tasks(账本只存路径和指纹);阶段视图里顺带算的剩余预算归 rules(runBalance)
不搬 私人笔记原来是账本外的文件,整份覆盖写;现在进账本,每次修改追加一版

七、测试

用真实的数据库文件,放在临时目录里,每个测试自己建、自己删。重点:

  • 用 SQL 直接执行 UPDATE、DELETE,被触发器拒绝。
  • 同种类、同键、同内容写两次只有一条;同键不同内容报错。
  • 第三部分的四种拒收各有一条测试;同时测对应的正常情况能写进去。
  • 第四部分可见性表里的每一格;截止序号之后的记录看不到;提案里的作者自评不出现在别人能看到的提案记录里。
  • 事务中途出错,一条都没写进去。
  • 关掉再打开,读出的记录和快照不变;表结构版本不对时打开报错。
  • 未完成的动作:有登记没结果的运行、有开始没结束的调用能被找出来,写了结果之后就不再出现。

账本这部分没有公式,预期值就是"应该写进去什么、应该被拒绝什么",照方法.md 和结构.md 的文字写,不从实现里推。

八、独立审阅

写完、测试通过后,另派一个全新上下文的子 Agent 审阅。它只拿到结构.md 第四部分、方法.md 第三、七、九部分和 src/ledger/、相关测试,不给它看你的思路。

  • 让它找:会让记录被改掉、被重复记账、被提前泄露、恢复后对不上的具体场景。
  • 它提出的每条问题,先问"不写会怎样":
    • 答案是"会报错停下"或"出错后马上就能看出来",就不加代码,在报告里列出来;
    • 会悄悄写错账或泄露的,才修,修了附测试。
  • 它不写 md 文件,结论直接回复给你。

九、规模与文档

  • 预期 900–1400 行(不含测试)。超过 1400 行就停下来检查:是需求本身复杂,还是专门处理罕见情况的分支在变多。
  • 完成后更新:
    • docs/结构.md 第八部分:补 ledger 对外的函数和类型,每项一句话;
    • WORK_LOG.md。
  • 不新建其他 md 文件。

十、验收与报告

做完后本地提交,然后在回复里交一份报告,监督者会对着实际文件逐项核对:

  1. 提交号,新增文件列表,每个文件的行数,ledger 的总行数。
  2. npm run check 和 npm test 的完整输出。
  3. 最终的记录种类表:每种的唯一键、内容定义、谁会写它。
  4. 独立审阅提出的问题,以及每条怎么处理的(修了的附测试名,没修的写"不写会怎样"的答案)。
  5. 你和这份任务书、结构.md、方法.md 不一致的地方,以及为什么。
  6. 规则不清楚的地方:读 CLAUDE.md、结构.md、方法.md 时,哪里让你犹豫、哪里两份文件说法不一致、哪里你是靠猜的。没有就写"没有"。