脚本 e2e 逼出的八条:闸看错了表 · 拒回喂指针 · 零行骗过闸与哨兵 · 早收不该算撞墙

凭证到位后 `w05` 五案例连跑六轮,逼出八条**浏览器走查逮不到**的缺陷 ——
脚本每张卡都先空手交一次、每个岔路口都按序代答,人手点的时候永远不会去试
「空着交上去会怎样」。末轮:跑到的四例全部 released,三例被拒 0
(第五例停在大模型账户余额不足,不是代码问题)。

平台侧(六条):
- L 数据工单的提交闸没牙 —— 卡没绑表就回落到必交付物,而数据工单要人补的是
  `parts.*` 主数据,不在交付物名单里:闸盯着一堆无关的表、一格不缺,空手交卷一路放行。
  契约新增 `ExecutionWorkOrder.gateLedgers`(这张卡该拦哪几张表,发卡的人说了算),
  `gateAddressesOf` 变三级取一:绑的表 > 卡自报 > 交付物。
- M `finish` 撞 doneWhen 为假的拒回只谈「行与列」,而缺的是一次**工具调用**;
  现在摆出判据原文 + 那几个名字此刻的读数,`tool_*_calls` 读到 0 的单独点名(`unmetWhy`)。
- N 人点了决策卡,模型每拍再发一张,连拒五拍熔断。把「他选的是 approve」写进拒回**没用** ——
  下一拍它又从头翻一次会滚走的窗口。改结构:`answeredCardFact` 把
  「这张卡已答过 → 他选的是什么」做成每拍都在的事实行。
- O 模型传 `requirement: null`,工具 `null.trim()` 当场崩(边界上一律经 `text()` 归一)。
- P 拒回里的 `count=(没写过)` 是 S-FEEL 函数名被当成台账名 —— 假线索模型会去追。
- Q/S 零行 = 零缺,闸与哨兵一起放过空手交卷的数据工单(`emptyGate`:卡自报的表一行都没有
  = 没完成);「早收」头一次只落观察、第二次起才算被拒(与「重发已答过的卡」同一把尺)。

活动侧(1.2.7 → 1.2.9):三张执行工单各自声明 `gateLedgers`;说明书③补「发卡前骨架必须先有行」、
⑤补这张卡的前提(一颗 absent 都不剩);哨兵 `parts_profile_rows >= created` 逐颗对账;
S0_bind 写明发录入卡那一拍别抢写需求行。

