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

10 KiB
Raw Blame History

TASK · 关掉 JyotishAPIHandler.__new__ 后门(mixin 抽取)

  • 日期:2026-09-162026-09-16 第二版:闭包规模与方案全部改写,见 §1.1
  • 基线 commitorigin/staging @ dc8cae31
  • 执行分支:codex/api-server-backdoor-close-20260916
  • 落点:scripts/jyotish_api_server.py、新建的 scripts/consultation_compute_mixin.py(名字可另议)、scripts/consultation_workflow_service.pyscripts/capture_report_blocked_repairs_golden.pyscripts/local_accuracy_report.pytests/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 个
JyotishAPIHandler225 方法 / 8,219 行) 51 % 方法 / 36 % 行

闭包按入口干净地劈成两半:

部分 规模 覆盖的伪造点
vedastro 证据 + 合盘 7 方法 / 300 行 consultation_workflow_service.py:27local_accuracy_report.py:141
咨询工作流 114 方法 / 4,086 行 consultation_workflow_service.py:20capture_report_blocked_repairs_golden.py:59

「0 个碰 HTTP 上下文」这条前提仍然成立,这是本单能做的基础。

2. 事故实证(未变)

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 = 33scripts 4 + tests 29)。tests/ 里那 29 处含 test_api_server_security.py,而该文件一个字不许改,所以 tests 侧保持不增长即可,归零只针对 scripts 侧 4 → 0
  3. 原 decomposition 单的阶段 3do_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. 把闭包里的方法整体移进 ConsultationComputeMixinJyotishAPIHandler(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_workflowBadRequest_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.pylocal_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. 开工前置命令

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

验收命令:

.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,唯一原因是无 rsyncstaging-backend-workflows.test.ts)与无 Docker 的既有环境缺口,见 BLOCKED.md BLK-002 / BLK-003。pytest 段必须是 0 faileddc8cae31 之后是 792 passed / 1 skipped)。

8. BUG 编号起点

本单不占 BUG 号。基线 dc8cae31 上最大号 BUG-736

9. 不在本单范围

  • 阶段 3do_POST 78 个 elif 改路由表)——不立单,由门禁长期推进
  • execute_consultation_workflow 里的任何业务逻辑改动(只能整体搬,不能改)
  • tests/ 下那 29 处 __new__(保持不增长即可)