-
起因是一个看起来无害的问题:<repo>/data/users.sqlite 为什么是 0 字节。 答案是没人用它——user-db 走三级上跳落在仓库外的 ~lemonAI/data(跨兄弟仓库 共享的平台数据,与 skill-db / benchmark-db 一致;仓库内 data/ 放 graph/companies/ devloop 这类租户数据)。那个空文件是遗留,已删。 顺着查下去发现三处同型缺陷,形态都是「产生 → 传递 → 落库 → 读回」漏掉读回, 而且是最有欺骗性的一种:测试设了隔离用的 env,没有人读,于是数据全写进 **共享的生产库**,测试照样全绿,afterAll 删的 /tmp 文件从来没被创建过、清理还“成功”。 1) USERS_DB_PATH 压根不存在 user-db.test.js 只能直写共享用户库(注释老实写着 "uses hardcoded data/users.sqlite"),afterAll 是空的、拿「邮箱按 UUID 唯一所以不会撞」 当成了不用清理的理由。不撞是真的,不堆积是假的:357 个用户里 356 个是夹具。 加 USERS_DB_PATH 覆盖口(口径同 graph-db 的 GRAPH_DB_PATH),测试改为 先设 env 再清 require.cache 再 require(DB_PATH 是加载时求值的常量, 顺序错了照样静默失效),afterAll 真删库文件。 2) BENCHMARK_DB_PATH 有人设、没人读 benchmark-db.test.js:13 与 integration/api-routes.test.js:43 都在设它, 而 benchmark-db.js 那一行不读 → 共享 benchmark 库攒了 342 行 skill_name=benchmark_test。补上读取后,两个测试文件一行不改就真隔离了 (日志里 Initialized SQLite at /tmp/... 第一次是真的)。 3) SKILLS_DIR 是写错的变量名 config.js:13 读的是 SKILLS_CODE_DIR,所以 skillsCodeDir 一直指向那个 2.7 万条的真实共享 skills 目录。两个测试文件的变量名改正。 新增 guard server/tests/unit/test-isolation-env.test.js: 测试赋值过的**路径类** env(_DIR/_PATH/_ROOT 结尾)必须有生产读者。 只管路径类是刻意的——那是会造成静默数据污染的地方,而 HOME(os.homedir 间接读)、 JWT_SECRET、PORT 这类天然不在范围内,不需要维护会腐烂的白名单。 顺带钉住「共享 data 在仓库外」这个有意的分层,免得被“修”回仓库内。 guard 第一版把自己文档注释里的举例当成真赋值、自己判红了,所以两侧都先剥注释—— 凡是静态扫源码的检查都要先想清楚该量的是代码还是文本。 数据清理:共享 ~lemonAI/data/users.sqlite 删掉 358 行夹具 (不是 356——修好之前又跑了两轮测试,每轮 +2,正好印证泄漏一直活跃), 删前备份 + 计数闭合校验(保留行 + 匹配行 == 总数才动手), 删后真人 bboonnee@gmail.com 完好,总数 1。 验证:854 全绿(84 文件)。跑完全套后 users / benchmark_logs / skills 三个共享库的 sha256 逐位不变——修好之前这不可能。 3 处变异逐一精确变红(去掉任一 env 读取、把变量名改回错的)后逐字还原。 未动:JWT_SECRET / API_KEYS 生产侧无读者或名字不符(config.js 读 OPEN_API_KEYS), 不造成数据污染,另报;共享 benchmark 库那 342 行夹具未清,等确认。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>