Files
awoooi/docs/adr/ADR-112-awooop-contract-governance.md
Your Name 13e51802fe feat(awooop): Phase 0 全 ADR + Phase 1 control plane schema(含 critic 四項修正)
## 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>
2026-05-04 13:37:11 +08:00

4.7 KiB
Raw Blame History

ADR-112: AwoooP Contract Governance & Publishing Workflow

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


背景

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_canonicalJSON canonical formsorted keys
  • 簽章存在 awooop_contract_revisions.publish_signature 欄位
  • runtime 讀取 active revision 時驗簽(已在 publish 時驗過)
  • audit 查詢時可重新驗簽forensic

D3 — Activation Approval Workflow

activate 操作分為兩種路徑:

路徑 A需 approval

  • 六合約中的 policy_routingmcp_gatewayruntime_run_state contract
  • 因為這些 contract 直接影響運行中的 run 和安全邊界
  • 走 multi_sig_redis approval flow已有加入 approval_token HS256 簽章ADR-116

路徑 Bpre-approved可直接 activate

  • agent contract只改 I/O schema不改安全邊界
  • project_tenant contract只改 display_name 等 metadata
  • channel_event contractchannel 設定變更)
  • 但仍需 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 bumpbreaking change需 activation approval + 30 天 migration
  • minor bumpbackward compatiblepre-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_keyK8s Secret 管理)
  • 開發者 activate policy_routing contract 需要走 approval增加摩擦

Risks

  • signing_key rotation 可能導致歷史簽章驗證失敗forensic 用途)
  • 緩解:簽章存入 DBrotation 後歷史記錄仍可用舊 key 驗key 版本號記錄)

驗收標準

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

關聯

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