WIP-357 闭环 · R5 红线修复独立复核签字 v1

判据:docs/delivery/acceptance/357-token-exchange补跑与R5复核-v1.md(2026-09-16 08:4x)第三节「配套回归清单」五条
执行:波波(验收检测岗 · A 段定岗「357 闭环」)|时间戳 2026-09-16 09:49|机位 100.64.1.230:8089(staging)
原则:不采信自报实证——修复方小齐已在 docs/delivery/evidence/ba/358/r5-fix-verification.md 出具三例结果,本岗仍独立复跑并补测其未覆盖的两条。

一、结论

项修复前(08:42 实测)修复后(09:49 独立复核)判定
R5 跨租户(受限=1 → B=3)🔴 200(应 403)✅ 403 「请求目的地与凭证目的地不符」PASS · 红线销项
R5b 对照(受限=1 → 自身=1)200200(31 条)PASS
R5c 受限不带参(走凭证默认)🔴 409(应 200)✅ 200(31 条)PASS(语义变更,见 §五)
R2/R3/R4 三例200/404/403200/404/403PASS · 无回归
总判:R5 由 FAIL 转 PASS,本岗建议销项。 357 闭环条件满足(R2/R3/R4 签字收尾 + R5 独立复核通过)。

二、九例实测(HTTP 码 + 数据条数双维度)

#用例凭证期望实测条目数判定
S0/auth/me 凭证自证admin 主 JWT200200—✅
R2token-exchange(active=1)主 JWT200200—✅ 锚点 destination_id="1"
R3token-exchange(不存在 999)主 JWT404404—✅
R4token-exchange(未启用 2)主 JWT403403—✅
R5admin/merchants?destination_id=3受限 JWT(dest=1)403403—✅ 红线修复
R5b?destination_id=1受限 JWT(dest=1)20020031✅
R5c不带参(走凭证默认)受限 JWT(dest=1)20020031✅
R6a不带参(多 active)主 JWT(无 claim)409409—✅ 回归①②小齐未测
R6b?destination_id=1主 JWT(无 claim)20020031✅ 小齐未测
R6c?destination_id=3主 JWT(无 claim)2002000✅ 小齐未测

相互印证:主 JWT payload = {"user_id","identities","token_version","exp"}——确无 destination_id claim,因此 R6a/R6b/R6c 正是「修复会不会误伤老凭证/admin 全局视图」的有效探针。


三、数据层交叉断言(不只验 HTTP 码)

HTTP 码对了不代表数据对了,故加验返回条目数:

断言结果说明
受限凭证默认落点 = 自身目的地(R5b 条数 == R5c 条数)PASS31 == 31 ⇒ 不带参确实落到 dest=1,非全局
admin 全局视图未被误伤(主 JWT 仍能取 1 与 3)PASSR6b=200(31 条)/R6c=200(0 条)
主 JWT 行为与修复前一致(多 active 仍 409)PASSR6a=409,未因修复而静默降级

🔴 本岗自曝弱断言:原设计第 2 条断言「R5c 条数 ≤ max(R6b,R6c)」强度不足——目的地 3 的商户数本就为 0(R6c=0 条),该断言在数据全空时也会成立。真正的强证据是 R5 的 403 本身,数据层只能证明「默认落点正确」,无法形成跨目的地数据对照。判据不因弱断言而松动,特此登记以免后续引用时高估证据强度。


四、部署版本指纹(原缺口的替代方案,含强度自评)

上轮报告「容器内 /app 非 git 仓库 ⇒ 缺少 commit 指纹 ⇒ 严格按剧本 §8 应判 BLOCKED」。本轮用三重替代补齐:

  1. 源码文本(最强):容器内 /app/app/security.py 第 46-49 行直接读到修复代码与其注释

u["destination_id"] = int(payload["destination_id"]) # R5 红线修复:受限 JWT 的目的地锚点恢复消费(原 claim 被丢弃→tenant.py 跨租户 403 校验死代码,波波 357 R5 实证)

  1. 源码 mtime:security.py / tenant.py = 2026-09-16 01:20:15 +0000
  2. 容器启动:StartedAt = 2026-09-16T01:20:25Z

交叉验证:修复 commit e2973cac 时间 = 2026-09-16 09:20:15 +0800 = 01:20:15 UTC,与源码 mtime 完全吻合,容器 10 秒后启动。

⚠️ 强度自评(不夸大):mtime 只能证明「镜像构建时刻」,无法唯一绑定 commit hash;本例的强绑定由第 1 项源码文本提供。

🔴 残留缺口不撤:本例能凑齐是因为源码可读。若换成编译型或压缩产物,同样的方法失效 ⇒ 仍建议构建时写入 /app/COMMIT(或 GIT_SHA 环境变量),否则 335 任何在容器上跑的验收,证据链都缺一角。


五、🟡 新发现两项(不阻断销项,须登记)

  1. R5c 语义发生变更:409 → 200。修复前受限 JWT 不带参落进「多 active ⇒ 409」默认分支;修复后因凭证自带锚点而直接落到自身目的地。

本岗认为这是语义改进(有限锚点凭证本就不该走「无目的地上下文」分支),但 chain-identity-v1.md §3 目前只规定「签发」,未规定「不带参时的落点语义」。

⇒ 请契约持有席明确一句:「受限 JWT 不带 destination_id 时,服务端以凭证锚点为目的地(非全局)」。与上轮建议第 3 条同源,属契约侧待补。

  1. 验证面仍窄:本轮只覆盖 admin/merchants 一个读端点。挂 resolve_destination 的其余端点与全部写端点未被 R5 回归覆盖 ⇒ 建议在 335 第五幕补一次多端点抽样,而非以单点全绿代表全局。

六、边界声明


七、建议

  1. R5 判据升为 335 第五幕必过项(环境现已具备:两个 active 目的地 + 修复上线 ⇒ 首次具备实测条件)
  2. R5 与 352 Q2 批2 为同族第三次发作(能力在库里、调用方没消费)⇒ 建议派工岗新增检查项:比对「声明点」与「消费点」计数,别每次靠验收撞见
  3. 🟡 JWT_SECRET 仍为默认值 dev-secret-change-me(本岗借此取证)——目前仅 Tailscale 内网可达 + APP_ENV=dev,上线前必须配真值
签字:PASS(建议销项) —— R5 红线 failures 已由本岗独立复跑实证修复并补齐两条未测回归
上报:@R1(销项 + 判据升必过项)/@契约持有席(§五-1 契约补消费侧+不带参语义)/@小齐(§五-2 多端点回归面)
署名:波波 2026-09-16 09:5x