Files
awoooi/docs/awooop/inventory/INV-7-pr-cutting-plan.md
Your Name 8629ac709b
Some checks failed
run-migration / migrate (push) Failing after 59s
Code Review / ai-code-review (push) Successful in 1m8s
Type Sync Check / check-type-sync (push) Successful in 2m27s
feat(awooop): Phase 1-8 完整實作 — AwoooP Agent Platform 六平面架構
## Phase 1-3: Control Plane + Contract System
- awooop_phase1_control_plane_2026-05-04.sql: 12 張核心表 + RLS
- awooop_phase1_batch1_rls_2026-05-04.sql: 全部 FORCE RLS + GRANT
- packages/awooop-contracts/: 六合約 JSON Schema + golden fixtures
- src/models/awooop_contracts.py: Pydantic v2 contract models(extra=forbid)
- src/repositories/contract_repository.py: contract lifecycle(draft→published→active)
- src/services/contract_service.py: HMAC publish sig + Redis multi-sig activate
- src/services/schema_validator.py: LLM output validator(retry×3, E-SCHEMA-001)

## Phase 2: Tenant Isolation
- awooop_phase2_budget_ledger_2026-05-04.sql: budget_ledger + RLS
- src/services/budget_service.py: Token Budget Hard Kill 三層防線
- src/core/context.py: PROJECT_ID ContextVar(31 background loop 自動繼承)
- src/db/base.py + models.py: project_id 欄位 + RLS set_config 注入
- src/hermes/nl_gateway.py: project_id Redis key 前綴(Phase A 雙寫)
- src/services/anomaly_counter.py: per-project 改造(Phase A fallback)

## Phase 4: Platform Shell in Shadow Mode
- awooop_phase4_run_state_2026-05-04.sql: run_state + step_journal + idempotency
- src/services/run_state_machine.py: 8-state FSM + SKIP LOCKED + stale reaper
- src/services/platform_runtime.py: UUID v7 + W3C trace_id + shadow_execute
- src/services/audit_sink.py: PII/secret redaction 9 patterns
- src/api/v1/platform/runs.py: POST/GET /v1/platform/runs(Router→Service 架構)
- src/workers/platform_worker.py: SKIP LOCKED worker + heartbeat + reaper loop
- src/main.py: platform router + lifespan worker start/stop

## Phase 5: MCP Gateway 五閘門
- awooop_phase5_mcp_gateway_2026-05-04.sql: 4 表 + RLS
- src/plugins/mcp/gateway.py: McpGateway(Gate 1~5, E-MCP-GATE-001~009)
- src/plugins/mcp/redaction_middleware.py: 雙層 redaction + 16K 截斷
- src/plugins/mcp/registry.py: __provider name mangling(ADR-116)
- src/plugins/mcp/credential_resolver.py: k8s secret ref 解析
- tests/test_mcp_credential_isolation.py: 10 個迴歸測試(secret leak 防再現)

## Phase 6-8: EwoooC + Channel Hub + Approval Token
- awooop_phase6_ewoooc_onboarding_2026-05-04.sql: ewoooc tenant + 4 read-only MCP tools
- awooop_phase7_channel_hub_2026-05-04.sql: conversation_event + outbound_message
- src/services/provider_proxy.py: ProviderProxy + PlatformEnvelope(ADR-115)
- src/services/channel_hub.py: Telegram inbound mirror + Progressive Feedback(30s)
- src/services/awooop_approval_token.py: HS256 + jti NX replay 防護 + suggest mode

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 19:31:53 +08:00

