M5/M6 修复串:判据 ③ 全链路跑通 —— 六处「声明与引擎对不上」

判据 ③ 现在从入站到 released 一路走通:步序推进 → LLM 步(密钥是占位符,
按声明降级为人兜底)→ 台账草稿层 → D1 条件跳步 → S4 工具 → S5 DMN 三分类
(LM358DR → Type A · 直接快答)→ 验收放行 → 草稿合并正式版 2 行 → 输出口
intake 发射 → 终态 released。

沿路修掉的六处,每一处都是「声明是这么写的,引擎不这么读」:

1. 人兜底卡只问一个笼统「结果」框 → 改问**这一步声明的出参字段**。
   出参契约属于步、不属于执行者:同一步谁做完,落进作用域的形状都得一样。
2. 人事件的载荷是回传协议壳 `{mode,workOrderId,values}`,
   引擎把整个壳当出参写 → 取 `values`。
3. 引擎作用域是 `{'域.对象': 行数组}`,而所有活动的步声明写的都是裸字段名 →
   任务视图三层铺开:起单入参在底、各对象最新行的字段铺顶层、地址式整表保留。
4. 起单入参从不落库、也进不了作用域 → task 存 `input_data`,断点续跑后仍在。
5. 步出参无法声明**写哪一行** → 出参加 `key`(主键列 → 作用域路径);
   六个活动包的每处出参补上键。台账几乎都有主键,不声明键就只能写无主键行。
6. `tick` 不处理 `finished`:谱走完仍按 running 一拍拍推,把预算烧到 exhausted,
   把「没人裁」误报成「跑爆了」→ 放行通过则落地收敛后 released;未放行则挂起等人。
   `flow.advance` 据末步验收裁决报 released —— **完成只出自放行通过**。

另两处装配缺口:
  · `release.land()` 实现完整却无人调用 —— 放行后从不落地。经 LandingGateway 接上。
  · `process.onTaskTerminal()` 实现完整却无人调用 —— 任务终态报不到流程层,
    流就停在那个节点上,既不流转也不走失败走向。task 在 L6 不得反向依赖 L7,
    这一跳由 app 缝合(延后绑定,同 container 里 TriggerMatcher 的手法)。