e2e 脚本加 `JCAI_E2E_BEARER` 直供旁路(服务账号 client_credentials 仍是正路)。
门禁 typecheck / lint / depcruise / test(2091)全绿;api:check 的差异仍只在 org/* 与 api/*
(工作树里先前那批鉴权改动),card.d.ts 是本轮的纯新增字段。

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 09:31:35 +08:00
co-authored by Claude Opus 5
parent e235207e4a
commit df18882322
16 changed files with 490 additions and 43 deletions
@@ -41,6 +41,7 @@ export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
<caseFileItem id="sub_eval_pn_check_exists_status_abandoned" name="已放弃的候选数(基座现数)"/>
<caseFileItem id="sub_eval_verdict_rows" name="结论行数(基座现数)"/>
<caseFileItem id="parts_profile_rows" name="本单建的芯片主数据行数(基座现数)"/>
<caseFileItem id="sub_eval_pn_check_exists_status_created" name="经数据工单建好的料号数(基座现数)"/>
<caseFileItem id="parts_profile_missing" name="本单建的芯片主数据缺必填的行数(基座现数)"/>
<caseFileItem id="parts_parameter_missing" name="本单建的标准化参数集缺必填的行数(基座现数)"/>
<caseFileItem id="parts_document_missing" name="本单建的资料记录缺必填的行数(基座现数)"/>
@@ -79,7 +80,17 @@ export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
-->
<sentry id="s_pns_resolved">
<onPart id="op_pr" caseFileItemRef="sub_eval_pn_check_exists_status_absent"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_pr"><condition>sub_eval_pn_check_rows = count(candidateInternalPns) + 1 and sub_eval_pn_check_missing = 0 and sub_eval_pn_check_exists_status_absent = 0 and sub_eval_pn_check_exists_status_abandoned &lt; count(candidateInternalPns) and parts_profile_missing = 0 and parts_parameter_missing = 0 and parts_document_missing = 0</condition></ifPart>
<!--
**标了 created 的每一颗都要有一行主数据**(2026-08-27 e2e 两轮各现一形):
「没写过 ≠ 写了 0 行」(2026-08-01 A1 立的那一课,在这里以新形状复现):
\`_missing\` 只数**已有行**缺不缺格子 —— 一行都没写时它是 0,门就开了。
实测:模型压拍没写骨架就把两张数据工单发了出去、人也「交」了,
\`parts_profile_missing = 0\` 成立,这一步照过,而库里那颗料根本不存在。
第一刀写成「created ≥ 1 就得有行」还是太松:三候选那单里模型把**第二颗**也标成 created
却只建了第一颗,一行足以让门开着,直到 S2 写关系时才被引用检查拦下。
改成**逐颗对账**:\`parts_profile_rows >= created\` —— 标了几颗建好,就得有几行。
-->
<ifPart id="if_pr"><condition>sub_eval_pn_check_rows = count(candidateInternalPns) + 1 and sub_eval_pn_check_missing = 0 and sub_eval_pn_check_exists_status_absent = 0 and sub_eval_pn_check_exists_status_abandoned &lt; count(candidateInternalPns) and parts_profile_missing = 0 and parts_parameter_missing = 0 and parts_document_missing = 0 and parts_profile_rows &gt;= sub_eval_pn_check_exists_status_created</condition></ifPart>
</sentry>
<!-- ③ 评估输入已确认(旗 + 清单有行且必填齐)—— 标准化那一步的入场前提 -->
<sentry id="s_input_confirmed">
@@ -213,7 +224,11 @@ PN 在库但个别字段缺 = 「有而不全」,归执行工单 01 补。**该
\`value\` 只有 \`"create"\` / \`"abandon"\` / \`"custom:"\` 开头三种:\`"custom:"\` 开头按放弃处理、\`note\` 就是放弃原因;
点了 \`"abandon"\` 没写原因,\`abandon_reason\` 写「负责人本轮放弃(未注明原因)」。
③ 数据工单 · P-08(所有要建的料号一起,一张单;零输入,填报全在工作区的件上):
③ 数据工单 · P-08(所有要建的料号一起,一张单;零输入,填报全在工作区的件上)——
**发这张卡之前,\`parts.profile\` 与 \`parts.parameter\` 必须先有骨架行**(名单里每颗料各一行 + 它的参数各一行)。
不管这一拍是不是顺手把上一步收掉的(压拍常发生,那时这一步的剧本还没摊开),写骨架与发卡都得在同一串里,顺序不能反 ——
件上一行都没有的话,人打开是一张空表、什么也补不了,而这一单的主数据从头到尾不会存在
(实测走到 S2 才被引用检查拦下,那时两张数据工单都已经「办完」了)
{"kind":"human","itemId":"S0d_p08_master","workOrder":"execution","card":{
"title":"数据工单 · 芯片主数据创建(P-08):<料号列表,顿号分隔>",
"ask":"这些料号在芯片主数据里查无记录,请在工作区的件上把整条记录建齐(一颗一行,可加行)。",
@@ -222,8 +237,12 @@ PN 在库但个别字段缺 = 「有而不全」,归执行工单 01 补。**该
"有 datasheet 的把引用与版本一起填(成对);制造商与品牌不一致时分开写",
"改完保存,回这张卡确认 —— 确认后这批主数据即入库,活动从等待数据唤醒"],
"standard":"每颗料号表 1 一行,必填列全齐;表 2 至少含输入电压范围 / 输出电压 / 输出电流 / 工作温度 / 封装相关的硬约束项各一行。",
"gateLedgers":["parts.profile","parts.parameter"],
"basis":[{"k":"触发原因","v":"按 internal_pn 查 P-08 查无记录(完全没有,不是字段缺失)"},
{"k":"AI 预填","v":"<有 datasheet 就写「已按 datasheet 预填 N 项,请核对」,没有写「未上传 datasheet,请全填」>"}]}}
**\`gateLedgers\` 必须给**:这是零输入卡,基座不知道这一张要人补哪几张表 ——
不给它就回落到必交付物那几张(与这张卡无关),于是件上红带点着「缺品牌名称」而提交钮照样绿,
空手交卷一路放行(2026-08-27 实测)。给了之后,闸与红带、放行门读的是同一个数。
④ 数据工单 · P-10(同一批料号的资料记录,一张单;在 P-08 那张确认之后再发):
{"kind":"human","itemId":"S0d_p10_document","workOrder":"execution","card":{
@@ -233,15 +252,22 @@ PN 在库但个别字段缺 = 「有而不全」,归执行工单 01 补。**该
"同一料号多份资料就多加几行,序号 01/02… 自己续",
"改完保存,回这张卡确认"],
"standard":"每颗料号至少一行 datasheet 记录,必填列全齐。",
"gateLedgers":["parts.document"],
"basis":[{"k":"触发原因","v":"按 internal_pn 查 P-10 查无记录"}]}}
⑤ 执行工单 01 · 评估输入与资料补齐确认(S1_input_confirm;零输入)——
**发它的前提:所有料号都已落定** —— \`${D}.pn_check\` 里**一颗 absent 都不剩**(每颗要么 found、要么经数据工单 created、
要么人点了放弃 abandoned),且建过的那几颗在 \`parts.profile\` 里真有行。还有 absent 就**回去把它们办完**
(候选总数 ≥2 的逐颗发决策卡问、原 PN 与独苗候选直接建),别先发这张卡:
硬发出去人也补不成 —— 后面写替代关系时会被引用检查逐颗拦下,而那时数据工单都已经「办完」了(2026-08-27 实测两轮)。
**有几颗 absent 就要办几颗**,办完第一颗别忘了还有第二颗。
**发这张卡之前,\`${D}.input_gap\` 必须先有行**:清单为空的卡就是让人对着一张空表签字,他补不了任何东西。
不管这一拍是不是顺手把上一步收掉的(压拍常发生,那时 S1 的剧本还没摊开),写清单与发卡都得在同一串里,顺序不能反:
已绑定四项(customer / request / original_pn / candidate_N,status="ok")、待补两项(application_scenario / bom_position,status="gap")、
默认三项(substitution_type / rule_version / owner,status="default")、每颗候选的「未披露」参数各一行(status="gap")、原 PN 资料版本(status="ok");
逐列口径见这一步的剧本。
{"kind":"human","itemId":"S1_input_confirm","workOrder":"execution","card":{
"gateLedgers":["${D}.input_gap"],
"title":"评估输入与资料补齐确认:<原 PN> → <N> 个候选",
"ask":"启动绑定的四项已锁定。请在工作区的件上补齐「待补 / 待确认」行的补充内容,默认值可改可确认。",
"steps":["应用场景、BOM 位是启动时未收的:必须补(写在「补充内容」列)",
@@ -300,6 +326,8 @@ export const ASSESSMENT_STEPS = [
【客户在不在库】在 \`customer.profile\` 里找 \`customerCode\`:在 → 直接用;不在 → 照说明书①发数据录入卡(名字用 \`customerName\`),
发完这一拍结束;人确认后这家客户就在草稿层里了,下一拍接着做。**客户不在库也不许自己 write customer.profile**。
**那一拍只发卡,别顺手写 \`demand.request\`** —— 需求行的客户编码有引用检查(客户还没落草稿层,写下去当场被拒);
等人确认完,下一拍再写需求行与核对表,一次写齐。
【写 \`demand.request\` 一行(V1)】request_id、customer_code(= customerCode)、requirement_version = "V1"、request_type = "技术需求"、
request_description(入参原话,不改写)、input_snapshot_ref、original_internal_pn、request_status = "pending"、current_activity = "ACT-WORK-05"、
@@ -364,6 +392,10 @@ current_stage = "assessing"。application_scenario / bom_position **这一步不
brand_name 有就写}(这一行是骨架,下一项 S0d_p10_document 会把它填成正式记录)。
4. 同一拍发说明书③那张 P-08 数据工单(title 里列全料号),发完这一拍结束(挂起)。
**\`created\` 是「这颗料真的建好了」,不是「我打算建」**:只有它在 \`parts.profile\` 里真有一行(件上填好交回来了)
才改成 created —— 门上有一条逐颗对账(建好几颗就得有几行主数据),标了却没建,这一步过不去,
到 S2 写关系时还会被引用检查再拦一次。
【P-08 那张确认回来了】这一拍的动作串**恰好两个,顺序不能反**:
① 先 \`finish S0d_p08_master\` —— **必须先把这一项收掉**。不收掉,引擎挑工作区画面时按
「当下还没干完的第一步」取 view,S0d_p08_master 只要还 Active,画面就一直停在
@@ -89,7 +89,7 @@ export const manifest: ActivityManifest = {
identity: {
activityId: ACTIVITY_ID,
name: 'ACT-WORK-05 · 差异评估',
packageVersion: '1.2.6',
packageVersion: '1.2.9',
category: '需求分析与方案匹配',
sdkVersion: '0.1.0-m5',
checksum: 'dev-checksum-part-substitution-assessment',