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
This commit is contained in:
Jesse_Chen
2026-09-15 15:47:32 +00:00
co-authored by Claude Fable 5
parent 69ede4367f
commit ce1939b074
5 changed files with 429 additions and 0 deletions
+2
View File
@@ -232,6 +232,8 @@
| `TASK-staging-dispatch-autofill-sha-20260915.md` | `PROGRESS-staging-dispatch-autofill-sha-20260915.md` | `Migrate Staging Database` 每次都要手抄 40 位 SHA,而那个值恰恰是「最新一个过门禁的 staging 提交」——机器能自己算,查询代码那一步里就有。改成留空自动解析、填了仍走原路径(回滚用),三条安全属性一条不丢。**产品 2026-09-15 明确授权修改该 workflow,执行方不得以 AGENTS.md §2.7 拒改**;生产两个按钮保持手填,那是护栏不是麻烦 | 待验收 | `codex/staging-dispatch-autofill-sha-20260915` |
| `TASK-staging-auto-migrate-on-deploy-20260915.md` | `PROGRESS-staging-auto-migrate-on-deploy-20260915.md` | 门禁通过后自动先跑 staging 迁移再部署,不再手点(迁移幂等、无挂起时是 no-op,`db-migrate.mjs --check` 挂起返 3 可用于日志)。今天 `deploy-staging.yml` 完全不提迁移,忘点就让新代码跑在旧 schema 上且无人拦。**产品再次授权改 workflow,范围限 `backend-quality-gate.yml` 的 dispatch 段**;迁移失败必须阻断部署;回滚不自动迁移;生产完全不动。⚠️ 同轮必须把「迁移须对已部署代码向后兼容、破坏性变更拆两轮」写进 AGENTS.md §7.6 | 待验收 | `codex/staging-auto-migrate-on-deploy-20260915` |
| `TASK-api-server-decomposition-20260916.md` | `PROGRESS-api-server-decomposition-20260916.md` | **重构单(串行在 qizheng 单之后)**:把业务逻辑搬出 `JyotishAPIHandler`。核心不是行数,是全仓 3 处靠 `JyotishAPIHandler.__new__` 伪造空壳 handler 借方法(`consultation_workflow_service` ×2、`capture_report_blocked_repairs_golden`、`local_accuracy_report`,MCP 也走这条),依赖方向反了、handler 没有 `headers`/`wfile` 随时可炸。四阶段:拆 `__new__` 后门 → 抽 ≥150 行业务方法 → `do_POST`/`do_GET` 改路由表 → 重新冻结行数 baseline(余量 300→50)。纯搬运不改行为,`test_api_server_security.py` 3841 行断言一条不许改。预计 11,314 → 约 9,230 行。BUG 段 710+ | 待领取 | — |
| `TASK-chart-vedastro-decouple-20260915.md` | `PROGRESS-chart-vedastro-decouple-20260915.md` | **P0**:星盘页首屏那一发 `/api/chart` 没传 `skip_vedastro_main_entry_overview`,实测冷算 0.400.66 秒里约 0.36 秒是 VedAstro 空转(本机连 endpoint 都没配);生产 env 开着 network + fanout,等于首屏同步等 24 个外部请求 + 3 次领域扫描,而 `chart-view-mapper.ts` / `chart-view-contract.ts` 根本不读这份证据。星历页同端点传了标志,两页策略相反。BUG-718,**复发自 BUG-161**(前台请求不得同步串联可选外部证据)。串行在 chart-page-blocking-open 之后 | 待领取 | — |
| `TASK-vedastro-runtime-ops-20260915.md` | `PROGRESS-vedastro-runtime-ops-20260915.md` | 运行期真相单(与上单并行,文件不重叠;**不得改 `jyotish_api_server.py`**):官方 `vedastro==1.23.25` 其实是 REST 客户端(46 KB,全打 `api.vedastro.org`),且 import 时请求 pypi 并 `pip install --upgrade` 自升级——本机实测 pin 装完一 import 就变 1.23.26`requirements.txt` 的锁在运行期是假的(BUG-719);无 key 时免费层排队是同步 sleep + 全局锁,24 个请求 ≈ 4.8 分钟堵住前台线程(BUG-720,定级依赖生产 key 是否配置)。生产 env 核对清单在 `docs/testing/vedastro-runtime-20260915.md`,**只能由产品负责人执行**。台账 ERR-107 / ERR-108 | 待领取 | — |
## 命名与归档
@@ -0,0 +1,146 @@
# 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.400.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()`(基线第 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/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-065VedAstro 事件扫描请求乘法导致校正数分钟无响应,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`,不得写成「通过」。
@@ -0,0 +1,176 @@
# 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 探测(P0BUG-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-089Staging CI 自动升级 MCP 2.0 导致导入失败——同族:自动升级打破固定版本)。
- **BUG-720**(免费层同步排队):`相关记录` 带上 ERR-108、BUG-065VedAstro 请求乘法导致数分钟无响应)、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`,回填前不得写「通过」。