契约:三个端与路由归属(access 必填)、Principal / 角色→端 / 名册同步 schema、退役路由标 @deprecated(ADR-012)

- packages/contracts/src/api/http.ts:新增 SURFACES / surfaceSchema / Surface / SURFACE_LABEL /
  RouteAccess;RouteContract.access 必填;API_ERROR_CODES 增 'gone'。
- packages/contracts/src/org.ts:staffSchema 补 email? / roles(realm 角色快照);新增
  principalSchema/Principal、roleAccessSchema/RoleAccess、rosterSyncOutcomeSchema/RosterSyncOutcome、
  inFlightWorkSchema/InFlightWork;旧岗位授权(grant* / governanceFunction*)与 sessionSchema/Session
  全部标 @deprecated 留一轮(web 编译不受影响,下个迭代删)。
- workbench.ts / governance.ts / ingest.ts:每条既有路由补 access(按所属端/两端共用/退役 public);
  新增 meRoute、authTicketRoute(access:'any')与 syncStaffRoute、setStaffPositionRoute、
  staffInFlightRoute、listRoleAccessRoute、setRoleAccessRoute、deleteRoleAccessRoute(access:'ops')。
- apps/api 的 org-service.ts 两处 staff.insert 补 roles: [](staffSchema 新必填字段,该文件下个
  迭代会整份替换,这里只是让本次 typecheck 过)。
- 新增 packages/contracts/src/__tests__/route-access.test.ts:95 条路由的 access 归属断言。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kAzS35kMnZNt4YGgJXtfw
This commit is contained in:
xcs
2026-08-25 15:54:20 +08:00
co-authored by Claude Opus 5
parent 61f96d5ab2
commit a5c8d4786d
13 changed files with 771 additions and 65 deletions
+79 -6
View File
@@ -3,20 +3,23 @@
* 工作台五要素:任务 / 审批 / 消息 / 纪要 / 汇报 —— 同一份过程事实的五种投影。
*/
import { z } from 'zod';
/** 我是谁 —— 无会话即 401,前端据此落登录页(ADR-010) */
/** @deprecated 随 ADR-012 退役,后端回 410;改用 meRoute */
export declare const sessionRoute: {
readonly method: "GET";
readonly path: "/api/session";
readonly access: "public";
readonly output: z.ZodObject<{
staff: z.ZodObject<{
id: z.core.$ZodBranded<z.ZodString, "StaffId", "out">;
account: z.ZodString;
name: z.ZodString;
email: z.ZodOptional<z.ZodString>;
positionId: z.core.$ZodBranded<z.ZodString, "PositionId", "out">;
state: z.ZodDefault<z.ZodEnum<{
active: "active";
disabled: "disabled";
}>>;
roles: z.ZodDefault<z.ZodArray<z.ZodString>>;
}, z.core.$strip>;
position: z.ZodObject<{
id: z.core.$ZodBranded<z.ZodString, "PositionId", "out">;
@@ -56,13 +59,11 @@ export declare const sessionRoute: {
}, z.core.$strip>>;
}, z.core.$strip>;
};
/**
* 登录 —— 单组织 cookie session(编码规范 §4 / ADR-010)。
* 成功即种 `jcai_session`(HttpOnly);失败只回一句话,不区分账号不存在还是口令错。
*/
/** @deprecated 随 ADR-012 退役,后端回 410;改用 meRoute */
export declare const loginRoute: {
readonly method: "POST";
readonly path: "/api/login";
readonly access: "public";
readonly body: z.ZodObject<{
account: z.ZodString;
password: z.ZodString;
@@ -72,11 +73,13 @@ export declare const loginRoute: {
id: z.core.$ZodBranded<z.ZodString, "StaffId", "out">;
account: z.ZodString;
name: z.ZodString;
email: z.ZodOptional<z.ZodString>;
positionId: z.core.$ZodBranded<z.ZodString, "PositionId", "out">;
state: z.ZodDefault<z.ZodEnum<{
active: "active";
disabled: "disabled";
}>>;
roles: z.ZodDefault<z.ZodArray<z.ZodString>>;
}, z.core.$strip>;
position: z.ZodObject<{
id: z.core.$ZodBranded<z.ZodString, "PositionId", "out">;
@@ -116,10 +119,11 @@ export declare const loginRoute: {
}, z.core.$strip>>;
}, z.core.$strip>;
};
/** 登出 —— 服务端会话立刻作废,不等 cookie 到期 */
/** @deprecated 随 ADR-012 退役,后端回 410;改用 meRoute */
export declare const logoutRoute: {
readonly method: "POST";
readonly path: "/api/logout";
readonly access: "public";
readonly output: z.ZodObject<{
ok: z.ZodBoolean;
}, z.core.$strip>;
@@ -127,6 +131,7 @@ export declare const logoutRoute: {
export declare const healthRoute: {
readonly method: "GET";
readonly path: "/api/health";
readonly access: "public";
readonly output: z.ZodObject<{
ok: z.ZodBoolean;
version: z.ZodString;
@@ -136,6 +141,51 @@ export declare const healthRoute: {
}>;
}, z.core.$strip>;
};
/** 我是谁、我能进哪几个端(ADR-012)—— 中间件解好 principal 放进上下文,这里只回投影 */
export declare const meRoute: {
readonly method: "GET";
readonly path: "/api/me";
readonly access: "any";
readonly output: z.ZodObject<{
staff: z.ZodObject<{
id: z.core.$ZodBranded<z.ZodString, "StaffId", "out">;
account: z.ZodString;
name: z.ZodString;
email: z.ZodOptional<z.ZodString>;
positionId: z.core.$ZodBranded<z.ZodString, "PositionId", "out">;
state: z.ZodDefault<z.ZodEnum<{
active: "active";
disabled: "disabled";
}>>;
roles: z.ZodDefault<z.ZodArray<z.ZodString>>;
}, z.core.$strip>;
position: z.ZodObject<{
id: z.core.$ZodBranded<z.ZodString, "PositionId", "out">;
code: z.ZodString;
name: z.ZodString;
note: z.ZodOptional<z.ZodString>;
}, z.core.$strip>;
roles: z.ZodArray<z.ZodString>;
ends: z.ZodArray<z.ZodEnum<{
design: "design";
ops: "ops";
workbench: "workbench";
}>>;
}, z.core.$strip>;
};
/**
* 60 秒票据(ADR-012 §9.3)—— 只为两条带不了 Authorization 头的路:
* `EventSource('/api/stream')` 与 `<a href="…&raw=1" download>`。GET 上 `?ticket=` 认它。
*/
export declare const authTicketRoute: {
readonly method: "GET";
readonly path: "/api/auth/ticket";
readonly access: "any";
readonly output: z.ZodObject<{
ticket: z.ZodString;
expiresAt: z.ZodString;
}, z.core.$strip>;
};
/** 任务卡 —— 清单里一条 = 一个活动的一次运行,不是一整条工作流 */
export declare const taskCardSchema: z.ZodObject<{
id: z.core.$ZodBranded<z.ZodString, "TaskId", "out">;
@@ -177,6 +227,7 @@ export type TaskCard = z.infer<typeof taskCardSchema>;
export declare const listTasksRoute: {
readonly method: "GET";
readonly path: "/api/desk/tasks";
readonly access: "workbench";
readonly output: z.ZodObject<{
tasks: z.ZodArray<z.ZodObject<{
id: z.core.$ZodBranded<z.ZodString, "TaskId", "out">;
@@ -2138,6 +2189,7 @@ export type TaskDetail = z.infer<typeof taskDetailSchema>;
export declare const taskDetailRoute: {
readonly method: "GET";
readonly path: "/api/desk/tasks/:taskId";
readonly access: "workbench";
readonly params: z.ZodObject<{
taskId: z.core.$ZodBranded<z.ZodString, "TaskId", "out">;
}, z.core.$strip>;
@@ -4043,6 +4095,7 @@ export declare const taskDetailRoute: {
export declare const replyWorkOrderRoute: {
readonly method: "POST";
readonly path: "/api/desk/work-orders/reply";
readonly access: "workbench";
readonly body: z.ZodObject<{
reply: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>;
@@ -4058,6 +4111,7 @@ export declare const replyWorkOrderRoute: {
export declare const replyChatCardRoute: {
readonly method: "POST";
readonly path: "/api/desk/chat-cards/reply";
readonly access: "workbench";
readonly body: z.ZodObject<{
reply: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>;
@@ -4072,6 +4126,7 @@ export declare const replyChatCardRoute: {
export declare const markCommReadRoute: {
readonly method: "POST";
readonly path: "/api/desk/comms/:commId/read";
readonly access: "workbench";
readonly params: z.ZodObject<{
commId: z.ZodString;
}, z.core.$strip>;
@@ -4120,6 +4175,7 @@ export declare const markCommReadRoute: {
export declare const saveTableRoute: {
readonly method: "POST";
readonly path: "/api/desk/tasks/:taskId/table";
readonly access: "workbench";
readonly params: z.ZodObject<{
taskId: z.core.$ZodBranded<z.ZodString, "TaskId", "out">;
}, z.core.$strip>;
@@ -4149,6 +4205,7 @@ export declare const saveTableRoute: {
export declare const openAssetFileRoute: {
readonly method: "GET";
readonly path: "/api/desk/tasks/:taskId/file";
readonly access: "workbench";
readonly params: z.ZodObject<{
taskId: z.core.$ZodBranded<z.ZodString, "TaskId", "out">;
}, z.core.$strip>;
@@ -4199,6 +4256,7 @@ export declare const openAssetFileRoute: {
export declare const sayRoute: {
readonly method: "POST";
readonly path: "/api/desk/tasks/:taskId/say";
readonly access: "workbench";
readonly params: z.ZodObject<{
taskId: z.core.$ZodBranded<z.ZodString, "TaskId", "out">;
}, z.core.$strip>;
@@ -4393,6 +4451,7 @@ export type SystemMessageView = z.infer<typeof systemMessageSchema>;
export declare const listMessagesRoute: {
readonly method: "GET";
readonly path: "/api/desk/messages";
readonly access: "workbench";
readonly output: z.ZodObject<{
messages: z.ZodArray<z.ZodObject<{
id: z.ZodString;
@@ -4448,6 +4507,7 @@ export declare const listMessagesRoute: {
export declare const markMessageReadRoute: {
readonly method: "POST";
readonly path: "/api/desk/messages/:messageId/read";
readonly access: "workbench";
readonly params: z.ZodObject<{
messageId: z.ZodString;
}, z.core.$strip>;
@@ -4459,6 +4519,7 @@ export declare const markMessageReadRoute: {
export declare const answerMessageRoute: {
readonly method: "POST";
readonly path: "/api/desk/messages/:messageId/answer";
readonly access: "workbench";
readonly params: z.ZodObject<{
messageId: z.ZodString;
}, z.core.$strip>;
@@ -4473,6 +4534,7 @@ export declare const answerMessageRoute: {
export declare const confirmTriggerPendingRoute: {
readonly method: "POST";
readonly path: "/api/desk/trigger-pending/:id/confirm";
readonly access: "workbench";
readonly params: z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>;
@@ -4521,6 +4583,7 @@ export type MinuteView = z.infer<typeof minuteSchema>;
export declare const listMinutesRoute: {
readonly method: "GET";
readonly path: "/api/minutes";
readonly access: "workbench";
readonly output: z.ZodObject<{
minutes: z.ZodArray<z.ZodObject<{
id: z.ZodString;
@@ -4550,6 +4613,7 @@ export declare const listMinutesRoute: {
export declare const createMinuteRoute: {
readonly method: "POST";
readonly path: "/api/minutes";
readonly access: "workbench";
readonly body: z.ZodObject<{
title: z.ZodString;
body: z.ZodString;
@@ -4587,6 +4651,7 @@ export declare const createMinuteRoute: {
export declare const appendResolutionRoute: {
readonly method: "POST";
readonly path: "/api/minutes/:minuteId/resolutions";
readonly access: "workbench";
readonly params: z.ZodObject<{
minuteId: z.ZodString;
}, z.core.$strip>;
@@ -4622,6 +4687,7 @@ export declare const appendResolutionRoute: {
export declare const registerResolutionRoute: {
readonly method: "POST";
readonly path: "/api/minutes/resolutions/:resolutionId/register";
readonly access: "workbench";
readonly params: z.ZodObject<{
resolutionId: z.ZodString;
}, z.core.$strip>;
@@ -4704,6 +4770,7 @@ export type ReportView = z.infer<typeof reportSchema>;
export declare const draftReportRoute: {
readonly method: "GET";
readonly path: "/api/reports/:period";
readonly access: "workbench";
readonly params: z.ZodObject<{
period: z.ZodEnum<{
day: "day";
@@ -4759,6 +4826,7 @@ export declare const draftReportRoute: {
export declare const listReportsRoute: {
readonly method: "GET";
readonly path: "/api/reports";
readonly access: "workbench";
readonly output: z.ZodObject<{
reports: z.ZodArray<z.ZodObject<{
id: z.ZodString;
@@ -4809,6 +4877,7 @@ export declare const listReportsRoute: {
export declare const submitReportRoute: {
readonly method: "POST";
readonly path: "/api/reports/:reportId/submit";
readonly access: "workbench";
readonly params: z.ZodObject<{
reportId: z.ZodString;
}, z.core.$strip>;
@@ -4884,6 +4953,7 @@ export type ApprovalItem = z.infer<typeof approvalItemSchema>;
export declare const listApprovalsForMeRoute: {
readonly method: "GET";
readonly path: "/api/approvals";
readonly access: "workbench";
readonly output: z.ZodObject<{
approvals: z.ZodArray<z.ZodObject<{
id: z.ZodString;
@@ -4910,6 +4980,7 @@ export declare const listApprovalsForMeRoute: {
export declare const decideApprovalRoute: {
readonly method: "POST";
readonly path: "/api/approvals/:workflowId/:nodeId/decide";
readonly access: "workbench";
readonly params: z.ZodObject<{
workflowId: z.ZodString;
nodeId: z.ZodString;
@@ -4940,6 +5011,7 @@ export type ListApprovalsForMeOutput = z.infer<typeof listApprovalsForMeRoute.ou
export declare const listRegistrationTriggersRoute: {
readonly method: "GET";
readonly path: "/api/triggers/registration";
readonly access: "workbench";
readonly output: z.ZodObject<{
triggers: z.ZodArray<z.ZodObject<{
id: z.ZodString;
@@ -4953,6 +5025,7 @@ export declare const listRegistrationTriggersRoute: {
export declare const fireRegistrationRoute: {
readonly method: "POST";
readonly path: "/api/triggers/:triggerId/register";
readonly access: "workbench";
readonly params: z.ZodObject<{
triggerId: z.ZodString;
}, z.core.$strip>;