fix(report): project ordinary reports onto public prose only

This commit is contained in:
jesse-ux
2026-09-22 12:13:53 +08:00
parent 337d820076
commit dd4141a030
17 changed files with 1401 additions and 48 deletions
+16
View File
@@ -13231,3 +13231,19 @@
- 相关记录:BUG-936
- 复发自:无
- 修复版本:未发布(本分支未推送)
## BUG-999 | 普通报告导出仍带出内部技法与工作流字段
- 状态:resolved(本地候选,未部署)
- 首次发现:2026-09-22
- 最近更新:2026-09-22
- 影响面:咨询 Markdown 导出、个人报告阅读、普通 Markdown 下载、`GET /api/reports/:id` 与报告列表摘要。聊天消息行不在本次回归里。
- 用户现象:聊天里已经不显示内部证据徽章,但导出的报告和已存 Markdown 仍可能出现 `technique_truth``workflow_route``workflow_status``precise_timing``missing_layers`,以及评分、内部地址、密钥样式字段、模型调试和 job/attempt/provider 元数据。
- 触发条件:从咨询会话复制或导出报告;打开或下载一份已经写好的个人长报告,包括命中旧缓存 Markdown 的情况。
- 根因:聊天层按 BUG-011 隐藏了这些字段,报告阅读、导出和 API 没有同一套普通用户 allowlist。普通下载还把 `/professional-reference` 当成正文来源。可见性不能靠接口名字判断。
- 修复:新增 `frontend/src/lib/report-public-projection.ts`。文档类型显式区分聊天导出、个人报告详情、普通 Markdown 下载和专业参考。普通输出只保留正文、结论、行动建议、自然语言限制、图盘围栏和安全的引擎 SVG。内部限制改写成白话,不写出内部键。阅读 API、详情分类、普通下载和列表摘要都走这层投影。专业参考响应标明 `documentKind: professional_reference`;本轮没有单独专业权限,所以它的正文同样 fail-safe 到普通投影。普通下载改为读 `GET /api/reports/:id`,不再请求专业参考。
- 验证:`frontend/tests/report-public-projection.test.ts` 覆盖字段在与不在、旧缓存、专业参考种类、图盘围栏、HTML/script/iframe/object/embed/img、危险 URL,以及普通输出不含内部 URL、密钥和模型字段。`consultation-report-export``personal-report-longform-md`、报告归属与 ready 检查的测试名保留。`tsc --noEmit` 通过,`npm run lint` 0 error。没有登录态,浏览器导出未验收,见 `docs/testing/report-public-content-20260922.md`
- 防复发:普通报告的公开字段以 allowlist 为准,不能只靠删除若干字符串。缓存命中和已存 Markdown 必须再过投影。专业参考不得再被普通下载当作 fallback。不得放宽 Markdown 的 XSS、危险 URL、HTML 和 `jyotish-chart` 围栏合同。
- 相关记录:BUG-011、BUG-058、BUG-188
- 复发自:无
- 修复版本:本分支 `fix(report): project ordinary reports onto public prose only`(未推送,未部署)
@@ -0,0 +1,44 @@
# 进度 · 普通用户报告公开内容分层(2026-09-22)
基线:`origin/staging` `10baeb2fa865743c428806e115ac92ca9d92fb71`。分支 `codex/report-public-content-20260922`。未推送。
聊天消息行仍然不渲染 `techniqueTruth``workflowReceipt`、证据徽章和单条下载按钮(`chat-message-row.tsx` 无这些符号;`consultation-report-export.test.ts` 的「assistant answer does not expose internal report controls」仍在)。漏洞在导出和报告 API。
## 投影
- 新增 `frontend/src/lib/report-public-projection.ts`。文档类型是显式参数:`chat_export``personal_report_detail``ordinary_markdown_download``professional_reference`。不根据路由名决定可见性。
- 普通 allowlist`title``prose``conclusion``action``limitation``chart_fence``engine_svg`
- 内部字段不序列化:`technique_truth``workflow_route``workflow_status``precise_timing``missing_layers`、评分/权重/执行账本、内部 URL、secret、模型调试、raw tool response、job/attempt/provider。
- 限制改写成白话,不带内部键。没有这些信号时不补限制段。
## 接入
- `consultationReportMarkdown` 只走聊天导出投影。
- `resolveReportRead``longformMarkdown` 与返回给浏览器的 `reportDocument` 走投影。持久化行不改。
- `classifyReportEnvelope` 在详情阅读前再投影一次,旧响应也不能直接上屏。
- 普通下载读 `GET /api/reports/:id`,并用 `ordinaryReportDownloadMarkdown` 投影后再去掉 `jyotish-chart` 围栏。不再请求 professional-reference。
- 列表 `card_summary` 走同一段公开正文投影。
- professional-reference 仍是独立入口,响应 `documentKind``professional_reference`。本轮没有单独专业权限,正文 fail-safe 到普通投影,避免只靠接口名字放行内部字段。
## 断言变化
| 测试 | 原值 | 新值 | 原因 |
| --- | --- | --- | --- |
| `consultation-report-export.test.ts`「exports latest consultation answer…」 | 匹配 `technique_truth: partial``workflow_route: career``precise_timing: blocked``missing_layers: MEVG``未闭环内容不得包装成确定预测` | 仍匹配标题和「先看阶段」;匹配白话限制;不匹配上述内部键、`MEVG``career``Claim boundary` | 这些内部字段不再是普通导出的预期结果 |
| `personal-report-entry.test.ts`「legacy consultation Markdown export…」 | 匹配 `workflow_route: career``precise_timing: blocked` | 匹配「先看阶段」和「这次说不到具体哪一天」;不匹配 `workflow_route``precise_timing``technique_truth``MEVG` | 同一导出合同,不能继续把泄漏当回归锁 |
| `personal-report-entry.test.ts`「ready reports expose Markdown export…」 | `longformDownloadSource` 匹配 `professional-reference` | 不匹配 `professional-reference`;匹配 `projectOrdinaryReportMarkdown``GET /api/reports/:id` | 普通下载不得再把专业参考当 fallback |
| `professional-report-reference-route.test.ts`「cached appendix returns markdown…」 | `{ format: "markdown", markdown: "# Professional reference" }` | 增加 `documentKind: "professional_reference"`markdown 仍是该句 | 响应必须显式标成专业参考;这条夹具没有内部字段,正文保持原样 |
`personal-report-longform-md.test.ts` 的 XSS / 表格 / `skipHtml` 断言未改。报告归属与 ready 检查的测试名未改,`{ ok: true }` 回放文档不是报告形状,原样返回。
## 验证
- `frontend``.\node_modules\.bin\tsc --noEmit` 退出码 0。
- `npm run lint`0 error119 条既有 warning(未改;新投影文件无 warning)。
- 定向 `npx tsx --test``report-public-projection` 9、`consultation-report-export` 2、`personal-report-entry` 20、`personal-report-longform-md` 7、`personal-report-view` 23、`report-chart-block` 8、`professional-report-reference-route` 4、`personal-report-api` 58,全部 fail 0。
- 未跑全量 `npm test``next build`
- 无登录态,浏览器导出未验收:`docs/testing/report-public-content-20260922.md`
## Bug
BUG-999resolved(本地候选,未部署)。相关记录 BUG-011、BUG-058、BUG-188。聊天层隐藏仍在,本条记导出/API 洞,不把旧记录改成复发。
@@ -0,0 +1,10 @@
# 普通报告公开内容:浏览器导出未在本轮验收
本轮只在工作树里跑了类型检查、lint 和定向 node:test。没有登录态,也没有打开浏览器。
下面两项不能写成通过,需要一位已登录的人在 staging 上对照一份**合成或新生成**的报告再看:
- [ ] 打开 `/reports/<id>`,正文里看不到 `technique_truth``workflow_route``workflow_status``precise_timing``missing_layers`、评分或模型调试字段。
- [ ] 点「导出报告(.md)」,下载文件同样没有这些字段;`jyotish-chart` 围栏不在文件里,引擎 SVG 还在。旧报告也要抽一份看,不能只看新生成的。
专业参考接口不是这个按钮的下载来源。本轮没有用真实账号调用它。