人兜底也要问**行标识**:主键问不到就写不进台账

A6「生成正式报价单」转人兜底时只问了出参的值(unitPrice / leadTime),
没问报价单号 —— 而 quote_no 是 formal_quote 的主键之一。结果人填完照样落不了地:
放行合并时被非空约束挡下(实测:null value in column "quote_no")。

`fallbackFields` 改为**键与值一起问**:出参声明里的主键路径,凡作用域里没有的都要问;
上游已给过的不再重复问。与前面「出参契约属于步、不属于执行者」是同一条:
这一步该产出的**行**和**值**,换谁来做都得齐。

gates: tsc / eslint / depcruise / 399 测试 全绿

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
2026-07-27 05:28:13 +08:00
co-authored by Claude Opus 5
parent 227764b7da
commit 3af6252394
4 changed files with 41 additions and 3 deletions
@@ -32,6 +32,28 @@ describe('人兜底:出参契约属于步,不属于执行者', () => {
expect(fallbackFields(llmStep())).toEqual(['pn', 'brand', 'eau']);
});
it('行标识也要问 —— 主键取不到就写不进台账', () => {
const step = flowStepSchema.parse({
kind: 'llm',
name: 'S1',
label: '生成正式报价单',
input: {},
model: 'm',
template: 't',
outputSchema: { type: 'object' },
output: {
domain: 'q',
object: 'formal_quote',
key: { quote_no: 'quoteNo', pn: 'pn' },
fields: { unit_price: 'unitPrice' },
},
failure: { retry: { times: 0, backoff: 'PT1S' }, fallback: { kind: 'human', workOrder: 'execution' } },
});
// pn 上游已给,不必再问;quoteNo 没人给过,必须问
expect(fallbackFields(step, { pn: 'LM358DR' })).toEqual(['quoteNo', 'unitPrice']);
expect(fallbackFields(step, {})).toEqual(['quoteNo', 'pn', 'unitPrice']);
});
it('无出参声明的步不凭空造字段', () => {
const step = flowStepSchema.parse({
kind: 'llm',