docs(ops): outage behaviour audit for VedAstro, location and model providers (#119 item 42)
Code-and-stub audit instead of a live drill (no staging SSH). Per-dependency tables of what the user sees, charge/refund, logs/alerts and evidence, plus gaps ranked by risk. Four clear bugs recorded as investigating: BUG-1504 (VedAstro truncated read -> 500), BUG-1505 (report lease expiry keeps credits), BUG-1506 (profile edit wipes place on timezone failure), BUG-1507 (legacy China profiles silently +8). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
This commit is contained in:
@@ -20570,3 +20570,67 @@
|
||||
- 相关记录:BUG-149(门禁里 npm ci 没有安装期限)。
|
||||
- 复发自:无
|
||||
- 修复版本:staging 集成分支 `codex/chart-base-layer-label`。
|
||||
|
||||
## BUG-1504 | VedAstro 响应读到一半连接断开时,报告生成与不带 skip 的排盘返回 500
|
||||
|
||||
- 状态:investigating
|
||||
- 首次发现:2026-11-01(Gitea #119 第 42 项,外部依赖故障审计)
|
||||
- 最近更新:2026-11-01
|
||||
- 影响面:同步调 VedAstro 官方快照的路径,包括个人报告生成(排盘主入口概览、生时门禁)和不带 `skip_vedastro_main_entry_overview` 的 `/api/chart`。星盘页、对话不受影响:前者总是跳过,后者在后台线程里调用。
|
||||
- 用户现象:报告以 `workflow_server_error` 失败(会退点);接口返回 `ERR_INTERNAL`。
|
||||
- 触发条件:VedAstro 已经返回响应头,但响应体读到一半连接被重置或截断(`http.client.IncompleteRead`、`ConnectionResetError`、`ssl.SSLError`)。
|
||||
- 根因(读码 + 本机 stub 确认 IncompleteRead):`scripts/vedastro_service_adapter.py` 的快照分发(约 2782–2808 行)和区间扫描(约 3010–3046 行)只接住 `HTTPError`、`URLError`、`RemoteDisconnected`、超时和 `JSONDecodeError`;`_attach_vedastro_main_entry_overview` 外面也没有 try,所以异常一直冒到请求总入口。
|
||||
- 修复:未修。本轮只审计,修法待产品决定是否排期(把这几类读错误归为 `network_error`)。
|
||||
- 验证:本机把 `urlopen` 换成读响应时抛 `IncompleteRead` 的 stub,调用排盘得到 500。脚本是临时的,没有入库;做法见 `docs/operations/outage-behaviour-2026-11-01.md` 第一节。
|
||||
- 防复发:待修复单补「读错误降级、不 500」的测试。
|
||||
- 相关记录:Gitea #119 第 42 项;`docs/operations/outage-behaviour-2026-11-01.md`。
|
||||
- 复发自:无
|
||||
- 修复版本:未修。
|
||||
|
||||
## BUG-1505 | 报告任务最后一次尝试租约过期时,报告标为失败但点数不退
|
||||
|
||||
- 状态:investigating
|
||||
- 首次发现:2026-11-01(Gitea #119 第 42 项,外部依赖故障审计)
|
||||
- 最近更新:2026-11-01
|
||||
- 影响面:个人报告、合盘、年运等走 `personal_report_jobs` 的报告任务。
|
||||
- 用户现象:报告显示失败(`calculation_unavailable`),点数已经扣掉,没有退回。用户重新生成要再付一次,只有删除这份失败报告时,删除触发器才会退点。
|
||||
- 触发条件:任务已经是最后一次尝试(`max_attempts` 为 3),这时 worker 进程退出、重启或卡死,租约(600 秒)过期,下一轮由回收函数收尾。发版重启时最容易遇到。另一种情况:worker 正常走到失败分支,但 `billing.release` 返回 false 或抛错,返回值被忽略。生时校正的 `release()` 也一样(`frontend/src/lib/rectification-agentic/v9/agent-run-finish.ts:49`)。
|
||||
- 根因(读码 + 本机 stub 确认):`frontend/supabase/migrations/20260814040000_personal_report_jobs_v2.sql` 的 `recover_expired_personal_report_jobs` 只把任务和报告改成 failed,不调 `release_usage`;worker 的 tick 也不退点。`frontend/src/lib/personal-report-worker-core.ts:240-243` 不检查 `release` 的返回值。
|
||||
- 修复:未修。本轮只审计,待产品决定排期。
|
||||
- 验证:本机 stub 调用回收,`failedCount=1`,没有任何退点调用;`release` 返回 false 时报告照样标失败。脚本是临时的,没有入库。
|
||||
- 防复发:待修复单补「租约过期终态失败必须退点」「退点失败要重试或告警」的测试。
|
||||
- 相关记录:BUG-1461(解读失败不让报告失败);`20260925040000_deleted_report_release.sql`;`docs/operations/outage-behaviour-2026-11-01.md`。
|
||||
- 复发自:无
|
||||
- 修复版本:未修。
|
||||
|
||||
## BUG-1506 | 修改已存人物的出生资料时,时区服务失败会默默清空出生地
|
||||
|
||||
- 状态:investigating
|
||||
- 首次发现:2026-11-01(Gitea #119 第 42 项,外部依赖故障审计)
|
||||
- 最近更新:2026-11-01
|
||||
- 影响面:用 `BirthPlacePicker` 编辑已存人物的地方(本人资料、人物列表)。新建人物不受影响:新建时会显示错误和「重试」。
|
||||
- 用户现象:修改出生日期、时间或地点后,如果 `/api/locations/timezone` 返回 503、连不上或报错,原来显示的「省 · 市 · 区县 … UTC+8」变回「按省、市、区县逐级选择即可。」。没有错误提示,也没有「重试」按钮,用户要重新选地点,而且可能以为已经存好了。
|
||||
- 触发条件:编辑已有地点的人物时,时区解析失败。
|
||||
- 根因(读码 + 本机渲染确认):失败时 `frontend/src/components/birth-place-picker.tsx` 调 `onChange(null)`(约 274 行)。`frontend/src/components/birth-location-fields.tsx:21-35` 把地点代码全部清空,选择器的 `externalKey` 因此变化,草稿按空值重置(`birth-place-picker.tsx:129-132`),刚设置的错误状态也一起被丢掉。
|
||||
- 修复:未修。本轮只审计,待产品决定排期。
|
||||
- 验证:本机用真实 React 渲染 `BirthPlacePicker`,把 fetch 换成返回 503 的 stub,复现上述现象。脚本是临时的,没有入库。
|
||||
- 防复发:待修复单补「编辑时时区失败保留原地点并显示重试」的行为测试;现有 `frontend/tests/birth-place-picker.test.ts` 只断言源码文本。
|
||||
- 相关记录:`docs/operations/outage-behaviour-2026-11-01.md` 第二节。
|
||||
- 复发自:无
|
||||
- 修复版本:未修。
|
||||
|
||||
## BUG-1507 | 早期中国出生资料没有时区 ID,被默默按 +8 计算(1986–1991 夏令时出生差一小时)
|
||||
|
||||
- 状态:investigating
|
||||
- 首次发现:2026-11-01(Gitea #119 第 42 项,外部依赖故障审计时顺带发现,与故障无关)
|
||||
- 最近更新:2026-11-01
|
||||
- 影响面:只有中国省市代码、没有坐标、地点名和 `timezone_id` 的早期资料(`20260724010000_global_birth_locations.sql` 没有回填)。前端首页资料和服务端今日星语、合盘的出生参数都会用到。受影响行数未取证。
|
||||
- 用户现象:没有报错。1986–1991 年夏季(中国实行夏令时期间)出生的人,按 +8 起盘,应为 +9,差一小时。重新保存本人资料时,`timezone_offset: 8`、`timezone_id: null` 会被写进库。
|
||||
- 触发条件:上述早期资料参与排盘或被重新保存。
|
||||
- 根因(读码 + 本机确认):`frontend/src/lib/home-profile.ts:74` 写死 `tz: country.timezone`(8);`frontend/src/lib/global-birth-payloads.ts:36-37`、`:65-66` 在没有 offset 时回落到 `chinaLocations.country.timezone`。新资料走 IANA 历史时区,Python `resolve_timezone_context` 对 1988-07-01 北京给 +9,两条路径结果不一致。
|
||||
- 修复:未修。需要产品决定:是否回填 `timezone_id=Asia/Shanghai` 并按历史时区重算,以及要不要通知受影响用户。
|
||||
- 验证:本机 stub 下,今日星语和合盘的出生参数都得到 tz 8,没有联网;早期资料保存后写入 `timezone_offset: 8`、`timezone_id: null`。Python 对同一时刻给 +9。脚本是临时的,没有入库。
|
||||
- 防复发:待修复单补测试。
|
||||
- 相关记录:BUG-1506;`docs/operations/outage-behaviour-2026-11-01.md` 第二节。新疆 `Asia/Urumqi`(+6)对报北京时间的用户差两小时,另待产品决定,没有单独记 Bug。
|
||||
- 复发自:无
|
||||
- 修复版本:未修。
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
# 外部依赖挂掉时网站会怎样(2026-11-01)
|
||||
|
||||
Gitea #119 第 42 项:「VedAstro、地点服务、模型供应商挂掉时网站会怎样,没演练过」。
|
||||
|
||||
- 做法:没有 staging 的 SSH,所以没做现场断网演练。改成读代码,加上在本机把依赖换成假服务(stub)跑真实代码。stub 没有连任何外部服务,也没有用任何模型密钥。
|
||||
- 代码版本:分支 `codex/outage-shadbala-119`,基于 `origin/staging` `0f1c179e`。
|
||||
- 标注:**[实测]** 是在本机跑过代码确认的;**[读码]** 只是读代码得出的结论。
|
||||
- stub 脚本是一次性的,没有入库。做法写在每张表的「依据」一栏,能照着复现。
|
||||
- 生产参数按 `deploy/README.md` 取值,例如 `VEDASTRO_TIMEOUT_SECONDS=20`(`deploy/README.md:111`)。
|
||||
|
||||
## 先说结论
|
||||
|
||||
1. **三类依赖都不会让整站挂掉。** 星盘页、建档完全不依赖 VedAstro 和模型;地点和时区在本机计算,没有外部供应商。
|
||||
2. **对话的扣点做得对。** 模型失败、超时、中途断流都会退点,测试有覆盖。
|
||||
3. **报告这一侧最弱。**
|
||||
- 报告生成要同步等 VedAstro,总耗时没有上限。VedAstro 卡住时会超过前端 90 秒的等待,报告失败。
|
||||
- 报告任务的最后一次尝试如果因进程重启而租约过期,点数不退。
|
||||
- 解读段(模型写的部分)失败时,报告照常交付、全额扣点,用户看不到任何提示。
|
||||
4. **任何一家外部依赖挂了都不会告警。**
|
||||
- `/api/health` 只检查配置和 swisseph,不探测 VedAstro、模型供应商或时区库。
|
||||
- VedAstro 模块不写日志。
|
||||
- 报告侧的模型失败不写 `errorCode`,错误汇总脚本数不到它。
|
||||
5. **模型调用没有重试,也没有备用供应商。** 一次 429 或 503 就让这一轮失败(会退点)。
|
||||
|
||||
## 一、VedAstro(外部参照引擎)
|
||||
|
||||
关键参数:
|
||||
|
||||
- 单次请求超时 20 秒(生产值)。代码默认值是 4 秒(`scripts/vedastro_service_adapter.py:341`),生产不用它。
|
||||
- 遇到 429 / 502 / 503 / 504 重试 2 次,间隔 1 秒(`:344-348`)。超时不重试。
|
||||
- 一次完整快照最多发 24 个 POST。
|
||||
|
||||
| 场景 | 用户看到什么 | 扣点 / 退点 | 日志 / 告警 | 依据 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 星盘页 `/api/chart-view` | 不受影响。前端一律带 `skip_vedastro_main_entry_overview: true` | 不涉及 | 无 | `frontend/src/lib/chart-view-load.ts:95` [读码] |
|
||||
| 建档 / 保存人物 | 不受影响。只预热本地数据包,不调 VedAstro | 不涉及 | 无 | `frontend/src/lib/consult-packet-warm.ts:61` [读码] |
|
||||
| 对话:VedAstro 超时、5xx 或连不上 | 后台线程去取,前台最多等 1.5 秒。等不到就标 `official_blocked / foreground_optional_evidence_timeout`,回答照常出 | 正常扣点(VedAstro 只是补充证据) | 无日志 | `scripts/vedastro_foreground.py:62-65`;[实测] stub 下前台 1.50 秒返回 |
|
||||
| 对话:同一张盘当天再问 | 直接用当天的失败记录(#130「失败记一天」),0 秒返回,不再请求 VedAstro | 同上 | 无 | 失败记录存在 `scratch/local/vedastro_snapshot_negative_cache/`,键是出生资料、岁差、交点和 UTC 日期的哈希,UTC 换日后失效(`scripts/vedastro_consultation_snapshot.py:93-189`)。[实测] 第二轮 0.000 秒,`freshness=negative`。测试 `tests/test_vedastro_negative_day_20261028.py` |
|
||||
| 报告生成:VedAstro 卡住 | 报告任务同步执行两段。一段是排盘时的主入口概览,约 4 × 20 ≈ 80 秒;另一段是生时门禁里的官方快照,约 48 秒。合计约 128 秒,超过前端 90 秒的等待(`frontend/src/mastra/consultation-workflow.ts:149`),报告以 `workflow_timeout` 失败。期间一直占着进程级两个重计算名额中的一个 | 报告任务失败后退点;但见下文「模型供应商」一节的租约过期缺口 | 无 VedAstro 日志 | `frontend/src/app/api/reports/route.ts:384` 和 `frontend/src/lib/personal-report-worker.ts:137` 都没传 `foreground`,所以 `defer_optional_external_evidence=false`。[实测] 把超时缩到 2 秒,排盘段 8.0 秒、门禁段 15.3 秒,按比例推到生产值 |
|
||||
| 报告生成:VedAstro 返回 5xx 但很快 | 报告完成,VedAstro 一栏标 partial | 正常扣点 | 无 | `frontend/src/lib/personal-report-generation.ts:552` [读码];[实测] 503 下排盘段 27 秒,主要是重试间隔 |
|
||||
| **报告生成 / 不带 skip 的 `/api/chart`:读响应时连接被截断** | **HTTP 500 `ERR_INTERNAL`,报告以 `workflow_server_error` 失败** | 报告失败后退点 | `[api_server] request failed`(`scripts/jyotish_api_server.py` 请求总入口的 `logging.exception`) | 快照分发只接 `HTTPError`、`URLError`、`RemoteDisconnected`、超时和 JSON 错误(`scripts/vedastro_service_adapter.py:2782-2808`、`:3010-3046`)。`http.client.IncompleteRead`、`ConnectionResetError` 漏出去,`_attach_vedastro_main_entry_overview` 外面也没有 try。[实测] IncompleteRead → 500。记为 BUG-1504 |
|
||||
| 生时校正 `/api/rectification/v5/vedastro-validate`:VedAstro 整体不通 | 11 秒后返回 `status=fail`(`vedastro_timeout`),页面显示不能确认到分钟 | 校正按案例扣点,VedAstro 失败不单独退 | 无 | `scripts/rectification/vedastro_validate_cache.py:36`(11 秒预算)[实测] 11.04 秒 |
|
||||
| 生时校正同上:VedAstro 连续返回 503 | Python 端要 24 秒,前端 12 秒就放弃(`frontend/src/lib/rectification-agentic/v9/engine-client.ts:942`),按超时失败处理。Python 线程继续跑完,继续占名额 | 同上 | 无 | 11 秒预算只管「能不能开始一次调用」,一次快照调用内部还有 24 个 POST,每个含重试。[实测] 24.05 秒 |
|
||||
| 生时校正:同一组候选当天再验 | 直接用失败记录,24 小时内不重试;存在进程内存里,重启就丢 | 同上 | 无 | `vedastro_validate_cache.py:33`(`KEEP_SECONDS`)[实测] 第二次 0.00 秒 |
|
||||
|
||||
## 二、地点与时区服务
|
||||
|
||||
架构上没有外部供应商可挂:
|
||||
|
||||
- 出生地选择器是逐级下拉。中国数据随包打包;其他国家读站内静态文件 `/data/world-locations/<cc>.json`(`frontend/src/components/birth-place-picker.tsx:152-155`)。
|
||||
- 时区走浏览器 → `POST /api/locations/timezone` → Python `/api/location/timezone` → `timezonefinder` + `zoneinfo`,全在本机。[实测] 每次 7–100 毫秒。
|
||||
- 唯一的外部供应商 Geoapify 只在 `GET /api/locations/search` 里用,而界面上没有任何地方调用这个接口。
|
||||
|
||||
所以这里的「挂掉」实际指 Python 容器不可用或卡住。
|
||||
|
||||
| 场景 | 用户看到什么 | 扣点 / 退点 | 日志 / 告警 | 依据 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 新建人物,时区接口 503 或连不上 | 「时区服务暂时不可用,稍后重试即可。」加「重试」按钮,草稿保留,「确定」按钮不可点。不能手填经纬度或时区 | 不涉及 | 路由和服务都不写日志 | `birth-place-picker.tsx:272-276`、`:490-493`;`home-onboarding-shell.tsx:137` [读码];[实测] stub 503 / 连接拒绝 / 坏 JSON 都返回 `unavailable` |
|
||||
| 新建人物,时区接口卡住 | 「正在确认该地点的时区…」一直转,我们的代码没有超时,要等 Node 默认约 300 秒 | 不涉及 | 无 | 选择器到 Next 再到 Python 三段都没有 AbortSignal 或超时 [读码];[实测] stub 卡住时转圈不结束 |
|
||||
| **修改已存人物的出生日期、时间或地点,时区接口失败** | **已存的出生地被清空,没有报错,也没有「重试」按钮**,提示回到「按省、市、区县逐级选择即可。」用户可能以为已经存好 | 不涉及 | 无 | 失败时 `onChange(null)`(`birth-place-picker.tsx:274-275`),父组件把地点代码清空(`birth-location-fields.tsx:21-35`),选择器按新键重置草稿(`:129-132`)。[实测] 真实渲染 + fetch stub。记为 BUG-1506 |
|
||||
| 海外国家地区列表加载失败 | 「该国家的地区列表暂时不可用,请稍后重试。」没有重试按钮,要换国家再换回来 | 不涉及 | 无 | `birth-place-picker.tsx:149-171`、`:336` [读码] |
|
||||
| 星盘页:本人资料没存时区,查询时时区失败 | 「出生地的时区没有对上,这张盘先不能算。我们已经记录下来了。」引导去改资料。这是临时故障,文案却让用户以为资料有错;「已经记录下来」实际只是一行 `console.warn`。卡住时 25 秒后显示「这张盘等了太久没有回来…」 | `billed:false` | `console.warn("chart_view_failure", {reason:"timezone_resolver_failure"})`,不告警 | `frontend/src/lib/chart-view-service.ts:190-199`、`chart-view-labels.ts:184`、`:205` [读码];测试 `chart-profile-update-consistency.test.ts`「classifies query, timezone…」 |
|
||||
| 对话:本人资料没存时区,时区失败 | 503「暂时无法核对完整出生资料…请重新保存后再试,本次不会扣点。」同样把临时故障说成资料问题 | **不扣点**:时区在 `reserve()` 之前解析 | 不写日志 | `consultation-route-service.ts:567-574` 对比 `:622` [读码];测试 `consultation-route-service.test.ts`「historical timezone failure remains pre-billing」[实测通过] |
|
||||
| 报告、合盘、今日星语 | 不受影响:这些入口要求资料里已存时区,不再联网解析 | 不涉及 | 不涉及 | `frontend/src/lib/subject-birth.ts:83-91` [读码] |
|
||||
| 生时校正开案 | 500「校正服务暂时不可用」 | 不扣点:时区在开案 RPC 之前 | 不写日志 | `frontend/src/lib/rectification-agentic/v9/case-service.ts:479-481` 对比 `:512` [读码] |
|
||||
| 生时评估流程 | 503「生时评估暂时不可用 / 已保留当前资料,请稍后重试。」只有这一处文案准确 | 不涉及 | 不写日志 | `frontend/src/app/api/birth-time-journey/route.ts:254-260` [读码] |
|
||||
| 星历页 | 本人盘部分直接变空,没有提示 | 不涉及 | 无 | `frontend/src/app/api/ephemeris/route.ts:124` [读码] |
|
||||
|
||||
顺带发现的正确性问题,与故障无关:
|
||||
|
||||
- 早期中国资料只有省市代码,没有坐标和 `timezone_id`。这类资料在前后端都会被默默按 +8 计算,重新保存时还会把 `timezone_offset: 8` 写进库。
|
||||
- 出处:`frontend/src/lib/home-profile.ts:74`、`frontend/src/lib/global-birth-payloads.ts:36-37`、`:65-66`。
|
||||
- 1986–1991 年夏季(中国实行夏令时)出生的人,正确值是 +9,所以差一小时。[实测] Python 对 1988-07-01 北京给 +9。
|
||||
- 记为 BUG-1507。
|
||||
- `timezonefinder` 把乌鲁木齐、喀什定为 `Asia/Urumqi`(+6)[实测]。如果用户报的是北京时间,会差两小时。要不要处理,需要产品决定。
|
||||
|
||||
## 三、模型供应商
|
||||
|
||||
关键参数:
|
||||
|
||||
- 模型目录从数据库读(`frontend/src/lib/model-catalog.ts:60-77`),`frontend/src/mastra/model.ts` 只是占位。
|
||||
- **不重试**:Mastra 智能体的 `maxRetries` 默认是 0,项目里没有任何地方改它。[实测] 503、429、连接拒绝都只发了 1 次请求。
|
||||
- **没有备用供应商**:`frontend/src/lib/consult/session-model-fallback.ts:33-50` 只在会话固定的模型版本被下架时换成默认模型,调用失败时不换。
|
||||
- 对话的计时:工具阶段 110 秒(`frontend/src/mastra/consultation-tools.ts:77`),回答阶段另计 300 秒(`:108`)。浏览器端没有超时。
|
||||
- 报告解读:每次 120 秒,最多 2 次(`personal-report-reading.ts:33`、`:183`)。
|
||||
- 合盘:每节 90 秒,最多 2 次。
|
||||
- 生时校正:每次 210 秒,每轮最多 225 秒、2 次(`rectification-run-budget.ts:8-11`)。
|
||||
|
||||
| 场景 | 用户看到什么 | 扣点 / 退点 | 日志 / 告警 | 依据 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 对话:供应商 503、429 或连不上 | 「咨询暂时无法完成,本次不会扣点。」输入框解锁。没有自动重试,也不提示换模型 | **退点**(`frontend/src/app/api/consult/route.ts:1641-1645` → `settlement.ts:143-167`) | `[consult-provider-error]`,`[agent-observability]` 带 `errorCode=provider_error`。错误汇总脚本 5 分钟内同类满 10 次才告警 | [实测] 真实 Mastra 智能体 + 本机 stub 供应商。测试「generic provider stream error is calculation_failed」(BUG-938) |
|
||||
| 对话:回答写到一半断流 | 已经显示的文字保留,后面接失败提示。保留的文字只在浏览器里,刷新就没了 | 退点 | 同上 | [实测] 已送出 283 字后断流,`run.failed` |
|
||||
| 对话:供应商接了连接但不回 | 转圈加进度文字,最长约 300 秒;工具阶段卡住时最坏约 410 秒。最后显示「计算已完成,但这次没有生成回答…请再发送一次」,文案不对 | 退点 | `errorCode=empty_answer` | [实测] 把回答时钟缩到 2 秒复现 |
|
||||
| 对话:浏览器断开或服务器进程退出 | 页面转入「回答仍在后台生成…」,每 1.75 秒查一次状态。15 分钟后显示「回答已取消…」 | 进程还在:正常结算。进程已退出:15 分钟后由状态接口补退,前提是用户页面还在查 | 无 | `frontend/src/app/api/consult/status/route.ts:79-106` [读码];测试「reserved consultations recover through the status endpoint」 |
|
||||
| 个人报告:解读段模型失败(2 × 120 秒) | 报告显示「已完成」,但没有解读段,也没有任何提示(BUG-1461 的设计是解读失败不让报告失败) | **全额扣点** | 只有 `console.info [personal-report] reading=model_failed`,不告警 | `frontend/src/lib/personal-report-worker-core.ts:436-451` [读码] |
|
||||
| 合盘 / 年运报告:模型失败 | 任务最多试 3 次,然后显示「报告生成失败」和错误码。页面 8 分钟后停止轮询并显示超时 | 最终失败时退点 | 没有 observability 行,不告警 | `synastry-full-run.ts:213-219`、`annual-report-server.ts:463-467` [读码];测试「terminal durable generation failure releases…」 |
|
||||
| **报告任务最后一次尝试时进程重启或卡死(租约过期)** | 报告变成失败(`calculation_unavailable`) | **不退点。** 回收函数 `recover_expired_personal_report_jobs` 只改状态、不调 `release_usage`;只有用户删除报告时才由删除触发器退回。用户重新生成就要再付一次 | 无 | `frontend/supabase/migrations/20260814040000_personal_report_jobs_v2.sql`(回收函数)对比 `20260925040000_deleted_report_release.sql`。[实测] stub 下回收 `failedCount=1`、没有任何退点调用。记为 BUG-1505 |
|
||||
| 报告或校正的退点 RPC 本身失败 | 报告照常标失败 | 不退,返回值被忽略 | 无 | `personal-report-worker-core.ts:242`;`rectification-agentic/v9/agent-run-finish.ts:49` [实测 报告侧] |
|
||||
| 生时校正:模型失败 | 自动重试一次,然后「本轮没有完成,状态已记录。」中途失败时可能显示「服务端运行超时」,但实际不是超时 | 退点(返回值被忽略,同上) | `[rectification-v10] attempt failed …` | `v9/agent-run-attempt.ts:434`、`:503-514` [读码] |
|
||||
| 星盘页 / 建档 | 不调用模型 | 不涉及 | 不涉及 | [读码] |
|
||||
| 旧版对话路径(`CONSULTATION_AGENTIC_RUNTIME=legacy`)中途出错 | 文字流中断 | 按已输出部分扣点 | 无 | `consult/route.ts:1683-1690` [读码]。默认是 `enabled`,这条路径目前不走 |
|
||||
|
||||
## 四、告警现状(三类依赖共用)
|
||||
|
||||
- 探活脚本 `deploy/monitor/healthcheck-alert.sh` 只看 `/api/health`。
|
||||
- Next 侧检查的是数据库迁移、模型目录配置和 Python 是否在线。
|
||||
- Python 侧只检查 swisseph。
|
||||
- 都不探测 VedAstro、模型供应商或时区库,所以三者挂掉时探活仍是绿的。
|
||||
- 错误汇总 `deploy/monitor/error-digest.sh` 只数 `[agent-observability]` 的 `errorCode` 和 5xx。
|
||||
- 对话失败是 200 流里夹 `run.failed`,只能靠 `errorCode` 计数。
|
||||
- VedAstro、地点、报告侧的模型失败都不写 `errorCode`。
|
||||
- 第 20 项(飞书 Webhook、在主机上安装监控)还没做,所以以上告警现在都不会发出去。
|
||||
|
||||
## 五、缺口(按风险从高到低)
|
||||
|
||||
1. **报告任务租约过期不退点(BUG-1505)。** 进程在最后一次尝试时重启就会触发,发版时尤其容易遇到。点数扣掉了、报告失败,用户要么删报告,要么重新付一次。退点 RPC 失败也被忽略。
|
||||
2. **报告生成同步等 VedAstro,没有总时限。** VedAstro 卡住时约 128 秒,超过前端 90 秒,报告失败;同时占着两个重计算名额中的一个,并发两份报告会让对话和校正拿到 429。这条路径的失败也不写失败缓存,下一份报告会重来一遍。可选做法:报告也走 `defer_optional_external_evidence`,或者跳过主入口概览,或者复用对话的前台预算和失败缓存。
|
||||
3. **VedAstro 连接被截断会变成 500(BUG-1504)。** 报告失败(会退点),不带 skip 的 `/api/chart` 也会 500。
|
||||
4. **出生资料编辑时,时区服务失败会默默清掉出生地(BUG-1506)。**
|
||||
5. **早期中国资料默默按 +8 计算(BUG-1507)。** 这是正确性问题,与故障无关:1986–1991 年夏季出生的差一小时。新疆 +6 要产品决定。
|
||||
6. **模型调用不重试、没有备用供应商。** 一次 429 或 503 就让付费用户这一轮失败。会退点,但体验差;目录里配了多个模型,失败时也不切换。
|
||||
7. **外部依赖挂了没有任何告警或日志。** 健康检查不探测,VedAstro 模块不写日志,报告侧模型失败不写 `errorCode`。凌晨低流量时,对话也达不到 5 分钟 10 次的阈值。
|
||||
8. **个人报告解读失败仍全额扣点,用户不知道。** 这是 BUG-1461 的设计结果,需要产品决定:是否降价、提示用户,或者重试。
|
||||
9. **生时校正的 VedAstro 验证预算在 5xx 下失效。** Python 端 24 秒,前端 12 秒放弃,线程继续占名额。
|
||||
10. **VedAstro 失败缓存不会被成功覆盖。** 对话按 UTC 日期,校正按 24 小时;一次 5 分钟的短暂故障,会让这张盘当天都拿不到官方证据。
|
||||
11. **超时与文案。**
|
||||
- 地点到 Python 的整条链没有超时,卡住时转圈约 5 分钟。
|
||||
- 对话卡住时最长约 300 秒,之后显示「计算已完成…」。
|
||||
- 星盘页和对话把临时故障说成「资料不对、请重新保存」。
|
||||
- 星盘页声称「已经记录下来了」,实际只有 `console.warn`。
|
||||
12. **低风险。**
|
||||
- 没有页面调用的 `/api/locations/search` 仍保留 Geoapify 路径:没有超时,供应商返回非 JSON 时 500。
|
||||
- 海外国家列表没有重试按钮;星历页默默丢掉本人盘。
|
||||
- 中途断流时保留的部分回答,刷新后就没了。
|
||||
- `tests/test_vedastro_service_adapter_executor.py` 有 3 个用例在 teardown 报错:用例把 `time.monotonic` 换成了有限迭代器,conftest 收尾时再调用它就抛 `StopIteration`。这是测试本身的问题,不在 quick 门禁里。
|
||||
|
||||
## 六、本轮跑过的现有测试
|
||||
|
||||
- Python:`tests/test_vedastro_negative_day_20261028.py`、`test_rectification_v5_vedastro_validation.py`、`test_vedastro_snapshot_cache.py`、`test_vedastro_consultation_snapshot.py`、`test_vedastro_health_probe_cache.py`、`test_vedastro_service_adapter_executor.py`、`test_vedastro_gateway.py`、`test_vedastro_free_tier_state.py`、`test_vedastro_executor_isolation_20261028.py`、`test_vedastro_runtime_ops.py`、`test_vedastro_rest_bridge.py`。结果:107 通过;另有 3 个 teardown 报错,见上文第 12 条。
|
||||
- 前端,地点:`global-birth-location`、`birth-place-picker`、`global-birth-profile-routes`、`adopted-birth-date`、`consultation-route-service`,50/50 通过。
|
||||
- 前端,模型与扣点:`consultation-agentic-runtime`、`consultation-billing`、`consultation-recovery`、`consultation-stream-recovery`、`consultation-recovery-poll`、`stream-text-response`、`consult-answer-truncation-20260926`、`consultation-smalltalk`、`consult-session-model-fallback`、`model-catalog`、`personal-report-worker`、`synastry-full-report`、`rectification-v9-stream`、`session-title-agent`、`session-context-summary`、`report-reading-20261027`。结果:307 项中 306 通过;1 项失败是因为本机 Node 环境没有 swisseph,属于环境问题。
|
||||
- 没有测试覆盖的:
|
||||
- 任何一段的超时或卡住
|
||||
- 模型重试和备用供应商
|
||||
- 租约过期后的退点,以及退点 RPC 失败
|
||||
- 选择器的出错、重试和编辑时清空
|
||||
- 时区失败时对话和校正的文案
|
||||
- 早期中国资料的 +8 回退
|
||||
Reference in New Issue
Block a user