Files
Jyotisha/docs/tasks/TASK-api-server-decomposition-20260916.md
T
Jesse_ChenandClaude Opus 5 e4788dfc00 docs(tasks): 换掉两条增长冻结口径 + page.tsx 状态下沉第一簇 + C1 提前
产品 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
2026-09-15 23:15:37 +00:00

216 lines
14 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.
# 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,3142026-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 行** |
| 方法长度 100150 行 | 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` 追加本单。