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:
@@ -260,6 +260,22 @@ describe('说明书与剧本的骨架', () => {
|
||||
for (const step of ASSESSMENT_STEPS) expect(step.guide, step.item).not.toMatch(disableCallPattern);
|
||||
});
|
||||
|
||||
/*
|
||||
* 2026-08-28 用户实测:模型把台账里原 PN 那一行的规格书(标题/版本/链接逐字)抄到了候选料号头上。
|
||||
* 那三格只可能从人那儿来 —— 声明归人填,模型 write 当场被拒。
|
||||
*/
|
||||
it('P-10 的资料记录归人填:humanWrites 声明了 parts.document,writes 里不再有它,剧本也写明「你写不进去」', () => {
|
||||
const p10 = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p10_document');
|
||||
expect(p10?.humanWrites).toEqual(['parts.document']);
|
||||
expect(p10?.writes).not.toContain('parts.document');
|
||||
expect(p10?.guide).toMatch(/由人在件上填,你写不进去/);
|
||||
expect(p10?.guide).toMatch(/别拿原 PN 或别的料号的规格书顶上/);
|
||||
// 骨架仍由上一步铺(P-08 那步要写 parts.document)
|
||||
const p08 = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p08_master');
|
||||
expect(p08?.writes).toContain('parts.document');
|
||||
expect(p08?.humanWrites ?? []).toEqual([]);
|
||||
});
|
||||
|
||||
it('S0d 拆成两项各自绑自己的 view(P-10 那份件不会永远推不出来)', () => {
|
||||
const p08 = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p08_master');
|
||||
const p10 = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p10_document');
|
||||
|
||||
@@ -436,15 +436,26 @@ current_stage = "assessing"。application_scenario / bom_position **这一步不
|
||||
/* 这一步派的是数据工单 —— 由剧本声明,模型抄不抄 variant 都一样(2026-08-27 实测它丢了) */
|
||||
cardVariant: 'data' as const,
|
||||
intent: '料号不在库:派 P-10 数据工单让人从零建原厂技术资料',
|
||||
writes: ['parts.document', `${D}.pn_check`],
|
||||
/* parts.document 归人填(见下面 humanWrites)—— 这一步只改核对表的旗 */
|
||||
writes: [`${D}.pn_check`],
|
||||
reads: [`${D}.pn_check`, 'parts.profile', 'parts.document'],
|
||||
view: 'p10-document-create',
|
||||
tools: [],
|
||||
/*
|
||||
* **资料记录归人填**(2026-08-28 用户实测):标题 / 版本 / 链接只可能从人那儿来 ——
|
||||
* 模型手边唯一「像样」的资料就是台账里原 PN 那一行,实测它逐字抄了过来安在候选头上,
|
||||
* 而这份假证据会直接喂进差异比对当「硬约束有证据支持」。骨架(键 + 类型 + 状态)
|
||||
* 上一步 S0d_p08_master 已经铺好,这一步不需要写它;写就当场拒,drop 仍可撤。
|
||||
*/
|
||||
humanWrites: ['parts.document'],
|
||||
/* 人工激活:发卡就算启动。doneWhen 同 P-08 的道理:人答过卡之后,建过的每颗料都有一行资料记录且必填不缺 → 引擎自己收 */
|
||||
doneWhen: `parts_document_rows > 0 and parts_document_rows >= parts_profile_rows and parts_document_missing = 0`,
|
||||
guide: `这一项只在 \`${D}.pn_check\` 里有 absent 行时走(要走到这一步,S0d_p08_master 已经收完 —— 引擎按入库事实自己收);都在库时入场前提本就不成立,
|
||||
这一项与 S0d_p08_master 都不会进你的「当下可为」。
|
||||
|
||||
**资料记录那三格(标题 / 版本 / 文件链接)由人在件上填,你写不进去**(写了当场拒)——
|
||||
别拿原 PN 或别的料号的规格书顶上:那是另一颗料的资料,后面会被当成这颗料的证据用。骨架上一步已经铺好了。
|
||||
|
||||
【发 P-10 数据工单】「建」的名单就是 \`${D}.pn_check\` 里仍是 exists_status = "absent" 的那几行(候选总数 ≥2 时被放弃的那些,
|
||||
S0d_p08_master 那一步已经把它们改成 "abandoned",不在这份名单里)。同一拍照说明书④发那张 P-10 数据工单(title 里列全料号,只发一次),
|
||||
发完这一拍结束(挂起)。
|
||||
|
||||
@@ -90,7 +90,7 @@ export const manifest: ActivityManifest = {
|
||||
identity: {
|
||||
activityId: ACTIVITY_ID,
|
||||
name: 'ACT-WORK-05 · 差异评估',
|
||||
packageVersion: '1.6.4',
|
||||
packageVersion: '1.6.5',
|
||||
category: '需求分析与方案匹配',
|
||||
sdkVersion: '0.1.0-m5',
|
||||
checksum: 'dev-checksum-part-substitution-assessment',
|
||||
|
||||
@@ -2237,6 +2237,7 @@ export declare const installActivityRoute: {
|
||||
review: "review";
|
||||
}>>;
|
||||
acceptanceChecklist: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
||||
humanWrites: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
||||
}, z.core.$strip>>>;
|
||||
phases: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
||||
name: z.ZodString;
|
||||
|
||||
@@ -207,6 +207,7 @@ export declare const planStepSchema: z.ZodObject<{
|
||||
review: "review";
|
||||
}>>;
|
||||
acceptanceChecklist: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
||||
humanWrites: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
||||
}, z.core.$strip>;
|
||||
export type PlanStep = z.infer<typeof planStepSchema>;
|
||||
/**
|
||||
@@ -272,6 +273,7 @@ export declare const loopModelSchema: z.ZodObject<{
|
||||
review: "review";
|
||||
}>>;
|
||||
acceptanceChecklist: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
||||
humanWrites: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
||||
}, z.core.$strip>>>;
|
||||
phases: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
||||
name: z.ZodString;
|
||||
@@ -331,6 +333,7 @@ export declare const businessModelSchema: z.ZodObject<{
|
||||
review: "review";
|
||||
}>>;
|
||||
acceptanceChecklist: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
||||
humanWrites: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
||||
}, z.core.$strip>>>;
|
||||
phases: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
||||
name: z.ZodString;
|
||||
@@ -488,6 +491,7 @@ export declare const manifestAssetsSchema: z.ZodObject<{
|
||||
review: "review";
|
||||
}>>;
|
||||
acceptanceChecklist: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
||||
humanWrites: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
||||
}, z.core.$strip>>>;
|
||||
phases: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
||||
name: z.ZodString;
|
||||
@@ -793,6 +797,7 @@ export declare const activityManifestSchema: z.ZodObject<{
|
||||
review: "review";
|
||||
}>>;
|
||||
acceptanceChecklist: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
||||
humanWrites: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
||||
}, z.core.$strip>>>;
|
||||
phases: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
||||
name: z.ZodString;
|
||||
|
||||
@@ -67,6 +67,13 @@
|
||||
(剧本两处写死「先 finish 再发下一张」,模型照样忘,两步同时 Active、件停在上一步)。
|
||||
拍首扫一遍,**拍尾再扫一遍**(人答卡那一拍里模型写完东西 doneWhen 才真;这一拍刚发卡的项排除;
|
||||
提交放行那一拍不扫,免得把在途验收信封擦掉)。没声明 doneWhen 的人工激活项一个字不变。
|
||||
- **人填的表模型不许写**(2026-08-28):剧本 `PlanStep.humanWrites` 声明的台账地址,`write` 当场拒并指路
|
||||
「去发那张数据工单,值由人填」;`drop` 不拦(写错了得撤得掉)。判法认行动点名的项,点不出退回当下在办的那一步。
|
||||
起因:P-10 资料记录那三格只可能从人那儿来,而模型把台账里**原 PN 那一行**的规格书逐字抄到了候选料号头上 ——
|
||||
行找对了、料找错了,这份假证据会喂进差异比对当「硬约束有证据支持」。**只声明「模型永远没有来源」的表**。
|
||||
- **还欠一张卡的那一步,提示词不摆 `doneWhen`**(同日):判据同时也是**摆在模型眼前的一个目标**,后面还跟着
|
||||
「成立了就 finish」—— 而人工激活的项在卡发出去之前根本不许 finish。同一份提示词里两句话打架,实测它选了判据那句、
|
||||
去凑数据,然后 finish 连撞五拍墙熔断。卡发出去、人答过之后判据照常摆。
|
||||
- **画面跟着这一拍发出去的卡走**(同日):human / entry / decide 把「卡是哪一步的」交回拍内,
|
||||
那一步声明了画面就推它,压过「第一步没干完的」—— 卡是发给人的那件事,件必须是那件事的件。
|
||||
- **里程碑前提达成即达成**(2026-07-30,`achieveMilestones`):CMMN 的里程碑没有执行体,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -656,6 +656,11 @@ export function createTurnRunner(deps: TurnRunnerDeps): LoopEngineService {
|
||||
* **这一步派的卡穿哪副面孔**(剧本 `cardVariant`,2026-08-27):按行动里的项号查,项号没给 / 挂不上就按
|
||||
* 当下在办的那一步 —— 从前只按项号查,模型漏了 itemId,审核卡就退成「需要你定」。
|
||||
*/
|
||||
/**
|
||||
* 这张台账「归人填」吗 —— 归人填就返回拒绝理由(剧本 `humanWrites`,见 contracts)。
|
||||
* 只拦 `write`:`drop` 得留着,不然模型写错了撤不掉。
|
||||
*/
|
||||
humanWritesOf?: (itemId: string | undefined, address: string) => string | undefined,
|
||||
faceOf?: (itemId: string | undefined) => { variant?: 'review'; checklist?: ReadonlyArray<string>; release?: true } | undefined,
|
||||
): Promise<{
|
||||
runtime: CaseRuntime;
|
||||
@@ -920,6 +925,14 @@ export function createTurnRunner(deps: TurnRunnerDeps): LoopEngineService {
|
||||
const domain = action.address.slice(0, at);
|
||||
const object = action.address.slice(at + 1);
|
||||
|
||||
/*
|
||||
* **人填的表,模型不许写**(2026-08-28,用户送来的实测单):数据工单要人在件上补的那几张表,
|
||||
* 值只可能从人那儿来。实测模型把台账里原 PN 那一行的规格书逐字抄到了候选料号头上 ——
|
||||
* 它不是凭空编,是「手边有一行看着对的就拿来用」。墙立在这儿,路在拒回里(去发那张卡)。
|
||||
*/
|
||||
const humanOnly = humanWritesOf?.(action.itemId, action.address);
|
||||
if (humanOnly !== undefined) throw new Error(humanOnly);
|
||||
|
||||
// 挂项(尽力):阶段不挂(容器)、挂不上不拦 —— 理由随留痕带出,模型下一拍自己修记账
|
||||
const { parsed: model } = await loaded(taskId);
|
||||
const target = model.items.find((i) => i.id === action.itemId);
|
||||
@@ -1929,11 +1942,19 @@ export function createTurnRunner(deps: TurnRunnerDeps): LoopEngineService {
|
||||
* worldTouched(见 lifecycle.ts)在引擎这一侧本来就是现成的布尔,直接摆成一句
|
||||
* 事实,模型只需要读,不需要从一堆轨迹文本里自己判断「这条回复是不是答的是这一项」。
|
||||
*/
|
||||
/**
|
||||
* **这一步还欠一张卡** —— 标了人工激活、而这一单里还没给它发过卡 / 立过监听。
|
||||
* 此刻它唯一能做的就是把那张卡发出去:别的话都不该说给它听(见下面 doneWhen 那段)。
|
||||
*/
|
||||
const owesCard =
|
||||
current !== undefined &&
|
||||
parsed.items.find((i) => i.id === current.item)?.manualActivation === true &&
|
||||
stateRuntime.items.find((i) => i.id === current.item)?.worldTouched !== true;
|
||||
const currentTouch = ((): string => {
|
||||
if (current === undefined) return '';
|
||||
const declared = parsed.items.find((i) => i.id === current.item);
|
||||
if (declared?.manualActivation !== true) return '';
|
||||
const touched = stateRuntime.items.find((i) => i.id === current.item)?.worldTouched === true;
|
||||
const touched = !owesCard;
|
||||
/*
|
||||
* 终局放行那一步的事实行**只指向 submit**(2026-08-27 W05 实测):下面那句「唯一合法的动作是先发
|
||||
* 那一张卡」本是给人工激活的执行 / 数据 / 决策步写的,放行步读到它就去发了一张执行工单。
|
||||
@@ -1973,7 +1994,17 @@ export function createTurnRunner(deps: TurnRunnerDeps): LoopEngineService {
|
||||
* 差的那个数摆出来,这一步就从死胡同变成一句话能办的事:
|
||||
* 「missing_key_total 现在是 1,要它变成 0」。
|
||||
*/
|
||||
current.doneWhen === undefined
|
||||
/*
|
||||
* **还欠一张卡的那一步,不摆干完的判据**(2026-08-28,用户送来的实测单)。
|
||||
*
|
||||
* 判据本意是「引擎自己核,省一拍」,可它同时是**摆在模型眼前的一个目标**,
|
||||
* 后面还跟着一句「成立了就 finish」—— 而人工激活的项在卡发出去之前根本不许 finish
|
||||
* (上面 currentTouch 那句写着「唯一合法的动作是先发那一张卡」)。同一份提示词里两句话
|
||||
* 直接打架:实测它选了判据那句,把台账里原 PN 的资料抄到候选头上去凑「必填不缺」,
|
||||
* 然后 finish 连撞五拍墙、熔断。**一个闲着的模型 + 一个它够得着的判据 = 它一定会去凑。**
|
||||
* 卡发出去、人答过之后判据照常摆(那时它本来就该收步)。
|
||||
*/
|
||||
current.doneWhen === undefined || owesCard
|
||||
? ''
|
||||
: `干完的判据(引擎会自己核):${current.doneWhen}` +
|
||||
`${readingsOf(current.doneWhen, scope)} —— 成立了就 finish ${current.item},` +
|
||||
@@ -2201,6 +2232,21 @@ export function createTurnRunner(deps: TurnRunnerDeps): LoopEngineService {
|
||||
}
|
||||
},
|
||||
index < planned.length - 1,
|
||||
/*
|
||||
* 归人填的台账:**认行动点名的那一项**,点不出(或那一项没剧本)才退回当下在办的那一步。
|
||||
* 与 faceOf 同一套判法。压拍时骨架写在上一步名下、填值写在这一步名下,正好各归各的。
|
||||
*/
|
||||
(itemId, address) => {
|
||||
const named = itemId === undefined ? undefined : stepOf(itemId);
|
||||
const step = named ?? current;
|
||||
if (step === undefined || !(step.humanWrites ?? []).includes(address)) return undefined;
|
||||
return (
|
||||
`${address} 这张表归人填 —— 它是「${step.item}」这一步的数据工单要人在件上补的表(剧本声明 humanWrites),` +
|
||||
'值只能从人那儿来,你没有来源:别拿别的料号 / 别的行的值顶上(那是假数据,后面会被当证据用)。' +
|
||||
'这一步该做的是把那张数据工单发出去(human + execution + 数据工单面孔),人填完交回,判据自己就成立了。' +
|
||||
'已经写进去的错值用 drop 撤掉 —— drop 不拦。'
|
||||
);
|
||||
},
|
||||
// 面孔与人工验收清单按剧本定:行动点名的项声明了就用它的,没声明就按当下在办的那一步
|
||||
(itemId) => {
|
||||
const named = itemId === undefined ? undefined : stepOf(itemId);
|
||||
|
||||
@@ -239,6 +239,7 @@ P-11 一条(`LM2596S-3.3 → LM2596S-ADJ | alternate | fae_confirmed`);P-01 回
|
||||
| AA | **数据工单确认了,数据没入库**:卡上写着「确认后这批主数据即入库」,人建好的 P-08/P-10 记录要等整条评估放行才进正式表;试跑单(不落主数据)永远不进(2026-08-27 用户指出) | 草稿层通往正式表只有整单放行一道口子(`releaseWrite(taskId)`),回传路由只记办结 + 抛人事件;而引擎的 `mine:` / `_rows` / 哨兵全靠草稿层认「本单写下的行」,清掉就断 | `releaseWrite(taskId, {only, cause})` 范围放行:只并这张卡 `gateLedgers` 那几张表,并过的行**留在草稿层打 `landed` 记号**(读面不变,整单放行不再写);回传路由在保存与回传落定后调它,痕点名几张表各几行;`createOnly` / `drop` 对已落地行改口(别 drop,撤了读面少一行);活动 1.4.8 剧本改口「已入正式表」 | **平台**(结构)+ 活动 |
|
||||
| AC | **两颗输入电压范围一模一样却判 MISMATCH、整颗不推荐**(2026-08-27 用户送来 0bddd862):客户写「输入电压在 45V 以内」,模型译成 requirement `>= 45`,原 PN 与候选上限都是 40V | `judgePair` 客户约束一票否决:候选过不了客户线就 mismatch,不看原 PN 自己过不过得了 —— 需求边界与在用料的差距被记在候选头上 | 工具 1.1.0:候选与原 PN 相等 / 盖住且原 PN 也不满足客户线 → 仍按比对结果判(MATCH,风险中),备注点名「需求边界,请回头核对需求」;候选更窄且过不了线仍 MISMATCH | **平台**(工具) |
|
||||
| AD | **比对没有「认真比每一条」的机制**(2026-08-27 用户:「这个做差异评估比对的 harness 肯定有问题」):模型交代里念出「输入上限 32V 低于客户 40V」—— 这两个数不是这单的,是 S3 剧本示例的原话;工具入参只存哈希,喂了哪 9 对值事后查不到;写回台账时还把工具的 `equal` 改成了 `less` | 比对入参由模型从台账抄给工具(抄错 / 抄示例都拦不住),出参再由模型抄回台账,两头都没有基座核;示例引了具体值 | 工具 1.2.0:只报原 PN + 候选料号 + 客户约束,参数 / 封装 / 生命周期 / 证据引用由工具从 parts.* 现读(taskId 经运行时交给处理器,读任务视图);出参带原值,写盘照抄;≤800 字的工具入参整份留痕;剧本示例只留占位形 | **平台**(工具运行时 + 工具)+ 活动 |
|
||||
| AE | **AI 把原 PN 的规格书填到了候选料号的资料记录上**(2026-08-28 柴浩实测,单 6ddcecf8):P-10 那一行的标题 / 版本 / 链接与台账里 LM2596S-5.0 那一行逐字相同;随后 `finish S0d_p10_document` 连撞五拍墙熔断 | ①它把 P-08 的回卡认成了 P-10 的(卡从没发过);②我 08-27 给这一步加的 doneWhen 把「资料必填不缺」摆成目标、还配一句「成立了就 finish」,与「唯一合法的动作是先发那一张卡」在同一份提示词里打架;③它顺手把 `exists_status` 改成 created,发卡名单自己清空 —— 一个合法动作都不剩 | 剧本 `PlanStep.humanWrites` 声明「这几张表归人填」,模型 write 当场拒、drop 不拦;还欠一张卡的那一步提示词不摆 doneWhen;W05 的 S0d_p10_document 声明 `humanWrites: ['parts.document']` 并从 writes 里去掉 | **平台**(引擎 + 契约)+ 活动 |
|
||||
|
||||
**可推广的三条**(已记 `docs/debt.md`):
|
||||
|
||||
|
||||
@@ -295,6 +295,14 @@ start 已收项无事发生同族)。剧本里也别再教 start —— 教它
|
||||
(与模型同拍撞车时:引擎收过了,模型这拍的 finish 当已完成,不抛非法迁移);
|
||||
**拍尾再求一次**(人答卡那一拍里模型写完东西 doneWhen 才真,不等下一拍;这一拍刚发卡的项不算)。
|
||||
|
||||
**人填的表要声明 `humanWrites`**(2026-08-28 立):数据工单要人在件上补、而模型**永远没有来源**的那几张表
|
||||
(P-10 的资料标题 / 版本 / 文件链接),写进剧本的 `humanWrites` —— 模型 write 当场拒、drop 仍可撤。
|
||||
不声明的后果实测过:模型被 doneWhen 逼着凑「必填不缺」,把台账里**原 PN 那一行**的规格书逐字抄到候选料号头上,
|
||||
行找对了料找错了,而这份假证据会喂进差异比对当「硬约束有证据支持」。
|
||||
**只声明模型没有来源的表** —— 有附件时该预填的(P-08 的参数值从 datasheet 抽)是它的活,别拦。
|
||||
配套:**还欠一张卡的那一步,提示词不摆 doneWhen** —— 判据同时是摆在模型眼前的目标,而它此刻只许发卡。
|
||||
**一个闲着的模型 + 一个它够得着的判据 = 它一定会去凑那个判据。**
|
||||
|
||||
**人工激活的项也可以给 doneWhen,引擎只在人答过卡之后才求它**(2026-08-27 晚立):
|
||||
门槛是 `worldTouched`(卡真发过)且没有未答的卡 —— 卡没发过时 doneWhen 再真也不收,
|
||||
那才是替人签字。答过之后,「办完了没有」若是基座数得出来的事实,就写成 doneWhen 交给引擎:
|
||||
|
||||
@@ -175,6 +175,22 @@ export const planStepSchema = z.object({
|
||||
* 卡上一条一枚「达标 / 不达标」开关,全部达标才能「确认通过」。
|
||||
*/
|
||||
acceptanceChecklist: z.array(z.string().min(1)).optional(),
|
||||
/**
|
||||
* **这一步的这几张台账归人填,模型不许写**(2026-08-28 用户实测逼出来的)。
|
||||
*
|
||||
* 数据工单要人在件上补的那几张表 —— 值只可能从人那儿来,模型没有任何来源。
|
||||
* 实测:P-10 原厂技术资料那一步,模型以为卡已经答过了,又被 `doneWhen`(资料必填不缺)
|
||||
* 摆成目标,就把**台账里原 PN 那一行的规格书**(标题 / 版本 / 链接逐字)抄到了候选料号头上 ——
|
||||
* 行找对了、料找错了,而这份假证据会直接喂进差异比对当「硬约束有证据支持」。
|
||||
*
|
||||
* 一个闲着的模型 + 一个它够得着的判据 = 它一定会去凑那个判据。所以这里立墙:
|
||||
* 声明了的地址,`write` 当场拒并指路「发那张卡去,值由人填」。`drop` 不拦(写错了得撤得掉)。
|
||||
*
|
||||
* **只声明「模型永远没有来源」的表**。有附件时该预填的(P-08 的参数值从 datasheet 抽)别声明 ——
|
||||
* 那是它的活。判法认点名的项,点不出退回当下在办的那一步。
|
||||
*/
|
||||
/* 可选:老活动一个字都不用改(`writes`/`reads` 用 default([]) 是历史,这里不跟 —— 加默认值会逼六个包全改) */
|
||||
humanWrites: z.array(z.string()).optional(),
|
||||
});
|
||||
export type PlanStep = z.infer<typeof planStepSchema>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user