WIP-336 · 38 域矩阵 P0 a端域适配 — 交付证据(扣子二号)

闸门:仅改 admin-console/src(A端平台运营后台)。后端 destination 绑定归小齐 D3,本单未改 backend 一行。
真源:docs/delivery/evidence/333/03-隔离域适配矩阵.md(38 缺口域);契约 docs/contracts/chain-identity-v1.md;规则 backend/app/tenant.py。
时间:2026-09-15 00:1x–00:3x(刘总 23:56 三令 · gpt6 03-夜班分派 · 二号第二件)

1. 范围界定(为什么 a端 P0 = 前端消费侧)

矩阵 38 项「适配范围」全部是后端「查询+写入加 destination_id 绑定」,负责人为后端(小齐 D3✓)。

其中 admin-console 直接消费的后端 P0 域:

后端域端点数隔离表(前4)优先级现状(grep)
admin_backend.py27analytics_events, fulfillment_items, key_balances, merchantsP0无 destination 绑定点(后端缺口)
admin_distribution.py22merchant_bills, merchants, ordersP0无 destination 绑定点(后端缺口)
admin_ops.py23merchants, orders, packagesP0无 destination 绑定点(后端缺口)

⇒ 二号不可越闸门改 backend。a端可零依赖落地的 P0 = 目的地租户隔离的前端消费侧:

  1. http 层把租户隔离三错误码(403/409/404)从通用「请求失败」中语义化分类;
  2. 按契约 §3 预置目的地上下文服务(token-exchange 换发受限 JWT + 当前上下文持有);
  3. 顶栏只读展示当前目的地租户上下文,不臆造目的地列表。

2. 交付物(admin-console/src)

文件改动
services/http.tsApiError 新增 kind 分类;classifyTenant() 按后端 tenant.py 固定文案精确判定:409→tenant_context_required(多目的地回落失效)、404 且文案含目的地→tenant_not_found、403 且文案含目的地/跨租户→tenant_forbidden;其余 403 归 business(不吞权限错误)。网络错误→network、401→unauthorized。409 仅在 detail 命中目的地/租户词时才分类,避免误伤支付状态机等业务 409。后端中文 detail 原样透出,不改写、不静默降级。
services/tenant.ts(新建)switchDestination(id,{scope,reason}) 调 POST /api/miniapp/auth/chain/token-exchange,解包 {ok,data},把受限 JWT 写回 session(保留 adminName/role/phone),并持久化 DestinationContext;getDestinationContext()(过期自动清空,不静默续期);clearDestinationContext()。失败原样抛带 status/kind 的错误,不清空现有上下文。
services/contract.ts新增 DestinationToken / DestinationTokenResp(token/destination_id/destination_name/scope/expires_at),对齐契约 §3。
components/Layout.tsx顶栏新增只读目的地上下文条:显式上下文→高亮「目的地:{name} #{id}」(title 带 scope);无显式上下文→中性「目的地:默认(令牌回落)」。不提供切换选择项(理由见阻断 ⓑ)。
components/Layout.css.ac-tenant-ctx / .is-default / .is-explicit / .ac-tenant-dot,复用既有 --color-* 变量。

3. 前后端匹配核验(读源)

项前端后端结果
路径API_BASE + '/auth/chain/token-exchange' = /api/miniapp/auth/chain/token-exchangeauth_chain.py APIRouter(prefix="/api/miniapp/auth/chain") + @router.post("/token-exchange")✅
方法POSTPOST✅
鉴权带 Authorization: Bearer <session.token>Depends(get_current_user)✅
请求体{destination_id, scope, reason}ExchangeReq(同三字段)✅
响应解包 {ok,data} 取 data.*返回 {"ok":true,"data":{token,destination_id,destination_name,scope,expires_at}}✅
404/403文案匹配分类「目的地不存在」404 / 「目的地未启用,不得签发访问凭证」403✅
409 文案匹配「存在多个启用目的地…」分类tenant.py _default_destination 多 active 抛 409 同文案✅

4. 验证

5. 阻断与下一步(后端缺口,移交小齐/统筹;未闭环不标完工)

6. WIP-330 P0 解阻续作(2026-09-15 11:4x,用户解阻ⓐⓑ两项)

用户 11:4x:ⓐ admin 三域 destination 绑定进行中(admin_ops merchants 段已上 main,resolve_destination 可用);ⓑ destinations 列表端点已上线;ⓒ token admin scope 排下批。⇒ 336 切换器 UI 可开工。

解除的阻断

新增文件(仅 admin-console/src)

文件变更
components/DestinationSwitcher.tsx(新)切换器 UI:点击展开按需拉取 destinations、切换(token-exchange)、回退默认、403/404/409/401 差异提示、当前项高亮
components/DestinationSwitcher.css(新)切换器/面板/项/错误态样式
components/Layout.tsx顶栏 336 静态上下文条 → 替换为 <DestinationSwitcher />
services/tenant.ts新增 listDestinations()(GET destinations,admin 鉴权,失败诚实抛出)
services/api.ts新增 listAdminDestinations()(MINI_ADMIN + '/destinations',CodeEnvelope 解包)
services/contract.ts新增 DestinationList(destinationId/name/status/merchantCount/orderCount)

契约对齐(读源核验,非臆造)

验证

剩余阻断(ⓒ + 后端余段)