ZSY_test_guard
qyqy_develop
这两道守卫对应 PR #7 评审暴露出来的两个静默盲区。都不是新功能,是补"能被自动发现"的能力。
docs/05
tools/check_docs_endpoint_ids.py
tools/check_authoritative_docs.py 只校验 docs/ 的文件名编号,不校验 §19 的端点编号。 两条线各自新增端点时都占了 A034/A035,合并后 §19 同时存在两个 A034 与两个 A035, 而文档守卫照样通过 —— 编号复用比"编号不够"更麻烦,而且静默遗留。
tools/check_authoritative_docs.py
docs/
A034
A035
口径要点:不枚举前缀白名单。 同一件事上还踩过一次"扫描正则写成 [AMKCS], 漏掉 O/R 两段,把 62 个端点报成 55 个"——漏掉的号段一旦将来被复用,脚本仍会报"重复 0"。 所以覆盖报告会把"扫到哪几个号段、各多少条"一并打印:
[AMKCS]
O
R
§19 覆盖:62 个端点编号 / 6 个号段(A×40、C×7、K×4、M×4、O×3、R×4) docs/05 §19 端点编号无重复
首列不是 XXX### 形状的行会被显式列出而不是静默跳过。只读、不依赖任何环境变量(纯解析文档), 裸检出环境可直接跑。测试见 tests/unit/tools/test_docs_endpoint_ids.py(7 例,含"空扫也算通过"的反例)。
XXX###
tests/unit/tools/test_docs_endpoint_ids.py
tests/unit/api/test_permission_enforcement.py
既有用例(含 tests/integration/test_customer_service_handover_admin_mysql.py)都通过 build_request_context 覆写注入已经带好权限的上下文,只覆盖"有权限能通", 覆盖不到"缺权限必须被拒"。而"权限码在库里根本不存在"这类环境数据问题恰恰只会在这一层暴露:
tests/integration/test_customer_service_handover_admin_mysql.py
build_request_context
权限判定发生在身份解析之后(app/api/dependencies/auth.py → IdentityService.resolve), 服务层测试是自己构造 RequestContext 的、权限字段由测试塞入,所以全绿也照样漏。
app/api/dependencies/auth.py
IdentityService.resolve
RequestContext
覆盖客服二期三个权限码对应的 5 个端点,正反双向断言:
403
AGENT_PERMISSION_DENIED
只替换两处边界:build_request_context(跳过 JWT 与身份库)与 AuthorizationService 的审计落库 (内存替身);真实路由、真实权限判定、真实 403 信封。
AuthorizationService
已做反向验证:给一个不存在的权限码时,5 个端点全部 403 —— 确认正向断言不是空过。
origin/qyqy_develop
4d8edb4
pytest tests/unit tests/contract -q -p no:cacheprovider
ruff check app tests tools alembic
mypy app
require(...)
seed_test_rbac.py
financial:nl2sql:read
probe:read
risk:alert:read
risk:alert:scan
risk:alert:write
risk:report:mail
tools/check_rbac_seed_consistency.py
Settings()
.env
neo4j_uri
jwt_audience
redis_url
补两道守卫,覆盖 2026-09-12 那次合并评审暴露出来的两个盲区。 一、`docs/05` §19 端点编号唯一性(新增 `tools/check_docs_endpoint_ids.py`) - 背景:`tools/check_authoritative_docs.py` 只校验 `docs/` 的**文件名编号**,不校验 §19 的**端点编号**。两条线各自新增端点时都占了 `A034`/`A035`,合并后 §19 同时 存在两个 `A034` 与两个 `A035`,而文档守卫照样通过 —— 编号复用会静默遗留。 - 口径要点:**不枚举前缀白名单**,前缀从数据里归纳后连同数量一起输出。同一件事上 还踩过一次"扫描正则写成 `[AMKCS]`,漏掉 `O`/`R` 两段,把 62 个端点报成 55 个"—— 漏掉的号段一旦被复用,脚本仍会报"重复 0"。所以覆盖报告会打印 `62 个端点编号 / 6 个号段(A×40、C×7、K×4、M×4、O×3、R×4)`,让漏扫本身可见。 - 只读、不依赖任何环境变量(纯解析文档),可在裸检出环境直接跑。 二、端点权限判定的 HTTP 层用例(新增 `tests/unit/api/test_permission_enforcement.py`) - 背景:既有用例都通过 `build_request_context` 覆写注入**已经带好权限**的上下文, 只覆盖"有权限能通",覆盖不到"缺权限必须被拒"。而"权限码在库里根本不存在"这类 环境数据问题(本次三个新权限码)恰恰只会在这一层暴露:权限判定发生在身份解析之后, 服务层测试自己构造 `RequestContext`,权限字段由测试塞入,所以全绿也照样漏。 - 覆盖客服二期三个权限码对应的 5 个端点: `handover:read`(工单列表/详情)、`memory:candidate:review`(候选列表/审核)、 `memory:candidate:confirm`(用户确认)。 - 正反双向断言:缺权限 → 必须 403 且错误码为 `AGENT_PERMISSION_DENIED`; 带权限 → **不能**再是 403(这一条把端点要求的权限码钉住,改动即红)。 - 只替换两处边界:`build_request_context`(跳过 JWT 与身份库)与 `AuthorizationService` 的审计落库(内存替身);真实路由、真实权限判定与真实 403 信封。 - 已做反向验证:给一个不存在的权限码时,5 个端点全部被拒(403),确认正向断言非空过。 验证(隔离 worktree,基线 `origin/qyqy_develop` = 4d8edb4): pytest tests/unit tests/contract -> 1294 passed, 2 skipped, 0 failed; ruff check app tests tools alembic 全通过;mypy app 244 源文件 0 错; check_authoritative_docs(50 份文档无撞号)与本脚本均通过。
按要求改为直接推送,不再走 PR 分流:origin/qyqy_develop 由 4d8edb4 快进到 c5adf01。
c5adf01
推送坐标
test: 补端点编号守卫与权限判定的 HTTP 层用例
内容(详见 PR 描述)
check_authoritative_docs.py
62 个端点编号 / 6 个号段(A×40、C×7、K×4、M×4、O×3、R×4)
本地验证:pytest tests/unit tests/contract → 1294 passed, 2 skipped, 0 failed;Ruff 全通过;Mypy 244 文件 0 错;两个文档守卫通过。
pytest tests/unit tests/contract
此处按「已推送」关闭 PR,仅为看板整洁;分支 ZSY_test_guard 内容已全部在 qyqy_develop 中。
No dependencies set.
The note is not visible to the blocked user.
背景
这两道守卫对应 PR #7 评审暴露出来的两个静默盲区。都不是新功能,是补"能被自动发现"的能力。
一、
docs/05§19 端点编号唯一性 ——tools/check_docs_endpoint_ids.pytools/check_authoritative_docs.py只校验docs/的文件名编号,不校验 §19 的端点编号。两条线各自新增端点时都占了
A034/A035,合并后 §19 同时存在两个A034与两个A035,而文档守卫照样通过 —— 编号复用比"编号不够"更麻烦,而且静默遗留。
口径要点:不枚举前缀白名单。 同一件事上还踩过一次"扫描正则写成
[AMKCS],漏掉
O/R两段,把 62 个端点报成 55 个"——漏掉的号段一旦将来被复用,脚本仍会报"重复 0"。所以覆盖报告会把"扫到哪几个号段、各多少条"一并打印:
首列不是
XXX###形状的行会被显式列出而不是静默跳过。只读、不依赖任何环境变量(纯解析文档),裸检出环境可直接跑。测试见
tests/unit/tools/test_docs_endpoint_ids.py(7 例,含"空扫也算通过"的反例)。二、端点权限判定的 HTTP 层用例 ——
tests/unit/api/test_permission_enforcement.py既有用例(含
tests/integration/test_customer_service_handover_admin_mysql.py)都通过build_request_context覆写注入已经带好权限的上下文,只覆盖"有权限能通",覆盖不到"缺权限必须被拒"。而"权限码在库里根本不存在"这类环境数据问题恰恰只会在这一层暴露:
覆盖客服二期三个权限码对应的 5 个端点,正反双向断言:
403且错误码AGENT_PERMISSION_DENIED403(把端点要求的权限码钉住,改动即红)只替换两处边界:
build_request_context(跳过 JWT 与身份库)与AuthorizationService的审计落库(内存替身);真实路由、真实权限判定、真实 403 信封。
已做反向验证:给一个不存在的权限码时,5 个端点全部 403 —— 确认正向断言不是空过。
验证(隔离 worktree,基线
origin/qyqy_develop=4d8edb4)pytest tests/unit tests/contract -q -p no:cacheprovider→ 1294 passed, 2 skipped, 0 failedruff check app tests tools alembic→ All checks passedmypy app→ 244 个源文件 0 错tools/check_authoritative_docs.py→ 50 份文档无撞号;tools/check_docs_endpoint_ids.py→ 无重复顺带两个实测观察(不在本 PR 范围内,供你判断)
require(...),但既不在seed_test_rbac.py、也不在 §19 的权限列:financial:nl2sql:read、probe:read、risk:alert:read、risk:alert:scan、risk:alert:write、risk:report:mail。它们属风险线与工具线,不是我们的改动,所以我没有动种子(避免跨线改号段)。如果这不是有意为之,那就是同一类"环境数据缺失 → 403"的隐患;要不要补,你定。
tools/check_rbac_seed_consistency.py单独执行时依赖完整环境变量(它 import 种子与 grant 脚本→ 触发
Settings();我这边无.env时会报neo4j_uri/jwt_audience/redis_url缺失),而它的 pytest 用例在同一环境下通过 —— 两者行为不一致。如果希望门禁在裸检出环境也能跑,
可以考虑在脚本里给 Settings 注入占位值,或改成不 import 运行期模块只解析常量。
已直接推送至
qyqy_develop(快进,未使用合并提交)按要求改为直接推送,不再走 PR 分流:
origin/qyqy_develop由4d8edb4快进到c5adf01。推送坐标
c5adf01(test: 补端点编号守卫与权限判定的 HTTP 层用例,3 文件 +450)ZSY_test_guard(与qyqy_develop推送后同一提交)4d8edb4是c5adf01的祖先(快进),合并预演无冲突内容(详见 PR 描述)
tools/check_docs_endpoint_ids.py+ 7 例测试 —— 补check_authoritative_docs.py不校验 §19 端点编号的盲区;带覆盖自检输出(62 个端点编号 / 6 个号段(A×40、C×7、K×4、M×4、O×3、R×4)),不枚举前缀白名单。tests/unit/api/test_permission_enforcement.py+ 11 例 —— 补"缺权限必须被拒"的盲区,覆盖 5 个端点 / 3 个权限码,正反双向断言。本地验证:
pytest tests/unit tests/contract→ 1294 passed, 2 skipped, 0 failed;Ruff 全通过;Mypy 244 文件 0 错;两个文档守卫通过。此处按「已推送」关闭 PR,仅为看板整洁;分支
ZSY_test_guard内容已全部在qyqy_develop中。Pull request closed