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>
This commit is contained in:
@@ -11,8 +11,9 @@ Schema 設計原則:
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from sqlalchemy import (
|
||||
JSON,
|
||||
@@ -25,6 +26,7 @@ from sqlalchemy import (
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
Numeric,
|
||||
String,
|
||||
Text,
|
||||
text,
|
||||
@@ -34,6 +36,7 @@ from sqlalchemy import (
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import ENUM as PgEnum
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.dialects.postgresql import UUID as pg_UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from src.db.base import Base
|
||||
@@ -368,6 +371,13 @@ class AuditLog(Base):
|
||||
default="default",
|
||||
nullable=False,
|
||||
)
|
||||
# AwoooP Phase 2.3 (2026-05-04 ogt): 多租戶隔離欄位,配合 Batch 1 RLS migration
|
||||
project_id: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
default="awoooi",
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
|
||||
# Execution Result
|
||||
success: Mapped[bool] = mapped_column(default=False, nullable=False)
|
||||
@@ -671,6 +681,13 @@ class IncidentRecord(Base):
|
||||
primary_key=True,
|
||||
comment="事件唯一識別碼 (如 INC-20260322-A1B2C3)",
|
||||
)
|
||||
# AwoooP Phase 2.3 (2026-05-04 ogt): 多租戶隔離欄位,配合 Batch 1 RLS migration
|
||||
project_id: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
default="awoooi",
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
|
||||
# === 狀態與嚴重度 ===
|
||||
status: Mapped[str] = mapped_column(
|
||||
@@ -813,6 +830,13 @@ class KnowledgeEntryRecord(Base):
|
||||
primary_key=True,
|
||||
default=generate_uuid,
|
||||
)
|
||||
# AwoooP Phase 2.3 (2026-05-04 ogt): 多租戶隔離欄位,配合 Batch 1 RLS migration
|
||||
project_id: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
default="awoooi",
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
|
||||
# Core Fields
|
||||
title: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
@@ -1075,6 +1099,13 @@ class PlaybookRecord(Base):
|
||||
String(36), primary_key=True,
|
||||
comment="Playbook 唯一識別碼 (PB-YYYYMMDD-XXXXXX)",
|
||||
)
|
||||
# AwoooP Phase 2.3 (2026-05-04 ogt): 多租戶隔離欄位,配合 Batch 1 RLS migration
|
||||
project_id: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
default="awoooi",
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
|
||||
# Core Fields
|
||||
name: Mapped[str] = mapped_column(String(256), nullable=False)
|
||||
@@ -1612,3 +1643,45 @@ class AIProviderVersionHistory(Base):
|
||||
__table_args__ = (
|
||||
Index("ix_provider_version_captured", "provider", "captured_at"),
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# BudgetLedgerRecord — ADR-120 Token Budget Hard Kill(Phase 2.6)
|
||||
# 2026-05-04 ogt + Claude Sonnet 4.6
|
||||
# =============================================================================
|
||||
|
||||
class BudgetLedgerRecord(Base):
|
||||
"""
|
||||
LLM call 費用記帳表(ADR-120 D5)
|
||||
|
||||
每次 LLM call 完成後插入一筆記錄,供:
|
||||
- Tenant Budget 累計計算(Redis 快取,每分鐘從此表同步)
|
||||
- 儀表板消費統計
|
||||
- 告警閾值觸發(80% / 95% / 100%)
|
||||
"""
|
||||
__tablename__ = "budget_ledger"
|
||||
|
||||
id: Mapped[UUID] = mapped_column(
|
||||
pg_UUID(as_uuid=True),
|
||||
primary_key=True,
|
||||
server_default=text("gen_random_uuid()"),
|
||||
)
|
||||
project_id: Mapped[str] = mapped_column(
|
||||
String(64), nullable=False, default="awoooi", index=True
|
||||
)
|
||||
agent_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
||||
run_id: Mapped[UUID | None] = mapped_column(pg_UUID(as_uuid=True), nullable=True)
|
||||
model: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
||||
provider: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
||||
prompt_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
completion_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
cost_usd: Mapped[Decimal] = mapped_column(
|
||||
Numeric(10, 4), nullable=False, default=Decimal("0.0000")
|
||||
)
|
||||
recorded_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=text("NOW()")
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_budget_ledger_project_date", "project_id", "recorded_at"),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user