Files
Jyotisha/docs/tasks/TASK-api-server-service-extract-20261013.md
T
Jesse_ChenandClaude Opus 5.5 2994588ad4 docs(tasks): fold product decisions into audit briefs (BUG-1344..1346)
- dasha-unify: fix Ashtottari birth balance (BUG-1344)
- rect-scoring-perf: server-side candidate cap 241 (BUG-1345)
- node-runtime-resources: enable user-side session cookie cache, 300 s (BUG-1346)
- consult-prep-parallel: classifier/reserve concurrency declined
- answer-effort-ab: model key supplied by product at acceptance
- drop stricter-than-test line-count red lines (AGENTS.md section 6)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Ad9rqAB8pXXvyzh7f4X8v
2026-10-14 00:41:02 +08:00

198 lines
18 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 单体第一阶段:拆出不带 HTTP 的计算类,生产代码不再伪造 handler(issue #待定)
- 日期:2026-10-13
- 来源:2026-10-13 全仓屎山与性能排查 ·「`jyotish_api_server.py` 单体 / `JyotishAPIHandler.__new__` 伪造」条目
- 执行方:**Grok(Windows + Docker)**,分支 `codex/api-server-service-extract-20261013`,只推这个分支
- 验收:Claude(Linux)
- BUG 编号:**无 BUG 编号**(结构改造,不是缺陷),记录写 PROGRESS(另:`TASK-upstream-sync7-20261014` 占 BUG-1318~1322,本批号段整体从 BUG-1323 起)
- **开工前置**:`dasha-unify`(BUG-1341)、`python-hygiene`(BUG-1340)两单已合入 `origin/staging`。两单都改 `scripts/jyotish_api_server.py`,本单整类重排,必须最后做。
- **主会话补充的串行链(2026-10-14)**:引擎/API 文件链按此顺序逐单合入后开工(`upstream-sync7` 也改 `jyotish_engine.py` / `jyotish_api_server.py`,若在途,后合一方 rebase):`api-disk-cache-retention` + `report-reference-perf` → `api-chart-subprocess-gate` → `pl9-render-split` → `python-hygiene` → `dasha-unify` → `api-server-service-extract`。本单的前置以这条链为准(与上一行不一致时以本行为准)。
## 1. 基线
- 基线 commit:`origin/staging` `77057a819`(写作时);开工以前置两单合入后的 `origin/staging` 为准,第 9 节命令重测全部数字。
- 前序任务书(必读):`docs/tasks/TASK-api-server-decomposition-20260916.md`(阶段 1 目标「`__new__` 归零」)、`docs/tasks/TASK-api-server-backdoor-close-20260916.md` 与 `docs/tasks/PROGRESS-api-server-backdoor-close-20260916.md`(mixin 方案 spike **红**、退回 A,scripts 侧伪造 4 → 2)。
## 2. 事故实证(`77057a819`)
### 2.1 现状数字(排查报告有两处要修正)
| 指标 | 排查报告 | 实测 | 口径 |
| --- | --- | --- | --- |
| 文件行数 | 10,970 | **10,970** | `wc -l` |
| `JyotishAPIHandler` 类体 | 7,751 行 / 215 方法 | **7,752 行 / 215 个类级方法**;合同口径(全文件 `^ def`)**218** | AST / `tests/test_api_server_growth_contract.py` |
| `JyotishAPIHandler.__new__` | 34 处、23 个测试文件 | **31 处**:`scripts/` 2(`consultation_workflow_service.py::execute_consultation_workflow`、`capture_report_blocked_repairs_golden.py::_handler`),`tests/` 29(**22** 个文件) | 合同口径(文本计数,不含合同文件本身) |
| 合同基线 | 31 | 行数 10,924 + 300;方法 218;伪造 31;scripts 伪造 2 | 合同文件常量 |
### 2.2 `execute_consultation_workflow` 不是 400 行的问题
排查报告建议「把 `execute_consultation_workflow`(400 行)及其依赖方法改成不依赖 self 的服务模块」。AST 求它的**传递闭包**(从模块级 `execute_consultation_workflow` 出发,沿 `handler.x` / `self.x` 与同文件模块级函数引用展开):
| 闭包成分 | 个数 | 行数 |
| --- | ---: | ---: |
| `JyotishAPIHandler` 方法 | **107** | **3,805** |
| 同文件模块级函数 | **62** | **1,958** |
| 用到的 mixin 方法 | 6 | (已在 `offline_compute_mixins.py`) |
| 引用 HTTP 成员(`headers` / `wfile` / `send_*` / `client_address` …) | **0** | — |
也就是说,「不依赖 self 的服务模块」= 搬 ~5,800 行,远超一轮可控的范围;而且 9 月 16 日的 spike 已证明这条路会撞墙:闭包里有 5 个模块级函数被测试**按 `jyotish_api_server` 模块属性打补丁**(`_load_local_module`、`_write_async_job_record`、`_async_job_dir`、`_sqlite_job_db_path`、`_attach_vedastro_main_entry_overview`),其中 `tests/test_api_server_security.py`(约第 2399 行 `_compute_consultation_workflow` 用例)与 `tests/test_consultation_consumer_context.py`(约第 211 行)直接走咨询链。函数搬到新模块后,查找走新模块 globals,补丁静默失效。
### 2.3 真正可以一刀切开的地方:HTTP 只占 15 个方法
对类内 215 个方法求「直接碰 HTTP 成员或覆写 `BaseHTTPRequestHandler`」及其**向上**调用者:
| 集合 | 方法 | 行数 |
| --- | ---: | ---: |
| 直接碰 HTTP | 9:`_json`、`_html`、`_send_cors_headers`、`_read_json_body`、`_enforce_request_security`、`_job_access_token`、`do_GET`、`do_POST`、`do_OPTIONS` | 456 |
| 调用上面任一的方法 | 6:`_error_json`、`_get_chart_job`、`_get_high_rigor_job`、`_require_dynamic_rectification_token`、`_compute_dynamic_rectification_score`、`_compute_dynamic_rectification_opportunities` | 119 |
| **合计 HTTP 侧** | **15** | **575** |
| 其余(纯计算) | 200 | ~7,170 |
另外核实:
- 类里**没有任何方法给 `self.xxx` 赋值**(无实例状态);唯一的动态读取是 `_enforce_request_security` 的 `getattr(self, 'client_address', …)`(属于 HTTP 侧)。
- 与 `BaseHTTPRequestHandler` 同名的类属性只有 `server_version`(必须留在 HTTP 类,否则 MRO 会让基类的值生效)。
- 咨询闭包 107 个方法与 HTTP 侧 15 个方法**交集为空**。
所以:把类拆成「纯计算基类 + HTTP 子类」,**两个类都留在同一个文件里**——模块 globals 不变,测试补丁落点不变,`self` 语义不变;纯计算类可以正常 `JyotishComputeService()` 实例化,不再需要绕过 `BaseHTTPRequestHandler.__init__`。
### 2.4 测试里的 29 处伪造
按「文件内是否调用 HTTP 侧方法或读写 `headers` / `wfile` / `client_address`」粗分(grep 启发式,执行方以实际运行为准):
| 类别 | 文件数 | 伪造处数 |
| --- | ---: | ---: |
| 需要 HTTP 侧(`test_api_server_security.py`、`test_api_async_job_contract.py`、`test_runtime_security_p0.py`、`test_dynamic_rectification_scoring.py`、`test_professional_report_reference_api.py`) | 5 | 5 |
| 只用计算方法 | 17 | 24 |
### 2.5 咨询工作流输出的确定性(golden 方法依据)
用 `scripts/consultation_workflow_service.execute_consultation_workflow`(MCP 入口)跑虚构盘(1990-01-01 12:00 新德里 28.6139/77.2090 +5.5,`theme=["career"]`,`defer_optional_external_evidence=True`,`today=2026-10-13`,`VEDASTRO_ENABLE_NETWORK=0`),约 1 秒、响应约 420 KB:
| 对比 | 结果 |
| --- | --- |
| 两次都清空排盘缓存(`scratch/local/api_chart_cache`) | 只差 2 个字段:`chart.runtime_cache.cache_created_at` / `cache_expires_at`(墙钟) |
| 换 `PYTHONHASHSEED` | 同上,与种子无关 |
| 第二次命中排盘缓存 | 另有 36 处差异:`chart.consultation_native_layers.argala.*` 里 `Sun` / `Saturn` 先后顺序互换(**附带发现**:缓存往返改变了 Argala 列表顺序,语义相同;本单不修,列入回报) |
结论:golden 比对必须「清空排盘缓存 + 去掉这两个墙钟字段」,其余逐字节相等。
## 3. 根因
业务逻辑长在 HTTP handler 类上,非 HTTP 调用方(MCP、golden 生成)没有合法实例可用,只能 `__new__` 伪造。9 月两单想把逻辑**搬出文件**,撞上「测试按模块属性打补丁」;本单换方向:**不搬出文件,先在文件内把 HTTP 与计算分成两个类**,伪造就失去存在理由。
## 4. 决策记录
- 2026-10-13 产品负责人同意按排查结论全部立单交 Grok 执行。
- **范围修正(主会话转交产品确认)**:排查报告的「第一阶段把 `execute_consultation_workflow` 及依赖改成独立服务模块」实测闭包约 5,800 行且会触发 9 月 spike 的补丁失效,**本单不做**;改为同文件内拆类(约 600 行纯移动),达成同一个可验证目标:**生产伪造 2 → 0**。
- 9 月 `TASK-api-server-backdoor-close` 红线「`tests/test_api_server_security.py` 一条断言不许改」**继续有效**:本单不改它的任何断言,也不改它的补丁落点。
- 本单允许改测试里的**构造语句**(`JyotishAPIHandler.__new__(JyotishAPIHandler)` → `JyotishComputeService()`),这不是断言;仍按三栏写进进度记录。`test_api_server_security.py` 里那 1 处伪造属于 HTTP 侧,不改。
- 增长合同基线**只许下调**:scripts 伪造 2 → 0;总伪造 31 → 实测;方法数与行数不变或下降。
- `consultation_workflow_service.py` 仍 import `jyotish_api_server`(依赖方向的根治属于后续阶段,需要拍板改补丁落点,见末尾)。
## 5. 硬红线
1. **纯移动**:方法体逐字不改;只允许把 15 个 HTTP 侧方法与 `server_version` 从原类剪到新 HTTP 子类、改类声明、调整两个调用方的构造语句。用 `git diff --color-moved=dimmed-zebra` 与 AST 方法体对比(第 6 节 T2)证明。
2. `tests/test_api_server_security.py` 一个字节不改且全绿。
3. `scripts/jyotish_api_server.py`:行数只按 `tests/test_api_server_growth_contract.py` 的粗护栏(AGENTS.md §6,开工实测基线 + 300);拆类新增的类声明与空行不需要抵消,**不得**为凑行数删代码、合并语句或删注释。顺带发现的零引用函数(写作时 `_strict_adjudication_bundle_from_contract`、`_high_rigor_job_path`)只记进 PROGRESS,不在本单删。
4. 类方法数(合同口径)不得增长;`JyotishAPIHandler.__new__` 计数只许降。
5. 不新增端点、不改任何端点请求 / 响应合同;HTTP 路由、安全检查、限流 429、异常映射行为不变。
6. 新计算类不得继承 `BaseHTTPRequestHandler`,其方法不得引用 HTTP 成员(加 AST 测试钉住)。
7. 不改 `.gitea/workflows/**`、不动表结构、不升级依赖、不碰 `skills/*/versions/**`。
8. golden 只能来自真实引擎输出;改既有断言写三栏(本单预期只有增长合同常量)。
## 6. 任务分解
### T1 · 搬前 golden(必做,先做)
- 写一个采集脚本(放 `scripts/research/` 或测试 helper,**不放进 `scripts/` 根目录**),在**基线工作树**上用第 2.5 节方法采集:
1. MCP 入口 `consultation_workflow_service.execute_consultation_workflow`:career、wealth、relationship 三题各一次;
2. HTTP 侧 handler 的 `_compute_consultation_workflow`(构造方式与现有测试相同)同三题;
3. `scripts/capture_report_blocked_repairs_golden.py` 现有产物;
4. `mcp_server.py` 里经由 `build_runtime_evidence_helpers` 的路径一次(9 月已有端到端测试,复用)。
- 去掉 `cache_created_at` / `cache_expires_at`,`json.dumps(sort_keys=True, ensure_ascii=False)` 落盘到 `tests/golden/`(体积过大则只存 sha256 + 关键键路径,fixture 写 `fixtureProvenance`:命令、基线 SHA、环境变量)。
- 验收:基线上连跑两次哈希一致(证明可复现)。
### T2 · 同文件拆类(必做)
- `class JyotishAPIHandler(BaseHTTPRequestHandler, VedastroEvidenceMixin, SynastryMixin)` 改为 `class JyotishComputeService(VedastroEvidenceMixin, SynastryMixin)`,保留 200 个计算方法原位不动。
- 在其后新增 `class JyotishAPIHandler(BaseHTTPRequestHandler, JyotishComputeService)`,放入 `server_version` 与第 2.3 节 15 个 HTTP 侧方法(开工按同口径重算名单,进度记录贴脚本输出)。`ThreadingHTTPServer((host, port), JyotishAPIHandler)` 不变。
- 类 docstring 一行说明两类分工即可(计入行数,用第 5 节第 3 条的死代码抵消)。
- 验收:
- AST 对比脚本:基线与本分支两个文件里,所有方法按名取 `ast.dump(body)`,逐个相等;方法总集合相等;只有 16 个成员换了所属类。
- `JyotishAPIHandler.__mro__` 中 `BaseHTTPRequestHandler` 先于 `JyotishComputeService`;`JyotishAPIHandler.server_version` 值不变。
- `tests/test_api_server_security.py` 全绿、零改动(`git diff --stat` 为空)。
- `tests/test_offline_compute_mixins.py` 第 85–86 行 `issubclass(JyotishAPIHandler, VedastroEvidenceMixin / SynastryMixin)` 仍成立(经由新基类)。
### T3 · 生产伪造归零(必做)
- `scripts/consultation_workflow_service.py::execute_consultation_workflow`:`JyotishAPIHandler.__new__(JyotishAPIHandler)` → `JyotishComputeService()`。
- `scripts/capture_report_blocked_repairs_golden.py::_handler`:同上(返回类型注解随之改)。
- 验收:
- T1 的 MCP 与 HTTP 两路 golden 逐字节一致(第 2.5 节方法);`capture_report_blocked_repairs_golden.py` 产物与 T1 一致。
- `grep -rn "JyotishAPIHandler.__new__" scripts` 零命中。
### T4 · 测试伪造下调(可砍)
- 对第 2.4 节「只用计算方法」的 17 个文件,把构造语句换成 `JyotishComputeService()`;逐文件跑,**红了就回退该文件**(说明它其实用到 HTTP 侧),回退名单写进进度记录。
- 每处改动按三栏记录(原值 `JyotishAPIHandler.__new__(JyotishAPIHandler)` / 新值 `JyotishComputeService()` / 原因:计算类可正常实例化,不再需要绕过 HTTP 构造)。
### T5 · 合同与守卫(必做)
- `tests/test_api_server_growth_contract.py`:
- `JYOTISH_API_HANDLER_NEW_SCRIPTS_BASELINE` 2 → **0**;`JYOTISH_API_HANDLER_NEW_COUNT_BASELINE` 31 → 收尾实测;方法数基线若因删死代码下降同步下调;行数基线不动(只要不超)。三栏写在常量旁注释与进度记录。docstring 更新日期与说明。
- 新增两条:① `JyotishComputeService` 不是 `BaseHTTPRequestHandler` 子类;② AST 检查 `JyotishComputeService` 的方法体不引用 `self.headers` / `wfile` / `rfile` / `client_address` / `send_response` / `send_header` / `end_headers` / `path` / `command`,也不调用第 2.3 节 HTTP 侧方法名。
- 反向验证:在计算类里临时加一行 `self.headers`,第 ② 条变红;在 scripts 里临时加一处 `JyotishAPIHandler.__new__`,scripts 伪造条变红。输出贴进度记录后撤回。
- `AGENTS.md` §6 措辞若提到伪造基线数字,同步数字(纯文档)。
### T6 · 记录(必做)
- `docs/tasks/PROGRESS-api-server-service-extract-20261013.md`:第 2 节全部数字的开工重测值、HTTP 侧名单脚本输出、AST 方法体对比结果、golden 对照、T4 回退名单、反向验证输出、搬前 / 搬后行数与方法数。
- 不写 BUG_HISTORY;不写 CHANGELOG(无用户可感知变化);不改 `frontend/DESIGN.md`。
## 7. 让步顺序
时间不够按此顺序砍:**T4 → T5 第 ② 条 AST 守卫(保留 ① 与基线下调)**。T1、T2、T3、T5 基线下调、T6 不可砍。若 T2 让 `test_api_server_security.py` 任何一条变红且不是搬错所致,**停下回报,不得改测试**。
## 8. 验收命令分工
| 谁 | 命令 |
| --- | --- |
| Grok 自跑 | `python -m pytest tests/test_api_server_security.py tests/test_api_server_growth_contract.py tests/test_offline_compute_mixins.py tests/test_consultation_workflow_domains.py tests/test_consultation_consumer_context.py tests/test_consultation_plan_contract.py tests/test_commercial_domain_calculation_contract.py tests/test_calculation_p0_regressions.py -q` + T4 改到的每个测试文件 + T1/T3 golden 对照 + AST 方法体对比。快速门、全量 pytest **不跑**,以 Claude 为准 |
| Claude 验收 | 上述 + 快速门 + 全量 `pytest` 与基线逐名比对 + 本机起 API 服务做冒烟:未登录 `/api/health` 200、`/api/chart` 虚构盘 200、超大请求体与非 JSON 的错误码与基线相同 |
| 不需要 | `test:db`、前端 |
## 9. 开工前置命令
```bash
git fetch origin --prune
git log --oneline origin/staging | grep -i "dasha-unify\|python-hygiene" # 两单都已合入
git worktree add -b codex/api-server-service-extract-20261013 .worktrees/api-server-service-extract-20261013 origin/staging
cd .worktrees/api-server-service-extract-20261013
wc -l scripts/jyotish_api_server.py
grep -c "^ def \|^ async def " scripts/jyotish_api_server.py
grep -rn "JyotishAPIHandler.__new__" --include=*.py scripts tests | grep -v test_api_server_growth_contract | wc -l
grep -rn "JyotishAPIHandler" --include=*.py scripts tests mcp_server.py | grep -v "__new__" | head -40 # 其他按类名的用法(issubclass、注解、import)
python -m pytest tests/test_api_server_security.py -q | tail -3
python -m pytest tests -q -p no:cacheprovider > /tmp/baseline-full.txt 2>&1; tail -3 /tmp/baseline-full.txt
```
## 10. BUG 编号起点
无(结构改造)。若途中发现缺陷,按开工时 `docs/BUG_HISTORY.md` 最大号 +1 另记,并在回报里说明。
## 11. 交付物清单
- 代码:`scripts/jyotish_api_server.py`(拆类、删两处死代码)、`scripts/consultation_workflow_service.py`、`scripts/capture_report_blocked_repairs_golden.py`
- 测试:`tests/test_api_server_growth_contract.py`(基线下调 + 两条新守卫)、T1 golden 与对照测试、T4 改到的测试文件
- 文档:`docs/tasks/PROGRESS-api-server-service-extract-20261013.md`;(可能)`AGENTS.md` §6 数字
- 不改:`frontend/**`、`frontend/DESIGN.md`、`CHANGELOG.md`、`docs/BUG_HISTORY.md`
## 后续阶段(本单不做)
1. **把咨询闭包搬出文件**(107 方法 / 3,805 行 + 62 函数 / 1,958 行):前提是先解决 5 个被按模块属性打补丁的函数。可行做法是把 `_load_local_module` 与异步任务记录层抽成独立模块、两边都按「模块属性」调用,并把测试补丁落点改到新模块——**会改 `test_api_server_security.py` 的补丁行,需产品拍板**(9 月红线)。之后 `consultation_workflow_service.py` 才能不再 import 单体。
2. 按入口继续切计算类:图表计算、报告、校正、外部证据各成模块(每次一个,增长合同只许降)。
3. `do_POST` / `do_GET` 改路由表(9 月 decomposition 阶段 3,仍未做)。
4. 测试里剩下的 5 处 HTTP 侧伪造:给 HTTP 子类一个测试用构造工厂(放 `tests/` 辅助模块),伪造计数归零。
5. 附带发现:排盘缓存命中时 Argala 列表顺序与未命中不同(第 2.5 节),另开单。