Files
Jyotisha/docs/tasks/TASK-api-server-backdoor-close-20260916.md
T
Jesse_ChenandClaude Opus 5 5094fd2620 docs(tasks): 后门单改写为 mixin 方案(产品选 B),修正闭包规模
第一版写「8 方法 / 314 行」错了一个数量级。错因:闭包只跟 self._x(),而
_compute_consultation_workflow 只有 6 行、转给模块级
execute_consultation_workflow(self, ...),那个函数体 394 行、对传进来的
handler 调 16 个方法再往下扇出。跟丢那一跳,300 行被当成了全部。
执行方重算:约 115 方法 / 4,104 行,占全类 51% 方法;碰 HTTP 上下文的
仍是 0 个——本单成立的前提没变。

产品在 A/B/C 中选定 B(mixin 抽取),并授权把 §4「不得搬 ≥150 行业务
方法」按本意解释:那条红线针对的是 C 那种逐行改写,而 mixin 是把方法体
原样挪家、self 含义不变、靠 MRO 解析,HTTP 那一面零变化。决策记录里已
写明这是该红线的推翻记录。

新增 spike 闸门:搬进 mixin 后 test_api_server_security.py 一字不改直接
跑,绿则继续、红则退回 A(7 方法/300 行、关 2 个伪造点)。已查明两个降
风险事实(security 测试无结构性断言、增长合同用整文件正则),并点名真
风险是循环 import——闭包引用同文件 44 个模块级函数 + 21 个常量/类。

另修正 __new__ 归零口径:合同基线 33 = scripts 4 + tests 29,tests 侧含
不许改的 security 测试,所以归零只针对 scripts 侧 4 → 0。

纯文档推送,不触发门禁、不发布镜像、不部署。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
2026-09-16 03:14:41 +00:00

