# TASK · 内测主路径:他人报告读不到资料、AI 生成标识与免责、状态板清账、内测冒烟清单(2026-10-14) - 日期:2026-10-14 - 来源:产品 10-14 拍板「内测跑生产」「给别人出报告内测要开」;内测体检(`scratchpad/beta-compliance-20261014.md`、`beta-defects-20261014.md`)。 - 执行方:**Grok(Windows + Docker)**,分支 `codex/beta-main-path-20261014`,只推这个分支 - 验收:Claude(Linux) - BUG 编号:开工时核对 `docs/BUG_HISTORY.md` 最大号顺延(写作时最大号 BUG-1317;#56 从 1318 起、#58 预留 1328,以实际为准)。T1 沿用 **BUG-1063**;T2 新号;T3–T5 不取新号(状态与文档)。 - 串行:与 #56(Python 引擎)、#57(校正打分)、#58(Node 资源)、容量单 `TASK-beta-capacity-20261014`(限流 / 排队)**不改同一文件**;T2 改 `frontend/src/components/chat-composer.tsx` 与报告阅读组件,若同期有别的单改这两个文件,以先合入者为准、后者变基。 ## 1. 基线 - 基线 commit:`origin/staging` `48a2fadd`(开工时以最新为准,进度记录写明)。 ## 2. 事故实证(行号按符号定位) **T1 / BUG-1063 他人报告读不到资料**(状态 investigating,09-27 静态证据): - `frontend/src/lib/personal-report-worker-subject.ts::loadReportWorkerProfile` → `frontend/src/lib/subject-birth.ts::loadSubjectBirth(client, …)`,用后台 admin 客户端 `from("chart_profiles").select(CHART_SUBJECT_SELECT)`。 - admin 客户端在自托管模式下是 `set local role service_role`(`admin-client-core.ts`)。 - `frontend/supabase/migrations/20260718100000_repair_missing_chart_profiles.sql` 第 50 行 `revoke all on table public.chart_profiles from anon, authenticated, service_role;`,之后**没有任何迁移**给 `service_role` 授回 `chart_profiles` 的任何权限(已 grep 全部迁移:只有给 `authenticated` 的 select / 列级 insert / update / delete)。 - 本人报告不受影响:本人出生资料在 `profiles` 表;只有「人物档案里的其他人」走 `chart_profiles`。 - 推断后果:他人报告 worker 读资料报权限错 → `subject_unavailable` → `profile_incomplete`(可重试)→ 一直排队或最终失败退款。**未在真实库复现。** **T2 AI 生成标识**:只在协议文本里(`frontend/src/lib/legal-documents.ts`「本服务的回答与报告由人工智能生成……」);报告结尾有「不构成医疗、法律或投资建议」(`personal-report-generation.ts`);**对话界面(普通对话、校正对话)与报告阅读页顶部都没有显式标识,对话里也没有免责小字**。依据:《人工智能生成合成内容标识办法》2025-09-01 施行,要求对生成的文本内容在起始、末尾或中间适当位置添加显式提示(来源:国家网信办 https://www.cac.gov.cn/2025-03/14/c_1743654685899683.htm ;解读 https://www.pwccn.com/zh/tmt/method-identifying-synthetic-content-generated-ai-sep2025.pdf )。 **T3 / BUG-1080 新账号首次建盘只见兑换弹窗**:修复 `dec36bce` 已在 staging(已核:`git merge-base --is-ancestor dec36bce origin/staging` 成立),状态行仍写「修复在分支,未部署」。只差真机。 **T4 / BUG-283 今日星语回退**:状态 regressed,但修复版本写「已被 BUG-299 覆盖」,BUG-299 为 resolved(首屏只调 `/api/daily_guidance`)。实为状态未改。 **T5 状态板滞后**(修复已在 staging,状态行仍写「分支 / 未部署」): | BUG | 状态行现写 | 事实 | | --- | --- | --- | | 906、907 | investigating(分支已修,未部署) | `82c8aeb0` 在 staging | | 1080 | investigating(分支,未部署) | `dec36bce` 在 staging | | 1128 | 字体部分 resolved(待部署)/ JS 部分 blocked | `8112f62b` 在 staging;JS 部分 blocked 保持 | | 1244、1245、1247~1253、1255~1259 | fixed-pending-verify(分支 `codex/…`;部署前不标) | 均已部署 staging(见各自修复版本行或 CHANGELOG);1246 已撤回,不动 | | 1254 | fixed-pending-verify(需门禁连续通过) | 按门禁记录核实 | ## 3. 根因 - T1:7-18 修 `chart_profiles` 权限时按「只给登录用户」收口,没考虑后台 worker;9-27 BUG-1031 把 worker 改走 `loadSubjectBirth` 后才用到这张表;mock 客户端测不出表权限。 - T2:合规那一轮(09-30)只做了协议与同意,没有落到界面。 - T3–T5:多会话并行合入时只改代码不回填状态行。 ## 4. 决策记录(产品 2026-10-14) 1. 内测开放「给别人出报告」→ BUG-1063 必修。 2. **T1 修法选「给 `service_role` 授 `chart_profiles` 逐列只读」**,不选 SECURITY DEFINER 只读函数。理由:① 调用方是只在服务端运行的后台 worker,`service_role` 本来就读得到本人出生资料(`profiles`),把同一组列扩到他人档案不扩大信任边界;② 定义者函数对受信的服务端调用方不提供额外保护(它照样能传任何 user_id),却多一条读路径要维护、要和 `CHART_SUBJECT_SELECT` 同步两处;③ 列级授权能精确限制到 `CHART_SUBJECT_SELECT` 的列,用一条合同测试锁住「select 列 ⊆ 授权列」。只授 SELECT,不授写。 3. **T2 显式标识放法**:对话——在 `ChatComposer` 组件内输入框下方加一行小字(一个组件,普通对话与校正对话两处同时生效,`page.tsx` 不改);报告——阅读页标题下的元信息行加「AI 生成」,结尾免责保留;分享出去的报告页同样有。**推翻 `frontend/DESIGN.md`「No strip below the composer」的字面**:那条针对的是常驻 44 px 空条;本单是一行说明文字、不占工具位,DESIGN 同步改写这条并写明原因。文案建议:对话「回答由 AI 生成,仅供参考,不构成医疗、法律或投资建议。」;报告「本报告由 AI 生成,仅供参考。」(对照 VOICE,产品可改字)。 4. 隐式标识(文件元数据):下载的 PDF / Word / Markdown 写入「AI 生成」元数据——列为 T2b,可让。 5. T4:BUG-283 改为 `superseded`(被 BUG-299 取代),不改代码;首页今日星语是否正常放进冒烟清单,真机若仍失败另立新号。 6. T5 只按事实改状态行(「已部署 staging ``,真机欠」),**不得**把 fixed-pending-verify 改成 resolved,不改根因与结论。 ## 5. 硬红线 1. 迁移只做加法(`grant select (<列>) on public.chart_profiles to service_role`),对当前已部署代码向后兼容;新迁移文件名时间戳不得与现有重复;动表的轮次**必须真跑 `npm run test:db`**(AGENTS §7.6),Grok 有 Docker。 2. 不授 `service_role` 任何写权限;不改 RLS;不动 `authenticated` 现有授权。 3. 前端红线(AGENTS §7):tsc 0、lint 0 error、npm test 失败名单与基线逐名相同(看 `# cancelled` 与退出码)、build `/` Static、首屏 gzip ±2%;`page.tsx` 的 `Home()` 状态数不增长;改 UI 同提交更新 DESIGN;新文案进 VOICE。改既有断言写「原值 / 新值 / 原因」三栏(先 grep `frontend/tests/` 里按 `chat-composer` 源码文本断言的合同测试,AGENTS §7.8)。 4. 不改生时校正打分、不改对话提示词、不改报告生成内容(只加阅读页与组件上的标识)。 5. 不写真实用户资料;测试只用公开名人或虚构盘。不改 workflow、不推 staging。 ## 6. 任务分解 | 项 | 内容 | 验收 | | --- | --- | --- | | T0 | **先红后绿**:在 `frontend/tests/database-chart-subject.test.ts`(或新建 `database-report-worker-subject.test.ts`)加用例:用后台 admin(`service_role`)客户端对另一个人物档案调用 `loadReportWorkerProfile` / `loadSubjectBirth`,期望读到资料。改前在本地 PostgreSQL(Docker `test:db`)跑出**权限拒绝**并把错误原文写进进度记录 | 进度记录有改前红的输出;若改前竟然是绿的(说明另有授权),停下回报,不做 T1 | | T1 | 新迁移:`grant select () on table public.chart_profiles to service_role;`。新增合同测试:解析 `CHART_SUBJECT_SELECT` 与迁移里的授权列,断言前者 ⊆ 后者(以后加列忘了授权会红)。BUG-1063 记录补根因确认、修复、验证 | T0 用例转绿;`npm run test:db` 全量与基线比对;合同测试;BUG-1063 → fixed-pending-verify(部署 + 真机给别人出一份报告后才 resolved) | | T2 | 对话:`ChatComposer` 内输入框下方一行说明(`--type-overline`、`--color-ink-tertiary`,移动端不换行溢出,不挡发送键)。报告阅读页(`personal-report-page.tsx` / `personal-report-document-view.tsx` 标题区)与分享页加「本报告由 AI 生成,仅供参考。」。中英文两版(英文:「AI-generated. For reference only; not medical, legal or financial advice.」/「This report is AI-generated and for reference only.」) | 组件测试:两处对话面都渲染说明;报告中英文都有;移动端快照或断言 class;DESIGN、VOICE 更新;真机清单一条 | | T2b | 下载文件写入「AI 生成」元数据(PDF Producer/Subject、DOCX core properties、Markdown 首行注释——按现有导出实现能力取舍) | 单元测试读回元数据;做不到的格式写进进度记录 | | T3 | BUG-1080 状态行改为「修复 `dec36bce` 已部署 staging,真机欠」;冒烟清单收录 | 文档 | | T4 | BUG-283 状态改 `superseded(由 BUG-299 取代)`;冒烟清单收录「首页今日星语」 | 文档 | | T5 | 按 §2 表格逐条改状态行;每条写部署证据(提交号在 staging、对应部署记录或 CHANGELOG 行);1254 按门禁记录判断 | 文档;`tests/test_repo_privacy_markers.py` 过 | | T6 | 冒烟清单:把 `scratchpad/beta-smoke-checklist-20261014.md` 草稿(由 Claude 随单附上)落成 `docs/testing/beta-smoke-20261014.md`,补上 T1/T2 的条目编号 | 文档 | ## 7. 让步顺序 T1 不可让(内测要开他人报告)。T2 对话与报告显式标识不可让(法规);T2b 可让。T3–T6 文档项不可让(产品需要一张真实的状态板),但不阻塞 T1/T2 的合入。 ## 8. 验收门 - `npm run test:db`(Grok,Docker)全量,与 staging 基线比对;Claude 侧用本机 PostgreSQL 替身复跑 T0/T1 相关用例。 - 前端 tsc / lint / npm test / build / gzip。 - Python 不动;若碰到 `scripts/`,跑快速门全量。 ## 9. 开工前置 ``` git fetch origin --prune git status -sb git worktree add -b codex/beta-main-path-20261014 .worktrees/beta-main-path-20261014 origin/staging grep -rn "chat-composer" frontend/tests | head # 找按源码文本断言的合同测试 ``` ## 10. 需产品提供 / 拍板 1. 同意 T1 修法(`service_role` 逐列只读)——若更倾向只读函数,告诉我,改单。 2. 标识文案:对话「回答由 AI 生成,仅供参考,不构成医疗、法律或投资建议。」、报告「本报告由 AI 生成,仅供参考。」——同意或改字;以及同意改 DESIGN「输入框下方不放东西」这条的字面。 3. 冒烟清单由谁走:产品用 staging 测试邮箱 + 固定验证码真机走,还是先由 Claude 用接口层(无浏览器)走一遍可接口化的条目(他人报告、「你好」不扣点、限流),再由产品补真机部分。 4. 生产首发后,冒烟清单是否在生产再走一遍(建议走前 5 条,用产品自己的账号)。 ## 11. 产品答复(2026-10-14,覆盖上文对应建议值) 1. 标识文案**同意**:对话「回答由 AI 生成,仅供参考,不构成医疗、法律或投资建议。」;报告「本报告由 AI 生成,仅供参考。」 2. BUG-1063 修法按决策记录 2(逐列只读授权)。