Files
Neo-ZQYY/docs/_overview/wave1-findings/01-W1-findings-response.md
Neo 8458cfaae2 docs(audit): Wave 1 findings 第二轮反馈追加 (5 项深入答疑)
回答 Neo 在 01-W1-findings-response.md 上写的 5 个新问题:

1. F1-3 Frozen >1 年 - 本地硬盘 tar.zst 即可, 不必对象存储
2. F2-1 OpenAPI 作用深入浅出 - "厨房菜单 vs 大堂菜单"比喻 +
   5 个使用场景 (FastAPI 双层结构 + 28 天 stale 不破坏运行的原因)
3. F2-2 自建 Gitea 优化 - .gitea/workflows/ 替代 GitHub Actions,
   默认仍 5 分钟版 (步骤 1+2+5), 启 Actions 可选
4. F3-2 system prompt 计费 - 100% 计入 (Qwen 无 caching 折扣) +
   推荐方案 A 全 prompt 入 git 单源 (新增 Wave 5 prompt 治理任务)
5. F3-4 全 API 端点遍历 - ~25-30 端点 / 半天集中改造,
   兑现 Wave 0 全览调研价值 (推荐 Wave 5 集中, 不分散)

最终 Wave 总分配:
- Wave 1 (进行中): F1-5
- Wave 2 前 (立即): F2-1A 恢复脚本
- Wave 2: F1-1 / F2-1B hook / F3-1 / F3-2A / F3-3
- Wave 4: F1-2 / F1-3 三层归档 / F3-5
- Wave 5: F2-2 tests / F3-4 全 API 沙箱校验 / F3-2B prompt 治理 (新增)

待 Neo 拍板 3 项:
- F2-2 是否启 Gitea Actions
- F3-2B 全 App prompt 入 git 单源 是否启动
- F3-4 集中 Wave 5 vs 分散
2026-05-05 00:20:19 +08:00

