Archived
feat(w05): 数据齐了也叫人一次 —— S1 改派只读的验收工单,件同一份、编辑钮收起
用户:「就算数据是足的也进入这个数据工单,就派发一个验收工单,界面和数据要和现在的 第一个数据工单一致,只不过把芯片卡片上的编辑按钮去掉,只展示不能编辑,然后把标题旁边的 数据工单改成验收工单」。 病根在引擎拍首那道 doneWhen 扫描:输入本来就齐的单,S1 的 doneWhen 第一拍就为真, 配方一笔都还没跑,这一步当场被收掉 —— 卡永远发不出去,人一眼没看过就进了 S2。 治法:CMMN 给 S1 标 manualActivation,那道扫描多一个门槛(worldTouched),次序变成 「配方发卡 → 人答 → 下一拍 doneWhen 为真才收」;doneWhen 仍是真门,只是不抢在人前面。 平台:执行工单第三副面孔 variant:'accept'(件只读、人只确认「确认无误」)—— 与 mode:'acceptance'(submit 抬起来的收口验收,带打回重做)分清;三副面孔的判断 抽成纯函数 order-face.ts(有测),chat-bubbles 只摆。 活动:S1 两笔互斥的 card(carded 幂等保证一步只发一张)+ 案卷粘着的旗 order_kind; 一张件两副面孔 —— 题头/标题/页脚三处名字与卡角编辑钮按 v-data / v-accept 由样式表挑 (藏的是容器 .fabs,逐枚去盖打不完)。 测:引擎测钉死那一对(标了人工激活 → 卡发得出去、步不收;没标 → 步当场被收、卡发不出); auto-runner variant 透传;order-face 4 条;包内 2 条(两笔卡 + 粘旗)、view 1 条; 两处变异验证。e2e w05 5/5 released、被拒 0、模型调用恰 1 次 —— 都在库那例摆的正是验收工单。 2558 测、四门禁绿;2.15.0 装库核过。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
@@ -39,5 +39,6 @@
|
||||
- **`pendingCards(taskId)`**(2026-08-27):这一单正等人交回的卡 = 没办结的工单 + 没作答、没被顶替的对话卡
|
||||
(`CardStore.listOpenChatCards` 按 `superseded_by is null` 取)。引擎每拍开头问它:有卡没答就不问模型
|
||||
- **数据工单是执行工单的 `variant:'data'`**,同样不构成新的 mode:发卡 / 闸 / 回传全走执行工单那条路,只是对话区换一副面孔(2026-08-27)。
|
||||
- **验收工单是执行工单的 `variant:'accept'`**(2026-08-31):件只读、人只核对确认(「确认无误」),同样不构成新的 mode。**与 `mode:'acceptance'` 分清**:那个是 `submit` 抬起来的收口验收(签下去落账发口、带打回重做);这个只是「这一步的活干完了,请你过目」,回传即办结。三副面孔的判断在 `apps/web/.../desk-task/internal/order-face.ts`(纯函数,有测)。
|
||||
**确认即入库**(2026-08-27 下午)不在本模块:回传落定之后由 app 的回传路由调 `landDataOrder`
|
||||
(`apps/api/src/app/data-order-landing.ts`)把这张卡 `gateLedgers` / 绑的表并进正式表 —— 本模块只管回传本身
|
||||
|
||||
@@ -158,6 +158,7 @@
|
||||
|
||||
近变(2026-08-27):`cards.issue` 的入参多一项可选 `itemId`(这张卡挂在哪个计划项上)—— 组装层据此查剧本声明
|
||||
(`PlanStep.cardVariant`:这一步派的是数据工单),**不靠模型抄卡骨架**(实测它会把 `variant` 抄丢)。
|
||||
`auto` 配方的 `card` 动作也带 `variant`:执行工单认 `data`(数据工单)与 `accept`(验收工单 —— 件只读、人只确认,2026-08-31),决策卡认 `review`(审核卡);按值窄化,别串台。
|
||||
引擎自己发的卡(验收 / 兜底)没有项,不传。
|
||||
|
||||
近变(2026-08-27):`cards.decide` 同样多一项可选 `itemId`(`cardVariant: 'review'` = 审核卡摆成验收工单的样子,机制仍是决策卡);
|
||||
|
||||
@@ -425,6 +425,35 @@ describe('card 与 submit', () => {
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* **验收工单 = 执行工单的第三副面孔**(2026-08-31 用户定:「数据是足的也进入这个数据工单,
|
||||
* 就派发一个验收工单…只展示不能编辑」)。引擎这一侧与数据工单一模一样,只把 `variant`
|
||||
* 原样带下去 —— 面孔归对话区,件的只读归模版(它按同一面旗换脸)。
|
||||
*/
|
||||
it('execution + accept:验收工单,variant 原样带到卡上', async () => {
|
||||
const { runner, cards, ctx } = harness({ pn: 'XL2596S-5.0' });
|
||||
await runner.run(
|
||||
{
|
||||
do: [
|
||||
{
|
||||
kind: 'card',
|
||||
as: 'execution',
|
||||
variant: 'accept',
|
||||
title: '验收工单 · 差异评估的输入核对',
|
||||
guide: ['两张卡的主数据核一遍'],
|
||||
basis: [{ k: '候选料号', v: { in: 'pn' } }],
|
||||
gateLedgers: ['parts.profile', 'parts.parameter'],
|
||||
},
|
||||
],
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
expect(cards.issued[0]).toMatchObject({
|
||||
workOrder: 'execution',
|
||||
card: { mode: 'execution', variant: 'accept', title: '验收工单 · 差异评估的输入核对', fields: [] },
|
||||
});
|
||||
});
|
||||
|
||||
it('这一步已经发过卡:**不再发第二张**,如实留一条观察', async () => {
|
||||
const { runner, notes, ctx } = harness({});
|
||||
const out = await runner.run(
|
||||
|
||||
@@ -2253,6 +2253,48 @@ describe('auto 步:配方在拍首跑', () => {
|
||||
expect(out.runtime.items.find((i) => i.id === 'S1')?.state).toBe('Completed');
|
||||
});
|
||||
|
||||
/*
|
||||
* **「数据本来就齐」那条路上,人工激活是命门**(2026-08-31 用户定:「就算数据是足的也进入
|
||||
* 这个数据工单,就派发一个验收工单」)。
|
||||
*
|
||||
* 引擎在拍首先扫一遍 doneWhen(`sweepRipe`),为真就自己收步 —— 数据本来就齐的单,那一步的
|
||||
* doneWhen 第一拍就为真,**配方一笔都还没跑**,步骤当场被收掉,卡永远发不出去。
|
||||
* 标了人工激活,那道扫描多一个门槛:`worldTouched`(卡真发过)—— 于是次序变成
|
||||
* 「配方发卡 → 人答 → 下一拍 doneWhen 为真才收」。doneWhen 照旧是这一步的真门,只是不抢在人前面。
|
||||
*/
|
||||
const MANUAL_MODEL: CaseModel = {
|
||||
name: '一步的活动(人工激活)',
|
||||
autoComplete: false,
|
||||
items: [{ id: 'S1', name: '补数据', type: 'humanTask', discretionary: false, manualActivation: true, entryCriteria: [], exitCriteria: [] }],
|
||||
sentries: [],
|
||||
} as never;
|
||||
|
||||
const RIPE_STEP = {
|
||||
item: 'S1',
|
||||
guide: '',
|
||||
writes: [],
|
||||
reads: [],
|
||||
tools: [],
|
||||
doneWhen: 'ready = true',
|
||||
auto: { do: [{ kind: 'card' as const, as: 'execution' as const, variant: 'accept' as const, title: '验收工单 · 核对', basis: [{ k: '料号', v: 'XL2596S-5.0' }] }] },
|
||||
};
|
||||
|
||||
it('人工激活 + doneWhen 已为真 + 卡还没发过 → 拍首**不收步**,轮到配方把卡发出去', async () => {
|
||||
const h = harness({ parsed: MANUAL_MODEL, runtime: AUTO_RUNTIME, sfeelCondition: () => true, steps: [RIPE_STEP] });
|
||||
const out = await h.runner.runTurn(TASK);
|
||||
expect(h.issued.map((i) => i.card.variant)).toEqual(['accept']);
|
||||
expect(out.runtime.items.find((i) => i.id === 'S1')?.state).not.toBe('Completed');
|
||||
expect(h.decided).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('**同一份配方、同一个 doneWhen,没标人工激活就当场被收掉** —— 卡一张都发不出去(病根本身)', async () => {
|
||||
const h = harness({ parsed: AUTO_MODEL, runtime: AUTO_RUNTIME, sfeelCondition: () => true, steps: [RIPE_STEP] });
|
||||
const out = await h.runner.runTurn(TASK);
|
||||
/* 只数执行工单 —— 这一步被收掉之后整单收口,尾巴上那张 acceptance 是放行验收单,与这条无关 */
|
||||
expect(h.issued.filter((i) => i.workOrder === 'execution')).toHaveLength(0);
|
||||
expect(out.runtime.items.find((i) => i.id === 'S1')?.state).toBe('Completed');
|
||||
});
|
||||
|
||||
it('有卡在等人交回:配方照样写,但一张新卡都不发', async () => {
|
||||
const h = harness({
|
||||
parsed: AUTO_MODEL,
|
||||
|
||||
@@ -367,7 +367,8 @@ export function createAutoRunner(deps: AutoRunnerDeps): {
|
||||
basis,
|
||||
/* 零输入:填报全在工作区的件上(2026-08-02 用户定) */
|
||||
fields: [],
|
||||
...(action.variant === 'data' ? { variant: 'data' } : {}),
|
||||
/* 执行工单认两副变体面孔:`data` 数据工单 / `accept` 验收工单;`review` 是决策卡那边的,按值窄化别串台 */
|
||||
...(action.variant === 'data' || action.variant === 'accept' ? { variant: action.variant } : {}),
|
||||
...(action.guide === undefined ? {} : { steps: [...action.guide] }),
|
||||
...(action.standard === undefined ? {} : { standard: evalText(action.standard, base) }),
|
||||
...(action.gateLedgers === undefined ? {} : { gateLedgers: [...action.gateLedgers] }),
|
||||
|
||||
Reference in New Issue
Block a user