fix(loop-engine): S1 次序回归测试改 import 真读数函数,删掉已经漂移的手抄

上一轮判断"读数层四个函数也躲不掉、只能手抄"是错的:rowReadings/missingReadings/
tallyReadings/distinctReadings 是 apps/api/src/app/wiring/engines.ts 的具名导出,测试文件住在
apps/api/src/modules/loop-engine/__tests__/,import 它们是 apps→apps,activities-isolated 与
apps-not-into-activities 两条隔离规则一条都不沾。真正躲不掉的手抄只有六笔配方常量本身(ACT_*,
因为那条边是 apps/* → activities/* 的反方向,才是两条规则真堵住的)。

而且手抄件已经在同一笔提交里漂了:missingReadings 拼缺项名的真身是
gaps.map((f) => f.label ?? f.name)(engines.ts:378),手抄件漏了 label ?? 那半句,连带镜像的
Declared 类型没有 label 字段,这条分支从结构上就复刻不出来。

删掉约 150 行手抄,改成 import 真身。相对路径要爬三层撞了 eslint 的 no-restricted-imports,
新增 @app/* 路径别名(tsconfig.base.json + vitest.config.ts 各一行,与既有 @modules/* / @shared/*
同一种写法)—— 取名核对过不撞 pnpm 工作区包名 @app/api / @app/web(两者从未被当作 TS import
specifier 用过,且 apps/api 的 package.json exports 只暴露了 "." 一个入口)。

变异复验(删手抄之后这条用例仍能精确复现原 bug):旧次序 → RED,Received 恰是评审报告点名的
那个空字符串,与手抄版本逐字相同;新次序 → GREEN。depcruise 单独确认零违规。

文件头注按"测什么、不测什么"重写:次序不倒退已由 steps.test.ts 用精确数组相等钉在真
ASSESSMENT_STEPS 上;这份镜像守的是引擎逐 action 刷新时机,在形状上有代表性即可,不是逐字
等于真配方的承诺。docs/debt.md 补一条,点名唯一还留着的手抄(ACT_* 六个常量)与谁改了 S1
配方形状该顺手核什么。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
xcs
2026-08-29 15:15:18 +08:00
co-authored by Claude Opus 5
parent defa048270
commit 02e0d1206d
4 changed files with 98 additions and 150 deletions
+31
View File
@@ -1379,3 +1379,34 @@ pnpm 硬链到全局 store,真实占盘远小于这个数,但 `du` 看着吓人
+ 三套 e2e;②本轮改的是配方那一条新路,`git diff --stat b995148...HEAD -- activities/`
为空这条硬约束意味着不能顺手把模型路径也捎带上、再靠一次浏览器走查收尾 ——
排查与验收的工作量不是这一条闭合修复能捎带的。**应当独立起一条、排在 W05 重写之前**。
## S1 配方回归测试里躲不掉的手抄(2026-08-29,Task 4 评审二轮)
`apps/api/src/modules/loop-engine/__tests__/w05-gate-reason-order.test.ts` 证的是**引擎逐 action
刷新时机**这件事(案卷投影必须排在读它的基座读数所依赖的那几笔之后)—— 这一条只有把真引擎跑起来
才测得出;`activities/part-substitution-assessment/__tests__/steps.test.ts` 的"六笔,次序是…"用例
用精确数组相等钉的是**真** `ASSESSMENT_STEPS` 本身,两份测试各守各的,互不重复。
这份引擎级测试**手写了六个 `ACT_*` 常量**,在形状上代表 `case-model.ts` 里 `S1_inputs.auto.do`
当前用到的四种地址写法(字面 `rows` / 单层 `each+where` / 两层 `each+where+outer` / `once`)——
这是**躲不掉**的手抄:`.dependency-cruiser.cjs` 的 `activities-isolated` / `apps-not-into-activities`
两条规则对测试文件没有例外,`apps/*` 与 `activities/*` 互相不许 import,所以测试文件不能直接
`import { ASSESSMENT_STEPS } from '.../case-model.ts'`。
(读数层四个纯函数 `rowReadings` / `missingReadings` / `tallyReadings` / `distinctReadings` **不在
此列**——它们是 `apps/api/src/app/wiring/engines.ts` 的具名导出,从 `apps/api/src/modules/
loop-engine/__tests__/` import 它们是 `apps → apps`,两条隔离规则一条都不沾。第一版误判"读数层也
躲不掉"而手抄了一份,评审查出与生产版已经漂了一处(`missingReadings` 拼缺项名用
`f.label ?? f.name`,手抄件漏了 `?? f.name` 那半句),已改为直接 import 真身,删掉约 150 行手抄,
换成 `@app/*` 路径别名〔`tsconfig.base.json` + `vitest.config.ts` 各新增一行,与既有 `@modules/*`
/ `@shared/*` 同一种写法〕——之前的三层相对路径撞了 `eslint.config.js` 的 `no-restricted-imports`。)
**欠账**:这份镜像**不会随 `case-model.ts` 自动同步**——它守的是引擎机制而不是"逐字等于当前配方"
这个没人能守的承诺,所以次序变化不影响它的有效性,但如果 S1 配方的**形状**变了(比如新增了
`analyze` 动词、某一笔的 `from` 换了一种从没出现过的地址写法、或某个 `when` 链换了结构),这份
文件的代表性可能跟不上而不自知——没有测试会为此变红,装包也不会拒。
**谁改了 `S1_inputs.auto` 的形状(不是次序),该顺手做的事**:打开
`w05-gate-reason-order.test.ts`,核一遍 `ACT_*` 六个常量描述的地址/字段写法是否还能代表
`case-model.ts` 当前用到的构造种类;不必逐字同步(那不是这份测试的目标),但要能回答
"如果生产那边这一类写法出了刷新时机的 bug,这份镜像能不能测得出来"。