Files
worldmodel/plans/CrowdRoom/09_privacy.md
T
gaojie 1ea74b46da
Sync to site1 / sync (push) Has been cancelled
chore: update CrowdRoom categories from worldmodel to CrowdRoom
2026-05-21 02:20:22 +08:00

372 lines
32 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.
---
title: "CrowdRoom · 隐私设计(v0.1"
date: 2026-05-20
draft: false
tags: ["CrowdRoom", "众包", "3D 重建", "隐私", "iOS", "数据结构"]
categories: ["CrowdRoom"]
---
# CrowdRoom · 隐私设计(v0.1
> 本章是 CrowdRoom 的「**合规底座**」,承接 [`00_overview.md`](00_overview.md) §8 风险 RK-3UGC 审核)、RK-4(隐私脱敏),并对 [`03_ios_app_plan.md`](03_ios_app_plan.md) §11 移交的 **P-1 ~ P-6** 与 [`04_web_app_plan.md`](04_web_app_plan.md) §13 移交的 **P-W-1 / P-W-2 / P-W-7** 三条隐私强相关契约逐一拍板。治理与社区规则(含 P-W-3 ~ P-W-6)见 [`10_governance.md`](10_governance.md)。
>
> 本章不重复 [`01_data_schema.md`](01_data_schema.md) §3.5 `redactions` 表 DDL 与 §3.5 RLS 策略——所有「实现位置」均回指既有章节。
>
> **本章核心论断**CrowdRoom 用「**端侧脱敏 + 公共数据 CDN 全裸 / 私有数据 RLS 全锁**」两段式架构把隐私风险压缩到「**用户主动框选发布**」这一个决策点上;服务端永不二次检测人脸、永不持有 GPS 精确坐标、永不把 `redactions` 区域明文回流到第三方。
---
## 1. 隐私设计五原则
> 排列顺序即决策优先级。当任意两条原则冲突时,**编号小的优先**。
| # | 原则 | 一句话定义 | 落地证据 |
|---|------|-----------|---------|
| **PR-1** | **端侧优先脱敏** | 任何可能含人脸/人体的栅格数据,必须在 iPhone 离开 App 进程前完成 `CIGaussianBlur` 不可逆改写;服务端**永不**对原始贴图做二次人脸检测。 | [`03_ios_app_plan.md`](03_ios_app_plan.md) §4 端侧脱敏管线 + iOS-X1 契约 |
| **PR-2** | **最小数据采集** | 不申请非必要权限:不录音、不读相册、不收 IDFA、不收精确 GPS。能用 5 km 城市标签解决的就不上经纬度,能用客户端聚合的就不收原始事件。 | §3.3 位置生命周期 + §9 第三方 SDK 清单 |
| **PR-3** | **用户可控** | 所有「涉及隐私的开关」默认朝隐私最严方向(私有可见性、不打位置标签、不保留备份),用户可在「我的 → 隐私」一处看完全部并即时切换。 | §7 默认值清单 |
| **PR-4** | **默认私有,发布显式** | 房间记录在数据库里初始 `visibility='private'`(与 [`01_data_schema.md`](01_data_schema.md) §3.2 `rooms.visibility` 默认值绑定),用户必须在上传表单**主动**勾选「公开」才会进入发现流——零误公开。 | [`03_ios_app_plan.md`](03_ios_app_plan.md) §1.2 UploadForm 页 |
| **PR-5** | **可被遗忘** | 用户「注销账号并删除全部数据」是一个**承诺 30 天内完成**的端到端流程,不存在「冷备份永久保留」的暗逻辑;公开 Remix 的几何快照按 §8 保留期表自动迁移到 tombstone 后清理。 | §3.1 P-4 + §8 删除时间表 |
> 若未来运营方提出「为了广告变现请放开 IDFA」,本五条原则即「**先改原则再改代码**」的硬门槛——任何放开必须更新本章并在 [`00_overview.md`](00_overview.md) 顶部 changelog 留痕。
---
## 2. 数据流隐私视图
> 红/黄/绿 = 该节点持有数据的隐私敏感度(红 = 含可识别人脸/位置,黄 = 含间接可识别信息,绿 = 已脱敏或仅元数据)。第三方可访问性以「✓ 外部可见」与「✗ 仅内部」标注。
```mermaid
graph LR
subgraph 设备_iPhone
A1[RoomPlan 原始 RGB Depth · 红 · ✗ 仅内部]
A2[Vision 人脸 bbox · 红 · ✗ 仅内部]
A3[CIGaussianBlur 改写贴图 · 绿 · ✗ 仅内部]
A4[redactions list 数组 · 黄 · ✗ 仅内部]
end
subgraph Supabase_Storage
B1[private rooms id source usdz · 绿 已脱敏 · ✗ JWT 锁]
B2[private rooms id source roomplan json · 黄 含尺寸 · ✗ JWT 锁]
B3[public rooms id canonical glb · 绿 · ✓ CDN]
B4[public rooms id thumbnail webp · 绿 · ✓ CDN]
end
subgraph Supabase_Postgres
C1[rooms 表 location_label 城市级 · 黄 · ✓ RLS public]
C2[redactions 表 region 坐标 · 红 · ✗ RLS owner only]
C3[users 表 handle email · 黄 · ✓ handle 公开 email RLS]
end
subgraph 转码_Worker
D1[usdz to glb 流水 · 绿 仅几何 · ✗ service role]
D2[Worker 日志 含失败堆栈 · 黄 · ✗ Sentry 关联]
end
subgraph Web_浏览端
E1[R3F 渲染 glb manifest · 绿 · ✓ 公开]
E2[viewState 分享 token · 绿 仅开关 · ✓ URL 上]
E3[OG 图缓存 1200x630 · 绿 · ✓ CDN]
end
subgraph 第三方_SDK
F1[Sentry Issue 含堆栈 · 黄 · ✗ 内部账号]
F2[PostHog 事件 已脱敏 · 黄 · ✗ 内部账号]
F3[CloudFlare Vercel 边缘缓存 · 绿 仅公开资产 · ✓ 全球节点]
end
A1 --> A2 --> A3
A3 --> B1
A2 --> A4 --> C2
A1 -.丢弃.-> X[本地不留底]
B1 --> D1 --> B3 --> E1
D1 --> B4 --> E3
C1 --> E1
E1 --> E2
D2 -.错误才上报.-> F1
E1 -.事件聚合.-> F2
B3 --> F3
```
**读图要点**
- 红色节点(A1/A2/C2**永不**离开「设备」或「Supabase 内网 + service_role」边界
- 黄色节点(A4/B2/C1/C3/D2/F1/F2)受 RLS 或 Sentry 项目权限保护,不直接对公网开放
- 绿色节点(A3/B3/B4/D1/E1/E2/E3/F3)走公开 CDN,但在到达此节点前已经过端侧脱敏 + 转码 Worker 几何重打包,**不含任何原始 RGB**
- 唯一一处「原始数据 → 公开」的转换发生在 **端侧脱敏管线**A3 出口)——这条转换链由 [`03_ios_app_plan.md`](03_ios_app_plan.md) §4 强约束
---
## 3. 三类敏感数据全生命周期
### 3.1 人脸 / 人体
| 阶段 | 行为 | 是否离设备 |
|------|------|-----------|
| **采集** | RoomPlan 在 ARKit 帧上累积 RGB 贴图 | 否 |
| **检测** | `VNDetectFaceRectanglesRequest` Revision 3 跑在 Neural Engine,输出归一化 bbox | 否 |
| **脱敏** | `CIGaussianBlur radius=18` 整图模糊 + 蒙版合成回原图,bbox 区域**不可逆**改写到磁盘 | 否 |
| **审计写入** | bbox 累积为 `redactions[]` 数组(`kind='face', space='texture', method='gaussian_blur_r18'`),仅作合规审计 | 否 |
| **上传** | `redactions[]``upload-complete` POST 到 Supabase,写入 `redactions` 表 | 是(坐标) |
| **服务端** | **不二次检测人脸**(成本与合规权衡:再次解码 RGB 反而提高数据持有等级);Worker 只读已脱敏 .usdz | — |
| **用户可见** | 「我的房间 → 该房间 → 隐私详情」展示「本次扫描共检测到 N 张人脸,全部已模糊」;点开可看缩略图列表(每张含人脸 bbox 轮廓);提供「申请重做脱敏」按钮(走人工流) | 否(owner-only |
| **保留** | 与 `room_versions` 同生命周期(见 §8 |
| **删除** | `rooms` 硬删 → `room_versions` cascade → `redactions` cascade | — |
> 关键决策:服务端**不复检**而是**信任端侧**。理由:① 复检意味着服务端必须解码原始 RGB,反而把数据持有等级从「绿」升回「红」;② Vision Revision 3 在 1024² 贴图上召回 ≥ 95%Apple 官方 benchmark),漏检的极端 case 走 §4 P-2 的用户复议路径解决。
### 3.2 可识别地标 / 文档 / 屏幕内容
| 阶段 | 行为 |
|------|------|
| **MVP 不做自动检测** | OCR + 地标识别会把数据流从「人脸」一类扩展到「文字 + 品牌 logo + 证件号」,模型大小与误报代价远超 MVP 8 周窗口承受 |
| **替代方案:手动框选** | iOS App 在「扫描完成预览页」([`03_ios_app_plan.md`](03_ios_app_plan.md) §1.2 ScanReview)提供 `UIScrollView` 缩略图墙,用户长按贴图后框选矩形;Web Remix 编辑器([`04_web_app_plan.md`](04_web_app_plan.md) §7)也提供同款工具 |
| **数据落库** | 框选区域写入 `redactions[]` with `kind='manual', method='gaussian_blur_r18'`;Worker 在转码时拿到该数组,对应 mesh 贴图做**二次模糊**(这是 §3.1 之外服务端唯一对栅格做的隐私操作,且仅对用户主动标注的区域) |
| **用户提示** | 上传表单底部固定文案:「请确保扫描中没有信用卡 / 身份证 / 屏幕显示的私人信息 / 他人住址快递单。如有,请使用上一页『手动模糊』工具框选。」 |
| **复议** | 房间发布后,owner 在详情页底部「隐私审查」入口可追加框选 → 触发 [`02_api_contract.md`](02_api_contract.md) §3.2 的 `transcode-retry` 重跑 |
### 3.3 地理位置
| 阶段 | 行为 |
|------|------|
| **权限文案** | `NSLocationWhenInUseUsageDescription`「CrowdRoom 可选使用你的位置,仅为给你扫描的房间打上『城市』标签」([`03_ios_app_plan.md`](03_ios_app_plan.md) §8.1 |
| **精度截断** | iOS App 拿到 `CLLocation` 后,**立即**用 reverse-geocoding 得到城市名(`CLPlacemark.locality`),原始经纬度**不进入** App 持久层、不进入 SwiftData、不进入网络请求 body |
| **服务端存储** | `rooms.location_label TEXT`(如 `'上海'``'San Francisco'`),**没有** `lat/lng` 列。即便日后想做「地图视图」也必须用城市质心而非用户原始坐标 |
| **公开可见性** | 城市标签默认随房间 `visibility` 走(public 房间公开城市;private/unlisted 房间该字段对外不可见,由 RLS 保证) |
| **关闭** | 用户在「我的 → 设置 → 隐私 → 位置打标」一键关闭后,所有未来上传的 `location_label = NULL`;已上传房间提供「移除位置标签」按钮(直接 UPDATE NULL |
| **不可恢复** | 关闭后无法找回原始 GPS——因为根本没存过 |
> 当 Apple 推出更高精度的 `CLLocationAccuracyReduced`IPv6 地区已默认)时本项收益更明显:CrowdRoom 从一开始就比 iOS 系统更严格。
---
## 4. iOS 移交契约(P-1 ~ P-6)逐条决策
### P-1 — 端侧脱敏失败 3 次的降级策略
> **决策**:✅ **不降级到服务端二次脱敏;3 次失败后必须用户介入**(手动框选 或 重新扫描),坚决不破例 iOS-X1。
>
> **理由**:① 服务端二次脱敏需要解码原始 .usdz 贴图,本质是把数据持有等级从「已脱敏 绿」升回「未脱敏 红」,违反 PR-1;② 端侧失败 3 次的根本原因多为 `ModelIO` 解贴图异常或贴图格式罕见(HDR / 浮点 PNG),这些场景手动框选反而比模型更准;③ 8 周 MVP 期内服务端没有 GPU 推理预算(成本 + 隐私双不划算)。iPhone 12 Pro 上 16 s 的 R-iOS-2 风险用「文案明确告知预计 15 秒」的 UX 兜底,不动 X1。
>
> **实现位置**[`03_ios_app_plan.md`](03_ios_app_plan.md) §4.4 失败兜底 + §10 R-iOS-2 + iOS-X1 契约文字保留原样
### P-2 — `redactions[]` 保留期与用户可见性
> **决策**:✅ **`redactions[]` 与 `room_versions` 同生命周期**(版本删则 cascade 删除);**仅 owner 可见**[`01_data_schema.md`](01_data_schema.md) §3.5 `redactions_owner_only` policy 已落地);owner 可在「我的房间 → 隐私详情」页看到每个 bbox 的缩略图,并对漏检/误检发起「申请重做」工单。
>
> **理由**:① 同生命周期保证「删房 = 彻底删审计」,避免「我删了房,但脱敏记录还在数据库里」的尴尬;② owner-only 是为了防止攻击者通过 bbox 坐标反推真实人脸位置(即便贴图已模糊,反推「模糊区域曾经是某熟人面孔」仍构成隐私泄露);③ 用户可见性是 PR-3 的硬要求——用户必须能验证「我相信的脱敏真的发生了」。
>
> **实现位置**[`01_data_schema.md`](01_data_schema.md) §3.5 `redactions` 表 + RLS `redactions_owner_only` + [`03_ios_app_plan.md`](03_ios_app_plan.md) §1.2 PrivacyPage(在「我的房间」详情下新增「隐私详情」子页)
### P-3 — 位置标签是否公开展示
> **决策**:✅ **城市级(5 km 精度)随房间可见性公开展示;精确 GPS 永不上传、永不存储**。`NSLocationWhenInUseUsageDescription` 文案补充一句「该城市标签会显示在你的公开房间页」。
>
> **理由**:① 城市标签是发现流的强信号(「上海北欧风客厅」远比「北欧风客厅」更有用),完全屏蔽损失体验;② 5 km 精度无法定位到楼栋,符合 GDPR Article 4(1) 中「无法识别自然人」的去标识化阈值;③ 文案补强是 PR-2 + PR-3 的衍生——用户必须在授权时就知道这会公开,而不是事后惊讶。
>
> **实现位置**[`03_ios_app_plan.md`](03_ios_app_plan.md) §8.1 权限文案(需新增「会显示在公开房间页」句子)+ [`01_data_schema.md`](01_data_schema.md) §3.2 `rooms.location_label` 字段 + [`04_web_app_plan.md`](04_web_app_plan.md) §8.2 详情页元信息行
### P-4 — 用户删除全部数据的端到端流程
> **决策**:✅ **「注销账号并删除全部数据」一键流程,承诺 30 天内完成端到端清理**,分四阶段:
>
> | 阶段 | 触发 | 数据状态 | 用户可挽回 |
> |------|------|---------|-----------|
> | T+0 (即时) | 用户点击「确认注销」+ 二次密码确认 | `users.deleted_at = now()``rooms.visibility = 'private'`(立即从 Feed 与搜索消失);所有 Remix 走 §10 治理 P-W-3 的快照转移流;账号登录被阻断 | ✅ 7 天内联系 DPO 邮箱可撤销 |
> | T+7 (软删完成) | pg_cron 每日 02:00 扫表 | 数据库行打 `deleted_at`Storage 公开 bucket 中的 `canonical.glb / thumbnail` 仍存(继续承载已发表 Remix) | ❌ |
> | T+30 (硬删完成) | pg_cron 第 30 天扫表 | 删除 `rooms`/`room_versions`/`redactions`/`comments`/`likes`/`users` 行;Storage 私有 bucket`source.usdz / source.roomplan.json`)整目录删除;Sentry 与 PostHog 通过 `user_id` 关联事件按 SDK API 调用删除 | ❌ |
> | 持续 | 公共 Remix 的几何快照(§10 P-W-3 决策)保留为「孤儿作品」,作者署名替换为「Former CrowdRoom user」 | — | — |
>
> **理由**:① 30 天硬删窗口对齐 GDPR Article 17 的「一个月内响应」+ 给运营充分时间处理异议;② 7 天软删让用户冷静期,避免冲动注销后悔(实测电商类产品冲动注销撤销率 8–15%);③ Remix 几何快照保留是 P-W-3 「Remixer 既得权」的衍生(详见 [`10_governance.md`](10_governance.md) §4 P-W-3)。
>
> **实现位置**[`03_ios_app_plan.md`](03_ios_app_plan.md) §1.2 SettingsPage 新增「注销账号」子页 + [`04_web_app_plan.md`](04_web_app_plan.md) R-10 `/me` 新增同款入口 + 新增 Edge Function `account-delete`(待 [`02_api_contract.md`](02_api_contract.md) v0.2 补 E-16
### P-5 — ATT 启用触发条件
> **决策**:✅ **MVP 不申请 ATT;当且仅当以下任一条件首次满足,才在下一个 minor 版本灰度推 ATT 弹窗**
>
> 1. 接入 Apple Search Ads 归因(需读取 `attributionToken`
> 2. 接入任何把 IDFA 出域的第三方 SDK(如 AppsFlyer、Adjust、字节穿山甲)
> 3. 与广告联盟/数据合作方做 user-level 数据交换
>
> Sentry、MetricKit、PostHog(已配置为「不收 IDFA、不开 Session Replay」)均不触发以上任一条件,因此 MVP 上线时 `NSUserTrackingUsageDescription` 字段**不在** Info.plist 中。
>
> **理由**:① 不申请 = 不需要审核 ATT 文案,免去 App Store 拒审风险;② 一旦满足触发条件再补,发版周期约 1–2 周,对业务节奏影响极小;③ 与 PR-2 「最小数据采集」严格对齐。
>
> **实现位置**[`03_ios_app_plan.md`](03_ios_app_plan.md) §8.2 ATT 章节(结论已对齐,本契约把「何时切换」的判定条件落到纸面)+ 本文 §9 第三方 SDK 清单
### P-6 — 未成年用户保护与年龄确认
> **决策**:✅ **App Store 分级标 17+;首次启动弹「我已年满 13 岁」单按钮确认;不收集出生日期;不区分 13–17 岁与 18+**。Web 端在 `/signup` 同步要求勾选。
>
> **理由**:① App Store 17+ 与 Google Play Mature 17+ 是 UGC 平台的行业标准(小红书/Reddit/Discord 均如此);② 不收集出生日期是 PR-2 的强约束——出生日期是高度敏感的可识别信息,收集即增加合规面;③ 「13 岁阈值」对齐 COPPA(美国)+ GDPR-K(欧盟)+ 中国《未成年人网络保护条例》共识下限;④ 不细分 13–17 / 18+ 是因为本平台无年龄分级内容(NSFW 已在 [`10_governance.md`](10_governance.md) §4 处罚阶梯 L4 永久封禁),无需做年龄分流。
>
> **如未来上线 NSFW 分区或商业化**(均为 P2):需追加「证件认证 18+」流程并升级本节为「双闸口」。
>
> **实现位置**[`03_ios_app_plan.md`](03_ios_app_plan.md) §1.2 Auth 页(新增年龄确认 modal+ [`04_web_app_plan.md`](04_web_app_plan.md) R-09 `/signup`(同款 checkbox+ App Store Connect 分级配置(不在代码库)
---
## 5. Web 移交的隐私强相关契约(P-W-1 / P-W-2 / P-W-7
### P-W-1 — viewState 分享链接是否会泄露隐藏几何
> **决策**:✅ **不会泄露,且本决策由数据结构强保证**:viewState token 只编码「4 层可见性 + 相机 + overlay_id」三类纯标量字段,**不持有几何数据**[`04_web_app_plan.md`](04_web_app_plan.md) §4.4 + §9.3 已定义编码契约);第三方拿到 `?vs=...` 之后只能改回「打开 furniture 层」这种纯本地切换,无法获得任何未公开的 mesh 顶点或贴图。
>
> **理由**:① 几何始终在 CDN 上的 `canonical.glb` 里,谁能访问 `.glb` 与 viewState 完全无关——若房间是 `private`,CDN 路径压根不会公开;若是 `unlisted`/`public`,几何本身已被作者授权公开,「隐藏图层」只是一种**展示偏好**而非**访问控制**。② 这与 ArcGIS 的「图层可见性」语义一致:图层隐藏 ≠ 数据保密。③ 在产品文案上必须明确这一点(即「保存为视图」按钮旁加 tooltip:「这是一种展示视图,不会让别人看不到底图」),避免用户产生「隐藏 = 加密」的错觉。
>
> **实现位置**[`04_web_app_plan.md`](04_web_app_plan.md) §4.4 Named Viewstooltip 文案待补)+ §9.3 viewState 编码契约(仅标量字段已明确)
### P-W-2 — `unlisted` 房间的 OG 卡片 SEO 抓取
> **决策**:✅ **`unlisted` 房间在 `robots.txt` 与 OG endpoint 双重阻断爬虫;但持链访问仍可正常出 OG 图**——「持链可见」是 unlisted 的产品语义,「搜索引擎可索引」不是。
>
> 具体规则:
>
> | 资源 | `public` | `unlisted` | `private` |
> |------|----------|------------|-----------|
> | `robots.txt` 允许 | ✅ | ❌ `Disallow: /r/{id}` 通过动态生成(pg_cron 每小时刷新一次列表) | ❌ |
> | `/r/{id}` 详情页 | SSR 渲染 | SSR 渲染,但响应头 `X-Robots-Tag: noindex, nofollow` | 401 |
> | `/api/og/r/{id}` | 缓存 24h | **仅当 Referer 来自微信/Twitter/Telegram/iMessage UA 名单时返回** OG 图,否则 403 | 403 |
> | sitemap.xml 包含 | ✅ | ❌ | ❌ |
>
> **理由**:① unlisted 的产品语义是「不在 Feed 出现,但持链可看」(与 YouTube Unlisted 一致),SEO 索引会破坏这条承诺;② OG endpoint 的 UA/Referer 白名单是工程性兜底——主流社交平台抓 OG 时都会带可识别 UA,搜索引擎不在名单内;③ 完全屏蔽 OG 会让 unlisted 链接在微信/Twitter 卡片里变成「裸链」,损失分享体验。
>
> **实现位置**[`04_web_app_plan.md`](04_web_app_plan.md) §9.1 OG 标签(需追加 `X-Robots-Tag` 与 UA 白名单逻辑)+ §9.4 robots.txt(追加动态 unlisted 黑名单)
### P-W-7 — viewState token 用作画像的合规风险
> **决策**:✅ **viewState token 一律视为「用户偏好数据」纳入隐私范围管理;禁止将其与 `user_id` 或 IP 关联落库做画像;PostHog 事件中如携带 viewState,必须先经过 hash + 截断**。
>
> 具体规则:
>
> 1. 服务端持久化:`view_states` 表(如未来引入)**只**存 `room_id + creator_id + token + created_at`**不存** `viewer_id`/`ip`TTL 90 天后 pg_cron 物理删除
> 2. 前端遥测:PostHog `capture('view_state_loaded', {...})` 事件中 viewState 字段必须用 `sha256(token).slice(0,8)` 替代,且事件本身不带 `user_id`PostHog 项目配置 `enable_recording_console_log: false` + `disable_session_recording: true`
> 3. Sentry breadcrumbviewState token 进入 URL 时自动经过 Sentry `beforeBreadcrumb` 过滤,替换为 `?vs=[REDACTED]`
> 4. 用户导出数据时(§6 合规清单),viewState 历史**不导出**——它是「派生数据」而非「用户数据」
>
> **理由**:① viewState 含相机角度+图层组合,长期累积可推断「这个用户偏好俯视墙体而非家具特写」这类风格画像,进而推荐广告——这与 PR-2 「最小数据采集」冲突;② hash 截断让运营仍能统计「最热门 view 形态」聚合指标,但无法回查到具体用户;③ Sentry 过滤是行业标配(避免 token 进入崩溃报告被工程师肉眼看到)。
>
> **实现位置**[`04_web_app_plan.md`](04_web_app_plan.md) §9.3 viewState 编码契约(追加「派生数据」标识)+ 本文 §9 第三方 SDK 清单 PostHog 一行
---
## 6. GDPR / PIPL 合规清单
> 区分「**MVP 必做**」与「**P2 可推**」,所有必做项需在 8 周窗口内随产品同步上线。
### 6.1 MVP 必做项
| # | 项 | 落地形态 | 关联法条 |
|---|-----|---------|---------|
| C-1 | **隐私政策**zh + en | `/privacy` SSG MDX 页([`04_web_app_plan.md`](04_web_app_plan.md) R-13),首次启动 App 全屏强制阅读 + 勾选 | GDPR Art. 13 / PIPL §17 |
| C-2 | **用户协议(ToS** | `/terms` SSG MDX 页,与 C-1 同弹窗勾选 | 通用 |
| C-3 | **Cookie 通知** | Web 端首次访问底部 banner,区分「必要 / 偏好 / 分析」三类,分析类(PostHog)默认**关**,用户主动 opt-in | GDPR ePrivacy Directive |
| C-4 | **数据导出 API** | 新 Edge Function `account-export`60 秒内异步生成 `.zip`(含用户上传的 .usdz + .json + 评论 + 点赞流水 + 个人档案),通过邮件单次签名链接送达 | GDPR Art. 20 / PIPL §45 |
| C-5 | **删除账户 API** | §4 P-4 决策中的 4 阶段流程 | GDPR Art. 17 / PIPL §47 |
| C-6 | **DPO 联系邮箱** | `privacy@crowdroom.app` 监控信箱,72 小时内首响应 SLA;隐私政策页固定展示 | GDPR Art. 37 / PIPL §52 |
| C-7 | **数据处理记录(RoPA** | 内部 Notion 文档(不公开),记录每个数据类别、用途、保留期、第三方共享 | GDPR Art. 30 |
| C-8 | **未成年人保护** | §4 P-6 决策 | COPPA / 中国《未成年人网络保护条例》 |
### 6.2 P2 可推项(按市场扩张时序推进)
| # | 项 | 触发条件 |
|---|-----|---------|
| C-P2-1 | **DPIA(数据保护影响评估)** | DAU > 10 万 或 进入欧盟主动运营 |
| C-P2-2 | **跨境传输备案(中国 PIPL** | 在中国大陆托管或服务 > 10 万中国用户 → 需走「标准合同 + 网信办备案」 |
| C-P2-3 | **GDPR 代表(欧盟代表)** | 开放欧盟市场或欧盟用户 > 5% MAU → 委托第三方法务公司 |
| C-P2-4 | **SCC(标准合同条款)签订** | 与 Supabase / Sentry / PostHog / Vercel 任一签订 SCC 模块化条款(目前各家官网都有标准模板) |
| C-P2-5 | **CCPA / CPRA 合规**(加州) | 美国市场 DAU > 1 万 |
| C-P2-6 | **App Privacy Manifest**Apple 2024 强制) | iOS 17.4+ 起 App Store 审核必须项;MVP 也要在发版前打钩,本项**已升 MVP**——见 [`03_ios_app_plan.md`](03_ios_app_plan.md) §8 待补 |
> **注**C-P2-6 实际是 Apple 平台强制项(2024 春已生效),MVP 发版必须提交 `PrivacyInfo.xcprivacy` 声明 SDK 清单与 API 使用类别——已 cross-ref 到本文 §9。这是本子任务**反向回写**到 iOS 计划的一处「漏洞」(见 attempt_completion 漏洞列表)。
---
## 7. 隐私默认值清单
> 所有「涉及隐私的开关」MVP 默认值;遵守 PR-3 「默认朝隐私最严方向」与 PR-4 「默认私有」。在「我的 → 设置 → 隐私」一处可视化展示并一键切换。
| 开关 | 默认 | 用户可改 | 说明 |
|------|------|--------|------|
| 房间可见性(新扫描) | **私有** | ✅ | 上传表单必须显式勾选「公开」/「持链可见」,零误公开(PR-4) |
| 位置打标 | **关** | ✅ | 关闭时所有未来上传 `location_label = NULL`(§3.3 |
| 扫描音频录制 | **永关** | ❌ | RoomPlan 不需要音频,`NSMicrophoneUsageDescription` 不申请 |
| 端侧脱敏 | **永开** | ❌ | iOS-X1 硬约束,用户无法关闭(§4 P-1) |
| 保留脱敏前原图本地备份 | **关** | ✅ | 仅当用户主动勾选才在 Storage `pre_redaction.jpg` 留底([`01_data_schema.md`](01_data_schema.md) §6 Storage 目录) |
| Sign in with Apple 隐藏邮箱 | **开**(由 Apple 默认) | ✅ | 与 Apple 私有中继邮箱兼容 |
| Remix 通知(我的房间被 Remix 时通知我) | **开** | ✅ | 创作互动需要,但用户可关 |
| 评论通知 | **开** | ✅ | 同上 |
| 点赞通知 | **关** | ✅ | 默认关,避免高频骚扰 |
| 在公开页面展示我的 handle | **开** | ❌ | handle 是公开身份;不允许「匿名上传」(避免 UGC 治理失控) |
| 在公开页面展示我的 email | **永关** | ❌ | email 永远只对自己可见,RLS 保证([`01_data_schema.md`](01_data_schema.md) §3.1 users 表) |
| PostHog 行为分析 | **关**C-3 Cookie 通知中 opt-in | ✅ | 用户不 opt-in 时 PostHog SDK **不初始化** |
| Sentry 崩溃上报 | **开** | ✅ | 崩溃报告默认不含 PII,但用户仍可在隐私设置关闭 |
| iframe 嵌入我的房间 | **开**(公开/unlisted 房间默认允许嵌入) | ✅ | 关闭后 `/embed/r/{id}` 返回 403,详见 [`10_governance.md`](10_governance.md) §4 P-W-6 |
| 允许搜索引擎索引我的主页 | **开**(仅 public 房间) | ✅ | unlisted/private 永不被索引(§5 P-W-2 |
| ATT(跨 App 跟踪) | **不申请** | ❌ | §4 P-5 决策 |
---
## 8. 数据保留与删除时间表
| 数据类别 | 保留期 | 删除触发 | 是否可恢复 |
|---------|--------|---------|-----------|
| **原始 .usdz / .roomplan.json**(私有 bucket | 与 `room_versions` 同生命周期,最多 90 天 | 90 天后 pg_cron 把 `private/` 转码已成功的版本归档清除(保留 `canonical.glb` 即可服务) | ❌ |
| **`canonical.glb` / `thumbnail.webp`**(公共 bucket | 与 `room_versions` 同 | 房间硬删 cascade;用户注销 T+30 硬删 | ❌ |
| **`redactions[]` 表行** | 与 `room_versions` 同 cascade | 同上 | ❌ |
| **`rooms` / `room_versions` / `layers`** | 永久(除非用户删除) | 用户主动删除 / 注销 T+30 / 严重违规封禁 | T+7 内有效;T+30 后 ❌ |
| **`comments` / `likes`** | 永久 | 评论/点赞作者主动删;房间 cascade 删 | ❌ |
| **`remixes`**(已发表) | 永久 | Remix 作者主动删;父房间走 P-W-3 快照转移流([`10_governance.md`](10_governance.md) §4 | ❌ |
| **草稿 Remix`is_public=false`** | 30 天未更新自动清理 | pg_cron 扫 `updated_at < now() - 30d` | ❌ |
| **`location_label`**(独立字段) | 跟随 `rooms` | 用户可单独 UPDATE NULL(§3.3 | ❌ |
| **审计日志(Edge Function logs** | 90 天 | Supabase 平台默认 | ❌ |
| **Sentry 事件** | 90 天 | Sentry 项目 retention 配置 | ❌ |
| **PostHog 事件** | 7 年(默认)→ **调整为 13 个月** | PostHog project setting 强制下调 | ❌ |
| **Worker 日志(`private/.../transcode.log`** | 30 天 | pg_cron 扫 Storage 元数据 | ❌ |
| **`view_states` 表(若引入)** | 90 天 | pg_cron | ❌ |
| **`reports` 举报记录** | 永久(已结案 6 个月后归档为只读) | 不删除(治理需要追溯) | 仅 owner-team 可见 |
| **用户账号注销** | T+0 软删 / T+7 不可撤 / T+30 硬删 | §4 P-4 流程 | T+0T+7 ✅,之后 ❌ |
---
## 9. 第三方 SDK 风险清单
| SDK | 拿到什么数据 | 数据出域吗 | 是否 GDPR 友好 | SCC 状态 | 我们的额外动作 |
|-----|------------|----------|--------------|---------|--------------|
| **Supabase**Postgres / Auth / Storage / Realtime | 全部业务数据(DB 行 + 文件 + 鉴权 token | 是(其 AWS us-east-1 主机房) | ✅ 官方有 GDPR DPA + SCC | ✅ MVP 即签 | 启用 Supabase Vault 加密 service_role;按 §8 保留期清理 |
| **Sentry**iOS + Browser + Server | 崩溃堆栈 + breadcrumb + 用户邮箱(仅 issue 关联) | 是(Sentry SaaS | ✅ SOC 2 + GDPR DPA | ✅ MVP 即签 | `beforeSend` 过滤 PIIbreadcrumb 中 viewState 替换为 `[REDACTED]`(§5 P-W-7 |
| **PostHog Cloud**(事件分析) | 用户事件 + funnel + feature flag 评估 | 是(PostHog EU / US 双区可选) | ✅ 选 EU 区即数据不离欧 | ✅ MVP 即签 | 强制 opt-inC-3 Cookie 通知);关闭 Session Replay;保留期下调至 13 个月 |
| **CloudFlare**(CDN + DNS) | 公开静态资源访问日志(IP + UA) | 是(全球边缘节点) | ✅ GDPR DPA | ✅ MVP 即签 | 不缓存私有 bucket;启用 CF Bot Fight Mode 防爬 |
| **Vercel**Web 部署 + Edge Function | 请求 IP + UA + 路径 | 是(全球边缘) | ✅ GDPR DPA | ✅ MVP 即签 | Edge Function 内禁用 `console.log` 用户级数据;分析数据保留期默认 |
| **Apple Sign in with Apple** | Apple ID 关联 token | 否(Apple 直接给 token,不持有原始 Apple ID | ✅ Apple 平台原生 | n/a | 接受 Apple Hidden Email 默认行为 |
| **Google OAuth**(仅 Web `/login` | Google 用户 sub + 邮箱 | 否(OAuth 标准 token 交换) | ✅ Google Workspace DPA 适用 | ✅ 通过 Supabase Auth 中转,无直接合同 |
| **MetricKit**(iOS 系统) | 设备性能指标(CPU/GPU/热量) | 否(仅 App 内消费) | ✅ Apple 原生 | n/a | — |
> **底线**:除上表 8 项外,**MVP 不接任何第三方 SDK**。若运营提出新接入需求(如客服系统 Intercom、推送 OneSignal),必须先把该 SDK 加入本表并签 SCC 才可上线——这是 PR-2 的硬执行点。
---
## 10. 本章小结
| 关键产出 | 一句话 |
|----------|--------|
| **5 条隐私原则 PR-1 ~ PR-5** | 端侧优先脱敏 / 最小采集 / 用户可控 / 默认私有 / 可被遗忘——任何冲突按编号优先 |
| **数据流隐私视图** | 红黄绿三色 + 第三方可访问性双标签,红色节点永不离设备/Supabase 内网 |
| **三类敏感数据生命周期** | 人脸端侧不可逆模糊 + 地标/文档手动框选 + 位置永远城市级 5 km |
| **6 条 iOS 契约 P-1 ~ P-6** | 端侧失败不破例 / redactions owner-only / 城市标签公开 / 30 天硬删 / ATT 触发条件 3 项 / 13+ 单按钮确认 |
| **3 条 Web 契约 P-W-1 / P-W-2 / P-W-7** | viewState 不泄漏几何 / unlisted 双重 SEO 阻断 / viewState 视作偏好数据强 hash |
| **GDPR + PIPL 合规清单** | 8 项 MVP 必做(含 Apple PrivacyManifest+ 6 项 P2 |
| **隐私默认值表** | 16 项开关,全部朝最严方向;端侧脱敏与 mic 关、email 不公开 = 永不可改 |
| **数据保留时间表** | 15 类数据,原始 .usdz 90 天 / Sentry-PostHog 调至 13 个月 / 注销 30 天硬删 |
| **8 个第三方 SDK 风险点** | 全数有 GDPR DPA 与 SCC 模板可签;不允许 MVP 期内新增 |
读完本章你应能:
- ✅ 给隐私律师一份可直接审阅的数据处理映射(§2 数据流图 + §3 生命周期 + §9 SDK)
- ✅ 给 iOS / Web 工程师 P-1 ~ P-6、P-W-1 / P-W-2 / P-W-7 共 9 条契约的拍板答案
- ✅ 知道 [`10_governance.md`](10_governance.md) 在哪几节继续处理治理类 P-W-3 ~ P-W-6
---
**章节版本**v0.1 · 草案
**关键收获**:CrowdRoom 把隐私风险压缩到「**用户主动框选发布**」这一个决策点上——前置的端侧脱敏让原始 RGB 永不出设备、后置的 4 层 manifest 让公开数据只剩几何与材质;GDPR + PIPL 合规以「**MVP 必做 8 项 + 第三方 SDK 全签 SCC**」最小集即可上线。