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

18 KiB
Raw Blame History

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. 开工前置命令

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 节),另开单。