三面拆分⑭:宽进收敛 —— 起单不卡缺项,进活动受理归一,信息不全派补全单

用户定的逻辑:「调用活动,输入数据可以不定或者为空;进入活动后受理归一,
信息不全派补全单。」把收敛点从门口挪进活动 —— 三道门口闸全撤:

  · 触发装配:缺必填**不再入死信**(AssemblyResult 的 missing 改 partial,带上已装配的);
    装到多少带多少照样起单。死信只留给真错误(勾了确认没指定人这类配置错误)。
  · process.start:撤掉调用接口缺项闸 —— 从前缺一个字段整条流都起不来,
    现实是客户来信本来就常缺客户码,拦在门外等于要求外界替系统把信息备齐。
  · task.start(替代旧 AM 12.1「缺项不得启动」):缺必填的任务落成「等人」,
    缺的字段做成**补全卡**(elicitation,标签取 schema 的 title)发到负责人的
    任务对话流,并武装 human 监听(IntakeDesk 端口;不占活动的人卡预算)。
    答复只收契约声明过的键,合并进 data;齐了或答「没法填」才转 running 开跑;
    答一半就就缺的部分再派一张卡。等人期间谱一步不走、不扣步数。

  · 测试台预检改成**信息**不是闸:灯灭不拦起单,文案直说
    「抽不出的键由受理补全单找人收齐」。

5 条受理归一单测 + 全链实测:投一封只写「想问下工控板价格」的信(零键)→
流照起 → A1 落负责人工位、等人、补全卡在对话流 → 答卡(customerCode/rfqRef)→
任务醒、合并、谱跑 3 步到验收。修一处接线错误:监听 deadline 要 ISO-8601
时长(P2D)不是时刻,传错整个补全监听静默没装上。

trigger/task/process 三份 CONTRACT.md 与产品设计.md 同步(宽进纪律成文)。
五闸绿;契约快照随本提交入库。

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-29 13:46:13 +08:00
co-authored by Claude Opus 5
parent f9b2c07731
commit 8d50ceda6a
21 changed files with 391 additions and 65 deletions
@@ -7,7 +7,6 @@
import { describe, expect, it } from 'vitest';
import { processPackageSchema } from '@contracts';
import type { ProcessPackage } from '@contracts';
import { CallInterfaceViolationError } from '../contract.ts';
import { createProcessService } from '../internal/usecase/process-service.ts';
import type { ProcessServiceDeps } from '../internal/usecase/process-service.ts';
import type { WorkflowRecord } from '../port.ts';
@@ -93,7 +92,7 @@ function harness(validationErrors: readonly string[]): Harness {
return { service: createProcessService(deps), inserted, published };
}
describe('起单:凡单必合契约', () => {
describe('起单:宽进 —— 输入可以不定或为空', () => {
const input = {
packageId: 'p1',
subject: '测试单',
@@ -101,17 +100,12 @@ describe('起单:凡单必合契约', () => {
data: {},
};
it('入参不合调用接口 → 抛错,且**不建工作流、不发事件**', async () => {
const { service, inserted, published } = harness(["must have required property 'customerCode'"]);
await expect(service.start(input)).rejects.toBeInstanceOf(CallInterfaceViolationError);
// 校验若放在建单之后,这里就会留下一条永远推不动的在途单,污染在途口径
expect(inserted).toHaveLength(0);
expect(published).toHaveLength(0);
});
it('错误带上缺了哪个字段 —— 登记人据此补齐,而不是猜', async () => {
const { service } = harness(["must have required property 'customerCode'"]);
await expect(service.start(input)).rejects.toThrow(/customerCode/);
it('宽进(2026-07-30):入参缺项**照样建单** —— 收敛点在活动,不在门口', async () => {
const { service, inserted } = harness(["must have required property 'customerCode'"]);
const record = await service.start(input);
expect(record.id).toBeDefined();
expect(inserted).toHaveLength(1);
// 缺的字段就是缺着 —— 进活动后受理归一,信息不全由任务侧派补全单收齐
});
it('入参合契约 → 正常建单,来源记在单上', async () => {