This repository has been archived on 2026-09-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BPOS-MVP/api-report/apps/api/src/modules/process/port.d.ts
T
ChaiHaoandClaude Fable 5 7c609e852c Mastra 化阶段1:抽 FlowRuntime 端口 —— 编排层执行面成为可替换切面,解耦完全成形
目标(用户定):完全实现编排层与活动解耦。解耦线(TaskGateway 起活动 / 终态回报)
本就存在;缺的是 CONTRACT M5-1 那句「Mastra 编译器作可替换 adapter、usecase 不动」
的端口 —— 勘察坐实它此前不存在,状态机直接写在 usecase 里揉着五端口副作用编排。

- port.ts 立 FlowRuntime 端口:begin / onTaskTerminal / applyFailure /
  onApprovalVerdict / refreshState / terminate —— 执行面的全部
- 状态机整体迁入 internal/runtime/self-hosted-runtime.ts(逐字搬迁,行为零变化):
  enter(调度+组轮值分派+实例落 assignee)、advanceFrom(条件出边选路+死路诊断+
  分支不连坐)、失败走向四选一、审批裁决(边优先、声明兜底)、三态聚合投影、终止级联
- process-service 573→248 行薄壳:定义生命周期(保存/激活/校验)+ 起单前置
  (取生效版/入参校验/工作组快照/建单留痕)+ 五方法委托;
  ProcessServiceDeps.runtime 可注入 —— Mastra 编译产物从这里换入,路由/接线零动
- index 出口 FlowRuntime 型;CONTRACT.md 记切面与已核实的语义冲突清单
  (reopen 无原语待 spike / parallel 失败连坐须 step 内自吞 / abnormal 三态 /
  node_instance 投影必留);debt M5-1 → 切面已立

等价性护栏:process 49 单测 + 八例端到端真库,提取前后原样全绿 —— 行为零变化实证。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
2026-07-28 23:12:04 +08:00

161 lines
6.8 KiB
TypeScript

