Files
awoooi/docs/adr/ADR-111-awooop-bootstrap-order.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

167 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-111: AwoooP Bootstrap Order & Identity Paradox
**狀態**Accepted
**日期**2026-05-03台北
**決策者**:統帥
**範圍**AwoooP 平台啟動順序、project_id 標記體系、31 個 background loop 的 identity 策略
**關聯**ADR-106架構、ADR-107儲存、ADR-123background loop migration
---
## 背景
AWOOOI 作為 AwoooP 的 first tenant 和 first runtime host在系統啟動時存在一個根本矛盾
- AwoooP 的核心原則是「所有操作必須帶 project_id」
-`apps/api/src/main.py`**31 個 background loop**onboarder 實測),全部都沒有 project_id
- AWOOOI 既是「需要被 project_id 保護的 tenant」又是「在任何 project 被建立前就要運行的平台基礎設施」
這就是 **Bootstrap Paradox**:你需要平台先啟動,才能建立 project但啟動時又沒有 project。
---
## 問題清單
1. 31 個 background loop 無 project_idmain.py 實測)
2. AWOOOI cron/job/healthcheck 在 AwoooP 對象化之前就必須運行
3. `run_ai_slo_watchdog_loop()` 是平台自我監控,不屬於任何 tenant
4. `_run_model_version_tracker_loop()` 追蹤所有 tenant 共用的 AI provider 版本,屬於 platform resource
5. GCP Ollama 三層容災ADR-110的 failover 狀態(`ollama:current_primary`)是 platform_resource不屬於任何 tenant
---
## 決策
### D1 — 三種 project_id 標記
所有入口點loop、webhook、job、CLI必須帶其中一個標記
| 標記 | project_id 值 | 適用情境 |
|------|-------------|---------|
| `platform_internal` | `__platform__` | 平台自我維護、不屬於任何 tenant允許讀取 platform_resource但必須記 audit |
| `legacy_awoooi_default` | `awoooi` | 過渡期AWOOOI 業務 loop 尚未改造前的臨時 fallback |
| `requires_project_id` | 從呼叫鏈或 event envelope 取得 | 已完成多租戶改造的入口點 |
### D2 — Platform Resource 明確清單
以下 Redis key 和狀態是 **platform_resource**,使用前綴 `platform:` 而非 `{project_id}:`
| Resource | Key | 理由 |
|----------|-----|------|
| Ollama topology | `platform:ollama:topology` | GCP-A/GCP-B/Local 三層路由,所有 tenant 共用ADR-110|
| Telegram polling leader | `platform:telegram:polling:leader` | 全域 pod 鎖,不屬於任何 tenant |
| Gemini daily count | `platform:ollama:gemini_daily_count:{date}` | 全平台 Gemini 緊急路由計數 |
| SLO watchdog state | `platform:slo_watchdog:*` | 平台自我監控 |
| Model version cache | `platform:model_version:*` | 所有 tenant 共用的 provider 版本資訊 |
### D3 — 啟動順序Hard Order
```
1. DB migrationsawooop_projects + awooop_contract_revisions 等)
2. Seed AWOOOI project_idINSERT IF NOT EXISTS
3. platform_internal services 啟動SLO watchdog、model version tracker
4. legacy_awoooi_default services 啟動AWOOOI 業務 loop帶 project_id=awoooi
5. API server 開始接收請求
6. requires_project_id services 啟動Phase 2+ 改造後的新入口點)
```
**禁止**API server 在 step 2seed AWOOOI project完成前接受任何需要 project_id 的請求。
**實作**FastAPI lifespan context manager 控制啟動順序。
### D4 — 過渡期豁免規則legacy_awoooi_default
以下條件下,`project_id=awoooi` 作為過渡期 fallback 是允許的:
- 明確標記為 `legacy_awoooi_default`(不能靜默 fallback
- 寫入 audit log標記 `legacy=true`
- 有退場時程Phase 4 完成後 90 天內改造完畢)
### D5 — Hard Reject 規則
在 Phase 2 完成後,以下情況**必須拒絕**(不能靜默 fallback
- 新建立的 API endpoint 收到無 project_id 的請求(非 legacy
- `requires_project_id` 標記的 loop 缺少 project_id 的 context variable
### D6 — GCP Ollama 拓撲的 bootstrap 處理
ADR-110 的三層 Ollama 拓撲是 platform_resource
- bootstrap 時必須在 step 3 之前確認 GCP-A/GCP-B 健康狀態
- `platform:ollama:topology` Redis key 由 `ollama_failover_manager.py` 在啟動時初始化
- 所有 tenant 的 LLM call 使用這個 platform-level topology不能 per-project 覆蓋safety: no per-tenant Ollama endpoint override
---
## 實作指引
### Python contextvars 實作
```python
# apps/api/src/core/context.py新建
import contextvars
project_id_ctx_var: contextvars.ContextVar[str] = contextvars.ContextVar(
'project_id', default='__platform__'
)
def get_current_project_id() -> str:
return project_id_ctx_var.get()
async def run_with_project(coro, project_id: str):
"""在指定 project context 中執行 coroutine"""
token = project_id_ctx_var.set(project_id)
try:
return await coro
finally:
project_id_ctx_var.reset(token)
```
### main.py 改造示意Phase 2 完成後)
```python
# platform_internal loop
asyncio.create_task(run_with_project(
run_ai_slo_watchdog_loop(), "__platform__"
))
# legacy_awoooi_default loop過渡期
asyncio.create_task(run_with_project(
run_incident_analysis_sweeper(), "awoooi"
))
```
---
## 後果
### Benefits
- Bootstrap Paradox 有明確解法(三種標記 + 啟動順序)
- platform_resource 明確隔離,不會和 tenant 資料混算
- GCP Ollama 三層拓撲作為 platform_resource不受 per-tenant policy 影響
### Costs
- 31 個 background loop 全部需要改造INV-8 列出優先序)
- main.py 啟動邏輯需要重構step 1~6 排序)
- 需要新建 `apps/api/src/core/context.py`
### Risks
- legacy_awoooi_default 標記若不退場,會永遠留在 codebase
- 緩解ADR-123 明確定義退場時程Phase 4 後 90 天
---
## 驗收標準
- [ ] `apps/api/src/core/context.py` 建立project_id contextvars
- [ ] main.py 31 個 loop 全部帶 project_id contextPR-10 完成)
- [ ] `platform_internal` loop 帶 `project_id=__platform__`
- [ ] `legacy_awoooi_default` loop 帶 `project_id=awoooi`
- [ ] 啟動順序 D3 被 FastAPI lifespan 強制執行
- [ ] `platform:ollama:topology` key 在啟動時正確初始化
## 關聯
- ADR-106架構、ADR-107儲存、ADR-110GCP Ollama 拓撲)
- ADR-123Background Loop Migration Strategy配套 ADR
- INV-3Entrypoints、INV-8Background Loop Catalog