星盘页首屏那一发 /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 外网调用脱钩(2026-09-15)
基线
- 基线 commit:
origin/staging=69ede436(fix(ci): migrate staging automatically before deploy) - 分支:
codex/chart-vedastro-decouple-20260915 - 串行依赖:本单改
frontend/src/lib/chart-view-load.ts,与TASK-chart-page-blocking-open-20260915(BUG-715/716/717,代码已在 staging)同文件。该单的代码已合入基线,本单在其之上改;不得回退它的 layer / 缓存 / 日志分档三项修复。 - BUG 编号起点:BUG-718(开工时复核
docs/BUG_HISTORY.md当前最大号 717,BUG-905是他单误称,本仓无该条目)
事故实证
以下数字是 2026-09-15 在本机对基线代码实测得到的(引擎跑在 .venv,swisseph 可用,JYOTISH_API_CHART_CACHE_TTL_SECONDS=0 关缓存冷算)。
一、星盘页首屏那一发 /api/chart 有 99% 的时间花在外网
| 调用 | 页面 | 冷算耗时 | 响应体积 |
|---|---|---|---|
/api/chart(星盘页原样,不带 skip) |
/chart |
0.40–0.66 s | 163 KB |
/api/chart(带 skip_vedastro_main_entry_overview) |
/ephemeris |
5 ms | 58 KB |
/api/varga_full(17 张分盘) |
/chart |
5 ms | 22 KB |
/api/dasha/chara |
/chart |
3 ms | 177 KB |
/api/western |
/chart |
2 ms | 8 KB |
/api/qizheng |
/chart |
160 ms | 15 KB |
| 任意一条缓存命中 | — | 5 ms | — |
同一台机器上单独计时 vedastro_evidence_orchestrator.orchestrate_vedastro_evidence(route='overview'):0.36 s / 次,连调两次都是 0.36 s(无进程内记忆),返回 status=service_endpoint_not_configured——这还是本机没有配 endpoint、一个外部请求都没发出去的情况下的空转成本。
TASK-chart-page-blocking-open-20260915 把这 0.6 秒记成「引擎五个调用合计 0.75 秒」并据此设了 10 秒超时。那份实测没错,但 0.6 秒里几乎全部不是 Swiss Ephemeris,是 VedAstro 分支。
二、生产配置下这一发会变成外网请求乘法
frontend/src/lib/chart-view-load.ts的birthPayload()(基线第 40–60 行)构造/api/chart请求体,没有skip_vedastro_main_entry_overview;同文件assembleChartView()用它发第一发引擎调用。frontend/src/app/api/ephemeris/route.ts两处(基线第 93、185 行)都显式传了skip_vedastro_main_entry_overview: true。同一个引擎端点,两个页面策略相反。scripts/jyotish_api_server.py_compute_chart_sync()里if not body.get('skip_vedastro_main_entry_overview'):→_attach_vedastro_main_entry_overview(...)(基线第 7090–7091 行)。- 该 attach 走
vedastro_evidence_orchestrator.orchestrate_vedastro_evidence(route='overview'):先run_official_full_snapshot_for_case,再对career / marriage / wealth三个领域各跑一次run_range_scan_for_case。 - full snapshot 的请求清单(实跑
_official_full_snapshot_manifest数出来的):events_overview1 +dasha_all1 +chart_core10 +house_core12 = 24 个外部 HTTP 请求,fanout 开关VEDASTRO_FULL_SNAPSHOT_FANOUT_ENABLED默认值就是"1"(scripts/vedastro_service_adapter.py:2520)。 deploy/README.md的生产 env 段写着VEDASTRO_API_ENDPOINT=https://api.vedastro.org/api、VEDASTRO_ENABLE_NETWORK=1、VEDASTRO_RANGE_SCAN_NETWORK_ENABLED=1、VEDASTRO_TIMEOUT_SECONDS=20,没有关 fanout。
也就是说:生产上每一次缓存未命中的星盘页打开,首屏那一发要等 24 个外网请求加 3 次领域扫描。前端这条链路只等 10 秒(chart-view-engine.ts 的 CHART_VIEW_ENGINE_TIMEOUT_MS),VedAstro 侧超时 20 秒。
三、这份外部证据星盘页根本不用
frontend/src/lib/chart-view-mapper.ts全文不出现vedastro、也不读modules。frontend/src/lib/chart-view-contract.ts全文不出现vedastro。
/api/chart 响应里那份 modules.vedastro_range_scan_result 从 Python 传到 Next,再被 mapper 整段丢弃。它不展示、不进合同、不给任何下游消费。
四、docs/engine/vedastro-gateway.md 自己不推荐这种用法
完整模式会增加外部请求数量和首包等待时间,更适合后台任务、预计算或非实时专业解盘,不建议直接放在聊天首轮的同步关键路径。
根因
/api/chart 的默认行为是「顺带取一份 VedAstro 外部证据」,跳过它要显式传标志。这个标志是 BUG-161 修复时引入的(defer_optional_external_evidence,scripts/jyotish_api_server.py:2183/2292/2334),当时只给 /api/consult 这一条前台路径装上了。
BUG-161 的防复发原文:「用户前台请求不得同步串联多个可选外部证据调用;快速排盘与完整证据排盘必须使用不同缓存键。」
2026-09-15 新建的 /chart 是一条新的前台路径。星历页作者按老规矩传了标志,星盘页作者没传。防复发条款只写在 Bug 历史里,代码里没有任何东西拦住新页面漏传——这是 BUG-161 的复发面,不是新问题。
顺带说明:缓存键那一半是好的。_build_api_chart_cache_payload 已经把 skip_vedastro_main_entry_overview 放进 key(基线第 2634 行附近),快慢两种排盘天然不共享缓存,本单不需要动它。
决策记录
- 2026-09-15,产品负责人在会话中看过上述实证后授权本单,口径是:星盘页首屏不挂 VedAstro。
- 不做「按需加载的 VedAstro 层」。理由是上面第三条:当前没有任何消费方。按产品既有偏好(多余入口宁可删除也不修),先把它从这条路径上摘干净;将来星盘页真要展示外部交叉验证,另立任务书连同 UI 一起设计。
- 本单不推翻任何既有红线。它是把 BUG-161 的防复发从「聊天入口」扩大到「所有前台页面路径」,并第一次用测试把它钉住。
- 本单不动
HEAVY_COMPUTE_PATHS、不动JYOTISH_HEAVY_COMPUTE_CONCURRENCY、不动前端 10 秒超时、不放宽任何限流——BUG-717 的防复发继续有效。 - 生产 env 与镜像层面的两件事(API key 是否配置、SDK 自动联网升级)不在本单,见
TASK-vedastro-runtime-ops-20260915.md。两单可并行,文件不重叠。
硬红线
- 只改前台请求体与测试。不得修改
scripts/jyotish_api_server.py、scripts/vedastro_service_adapter.py、scripts/vedastro_evidence_orchestrator.py。 - 不得把
_attach_vedastro_main_entry_overview的默认行为改成「默认不取」——/api/consult、/api/professional_reading、高严谨工作流、报告链仍然依赖它,改默认值会静默削掉解读链的外部证据(AGENTS Part B / MEVG 是硬约束)。本单只在调用方显式传标志。 - 不得回退 BUG-715/716/717 的三项修复(layer 按需、5 分钟引擎结果缓存、失败原因分档日志与文案)。
- 不得给星盘页加 spinner / 骨架 / 「正在加载」(AGENTS §6)。
- 测试总数不得低于开工时
origin/staging实测;改任何既有断言要写「原值 / 新值 / 原因」三栏。 - 不得顺手升级依赖、不得顺手修不在本单里的 warning。
任务分解
1. 星盘页首屏请求显式跳过 VedAstro(P0)
在 frontend/src/lib/chart-view-load.ts 的 birthPayload() 返回体里加 skip_vedastro_main_entry_overview: true,与 frontend/src/app/api/ephemeris/route.ts 的既有写法一致。
注意 assembleChartView() 里 followUp 是 {...payload, planets, ascendant, houses},所以四个后续 layer 调用会自动带上这个字段——/api/varga_full、/api/dasha/chara 不读它,/api/western、/api/qizheng 也不读,无副作用。确认一遍即可,不要为此拆两个 payload。
验收标准
assembleChartView发往/api/chart的 body 里skip_vedastro_main_entry_overview === true。- 进度记录里给出改前 / 改后同一份出生资料的实测对比:冷算耗时、响应字节数。改后主盘应在 10 ms 量级(本机基线 5 ms)。
- 星盘页五个 Tab 的渲染结果与改前逐项一致(mapper 不读
modules,预期零差异)。任一 Tab 出现差异即停手上报,不要自行调 mapper。
2. 用合同测试钉住「前台页面必须显式声明外部证据策略」(P0)
新增合同测试(建议放 frontend/tests/chart-view-route.test.ts 或同目录新文件),至少锁住两条:
- 无 layers 时,
assembleChartView对/api/chart的请求体带skip_vedastro_main_entry_overview: true; /api/ephemeris路由的两处 natal / transit 请求体同样带该字段(现状已满足,本轮只是补上锁)。
第二条可以用源码合同的方式写(读文件断言),与既有 chart/page.tsx 禁止 force-dynamic 的源码合同同一路数;能用真实桩跑通就优先跑桩。
验收标准
- 把
birthPayload()里那一行删掉,测试必须红;恢复后绿。执行方要在进度记录里写明自己做过这个反向验证(防止写出恒真断言)。 npx tsx --test frontend/tests/chart-view-route.test.ts(或新文件)通过。
3. Bug 历史(P0)
新增 BUG-718,字段按 docs/BUG_HISTORY.md 既有格式写全,其中:
复发自:BUG-161(必填,不得写「无」)。说明旧防复发为什么没拦住:它以/api/consult的defer_optional_external_evidence形式落在一条路径上,没有任何代码级约束能作用于 2026-09-15 新建的/chart。相关记录:BUG-161、BUG-065、BUG-715、BUG-716、BUG-717、ERR-107、ERR-108、TASK-chart-page-blocking-open-20260915。验证:贴任务 1 的耗时 / 字节数对比与任务 2 的反向验证结论。防复发:写成可执行的一句——任何前台页面路由调/api/chart必须显式声明外部证据策略,并有合同测试覆盖;新增页面路由时按此检查。
BUG-065(VedAstro 事件扫描请求乘法导致校正数分钟无响应,288 次同步请求)是同族根因的更早一次,在 相关记录 里带上即可,不必标成复发自它。
4. 文档(P1)
CHANGELOG.md:一句话——打开星盘页不再等外部占星服务,本地直接出盘。用户可感知的是「快」,不要写内部端点名。- 不改
frontend/DESIGN.md(本单不动视觉)。 docs/testing/chart-page-20260915.md追加一条真人走查项:登录后打开/chart,主盘应几乎即时出现;断网或外部服务异常时,星盘页仍应正常出盘(因为它已经不依赖外网)。
让步顺序
资源不够时按此顺序保:任务 1 → 任务 3 → 任务 2 → 任务 4。任务 1 单独交付也是净收益;任务 2 缺席则必须在进度记录里明确写「防复发未落测试」,不得沉默。
开工前置命令
cd /workspace/Jyotisha
git fetch origin --prune
git worktree add -b codex/chart-vedastro-decouple-20260915 \
.worktrees/chart-vedastro-decouple-20260915 origin/staging
cd .worktrees/chart-vedastro-decouple-20260915/frontend && npm ci
开工前必读:docs/BUG_HISTORY.md 的 BUG-161、BUG-065、BUG-715/716/717;docs/research/pre_work_error_ledger.md 的 ERR-107、ERR-108。
本单只改前端请求体与测试,不碰引擎与适配器,scripts/pre_work_check.py 可不跑(AGENTS §9 纯前端例外)。
验收口径
./node_modules/.bin/tsc --noEmit0 错;npm run lint0 error。- 前端相关测试套件 fail=0,总数不低于开工基线。
next build后/仍○ Static;本单不碰首屏包,gzip 应无变化,有变化要解释。- 部署后
https://staging.jyotisha.chat/api/health的deployment.gitCommit等于本轮 staging 提交。 - 真人走查(无登录态 / 无 Chrome 的环境缺口)写进
docs/testing/chart-page-20260915.md,不得写成「通过」。