diff --git a/docs/tasks/README.md b/docs/tasks/README.md index 728f5e60..c18b7e7f 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -224,6 +224,7 @@ | `TASK-qizheng-native-chart-20260915.md` | `PROGRESS-qizheng-native-chart-20260915.md` | **后端单(独占 `scripts/jyotish_api_server.py`)**:vendored `stem-branch` 0.8.0(Apache-2.0)归档 + API 镜像 Node runtime + 七政适配器(计都派别与宿度坐标系参数化、boundary 按实测重写)+ 三个只读端点 `/api/qizheng`、`/api/western`、`/api/ephemeris_events`。实证三条:四柱时柱按 UTC 算(BUG-700,本轮不修不调用)、`ketuMode` 写死未暴露(BUG-701)、boundary 把空神煞与未闭合庙旺说成已生成(BUG-702)。BUG 段 700–703 | 待领取 | — | | `TASK-chart-page-20260915.md` | `PROGRESS-chart-page-20260915.md` | **前端单(独占 `app-sidebar.tsx`,同时加星盘与星历两个入口)**:P0 只读星盘页,五个 Tab(星盘 / 基础信息 / 大运 / 西洋盘 / 七政四余),中宫排盘参数卡,三套坐标系各自标注且禁止互相换算。不扣点不调模型不出 spinner;不碰 `page.tsx`(1951/2000);**不搬 `vedic-chart-svg.tsx`**(rectification-board 也在用)。BUG 段 704–706 | 待领取 | — | | `TASK-ephemeris-page-20260915.md` | `PROGRESS-ephemeris-page-20260915.md` | **前端单**:P1 星历页,今日五要素 + 当日行运(相对本命宫位)+ 未来九十天换座与停滞,底部「带这天去提问」出口。页面不得出现任何运势判断。含实证缺陷:panchanga 写死 Lahiri 与账户 Raman 分裂(BUG-707,只标注不修)。侧边栏入口由 chart-page 单交付。BUG 段 707–709 | 待领取 | — | +| `TASK-api-server-decomposition-20260916.md` | `PROGRESS-api-server-decomposition-20260916.md` | **重构单(串行在 qizheng 单之后)**:把业务逻辑搬出 `JyotishAPIHandler`。核心不是行数,是全仓 3 处靠 `JyotishAPIHandler.__new__` 伪造空壳 handler 借方法(`consultation_workflow_service` ×2、`capture_report_blocked_repairs_golden`、`local_accuracy_report`,MCP 也走这条),依赖方向反了、handler 没有 `headers`/`wfile` 随时可炸。四阶段:拆 `__new__` 后门 → 抽 ≥150 行业务方法 → `do_POST`/`do_GET` 改路由表 → 重新冻结行数 baseline(余量 300→50)。纯搬运不改行为,`test_api_server_security.py` 3841 行断言一条不许改。预计 11,314 → 约 9,230 行。BUG 段 710+ | 待领取 | — | ## 命名与归档 diff --git a/docs/tasks/TASK-api-server-decomposition-20260916.md b/docs/tasks/TASK-api-server-decomposition-20260916.md new file mode 100644 index 00000000..e53e75a9 --- /dev/null +++ b/docs/tasks/TASK-api-server-decomposition-20260916.md @@ -0,0 +1,206 @@ +# TASK-api-server-decomposition-20260916 · 把业务逻辑搬出 HTTP handler + +## 基线 + +- 代码基线:`origin/staging` = **`2d7698ea`**(文档树 `6f74aa67`)。开工时以最新 `origin/staging` 为准。 +- **串行依赖**:本单必须等 `TASK-qizheng-native-chart-20260915` **合入 staging 之后**才开工。那一单独占 `scripts/jyotish_api_server.py` 的写权,并行等于自找冲突。 +- 本单不改任何行为,不新增功能,不动前端。 + +## 为什么要做:不是因为文件长,是因为它成了事实上的服务层 + +`scripts/jyotish_api_server.py` 现在 **11,314 行**,契约上限 11,363(`tests/test_api_server_growth_contract.py`:baseline 11,063 + 300 bugfix 余量)——**只剩 49 行**。 + +但真正的问题不是行数,是这个: + +```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 处**这样做: + +| 位置 | 用途 | +| --- | --- | +| `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 · 重新冻结行数契约(必做,收尾) + +- 把 `tests/test_api_server_growth_contract.py` 的 `JYOTISH_API_SERVER_LINE_COUNT_BASELINE` 更新为**本单收尾时的实际行数**。 +- 余量从 **300 改为 50**。 +- 同步更新该文件顶部 docstring 里的日期与说明,以及 `AGENTS.md` §6 里引用的口径。 +- **不得**在本单之外的任何轮次里调高 baseline。 + +**验收标准** + +- `tests/test_api_server_growth_contract.py` 通过,且新 cap = 新实际行数 + 50。 +- `AGENTS.md` §6 的措辞与新口径一致。 + +## 预期收益(按 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` 追加本单。