星盘页首屏那一发 /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
177 lines
12 KiB
Markdown
177 lines
12 KiB
Markdown
# 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 探测(P0,BUG-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-089(Staging CI 自动升级 MCP 2.0 导致导入失败——同族:自动升级打破固定版本)。
|
||
- **BUG-720**(免费层同步排队):`相关记录` 带上 ERR-108、BUG-065(VedAstro 请求乘法导致数分钟无响应)、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`,回填前不得写「通过」。
|