fix(consultation): 外网证据按盘+日期缓存,超时取消前台任务
Independent Staging Quality Gate / validate (push) Canceled after 2m21s
Independent Staging Quality Gate / publish (push) Canceled after 0s

BUG-727:同日 VedAstro 快照零等待,跨日先用旧的并后台刷新;join 超时必须 cancel,budget 不超过 2×join。BUG-728:western_evidence_packet 无读取点,默认不再进咨询响应。jyotish_api_server.py 未增长(11334→11291)。
This commit is contained in:
jesse-ux
2026-09-16 07:36:09 +08:00
parent b9c053778a
commit e61535f464
14 changed files with 1072 additions and 87 deletions
+32 -1
View File
@@ -11285,7 +11285,6 @@
- 相关记录:BUG-473、BUG-249、BUG-250、BUG-260、BUG-617
- 复发自:BUG-473(影响面含本文件,但两条防复发只约束正在流的那一行)
- 修复版本:待发布
## BUG-726 | 一轮校正把同一份 Case 档案从数据库取多次
- 状态:resolved
@@ -11301,3 +11300,35 @@
- 相关记录:BUG-176
- 复发自:无
- 修复版本:待发布
## BUG-727 | 普通聊天每轮每域同步等外网,超时不取消导致线程池饱和
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:`POST /api/consultation_workflow` 前台路径、`execute_consultation_workflow``vedastro_gateway`、前台 VedAstro 线程池
- 用户现象:普通聊天每发一条、每个问题域都要等外网;本地排盘只占约 3%。超时后技法表 VedAstro 云状态仍是 blocked,之后每一轮继续白等约 1.5 秒。
- 触发条件:网页咨询 `defer_optional_external_evidence: true`;同一张盘一天内多轮提问;前台线程池默认 2 个 worker。
- 根因:外网证据本就按「盘 + UTC 日期」组织,却没有按这个键缓存,每轮重新 TLS 握手打 `api.vedastro.org``_join_foreground_vedastro` 超时返回 `official_blocked` 但不 `cancel()` 后台任务,任务继续跑满预算;join 1.5 s、budget 8 s、workers 2 三个数不在同一处约束,平均每 4 秒一轮就把池子占死。
- 修复:新增独立模块 `scripts/vedastro_snapshot_cache.py`(目录与 key 函数都与 `_api_chart_cache` 分开)。同日命中直接用、零等待、不 submit;1–7 天前先用旧的并后台刷新;`entrypoint=daily_starlanguage` 只接受当天。超时必须 `future.cancel()`join / budget / workers 成组声明,且 `budget ≤ 2 × join`。本单不是推翻 BUG-301:前台仍取官方层,只是用缓存去掉重复等待。
- 验证:`tests/test_vedastro_snapshot_cache.py`(同日第二次零网关调用、跨日 stale+刷新不等待、daily_starlanguage 不吃昨天、缓存目录≠chart 缓存、N+1 等待不超过 join、三常数同块且 budget 比例、超时取消排队任务);既有前台赶上/超时降级回归。
- 防复发:前台外部证据必须有「盘 + 日期」级缓存;任何有界等待都必须同时取消它等待的后台任务。
- 相关记录:BUG-161、BUG-301、BUG-718
- 复发自:无(BUG-301 是故意把 VedAstro 放回前台,本单用缓存同时满足 161 与 301)
- 修复版本:待发布
## BUG-728 | `western_evidence_packet` 无人读却每轮每域传约 122 KB
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:`POST /api/consultation_workflow` 响应体、`consumer_context.western_spectrum`
- 用户现象:一轮咨询响应约 52 万字符,其中西洋证据整包约 122 KB;前端解析后丢掉。三个域就是约 366 KB 无效 JSON。
- 触发条件:普通聊天每个问题域调用 `/api/consultation_workflow`
- 根因:西洋盘计算是 must-use 层,但整包被无条件放进前台响应。`frontend/src` 零读取点;被读的是 `consumer_context.western_spectrum` 压缩投影。
- 修复:默认不把 `western_evidence_packet` 放进咨询工作流响应,计算与 `western_spectrum` 仍保留。MCP、高严谨、显式 `include_western_evidence_packet` / `western_oracle_payload` 仍返回整包。`consultationWorkflowResponseSchema``.passthrough()`,去掉该键仍能解析。
- 验证:`tests/test_vedastro_snapshot_cache.py` 默认省略、显式请求保留、oracle payload 仍返回;`frontend/tests/consultation-workflow-contract.test.ts` 无该键仍 `safeParse` 成功。
- 防复发:工作流响应新增大字段前必须有读取点;没有读取点的字段不得进入前台响应。
- 相关记录:BUG-727
- 复发自:无
- 修复版本:待发布
@@ -0,0 +1,93 @@
# PROGRESS · 普通聊天外网证据缓存(2026-09-15 / 2026-09-16
工作树:`.worktrees/consultation-external-evidence-cache-20260915`
分支:`codex/consultation-external-evidence-cache-20260915`
任务书基线:`origin/staging` @ `6b3248bf`(任务书写)
开工时远端:`origin/staging` @ `11893c7f973573da78d6dbb48db89cde8b373ecc`
预占 BUG**727 / 728**(基线最大号 720;721–726 为同日其它单预占,未冲突)
交付 commit`d49bd9cd`
## 串行基线偏离(产品授权)
`TASK-api-server-decomposition-20260916` **未合入** staging。任务书要求以拆分后的 `scripts/jyotish_api_server.py` 为基线;产品负责人要求本单现在就做。因此:
- **没有增长** `scripts/jyotish_api_server.py`。新逻辑在 `scripts/vedastro_snapshot_cache.py``scripts/vedastro_foreground.py`;主文件只做薄导入、把 submit/join 换成 `start_foreground_vedastro` / `finish_foreground_vedastro`、按需挂 `western_evidence_packet`
- 冻结合同:`wc -l` 口径开工 **11334** 行,交付 **11291** 行(少 43);上限 11063+300=**11363**。
未改 context-memory 的前端会话文件,未改 session-capacity 的 SQL 迁移。
## 任务对照
| 任务 | 状态 | BUG |
| --- | --- | --- |
| 5.1 盘 + UTC 日期缓存 | 完成 | BUG-727 |
| 5.2 超时 cancel + join/budget/workers 成组,`budget ≤ 2×join` | 完成 | BUG-727 |
| 5.3 `western_evidence_packet` 按需返回 | 完成 | BUG-728 |
| 5.4 staging 单域耗时清单 | 完成(环境缺口:无 staging 登录) | — |
| 5.5 BUG-727 / 728 | 完成 | — |
## 实现要点
- 缓存键:`sha256(年月日时分秒 + lat + lon + tz + ayanamsa + node + UTC reference_date)`,文件名只有哈希。目录 `scratch/local/vedastro_snapshot_cache`,与 `_api_chart_cache` 分开。
- 同日命中:直接用,不 submit。1–7 天:先用旧的(网关包上标 `snapshot_reference_date`),后台刷新只写缓存。`entrypoint=daily_starlanguage` 只接受当天。未命中才走 1.5 s join。
- 只缓存 `official_verified` 且带 raw 的包;超时/blocked 不落盘,避免把未交付层标成 executedBUG-301)。
- `_join_foreground_vedastro` 超时 `cancel_event.set()` + `future.cancel()`。budget 默认改为 `2×join`(1.5 s → 3 s),不再默认跑满 8 s。
- 非前台路径(报告/高严谨)仍同步跑网关、不受 join 上限,成功后同样写缓存。
- 前台响应默认去掉 `western_evidence_packet`,仍算、仍投影 `consumer_context.western_spectrum`。MCP / 高严谨 / `include_western_evidence_packet` / `western_oracle_payload` 仍返回整包。
- 前端 `runConsultationWorkflow``entrypoint` 传给 Python,供 daily 入口拒绝隔日缓存。
## `western_evidence_packet` 检索
| 位置 | 处理 |
| --- | --- |
| `frontend/src` | **零命中**,按「无人读」从咨询响应去掉 |
| `scripts/jyotish_api_server.py` | 仍计算;默认不进响应 |
| `scripts/vedastro_foreground.py` | `should_include_western_evidence_packet` |
| `scripts/western_evidence_packet.py` / `western_chart_engine.py` / `western_oracle_adapter.py` | 保留计算 |
| `scripts/unified_consultation_orchestrator.py` | 仍传入做审计日志 |
| `scripts/cross_system_arbitrator.py` | 保留 |
| `mcp_server.py` | 保留;`surface=skill_mcp` 仍返回整包 |
| `tests/` | oracle / MCP / 按需返回的断言保留 |
## 响应体积
| | 字符 |
| --- | ---: |
| 任务书基线(career 同参第二轮) | 523 k,其中 `western_evidence_packet` 122 k |
| 本机改后(stub 工作流,默认响应) | 该键不存在 |
| 显式 `include_western_evidence_packet` | 键在,体积 ≥ 默认响应 |
未在 staging 重测 523 k 真包(无登录)。按基线,每域少约 122 k 字符。
## 测试
本机无 `G:\Ferti\Jyotisha\.venv`,用 `C:\Users\74082\anaconda3\python.exe`3.11.7)。任务书写的是 Python 3.13 + swisseph。
| 命令 | 结果 |
| --- | --- |
| `pytest tests/test_vedastro_snapshot_cache.py` | **10 passed** |
| 上项 + 前台赶上/超时/oracle/growth | **18 passed** |
| `pytest tests/test_consultation_consumer_context.py tests/test_vedastro_runtime_ops.py tests/test_vedastro_snapshot_cache.py tests/test_api_server_growth_contract.py tests/test_consultation_workflow_domains.py` | **pass**1 skip:本机未装 vedastro pin |
| `pytest tests/test_api_server_security.py` + 上列相关 | **169 passed / 6 failed / 1 skipped**。6 条失败在 **HEAD 无本单改动时同样失败**thematic `relationship_strict_evidence` / `月度主状态``shadbala` advanced`timezone inference dependency unavailable`)。不是本单引入。 |
| `frontend``consultation-workflow-contract` + `consultation-spectrum-parity` | **13 passed / 0 failed** |
| `scripts/run_quality_gate.py --profile quick` | **compile 通过**(含新模块)。停在 `interpretation_source_inventory_gate.py``ModuleNotFoundError: No module named 'mcp'`Anaconda 无 FastMCP)。未改依赖。 |
## 开工预检
`python scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45`**fail**。`remote_visibility=verified`fragment/external engine 扫描通过;`tests/test_preflight_fragment_scan.py` 在本机找不到 `.workbuddy/skills/jyotish-vedic-astrology`(Windows 工作树无该镜像)。属 ERR-007 同类环境,未新开台账。不得把 `.workbuddy` 当运行主仓。
## 5.4 staging 耗时
清单:`docs/testing/consultation-external-evidence-cache-20260915.md`。无 staging 登录,**环境缺口**。改前数字用任务书本机表(career 928/865 ms)。改后 staging 两轮 `consultationToolDurationMs` 待产品回填。本单不改三域上限 3。
## 观察项(本单不修)
`reference_date` 缺省 UTC 当天,与 UTC+8「今天」早上八点前后会错开一天。
## 既有断言改动
| 文件 | 原值 | 新值 | 原因 |
| --- | --- | --- | --- |
| 咨询工作流默认响应 | 总有 `western_evidence_packet` | 默认无该键 | BUG-728 无读取点 |
| `_foreground_vedastro_budget_seconds` 默认 | 8 s212 | `min(env, 2×join)`join 默认 1.5 → budget 3 | 5.2 让步:预算不超过 join 两倍 |
| `tests/conftest.py` | 无缓存隔离 | autouse 把 snapshot cache 指到 tmp | 避免测试互相命中磁盘缓存 |
+1 -1
View File
@@ -238,7 +238,7 @@
| `TASK-rectification-failure-attribution-20260915.md` | — | **三处把系统故障说成别的东西(独占 `route.ts`**:意图分类器两次异常返回的 `null` 与用户真的「说不清」共用一条分支,回一句「我不太确定这句是不是在回答上面的问题」,**用户这句里的经历直接丢弃且不写证据**(BUG-722,采集题分支早已改对、点选题分支没跟上);引擎 429(`ERR_COMPUTE_BUSY` + `Retry-After`)被压成 `engine_request_failed`,不重试不打日志,证据记下了但范围不动、模型照说「记下了」(BUG-723**复发自 BUG-715**);attempt 210s × 2 = 420s > 路由 `maxDuration` 240s,重试必超预算(BUG-724**复发自 BUG-059**BUG-388 的防复发只写了单次尝试)。超时改成整轮一个预算,不砍 attempt 也不提 240。**产品 2026-09-15 决定:意图分类继续用会话选定的贵模型,不新增「工具模型」角色** | 待领取 | — |
| `TASK-rectification-settled-render-split-20260915.md` | — | **前端性能单(独占校正会话组件,可并行)**:`rectification-agentic-chat.tsx` 1973 行、`useMemo` 0 个、`memo` 0 个,`messages.map` 内联在组件体里且逐条新建时间轴数组与 choice card`ChatMessageRow` 无 memo、结算态 Markdown 走没有缓存的 `renderProse`。流式每帧(~60/s)重渲整条会话并重跑每条已结算消息的 Markdown。BUG-473 在本文件只落地了 `stream-frame-buffer`,咨询面的 `SettledMessageList` + `HistoryMessageEntry` 拆分没有跟过来。**零行为变化**;验收必须有按帧驱动的渲染计数断言(照 `home-streaming-render-split.test.ts`)。BUG 段 725 | 待领取 | — |
| `TASK-rectification-request-dossier-cache-20260915.md` | — | **低风险单,串行在 failure-attribution 之后(同改 `route.ts`)**:一轮 Agent 对话实测取 3.44 次整份 Case 档案(点选题 2.07 次),全仓约 40 个调用点、请求内零缓存;档案是「最近 50 轮 turns + 全部 evidence + 合成收据」的大 jsonb。做法是包装 `accounting` 客户端做**写即失效**的请求作用域缓存(两个只读投影命中缓存,其余任何 RPC 先清空再转发),**零调用点改动**。不得做成「请求内只读一次」——档案在请求内会变。BUG 段 726 | 待领取 | — |
| `TASK-consultation-external-evidence-cache-20260915.md` | | **普通聊天性能单(Python2026-09-15 产品拍板改为**排在 api-server-decomposition **之前**)**:每轮每域同步等外网,cProfile 前三名全是 `api.vedastro.org` 的 HTTPS 往返(0.801 + 0.786 + 0.206 s),本地 swisseph 只有 0.022 s。三个护栏数字凑不齐:前台等 1.5 s、后台跑 8 s、线程池只有 2 个 worker,且超时**不 cancel** → 每 4 秒一轮就长期饱和,之后每轮白等再拿 `official_blocked`BUG-727)。另 `western_evidence_packet` 122 KB 前端零读取点(BUG-728)。**产品定案**:按「出生数据+岁差+交点+UTC 日期」缓存(与引擎 `_official_snapshot_reference_date` 同键,否决自定 TTL),同日 0 等待 / 跨日先用旧的(≤7 天)后台刷新 / `daily_starlanguage` 要求当天 / 冷启动才走 1.5 s。**不许「干脆不调」——那会重开 BUG-301。** 另含 staging 单域耗时实测单(代码注释里的 21 s 与本机 0.5 s 差 40 倍,三域上限就是从它推的)。BUG 段 727–728 | 待领取 | |
| `TASK-consultation-external-evidence-cache-20260915.md` | `PROGRESS-consultation-external-evidence-cache-20260915.md` | **普通聊天性能单(Python2026-09-15 产品拍板改为排在 api-server-decomposition 之前)**:每轮每域同步等外网,cProfile 前三名全是 `api.vedastro.org` 的 HTTPS 往返(0.801 + 0.786 + 0.206 s),本地 swisseph 只有 0.022 s。三个护栏数字凑不齐:前台等 1.5 s、后台跑 8 s、线程池只有 2 个 worker,且超时**不 cancel** → 每 4 秒一轮就长期饱和,之后每轮白等再拿 `official_blocked`BUG-727)。另 `western_evidence_packet` 122 KB 前端零读取点(BUG-728)。**产品定案**:按「出生数据+岁差+交点+UTC 日期」缓存(与引擎 `_official_snapshot_reference_date` 同键,否决自定 TTL),同日 0 等待 / 跨日先用旧的(≤7 天)后台刷新 / `daily_starlanguage` 要求当天 / 冷启动才走 1.5 s。**不许「干脆不调」——那会重开 BUG-301。** 另含 staging 单域耗时实测单(代码注释里的 21 s 与本机 0.5 s 差 40 倍,三域上限就是从它推的)。BUG 段 727–728 | 待验收 | `codex/consultation-external-evidence-cache-20260915` |
| `TASK-consultation-context-memory-20260915.md` | — | **记忆三缺口(TS,可并行)**:历史超预算时从最老整轮丢弃,`droppedCount` 算了却**全仓零读取点**,模型不知道少看了几轮——单条截断有「省略 N 字」标记,整轮丢弃没有(BUG-729,BUG-555 防复发只写了「头部截断」所以漏网);写摘要阈值写死 16,000,历史预算却是 `clamp((窗口−60k)×1.5, 4k, 40k)`,窗口 < **70,667** 时预算低于阈值 → 每轮静默丢(BUG-730,后台上架中等窗口模型即触发);写满时服务端存着摘要,`continueInNewChat` 只带问题不带摘要,而 `context_summary` 根本不在任何会话接口的列里(BUG-731)。**产品定案:静默继承**,且摘要文本永远不许由客户端提供(`chatSessionCreateSchema` 只收来源会话 uuid)。BUG 段 729731 | 待领取 | — |
| `TASK-consultation-session-capacity-20260915.md` | — | **对话上限单(一份迁移,可并行;不碰 route.ts)**`append_consultation_question` 的 200,000 字符额度里,`thinkingText`(≤4,000) + `thinkingSections`(实测 1,521/2,243/2,977) 占一半以上,而 `techniqueTruth`/`workflowReceipt`/`agentExecutionReceipt` 照样入库却不计入——同一条上限身兼二职且两职都没做好,约 **19 轮** 就「已写满」(200 条那档永远碰不到)。**产品定案:思考文本不计入**,额度只数用户读得到的正文(约 19 → 约 50 轮),另设一条按 `length(elem::text)` 把全部字段算全的物理上限(算式取 1,000,000,写进迁移注释)护住数据库行;两档都返回同一个 `session_full`。保留 advisory lock / 幂等 / 满员拒绝(BUG-464 防复发)。BUG 段 732 | 待领取 | — |
| `TASK-freeze-metric-change-20260915.md` | — | **规则单(后面两单的前置,无 BUG 号)**:两条增长冻结余量都用完(`page.tsx` 1,951/1,951 余 **0**`jyotish_api_server.py` 11,334/11,363 余 **29**),冻结从「逼新代码往外走」退化成「拦路」。实证:`page.tsx` 行数砍 59% 但 `Home()` 的 `useState` 从 56 涨到 **66**(拆的是代码不是状态);api server **225 个类方法只有 12 处真碰 HTTP 上下文**,4 处 `__new__` 伪造空壳就是这么来的。**产品拍板换口径**:主门改成「`Home()` 的 useState/useRef 不得增长」与「类方法数 + `__new__` 计数不得增长」,行数降级为粗护栏;**同时推翻 §6「参数式 hook 内部保持 0 个 React hook」**(那正是状态搬不走的原因)。改 `AGENTS.md` §6 + 两个合同测试,不碰业务代码 | 待领取 | — |
@@ -0,0 +1,41 @@
# 真人核对 · staging 单域咨询耗时(外网证据缓存,2026-09-15)
执行环境无 staging 登录态。下列条目留给有真实账号的人在 staging 走查。本单自动化不替代这些。
目的:同一张盘、同一个问题域连发两轮,记录 `consultationToolDurationMs`。第一轮冷(可能等外网),第二轮应命中「盘 + UTC 日期」缓存,接近本地计算。
代码注释里的域上限 3 是按「staging 实测三域共 62.9 秒」反推的,即一域约 21 秒。本机同样调用只要约 0.5 秒。**本单不改三域上限**;若单域已显著低于 21 秒,把数字写进进度记录,放宽另开单。
## 准备
1. 打开 `https://staging.jyotisha.chat/login` 并登录(用自己的受控账号)。
2. 确认 `https://staging.jyotisha.chat/api/health``deployment.gitCommit` 等于含本单代码的那次 staging 提交。
3. 选一张**当天还没问过**的盘(或等 UTC 零点后再测),避免误用旧缓存。不要把出生资料贴回对话。
## 冷启动(第一轮)
4. 新建对话,只问一个域(例如事业)。不要点「深入看今日」。
5. 等回答出来。记下:
- 主观等待 `____`
- 若浏览器/服务端日志能看到 `consultationToolDurationMs``____` ms
- 技法表「VedAstro 云状态」是 executed 还是 blocked`____`
## 同日第二轮(应 0 等待)
6. 同一条对话里,用同一张盘再问同一域(换一句问法即可)。
7. 记下:
- 主观等待 `____`
- `consultationToolDurationMs``____` ms
- VedAstro 云状态:`____`
8. 第二轮应明显短于第一轮,且不应再出现「空等约 1.5 秒再 blocked」。
## 跨日(可选,UTC 过零点后)
9. 第二天用同一张盘再问同一域。第一句应马上有回答(用昨天的证据),证据里能看出是哪一天的快照;下一轮才换成当天。
10. 点「深入看今日」时,不得沿用昨天的快照;没有当天的就走原来的 1.5 秒有界等待。
## 回填后怎么处理
把 5、7 的两组数字交给验收方,写进 `docs/tasks/PROGRESS-consultation-external-evidence-cache-20260915.md`。若单域已稳定低于 21 秒,结论写成「三域上限可以放宽到 N」的依据,**不要在本单改上限**。
**在这份清单回填之前,任何人不得声称「staging 单域耗时已验证」。**