162 lines
10 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 · 关掉 `JyotishAPIHandler.__new__` 后门(mixin 抽取)
- 日期:2026-09-16**2026-09-16 第二版:闭包规模与方案全部改写,见 §1.1**)
- 基线 commit`origin/staging` @ `dc8cae31`
- 执行分支:`codex/api-server-backdoor-close-20260916`
- 落点:`scripts/jyotish_api_server.py`、新建的 `scripts/consultation_compute_mixin.py`(名字可另议)、`scripts/consultation_workflow_service.py``scripts/capture_report_blocked_repairs_golden.py``scripts/local_accuracy_report.py``tests/test_api_server_growth_contract.py`
- **取代 `TASK-api-server-decomposition-20260916` 的阶段 1**;该单其余阶段的处置见 §3.3
- 与已合入的 residual-hotspots、home-state-lowering-batch2 无文件重叠
- 规模:约 115 个方法 / 4,104 行**整体移进 mixin,方法体一字不改**。零行为变化。
---
## 1. 第一版的规模前提是错的
第一版写「8 个方法 / 314 行」,**错了一个数量级**。错因已定位:闭包只跟了 `self._x()`,而 `_compute_consultation_workflow` 只有 6 行,转给**模块级**的 `execute_consultation_workflow(self, ...)`——那个函数体 394 行、直接对传进来的 `handler` 调 16 个方法,再往下扇出。跟丢了那一跳,300 行就被当成了全部。
执行方按 §5.1 重算后的实测:
| | 数量 |
| --- | ---: |
| 需要搬的类方法 | **约 115 个** |
| 合计行数 | **约 4,104 行** |
| 其中碰 HTTP 上下文(`self.headers` / `wfile` / `rfile` / `path` / `client_address` | **0 个** |
| 占 `JyotishAPIHandler`225 方法 / 8,219 行) | 51 % 方法 / 36 % 行 |
闭包按入口干净地劈成两半:
| 部分 | 规模 | 覆盖的伪造点 |
| --- | ---: | --- |
| vedastro 证据 + 合盘 | 7 方法 / 300 行 | `consultation_workflow_service.py:27``local_accuracy_report.py:141` |
| 咨询工作流 | 114 方法 / 4,086 行 | `consultation_workflow_service.py:20``capture_report_blocked_repairs_golden.py:59` |
**「0 个碰 HTTP 上下文」这条前提仍然成立**,这是本单能做的基础。
## 2. 事故实证(未变)
```python
handler = JyotishAPIHandler.__new__(JyotishAPIHandler) # 不跑 __init__ 的空壳
```
`__new__` 绕过 `BaseHTTPRequestHandler.__init__`,造出来的对象没有 `headers` / `wfile` / `rfile` / `client_address`。今天能跑只是因为被借用的方法碰巧没碰它们——实测 115 个里 0 个碰。
**这是一颗类型检查看不见的地雷**:任何人往这些方法里加一行 `self.headers.get(...)`MCP 与两个离线脚本就会在运行时 `AttributeError`,单元测试与 `tsc` 都发现不了。
更根本的是依赖方向反了:`consultation_workflow_service.py` 自称「shared consultation workflow boundary for API and MCP callers」,实际却反过来依赖 HTTP 单体文件。全仓 43 个文件依赖该模块。
## 3. 决策记录
产品 2026-09-16 在 A / B / C 三案中**选定 Bmixin 抽取)**,并明确:
1. **§4 的「不得搬 ≥150 行业务方法」按本意解释,mixin 放行。** 那条红线的本意是「别做可能悄悄改行为的大重构」,针对的是 C 那种把方法逐行改写成模块级函数。**mixin 是把方法体原样挪个家:`self` 含义不变、方法集合不变、靠 MRO 解析,HTTP 那一面完全没有变化。** 执行方不得以「任务书第一版禁止搬 ≥150 行方法」为由拒改——这一条就是那条红线的推翻记录。
2. **目标是 `__new__` 在 `scripts/` 生产侧归零。** 合同测试现行基线 `NEW_COUNT_BASELINE = 33`scripts 4 + tests 29)。`tests/` 里那 29 处含 `test_api_server_security.py`,而该文件一个字不许改,所以 **tests 侧保持不增长即可,归零只针对 scripts 侧 4 → 0**
3. **原 decomposition 单的阶段 3`do_POST` 78 个 elif 改路由表)仍然不做**,由 `3b17c1b2` 换好的门禁(类方法数 + `__new__` 计数只许降)长期推进。阶段 2 被本单以 mixin 形式吸收。
4. **B 必须先过 spike 闸门**(见 §5.2)。spike 红了就退回 A(只搬 7 方法 / 300 行、关掉 2 个伪造点),**不得硬做**。
5. **纯搬运,零行为变化。** `tests/test_api_server_security.py` 的 3,841 行断言一条不许改。
## 4. 硬红线
1. **`tests/test_api_server_security.py` 一条断言不许改。** 它红了说明不是纯搬运——回去改实现,不是改断言。这是判断「mixin 有没有搬坏」的主证据。
2. **方法体一字不改。** mixin 里的方法必须与搬走前逐字相同(除了必要的 import 调整)。允许用 `git diff -M` / `--color-moved` 之类的手段证明是移动而非改写,并把证明写进进度记录。
3. **mixin 模块不得 import `jyotish_api_server`。** 那等于把后门换了个地方开。要有断言钉住这个方向。
4. **不得改 `do_POST` / `do_GET` 的路由结构**(阶段 3 不做)。
5. 搬完后 `JyotishAPIHandler` 仍须通过继承拥有这些方法——**HTTP 侧的可见行为必须零变化**。
6. 不得顺手升级依赖、不得顺手修不在本单里的 warning。
## 5. 任务分解
### 5.1 重算闭包(已完成,结论见 §1)
执行方已按第一版 §5.1 重算并提交在 `dfd65528`(仅文档提交)。开工时以当时代码复核一次即可,不必从头再算。
### 5.2 spike 闸门(**必须先做,不产出正式交付**)
一次性验证,目的是回答「mixin 搬动会不会打红安全测试、会不会解不开循环 import」:
1. 把闭包里的方法整体移进 `ConsultationComputeMixin``JyotishAPIHandler(BaseHTTPRequestHandler, ConsultationComputeMixin)`
2. **`tests/test_api_server_security.py` 一个字不改直接跑。**
判定:
- **绿** → B 可行,继续 5.3。
- **红** → 记录红在哪、为什么,**退回 A**(只搬 vedastro 证据 + 合盘那 7 方法 / 300 行,关掉 2 个伪造点,scripts 侧 4 → 2),并在进度记录里写明 B 为什么不可行。
已知的两个降风险事实(我查过,可直接用):
- `test_api_server_security.py` 里**没有任何** `__dict__` / `inspect` / `__qualname__` / `__module__` 结构性断言,只按行为断言。
- 增长合同数类方法用的是整文件正则 `^ (?:async )?def`,方法搬到别的文件后计数自然下降,口径不用改。
**B 的真风险是循环 import**:闭包里的方法引用了同文件的 **44 个模块级函数**与 **21 个模块级常量/类**`execute_consultation_workflow``BadRequest``_load_local_module` 等)。这些要么跟着搬进 mixin 模块、要么提到第三个共享模块。spike 必须把这一条也验掉——解不开就是 A。
- 验收:spike 结论(绿/红、红在哪)写进进度记录,无论走 B 还是退 A。
### 5.3 正式搬运
按 spike 验证过的形状做完整搬运。若闭包在两个入口上确实可分(§1 那张表),建议拆成两个 mixin(vedastro 证据 + 合盘 / 咨询工作流),便于将来继续细分;但不强制。
- 验收:`JyotishAPIHandler` 的方法数显著下降,新值写进合同测试。
- 验收:mixin 模块不 import `jyotish_api_server`,有断言。
- 验收:`git diff` 能证明方法体是移动而非改写。
### 5.4 四个调用方改成直接用 mixin
`consultation_workflow_service.py`(两处)、`capture_report_blocked_repairs_golden.py``local_accuracy_report.py` 改成实例化 mixin,删掉 `JyotishAPIHandler.__new__` 与对 `jyotish_api_server` 的 import。
- 验收:四个文件都不再 import `jyotish_api_server`
- 验收:MCP 侧(`mcp_server.py:741` / `:4749` 经由 `consultation_workflow_service`)仍可用——至少一条端到端断言。
### 5.5 合同测试收基线
`tests/test_api_server_growth_contract.py``__new__` 的 **scripts 侧**计数收到 0(tests 侧保持不增长);类方法数基线更新为收尾实测值;行数粗护栏 baseline 重设为收尾实测 + 300。同步更新文件顶部 docstring 的日期与说明。
- 验收:人为加回一处 `JyotishAPIHandler.__new__`(在 `scripts/` 下)必须让测试变红(贴反向验证)。
- 验收:人为加一个类方法必须变红(贴反向验证)。
- 验收:该文件既有的三条断言(`AGENTS.md` 必须含 `must not grow` / `thinly registered`、该测试必须在 `CORE_PYTEST_TARGETS` 与快速门里)仍绿。
### 5.6 记录
本单不产生 Bug 记录(结构改造,不是缺陷),不进 `CHANGELOG.md`。进度记录里必须有:spike 结论、闭包清单、搬前/搬后的类方法数与行数、两次反向验证结果、方法体未改写的证明。
同轮把原 `TASK-api-server-decomposition-20260916` 在状态板上标成**已取代**。
## 6. 让步顺序
1. 5.2 spike **不得砍**——它是 B 与 A 的分水岭。
2. spike 绿则 5.3 + 5.4 必须一起做(只搬不改调用方,后门还在)。
3. spike 红则整单退化为 A,并在进度记录里写清楚,**不要硬做 B**。
4. 5.5、5.6 不得砍。
## 7. 开工前置命令
```bash
git fetch origin --prune
cd .worktrees/api-server-backdoor-close-20260916 # 分支已存在,rebase 到最新 staging
git rebase origin/staging
git status -sb | head -1
grep -rn "JyotishAPIHandler.__new__" --include=*.py . | grep -v "skills/.*/versions/"
grep -cE '^ (async )?def ' scripts/jyotish_api_server.py
wc -l scripts/jyotish_api_server.py
python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45 # AGENTS §9
```
验收命令:
```bash
.venv/bin/python -m pytest tests/test_api_server_security.py tests/test_api_server_growth_contract.py \
tests/test_consultation_consumer_context.py tests/test_vedastro_gateway.py \
tests/test_vedastro_snapshot_cache.py
.venv/bin/python scripts/run_quality_gate.py --profile quick # 退出码不要隔着管道取
```
**注意**:快速门当前在本机退出 1,唯一原因是无 `rsync``staging-backend-workflows.test.ts`)与无 Docker 的既有环境缺口,见 `BLOCKED.md` BLK-002 / BLK-003。**pytest 段必须是 0 failed**`dc8cae31` 之后是 792 passed / 1 skipped)。
## 8. BUG 编号起点
本单不占 BUG 号。基线 `dc8cae31` 上最大号 **BUG-736**
## 9. 不在本单范围
- 阶段 3`do_POST` 78 个 elif 改路由表)——不立单,由门禁长期推进
- `execute_consultation_workflow` 里的任何业务逻辑改动(只能整体搬,不能改)
- `tests/` 下那 29 处 `__new__`(保持不增长即可)