Files
qinglong/docs/adr/ADR-0035-approved-action-manual-recovery-authorization.md
T

114 lines
7.6 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.
# ADR-0035Approved Action 人工恢复授权与强认证事实
- 状态:Proposed
- 日期:2026-07-19
- 关联:QL-RFC-0001、ADR-0028、ADR-0029、ADR-0030、ADR-0031、ADR-0033
## 上下文
ADR-0033 只允许人工把 post-start unknown action 显式终结为 succeeded、failed 或 blocked,禁止把旧 dispatch 重置后再次执行。这个操作会改变审计事实,也可能掩盖一个已经发生但无法验证的外部副作用,因此普通登录、可变 username、Agent 身份或仅在应用层判断一次角色都不足以授权。
人工服务先做 Policy 判断、随后 Repository 写终态时还存在撤权竞态:如果 owner/admin binding 或 Project version 在两步之间变化,旧的 allow 结果不能继续生效。授权记录若在终态提交之后另写,又会留下“动作已经被人裁决,但无法证明当时认证与授权上下文”的崩溃窗口。
因此人工恢复必须把稳定 User、近期强认证、Project Policy version fence 和低敏授权事实绑定到同一个终态事务。
## 决策
### 1. `approval.recover` 是独立的 Project permission
人工恢复只请求 `approval.recover`,不复用 `approval.decide``project.manage` 或通配 Tool 权限。固定角色矩阵为:
- owner、admin:允许;
- operator、viewer:拒绝;
- 未绑定、已撤销或 archived Project:默认拒绝。
首版继续使用静态 Policy Core;未来接 OPA 或自定义策略时也必须返回 Project/RoleBinding version fence,不能只返回一个布尔 allow。
### 2. 只接受仍有效的稳定 User principal
应用服务接收 ADR-0029 的 exact-shape `AuthenticatedPrincipal`,并在读取 recovery storage 前验证:
- `subject.type=user`
- principal 在服务时钟上满足 `authenticatedAtMs <= now < expiresAtMs`
- assurance 为 `multi_factor``hardware``local_console`
- `now - authenticatedAtMs <= 5 分钟`
`single_factor``service`、Agent、API App、MCP Client、Worker 和 System 均不能进行人工恢复。legacy `twoFactorActivated` 仍不是 MFA 事实;只有未来可信 authentication adapter 验证真实 ceremony 后才能产生上述 assurance。本 ADR 不定义 MFA 协议、恢复码或 local-console issuer。
### 3. Policy 判断必须携带可提交的 version fence
服务从 recovery snapshot 取得 immutable Project ID,以稳定 User 请求 `approval.recover`。只有 `allow` 且 Project version、当前 RoleBinding version 都存在时才构造授权事实。
SQLite Repository 在同一个 `BEGIN IMMEDIATE` 中重新读取最新 Project 与该 User 的 RoleBinding version:任一 version 不一致即以 fence rejection 失败,且在写 recovery resolution 或 execution 终态前退出。这样 revoke、role change、Project archive 或其他 version mutation 无法穿透已经计算出的 allow。
### 4. `0024` 授权事实与人工终态原子提交
`ApprovedActionRecoveryAuthorizationFacts` 与 human resolution 一对一,保存:
- dispatch、Project、manual mutation ID 和稳定 User ID
- authentication ID、强 assurance、认证时间;
- Policy 判断使用的 Project/RoleBinding version
- 授权时间;
- 对以上 canonical 字段计算的 SHA-256 fact digest。
表只允许 User 和三种强 assurance,要求正 version、认证时间不晚于授权时间且间隔不超过五分钟,并通过外键绑定 recovery resolution 与 Project。它不保存 cookie、JWT、MFA secret、recovery code、请求 header 或无界理由。
Repository 在一个事务内依次复验 execution/recovery fence、复验 Policy version、插入 human resolution、推进 execution/control 终态并插入授权事实。任一约束、trigger 或 commit 失败全部回滚,不能留下“有终态无授权事实”或“有授权事实无终态”。automatic evidence resolution 不创建人工授权事实。
### 5. 幂等重放必须包含相同授权事实
相同 manual mutation 只有在 decision、reason、evidence、User、时间和 authorization fact 全部精确一致时返回原结果。字段漂移、digest 不匹配或一条旧 human resolution 缺少 `0024` fact 时不补写、不重新解释,稳定返回 terminal/conflict 语义。
服务在重放前仍验证当前 principal 和当前 Policy;历史 allow 事实只用于审计与精确幂等,不成为永久授权 capability。
### 6. 当前保持 production unreachable
本切片已经实现 permission matrix、强认证应用服务、`0024` migration、SQLite 原子 Repository 和 contract tests,但没有注册到 app、loader、legacy service、API、CLI、MCP 或 UI。生产启用仍需要:
- 真实 MFA/hardware/local-console authentication adapter 与 credential 生命周期;
- 人工恢复列表、低敏证据预览、二次确认、rate limit 和审计 API/UI
- 对外错误屏蔽,避免未授权调用者区分 dispatch 是否存在;
- profile-aware recovery lifecycle、指标、告警与积压门禁;
- PostgreSQL adapter 与多副本 revoke/resolve 竞争 contract
- edge 设备和 cluster 节点上的故障注入与资源门禁。
## 影响
正面影响:
- 高风险 unknown 终结不能由 Agent、普通 session 或 operator 越权执行;
- 授权撤销竞态由数据库 version fence 关闭;
- 认证、授权与终态形成一个可验证、低敏、原子的审计事实;
- edge 只增加一次人工操作时的短事务和单行事实,不增加 watcher、timer 或常驻连接;
- cluster adapter 可以复用相同语义,通过 PostgreSQL row lock 实现多副本一致裁决。
代价与风险:
- 单用户部署若没有可信强认证入口,将不能使用人工恢复;这是安全门禁,不应降级为密码确认;
- 每条人工 resolution 增加一行保留事实和关联索引;
- authentication ID 虽非 Secret,仍属于安全审计标识,API 默认不得向普通读取者暴露;
- 当前只有 SQLite adapter,不能据此宣称 cluster-control 已可用。
## 未选择的方案
1. **owner/admin 登录即可恢复**single-factor session 不足以授权未知副作用终结,拒绝。
2. **复用 `approval.decide`**:审批新动作与裁决已发生但未知的动作风险不同,拒绝。
3. **只在应用层检查 Policy**:检查与提交之间存在 revoke/archive 竞态,拒绝。
4. **终态提交后异步写审计日志**:崩溃会产生不可证明的人工裁决,拒绝。
5. **把完整 JWT/MFA 证明写数据库**:扩大 Secret 与隐私暴露面,拒绝。
6. **旧单用户环境自动提升为 local console**:UID、TTY 或私有文件本身不能证明可信 ceremony,拒绝。
7. **授权事实缺失时自动补写**:无法还原历史认证与 Policy snapshot,拒绝。
## 验证要求
- owner/admin 允许,operator/viewer/未绑定/revoked/archived 拒绝;
- 非 User、过期 principal、single-factor/service 或超过五分钟的强认证在 storage read 前拒绝;
- Project/RoleBinding 在 Policy allow 后变化时,Repository 在任何终态写入前 fence rejection
- resolution、execution/control 终态与 authorization fact 同事务提交,fact 插入失败全部回滚;
- fact exact-shape、强 assurance、时间窗口、version、digest 或 identity 任一漂移均 fail closed
- 精确 manual mutation 重放返回同一结果,变形重放不得覆盖原事实;
- automatic evidence resolution 不依赖或伪造人工授权事实;
- migration/schema ownership 覆盖表、索引、约束和外键;
- production reachability 搜索证明服务和 Repository 未被旧 app/loader/API/service 路径导入;
- Node 22 兼容门禁与固定 Node 24 目标门禁均通过。