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:
xcs
2026-08-27 23:20:15 +08:00
co-authored by Claude Fable 5
parent 75553371b0
commit 0923fbfe4d
34 changed files with 5137 additions and 163 deletions
+166
View File
@@ -0,0 +1,166 @@
/**
* 运营中心 · 活动模块的路由契约(`docs/API文档-活动模块.md`)。
*
* 全部 `access: 'ops'` —— 活动区落在运营中心,与工作流 / 任务看板同端。
* `/api/gov/*` 的写路由由 app 中间件自动广播 `{kind:'system', id:'gov'}`。
* 设计端的活动库(包版本 / 资产版本 / 装入)在 `/api/gov/activity-packages`(governance.ts),
* `/api/gov/activities/*` 整个归运营端(2026-08-27 用户定)。
*/
import { z } from 'zod';
import type { RouteContract } from './http.ts';
import {
activityDetailSchema,
activityStatusSchema,
activitySummarySchema,
instanceDetailSchema,
instanceListSchema,
instanceStateSchema,
launchInputSchema,
launchParamsSchema,
} from '../activity-ops.ts';
const activityParams = z.object({ activityId: z.string().min(1) });
const instanceParams = activityParams.extend({ instanceId: z.string().min(1) });
/** 启动日期段,`YYYY-MM-DD`,含两端整日 */
const day = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, '日期要写成 YYYY-MM-DD');
/* ── L1 ── */
export const listOpsActivitiesRoute = {
method: 'GET',
path: '/api/gov/activities',
access: 'ops',
/** 不带 status = 全量;量小不分页 */
query: z.object({ status: activityStatusSchema.optional() }),
output: z.object({ activities: z.array(activitySummarySchema) }),
} as const satisfies RouteContract;
/* ── L2 ── */
export const getOpsActivityRoute = {
method: 'GET',
path: '/api/gov/activities/:activityId',
access: 'ops',
params: activityParams,
output: activityDetailSchema,
} as const satisfies RouteContract;
/**
* 沙箱要渲染的活动视图 —— 处理器回 `text/html`(不是 JSON),`output` 只是形式上的字符串。
* iframe 带不了 Authorization 头,走票据 `?ticket=`(GET,中间件已认)。404 = 该活动没配视图。
*/
export const opsActivityViewRoute = {
method: 'GET',
path: '/api/gov/activities/:activityId/view',
access: 'ops',
params: activityParams,
output: z.string(),
} as const satisfies RouteContract;
export const listOpsInstancesRoute = {
method: 'GET',
path: '/api/gov/activities/:activityId/instances',
access: 'ops',
params: activityParams,
query: z.object({
/** 实例号模糊搜(对 `no` 与 `id` 的子串) */
id: z.string().optional(),
ownerId: z.string().optional(),
state: instanceStateSchema.optional(),
startedFrom: day.optional(),
startedTo: day.optional(),
page: z.coerce.number().int().min(1).default(1),
/** 默认 8(与前端单页一致),上限 50 */
size: z.coerce.number().int().min(1).max(50).default(8),
}),
output: instanceListSchema,
} as const satisfies RouteContract;
/* ── 启动 ── */
export const opsLaunchParamsRoute = {
method: 'GET',
path: '/api/gov/activities/:activityId/launch-params',
access: 'ops',
params: activityParams,
output: launchParamsSchema,
} as const satisfies RouteContract;
/**
* 启动活动 = 创建实例。同一 `idempotencyKey` 重放返回第一次创建的实例;
* 412 活动已禁用;400 缺必填 / 类型不符 / select 值不在候选 / 责任人不在职;409 同键正在创建中。
*/
export const launchOpsInstanceRoute = {
method: 'POST',
path: '/api/gov/activities/:activityId/instances',
access: 'ops',
params: activityParams,
body: launchInputSchema,
output: instanceDetailSchema,
} as const satisfies RouteContract;
/* ── L3 ── */
export const getOpsInstanceRoute = {
method: 'GET',
path: '/api/gov/activities/:activityId/instances/:instanceId',
access: 'ops',
params: instanceParams,
output: instanceDetailSchema,
} as const satisfies RouteContract;
/**
* 实例文件:默认 JSON + base64(与 `GET /api/gov/upload/:id` 同形);`?raw=1` 直出字节
* (RFC 5987 文件名),配 `?ticket=` 给 `<a href download>` 用。
*/
export const opsInstanceFileRoute = {
method: 'GET',
path: '/api/gov/activities/:activityId/instances/:instanceId/files/:io/:index',
access: 'ops',
params: instanceParams.extend({ io: z.enum(['input', 'output']), index: z.coerce.number().int().min(0) }),
query: z.object({ raw: z.string().optional() }),
output: z.object({ name: z.string(), contentType: z.string(), dataBase64: z.string() }),
} as const satisfies RouteContract;
/* ── 禁用 / 激活 / 终止 ── */
/** 任何时刻都可禁用:只拦新启动,在途实例继续跑完;已禁用再禁用 = 幂等 200 */
export const disableOpsActivityRoute = {
method: 'POST',
path: '/api/gov/activities/:activityId/disable',
access: 'ops',
params: activityParams,
output: activityDetailSchema,
} as const satisfies RouteContract;
export const activateOpsActivityRoute = {
method: 'POST',
path: '/api/gov/activities/:activityId/activate',
access: 'ops',
params: activityParams,
output: activityDetailSchema,
} as const satisfies RouteContract;
/** body 空;→ `state='failed'` + `reason.kind='terminated'`;412 实例已终态 */
export const terminateOpsInstanceRoute = {
method: 'POST',
path: '/api/gov/activities/:activityId/instances/:instanceId/terminate',
access: 'ops',
params: instanceParams,
output: instanceDetailSchema,
} as const satisfies RouteContract;
/** 全部 11 条 —— 文档总表与覆盖测试按它数 */
export const ACTIVITY_OPS_ROUTES: readonly RouteContract[] = [
listOpsActivitiesRoute,
getOpsActivityRoute,
opsActivityViewRoute,
listOpsInstancesRoute,
opsLaunchParamsRoute,
launchOpsInstanceRoute,
getOpsInstanceRoute,
opsInstanceFileRoute,
disableOpsActivityRoute,
activateOpsActivityRoute,
terminateOpsInstanceRoute,
];
+5 -2
View File
@@ -537,10 +537,12 @@ export const saveWorkGroupRoute = {
* 安装两审在服务端跑:静态审读清单(工具契约在否、模型名绑没绑、模版有没有脚本、
* 声明与已物化的台账冲不冲),回放审重演历史(首装无历史即免,以样例集替代)。
* **恒为新增、版本并存**:装入不覆盖旧版本,在途任务按其启动时版本跑完。
* **路径(2026-08-27)**:`/api/gov/activity-packages` —— 从前叫 `/api/gov/activities`;那个前缀整个让给了运营端
* 活动模块(L1/L2/L3,`api/activity-ops.ts`),这里管的是**包**(版本 / 资产 / 装入),名副其实。
*/
export const installActivityRoute = {
method: 'POST',
path: '/api/gov/activities',
path: '/api/gov/activity-packages',
access: 'design',
body: z.object({
/** 清单六区:身份 / 七件契约 / 声明 / 资产初始版 / 样例集 / 代码入口 */
@@ -606,9 +608,10 @@ export const registerToolRoute = {
/* ── 活动库管理:两维分类 × 包版本 × 资产版本 ─────────────────────────── */
/** 路径同 `installActivityRoute` 的说明:`/api/gov/activity-packages`(2026-08-27 让位给运营端活动模块) */
export const listActivitiesRoute = {
method: 'GET',
path: '/api/gov/activities',
path: '/api/gov/activity-packages',
access: 'design',
output: z.object({
activities: z.array(
+1
View File
@@ -4,3 +4,4 @@ export * from './governance.ts';
export * from './org-directory.ts';
export * from './ingest.ts';
export * from './mail.ts';
export * from './activity-ops.ts';