Files
Jyotisha/docs/tasks/TASK-python-hygiene-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

275 lines
29 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 · Python 卫生:免费层排队开关报反、静默吞异常、功能吉凶兜底三份合一、测试专用脚本归位(issue #待定)
- 日期:2026-10-13
- 来源:2026-10-13 全仓屎山与性能排查 ·「Python 卫生」条目(开关重名、吞异常、功能吉凶兜底重复、`scripts/` 根目录 443 个文件)
- 执行方:**Grok(Windows + Docker)**,分支 `codex/python-hygiene-20261013`,只推这个分支
- 验收:Claude(Linux)
- BUG 编号:**BUG-1340**(开工时核对 `docs/BUG_HISTORY.md` 最大号;写作时最大号 BUG-1317;号段由主会话统一分配)(另:`TASK-upstream-sync7-20261014` 占 BUG-1318~1322,本批号段整体从 BUG-1323 起)
- **开工前置**:`api-disk-cache-retention`、`api-chart-subprocess-gate` 两单已合入 `origin/staging`(两单都改 `scripts/jyotish_api_server.py`;本单 T2 也改它,必须串行)
- **主会话补充的串行链(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`。本单的前置以这条链为准(与上一行不一致时以本行为准)。
- **后继**:`dasha-unify`(BUG-1341)、`api-server-service-extract` 依赖本单合入
## 1. 基线
- 基线 commit:`origin/staging` `77057a819`(写作时);开工以前置两单合入后的 `origin/staging` 为准,第 9 节命令会核对。
- 所有行号以写作基线为准,执行时按符号定位。
## 2. 事故实证(`77057a819`,全部可复现)
### 2.1 BUG-1340:免费层排队「开关」报的状态与实际行为相反
排查报告原说法是「`offline_compute_mixins.py:86` 只认三个变量名中的一个,所以配了可能不生效」。**核实后这一说法不成立**:`scripts/offline_compute_mixins.py::_free_tier_queue_enabled_env` 三个名字都读(`VEDASTRO_FREE_TIER_QUEUE` / `_ENABLED` / `VEDASTRO_ENABLE_FREE_TIER_QUEUE`)。真正的问题更糟:
- 这三个变量**不控制任何行为**。真正的限流在 `scripts/vedastro_service_adapter.py::_acquire_free_tier_slot`,它只看三件事:请求地址是不是官方公共端点、有没有 `VEDASTRO_API_KEY`、`VEDASTRO_FREE_TIER_MAX_REQUESTS` / `VEDASTRO_FREE_TIER_WINDOW_SECONDS`(默认 5 次 / 60 秒)是否大于 0。
- 三个变量只被三处**报告字段**读取,每处各写一遍解析:`offline_compute_mixins.py::_free_tier_queue_enabled_env`(进 `free_tier_strategy.queue_enabled`)、`vedastro_user_entrypoint.py::_cache_and_queue_report`、`diagnose_vedastro_mode.py`(诊断输出 `free_tier_queue_enabled`)。
- 结果:审计字段与实际限流**两个方向都会报错**。
复现(虚构环境,无网络):
```bash
cd <工作树>
env -u VEDASTRO_API_KEY -u VEDASTRO_FREE_TIER_QUEUE PYTHONPATH=.:scripts python - <<'EOF'
import os
from offline_compute_mixins import _free_tier_queue_enabled_env
import vedastro_service_adapter as a
url = "https://api.vedastro.org/api/Calculate/x"
print(_free_tier_queue_enabled_env(), a._acquire_free_tier_slot(url)["mode"])
os.environ["VEDASTRO_FREE_TIER_QUEUE"] = "1"; os.environ["VEDASTRO_FREE_TIER_MAX_REQUESTS"] = "0"
print(_free_tier_queue_enabled_env(), a._acquire_free_tier_slot(url)["mode"])
EOF
```
写作时实测:
| 配置 | 报告字段 `queue_enabled` | 实际 `_acquire_free_tier_slot` |
| --- | --- | --- |
| 不配任何排队变量、无 key | `False` | `free_tier_queue_active`(**在限流**) |
| `VEDASTRO_FREE_TIER_QUEUE=1`、`MAX_REQUESTS=0` | `True` | `free_tier_queue_disabled`(**没限流**) |
此外 `scripts/vedastro_gateway.py::build_gateway_config` 的 `queue_enabled` 读的是另一组名字(`VEDASTRO_GATEWAY_QUEUE_ENABLED` / `VEDASTRO_QUEUE_ENABLED`),含义是「网关离线排队作业」,与免费层限流无关,但 `docs/engine/vedastro-gateway.md` 把两者写在同一句里(「`VEDASTRO_GATEWAY_QUEUE_ENABLED=1` / `VEDASTRO_FREE_TIER_QUEUE=1` 用于把昂贵或被限流的外部请求排队」)。`.env.cn.example` 同时配了两者。
### 2.2 静默吞异常:29 处
口径:`scripts/*.py`(根目录)+ `mcp_server.py`,`except Exception:` / `except BaseException:` / 裸 `except:` 且处理体**只有一句** `pass` / `continue` / `return None`。AST 扫描脚本见第 9 节。写作时 **29 处**(宽泛 `except Exception` 全部 343 处,大多数有降级返回或记录,不在本单范围):
| 文件 | 处数 | 位置(符号) |
| --- | ---: | --- |
| `scripts/muhurta.py` | 4 | `_swisseph_solar_times_local`(2)、`_jd_for_local_time`、`_swisseph_sun_moon_lon` |
| `scripts/jyotish_engine.py` | 4 | `write_pl9_pdf`(字体循环 continue)、`cmd_planetary_congregation`、`_conflict_arbitration`(Ashtakavarga 失败后冲突仲裁静默少一层)、`cmd_full_reading`(过境月宿) |
| `scripts/yoga_engine.py` | 3 | `_compiled_exec_code`、`_run_custom_expr`(2)——自定义规则表达式求值 |
| `scripts/tajika.py` | 2 | `_year_lord_is_daytime`、`_resolve_external_pyjhora_python` |
| `scripts/pl9_full_data_export.py` | 2 | `_render_pl9_ai_density_raw_data_markdown_en` / `_zh`——**报告「Sudarshana 三参照落宫」整段失败时静默消失** |
| 其余各 1 | 14 | `annual_tajika_pack._iana_timezone_id`、`transit_trigger._get_transit_lon_precise`、`local_oss_observation_inventory.git_commit`、`jyotishganit_shadbala_surface_probe.clean`、`vedastro_python_bridge._serialize_runtime_value`、`nakshatra_dasha.calc_nakshatra_dasha_breakdown`(裸 except)、`solar_return._get_sun_lon_jd`、`oracle_boundary_audit._date_delta_days`、`cmd_nakshatra_adv.cmd_nakshatra_dasha`(裸 except)、`reference_transparency_contract._case_chart`、`daily_guidance_service._current_mahadasha`、`cmd_muhurta._get_sun_moon_lons`、`vedastro_gateway._sdk_version`、`mcp_server.strict_workflow` |
排查报告写 `vedastro_python_bridge` 2 处,实测 1 处;其余与报告一致。
### 2.3 功能吉凶兜底三份
核心判定已在 `scripts/functional_benefics.py::derive_functional_benefic_malefic`(未知上升本身就返回 `status: blocked` 的完整形状)。外面又包了三层几乎相同的兜底:
| 位置 | 形状 | 差异 |
| --- | --- | --- |
| `scripts/jyotish_engine.py::_functional_benefic_malefic_snapshot` | try/except 包 import + 调用,异常时手写 blocked 字典 | `effect_on_confidence` = 「未完成功能性吉凶星判定,需降低高严谨结论置信度: {exc}」 |
| `scripts/jyotish_api_server.py::JyotishAPIHandler._functional_benefic_malefic_snapshot` | 同上,逐字复制 | 同上;`ascendant` 不是 dict 时自己会抛 AttributeError 再被吞 |
| `mcp_server.py::_derive_functional_benefic_malefic` | 先判 `chart.ascendant` 是否 dict | 文案 「Missing chart.ascendant; functional layer blocked.」 |
调用点:引擎 `cmd_full_reading`(`_functional_benefic_malefic_snapshot(planets, chart.get('ascendant', {}))`)、API `_compute_chart_sync` 与模块级 `_attach_local_consultation_layers`(`handler._functional_benefic_malefic_snapshot`)、MCP 两处 `present["functional_benefic_malefic"] = _derive_functional_benefic_malefic(modules)`。
### 2.4 `scripts/` 根目录:443 个 `.py`,约 1/3 不在任何生产路径上
方法(脚本见第 9 节 `refs2.py` / `classify2.py` 思路,执行方可重写):对每个 `scripts/<m>.py`,按标识符在全部受控文件里找 `m`;以 `jyotish_api_server`、`mcp_server.py`、`jyotish_vedic/**/*.py`、`frontend/src/**`、`deploy/**`、`.gitea/**` 为根,沿 `scripts/` 内的引用求传递闭包。写作时:
| 类别 | 个数 | 行数 |
| --- | ---: | ---: |
| 生产可达 | 253 | — |
| 生产不可达 | 190 | 23,438 |
|  其中被 `tests/` 引用 | 184 | 22,625 |
|  其中**只被测试引用、也不被其他脚本引用**(排查报告「138 个、约 14,600 行」的口径) | **140** | **14,981** |
|  零引用(无测试、无脚本) | 6 | 813 |
140 个里**不能动**的(写作时):
| 原因 | 个数 | 说明 |
| --- | ---: | --- |
| 被 `references/**/*.json` 证据索引按路径记录 | 96 | 例:`references/oracle/full_technique_invocation_matrix_2026_07_22.json` 记着 `"scripts/answer_quality_audit.py"`。搬走即改写历史证据指针 |
| 运营文档(`AGENTS.md`、`SKILL.md`、`skills/**`、`references/**/*.md`、`docs/testing/**`)写了运行方式 | 3 | 例:`AGENTS.md` §9 的 `scripts/pre_work_check.py` |
| `scripts/run_quality_gate.py` 写死路径 | 1 | |
| golden 生成器 | 1 | `generate_ephemeris_events_golden`(AGENTS §7.4 golden 出处) |
| 校正冻结身份(ERR-110 / ERR-114) | — | `scripts/research/sealed_holdout_rerun.py::PRODUCTION_FILES` / `RESEARCH_FILES` 与 `references/real_case_calibration/minute_rectification_holdout_v3.json` `frozen_scoring.files`;`minute_rectification_*` 多个在其中,**按字节哈希核对,搬动或改一个字节即打红** |
剩下可搬的「干净集」**42 个、4,353 行**;其中 `niryaana_shoola_dasha` 归 `dasha-unify` 处理,本单不碰,实际 **41 个**。涉及 44 个测试文件,其中 33 个只引用干净集里的模块。**这 44 个测试没有一个在快速门清单里**(`scripts/run_quality_gate.py` 快速门是显式白名单;全量 `tests/` 只在手动 release 门跑),所以「搬到 `tests/audit/` 并从快速门排除」不改变任何门禁实际跑的内容。
6 个零引用文件核实结果:
| 文件 | 结论 |
| --- | --- |
| `build_bright_stars.py` | 生成 `scripts/data/bright_stars.json`,`docs/tasks/PROGRESS-birth-sky-cover-20260928.md` 写了手工运行方式 → **保留** |
| `generate_birth_sky_golden.py` | `frontend/tests/birth-sky-test-support.ts` 与 `frontend/tests/fixtures/birth-sky-golden.json` 记为 golden 出处 → **保留**(AGENTS §7.4) |
| `rectification_prior_calibration.py` | `docs/BUG_HISTORY.md`、两份研究 / 任务文档引用 → **保留** |
| `conversational_minute_rectification_replay.py`、`local_oss_observation_inventory.py`、`vedicastro_kp_surface_locator.py` | 被 `references/oracle/*.json` 证据索引按路径记录 → **保留** |
结论:**6 个零引用一个都不删**。
打包:`pyproject.toml` `[tool.setuptools.packages.find] include = ["jyotish_vedic*", "scripts*"]`,`scripts/__init__.py` 存在,整个 `scripts` 包都打进 wheel;镜像 `deploy/railway-api.Dockerfile` `COPY scripts ./scripts` 全量复制。`deploy/gated-paths.txt` 含 `scripts/**` 与 `tests/**`,搬到子目录不改变门禁触发。
## 3. 根因
- BUG-1340:免费层限流(BUG-720 引入预算)在 adapter 里自洽,但更早的「排队开关」报告字段从未接到限流实现上,三处各自复制解析;属于**从未接线**,不是回归。BUG-720 的防复发写的是「前台同步路径不得无上限排队」,没有覆盖「报告字段与行为一致」。
- 吞异常:早期代码风格;BUG-1037 已经记过一次「宽泛异常把调用不存在的函数吞掉」。
- 功能吉凶兜底:BUG-1181(功能吉凶 v2)只改了核心模块,三个消费层的兜底是更早各自复制的。
- 根目录膨胀:研究 / 审计 / 探针脚本与生产模块同目录;部分被证据索引按路径钉住,后续只能「不再新增」而不能整体搬。
## 4. 决策记录
- 2026-10-13 产品负责人同意按排查结论全部立单交 Grok 执行。
- **BUG-1340 修法选「报告跟随真实行为」**:三个旧变量名**不变成真开关**(变成真开关等于给运维一个关掉免费层限流的口子,违背 BUG-720 防复发「不得靠调大免费层配额绕过第三方额度」)。报告字段改为由 adapter 的同一套条件算出;三个旧变量名保留读取但只用于在诊断里提示「已废弃、无作用」。
- **不删除任何脚本**(第 2.4 节 6 个零引用全部有出处)。
- 搬迁只做「干净集」41 个;96 个被证据索引钉住的、冻结身份里的、运营文档里的一律不动。
- 测试分层:搬走的测试放 `tests/audit/`,**仍在全量 `pytest`(release 门)里跑**;快速门本来就不跑它们,所以不改任何门禁清单。若主会话或产品希望 release 门也排除 `tests/audit/`,那是削弱门禁,**本单不做,需拍板**。
- 不改 `.gitea/workflows/**`:排查报告「可选:ruff BLE001/S110 只拦新增」改成仓内 pytest 预算测试(第 6 节 T5),不动 workflow 里的 ruff 文件清单。
- 不推翻任何既有红线。
## 5. 硬红线
1. `scripts/jyotish_api_server.py`:行数只按增长合同的粗护栏(AGENTS.md §6),`tests/test_api_server_growth_contract.py` 的类方法数、`JyotishAPIHandler.__new__` 计数不得增长(本单 T2 预期方法数不变、行数下降)。
2. **校正冻结身份内的文件一个字节都不许改、不许搬**(ERR-110 / ERR-114):开工先打印两份清单(第 9 节命令),本单任何改动命中清单即停下回报。
3. 不改 `.gitea/workflows/**`、`deploy/gated-paths.txt`、`pyproject.toml` 的打包配置、`deploy/*.Dockerfile`。
4. 日志**不得包含出生资料、坐标、姓名、请求体、异常消息原文**:异常消息可能带输入值(例如 `strptime` 报错会带出生日期字符串)。WARNING 级只写「模块.函数 + 异常类名」;完整 `exc_info` 只允许在 DEBUG 级。
5. 加日志不得改变任何返回值、返回形状或控制流;不得把 `except Exception` 收窄到会让原本被吞的异常抛出去的程度(收窄只在能证明只可能抛该类型时做,并附测试)。
6. 不往 stdout 写任何东西(引擎 CLI 用 stdout 输出 JSON;日志只走 `logging`,默认落 stderr)。
7. 功能吉凶合一后,**正常路径输出逐字节不变**;新 helper 不得放进 `scripts/functional_benefics.py`(它在冻结身份 `PRODUCTION_FILES` 里)。
8. 不删除任何脚本;搬迁不改脚本内容(只允许 import 路径与 `ROOT` 层级修正,并逐个列在进度记录)。
9. 不动表结构与迁移;不顺手升级依赖、不顺手修不在本单的 ruff 告警(`jyotish_api_server.py` 现有 8 条 F401/F841 不在范围)。
10. 改既有断言写「原值 / 新值 / 原因」三栏;golden 只能来自真实引擎输出。
11. 不碰 `skills/*/versions/**`(冻结的历史 skill 副本)。
## 6. 任务分解
### T1 · BUG-1340:排队状态单一来源(必做)
改什么:
- 在 `scripts/vedastro_service_adapter.py` 新增一个纯函数(建议名 `free_tier_throttle_state(request_url: str | None = None) -> dict`),用与 `_acquire_free_tier_slot` **完全相同的条件**算出 `{"active": bool, "reason": "...", "max_requests": int, "window_seconds": float, "wait_budget_seconds": float}`;`_acquire_free_tier_slot` 的判断改为调用它(行为不变)。`request_url` 缺省时取官方公共端点。
- `offline_compute_mixins._free_tier_queue_enabled_env`、`vedastro_user_entrypoint._cache_and_queue_report`、`diagnose_vedastro_mode` 三处改为读这个函数;三处各自的 `_bool_env(...)` 三连删除。
- 三个旧变量名:只在 `diagnose_vedastro_mode` 的输出里多给一个 `deprecated_env_present: [...]`,说明「已废弃,不控制限流;限流由 key 与 `VEDASTRO_FREE_TIER_MAX_REQUESTS` / `_WINDOW_SECONDS` 决定」。
- `docs/engine/vedastro-gateway.md` 与 `.env.cn.example`:拆开「网关离线排队」(`VEDASTRO_GATEWAY_QUEUE_ENABLED`)与「免费层限流」(key + `MAX_REQUESTS` / `WINDOW_SECONDS` / `WAIT_BUDGET_SECONDS`)两件事,删掉 `VEDASTRO_FREE_TIER_QUEUE=1` 示例或标注废弃。
- 不改:`_acquire_free_tier_slot` 的等待、预算、降级逻辑(BUG-720);`vedastro_gateway.build_gateway_config`。
验收:
- 新测试 `tests/test_vedastro_free_tier_state.py`:第 2.1 节两行表各一条(报告字段 == adapter 实际 `mode == "free_tier_queue_active"`);有 key 时 `active=False`;自建端点时 `active=False`;源码 AST 断言除 `diagnose_vedastro_mode` 外不再出现这三个变量名字符串。
- 既有 `tests/test_vedastro_runtime_mode_diagnostics.py`、`tests/test_vedastro_user_entrypoint.py`、`tests/test_vedastro_gateway.py`、`tests/test_api_server_security.py`(`VEDASTRO_FREE_TIER_QUEUE` 相关几条)、`tests/test_offline_compute_mixins.py` 全绿;凡断言「设了 `VEDASTRO_FREE_TIER_QUEUE=1` 就 `queue_enabled=True`」的,按三栏改成跟随真实状态。
- Grok 自跑上述文件;Claude 复验 + 快速门。
### T2 · 功能吉凶兜底合一(必做)
改什么:
- 新建 `scripts/functional_layer.py`(名字可议,**不得**是 `functional_benefics.py`),一个函数 `functional_layer_for_ascendant(ascendant) -> dict`:`ascendant` 是 dict 取 `sign`,否则视为缺失;正常路径直接 `return derive_functional_benefic_malefic(sign)`;import 或计算异常时返回与现有引擎版相同的 blocked 字典(字段顺序一致)。
- 三处改为调用它:引擎模块级函数改为一行转发(或调用点直接换);`JyotishAPIHandler._functional_benefic_malefic_snapshot` 保留方法名(`_attach_local_consultation_layers` 与测试按名调用),方法体改为一行转发;`mcp_server._derive_functional_benefic_malefic` 改为取 `chart.ascendant` 后调用它。
- 三处 blocked 文案不一致:统一为引擎版文案;MCP「Missing chart.ascendant」场景改走统一文案。任何断言旧文案的测试按三栏改。
验收:
- 新测试:十二个上升星座,三条路径(引擎函数、handler 方法、MCP 函数)返回与 `derive_functional_benefic_malefic` 逐字节一致(`json.dumps(sort_keys=True)` 比较);`ascendant=None` / `{}` / 非法星座三种输入三条路径输出相同;强制 import 失败(monkeypatch)时返回 blocked 且字段齐全。
- 写一个对照脚本(放 scratch,不入库):基线工作树与本分支各跑一次虚构盘(1990-01-01 12:00 新德里 28.6139/77.2090 +5.5)的引擎全量解读与 MCP `strict_workflow`,`modules.functional_benefic_malefic` 逐字节一致,贴进进度记录。
- `jyotish_api_server.py` 行数下降、方法数不变;`tests/test_api_server_growth_contract.py` 绿。
- `tests/test_rectification_validation_integrity_gate.py`、`tests/test_sealed_holdout_contract_freshness.py` 绿(证明没碰冻结文件)。
### T3 · 29 处静默吞异常:分类处理(必做)
每处按下表二选一,进度记录逐条列「位置 / 类别 / 处理」:
| 类别 | 判定 | 处理 |
| --- | --- | --- |
| A 预期失败 | 可选依赖导入(`import swisseph`、`importlib.metadata.version("vedastro")`)、用户可控字符串解析(时区名、日期)、自定义规则表达式求值(`yoga_engine` 3 处,每条规则都可能失败,高频) | 保持静默;收窄到确定的异常类型(如 `ImportError`、`ValueError`、`ZoneInfoNotFoundError`)**仅当**能证明只会抛这些,否则保留 `Exception` 并加一行注释说明为何静默 + `# noqa: BLE001` |
| B 降级吞掉 | 计算失败导致输出里少一段、少一层而用户 / 调用方看不出:`pl9_full_data_export` 2 处、`jyotish_engine._conflict_arbitration` / `cmd_planetary_congregation` / `cmd_full_reading`、`cmd_nakshatra_adv`、`transit_trigger`、`daily_guidance_service`、`tajika._year_lord_is_daytime`、`solar_return`、`muhurta` 计算类 3 处、`cmd_muhurta`、`reference_transparency_contract`、`mcp_server.strict_workflow` | 加模块级 `logger = logging.getLogger(__name__)`;`logger.warning("<module>.<function> degraded: %s", type(exc).__name__)`,`logger.debug(..., exc_info=True)` 可选;返回值不变 |
- 裸 `except:` 两处(`nakshatra_dasha`、`cmd_nakshatra_adv`)以及 `jyotish_engine` 两处裸 `except:` 至少改成 `except Exception:`(不再吞 `KeyboardInterrupt` / `SystemExit`),这是唯一允许的控制流变化,写进进度记录。
- `pl9_full_data_export` 两处另加:在报告该段位置写一行可见的「本段数据缺失」吗?——**本单不做**(改报告正文属于用户可感知变化,需拍板),只加日志。
- 验收:
- 新测试 `tests/test_silent_except_logging.py`:至少覆盖 `pl9_full_data_export`(造一个让 Sudarshana 段抛错的 packet)、`daily_guidance_service._current_mahadasha`、`jyotish_engine._conflict_arbitration` 三处,用 `caplog` 断言有一条 WARNING、消息里只有模块函数名与异常类名,**不含**测试盘的出生日期字符串、经纬度。
- 引擎全量解读(虚构盘)在基线与本分支输出逐字节一致(同 T2 的对照脚本,`PYTHONHASHSEED=0`)。
### T4 · 测试专用脚本归位(可砍,最后做)
改什么:
- 新建 `scripts/audit/__init__.py`(一行 docstring:「审计 / 探针 / 研究脚本,不在生产路径上;生产代码不得 import」)。
- 把第 2.4 节「干净集」41 个脚本 `git mv` 到 `scripts/audit/`。干净集由执行方**开工时重算**(同口径),并把最终名单写进进度记录;与写作时名单(`move.txt`,见附录)差异逐条解释。排除项:冻结身份两份清单、`run_quality_gate.py` 出现的、被 `references/**/*.json` / 运营文档引用的、golden 生成器、`niryaana_shoola_dasha`(归 `dasha-unify`)、`production_smoke`(名字像运维工具,执行方先确认无任何运维文档 / 部署脚本调用,确认不了就留在原处)。
- 只引用干净集模块的测试(写作时 33 个)`git mv` 到 `tests/audit/`;其余引用了干净集模块的测试留在原处,只改 import。
- 脚本内 `ROOT = Path(__file__).resolve().parents[1]` 之类层级常量随之 +1;逐个列出。
- 新测试 `tests/test_audit_scripts_boundary.py`:AST 断言 `scripts/` 根目录、`mcp_server.py`、`jyotish_vedic/**` 不 import `scripts.audit` / `audit.*`。
- 不改:任何脚本的逻辑;`pyproject.toml`(`scripts*` 已覆盖子包);Dockerfile;门禁清单。
验收:
- 被搬脚本的 CLI 仍能跑(每个 `python scripts/audit/<x>.py --help` 或无参运行的退出码与搬前一致,列表格)。
- 全量 `pytest` 通过条数与基线一致(只是路径变化),失败清单逐名与基线一致。
- `python scripts/run_quality_gate.py --profile quick` 的 pytest 段条数与基线一致(证明快速门没变)。
### T5 · 吞异常与根目录的「只拦新增」守卫(必做,轻量)
- 新测试 `tests/test_python_hygiene_budget.py`(加入 `scripts/run_quality_gate.py` 快速门清单,这是 `scripts/` 内文件,不是 workflow):
1. 第 2.2 节口径的静默吞异常计数 ≤ 本单收尾实测值(只许降);`# noqa: BLE001` 注释的 A 类不计入。
2. `scripts/` 根目录 `.py` 文件数 ≤ 本单收尾实测值(只许降);新研究脚本应放 `scripts/audit/` 或 `scripts/research/`。
- 验收:人为加一处 `except Exception: pass` 让 1 变红;人为在根目录加一个空 `.py` 让 2 变红;两次反向验证输出贴进进度记录。
### T6 · 记录(必做)
- `docs/BUG_HISTORY.md` 新增 **BUG-1340**(免费层排队状态报告与实际限流相反;关联 BUG-720)。
- `docs/tasks/PROGRESS-python-hygiene-20261013.md`:第 2 节各表的收尾实测、T3 逐条分类表、T4 名单与差异、反向验证输出。
- `CHANGELOG.md`:一条(诊断与审计字段口径变化,运维可见);Skill 版本不 bump。
- `frontend/DESIGN.md`:不改。
## 7. 让步顺序
时间不够按此顺序砍:**T4 → T5 第 2 条 → T3 的 A 类收窄(保留注释即可)**。T1、T2、T3 的 B 类、T6 不可砍。
## 8. 验收命令分工
| 谁 | 命令 |
| --- | --- |
| Grok 自跑 | `python -m pytest tests/test_vedastro_free_tier_state.py tests/test_vedastro_runtime_mode_diagnostics.py tests/test_vedastro_user_entrypoint.py tests/test_vedastro_gateway.py tests/test_offline_compute_mixins.py tests/test_api_server_growth_contract.py tests/test_silent_except_logging.py tests/test_python_hygiene_budget.py tests/test_rectification_validation_integrity_gate.py tests/test_sealed_holdout_contract_freshness.py -q`;T4 涉及的测试文件;T2/T3 对照脚本。快速门、全量 pytest **不跑**,以 Claude 为准 |
| Claude 验收 | 上述 + 快速门 + **全量 `pytest` 与基线逐名比对**(本单碰引擎文件,按「引擎改动跑全量」规则)+ `python -m pytest tests/test_api_server_security.py -q` + 隐私标记测试 `tests/test_repo_privacy_markers.py` |
| 不需要 | `test:db`、前端测试(不碰前端) |
## 9. 开工前置命令
```bash
git fetch origin --prune
git log --oneline origin/staging | grep -i "api-disk-cache-retention\|api-chart-subprocess-gate" # 两单都已合入才开工
git worktree add -b codex/python-hygiene-20261013 .worktrees/python-hygiene-20261013 origin/staging
cd .worktrees/python-hygiene-20261013
grep -n "^## BUG-" docs/BUG_HISTORY.md | tail -3 # 核对最大号
python - <<'EOF' # 冻结身份两份清单
import json, sys; sys.path[:0] = ["scripts/research", "scripts", "."]
from sealed_holdout_rerun import PRODUCTION_FILES, RESEARCH_FILES
print(PRODUCTION_FILES, RESEARCH_FILES)
print(json.load(open("references/real_case_calibration/minute_rectification_holdout_v3.json"))["frozen_scoring"]["files"])
EOF
wc -l scripts/jyotish_api_server.py # 记基线
python -m pytest tests -q -p no:cacheprovider > /tmp/baseline-full.txt 2>&1; tail -3 /tmp/baseline-full.txt # 全量基线(Windows 已知失败按 windows-known-failures.txt 比)
grep -rn "FREE_TIER_QUEUE\|_functional_benefic_malefic_snapshot\|_derive_functional_benefic_malefic" tests scripts mcp_server.py # 找按源码断言的合同测试
```
静默吞异常扫描(AST,口径与第 2.2 节一致):遍历 `scripts/*.py` 与 `mcp_server.py` 的 `ast.ExceptHandler`,类型为空 / `Exception` / `BaseException`,且 `body` 只有一句 `Pass` / `Continue` / `Return(None)`。
## 10. BUG 编号起点
BUG-1340(写作时最大号 BUG-1317;号段由主会话统一分配)。开工时核对,若被占用顺延并在进度记录说明。
## 11. 交付物清单
- 代码:`scripts/vedastro_service_adapter.py`、`scripts/offline_compute_mixins.py`、`scripts/vedastro_user_entrypoint.py`、`scripts/diagnose_vedastro_mode.py`、新 `scripts/functional_layer.py`、`scripts/jyotish_engine.py`、`scripts/jyotish_api_server.py`、`mcp_server.py`、T3 涉及的 15 个文件、`scripts/audit/**`、`tests/audit/**`、`scripts/run_quality_gate.py`(只加 T5 测试一行)
- 测试:`tests/test_vedastro_free_tier_state.py`、`tests/test_silent_except_logging.py`、`tests/test_python_hygiene_budget.py`、`tests/test_audit_scripts_boundary.py`
- 文档:`docs/tasks/PROGRESS-python-hygiene-20261013.md`、`docs/BUG_HISTORY.md`(BUG-1340)、`CHANGELOG.md`、`docs/engine/vedastro-gateway.md`、`.env.cn.example`
- 不改:`frontend/DESIGN.md`、`.gitea/**`、`deploy/**`、`pyproject.toml`
## 附录 · 写作时「干净集」名单(42 个,执行方开工重算)
archive_vedastro_nuget_probe、configure_vedastro_secret、evidence_packet_index_integrity、high_rigor_closure_snapshot、jyotishyamitra_adapter_probe、marriage_benchmark_summary、minute_rectification_development_eval、minute_rectification_holdout_intake、minute_rectification_source_audit、niryaana_shoola_dasha(归 dasha-unify,排除)、ocr_extract、oracle_batch_closure_pack、oracle_packet_assistant_index、prashna_oracle_queue、prashna_sphuta_candidate_replay、prashna_sphuta_review_result_validator、prepare_oracle_capture_packets、prepare_vedicastro_kp_tmp_env、print_cline_mcp_config、production_smoke(先确认)、public_real_case_negative_controls、rangacharya_readiness、shadbala_component_joined_closure、shadbala_d3_branch_audit、shadbala_oracle_component_cluster_summary、shadbala_sthana_targeted_audit、skill_truth_overlay_view、three_engine_mismatch_arbitrator、traditional_maturity_gap_audit、vedastro_contract_arbitrator、vedastro_divisional_contract_probe、vedastro_ingestion_closure_pack、vedastro_method_catalog_sync、vedastro_official_contract_audit、vedastro_official_source_contract、vedastro_reproducible_build_probe、vedastro_strength_oracle_packet、vp_jain_shadbala_benchmark、western_real_case_calibration、xalen_arbitration_gate、xalen_difference_attribution、xalen_public_case_batch
`minute_rectification_*` 三个不在写作时的冻结清单里,但执行方必须用第 9 节命令再核一次。
## 后续阶段(本单不做)
1. 96 个被证据索引钉住的测试专用脚本:要么接受留在根目录,要么另开单「证据索引路径迁移」(改历史证据 JSON,需拍板)。
2. 其余约 314 处宽泛 `except Exception`(有降级返回的)逐个审。
3. `pl9_full_data_export` 段落失败时在报告里可见提示(用户可感知,需拍板)。
4. `jyotish_api_server.py` 现有 8 条 ruff F401/F841。