Archived
feat(contracts): PlanStep.humanWrites —— 人填的表模型不许写;还欠一张卡的那一步不摆 doneWhen
柴浩实测单 6ddcecf8(连撞五拍熔断):P-10 资料记录的标题 / 版本 / 链接与台账里 `LM2596S-5.0` 那一行 **逐字相同** —— 不是幻觉,是「手边有一行看着对的就拿来用」(那一步 reads 摊开了 `parts.document`, 视野里唯一一份真资料就是原 PN 的)。行找对了、料找错了,而这份假证据会直接喂进差异比对当「硬约束有证据支持」。 三层合谋:①把 P-08 的回卡认成 P-10 的(P-10 的卡从没发过);②08-27 给这一步加的 doneWhen 把 「资料必填不缺」摆成目标、后面还跟「成立了就 finish」,**与同一份提示词里「唯一合法的动作是先发那一张卡」 直接打架**;③它顺手把 `exists_status` 改成 created,按剧本发卡名单自己空了 —— 一个合法动作都不剩。 止血两刀: ①**`PlanStep.humanWrites`**(契约,可选字段、老活动一个字不用改):声明的台账 `write` 当场拒并指路 「去发那张卡,值由人填」;`drop` 不拦(写错了得撤得掉)。判法认点名的项,点不出退回当下在办的那一步 (压拍时骨架写在上一步名下正好放行)。**只声明「模型永远没有来源」的表** —— 有附件时该预填的 (P-08 的参数值从 datasheet 抽)别声明,那是它的活。 ②**还欠一张卡的那一步不摆 doneWhen** —— 判据同时是摆在模型眼前的目标,而它此刻只许发卡。 W05@1.6.5 声明 `humanWrites: ['parts.document']` 并从 writes 去掉。4 测(墙 / 放行 / drop / 声明)。 **一个闲着的模型 + 一个它够得着的判据 = 它一定会去凑那个判据。** 契约变更四步:depcruise 反查下游绿、loop-engine CONTRACT.md 同步、api-report 随本提交入库、 可选字段非破坏性。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0167qWjeyBwvfdGovd7FWbCu
This commit is contained in:
@@ -1430,6 +1430,48 @@ describe('画面不再静默丢帧', () => {
|
||||
expect(h.pushed).toEqual(['step2-view']);
|
||||
});
|
||||
|
||||
/*
|
||||
* **人填的表模型不许写**(2026-08-28,用户送来的实测单):数据工单要人在件上补的表,值只可能从人那儿来。
|
||||
* 实测模型把台账里原 PN 那一行的规格书逐字抄到候选料号头上 —— 行找对了、料找错了,而这份假证据
|
||||
* 会直接喂进差异比对当「硬约束有证据支持」。
|
||||
*/
|
||||
it('剧本声明 humanWrites 的台账:write 当场拒,拒回指向「去发那张卡」', async () => {
|
||||
const h = harness({
|
||||
parsed: OPEN_MODEL,
|
||||
runtime: OPEN_RUNTIME,
|
||||
steps: [{ item: 'i1', intent: '派数据工单', guide: '做法', writes: [], reads: [], tools: [], view: 'x', humanWrites: ['parts.document'] }],
|
||||
action: { kind: 'write', itemId: 'i1', address: 'parts.document', rows: [{ key: { internal_pn: 'X' }, patch: { document_ref: '抄来的' } }] },
|
||||
});
|
||||
await h.runner.runTurn(TASK);
|
||||
expect(h.written).toHaveLength(0);
|
||||
const rejected = h.appended.find((e) => e.title.startsWith('行动被拒'));
|
||||
expect(rejected?.title).toMatch(/归人填/);
|
||||
expect(rejected?.title).toMatch(/数据工单发出去/);
|
||||
});
|
||||
|
||||
it('没声明的表照写;drop 不拦(写错了得撤得掉)', async () => {
|
||||
const steps = [{ item: 'i1', intent: '派数据工单', guide: '做法', writes: [], reads: [], tools: [], view: 'x', humanWrites: ['parts.document'] }];
|
||||
const ok = harness({
|
||||
parsed: OPEN_MODEL,
|
||||
runtime: OPEN_RUNTIME,
|
||||
steps,
|
||||
action: { kind: 'write', itemId: 'i1', address: 'parts.profile', rows: [{ key: { internal_pn: 'X' }, patch: { brand_name: 'TI' } }] },
|
||||
});
|
||||
await ok.runner.runTurn(TASK);
|
||||
expect(ok.written).toHaveLength(1);
|
||||
expect(ok.appended.some((e) => e.title.startsWith('行动被拒'))).toBe(false);
|
||||
|
||||
const dropped = harness({
|
||||
parsed: OPEN_MODEL,
|
||||
runtime: OPEN_RUNTIME,
|
||||
steps,
|
||||
action: { kind: 'drop', itemId: 'i1', address: 'parts.document', key: { internal_pn: 'X' } },
|
||||
});
|
||||
await dropped.runner.runTurn(TASK);
|
||||
// drop 可能因「本来就没有这一行」被拒,但**不该**是被这道墙拒的
|
||||
expect(dropped.appended.some((e) => e.title.startsWith('行动被拒') && e.title.includes('归人填'))).toBe(false);
|
||||
});
|
||||
|
||||
it('活步都收完 → 一帧都不推,最后摆上去的那一张(放行页)留在档案里', async () => {
|
||||
const h = harness({
|
||||
parsed: OPEN_MODEL,
|
||||
|
||||
Reference in New Issue
Block a user