# ADR-112: AwoooP Contract Governance & Publishing Workflow

**狀態**：Accepted
**日期**：2026-05-03（台北）
**決策者**：統帥
**範圍**：誰可以 publish / activate contract revision、如何簽章、approval workflow
**關聯**：ADR-106（六合約）、ADR-107（儲存）、ADR-113（outbox）

---

## 背景

ADR-107 D2 確定了「每個 published contract 必須產生一個 immutable revision」，但沒有定義：

1. 誰有權限 publish 一個 contract revision？
2. 誰有權限 activate（切換 active pointer）？
3. 如何防止未授權的 contract 修改影響正在運行的 runtime？
4. 簽章機制是什麼？
5. publish 和 activate 的 approval workflow 如何設計？

---

## 決策

### D1 — 三層授權模型

| 操作 | 允許者 | 條件 |
|------|--------|------|
| `draft` | project member（任何有 project 寫權限的人）| 無限制 |
| `publish` | contract_publisher（特定角色）| 簽章驗證通過 |
| `activate` | contract_activator（特定角色）OR 自動（若已預審）| approval workflow 通過 |

### D2 — 簽章規格

每次 publish 操作必須帶有：

```
HMAC-SHA256(body_json_canonical + revision_id + publisher_id + timestamp, signing_key)
```

- `signing_key`：從 K8s Secret `awooop-contract-signing-key` 讀取
- `body_json_canonical`：JSON canonical form（sorted keys）
- 簽章存在 `awooop_contract_revisions.publish_signature` 欄位
- runtime 讀取 active revision 時**不**驗簽（已在 publish 時驗過）
- audit 查詢時可重新驗簽（forensic）

### D3 — Activation Approval Workflow

activate 操作分為兩種路徑：

**路徑 A（需 approval）**：
- 六合約中的 `policy_routing`、`mcp_gateway`、`runtime_run_state` contract
- 因為這些 contract 直接影響運行中的 run 和安全邊界
- 走 multi_sig_redis approval flow（已有），加入 `approval_token` HS256 簽章（ADR-116）

**路徑 B（pre-approved，可直接 activate）**：
- `agent` contract（只改 I/O schema，不改安全邊界）
- `project_tenant` contract（只改 display_name 等 metadata）
- `channel_event` contract（channel 設定變更）
- 但仍需 CODEOWNERS review

### D4 — CODEOWNERS

```
# .github/CODEOWNERS（或 Gitea 等效）
packages/awooop-contracts/    @awoooi-platform-team
apps/api/src/core/config.py   @awoooi-platform-team
docs/adr/ADR-11*.md           @awoooi-platform-team
```

### D5 — 版本命名規則

```
{contract_family}/{contract_id}/v{major}.{minor}

例：
  agent/openclaw-sre/v1.0
  policy_routing/awoooi-sre/v2.1
  mcp_gateway/awoooi-k8s/v1.3
```

- major bump：breaking change（需 activation approval + 30 天 migration）
- minor bump：backward compatible（pre-approved 路徑）

### D6 — Draft 隔離保證

- draft revision 的 `lifecycle_status = 'draft'`
- runtime reads 只走 `awooop_published_revisions` view（不含 draft）
- DB 層：`awooop_published_revisions` view 的 WHERE 子句保證隔離
- 應用層：contract service 的 `get_active()` 方法只查 published + active

### D7 — Audit 要求

所有 draft / publish / activate / rollback 操作必須寫入 `audit_logs`：
```
{action, contract_family, contract_id, revision_id, actor_id, project_id, trace_id, timestamp, result}
```

---

## 實作細節

### Publish API

```
POST /v1/platform/projects/{project_id}/contracts/{contract_id}/publish

Request:
{
  "revision_id": "uuid",
  "body_json": {...},
  "body_schema_version": "v1.0",
  "publish_signature": "HMAC-SHA256(...)"
}

Response:
{
  "revision_id": "uuid",
  "body_hash": "sha256hex",
  "published_at": "2026-05-03T..."
}
```

### Activation API（需 approval）

```
POST /v1/platform/projects/{project_id}/contracts/{contract_id}/activate

Request:
{
  "revision_id": "uuid",
  "approval_token": "HS256..."  # 對於需要 approval 的路徑
}
```

---

## 後果

### Benefits
- contract 修改有完整 audit trail
- runtime 不可能意外讀到未發布的 draft
- breaking change 有 30 天 migration window 保護

### Costs
- publish 操作需要 signing_key（K8s Secret 管理）
- 開發者 activate policy_routing contract 需要走 approval（增加摩擦）

### Risks
- signing_key rotation 可能導致歷史簽章驗證失敗（forensic 用途）
- 緩解：簽章存入 DB，rotation 後歷史記錄仍可用舊 key 驗（key 版本號記錄）

---

## 驗收標準

- [ ] `awooop_contract_revisions` 表有 `publish_signature` 欄位
- [ ] publish API 驗簽，失敗時拒絕
- [ ] runtime `get_active()` 不返回 draft（整合測試）
- [ ] activation approval workflow（路徑 A）通過 vuln-verifier PoC
- [ ] CODEOWNERS 設定完成

## 關聯

- ADR-106（六合約定義）、ADR-107（contract revision schema）
- ADR-113（active revision 切換 outbox）、ADR-116（approval token security）
