Archived
feat(loop-engine): card 与 submit 两个动词;审核卡放宽到单选项
ADR-014 §8 原写「不发决策卡、不 submit」—— 那是假设还有 agent 在场时的保守话。 全程无 agent 的活动没有别人来干这两件事,不开口最后两步就没人做。 护栏没松:submit 仍要过提交门(判据全绿),系统提交不比模型提交宽松。 发过的卡不再发第二张;发了卡就收拍,排在后面的笔一律不跑。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
@@ -12,6 +12,7 @@ type AnalyzeStub = { run: (i: { payload: readonly unknown[]; model: string; inst
|
||||
function harness(seed: Record<string, unknown> = {}, override: { analyze?: AnalyzeStub } = {}) {
|
||||
const drafts = new Map<string, { domain: string; object: string; key: Record<string, string>; patch: Record<string, unknown> }>();
|
||||
const notes: string[] = [];
|
||||
const cards = { issued: [] as Array<Record<string, unknown>> };
|
||||
const view = (): Record<string, unknown> => {
|
||||
const mine: Record<string, unknown[]> = {};
|
||||
for (const d of drafts.values()) (mine[`mine:${d.domain}.${d.object}`] ??= []).push({ ...d.key, ...d.patch });
|
||||
@@ -37,8 +38,8 @@ function harness(seed: Record<string, unknown> = {}, override: { analyze?: Analy
|
||||
},
|
||||
},
|
||||
cards: {
|
||||
issue: () => Promise.resolve({ id: 'card-1' }),
|
||||
decide: () => Promise.resolve({ id: 'card-2' }),
|
||||
issue: (i: Record<string, unknown>) => { cards.issued.push({ ...i, kind: 'issue' }); return Promise.resolve({ id: 'card-1' }); },
|
||||
decide: (i: Record<string, unknown>) => { cards.issued.push({ ...i, kind: 'decide' }); return Promise.resolve({ id: 'card-2' }); },
|
||||
entry: () => Promise.resolve({ id: 'card-3' }),
|
||||
ask: () => Promise.resolve({ id: 'card-4' }),
|
||||
unanswered: () => Promise.resolve([]),
|
||||
@@ -58,7 +59,7 @@ function harness(seed: Record<string, unknown> = {}, override: { analyze?: Analy
|
||||
};
|
||||
const ctx = { taskId: TASK, stepId: 'S1', ownerId: OWNER, humanCallCap: 8, quiet: false, carded: false };
|
||||
// 假端口只实现用得到的那几个方法,形状对不上 AutoRunnerDeps 的全貌 —— 测试文件里 `as never` 是既有做法
|
||||
return { runner: createAutoRunner(deps as never), drafts, notes, ctx };
|
||||
return { runner: createAutoRunner(deps as never), drafts, notes, cards, ctx };
|
||||
}
|
||||
|
||||
describe('配方执行器', () => {
|
||||
@@ -246,3 +247,95 @@ describe('analyze 动词', () => {
|
||||
expect(called).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('card 与 submit', () => {
|
||||
it('execution:发一张数据工单,itemId 挂在这一步上,basis 现算', async () => {
|
||||
const { runner, cards, ctx } = harness({ pn: 'XL2596S-5.0' });
|
||||
const out = await runner.run(
|
||||
{
|
||||
do: [
|
||||
{
|
||||
kind: 'card',
|
||||
as: 'execution',
|
||||
variant: 'data',
|
||||
title: { cat: ['数据工单 · 补齐 ', { in: 'pn' }] },
|
||||
guide: ['选一个芯片类型', '把参数值填齐'],
|
||||
standard: '必填格全部填上,红带消失',
|
||||
basis: [{ k: '候选料号', v: { in: 'pn' } }],
|
||||
gateLedgers: ['parts.profile', 'parts.parameter'],
|
||||
},
|
||||
],
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
expect(out.issuedCardId).toBe('card-1');
|
||||
expect(cards.issued[0]).toMatchObject({
|
||||
workOrder: 'execution',
|
||||
itemId: 'S1',
|
||||
card: {
|
||||
mode: 'execution',
|
||||
title: '数据工单 · 补齐 XL2596S-5.0',
|
||||
variant: 'data',
|
||||
steps: ['选一个芯片类型', '把参数值填齐'],
|
||||
standard: '必填格全部填上,红带消失',
|
||||
basis: [{ k: '候选料号', v: 'XL2596S-5.0' }],
|
||||
gateLedgers: ['parts.profile', 'parts.parameter'],
|
||||
fields: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('这一步已经发过卡:**不再发第二张**,如实留一条观察', async () => {
|
||||
const { runner, notes, ctx } = harness({});
|
||||
const out = await runner.run(
|
||||
{ do: [{ kind: 'card', as: 'execution', title: 'x', basis: [{ k: 'a', v: 'b' }] }] },
|
||||
{ ...ctx, carded: true },
|
||||
);
|
||||
expect(out.issuedCardId).toBeUndefined();
|
||||
expect(notes.some((n) => n.includes('已经发过'))).toBe(true);
|
||||
});
|
||||
|
||||
it('decide + review:摆成验收工单,单一选项也发得出去', async () => {
|
||||
const { runner, ctx } = harness({});
|
||||
const out = await runner.run(
|
||||
{
|
||||
do: [
|
||||
{
|
||||
kind: 'card',
|
||||
as: 'decide',
|
||||
variant: 'review',
|
||||
title: '差异评估结论审核',
|
||||
ask: '逐条核对过了吗?',
|
||||
options: [{ value: 'approve', label: '确认通过' }],
|
||||
basis: [{ k: '候选', v: 'XL2596S-5.0' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
expect(out.issuedCardId).toBe('card-2');
|
||||
});
|
||||
|
||||
it('发了卡就**收拍**:排在后面的笔一律不跑', async () => {
|
||||
const { runner, drafts, ctx } = harness({});
|
||||
await runner.run(
|
||||
{
|
||||
do: [
|
||||
{ kind: 'card', as: 'execution', title: 'x', basis: [{ k: 'a', v: 'b' }] },
|
||||
{ kind: 'project', into: 'sub_eval.part', from: { once: true }, fields: { eval_ref: 'a', internal_pn: 'b' } },
|
||||
],
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
expect(drafts.size).toBe(0);
|
||||
});
|
||||
|
||||
it('submit:回给 turn-runner,理由现算;有卡在等人时不 submit', async () => {
|
||||
const { runner, ctx } = harness({ n: 3 });
|
||||
const out = await runner.run({ do: [{ kind: 'submit', rationale: { cat: ['已判定 ', { in: 'n' }, ' 条参数'] } }] }, ctx);
|
||||
expect(out.submit).toEqual({ rationale: '已判定 3 条参数' });
|
||||
|
||||
const quiet = await runner.run({ do: [{ kind: 'submit', rationale: 'x' }] }, { ...ctx, quiet: true });
|
||||
expect(quiet.submit).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user