# 隐私数据请求开发说明 ## 1. 当前交付边界 当前版本完成了`ACCESS`数据访问申请和`DELETE`主体删除申请的端到端闭环。`ACCESS`由Worker在服务端租户/家庭/长者边界内盘点数据范围;`DELETE`在受理事务内立即冻结主体,再由Worker对六个子系统逐项处理、持久化状态并对账,只有全部子任务进入`COMPLETED`或经核验的`SKIPPED`后才将总请求置为`COMPLETED`。 隐私请求结果始终设置`content_included=false`,不把档案、转写、故事或记忆正文复制到请求表、审计记录和普通日志。正文导出由独立私有导出任务实现,见`data-export.md`。当前仍未开放`CORRECTION`和独立的`CONSENT_WITHDRAWAL`批处理请求。 ## 2. 权限与安全边界 | 能力 | 家庭所有者 | 管理员 | 编辑者/查看者 | |---|:---:|:---:|:---:| | `privacy.read` | 允许 | 允许 | 拒绝 | | `privacy.manage` | 允许 | 拒绝 | 拒绝 | 服务端从OIDC主体重新解析账号、客户组织、家庭成员和长者归属,不使用浏览器提交的租户条件。创建请求必须提交16至128字符的`Idempotency-Key`;同键同正文返回原请求,同键不同正文返回`IDEMPOTENCY_KEY_CONFLICT`。详情响应使用`Cache-Control: no-store`和强ETag。 `DELETE`还必须满足以下门禁: - `reason`为1至500字符,但服务端仅持久化SHA-256摘要,不保存原因正文; - `confirmation`必须与主体当前称呼完全一致,确认值不持久化; - 主体存在`PREPARING`、`ROOM_ISSUED`、`STARTING_AGENT`、`ACTIVE`或`STOPPING`通话时返回`SUBJECT_ACTIVE_CONVERSATION`,必须先正常结束通话; - 受理后主体立即变为`deleting`,一般主体、内容、设备和导出接口不再返回该主体;所有有效授权同时撤回,记忆停用并清除可召回向量; - 删除申请详情使用请求编号仍可查询,即使主体已进入`deleting`或`deleted`。 ## 3. 业务流程 ```mermaid sequenceDiagram participant U as 家庭所有者 participant A as API participant D as PostgreSQL participant O as Outbox participant W as Worker U->>A: POST /subjects/{id}/privacy-requests A->>A: 验证OIDC、privacy.manage、长者边界和幂等键 A->>D: 创建SUBMITTED请求与PENDING子任务 A->>O: privacy.access_requested A-->>U: 202 + 请求编号 W->>D: VERIFYING -> EXECUTING W->>D: 按冻结分类统计当前长者数据 W->>D: 子任务COMPLETED,请求COMPLETED W->>D: 写入无正文系统审计 U->>A: GET /privacy-requests/{id} A-->>U: 状态、分类数量和子任务 ``` ### 3.1 DELETE删除与对账流程 ```mermaid sequenceDiagram participant U as 家庭所有者 participant A as API participant D as PostgreSQL participant O as Outbox participant W as Worker participant R as Redis participant S as MinIO U->>A: DELETE申请+原因+精确称呼确认 A->>D: 校验权限、确认值及无活动通话 A->>D: 主体deleting、撤回授权、停用记忆/向量 A->>D: 创建6个PENDING子任务 A->>O: privacy.delete_requested A-->>U: 202 + 请求编号 W->>R: 删除会话租约和设备在线键 W->>S: 删除所有私有导出对象 W->>D: 核验媒体对象和外部索引引用 W->>D: 清空pgvector及供应商引用 W->>D: 脱敏正文、快照、回调、绑定和档案 W->>D: 六任务对账+最小tombstone W->>D: 请求COMPLETED或PARTIAL_FAILED ``` 六类子任务如下: | 子系统 | 当前动作 | 成功判定 | |---|---|---| | `redis` | 删除该主体历史通话租约键和关联设备在线键 | Redis删除操作成功,允许键本来不存在 | | `export_files` | 删除全部`export_task.object_ref`对应私有对象,任务改为`EXPIRED`并清空对象元数据 | 每个对象删除成功且数据库引用为空 | | `object_storage` | 删除该主体全部已登记录音对象,并把分片置为`DELETED`、清空对象引用和文件摘要 | 对象存储删除失败时记`FAILED`并持续重试;无对象时安全完成 | | `pgvector` | 清空本地embedding和provider引用,索引记`DELETED` | 目标主体本地向量均不可召回 | | `provider_index` | 检查外部供应商索引引用 | 无外部引用时记`SKIPPED`;存在引用但无删除适配器时失败并使总请求`PARTIAL_FAILED` | | `postgresql` | 对档案、事实、转写、摘要、故事、记忆、快照、回调和绑定做删除或不可逆脱敏 | 主体为`deleted`,正文为固定删除标记,审计只留最小证明 | ## 4. 接口 | 接口 | 能力 | 关键约束 | 结果 | |---|---|---|---| | `POST /api/v1/subjects/{id}/privacy-requests` | `privacy.manage` | 接受`ACCESS`或`DELETE`;必须`Idempotency-Key`;DELETE还需原因和精确确认 | `202`受理,幂等重放为`200` | | `GET /api/v1/subjects/{id}/privacy-requests` | `privacy.read` | 固定长者边界 | 请求列表及盘点摘要 | | `GET /api/v1/privacy-requests/{id}` | `privacy.read` | 服务端反查家庭归属;禁止缓存 | 请求详情和子任务 | 可请求的分类为`profile`、`consents`、`device_bindings`、`conversations`、`transcripts`、`summaries`、`stories`和`memories`。不指定分类时盘点全部分类。 ## 5. 数据库 | 表 | 作用 | 关键约束 | |---|---|---| | `privacy_request` | 隐私请求聚合根 | 租户+申请人+幂等键唯一;请求摘要用于检测正文冲突 | | `privacy_subtask` | 子系统执行与重试状态 | 请求+子系统+目标唯一;保留尝试次数和最后错误 | | `tombstone` | 后续删除闭环的最小不可逆证明 | 只保存资源摘要和保留原因,不保存待删正文 | V9迁移提供了请求、子任务和最小证明模型。当前`ACCESS`和`DELETE`已开放;`CORRECTION`与独立`CONSENT_WITHDRAWAL`仍保持关闭。 独立临时空库已完成`up 1—9 → down -all → up 1—9`,最终迁移状态为`9|dirty=false`,验证库随后已删除。 ## 6. 自动化验证 `scripts/smoke-privacy.ps1`使用真实本地OIDC令牌验证: - 所有者创建、同键重放和同键不同范围冲突; - 编辑者提交请求返回403; - Worker完成数据库子任务,请求收敛为`COMPLETED`; - 返回的分类严格等于请求范围且`content_included=false`; - 列表可见、测试数据和关联Outbox/审计可清理。 `scripts/smoke-privacy-delete.ps1`使用临时家庭和主体,并验证: - 错误称呼确认和非所有者提交均被拒绝; - 存在活动通话时删除返回409,正常结束后才允许受理; - 受理后普通主体读取和新建导出立即被阻断; - 冻结后的同键同正文仍能安全重放,不会创建第二个请求; - 六个子任务全部收敛为`COMPLETED`或经核验的`SKIPPED`; - 档案和稳定事实正文已脱敏,原因明文未进入请求表; - 私有导出数据库引用与MinIO对象均已删除; - 仅留下不含个人正文的SHA-256主体摘要墓碑; - 测试完成后临时业务数据、Outbox、审计和对象均被清理。 家庭工作台的“提交全量盘点”现通过页面内受控操作区提交。操作区固定当前长者公开编号和八类盘点范围:长者档案、授权记录、设备绑定、通话、话轮、摘要、故事和记忆;同时明确ACCESS结果仅返回各分类数量、处理状态和子任务状态,不会把个人正文复制到隐私请求、审计或普通日志。用户必须显式确认范围后才能提交,取消或切换业务上下文会清除草稿且不会创建请求。 页面按低强度策略使用真实OIDC执行无提交冒烟:检查固定对象、八类范围、正文边界、确认门禁和取消行为,并验证零创建请求、请求数量不变、无横向溢出及无非预期浏览器错误。服务端权限、幂等、租户隔离和Worker闭环继续由既有定向脚本覆盖,不在每次页面改动后重复执行全套隐私删除回归。