Archived
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:
co-authored by
Claude Opus 5
parent
2850e6d4ef
commit
3c974d2860
@@ -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,
|
||||
),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user