Files
Jyotisha/docs/tasks/TASK-vedastro-runtime-ops-20260915.md
T
Jesse_ChenandClaude Fable 5 ce1939b074 docs(tasks): 星盘页与 VedAstro 外网调用脱钩 + 运行期真相两单
星盘页首屏那一发 /api/chart 没传 skip_vedastro_main_entry_overview,
本机实测冷算 0.40–0.66 秒里约 0.36 秒是 VedAstro 空转(连 endpoint 都
没配的情况下);带标志的同一调用是 5 毫秒。生产 env 开着 network 与
fanout,等于首屏同步等 24 个外部请求加 3 次领域扫描,而前端 mapper 与
contract 根本不读这份证据。BUG-718,复发自 BUG-161。

运行期单记录两条新发现:官方 vedastro==1.23.25 其实是 REST 客户端,
且 import 时请求 pypi 并 pip install --upgrade 自升级(实测 pin 装完
一 import 即变 1.23.26);无 key 时免费层排队是同步 sleep 加进程级
全局锁,24 个请求约 4.8 分钟堵住前台线程。台账补 ERR-107 / ERR-108,
生产 env 核对清单交产品负责人执行。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
2026-09-15 15:47:32 +00:00

177 lines
12 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 · VedAstro 运行期真相:SDK 自更新、免费层排队与生产模式确认(2026-09-15)
## 基线
- 基线 commit`origin/staging` = `69ede436`
- 分支:`codex/vedastro-runtime-ops-20260915`
- 并行关系:与 `TASK-chart-vedastro-decouple-20260915`(只改 `frontend/**`)文件不重叠,可并行。与 `TASK-api-server-decomposition-20260916`(独占 `scripts/jyotish_api_server.py`)也不重叠——**本单不得改 `scripts/jyotish_api_server.py`**。
- BUG 编号起点:**BUG-719**718 已由 chart-vedastro-decouple 单占用;开工时复核 `docs/BUG_HISTORY.md` 最大号)
- ERR 台账:本单相关的 **ERR-107 / ERR-108** 已写入 `docs/research/pre_work_error_ledger.md`,开工前必读。
## 事故实证
2026-09-15 在本机把 `requirements.txt` 固定的官方 SDK 装进 `.venv` 拆开看过(验完已卸载,`.venv` 已恢复原状)。
### 一、`vedastro==1.23.25` 不是计算库,它本身就是 REST 客户端
包总共 46 KB。`vedastro/calculate.py` 里每个方法最终都汇到同一处:
```python
base_url = "https://api.vedastro.org/api/Calculate"
response = requests.post(url, json=params, timeout=120)
```
`vedastro/__init__.py` 第一行注释:`# VedAstro Python Library - REST API Mode`。包内没有任何本地天文计算。
**结论:所谓「官方 Python SDK 路径」和我们自己的 HTTP adapter 路径,打的是同一个 `api.vedastro.org`。现在是两套客户端接同一个外部 API**,一套经 `scripts/vedastro_python_bridge.py` / `scripts/vedastro_official_capability_runner.py` 起子进程,一套是 `scripts/vedastro_service_adapter.py` 自己 `urlopen`
### 二、SDK 在 import 时联网检查版本并自动升级自己
`vedastro/update_check.py`
```python
response = requests.get('https://pypi.org/pypi/vedastro/json', timeout=5)
...
subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", package_name, "--quiet"])
```
`__init__.py` 在导出任何符号之前就调用了它。本机实测:`pip install vedastro==1.23.25` 之后第一次 `import vedastro`,它自己把版本升到了 **1.23.26** 并打印「Please restart your script」。
生产镜像 `deploy/railway-api.Dockerfile``python -m pip install -r requirements.txt``requirements.txt:9``vedastro==1.23.25`。于是:
- 每次 spawn 一个 bridge 子进程 → 一次 `import vedastro` → 一次 `pypi.org` 请求(5 秒超时)。容器出网受限时,这是每次调用白付的等待。
- 容器文件系统可写时,pin 的 1.23.25 会在运行期漂成 pypi 上的最新版。**`requirements.txt` 的版本锁在运行期是假的**,而这个包的内容直接决定我们发给外部 API 的请求形状。
### 三、没有 API key 时,免费层排队是同步 sleep + 进程级全局锁
`scripts/vedastro_service_adapter.py`
- `DEFAULT_FREE_TIER_MAX_REQUESTS = 5`、窗口 60 秒(第 354–355 行)
- `_FREE_TIER_REQUEST_LOCK = threading.Lock()`(第 358 行)
- `_acquire_free_tier_slot()`(第 1877 行):命中官方公共 endpoint 且 `VEDASTRO_API_KEY` 为空时,**持锁 `time.sleep()` 等到窗口让出名额**;有 key 直接返回 `api_key_present` 不排队。
一次 official full snapshot 的请求清单是 24 个(实跑 `_official_full_snapshot_manifest` 数出来:`events_overview` 1 + `dasha_all` 1 + `chart_core` 10 + `house_core` 12),fanout 默认开(第 2520 行默认值 `"1"`)。
**无 key 时的算术:24 ÷ 5 个/分钟 ≈ 4.8 分钟**,且这段时间里该进程所有 VedAstro 调用被那把全局锁串成一条队。生产是 2 vCPU 单进程。
### 四、本机实测的两个延迟参照
- 单次 `api.vedastro.org` 往返:**0.38 s**(本机可直连公网)。
- 快速模式(配 endpoint + `VEDASTRO_ENABLE_NETWORK=1` + `VEDASTRO_FULL_SNAPSHOT_FANOUT_ENABLED=0`)跑一次 `run_official_full_snapshot_for_case`**3.65 s`status=ok`**,拿到 `ashtakavarga / chara_dasha_now / dasha_all / shadbala / vimshottari_now` 五个 section 全部 `ok`
**环境缺口**:生产在国内 VPS`118.194.235.34`),到 `vedastro.org` 的真实 RTT 与可达性我测不到。上面两个数字是「能直连公网的机器」的下限,不得当作生产表现。
### 五、生产 env 的已知与未知
`deploy/README.md` 生产段写明的:`VEDASTRO_GATEWAY_MODE=official_first``VEDASTRO_API_ENDPOINT=https://api.vedastro.org/api``VEDASTRO_ENABLE_NETWORK=1``VEDASTRO_RANGE_SCAN_NETWORK_ENABLED=1``VEDASTRO_TIMEOUT_SECONDS=20``VEDASTRO_API_KEY=<server-secret>`
未知的(会话内无法查证,必须由产品负责人在服务器上确认):
1. `VEDASTRO_API_KEY` 是否真的有值。**这一项决定第三条是不是正在生产上发生。**
2. `VEDASTRO_FULL_SNAPSHOT_FANOUT_ENABLED` 是否被显式设为 `0`。README 没写,代码默认是开。
3. `VEDASTRO_CACHE_TTL_SECONDS` / `VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS` 的实际值。
`docs/engine/vedastro-gateway.md` 推荐的「聊天快速模式」正是 fanout 关 + range scan 关,并明确写着完整模式「不建议直接放在聊天首轮的同步关键路径」。
## 根因
三件事叠在一起:
1. 我们把一个第三方 REST 客户端当成「本地 SDK」在用,因此没人把它的行为当外部依赖审。
2. 那个客户端在 import 期做了两件超出预期的事:联网、以及改写自己所在的运行环境。
3. 我们自己的限流兜底(免费层排队)是为「偶尔调一次」设计的,而调用量是 24 起步。
## 决策记录
- 2026-09-15 产品负责人授权本单。范围是运行期真相:SDK 自更新隔离、免费层排队不得阻塞前台、生产模式确认。
- **不在本单做的**:不砍掉两套客户端中的任何一套。虽然 SDK 只是 REST 包装、理论上 adapter 一套就够,但 SDK 那条目前现成提供了 `ashtakavarga / shadbala / vimshottari_now / chara_dasha_now / dasha_all` 五个 section 的包装,砍掉需要在 adapter 里补等价请求并重新对账,属于独立一轮。本单只把它的副作用关掉。
- **不得改** `_attach_vedastro_main_entry_overview` 的默认行为。解读链(`/api/consult``/api/professional_reading`、高严谨工作流)依赖它,AGENTS Part B 的 MEVG 是硬约束。前台页面用显式标志跳过,见 chart-vedastro-decouple 单。
- 生产 `.env.production` 的修改与服务重建**只由产品负责人触发**,执行方不得代劳、不得 SSH 改生产。
## 硬红线
1. 任何提交、任务书、进度记录、Bug 历史、测试 fixture 里**不得出现 key 值**AGENTS §8、ERR-077 的教训)。只写变量名与「已配置 / 未配置」。
2. 不得把任何 VedAstro 凭据放进前端或前端可读的响应。
3. 不得改 `.gitea/workflows/**`、不改 DNS、不提升 `main`
4. 不得改 `scripts/jyotish_api_server.py`(由 api-server-decomposition 单独占)。
5. 不得为了让调用变快而放宽 SSRF 防护、超时下限或限流。
6. 不得声称生产行为已验证——除非拿到产品负责人回填的真实环境证据。
## 任务分解
### 1. 隔离 SDK 的自动升级与 pypi 探测(P0BUG-719
目标:子进程 `import vedastro` 不得请求 `pypi.org`,不得 `pip install --upgrade` 改写运行环境;运行期版本必须等于 `requirements.txt` 固定的版本。
实现方式由执行方选,建议按此优先级评估:
- 镜像层 post-install 把 `vedastro/update_check.py``check_for_update` 改写成 no-op`Dockerfile` 内一条确定性的 patch 命令,patch 失败必须让构建失败,不得静默跳过);
- 或在 spawn 子进程时注入使 pip 网络安装不可能的环境(例如 `PIP_NO_INDEX=1`),但这只挡住升级动作,挡不住那次 5 秒超时的 pypi 请求,**单独使用不算达标**;
- 不接受的做法:删 `requirements.txt` 里的 pin、改成不装 SDK、或把 bridge 整条砍掉(那是另一轮的事)。
**验收标准**
- 新增测试:断言运行期 `importlib.metadata.version("vedastro")` 等于 `requirements.txt` 里 pin 的版本。
- 新增测试或构建期断言:`check_for_update` 已被中和(调用它不产生网络请求)。用桩验证,不要真连 pypi。
- 进度记录里给出改前 / 改后一次 bridge 子进程调用的耗时对比。
- 反向验证:去掉 patch,断言必须红。
### 2. 免费层排队不得阻塞前台请求(P1,BUG-720)
现状:无 key 时前台请求线程会持全局锁 sleep 数分钟。
**定级依赖任务 4 的回填结果**
- 若生产**有** key → 这条在生产不触发,降为 P2,但仍要做,因为 staging 与本地开发经常无 key,并且 key 失效时会静默退化成这个行为。
- 若生产**无** key → 升为 P0,与任务 1 同批交付。
要求:
- 前台同步路径(任何会被 HTTP 请求线程等待的调用)不得进入无上限排队。超出预算即 fail-fast 返回明确的降级状态,由调用方按既有降级路径处理。
- 排队等待的总预算必须可配置且有默认上限,默认值不得大于 `VEDASTRO_TIMEOUT_SECONDS`
- 不得用「提高免费层配额」的方式绕过——那是别人家的服务,不是我们的开关。
**验收标准**
- 新增回归:模拟名额耗尽时,前台路径在预算内返回降级状态而不是 sleep 到超时;断言返回体里能看出是限流降级(不是「外部服务不可用」这种混淆文案)。
- 断言全局锁不会被单个请求持有超过预算。
- 既有 VedAstro adapter 测试全绿,总数不降。
### 3. 让当前模式可观测(P2)
`/api/vedastro_gateway/status` 应能一眼看出本进程当前处在哪种模式,至少包含:是否配置 endpoint、是否有 key(**布尔值,不是 key 本身**)、fanout 是否开、range scan 是否开、免费层排队是否 active、运行期 SDK 版本、两个缓存 TTL。
**验收标准**:新增测试断言响应里这些字段存在且不泄露凭据;至少覆盖「有 key」「无 key」两种桩。
### 4. 生产模式确认(P0,产品负责人执行)
清单写在 `docs/testing/vedastro-runtime-20260915.md`,执行方**不得代劳**。执行方的责任是:清单回填后,把结论写进 BUG-720 的触发条件与定级,并据此决定任务 2 的优先级。
### 5. Bug 历史与台账(P0
- **BUG-719**SDK 自更新):`相关记录` 带上 ERR-107、BUG-089Staging CI 自动升级 MCP 2.0 导致导入失败——同族:自动升级打破固定版本)。
- **BUG-720**(免费层同步排队):`相关记录` 带上 ERR-108、BUG-065VedAstro 请求乘法导致数分钟无响应)、BUG-161(前台不得同步串联可选外部证据)、BUG-718。若任务 4 尚未回填,状态写 `investigating`,把已确认的代码事实写清楚,**不得预先标 `resolved`**,也不得编造生产表现。
- 两条都不得写入任何凭据、真实出生资料或用户标识。
## 让步顺序
任务 1 → 任务 4(本来就是产品侧)→ 任务 2 → 任务 5 → 任务 3。任务 1 可单独交付。
## 开工前置命令
```bash
cd /workspace/Jyotisha
git fetch origin --prune
git worktree add -b codex/vedastro-runtime-ops-20260915 \
.worktrees/vedastro-runtime-ops-20260915 origin/staging
cd .worktrees/vedastro-runtime-ops-20260915
python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45
```
本单涉及外部 oracle 与镜像边界,AGENTS §9 预检必跑。开工前必读:`docs/research/pre_work_error_ledger.md`(尤其 ERR-077、ERR-079、ERR-107、ERR-108)、`docs/engine/vedastro-gateway.md``docs/BUG_HISTORY.md` 的 BUG-065 / BUG-089 / BUG-161。
## 验收口径
- `.venv/bin/python scripts/run_quality_gate.py --profile quick` 通过。
- `.venv/bin/python -m pytest tests/<vedastro 相关文件>` fail=0,总数不低于开工基线。
- 镜像层改动:必须给出构建证据(patch 生效、版本一致),不得只凭代码推断。
- 生产表现一律写成环境缺口,附 `docs/testing/vedastro-runtime-20260915.md`,回填前不得写「通过」。