范式退役:活动只剩一种、只跑 Loop;审批活动可管;活动库重做

用户定的两件,一并落地。

**一、活动不再分 flow 和 loop —— 彻底退役 flow**
  · `flow-engine` 模块整个删除(runner 379 行 + step-sequence 184 行 + 测试);
  · `paradigm` 从清单身份、`activity_package`/`task` 两张表、任务卡与活动库一并退场;
    `businessModel` 不再是判别联合 —— 只有案例模型(CMMN + 说明书 + DMN)一形;
  · task 的引擎分派从「按 paradigm 取两个之一」收成**唯一的引擎**;
  · `activities/` 四个 flow 活动(A1 受理 / A4 定价 / A6 议价 / A7 归档)各写了一份
    案例模型:阶段照原来的步分,触达世界的项一律 manualActivationRule,
    能算的交 DMN(A1 的三分类 P-03、A4 的 P-01/P-02)不交 Agent;
  · 六个占位槽位的骨架从「机器步落台账 + 一张验收单」改成案例骨架,包版本 0.8.0 → 0.9.0。

**代价写在明处,不假装没有**:`turn-runner` 每一拍都要 `agent.decide` ——
环境里没有模型 key,案例活动一步也走不动。三套端到端(统一报价 / 新建流程包 / 测试台
Trace)整套跳过,文件头写明恢复条件:①有可用模型 ②走法改写成按 Enabled 集推进。
**不许把断言改松让它变绿** —— 绿得不诚实比红着更坏。debt.md 已记。

**二、审批活动可管**(新能力)
  · `sys.approval_template`:审批点**可复用的那一半** —— 审批人看哪几项、按什么名字看、
    怎么催、说什么话。谁来审不在里面(与活动点同口径,运营时轮值);驳回回哪个节点
    也不在里面(那是每张图自己的事,留在节点上);
  · 三条路由 + 引用反查:还被审批点引着就不许删(409),删了那些节点会**静默**退回旧行为;
  · 运行时 `basisOf`:引了模板就按模板筛与改名,没引即旧行为(载荷全摊开)。
    模板改了只影响新发出的审批单 —— 在途单的依据在发出那一刻已定形。

**三、活动库重做**:一区两格 —— 「活动(装进来的)」与「审批活动(配出来的)」,
都用流程卡那套两段式卡片(头 + 四格数据板),幽灵卡当入口(装入活动 / 新建审批活动);
「标准/案例」那一维随范式一起撤,业务分类成了唯一的一维,以行内小签呈现。

