Archived
feat(ops): 运营中心 · 活动模块接口落地 —— L1/L2/L3 十一条(全 ops)+ 设计端活动库两条改名让位
`apps/api/src/app/activity-ops/*`(service / store / instance-state / instance-steps / launch-params / summaries / view-html / view-scope)+ `routes/activities.ts`;契约 `activity-ops.ts` / `api/activity-ops.ts` (与队友按文档补的那版 43 个导出同名,多 INSTANCE_REASON_KINDS / STEP_RESULTS / launchOptionSchema); 迁移 0035_activity_ops;《API文档-活动模块》按契约回写。 设计端活动库 GET/POST `/api/gov/activities` 改名 `/api/gov/activity-packages`,`/api/gov/activities/*` 整个让给运营端(前端 import 路由对象,零改动);activity-registry CONTRACT.md 与路由总表同步。 2026-08-27 另一会话做的,一直留在工作树没入库;与同批另两条提交互相依赖,以合并提交为验证单位。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0167qWjeyBwvfdGovd7FWbCu
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
/** L3 动作过程记录:计划项 × 运行态 × 引擎痕 */
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { parseCmmn } from '@modules/loop-engine';
|
||||
import type { ItemRuntime } from '@modules/loop-engine';
|
||||
import { stepsOf } from '../activity-ops/instance-steps.ts';
|
||||
|
||||
const XML = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL" id="d">
|
||||
<case id="c" name="试">
|
||||
<casePlanModel id="p" name="p">
|
||||
<humanTask id="S1" name="归一"/>
|
||||
<humanTask id="S2" name="交人确认">
|
||||
<itemControl><manualActivationRule id="m_s2"/></itemControl>
|
||||
</humanTask>
|
||||
<humanTask id="S3" name="放行"/>
|
||||
<milestone id="M1" name="里程碑"/>
|
||||
</casePlanModel>
|
||||
</case>
|
||||
</definitions>`;
|
||||
|
||||
const rt = (states: Record<string, ItemRuntime['state']>): ItemRuntime[] => Object.entries(states).map(([id, state]) => ({ id, state, instance: 1 }));
|
||||
const line = (title: string, at: string) => ({ kind: 'engine-transition', title, at });
|
||||
const intents = new Map([['S1', '把来件归一成台账行'], ['S2', '派一张执行单交人']]);
|
||||
|
||||
describe('stepsOf', () => {
|
||||
const items = parseCmmn(XML).items;
|
||||
|
||||
it('只摆任务项(里程碑不算),按文档序;人工激活的项是人办的;时刻与用时来自引擎痕', () => {
|
||||
const steps = stepsOf({
|
||||
items,
|
||||
runtimeItems: rt({ S1: 'Completed', S2: 'Active', S3: 'Available' }),
|
||||
intents,
|
||||
history: [
|
||||
line('开始办:归一', '2026-08-27T02:00:00.000Z'),
|
||||
line('完成:归一', '2026-08-27T02:03:00.000Z'),
|
||||
line('开始办:交人确认', '2026-08-27T02:03:30.000Z'),
|
||||
],
|
||||
state: 'running',
|
||||
ownerName: '柴浩',
|
||||
llm: 'deepseek-v4-flash',
|
||||
});
|
||||
expect(steps.map((s) => [s.seq, s.name, s.by, s.byName, s.res])).toEqual([
|
||||
[1, '归一', 'agent', 'deepseek-v4-flash', 'done'],
|
||||
[2, '交人确认', 'human', '柴浩', 'current'],
|
||||
[3, '放行', 'agent', 'deepseek-v4-flash', 'pending'],
|
||||
]);
|
||||
expect(steps[0]).toMatchObject({ at: '2026-08-27T02:00:00.000Z', durationMs: 180_000, note: '把来件归一成台账行' });
|
||||
expect(steps[1]).toMatchObject({ at: '2026-08-27T02:03:30.000Z', durationMs: null, note: '派一张执行单交人' });
|
||||
expect(steps[2]).toMatchObject({ at: null, durationMs: null, note: '' });
|
||||
});
|
||||
|
||||
it('自动收步的项拿收步那一刻当时刻;没有 runtime 的项是 pending', () => {
|
||||
const steps = stepsOf({
|
||||
items,
|
||||
runtimeItems: rt({ S1: 'Completed' }),
|
||||
intents,
|
||||
history: [line('自动收步:S1 的成果已在台账里(rows > 0)—— 不必再问模型', '2026-08-27T02:01:00.000Z')],
|
||||
state: 'running',
|
||||
ownerName: '柴浩',
|
||||
llm: 'm',
|
||||
});
|
||||
expect(steps[0]).toMatchObject({ res: 'done', at: '2026-08-27T02:01:00.000Z', durationMs: null });
|
||||
expect(steps[1]?.res).toBe('pending');
|
||||
});
|
||||
|
||||
it('终止 / 失败:正在办的那一步记 fail;都没开就记第一个没到的;驳回记最后一个已完成的', () => {
|
||||
const facts = { items, intents, history: [], ownerName: '柴浩', llm: 'm' };
|
||||
expect(stepsOf({ ...facts, runtimeItems: rt({ S1: 'Completed', S2: 'Active' }), state: 'failed' }).map((s) => s.res)).toEqual(['done', 'fail', 'pending']);
|
||||
expect(stepsOf({ ...facts, runtimeItems: rt({ S1: 'Terminated', S2: 'Terminated' }), state: 'failed' }).map((s) => s.res)).toEqual(['fail', 'pending', 'pending']);
|
||||
expect(stepsOf({ ...facts, runtimeItems: rt({ S1: 'Completed', S2: 'Completed', S3: 'Completed' }), state: 'rejected' }).map((s) => s.res)).toEqual(['done', 'done', 'fail']);
|
||||
expect(stepsOf({ ...facts, runtimeItems: rt({ S1: 'Failed' }), state: 'running' }).map((s) => s.res)).toEqual(['fail', 'pending', 'pending']);
|
||||
});
|
||||
|
||||
it('重复实例化的项看最新那一实例', () => {
|
||||
const steps = stepsOf({
|
||||
items,
|
||||
runtimeItems: [{ id: 'S2', state: 'Completed', instance: 1 }, { id: 'S2', state: 'Active', instance: 2 }],
|
||||
intents,
|
||||
history: [],
|
||||
state: 'running',
|
||||
ownerName: '柴浩',
|
||||
llm: 'm',
|
||||
});
|
||||
expect(steps[1]?.res).toBe('current');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user