8db71aaf81
- README.md is now the product/repo front door (architecture, repo map, local dev, test tiers, delivery flow, doc map). Engine positioning, VedAstro/Codex setup and the oracle/benchmark command reference move verbatim to docs/engine/README.md, docs/engine/vedastro-gateway.md and docs/benchmark/README.md. Capability badges realigned with the registry (91/78/8/0); tests/test_readme_badges.py was red on staging. - AGENTS.md: Part A (environment truth, delivery, worktrees, record placement, bug workflow, growth freeze, frontend red lines, privacy, pre-work check, test tiers) and Part B (reading-rigor constraints). GitHub issue-tracker/triage boilerplate removed: GitHub is a read-only mirror. All strings locked by tests/ are preserved. - CLAUDE.md added: roles, three working modes, task-brief sections, acceptance criteria, session discipline; imports AGENTS.md. - 50 tracked TASK-*/PROGRESS-* files and 3 never-committed briefs move to docs/tasks/ with an index; REPO_LAYOUT.md merged into README. Docs-only change (no gated path touched). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
213 lines
13 KiB
Markdown
213 lines
13 KiB
Markdown
# PROGRESS · 个人报告写作阶段 report_schema_invalid(2026-09-02)
|
||
|
||
任务书:`TASK-report-section-writer-failure-20260902.md`(`838d9e76`)
|
||
分支:`codex/report-writer-failure-20260902`
|
||
基线:`origin/staging` @ `838d9e76`
|
||
|
||
开工前置:已读 `docs/research/pre_work_error_ledger.md`、`frontend/AGENTS.md`;`docs/BUG_HISTORY.md` 检索 BUG-352 / 451 / 486–489 / 495。按任务书从 `origin/staging` 建独立 worktree,不碰原仓脏树。
|
||
|
||
隐私:下文只有错误码、token 计数、`finishReason`、时间戳与 request UUID(任务书已公开)。无 prompt、bundle、模型原文、姓名、出生资料或账号标识。
|
||
|
||
---
|
||
|
||
## 任务 0 · Staging 实证(门控)
|
||
|
||
取证主机:staging `118.26.111.127`,路径 `/opt/jyotisha-staging`。Postgres 容器 `jyotisha-staging-postgres-1`。
|
||
|
||
### 日志窗口丢失
|
||
|
||
取证时公开 staging health 仍指向 `35688015`(`838d9e76` 的父提交)。Web 容器在 `2026-09-02T14:00:21Z`(22:00 +08)被 recreate,与任务书提交 `838d9e76`(`14:00:43Z`)几乎同时。事故窗口(13:19–13:21 UTC)的 `docker logs` 已不在。`[personal-report-agent]` 与 `[personal-report] generation_failed` 无法回收。`usage_ledger` 对该 requestId 为 0 行。
|
||
|
||
因此 **不能** 用本事故的 `finishReason` / `outputTokens` 分布来确认「主嫌 · 截断」。截断判定只能写 `blocked`。
|
||
|
||
### 事故行(requestId `77b79067-bd70-465e-ac23-adc4500d4738`)
|
||
|
||
| 面 | 结果 |
|
||
|---|---|
|
||
| `personal_reports` | `failed` / `report_schema_invalid` / created 13:19:23 UTC / updated 13:21:38 UTC |
|
||
| `personal_report_jobs` | `failed` / `last_error_code=report_schema_invalid` / phase `failed` / **progress 43%** / attempt **1 of 3** / finished 13:21:38 UTC |
|
||
| `theme-career` | **blocked**,attempts **2/2**,`last_error_code=section_evidence_insufficient`,updated 13:21:38.398 UTC |
|
||
| `theme-marriage` | pending,0 attempts |
|
||
| `theme-timing` | pending,0 attempts |
|
||
| `theme-wealth` | pending,0 attempts |
|
||
|
||
43% = `30 + floor((1/4)*55)`:career 被 block 后第一次 `onProgress`。Job 在 career block 后约 39ms 结束,其余三章从未 `start`。
|
||
|
||
历史 `report_schema_invalid`(08-30 起,与任务书 5 个小时一致):跨这些小时的 section 行里,本事故是唯一一条 blocked + `section_evidence_insufficient`。其余为 pending 或 ready。
|
||
|
||
### 定案
|
||
|
||
1. **主嫌(CJK `/2` 截断)未证实。** 落库码不是 `section_output_truncated`。截断在 writer 路径通常会变成 `PersonalReportAgentOutputError("report_schema_invalid")` → `section_output_invalid`。本事故没有 telemetry,不得写成 `finishReason=length`。
|
||
2. **次嫌(evidenceRefs 回显)是 career 章的最佳解释。** 旧 `classifySectionErrorCode` 把任何含 `"evidence"` 的消息打成 `section_evidence_insufficient`。`report_writer_evidence_refs_mismatch` 含子串 `evidence`,会被误分类。career 2/2 次、约 29s,符合 assert 失败 + 一次 repair,而不是四章长输出截断。
|
||
3. **第三层:首章 blocked 后没有继续写其余章。** 产品测试已要求其他章仍应交付。Job 以不可重试的 `report_schema_invalid` 失败(attempt 1/3),说明 worker 收到了 `{status:"failed"}` 或 `invalid_request`/`storage_invalid`,而不是 retryable 抛错。无日志无法闭合「是 filter / onProgress / 其它未捕获抛错」。本轮仍把每章失败隔离开,避免一章 throw 中止整份。
|
||
|
||
`all_sections_blocked` 原先裸 `return`、不走 `failSchema`,因此即使日志还在,也可能看不到 `generation_failed`。本轮改为走 `failSchema`。
|
||
|
||
任务 1 仍按任务书授权落地中文口径公式,校准用的是 **08-30 任务 0** 的整份调用观测(`outputTokens=3069`,`finishReason=stop`),**不是** 本事故。research / deep 的 3072 cap 保持不变;若以后实测需要更高,另开任务。
|
||
|
||
---
|
||
|
||
## 任务 1 · CJK 输出预算
|
||
|
||
禁止 `targetCharacters.max / 2`。
|
||
|
||
口径:`1` 中文字符 ≈ `1.3` token(`CJK_OUTPUT_TOKENS_PER_CHAR`)。
|
||
|
||
章节结构余量 `568`(narrative 之外的 title / actions / caveats / refs / JSON 外壳):
|
||
|
||
| 部分 | 估算 |
|
||
|---|---|
|
||
| title ≈ 80 字 | `ceil(80 × 1.3) = 104` |
|
||
| 3 条 actions ≈ 120 字 | `156` |
|
||
| caveats ≈ 80 字 | `104` |
|
||
| evidenceRefs JSON | `80` |
|
||
| JSON 键与括号 | `124` |
|
||
| 合计 | `568` |
|
||
|
||
摘要结构余量 `580` **已经包含** headline / priorities / JSON,不再把 headline 算第二次:
|
||
|
||
`ceil(2000 × 1.3) + 580 = 2600 + 580 = 3180`,再与章节同一 cap **3072** 取 min(08-30 整份 `outputTokens=3069` / `stop`)。落地 **3072**。
|
||
|
||
章节:`min(3072, max(1024, ceil(maxChars × 1.3) + 568))`
|
||
|
||
| 深度 | max 字 | 未封顶 | 落地预算 |
|
||
|---|---|---|---|
|
||
| concise | 500 | 1218 | **1218** |
|
||
| standard | 1200 | 2128 | **2128** |
|
||
| deep | 2000 | 3168 | **3072**(cap,缺口 96 token) |
|
||
| research | 2800 | 4208 | **3072**(cap;约合 narrative 2504 字,低于 2800 上限) |
|
||
| summary | 2000 字段 | 3180 | **3072**(与章节同一 cap) |
|
||
|
||
08-30 整份 writer 一次调用 `outputTokens=3069` 且 `stop`,说明 3072 能装下一份大 JSON。本事故未证实截断,因此不把 cap 自行抬高。length 修复重试:同一 `outputBudget`(不得低于首次),提示词要求压到 `targetCharacters.min` 并闭合 JSON。
|
||
|
||
---
|
||
|
||
## 任务 2 · evidenceRefs 回显
|
||
|
||
- `sectionPrompt` 增加:`evidenceRefs 必须逐字复制 plan.evidenceRefs 数组,不得增删`。
|
||
- `assertWriterOutput` 仍是 id/theme 全等 + refs **集合相等**,语义未放宽。
|
||
- Repair 只拼类别词:`identity mismatch` / `refs mismatch` / `schema invalid` / `output truncated`。refs 类再次重申逐字复制。不把 ref id、正文或 bundle 拼进 repair。
|
||
- `classifySectionErrorCode` 先匹配 identity / refs,再匹配泛化 `"evidence"`,避免再把 refs mismatch 写成 `section_evidence_insufficient`。
|
||
|
||
---
|
||
|
||
## 任务 3 · 失败可观测
|
||
|
||
不改表。应用层聚合已有 `personal_report_sections` 行。
|
||
|
||
- 详情 API:`failureSummary`、`innerReason`(`all_sections_blocked` 或 `section_generation_incomplete`)、`sectionErrorCodes`。
|
||
- 列表 API:失败行多查 `request_id, status, last_error_code`(无正文),写入 `failureSummary`。
|
||
- 报告中心:`failureSummary ?? failureCode`。详情页摘要为主文案,等宽错误码为辅。
|
||
|
||
本事故若用新摘要,应接近:「4 个主题中 1 个写作失败:证据引用未对齐,3 个未开始」。四章都写成但整份失败时,应接近:「4 个主题已写成,整份报告未完成装配」,不得再显示成空的「结构校验」。旧码仍会显示「证据不足」,因为历史行的 `last_error_code` 不会回写。
|
||
|
||
---
|
||
|
||
## 任务 4 · 真实验证
|
||
|
||
`GET https://staging.jyotisha.chat/api/health` → `.deployment.gitCommit` = `eda37c1523341898850e6d71d63c8dd40563973b`。Web 容器 `2026-09-02T17:31:48Z` recreate。未提升 main。
|
||
|
||
Web `docker logs` 在 Ready 之后几乎不再收录 `console.info`。本轮拿不到 `[personal-report-agent]` 的 `finishReason` / 每章 token。`usage_ledger` 对未 ready 的请求无 complete 行。2 倍线的真实 `inputTokens` 仍缺测;墙钟如下,不自行裁决。
|
||
|
||
### Run A · requestId `54be49f1-a657-43fa-88ce-7d7e8d575831`
|
||
|
||
standard `personal_full`,主题 career / marriage / wealth / timing。started 17:44:07 UTC,终态 17:54:30 UTC(约 10.4 分钟)。
|
||
|
||
| 章 | status | attempts | narrative 字数 | last_error_code |
|
||
|---|---|---:|---:|---|
|
||
| theme-career | ready | 1 | 1557 | (空) |
|
||
| theme-marriage | ready | 2 | 1395 | (空) |
|
||
| theme-timing | ready | 2 | 842 | (空) |
|
||
| theme-wealth | ready | 1 | 470 | (空) |
|
||
|
||
事故那一章(career)这次 **1 次成功**,没有 `section_output_truncated` / `section_refs_mismatch`。四章都有正文。
|
||
|
||
Job 在写作过程中 `lease_expired` 两次(默认租约 120s,单章可超过 2 分钟;heartbeat 未能保住租约),第三次 attempt 写完 wealth 后约 39ms 整份 `report_schema_invalid`。进度停在 71%(timing 完成后的值),wealth 的 85% `onProgress` 未落库。四个 section 行仍是 ready。摘要/装配没有产出 `report_document`。reservation `released`。
|
||
|
||
默认 `leaseSeconds=120` 不够串行四章写作。心跳 `setInterval` 被 `unref` 后,Next.js instrumentation 在没有 HTTP 请求时会跳过定时器,单章 4 分钟写作期间租约无法续上。补丁:**600s 租约、20s 心跳、心跳定时器保持 ref、章节 `onProgress` 先 `heartbeatLease` 再写进度**。摘要预算与章节同一 cap **3072**(原公式 3180)。
|
||
|
||
### 回溯抽查(只记是否对得上 ref,不摘正文)
|
||
|
||
| # | 章 | 叙事层(布尔) | 对回的 evidenceRef |
|
||
|---|---|---|---|
|
||
| 1 | wealth | 含 yoga 词 | `ev-tech-yoga` |
|
||
| 2 | 四章皆含 | 含 大运/主运/副运 | `ev-tech-vimshottari` |
|
||
| 3 | marriage | 含 yoga 词 + 大运词 | `ev-tech-d9` + `ev-tech-dk` |
|
||
| 4 | career | 含 yoga 词 + 大运词 | `ev-tech-amk` + `ev-tech-vimshottari` |
|
||
| 5 | wealth | 含 yoga 词 + 大运词 | `ev-tech-d2` + `ev-tech-d11` |
|
||
|
||
未在四章叙事中扫到强弱/shadbala 用词,不记为回溯命中。
|
||
|
||
### Run B · requestId `8e75299b-58ac-43c2-8bf4-59f9bd394734`
|
||
|
||
为区分租约与摘要失败另开一次。四次 `POST /api/consultation_workflow` 均为 200,随后 worker 在 `buildReportEvidenceBundleV2` 处非重试失败,`calculation_unavailable`,无 section 行。未再消耗第三次生成。
|
||
|
||
### 相对 2 倍线
|
||
|
||
无每章 `inputTokens`/`outputTokens`。Run A 墙钟约 10.4 分钟(含两次租约回收与 marriage/timing 的 section repair)。数据摆出,不裁决。
|
||
|
||
### Run C · requestId `89c04491-81ca-457e-b09d-dc81c3af36b3`
|
||
|
||
`GET /api/health` `.deployment.gitCommit` = `5c0bec0c9419e12e2c1fcab8e75fd35d535cb50e`。Web 容器 `2026-09-02T18:24:50Z`。未提升 main。
|
||
|
||
standard `personal_full`,主题 career / marriage / wealth / timing。started 01:31:16 UTC,终态 01:45:53 UTC(约 14.6 分钟)。reservation `released`。
|
||
|
||
| 章 | status | attempts | narrative 字数 | last_error_code |
|
||
|---|---|---:|---:|---|
|
||
| theme-career | ready | 1 | 170 | (空) |
|
||
| theme-marriage | ready | 2 | 1338 | (空) |
|
||
| theme-timing | ready | 1 | 926 | (空) |
|
||
| theme-wealth | ready | 1 | 745 | (空) |
|
||
|
||
Telemetry(`[personal-report-agent]`,本轮 docker logs 可读):
|
||
|
||
| 调用 | outcome | elapsedMs | inputTokens | outputTokens | finishReason | repair |
|
||
|---|---|---:|---:|---:|---|---|
|
||
| career | resolved | 5721 | 5215 | 560 | stop | false |
|
||
| marriage | failed | 9430 | null | null | tripwire | false |
|
||
| marriage | failed | 9447 | null | null | tripwire | true |
|
||
| marriage | resolved | 13618 | 5076 | 1240 | stop | false |
|
||
| timing | resolved | 8052 | 4474 | 816 | stop | false |
|
||
| wealth | resolved | 7446 | 5228 | 759 | stop | false |
|
||
| summary | (无日志) | | | | | |
|
||
|
||
无 `finishReason=length`。四章 `stop` 合计 input 19993 / output 3375。无 summary telemetry。
|
||
|
||
Job attempt 1 在 marriage 写作中心跳停更(heartbeat 与 lease 同步老化,最长观测 584s),`lease_expired` 后 attempt 2。四章随后都写成 ready。wealth 落库 `01:45:53.549`,整份失败 `01:45:53.583`(约 34ms)。进度停在 **57%**(marriage 完成后的 2/4),timing/wealth 的 onProgress 未落库。无 `[personal-report] generation_failed` 行 → 不是 `failSchema` 的摘要/装配返回,而是 `onProgress`/续租抛错被 worker 映射成 `report_schema_invalid`。
|
||
|
||
### Run C 回溯抽查(只记是否对得上 ref)
|
||
|
||
| # | 章 | 叙事层(布尔) | 对回的 evidenceRef |
|
||
|---|---|---|---|
|
||
| 1 | 四章皆含 yoga 词 | true | wealth 另有 `ev-tech-yoga` |
|
||
| 2 | marriage / timing / wealth | 大运/主运/副运 true;career false | 四章皆有 `ev-tech-vimshottari` |
|
||
| 3 | marriage | yoga + 大运 | `ev-tech-d9` + `ev-tech-dk` |
|
||
| 4 | career | yoga;无大运用词 | `ev-tech-amk` + `ev-tech-vimshottari` |
|
||
| 5 | wealth | yoga + 大运 | `ev-tech-d2` + `ev-tech-d11` |
|
||
|
||
未扫到强弱/shadbala 用词。
|
||
|
||
### 相对 2 倍线
|
||
|
||
四章 writer 墙钟合计约 35s 模型时间;整份 14.6 分钟含一次租约回收与 marriage tripwire。inputTokens 已测(上表)。数据摆出,不裁决。
|
||
|
||
### 任务 4 结论
|
||
|
||
Writer 修正在章节层成立。整份 ready 被两件事挡住:Next.js instrumentation 里章节写作期间心跳定时器仍不续租;最后一章 `complete` 之后 `onProgress` 抛错被标成 schema 失败,摘要从未开始。跟进补丁:进度投影失败不得中止已落库章节的摘要/装配。
|
||
|
||
---
|
||
|
||
## 质量门
|
||
|
||
| 门 | 结果 |
|
||
|---|---|
|
||
| `frontend/node_modules/.bin/tsc --noEmit` | 通过(exit 0,无输出) |
|
||
| `eslint`(改动的 TS/TSX 文件) | exit 0,无 error 输出 |
|
||
| 聚焦 `tsx --test`:plan / writer-failure / generation-v2 / api / view / agent-telemetry | 104 pass / 0 fail |
|
||
| 复跑 api + writer-failure | 53 pass / 0 fail |
|
||
| `personal-report-generation.test.ts` + `personal-report-worker.test.ts` | 51 pass / 0 fail |
|
||
|
||
未跑全量 `tests/*.test.ts`,不声称全套绿。未改 `.gitea/workflows/**`,未提升 main。
|
||
|
||
任务 4 补丁(租约 600s、摘要 cap 3072、心跳保持 ref + 进度续租 + onProgress 不中止装配):`tsc --noEmit` 通过;generation-v2 / worker / writer-failure 聚焦测试通过。
|