产品 2026-09-15 三项拍板,落成两份新单与两处既有单的修订: - 新增 TASK-freeze-metric-change-20260915(无 BUG 号,后面两单的前置): 两条冻结余量已用完(page.tsx 1951/1951 余 0;api server 11334/11363 余 29), 冻结从「逼新代码往外走」退化成拦路。实证:page.tsx 行数砍 59% 但 Home() 的 useState 从 56 涨到 66;api server 225 个类方法只有 12 处真碰 HTTP。 主门换成耦合指标,行数降为粗护栏;同时推翻 §6「参数式 hook 内部保持 0 个 React hook」——那正是状态搬不走的原因。 - 新增 TASK-home-state-lowering-20260915(无 BUG 号):先搬 rectification* 那 15 个 state 进已经是 dynamic 子树的校正面,Home() useState 66 → ≤53。 零行为变化;串行在 freeze-metric-change + C2 + R3 之后。 - 修订 TASK-consultation-external-evidence-cache-20260915:依赖反转,C1 排在 API server 拆解之前(它动模块级函数,拆解动类方法);补「不得新增类方法、 行数余量仅 29」的硬红线。 - 修订 TASK-api-server-decomposition-20260916:串行依赖加 C1 与 freeze-metric-change;__new__ 计数按 grep 的 4 计(原文 3 是文件数); 阶段 4 收尾口径改写;基线 11,314 → 11,334。 纯文档推送,不触发门禁、不发布镜像、不部署。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
216 lines
14 KiB
Markdown
216 lines
14 KiB
Markdown
# TASK-api-server-decomposition-20260916 · 把业务逻辑搬出 HTTP handler
|
||
|
||
## 基线
|
||
|
||
- 代码基线:`origin/staging` = **`2d7698ea`**(文档树 `6f74aa67`)。开工时以最新 `origin/staging` 为准。
|
||
- **串行依赖**:本单必须等 `TASK-qizheng-native-chart-20260915` **合入 staging 之后**才开工。那一单独占 `scripts/jyotish_api_server.py` 的写权,并行等于自找冲突。
|
||
- **2026-09-15 新增串行依赖**:还要等 `TASK-consultation-external-evidence-cache-20260915`(C1)合入。产品拍板 C1 先做——它动的是模块级函数(`execute_consultation_workflow`、`_join_foreground_vedastro`、三个前台 VedAstro 常量),本单动的是 `JyotishAPIHandler` **类**,重叠不大;C1 的用户价值(每轮省掉一次外网等待)高于一次纯搬运。**本单要吸收 C1 的 diff**:开工时 `execute_consultation_workflow` 里会多出走缓存模块的分支,照常搬运即可,不得把它改回去。
|
||
- **2026-09-15 新增前置**:`TASK-freeze-metric-change-20260915` 决定了阶段 4 的收尾口径(见下方阶段 4 的改写),本单收尾前它必须已合入。
|
||
- 本单不改任何行为,不新增功能,不动前端。
|
||
|
||
## 为什么要做:不是因为文件长,是因为它成了事实上的服务层
|
||
|
||
`scripts/jyotish_api_server.py` 现在 **11,334 行**(立单时 11,314,2026-09-15 实测已涨到 11,334),契约上限 11,363(`tests/test_api_server_growth_contract.py`:baseline 11,063 + 300 bugfix 余量)——**只剩 29 行**。
|
||
|
||
但真正的问题不是行数,是这个:
|
||
|
||
```python
|
||
# scripts/consultation_workflow_service.py,全文只有 32 行
|
||
def execute_consultation_workflow(body, *, surface="api"):
|
||
from jyotish_api_server import JyotishAPIHandler, execute_consultation_workflow as _execute
|
||
handler = JyotishAPIHandler.__new__(JyotishAPIHandler) # ← 伪造一个从未初始化的 handler
|
||
return _execute(handler, body=body, surface=surface)
|
||
|
||
def build_runtime_evidence_helpers(chart):
|
||
handler = JyotishAPIHandler.__new__(JyotishAPIHandler) # ← 又一个
|
||
return {
|
||
"vedastro_official": handler._high_rigor_vedastro_official_summary(chart),
|
||
"vedastro_archive_manifest": handler._compute_vedastro_gateway_archives(),
|
||
"interpretation_coverage": handler._interpretation_source_runtime_coverage(chart),
|
||
}
|
||
```
|
||
|
||
那个声称是「shared consultation workflow boundary for API and MCP callers」的模块,**反过来依赖单体文件**,并且靠 `__new__` 绕过 `BaseHTTPRequestHandler.__init__` 造一个空壳 handler,只为借用它身上的方法。
|
||
|
||
全仓 **3 个文件、4 处调用**这样做(下表第一行含两处;收尾断言按 `grep -c` 的 **4** 计,不要按文件数的 3 计):
|
||
|
||
| 位置 | 用途 |
|
||
| --- | --- |
|
||
| `scripts/consultation_workflow_service.py:20` / `:27` | 给 `mcp_server.py:741` 与 `:4749` 供咨询链与证据助手 |
|
||
| `scripts/capture_report_blocked_repairs_golden.py:59` | 生成 golden |
|
||
| `scripts/local_accuracy_report.py:141` | 离线准确率报告 |
|
||
|
||
**这就是这个文件长不下去的根因**:业务逻辑长在 HTTP handler 类上,非 HTTP 的调用方(MCP、离线脚本、golden 生成)没有别的入口,只能伪造 handler。于是新逻辑也只能继续往这个类里长。
|
||
|
||
这个后门还很脆:空壳 handler 没有 `self.headers` / `self.wfile` / `self.client_address`,今天能跑只是因为被借用的那几个方法碰巧没碰它们。任何人往里面加一行 `self.headers.get(...)` 就会在 MCP 侧炸。
|
||
|
||
**所以本单的目标是:让 `JyotishAPIHandler.__new__` 这个后门消失。行数下降是副产品。**
|
||
|
||
### 现状量化(AST 口径,立单时实测)
|
||
|
||
| 指标 | 值 |
|
||
| --- | ---: |
|
||
| 总行数 | 11,314 |
|
||
| `class JyotishAPIHandler` | 8,070 行 / 222 方法 |
|
||
| 路由分支 | 72 个,其中 **62 个已经 ≤4 行**(薄注册模式一直在用) |
|
||
| `_load_local_module(...)` 调用 | 83 次 |
|
||
| 方法长度 <20 行 | 110 个 |
|
||
| 方法长度 ≥150 行 | **7 个,合计 2,073 行** |
|
||
| 方法长度 100–150 行 | 11 个,合计 1,321 行 |
|
||
|
||
结论:**分派已经很薄,体积集中在少数几个长方法上。** 因此按"业务横切成 chart.py / dasha.py / consult.py"收益小、diff 大、冲突面广,**本单不采用**。
|
||
|
||
## 决策记录
|
||
|
||
产品负责人在 2026-09-15 的对话中授权:
|
||
|
||
1. 拆 `scripts/jyotish_api_server.py`,**按体积与依赖方向抽取,不按业务横切**。
|
||
2. 本单只做阶段 1–3;把 222 个方法拆成 mixin 类的「第三刀」**先不做**,等前三阶段落地后复盘再定。
|
||
3. 收尾时**重新冻结行数 baseline**,并把 bugfix 余量从 300 收到 **50**——否则文件会慢慢涨回去。
|
||
4. 全程**纯搬运**:不改行为、不改签名、不顺手优化。
|
||
|
||
## 硬红线
|
||
|
||
1. **纯搬运。** 不得修改任何被搬运代码的逻辑、参数、返回结构、异常类型。允许的改动只有:函数从方法变成模块级函数(`self` 参数消失或显式传入)、import 调整、调用点改成薄转发。
|
||
2. **`tests/test_api_server_security.py`(3,841 行)是本单唯一的安全网,必须逐条通过,一个断言都不许改。** 若某条断言因搬运而失败,说明搬错了,改代码不改断言。
|
||
3. **一轮搬一个模块。** 每搬完一个模块提交一次,并跑 `.venv/bin/python scripts/run_quality_gate.py --profile quick`。不得把多个模块攒成一个大提交。
|
||
4. **不得改 `skills/jyotish-vedic-astrology/versions/**` 下的任何文件**——那是冻结的历史 skill 版本副本,里面有同名脚本,改了等于篡改历史版本。
|
||
5. 不得改 `.gitea/workflows/**`、不得改 `deploy/gated-paths.txt` 的既有条目、不得提升 `main`。
|
||
6. 不得新增任何端点、不得改任何端点的请求/响应合同。
|
||
7. 不改前端、不改数据库结构。
|
||
8. 每个新模块必须**不 import `jyotish_api_server`**。依赖方向只能是 `jyotish_api_server → 新模块`,反向即为不通过。
|
||
|
||
## 任务分解
|
||
|
||
### 阶段 1 · 拆掉 `__new__` 后门(本单的核心,必做)
|
||
|
||
把三处伪造 handler 所借用的逻辑变成真正的模块级函数。
|
||
|
||
涉及的四个东西:
|
||
|
||
| 现状 | 行数 | 去向 |
|
||
| --- | ---: | --- |
|
||
| 模块级 `execute_consultation_workflow(handler, ...)`(`:2160-2560`) | 401 | 改成不需要 handler 的纯函数 |
|
||
| `JyotishAPIHandler._high_rigor_vedastro_official_summary`(`:4884-5103`) | 220 | 新模块 `scripts/vedastro_runtime_evidence.py` |
|
||
| `JyotishAPIHandler._compute_vedastro_gateway_archives` | — | 同上 |
|
||
| `JyotishAPIHandler._interpretation_source_runtime_coverage` | — | 同上 |
|
||
|
||
做法:
|
||
|
||
1. 先读清楚这四个函数**实际用到了 handler 的哪些成员**。如果只用到别的纯函数/模块,那 `self` 就是纯装饰,直接去掉。
|
||
2. 搬到新模块后,`JyotishAPIHandler` 上留**薄转发方法**(每个 ≤3 行),保证 HTTP 侧行为零变化。
|
||
3. 改写 `scripts/consultation_workflow_service.py`:直接 import 新模块,**删掉两处 `JyotishAPIHandler.__new__`**,并去掉对 `jyotish_api_server` 的 import。
|
||
4. 同样修 `scripts/capture_report_blocked_repairs_golden.py:59` 与 `scripts/local_accuracy_report.py:141`。
|
||
|
||
**验收标准**
|
||
|
||
- 全仓 `grep -rn "JyotishAPIHandler.__new__" --include=*.py .`(排除 `skills/*/versions/**`)**零命中**。
|
||
- `scripts/consultation_workflow_service.py` 不再 import `jyotish_api_server`。
|
||
- `.venv/bin/python -m pytest tests/test_api_server_security.py` 全绿,断言未改。
|
||
- MCP 侧:`mcp_server.py:741` 与 `:4749` 两条路径各跑一次,输出与搬运前逐字节一致(把对照写进进度记录)。
|
||
- `scripts/local_accuracy_report.py` 与 `scripts/capture_report_blocked_repairs_golden.py` 各跑一次,产物与搬运前一致。
|
||
- 新增 `tests/test_api_server_service_boundary.py`:断言新模块不 import `jyotish_api_server`(用 `ast` 静态检查,不靠约定)。
|
||
|
||
### 阶段 2 · 抽走其余 ≥150 行的业务方法(必做)
|
||
|
||
| 方法 | 行数 | 去向 |
|
||
| --- | ---: | --- |
|
||
| `_attach_local_consultation_layers`(`:1496-1833`,模块级) | 338 | `scripts/consultation_local_layers.py` |
|
||
| `_compute_active_rectification_events`(`:8951-9254`) | 304 | 推回**已存在**的 `scripts/active_rectification_event_engine.py` |
|
||
| `_build_chart_prompt_pack`(`:7181-7469`) | 289 | `scripts/chart_prompt_pack.py` |
|
||
| `_compute_chart_sync`(`:6814-7093`) | 280 | `scripts/chart_compute_service.py` |
|
||
|
||
每搬一个,handler 上留 ≤3 行薄转发。
|
||
|
||
**验收标准**
|
||
|
||
- 每个模块一次提交,每次提交后 `run_quality_gate.py --profile quick` 通过。
|
||
- `tests/test_api_server_security.py` 每次都全绿。
|
||
- `tests/test_calculation_p0_regressions.py` 全绿(`docs/research/pre_work_error_ledger.md` ERR-042 的防复发项:domain / CLI / REST 必须共享 `domain_calculation_service.py`、effective parameters 与 `result_hash`)。
|
||
- 每个新模块配一个 golden 单测,fixture 来自**真实引擎响应**,不得手造形状。
|
||
|
||
### 阶段 3 · `do_POST` / `do_GET` 改路由表(必做)
|
||
|
||
现状:`do_POST` 241 行(`:3475-3715`)+ `do_GET` 104 行(`:3370-3473`),共 72 个 `elif path == ...` 分支。**每加一个端点要吃 3–4 行预算。**
|
||
|
||
- 改成 `{path: bound_handler}` 字典分派,保留现有的 `_enforce_request_security` / `_read_json_body` / `acquire_heavy_compute_slot` / 异常映射的统一包装。
|
||
- 表化后每个端点 **1 行**。
|
||
- 顺带必须搬走的:`/api/location/resolve` 分支把城市别名字典 `{'beijing': '北京', ...}` **直接写在 `do_POST` 里**(`:3485` 一带),搬进对应模块。
|
||
|
||
**验收标准**
|
||
|
||
- 72 条路径的请求/响应与改动前逐条一致(用 `test_api_server_security.py` 覆盖 + 一份路径清单对照写进进度记录)。
|
||
- 404 行为、方法不允许、限流 429、异常映射全部不变。
|
||
- `do_POST` + `do_GET` 合计 ≤ 90 行。
|
||
|
||
### 阶段 4 · 收尾重新冻结(必做,收尾)—— 2026-09-15 改写
|
||
|
||
**原方案(行数 baseline 重设 + 余量 300→50)已作废。** 产品 2026-09-15 拍板换口径:余量收到 50 行只是把今天的问题推到三个月后,下一次 bugfix 又会立刻撞墙。新口径由 `TASK-freeze-metric-change-20260915` 落地,本单收尾时按它已经立好的尺子填新基线:
|
||
|
||
| 门 | 收尾时要达到 |
|
||
| --- | --- |
|
||
| 主门 · `JyotishAPIHandler.__new__` 全仓计数 | **必须为 0**(今天 4)——这是「拆干净了」唯一不可伪造的证据 |
|
||
| 主门 · `JyotishAPIHandler` 类方法数 | 显著低于 225,并把新值写成不得增长的新基线 |
|
||
| 粗护栏 · 文件行数 | baseline 重设为收尾实际行数,余量保持 **300**(不再收到 50) |
|
||
|
||
- 同步更新 `tests/test_api_server_growth_contract.py` 顶部 docstring 的日期与说明。
|
||
- **不得**在本单之外的任何轮次里调高任何一条基线。
|
||
|
||
**验收标准**
|
||
|
||
- `grep -rn "JyotishAPIHandler.__new__" --include=*.py .`(排除 `skills/*/versions/**`)**零命中**,且该断言已经写进合同测试。
|
||
- 类方法数新基线已写入合同测试,且人为加一个类方法能让它变红(贴反向验证)。
|
||
- `AGENTS.md` §6 的措辞与新口径一致(措辞由 freeze-metric-change 单先行落地,本单只填数)。
|
||
|
||
## 预期收益(按 AST 实测推算,允许 ±10%)
|
||
|
||
| 阶段 | 预计减少 | 累计行数 |
|
||
| --- | ---: | ---: |
|
||
| 起点 | — | 11,314 |
|
||
| 阶段 1 + 2 | 约 −1,830 | 约 9,500 |
|
||
| 阶段 3 | 约 −265 | 约 9,230 |
|
||
| (后续可选:100–150 行档 11 个方法) | 约 −1,320 | 约 7,900 |
|
||
|
||
收尾后余额从 **49 行**变成约 **50 行**(因为 baseline 重新冻结),但**每个新端点只吃 1 行**而不是 3–4 行,且长逻辑再也进不来。
|
||
|
||
## 让步顺序
|
||
|
||
1. **阶段 1 不可让步**。`__new__` 后门是本单存在的理由;只做行数不做这个等于没做。
|
||
2. 阶段 2 可以只搬其中两个模块,剩下的写进进度记录留到下一轮。
|
||
3. 阶段 3 可以推迟,但推迟时**阶段 4 也要跟着推迟**——不能在分派还是 `elif` 链的时候就把余量收到 50,那会立刻卡死下一个端点。
|
||
4. **不可让步**:纯搬运不改行为、安全套件断言不许改、新模块不得反向 import、不碰冻结的 skill 版本目录。
|
||
|
||
## 开工前置命令
|
||
|
||
```bash
|
||
cd /workspace/Jyotisha
|
||
git status -sb
|
||
git fetch origin --prune
|
||
git log --oneline -1 origin/staging # 必须已包含 TASK-qizheng-native-chart-20260915 的合入
|
||
git worktree add -b codex/api-server-decomposition-20260916 \
|
||
.worktrees/api-server-decomposition-20260916 origin/staging
|
||
cd .worktrees/api-server-decomposition-20260916
|
||
python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45
|
||
|
||
# 记下搬运前的基线,验收时逐项对照
|
||
wc -l scripts/jyotish_api_server.py
|
||
.venv/bin/python -m pytest tests/test_api_server_security.py -q | tail -3
|
||
.venv/bin/python -m pytest tests/test_calculation_p0_regressions.py -q | tail -3
|
||
grep -rn "JyotishAPIHandler.__new__" --include=*.py . | grep -v "skills/.*/versions/"
|
||
```
|
||
|
||
按模块逐次提交,全部完成后 `git push origin HEAD:staging`(会触发 `backend-quality-gate`,`scripts/**`、`tests/**` 都在门禁路径内),推送后核对远端 SHA 与 `/api/health` 的 `deployment.gitCommit`。
|
||
|
||
## BUG 编号起点
|
||
|
||
`origin/staging` 上当前最大号立单时是 **BUG-699**,已被三份并行单预占到 709。本单从 **BUG-710** 起,开工时重新核对。
|
||
|
||
本单是重构,正常情况下**不需要开 BUG 号**。唯一预期会用到的场景:搬运过程中发现 `__new__` 空壳 handler 已经在某条路径上造成了真实故障——那才开一条,并关联本单。
|
||
|
||
## 进度与记录
|
||
|
||
- 进度记录:`docs/tasks/PROGRESS-api-server-decomposition-20260916.md`。**必须逐模块记录搬运前后的行数、测试结果、以及 MCP / 离线脚本的输出对照。**
|
||
- 本单不产生用户可感知的行为变化,`CHANGELOG.md` 只写一句结构说明,Skill 版本**不 bump**。
|
||
- 环境缺口写 `BLOCKED.md`。
|
||
- 索引:`docs/tasks/README.md` 追加本单。
|