还有一处版本化缺陷:资产播种恒用 v1,`shouldSeedAsset` 一比就跳过 ——
只有**第一个**包版本的资产此生有效,之后装什么都跑旧的。改为播下一版;
进化管线放行出的更高版仍不被盖过;同版重复安装不再播种。

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
This commit is contained in:
2026-07-27 04:31:23 +08:00
co-authored by Claude Opus 5
parent 3b9587563a
commit e72182003a
35 changed files with 3901 additions and 53 deletions
+94 -4
View File
@@ -86,6 +86,20 @@ const sfeel = {
},
};
/** 延后绑定的流程层引用 —— 只用到「任务终态上报」这一件 */
function createDeferredProcess(): {
onTaskTerminal: ProcessService['onTaskTerminal'];
bind(real: ProcessService): void;
} {
let real: ProcessService | undefined;
return {
onTaskTerminal: async (taskId, terminal, ports) => real?.onTaskTerminal(taskId, terminal, ports),
bind(next) {
real = next;
},
};
}
export function wireEngines(deps: EngineWiringDeps): EngineWiring {
const { db, clock, ids } = deps;
@@ -101,10 +115,22 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
/* ── 任务数据:引擎读**任务视图** = 正式版 + 本任务草稿层 ───────── */
const taskData = {
/**
* 任务视图 = 正式版 + 本任务草稿层(AM 4.2),再叠上起单入参。
*
* 作用域的**形状要对得上声明的写法**:步声明里写的是裸字段名
* (`input: { pn: 'pn' }`),不是 `域.对象.行号.字段`。所以这里三层铺开:
* ① 起单入参在底 —— 契约的 data 是这单的出发事实;
* ② 各对象**最新一行**的字段名铺到顶层 —— 过程工作区的数据形态即草稿层;
* ③ `域.对象` 仍保留整表,要整表的照样取得到。
* 顶层同名以「后写的赢」为准:草稿层是这一单最新的过程事实,盖过起单入参。
*/
async view(taskId: TaskId) {
// 引擎对任务视图求值,不依赖事件(AM 4.2)
const task = await taskStore.get(taskId);
if (task === undefined) throw new Error(`任务不存在:${taskId}`);
const objects = await deps.ledger.listObjects();
const scope: Record<string, unknown> = {};
const scope: Record<string, unknown> = { ...task.data };
for (const address of objects) {
const page = await deps.ledger.taskView(taskId, {
address,
@@ -114,6 +140,12 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
offset: 0,
});
scope[`${address.domain}.${address.object}`] = page.rows;
const latest = page.rows[page.rows.length - 1];
if (latest === undefined) continue;
for (const [field, value] of Object.entries(latest)) {
// 空值不盖掉已有事实:没写进去的字段不该把起单入参抹成 undefined
if (value !== null && value !== undefined) scope[field] = value;
}
}
return scope;
},
@@ -295,6 +327,29 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
clock,
});
/**
* process 晚于 task 构造 —— 延后绑定(与 container 里 `TriggerMatcher` 同一手法)。
* 未绑定时静默不报:装配尚未完成的那一瞬没有任务会终态。
*/
const processRef = createDeferredProcess();
/** 任务的输出口载荷:活动声明的必交付输出口 → 该任务视图上的字段 */
async function outputPortsOf(taskId: TaskId): Promise<Record<string, Record<string, unknown>>> {
const task = await taskStore.get(taskId);
if (task === undefined) return {};
const manifest = await deps.registry.manifestOf(task.activityId);
const scope = await taskData.view(taskId);
const ports: Record<string, Record<string, unknown>> = {};
for (const port of manifest.contract.output.mandatory) {
const payload = port.payload as { properties?: Record<string, unknown> };
const fields = Object.keys(payload.properties ?? {});
ports[port.name] = Object.fromEntries(
fields.filter((f) => scope[f] !== undefined).map((f) => [f, scope[f]]),
);
}
return ports;
}
/* ── task(L6):范式在此汇流,对上层不可区分 ──────────────────── */
const task = createTaskService({
store: taskStore,
@@ -330,7 +385,12 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
load: (taskId, model) => flow.load(taskId, model as never).then(() => undefined),
advance: async (taskId) => {
const outcome = await flow.advance(taskId);
return { status: outcome.status, ...(outcome.blockedReason === undefined ? {} : { reason: outcome.blockedReason }) };
return {
status: outcome.status,
...(outcome.blockedReason === undefined ? {} : { reason: outcome.blockedReason }),
// 谱走完时把放行裁决带上来 —— 完成只出自放行通过
...(outcome.released === undefined ? {} : { released: outcome.released }),
};
},
onEvent: async (taskId, event) => {
const outcome = await flow.resume(taskId, {
@@ -342,6 +402,22 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
},
},
},
landing: {
/** 放行通过即刻落地:草稿合并 + 动作执行(附幂等键)→ 收敛才发射输出口(OS 7.3) */
async land(input) {
const outcome = await deps.release.land({
taskId: input.taskId,
ownerId: input.ownerId,
humanCallCap: input.humanCallCap,
selectedPorts: {},
});
return {
converged: outcome.verdict.kind === 'converged',
emittedPorts: outcome.emittedPorts,
ledgerRowsMerged: outcome.ledgerRowsMerged,
};
},
},
memory: {
append: memoryRecorder.append as never,
seal: (taskId) => deps.memory.seal(taskId),
@@ -375,6 +451,17 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
occurredAt: clock.now().toISOString(),
source: { kind: 'engine', detail: 'task' },
});
/*
* 任务终态到达 → 通知流程层(L7)。
*
* task 在 L6、process 在 L7,task 不得反向依赖 process —— 这一跳由 app 这层缝合。
* 「工作流状态是聚合投影」的前提是节点实例的终态**报得上去**:不报,
* 流就停在那个节点上,既不流转也不走失败走向。
* 用延后绑定是因为 process 在本函数里晚于 task 构造。
*/
const ports = await outputPortsOf(taskId);
await processRef.onTaskTerminal(taskId, terminal, ports);
},
},
notifier: {
@@ -395,7 +482,7 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
});
/* ── process(L7) ─────────────────────────────────────────────── */
const process = createProcessService({
const process: ProcessService = createProcessService({
store: createProcessStore(db),
activities: {
async faces() {
@@ -556,5 +643,8 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
ids,
});
// 延后绑定落定:此后任务终态才报得到流程层
processRef.bind(process);
return { task, process, trigger };
}