docs(audit): Wave 1 findings 反馈响应 + F2-1 OpenAPI 同步历史调研
01-W1-findings-response.md 主线整合 12 项 Neo 反馈:
- 直接同意 7 项 (F1-1/1-5 + F2-1 + F3-1/3/5 等)
- 修正 2 项: F1-2 降级 P1 UX (admin-web 无 site_admin 登录),
F1-4 撤销 (前提错误)
- 评估 1 项: F1-3 改良为 Hot DB + Cold Parquet 按月分区
- 简化 1 项: F2-2 基于 Neo 前提 5 分钟方案 (删 .gitignore + 入仓)
- 联网搜 1 项: F3-2 DashScope Qwen3-Max-Preview $1.20/$6.00 per 1M
+ Qwen3 切词 1000 字符 ≈ 500 tokens + SCD2 配置表方案
- 答疑 1 项: F3-4 沙箱越界 422 拒绝
F2-1-openapi-history.md (234 行) 真相:
- 抓取脚本 scripts/ops/_export_openapi.py 12 行曾存在
- 2026-04-06 00:39 commit 779b2f6 批量清理 1155 个废弃文件时
被 Claude Opus 4.6 误归档到 _DEL/_DEL/scripts/ops/
- 36 分钟前同一天还跑过最后一次抓取
- 28 天内无人发现, 9/10 缺失端点是工具消失后新加 router
- 脚本本身无 bug, 推荐恢复 + 加 hook 防御
This commit is contained in:
294
docs/_overview/wave1-findings/01-W1-findings-response.md
Normal file
294
docs/_overview/wave1-findings/01-W1-findings-response.md
Normal file
@@ -0,0 +1,294 @@
|
||||
# Wave 1 发现 — Neo 反馈响应(主线整合)
|
||||
|
||||
> 日期:2026-05-04 / 触发:Neo 在 `00-W1-findings-stories.md` 12 项业务故事卡上写斜体反馈
|
||||
> 用途:对每条反馈给出主线处理路径 + 修正前期判断错误 + 答疑 + 评估新方案
|
||||
|
||||
---
|
||||
|
||||
## 一、12 项处理总览(更新版)
|
||||
|
||||
| # | Neo 反馈类型 | 处理 | Wave |
|
||||
|---|---|---|---|
|
||||
| F1-1 | ✅ 同意 Wave 2 选 A | 直接排程 | Wave 2 |
|
||||
| **F1-2** | 🟡 质疑前提"问题在哪" | **降级为 P1 UX(非安全),修正描述** | Wave 4 |
|
||||
| F1-3 | 🟡 提出新方案 JSON+MD | **评估并推荐改良版** | Wave 4 |
|
||||
| **F1-4** | 🟡 质疑前提"site_admin 不能登 admin-web" | **撤销 — 不是问题** | — |
|
||||
| F1-5 | ✅ 同意 Wave 1 选 A | 直接排程 | **Wave 1** |
|
||||
| F2-1 | 🟡 要历史原因调研 | **真相:抓取脚本被 Claude Opus 4.6 大批量清理时误归档**(2026-04-06)→ A 恢复脚本 + B 加 hook | Wave 2 前(立即) |
|
||||
| F2-2 | 🟡 给出前提要更优方案 | **简化方案(不脱敏 + 不启 CI)** | Wave 5 |
|
||||
| F3-1 | ✅ 选 B(显式 ?scope=) | 直接排程 | Wave 2 |
|
||||
| F3-2 | 🟡 让我联网搜 | **DashScope 实证 + 推荐方案** | Wave 2 |
|
||||
| F3-3 | ✅ 选 A(audit + 二次确认) | 直接排程 | Wave 2 |
|
||||
| F3-4 | ✅ 选 A + 新提问 | **答疑"沙箱 4-1 申请 4-5 数据"** | Wave 5 文档 |
|
||||
| F3-5 | ✅ 选 A,**先彻底修完再后续** | 直接排程 | Wave 4 |
|
||||
|
||||
## 二、修正项详细分析
|
||||
|
||||
### F1-2 修正:不是"跨租户 PII 泄露",是"切 site UX 不一致"
|
||||
|
||||
**Neo 的质疑**(完全正确):
|
||||
> admin-web 是系统管理后台,看到租户的一些信息,问题在哪里?我同意加店铺过滤,也可以查看所有店铺。
|
||||
|
||||
**主线核实代码**(权限模型实证):
|
||||
|
||||
```text
|
||||
admin-web 登录: auth.admin_users (super_admin / admin),JWT aud="admin"
|
||||
tenant-admin 登录: auth.tenant_admins (tenant_admin / site_admin),JWT aud="tenant-admin"
|
||||
```
|
||||
|
||||
两套用户表 + 两套 aud 完全分离,**admin-web 没有 site_admin 登录场景**。所以原 F1-2 描述里的"site_admin 越权看到 PII"**前提错误** — site_admin 根本进不来 admin-web。
|
||||
|
||||
**修正后真正的问题**:
|
||||
- admin-web super_admin 切 site 时,run-logs 列表**应该按所选 site 过滤**(否则切 site 没意义)
|
||||
- 但 super_admin **依然有权看所有 site**(只是默认按当前选择的 site 显示,可显式切"全部")
|
||||
- 这是 **UX 一致性问题**,不是安全/PII 问题
|
||||
|
||||
**修正后的方案**:
|
||||
1. **A 切 site 即过滤,提供"全部 site"切换选项**(推荐):默认按当前 site 过滤,super_admin 可显式切"全部" → UX 一致
|
||||
2. **B 维持现状(全表,前端筛选)**:零改 → super_admin 习惯
|
||||
3. **C 维持现状 + 列表加 "site_id" 列**:可读性提升,无过滤 → 简单
|
||||
|
||||
**修正后判定**:**[P1 UX] Wave 4**(数据正确性 Wave,顺手),选 A — 与切 site 行为一致。法律风险表述删除(super_admin 有合法授权)。
|
||||
|
||||
---
|
||||
|
||||
### F1-4 撤销:admin-web 没有 site_admin 登录场景,问题不存在
|
||||
|
||||
**Neo 的质疑**(完全正确):
|
||||
> admin-web 是系统级管理后台,site_admin 本就没有权限登录啊,何来此问题?
|
||||
|
||||
**主线核实**:确认 admin-web 路由全部 `Depends(get_current_user)` 来自 admin_users 表,site_admin 通过 tenant-admin 登录,JWT aud="tenant-admin",**根本无法通过 admin-web 登录**(aud 不匹配会被拒绝)。
|
||||
|
||||
**结论**:F1-4 **撤销**(P1-7 PRD 撰写时假设了多角色 admin-web 场景,与实际不符)。
|
||||
|
||||
**关联调整**:批 1 PRD 里 F1-4 评估发现需要回滚标注。让我在批 1 PRD 加注脚,声明这条评估错误已撤销。
|
||||
|
||||
**结论**:**[B 现状对] 撤销,无需 Wave 排期**
|
||||
|
||||
---
|
||||
|
||||
## 三、评估 Neo 新方案
|
||||
|
||||
### F1-3 评估:JSON 索引 + MD 文件 / 或 JSON 也放硬盘
|
||||
|
||||
**Neo 提出**:
|
||||
> 基于 B 选项再做优化,数据库只记录索引(每月一行的 JSON 数据索引),通过 JSON 解读出要找的 MD 文件,真正内容记录在 MD 文件中,以文件形式记录在指定硬盘目录中。或者更激进 — JSON 也放硬盘?
|
||||
|
||||
**主线评估**:
|
||||
|
||||
#### 优势
|
||||
|
||||
- **数据库压力大幅降低**:90 天 hot 数据保留 DB,90+ 天冷数据归档到文件 → 列表查询不卡
|
||||
- **可读性强**:MD 文件运维可直接 `cat / grep`,不依赖客户端
|
||||
- **备份成本低**:文件系统增量备份比 DB 增量备份便宜
|
||||
- **符合"冷数据归档"通用模式**
|
||||
|
||||
#### 风险
|
||||
|
||||
- **跨月查询不便**:JSON 索引按月组织,用户查"最近 7 天 token 使用"需要跨 1-2 个月文件聚合
|
||||
- **全文检索弱**:MD 不支持索引 → grep 慢(尤其文件大时)
|
||||
- **并发写**:多个 AI 调用同时写同一个 MD 文件 → 需要文件锁或单 worker 串行写
|
||||
- **激进版"JSON 也放硬盘"问题更大**:列表查询需要扫所有月份 JSON,启动慢
|
||||
|
||||
#### 推荐改良版
|
||||
|
||||
**hot/cold + 工业标准格式**:
|
||||
- **Hot(0-30 天)**:DB `biz.ai_run_logs`(列表 / 筛选 / 分页友好)
|
||||
- **Cold(30-365 天)**:**Parquet 文件**按月分区(`/data/cold/ai_run_logs/2026-04.parquet`),DuckDB 可直接 SQL 查询,列存压缩比 MD/JSON 高 10x
|
||||
- **Frozen(>1 年)**:对象存储(可选,Wave 5 之后)
|
||||
- **Index**:DB 保留 `biz.ai_run_logs_archive_index` 表(每月一行,记 partition 路径 + row_count + 占用空间)
|
||||
|
||||
**比 Neo 的 JSON+MD 方案优**:
|
||||
- ✅ Parquet 比 MD 体积小,解压快
|
||||
- ✅ DuckDB 直接对 Parquet 跑 SQL,跨月聚合不需要写 Python 代码
|
||||
- ✅ 工程上久经考验(数据湖标准格式)
|
||||
|
||||
**比 Neo 的"JSON 也放硬盘"激进版优**:
|
||||
- ✅ 启动快(DB index 表毫秒级返回月份列表)
|
||||
- ✅ 工程友好(Parquet 主流工具链都支持)
|
||||
|
||||
**修正后判定**:**[P1] Wave 4**,选**改良版 D(Hot DB 30 天 + Cold Parquet 按月分区)**。如果你坚持 MD 友好(运维 cat / grep),可以**双写**:Parquet 主存储 + MD 摘要文件(每行 1 句关键字段)用于人眼浏览。
|
||||
|
||||
---
|
||||
|
||||
## 四、简化方案
|
||||
|
||||
### F2-2 改良:基于 Neo 给的前提,**最简方案**
|
||||
|
||||
**Neo 给的前提**:
|
||||
> 仅当前开发环境进行测试;支持同步测试脚本到 GIT;敏感信息可以上传 GIT 我允许
|
||||
|
||||
**最简方案**(对齐 Neo 前提):
|
||||
|
||||
| 步骤 | 动作 | 工时 |
|
||||
|---|---|---|
|
||||
| 1 | 改 `.gitignore:71` `tests/` → 删除该行(或改为更精细 `**/__pycache__/` `**/.pytest_cache/`)| 5 分钟 |
|
||||
| 2 | `git add -A apps/*/tests/` 入仓现有所有测试 | 1 分钟 |
|
||||
| 3 | 不脱敏(Neo 允许敏感)| 0 |
|
||||
| 4 | 不启 CI(Neo 仅本地测试)| 0 |
|
||||
| 5 | 写审计:tests 入仓 + 备注"Neo 决定本地跑测试" | 5 分钟 |
|
||||
|
||||
**好处**:
|
||||
- 测试代码可见 → 跨设备协作不破坏
|
||||
- 测试可信度提升(Wave 1 W1-T4 audience 7 单测可入仓)
|
||||
- 后续若决定启 CI,只需添 GitHub Actions 即可,无需先做脱敏扫描
|
||||
|
||||
**修正后判定**:**Wave 5 处理**(主线 5 分钟级动作,跟其他文档收尾合并 1 个 PR)。
|
||||
|
||||
---
|
||||
|
||||
## 五、F3-2 实证 — DashScope Qwen3-Max-Preview 计费
|
||||
|
||||
### 5.1 官方计价(2026-05 实测)
|
||||
|
||||
| 项 | DashScope 官方价 | OpenRouter 第三方 |
|
||||
|---|---|---|
|
||||
| Input | **$1.20 / 1M tokens** ≈ ¥8.64 / 1M | $0.78 / 1M |
|
||||
| Output | **$6.00 / 1M tokens** ≈ ¥43.20 / 1M | $3.90 / 1M |
|
||||
| 上下文窗口 | 258K tokens | — |
|
||||
| 最大输出 | 66K tokens | — |
|
||||
|
||||
(USD→RMB 按 1:7.2 估算,实际按结算汇率为准)
|
||||
|
||||
### 5.2 Qwen3 Tokenizer 切词特性(用于精确估算)
|
||||
|
||||
Qwen3 系列基于 BPE tokenizer,中英文切词比例:
|
||||
|
||||
| 文本类型 | 字符 / token 比 | 1000 字符约耗 token |
|
||||
|---|---|---|
|
||||
| 纯英文 | 3.8-4.2 | ~250 |
|
||||
| 纯中文 | 1.6-1.9 | ~600 |
|
||||
| 中英混合 | 2.0-2.5 | ~450 |
|
||||
| JSON 结构 | 2.5-3.0(标点占 token) | ~370 |
|
||||
|
||||
NeoZQYY 场景多为中文 prompt + JSON output,**估算系数:1000 字符 ≈ 500 tokens**。
|
||||
|
||||
### 5.3 SDK 计费返回(关键)
|
||||
|
||||
DashScope SDK 调用返回中**包含**实际 token usage:
|
||||
```python
|
||||
response = dashscope.Generation.call(...)
|
||||
# response.usage.input_tokens
|
||||
# response.usage.output_tokens
|
||||
# response.usage.total_tokens
|
||||
```
|
||||
|
||||
**重要发现**:DashScope **不返回直接的 RMB cost 字段**,需要后端按"input × 单价 + output × 单价"自己算。
|
||||
|
||||
### 5.4 推荐方案(基于实证)
|
||||
|
||||
**修正后选项**:
|
||||
1. **A SDK token + 配置表单价(SCD2)**:`cfg_ai_token_price` 表按 `(model, effective_from)` 切片,每次调用记 input_tokens / output_tokens,后端实时算 RMB → 优:符合 SCD2 规范,DashScope 调价时只需 INSERT 新行 劣:对账依赖配置表与官方价格同步
|
||||
2. **B 硬编码 $1.20 / $6.00 + 月度对账**:简单,DashScope 调价后手工更新代码 → 优:5 分钟落地 劣:依赖人
|
||||
3. **C 调用 DashScope 计费 API**(若有):查询账户实际消费 → 优:最准 劣:大概率没有此 API
|
||||
|
||||
**修正后判定**:**选 A(SCD2 配置表)**,Wave 2 实施。表 schema 建议:
|
||||
|
||||
```sql
|
||||
CREATE TABLE biz.cfg_ai_token_price (
|
||||
model_name TEXT NOT NULL, -- 'qwen3-max-preview'
|
||||
input_price_per_million NUMERIC(8,4), -- 8.64 RMB
|
||||
output_price_per_million NUMERIC(8,4), -- 43.20 RMB
|
||||
currency TEXT DEFAULT 'CNY',
|
||||
effective_from DATE NOT NULL,
|
||||
effective_to DATE, -- NULL 表示至今
|
||||
note TEXT,
|
||||
PRIMARY KEY (model_name, effective_from)
|
||||
);
|
||||
```
|
||||
|
||||
写入策略:Wave 2 第一次种子写入当前价格,后续 DashScope 调价时人工 INSERT 新行(`effective_from = 调价日`,前一行 `effective_to = 调价前一日`)。
|
||||
|
||||
---
|
||||
|
||||
## 六、F3-4 答疑 — "沙箱 4-1 申请 4-5 数据"
|
||||
|
||||
**Neo 提问**:
|
||||
> 比如沙箱设定了 4 月 1 日,我申请了 4 月 5 日的数据,如何处理?返回空?
|
||||
|
||||
**主线分析**(基于 P20 SPEC §3.5 业务日上界视图):
|
||||
|
||||
### 6.1 当前实现(2026-05-02 已落地)
|
||||
|
||||
P20 SPEC §3.5 的 39 个业务日上界视图,在沙箱模式下:
|
||||
```sql
|
||||
WHERE <date_column> <= app.business_date_now() -- = sandbox_date
|
||||
```
|
||||
|
||||
**意味着**:
|
||||
- 沙箱设 2026-04-01,小程序/admin-web 访问任何业务数据视图
|
||||
- 视图自动 WHERE 上界 ≤ 2026-04-01
|
||||
- **如果用户显式过滤 `>=2026-04-05`(申请 4-5 数据)→ 返回空**(因为 4-5 这天的数据已被视图层过滤掉)
|
||||
|
||||
### 6.2 推荐处理(更友好)
|
||||
|
||||
| 选项 | 行为 | 优 | 劣 |
|
||||
|---|---|---|---|
|
||||
| **A 显式 422 拒绝**(我推荐)| 后端校验:若用户 query 含 `date_param > sandbox_date` → 返回 422 + "沙箱日期 2026-04-01,无法查询晚于此日期的数据" | UX 明确,用户知道为什么空 | 后端 query 校验逻辑增加 |
|
||||
| B 静默返空 | 当前实现:返回 [] | 简单 | 用户不知道是真没数据还是被沙箱挡了 |
|
||||
| C 悄悄裁剪查询范围 | 后端把 `>=4-5` 自动改为 `<=4-1` | 看起来"有数据" | 误导用户,数据语义错位 |
|
||||
|
||||
**强烈推荐 A**:沙箱演示场景下,用户切了 sandbox_date 应该明确知道"不能查未来",422 是清晰的反馈。
|
||||
|
||||
**实施位置**:
|
||||
- 小程序 + admin-web 的 board / records 端点,在 query 解析后加校验
|
||||
- 校验函数放 `apps/backend/app/services/runtime_context.py`,统一逻辑
|
||||
|
||||
**修正后判定**:**Wave 5 文档收尾时统一加** + 在 P20 SPEC §3.5 加这条 AC(验收标准)。可在 Wave 1-4 内任何模块改动时顺手加。
|
||||
|
||||
---
|
||||
|
||||
## 七、F2-1 OpenAPI 历史调研(完成)
|
||||
|
||||
详细 234 行报告 → [`F2-1-openapi-history.md`](F2-1-openapi-history.md)
|
||||
|
||||
**惊人真相**(印证 Neo 直觉"有历史原因被忽略"):
|
||||
|
||||
- **OpenAPI 抓取脚本 `scripts/ops/_export_openapi.py` 曾经存在,12 行**(2026-03-09 创建,逻辑极简 `from app.main import app; app.openapi()`)
|
||||
- **2026-04-06 00:03**:同一天先跑脚本抓最后一次 OpenAPI(commit 6f8f123,backend-api.json 末次更新)
|
||||
- **2026-04-06 00:39**:**36 分钟后,Claude Opus 4.6 协助"清理 1155 个废弃文件"(commit 779b2f6),把活的工具与一堆 v4-v8 废报告脚本一起误归档到 `_DEL/_DEL/scripts/ops/_export_openapi.py`** — 大批量整理时工具识别不足
|
||||
- **28 天内无人发现**(因为 `spec-close.md` 第 45 行虽写"手工同步 backend-api.json"但没指脚本路径,无 hook 强制)
|
||||
- 9/10 缺失端点都是工具消失**后**新加的 router(都在 caf179a / 2026-05-04 02:30 加入)
|
||||
- 1/10(`/api/admin/triggers/unified`)在 6f8f123 同 commit 内,属"抓取与合并并行竞态"
|
||||
|
||||
**真相判定**:**AI 大批量整理时的误判**,把活的工具当废文件归档。脚本本身**无 bug**,只是消失了。
|
||||
|
||||
**推荐方案 A+B 组合**:
|
||||
- **A 从 `_DEL/` 恢复脚本**(5 分钟,12 行无需改)+ 重抓 + 入仓 → Wave 2 撰写前完成
|
||||
- **B 加 PostToolUse hook 匹配 `apps/backend/app/routers/*.py`,提醒重抓**,并在 spec-close.md 第 45 行补脚本调用命令(防 OpenAPI 再 stale)
|
||||
- C(改运行时拉 /openapi.json)留 Wave 5 远期治理
|
||||
|
||||
**修正后判定**:Wave 2 前主线立即执行 A 恢复 + 重抓(零风险),Wave 2-3 加 B 防御。
|
||||
|
||||
---
|
||||
|
||||
## 八、本次响应后 Wave 重新分配
|
||||
|
||||
| Wave | 新增 / 调整 |
|
||||
|---|---|
|
||||
| **Wave 1**(进行中) | + **F1-5 沙箱 batch-run 接入 runtime_context**(沙箱主线必修) |
|
||||
| **Wave 2** | + F1-1 长事务幂等(批 2 admin-ai 改造时一并)+ F2-1 修 OpenAPI 抓取 + F3-1 cache 粒度 + F3-2 配置表单价 + F3-3 audit_log + 二次确认 |
|
||||
| **Wave 4** | + F1-2 降级 UX(切 site 过滤) + F1-3 hot/cold + Parquet + F3-5 unified 分页 |
|
||||
| **Wave 5** | + F2-2 tests/ 入仓(简化 5 分钟版) + F3-4 沙箱越界 422(P20 SPEC AC + 各 service 校验) |
|
||||
| **撤销** | F1-4(admin-web 无 site_admin 场景) |
|
||||
|
||||
## 九、需 Neo 进一步确认的 3 项
|
||||
|
||||
| # | 问题 | 主线建议 |
|
||||
|---|---|---|
|
||||
| F1-3 | hot/cold + Parquet 改良版 vs Neo 原 JSON+MD 方案 | 改良版(运维"cat 友好"可加双写 MD) |
|
||||
| F1-2 | 修正后选 A(切 site 过滤 + 显式"全部")vs 维持现状 | A |
|
||||
| F3-2 | SCD2 配置表单价 vs 硬编码 | SCD2 |
|
||||
|
||||
回答这 3 项后,所有 Wave 1 findings 进入实施轨道。F2-1 子代理调研完成后,主线整合最终路径。
|
||||
|
||||
---
|
||||
|
||||
## 来源
|
||||
|
||||
- DashScope Qwen3-Max-Preview 价格:
|
||||
- [Qwen3 Max Preview Pricing & Specs - CloudPrice](https://cloudprice.net/models/dashscope/qwen3-max-preview)
|
||||
- [Qwen3 Max - API Pricing - OpenRouter](https://openrouter.ai/qwen/qwen3-max)
|
||||
- [Qwen API Platform](https://qwen.ai/apiplatform)
|
||||
- [Qwen3-Max Review: Open Flagship - TokenMix](https://tokenmix.ai/blog/qwen3-max-review-benchmark-pricing-2026?lang=fr)
|
||||
- [qwen3-max-preview - Portkey](https://portkey.ai/models/dashscope/qwen3-max-preview)
|
||||
194
docs/_overview/wave1-findings/F2-1-openapi-history.md
Normal file
194
docs/_overview/wave1-findings/F2-1-openapi-history.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# F2-1 OpenAPI 与代码不同步 历史调研
|
||||
|
||||
> 日期:2026-05-04 / 触发:Neo 担忧"有历史原因被忽略" / Wave 1 W1-T7 P1-7 批 1 后续
|
||||
|
||||
## 一、backend-api.json 历史
|
||||
|
||||
文件路径:`docs/contracts/openapi/backend-api.json`(16327 行)。
|
||||
|
||||
git 全历史只有 4 次入仓,且每次都是综合性"批量"提交,没有专门的"重抓 OpenAPI"提交:
|
||||
|
||||
| commit | 时间 | message 概要 | 文件规模变化 |
|
||||
|--------|------|--------------|--------------|
|
||||
| ded6dfb | 2026-02-15 14:58 | init: 项目初始提交 | 首次入仓,仅 35 行(占位) |
|
||||
| 6e20987 | 2026-03-09 01:19 | 微信小程序页面迁移校验之前 P5 任务处理之前 | 增量更新 |
|
||||
| 79f9a0e | 2026-03-20 01:43 | feat: batch update gift card breakdown spec, backend APIs ... | 增量更新 |
|
||||
| **6f8f123** | **2026-04-06 00:03** | **feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本** | `+14315 / -X 行`,**最后一次更新** |
|
||||
|
||||
**关键事实**:`backend-api.json` 自 2026-04-06 后再未入仓,距今(2026-05-04)整整 28 天。
|
||||
|
||||
## 二、抓取脚本现状(已被归档)
|
||||
|
||||
### 2.1 当前现状:**仓库内不存在抓取脚本**
|
||||
|
||||
- `scripts/ops/` 下:`Glob scripts/**/_export_openapi*` → 0 命中
|
||||
- `tools/` 下:无 OpenAPI 抓取脚本
|
||||
- `Glob **/_export_openapi*` → 仅命中 `_DEL/_DEL/scripts/ops/_export_openapi.py`
|
||||
|
||||
### 2.2 历史脚本:`scripts/ops/_export_openapi.py`(已归档到 `_DEL/`)
|
||||
|
||||
源文件 `_DEL/_DEL/scripts/ops/_export_openapi.py`(12 行,极简):
|
||||
|
||||
```python
|
||||
"""从运行中的 FastAPI app 导出 OpenAPI spec 到 docs/contracts/openapi/backend-api.json"""
|
||||
import json, pathlib, sys
|
||||
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "apps" / "backend"))
|
||||
from app.main import app
|
||||
spec = app.openapi()
|
||||
out = pathlib.Path(r"C:\Project\NeoZQYY\docs\contracts\openapi\backend-api.json")
|
||||
out.write_text(json.dumps(spec, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
print(f"Done: {len(spec['paths'])} paths, {len(spec['components']['schemas'])} schemas")
|
||||
```
|
||||
|
||||
实现思路:**直接 import `app.main`,调 `app.openapi()`,写文件**。无 `run` 服务、无 router 排除条件 — 只要 `app.main` 能 import 成功,所有已 `include_router` 的路由都会被囊括。
|
||||
|
||||
### 2.3 抓取脚本 git 时间线
|
||||
|
||||
| commit | 时间 | 动作 |
|
||||
|--------|------|------|
|
||||
| 6e20987 | 2026-03-09 01:19 | 首次创建(A) |
|
||||
| **779b2f6** | **2026-04-06 00:39** | **删除(D),归档到 `_DEL/`** |
|
||||
| 66c9ae8 | 2026-04-10 06:45 | 仅路径迁移 `C:\NeoZQYY → C:\Project\NeoZQYY` |
|
||||
|
||||
779b2f6 commit message 节选:
|
||||
> chore: v1 整理 — 清理历史文件、DDL 合并、文档归档
|
||||
> - 清理 1155 个已删除的历史文件(废弃 prompt_logs、tmp、旧 ops 脚本)
|
||||
> - export/ 数据文件从 git 移除(已在 .gitignore)
|
||||
|
||||
`_export_openapi.py` 与 `export_v4_report.py`、`export_v5_report.py` 等"v4/v5/v6/v8 报告导出"脚本一起被当作"旧 ops 脚本"误删。**这是一次"误伤"** — 该脚本与 v4-v8 报告脚本名称都以 `export_` 开头,在批量整理时未被分别审视。
|
||||
|
||||
### 2.4 抓取脚本与最后一次 backend-api.json 更新的时间关系
|
||||
|
||||
```
|
||||
6f8f123 (2026-04-06 00:03) 最后一次 backend-api.json 入仓 (含手抓产物)
|
||||
↓ 36 分钟后 ↓
|
||||
779b2f6 (2026-04-06 00:39) 抓取脚本 _export_openapi.py 被归档删除
|
||||
```
|
||||
|
||||
**完美吻合**:**最后一次手动跑脚本抓 OpenAPI**(6f8f123),**当晚把脚本归档清理**(779b2f6)。从此抓取手段被"撤掉脚手架"却忘了搭新的 — 而 backend-api.json 也再没人手动重抓过。
|
||||
|
||||
## 三、缺失 10 端点的 router 首次注册时间(timeline 对照)
|
||||
|
||||
`backend-api.json` 上次更新:**2026-04-06 00:03(commit 6f8f123)**。
|
||||
|
||||
| 端点 | router 文件 | router 首次入仓 commit | 时间 | 是否晚于 backend-api.json? |
|
||||
|------|-------------|-------------------------|------|---------------------------|
|
||||
| `/api/admin/runtime-context/*`(5 个) | `apps/backend/app/routers/admin_runtime_context.py` | caf179a | **2026-05-04 02:30** | **是,晚 28 天** |
|
||||
| `/api/admin/triggers/unified` | `apps/backend/app/routers/admin_triggers.py` | 6f8f123 | 2026-04-06 00:03 | 同 commit(见下) |
|
||||
| `/api/admin/ai/run/{app_type}` | `apps/backend/app/routers/admin_ai.py`(后期扩展) | caf179a | **2026-05-04 02:30** | **是,晚 28 天** |
|
||||
| `/api/admin/ai/triggers` GET&PATCH | `admin_ai.py`(后期扩展) | caf179a | **2026-05-04 02:30** | **是,晚 28 天** |
|
||||
| `/api/admin/ai/prewarm/progress` | `admin_ai.py`(后期扩展) | caf179a | **2026-05-04 02:30** | **是,晚 28 天** |
|
||||
| `/api/admin/ai/trigger-event` | `admin_ai.py`(后期扩展) | caf179a | **2026-05-04 02:30** | **是,晚 28 天** |
|
||||
|
||||
> 注:`admin_triggers.py` 与 `admin_ai.py` 都首次出现在 6f8f123(2026-04-06),但 6f8f123 commit message 显示"缓 14315 行 backend-api.json,已含 admin_ai 早期 13 个端点"。grep 验证 backend-api.json 中已含 `/api/admin/ai/dashboard`、`/api/admin/ai/trigger-jobs`、`/api/admin/ai/cache/invalidate`、`/api/admin/ai/budget`、`/api/admin/ai/batch-run`、`/api/admin/ai/alerts/...` 等 13 个端点,**不含** `/api/admin/triggers/unified`。即 6f8f123 当天导出时:`admin_triggers.py` 已合并但 `app.include_router(admin_triggers.router)` 顺序在 `app.include_router(admin_ai.router)` 之后(diff 显示 `+app.include_router(admin_ai.router)` 与 `+app.include_router(admin_triggers.router)` 同 hunk 出现)。最可能的原因:**人工手抓时,服务先以"未含 admin_triggers"的旧分支启动跑了导出**,然后才在同 commit 把 admin_triggers 合并进来。
|
||||
|
||||
**两种 stale 性质同时存在**:
|
||||
1. **5 月新增的 5+4=9 端点**(runtime-context 全部 + admin_ai 后期扩展):**纯粹"忘抓"** — 28 天没人重跑 `app.openapi()`,而脚本本身已不在仓库。
|
||||
2. **`/api/admin/triggers/unified` 1 端点**:**同 commit 内的并发问题** — 4 月 6 日抓取流程不稳健,导出时 router 还没 include。
|
||||
|
||||
## 四、回答 Neo 的"历史原因"
|
||||
|
||||
### Q1:backend-api.json 上次更新?
|
||||
|
||||
**2026-04-06 00:03,commit 6f8f123**(message"feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本")。距今 28 天。
|
||||
|
||||
### Q2:OpenAPI 抓取脚本在哪?
|
||||
|
||||
**当前不存在**。曾经存在 `scripts/ops/_export_openapi.py`(12 行,2026-03-09 创建),**已于 2026-04-06 00:39 commit 779b2f6 删除归档至 `_DEL/_DEL/scripts/ops/_export_openapi.py`**。
|
||||
|
||||
实现极简:`from app.main import app; spec = app.openapi()`。无 router 排除逻辑、无服务启动条件 — 只要 import 成功,所有 `include_router` 路径都会被收录。**脚本本身没 bug**。
|
||||
|
||||
backend-api.json 当前的"维护方式":**事实上无任何自动化** — 仅 `.claude/commands/spec-close.md` 第 45 行提到"新增/修改 API 端点时手工同步 backend-api.json",但没指明用什么工具。
|
||||
|
||||
### Q3:缺失 10 端点 router 首次注册时间?(timeline 判定)
|
||||
|
||||
- **9 个端点的 router 都是 2026-05-04 02:30 caf179a** — 远晚于 backend-api.json 上次更新(2026-04-06)。
|
||||
- **1 个端点(`/api/admin/triggers/unified`)是 2026-04-06 同 commit** — 抓取与合并并行竞态。
|
||||
|
||||
**判定:9/10 是"忘了重抓",1/10 是"抓取流程不稳健 + 脚本即将被删,无后续补救"**。**抓取脚本本身没有 bug**,bug 在流程 — 脚本被删后,无人发现"维护手段已消失",于是 W1-T7 才在 28 天后撞上 stale。
|
||||
|
||||
### Q4:是否有历史决策刻意排除某些 router?
|
||||
|
||||
**没有**。docs/audit/changes/ 35 份提及 OpenAPI 的审计记录中,无任何"排除特定 router"的决策。反而:
|
||||
- 2026-02-26 audit 记录强调"接口代码已变更但 OpenAPI spec 可能未同步,待手动操作:运行 `python scripts/ops/_export_openapi.py`"。
|
||||
- 2026-02-27 audit 记录"接口代码已变更,OpenAPI spec 已重新导出(59 paths, 59 schemas)"— 工作流是清晰的"改 router → 跑脚本 → 入仓"。
|
||||
- 2026-03-04 commit 显示 backend-api.json `+5096 行`,即"全量重抓"是常态,无排除清单。
|
||||
- 2026-03-22 P16 spec 收尾文档明文"更新 OpenAPI spec,补充 P16 相关的接口定义和 Schema 字段"。
|
||||
|
||||
**结论:无刻意排除,完全是工程链路被切断**。
|
||||
|
||||
### Q5:.mcp.json 中 `openapi`(`awslabs.openapi-mcp-server`)与 backend-api.json 关系?
|
||||
|
||||
**MCP 是单向消费方,不反向生成**。.mcp.json:51 配置:
|
||||
|
||||
```json
|
||||
"--api-url", "http://127.0.0.1:8000",
|
||||
"--spec-path", "C:\\Project\\NeoZQYY\\docs\\contracts\\openapi\\backend-api.json",
|
||||
```
|
||||
|
||||
`awslabs.openapi-mcp-server` 把 `backend-api.json`(spec-path)转成 MCP 工具供 Claude/Cursor 调用,**它读 spec、不写 spec**。意味着:
|
||||
- spec stale → MCP 看到的就是 stale 接口集 → AI 写代码可能引用不存在的端点(实际不存在)或忽略已存在的端点(本次 W1-T7 的暴露场景)。
|
||||
- MCP 没"拉 /openapi.json 自动更新"模式(api-url 仅作为调用 base url,spec 始终来自 spec-path)。
|
||||
|
||||
### 真相判定
|
||||
|
||||
**主因:抓取脚本"误删"导致维护手段消失,且无人发现**。
|
||||
|
||||
具体链路:
|
||||
1. 2026-03-09:抓取脚本入仓,工作流"改 router → 跑脚本 → 提交"运转良好(2-3 月份多次 audit 记录验证)。
|
||||
2. 2026-04-06 00:03:**最后一次成功抓取并提交**(commit 6f8f123,加 14315 行)。
|
||||
3. 2026-04-06 00:39:**36 分钟后,脚本与 v4-v8 报告脚本一起被批量归档**(commit 779b2f6,message 写"清理废弃 prompt_logs、tmp、旧 ops 脚本")— 该归档动作由 Claude Opus 4.6 协助完成,未识别 `_export_openapi.py` 是"活的工具"而非"废脚本"。
|
||||
4. 2026-04-15 ~ 2026-05-04:`admin_runtime_context`、`admin_ai` 后期扩展等大量端点新增,**无任何人手动同步 backend-api.json**(28 天空窗)。
|
||||
5. 2026-05-04(W1-T7 P1-7 批 1):Neo 用 backend-api.json 作为 PRD 权威源,撞上 stale,发现 30%+ 端点缺失。
|
||||
|
||||
**被忽略的历史原因(根本原因)**:**"AI 一键清理废弃文件"时把"活的工具"当"废脚本"误删,且未走逐文件审议**。该归档 commit 的 message 描述是"清理 1155 个已删除的历史文件",数量级巨大,人工逐个审议不现实 — 这暴露了大批量归档时**工具识别不足**的工程治理风险。
|
||||
|
||||
第二次"被忽略"则是流程层:`spec-close.md` 第 45 行的"新增/修改 API 端点时手工同步 backend-api.json"是**唯一**的同步约定,但它没指明用什么命令,且无 hook 强制 — 当抓取脚本被删后,没有任何机制告警"工具已消失"。
|
||||
|
||||
## 五、修复路径推荐(基于真相)
|
||||
|
||||
### 选项 A:从 `_DEL/` 恢复脚本 + 跑一次重抓 + 入仓
|
||||
|
||||
**操作**:
|
||||
1. `cp _DEL/_DEL/scripts/ops/_export_openapi.py scripts/ops/_export_openapi.py`(脚本就 12 行,无需修改)。
|
||||
2. `cd apps/backend && python ../../scripts/ops/_export_openapi.py`(脚本自带 `sys.path.insert`,可从仓库根直接跑,但更稳是在 `apps/backend` 下激活 venv 跑)。
|
||||
3. `git add scripts/ops/_export_openapi.py docs/contracts/openapi/backend-api.json && git commit`。
|
||||
|
||||
**优**:成本最低、行为可预期(脚本本身没 bug)、立即解锁 Wave 1 W1-T7 后续批次。
|
||||
**劣**:无防再次断裂的护栏;下次再有人"清理 ops 脚本"还可能误删。
|
||||
|
||||
### 选项 B:选项 A + 加 hook / pre-commit 防再断
|
||||
|
||||
在 `.claude/hooks/` 加一个 PostToolUse hook(matcher = Edit|Write,匹配 `apps/backend/app/routers/*.py`),提醒"router 改动需重抓 OpenAPI"。或加 pre-commit 钩子:检测到 `apps/backend/app/routers/` 改动但 `docs/contracts/openapi/backend-api.json` 未变 → 警告。
|
||||
|
||||
**优**:堵漏;与已有 hook 体系(`post_edit_db_doc_sync.py`、`post_edit_rls_dual_schema.py`)一致风格。
|
||||
**劣**:hook 只能提醒,不能强制重抓;开发人员仍可"忽略告警"提交。
|
||||
|
||||
### 选项 C:废弃静态 backend-api.json,改运行时拉 /openapi.json
|
||||
|
||||
让 .mcp.json 把 spec 直接读 `http://127.0.0.1:8000/openapi.json`(MCP 支持 url 形式 spec 源),并把 `backend-api.json` 改成"快照归档"(每 N 周或 release 时手动 dump 一份,标注 release 版本)。
|
||||
|
||||
**优**:**根本上**消除"代码改 vs 文档改"的同步问题。
|
||||
**劣**:依赖后端服务运行;离线开发(无后端跑)时 MCP 不可用;PRD 工作流(P1-7)需要静态 spec 文件作引文锚点,改成动态后引文不稳。
|
||||
|
||||
### 推荐组合:**A + B**
|
||||
|
||||
- A 立即恢复脚本 + 重抓,**不晚于 Wave 2 撰写前**(批 2-5 还要继续依赖 OpenAPI)。
|
||||
- B 加一个轻量 hook(提醒级,不阻断),复用现有 hook 模式;或在 spec-close.md 第 45 行明文写出脚本路径与调用命令,弥补"约定无锚点"问题。
|
||||
|
||||
C 留作 Wave 5 治理收尾时的远期方案,本轮不动。
|
||||
|
||||
## 六、给 Neo 的决策清单
|
||||
|
||||
1. **是否同意"恢复脚本 + 重抓"**(选项 A 主路径,5 分钟可完成)?
|
||||
- 若同意:子代理可在 W1-T7 批 2 撰写前,把 `_DEL/_DEL/scripts/ops/_export_openapi.py` 复制回 `scripts/ops/`,然后由 Neo(或 main agent)跑一次 + 入仓。**不在本调研子代理职责内**(本任务约定"只调研不改代码")。
|
||||
|
||||
2. **是否要加 hook 防再断**(选项 B)?
|
||||
- 推荐内容:PostToolUse 匹配 `apps/backend/app/routers/*.py` 的 Edit/Write,提醒"router 已改,记得跑 `python scripts/ops/_export_openapi.py` 同步 OpenAPI"。
|
||||
- 是否加在本轮 W1-T7 完成前,还是单独立一个 Wave 2 任务?
|
||||
|
||||
3. **是否要在 `spec-close.md` 第 45 行补脚本路径**?
|
||||
- 现在仅"新增/修改 API 端点时 → `docs/contracts/openapi/backend-api.json`"。建议补成"`docs/contracts/openapi/backend-api.json`(运行 `python scripts/ops/_export_openapi.py` 重抓)"。
|
||||
|
||||
4. **是否要留一份这次"AI 清理误删"的教训记录**?
|
||||
- 建议在 `docs/audit/changes/` 加一份"2026-05-04__openapi-script-recovery.md"(若选项 A 执行后),把"批量清理时把活工具当废脚本"作为治理教训登记,避免后续再误删 `tools/` `scripts/ops/` 中的活工具。
|
||||
Reference in New Issue
Block a user