- 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
9.6 KiB
任务书 · 个人报告写作阶段 report_schema_invalid 修复(2026-09-02)
基线:origin/staging 最新(开工时 git fetch 后以 origin/staging HEAD 为准;本任务书出稿时为 d9404976 之后)。
事故实证
blocked-repairs(7faf8555)部署后,2026-09-02 21:19(UTC 13:19,requestId 77b79067-bd70-465e-ac23-adc4500d4738)真实生成 personal_full(career/marriage/wealth/timing)failed,failureCode = report_schema_invalid。历史同码失败另有 4 次(08-30 三次、09-02 05:10 一次)。
证据链已收窄到模型写作路径:委托方已在本地对最新 staging 代码做过确定性全管线复现——真实引擎四主题响应 → buildReportEvidenceBundleV2(4 claim cards / 0 blocked)→ generatePersonalReport(注入 schema 合规的确定性假 writer + 内存 sectionService)→ PIPELINE-READY,4 章节装配、guard、最终解析全部通过。即 bundle 校验、plan 校验、按章过滤(filterReportEvidenceBundleForSection 的 finalize 校验)、装配与文档合同都没有确定性缺陷;失败只可能来自真实模型输出未通过校验的路径:每章两次尝试均失败 → 章节 blocked → 四章全 blocked → all_sections_blocked,或摘要生成失败。
两个候选根因(按嫌疑排序,任务 0 定案)
主嫌 · 输出预算的中文 token 口径反了。
frontend/src/lib/personal-report-generation.ts:
const SECTION_OUTPUT_TOKEN_BUDGET = 3072;
const SUMMARY_OUTPUT_TOKEN_BUDGET = 1536;
// 每章:
maxOutputTokens: Math.min(3072, Math.max(1024, Math.ceil(entry.targetCharacters.max / 2)))
standard 深度 targetCharacters.max = 1200(中文字符)→ 每章预算 max(1024, 600) = 1024 tokens。/2 隐含"2 字符 ≈ 1 token",这是英文口径;中文约 1 字 ≈ 1 token 或更多。writer 被系统提示与 plan 要求写 260–1200 字中文 narrative + title + actions + caveats + evidenceRefs 的完整 JSON,写入目标区间上半段必然超 1024 → finishReason: "length" → JSON 截断 → schema 校验失败 → 修复重试预算不变再截断 → 耗尽 2 次 → 章节 blocked。四章同构 → all_sections_blocked。摘要同理:schema 允许 summary 2000 字符中文,1536 预算同样可截断。
(这正是 TASK-report-sectioned-generation-20260830.md 红线"输出上限取值要用数据决定,不许拍脑袋"要防的事故。)
次嫌 · evidenceRefs 精确回显失败。
assertWriterOutput 要求 candidate.evidenceRefs 与 entry.evidenceRefs 集合相等。按章过滤后的 bundle evidenceRefs = entry.evidenceRefs ∪ 全局解读层 refs(yoga/functional 回执随章下发),模型若照抄 bundle 的 refs 而不是 plan entry 的 refs → mismatch → 失败。修复重试的提示词只说"未通过校验",不说差在哪。
决策记录
- 产品方向不变:writer 输出 schema 不放宽;
assertWriterOutput的 id/theme/evidenceRefs 精确匹配语义不改(服务器主权);章节串行不变。 - 授权:按任务 0 实测数据调整输出 token 预算公式与 repair 提示词;在报告失败记录/详情中透出失败细节(仅错误码级别,不含内容)。
硬红线
- 任务 0 是门控。 必须先从 staging 拿到 21:19 这次失败的实证(telemetry
finishReason分布 +personal_report_sections.last_error_code),确认主嫌/次嫌,再改数值。预算新值必须由数据 + 中文 token 口径推导,在 PROGRESS 写出推导过程,不得再拍脑袋。 - writer 输出 schema 只可收紧;
assertWriterOutput语义不改(允许把失败原因喂给 repair 提示词,不允许放松校验)。 - 隐私红线延续:日志/PROGRESS 只引用错误码、token 数、finishReason;不得摘录 prompt、bundle、模型输出、用户资料。
- 不改数据库结构(
personal_report_sections已有last_error_code,透出走应用层)。 - 上两轮任务书的其余红线继续有效(隔离、assertionLevel、最低证据组、串行、
./node_modules/.bin/tsc而非npx tsc、不改.gitea/workflows/**、不提升 main)。
开工前置
git fetch origin --prune
git worktree add -b codex/report-writer-failure-20260902 \
../.worktrees/report-writer-failure-20260902 origin/staging
读 pre_work_error_ledger.md、frontend/AGENTS.md;docs/BUG_HISTORY.md 检索(本轮与 8-30 sectioned 轮的任务 0 观测、BUG-486 系列相邻)。
先读:personal-report-generation.ts 的 generateSectionedPersonalReport(预算、重试、blocked 判定)、src/mastra/personal-report.ts 的 runStructured / REPAIR_PROMPT_SUFFIX / telemetry、personal-report-worker-core.ts 的失败映射、PROGRESS-report-sectioned-20260830.md 的任务 0 数据。
任务 0(P0,门控)· 用 staging 实证定案
做法
执行环境有 staging 部署访问权,取证 requestId 77b79067-bd70-465e-ac23-adc4500d4738:
- 服务器日志中该时段的
[personal-report-agent]telemetry 行:每次尝试的outcome/finishReason/outputTokens,与[personal-report] generation_failed的innerReason。 personal_report_sections表该 requestId 的行:section_id/status/attempt_count/last_error_code。- 汇总成表写进 PROGRESS(只有错误码与数值)。
判定
finishReason: "length"占多数 /last_error_code = section_output_truncated→ 主嫌成立,任务 1 为主修。section_output_invalid且 telemetryfinishReason: "stop"→ 次嫌成立,任务 2 为主修。- 两者混合 → 两任务都做(大概率如此,都在本轮范围内)。
- 若实证指向第三种原因(如 summary 失败、worker 层指纹错配),停下,把数据写进 PROGRESS/BLOCKED 再定方案,不得按本任务书猜测硬改。
任务 1(P0)· 输出预算按中文口径重算
做法
- 每章预算改为以中文口径推导:narrative 上限
targetCharacters.max字 ≈max × 1.3tokens,再加 title/actions/caveats/refs 与 JSON 结构余量(按任务 0 实测 outputTokens 校准),向上取整后 cap 在SECTION_OUTPUT_TOKEN_BUDGET(3072;若实测 research 深度需要更高,单独说明再调)。禁止再出现"字符 ÷ 2"式换算。 - 摘要预算同口径重算(schema 允许 headline 200 + summary 2000 + priorities,1536 明显不足)。
- 修复重试遇
finishReason: "length"时,repair 提示词明确要求压缩篇幅至targetCharacters.min附近,且重试预算不得低于首次。 - telemetry 已记
finishReason与 tokens,保持。
验收
- 单元测试锁新预算公式(各深度的每章/摘要预算值表)。
- staging 部署后真实生成一份 standard personal_full:四章有正文;telemetry 无
length;PROGRESS 给每章实测outputTokens与预算余量。
任务 2(P1)· evidenceRefs 回显加固
做法
sectionPrompt明确一句:evidenceRefs 必须逐字复制 plan.evidenceRefs 数组,不得增删(模型已能看到 plan entry)。- 校验失败时把失败类别(identity mismatch / refs mismatch / schema invalid)拼进 repair 提示词(只有类别词,不带内容),让第二次尝试有的放矢。
assertWriterOutput语义不变。
验收
- 单元测试:refs mismatch 的 repair 提示词包含类别提示;断言校验语义未变(原有 mismatch 用例全保留)。
任务 3(P1)· 失败可观测透出
做法
- 报告失败时把
innerReason(如all_sections_blocked)与各章last_error_code汇总透出到报告详情 API(错误码级别;personal_report_sections已有数据,应用层聚合即可,不改表)。 - 报告中心失败卡片显示可读的失败摘要(如"4 个主题中 4 个写作失败:输出被截断"),替代裸码
report_schema_invalid。
验收
- 单元测试 + 一次人为构造的失败在页面上可读。
任务 4(P1)· 真实验证与上两轮欠账
staging 部署后真实生成 standard personal_full(四主题):
- 报告 ready、四章有正文、摘要非降级文案。
- writer 输出回溯抽查 ≥3 处(叙事中的 yoga/大运/强弱表述能对回 bundle 条目)。
- 每章实测
inputTokens/outputTokens/ 墙钟,对照PROGRESS-report-skill-parity-20260901.md的 2 倍线裁决(产品已接受估算 2.28×,以本次实测为准;实测超线把数据摆进 PROGRESS,不自行裁决)。
不在本轮范围
- writer schema、assert 语义、最低证据组、bundle 内容的任何改动。
- 章节并发、模型更换、提示缓存策略调整。
- 数据库结构。
收尾
PROGRESS-report-writer-failure-20260902.md;docs/BUG_HISTORY.md条目(编号对远端确认;与 8-30 任务 0 观测、final_parse_rejected历史关联)。- 建议推送顺序:任务 0 取证先写 PROGRESS(可不推代码);任务 1–3 一次推送;任务 4 部署后补记。
- 推送后核对
https://staging.jyotisha.chat/api/health的.deployment.gitCommit;流水线约 20 分钟。 - 不自行提升 main。
交付物清单
- 任务 0 实证表(telemetry finishReason/outputTokens + 各章 last_error_code)与定案结论
- 新预算公式 + 推导过程 + 各深度预算表 + 单元测试
- length 重试的压缩提示 + repair 类别提示 + 测试
- 失败详情透出(API + 报告中心文案)+ 测试
- 真实报告验收:四章正文、telemetry 无 length、回溯抽查 ≥3 处、实测 tokens/墙钟与 2 倍线对照
- 全套质量门实际输出、BUG_HISTORY、BLOCKED(如有止损)、PROGRESS