Commit ad045ddf9c6112e228d5e3d6b2772c028b86cc71
1 parent
fdeafcdb
fix(tests): 修掉此前被误判为"环境噪音"的 3 个真单测失败
之前几轮全量跑一直有 3 个失败,当时判断是环境耦合/pre-existing、跟本次改动无关就没细查, 这次逐个真查了一遍,三个都是真 bug: - system/agents/sdd-planning/SKILL.md:Evolve 训练产出从 train/evolve/SKILL.md 提升到 生产位置时,"训练依据"引用的相对路径没跟着调整(少了 train/ 前缀),指向了不存在的 system/agents/hunt/、system/agents/digest/;真实文件其实在同一个 agent 自己的 train/hunt/、train/digest/ 下,补前缀即可。 - graduation-criteria-reality.test.js:断言死等 code-planner 的状态回到 'active',但 该本体的 config.json 早已从曾经踩坑的 "training" 改成了真正毕业态的 "enabled"—— seedAgents() 把 DB 同步成 'enabled' 是设计如此(同为毕业态,不是降级),不该被判失败。 改成用 isUngraduatedStatus() 判断"没被打回未毕业域",不再钉死具体状态值;顺手把 graph-seed-service.js 里那句会随源码变化过期的注释也改成不点名具体值。 - graph-engine-extend-budget.test.js:一颗时间炸弹——用硬编码绝对时间戳 '2026-08-23T05:23:52.886Z' 模拟"卡住很久了",但被测代码真的调 Date.now() 算经过时长, 写这条用例时"经过时长"确实超过 2h 上限、又低于追加后的 6h 上限;现在真实时间已经过去 178 小时,连追加后的上限都超了,测试注定要红,跟被测代码毫无关系。改成 Date.now() - 3小时 相对计算,不再随时间推移漂移。 全量 vitest 131/131 文件、1089/1089 用例通过。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Showing
4 changed files
with
18 additions
and
7 deletions
server/services/graph-seed-service.js
| @@ -458,8 +458,10 @@ function seedAgents() { | @@ -458,8 +458,10 @@ function seedAgents() { | ||
| 458 | // **只写不降级**:config.json 里的 status 是本体的**出生默认值**,而毕业与否是运行时 | 458 | // **只写不降级**:config.json 里的 status 是本体的**出生默认值**,而毕业与否是运行时 |
| 459 | // 从磁盘定义学到的事实(promoteDraftIfComplete / reconcile 的 E 分支)。原样 upsert 的话, | 459 | // 从磁盘定义学到的事实(promoteDraftIfComplete / reconcile 的 E 分支)。原样 upsert 的话, |
| 460 | // 每次进程重启 seedAgents 都会把已经转正的行按 config.json 打回 draft/training —— | 460 | // 每次进程重启 seedAgents 都会把已经转正的行按 config.json 打回 draft/training —— |
| 461 | - // 闸门抬起来一次、下次启动又落下去,而且一处报错都没有(code-planner 的 config.json | ||
| 462 | - // 至今写着 "training")。所以:库里已经脱离未毕业域的行,不许再被推回去;其余照写。 | 461 | + // 闸门抬起来一次、下次启动又落下去,而且一处报错都没有(真实撞过的样本是 code-planner, |
| 462 | + // 它的 config.json 当时写着 "training",见 `graduation-criteria-reality.test.js`; | ||
| 463 | + // 后来 config.json 已经改成了毕业态的 "enabled",这条注释只留问题形状,不再钉死具体值, | ||
| 464 | + // 避免又变成一处会随源码变化过期的死引用)。所以:库里已经脱离未毕业域的行,不许再被推回去;其余照写。 | ||
| 463 | const status = (existing && isUngraduatedStatus(configStatus) && !isUngraduatedStatus(existing.status)) | 465 | const status = (existing && isUngraduatedStatus(configStatus) && !isUngraduatedStatus(existing.status)) |
| 464 | ? existing.status | 466 | ? existing.status |
| 465 | : configStatus; | 467 | : configStatus; |
server/tests/unit/graduation-criteria-reality.test.js
| @@ -196,10 +196,14 @@ describe.skipIf(!fs.existsSync(CODE_PLANNER_DIR))('code-planner 能被新公å¸é | @@ -196,10 +196,14 @@ describe.skipIf(!fs.existsSync(CODE_PLANNER_DIR))('code-planner 能被新公å¸é | ||
| 196 | expect(r.graduated).toContain('code-planner'); | 196 | expect(r.graduated).toContain('code-planner'); |
| 197 | expect(statusOf()).toBe('active'); | 197 | expect(statusOf()).toBe('active'); |
| 198 | 198 | ||
| 199 | - // seedAgents 会把 config.json çš„ status åŽŸæ · upsert —— å®ƒå†™ç€ "training"。 | ||
| 200 | - // ä¸åŠ ã€Œåªå†™ä¸é™çº§ã€çš„è¯ï¼Œè¿™é‡Œä¼šæŠŠåˆšè½¬æ£çš„è¡Œæ‰“å›žåŽ»ï¼Œæ¯æ¬¡è¿›ç¨‹é‡å¯éƒ½é‡æ¼”一é。 | 199 | + // seedAgents 会把 config.json çš„ status åŽŸæ · upsert。「åªå†™ä¸é™çº§ã€å®ˆçš„æ˜¯ã€Œä¸è®¸è¢« |
| 200 | + // config.json 推回 draft/trainingã€ï¼Œ**ä¸**ä¿è¯è½¬æ£åŽçš„å€¼åŽŸæ ·ä¸å˜â€”—config.json å½“å‰ | ||
| 201 | + // 写的是什么已毕业状æ€ï¼ˆ"enabled" 也好ã€åˆ«çš„也好,åªè¦ä¸æ˜¯ draft/training),upsert 就会 | ||
| 202 | + // 用那个值覆盖,这是设计如æ¤ï¼Œä¸æ˜¯æœ¬ç”¨ä¾‹è¦æµ‹çš„东西(2026-08-30 夿 ¸ï¼šconfig.json å·²ç»ä»Ž | ||
| 203 | + // 写æ»çš„ "training" 改æˆäº† "enabled",ä¸å†ç¡¬ç¼–ç 具体值,é¿å…è¿™æ¡æµ‹è¯•冿¬¡å› 为 config.json | ||
| 204 | + // 内容å˜åŒ–而å‡çº¢ï¼‰ã€‚ | ||
| 201 | seed.seedAgents(); | 205 | seed.seedAgents(); |
| 202 | - expect(statusOf()).toBe('active'); | 206 | + expect(graphService.isUngraduatedStatus(statusOf())).toBe(false); |
| 203 | 207 | ||
| 204 | // 幂ç‰ï¼šç¬¬äºŒé没有å¯è½¬æ£çš„行 | 208 | // 幂ç‰ï¼šç¬¬äºŒé没有å¯è½¬æ£çš„行 |
| 205 | expect(seed.reconcileDanglingRows().graduated).toEqual([]); | 209 | expect(seed.reconcileDanglingRows().graduated).toEqual([]); |
server/tests/unit/graph-engine-extend-budget.test.js
| @@ -36,7 +36,12 @@ describe('extendBudget · 追加预算并结清卡死的墙钟锚点', () => { | @@ -36,7 +36,12 @@ describe('extendBudget · 追加预算并结清卡死的墙钟锚点', () => { | ||
| 36 | beforeEach(() => wdb.getDb().prepare('DELETE FROM graph_runs').run()); | 36 | beforeEach(() => wdb.getDb().prepare('DELETE FROM graph_runs').run()); |
| 37 | 37 | ||
| 38 | it('run_b962ea87f58f 的真实形态:追加后立即可用,且锚点被断开', () => { | 38 | it('run_b962ea87f58f 的真实形态:追加后立即可用,且锚点被断开', () => { |
| 39 | - const stuckStartedAt = '2026-08-23T05:23:52.886Z'; // 距 now() 已远超 2h | 39 | + // ⚠️ 曾经硬编码成绝对时间戳 '2026-08-23T05:23:52.886Z'(真实事故发生时的时刻)—— |
| 40 | + // 这是一颗时间炸弹:随真实时间流逝,这个绝对值与 Date.now() 的差值只会越来越大, | ||
| 41 | + // 迟早会超过下面追加后的 6h 上限,让断言从「追加后立即可用」变成「怎么追加都不够」, | ||
| 42 | + // 而这条红跟被测代码毫无关系。改成相对 Date.now() 回退 3 小时——同时满足 | ||
| 43 | + // 「超过追加前 2h 上限」与「低于追加后 6h 上限」,且不会随时间推移而漂移。 | ||
| 44 | + const stuckStartedAt = new Date(Date.now() - 3 * HOUR).toISOString(); | ||
| 40 | const run = seedRun('r_stuck', { | 45 | const run = seedRun('r_stuck', { |
| 41 | total_agent_calls: 100, total_tokens: 5_000_000, wallclock_ms: 2 * HOUR, | 46 | total_agent_calls: 100, total_tokens: 5_000_000, wallclock_ms: 2 * HOUR, |
| 42 | spent: { agent_calls: 35, tokens: 249_324, active_ms: 0, started_at: stuckStartedAt }, | 47 | spent: { agent_calls: 35, tokens: 249_324, active_ms: 0, started_at: stuckStartedAt }, |
system/agents/sdd-planning/SKILL.md
| @@ -3,7 +3,7 @@ | @@ -3,7 +3,7 @@ | ||
| 3 | > 本版本为 Evolve 训练产出(v1,首次训练)。基线为生产版 `system/agents/sdd-planning/agent.md`, | 3 | > 本版本为 Evolve 训练产出(v1,首次训练)。基线为生产版 `system/agents/sdd-planning/agent.md`, |
| 4 | > 仅强化「接口契约」「数据形态」两类的执行深度指导,其余框架(五类东西结构、 | 4 | > 仅强化「接口契约」「数据形态」两类的执行深度指导,其余框架(五类东西结构、 |
| 5 | > `contract.json`/`attempt-{n}.json` 模板、越权判断)与基线一致,未重写。 | 5 | > `contract.json`/`attempt-{n}.json` 模板、越权判断)与基线一致,未重写。 |
| 6 | -> 训练依据:`../hunt/knowhow_v1.md` + `../digest/benchmark_v1.md`(D2 判据可独立复现性,权重30+封顶)。 | 6 | +> 训练依据:`train/hunt/knowhow_v1.md` + `train/digest/benchmark_v1.md`(D2 判据可独立复现性,权重30+封顶)。 |
| 7 | 7 | ||
| 8 | ## 身份定位 | 8 | ## 身份定位 |
| 9 | 9 |