548 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 有合法授权)。
*反馈选择方案A*
---
### 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 句关键字段)用于人眼浏览。
*反馈:同意你的做法,选择**改良版 D(Hot DB 30 天 + Cold Parquet 按月分区)**。但Frozen(>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)。
*反馈我使用自建Gitea服务器和GitHub没关系啊。再看下方案进行优化修改。*
---
## 五、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 = 调价前一日`)。
*同意你的调研结果按这个来。另外system prompt要不要也计算在内我是否要更新一版全App的System prompt同步在项目中*
---
## 六、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 内任何模块改动时顺手加。
*反馈这个方式好友好的交互。但要注意你说说的所有API数据的端点都要加解析API和页面需要全部遍历之前提过的一个文档全览的一个任务的价值。让所有页面和功能都适配。*
---
## 七、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 防御。
*反馈同意你的建议吸取教训建立一些机制不要让类似事件再发生。另外我还是不太明白OpenAPI 脚本再本项目中的作用,结合业务,深入浅出的再向我解释下。*
---
## 八、本次响应后 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)
---
## 十、第二轮追加 — Neo 5 个新问题回答
### 10.1 F1-3 追问 — Frozen >1 年 用对象存储,**是放在本地硬盘么?**
**答**:**对**。"对象存储"是技术术语,**不一定是云**,在你项目里**直接本地硬盘即可**。
**澄清概念**:
| 形态 | 定义 | 你项目是否合适 |
|---|---|---|
| 云对象存储 | 阿里云 OSS / 腾讯云 COS / AWS S3 | 不适合(外部依赖 + 要钱) |
| 本地对象存储 | MinIO(开源,本地部署,S3 兼容) | 数据量大时再考虑 |
| **本地文件系统** | `/data/cold/.../frozen/YYYY.tar.zst` | **就用这个最简** |
**修正后 Frozen 方案**:
- 1 年以上的月份 Parquet 打包成 `frozen/2026.tar.zst`(zstd 压缩,比 gzip 小)
- 放服务器本地硬盘指定目录(如 `/data/cold/ai_run_logs/frozen/`)
- DB index 表记 `frozen_path = '/data/cold/.../2026.tar.zst'`,需要时主线脚本解压查
- **不必上 MinIO 或 OSS**,直到数据量超过 100GB 再考虑
**补充**:这种"本地硬盘冷归档"也是工业常见做法(很多自托管系统都这么干,比如 PostgreSQL WAL 归档)。
---
### 10.2 F2-1 追问 — OpenAPI 脚本作用 深入浅出
**用人话说**:OpenAPI 是后端给前端 / AI 工具 / 测试团队看的"**功能菜单 JSON**",菜单要随后端改动同步更新。
**菜单上写什么**:
- 项目有哪些 API(151 个)
- 每个 API 接受什么参数(GET / POST / 必填 / 可选)
- 每个 API 返回什么字段(响应结构)
- 每个 API 的权限要求(super_admin / 任意 admin / 公开)
**FastAPI 的双层结构**:
```text
后端运行时 ──→ 自动暴露 GET /openapi.json(实时生成的菜单,永远最新)
↓ 用 _export_openapi.py 抓
docs/contracts/openapi/backend-api.json(静态版本,存 git,不启动后端就能查)
```
**菜单(backend-api.json)在 NeoZQYY 的 5 个使用场景**:
| # | 用途 | 当前是否在用 |
|---|---|---|
| 1 | admin-web 前端类型自动生成(openapi-codegen)| **没用**(前端手写 axios)|
| 2 | MCP 工具读 spec(`.mcp.json``openapi` 配置)→ AI 助手知道项目有哪些 API | **在用**(但 stale 时 AI 不知道新 API)|
| 3 | Swagger UI 渲染(交互式 API 文档页面)| **在用**(后端 `/docs` 路由)|
| 4 | API 变更追踪(diff 新旧 backend-api.json)| 偶尔用 |
| 5 | Wave 1 W1-T7 PRD 撰写自动生成总表 | **本次在用**(发现缺端点)|
**为什么 28 天没发现 stale**:
- 前端**手写 axios**,不依赖 openapi-codegen 自动生成 → 没"前端自动断"反馈
- MCP 工具 stale 时 AI 助手只是"不知道某些新 API",不会破坏运行功能
- Swagger UI 走的是后端运行时 `/openapi.json`,不读 backend-api.json,**也不会受影响**
- **真正会断的是 Wave 1 这种"自动生成 PRD"工作流** — 28 天内没人做这事
**菜单与代码的关系比喻**:
- 后端 router 改了 → 相当于厨房新加了一道菜
- 自动菜单(`/openapi.json`)立刻有这道菜
- **静态菜单(backend-api.json)需要服务员(脚本)定期把厨房菜单抄到大堂**
- 服务员被误辞退后,大堂菜单 stale 28 天
**修复价值**:
- A 恢复脚本 + 重抓:服务员回来了
- B 加 hook:每次厨房改菜,自动叫服务员
---
### 10.3 F2-2 追问 — 自建 Gitea,方案优化
**修正方案**(对齐 Gitea 自建环境):
| 步骤 | 动作 | 备注 |
|---|---|---|
| 1 | 删 `.gitignore:71` `tests/` | 不变 |
| 2 | `git add apps/*/tests/` 入仓现有所有测试 | 不变 |
| 3 | (可选)加 `.gitea/workflows/test.yml` Gitea Actions | Gitea 1.20+ 支持 Actions(语法兼容 GitHub Actions),自建 Runner |
| 4 | 不脱敏(Neo 允许)| 不变 |
| 5 | 写审计 | 标"自建 Gitea + 仅本地测,可选 Gitea Actions" |
**步骤 3 Gitea Actions 是否启用**:
- **启**:Gitea 服务器有富余资源 + 想自动跑测试 + Runner 已配 → 推荐(类似 GitHub Actions 体验)
- **不启**:仅本地跑(Neo 当前前提)→ 跳过这步,5 分钟方案就够
**关键差异(对比 GitHub Actions)**:
- 配置文件路径:`.gitea/workflows/*.yml`(不是 `.github/workflows/`)
- 语法 95% 兼容 GitHub Actions
- Runner 在自己服务器(数据不出自己机房,符合 Neo 自建偏好)
- 免费(GitHub Actions 私有仓有 quota,Gitea 自建无限制)
**修正后判定**:**Wave 5 处理**,默认走 5 分钟版(步骤 1+2+5);如 Neo 需要 CI,Wave 5 末尾追加步骤 3。
---
### 10.4 F3-2 追问 — system prompt 计费 + 全 App prompt 同步项目
#### 问题 A:system prompt 是否计入 token 费用?
**答**:**100% 计入**。
DashScope 计费规则:
- `input_tokens` = system_prompt tokens + 历史消息 tokens + 本次 user 消息 tokens
- 每次调用都会重新算一遍 system prompt(没有"prompt 缓存折扣")
**示例**:
- system prompt 长 1000 字符(中文)≈ 600 tokens
- 1000 次调用累计 = 60万 input tokens
- 60万 × ¥8.64/1M = **¥5.18**(单 system prompt 部分一年的成本)
**优化建议**:
- 精简 system prompt(每减 100 字 → 一年省 ~¥1)
- 不要把"几乎不变的业务字典"塞 system prompt(可以塞 user prompt 末尾,某些 model 支持 prompt cache)
- Qwen 系列**没有 prompt caching 折扣**(Anthropic / OpenAI 部分模型有),所以全量计费
#### 问题 B:是否要更新一版全 App 的 system prompt 同步在项目中?
**强烈推荐 A 全 prompt 入 git 作为权威源**
**当前现状**(P2-6 调研发现):
- 8+1 个 APP 的 prompt 一部分在百炼云端控制台,一部分在 git (`apps/backend/app/ai/prompts/app[N]_*_prompt.py`)
- **双源风险**:云端调优后 git 没同步,或 git 改了云端没更新 → 实际运行用的是哪个?
**3 套方案对比**:
| 方案 | 描述 | 优 | 劣 |
|---|---|---|---|
| **A 全 prompt 入 git + SDK 显式传**(推荐)| 调用 dashscope SDK 时传 `prompt_template` 用 git 版本,云端控制台 prompt 仅作开发调试用 | git 是唯一权威 + 可 diff / blame | 工作量大,需要把云端现有 prompt 全部导出入 git |
| B 仅 git 备份 + 云端权威 | 每月手工导出云端 prompt 到 git docs | 简单 | 易过期,不可追溯 |
| C 维持现状 | 双源分裂 | 0 改 | 完全不可追溯 |
**A 实施步骤**:
1. 先做"勘察":列出 8+1 个 APP 哪些 prompt 在云端、哪些在 git
2. 把云端 prompt 全部导出到 `apps/backend/app/ai/prompts/`(每 APP 一文件)
3. SDK 调用改为传 `prompt_template = open(prompt_file).read()`
4. 云端控制台 prompt 标"已废弃,以 git 为准"或直接清空
5. 写 SPEC `docs/prd/specs/PXX-ai-prompt-governance.md` 约定"git 单源"
**关联 P2-6 决策**:Neo P2-6 反馈"暂时不改 prompt 我同意,但要记录后续观察的需求,需要跟踪" — 本项 B 是这个跟踪的具体落地。
**修正后判定**:**新增 Wave 任务 — Wave 5 prompt 治理**(Wave 5 文档收尾时统一处理,因为不阻塞功能)。
---
### 10.5 F3-4 追问 — 全 API 端点遍历改造工作
**Neo 的关联**:
> 注意你说说的,所有 API 数据的端点都要加解析,API 和页面,需要全部遍历(之前提过的一个文档全览的一个任务的价值。)让所有页面和功能都适配。
**完全正确**。这就是 **Wave 0 全览调研的价值兑现**
**覆盖范围估算**(基于 Wave 0 已有矩阵):
| 来源 | 端点数 | 需加 sandbox 校验子集 |
|---|---|---|
| 02b admin-web 矩阵 | 19 路由 | ~8 个(board / runtime-context / db-viewer / 大部分 admin_ai)|
| 02a 小程序矩阵 | 21 页 → ~25 业务 API | ~15 个(board-finance/customer/coach + performance + customer-records + ...)|
| tenant-admin(未在 Wave 0 矩阵但需补) | ~10 个 | ~5 个 |
| 批 1 PRD 23 端点 | — | 已覆盖部分 |
| **合计** | — | **~25-30 个端点需校验** |
**实施方式**(集中而非分散):
```python
# apps/backend/app/services/runtime_context.py
def assert_query_within_sandbox(
site_id: int,
query_dates: list[date], # 用户传入的所有日期参数
) -> None:
"""
沙箱模式下校验:用户查询的日期不能超过 sandbox_date。
超过则抛 HTTPException(422, "沙箱日期 X,无法查询晚于此日期的数据")。
"""
ctx = get_runtime_context(site_id)
if ctx.mode != "sandbox":
return # live 模式不校验
for d in query_dates:
if d > ctx.business_date:
raise HTTPException(
status_code=422,
detail=f"当前为沙箱模式 {ctx.business_date},不能查询晚于此日期的数据(请求 {d})",
)
```
各 service 调用方:
```python
# 任何接受日期 query 的端点
@router.get("/board/finance")
async def get_board_finance(
start_date: date,
end_date: date,
user: CurrentUser = Depends(...),
):
assert_query_within_sandbox(user.site_id, [start_date, end_date]) # 1 行
# 后续业务逻辑不变
```
**总工作量**:
- 1 个 helper 函数(15 分钟)
- ~25-30 处端点各加 1 行调用(每处 2 分钟,合计 1 小时)
- P20 SPEC §3.5 加这条 AC(10 分钟)
- 走查测试每端点 5-10 分钟,合计 ~3-4 小时
- **总和:半天工作量**(零分散)
**这正是 Wave 0 全览的回报**:不需要重新摸排哪些端点要改,矩阵已列出。
**修正后判定**:
- **集中改造**(不在各 Wave 顺手加)→ **Wave 5 末尾专门做一批**,半天搞定
- 关联 Wave 0 矩阵作为校核清单(Wave 5 末打钩"30 个端点已覆盖")
- P20 SPEC §3.5 加 AC + §14.4 走查清单加这条
---
## 十一、本次响应后 Wave 总分配(最终版)
| Wave | 主题 + 新增 |
|---|---|
| **Wave 1**(进行中) | + F1-5 沙箱 batch-run 接入 runtime_context |
| **Wave 2 前(立即)** | F2-1 恢复 OpenAPI 抓取脚本(从 _DEL/ 拉回 + 重抓 + 入仓)|
| **Wave 2** | F1-1 长事务幂等 + F2-1B 加 hook + F3-1 cache 粒度 + F3-2 SCD2 配置表 + F3-3 audit_log + 二次确认 |
| **Wave 4** | F1-2 切 site 过滤 UX + F1-3 Hot/Cold/Frozen 三层 + F3-5 unified 分页 |
| **Wave 5** | F2-2 tests/ 入仓(Gitea 简版)+ F3-4 全 API 沙箱校验集中改造(半天 + 30 端点)+ **F3-2B prompt 治理(全 prompt 入 git 单源)**(新增)|
| **撤销** | F1-4 |
## 十二、还需要 Neo 拍板的 3 项
| # | 问题 | 我的建议 |
|---|---|---|
| F2-2 | 是否启 Gitea Actions(还是仅本地跑)| 看 Neo 自建 Gitea 是否方便 — 不强求 |
| F3-2B | 全 App prompt 入 git 单源(选 A)是否启动 | **Y**,Wave 5 处理 |
| F3-4 | 集中 Wave 5 改造(半天)vs Wave 1-4 分散加 | **集中 Wave 5**(避免分散遗漏)|
回答这 3 项,Wave 1 findings 完整收口。F2-1 恢复脚本可以现在就做(零风险 5 分钟)。
---
> 下一轮 Neo 反馈完成后,主线立即按最终 Wave 分配执行。Wave 1 仅剩 W1-T8 §14 走查 + F1-5 沙箱 batch-run 接入 runtime_context 两项即可收尾。