三面拆分⑬:流程包纯编排化 —— 编排与传递契约归包,实现方式归库,激活看双判据

模型(用户定):流程包只决定活动编排 + 活动传递的契约,不决定活动的实现方式;
节点只分活动点与审批点;图只定名称与顺序,属性在卡上填,链接好实现即可运行;
激活 = ①Mastra 式图编排闭合完善 + ②活动装载,两面判据。

schema(库经外科手术清空,无历史包袱,网关彻底出局):
- contracts:删 gateway 节点变体 / GATEWAY_KINDS / GATEWAY_KIND_LABEL /
  'gateway-deprecated' 违规项 —— 节点四种:start/end(结构件)+ activity/approval
- routing:joinReady 整个删除(多入边即异或汇合,没有「等回齐」;AND 待并行组);
  selectEdges 精简;closure 删 checkGateway;process-service 删汇合等待与网关直传,
  节点一律只收装配后载荷(verdict/outcome 判据由系统注入,不走映射)

编排面(画布 = 名称 + 顺序,其余在属性卡):
- 元素盘五件:活动点(单一,不再分标准/案例 —— 那是实现方式)/审批点/起止/卡点
- 属性卡:节点类型二分(活动点/审批点);新增「链接活动实现」选择器(装载面,
  FlowNode.act 经 merge 成为语义 activityId);范式/版本降为只读装载信息
- 边属性可填:条件(S-FEEL)+ 映射(上游输出口/起单载荷/常量三种来源;
  workflow-var/expression 归系统隐性,不进设计者面)—— FlowEdge.condition/mapping
  工作副本,pkg-to-graph 回填、graph-to-pkg 合并,清空条件即变默认兜底边
- 异常看板 → 激活双判据看板:图编排(结构+属性+契约)与活动装载(unknown-activity)
  分面亮灯,两面清零激活才亮
- pkg-to-graph 去 paradigms 绿化与网关分支;图例/文案全线「活动点/审批点」

测试:closure 27(joinReady 两组 describe 随机制摘除)、advance-order 按真图形状重写
(去 as never 捏造的网关节点)、graph-to-pkg 9(新增 act 合并 + 边契约合并/清空用例)、
seed 8、端到端八例真库全绿;五闸退出码验过(api:check 提交后转绿)。

库:purge 后重种,唯一 unified-quote@v1 生效(10 节点 14 边,双面判据齐)。
债:M5-5 清账(兼容路径连 schema 一起摘);M5-4(并行组)仍挂。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
2026-07-28 18:29:32 +08:00
co-authored by Claude Fable 5
parent 6854524784
commit 281f4ef084
22 changed files with 458 additions and 634 deletions
@@ -23,7 +23,7 @@ import type {
import { checkClosure } from '../domain/closure.ts';
import { aggregateWorkflowState, isDigested, nextInstanceNumber, resolveOwner } from '../domain/owner-resolution.ts';
import type { NodeInstance } from '../domain/owner-resolution.ts';
import { assemble, dispatchOf, joinReady, rejectRouteOf, selectEdges, startNodeOf } from '../domain/routing.ts';
import { assemble, dispatchOf, rejectRouteOf, selectEdges, startNodeOf } from '../domain/routing.ts';
export type ProcessServiceDeps = {
readonly store: ProcessStore;
@@ -229,23 +229,13 @@ export function createProcessService(deps: ProcessServiceDeps): ProcessService {
const target = pkg.nodes.find((n) => n.id === edge.to);
if (target === undefined) continue;
if (target.kind === 'gateway' && target.mode === 'join') {
const instances = await deps.store.listInstances(record.id);
const converged = new Set(instances.filter((i) => i.terminal === 'released').map((i) => i.nodeId));
const tolerated = new Set(instances.filter((i) => i.failureDigested === true).map((i) => i.nodeId));
// 未回齐就等着 —— 别的分支到了会再来一次
if (!joinReady(pkg, target.id, converged, tolerated)) continue;
}
const assembled = assemble(edge, { ...scope, ports: { ...scope.ports, [fromNodeId]: payload } }, deps.sfeel.expression);
/*
* 网关不是责任单元,**按到达的东西路由**:它没有入参契约要满足,
* 上游的输出口载荷要原样穿过去,判据才读得到(如 G4 读 outcome)。
* 若只给边映射装配出来的那份,无映射的边就把判据依据整个丢掉,
* 异或网关会一条分支都选不中,整条流悄无声息地停住。
* 活动与审批点仍只收**装配后的**载荷 —— 它们有契约,不该被上游多余字段污染。
* 多入边即异或汇合:上游互斥,只会来一条 —— **没有「等回齐」这回事**。
* (等待逻辑随网关一起摘除;AND 并行待「并行组」声明时再带自己的汇合语义。)
* 节点只收**装配后的**载荷:活动与审批点都有契约,不被上游多余字段污染;
* 判据要用的隐性上下文(审批 verdict、放行选口 outcome)由系统注入判据作用域,不走映射。
*/
const arriving = target.kind === 'gateway' ? { ...payload, ...assembled } : assembled;
const arriving = assemble(edge, { ...scope, ports: { ...scope.ports, [fromNodeId]: payload } }, deps.sfeel.expression);
try {
await enter(record, pkg, target, arriving);
} catch (error) {
@@ -450,8 +440,7 @@ export function createProcessService(deps: ProcessServiceDeps): ProcessService {
if (disposition.kind === 'tolerate') {
/*
* 容忍 = 这条支线的失败被吸收,**流继续往下走**。
* 只把实例标成「已消化」而不往下推,汇合就永远等不到它:
* `joinReady` 认 tolerated,可没人再去问一次它够不够齐。
* 光把实例标成「已消化」而不往下推,这条线就断在这儿 —— 下游谁也不会再被叫起。
* 载荷为空 —— 容忍分支本就没有产出,下游的必填映射不得依赖它(闭合校验已拦)。
*/
await advanceFrom(record, pkg, nodeId, {});