走查逼出的六条:发卡前清单要有行 · 在库要拿得出依据 · 数据工单只建缺的那颗 · 录入卡自带 basis

ACT-WORK-05 差异评估的浏览器全流程走查(六轮六条路径,全部 released),
逐条治了实跑里现形的缺陷。活动包 1.2.2 → 1.2.6:

- H(1.2.3):模型压拍把执行工单发了、清单一行没写,人对着空表 ——
  「先写清单再发卡」只在剧本里,压过拍就只有说明书还在;规矩搬进说明书。
- I(1.2.4,结构治):原 PN 根本不在库,模型两颗都判「在库」,硬阻塞整条路被跳过。
  剧本已写到头 → pn_check 加 found_pn_ref(refersTo + requiredWhen):
  判在库就必须填一个基座查得到的料号,查无此值当场拒、填不出则收不了步。
  这是「机检只读基座数」第六次现形,前五次都是计数,这次是存在性。
- J(1.2.5):数据工单派给了在库的那颗,缺的那颗没人建 ——
  剧本把「建哪几颗」和「要不要问人」写在同一串分岔里,从没写过「found 的不建」;拆成两问。
- K(1.2.6):数据录入卡的参考信息栏摆着引擎内部家具(九个计划项全名)——
  entryCard.basis 契约里可选,活动没声明,基座就兜了内部事实;卡骨架补三行人话。

