基线:鉴权会话+工作组+工作台三区修复+编排工作区+产品设计文档
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -192,6 +192,19 @@ export const listProcessPackagesRoute = {
|
||||
} as const satisfies RouteContract;
|
||||
|
||||
/** 画图即校验 —— 与前端画布同源的闭合校验 */
|
||||
/**
|
||||
* 保存流程包(编排工作区的落笔)。
|
||||
* **保存 ≠ 发布**:落的是这一版的定义(草稿可反复存);已发布版本不可回改 ——
|
||||
* 后端按 (id, version, state) 判:published 版拒存,提示另起新版本。
|
||||
* 返回连同闭合报告:画图即校验,每次落笔都知道离「可发布」还差几条。
|
||||
*/
|
||||
export const saveProcessRoute = {
|
||||
method: 'PUT',
|
||||
path: '/api/gov/process',
|
||||
body: z.object({ pkg: processPackageSchema }),
|
||||
output: z.object({ pkg: processPackageSchema, report: closureReportSchema }),
|
||||
} as const satisfies RouteContract;
|
||||
|
||||
export const validateProcessRoute = {
|
||||
method: 'POST',
|
||||
path: '/api/gov/process/validate',
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
import type { z } from 'zod';
|
||||
|
||||
export const HTTP_METHODS = ['GET', 'POST', 'PATCH', 'DELETE'] as const;
|
||||
export const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] as const;
|
||||
export type HttpMethod = (typeof HTTP_METHODS)[number];
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,16 +5,35 @@
|
||||
import { z } from 'zod';
|
||||
import type { RouteContract } from './http.ts';
|
||||
import { sessionSchema } from '../org.ts';
|
||||
import { chatMessageSchema, deliverableSchema, formFieldSchema, stageSchema, viewBlockSchema, workOrderSchema } from '../card.ts';
|
||||
import { chatMessageSchema, deliverableSchema, fileDocSchema, formFieldSchema, stageSchema, viewBlockSchema, workOrderSchema } from '../card.ts';
|
||||
import { attributionSideSchema, taskRuntimeStateSchema, terminalStateSchema, workflowStateSchema } from '../terminal.ts';
|
||||
import { staffIdSchema, taskIdSchema } from '../id.ts';
|
||||
|
||||
/** 我是谁 —— 无会话即 401,前端据此落登录页(ADR-010) */
|
||||
export const sessionRoute = {
|
||||
method: 'GET',
|
||||
path: '/api/session',
|
||||
output: sessionSchema,
|
||||
} as const satisfies RouteContract;
|
||||
|
||||
/**
|
||||
* 登录 —— 单组织 cookie session(编码规范 §4 / ADR-010)。
|
||||
* 成功即种 `jcai_session`(HttpOnly);失败只回一句话,不区分账号不存在还是口令错。
|
||||
*/
|
||||
export const loginRoute = {
|
||||
method: 'POST',
|
||||
path: '/api/login',
|
||||
body: z.object({ account: z.string().min(1), password: z.string().min(1) }),
|
||||
output: sessionSchema,
|
||||
} as const satisfies RouteContract;
|
||||
|
||||
/** 登出 —— 服务端会话立刻作废,不等 cookie 到期 */
|
||||
export const logoutRoute = {
|
||||
method: 'POST',
|
||||
path: '/api/logout',
|
||||
output: z.object({ ok: z.boolean() }),
|
||||
} as const satisfies RouteContract;
|
||||
|
||||
export const healthRoute = {
|
||||
method: 'GET',
|
||||
path: '/api/health',
|
||||
@@ -49,10 +68,29 @@ export const listTasksRoute = {
|
||||
output: z.object({ tasks: z.array(taskCardSchema) }),
|
||||
} as const satisfies RouteContract;
|
||||
|
||||
/**
|
||||
* 流程链上的一环 —— 「一条 = 一个活动的一次运行」之外,人还要看见自己在整条流程的哪儿。
|
||||
* 别人负责的节点也在链上(只读),但不进任务清单。
|
||||
*/
|
||||
export const chainNodeSchema = z.object({
|
||||
nodeId: z.string(),
|
||||
label: z.string(),
|
||||
kind: z.enum(['activity', 'approval']),
|
||||
/** 谁做:姓名 · 岗位;岗位未落到具体人时只给岗位名 */
|
||||
ownerName: z.string(),
|
||||
ownerRole: z.string(),
|
||||
state: z.enum(['done', 'current', 'todo']),
|
||||
});
|
||||
export type ChainNode = z.infer<typeof chainNodeSchema>;
|
||||
|
||||
/** 任务详情 —— 工作区与对话区的服务端投影(前端零过程状态,刷新即重建) */
|
||||
export const taskDetailSchema = z.object({
|
||||
card: taskCardSchema,
|
||||
goal: z.string(),
|
||||
/** 本单负责人 —— 标题区落款与报告署名 */
|
||||
owner: z.object({ name: z.string(), role: z.string() }),
|
||||
/** 整条流程的节点链(含他人节点,只读) */
|
||||
chain: z.array(chainNodeSchema),
|
||||
budget: z.object({
|
||||
steps: z.string(),
|
||||
tokens: z.string(),
|
||||
@@ -61,6 +99,18 @@ export const taskDetailSchema = z.object({
|
||||
}),
|
||||
stages: z.array(stageSchema),
|
||||
deliverables: z.array(deliverableSchema),
|
||||
/**
|
||||
* 工作成果 = 一组**文件**,大部分是 HTML 汇报报告。
|
||||
* 一行一份、点进去看;文件本体随投影一并下发,前端不另取。
|
||||
*/
|
||||
results: z.array(
|
||||
z.object({
|
||||
label: z.string(),
|
||||
desc: z.string(),
|
||||
at: z.string().optional(),
|
||||
file: fileDocSchema,
|
||||
}),
|
||||
),
|
||||
workOrders: z.array(workOrderSchema),
|
||||
chat: z.array(chatMessageSchema),
|
||||
view: z.array(viewBlockSchema),
|
||||
@@ -94,6 +144,17 @@ export const replyWorkOrderRoute = {
|
||||
output: z.object({ ok: z.boolean(), producesCompletion: z.boolean() }),
|
||||
} as const satisfies RouteContract;
|
||||
|
||||
/**
|
||||
* 对话卡回传(决策 / 补全 / 评价)—— 与后端回传校验**同 schema**。
|
||||
* 对话流是一切回环输入的唯一入口(OS 4.1):卡就长在对话流上,答也就地答。
|
||||
*/
|
||||
export const replyChatCardRoute = {
|
||||
method: 'POST',
|
||||
path: '/api/desk/chat-cards/reply',
|
||||
body: z.object({ reply: z.record(z.string(), z.unknown()) }),
|
||||
output: z.object({ ok: z.boolean() }),
|
||||
} as const satisfies RouteContract;
|
||||
|
||||
/** 负责人的自由输入 —— 人事件的第二形态 */
|
||||
export const sayRoute = {
|
||||
method: 'POST',
|
||||
@@ -319,3 +380,41 @@ export const decideApprovalRoute = {
|
||||
} as const satisfies RouteContract;
|
||||
|
||||
export type ListApprovalsForMeOutput = z.infer<typeof listApprovalsForMeRoute.output>;
|
||||
|
||||
/* ── 登记触发:人工起单的统一形态(OS 3.1) ─────────────────────────── */
|
||||
|
||||
/**
|
||||
* 「登记触发 —— 人工起单的统一形态:登记人选择流程包、按调用接口 schema 填写输入
|
||||
* (表单由 schema 自动渲染)、提交即起单。」**登记人即来源**。
|
||||
*/
|
||||
export const listRegistrationTriggersRoute = {
|
||||
method: 'GET',
|
||||
path: '/api/triggers/registration',
|
||||
output: z.object({
|
||||
triggers: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
processPackageId: z.string(),
|
||||
enabled: z.boolean(),
|
||||
/** 调用接口的入参 schema —— 登记表单据此渲染 */
|
||||
inputSchema: z.record(z.string(), z.unknown()),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
} as const satisfies RouteContract;
|
||||
|
||||
export const fireRegistrationRoute = {
|
||||
method: 'POST',
|
||||
path: '/api/triggers/:triggerId/register',
|
||||
params: z.object({ triggerId: z.string() }),
|
||||
body: z.object({ data: z.record(z.string(), z.unknown()) }),
|
||||
output: z.object({
|
||||
ok: z.boolean(),
|
||||
workflowId: z.string().optional(),
|
||||
skipped: z.string().optional(),
|
||||
missing: z.array(z.string()).optional(),
|
||||
}),
|
||||
} as const satisfies RouteContract;
|
||||
|
||||
export type ListRegistrationTriggersOutput = z.infer<typeof listRegistrationTriggersRoute.output>;
|
||||
|
||||
@@ -73,6 +73,11 @@ export type FormField = z.infer<typeof formFieldSchema>;
|
||||
/* ── 视图块:视图区的结构化呈现(活动推送,只读) ────────────────────── */
|
||||
|
||||
const fileDocSchema = z.discriminatedUnion('kind', [
|
||||
/**
|
||||
* HTML 汇报报告 —— 成果文件的**主要形态**:活动按当下局面组织的一屏。
|
||||
* 渲染一律经 `@ui` 的 `ViewContainer` 沙箱,无脚本执行权(AM 07)。
|
||||
*/
|
||||
z.object({ kind: z.literal('html'), name: z.string(), html: z.string() }),
|
||||
z.object({ kind: z.literal('markdown'), name: z.string(), md: z.string() }),
|
||||
z.object({
|
||||
kind: z.literal('excel'),
|
||||
@@ -243,11 +248,31 @@ export const WORK_ORDER_MODE_LABEL: Readonly<Record<WorkOrderMode, string>> = {
|
||||
acceptance: '验收',
|
||||
};
|
||||
|
||||
/**
|
||||
* 办结口径:执行件只有「已提交」,验收件是放行三选一 —— 与回传协议同一套标识符。
|
||||
* 界面文案由字典给出(与原型逐字一致),不在组件里散落中文。
|
||||
*/
|
||||
export const WORK_ORDER_RESULTS = ['submitted', 'pass', 'redo', 'revise'] as const;
|
||||
export const workOrderResultSchema = z.enum(WORK_ORDER_RESULTS);
|
||||
export type WorkOrderResult = z.infer<typeof workOrderResultSchema>;
|
||||
|
||||
export const WORK_ORDER_RESULT_LABEL: Readonly<Record<WorkOrderResult, string>> = {
|
||||
submitted: '已提交',
|
||||
pass: '验收通过',
|
||||
redo: '驳回重做',
|
||||
revise: '驳回迭代',
|
||||
};
|
||||
|
||||
/** 驳回两式 —— 退回即重来,判定处只此一处 */
|
||||
export function isRejection(result: WorkOrderResult): boolean {
|
||||
return result === 'redo' || result === 'revise';
|
||||
}
|
||||
|
||||
/** 完成回传:谁、何时、答了什么(回传即留痕) */
|
||||
export const workDoneSchema = z.object({
|
||||
at: z.iso.datetime(),
|
||||
by: staffIdSchema,
|
||||
result: z.string(),
|
||||
result: workOrderResultSchema,
|
||||
values: z.record(z.string(), z.string()).optional(),
|
||||
});
|
||||
export type WorkDone = z.infer<typeof workDoneSchema>;
|
||||
|
||||
@@ -107,3 +107,25 @@ export const sessionSchema = z.object({
|
||||
grants: z.array(grantSchema),
|
||||
});
|
||||
export type Session = z.infer<typeof sessionSchema>;
|
||||
|
||||
/* ── 工作组(ADR-011) ────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* 工作组 —— 一组「岗位 → 员工」的绑定,起单时装载、随工作流走。
|
||||
*
|
||||
* **不是第五种负责人策略**(OS 03「策略封闭四选」不动):
|
||||
* 四策略回答的是「这个岗位该派给谁」,工作组直接给出了答案,策略于是无从发问。
|
||||
* 键是**岗位 code**(BD / PM / FAE …),与流程包 `owner.position` 同一口径。
|
||||
*/
|
||||
export const workGroupSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
/** 组归谁 —— 起单时按来源人取其组 */
|
||||
ownerId: staffIdSchema,
|
||||
members: z.record(z.string().min(1), staffIdSchema),
|
||||
});
|
||||
export type WorkGroup = z.infer<typeof workGroupSchema>;
|
||||
|
||||
/** 装进工作流的**快照**:改组不改已起的单(留痕不被改写) */
|
||||
export const workGroupBindingSchema = z.record(z.string().min(1), staffIdSchema);
|
||||
export type WorkGroupBinding = z.infer<typeof workGroupBindingSchema>;
|
||||
|
||||
@@ -71,6 +71,8 @@ export const edgeSchema = z.object({
|
||||
mapping: z.array(fieldMappingSchema).default([]),
|
||||
/** 边标签:画布上的出口名(如「TYPE-A」「通过」) */
|
||||
label: z.string().optional(),
|
||||
/** 边标签色调(呈现):ok = 绿(通过类) / bad = 红(驳回类) */
|
||||
tone: z.enum(['ok', 'bad']).optional(),
|
||||
/** 回边标记:人裁决后重开新任务实例(同节点、新任务 ID、留痕关联) */
|
||||
reopen: z.boolean().default(false),
|
||||
});
|
||||
@@ -117,8 +119,20 @@ export const GATEWAY_KIND_LABEL: Readonly<Record<GatewayKind, string>> = {
|
||||
const nodeBase = {
|
||||
id: nodeIdSchema,
|
||||
label: z.string().min(1),
|
||||
/** 画布坐标 —— 布局引擎的输入;不影响语义 */
|
||||
layout: z.object({ row: z.number().int(), col: z.number().int() }).optional(),
|
||||
/** 画布上的第二行小字(如「照谱执行 / 临场演绎」)—— 呈现注记,不影响语义 */
|
||||
sub: z.string().optional(),
|
||||
/**
|
||||
* 画布坐标 —— 布局引擎的输入;不影响语义。
|
||||
* row/col = 编译布局的行列;x/y = **手绘几何**(编辑器保存后,手绘成为呈现真相)。
|
||||
*/
|
||||
layout: z
|
||||
.object({
|
||||
row: z.number().int(),
|
||||
col: z.number().int(),
|
||||
x: z.number().optional(),
|
||||
y: z.number().optional(),
|
||||
})
|
||||
.optional(),
|
||||
};
|
||||
|
||||
export const processNodeSchema = z.discriminatedUnion('kind', [
|
||||
@@ -134,6 +148,13 @@ export const processNodeSchema = z.discriminatedUnion('kind', [
|
||||
kind: z.literal('activity'),
|
||||
/** 以声明入流:引用活动库中已安装的活动 */
|
||||
activityId: activityIdSchema,
|
||||
/**
|
||||
* 契约注记(编排定契约,AM 03):这个节点**声明**吃什么、吐什么。
|
||||
* 是「导出依赖文件」的骨架 —— 开发者对着声明在系统外开发活动;
|
||||
* 闭合校验的机检仍以边映射与活动契约面为准,注记是给人读的那一层。
|
||||
*/
|
||||
in: z.array(z.string()).optional(),
|
||||
out: z.array(z.string()).optional(),
|
||||
/** 版本约束:留空 = 装配时取最新生效版 */
|
||||
activityVersion: z.string().optional(),
|
||||
owner: ownerSpecSchema,
|
||||
@@ -160,6 +181,8 @@ export const processNodeSchema = z.discriminatedUnion('kind', [
|
||||
gateway: gatewayKindSchema,
|
||||
/** 分叉 / 汇合 */
|
||||
mode: z.enum(['fork', 'join']),
|
||||
/** 判据说明(人读):条件本体仍在出边的 S-FEEL 上,这里是「为什么这么分」 */
|
||||
basis: z.string().optional(),
|
||||
}),
|
||||
]);
|
||||
export type ProcessNode = z.infer<typeof processNodeSchema>;
|
||||
@@ -190,6 +213,12 @@ export const processPackageSchema = z.object({
|
||||
/** 流程变量声明:边映射的 workflow-var 来源 */
|
||||
variables: z.array(z.object({ name: z.string().min(1), schema: jsonSchemaSchema })).default([]),
|
||||
description: z.string().optional(),
|
||||
/**
|
||||
* 手绘图存证(可空):编辑器保存的 FlowGraph 原样落此 —— **手绘几何即呈现真相**。
|
||||
* 语义(节点/边/映射)永远以上面的 nodes/edges 为准,流转判断不读它;
|
||||
* 它只承载语义装不下的画布事实:卡点、行分组、锚定、折线点。
|
||||
*/
|
||||
diagram: z.record(z.string(), z.unknown()).optional(),
|
||||
});
|
||||
export type ProcessPackage = z.infer<typeof processPackageSchema>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user