G2 之后一直不分流,根因三条,逐条修: ① 输出口载荷是空的。`outputPortsOf` 原先从**作用域**里捞字段拼载荷, 而放行那一刻草稿层已合并并清空,捞到的只剩起单入参 —— 下游节点必然缺字段。 改为:载荷取自**落地收敛的事实**,由 `releaseWrite` 回报合并了哪些行, 经 release → task → app 一路带上来。这也正是「落地收敛,输出口才发射」的字面意思: 发射的就该是已经落地的那些事实。 `LandedFacts` 给两种读法:铺平(标量取值)与逐行(数组取值), 输出口声明里 `type: 'array'` 的属性填逐行,标量属性填铺平值(驼峰/下划线两种写法都认)。 ② 一条分支起不来,把整条终态上报连坐掀翻 —— 于是**上游任务的终态也报不上去**, 流停在原地,一行日志都没有。改为:该分支记 `node-start-failed` 事件 + 落一个 blocked 实例,其余分支照走,在治理台「需处置」里现形。 (这条是先修的:正因为它,前面两条才一直查不出来。) ③ 边映射引用了不存在的路径:`G2→A3` 的 pn 取 `intake.records`(那是想象中的 LLM 出参形状)、 `G2J→A4` 的 records 取 `initiation.records`(起单时根本还没有这些行)。 按输出口的真实声明改正。 结果:A1 released → G2 并行分叉 → **A3 渠道寻源落到林佩萱、A2 替代选型落到王梓名**, 两个 loop 活动同时在跑,岗位解析正确。 gates: tsc / eslint / depcruise(317 模块)/ api-report / 393 测试 全绿 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
94 lines
3.3 KiB
TypeScript
94 lines
3.3 KiB
TypeScript
/**
|
|
* module: release —— 本模块要外界提供的能力(编码规范 §10.1 / §5.3)。
|
|
*/
|
|
import type { ActionIntent, StaffId, TaskId } from '@contracts';
|
|
import type { LandingOutcome } from './internal/domain/precheck.ts';
|
|
|
|
export type Clock = { now(): Date };
|
|
export type IdGenerator = { next(): string };
|
|
|
|
export type IntentStore = {
|
|
save(intent: ActionIntent): Promise<void>;
|
|
list(taskId: TaskId): Promise<ActionIntent[]>;
|
|
setState(id: string, state: ActionIntent['state'], error?: string): Promise<void>;
|
|
};
|
|
|
|
/** 台账 —— app 注入 `ledger`(L1);放行写与冲突检查都在它那 */
|
|
export type LedgerGateway = {
|
|
checkConflicts(taskId: TaskId): Promise<Array<{ field: string; key: Record<string, string | number> }>>;
|
|
releaseWrite(taskId: TaskId): Promise<{
|
|
mergedRows: number;
|
|
emittedEvents: number;
|
|
/** 合并进正式版的行 —— 输出口发射的载荷取自这里 */
|
|
merged: ReadonlyArray<{
|
|
domain: string;
|
|
object: string;
|
|
key: Record<string, unknown>;
|
|
values: Record<string, unknown>;
|
|
}>;
|
|
}>;
|
|
};
|
|
|
|
/** 工具 —— app 注入 `toolbox`(L2);预检的②③④项与落地执行都经它 */
|
|
export type ToolGateway = {
|
|
/** 工具可用(未停用)与授权有效 */
|
|
isInvocable(tool: string, activityId: string): Promise<{ ok: boolean; reason?: string }>;
|
|
/** 入参 schema 校验 */
|
|
validateInput(tool: string, input: Record<string, unknown>): Promise<{ ok: boolean; errors: string[] }>;
|
|
/** 目标预校验:工具契约含 validate 能力时调之(ADR-005) */
|
|
validateTarget(input: {
|
|
tool: string;
|
|
activityId: string;
|
|
taskId: TaskId;
|
|
payload: Record<string, unknown>;
|
|
}): Promise<{ ok: boolean; error?: string } | undefined>;
|
|
/** 落地执行 —— 附幂等键 */
|
|
execute(input: {
|
|
tool: string;
|
|
activityId: string;
|
|
taskId: TaskId;
|
|
payload: Record<string, unknown>;
|
|
idempotencyKey: string;
|
|
}): Promise<LandingOutcome>;
|
|
};
|
|
|
|
/** 人类工具 —— app 注入 `human-tool`(L3):发验收工单与落地失败卡 */
|
|
export type CardGateway = {
|
|
issueAcceptance(input: {
|
|
taskId: TaskId;
|
|
ownerId: StaffId;
|
|
humanCallCap: number;
|
|
title: string;
|
|
basis: ReadonlyArray<{ k: string; v: string }>;
|
|
deliverable: string;
|
|
criteria: ReadonlyArray<{ label: string; level: 'L1' | 'L2' | 'L3' | 'L4' | 'L5'; pass: boolean }>;
|
|
actions: ReadonlyArray<{ label: string; effect: 'read' | 'write'; detail: string }>;
|
|
}): Promise<{ id: string }>;
|
|
issueLandingFailure(input: {
|
|
taskId: TaskId;
|
|
ownerId: StaffId;
|
|
humanCallCap: number;
|
|
failures: ReadonlyArray<{ label: string; reason: string }>;
|
|
}): Promise<{ id: string }>;
|
|
};
|
|
|
|
/** 任务上下文 —— app 注入(活动 ID 与契约的输出口声明) */
|
|
export type TaskContext = {
|
|
activityIdOf(taskId: TaskId): Promise<string>;
|
|
outputPortsOf(taskId: TaskId): Promise<{ mandatory: string[]; selective: Array<{ group: string; ports: string[] }> }>;
|
|
};
|
|
|
|
/** 输出口发射 —— app 注入 `event`(L1):落地收敛**之后**才发 */
|
|
export type PortEmitter = {
|
|
emit(input: { taskId: TaskId; port: string; payload: Record<string, unknown> }): Promise<void>;
|
|
};
|
|
|
|
export type MemoryRecorder = {
|
|
append(input: {
|
|
taskId: TaskId;
|
|
kind: 'action-intent' | 'release-verdict' | 'ledger-write';
|
|
title: string;
|
|
detail?: Record<string, unknown>;
|
|
}): Promise<void>;
|
|
};
|