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