• ## 病症
    
    agent-chat-tenant-isolation.test.js 每跑一次,往共享生产库 data/graph.sqlite
    落 2 条 goal + 2 条 task。实测 29→31。库里 31 条 goal 里 **22 条是它的残留**,
    测试期间一直全绿。
    
    根因是批 5 的存储迁移留下的半截清理:/task/dispatch 的元数据从
    system/tasklog/*.json 换进了 tasks 表,而这个用例的 afterAll 只会清那个目录 ——
    清理代码还在、清的东西已经不在那儿了。
    
    ## 这是第四次,但换了个方向进来
    
    前三次(BENCHMARK_DB_PATH / USERS_DB_PATH / SKILLS_DIR,记在
    test-isolation-env.test.js 文件头)都是「设了 env,生产代码不读」。
    那条守卫查的正是这个形态。
    
    这次是**一个字都没设** —— 没有可查的东西,守卫自然查不出来。
    静态扫源码也判不出「这个测试会不会开库」(要跨 require 链推导)。
    
    ## 修法
    
    1. 用例自己开一次性库(fs.mkdtempSync + GRAPH_DB_PATH),afterAll 连目录删掉。
       赋值必须在 require 路由**之前** —— graph-db.js 的 DB_PATH 是模块级 const。
    2. graph-db.getDb() 加运行期闸门:VITEST 为真且没设 GRAPH_DB_PATH → 抛,
       报错里点名 env、点明「要在 require 之前设」。
       放在 getDb() 不放模块顶层:只为取常量而 require 是合法用法
       (goal-task-model.test.js 就这么用),顶层抛会误伤。真正开库才是越界那一刻。
    3. 守卫 graph-db-prod-guard.test.js,4 例:撞库要抛且报错可照做、设了 env 要能开、
       没有 VITEST 时不生效(防止把生产一起锁死)、样本非空。
       反向验过:注释掉闸门那一行,第 1 例立刻红。
    
    ## 顺带修一条 flaky(我自己上一版留下的)
    
    agent-docs-port.test.js 先 walk 出文件清单再逐个读,中间别的用例把 zz-* 夹具的
    运行态目录清掉 → ENOENT。**报错长得像端口违例,其实跟端口无关**,实测 8 次跑中 2 次红。
    只放过 ENOENT 一种(文件没了),其它读错误照抛 —— 一律 try/catch 吞掉的话,
    权限错、编码错会被当成「无违例」。
    
    ## 存量清理
    
    删掉生产库里 created_by LIKE 'zz-tenant-%' 的 22 goal + 22 task
    (备份 /tmp/graph.sqlite.bak-c3a5d46)。零 run 引用它们。
    其余 9 条 goal 是真实数据,一条没动。
    
    全量:136 文件 / 1276 测试,连跑 8 次全绿(改之前 8 次里红 2 次)。
    跑完后生产库行数零增量。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • ## ① system/tasklog/ 改名保留(§十二.5)
    派单存储统一进 tasks 表后,旧目录改名为 system/tasklog.bak-unify/ 而不是
    直接删 —— 跑顺一个版本再清。回填脚本改成**两处都认**(先原位置再备份位置,
    顺序不能反:万一有人从备份恢复出一份新的 tasklog,那份才是要回填的)。
    
    ## ② agent 提示词里的端口 3000 → 3010
    packages/agent-core/base/ 下那几份是**所有 agent 的工具说明**,印着
    curl http://localhost:3000/... 而实际端口是 3010。agent 照着打拿到的是
    连接被拒、不是「端口错了」,它多半会去猜别的原因。
    
    这不是假想:批 5 回填出来的历史派单正文里,有人手写了「(端口 3010,
    不是 3000)」在纠正它 —— 靠人肉在每条任务书里打补丁。
    
    守卫 agent-docs-port.test.js:扫 base 模板与 agent 本体,禁 localhost:3000。
    判据刻意宽松(不校验「必须等于 config.port」—— 端口可被 PORT 覆盖,写死会在
    部署环境误报)。带「样本非空」前置断言。第一版扫出 5 条误报,全是某 agent
    目录下 Python venv 里 playwright 的 ThirdPartyNotices —— 那是第三方文件不是
    提示词,已把 venv/site-packages/workspace/conversations/train 一并跳过。
    
    ## ③ 边界守卫不再把并行会话的改动算到 agent 头上(§15.5 第 1 条)
    守卫的前提是「窗口内新增的仓库改动 = 这个节点写的」。多会话共用 worktree 时
    这个前提是假的 —— git status 的差集里没有「谁写的」这一维。实测已发生:
    批 3 的开发改动把一条 run 判成 boundary_escape 卡住。
    
    可检测的信号:节点**开跑那一刻**仓库就已经有源码目录的未提交改动 → 有人正在
    这个 worktree 上开发 → 窗口内新增的源码改动无法归属 → **降级为只记证据、
    不判罚**(与既有的 unattributable 桶同一语义),而不是关掉守卫:证据照留,
    人一眼看得出「这次没判罚,因为当时仓库是脏的」。
    
    BOUNDARY_GUARD=strict 强制判罚(CI / 干净环境),=record 一律只记。
    守卫 8 例,含关键对照:仓库脏时不判、干净时同样一处越界仍判 fail ——
    守卫没被削弱,只是不再乱扣帽子。
    
    ## ④ TaskTimeline 的标签不再撒谎
    conversationCount 在批 5 之后只会是 0/1(「有没有起过执行会话」),而界面
    写的是 "Convs: N" —— 读的人会以为这条任务只有一次对话。改成有/无的表达。
    agents 的语义变化在注释里说明。
    
    注:innovator-hunt/todo.md 与 reddit-scraper/todo.md 里的端口也改了,
    但它们是 gitignored 的运行态文件,不入库。
    
    全量:134 文件 / 1268 测试全绿,web:build 通过。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »