From c5cb779390ef62465a4bc9cb9ac5a703e7867e1a Mon Sep 17 00:00:00 2001 From: xcs Date: Wed, 26 Aug 2026 01:36:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=BD=AE=201:=E9=82=AE?= =?UTF-8?q?=E4=BB=B6=20GET=20=E6=94=B9=E5=BD=92=20ops,=E4=B8=8D=E6=92=91?= =?UTF-8?q?=E5=A4=A7=E4=B8=A4=E7=AB=AF=E5=85=B1=E7=94=A8=E8=AF=BB=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E5=90=8D=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 全仓测试 route-access.test.ts 钉着一份有意枚举、随 Keycloak spec 审过的名单:哪些读路由被两端共用。 之前把邮件 7 条 GET 标成 design|ops,名单从八条长到十四条。裁决改路由、不改名单:邮箱那一屏落在 运营中心,设计端没有消费方;入站正文装的是客户来往内容,ADR-012 已把「设计端能读客户主数据」记为 一处有意承担的代价,不再无故加宽。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr --- .../app/__tests__/mail-routes-registry.test.ts | 9 ++++----- packages/contracts/src/api/mail.ts | 17 +++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/api/src/app/__tests__/mail-routes-registry.test.ts b/apps/api/src/app/__tests__/mail-routes-registry.test.ts index 937861f..10a7776 100644 --- a/apps/api/src/app/__tests__/mail-routes-registry.test.ts +++ b/apps/api/src/app/__tests__/mail-routes-registry.test.ts @@ -1,6 +1,6 @@ /** * 邮件路由的归属:15 条全部进契约,注册表建得起来(字面 / 参数模版互不撞), - * 读标两端、写归运营中心(设计稿 §7.2)。 + * 读写都归运营中心(设计稿 §7.2 + 2026-08-26 裁决:设计端没有消费方,不再无故加宽)。 */ import { describe, expect, it } from 'vitest'; import * as contracts from '@contracts'; @@ -17,10 +17,9 @@ describe('邮件路由契约', () => { } }); - it('读标两端,写归 ops', () => { + it('读写都归 ops', () => { for (const route of MAIL_ROUTES) { - if (route.method === 'GET') expect(route.access).toEqual(['design', 'ops']); - else expect(route.access).toBe('ops'); + expect(route.access).toBe('ops'); } }); @@ -28,6 +27,6 @@ describe('邮件路由契约', () => { const registry = buildRouteRegistry(routesOf(Object.values(contracts))); expect(registry.lookup('POST', '/api/gov/mail/accounts/probe')).toBe('ops'); expect(registry.lookup('POST', '/api/gov/mail/accounts/acc-1/probe')).toBe('ops'); - expect(registry.lookup('GET', '/api/gov/mail/inbound/m-1/attachments/0')).toEqual(['design', 'ops']); + expect(registry.lookup('GET', '/api/gov/mail/inbound/m-1/attachments/0')).toBe('ops'); }); }); diff --git a/packages/contracts/src/api/mail.ts b/packages/contracts/src/api/mail.ts index e58f4e3..f7009d8 100644 --- a/packages/contracts/src/api/mail.ts +++ b/packages/contracts/src/api/mail.ts @@ -1,7 +1,8 @@ /** * 邮件收发的路由契约(设计稿 §7.2;ADR-013)。 * - * 读标两端(设计中心要看通道对上了哪个信箱,运营中心管配置);写归运营中心。 + * 读写都归运营中心(设计稿 §7.2 + 2026-08-26 裁决):邮箱那一屏落在运营中心,设计端没有消费方; + * 而入站正文装的是客户来往内容 —— ADR-012 已把「设计端能读客户主数据」记为一处后果,不再无故加宽。 * `/api/gov/*` 的写路由由 app 中间件自动广播 `{kind:'system', id:'gov'}`。 * 探测 / 试发 / 拉取的**厂商失败不是接口错误**:200 的 body 里带原话(脱敏)。 */ @@ -30,7 +31,7 @@ const listQuery = z.object({ export const listMailAccountsRoute = { method: 'GET', path: '/api/gov/mail/accounts', - access: ['design', 'ops'], + access: 'ops', output: z.object({ accounts: z.array(mailAccountViewSchema) }), } as const satisfies RouteContract; @@ -56,7 +57,7 @@ export const probeMailDraftRoute = { export const getMailAccountRoute = { method: 'GET', path: '/api/gov/mail/accounts/:accountId', - access: ['design', 'ops'], + access: 'ops', params: accountParams, output: mailAccountViewSchema, } as const satisfies RouteContract; @@ -109,7 +110,7 @@ export const sendTestMailRoute = { export const listMailInboundRoute = { method: 'GET', path: '/api/gov/mail/inbound', - access: ['design', 'ops'], + access: 'ops', query: listQuery, output: z.object({ mails: z.array(mailInboundSummarySchema) }), } as const satisfies RouteContract; @@ -117,7 +118,7 @@ export const listMailInboundRoute = { export const getMailInboundRoute = { method: 'GET', path: '/api/gov/mail/inbound/:mailId', - access: ['design', 'ops'], + access: 'ops', params: mailParams, output: mailInboundDetailSchema, } as const satisfies RouteContract; @@ -126,7 +127,7 @@ export const getMailInboundRoute = { export const mailAttachmentRoute = { method: 'GET', path: '/api/gov/mail/inbound/:mailId/attachments/:index', - access: ['design', 'ops'], + access: 'ops', params: z.object({ mailId: z.string().min(1), index: z.coerce.number().int().min(0) }), output: z.object({ name: z.string(), contentType: z.string(), dataBase64: z.string() }), } as const satisfies RouteContract; @@ -135,7 +136,7 @@ export const mailAttachmentRoute = { export const mailRawRoute = { method: 'GET', path: '/api/gov/mail/inbound/:mailId/raw', - access: ['design', 'ops'], + access: 'ops', params: mailParams, output: z.object({ name: z.string(), contentType: z.literal('message/rfc822'), dataBase64: z.string() }), } as const satisfies RouteContract; @@ -153,7 +154,7 @@ export const redeliverMailInboundRoute = { export const listMailOutboundRoute = { method: 'GET', path: '/api/gov/mail/outbound', - access: ['design', 'ops'], + access: 'ops', query: listQuery, output: z.object({ mails: z.array(mailOutboundViewSchema) }), } as const satisfies RouteContract;