Files
Jyotisha/docs/tasks/TASK-api-chart-subprocess-gate-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
22 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 · 星盘接口与报告的多余子进程、合盘串行、重计算名额轻重混用(issue #待定)
- 日期:2026-10-13
- 来源:「2026-10-13 全仓屎山与性能排查」P1 · 星盘接口子进程与闸门
- 执行方:**Grok(Windows + Docker)**,分支 `codex/api-chart-subprocess-gate-20261013`,只推这个分支
- 验收:Claude(Linux)
- BUG 编号:**BUG-1333~1334**(开工时核对 `docs/BUG_HISTORY.md` 最大号;写作时最大号 BUG-1317,BUG-1323~1332 已被同批其它任务书占用)(另:`TASK-upstream-sync7-20261014` 占 BUG-1318~1322,本批号段整体从 BUG-1323 起)
- **开工前置**:`TASK-api-disk-cache-retention-20261013`(分支 `codex/api-disk-cache-retention-20261013`)**已合入 `origin/staging`**。两单都改 `scripts/jyotish_api_server.py`,且本单 A/B 依赖那一单的缓存键规范化;未合入不得开工。
- **主会话补充的串行链(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 有核对命令)。
- 生产镜像只装 `requirements.txt`:**有** `vedastro==1.23.25`,**没有** PyJHora(PyJHora 在 `requirements-reference-engines.txt`,AGPL,只作离线参照)。本单所有计时**必须**在「模拟生产依赖」下再跑一遍:把只含下面这个 `sitecustomize.py` 的目录放在 `PYTHONPATH` 最前(子进程会继承)。
```python
# <临时目录>/nojhora/sitecustomize.py —— 只挡 jhora;要模拟「无 VedAstro SDK」时把 'vedastro' 也加进集合
import sys
class _Block:
def find_spec(self, name, path=None, target=None):
if name.split('.')[0] in {'jhora'}:
raise ModuleNotFoundError(f"No module named '{name}'")
return None
sys.meta_path.insert(0, _Block())
```
## 2. 事故实证
计时脚本:用 `JyotishAPIHandler.__new__(JyotishAPIHandler)` 造处理器,直接调 `_compute_chart_sync` / `professional_report_reference.build_professional_report_reference`,monkeypatch `subprocess.run` 记录每次命令与耗时;`JYOTISH_API_CHART_CACHE_TTL_SECONDS=0` 避开缓存。虚构盘 1990-01-01 12:00 新德里 28.6139/77.2090 +5.5。本机(Linux,无 vedastro SDK、外网关闭)实测:
### 2.1 `/api/chart` 不带 skip 每次起 2 个子进程(BUG-1333)
| 请求 | 子进程 | 热态耗时(4 次,后 3 次中位) | 响应体 |
| --- | --- | --- | --- |
| 不带 `skip_vedastro_main_entry_overview` | 2 个:`vedastro_official_capability_runner.py --bundle official_full_snapshot` 与 `--bundle official_full_capability_catalog` | **约 1.03–1.09 s**(排查报告另一次测得 0.56 s) | 160,643 字节 |
| 带 skip | 0 | **约 5–9 ms** | 55,345 字节 |
- 子进程来自 `scripts/jyotish_api_server.py::_attach_vedastro_main_entry_overview` → `vedastro_evidence_orchestrator.orchestrate_vedastro_evidence(route='overview')` → `scripts/vedastro_service_adapter.py` 的 `subprocess.run`。本机没有 SDK,子进程秒败;**生产装了 SDK,若开了外网,这两个子进程会真的打 VedAstro,耗时上限是 `VEDASTRO_TIMEOUT_SECONDS`**——本机测不到,是环境缺口。
- 出生资料以 `--birth-json` 明文出现在子进程命令行里(同机 `ps` 可见)。记录,本单不改。
- `/api/chart` 不在 `scripts/api_heavy_compute_gate.py::HEAVY_COMPUTE_PATHS` 里(`tests/test_api_heavy_compute_gate.py::test_health_and_light_paths_are_not_gated` 明确断言 `"/api/chart" not in HEAVY_COMPUTE_PATHS`),所以这 1 s 级的子进程调用不受名额约束。
### 2.2 前端调用方逐一核对
| 调用方 | 是否带 skip | 用到响应里的什么 | 需要 VedAstro 主入口概览吗 |
| --- | --- | --- | --- |
| `frontend/src/lib/chart-view-load.ts`(星盘页主盘,`payload`) | 带 | 盘面 | 否 |
| 同文件 transit 层 → `frontend/src/lib/transit-chart-body.ts::transitChartBody` | 带 | 行运盘 | 否 |
| `frontend/src/app/api/ephemeris/route.ts`(两处) | 带 | 盘面 | 否 |
| `frontend/src/app/api/synastry/route.ts::POST` | **不带** | 只读 `planets`、`ascendant`、月亮经度(`moonLongitude`) | **否** |
- 合盘还**串行**:`selfChart` 与 `partnerChart` 两次 `await postPython("/api/chart", …)` 先后执行;romance 分支里两次 `/api/varga_full`(D9)也是串行(business 分支已是 `Promise.all`)。按本机数字,合盘一次光是两次 chart 就约 2 s,带 skip 并发后约 10 ms 级。
- Python 内部:`execute_consultation_workflow` 的 `compute_chart` 步骤按 `defer_optional_external_evidence` 传 skip(前台咨询 skip,BUG-161);个人报告、高严谨、主题报告、MCP / API Explorer(`API_COMMAND_MAP` 的 `chart`、`full-reading`)走不 skip 的完整链。**这些保持不变**(BUG-161:个人报告保持默认完整工作流)。
### 2.3 报告每次起 10 个子进程(BUG-1334)
`/api/professional_report_reference`(`edition=full_data`、`languages=["zh","en"]`,个人报告长文的真实请求形状,见 `frontend/src/lib/personal-report-longform-generate.ts`),各跑 2 次:
| 环境 | 总耗时 | 子进程数 | 子进程合计 |
| --- | --- | --- | --- |
| 本机(装了 PyJHora) | 19.8 s / 18.4 s | 10 | 6.5 s / 6.0 s |
| 模拟生产(挡 `jhora`,本机本就无 vedastro SDK) | 13.5 s / 13.2 s | 11 | 0.71 s / 0.92 s |
子进程明细(模拟生产):
| 子进程 | 次数 | 每次 | 来源 |
| --- | --- | --- | --- |
| `vedastro_official_capability_runner.py`(snapshot / catalog)+ 无 SDK 时多一个 `vedastro_python_bridge.py` | 2–3 | 0.06–0.09 s | 官方快照,报告完整链的一部分,**保留** |
| `oracle_collection_queue.py` → `oracle_evidence_validator.py` | 2 | 0.03–0.27 s | `scripts/jyotish_engine.py::_oracle_progress_snapshot`:读仓库里的静态文件 `references/oracle/dasha_shadbala_oracle_cases.json` 做校验,**与用户盘无关,每份报告重跑** |
| `python -c "from scripts.annual_pyjhora_replay import _isolated_tajaka_worker…"` | 3 | 0.06–0.08 s | `scripts/annual_pyjhora_replay.py::_run_isolated_tajaka_year_lord` |
| `python -c "… _isolated_annual_worker…"` | 3 | 0.06–0.08 s | `::_run_isolated_annual_replay` |
- **修正排查报告**:生产没有 PyJHora 时这 6 个 PyJHora 子进程确实「快速失败但仍付启动开销」,但每个只有约 60–80 ms,合计约 0.4 s;排查报告里的「年度回放 3 次 ~4.2 s、Tajika 3 次 ~0.9 s」只在装了 PyJHora 的机器(开发机、参照环境)成立。生产上的收益是**每份报告约 0.5–0.9 s、8 个子进程**,不是 5 s。
- 另:`scripts/tajika.py::_resolve_external_pyjhora_python` 不缓存「找不到 PyJHora」的结论,每次都对 `python3.14`、`python3` 各起一个 `find_spec` 探测子进程(当前解释器路径与候选相同时才跳过)。生产镜像里 `sys.executable` 与 `which python3` 是否同一路径未核实——执行方在 Docker 镜像里核实并写进 PROGRESS。
### 2.4 重计算名额轻重混用(只评估)
- `HEAVY_COMPUTE_PATHS` 共 22 条,共用一个 `BoundedSemaphore(JYOTISH_HEAVY_COMPUTE_CONCURRENCY=2)`,满了立刻 429(`acquire_heavy_compute_slot` 非阻塞)。
- 排查报告数字(本单 A/B 复测):`/api/consultation_workflow` 卡片缓存命中时约 0.2 s;个人报告约 18 s(本机模拟生产 13 s);校正打分 5–12 s。两份报告同时在算时,任何一条 0.2 s 的咨询请求都会拿到 429(前端 `frontend/src/mastra/consultation-workflow.ts` 映射为 `workflow_rate_limited`)。
- 现在 429 发生时日志里看不出名额被谁占着,staging 上无法从记录确认这种「连带 429」的频率。
## 3. 根因
1. **BUG-1333**:合盘路由写于 BUG-161 之前或之外,没跟上「只读盘面的调用方带 skip」的约定;`/api/chart` 默认走完整链(为 MCP / 报告保留),于是任何漏带 skip 的调用方都会白付两个 VedAstro 子进程。串行 `await` 是写法问题。不是回归,是约定从没落到这个路由,也没有合同测试拦。
2. **BUG-1334**:PyJHora 回放为了 AGPL 隔离用子进程(正确),但没在父进程先判断「本机根本没装 PyJHora」;oracle 进度快照是给离线审计表用的,被挂在每份报告的技法审计表构建里,又没有进程内复用。
3. **名额**:闸门按「路径」一刀切,不按耗时分级;BUG-717 明令不得靠放宽路径或调高并发解决 429,所以轻重分名额不是技术问题,是产品取舍。
关联:BUG-161、BUG-301(前台仍取官方层)、BUG-715 / BUG-717(星盘页不得 429、不得放宽 `HEAVY_COMPUTE_PATHS` 或提高并发)、BUG-720(前台不得无上限排队)、BUG-721(禁止模块级缓存跨请求持有出生资料派生数据)、BUG-723(429 必须单独成档)、BUG-1302(预热不占名额)、BUG-1265 / 验收须模拟无 PyJHora。
## 4. 决策记录
- 产品负责人 2026-10-13 同意按排查结论全部立单交 Grok 执行;P1,必须 A/B。
- 授权范围:合盘改带 skip 并发;报告里与用户盘无关或必然失败的子进程在父进程短路;给闸门加观测。
- **不推翻 BUG-717**:本单**不**把任何路径移出 `HEAVY_COMPUTE_PATHS`,**不**新增第二个并发池,**不**改 `JYOTISH_HEAVY_COMPUTE_CONCURRENCY` 的语义与默认值。轻重分名额只做评估与模拟(T5),结论交产品**拍板**后另立单。
- **不推翻 BUG-161 / BUG-301**:`/api/chart` 不带 skip 时的默认行为(附 VedAstro 主入口概览)不变;报告、高严谨、MCP 的完整链不变。
- `/api/chart` 不带 skip 时占名额(T3)是**收紧**而非放宽,但会让 MCP 等外部调用方可能拿到 429,所以做成开关、**默认关**,A/B 有证据再提议打开(打开仍需拍板)。
## 5. 硬红线
1. `scripts/jyotish_api_server.py`:行数只按 `tests/test_api_server_growth_contract.py` 的粗护栏(AGENTS.md §6:不另加比测试更严的行数红线,也不得压行凑数),类方法数与 `JyotishAPIHandler.__new__` 伪造点不得增长。闸门的判定逻辑(含 BUG-1302 的 `consult_card_warm_only` 判断)可以搬进 `scripts/api_heavy_compute_gate.py`,主文件调用点保持一行。
2. `frontend/src/app/(app)/page.tsx` 不碰;不写第二套请求封装。
3. 不改 `deploy/**`、`.gitea/workflows/**`、数据库结构与迁移;不升级依赖。
4. 不改引擎数值。所有改动在「模拟生产依赖」与「装了 PyJHora」两种环境下,报告 Markdown(中英)与 `fact_table_packet` **逐字节一致**(去掉生成时间戳等已知易变字段,易变字段清单写进差分脚本并在 PROGRESS 列出)。
5. 进程内复用只允许缓存**与用户无关**的结论(PyJHora 是否可导入、静态 oracle 文件的校验结果,键含文件路径 + mtime + 大小);不得缓存任何含出生资料的结果(BUG-721)。
6. `tests/test_api_heavy_compute_gate.py` 现有断言(含 `"/api/chart" not in HEAVY_COMPUTE_PATHS`、`DEFAULT_CONCURRENCY == 2`)一条不改。
7. 改既有断言写「原值 / 新值 / 原因」三栏;golden 来自真实引擎输出。
## 6. 任务分解
### T1 · 合盘:带 skip、两两并发(BUG-1333)
- `frontend/src/app/api/synastry/route.ts`:两次 `/api/chart` 请求体加 `skip_vedastro_main_entry_overview: true`(在路由里加,**不改** `synastryBirthPayload`,因为 `global-birth-payloads.ts` 别处也用);`selfChart` / `partnerChart` 改 `Promise.all`;romance 分支两次 D9 `/api/varga_full` 改 `Promise.all`;`/api/synastry` 调用仍在两者之后。错误语义不变(任一失败仍走现有 `catch` 返回 `blocked`)。
- 新合同测试(前端):桩 `fetch`,断言两次 `/api/chart` 请求体都带 skip、两次是并发发出(第二次在第一次 resolve 之前已发出);romance 与 business 两分支的最终 JSON 与改前桩输出一致。
- 同时加一条**源码合同**:`frontend/src/app/api/**` 与 `frontend/src/lib/**` 里所有向 Python `/api/chart` 发的请求体都必须带 `skip_vedastro_main_entry_overview: true`,除非在白名单里写明理由(当前白名单为空)。
- 验收:`tsc --noEmit` 0 错、`npm run lint` 0 error、新测试与 `frontend/tests/chart-view-route.test.ts`、`frontend/tests/ephemeris-route.test.ts` 单文件通过。
- 分工:Grok 自跑;Claude 复验 + 前端全量 `npm test`。
### T2 · 报告子进程短路(BUG-1334)
- `scripts/annual_pyjhora_replay.py`:在起子进程前,父进程用 `importlib.util.find_spec("jhora")` 判断(进程内记一次,可被测试重置)。不可导入时**不起子进程**,直接返回与「子进程在无 PyJHora 环境下返回的结果」**完全相同**的 dict。做法:先在模拟生产环境里真实跑一次子进程,把返回值存成 golden(`tests/fixtures/…`,写明生成命令),短路分支的返回值必须与 golden 相等(`request_hash`、`settings` 等按入参计算的字段照常计算)。
- `scripts/tajika.py::_resolve_external_pyjhora_python`:结果进程内记一次(含「没找到」)。
- `scripts/jyotish_engine.py::_oracle_progress_snapshot`:按 (`oracle_file` 路径, mtime, 大小) 进程内记一次;文件变了自动重算。
- 不改:VedAstro 官方快照子进程;装了 PyJHora 时的子进程隔离(AGPL 边界,必须仍是子进程)。
- 验收:
- 新测试:无 PyJHora 时 `_run_isolated_tajaka_year_lord` / `_run_isolated_annual_replay` 不调用 `subprocess.run` 且返回值等于 golden;装了 PyJHora(或 `find_spec` 被桩成可用)时仍调用子进程;`_oracle_progress_snapshot` 连调两次只跑一次子进程,`touch` 文件后重跑。
- 既有 `tests/test_report_reader_main.py`、`tests/test_report_longform_parity.py`、`tests/test_report_density_facts.py` 及 `grep -rln "annual_pyjhora_replay\|_oracle_progress_snapshot\|_resolve_external_pyjhora_python" tests` 命中的测试全绿。
- 分工:Grok 自跑;Claude 复验 + 全量 pytest 与基线逐名比对(引擎相关文件有改动)。
### T3 · `/api/chart` 不带 skip 时占重计算名额(开关,默认关)(BUG-1333)
- `scripts/api_heavy_compute_gate.py` 新增 `acquire_heavy_compute_slot_for_request(path, body)`(或同义名):现有按路径的逻辑原样保留;另加:`path == "/api/chart"` 且 body 不带真值 `skip_vedastro_main_entry_overview` 且 body 不带 `async`/`enqueue` 且开关 `JYOTISH_GATE_CHART_OVERVIEW=1` 时,占一个名额。BUG-1302 的 `consult_card_warm_only` 判断一并搬进来。
- `jyotish_api_server.py` 调用点改成这一个函数(新逻辑放新模块,主文件只留调用)。
- `HEAVY_COMPUTE_PATHS` 不加 `/api/chart`。
- 验收:新测试——开关关时任何 `/api/chart` 都不占名额;开关开时只有不带 skip 的同步请求占名额,带 skip 的、`async` 的不占;`test_warm_consultation_does_not_consume_the_user_slot` 等既有测试不改仍绿。
- 分工:Grok 自跑;Claude 复验。
### T4 · 名额观测(BUG-1333 防复发的一部分)
- `api_heavy_compute_gate.py` 记录当前占名额的路径(只记路径字符串与开始时间,进程内计数);`HeavyComputeBusy` 抛出时,服务端打一行结构化日志:被拒路径、当前占用者路径列表与已占秒数。**不得**记 body、出生资料、用户标识。
- 若能不增长主文件:`/api/health` 的返回里加 `heavy_compute: {limit, in_use}`(只数字);做不到不增长就不做,写进 PROGRESS。
- 验收:新测试——饱和时日志行含占用者路径、不含 body 字段;释放后计数归零。
- 分工:Grok 自跑;Claude 复验。
### T5 · A/B 验证(P1 必做)
- 脚本 `scripts/research/api_chart_subprocess_ab.py`(位置执行方定,写进 PROGRESS)。A = 前置单合入后的 `origin/staging`(`git show <sha>:<path>` 导出到临时目录同进程 import),B = 本分支。每组重复 3 次取中位数,两种依赖环境(模拟生产 / 装了 PyJHora)各一遍;Grok 侧若装了 vedastro SDK 且有外网,再加一组「真 SDK」并如实标注。
1. **合盘**:同一对虚构盘,A 的调用序列(两次不带 skip、串行)vs B(两次带 skip、并发),在 Python 侧用线程池模拟并发。度量:总耗时、子进程数、响应字节。正确性:合盘最终 JSON(`selfChart`/`partnerChart` 摘要、`synastry`、`relationshipReport`)A/B 逐字节一致。
2. **报告**:`build_professional_report_reference`(真实请求形状),度量总耗时、子进程数与合计耗时;正确性:中英 Markdown 与 `fact_table_packet` 去易变字段后逐字节一致。
3. **闸门模拟**(评估用,不改生产行为):用真实 `api_heavy_compute_gate` 模块,按实测耗时用 `sleep` 模拟混合负载(例:每分钟 2 份报告、20 次咨询 0.2 s、6 次校正打分 8 s,随机种子固定),统计各路径 429 比例。比较方案:O1 现状;O2 轻路径独立小池(**违反 BUG-717,只模拟不实现**);O3 轻路径有界等待(≤1.5 s,不增并发,只模拟);O4 名额预留(重路径最多占 `N-1`,只模拟)。输出每个方案下咨询 429 率、报告/校正 429 率、CPU 最高并发数。
- 目标:合盘 chart 段耗时 B ≤ A 的 10%、子进程 4 → 0;报告子进程 B ≤ 3(模拟生产)、总耗时不劣于 A;所有正确性比对 0 差异。达不到如实写。
- 开关默认值:T3 默认关;T1、T2 不设开关(纯去冗余,正确性由逐字节比对保证)。
- 记录写 `docs/testing/api-chart-subprocess-gate-ab-20261013.md`;O1–O4 的模拟结果整理成给产品看的一张表(方案 / 咨询 429 率 / 报告 429 率 / 是否触碰 BUG-717)。
- 分工:Grok 跑并交记录;Claude 在 Linux 上复跑 1、2 两项(数字差 >20% 以 Claude 为准)。
### T6 · 记录
- `docs/BUG_HISTORY.md`:BUG-1333(`/api/chart` 漏带 skip 的合盘白付 VedAstro 子进程且串行、不占名额、429 无观测);BUG-1334(报告每份 10 个子进程,其中 PyJHora 回放在生产必然失败、oracle 静态校验每次重跑)。BUG-1334 的「验证」里写清生产收益是约 0.5–0.9 s / 份,不是 5 s。
- `docs/tasks/PROGRESS-api-chart-subprocess-gate-20261013.md`,含 T5 O1–O4 结论与「需拍板」一节。
- `CHANGELOG.md`:「合盘更快」一句。
- `docs/tasks/README.md` 索引加本单。
- 不改 `frontend/DESIGN.md`(无 UI 变化)。
## 7. 让步顺序
时间不够时按此顺序往后砍:T4 的 `/api/health` 字段 → T3 → T5.3 闸门模拟 → T2 的 `_resolve_external_pyjhora_python`。T1、T2 主体、T5.1–5.2、T6 不可砍。
## 8. 验收命令分工
| 谁 | 命令 |
| --- | --- |
| Grok 自跑 | `.venv` 下:`python -m pytest tests/test_api_heavy_compute_gate.py tests/test_api_server_growth_contract.py tests/test_report_reader_main.py tests/test_report_longform_parity.py tests/test_report_density_facts.py <T2/T3/T4 新测试> -q`;前端 `tsc --noEmit`、`npm run lint`、T1 新测试与 `chart-view-route` / `ephemeris-route` 单文件;T5 脚本。前端全量、Python 快速门、全量 pytest **不跑**,以 Claude 为准 |
| Claude 验收 | 上述全部 + `run_quality_gate.py --profile quick` + 全量 `pytest` 与基线逐名比对(T2 动了引擎文件)+ 前端全量 `npm test` + 在挡 `jhora` 的环境复跑 T2 测试与 T5.1–5.2 + `wc -l scripts/jyotish_api_server.py` 不超过开工值 |
| 不需要 | `test:db`;`next build` 只在 Claude 侧跑一次确认 `/` 仍 Static(只改了 API 路由) |
## 9. 开工前置命令
```bash
git fetch origin --prune
# 前置单必须已合入:
git log origin/staging --oneline | grep -i "api-disk-cache-retention\|BUG-1330" | head -3 # 必须有结果
git merge-base --is-ancestor origin/codex/api-disk-cache-retention-20261013 origin/staging && echo merged
git worktree add -b codex/api-chart-subprocess-gate-20261013 .worktrees/api-chart-subprocess-gate-20261013 origin/staging
cd .worktrees/api-chart-subprocess-gate-20261013
git log -1 --format=%h
grep -n "^## BUG-" docs/BUG_HISTORY.md | tail -3 # 确认 1333、1334 未被占
wc -l scripts/jyotish_api_server.py # 记下开工行数
grep -rn "skip_vedastro_main_entry_overview\|\"/api/chart\"" frontend/src --include=*.ts --include=*.tsx
grep -rln "annual_pyjhora_replay\|_oracle_progress_snapshot\|_resolve_external_pyjhora_python\|acquire_heavy_compute_slot" scripts tests
# Docker 镜像内核实(写进 PROGRESS):
# python -c "import sys,shutil;print(sys.executable, shutil.which('python3'), shutil.which('python3.14'))"
# python -c "import importlib.util as u;print(u.find_spec('jhora'))" # 预期 None
```
## 10. 环境缺口(预告)
- 生产装了 vedastro SDK:不带 skip 的 `/api/chart` 在开外网时的真实耗时(上限 `VEDASTRO_TIMEOUT_SECONDS`)本机测不到;合盘改动部署 staging 后,由 Claude 用虚构盘在内网复测一次合盘耗时,或写进 `docs/testing/` 清单交产品负责人在页面上计时。
- 闸门「连带 429」的真实频率:T4 上线后看 staging 日志一周再给产品结论。
- 轻重分名额方案(O2/O3/O4):**需产品拍板**,本单不实施。