feat(w05): S3 审核卡与 S4 落账放行 —— 摘要只读,改的是四态

关系与验证记录的投影抽成模块级常量,S2 与 S3 共用同一份:
人改了某一条四态,等级 / 风险 / 摘要 / 验证要求跟着重算。
各写一份早晚分叉,而分叉那一刻屏上会同时摆着两个互相矛盾的等级。
次序是投影在前、卡在后:人交回后卡这一笔只跳过自己,投影仍跑 —— 重算才发生在该发生的那一刻。

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 16:47:08 +08:00
co-authored by Claude Opus 5
parent cd88d0e026
commit 725f1ecb0c
2 changed files with 189 additions and 87 deletions
@@ -297,3 +297,44 @@ describe('S2 配方 —— AI 只出场一次', () => {
}
});
});
describe('S3 / S4 配方', () => {
const s3 = stepOf('S3_review')?.auto?.do ?? [];
const s4 = stepOf('T_release')?.auto?.do ?? [];
const s2 = stepOf('S2_assess')?.auto?.do ?? [];
it('S3 四笔:两笔重算在前、卡在后 —— 人答完那一刻重算才发生', () => {
expect(s3.map((a) => a.kind)).toEqual(['project', 'project', 'card', 'say']);
expect(s3[0]?.kind === 'project' && s3[0].into).toBe('substitution.relation');
expect(s3[1]?.kind === 'project' && s3[1].into).toBe('validation.record');
});
it('**S2 与 S3 用的是同一份投影** —— 各写一份早晚分叉', () => {
expect(s3[0]).toBe(s2[2]);
expect(s3[1]).toBe(s2[3]);
});
it('S3 的卡:审核面孔、单一选项、挂本步的件', () => {
const card = s3[2];
expect(card?.kind === 'card' && card.as).toBe('decide');
expect(card?.kind === 'card' && card.variant).toBe('review');
expect(card?.kind === 'card' ? card.options ?? [] : []).toHaveLength(1);
expect((card?.kind === 'card' ? card.options ?? [] : [])[0]?.value).toBe('approve');
expect((card?.kind === 'card' ? card.basis ?? [] : []).length).toBeGreaterThan(0);
});
it('S4 两笔:写结论一句话 → 提交放行', () => {
expect(s4.map((a) => a.kind)).toEqual(['project', 'submit']);
expect(s4[0]?.kind === 'project' && s4[0].into).toBe('sub_eval.case');
expect(s4[1]?.kind === 'submit' && typeof s4[1].rationale !== 'undefined').toBe(true);
});
it('`submit` 所在的步声明了人工验收清单 —— 系统提交不比模型提交宽松', () => {
expect((stepOf('T_release')?.acceptanceChecklist ?? []).length).toBeGreaterThanOrEqual(4);
});
it('两步都不给 doneWhen —— 收步由人答卡决定', () => {
expect(stepOf('S3_review')?.doneWhen).toBeUndefined();
expect(stepOf('T_release')?.doneWhen).toBeUndefined();
});
});