星盘页首屏那一发 /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
147 lines
12 KiB
Markdown
147 lines
12 KiB
Markdown
# 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_overview` 1 + `dasha_all` 1 + `chart_core` 10 + `house_core` 12 = **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`。两单可并行,文件不重叠。
|
||
|
||
## 硬红线
|
||
|
||
1. 只改前台请求体与测试。**不得修改** `scripts/jyotish_api_server.py`、`scripts/vedastro_service_adapter.py`、`scripts/vedastro_evidence_orchestrator.py`。
|
||
2. 不得把 `_attach_vedastro_main_entry_overview` 的默认行为改成「默认不取」——`/api/consult`、`/api/professional_reading`、高严谨工作流、报告链仍然依赖它,改默认值会静默削掉解读链的外部证据(AGENTS Part B / MEVG 是硬约束)。本单只在调用方显式传标志。
|
||
3. 不得回退 BUG-715/716/717 的三项修复(layer 按需、5 分钟引擎结果缓存、失败原因分档日志与文案)。
|
||
4. 不得给星盘页加 spinner / 骨架 / 「正在加载」(AGENTS §6)。
|
||
5. 测试总数不得低于开工时 `origin/staging` 实测;改任何既有断言要写「原值 / 新值 / 原因」三栏。
|
||
6. 不得顺手升级依赖、不得顺手修不在本单里的 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 缺席则必须在进度记录里明确写「防复发未落测试」,不得沉默。
|
||
|
||
## 开工前置命令
|
||
|
||
```bash
|
||
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 --noEmit` 0 错;`npm run lint` 0 error。
|
||
- 前端相关测试套件 fail=0,总数不低于开工基线。
|
||
- `next build` 后 `/` 仍 `○ Static`;本单不碰首屏包,gzip 应无变化,有变化要解释。
|
||
- 部署后 `https://staging.jyotisha.chat/api/health` 的 `deployment.gitCommit` 等于本轮 staging 提交。
|
||
- 真人走查(无登录态 / 无 Chrome 的环境缺口)写进 `docs/testing/chart-page-20260915.md`,不得写成「通过」。
|