M4: 范式引擎与任务运行时

skill-engine(L4):载方法不载权力(白名单外工具即拒)、结晶片段经 ToolInvoker 留痕、
  装配入信封、技能目录渐进披露

flow-engine(L5):四种步型、确定性谓词跳步、重试与声明给定的兜底、
  异步步即监听(时限必配)、写类工具登记动作意向、步序位置持久

loop-engine(L5):自研 CMMN 子集解析器(表外构件直接报错,子集封顶可机器证明)、
  两副标准生命周期与迁移拓扑(引擎不得私改)、Enabled ≠ Active、
  语义性前提标待裁量评估(引擎不代裁量)、DMN 确定性求值 + 命中行留痕、
  DRD 缺口演绎与拓扑序、双向驱动与交集导航(断点即人环)、三型行动、
  终局唯放行与 Harness

task(L6):终态五名裁定(released 唯一成功)、Harness 预算环外强制与停滞熔断
  (监听中是健康态)、控制三语义(叫停不是终态,记最重档纠正)、改派留痕通知双方、
  断点续跑三份持久状态、封卷触发蒸馏钩子

301 个测试通过,门禁五道全绿

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
JCAI Main Agent
2026-07-27 01:51:22 +08:00
co-authored by Claude Opus 5
parent 2850e6d4ef
commit 3c974d2860
55 changed files with 7113 additions and 1 deletions
@@ -0,0 +1,281 @@
/**
* CMMN 标准生命周期与双向驱动 —— 纯逻辑,零 IO(编码规范 §5.3;语义出处 AM 04)。
*
* 「状态的权威是 CMMN 规范的标准生命周期:**活动不定义状态,模型不发明状态**;
* 引擎按规范推进,守卫(时限 / 预算 / 预授权 / 监听)仅可参数化。」
*/
import type { CaseModel, Criterion, PlanItem } from './cmmn-parse.ts';
/* ── 两副状态表(AM 4.1) ────────────────────────────────────────────── */
/** 案计划(整案)五态 */
export const CASE_STATES = ['Active', 'Completed', 'Suspended', 'Terminated', 'Failed'] as const;
export type CaseState = (typeof CASE_STATES)[number];
/** 计划项八态 */
export const ITEM_STATES = [
'Available',
'Enabled',
'Disabled',
'Active',
'Suspended',
'Failed',
'Completed',
'Terminated',
] as const;
export type ItemState = (typeof ITEM_STATES)[number];
/** 迁移拓扑 —— 引擎不得私改 */
const ITEM_TRANSITIONS: Readonly<Record<ItemState, readonly ItemState[]>> = {
Available: ['Enabled', 'Active', 'Terminated', 'Suspended'],
Enabled: ['Active', 'Disabled', 'Terminated', 'Suspended'],
Disabled: ['Enabled', 'Terminated'],
Active: ['Completed', 'Failed', 'Suspended', 'Terminated'],
Suspended: ['Available', 'Enabled', 'Active', 'Terminated'],
Failed: ['Active', 'Terminated'],
Completed: [],
Terminated: [],
};
const CASE_TRANSITIONS: Readonly<Record<CaseState, readonly CaseState[]>> = {
Active: ['Completed', 'Suspended', 'Terminated', 'Failed'],
Suspended: ['Active', 'Terminated'],
Failed: ['Active', 'Terminated'],
Completed: [],
Terminated: [],
};
export class IllegalTransitionError extends Error {
constructor(
readonly from: string,
readonly to: string,
) {
super(`生命周期迁移非法:${from} → ${to}(语义忠实:引擎不得私改迁移拓扑)`);
this.name = 'IllegalTransitionError';
}
}
export function canTransitionItem(from: ItemState, to: ItemState): boolean {
return ITEM_TRANSITIONS[from].includes(to);
}
export function assertItemTransition(from: ItemState, to: ItemState): void {
if (!canTransitionItem(from, to)) throw new IllegalTransitionError(from, to);
}
export function canTransitionCase(from: CaseState, to: CaseState): boolean {
return CASE_TRANSITIONS[from].includes(to);
}
/* ── 运行时快照 ─────────────────────────────────────────────────────── */
export type ItemRuntime = {
readonly id: string;
readonly state: ItemState;
/** repetitionRule 的实例序号 */
readonly instance: number;
/** 语义性前提待裁量评估的标记 */
readonly awaitingJudgement?: boolean;
};
export type CaseRuntime = {
readonly state: CaseState;
readonly items: readonly ItemRuntime[];
/** 已达成的里程碑 */
readonly milestones: readonly string[];
};
export function initialRuntime(model: CaseModel): CaseRuntime {
return {
state: 'Active',
items: model.items.map((item) => ({
id: item.id,
// 裁量项不随阶段自动入场:从 planning table 由 Agent 临场规划
state: item.discretionary ? 'Available' : 'Available',
instance: 1,
})),
milestones: [],
};
}
/* ── 前提求值 ───────────────────────────────────────────────────────── */
export type ConditionEvaluator = (expression: string, scope: Readonly<Record<string, unknown>>) => boolean;
export type CriterionVerdict =
| { readonly kind: 'satisfied' }
| { readonly kind: 'unsatisfied' }
/** 语义性前提不可机算 —— 标「待裁量评估」,交上层 Agent,引擎**不代裁量** */
| { readonly kind: 'needs-judgement'; readonly statement: string };
export function evaluateCriterion(
criterion: Criterion,
scope: Readonly<Record<string, unknown>>,
changedRefs: ReadonlySet<string>,
evaluate: ConditionEvaluator,
): CriterionVerdict {
// onPart:监听的数据项写入 / 计划项迁移发生过才算「件到」
if (criterion.onParts.length > 0) {
const allArrived = criterion.onParts.every((part) => changedRefs.has(part.ref));
if (!allArrived) return { kind: 'unsatisfied' };
}
if (criterion.ifPart === undefined) return { kind: 'satisfied' };
if (criterion.semantic) return { kind: 'needs-judgement', statement: criterion.ifPart };
return evaluate(criterion.ifPart, scope) ? { kind: 'satisfied' } : { kind: 'unsatisfied' };
}
/* ── Enabled 集发布:引擎推进的终点,Agent 裁量的起点 ────────────────── */
export type EnabledEntry = {
readonly id: string;
readonly name: string;
readonly type: PlanItem['type'];
/** manualActivationRule = true:进入 Active 永远出自 Agent 的裁量启动 */
readonly requiresManualStart: boolean;
readonly discretionary: boolean;
};
export type Redlight = { readonly criterion: string; readonly statement: string };
export type EnabledSet = {
readonly entries: readonly EnabledEntry[];
/** 待裁量评估的语义性前提 */
readonly judgements: readonly Redlight[];
/** 目标缺口:未绿的判据 */
readonly gaps: readonly string[];
/** 阶段位置:Active 的阶段 */
readonly activeStages: readonly string[];
};
/**
* 前提前推(开展):任务视图变化 → 前提满足 → 新计划项 Enabled。
* 「**Enabled ≠ Active**」—— 引擎按规范推进到 Enabled 为止;
* 凡触达世界或产出关键交付的计划项必须 manualActivationRule = true。
*/
export function publishEnabled(
model: CaseModel,
runtime: CaseRuntime,
scope: Readonly<Record<string, unknown>>,
changedRefs: ReadonlySet<string>,
evaluate: ConditionEvaluator,
unmetCriteria: readonly string[] = [],
): EnabledSet {
const byId = new Map(runtime.items.map((i) => [i.id, i]));
const entries: EnabledEntry[] = [];
const judgements: Redlight[] = [];
for (const item of model.items) {
const state = byId.get(item.id)?.state ?? 'Available';
if (state !== 'Available' && state !== 'Enabled') continue;
// 父阶段未激活的项不入场
if (item.parent !== undefined && byId.get(item.parent)?.state !== 'Active') continue;
let satisfied = true;
for (const criterion of item.entryCriteria) {
const verdict = evaluateCriterion(criterion, scope, changedRefs, evaluate);
if (verdict.kind === 'needs-judgement') {
judgements.push({ criterion: criterion.id, statement: verdict.statement });
satisfied = false;
break;
}
if (verdict.kind === 'unsatisfied') {
satisfied = false;
break;
}
}
if (!satisfied) continue;
entries.push({
id: item.id,
name: item.name,
type: item.type,
requiresManualStart: item.manualActivation,
discretionary: item.discretionary,
});
}
return {
entries,
judgements,
gaps: unmetCriteria,
activeStages: runtime.items
.filter((i) => i.state === 'Active' && model.items.find((m) => m.id === i.id)?.type === 'stage')
.map((i) => i.id),
};
}
/**
* 判据后拉(完成):目标缺口 → 由什么推出 → 落到一个 Enabled 的计划项。
* 「导航即交集,断点即人环:下一步 = 当下可为(Enabled 集)∩ 目标所需;
* **交集为空即断点** —— 调人或立监听的判定时机。」
*/
export type Navigation =
| { readonly kind: 'proceed'; readonly candidates: readonly EnabledEntry[] }
/** 断点:目标所需的缺口无法由任何 Enabled 项补足 */
| { readonly kind: 'breakpoint'; readonly gaps: readonly string[] }
| { readonly kind: 'converged' };
export function navigate(enabled: EnabledSet, required: ReadonlySet<string>): Navigation {
if (enabled.gaps.length === 0) return { kind: 'converged' };
const intersection =
required.size === 0 ? enabled.entries : enabled.entries.filter((e) => required.has(e.id));
if (intersection.length === 0) return { kind: 'breakpoint', gaps: enabled.gaps };
return { kind: 'proceed', candidates: intersection };
}
/* ── 自动完成与终局 ─────────────────────────────────────────────────── */
/** 「齐了即收」(AutoComplete):必做项完成且无进行中项即结案 */
export function autoCompleteReady(model: CaseModel, runtime: CaseRuntime): boolean {
if (!model.autoComplete) return false;
const byId = new Map(runtime.items.map((i) => [i.id, i]));
const required = model.items.filter((i) => !i.discretionary && i.type !== 'timerEventListener');
const allDone = required.every((i) => {
const state = byId.get(i.id)?.state;
return state === 'Completed' || state === 'Terminated' || state === 'Disabled';
});
const noneRunning = runtime.items.every((i) => i.state !== 'Active');
return allDone && noneRunning;
}
/**
* 终局唯放行与 Harness(必须):
* 案计划 Completed **必经放行卡**;Terminated 只出自 Harness 与负责人;
* **AI 不得产生终局迁移**。
*/
export const TERMINAL_AUTHORITIES = ['release', 'harness', 'owner', 'governance'] as const;
export type TerminalAuthority = (typeof TERMINAL_AUTHORITIES)[number];
export function mayCompleteCase(by: TerminalAuthority): boolean {
return by === 'release';
}
export function mayTerminateCase(by: TerminalAuthority): boolean {
return by === 'harness' || by === 'owner' || by === 'governance';
}
/** 里程碑与事件监听器只有一条主迁移:Available →(occur)→ Completed */
export function occur(runtime: CaseRuntime, itemId: string): CaseRuntime {
return {
...runtime,
items: runtime.items.map((i) => (i.id === itemId ? { ...i, state: 'Completed' } : i)),
milestones: runtime.milestones.includes(itemId) ? runtime.milestones : [...runtime.milestones, itemId],
};
}
export function transitionItem(runtime: CaseRuntime, itemId: string, to: ItemState): CaseRuntime {
const current = runtime.items.find((i) => i.id === itemId);
if (current === undefined) throw new IllegalTransitionError('(不存在)', to);
assertItemTransition(current.state, to);
return { ...runtime, items: runtime.items.map((i) => (i.id === itemId ? { ...i, state: to } : i)) };
}
/** repetitionRule:驳回是**迁移不是状态** —— 相关计划项以新实例重新入场 */
export function reenter(runtime: CaseRuntime, itemId: string): CaseRuntime {
return {
...runtime,
items: runtime.items.map((i) =>
i.id === itemId ? { id: i.id, state: 'Available', instance: i.instance + 1 } : i,
),
};
}