--- title: "CrowdRoom · 隐私设计(v0.1)" date: 2026-05-20 draft: false tags: ["CrowdRoom", "众包", "3D 重建", "隐私", "iOS", "数据结构"] categories: ["worldmodel"] --- # CrowdRoom · 隐私设计(v0.1) > 本章是 CrowdRoom 的「**合规底座**」,承接 [`00_overview.md`](00_overview.md) §8 风险 RK-3(UGC 审核)、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 Views(tooltip 文案待补)+ §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 breadcrumb:viewState 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+0–T+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` 过滤 PII;breadcrumb 中 viewState 替换为 `[REDACTED]`(§5 P-W-7) | | **PostHog Cloud**(事件分析) | 用户事件 + funnel + feature flag 评估 | 是(PostHog EU / US 双区可选) | ✅ 选 EU 区即数据不离欧 | ✅ MVP 即签 | 强制 opt-in(C-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**」最小集即可上线。