审核通过与否读基座数;「重做」不再被指向 finish;审核卡摆成验收工单(W05 1.4.7)

用户两报:①「我上一步明确要求重做了,怎么会继续向下推进」②「验收工单弹出的是需要你定,明显不对」。

①病根两处合谋:人两次答 custom:重做一次,模型把一模一样的问法又发了一遍被闸拦下;而拒回的话
不分答案一律说「照这个选择把结论落进台账那面旗,再 finish」—— 对通过是对的,对重做是把模型往错路上推,
它照办写了 review_approved_total = 1,而 S4 的哨兵读的正是这面模型自写的旗(被叫的那一方自己记账,A6 那一课)。
- 基座新读数 decision_last / decision_<值>_total(按 card-reply 数);S4 哨兵改读 decision_last = "approve",
  人最后一次真点了通过才开门;静态审认这几个名字。
- 拒回 / 观察 / 事实行三处同一段 chosenGuidance 按答案分岔:custom: 不是通过 —— 按他写的改、换问法重发、
  不许写通过旗、不许 finish;选项值指回说明书那一档。
- S4 剧本驳回分支明写不许写旗、ask 开头必须写明这一轮改了什么(字面一样的卡会被吞)。
②平台的验收工单是终局,中间审核只能靠决策卡循环 —— 但不该穿「需要你定」的衣服:契约 decisionCard.variant:'review',
  PlanStep.cardVariant 扩到 'review',cards.decide 带 itemId,引擎按剧本决定;对话区摆成「工单 · 验收 / 审核」。
