Files
Jyotisha/docs/tasks/TASK-report-density-fix-20260923.md
T
jesse-uxandClaude Code a12f2c0d26
Independent Staging Quality Gate / validate (push) Failing after 11m4s
Independent Staging Quality Gate / publish (push) Skipped
fix(report): make density facts readable and printable
Unify reader cleanup rules, lock writer table guards, and register the exact fictional timestamp collision. Preserve existing ordinary-report safety contracts and source-data gaps.

Validation: report Node 165/165, final safety 29/29, Python 101/101, Chrome 28/28; both PDFs retain all 130 rows. Full Node 3704 tests with the same 91 baseline failures. Privacy test: 62 passed, 1 failed due to 17 protected-file READ_ERRORs; not a green gate. Build, DB, manual checklist and controlled-login gaps remain documented. User explicitly authorized staging push with these gaps disclosed.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 15:26:25 +08:00

202 lines
15 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.
# TASK-report-density-fix-20260923 · 报告密度验收修复单
## 基线 commit
- 被验收实现:`bbd96d3b`,原 SHA `f968cb21`(2026-09-23 为清除 BUG-1008 重写 staging 历史:原 `baeec66f`→`a7f82dc2`、`f968cb21`→`bbd96d3b`、`fcca56fe`→`abf28321`,代码逐字节不变)(`feat(report): add dense personal report appendix`,已在 `origin/staging`,**未部署**:staging 门禁 run 2854 红,`/api/health` 仍为 `1bc6a954`)
- 原任务书:`docs/tasks/TASK-report-density-20260922.md`
- 修复分支:`codex/report-density-fix-20260923`,从 `origin/staging` 起
- 验收基线:`f77a1b5a`(实现的父提交)
## 执行状态(2026-09-23)
- **执行中**:已 fetch 并从 `0d37bec15` 创建独立工作树 `.worktrees/report-density-fix-20260923`,分支 `codex/report-density-fix-20260923`。下方验收结论是对原实现 `bbd96d3b` 的历史验收,不是本轮修复通过。
- F1/F2/F4/F5 分工实施;F6 与独立验收由主会话负责。F3 补 Bug 历史、进度、索引和 API 既有断言三栏;**真人清单新建被工具拒绝,文件仍不存在,F3 尚未全部完成**。不换工具绕过。
- 实际结果与缺口见 `docs/tasks/PROGRESS-report-density-20260922.md`、`BLOCKED.md`。没有受控登录 / Chrome 证据,不预写测试成功或部署;BUG-1008 不改。
## 验收结论(原实现)
| 项 | 结论 | 证据 |
|---|---|---|
| 任务 1 附录清洗 | **通过** | 真实引擎附录(3,559 行)经发货链路:行数不变,表格行 1,478 → 1,478;任务书点名的 11 个词全部归零(`parameter_sensitive` 697、`cmd_full_reading` 68、`PyJHora`/`JHora` 59、`PL9` 59 等);原始字段名一个不少;英文散文未被改 |
| 任务 2 独立附录入口 | **通过** | 普通正文投影逻辑未改;新增 `raw-appendix` 路由;`personal_report_sections` 的两处读取都过滤了快照行;快照写入走既有 RPC,`section_id` 符合约束,无需迁移 |
| 任务 3 事实表 | **未通过(P1-A)** | 服务端装配 ✓、writer 看不到 `factTables` ✓、fixture 经独立重跑确认为真实引擎输出 ✓;但**显示给用户的表是引擎字段路径的键值清单**,见下 |
| 任务 4 分盘 16 张 | **通过** | `CHART_IDS` 16 种;复用 BUG-616 修过的网格,无 float / 负 margin;`next build` 后 `/` 仍 `○ Static`;首屏 gzip 623,288 → 623,329(+0.007%) |
| 任务 5 辅助大运族 | **通过** | 16 个大运族各有一行白话适用性说明;模块只读引擎的 `applicable`,不自行判定、不放宽 |
| 前端测试 | **通过** | Node 22(与门禁同版本):基线 3,698 条 / 26 失败 → 实现 3,730 条 / 26 失败,**失败名单逐条一致**,新增 32 条全过 |
| tsc / lint | **通过** | `tsc --noEmit` 0 错;`npm run lint` 0 error |
| Python 定向 | **通过** | 新增与相关 5 个文件 88 passed,含 `test_api_server_growth_contract.py` |
| Python 快速门 | **未通过(P1-B)** | 新增失败 `test_repo_privacy_markers.py::test_tracked_repository_has_no_privacy_markers`,基线无此失败 |
| 记录文件 | **未通过(P1-C)** | 见下 |
## 事故实证
### P1-A · 普通报告里的"事实表"是字段路径清单
`frontend/src/lib/report-fact-tables.ts` 的 `assembleReportFactTables` 对 8 组数据都输出同一种两列表 `原始字段 | 数值 / 状态`,行名是引擎键路径。在实现自带的 fixture 上实测:
| 指标 | 值 |
|---|---:|
| 8 张表合计行数 | 948 |
| 行名含 snake_case 引擎键 | 447 |
| 行名含数组下标 `[n]` | 521 |
样例行:`MD[0].years | 0.72`、`planets.Sun.sthana_bala.total | 213.42`、`upagrahas.raw.Kaala.sign_idx | 10`、`solar_return.data.jd_ut | 2461041.6620078036`(儒略日)。`report-fact-tables.tsx` 原样渲染这些字符串。
这不是原始附录,这是**普通报告页**。原任务书决策记录写明对 BUG-999 的推翻"仅限附录通道",普通正文不动;把 447 个内部键放进普通报告,违反了这条边界。
公平起见需要写明:原任务书任务 3 只写了"8 组表"和"每一行可追溯到 packet 真实键",**没有规定列结构**。执行方把"可追溯"做成了"行名即键路径",这是任务书欠规格。本修复单补上规格。
另一个问题:`MD[0].years = 0.72` 是出生时剩余年数,不是这段大运的年数(该段 1982-09-20 → 2000-09-20 为 18 年)。键值清单把这个区别抹掉了。
### P1-B · 隐私门新增失败
扫描在该实现(原 `f968cb21`)上命中 3 处:
| 规则 | 文件 | 行 | 定性 |
|---|---|---:|---|
| R001、R003 | `docs/tasks/TASK-report-density-20260922.md` | 13 | **真实个人出生资料**,由原任务书撰写者(Claude)写入。已在本提交中清除,见 BUG-1008 |
| R003 | `frontend/tests/fixtures/report-density-fictional-reader.json` | 22 | **数值碰撞,不是个人资料**。一个年度表时间戳里有 5 个字符恰好与私有标记相同。验收时用 fixture 声明的虚构输入(2000-01-01 12:00、0°/0°、Lahiri)独立重跑,同一行原样重现,表格行 1,481 / 1,481 |
### P1-C · 记录文件
- `docs/BUG_HISTORY.md` 没有 BUG-1003~1007 任一条(AGENTS.md §5.3 要求同一变更内写入)
- `docs/tasks/README.md` 与 `BLOCKED.md` 引用的 `docs/tasks/PROGRESS-report-density-20260922.md` **不存在**
- `BLOCKED.md` 引用的 `docs/testing/report-density-20260922.md` **不存在**
- `BLOCKED.md` 与 `CHANGELOG.md` 写"未提交、推送、部署",而实现已在 staging
- `frontend/tests/personal-report-api.test.ts` 改了一条既有断言(`select(...)` 字符串,同时新增一条过滤断言)。改动是加强不是弱化,但缺 AGENTS.md §7.3 要求的"原值 / 新值 / 原因"三栏说明
### P2-A · Python 与 TS 两份清洗规则结果不同
`scripts/reader_appendix_language.py` 与 `frontend/src/lib/reader-appendix-language.ts` 是同一套规则的两份拷贝。对同一份真实附录,两者输出有 36 行不同:Python 版留下"外部参照资料 第 42 页"这类第三方页码。原因是 Python `re` 的 `\b` 把中文算作单词字符,页码分组匹配不上;JS 的 `\b` 只认 ASCII。
目前主附录只走 TS 版,所以**用户现在拿不到页码**;但 Python 版被用于适用性段落,且两份之间没有一致性测试。
### P2-B · writer 写表的拦截没有测试
`validatePersonalReportAgentOutputAgainstPlan` 里的 `hasWriterTable` 在 writer 输出含任何 markdown 表格、`<table` 或 `factTables` 键时抛 `report_writer_fact_tables_forbidden`。行为符合任务书,但全仓没有一条测试覆盖这条错误码。另外 writer 的提示词里没有说不许写表,模型一旦写了,结果是整节硬失败,而不是事先被引导避开。
### P2-C · 事实表打印不出来
事实表放在默认关闭的 `<details>` 里。关闭的 `<details>` 内容不进打印,所以报告页"打印 / 存 PDF"得到的文件里没有任何事实表。
### P3 · 可选
- 复合状态 `pyjhora_behavior_only / not_multiengine_parity` 两半译成同一句,附录里出现 318 次"仅单一外部参照,未做多引擎核对 / 仅单一外部参照,未做多引擎核对"
- 四个未点名的状态词仍在附录里:`unclosed_divisional_chart` 19、`thematic_or_audit_surface` 19、`not_applicable` 20、`computed_pending_validation` 3
- 正文散文里仍有 `producer`、`parity`
- `blocked` 在附录译成"暂不可用",报告页 `CLAIM_STATUS_LABELS` 用"阻塞",两处不一致
- Patyayini 译作"帕特雅伊尼",其他大运族保留原名
## 根因
1. P1-A:原任务书没规定列结构,执行方把"可追溯"实现成"行名即路径"。
2. P1-B 的真实泄漏:纯文档推送不触发门禁(`docs/**` 不在 `deploy/gated-paths.txt`),隐私扫描要等下一次代码推送才跑;撰写者推送前没有自跑扫描。
3. P1-B 的碰撞:fixture 是真实引擎输出,含大量时间戳;扫描器已有"已审阅数值碰撞"机制,新 fixture 没登记。
4. P2-A:同一规则写了两份。
## 决策记录
- 本修复单不推翻任何既有决策。原任务书的三条产品拍板与对 BUG-999 的两处推翻(仅限附录通道)继续有效。
- P1-A 明确:**事实表属于普通报告,普通报告的可见文字不得出现引擎键名、数组下标或内部路径**。可追溯性保留在每行的 `sourcePath` 字段里(机器可读、不渲染)。
- Sade Sati 三轮日期:原任务书写"已逐项确认 packet 中存在"是**撰写者的错误**。packet 里只有阶段框架(上升 / 高峰 / 下降星座),`scripts/sade_sati.py` 也没有计算三轮起止日期的函数。三轮日期需要新计算,**不在本修复单范围**,维持执行方在 `BLOCKED.md` 的如实记录。
- 历史清除:产品负责人 2026-09-23 已决定清除。staging 历史已重写(见基线段 SHA 对照),新历史中不再含该资料。**旧提交在 Gitea 上仍可按 SHA 匿名访问,须管理员执行服务器垃圾回收**;在确认旧 SHA 返回 404 之前,GitHub 只读镜像不得重新同步。执行方不得再次强推或 rebase staging。
## 硬红线
1. 不得弱化隐私扫描:不改私有标记、不改扫描规则、不把整个 fixture 加入排除。碰撞只能按测试文件里既有的"已审阅数值碰撞"机制逐处登记。
2. 事实表的数值一律来自 packet,不得补算、不得四舍五入以外的任何变换。
3. 不得动普通正文投影 `projectOrdinaryReportDocument` 与 BUG-999 的 XSS / 危险 URL / `jyotish-chart` 围栏合同。
4. 不得强推、rebase 或重写 `staging`。
5. 测试总数不得低于 3,730(Node 22 实测)。
6. 出生资料不入仓:新 fixture 只能用虚构输入并声明 `fictional: true`;进度记录与 Bug 历史只写"真实个人资料",不写具体值。
## 任务分解
### F1 · 事实表改成真正的表(BUG-1009,对应 P1-A)
每组表的列如下。行星、星座优先用 packet 自带的中文字段(如 `lord_cn`),没有则用既有前端映射;度数与分值保留 2 位小数;日期只显示 `YYYY-MM-DD`;布尔值显示"是 / 否"。
| 组 | 表 | 列 |
|---|---|---|
| vimshottari | 主运表(全部 9 段) | 主运 · 起 · 止 · 年数 · 当前 |
| | 当前主运下分运表 | 分运 · 起 · 止 · 当前 |
| | 当前分运下小运表 | 小运 · 起 · 止 · 当前 |
| ashtakavarga | SAV | 星座 · SAV 点数 |
| | BAV | 行星 × 12 星座 |
| shadbala | 六分量 | 行星 · 位置力 · 方向力 · 时间力 · 动态力 · 自然力 · 相位力 · 合计 |
| functional_roles | 功能吉凶 | 类别(功能吉星 / 功能凶星 / 中性 / Yogakaraka)· 行星 |
| avasthas | 行星状态 | 行星 · Bala · Jagrat · Deeptadi · Lajjitadi · Shayanadi |
| special_points | 附属星点与特殊上升点 | 名称 · 星座 · 度数 |
| sade_sati | 阶段框架 | 阶段(上升 / 高峰 / 下降)· 星座;另起一行说明当前是否在进行中 |
| annual | 年度盘 | 行星 · 星座 · 度数;年度上升与年龄在表头说明里写 |
| | Saham | 名称 · 星座 · 度数 |
"年数"列用该段真实长度;出生时剩余年数单独写进表头说明,不得填进"年数"。儒略日、`dt_ut` 这类内部时间字段不展示。
**验收标准**:
- 在实现自带 fixture 上,所有事实表的**可见单元格**中 snake_case 标记为 0、数组下标 `[n]` 为 0、超过 2 位的小数为 0;
- 每行保留 `sourcePath`,且仍能在 packet 中取到对应值(用测试逐行回查);
- 主运表第一段"年数"等于该段起止之差(fixture 上为 18),不得是 0.72;
- 新增测试断言上述三条;既有测试不得弱化。
### F2 · 隐私门转绿(BUG-1010,对应 P1-B 碰撞部分)
按 `tests/test_repo_privacy_markers.py` 里既有的"已审阅数值碰撞"机制,登记 `frontend/tests/fixtures/report-density-fictional-reader.json` 第 22 行这一处碰撞。登记时在注释里写明:来自虚构输入的引擎时间戳,验收已独立重跑复现。任务书那一处真实泄漏已在本修复单的同一提交中从 HEAD 清除,执行方不需要处理。
**验收标准**:`python3 -m pytest tests/test_repo_privacy_markers.py -q` 全过;扫描器规则、私有标记、排除列表之外的任何东西都没有改。
### F3 · 补齐记录文件(对应 P1-C)
- `docs/BUG_HISTORY.md` 写入 BUG-1003~1007(原任务书五项)与本单的 BUG-1009、BUG-1010,状态按事实写(部署前不得写 resolved 以外的"已上线");
- 新建 `docs/tasks/PROGRESS-report-density-20260922.md`,含改动既有断言的"原值 / 新值 / 原因"三栏;
- 新建 `docs/testing/report-density-20260922.md`,写成真人可照做的浏览器清单:打开一份新生成报告 → 看分盘核对 16 张不重叠 → 展开每张事实表 → 点"下载原始附录" → 确认普通 Markdown 下载内容与改动前一致 → 打印 / 存 PDF;
- 更正 `BLOCKED.md` 与 `CHANGELOG.md` 里"未提交、推送"的说法。
**验收标准**:三个文件存在且互相引用一致;`git grep -n "未提交、推送"` 在这两处不再命中。
### F4 · 清洗规则只留一份真相(对应 P2-A)
二选一:删掉一份拷贝,让两处共用;或保留两份但修好 Python 页码正则,并加一致性测试(同一份 fixture markdown,Python 输出与 TS 输出逐字相同)。用户偏好是"多余入口宁可删除也不修",优先前者。
**验收标准**:对实现自带 fixture 的 markdown,两条路径(若都保留)逐字相同;"第 N 页"在两条路径的输出中都为 0。
### F5 · writer 写表拦截补测试与提示(对应 P2-B)
加一条测试覆盖 `report_writer_fact_tables_forbidden`;在 writer 提示词里写一句"不要输出表格,表格由系统提供"。
**验收标准**:新测试在去掉拦截代码后变红、恢复后变绿。
### F6 · 事实表可打印(对应 P2-C)
打印 / 存 PDF 时事实表全部展开。实现方式不限。
**验收标准**:`docs/testing/report-density-20260922.md` 里写一条打印核对;如能用本机 Chrome 无头打印验证,附 PDF 页数。
P3 各项可顺手修,修了要在进度记录里列出,不修不算未通过。
## 让步顺序
1. F2 + F3(门禁转绿、记录补齐;不做这两项,任何部署都出不去)
2. F1(普通报告里的字段路径是用户可见的主要问题)
3. F4、F5、F6
## 开工前置命令
```bash
git fetch origin --prune
git status -sb
git worktree add -b codex/report-density-fix-20260923 .worktrees/report-density-fix-20260923 origin/staging
python3 -m pytest tests/test_repo_privacy_markers.py -q # 开工时应只剩 fixture 那 1 处
```
前端测试请用 Node 22(门禁同版本)。本机 Node 20 下有 32 条测试因子进程 `@/` 别名解析失败而误红,其中 11 条在基线上同样失败,属环境问题。
## BUG 编号
- BUG-1003~1007:原任务书五项,执行方在 F3 补写
- BUG-1008:任务书写入真实出生资料(Claude 已在本提交写入 Bug 历史)
- BUG-1009:普通报告事实表显示引擎字段路径(F1)
- BUG-1010:新 fixture 的隐私扫描数值碰撞未登记(F2)
开工时核对 `docs/BUG_HISTORY.md` 最大号,若有他会话占用则顺延。