范式退役:活动只剩一种、只跑 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
@@ -26,6 +26,8 @@ export interface FlowNode {
* (蛇形折行与尺寸计算),不表示它在模型上属于活动。
*/
flavor?: "case" | "approval" | undefined;
/** 仅审批点:引哪张审批模板(活动库 · 审批活动)—— 不引即旧行为:审批卡把载荷全摊开。 */
templateId?: string | undefined;
/** Case 节点引用的库案例 id(cases[].id):L2 只是引用,下钻编辑共享库案例。 */
caseRef?: string;
label: string;
@@ -67,6 +67,12 @@ export function mergeGraphIntoPackage(
// 审批人不入编排(运营时分配):功能属性只剩驳回走向 / 催办 / 委托消息
// 驳回走向是声明式功能属性,画布不改它 —— 没有旧声明就先「终止」,激活前在属性里补
reject: priorAp?.reject ?? { kind: 'terminate' as const },
// 审批模板:属性卡刚选的优先,其次旧声明 —— 画布上看不见它,但存盘不能丢
...(n.templateId !== undefined && n.templateId !== ''
? { templateId: n.templateId }
: priorAp?.templateId === undefined
? {}
: { templateId: priorAp.templateId }),
...(priorAp?.nudge === undefined ? {} : { nudge: priorAp.nudge }),
...(priorAp?.message === undefined ? {} : { message: priorAp.message }),
};
+1 -1
View File
@@ -91,7 +91,7 @@ export function packageToGraph(pkg: ProcessPackage, marks: readonly InstanceMark
};
if (n.kind === 'approval') {
// approval **不是一种活动**:它挂在 kind:"activity" 下仅因共用同一套方框几何
return { ...base, flavor: 'approval' };
return { ...base, flavor: 'approval', ...(n.templateId === undefined ? {} : { templateId: n.templateId }) };
}
return {
...base,
+10
View File
@@ -25,12 +25,14 @@ import type {
GovWorkflowOutput,
ProcessPackage,
} from '@contracts';
import type { ApprovalTemplate } from '@contracts';
import {
browseLedgerRoute,
govMetricsRoute,
govTaskLogRoute,
govWorkflowRoute,
listActivitiesRoute,
listApprovalTemplatesRoute,
listActivityMemoryRoute,
listApprovalsRoute,
listChannelsRoute,
@@ -118,6 +120,14 @@ export const useGovTriggers = (): UseQueryResult<GovTriggersOutput> =>
export const useGovActivities = (): UseQueryResult<GovActivities> =>
useQuery({ queryKey: ['gov', 'activities'], queryFn: () => call<GovActivities>(listActivitiesRoute) });
/** 审批模板(审批活动)—— 活动库的第二格、编排里审批点的选项 */
export type GovApprovalTemplates = { templates: ReadonlyArray<ApprovalTemplate & { usedBy: ReadonlyArray<{ packageId: string; nodeId: string }> }> };
export const useGovApprovalTemplates = (): UseQueryResult<GovApprovalTemplates> =>
useQuery({
queryKey: ['gov', 'approval-templates'],
queryFn: () => call<GovApprovalTemplates>(listApprovalTemplatesRoute),
});
export const useGovTools = (): UseQueryResult<GovTools> =>
useQuery({ queryKey: ['gov', 'tools'], queryFn: () => call<GovTools>(listToolsRoute) });
+1 -1
View File
@@ -119,7 +119,7 @@ export const ZM: Readonly<Record<GovZone, { title: string; desc: string; accent:
title: '活动库',
icon: 'boxes',
accent: 'linear-gradient(140deg,#0aa294,#0a6a64)',
desc: '已安装活动的资产货架:活动是任务的定义——编排引用一个活动,运行时就长出一个任务。审批点与网关是系统内置要素,零开发、不入库。业务分类 × 活动类型两维,每活动两条版本序列。',
desc: '一区两格:装进来的活动(系统外开发、经安装通道入库,一个活动 = 一类任务的定义)与配出来的审批活动(审批零开发,这里配的是模板:审批人看什么、怎么催、说什么)。活动不再分标准/案例——业务分类是唯一的一维;每活动两条版本序列:包版本走安装两审,资产版本走进化管线。',
},
tools: {
title: '工具箱',
+1 -6
View File
@@ -2,7 +2,7 @@
* 状态 → 色调 / 图标字典 —— 自原型 `Governance.tsx` 第 92–116 行平移。
* 原型用中文状态名做键;我们的状态名是契约里的枚举,故键换成枚举、标签仍取契约的 `*_LABEL`。
*/
import type { Paradigm, TaskRuntimeState, TerminalState, WorkflowState } from '@contracts';
import type { TaskRuntimeState, TerminalState, WorkflowState } from '@contracts';
export type ToneIco = { tone: string; ico: string };
@@ -32,11 +32,6 @@ export const TERMINAL_ST: Readonly<Record<TerminalState, ToneIco>> = {
terminated: { tone: 'mute', ico: 'power' },
};
export const PARA: Readonly<Record<Paradigm, ToneIco & { label: string; full: string }>> = {
loop: { tone: 'collab', ico: 'repeat', label: '案例 · loop', full: '临场演绎,调度权归 AI' },
flow: { tone: 'auto', ico: 'git-commit', label: '标准 · flow', full: '照谱执行,调度权归系统' },
};
export const APR_ST: Readonly<Record<'pending' | 'passed' | 'rejected', ToneIco & { label: string }>> = {
pending: { tone: 'warn', ico: 'hourglass', label: '待审批' },
passed: { tone: 'ok', ico: 'circle-check', label: '已通过' },