- Introduced `_recent_days_series_hint` to handle queries related to "最近N天" for time series data aggregation. - Added `_cn_num_to_int` function to convert Chinese numerals to integers for better query parsing. - Updated `_nl_sql_hints` to incorporate the new hint generation logic, ensuring accurate SQL output for recent days queries. - Enhanced documentation to reflect these changes and improve clarity on the new functionalities.
TEST-RK-001 · 风控线(风控 Agent)端到端包
当前结论(2026-09-13):
- Part A 缺口补测(真实 HTTP):23 PASS / 0 FAIL / 0 SKIP / 8 INFO,退出码 0。 其中 RK7 = 22 PASS / 4 INFO(后端补漏)· RK8 = 1 PASS / 4 INFO(浏览器复跑对照)。
- Part B 浏览器:
10-risk-pages.mjs21 PASS / 0 FAIL ·03-risk.mjs14 PASS / 0 FAIL / 2 INFO。 - Part C 回归基线(
risk_e2e_smoke.py原样未改):63 PASS / 0 FAIL / 1 SKIP,退出码 0。
读数纪律(沿用
TEST-CT-001/TEST-AN-003): ① INFO 不是 PASS,不得计入通过率分母;② 本线后端 4 条路由此前已被基线全覆盖,本包补的是边界与语义,不是"从零覆盖";③ 既有基线脚本一个字都没改 —— 回归对照的前提就是它不变。
⚠️ 与既有覆盖的关系(如实声明): 风控线是本项目后端覆盖最完整的一条线:
risk_e2e_smoke.py(63 条,本包复跑退出码 0)已覆盖/alerts·/suitability/check·/aml/scan+ 处置状态机。docs/memory/tests/2026-09-11-risk-agent-e2e亦已覆盖 Agent 侧。 本包新增的是「边界与语义取证」:分页边界、前后差值一致性、未知资源的角色分裂语义、以及 F-01 恒真断言修复后的浏览器对照(RK8)。
产物
| 产物 | 路径 | 位置 |
|---|---|---|
| 企业级测试日志 | TEST-LOG-2026-09-13-RK-001.md |
本包 |
| 缺口补测脚本(RK7–RK8) | scripts/dev/risk_gap_e2e_smoke.py |
仓库内(本轮新建) |
| 回归基线脚本(原样未改) | scripts/dev/risk_e2e_smoke.py |
仓库内(既有) |
| 浏览器脚本(本轮收复进仓库) | scripts/e2e/10-risk-pages.mjs · scripts/e2e/03-risk.mjs |
仓库内(原在仓库外) |
结果明细 _raw/(逐用例原始记录,未加工)
| 文件 | 内容 | 结果 |
|---|---|---|
rk-gap.md |
RK7–RK8 逐条矩阵(期望 / 实测 / 证据) | 23 PASS / 0 FAIL / 8 INFO |
rk-gap.stdout.txt |
同轮终端原始输出 | 退出码 0 |
results-10-risk.json |
10-risk-pages.mjs 的原生 JSON 结果(Part B) |
21 PASS / 0 FAIL |
_partc-risk.md |
Part C 回归基线报告(risk_e2e_smoke.py) |
63 PASS / 0 FAIL / 1 SKIP |
_partc-risk.stdout.txt |
同轮终端原始输出 | 退出码 0 |
shots/ |
Part B 页面截图(10-risk-*.png · risk-*.png) |
— |
为什么 RK8 单独成组、而非直接改
risk_e2e_smoke.py:risk_e2e_smoke.py是 Part C 的回归基线,其价值在于「退出码仍为 0」。把对照逻辑塞进去会让"真回归"与"对照发现"不可区分。故本包另起脚本,既有基线一字未动。
缺陷与发现(报告侧;本轮不修)
RK 发现-1 —— 未知资源的「找不到」语义在同一路由内分裂(P3,契约不一致)
POST /api/risk/suitability/check 对未知客户的行为按角色三分,没有任何一个角色走到 risk.py:134 的 404 分支:
| 角色 | 实测 | 说明 |
|---|---|---|
risk_officer(全量读,无归属查询) |
HTTP 200 blocked=true mismatch_type='not_found' block_response_code='SUIT_NOT_FOUND' |
fail-closed,结构化返回 |
advisor(归属查询分支) |
HTTP 403 AUTH_403_NOT_ASSIGNED |
先撞归属校验 |
customer(self 域) |
HTTP 403 AUTH_403_NOT_OWNER |
先撞数据域校验 |
同一路由对未知产品(客户已知)则是 HTTP 200 + blocked。⇒ 同一条「找不到」语义,在不同角色与不同客体下呈现 200 / 403 ×2 三种形态,404 从未出现。
- 根因: 归属/域校验早于资源存在性校验执行,故"不存在"被"无权"遮蔽。
- 不是漏洞:fail-closed,未静默放行,三个角色都拒了。
- 未覆盖路径:
app/api/risk.py:134的raise ApiError(404, "NOT_FOUND", …)(LookupError分支)本轮未能构造出可达条件。记为未覆盖路径,不记为缺陷。 - 待拍板: 是否要求"未知客户"在所有角色下统一为 404(信息更准)还是维持现状(不泄漏客户存在性,安全上更优)。
RK 发现-2 —— risk_alert.created_at 的时区口径(P3,系统性坑的根源)
实测 created_at = '2026-09-13T21:05:39.109000',为本地时区无偏移后缀的 ISO 串。这与本项目已多次踩过的 R4 窗口陷阱同源:audit_log.created_at >= DATE_SUB(NOW(), INTERVAL 15 MINUTE) 这类"近 N 分钟"查询在本轮 §执行顺序里必须留出 ≥15 分钟窗口,否则距上轮 <15 分钟复跑会假 FAIL。记录以备统一口径。
已验证转绿的正面证据(本包价值最高的部分)
RK8 —— F-01 恒真断言修复后的对照:21/21 零退化 ★
03-risk.mjs:23 原为 ok(..., true, ...) 的恒真断言(永远 PASS,不构成证据)。修复后重跑 10-risk-pages.mjs 并与仓库外基线 e2e-jinrong/results-10-risk.json 逐条对照:
| 对照项 | 实测 |
|---|---|
| 交集用例数 | 21 / 21(基线 21 条,本轮 21 条,无增无减) |
| PASS → FAIL 退化 | ∅ 无 |
| 修复的旧 FAIL 条目 | 无 |
| 仅本轮新增/仅基线有的用例 | 无 |
这条对照的价值在于排除:恒真断言被替换为真实断言后,原来"PASS"的 21 条全部仍然 PASS ⇒ 证实那些 PASS 不是被恒真断言撑起来的,原基线可信。这是"修腐化资产"必须给出、也最容易漏掉的反证。
RK7 —— 后端边界与一致性(22 PASS)
| 断言 | 取证 |
|---|---|
page=0 / page=-3 → 422(Query(ge=1) 下界) |
两条负例均 422 REQUEST_VALIDATION_FAILED |
超末页 page=999999 → 200,items=[] 且 total 与首页一致 |
total=53 两页一致 ⇒ 分页元数据不随页码漂移 |
page1 ∩ page2 = ∅(分页不重复) |
重复 [];并由 7-03a 保证两页各自非空(否则空集会假过) |
stats.pending_review_count 与 total 同响应自洽 |
stats=36 total=36 |
| 接口与库对账 | API total=36 / DB 36 ✅ |
| 处置 → 计数恰好 −1(前后差值) | 36 → 35,断言的是差值而非绝对计数 |
| 阻断响应补齐 G-08 三要素 | advice='请联系持证投资顾问' · notice='本次请求已记录' |
| 通道值与账号类型不符 → 矩阵层先拦 | 403 AUTH_403_AGENT_MISMATCH(早于归属校验) |
关于「前后差值」纪律:
7-06a断言的36 → 35是同一条用例内紧邻采集的前后差值,不是绝对计数。这正是计划第七节第 1 层防线(禁止绝对计数)的落地 —— 它让重跑不产生假 FAIL。
复现三连
# 0) 前置:uvicorn 8000 + MySQL 在跑
curl -s http://127.0.0.1:8000/api/ready # 期望 ok=true
# 1) RK7 + RK8 缺口补测(退出码 0 全过 / 1 有 FAIL / 2 前置失败)
python scripts/dev/risk_gap_e2e_smoke.py --report D:/tmp/e2e-gap/rk-gap.md
# 2) 回归基线(必须保持退出码 0)
python scripts/dev/risk_e2e_smoke.py --report D:/tmp/e2e-gap/_partc-risk.md
# 3) 浏览器(须 Vite 5173;串行执行,勿与其他包并发)
cd scripts/e2e && node 03-risk.mjs && node 10-risk-pages.mjs
数据副作用(已登记): RK7-06 会把 1 条 pending_review 预警推进为 confirmed_normal。risk_alert 是追加型审计表 —— 按语义永不还原(还原反而破坏审计链)。本轮共执行 4 次含处置的跑批,pending_review 计数 41 → 35。这是已知的、刻意的残留,不是数据损坏:
- 影响面:风控首页「今日新增 / 待审核预警总数」KPI 与预警台账行数。
- 可复现:
python scripts/dev/risk_gap_e2e_smoke.py --only RK7每跑一次再 −1。 - 重新灌数:
scripts/demo/seed_risk_agent.ps1(如需恢复演示态)。
剩余风险 / 未覆盖
| # | 项 | 说明 |
|---|---|---|
| 1 | risk.py:134 的 404 分支仍未被任何用例触达 |
三个演示角色分别得 200/403/403。可达条件未构造出来 ⇒ 未覆盖路径,不是缺陷 |
| 2 | pending_review 计数不可还原 |
见上「数据副作用」。凡依赖"预警总数"的用户可见数字,已与 Phase 0 快照不同 |
| 3 | created_at 时区口径未统一 |
RK 发现-2;"近 N 分钟"类查询重跑需 ≥15 分钟窗口 |
| 4 | RK8 只对照了 10-risk-pages.mjs |
03-risk.mjs 的恒真断言(F-01)已修复并实跑 PASS,但未与历史基线逐条对照(该脚本历史产物无对应 results-03-*.json) |
| 5 | AML 扫描未做写入侧深挖 | 依赖 prepare_all.ps1 预处理,本轮只跑既有基线口径 |