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

137 lines
6.2 KiB
Markdown
Raw Permalink 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.

# 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 实例后补双节点时钟偏移和故障注入,不影响本地合同实现。