2106 测 / typecheck / lint / depcruise 绿;api-report 已刷;CONTRACT.md 同步。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJcX8A6TWp4GRs41TYryEh
This commit is contained in:
xcs
2026-08-27 14:46:45 +08:00
co-authored by Claude Fable 5
parent 94e66d5a58
commit 75553371b0
15 changed files with 154 additions and 26 deletions
@@ -146,7 +146,8 @@ describe('说明书与剧本的骨架', () => {
for (const item of ['S0d_p08_master', 'S0d_p10_document']) {
expect(ASSESSMENT_STEPS.find((s) => s.item === item)?.cardVariant, item).toBe('data');
}
for (const step of ASSESSMENT_STEPS) if (!['S0d_p08_master', 'S0d_p10_document'].includes(step.item)) expect(step.cardVariant, step.item).toBeUndefined();
expect(ASSESSMENT_STEPS.find((s) => s.item === 'S4_review')?.cardVariant).toBe('review');
for (const step of ASSESSMENT_STEPS) if (!['S0d_p08_master', 'S0d_p10_document', 'S4_review'].includes(step.item)) expect(step.cardVariant, step.item).toBeUndefined();
expect(NARRATIVE).toContain('"title":"数据工单 · 芯片主数据创建(P-08)');
expect(NARRATIVE).toContain('"title":"数据工单 · 原厂技术资料创建(P-10)');
expect(NARRATIVE).toContain('"title":"评估输入与资料补齐确认');
@@ -330,6 +331,14 @@ describe('说明书与剧本的骨架', () => {
}
});
it('审核通过与否读基座数 decision_last,不由模型自写的旗说了算;驳回分支明写不许写旗、不许 finish(2026-08-27 实测:人答「重做」它照样推到放行)', () => {
expect(CMMN_XML).toContain('decision_last = "approve"');
expect(CMMN_XML).toContain('<caseFileItem id="decision_last"');
const g = ASSESSMENT_STEPS.find((s) => s.item === 'S4_review')?.guide ?? '';
expect(g).toMatch(/不许写 review_approved_total、不许 finish/);
expect(g).toMatch(/ask 的开头必须写明这一轮改了什么/);
});
it('五段进度,每段名 ≤10 字;除放行外每项恰属一段', () => {
expect(ASSESSMENT_PHASES).toHaveLength(5);
for (const p of ASSESSMENT_PHASES) expect(p.name.length, p.name).toBeLessThanOrEqual(10);
@@ -64,6 +64,7 @@ export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
<caseFileItem id="tool_part_substitution_assessment_compare_parameters_calls" name="比对工具成功调用次数(基座现数)"/>
<caseFileItem id="tool_part_substitution_assessment_recommend_calls" name="定级工具成功调用次数(基座现数)"/>
<caseFileItem id="review_approved_total" name="差异结论已审核通过(仅通过时置一)"/>
<caseFileItem id="decision_last" name="最后一张决策卡人选的值(基座按留痕数)"/>
<caseFileItem id="landing_prepared_total" name="拟落账已备好(S5 写完时置一)"/>
</caseFileModel>
<casePlanModel id="plan" name="差异评估" autoComplete="true">
@@ -113,7 +114,12 @@ export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
<!-- ⑥ 审核已通过 —— 拟落账的入场前提 -->
<sentry id="s_reviewed">
<onPart id="op_r" caseFileItemRef="review_approved_total"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_r"><condition>review_approved_total &gt; 0 and substitution_difference_item_missing = 0</condition></ifPart>
<!--
审核通过与否**不由模型自写的旗说了算**(2026-08-27 实测:人两次答「重做」,模型照拒回的指引写了旗,
S4 被收掉一路推到放行)。\`decision_last\` 是基座按 card-reply 数出来的:人最后一次真点了 approve 才开门。
旗留着记账,但它单独说了不算。
-->
<ifPart id="if_r"><condition>review_approved_total &gt; 0 and decision_last = "approve" and substitution_difference_item_missing = 0</condition></ifPart>
</sentry>
<!-- ⑦ 拟落账已备好:每条关系都到 FAE 确认、P-01 回写五项齐 —— 放行门的入场前提 -->
<sentry id="s_landing_ready">
@@ -540,6 +546,8 @@ difference_status / compare_result / risk_level / impact_statement / unknown_dis
},
{
item: 'S4_review',
/* 中间审核摆成验收工单的样子(机制是可循环的决策卡);由剧本声明,模型抄不抄都一样 */
cardVariant: 'review' as const,
intent: '差异评估结论审核:决策卡循环,通过或驳回 + AI 按意见改台账重发',
writes: ['substitution.relation', 'substitution.difference_item', 'validation.record', `${D}.verdict`],
reads: ['substitution.relation', 'substitution.difference_item', 'validation.record', `${D}.verdict`],
@@ -550,11 +558,13 @@ difference_status / compare_result / risk_level / impact_statement / unknown_dis
轨迹里找这一项已答过、还没处理的 [card-reply]:
· 没有 → 照说明书⑥发第一张决策卡(ask 里把候选数、比对条数、四态各几条、逐候选推荐等级说全),发完这一拍结束。
· "approve" → 写 \`${D}.verdict.review_approved_total = 1\`,finish 这一项。别再发卡。
· "reject" / "custom:…" → **驳回**。按意见改台账(同 PK 覆盖写,不开新行、不改行数):
· "reject" / "custom:…" → **驳回**。**不许写 review_approved_total、不许 finish** —— 下一步的门读的是基座数
\`decision_last\`(人最后一次真点了 approve 才开),写了旗也开不了门,只会把自己绕进死循环。按意见改台账(同 PK 覆盖写,不开新行、不改行数):
· 意见点名某个参数 → 改那一行 \`substitution.difference_item\` 的 impact_statement / unknown_disposition / evidence_ref;要挂验证的补一行 \`validation.record\`;
· 意见说推荐理由站不住 → 改 \`substitution.relation.recommendation_reason\`(等级不许改 —— 那是工具算的;要改先回 S3 补证据重比,本活动不回头,写清楚为什么维持);
· 没写具体意见("reject")→ 自己挑最站不住的一处改实(证据最含糊的那条 evidence_ref、去向最空的那条 unknown_disposition),**至少改对一处**;
然后**换一种问法**再发一张决策卡(把改了什么写进 ask),发完结束。
然后**换一种问法**再发一张决策卡 —— **ask 的开头必须写明这一轮改了什么**(与上一张字面不同;
字面一样的卡会被当成同一张吞掉,连吞两次就算被拒),发完结束。
**基座对着字面比对意见与草稿**:观察里点名了哪一行,就改那一行,不挪到别的行头上。`,
},
{
@@ -89,7 +89,7 @@ export const manifest: ActivityManifest = {
identity: {
activityId: ACTIVITY_ID,
name: 'ACT-WORK-05 · 差异评估',
packageVersion: '1.4.6',
packageVersion: '1.4.7',
category: '需求分析与方案匹配',
sdkVersion: '0.1.0-m5',
checksum: 'dev-checksum-part-substitution-assessment',