迁移 0020:两张表 drop paradigm、建 approval_template。已在开发库跑过并重播种。
实测(丁子乔账号):建「定价审批」模板 → 流程包 A5 属性里选得到 → 引用反查计数正确。
四闸绿(api:check 随本提交)。

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-29 23:53:51 +08:00
co-authored by Claude Opus 5
parent 18c3e3fd09
commit 1fd215cf40
88 changed files with 1319 additions and 2966 deletions
@@ -53,7 +53,6 @@ function harness(): Harness {
},
activities: {
load: async () => ({
paradigm: 'flow' as const,
caps: { steps: 40, tokens: 200000, humanCalls: 2, activeMinutes: 60 },
envelope: {},
inputSchema: INPUT_SCHEMA,
@@ -62,10 +61,7 @@ function harness(): Harness {
}),
},
schemas: { validate: () => ({ ok: true, errors: [] }) },
engines: {
flow: { load: async () => undefined, advance: async () => ({ status: 'running' }), onEvent: async () => ({ status: 'running' }) },
loop: { load: async () => undefined, advance: async () => ({ status: 'running' }), onEvent: async () => ({ status: 'running' }) },
},
engine: { load: async () => undefined, advance: async () => ({ status: 'running' }), onEvent: async () => ({ status: 'running' }) },
memory: { append: async () => undefined, hasHistory: async () => true, seal: async () => undefined, distill: async () => undefined },
landing: {
land: async () => ({ converged: true, emittedPorts: [] as string[], ledgerRowsMerged: 0, facts: {} as LandedFacts }),
@@ -33,7 +33,6 @@ function harness(land: () => Promise<never> | Promise<{ converged: boolean; emit
h.task = {
id: TASK,
activityId: 'a-1',
paradigm: 'flow',
ownerId: 's-1' as Task['ownerId'],
title: '占位活动',
runtime: 'running',
@@ -59,7 +58,6 @@ function harness(land: () => Promise<never> | Promise<{ converged: boolean; emit
},
activities: {
load: async () => ({
paradigm: 'flow' as const,
caps: h.task.caps,
envelope: {},
inputSchema: {},
@@ -68,10 +66,11 @@ function harness(land: () => Promise<never> | Promise<{ converged: boolean; emit
}),
},
schemas: { validate: () => ({ ok: true, errors: [] }) },
engines: {
// 谱走完 + 放行通过 —— 接下来就该落地
flow: { load: async () => undefined, advance: async () => ({ status: 'finished', released: true, selectedPorts: {} }), onEvent: async () => ({ status: 'running' }) },
loop: { load: async () => undefined, advance: async () => ({ status: 'running' }), onEvent: async () => ({ status: 'running' }) },
// 案子办完 + 放行通过 —— 接下来就该落地
engine: {
load: async () => undefined,
advance: async () => ({ status: 'finished', released: true, selectedPorts: {} }),
onEvent: async () => ({ status: 'running' }),
},
memory: {
append: async (input) => {
+1 -3
View File
@@ -4,7 +4,7 @@
* 任务运行时:生命周期与**终态五名裁定**、Harness 预算执行器 / 停滞熔断、
* 控制三语义、改派、断点续跑。范式(loop / flow)在此汇流 —— 对流程层与交互层不可区分。
*/
import type { BudgetCaps, ControlSemantic, Paradigm, StaffId, TaskId, TaskRuntimeState, TerminalState } from '@contracts';
import type { BudgetCaps, ControlSemantic, StaffId, TaskId, TaskRuntimeState, TerminalState } from '@contracts';
import type { BudgetBreach, Consumption, HarnessVerdict } from './internal/domain/harness.ts';
import type { ControlOutcome, ResumeReadiness, TaskSnapshot, TerminalCause } from './internal/domain/lifecycle.ts';
@@ -15,7 +15,6 @@ export type {
ControlOutcome,
ControlSemantic,
HarnessVerdict,
Paradigm,
ResumeReadiness,
TaskRuntimeState,
TaskSnapshot,
@@ -50,7 +49,6 @@ export type LandedFacts = {
export type Task = {
readonly id: TaskId;
readonly activityId: string;
readonly paradigm: Paradigm;
/** 起单来源:工作流节点;登记触发的直起单为 undefined */
readonly workflowId?: string;
readonly nodeId?: string;
-1
View File
@@ -9,7 +9,6 @@ export type {
ControlOutcome,
ControlSemantic,
HarnessVerdict,
Paradigm,
ResumeReadiness,
StartTaskInput,
Task,
@@ -9,7 +9,6 @@ export const taskTable = sys.table(
{
id: text('id').primaryKey(),
activityId: text('activity_id').notNull(),
paradigm: text('paradigm').notNull(),
workflowId: text('workflow_id'),
nodeId: text('node_id'),
/** 恰有一名负责人 */
@@ -2,7 +2,7 @@
* task 存储 adapter —— Drizzle 只准出现在 adapter(编码规范 §5.3)。
*/
import { and, asc, eq, isNull, sql } from 'drizzle-orm';
import type { BudgetCaps, Paradigm, StaffId, TaskId, TaskRuntimeState, TerminalState } from '@contracts';
import type { BudgetCaps, StaffId, TaskId, TaskRuntimeState, TerminalState } from '@contracts';
import type { Db } from '@shared/db.ts';
import type { TaskStore } from '../../port.ts';
import type { Task } from '../../contract.ts';
@@ -15,7 +15,6 @@ function toTask(row: Row): Task {
return {
id: row.id as TaskId,
activityId: row.activityId,
paradigm: row.paradigm as Paradigm,
...(row.workflowId === null ? {} : { workflowId: row.workflowId }),
...(row.nodeId === null ? {} : { nodeId: row.nodeId }),
ownerId: row.ownerId as StaffId,
@@ -43,7 +42,6 @@ export function createTaskStore(db: Db): TaskStore {
await db.insert(taskTable).values({
id: task.id,
activityId: task.activityId,
paradigm: task.paradigm,
workflowId: task.workflowId ?? null,
nodeId: task.nodeId ?? null,
ownerId: task.ownerId,
@@ -30,8 +30,8 @@ export type TaskServiceDeps = {
readonly store: TaskStore;
readonly activities: ActivityLoader;
readonly schemas: SchemaValidator;
/** 范式引擎按 paradigm 分派 —— 对上层不可区分 */
readonly engines: Readonly<Record<'loop' | 'flow', ParadigmEngine>>;
/** 唯一的引擎(2026-07-29:活动不再分范式,内部实现都是 Loop) */
readonly engine: ParadigmEngine;
readonly memory: MemoryRecorder;
/** 落地收敛 —— 放行通过的那一刻才碰事实 */
readonly landing: LandingGateway;
@@ -72,10 +72,6 @@ export function createTaskService(deps: TaskServiceDeps): TaskService {
return task;
}
function engineOf(task: Task): ParadigmEngine {
return deps.engines[task.paradigm];
}
/** Harness 在环外先判:越限即熔断并挂起,处置归治理台 */
async function harnessCheck(task: Task, stallAfter: string) {
const lastProgress = (await deps.store.lastProgressAt(task.id)) ?? new Date(task.startedAt);
@@ -103,7 +99,6 @@ export function createTaskService(deps: TaskServiceDeps): TaskService {
const task: Task = {
id: taskIdSchema.parse(deps.ids.next()),
activityId: input.activityId,
paradigm: loaded.paradigm,
...(input.workflowId === undefined ? {} : { workflowId: input.workflowId }),
...(input.nodeId === undefined ? {} : { nodeId: input.nodeId }),
ownerId: input.ownerId,
@@ -123,7 +118,7 @@ export function createTaskService(deps: TaskServiceDeps): TaskService {
// message 计入初始观察、全程留痕;**不得扩大授权边界**
detail: { data: input.data, message: input.message, envelope: loaded.envelope },
});
await engineOf(task).load(task.id, loaded.businessModel);
await deps.engine.load(task.id, loaded.businessModel);
await deps.interaction.setPhase(task.id, 'active');
if (missing.length > 0) {
await deps.memory.append({
@@ -169,7 +164,7 @@ export function createTaskService(deps: TaskServiceDeps): TaskService {
}
const startedAt = deps.clock.now().getTime();
const outcome = await engineOf(task).advance(taskId);
const outcome = await deps.engine.advance(taskId);
// 步计量:loop 的一次决策循环 / flow 的一个声明步。
// 活跃时长按**这一拍真正花掉的时间**计 —— 挂起等人的时间不算活跃,那是世界等待。
const used = await deps.store.addConsumption(taskId, {
@@ -303,7 +298,7 @@ export function createTaskService(deps: TaskServiceDeps): TaskService {
}
}
await engineOf(task).onEvent(taskId, event);
await deps.engine.onEvent(taskId, event);
await deps.store.markProgress(taskId, deps.clock.now());
return this.tick(taskId);
},
+1 -2
View File
@@ -1,7 +1,7 @@
/**
* module: task —— 本模块要外界提供的能力(编码规范 §10.1 / §5.3)。
*/
import type { BudgetCaps, Paradigm, StaffId, TaskId, TerminalState } from '@contracts';
import type { BudgetCaps, StaffId, TaskId, TerminalState } from '@contracts';
import type { Consumption } from './internal/domain/harness.ts';
import type { LandedFacts, Task } from './contract.ts';
@@ -21,7 +21,6 @@ export type TaskStore = {
/** 活动装载 —— app 注入 `activity-registry`(L4) */
export type ActivityLoader = {
load(activityId: string): Promise<{
paradigm: Paradigm;
caps: BudgetCaps;
envelope: Record<string, unknown>;
/** 输入 data 的 JSON Schema —— 缺项不得启动 */