docs(report): record the skill-parity round, BUG-467 and what stayed blocked

PROGRESS carries the engine contract snapshot (including the fields the
task brief assumed exist but the consultation API does not return), the
claim card before/after text, the per-section prompt growth table, the
knowledge pack inventory, and the actual quality-gate output.

BLOCKED records four things: no model credentials and no PostgreSQL here,
so the live writer comparison could not be produced; the per-section
prompt p50 landing at 2.83x raw (2.28x cost-weighted), which is over the
brief's 2x line and needs a decision rather than an evidence cut; and the
fact that /api/consultation_workflow serves the reduced prompt pack, so
the server narrative seeds the brief pointed at do not exist yet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016P5RoqzmUQEbeC2qjAkeGr
This commit is contained in:
Jesse_Chen
2026-09-01 18:58:20 +00:00
parent ef1bd6dfa9
commit addb94af9b
3 changed files with 368 additions and 0 deletions
+7
View File
@@ -1,5 +1,12 @@
# BLOCKED
## 报告解读深度对齐(2026-09-01,分支 `codex/report-skill-parity-20260901`
- **端到端真实生成未做:执行环境没有任何模型凭据。** `ANTHROPIC_*` / `OPENAI_*` / `DEEPSEEK_*` 等全部未设置,仓内只有 `.env.official.example` / `.env.cn.example` / `deploy/.env.staging.identity.example`,不含可用 key。按红线不得读取或猜测凭据。因此任务书交付物 5 的「真实 writer 输出抽查」与交付物 7 的「改前改后同盘同主题章节全文对照 + 真实 inputTokens / 墙钟对照」本地无法产出。离线替代物已给出:`PROGRESS-report-skill-parity-20260901.md` 里的每章 prompt 载荷改前改后逐章对照、claim card 全文对照、以及对服务器确定性 conclusion 的 5 处逐行回溯。真实生成待部署后由委托方补。
- **分章生成路径还需要 PostgreSQL。** `generateSectionedPersonalReport` 依赖 `PersonalReportSectionService``personal_report_sections` 表)。本机没有 Docker / postgres / initdb / psql,与 BLOCKED.md 2026-08-30 条目同一缺口。本轮未改任何库结构、未新增迁移,`npm run test:db` 因此也未跑。
- **每章 prompt 原始 token p50 为改前的 2.83×(计费加权约 2.28×),超过任务书红线 6 的 2 倍线,等待裁决。** 不属于任务 3 的止损分支:`cachedSystemMessage` 已确认生效,通用知识包在缓存断点之内且缓存前缀逐字节稳定(有测试锁)。止损条款明确禁止「砍证据来凑成本」,因此本轮**未削减任何证据**,只把数据与四个可选降本项摆在 PROGRESS 里。请委托方决定是接受这个成本、还是选其中某项降本再推。
- **`/api/consultation_workflow``ai_prompt_pack` 是精简版,不含叙事种子 / dasa convergence / SAV。** 它来自 `scripts/jyotish_api_server.py``_build_chart_prompt_pack`,不是 `scripts/jyotish_engine.py``_build_ai_prompt_pack`。任务书列出的 `evidence_snapshot.timing.vimshottari``.convergence_top_domains``.strength.sav_scores``career_narrative` / `relationship_narrative` / `finance_narrative` 在该响应里**都不存在**(9 个主题一致)。本轮按验收条款标记为不可提取并改用同一响应里真实存在的等价路径,未改 Python 引擎(任务书把引擎输出改动列为不在范围内)。若要拿到真正的服务器叙事种子,需要另立项让 `/api/consultation_workflow` 附带完整 prompt pack——那是「响应新增字段」,属于任务 0 的止损分支,本轮未走。
## 客户端交互收尾(2026-08-30,分支 `codex/interaction-20260830`
- **`.message-actions button` 不能做到 44×44。** 四个 26×26 按钮,`gap: 1px`,中心距 27px;下方 follow-up 芯片只有 8px 底边距。44×44 伪元素会水平重叠 17px,并在有 follow-up 时垂直吃掉 1px 点击。热区做到不重叠的最大值 **27×34**(水平吃掉 1px 缝,垂直用掉 4px 底边距)。距 44×44 还差 17px 宽、10px 高。把 `gap` 拉到 18px 才能水平到 44,但那会改变视觉留白,本轮红线禁止。
+345
View File
@@ -0,0 +1,345 @@
# PROGRESS · 个人报告内容对齐 skill 解读深度(2026-09-01
任务书:`TASK-report-skill-parity-20260901.md`
分支:`codex/report-skill-parity-20260901`(基线 `8b7eda9f`,未 push
提交:`7714b5e5`(任务 0+1+2)、`deabcd8b`(任务 3+4 离线部分)、本文件所在提交(收尾)
开工前置:`docs/research/pre_work_error_ledger.md` 已读;`scripts/pre_work_check.py` 全绿(`python_runtime_ok/fragment_scan_ok/external_engine_adapters_ok/focused_tests_ok` 均为 true`remote_visibility_status=verified`);`frontend/AGENTS.md` 已读;`docs/BUG_HISTORY.md` 已检索 bundle / claim card / evidence 同类记录(最近相关为 BUG-457 报告分章生成)。
**偏差说明(经委托方授权)**:未建 worktree,直接在主检出上从 HEAD 建分支(node_modules / .venv 都在主检出)。任务 4 中依赖 staging 部署的部分跳过,只做不依赖部署的 telemetry 补记。
---
## 任务 0 · 引擎响应契约快照
### 做法
用虚构 smoke 出生数据(1990-05-15 10:30lat 31.23 / lon 121.47 / tz +8city "Smoke City",非真人)通过 `frontend/src/mastra/consultation-workflow.ts``runConsultationWorkflow` 真实调用本地 `scripts/jyotish_api_server.py``--port 5200`)的 `/api/consultation_workflow`career / marriage / wealth / education / migration / family / health / timing / general 各一次,9 次全部 `OK`。只记录 key 路径与类型,不记录值。
### 字段存在性清单(9 个主题响应一致)
| 任务书假定的路径 | 实际 | 类型 / 说明 |
|---|---|---|
| `chart.ai_prompt_pack.evidence_snapshot.functional_benefic_malefic` | **存在** | object,键 `status/ascendant/functional_benefics/functional_malefics/functional_neutrals/yogakarakas/owned_houses/effect_on_confidence/source`;本盘 `status="used"` |
| `chart.ai_prompt_pack.evidence_snapshot.strength.shadbala_ranking` | **存在** | array(7),元素 `{planet: string, rupas: number, level: string}`——**不是**任务书写的 `{planet, rank, total_rupas, min_required, strength_level}` |
| `chart.ai_prompt_pack.evidence_snapshot.strength.sav_scores` | **缺失** | 该路径不存在 |
| `chart.ai_prompt_pack.evidence_snapshot.timing.vimshottari` | **缺失** | `timing` 只有 `{current_mahadasha, remaining_years, start_date}`,没有 `vimshottari` 子对象,也没有 antardasha |
| `chart.ai_prompt_pack.evidence_snapshot.timing.convergence_top_domains` | **缺失** | 该路径不存在 |
| `chart.ai_prompt_pack.evidence_snapshot.career_narrative` | **缺失** | — |
| `chart.ai_prompt_pack.evidence_snapshot.relationship_narrative` | **缺失** | — |
| `chart.ai_prompt_pack.evidence_snapshot.finance_narrative` | **缺失** | — |
| `chart.modules.yoga.yogas` | **缺失** | 键名是 `yogas` 不是 `yoga` |
| `chart.modules.guided_topics` | **存在** | array(4),元素键 `id/title/reality_value/why_worth_exploring/evidence/confidence/vedastro/strict_adjudication_bundle/strict_audit_gate/monthly_adjudication_summary/official_day_signal_summary/suggested_questions/answer_mode/priority`id 为 `birth_time_rectification``relationship_partnership``career_direction``wealth_risk` |
**为什么缺**`/api/consultation_workflow` 返回的 `chart.ai_prompt_pack` 来自 `scripts/jyotish_api_server.py:6975``_build_chart_prompt_pack`**不是**任务书引用的 `scripts/jyotish_engine.py:1592``_build_ai_prompt_pack`。前者是精简版,没有叙事种子、没有 dasa convergence、没有 SAV。这不是本轮能改的(任务书把"引擎侧新增计算模块或改动既有模块输出"列为不在范围内,止损分支只允许新增字段,本轮未走该分支)。
### 同一响应里实际可用、任务书未列出的等价路径(已改用)
| 路径 | 类型 | 用途 |
|---|---|---|
| `chart.modules.ashtakavarga.sav.scores` | object,按**星座名**计分(12 键) | SAV:按 D1 上升整宫投影成 1–12 宫 |
| `chart.modules.ashtakavarga.sav.total` | number(本盘 337 | `savTotal` |
| `chart.modules.dasha_sub_periods.current.mahadasha` / `.antardasha` | `{lord, start, end}` | `currentDasha`(含 antardasha |
| `chart.yogas` | array,元素 `{name, planets, category\|cat, desc?}` | 已成盘 yoga |
| `chart.modules.yogas.yogas` | array,元素 `{name, rule_id, hit: bool, ...}` | **候选规则表**,本盘 `hit=true` 数为 0 |
**重要发现(防复发已写进 BUG-467)**`chart.modules.yogas.yogas` 是规则候选表,绝大多数 `hit=false`,把它当成"本盘的 yoga 名单"就是编造。提取层只收 `chart.yogas``hit === true` 的行。
### 缺失字段的处置
按验收条款"缺失即标记不可提取并跳过,不得伪造":
- `convergenceDomains`:本响应无来源 → 恒为空数组(schema 允许),代码保留 `evidence_snapshot.timing.convergence_top_domains``modules.dasa_convergence.top_convergent_domains` 两条读取路径,引擎哪天补上就自动生效。
- 三个 `*_narrative`:本响应无来源 → 叙事种子改由 `guided_topics` 文案 + 服务器确定性投影组成;`readEngineNarratives` 仍保留 `evidence_snapshot.*_narrative``modules.*_strict_evidence.user_narrative` 两条读取路径,且优先级高于 guided topic。
- `strength.sav_scores`:改读 `modules.ashtakavarga.sav.scores`,两条路径都在代码里。
- `timing.vimshottari`:改读 `modules.dasha_sub_periods.current`,两条路径都在代码里。
### 契约 fixture 测试
`frontend/tests/report-interpretive-facts.test.ts`(合成数据,文件头部把上表逐条写成注释)。10 条用例,锁住上述每条路径的读取行为、缺失路径的空值行为、以及 `hit=false` 不算成盘。
---
## 任务 1 · Bundle 承载解读性事实
### schema`frontend/src/lib/report-evidence-bundle-v2.ts`
`schemaVersion` 仍是 `report_evidence_bundle.v2`,两组新字段必填、允许空、`.strict()` 保持:
```
interpretiveFacts: {
yogas: { name(≤80, 净化 pattern), category(17 项闭合枚举), planets(celestial 枚举 ≤9), evidenceRef }[] // ≤40
functionalRoles: { planet(celestial), role(benefic|malefic|neutral|yogakaraka), ownedHouses(1-12, ≤12), evidenceRef }[] // ≤12
shadbalaRanking: { planet(celestial), rank(int 1-9), rupa(0-100|null) }[] // ≤9
savScores: { house(1-12), score(0-100) }[] // ≤12
savTotal: number(0-1000) | null
currentDasha: { mahadasha(celestial), antardasha(celestial|null), start, end } | null // 沿用既有 dashaPeriod 日期校验
convergenceDomains: string[] // ≤6 × ≤80,净化
}
themeNarrativeSeeds: { theme, headline(≤300), strengths(≤8×≤400), risks(≤8×≤400), boundaries(≤8×≤400), evidenceRefs(≤24) }[] // ≤12
```
`ownedHouses` 是相对任务书的**加法**(宫主分工是功能吉凶最有解读价值的一半,且是闭合的 1–12 整数数组),不是放宽。
### 确定性与 hash
`sortedBundleContent` 扩展:yogas 按 `name:category` 排序且 planets 去重排序;functionalRoles 按 planet 排序、ownedHouses 去重升序;shadbalaRanking 按 `rank, planet`savScores 按 houseconvergenceDomains 去重排序;seeds 按 theme 排序、evidenceRefs 去重排序。`computeReportEvidenceBundleHash` 因此覆盖新字段。测试 `bundle hash is stable under interpretive field ordering` 把五个数组全部 `reverse()` 后 hash 不变。
`shadbalaRanking.rank` **不采信引擎给的 rank**,而是按 `rupa` 降序、同分按行星名字典序重新推导,保证同一份输入永远得到同一份排名(本盘 Sun 与 Moon 都是 6.42 rupa,固定为 Moon(5) → Sun(6))。
### 净化与 fail closed
- 行星走既有 `safeCelestialName`;yoga 分类走 17 项闭合枚举,未知归 `other`yoga 名走 `^[\p{L}\p{N} ()/·,.'’++-]{1,80}$`
- `safeSeedText` 逐行清洗:控制字符 → 空格、折叠空白、超长截断,并对 21 个违禁子串(`vedastro` / `mevg` / `workflow` / `references/` / `scripts/` / `modules.` / `skill` / `prompt` / `oracle` / `engine` / `fallback` / `blocked` / `not_found` / `not_available` / `api` …)**整行丢弃**。这是相对任务书的收紧:引擎自产叙事里确实含外部供应商名与内部路由词,写作端红线不允许它们进 prompt。
- `validateReportEvidenceBundleV2` 新增 fail-closed`report_bundle_invalid_interpretive_ref``report_bundle_duplicate_functional_role``report_bundle_duplicate_shadbala_rank` / `_planet``report_bundle_duplicate_sav_house``report_bundle_current_dasha_invalid``report_bundle_duplicate_seed_theme``report_bundle_seed_theme_not_requested``report_bundle_invalid_seed_ref`。consensus ≥2 verified 防升级校验一字未改。
### 提取结果(真实 smoke 盘)
| 层 | 条数 | 样例(值不敏感,为虚构盘) |
|---|---:|---|
| yogas | 1 | `Amala Yoga / extended``chart.yogas` 两行同名合并为一行) |
| functionalRoles | 7 | Mars=yogakaraka(5,10)Jupiter/Moon=beneficMercury/Saturn/Venus=maleficSun=neutral |
| shadbalaRanking | 7 | Venus(1,8.35) → Saturn(2) → Mercury(3) → Jupiter(4) → Moon(5) → Sun(6) → Mars(7) |
| savScores | 12 | 上升 Cancer → 第 1 宫 24、第 10 宫 30、第 2 宫 27、第 11 宫 28 … |
| savTotal | 1 | 337 |
| currentDasha | 1 | Rahu / Sun2011-11-11 2029-11-11 |
| convergenceDomains | 0 | 源缺失,空 |
| themeNarrativeSeeds | 6 | 6 个非 blocked 主题各一条 |
---
## 任务 2 · Claim card 从回执升级为结论
### 改前 / 改后对照(同一份真实 smoke 盘,9 主题请求;career / marriage / timing 因缺 AmK / Transit / DK 仍是 blocked section,逻辑未动)
**改前(`8b7eda9f`)——所有主题只有主题名不同:**
```
[education] parameter_sensitive
conclusion : 服务器已闭合教育学习所需的最低证据组;本节只能在所列事实与确定性级别内解释。
supporting : D1=D1 上升为 Cancer,本次基础盘已建立
D24=D24 已执行并纳入本主题证据计划
Vimshottari=Vimshottari 已执行并纳入本主题证据计划
counterFacts: []
[wealth] parameter_sensitive
conclusion : 服务器已闭合财富结构所需的最低证据组;本节只能在所列事实与确定性级别内解释。
supporting : D1=… / D2=D2 已执行并纳入本主题证据计划 / D11=… / Yoga=… / Ashtakavarga=… / Vimshottari=…
```
**改后:**
```
[education] parameter_sensitive ← 级别未变
conclusion : 本命上升为 Cancer;按宫主判定的功能吉星为 Jupiter、Moon,功能凶星为 Mercury、Saturn、Venus。
本主题相关宫位的八分力总分:第 4 宫 25 分,第 5 宫 35 分(全盘合计 337 分)。
当前处于 Rahu 主运、副运 Sun,主运区间 2011-11-11 至 2029-11-11。
supporting : D1=本命上升 Cancer;功能吉星 Jupiter、Moon,功能凶星 Mercury、Saturn、Venus
D24=D24 已执行并纳入本主题证据计划
Vimshottari=当前 Rahu 主运、副运 Sun2011-11-11 至 2029-11-11
Ashtakavarga=八分力:第 4 宫 25 分,第 5 宫 35 分,全盘合计 337 分
Functional Benefic/Malefic=功能吉凶已判定,Mars 为 yogakaraka
Yoga=已成立组合:Amala Yoga
[wealth] parameter_sensitive ← 级别未变
conclusion : 本命上升为 Cancer;按宫主判定的功能吉星为 …。
本主题相关宫位的八分力总分:第 2 宫 27 分,第 11 宫 28 分(全盘合计 337 分)。
当前处于 Rahu 主运、副运 Sun,主运区间 2011-11-11 至 2029-11-11。
[general] single_system_inference ← 级别未变
conclusion : …本主题相关宫位的八分力总分:第 1 宫 24 分,第 10 宫 30 分(全盘合计 337 分)。…
```
六个可写主题的 conclusion 现在**逐主题不同**(主题相关宫位的 SAV 行不同),且不含任何模板回执句。
### 拼装规则
- `conclusion = seed.headline + 最多 2 条 seed.strengths`,按句号补全后拼接,截断 1200。
- seed 的 headline 优先取引擎叙事 headline(结论式);无叙事时取第一条服务器确定性投影行;再无则取 guided topic 标题。主题相关行(SAV 宫位)排在前面,保证每章 conclusion 不同。
- `supportingFacts`:每条 matched receipt 一条,值改为该技法的真实解读语句;此外把通用解读层(Functional Benefic/Malefic、Yoga、Ashtakavarga、Vimshottari)作为额外事实条目挂在**它们自己的 receipt** 上,不冒名给别的技法。
- `counterFacts` ← seed.risks(本盘引擎叙事缺失,为空;合成夹具用例 `engine risk lines become counter facts on the matching claim card` 覆盖了有 risks 的路径)。
- `boundaries` 留在种子里由 writer 引用(任务书允许的第二种处置),`verificationQuestions` 未动。
- **`assertionLevel` 推导一字未改**;只有"完全没有种子"时把 `multi_system_consensus` 压到 `single_system_inference`(用例 `a theme without any narrative seed keeps the receipt wording and loses consensus` 覆盖)。
### 端到端抽查(叙事内容可回溯到 bundle,3 处)
无模型凭据、无数据库,本地无法真跑 writer(见 BLOCKED.md)。改为对服务器确定性产出的 conclusion 做逐句回溯,这是 writer 唯一被允许复述的结论层:
| # | conclusion 中的表述 | 回溯到 bundle 的哪一行 |
|---|---|---|
| 1 | "本命上升为 Cancer" | `charts[D1].ascendant.sign = "Cancer"`;同时 `supportingFacts` 该条 `evidenceRef = ev-tech-d1``executionLedger[ev-tech-d1].status = verified` |
| 2 | "Mars 同时主管第 5、10 宫,在本盘属 yogakaraka" | `interpretiveFacts.functionalRoles[{planet:"Mars", role:"yogakaraka", ownedHouses:[5,10], evidenceRef:"ev-tech-functional_benefic_malefic"}]`;该 ref 在 `evidenceRefs` 中 status=partial,事实条目 status 同为 partial(未升级) |
| 3 | "当前处于 Rahu 主运、副运 Sun,主运区间 2011-11-11 至 2029-11-11" | `interpretiveFacts.currentDasha = {mahadasha:"Rahu", antardasha:"Sun", start:"2011-11-11", end:"2029-11-11"}`,绑 `ev-tech-vimshottari` |
| 4 | "本盘已成立的组合:Amala Yoga" | `interpretiveFacts.yogas[0] = {name:"Amala Yoga", category:"extended", evidenceRef:"ev-tech-yoga"}``modules.yogas.yogas` 里 6 条 `hit=false` 的候选规则**没有**出现在这里 |
| 5 | "第 2 宫 27 分,第 11 宫 28 分(全盘合计 337 分)" | `interpretiveFacts.savScores[{house:2,score:27},{house:11,score:28}]` + `savTotal=337`,绑 `ev-tech-ashtakavarga` |
---
## 每章 prompt 增量(红线 6:必须摆数据)
同一份真实 smoke 盘、`depth=standard`、6 个可写主题。测量对象是 `sectionPrompt` 的 JSON 载荷 `{bundle, plan, completedSectionTitles}``filterReportEvidenceBundleForSection` 之后)。改前数据是把三个改动文件 checkout 回 `8b7eda9f` 后用同一份 workflow 快照重跑得到的,不是估算。
**token 数是离线估算**(无 tokenizerCJK 字符按 1 token、其余按 1/4 token)。真实 `inputTokens` 必须来自部署后的一次真实生成。
| 章节 | 改前 chars | 改后 chars | 改前 est.tok | 改后 est.tok | 倍数 |
|---|---:|---:|---:|---:|---:|
| theme-general | 3459 | 6993 | 928 | 1999 | 2.15 |
| theme-family | 3774 | 7298 | 1044 | 2112 | 2.02 |
| theme-health_pressure | 3845 | 7354 | 1065 | 2121 | 1.99 |
| theme-education | 4169 | 7361 | 1162 | 2127 | 1.83 |
| theme-migration_home | 4511 | 7726 | 1270 | 2240 | 1.76 |
| theme-wealth | 5129 | 7711 | 1465 | 2262 | 1.54 |
| **p50** | **4004** | **7357** | **1113.5** | **2124** | **1.91** |
改后载荷构成(以 general 章为例,chars):`interpretiveFacts` 1365、`themeNarrativeSeeds` 500、`claimCards` 1262(改前约 500)、`executionLedger` 576、`evidenceRefs` 372、`charts` 1731(未变)、`calculationProfile` 232(未变)。
再加上任务 3 的静态系统内容(改前只有 24 字符的缓存边界句):
| 项 | 改前 est.tok | 改后 est.tok | 是否吃 prompt cache |
|---|---:|---:|---|
| 缓存前缀内的静态内容(通用包 + 边界句) | 22 | 877 | **是**`cachedSystemMessage`,前缀逐字节稳定,有测试锁) |
| 本章主题包(缓存边界之后) | 0 | 203313p50 214 | 否 |
| bundle 载荷 | 1113.5 | 2124 | 否 |
| **每章合计(原始 token** | **1135.5** | **3215** | — |
- **原始 token p50 倍数 = 2.83×,超过改前 2 倍。**
- 计费加权后(缓存写 1.25×、缓存读 0.1×,6 章摊到每章约 256 token):改后 ≈ 2594 token**约 2.28×**,仍超 2 倍。
**按红线 6 与任务 3 止损条款处置**`cachedSystemMessage` 已确认生效且缓存前缀逐字节稳定(`writer system content caches the general pack and appends only the chapter pack` 用例锁定),不属于"cache 未命中"的止损分支;止损条款明确禁止"砍证据来凑成本",因此**未削减任何证据**,数据原样摆出,是否接受这个成本请委托方裁决。可选的降本项(未做,逐条列出以便决策):
1. 主题包不下发,只留通用包:每章省约 214 token(→ 约 2.64×)。代价是丢掉主题级措辞纪律。
2. `themeNarrativeSeeds` 与 conclusion 前两句重复,去重可省约 150 token(→ 约 2.70×)。代价是 writer 看不到种子原始层与 claim 的差别。
3. `shadbalaRanking` 每章只留前 3 + 最弱 1:省约 90 token。代价是丢掉完整发力顺序。
4. `charts`1731 chars ≈ 450 token)是改前就有的开销,与本轮无关;真要压成本,这里比砍新证据划算。
---
## 任务 3 · 写作端静态解读知识包
### 目录与清单
`frontend/src/lib/report-interpretation-packs/``general.ts` / `themes.ts` / `index.ts`),纯静态 TS 常量。蒸馏来源:`references/modern-language-guide.md``references/common-misconceptions.md``references/house-domain-planet-mapping.md` 及各主题判读要点。
| 包 | 字符数 | est.tok | 内容 |
|---|---:|---:|---|
| general_method(通用) | 961 | 855 | 表达顺序、术语现代化映射、强弱与分值怎么说、多层验证、必须避免的推理错误、语气与禁语 |
| career | 366 | 313 | 主指标、10/6 宫怎么说、yogakaraka 怎么落地、禁止承诺升职/薪资/时点 |
| marriage | 330 | 296 | 7 宫=一切长期对等关系、D9 与本命的分工、土星不写成"晚婚"、禁止预测结婚年份与忠诚判断 |
| wealth | 298 | 261 | 赚到 vs 留住必须分开、低分不写成"破财"、禁止推荐任何标的/收益率/借贷建议 |
| education | 248 | 214 | 学得快 vs 学得深、困难写成方法不匹配、禁止预测通过与录取 |
| migration_home | 244 | 208 | 12 宫不写成"损失"、安定与离开并存、禁止指定国家城市方位与日期 |
| family | 231 | 203 | 关系模式而非人品、5 宫不是生育能力、禁止预测生育与亲属健康 |
| health_pressure | 243 | 214 | 只谈负荷与恢复、禁止任何诊断/点名疾病/用药建议 |
| timing | 281 | 256 | 时间层只能激活本命已有结构、主运/副运分工、未获准时不写年月日 |
| general(综合基础章) | 273 | 243 | 建框架不抢主题章结论、功能吉凶是角色分工不是性格好坏 |
单包上限 3000 字符(`PACK_CHARACTER_LIMIT`),最大的通用包用了 32%。
### 注入方式
`buildWriterSystemContents(theme)``frontend/src/mastra/personal-report.ts`)返回:
- `[0]` = 通用包 + `【上下文缓存边界】…`,作为 `cachedSystemMessage` 的内容 → **在缓存断点之内**,且逐字节稳定,第 2 章起是 cache read
- `[1]` = 本章主题包,放在缓存边界之后(每章不同,不能进前缀)。
- 摘要调用 `themePack=null`,只带通用包。
- 非 Anthropic 供应商 `cachedSystemMessage` 返回 null,此时降级为普通 system message(内容不变,只是不缓存)。
### 合同同步
- writer 指令新增:interpretiveFacts / themeNarrativeSeeds 是"可引用的事实层,但不授权 claimCard 之外的结论";知识包"只管怎么解释和怎么措辞,不是事实来源,不能提高确定性"。`Use its claimCards exclusively for narrative conclusions` 原句保留。
- `skills/jyotish-personal-report/SKILL.md` 升到 **1.1.0**`skills/jyotish-personal-report/versions/1.1.0/` 为不可变副本,registry 中 1.0.0 转 `deprecated`、1.1.0 为 `active`sha256 `6be2279b…10e9`)。
### 端到端改前改后同盘同主题章节全文对照
**未做,无法在本地做**:执行环境没有任何模型凭据(`ANTHROPIC_*` / `OPENAI_*` 等全部未设置,仓内只有 `.env.*.example`),也没有 PostgresDocker 不可用),`generateSectionedPersonalReport` 需要 `PersonalReportSectionService`。见 `BLOCKED.md`。可离线给出的替代物是上面的"每章 prompt 增量"表与 claim card 全文对照。
---
## 任务 4 · 观测(只做不依赖部署的部分)
`PersonalReportAgentTelemetry` 新增两个数值字段,沿用既有隐私边界(只记指标名与数值):
- `interpretiveFactCount`:本次调用收到的 bundle 里解读事实的条数(yogas + functionalRoles + shadbalaRanking + savScores + convergenceDomains + currentDasha + savTotal)。本盘各章为 **21**1 + 7 + 7 + 2~3 + 0 + 1 + 1,随该章 SAV 宫位裁剪略有差异)。
- `knowledgePackCharacters`:本次调用随 prompt 下发的静态知识包字符数 = 缓存前缀内容 + 本章主题包。章节调用为 **12181353**987 + 231~366),摘要调用为 **987**
`countInterpretiveFacts` 对缺字段/畸形输入返回 0 而不抛错——telemetry 不得让生成失败。测试 `telemetry carries interpretive and knowledge-pack sizes as numbers only` 断言两个字段是数字,且序列化后不含任何事实文本。
**部署后测量(章节数、每章 in/out tokens、总墙钟、与 `PROGRESS-report-sectioned-20260830.md` 对照)按授权跳过,由委托方在 staging 部署后补。**
---
## 修改既有测试断言的逐条说明(红线 9)
| 文件 | 断言 | 原值 | 原因 |
|---|---|---|---|
| `frontend/tests/personal-report-generation.test.ts` | `bundleV2Fixture()` | 无 `interpretiveFacts` / `themeNarrativeSeeds` | 两个字段按任务书要求为必填;夹具没有引擎快照,声明为 `emptyReportInterpretiveFacts()` + `[]`。断言语义未变。 |
| `frontend/tests/personal-report-generation-v2.test.ts` | 同上 | 同上 | 同上 |
| `frontend/tests/personal-report-plan.test.ts` | 同上 | 同上 | 同上 |
| `frontend/tests/personal-report-generation.test.ts:814` | `snapshot.version` | `"1.0.0"` | Skill 合同变更,版本 +1;已在断言上方注明原值与原因 |
| `frontend/tests/skill-registry.test.ts:92` | jyotish-personal-report 的 version / sha256 | `1.0.0` / `23149b9e…f982` | 同上;已在断言上方注明原值与原因 |
| `tests/test_jyotish_personal_report_skill_package.py` | `VERSION_ROOT` 与 registry 期望行 | `versions/1.0.0` | 同上;改为 `ACTIVE_VERSION` 常量并注明原值与原因,同时**新增**四条断言锁住 1.1.0 的新合同措辞(只加不减) |
没有任何断言被放松:`report-evidence-bundle-v2``.strict()`、consensus ≥2 verified 防升级、`assertionLevel` 推导、章节串行、writer 输出 schema 全部原样。
---
## 质量门实际结果
```
$ ./node_modules/.bin/tsc --noEmit # frontend
(无输出,通过)
$ ./node_modules/.bin/eslint src tests # frontend
✖ 28 problems (0 errors, 28 warnings)
28 条全部是既有的 no-unused-vars warning,与本轮无关)
$ npm run build # frontend
✓ Compiled successfully in 5.6s
✓ Generating static pages using 7 workers (62/62) in 447ms
Turbopack build encountered 5 warnings:
Warning: Dynamic filesystem access causes tracing of the whole project ×5
(该 5 条为既有告警,已记录在 BLOCKED.md 2026-08-17 条目)
$ ./node_modules/.bin/tsx --test tests/*.test.ts # 基线 8b7eda9f
# tests 2427 # pass 2393 # fail 24 # skipped 10 # todo 0
$ ./node_modules/.bin/tsx --test tests/*.test.ts # 本分支
# tests 2444 # pass 2410 # fail 24 # skipped 10 # todo 0
失败集逐条 diff:完全一致(SAME_FAILURE_SET),24 条全部是本机缺 Docker / PostgreSQL 的
数据库与部署夹具,以及 staging workflow YAML 校验,与 BLOCKED.md 既有记录一致:
admin code functions … / admin customer reset … / service and restricted admin database identities …
Owner recovery … / staging backups … / billing order adjustments … / billing, subscriptions, …
saving a published product … / database env validator … / migration runner … / foundation grants …
local PostgreSQL … / personal report job migration … / personal report sections enforce owner-read RLS …
redeem security … / self-hosted identity migration … / database roles have no cluster privileges
server compose defaults … / staging env validator … / Better Auth supports shared user OTP/password …
database drops secret refs … / changed staging workflows are syntactically valid YAML
live staging sync preserves env … / live staging sync repairs nested deploy-tree drift …
新增 17 个通过用例,0 个新增失败。
$ .venv/bin/python -m pytest tests/test_jyotish_personal_report_skill_package.py -q
.. [100%]
$ .venv/bin/python scripts/pre_work_check.py
checks 全 trueremote_visibility_status = verified
```
`npm run test:db` 未跑:Docker 不可用,且本轮红线禁止数据库结构改动与新增迁移,本轮确实没有改任何库结构或迁移。
---
## 交付物清单核对
| # | 交付物 | 状态 |
|---|---|---|
| 1 | 任务 0 契约快照(字段存在性清单 + fixture 测试) | 完成 |
| 2 | bundle 新字段 schema + 提取 + 净化 + hash 覆盖 + fail-closed 测试 | 完成 |
| 3 | `filterReportEvidenceBundleForSection` 主题裁剪 + 每章 prompt 增量数据 | 完成(增量数据超 2×,已摆出待裁决) |
| 4 | 实质化 claim cards + 回退与级别压制 + consensus 回归测试 | 完成 |
| 5 | 端到端抽查记录:叙事可回溯到 bundle(≥3 处) | 部分完成:对服务器确定性 conclusion 做了 5 处逐行回溯;真实 writer 输出因无模型凭据未跑(BLOCKED.md |
| 6 | 知识包目录 + 违禁词扫描测试 + SKILL.md 更新 | 完成 |
| 7 | 改前改后同盘同主题章节全文对照 + tokens/墙钟对照 | 未做:无模型凭据与数据库(BLOCKED.md);已给出 prompt 载荷与 claim card 全文对照作为离线替代 |
| 8 | `docs/BUG_HISTORY.md` 条目 | 完成:BUG-467(远端 `origin/staging` 最大号为 BUG-466,已确认) |
| 9 | `BLOCKED.md` | 完成 |
| 10 | `PROGRESS-report-skill-parity-20260901.md` | 本文件 |
| 11 | tsc / eslint / tsx --test / next build 实际输出 | 完成(见上) |
+16
View File
@@ -7166,3 +7166,19 @@
- 相关记录:BUG-464、BUG-465
- 复发自:无
- 修复版本:待发布
## BUG-467 | 引擎算出的解读事实被报告提取层全部丢弃,claim card 只剩执行回执
- 状态:resolved
- 首次发现:2026-09-01
- 最近更新:2026-09-01
- 影响面:`buildReportEvidencePacket``buildReportEvidenceBundleV2` 的 claim card 构造、`filterReportEvidenceBundleForSection``ReportEvidenceBundleV2` schema、报告 writer 指令与 `jyotish-personal-report` Skill
- 用户现象:个人报告读起来空洞、各主题高度雷同,说不出这张盘特有的任何东西;报告正文只能围绕"服务器已闭合本主题所需的最低证据组"这类回执文案展开。
- 触发条件:任意一次个人报告生成。`/api/consultation_workflow` 响应里已经带有解读层,但报告路径不读。
- 根因:结构性矛盾。引擎响应的 `chart.ai_prompt_pack.evidence_snapshot.functional_benefic_malefic``.strength.shadbala_ranking``chart.modules.ashtakavarga.sav``chart.modules.dasha_sub_periods.current``chart.yogas``chart.modules.guided_topics` 都存在,但提取层只保留行星/宫位/上升、两条 Dasha 时间线、varga 宫位占据和技法执行回执。于是 claim card 的 `conclusion` 是模板句、`supportingFacts` 是"XX 已执行并纳入本主题证据计划"。写作端合同又规定 claimCards 是唯一结论来源——结论来源里没有结论,模型只能输出空洞合规文本或悄悄违规自行解读原始盘面(schema 查不出来)。同时写作端拿不到任何解读方法论。
- 修复:`ReportEvidenceBundleV2` 新增 `interpretiveFacts`yogas / functionalRoles / shadbalaRanking / savScores+savTotal / currentDasha / convergenceDomains)与 `themeNarrativeSeeds`,均必填、允许为空、保持 `.strict()`、进入 canonical 排序与 `bundleHash`,并对悬空 ref、重复 rank/house/theme、越界文本 fail closed。提取走 allowlist:yoga 分类与功能吉凶角色是闭合枚举,行星过 `safeCelestialName`,SAV 按上升整宫把星座分投影成宫位分,叙事种子逐行过违禁词清洗(含外部供应商名与内部路由词一律丢行)。claim card 的 `conclusion` / `supportingFacts` 改为由这些事实确定性拼装,risks 进 `counterFacts``assertionLevel` 推导规则一字未改,只在完全没有种子时把 consensus 压到 `single_system_inference``filterReportEvidenceBundleForSection` 按主题裁剪种子与 SAV 宫位,同时让盘面通用层(功能吉凶、yoga、八分力、当前大运)的 receipt 随每章下发。新增 `frontend/src/lib/report-interpretation-packs/`:通用包进 `cachedSystemMessage` 缓存前缀,本章主题包放缓存边界之后;包内只写措辞与解释方式,不含路径、内部名词、供应商名与任何用户数据。Skill 升到 `1.1.0``1.0.0` 转 deprecated)并写明知识包不是事实来源。telemetry 只补记 `interpretiveFactCount``knowledgePackCharacters` 两个数值。
- 验证:先用虚构 smoke 出生数据真实调用本地 `/api/consultation_workflow` 九个主题各一次,把字段存在性写进 `PROGRESS-report-skill-parity-20260901.md``timing.vimshottari``timing.convergence_top_domains``strength.sav_scores`、三个 `*_narrative` 在该响应里确实不存在,按缺失处理,未伪造)。`frontend/tests/report-interpretive-facts.test.ts` 用合成夹具锁住每层提取条数、`hit:false` 的候选 yoga 不算成盘、非法行星名被丢弃、超长种子截断、外部供应商行被丢、hash 在字段顺序扰动下不变、越界输入 fail closed、有种子/无种子两种 claim card 行为、consensus 防升级回归、按主题裁剪。`frontend/tests/report-interpretation-packs.test.ts` 锁每主题可解析、单包 ≤3000 字符、违禁词/路径扫描、缓存前缀逐字节稳定。`./node_modules/.bin/tsc --noEmit``eslint` 0 error、`next build` 通过,前端全量 `tsx --test` 与基线同一失败集(均为本机缺 Docker/PostgreSQL 的数据库与部署夹具)。
- 防复发:bundle 内自由文本只允许来自服务器自产生成器,用户 question 与模型历史输出一律不得写入。新增字段必须有长度上限、数量上限与字符净化,schema 保持 `.strict()`,hash 必须覆盖。不得因内容变丰富而提升 `assertionLevel`,不得删改 consensus ≥2 verified 校验。`modules.yogas.yogas``hit=false` 的规则行不是成盘 yoga,不得当证据。知识包只约束措辞,不得成为 bundle 之外新占星断言的来源;包内不得出现路径、内部模块名、引擎/供应商名或 prompt 模板。写作 agent 仍然 no skills / no tools / no memory,章节仍串行。
- 相关记录:BUG-457
- 复发自:无
- 修复版本:待发布