feat(preflight): confirm + audit + tamper-proof version check (#41)

完成 preflight EPIC(#29)的最后一子项 #41:

- POST /api/heicode/preflight/confirm:重算摘要 → 仅 ready 时可确认 → 派生防篡改
  版本哈希 → 写审计事件 preflight.confirmed(谁/何时/哪个 version)→ 返回 version。
- 防篡改版本 computePreflightVersion:在**稳定安全面**(template + 资源
  binding_id/type/provider/name/status/has_secret + 高危 enum + 必需缺失项)上做
  sha256,刻意排除易变预算数字/agent_slot,避免版本无意义抖动。
- 部署校验:POST /api/heicode/agents 新增可选 preflight_version。默认仅在带了它时
  校验(向后兼容);HEICODE_PREFLIGHT_REQUIRED=true 时强制。确认后资源/模板/高危面
  漂移或被篡改 → 版本不匹配 → 部署拒绝。
- 重构:抽出 buildPreflightSummary(GET/confirm/deploy 共用);GET 现也回 version。

测试:版本确定性/稳定性(不随预算变)、资源篡改翻转版本、normalizeBindingIDs。
go build/vet 干净,controller preflight 测试全过。文档 §4.1/§4.1.1 更新。

Affects: Manager only(新增 confirm 端点 + 部署可选校验,默认向后兼容)。
无计费/审计 schema 改动(复用既有 agent_audit_events)。

Closes #41

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 12:55:40 +08:00
co-authored by Claude Opus 4.8
parent c39ed6a070
commit 9a073e5b41
5 changed files with 273 additions and 32 deletions
+22 -3
View File
@@ -152,7 +152,8 @@ signature = base64( ed25519_sign( device_priv, sha256(canonical) ) )
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| GET | `/api/heicode/preflight?template_id=&binding_ids=1,2,3` | `UserOrV2DeviceAuth` | 返回缺失项 + 可读执行摘要 |
| GET | `/api/heicode/preflight?template_id=&binding_ids=1,2,3` | `UserOrV2DeviceAuth` | 返回缺失项 + 可读执行摘要 + `version` |
| POST | `/api/heicode/preflight/confirm` | `UserOrV2DeviceAuth` | 确认摘要 → 记审计 + 返回防篡改 `version`(#41) |
- `binding_ids` 同部署入参(逗号分隔或重复 key,可空)。
@@ -174,7 +175,8 @@ signature = base64( ed25519_sign( device_priv, sha256(canonical) ) )
],
"budget": { "remaining_quota":1234567, "quota_per_unit":500000, "tier_max_agents":5, "current_agents":1 },
"approval_policy": { "mode":"per_high_risk_op" },
"ready": false
"ready": false,
"version": "pfv1_3a9c…"
}}
```
@@ -182,8 +184,25 @@ signature = base64( ed25519_sign( device_priv, sha256(canonical) ) )
- **`high_risk_ops`**:固定 enum —— `production_deploy` / `db_write` / `cloud_resource_delete` / `production_secret` / `large_budget`;由已绑资源类型推导,均 `requires_approval`。
- **红线**:`resources` 只暴露 `type/provider/name/status/has_secret`(布尔),**绝不返回 `secret_ref`/`channelId`/`base_url`/价格**。
- **`invalid_bindings`**:请求里无效 / 非本人 / 非 active 的绑定 id(不阻断,供前端提示)。
- **`version`**:防篡改摘要版本(#41),由**稳定安全面**派生(template + 资源 + 高危 + 必需缺失项);**不含**易变的预算数字,故余额波动不会改版本。
> confirm + 审计 + 防篡改版本校验(#41)将作为 `POST /api/heicode/preflight/confirm` 后续补充;当前 preflight 为只读。
### 4.1.1 确认 + 防篡改版本(#41)
`POST /api/heicode/preflight/confirm` body:`{ "template_id":"architect", "binding_ids":[17] }`
```json
{ "success": true, "data": {
"version": "pfv1_3a9c…", // 把它带到部署请求
"template_id": "architect",
"summary": { …同上执行摘要… },
"confirmed": true
}}
```
- 仅当 `ready=true` 才能确认;否则返回 `POLICY_REJECTED`(先补齐缺失项)。
- 确认会写一条审计事件 `preflight.confirmed`(谁 / 何时 / 哪个 `version`)。
- **部署校验**:`POST /api/heicode/agents` 可带 `preflight_version`。HM 用**当前**资源/模板状态重算版本并比对——确认后资源被增删/改类型/改凭证、或模板变更 → 版本不匹配 → 部署被拒(`preflight changed…,re-run confirm`)。
- 默认**仅在带了 `preflight_version` 时校验**(向后兼容,不带照常部署);设 `HEICODE_PREFLIGHT_REQUIRED=true` 则**强制**要求先 confirm。
---