/**
* module: process —— 本模块要外界提供的能力(编码规范 §10.1 / §5.3)。
*/
import type { ProcessNode, ProcessPackage, StaffId, TerminalState, WorkflowState } from '@contracts';
import type { NodeInstance } from './internal/domain/owner-resolution.ts';
import type { ActivityContractFace } from './internal/domain/closure.ts';
export type Clock = {
now(): Date;
};
export type IdGenerator = {
next(): string;
};
/** 工作流实例的持久面 —— 崩溃后可续跑 */
export type WorkflowRecord = {
readonly id: string;
readonly packageId: string;
readonly packageVersion: number;
readonly subject: string;
readonly state: WorkflowState;
/** 来源:凡单必有来源(触发器 / 登记人 / 上游系统) */
readonly source: {
kind: 'periodic' | 'event' | 'registration';
ref: string;
by?: string;
};
readonly initiation: Record<string, unknown>;
/**
* 工作组绑定的**快照**(ADR-011):起单时按来源人装载,之后改组不影响这条单。
* 留痕要能解释「当时为什么派给他」,而不是被今天的组织调整改写(同 AM 9.1 版本信封)。
*/
readonly workGroup?: Record<string, string>;
/** 各节点输出口载荷 —— 边映射装配的来源 */
readonly ports: Record<string, Record<string, unknown>>;
readonly vars: Record<string, unknown>;
readonly startedAt: string;
readonly finishedAt?: string;
};
/**
* 流程运行时 —— **执行面的可替换切面**(M5-1):按结构调度节点、条件出边选路、
* 失败走向、审批裁决、状态聚合。自持状态机(internal/runtime/self-hosted-runtime)
* 是默认实现;Mastra 编译产物(workflow-compiler)将是第二实现 ——
* 八例端到端是两个实现的等价性护栏。
* 定义生命周期(保存/激活/校验)不在此内:那决定「编译哪一份定义」,与引擎选型无关。
*/
export type FlowRuntime = {
/** 起单后进入起点(起单前置 —— 校验/建单/留痕 —— 归 usecase,不在此内) */
begin(record: WorkflowRecord, pkg: ProcessPackage): Promise<void>;
onTaskTerminal(taskId: string, terminal: TerminalState, ports: Record<string, Record<string, unknown>>): Promise<WorkflowState | undefined>;
applyFailure(workflowId: string, nodeId: string, instance: number, node: Extract<ProcessNode, {
kind: 'activity';
}>, terminal: TerminalState): Promise<WorkflowState>;
onApprovalVerdict(workflowId: string, nodeId: string, verdict: 'pass' | 'reject', payload: Record<string, unknown>): Promise<WorkflowState>;
refreshState(workflowId: string): Promise<WorkflowState>;
terminate(workflowId: string, reason: string): Promise<WorkflowState>;
};
export type ProcessStore = {
savePackage(pkg: ProcessPackage): Promise<void>;
/** 就地写回一版的定义(编辑即其身)—— 撞不存在的版本报错,不静默插入 */
updatePackageDefinition(id: string, version: number, pkg: ProcessPackage): Promise<void>;
/** 钉在该版上的未完结工作流数 —— 在途钉版不可变的判据 */
countPinnedInFlight(id: string, version: number): Promise<number>;
/** 只改状态,不动定义 —— state 在列上与 definition 里各存一份,两处要一起改 */
setPackageState(id: string, version: number, state: 'active' | 'retired'): Promise<void>;
getPackage(id: string, version?: number): Promise<ProcessPackage | undefined>;
listPackages(): Promise<ProcessPackage[]>;
insertWorkflow(record: WorkflowRecord): Promise<void>;
getWorkflow(id: string): Promise<WorkflowRecord | undefined>;
listWorkflows(limit: number): Promise<WorkflowRecord[]>;
updateWorkflow(id: string, patch: Partial<WorkflowRecord>): Promise<WorkflowRecord>;
listInstances(workflowId: string): Promise<NodeInstance[]>;
insertInstance(workflowId: string, instance: NodeInstance): Promise<void>;
updateInstance(workflowId: string, nodeId: string, instance: number, patch: Partial<NodeInstance> & {
taskId?: string;
}): Promise<void>;
findInstanceByTask(taskId: string): Promise<{
workflowId: string;
nodeId: string;
instance: number;
} | undefined>;
};
/** 活动契约面 —— app 注入 `activity-registry`(L4):编排校验与起单都要它 */
export type ActivityDirectory = {
faces(): Promise<Map<string, ActivityContractFace>>;
};
/** 岗位与在岗人员 —— app 注入 `org`(L1):负责人四策略解析的候选集 */
export type OrgDirectory = {
positions(): Promise<Set<string>>;
candidates(position: string): Promise<Array<{
staffId: string;
name: string;
openTasks: number;
}>>;
/** 某人的工作组绑定(ADR-011)—— 起单时按来源人取,取不到即不装载 */
workGroupBinding(staffId: string): Promise<Readonly<Record<string, string>> | undefined>;
/** 在岗与否 —— 绑定的人已离岗就不硬用,退回四策略 */
isActive(staffId: string): Promise<boolean>;
};
/** 起单与终态 —— app 注入 `task`(L6) */
export type TaskGateway = {
start(input: {
activityId: string;
ownerId: StaffId;
title: string;
workflowId: string;
nodeId: string;
data: Record<string, unknown>;
message?: string;
}): Promise<{
id: string;
}>;
};
/** 审批点 —— app 注入 `human-tool`(L3):审批单卡到审批人工位 */
export type ApprovalGateway = {
raise(input: {
workflowId: string;
nodeId: string;
approverId: StaffId;
subject: string;
basis: ReadonlyArray<{
k: string;
v: string;
}>;
}): Promise<{
id: string;
}>;
};
/**
* 调用接口的入参校验 —— app 注入 ajv。
* 「凡单必有来源」的另一半是「凡单必合契约」:不合契约**不得起单**,
* 且必须在**建工作流之前**判掉,否则会留下一条永远推不动的在途单。
*/
export type SchemaValidator = {
validate(schema: Record<string, unknown>, value: unknown): {
ok: boolean;
errors: string[];
};
};
/** S-FEEL —— app 注入 `@sfeel`:流转零 LLM */
export type Sfeel = {
condition(expression: string, scope: Readonly<Record<string, unknown>>): boolean;
expression(expression: string, scope: Readonly<Record<string, unknown>>): unknown;
};
/** 流转记入事件 —— app 注入 `event`(L1) */
export type FlowEventPublisher = {
publish(input: {
workflowId: string;
kind: string;
detail: Record<string, unknown>;
}): Promise<void>;
};
/** 终态回执 —— 登记人收消息 */
export type ReceiptNotifier = {
notify(input: {
to: StaffId;
workflowId: string;
subject: string;
state: WorkflowState;
}): Promise<void>;
};
export type { TerminalState };