aps-agent/docs/round-70-approval-database-...

137 lines
6.2 KiB
Markdown
Raw Permalink Normal View History

# Round 70 工作计划:审批数据库权威 Clock / TTL / CAS
> 日期:2026-08-04
> 来源:`plan.md` R67-B2、`docs/product/plan-completion-matrix.md` P0 可信试点基线
> 当前状态:计划完成,待进入实现
> 外部边界:当前机器 Docker daemon 不可用、无 mysql client、无真实 MySQL 8 测试实例。
## 1. 背景与问题
`DatabaseApprovalStore` 已具备共享 DB、revision CAS、一次性 grant、同事务 grant/event 和 InnoDB/Alembic 门禁,但审批和 grant 的有效期仍由 Gateway 进程传入 `now_epoch` 或 `time.time()`裁决。两个节点存在正负时钟偏移时,可能对同一请求得出不同 TTL 结论;请求状态转换 CAS 也未把 expiry 条件放入 SQL UPDATE,存在跨过到期边界后的 TOCTOU。
## 2. 本轮目标
1. MySQL 的审批、转派、过期、grant 创建/消费、事件时间全部使用同一写事务连接取得的数据库权威时间。
2. MySQL 安全判断忽略调用方绝对 `now_epoch`;SQLite 继续保留 test-only 注入时间以保证确定性测试。
3. 所有 request CAS 增加 `expires_at_epoch > db_now`,过期 CAS 使用 `<= db_now`。
4. grant consume/expire 使用同事务 `db_now`,状态和 event 不得部分提交。
5. 请求 TTL 仅继承调用方给出的持续时间 `expiresAtEpoch-createdAtEpoch`,起点重锚到 MySQL `db_now`;拒绝非有限、非正 TTL。
6. MySQL epoch 列由 `FLOAT` 升级为 `DOUBLE`,避免 2026 年 epoch 精度损失。
7. 保持文件审批 backend、Harness/API 合同和 SQLite test-only 边界不变。
## 3. 已确认设计决策
### 3.1 权威时钟
- MySQL:在实际写事务 session 上执行 `SELECT UNIX_TIMESTAMP(CURRENT_TIMESTAMP(6))`。
- SQLite:使用测试传入 `now_epoch`;未传时只为测试/维护兼容使用 `time.time()`。
- 其他数据库:继续失败关闭。
- DB 时钟查询失败:禁止回退应用时钟,操作失败关闭。
### 3.2 TTL 与边界
- `now < expires`:有效。
- `now == expires`:已过期。
- `now > expires`:已过期。
- request TTL = payload 的 `expiresAtEpoch-createdAtEpoch`,验证为有限且大于 0,再以 `db_now` 重建绝对时间。
- grant TTL 继续使用 `grant_ttl_seconds`,但起点改为 `db_now`。
### 3.3 CAS 与事务
每次尝试采用新 session:
```text
BEGIN
→ 获取 db_now
→ 读取当前 row/revision
→ 权限、SOD、payload 校验
→ status + revision + expiry 条件 UPDATE
→ 插入 grant(如有)
→ 插入 event
→ COMMIT
```
`rowcount=0` 视为 CAS/TTL 未命中并有限重试;数据库断连、权限、schema 或 DB clock 错误不得伪装为普通冲突。
### 3.4 Schema
当前 SQLAlchemy MySQL DDL 实测生成 `FLOAT`。本轮:
- request:`created_at_epoch / expires_at_epoch`
- grant:`created_at_epoch / expires_at_epoch / consumed_at_epoch`
- event:`decided_at_epoch`
升级为 MySQL `DOUBLE(asdecimal=False)`,SQLite 继续保持现有浮点合同;新增 Alembic revision `20260804_01`,并更新数据库后端启动门禁要求。
## 4. 写边界
### 允许修改
- `server/agent_core/approval_db_store.py`
- `server/db/models.py`
- `server/db/migrations/versions/20260804_01_approval_database_clock.py`
- `tests/golden/test_approval_database_clock.py`
- 必要时最小更新 `tests/golden/test_approval_database_store.py`
- Round 70 文档、`plan.md`、完成矩阵和 CHANGELOG
### 禁止修改
- 文件审批 backend `approval_store.py`
- Harness、Gateway API、前端
- approval migration 的 B1 业务逻辑
- SAP/MES/WMS 集成
- Round 69 数据生成器和 canonical 数据包
- 受保护 `server/data` 文件
## 5. 验收矩阵
### 5.1 本地必须通过
- SQLite 边界:到期前/等于/到期后审批、转派和 grant 消费。
- SQLite 并发:final approve、approve/reject、approve/expire、consume/expire、consume-once。
- DB clock 查询失败 fail-closed,不回退应用时间。
- grant/event/commit 故障注入整体回滚。
- MySQL mock/编译:权威时间 SQL、CAS expiry 条件、六个 epoch 列为 DOUBLE。
- Alembic upgrade/downgrade 与 ORM 一致。
- 原有 approval database/migration/store/harness 回归。
- 全量 golden、Ruff、compileall、8003/5173 健康和受保护数据哈希。
### 5.2 外部阻断,不冒充完成
当前无 Docker daemon、mysql client 和真实 MySQL 实例,因此本地不能证明:
- 两个独立 Gateway 节点正负时钟偏移;
- MySQL REPEATABLE READ 下的真实多连接并发;
- deadlock/lock wait timeout、commit 前后断连;
- session timezone、主从/HA 切换;
- 生产账号、代理、连接池和真实部署拓扑。
本轮完成后矩阵可标记 **Done (local contract) / Partial (external MySQL)**,不得标记生产完成。
## 6. 验证命令
```powershell
.venv\Scripts\python.exe -m ruff check server/agent_core/approval_db_store.py server/db/models.py server/db/migrations/versions/20260804_01_approval_database_clock.py tests/golden/test_approval_database_clock.py tests/golden/test_approval_database_store.py
.venv\Scripts\python.exe -m compileall -q server/agent_core/approval_db_store.py server/db/models.py server/db/migrations/versions/20260804_01_approval_database_clock.py
.venv\Scripts\python.exe -m pytest -q tests/golden/test_approval_database_clock.py tests/golden/test_approval_database_store.py tests/golden/test_approval_migration.py tests/golden/test_approval_store.py tests/golden/test_harness_p3.py
.venv\Scripts\python.exe -m pytest tests/golden -q
npm run test:node
npm run build:web
```
完成代码后刷新 GitNexus 并执行 `detect_changes(compare main)`;未经用户授权不 commit/push/merge/publish。
## 7. 风险门禁
- `DatabaseApprovalStore` upstream impact:LOW,7 个受影响符号。
- `ApprovalRequestRecord` upstream impact:HIGH,127 个受影响符号;修改模型前必须向用户明确预警,并用迁移、DDL 编译和全量回归保护。
- 若 implementation 发现需修改 Harness/API 或文件审批 backend,立即停止扩界并重新做 impact/计划审计。
## 8. Plan Audit
`PLAN AUDIT: PASS`
Blocking issues:无。
Clarification needed:无;MySQL 真实环境已明确列为外部验收门禁。
Non-blocking improvements:未来取得 MySQL 8 实例后补双节点时钟偏移和故障注入,不影响本地合同实现。