diff --git a/apps/api/src/services/governance_query_service.py b/apps/api/src/services/governance_query_service.py index 67f14cb3..ae05e2c6 100644 --- a/apps/api/src/services/governance_query_service.py +++ b/apps/api/src/services/governance_query_service.py @@ -275,14 +275,14 @@ async def _query_dispatch_table( NULL::text AS operator_note FROM governance_remediation_dispatch d JOIN ai_governance_events e ON e.id = d.governance_event_id - WHERE d.dispatch_status = :dispatch_status + WHERE d.dispatch_status = CAST(:dispatch_status AS governance_dispatch_status) ORDER BY d.dispatched_at DESC """) count_sql = text(""" SELECT count(*) AS cnt FROM governance_remediation_dispatch - WHERE dispatch_status = :dispatch_status + WHERE dispatch_status = CAST(:dispatch_status AS governance_dispatch_status) """) async with get_db_context() as db: diff --git a/apps/api/tests/test_ai_governance_endpoints.py b/apps/api/tests/test_ai_governance_endpoints.py index 0e133097..fab2558c 100644 --- a/apps/api/tests/test_ai_governance_endpoints.py +++ b/apps/api/tests/test_ai_governance_endpoints.py @@ -288,6 +288,7 @@ class TestQueueEndpoint: assert "d.dispatched_at AS created_at" in source assert "ORDER BY d.dispatched_at DESC" in source assert "NULL::text AS operator_note" in source + assert "CAST(:dispatch_status AS governance_dispatch_status)" in source assert "d.created_at" not in source assert "d.operator_note" not in source diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 77a8a37a..c3bafa66 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -5,11 +5,13 @@ **根因**: - `governance/events`:production `ai_governance_events.details.remediation` 已有 dict 形態,例如 `{"items": [...]}`;read model `GovernanceEvent.remediation` 期待字串,Pydantic validation 造成 500。 - `governance/queue`:查詢仍讀 `governance_remediation_dispatch.created_at` / `operator_note`,但 production migration schema 實際是 `dispatched_at` / `created_by`,沒有 `created_at` / `operator_note`。 +- `governance/queue`:第二層 production 差異是 `dispatch_status` 為 PostgreSQL enum;SQLAlchemy 參數被送成 varchar,未明確 cast 時 Postgres 會拒絕 enum = varchar 比較,導致真表被誤判成 `table_pending=true`。 **修正**: - `governance_query_service._extract_remediation()` 將 `details.remediation` 的 string / dict / list 正規化成短字串,避免歷史治理事件破壞 response schema。 - `_to_governance_event()` 對非 dict details 做 read-side guard。 - `_query_dispatch_table()` 對齊 production schema:以 `d.dispatched_at AS created_at`、`NULL::text AS operator_note` 相容現有前端 DTO,不改 DB schema。 +- `_query_dispatch_table()` 對 `dispatch_status` 明確 `CAST(:dispatch_status AS governance_dispatch_status)`,避免 enum/varchar 比較錯誤。 - 補測 `test_ai_governance_endpoints.py`,覆蓋 dict remediation normalization 與 queue 查詢欄位相容性。 **本地驗證**: diff --git a/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md b/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md index 5edca0df..bf2b824b 100644 --- a/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md +++ b/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md @@ -2119,8 +2119,8 @@ Phase 6 完成後 **T17b Governance API 紅燈修復(2026-05-14 台北)**: - 觸發:T17A 前端工作鏈路已能顯示治理卡點,但 live smoke 發現 `governance/events` 500、`governance/queue` 回 `table_pending=true`,會讓 Operator Console 無法可信呈現治理告警是否被 dispatch、跳過、修復或卡人工。 -- 根因:`ai_governance_events.details.remediation` 在 production 已有 dict/list 形態,read model 仍只收字串;`governance_remediation_dispatch` production schema 使用 `dispatched_at`,查詢卻讀不存在的 `created_at` / `operator_note`。 -- 修正:read-side normalization 將 remediation string/dict/list 正規化成短字串;queue query 改用 `d.dispatched_at AS created_at` 與 `NULL::text AS operator_note` 相容既有 DTO,不改 DB schema。 +- 根因:`ai_governance_events.details.remediation` 在 production 已有 dict/list 形態,read model 仍只收字串;`governance_remediation_dispatch` production schema 使用 `dispatched_at`,查詢卻讀不存在的 `created_at` / `operator_note`;第二層差異是 `dispatch_status` 為 PostgreSQL enum,未明確 cast 時會被 asyncpg 以 varchar 參數送入。 +- 修正:read-side normalization 將 remediation string/dict/list 正規化成短字串;queue query 改用 `d.dispatched_at AS created_at` 與 `NULL::text AS operator_note` 相容既有 DTO,並以 `CAST(:dispatch_status AS governance_dispatch_status)` 對齊 enum,不改 DB schema。 - 驗證:`py_compile` pass;`pytest tests/test_ai_governance_endpoints.py tests/test_governance_remediation_dispatch.py -q` 53 passed;ruff F/E9 pass;diff check pass。 - 目前進度更新:Alertmanager 低風險自動修復主線約 96%;完整 AI 自動化管理產品化約 75%。T17B 推版後,下一段收斂 governance dispatcher skipped reason / leader-dedupe / SLO emitter。