OpenClaw 的 SQLite 确定性测试夹具:从标签版本到可复算指纹的共享状态库快照
【免费下载链接】openclawThe AI that really does things. Any OS. Any Platform. The lobster way. 🦞项目地址: https://gitcode.com/GitHub_Trending/cl/openclaw
本文以 test/fixtures/sqlite/README.md 为骨架,讲解 OpenClaw 如何为共享状态数据库(shared state database)制作一份"确定性测试夹具"(deterministic fixture):它来自哪个标签、哪个 commit,经过哪些规范化步骤(固定时间戳、WAL checkpoint、VACUUM、确定性 gzip),以三组 SHA-256 指纹和表/索引计数作为契约,以及如何被src/state下的测试与 doctor 预检流程消费。读完后,你可以复算该夹具的完整性指纹,理解其字节级稳定性的来源,并将其作为状态库 schema 回归与预检测试的基线。
夹具解决什么问题
OpenClaw 的运行时把大量持久化数据(会话、任务、审计序列、cron 历史等)存放在一个 SQLite 共享状态库中,其 schema 由 src/state/openclaw-state-schema.sql 定义。围绕它有一整套 schema 版本、迁移与退役逻辑(如 src/state/openclaw-state-schema.ts、src/state/openclaw-schema-versions.ts、src/state/openclaw-state-schema-compatibility.ts)。
要让这些迁移/兼容性逻辑可测试,就需要一个"内容已知、字节可复现"的数据库样本:它必须是某个已发布标签真实初始化出来的 schema,而不是随手CREATE TABLE出来的近似结构。test/fixtures/sqlite/下的 openclaw-state-v2026.7.1-2.sqlite.gz 就是这个样本。
来源与血缘:锚定到发布标签
README 对该夹具的血缘给出了完整锚点:
- 夹具由 OpenClaw 标签
v2026.7.1-2、commit0790d9f593ad30c940ed93b5872a8cf6d6f3cf8c处创建的共享状态数据库而来; - 该标签运行时(package 版本
2026.7.1)使用 Nodev26.7.0与 SQLite3.51.0初始化了数据库。
锚定到具体 commit 的意义在于:夹具的 schema 行不是"当前 HEAD 的 schema",而是"某个已发布版本发布的 schema"。这对验证向后兼容、迁移路径(旧库在新代码上打开、升级、拒绝拒绝升级等)是核心前提——如果夹具的 schema 随 HEAD 漂移,兼容性断言就失去意义。
合成数据与规范化管线
README 说明,夹具在真实初始化后的库上叠加了固定的合成行(fixed synthetic rows),覆盖六类关注点:
- 持久化状态(durable state);
- 审计序列保留(audit sequence preservation);
- 诊断排序(diagnostic ordering);
- 任务外键(task foreign keys);
- cron 历史导入(cron history import);
- 一条有代表性的 commitment(承诺记录)。README 特别强调:这条 commitment 的唯一存在目的,是证明"已发布 commitment 数据的不可逆退役"(irreversible retirement of shipped commitment data);它不被归档,也不被导出。
为了让最终文件字节级可复现,管线在打包前执行了以下规范化步骤:
- 元数据时间戳固定(Metadata timestamps are fixed);
- WAL 完成 checkpoint(
PRAGMA wal_checkpoint语义,把 WAL 合并回主库文件); - 数据库执行 VACUUM(消除页级布局噪声);
- 以确定性参数压缩:Node 的
gzipSync(raw, { level: 9, mtime: 0 })。mtime: 0抹掉 gzip 头里的时间戳字段,level: 9固定压缩级别——两者共同保证同输入永远得到同输出的字节流。
README 同时声明:排序后的sqlite_schema行与在该 commit 处直接从src/state/openclaw-state-schema.sql初始化的数据库逐字节一致;合成数据与元数据规范化不会改变发布的 schema(released schema)。这为后文的"schema 指纹"契约提供了依据。
夹具契约:三个指纹与三个计数
README 给出的契约(fixture contract)是文章的核心,逐项如下:
| 项目 | 值 |
|---|---|
| 原始 SQLite 文件 SHA-256 | 8511bb91f02d104f8397a678045d04741c931b0ee7ce6650b5519e85 |
| gzip 文件 SHA-256 | c775499d9a46462ae2368090a0c4ec75877784c40694046dd3af63df77b8737c |
排序后sqlite_schema行 SHA-256 | f2fd6488e283470718547fb45886f04cc940b1de798e52fbf34a3a3408ae25e4 |
| 应用表数量 | 73 |
| 命名索引数量 | 103 |
STRICT表数量 | 0 |
三个指纹各守一层:
- gzip SHA-256守护仓库里实际存储的压缩文件,任何字节变动(哪怕重压缩)都会暴露;
- 原始 SQLite SHA-256守护解包后的数据库文件本体,即"规范化管线产物"的完整性;
- 排序
sqlite_schema行 SHA-256只覆盖 schema 定义、不覆盖数据页,因此它能单独回答"schema 是否与该发布版本一致"——即便数据被合法地增改,该指纹也应保持。
三个计数则是结构不变式(structural invariants):73 张应用表、103 个命名索引、零STRICT表。"零 STRICT 表"尤其值得注意:OpenClaw 当前发布的状态库不使用STRICT表,任何未来 schema 变更如果引入 STRICT 表,都意味着与既有夹具契约的偏离,需要显式地重签契约而非悄悄混入。
仓库内如何消费这份夹具
该夹具不是孤立文件,它被三处测试代码直接引用,构成"真实发布 schema + 合成数据"的测试基线:
- src/state/openclaw-state-db.test.ts 在测试中引用
../../test/fixtures/sqlite/openclaw-state-v2026.7.1-2.sqlite.gz,用于对状态库打开、schema 识别等行为的断言; - src/state/openclaw-database-preflight.test.ts 在数据库预检(preflight)测试中引用同一路径,验证预检逻辑面对真实发布 schema 时的判定;
- src/commands/doctor-config-preflight.admission.process.test.ts 展示了最典型的消费方式:
gunzipSync(fs.readFileSync("test/fixtures/sqlite/openclaw-state-v2026.7.1-2.sqlite.gz"))——在 Node 内存中解压出原始 SQLite 字节,再喂给 doctor 的 config-preflight / admission 流程做子进程级验证。
从源码结构看,这种"解压到内存、不落盘"的消费模式与 src/state/openclaw-state-db-readonly.ts、src/state/openclaw-database-preflight.ts 等模块配合,使得兼容性测试既使用了与真实发布版本一致的 schema 结构,又不污染开发者的本地状态目录。
实操:复算指纹并抽查结构
在只读仓库内,你可以通过以下命令独立验证 README 声明的契约(不需要任何构建步骤,仅依赖 Node 与系统 SQLite 工具):
验证 gzip 与原始文件的 SHA-256:
sha256sum test/fixtures/sqlite/openclaw-state-v2026.7.1-2.sqlite.gz # 期望: c775499d9a46462ae2368090a0c4ec75877784c40694046dd3af63df77b8737c node -e 'const {gunzipSync}=require("zlib"),fs=require("fs"),crypto=require("crypto"); const raw=gunzipSync(fs.readFileSync("test/fixtures/sqlite/openclaw-state-v2026.7.1-2.sqlite.gz")); console.log("raw sha256:",crypto.createHash("sha256").update(raw).digest("hex")); console.log("tables:",raw.length, "bytes decompressed");' # 期望 raw sha256: 8511bb91f02d104f818c70b08397a678045d04741c931b0ee7ce6650b5519e85复算"排序sqlite_schema行"指纹(把type,name,tbl_name,sql四列排序后逐行哈希):
node -e 'const {gunzipSync}=require("zlib"),fs=require("fs"),crypto=require("crypto"); const {DatabaseSync}=require("node:sqlite"); const raw=gunzipSync(fs.readFileSync("test/fixtures/sqlite/openclaw-state-v2026.7.1-2.sqlite.gz")); fs.writeFileSync("/tmp/openclaw-state-fixture.sqlite",raw); const db=new DatabaseSync("/tmp/openclaw-state-fixture.sqlite",{readOnly:true}); const rows=db.prepare("SELECT type,name,tbl_name,sql FROM sqlite_schema").all() .map(r=>[r.type,r.name,r.tbl_name,r.sql].join("\x00")) .sort(); console.log("schema sha256:",crypto.createHash("sha256").update(rows.join("\n")).digest("hex"));' # 注意: 需与 README 相同的拼接/排序约定比对 f2fd6488e283470718547fb45886f04cc940b1de798e52fbf34a3a3408ae25e4; # 若拼接约定不同, 应改用仓库生成该指纹的脚本口径。抽查表/索引计数与 STRICT 不变式(将夹具临时解包到仓库之外的/tmp后,仅作只读查询):
gunzip -c test/fixtures/sqlite/openclaw-state-v2026.7.1-2.sqlite.gz > /tmp/openclaw-state-fixture.sqlite sqlite3 /tmp/openclaw-state-fixture.sqlite \ "SELECT count(*) FROM sqlite_schema WHERE type='table' AND name NOT LIKE 'sqlite_%'; SELECT count(*) FROM sqlite_schema WHERE type='index' AND sql IS NOT NULL; SELECT count(*) FROM sqlite_schema WHERE type='table' AND sql LIKE '%STRICT%';" # 期望: 73 / 103 / 0需要说明的是:上面sqlite_schema指纹脚本中的列拼接与排序约定需与仓库实际生成该指纹的脚本保持一致,不同拼接口径会产生不同摘要;SHA-256 文件级指纹则无此歧义,是首要校验项。
适用前提与边界
- 该夹具锚定标签
v2026.7.1-2(package 版本2026.7.1),代表"发布 schema"而非 HEAD 最新 schema;如果 HEAD 的 src/state/openclaw-state-schema.sql 已演进,两者行数/指纹可能不同,这正是兼容性测试存在的原因; - 契约中的计数与指纹属于"快照契约":任何对夹具的重新生成都必须重新签发全部三项 SHA-256 并更新 test/fixtures/sqlite/README.md;
- 夹具中的 commitment 行是刻意保留的合成探针,用于验证退役语义(irreversible retirement),README 明确它不被归档或导出,消费方不应假设其数据可迁移;
- 仓库内另有 test/fixtures/state-corpus/ 一族的版本化状态语料(含
manifest.json与生成脚本generate.mjs),与本夹具定位不同:state-corpus 按运行时版本快照完整状态树,而本夹具聚焦单一共享状态库的 schema/字节级契约。
小结
test/fixtures/sqlite/用一个文件加一份契约,把"真实发布版本的数据库长什么样"固化成可复算的测试基线:血缘锚定到 tag 与 commit,规范化管线(固定时间戳 → WAL checkpoint → VACUUM →gzipSync(level 9, mtime 0))保证字节可复现,三项 SHA-256 加三项结构计数(73 表 / 103 索引 / 0 STRICT)封住任何意外漂移,最后被 src/state 下的状态库测试与 doctor 预检测试作为"已知答案"消费。对维护者而言,它的价值不在于数据本身,而在于让 schema 迁移、兼容判定与预检逻辑始终跑在与发布版本逐字节一致的基座上。
【免费下载链接】openclawThe AI that really does things. Any OS. Any Platform. The lobster way. 🦞项目地址: https://gitcode.com/GitHub_Trending/cl/openclaw
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考