Archived
fix(app): 决策卡的人工验收清单也按 itemId 兜底 —— 配方发的审核卡从前一条待确认都没有
装配层从前只给 `variant` 兜底,`checklist` 没有。配方(auto-runner)发审核卡时 只递了 variant —— 那张卡于是穿着验收工单的衣服、allowCustom 也对,**清单是空的**: 回传门 `checkChatCardReply` 的 `checklistMissing` 在 checklist 缺席时**恒过**, 「确认通过」当场可按。2026-08-27 用户当面定的那道人工验收闸就此凭空没了, 而屏上还看不出来 —— 清单区是空的,不是红的。 兜在装配层这一处(不是让每个调用方自己记得):模型路径将来漏传也一并保住。 判断抽成纯函数 `decisionCardFace` / `needsStepLookup`(照本仓 engines.ts 里 createOnlyViolation 那批纯函数的成例),9 测 —— 其中三条把挑出来的清单接到 真回传门上,证明闸真的因此有牙,不只是「函数回了个数组」。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* **决策卡的面孔与人工验收清单,兜底在装配层这一处**(2026-08-29 终审 C2)。
|
||||
*
|
||||
* 病:配方(`auto-runner`)发审核卡时只递了 `variant`,没递 `checklist` ——
|
||||
* 而装配层从前只给 `variant` 兜底。于是那张卡穿着验收工单的衣服、`allowCustom` 也对,
|
||||
* **一条待确认都没有**:回传门的 `checklistMissing` 在 checklist 缺席时恒过,
|
||||
* 「确认通过」当场可按,2026-08-27 用户当面定的那道人工验收闸凭空没了。
|
||||
* 屏上还看不出来 —— 清单区是空的,不是红的。
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { checkChatCardReply } from '@modules/human-tool';
|
||||
import { cardIdSchema } from '@contracts';
|
||||
import type { ChatCard, ChatCardReply } from '@contracts';
|
||||
import { decisionCardFace, needsStepLookup } from '../wiring/engines.ts';
|
||||
|
||||
const RELEASE_STEP = {
|
||||
cardVariant: 'review' as const,
|
||||
acceptanceChecklist: ['逐条核对差异明细', '确认推荐等级与依据一致'],
|
||||
};
|
||||
|
||||
describe('决策卡的面孔与清单(装配层兜底)', () => {
|
||||
it('调用方递了清单就用它的 —— 引擎按剧本定好递过来,兜底不抢', () => {
|
||||
const face = decisionCardFace({ variant: 'review', checklist: ['我说了算'] }, RELEASE_STEP);
|
||||
expect(face.checklist).toEqual(['我说了算']);
|
||||
expect(face.reviewFace).toBe(true);
|
||||
});
|
||||
|
||||
it('**配方那一路只递了 variant:清单退回剧本声明的**(C2 的正主)', () => {
|
||||
const face = decisionCardFace({ variant: 'review' }, RELEASE_STEP);
|
||||
expect(face.reviewFace).toBe(true);
|
||||
expect(face.checklist).toEqual(RELEASE_STEP.acceptanceChecklist);
|
||||
});
|
||||
|
||||
it('面孔也照旧退回剧本(2026-08-27 那条不许回退)', () => {
|
||||
const face = decisionCardFace({}, RELEASE_STEP);
|
||||
expect(face.reviewFace).toBe(true);
|
||||
});
|
||||
|
||||
it('剧本没声明清单:就是没有 —— 不发明一条', () => {
|
||||
const face = decisionCardFace({ variant: 'review' }, { cardVariant: 'review' });
|
||||
expect(face.checklist).toBeUndefined();
|
||||
});
|
||||
|
||||
it('空清单当没给:退回剧本(空数组在回传门那儿等于没有闸)', () => {
|
||||
const face = decisionCardFace({ variant: 'review', checklist: [] }, RELEASE_STEP);
|
||||
expect(face.checklist).toEqual(RELEASE_STEP.acceptanceChecklist);
|
||||
});
|
||||
|
||||
it('查不查剧本:没 itemId 查不了;两样都递全了不必查', () => {
|
||||
expect(needsStepLookup({})).toBe(false);
|
||||
expect(needsStepLookup({ itemId: 'T_release' })).toBe(true);
|
||||
expect(needsStepLookup({ itemId: 'T_release', variant: 'review' })).toBe(true);
|
||||
expect(needsStepLookup({ itemId: 'T_release', variant: 'review', checklist: ['a'] })).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* **闸真的因此有牙** —— 上面挑出来的清单接到回传门上,「确认通过」才拦得住。
|
||||
* 没有这一条,`decisionCardFace` 的断言只证明「函数回了个数组」。
|
||||
*/
|
||||
describe('清单缺席 = 闸恒过(这就是 C2 的后果)', () => {
|
||||
const CARD_ID = cardIdSchema.parse('11111111-1111-4111-8111-111111111111');
|
||||
const card = (checklist?: ReadonlyArray<string>): ChatCard => ({
|
||||
k: 'decision',
|
||||
id: CARD_ID,
|
||||
at: '2026-08-29T00:00:00.000Z',
|
||||
ask: '差异评估结论审核',
|
||||
basis: [],
|
||||
variant: 'review',
|
||||
allowCustom: false,
|
||||
options: [{ value: 'approve', label: '确认通过' }],
|
||||
...(checklist === undefined ? {} : { checklist: [...checklist] }),
|
||||
});
|
||||
const reply: ChatCardReply = { k: 'decision', cardId: CARD_ID, value: 'approve' };
|
||||
|
||||
it('没有清单:一条都没点也通得过 —— 这正是配方发的那张卡从前的样子', () => {
|
||||
expect(checkChatCardReply(card(), reply)).toEqual([]);
|
||||
});
|
||||
|
||||
it('清单兜底进来之后:一条没点就拦', () => {
|
||||
const face = decisionCardFace({ variant: 'review' }, RELEASE_STEP);
|
||||
const violations = checkChatCardReply(card(face.checklist), reply);
|
||||
expect(violations.map((v) => v.code)).toEqual(['checklist-incomplete']);
|
||||
});
|
||||
|
||||
it('清单点全了才放行', () => {
|
||||
const face = decisionCardFace({ variant: 'review' }, RELEASE_STEP);
|
||||
const violations = checkChatCardReply(card(face.checklist), { ...reply, checked: [...RELEASE_STEP.acceptanceChecklist] });
|
||||
expect(violations).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user