## Phase 0(文件層,全部 Accepted) - ADR-106/107:AwoooP 平台架構 + 儲存策略 - ADR-111~118:Bootstrap → RLS 七項核心 ADR - ADR-119~124:SAGA → Singleton Decomposition 六項 ADR - ADR-UI-01~04:Operator Console 四個 UI ADR ## Phase 1(DB schema + migration) - awooop_phase1_control_plane_2026-05-04.sql:7 張新表 + trigger + RLS - Step 1:三角色(platform_admin/migration BYPASSRLS,awooop_app 受 RLS) - Step 13:GRANT awooop_app 最小權限(7 條) - Step 14:RLS fail-closed,移除 __platform__ 後門 - awooop_phase1_batch1_rls_2026-05-04.sql:高流量四表三步式 ADD COLUMN - awooop_phase1_batch1_backfill.py:SKIP LOCKED 分批回填腳本 - awooop_models.py:7 個 SQLAlchemy 2.x models ## Critic 修正(4 Critical + 3 Major) - C-1:ADD CONSTRAINT IF NOT EXISTS → DO 塊 + pg_constraint 查詢 - C-2:__mapper_args__ 字串 list → primary_key=True on mapped_column - C-3:__platform__ RLS 後門 → 全移除,改用 BYPASSRLS role - C-4:awooop_app role 從未建立 → Step 1 + 7 條 GRANT - M-1:active_pointer_guard SECURITY DEFINER(FORCE RLS 跨租戶保護) - M-2:pg_partman create_parent 加冪等防護 - M-3:immutability trigger 新增身份欄位保護(project_id/family/contract_id) ## Task 1.2 修補 - agent_loader.py:硬編碼 Mac 路徑 → AGENTS_DIR 環境變數 - Dockerfile:補 COPY .claude/agents/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.7 KiB
4.7 KiB
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」,但沒有定義:
- 誰有權限 publish 一個 contract revision?
- 誰有權限 activate(切換 active pointer)?
- 如何防止未授權的 contract 修改影響正在運行的 runtime?
- 簽章機制是什麼?
- 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 Secretawooop-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_statecontract - 因為這些 contract 直接影響運行中的 run 和安全邊界
- 走 multi_sig_redis approval flow(已有),加入
approval_tokenHS256 簽章(ADR-116)
路徑 B(pre-approved,可直接 activate):
agentcontract(只改 I/O schema,不改安全邊界)project_tenantcontract(只改 display_name 等 metadata)channel_eventcontract(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_revisionsview(不含 draft) - DB 層:
awooop_published_revisionsview 的 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)