版本:v1.1(v1=代跑前判据底稿;v1.1=代跑结果已回传,追加 §六 复核结论)
任务卡:目标=复核 344 机位代跑结果;范围=只核证与出判据,不改 344 脚本/后端/证据(跨域属主限制);批准来源=小冈 R1 派工 WIP-356(00:0x)/义父连轴令;验收=判据可机械执行、结论有四态、缺陷登记到条。
| 项 | 真值 |
|---|---|
| 344 证据目录现有文件 | 336切换器-运行时三正反例预演.sh/G7-2-主理人5任务-admin消费侧正式用例.md/README.md/build.log/typecheck.log |
| 是否有代跑结果 | 🔴 无(无输出 txt/log;build.log 是构建日志,与运行时三正反例无关) |
| 判定 | BLOCKED-未代跑:无对象可复核。判 FAIL 会冤枉交付人(缺的是机位环境,不是交付) |
催办:请 R1 在机位(ADMIN_TOKEN+ 可访100.64.1.230:8089)执行并把输出回传本目录;回传后本岗按 §三 判据复核。
| 脚本用例 | 期望 | auth_chain.py:108 实现 | 契约 chain-identity-v1 §3 | 结论 |
|---|---|---|---|---|
| 正例:切换 active 目的地 | 200 + 受限 JWT | create_token(..., destination_id=dest["id"], scope=...) | §3 定义返回 token | ✅ 一致 |
负例1:不存在(123456789) | 404「目的地不存在」 | if not dest: raise 404 "目的地不存在" | 同 | ✅ 一致 |
| 负例2:未启用(非 active) | 403「目的地未启用」 | raise 403 "目的地未启用,不得签发访问凭证" | 同 | ✅ 一致 |
auth_chain.py:26 prefix=/api/miniapp/auth/chain + /token-exchange ⇒ 全路径 /api/miniapp/auth/token-exchange 正确,脚本 API_BASE=/api/miniapp 拼接无误。main.py:84 app.include_router(auth_chain.router) ✅ 已挂载(非孤儿 router)。| # | 缺陷 | 后果 | 建议修法 |
|---|---|---|---|
| S1 | 负例1/2 只判 HTTP 码、不判 body | 若将来端点被摘除或路径变更,FastAPI 默认 404/403 会假 PASS | 加 body 断言:404 须含「目的地不存在」;403 须含「目的地未启用」 |
| S2 | 第 3 例无未启用数据时 CODE=skip ⇒ 走 else 分支 fail+1 | 环境缺数被记成 FAIL,与脚本自述「不伪造」原则冲突 | 增设 blocked 计数,skip 不计 fail |
| S3 | 末尾恒 exit 0 | 无论失败多少,门禁/CI 无法据此拦截;上一轮本仓实跑 exit 1 是因 ADMIN_TOKEN:? 提前退出,不是用例失败 | 改 exit $((fail>0)) |
| S4 | 依赖机位 python3 解析 JSON | 机位 py 版本过低可能导致解析失败被误读 | 跑前 python3 -V 登记(AST 类工具另需 ≥3.12) |
说明:S1-S3 属脚本健壮性问题,344 交付人自述「机位待跑、不在本地伪造」是诚实的;本岗按缺陷登记,不判 FAIL。
四态判定:PASS/PASS-附条件/BLOCKED/FAIL。证据五要素缺任一 ⇒ 判 BLOCKED,不判 PASS。
(五要素:命令原文、完整回显、时间戳、hostname、相关版本号)
| # | 复核项 | PASS 判据 | BLOCKED 判据 | FAIL 判据 |
|---|---|---|---|---|
| R1 | 输出五要素齐全 | 五项全有 | 缺任一 | — |
| R2 | 正例(active) | HTTP 200 且返回 data.token 非空,且该 token 解出 destination_id=目标 | 环境无 active 目的地(须登记造数需求,不伪造) | 200 但 token 无 destination_id(⇒ 隔离未生效) |
| R3 | 负例1(不存在) | 404 且 body 含「目的地不存在」 | 端点不可达 | 404 但 body 为 FastAPI 默认 Not Found(⇒ 假 PASS,见 S1) |
| R4 | 负例2(未启用) | 403 且 body 含「目的地未启用」 | 环境无未启用目的地(不判 FAIL,见 S2) | 返回 200(⇒ 未启用目的地也能换凭证,红线级) |
| R5 | 跨租户(新增,本轮补) | 用 A 目的地受限 JWT 显式请求 B 目的地 ⇒ 403「与凭证目的地不符」 | token 未带 destination_id(走的是 admin 直连路径,非受限 JWT) | 返回 200 ⇒ 跨租户防线失效,红线级 |
| R6 | 与 352 联动 | 受限 JWT 访问已接 destination 的端点(当前 13 条)返回集随目的地变化 | 端点未接(BLOCKED-待 352) | 已接但不变化 ⇒ FAIL |
R5 是本次新增:原脚本三例覆盖不到「换到 A 后能否看 B」,而这恰是切换器存在的核心风险(见 §四)。
token-exchange 实现完整(防串租户设计正确),但它只做「声明目的地」,不做「授权目的地」:
token-exchange 换带 destination_id 的受限 JWT ⇒ 之后访问走 resolve_destination 令牌分支,跨租户 403 生效。exchange_token 只校验目的地存在且 active,不校验该用户是否有权访问该目的地;三域亦零授权表(memberships 仅用于 miniapp/workbench 侧)⇒ 任何已登录 admin 可换取任意 active 目的地的凭证。requested 参数访问已接端点亦可通过——因 get_current_user 返回字段不含 destination_id(security.py:33-47),resolve_destination 的跨租户 403(if token_dest is not None)恒不触发;只有 requested 为空时才撞 409(_default_destination 多目的地防串租户,文案即「先做 token 交换」)。| 若产品意图 | 当前实现 | 须补动作 |
|---|---|---|
| admin = 平台全局视角(本就该看全部) | ✅ 成立 | 契约明写「admin 面不按目的地鉴权」+ 记审计(谁何时切到哪个目的地) |
| admin = 按目的地隔离 | 🔴 不成立 | ①admin 端点强制受限 JWT(拒绝无 destination_id 的凭证)②或建管理员—目的地授权表 |
本岗不单方判 FAIL:这是产品题。裁决前,§三 R5 判 BLOCKED-待裁决;裁决为「按目的地隔离」后,R5 转为必过项。
完整机制推演:docs/contracts/admin-a端三大域消费侧核对表-v1.md§二·B;剧本:docs/plans/335-drill-playbook.md§5·C。
docs/delivery/evidence/ba/344/。本岗边界:只出判据与复核底稿,未改 344 任何文件、未改后端任何代码。
docs/delivery/evidence/ba/344/switcher-l3-run.md)| 项 | 判定 |
|---|---|
| 344 三正反例(代跑) | ✅ PASS-附条件(fixture 补齐后 404/403/200 4/4 全绿) |
| 证据五要素 | ⚠️ 附条件:有时间戳/环境/凭证/request_id 逐条可溯;缺命令原文、hostname、镜像或 commit 版本 ⇒ 补此三项即转无条件 PASS |
| R5 跨租户(本岗新增判据) | 🔴 BLOCKED-未覆盖(见 6.2) |
| 本岗 356③ | ✅ 收口:代跑前静态复核 + 结果复核均已完成 |
| 344 脚本原定 | 本次代跑实测 | |
|---|---|---|
| 端点 | POST /api/miniapp/auth/chain/token-exchange | GET /api/miniapp/admin/merchants?destination_id=N |
| 机制 | 换受限 JWT(带 destination_id) | admin 直连 JWT + requested 参数(不换凭证) |
| 语义 | 336 切换器「换凭证」 | 「带参读运营面」 |
⇒ 契约路径(受限 JWT)本次零覆盖:R2「正例须返回带 destination_id 的受限 JWT」、R5「A 凭证请求 B 应 403」均未测。
⇒ 代跑记录自述「任务原文 URL /api/miniapp/admin/ops/merchants 实测路由级 404,修正为 /api/miniapp/admin/merchants」
—— 该路径勘误成立(admin_ops.py:20 prefix 确为 /api/miniapp/admin,无 /ops 层级),但它是派工口径与 344 脚本口径的偏差,不是脚本错。
⇒ 建议:切换器语义若要闭环,须补跑 token-exchange 三例(本岗判据 R2/R5 已备)。在补跑前,不得据本次结果宣称「336 切换器已验证」。
代跑全程用 admin JWT 带 requested,未换受限 JWT 即读到 d=1 的 31 条商户 ⇒ 与本文档 §四「缺口 B:不换凭证直接带 requested 即可绕过 token-exchange」完全吻合。
⇒ 该缺口由「静态推演」升级为「运行时实证」。请 R1 在 §四 裁决时据此定级。
status=inactive)⇒ destinations 表 2 行。_default_destination 只数 WHERE status='active' ⇒ active 仍仅 1 个 ⇒ 不触发「多目的地 409」。需再插一个 active 目的地才具备 409 触发条件 —— 这恰好是 335 演练第一幕该做的造数,请 R1 提前准备。
destinations 表结构为 id / name / anchor_poi / status,无 is_test 列;代跑记录称「is_test 种子纪律内」,实际以 name 前缀标记(id=1 用 [测试]…、id=2 用 测试…(机位fixture),前缀不统一)。
⇒ 建议:统一 [测试] 前缀,或给表补 is_test 列(属后端属主域,本岗只登记不改)。
本岗边界:只复核与出判据,未改 344 证据目录、未改后端、未改 staging 数据。