Files
Jyotisha/docs/tasks/PROGRESS-report-writer-failure-20260902.md
T
Jesse_Chen 8db71aaf81 docs: product-level README, AGENTS.md split into code/reading parts, add CLAUDE.md, move task briefs to docs/tasks
- 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
2026-09-03 06:56:06 +00:00

213 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PROGRESS · 个人报告写作阶段 report_schema_invalid2026-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 / 486489 / 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` | pending0 attempts |
| `theme-timing` | pending0 attempts |
| `theme-wealth` | pending0 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** 取 min08-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 | 大运/主运/副运 truecareer 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 聚焦测试通过。