Files
Jyotisha/docs/tasks/TASK-chart-vedastro-decouple-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

12 KiB
Raw Blame History

TASK · 星盘页首屏与 VedAstro 外网调用脱钩(2026-09-15

基线

  • 基线 commitorigin/staging = 69ede436fix(ci): migrate staging automatically before deploy
  • 分支:codex/chart-vedastro-decouple-20260915
  • 串行依赖:本单改 frontend/src/lib/chart-view-load.ts,与 TASK-chart-page-blocking-open-20260915BUG-715/716/717,代码已在 staging)同文件。该单的代码已合入基线,本单在其之上改;不得回退它的 layer / 缓存 / 日志分档三项修复
  • BUG 编号起点:BUG-718(开工时复核 docs/BUG_HISTORY.md 当前最大号 717BUG-905 是他单误称,本仓无该条目)

事故实证

以下数字是 2026-09-15 在本机对基线代码实测得到的(引擎跑在 .venvswisseph 可用,JYOTISH_API_CHART_CACHE_TTL_SECONDS=0 关缓存冷算)。

一、星盘页首屏那一发 /api/chart 有 99% 的时间花在外网

调用 页面 冷算耗时 响应体积
/api/chart(星盘页原样,不带 skip /chart 0.400.66 s 163 KB
/api/chart(带 skip_vedastro_main_entry_overview /ephemeris 5 ms 58 KB
/api/varga_full17 张分盘) /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.tsbirthPayload()(基线第 4060 行)构造 /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(...)(基线第 70907091 行)。
  • 该 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/apiVEDASTRO_ENABLE_NETWORK=1VEDASTRO_RANGE_SCAN_NETWORK_ENABLED=1VEDASTRO_TIMEOUT_SECONDS=20没有关 fanout

也就是说:生产上每一次缓存未命中的星盘页打开,首屏那一发要等 24 个外网请求加 3 次领域扫描。前端这条链路只等 10 秒(chart-view-engine.tsCHART_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_evidencescripts/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.pyscripts/vedastro_service_adapter.pyscripts/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.tsbirthPayload() 返回体里加 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/consultdefer_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-065VedAstro 事件扫描请求乘法导致校正数分钟无响应,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/717docs/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/healthdeployment.gitCommit 等于本轮 staging 提交。
  • 真人走查(无登录态 / 无 Chrome 的环境缺口)写进 docs/testing/chart-page-20260915.md,不得写成「通过」。