278 lines
8.0 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.
# INV-7: PR Cutting Plan
**版本**v1.0 初稿
**日期**2026-05-03台北
**範圍**Phase 2 全部重構 PR 的切割方案refactor-specialist 設計)
**用途**:避免大 PR 難以 review每個 PR 獨立可合併、有 rollback 能力
---
## 原則
1. 每個 PR 必須可獨立合併(不依賴後 PR 才能運行)
2. 每個 PR 有 rollback 方式revert commit 或 feature flag
3. PR-01~05 無依賴可並行PR-06+ 有順序依賴
---
## PR 清單
### PR-01: telemetry.py hardcoded IP assert 移除
**範圍**`apps/api/src/core/telemetry.py:71`(約 1-3 行)
**前置**:無
**解決**P0-08 EwoooC 啟動失敗
```python
# 修改前
if "192.168.0.188" not in endpoint:
raise ValueError(f"Forbidden OTEL endpoint: {endpoint}")
# 修改後
allowed = [e.strip() for e in settings.ALLOWED_TELEMETRY_ENDPOINTS.split(",")]
if not any(a in endpoint for a in allowed):
raise ValueError(f"Forbidden OTEL endpoint: {endpoint}")
# 同步在 config.py 加入 ALLOWED_TELEMETRY_ENDPOINTS = "192.168.0.188"default
```
**review**vuln-verifier
**風險**:低(僅移除一個 IP assert
---
### PR-02: decision_manager.py silence key 常數化
**範圍**`apps/api/src/services/decision_manager.py:240`(約 2 行)
**前置**:無
**解決**P1-24 兩處定義不一致
```python
# 修改前decision_manager.py:240
silence_key = f"telegram_silence:{target}"
# 修改後
from apps.api.src.services.telegram_gateway import SILENCE_KEY_PREFIX
silence_key = f"{SILENCE_KEY_PREFIX}{target}"
```
**review**debugger確認 SILENCE_KEY_PREFIX = "telegram_silence:",功能等效)
**風險**:低
---
### PR-03: ollama_auto_recovery.py 第二定義移除
**範圍**`apps/api/src/services/ollama_auto_recovery.py:230`(約 5 行)
**前置**:需確認 INV-1 中 `ollama:current_primary` 的所有寫入點
**解決**P0-11 GCP 三層拓撲遷移必裂
```python
# 移除 ollama_auto_recovery.py 中第二個 ollama:current_primary 寫入點
# 統一從 config.py 或 ollama_failover_manager.py 讀取,不在 auto_recovery 自行決定 primary
```
**review**tool-expert確認 GCP 拓撲三層 failover 邏輯)
**風險**:中(影響 Ollama failover 路徑)
---
### PR-04: registry.py `_provider` → `__provider`
**範圍**`apps/api/src/plugins/mcp/registry.py:24-71`(約 20 行)
**前置**:無
**解決**P1-05 `_provider` public 可繞過 audit
```python
# 修改所有 self._provider → self.__provider
# 加 @property provider 讀取(需要時)
# 加 unit test:
# with pytest.raises(AttributeError):
# registry.__provider # double underscore = name mangling
```
**review**vuln-verifier
**風險**Python name mangling只影響直接反射存取
---
### PR-05: mcp_bridge.py namespace 動態化
**範圍**`apps/api/src/plugins/mcp/mcp_bridge.py:592,602,631,647,681`(約 30 行)
**前置**Phase 1 schema 未完成前,先改為 from settings fallback
**解決**P0-13 EwoooC K8s tool 無法使用
```python
# Phase 1 前的過渡做法PR-05a
DEFAULT_NAMESPACE = os.getenv("K8S_DEFAULT_NAMESPACE", "awoooi-prod")
namespace = parameters.get("namespace", DEFAULT_NAMESPACE)
# Phase 2 後的完整做法PR-05b需 Phase 1 schema 先完成):
namespace = parameters.get("namespace",
get_project_contract(project_id).allowed_k8s_namespaces[0])
```
**review**tool-expert
**風險**:低(有 env fallback功能等效
---
### PR-06: consensus_engine.py CONSENSUS_PREFIX 加 project 前綴
**範圍**`apps/api/src/services/consensus_engine.py`(約 15 行)
**前置****Redis 雙寫 Phase A 已完成**(舊 `consensus:` key 已雙寫新 key
**解決**P0-12 consensus engine 多租戶跨 tenant 共用
```python
# 修改前
CONSENSUS_PREFIX = "consensus:"
# 修改後consensus engine 接受 project_id 參數)
def get_consensus_prefix(project_id: str) -> str:
return f"{project_id}:consensus:"
```
**review**db-expert確認 consensus 語義正確、debuggerregression
**風險**:中(需 Redis 雙寫先完成)
---
### PR-07: security hardeningnonce + webhook replay + approval policy
**範圍**
- `apps/api/src/services/security_interceptor.py:451-490`nonce 重設計)
- `apps/api/src/api/v1/webhooks.py:679-728`webhook timestamp/nonce
- `apps/api/src/services/decision_manager.py`requires_approval policy-derived
**前置**ADR-116 已 Accepted規格凍結
**解決**P0-04、P0-05、P0-063 個 PoC 確認漏洞)
```python
# security_interceptor.py: nonce 必須 HMAC(server_secret + nonce)
# webhooks.py: 加 X-Timestamp header 驗證 ±5min window + Redis NX nonce
# decision_manager.py: requires_approval 從 policy contract 讀,禁止 LLM output 決定
```
**review**vuln-verifierPoC 重跑驗證修補有效)
**風險**:高(安全修補,必須 PoC 驗證)
---
### PR-08: Repository project_id filter 批次 1
**範圍**
- `IncidentRepository``incidents` 表)
- `PlaybookRepository``playbooks` 表)
- `KnowledgeRepository``knowledge_entries` 表)
**前置****Phase 1 schema migration 完成**`project_id` 欄位已存在)
**解決**P0-03 30+ 業務表無 project_id filter
```python
# 每個 repository 方法加 project_id 參數
# 例:
async def get_incident(self, incident_id: str, project_id: str) -> Incident:
return await self.session.execute(
select(Incident)
.where(Incident.incident_id == incident_id)
.where(Incident.project_id == project_id) # 新增
)
```
**review**db-expertquery plan 確認)
**風險**:中(約 200 行;需 regression test
---
### PR-09: Repository project_id filter 批次 2
**範圍**
- `McpAuditRepository`(加 trace_id + project_id
- `AiDecisionRepository`(加 run_id + project_id
- `ApprovalRepository`(加 trace_id + project_id
**前置**PR-08 合併後
**解決**P0-03 繼續
**review**db-expert + debuggertrace_id 貫穿驗證)
**風險**:中
---
### PR-10: Background loop 標記ADR-123
**範圍**`apps/api/src/main.py`31 個 loop約 150 行)
**前置**ADR-123 已 Accepted
**解決**P0-07 31 個 loop 無 project_id
```python
# 每個 loop 的 context 加上標記
# platform_internal: asyncio.create_task 前設定 contextvars
# legacy_awoooi_default: fallback project_id = 'awoooi'
async def _run_with_project_context(coro, project_id: str):
token = project_id_var.set(project_id)
try:
await coro
finally:
project_id_var.reset(token)
```
**review**critic確認標記正確
**風險**:中(影響所有 background loop 的 logging context
---
### PR-11: AnomalyCounter per-project 改造
**範圍**`apps/api/src/services/anomaly_counter.py:790`(約 80 行)
**前置**PR-10 完成loop 有 project_id context 後才能傳入)
**解決**P1-17 AnomalyCounter 全域單例
```python
# 修改前全域單例6 個 prefix 無 tenant
_anomaly_counter = AnomalyCounter(get_redis())
# 修改後per-project 工廠
def get_anomaly_counter(project_id: str) -> AnomalyCounter:
return AnomalyCounter(get_redis(), project_prefix=f"{project_id}:anomaly")
```
**review**debugger確認 6 個 prefix 全數改造)
**風險**:中
---
## 執行順序圖
```
並行群組 G-D無依賴可同時開
PR-01, PR-02, PR-03, PR-04, PR-05
↓ Phase 1 Schema 完成後
並行群組 G-E部分並行
PR-07安全修補優先
PR-06需 Redis 雙寫 Phase A
PR-08需 Phase 1 schema
↓ PR-08 合併後
PR-09依賴 PR-08
↓ ADR-123 Accepted 後
PR-10background loop 標記)
↓ PR-10 合併後
PR-11AnomalyCounter
```
---
## 驗收標準
- [ ] PR-01~05 全部合併無依賴Phase 0 完成後即可)
- [ ] PR-07 通過 vuln-verifier PoC 重跑驗證
- [ ] PR-08~09 通過 db-expert reviewquery plan 確認)
- [ ] PR-10~11 通過 regression test31 個 loop logging context 正確)
*最後更新2026-05-03台北*