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

15 KiB
Raw Blame History

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

开工前置命令

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 最大号,若有他会话占用则顺延。