包内自测 56 → 61 条;门禁 typecheck / lint / depcruise / test(2071)全绿
(api:check 的差异全在 org/* 与 api/*,是工作树里先前那批鉴权改动,本轮未添)。

docs/ACT-WORK-05-端到端测试记录.md:六轮走查的逐轮结论与 A–K 十一条发现总表。
docs/debt.md:四条平台欠账(disable 无路可走 · 前一项不收后一项的件推不出来 ·
拒回数不出缺的是工具调用 · 零行台账的提交闸无牙)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJcX8A6TWp4GRs41TYryEh
This commit is contained in:
xcs
2026-08-27 04:29:40 +08:00
co-authored by Claude Opus 5
parent 7ea08fb16a
commit eb80876554
6 changed files with 394 additions and 7 deletions
+91
View File
@@ -1181,3 +1181,94 @@ pnpm 硬链到全局 store,真实占盘远小于这个数,但 `du` 看着吓人
㉔ **`claimForResend` 会把抢到的那一行的 `created_at` 推到当下**:这是 CAS 判据的一部分
(抢到即离开可抢集合),代价是**这一列不再是「这一行何时建的」** —— 谁要拿它当来源凭据都会读错。
要么另立一列记真正的创建时刻,要么在读它的地方写明。目前无人拿它做凭据。
## 成员目录冷启 8 秒(2026-08-26,ADR-012)
`GET /api/gov/org/users` 第一次要 8 秒:Keycloak **没有「一次拿回全部人的生效角色」的接口**
(手册 §14.4 是逐人 `GET /users/{id}/role-mappings/realm/composite`),8 个人就是 8 趟远程往返。
并发从 8 提到 24 实测无改善(9.3 秒)—— 卡的是那台 Keycloak 每个请求自身的时延,不是我们的并发。
现状:usecase 层 60 秒读缓存(`DIRECTORY_TTL_MS`),任一写成功即整体清空 ——
**冷启 8 秒、60 秒内 0 毫秒**;写完立刻重拉一定看得到新结果。
结构性治法(没做):按**角色**问而不是按人问 —— `GET /roles/{name}/users` 拿直接持有者
(每个角色一趟)+ 展开 `default-roles-<realm>` 的组合(一趟),合起来就是全员的生效角色,
调用次数从 O(人数) 降到 O(角色数)。当时试过这条的半截(只用 `/roles/{name}/users`),
**数错了**:`user` 是经 `default-roles-corp` 继承的,那条路报「持有 0 人」而实际人人都有 ——
所以要做就得连组合展开一起做,不能只做一半。realm 长到几十上百人时再动它。
## 成员与组织:写完那几秒屏上还是旧的(2026-08-26,ADR-012)
组树那一趟读要 5-6 秒(逐组数成员 + 服务端 60 秒缓存被写清掉),于是**删一个组之后**
到画面更新之间有一段真空。实测三轮里有一轮**根本没刷过来** —— 网络记录里露了马脚:
中间夹着一趟 `GET /groups → 401`(token 到期,typed-fetch 强刷重试),那一次重取就此失败,
而 react-query 拿不到新数据就继续摆旧的。
已做的两刀:
- 服务端缓存**按写碰到的东西分开清**(`'users' | 'groups' | 'all'`)—— 建个组不再把
「全量成员的角色」那趟 N+1 拖下水;前端的失效键同批收窄(`ORG_KEYS`)。
- 删组成功后**先把已知事实落进缓存**(`setQueryData`,按路径前缀连子组一起摘),
再照常发重取 —— 与 `mirrorTwins` 同一条道理:值本来就在手上,不必等服务端再说一遍。
**还欠**:①**重取失败时界面继续摆旧数据,一声不吭** —— 这不是这一屏的毛病,
治理台每一屏都这样(`QueryGate` 只管首次载入,不管重取失败);要治得在共享层做
「这份数据是几时的 / 刚才那次刷新失败了」。②组树的成员数仍是逐组一趟,
组多了会线性变慢 —— 与「成员目录冷启 8 秒」是同一族问题(见上一条),
真治法都是把「按人问」换成「按组/按角色问」。
## 推送有多密请求就有多密:广播没有合并层(2026-08-26)
08-26 的「工作台疯狂轮询三个接口」查到底是两台后端互桥回弹(`broadcast-bridge.ts`,已修,
2 测钉死)。风暴掐断之后,**同一条放大链还在**,只是没了永动机:
- `task-service.tick` **每拍无条件** `setPhase` → `control` 信封 → `task` touch(不看 phase 有没有变);
每拍推帧 `view-html` 一条;推进器 `advanced` 一条、在流程里再加一条 `workflow` ——
一拍 3-4 条 `task` touch 在几十毫秒内到达;
- 前端 `invalidateFor` 逐条失效、不合并:每条 touch 各让 tasks / approvals / task 详情重取一次,
TanStack 默认还会取消在途请求重发(`cancelRefetch`)—— 一拍 9-12 趟请求,一条案例活动跑十几拍
就是上百趟,而画面只需要每拍刷一次。
治法两层:①源头 `setPhase` 只在 phase 真变时广播(与 `worthBroadcasting` 同一条道理);
②`invalidateFor` 加一个 50-100ms 的合并窗,同一批 touch 落地后按键去重只失效一次
(纯函数、可测)。两层都做,任一层失守另一层兜着。
## ACT-WORK-05 走查逼出来的三条平台欠账(2026-08-27)
差异评估活动(`part-substitution-assessment`)从零到 released 的浏览器走查里,
有三处**病在平台、这一轮只在活动侧绕过去了**——绕法是措辞,治法在共享面:
- **`disable` 这条路从来没通过**:`lifecycle.ts` 里 `Disabled` 只能从 `Enabled` 来,
而 `Enabled` 只由 `plan` 动作写(裁量项入场);普通计划项一直停在 `Available`,
于是任何 `disable` 都撞「生命周期迁移非法:Available → Disabled」。
全仓只有本活动用过它 —— E04/I01 一次都没用,所以没人发现。实测代价:连拒五拍熔断。
两条路二选一:要么让 `disable` 对 `Available` 的项也成立(语义:这一单用不上它),
要么在拒回里明说「本平台没有弃用计划项这回事,不需要办的项什么都不用做」。
**现状**:活动侧全包清了 `disable`,并在哨兵上加了「真有料号不在库」才开门。
- **前一项不收掉,后一项的件就推不出来**:引擎挑画面按「当下还没干完的第一步」取 view
(`turn-runner.ts` 2058-2064 附近)。模型把「人交回 → 发下一张卡」压成一拍、忘了 `finish`
上一项时,卡是新的、件还是旧的 —— **人拿到的卡写着「把资料填到件上」,屏上一个格子都没有**,
提交闸如实拦死,整单挂在那儿。E04 的 S4→S5 撞过同一堵墙(2026-08-23 已按 `currentRuntime`
重算在办那一步治过一次),这次是**两个人环项前后相邻**的形状,重算也救不了 ——
两项都还 Active,第一步仍是上一项。
真治法:发人卡那一刻,若卡绑的项与当前推的 view 所属项不是同一项,要么按卡所属项推件,
要么把这件事如实说给模型(现在它一点回声都收不到)。
**现状**:活动侧把动作串写死「①先 finish ②再发卡」,配测试钉措辞。
- **拒回只谈行与列,数不出「缺的是一次工具调用」**:`S3_assess` 的 doneWhen 里有
`tool_<工具>_calls > 0`(2026-08-03 立的「机检只读基座数」扩到工具那一条),
模型跳过工具、自己把推荐等级填了,于是 finish 恒被拒;而拒回文案只说
「是不是该写 N 行只写了 M 行 / 某一行缺了必填列」—— 把注意力全导向行与列,
模型连着五拍重写同样的明细,熔断。
真治法:收步判据里那半句若是 `tool_*_calls`,拒回就该直说「这一步的收步条件里还数着
某某工具的调用次数,当下是 0 —— 先去调它」。这是设计律「拒回的话是模型下一拍的行动指南」
在工具这一类上的缺口。
**现状**:活动侧在 guide 里写了写盘前自检。
- **执行工单绑的必交付台账一行都没有时,提交闸无牙**:卡不绑表时闸回落到
必交付声明的台账(`gateAddressesOf`),而 `missingReadings` 只数「已有行缺不缺必填」——
**零行 = 零缺**,闸开着。实测:模型压拍把卡发出去、清单一行没写,人对着空表,
按得下提交、按下去什么也没发生。
真治法待议:若一张必交付台账本单一行都没写,闸该拦并说「这张表还没有内容」;
但也有活动是让人在件上加第一行的(那时零行合法)—— 得由活动声明区分。
**现状**:活动侧把「发卡前清单必须先有行」写进说明书(每拍都在),配测试。