星盘页首屏那一发 /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
12 KiB
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 里每个方法最终都汇到同一处:
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:
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>。
未知的(会话内无法查证,必须由产品负责人在服务器上确认):
VEDASTRO_API_KEY是否真的有值。这一项决定第三条是不是正在生产上发生。VEDASTRO_FULL_SNAPSHOT_FANOUT_ENABLED是否被显式设为0。README 没写,代码默认是开。VEDASTRO_CACHE_TTL_SECONDS/VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS的实际值。
docs/engine/vedastro-gateway.md 推荐的「聊天快速模式」正是 fanout 关 + range scan 关,并明确写着完整模式「不建议直接放在聊天首轮的同步关键路径」。
根因
三件事叠在一起:
- 我们把一个第三方 REST 客户端当成「本地 SDK」在用,因此没人把它的行为当外部依赖审。
- 那个客户端在 import 期做了两件超出预期的事:联网、以及改写自己所在的运行环境。
- 我们自己的限流兜底(免费层排队)是为「偶尔调一次」设计的,而调用量是 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 改生产。
硬红线
- 任何提交、任务书、进度记录、Bug 历史、测试 fixture 里不得出现 key 值(AGENTS §8、ERR-077 的教训)。只写变量名与「已配置 / 未配置」。
- 不得把任何 VedAstro 凭据放进前端或前端可读的响应。
- 不得改
.gitea/workflows/**、不改 DNS、不提升main。 - 不得改
scripts/jyotish_api_server.py(由 api-server-decomposition 单独占)。 - 不得为了让调用变快而放宽 SSRF 防护、超时下限或限流。
- 不得声称生产行为已验证——除非拿到产品负责人回填的真实环境证据。
任务分解
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 可单独交付。
开工前置命令
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,回填前不得写「通过」。