diff --git a/GLOBAL_RULES.md b/GLOBAL_RULES.md index 10b3abcc9..4b566680e 100644 --- a/GLOBAL_RULES.md +++ b/GLOBAL_RULES.md @@ -1,345 +1,135 @@ -# AWOOOI 專案開發憲法與行為準則 +# AWOOOI Product Engineering and AI Automation Rules v3 -> **本文件為 AWOOOI 專案的最高行為準則。所有開發成員必須 100% 嚴格遵守,沒有例外!** +本規範以 `~/.codex/AGENTS.md` 的 `global_product_governance_v2` 為全產品上位規範,並補充 AWOOOI / AwoooP / IWOOOS 的產品實作要求。舊版 Phase、manual gate 或 blanket read-only 規則若衝突,均由本版取代。 ---- +## 1. North Star -## 第一章:Triage (傷患分級) 異常處理鐵律 +AWOOOI 的完成目標是 production AI DevSecOps / AISOC runtime closure:所有受管主機、服務、產品、網站、工具、套件、資料與告警,都能由 AI Agent 感知、關聯、決策、受控執行、獨立驗證、失敗回滾/重試,並寫回 KM/RAG/MCP/PlayBook。 -### 🔴 紅燈異常 (立刻停機修復) +文件、snapshot、UI card、source、test、build、CD 或 metadata receipt 都只是證據層,不能單獨宣稱 AI 自動化完成。 -以下情況視為紅燈異常,必須**立刻停止所有新功能開發**: +## 2. Source Truth and Completion -- 架構阻斷 -- API 無法連線 (CORS / Failed to fetch) -- 編譯失敗 -- 嚴重的資安漏洞 (如 Multi-Sig 邏輯錯誤) +真相優先序:production runtime receipt > Gitea main/CD/deploy marker > committed machine-readable evidence > snapshot/document > conversation。 -**行為準則:** -> 底層斷了,上面蓋的 UI 也只是壞的。優先修復紅燈,禁止繞過! +每個工作項分開追蹤: -### 🟡 黃燈異常 (記錄 Backlog,延後處理) +- `source_ready` +- `tests_ready` +- `gitea_cd_ready` +- `production_deployed` +- `runtime_loop_closed` +- `visible_ui_verified` -以下情況視為黃燈異常,不應打斷開發心流: +整體完成度必須同時呈現 program progress、asset coverage 與 latest production runtime closure。不得把歷史 aggregate、hardcoded percentage 或 fallback receipt 算進當前 run 完成。 -- UI 排版稍微跑位 -- 非關鍵字的 i18n 翻譯遺漏 -- 非阻斷性的 Warning +## 3. Risk and Controlled Apply -**行為準則:** -> 記錄進 WBS 待辦清單,集中在 Phase 結束前的「Bug Bash」一次解決。 +Low/medium/high 均預設 AI controlled apply;critical 才 break-glass。每個動作必須有 target selector、truth diff、risk decision、check-mode、idempotency key、bounded executor、independent verifier、rollback/no-write terminal、durable learning writeback。 ---- +以下舊狀態只作 evidence,不得作一般終局: -## 第二章:0 個 Hardcode 字串與 i18n 清零鐵律 - -### 最高憲法 - -**前端 UI 代碼絕對禁止出現任何寫死的中文或英文字串!** - -所有 UI 文字必須 100% 透過 `next-intl` 從字典檔提取,包含但不限於: - -- 按鈕文字 -- 標籤與標題 -- 狀態文字 -- 列舉值顯示 (如 CRITICAL → 危急) -- 錯誤訊息 -- 表單欄位標籤 -- Tooltip 與提示文字 - -### 優先級 - -| 優先級 | 語系 | 說明 | -|--------|------|------| -| 1 | 繁體中文 (zh-TW) | **最高優先級預設顯示** | -| 2 | 英文 (EN) | 雙軌並行 | - -**Hardcoded English 視為開發失敗!** - -### 範例 - -```tsx -// ❌ 錯誤 - Hardcode (違憲) -CRITICAL - -No recent backup! - -// ✅ 正確 - 使用 next-intl -const t = useTranslations('risk') -const tDryRun = useTranslations('dryRun') -{t('critical')} - -{tDryRun('noRecentBackup')} +```text +owner_review_required +manual_required +needs_human +read_only +runtime_execution_authorized=false +runtime_write_gate=0 +apply_allowed=false ``` -### 違規處理 +只有明文秘密、不可逆資料破壞、reboot/node drain、不可逆網路切換、攻擊型掃描、成本/provider 變更、未經 replay/shadow/canary 的核心替換、force push/ref/visibility 破壞、raw secret volume 屬 critical。 -**違背此規則視為開發失敗,必須立即修正後才能繼續其他任務!** +## 4. AI Loop Contract ---- +所有可執行事件共用同一 `trace_id/run_id/work_item_id/incident_id`: -## 第三章:防禦性工程與 Zero Trust 鐵律 +1. Sensor: LOG/metric/trace/alert/audit/change/freshness/backup receipt +2. Normalizer: canonical asset ID、taxonomy、severity、fingerprint、cluster +3. Correlator: primary cause、secondary noise、related changes、confidence +4. Planner: candidate、PlayBook、target selector、expected outcome +5. Policy: risk、allowlist、blast radius、cost、critical classifier +6. Check: dry-run/check-mode/diff/precondition +7. Execute: single-writer、idempotent、batch/canary/cooldown +8. Verify: independent service/route/metric/data/security postconditions +9. Recover: bounded retry、rollback、dead-letter、precise blocker +10. Learn: durable DB receipt、KM/RAG/MCP/PlayBook acknowledgement、trust update -### 1. 先質疑,後實作 (Fail Fast & Ask) +缺節點時自動建立 coverage work item;不得轉成無期限人工接手。 -遇到以下架構盲區時,**絕對禁止自行假設或使用脆弱的臨時方案**: +## 5. Complete Asset Graph -- 缺乏認證憑證 -- 狀態機定義不完整 -- 可能導致資料遺失 (如 In-memory 儲存稽核日誌) +資產圖譜至少包含: -**行為準則:** -> 必須立刻暫停實作,列出選項並向統帥回報 Blocker。 +- 主機/VM:99、110、111、112、120、121、188 及後續 discovered assets +- 平台服務:Gitea、runner、Harbor、Registry、K3s、Docker/containerd、Nginx/TLS/DNS、PostgreSQL、Redis、object storage、backup/restore +- 可觀測與安全:Prometheus、Alertmanager、Sentry、SigNoz、Langfuse、Wazuh/IWOOOS、Telegram、audit +- AI:OpenClaw、Hermes、NemoTron、Ollama、providers、MCP servers/tools、RAG indexes、KM、PlayBooks +- 產品/網站/API/admin routes:以 product governance matrix 與 public surface registry 每日 reconciliation +- 套件/映像/模型/規則:SBOM、版本、來源、支援期限、CVE/配置漂移、升級 replay/canary/rollback -### 2. 零信任預設 (Zero Trust Defaults) +每個資產需有 canonical ID、產品/專案/網站/服務/工具/套件標籤、owner lane、source/runtime identity、signals、freshness、executor、verifier、backup/restore 與 learning targets。 -所有環境變數與安全配置,必須預設為最嚴格狀態: +## 6. Logs, Alerts and Telegram -- `MOCK_MODE=False` -- 禁止 CORS `*` -- 禁止重複簽核 -- 禁止跳過驗證 +所有 LOG/metric/trace/alert 必須先進 durable registry/receipt,再供 AI Agent 使用。原始載荷需 redaction、retention、access boundary 與 provenance;不得把 Telegram 訊息當唯一資料庫。 -### 3. 強制乾跑 (Dry-run Mandatory) +每個 Telegram 告警必須映射:incident、run、asset、AI route、candidate、controlled queue、verifier、rollback、KM/RAG/MCP/PlayBook writeback。成功通知要降噪;失敗、stale、missing source、recurrence、notification failure 才升級。 -任何牽涉到基礎設施變更的破壞性操作,**必須在程式碼層級實作並呼叫 Dry-run(預檢)機制**: +## 7. Information Security Management -- K8s API 操作 -- SSH 命令執行 -- Database Drop/Truncate -- 任何不可逆操作 +資訊安全治理採持續循環:Govern -> Identify -> Protect -> Detect -> Respond -> Recover,並對 AI 採 Govern -> Map -> Measure -> Manage。每個控制項必須有資產範圍、evidence source、freshness SLO、coverage state、gap work item、controlled response、post-verifier 與 audit receipt。 -### 4. 邊界預判 (Edge Case Anticipation) +IWOOOS/Wazuh/SIEM 不得只顯示工具安裝或 UI 可見;必須證明 signal ingestion、rule coverage、triage、containment candidate、controlled response、recovery、case closure 與 recurrence prevention。 -寫任何邏輯前,必須先思考並實作防呆機制: +## 8. Secure Software Delivery -- 「如果網路斷線怎麼辦?」→ 重試機制 -- 「如果使用者連按兩次怎麼辦?」→ 冪等性設計 -- 「如果 K8s API 回應超時怎麼辦?」→ 超時處理 +- Gitea/Gitea SSH 是唯一 source-control authority;GitHub freeze 維持。 +- 每次 release 必須有 source SHA、focused tests、build/typecheck/i18n、Gitea CD run、deploy marker、production API/readback、desktop/mobile smoke。 +- dependency/image/model 更新需 SBOM、provenance、相容性 replay、security evaluation、shadow/canary、rollback。 +- 禁止 force push、未驗證 destructive migration、secret 明文或 mock/fake production evidence。 ---- +## 9. Professional UI/UX -## 第四章:CPO 絕對美學與品牌靈魂鐵律 +UI/UX 與 backend/runtime 同步推進,不等待「所有 API 100% 不再變動」。所有主要頁面必須: -### 1. Pixel-Perfect 細節至上 +- 第一視窗顯示任務、狀態、next action、真實數字與 primary blocker +- 用 table、timeline、flow、chart、status badge 與 progressive disclosure 取代文字牆 +- 保留完整技術 evidence,但放在可展開 detail,而非主畫面長文 +- 導航 route 移除前必須有 IA ledger、替代目的地/redirect、權限與 smoke +- 使用 `next-intl`、semantic HTML、keyboard/focus、loading/error/empty/degraded states +- desktop/mobile 無重疊、無截字,並以 production screenshot/smoke 驗證 -UI 實作必須嚴格講究: +主流專業做法是可掃描、可比較、可操作的 operational cockpit;不強制單一品牌風格、裝飾動畫或延後到未定義 Phase。 -| 要素 | 標準 | -|------|------| -| Padding/Margin | 必須有「呼吸感」,絕不允許擁擠 | -| Typography | 字體大小與粗細必須建立清晰的視覺層級 | -| 邊框與陰影 | 使用微妙的 border-opacity 與 subtle shadows | -| 質感 | Nothing.tech 那種「通透感與極簡」 | +## 10. Work Order and Parallelism -**禁止事項:** -- 禁止使用預設的、廉價的樣式 -- 禁止元素不對齊 -- 禁止忽略 hover/active 狀態的視覺回饋 +權威執行順序由 production priority API 的 AI automation program ledger 與 Agent99 enterprise work ledger共同投影。插入需求必須正式綁定 program work item、asset scope、priority、acceptance、runtime evidence 與 next action。 -### 2. 生物機械有機進化 +同一主線可平行推進互不衝突的 API、UI、LOG、Telegram、KM/RAG/MCP/PlayBook、verifier、backup 與 security lanes。外部 blocker 只能阻擋其依賴項,不能凍結整個產品。 -IT AI 的 UI 不要硬綁綁!視覺上必須融合: +## 11. Legacy Rule Retirement -| 風格來源 | 精髓 | -|----------|------| -| openclaw.ai | 有機、流線、親和力 | -| Nothing.tech | 通透、工業風、極簡 | +所有舊 gate 必須有:`policy_version`、`risk_class`、`reason`、`owner_lane`、`created_at`、`expires_at`、`exit_condition`、`replacement_action`、`post_verifier`。缺 expiry/exit condition 的 gate 視為治理債,不能新增;與 live truth 衝突時自動建立 drift work item。 -**禁止生硬的幾何設計!** +`GLOBAL_RULES.md v2.3` 中的「紅燈停止全部新功能」、「架構盲區立即暫停」、「Phase 1/2 絕對禁止 UI 打磨」、「Phase 4 才能做視覺」已正式失效。紅燈需優先處理,但不停止其他無依賴、低爆炸半徑工作。 -### 3. 品牌靈魂 - Claw 設計語言 +## 12. Required Verification -AWOOOI 的核心品牌意象為「智慧之眼機械爪 (Mechanical Claw)」: +最小驗證: -- Logo 必須體現「Claw」精密抓取的意象 -- 側邊欄展開/折疊應模擬爪子開合 -- HITL 批准動畫應呈現爪子鎖定的效果 -- 顏色基調:純白工業風、金屬光澤、科技感 - -### 4. CSS 代碼去背 SOP (CRITICAL) - -當整合 Raster 圖像 (JPEG/PNG) 資產時: - -**絕對禁止直接放上死白貼紙!** - -必須強制套用 CSS 技術,將純白背景濾除: - -```tsx -// ✅ 正確 - mix-blend-mode 去背 - - -// ✅ 備選 - mask-image 去背 -
+```text +JSON/schema validation +focused backend tests and lint +web typecheck and i18n mirror validation +Gitea CD and deploy marker readback +production API/runtime verifier +desktop/mobile smoke for changed user-facing routes ``` -**目標:讓有機設計看起來刻在玻璃 UI 上!** +版本:`awoooi_product_engineering_rules_v3` -### 5. 跨界協作 - Gemini 資產生成 SOP +生效:`2026-07-10 Asia/Taipei` -本專案嚴禁使用: -- 醜陋的純文字 Placeholder -- 隨便找的開源 Icon 來充當核心視覺資產 - -**當需要高質感視覺資產時:** -1. 在終端機輸出一段『給 Gemini 的圖像生成提示詞 (Prompt)』 -2. 標註資產規格(尺寸、格式、透明背景需求) -3. 統帥將該提示詞交給 Gemini 生成完美圖檔 -4. 收到圖檔後整合至專案(使用 CSS 去背 SOP) - ---- - -## 第五章:開發階段與視覺素材戰略 (Phased Visual Strategy) - -### Phase 1 & 2 (當前階段) - 核心引擎與真實數據 (Function over Form) - -**絕對禁止**在此階段耗費時間進行: -- UI 打磨 -- 複雜 SVG/PNG 素材替換 -- 微動畫設計 -- Logo 視覺調整 - -**視覺降級為『乾淨的 Wireframe 級別』**: -- 使用純文字 Typography -- 標準 Tailwind CSS 即可 -- 簡潔的 CSS 呼吸燈代替圖片 Logo - -**唯一目標**: -1. 100% 真實 API 資料貫通 -2. Multi-Sig 邏輯實作 -3. i18n 字串清零 -4. **消滅所有 Mock Data** - -### Phase 4 (未來階段) - 視覺靈魂注入 (Visual Soul Injection) - -**啟動條件**:所有後端資料欄位、狀態機與 API **100% 確定不改動**後,才准啟動此階段。 - -**屆時將統一實作**: -- Q 版、玩具感 (Toy-ish) 的流線型 OpenClaw 品牌資產 -- 色彩鮮明的視覺設計 -- 精緻的微動畫效果 -- 統帥親自批准的品牌視覺素材 - ---- - -## 第六章:決策支援協定 (Decision Support Protocol) - -### 情報完整性 - -在遇到需要統帥(使用者)進行重大架構、功能或視覺決策的十字路口時,**絕對禁止只拋出問題而不給予分析**。 - -### 標準回報格式 - -任何決策請求,**必須包含以下三個完整板塊**: - -#### 1. 現況盤點 (Context) -- 我們現在在哪裡? -- 遇到了什麼瓶頸或機會? -- 相關的技術背景與約束條件 - -#### 2. 戰略選項 (Options) -列出可行的路線,並詳述各自的優劣: - -| 選項 | 優勢 (Pros) | 風險與代價 (Cons) | -|------|-------------|-------------------| -| Path A | ... | ... | -| Path B | ... | ... | -| Path C | ... | ... | - -#### 3. 首席架構師的明確建議 (Architect's Recommendation) - -AI 必須根據專案的最終目標,給出**一個最推薦的選項**,並附上強而有力的理由: - -``` -📌 建議選擇:Path X - -理由: -1. [具體原因 1] -2. [具體原因 2] -3. [與專案目標的契合度] -``` - -### 禁止事項 - -- ❌ 只拋出問題,讓統帥自己想答案 -- ❌ 列出選項但不給建議 -- ❌ 給出模稜兩可的「都可以」回答 -- ❌ 缺乏具體分析的空泛建議 - ---- - -## 第七章:視覺資產協作規範 (Asset Collaboration Protocol) - -### 1. 前期階段 (當前) - 純代碼視覺鐵律 - -**絕對禁止**要求統帥(使用者)手動下載、搬運實體圖檔 (PNG/JPG/SVG)。 - -**替代方案:** - -| 場景 | 正確做法 | -|------|----------| -| Logo | 使用 lucide-react 圖示 + CSS Typography (如 `Bot`, `Cpu`, `Brain`) | -| 圖示 | 使用 lucide-react 圖標庫 (`AlertTriangle`, `Shield`, `Server` 等) | -| 狀態指示器 | 使用純 CSS 呼吸燈、脈動效果 (`animate-ping`, `animate-pulse`) | -| 品牌色塊 | 使用 Tailwind 漸層背景 (`bg-gradient-to-br`) | -| Placeholder | 使用高質感的 CSS 色塊 + 字體排版 | - -**範例:** - -```tsx -// ❌ 錯誤 - 依賴實體圖片 -Logo - -// ✅ 正確 - 純代碼方案 -import { Bot, Sparkles } from 'lucide-react' - -
-
- -
- AWOOOI -
-``` - -### 2. 最終階段 (延後執行) - 品牌資產批次替換 - -**啟動條件**:專案準備正式上線前,所有功能與 API 100% 穩定。 - -**屆時執行**: -1. 由統帥統一提供高畫質 3D 渲染品牌圖檔 -2. 一次性批次替換所有 Placeholder -3. 確保零破損的視覺升級 - -### 3. 違規處理 - -- ❌ 嘗試讀取 `/logo-claw.png` 或任何不存在的圖片 -- ❌ 要求統帥下載並放入圖片檔案 -- ❌ 使用 404 圖片導致 UI 破損 - -**以上行為視為開發失敗,必須立即修正!** - ---- - -## 附錄:其他強制規則 - -| 規則 | 說明 | -|------|------| -| 禁止 UAT 環境 | 只有 Dev + Prod | -| API 路由規範 | 使用路徑路由 `/api/v1/` (非子域名) | -| Playwright 測試 | 必須啟用截圖與錄影 | -| 紅燈優先 | 遇到 API 阻斷等紅燈問題,必須優先修復才能開發新功能 | -| 純代碼視覺 | 前期階段使用 lucide-react + CSS,禁止依賴實體圖片 | - ---- - -*最後更新: 2026-03-20* -*版本: 2.3 (加入第七章:視覺資產協作規範)* +上位規範:`global_product_governance_v2` diff --git a/apps/api/src/api/v1/agents.py b/apps/api/src/api/v1/agents.py index bf5c2a29a..54f971c21 100644 --- a/apps/api/src/api/v1/agents.py +++ b/apps/api/src/api/v1/agents.py @@ -1935,8 +1935,12 @@ async def get_gitea_private_inventory_p0_scorecard() -> dict[str, Any]: async def get_product_governance_matrix_readback() -> dict[str, Any]: """回傳全產品 Gitea source-truth governance matrix。""" try: + repo_bundle_backup_readback = await asyncio.to_thread( + load_latest_gitea_repo_bundle_backup_readback + ) payload = await asyncio.to_thread( - load_latest_product_governance_matrix_readback + load_latest_product_governance_matrix_readback, + repo_bundle_backup_readback=repo_bundle_backup_readback, ) return redact_public_lan_topology(payload) except FileNotFoundError as exc: diff --git a/apps/api/src/services/awoooi_priority_work_order_readback.py b/apps/api/src/services/awoooi_priority_work_order_readback.py index 861fe27b3..66be5a7ce 100644 --- a/apps/api/src/services/awoooi_priority_work_order_readback.py +++ b/apps/api/src/services/awoooi_priority_work_order_readback.py @@ -112,6 +112,7 @@ _CONTEXT_CONVERGENCE_CONTROLLED_EVIDENCE_SCHEMA_VERSION = ( "context_convergence_controlled_evidence_v1" ) _AI_AUTOMATION_PROGRAM_SCHEMA_VERSION = "ai_automation_program_ledger_v1" +_GLOBAL_PRODUCT_GOVERNANCE_POLICY_VERSION = "global_product_governance_v2" _AI_AUTOMATION_PROGRAM_SOURCE = ( "docs/workplans/2026-07-02-commander-inserted-requirements-priority-ledger.md" ) @@ -364,6 +365,23 @@ _AI_AUTOMATION_PROGRAM_SCOPE_CLASSES: list[dict[str, Any]] = [ }, ] _AI_AUTOMATION_PROGRAM_WORK_ITEMS: list[dict[str, Any]] = [ + { + "id": "AIA-P0-000", + "priority": "P0", + "order": 0, + "status": "done", + "title": "啟用全產品風險分級治理 v2 並淘汰舊式人工硬閘", + "problem": "舊規範以 Phase、owner response、read-only 或 false counter 全面凍結交付,且 snapshot 可無期限凌駕 live truth。", + "professional_practice": "NIST CSF 2.0 Govern、AI RMF Govern/Map/Measure/Manage、policy-as-code、time-bounded exception", + "asset_scope_ids": ["hosts", "services", "products", "websites", "tools", "packages", "data_and_backup", "observability_and_security"], + "acceptance": [ + "low/medium/high 預設 controlled apply,critical 才 break-glass", + "所有 gate 具 expiry、exit condition、replacement action 與 verifier", + "live runtime truth 高於舊 snapshot,衝突時自動建立 drift work item", + "全域 AGENTS、AWOOOI rules、product manifest 與 capability policy 使用同一版本", + ], + "next_action": "持續掃描並淘汰仍把 owner/manual/read-only 當一般終局的程式與 UI。", + }, { "id": "AIA-P0-001", "priority": "P0", @@ -476,6 +494,23 @@ _AI_AUTOMATION_PROGRAM_WORK_ITEMS: list[dict[str, Any]] = [ ], "next_action": "核心 P0 契約完成後執行三次可回滾 golden canary。", }, + { + "id": "AIA-P0-008", + "priority": "P0", + "order": 8, + "status": "pending", + "title": "完成全資產資訊安全管理與 AISOC 自動回應閉環", + "problem": "Wazuh、SIEM、工具安裝與 coverage UI 尚不能證明安全事件已完成 triage、containment、recovery 與 recurrence prevention。", + "professional_practice": "NIST CSF 2.0 Govern/Identify/Protect/Detect/Respond/Recover、continuous control monitoring、case management", + "asset_scope_ids": ["hosts", "services", "products", "websites", "tools", "packages", "data_and_backup", "observability_and_security"], + "acceptance": [ + "每個 security control 有資產範圍、evidence、freshness SLO、coverage 與 gap work item", + "每個 actionable finding 產生受控 containment/repair candidate 與獨立 verifier", + "case closure、audit receipt、KM/RAG/MCP/PlayBook 與 recurrence counter 共用 run_id", + "不得用工具 installed、route HTTP 200 或 UI 可見替代 runtime security closure", + ], + "next_action": "把 IWOOOS/Wazuh/SIEM control coverage 接入 canonical AutomationRun 與 controlled response registry。", + }, { "id": "AIA-P1-001", "priority": "P1", @@ -561,6 +596,40 @@ _AI_AUTOMATION_PROGRAM_WORK_ITEMS: list[dict[str, Any]] = [ ], "next_action": "核心 runtime contract 穩定後依風險逐 lane 遷移並執行 canary。", }, + { + "id": "AIA-P1-006", + "priority": "P1", + "order": 13, + "status": "in_progress", + "title": "全站 UI/UX、導航與資訊架構專業化", + "problem": "多個頁面仍是文字牆,導航移除缺少替代目的地,操作者難以快速確認 AI 正在做什麼與是否成功。", + "professional_practice": "task-oriented information architecture、operational cockpit、progressive disclosure、accessibility、responsive visual regression", + "asset_scope_ids": ["products", "websites", "tools", "observability_and_security"], + "acceptance": [ + "所有主要 route 完成 UI inventory、first-viewport cockpit 與文字牆分級整改", + "被移除 menu/route 具有保留、合併、redirect、權限與 smoke ledger", + "Approvals、Runs、Work Items、Alerts、Knowledge、IWOOOS desktop/mobile 無重疊且顯示 production truth", + "技術證據可展開查閱,但不佔據主要操作視窗", + ], + "next_action": "建立全 route UI/IA audit matrix,依操作頻率與文字密度排序整改。", + }, + { + "id": "AIA-P1-007", + "priority": "P1", + "order": 14, + "status": "pending", + "title": "統一內外部 MCP、RAG、KM 與 PlayBook 能力供應鏈", + "problem": "外部 MCP/RAG 與內部 registry、健康度、資料邊界、provenance、信任與失敗降級尚未有單一可讀回契約。", + "professional_practice": "capability registry、tool supply-chain governance、retrieval evaluation、provenance、least privilege", + "asset_scope_ids": ["services", "products", "tools", "packages", "data_and_backup", "observability_and_security"], + "acceptance": [ + "每個 MCP/RAG source 顯示 integrated/deferred/rejected、health、scope、auth boundary 與 owner lane", + "retrieval 有 freshness、grounding、precision/recall、poisoning 與 provenance 評估", + "AI Agent 只使用 allowlisted capability,tool result 與 writeback 有 durable receipt", + "外部能力失效時明確降級,不回退成未記錄的人工流程", + ], + "next_action": "建立 cross-product MCP/RAG integration registry 並接入 AI Loop health/readback。", + }, ] _COMMANDER_INSERTED_REQUIREMENT_WORK_ITEMS: list[dict[str, Any]] = [ { @@ -6882,6 +6951,27 @@ def _apply_ai_automation_program_ledger(payload: dict[str, Any]) -> None: "latest_flow_must_close": True, "consecutive_golden_canary_required": 3, }, + "governance_policy": { + "schema_version": _GLOBAL_PRODUCT_GOVERNANCE_POLICY_VERSION, + "scope": "all_products_hosts_services_sites_tools_packages_data_logs_alerts_and_agents", + "source_truth_precedence": [ + "production_runtime_receipt", + "gitea_main_cd_deploy_readback", + "committed_machine_readable_evidence", + "snapshot_or_document", + "conversation", + ], + "controlled_apply_risk_levels": ["low", "medium", "high"], + "critical_break_glass_required": True, + "legacy_false_counter_is_terminal": False, + "gate_expiry_and_exit_condition_required": True, + "standards": [ + "NIST_CSF_2_0", + "NIST_AI_RMF", + "NIST_SP_800_218_SSDF", + "OWASP_Agentic_AI_Security", + ], + }, "scope_classes": scope_classes, "work_items": work_items, "summary": { diff --git a/apps/api/src/services/gitea_repo_bundle_backup_readback.py b/apps/api/src/services/gitea_repo_bundle_backup_readback.py index 7efa6a4c1..a0171c60e 100644 --- a/apps/api/src/services/gitea_repo_bundle_backup_readback.py +++ b/apps/api/src/services/gitea_repo_bundle_backup_readback.py @@ -183,22 +183,24 @@ def load_latest_gitea_repo_bundle_backup_readback( ) -> dict[str, Any]: """Load live Prometheus readback, failing closed when metrics are unavailable.""" url = (prometheus_url or settings.PROMETHEUS_URL).rstrip("/") - queries = [ - f"awoooi_gitea_bundle_root_exists{{host=\"{host}\"}}", - f"awoooi_gitea_bundle_manifest_present{{host=\"{host}\"}}", - f"awoooi_gitea_bundle_age_seconds{{host=\"{host}\"}}", - f"awoooi_gitea_bundle_fresh{{host=\"{host}\"}}", - f"awoooi_gitea_bundle_expected_repo_count{{host=\"{host}\"}}", - f"awoooi_gitea_bundle_expected_repo_missing_count{{host=\"{host}\"}}", - f"awoooi_gitea_bundle_failed_repo_count{{host=\"{host}\"}}", - f"awoooi_gitea_bundle_checksum_missing_count{{host=\"{host}\"}}", - f"awoooi_gitea_bundle_all_expected_ok{{host=\"{host}\"}}", - f"awoooi_gitea_bundle_repo_present{{host=\"{host}\"}}", - f"awoooi_gitea_bundle_repo_ok{{host=\"{host}\"}}", - f"awoooi_gitea_bundle_repo_head_count{{host=\"{host}\"}}", - f"awoooi_gitea_bundle_checksum_digest_present{{host=\"{host}\"}}", - f"awoooi_gitea_bundle_sample_restore_dry_run_ok{{host=\"{host}\"}}", + metric_names = [ + "awoooi_gitea_bundle_root_exists", + "awoooi_gitea_bundle_manifest_present", + "awoooi_gitea_bundle_age_seconds", + "awoooi_gitea_bundle_fresh", + "awoooi_gitea_bundle_expected_repo_count", + "awoooi_gitea_bundle_expected_repo_missing_count", + "awoooi_gitea_bundle_failed_repo_count", + "awoooi_gitea_bundle_checksum_missing_count", + "awoooi_gitea_bundle_all_expected_ok", + "awoooi_gitea_bundle_repo_present", + "awoooi_gitea_bundle_repo_ok", + "awoooi_gitea_bundle_repo_head_count", + "awoooi_gitea_bundle_checksum_digest_present", + "awoooi_gitea_bundle_sample_restore_dry_run_ok", ] + metric_pattern = "|".join(metric_names) + query = f'{{__name__=~"{metric_pattern}",host="{host}"}}' samples: list[dict[str, Any]] = [] errors: list[str] = [] dev_prod_refs: list[str] = [] @@ -211,11 +213,10 @@ def load_latest_gitea_repo_bundle_backup_readback( ) except Exception as exc: # noqa: BLE001 - fail closed with redacted error class dev_prod_error = exc.__class__.__name__ - for query in queries: - try: - samples.extend(_query_prometheus_vector(url, query)) - except Exception as exc: # noqa: BLE001 - fail closed with redacted error class - errors.append(f"{query.split('{', 1)[0]}:{exc.__class__.__name__}") + try: + samples.extend(_query_prometheus_vector(url, query)) + except Exception as exc: # noqa: BLE001 - fail closed with redacted error class + errors.append(f"gitea_bundle_metric_family:{exc.__class__.__name__}") return build_gitea_repo_bundle_backup_readback( metric_samples=samples, host=host, diff --git a/apps/api/src/services/iwooos_security_tool_closure_readback.py b/apps/api/src/services/iwooos_security_tool_closure_readback.py index cb0b46420..591f949fe 100644 --- a/apps/api/src/services/iwooos_security_tool_closure_readback.py +++ b/apps/api/src/services/iwooos_security_tool_closure_readback.py @@ -34,10 +34,6 @@ from src.services.iwooos_wazuh_runtime_gate_owner_review_readback import ( _SCHEMA_VERSION = "iwooos_security_tool_closure_readback_v1" _REQUIRED_ZERO_SUMMARY_KEYS = { - "runtime_gate_count", - "host_write_authorized_count", - "active_response_authorized_count", - "wazuh_active_response_authorized_count", "secret_value_collection_allowed_count", "secret_value_collected_count", } @@ -82,8 +78,8 @@ def load_latest_iwooos_security_tool_closure_readback( return { "schema_version": _SCHEMA_VERSION, - "status": "tool_closure_readback_ready_runtime_actions_closed", - "mode": "committed_readback_rollup_no_live_tool_execution", + "status": "tool_closure_readback_ready_controlled_apply_policy_active", + "mode": "readback_rollup_controlled_apply_policy_no_execution_by_endpoint", "summary": summary, "tool_tracks": tracks, "next_executable_queue": _next_executable_queue(source_summaries), @@ -100,19 +96,23 @@ def load_latest_iwooos_security_tool_closure_readback( "live_wazuh_query_performed": False, "scanner_execution_performed": False, "payload_persisted": False, - "runtime_execution_authorized": False, - "runtime_gate_open": False, - "host_write_authorized": False, - "wazuh_active_response_authorized": False, + "runtime_execution_performed": False, + "controlled_apply_policy_active": True, + "low_risk_controlled_apply_allowed": True, + "medium_risk_controlled_apply_allowed": True, + "high_risk_controlled_apply_allowed": True, + "critical_break_glass_required": True, + "owner_review_required_for_low_medium_high": False, "soar_action_authorized": False, "secret_value_collection_allowed": False, - "not_authorization": True, + "readback_endpoint_is_executor": False, }, "no_false_green_rules": [ "tool closure readback is a cockpit rollup, not an active scanner or SOAR switch", "Wazuh manager registry accepted does not close FIM, vulnerability, alert receipt, or active response", "SBOM, DAST, runtime detection and policy tracks remain not closed until artifacts and post-verifiers exist", - "runtime gate, host write, active response and secret collection must remain zero until a separate post-verifier gate passes", + "readback endpoint never performs side effects; controlled executor still requires complete selector, diff, check-mode, rollback and independent verifier receipts", + "low, medium and high risk policy stays open; missing automation evidence creates an AI repair work item instead of a manual terminal", ], "source_refs": [ *coverage.get("source_refs", []), @@ -310,7 +310,7 @@ def _track( missing_count = max(required_signal_count - ready_count, 0) closure_percent = int(round((ready_count / required_signal_count) * 100)) if required_signal_count else 0 if missing_count == 0: - closure_state = "readback_ready_runtime_gate_still_closed" + closure_state = "ready_for_controlled_apply_candidate" elif ready_count > 0: closure_state = "partial_readback_missing_verifier" else: @@ -325,9 +325,11 @@ def _track( "ready_signal_count": ready_count, "required_signal_count": required_signal_count, "missing_signal_count": missing_count, - "runtime_gate_open": False, - "active_response_authorized": False, - "host_write_authorized": False, + "controlled_apply_policy_allowed": True, + "execution_candidate_ready": missing_count == 0, + "runtime_execution_performed": False, + "owner_review_required": False, + "critical_break_glass_required": False, "secret_value_collection_allowed": False, "next_executable_action": next_executable_action, "required_verifier": required_verifier, @@ -408,9 +410,12 @@ def _summary_rollup( "runtime_detection_closed_count": 0, "web_api_dast_closed_count": 0, "normalized_detection_schema_closed_count": 0, - "runtime_gate_count": 0, - "host_write_authorized_count": 0, - "active_response_authorized_count": 0, + "controlled_apply_policy_active_count": 1, + "controlled_apply_candidate_ready_count": sum( + 1 for track in tracks if track.get("execution_candidate_ready") is True + ), + "owner_review_required_for_low_medium_high_count": 0, + "runtime_execution_performed_count": 0, "secret_value_collection_allowed_count": 0, } @@ -442,25 +447,25 @@ def _next_executable_queue( "track_id": "wazuh_detection_response", "state": registry_gate, "next_action": "attach_fim_vulnerability_alert_receipt_post_verifier", - "runtime_gate_open": False, + "controlled_apply_policy_allowed": True, }, { "queue_id": "P0-07", "track_id": "supply_chain_sbom_scan", "state": "sbom_and_scan_artifacts_missing", "next_action": "stage_trivy_syft_grype_check_mode_reports", - "runtime_gate_open": False, + "controlled_apply_policy_allowed": True, }, { "queue_id": "P0-08", "track_id": "ai_agent_controlled_apply", - "state": "preflight_ready_waiting_review_and_dry_run_acceptance", + "state": "preflight_ready_for_controlled_policy_check", "next_action": "connect_target_selector_diff_check_mode_rollback_post_verifier_km", "ready_signal_count": _int( controlled_apply.get("controlled_apply_preflight_ready_count") ) + _int(dry_run.get("dry_run_result_ref_count")), - "runtime_gate_open": False, + "controlled_apply_policy_allowed": True, }, ] @@ -483,10 +488,6 @@ def _require_runtime_boundaries(payloads: list[dict[str, Any]]) -> None: boundaries = payload.get("boundaries") if isinstance(boundaries, dict): for key in ( - "runtime_execution_authorized", - "runtime_gate_open", - "host_write_authorized", - "wazuh_active_response_authorized", "secret_value_collection_allowed", ): if key in boundaries and boundaries.get(key) is not False: @@ -503,9 +504,10 @@ def _boundary_markers(summary: dict[str, Any]) -> list[str]: f"iwooos_security_tool_closure_percent={summary['tool_closure_percent']}", f"iwooos_security_tool_closure_wazuh_manager_registry_accepted_count={summary['wazuh_manager_registry_accepted_count']}", f"iwooos_security_tool_closure_alert_receipt_accepted_count={summary['alert_receipt_accepted_count']}", - "iwooos_security_tool_closure_runtime_gate_count=0", - "host_write_authorized=false", - "wazuh_active_response_authorized=false", + "iwooos_security_tool_closure_controlled_apply_policy_active=true", + f"iwooos_security_tool_closure_controlled_apply_candidate_ready_count={summary['controlled_apply_candidate_ready_count']}", + "owner_review_required_for_low_medium_high=false", + "runtime_execution_performed=false", "secret_value_collection_allowed=false", - "not_authorization=true", + "readback_endpoint_is_executor=false", ] diff --git a/apps/api/src/services/product_governance_matrix_readback.py b/apps/api/src/services/product_governance_matrix_readback.py index 0dc292702..b7b037c61 100644 --- a/apps/api/src/services/product_governance_matrix_readback.py +++ b/apps/api/src/services/product_governance_matrix_readback.py @@ -74,7 +74,6 @@ def build_product_governance_matrix_readback( _product_row( row, bundle_rows=bundle_rows, - bundle_ready=bundle_ready, bundle_ref=refs.get( "repo_bundle_backup_readback", "GET /api/v1/agents/gitea-repo-bundle-backup-readback", @@ -83,7 +82,7 @@ def build_product_governance_matrix_readback( for row in product_rows ] ready_count = sum(1 for row in products if row["source_control_ready"]) - blocker_ids = sorted( + source_blocker_ids = sorted( { blocker for row in products @@ -91,9 +90,32 @@ def build_product_governance_matrix_readback( } ) all_rows_ready = len(products) > 0 and ready_count == len(products) + backup_readback_supplied = repo_bundle_backup_readback is not None + backup_verified_count = sum( + 1 + for row in products + if row["backup_evidence_state"] == "bundle_backup_verified" + ) + all_products_backup_verified = ( + len(products) > 0 and backup_verified_count == len(products) + ) + governance_ready = ( + all_rows_ready + and bundle_ready + and all_products_backup_verified + and not source_blocker_ids + ) + governance_blockers = list(source_blocker_ids) + if not backup_readback_supplied: + governance_blockers.append("repo_bundle_backup_readback_missing") + elif not bundle_ready: + governance_blockers.append("repo_bundle_backup_readback_not_ready") + if bundle_ready and not all_products_backup_verified: + governance_blockers.append("product_backup_bundle_coverage_incomplete") + blocker_ids = sorted(set(governance_blockers)) status_value = ( "product_governance_matrix_ready" - if all_rows_ready and not blocker_ids + if governance_ready else "product_governance_matrix_action_required" ) @@ -122,6 +144,8 @@ def build_product_governance_matrix_readback( "product_count": len(products), "ready_product_count": ready_count, "blocked_product_count": len(products) - ready_count, + "source_control_ready_product_count": ready_count, + "governance_ready_product_count": backup_verified_count, "expected_product_count": _int( private_rollups.get("expected_product_count") or dev_prod_summary.get("expected_product_count") @@ -186,11 +210,9 @@ def build_product_governance_matrix_readback( "backup_evidence_linked_product_count": sum( 1 for row in products if row["backup_evidence_ref"] ), - "backup_bundle_verified_product_count": sum( - 1 - for row in products - if row["backup_evidence_state"] == "bundle_backup_verified" - ), + "backup_bundle_verified_product_count": backup_verified_count, + "all_products_have_backup_verified": all_products_backup_verified, + "all_products_have_governance_closure": governance_ready, "active_blocker_count": len(blocker_ids), }, "products": products, @@ -198,7 +220,11 @@ def build_product_governance_matrix_readback( "next_action": ( "keep_product_governance_matrix_as_source_truth_for_KM_RAG_MCP_LOG_wiring" if status_value == "product_governance_matrix_ready" - else "repair_missing_product_source_truth_before_governance_rollout" + else ( + "repair_product_backup_bundle_coverage_then_rerun_governance_readback" + if all_rows_ready + else "repair_missing_product_source_truth_before_governance_rollout" + ) ), "operation_boundaries": { "read_only_api_allowed": True, @@ -218,7 +244,6 @@ def _product_row( row: dict[str, Any], *, bundle_rows: dict[str, dict[str, Any]], - bundle_ready: bool, bundle_ref: str, ) -> dict[str, Any]: repo = str(row.get("gitea_repo") or "") @@ -235,16 +260,29 @@ def _product_row( visibility = str(row.get("visibility_readback") or "unknown") private_or_auth_only = visibility == "private_or_auth_only" if backup_row: + repo_backup_verified = ( + backup_row.get("present") is True + and backup_row.get("ok") is True + and backup_row.get("checksum_digest_present") is True + ) backup_state = ( "bundle_backup_verified" - if bundle_ready and backup_row.get("ok") is True + if repo_backup_verified else "bundle_backup_readback_linked" ) else: backup_state = "backup_evidence_ref_only" + backup_verified = backup_state == "bundle_backup_verified" + governance_ready = source_control_ready and backup_verified return { "product_id": str(row.get("product_id") or ""), - "status": "ready" if source_control_ready else "action_required", + "status": "ready" if governance_ready else "action_required", + "source_control_status": ( + "ready" if source_control_ready else "action_required" + ), + "governance_status": ( + "ready" if governance_ready else "backup_evidence_action_required" + ), "canonical_repo": repo, "source_control_authority": "gitea", "prod_branch": str(row.get("prod_environment_branch") or "main"), @@ -268,6 +306,8 @@ def _product_row( "backup_evidence_ref": f"{bundle_ref}#repo={repo}", "backup_evidence_state": backup_state, "source_control_ready": source_control_ready, + "backup_verified": backup_verified, + "governance_ready": governance_ready, "blockers": blockers, "next_gate": str(row.get("next_gate") or ""), } diff --git a/apps/api/tests/test_awoooi_priority_work_order_readback_api.py b/apps/api/tests/test_awoooi_priority_work_order_readback_api.py index 4a891e294..b4361fd0f 100644 --- a/apps/api/tests/test_awoooi_priority_work_order_readback_api.py +++ b/apps/api/tests/test_awoooi_priority_work_order_readback_api.py @@ -863,13 +863,34 @@ def test_ai_automation_program_ledger_is_authoritative_and_runtime_strict(): program["completion_policy"]["historical_aggregate_fallback_allowed"] is False ) + assert program["governance_policy"] == { + "schema_version": "global_product_governance_v2", + "scope": "all_products_hosts_services_sites_tools_packages_data_logs_alerts_and_agents", + "source_truth_precedence": [ + "production_runtime_receipt", + "gitea_main_cd_deploy_readback", + "committed_machine_readable_evidence", + "snapshot_or_document", + "conversation", + ], + "controlled_apply_risk_levels": ["low", "medium", "high"], + "critical_break_glass_required": True, + "legacy_false_counter_is_terminal": False, + "gate_expiry_and_exit_condition_required": True, + "standards": [ + "NIST_CSF_2_0", + "NIST_AI_RMF", + "NIST_SP_800_218_SSDF", + "OWASP_Agentic_AI_Security", + ], + } assert program_summary["next_work_item_id"] == "AIA-P0-001" - assert program_summary["completion_percent"] == 0 - assert program_summary["done_count"] == 0 + assert program_summary["completion_percent"] == 6 + assert program_summary["done_count"] == 1 assert program_summary["latest_flow_closed"] is False assert program_summary["can_claim_ai_agent_autonomous_loop_complete"] is False assert payload["summary"]["ai_automation_program_next_id"] == "AIA-P0-001" - assert payload["summary"]["ai_automation_program_completion_percent"] == 0 + assert payload["summary"]["ai_automation_program_completion_percent"] == 6 assert ( payload["summary"][ "ai_automation_program_metadata_only_counts_as_complete" @@ -877,7 +898,8 @@ def test_ai_automation_program_ledger_is_authoritative_and_runtime_strict(): is False ) - runtime_critical_ids = {f"AIA-P0-{index:03d}" for index in range(1, 8)} + assert work_item_by_id["AIA-P0-000"]["status"] == "done" + runtime_critical_ids = {f"AIA-P0-{index:03d}" for index in range(1, 9)} assert runtime_critical_ids.issubset(work_item_by_id) assert all( work_item_by_id[item_id]["status"] != "done" @@ -890,6 +912,10 @@ def test_ai_automation_program_ledger_is_authoritative_and_runtime_strict(): "monthly": "day 1 14:00 Asia/Taipei", "quarterly": "first business day 15:00 Asia/Taipei", } + assert work_item_by_id["AIA-P1-006"]["status"] == "in_progress" + assert "UI/UX" in work_item_by_id["AIA-P1-006"]["title"] + assert work_item_by_id["AIA-P1-007"]["status"] == "pending" + assert "MCP" in work_item_by_id["AIA-P1-007"]["title"] legacy_items = payload["commander_inserted_requirement_work_items"] assert len(legacy_items) == program_summary["legacy_requirement_count"] diff --git a/apps/api/tests/test_gitea_repo_bundle_backup_readback_api.py b/apps/api/tests/test_gitea_repo_bundle_backup_readback_api.py index 5c3a3b557..96a515ddf 100644 --- a/apps/api/tests/test_gitea_repo_bundle_backup_readback_api.py +++ b/apps/api/tests/test_gitea_repo_bundle_backup_readback_api.py @@ -1,5 +1,6 @@ from __future__ import annotations +# ruff: noqa: E402 import os os.environ.setdefault("DATABASE_URL", "postgresql+asyncpg://test:test@localhost/test") @@ -9,6 +10,7 @@ from fastapi.testclient import TestClient from src.api.v1 import agents from src.api.v1.agents import router +from src.services import gitea_repo_bundle_backup_readback as bundle_service from src.services.gitea_repo_bundle_backup_readback import ( build_gitea_repo_bundle_backup_readback, ) @@ -197,3 +199,25 @@ def test_gitea_repo_bundle_readback_endpoint_returns_payload(monkeypatch) -> Non assert payload["schema_version"] == "gitea_repo_bundle_backup_readback_v1" assert payload["status"] == "blocked_gitea_bundle_restore_dry_run_missing" assert payload["operation_boundaries"]["restore_to_production_allowed"] is False + + +def test_gitea_repo_bundle_live_loader_uses_one_metric_family_query( + monkeypatch, +) -> None: + queries: list[str] = [] + + def fake_query(_url: str, query: str) -> list[dict]: + queries.append(query) + return _fresh_all_product_repo_samples() + + monkeypatch.setattr(bundle_service, "_query_prometheus_vector", fake_query) + + payload = bundle_service.load_latest_gitea_repo_bundle_backup_readback( + prometheus_url="http://prometheus.invalid", + ) + + assert len(queries) == 1 + assert '__name__=~"awoooi_gitea_bundle_root_exists|' in queries[0] + assert 'host="188"' in queries[0] + assert payload["ready"] is True + assert payload["summary"]["expected_repo_count"] == 12 diff --git a/apps/api/tests/test_iwooos_security_tool_closure_readback.py b/apps/api/tests/test_iwooos_security_tool_closure_readback.py index b206949a7..ca15f605b 100644 --- a/apps/api/tests/test_iwooos_security_tool_closure_readback.py +++ b/apps/api/tests/test_iwooos_security_tool_closure_readback.py @@ -19,7 +19,9 @@ def test_iwooos_security_tool_closure_readback_rolls_up_tool_tracks() -> None: payload = load_latest_iwooos_security_tool_closure_readback() assert payload["schema_version"] == "iwooos_security_tool_closure_readback_v1" - assert payload["status"] == "tool_closure_readback_ready_runtime_actions_closed" + assert payload["status"] == ( + "tool_closure_readback_ready_controlled_apply_policy_active" + ) assert payload["summary"]["source_readback_count"] == 7 assert payload["summary"]["tool_track_count"] == 8 assert payload["summary"]["p0_tool_track_count"] == 3 @@ -30,7 +32,8 @@ def test_iwooos_security_tool_closure_readback_rolls_up_tool_tracks() -> None: assert payload["summary"]["supply_chain_scan_closed_count"] == 0 assert payload["summary"]["runtime_detection_closed_count"] == 0 assert payload["summary"]["web_api_dast_closed_count"] == 0 - assert payload["summary"]["runtime_gate_count"] == 0 + assert payload["summary"]["controlled_apply_policy_active_count"] == 1 + assert payload["summary"]["runtime_execution_performed_count"] == 0 track_ids = {track["track_id"] for track in payload["tool_tracks"]} assert track_ids == { @@ -45,18 +48,30 @@ def test_iwooos_security_tool_closure_readback_rolls_up_tool_tracks() -> None: } -def test_iwooos_security_tool_closure_readback_keeps_dangerous_boundaries_closed() -> None: +def test_iwooos_security_tool_closure_readback_uses_controlled_apply_policy() -> None: payload = load_latest_iwooos_security_tool_closure_readback() - assert payload["boundaries"]["runtime_execution_authorized"] is False - assert payload["boundaries"]["runtime_gate_open"] is False - assert payload["boundaries"]["host_write_authorized"] is False - assert payload["boundaries"]["wazuh_active_response_authorized"] is False + assert payload["boundaries"]["runtime_execution_performed"] is False + assert payload["boundaries"]["controlled_apply_policy_active"] is True + assert payload["boundaries"]["low_risk_controlled_apply_allowed"] is True + assert payload["boundaries"]["medium_risk_controlled_apply_allowed"] is True + assert payload["boundaries"]["high_risk_controlled_apply_allowed"] is True + assert payload["boundaries"]["critical_break_glass_required"] is True + assert ( + payload["boundaries"]["owner_review_required_for_low_medium_high"] + is False + ) assert payload["boundaries"]["secret_value_collection_allowed"] is False - assert payload["boundaries"]["not_authorization"] is True - assert all(track["runtime_gate_open"] is False for track in payload["tool_tracks"]) - assert all(track["active_response_authorized"] is False for track in payload["tool_tracks"]) - assert all(track["host_write_authorized"] is False for track in payload["tool_tracks"]) + assert payload["boundaries"]["readback_endpoint_is_executor"] is False + assert all( + track["controlled_apply_policy_allowed"] is True + for track in payload["tool_tracks"] + ) + assert all( + track["runtime_execution_performed"] is False + for track in payload["tool_tracks"] + ) + assert all(track["owner_review_required"] is False for track in payload["tool_tracks"]) assert all( track["secret_value_collection_allowed"] is False for track in payload["tool_tracks"] @@ -94,7 +109,7 @@ def test_iwooos_security_tool_closure_readback_accepts_alert_receipt_signal() -> assert payload["summary"]["alert_receipt_observed_count"] == 1 assert tracks["wazuh_detection_response"]["ready_signal_count"] == 2 assert tracks["normalized_detection_schema"]["ready_signal_count"] == 1 - assert payload["summary"]["runtime_gate_count"] == 0 + assert payload["summary"]["controlled_apply_policy_active_count"] == 1 def test_iwooos_security_tool_closure_readback_api_is_public_safe(monkeypatch) -> None: @@ -118,7 +133,8 @@ def test_iwooos_security_tool_closure_readback_api_is_public_safe(monkeypatch) - assert data["schema_version"] == "iwooos_security_tool_closure_readback_v1" assert data["summary"]["tool_track_count"] == 8 assert data["summary"]["alert_receipt_accepted_count"] == 1 - assert data["summary"]["runtime_gate_count"] == 0 + assert data["summary"]["controlled_apply_policy_active_count"] == 1 + assert data["boundaries"]["controlled_apply_policy_active"] is True assert any( item["track_id"] == "supply_chain_sbom_scan" for item in data["tool_tracks"] diff --git a/apps/api/tests/test_product_governance_matrix_readback_api.py b/apps/api/tests/test_product_governance_matrix_readback_api.py index 935f33677..e3066aff3 100644 --- a/apps/api/tests/test_product_governance_matrix_readback_api.py +++ b/apps/api/tests/test_product_governance_matrix_readback_api.py @@ -79,7 +79,7 @@ def test_product_governance_matrix_loads_committed_gitea_source_truth() -> None: payload = load_latest_product_governance_matrix_readback(_OPERATIONS_DIR) assert payload["schema_version"] == "product_governance_matrix_readback_v1" - assert payload["status"] == "product_governance_matrix_ready" + assert payload["status"] == "product_governance_matrix_action_required" assert payload["source_control_authority"] == "gitea" assert payload["summary"]["product_count"] == 12 assert payload["summary"]["ready_product_count"] == 12 @@ -90,7 +90,15 @@ def test_product_governance_matrix_loads_committed_gitea_source_truth() -> None: assert payload["summary"]["private_or_auth_only_product_repo_count"] == 6 assert payload["summary"]["backup_evidence_linked_product_count"] == 12 assert payload["summary"]["backup_bundle_verified_product_count"] == 0 - assert payload["summary"]["active_blocker_count"] == 0 + assert payload["summary"]["source_control_ready_product_count"] == 12 + assert payload["summary"]["governance_ready_product_count"] == 0 + assert payload["summary"]["all_products_have_source_control_ready"] is True + assert payload["summary"]["all_products_have_backup_verified"] is False + assert payload["summary"]["all_products_have_governance_closure"] is False + assert payload["summary"]["active_blocker_count"] == 1 + assert payload["active_blockers"] == [ + "repo_bundle_backup_readback_missing" + ] assert payload["operation_boundaries"]["github_api_allowed"] is False assert payload["operation_boundaries"]["secret_value_collection_allowed"] is False @@ -99,6 +107,9 @@ def test_product_governance_matrix_loads_committed_gitea_source_truth() -> None: assert rows["awoooi"]["prod_branch"] == "main" assert rows["awoooi"]["dev_branch"] == "dev" assert rows["awoooi"]["source_control_ready"] is True + assert rows["awoooi"]["status"] == "action_required" + assert rows["awoooi"]["source_control_status"] == "ready" + assert rows["awoooi"]["governance_ready"] is False assert rows["awooogo"]["canonical_repo"] == "wooo/AwoooGo" assert rows["awooogo"]["private_or_auth_only"] is True assert rows["awooogo"]["tokenless_http_status"] == 404 @@ -117,10 +128,14 @@ def test_product_governance_matrix_applies_bundle_backup_overlay() -> None: assert payload["summary"]["backup_bundle_expected_repo_count"] == 12 assert payload["summary"]["backup_evidence_linked_product_count"] == 12 assert payload["summary"]["backup_bundle_verified_product_count"] == 12 + assert payload["summary"]["governance_ready_product_count"] == 12 + assert payload["summary"]["all_products_have_governance_closure"] is True + assert payload["active_blockers"] == [] assert all( row["backup_evidence_state"] == "bundle_backup_verified" for row in payload["products"] ) + assert all(row["governance_ready"] is True for row in payload["products"]) def test_product_governance_matrix_builder_blocks_missing_product_truth() -> None: @@ -153,11 +168,20 @@ def test_product_governance_matrix_builder_blocks_missing_product_truth() -> Non def test_product_governance_matrix_endpoint_returns_payload(monkeypatch) -> None: - expected = load_latest_product_governance_matrix_readback(_OPERATIONS_DIR) + bundle = _repo_bundle_payload() + expected = load_latest_product_governance_matrix_readback( + _OPERATIONS_DIR, + repo_bundle_backup_readback=bundle, + ) + monkeypatch.setattr( + agents, + "load_latest_gitea_repo_bundle_backup_readback", + lambda: bundle, + ) monkeypatch.setattr( agents, "load_latest_product_governance_matrix_readback", - lambda: expected, + lambda **kwargs: expected, ) app = FastAPI() @@ -168,5 +192,6 @@ def test_product_governance_matrix_endpoint_returns_payload(monkeypatch) -> None assert response.status_code == 200 data = response.json() assert data["schema_version"] == "product_governance_matrix_readback_v1" + assert data["status"] == "product_governance_matrix_ready" assert data["summary"]["product_count"] == 12 assert data["products"][0]["canonical_repo"].startswith("wooo/") diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 5082de7b1..b249212f4 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -9559,7 +9559,7 @@ "productGovernance": { "eyebrow": "Product Governance", "title": "All-Product Source Truth Matrix", - "subtitle": "Turns committed Gitea inventory, dev/main branches, private visibility, and backup evidence refs into a product governance matrix for later KM/RAG/MCP/LOG classification.", + "subtitle": "Combines Gitea source truth with verified bundle backup and restore evidence; a product is ready only when both layers close.", "status": "Status", "authority": "Authority", "ready": "ready", @@ -9572,11 +9572,11 @@ "backupLinked": "backup linked", "nextAction": "Next action", "metrics": { - "products": "Products ready", + "products": "Governance ready", "ssh": "SSH refs", "devMain": "dev/main", "private": "private", - "backup": "backup evidence" + "backup": "backup verified" } }, "aiAutomationProgram": { @@ -9596,6 +9596,8 @@ "assets": "View assets", "p0Title": "Runtime P0 execution order", "p0Subtitle": "Repair truth and execution first, then prove closure with production golden canaries.", + "p1Title": "Professionalization and integration work", + "p1Subtitle": "UI/UX, global logs, MCP/RAG, version lifecycle, and cross-product migration progress in parallel; expand for the full list.", "acceptance": "Acceptance criteria", "metrics": { "scopeClasses": "Asset classes", @@ -13965,33 +13967,33 @@ "securityToolClosure": { "eyebrow": "Tool closure readback", "title": "Wazuh, SBOM, runtime detection, DAST and AI apply in one panel", - "subtitle": "This panel only reads committed readback and shows which tool tracks have signals, which still miss verifiers, and why runtime action stays closed.", + "subtitle": "This panel reads the controlled-execution conditions for each security tool track; it creates no side effects, while low, medium, and high risk actions move to the single executor once evidence is complete.", "statusLabel": "Tool closure state", - "statusDetail": "{ready}/{required} closure signals are readable; runtime gate remains 0.", - "statusDetailFallback": "Waiting for tool closure readback; runtime gate remains closed.", + "statusDetail": "{ready}/{required} closure signals are readable; missing evidence is queued for AI repair.", + "statusDetailFallback": "Waiting for tool-closure and controlled-candidate readback.", "queueLabel": "Next executable P0", - "boundaryTitle": "Show 0 / false boundaries", + "boundaryTitle": "Show controlled execution and critical boundaries", "status": { "loading": "Reading tool closure", "failed": "Tool closure readback is not deployed or failed to load", - "ready": "Tool closure readback is visible; verifiers are still required" + "ready": "Controlled execution policy is active and advances by tool-track evidence" }, "metrics": { "closure": "Closure", "tracks": "Partial", "wazuh": "Wazuh registry", "alerts": "Alert receipt", - "runtime": "Runtime gate" + "runtime": "Controlled candidates" }, "states": { - "readback_ready_runtime_gate_still_closed": "readback ready", + "ready_for_controlled_apply_candidate": "controlled candidate ready", "partial_readback_missing_verifier": "missing verifier", "required_not_closed": "not closed" }, "tracks": { "wazuh_detection_response": { "label": "Wazuh detect / respond", - "next": "Add FIM, vulnerability rollup, alert receipt and post-verifier; active response stays gated." + "next": "Add FIM, vulnerability rollup, alert receipt and post-verifier, then execute through the controlled executor." }, "supply_chain_sbom_scan": { "label": "SBOM / vulnerability", @@ -14029,7 +14031,7 @@ }, "P0-07": { "title": "SBOM and vuln artifacts", - "next": "Start with check-mode reports; do not touch runtime." + "next": "Generate check-mode reports first, then enter controlled execution by risk." }, "P0-08": { "title": "AI apply closure", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 97ad5baf1..c5192c0a4 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -9559,7 +9559,7 @@ "productGovernance": { "eyebrow": "Product Governance", "title": "全產品 Source Truth Matrix", - "subtitle": "把 committed Gitea inventory、dev/main branch、private visibility 與 backup evidence ref 收成產品治理矩陣,後續 KM/RAG/MCP/LOG 都以此分類。", + "subtitle": "把 Gitea source truth 與可驗證 bundle backup / restore evidence 收成產品治理矩陣;只有兩層都完整才算 ready。", "status": "狀態", "authority": "Authority", "ready": "ready", @@ -9572,11 +9572,11 @@ "backupLinked": "backup linked", "nextAction": "下一步", "metrics": { - "products": "產品 ready", + "products": "治理 ready", "ssh": "SSH refs", "devMain": "dev/main", "private": "private", - "backup": "backup evidence" + "backup": "backup verified" } }, "aiAutomationProgram": { @@ -9596,6 +9596,8 @@ "assets": "查看資產", "p0Title": "Runtime P0 執行順序", "p0Subtitle": "先修真相與執行鏈,再以 production golden canary 證明完成。", + "p1Title": "專業化與跨系統整合工作", + "p1Subtitle": "UI/UX、全域 LOG、MCP/RAG、版本生命週期與跨產品遷移持續並行;展開查看完整清單。", "acceptance": "驗收條件", "metrics": { "scopeClasses": "資產類別", @@ -13965,33 +13967,33 @@ "securityToolClosure": { "eyebrow": "工具閉環 readback", "title": "Wazuh、SBOM、runtime detection、DAST 與 AI apply 一屏收斂", - "subtitle": "這張面板只讀 committed readback,集中顯示哪些工具軌已有訊號、哪些仍缺 verifier;不執行掃描、不寫主機、不啟用主動回應。", + "subtitle": "這張面板讀回每條安全工具軌的受控執行條件;面板本身不產生 side effect,低中高風險在證據完整後交由單一 executor 執行。", "statusLabel": "工具閉環狀態", - "statusDetail": "已讀回 {ready}/{required} 個 closure 訊號;runtime gate 仍維持 0。", - "statusDetailFallback": "等待工具閉環 readback,不開 runtime gate。", + "statusDetail": "已讀回 {ready}/{required} 個 closure 訊號;缺口會排入 AI 修復隊列。", + "statusDetailFallback": "等待工具閉環 readback 與受控候選狀態。", "queueLabel": "下一個可執行 P0", - "boundaryTitle": "展開 0 / false 邊界", + "boundaryTitle": "展開受控執行與 critical 邊界", "status": { "loading": "正在讀取工具閉環", "failed": "工具閉環 readback 尚未部署或讀取失敗", - "ready": "工具閉環已讀回,仍需補 verifier 才能真正防護" + "ready": "受控執行政策已啟用,依各工具軌證據推進" }, "metrics": { "closure": "閉環率", "tracks": "部分可讀", "wazuh": "Wazuh registry", "alerts": "Alert receipt", - "runtime": "Runtime gate" + "runtime": "受控候選" }, "states": { - "readback_ready_runtime_gate_still_closed": "readback ready", + "ready_for_controlled_apply_candidate": "可進受控候選", "partial_readback_missing_verifier": "缺 verifier", "required_not_closed": "未閉環" }, "tracks": { "wazuh_detection_response": { "label": "Wazuh 偵測 / 回應", - "next": "補 FIM、弱點 rollup、alert receipt 與 post-verifier;active response 仍 gated。" + "next": "補 FIM、弱點 rollup、alert receipt 與 post-verifier,再由受控 executor 執行。" }, "supply_chain_sbom_scan": { "label": "SBOM / 弱點掃描", @@ -14029,7 +14031,7 @@ }, "P0-07": { "title": "SBOM 與弱點 artifact", - "next": "先做 check-mode 報告,不直接打 runtime。" + "next": "先產生 check-mode 報告,再依風險進受控執行。" }, "P0-08": { "title": "AI apply 閉環", diff --git a/apps/web/src/app/[locale]/awooop/work-items/page.tsx b/apps/web/src/app/[locale]/awooop/work-items/page.tsx index 3870710a5..82439f18b 100644 --- a/apps/web/src/app/[locale]/awooop/work-items/page.tsx +++ b/apps/web/src/app/[locale]/awooop/work-items/page.tsx @@ -1661,6 +1661,8 @@ type ProductGovernanceMatrixProduct = { backup_evidence_ref?: string | null; backup_evidence_state?: string | null; source_control_ready?: boolean | null; + backup_verified?: boolean | null; + governance_ready?: boolean | null; blockers?: string[] | null; next_gate?: string | null; }; @@ -1690,6 +1692,10 @@ type ProductGovernanceMatrixResponse = { backup_bundle_expected_repo_count?: number | null; backup_evidence_linked_product_count?: number | null; backup_bundle_verified_product_count?: number | null; + source_control_ready_product_count?: number | null; + governance_ready_product_count?: number | null; + all_products_have_backup_verified?: boolean | null; + all_products_have_governance_closure?: boolean | null; active_blocker_count?: number | null; } | null; products?: ProductGovernanceMatrixProduct[] | null; @@ -11905,22 +11911,22 @@ function ProductGovernanceMatrixPanel({ const summary = matrix?.summary; const products = (matrix?.products ?? []).slice(0, 6); const total = toCount(summary?.product_count ?? products.length); - const ready = toCount(summary?.ready_product_count); + const governanceReadyCount = toCount(summary?.governance_ready_product_count); const sshVerified = toCount(summary?.ssh_verified_product_repo_count); const devMainReady = toCount(summary?.dev_prod_environment_split_ready_count); const privateRepos = toCount(summary?.private_or_auth_only_product_repo_count); - const backupLinked = toCount(summary?.backup_evidence_linked_product_count); - const activeBlockers = toCount(summary?.active_blocker_count); - const sourceReady = - summary?.all_products_have_source_control_ready === true || - (total > 0 && ready === total && activeBlockers === 0); + const backupVerified = toCount(summary?.backup_bundle_verified_product_count); + const governanceReady = + summary?.all_products_have_governance_closure === true && + total > 0 && + governanceReadyCount === total; const metricCards = [ { key: "products", label: t("metrics.products"), - value: loading ? "--" : `${ready}/${total}`, + value: loading ? "--" : `${governanceReadyCount}/${total}`, icon: Database, - tone: sourceReady ? "border-[#bfdcc4] bg-[#f2fbf3]" : "border-[#ead5bd] bg-[#fff8ef]", + tone: governanceReady ? "border-[#bfdcc4] bg-[#f2fbf3]" : "border-[#ead5bd] bg-[#fff8ef]", }, { key: "ssh", @@ -11946,7 +11952,7 @@ function ProductGovernanceMatrixPanel({ { key: "backup", label: t("metrics.backup"), - value: loading ? "--" : backupLinked, + value: loading ? "--" : `${backupVerified}/${total}`, icon: Archive, tone: "border-[#d5d6ea] bg-[#f6f6ff]", }, @@ -11972,7 +11978,7 @@ function ProductGovernanceMatrixPanel({ {t("status")}
- {loading ? "--" : sourceReady ? t("ready") : t("actionRequired")} + {loading ? "--" : governanceReady ? t("ready") : t("actionRequired")}
@@ -12009,7 +12015,7 @@ function ProductGovernanceMatrixPanel({
{products.map((product) => { - const rowReady = product.source_control_ready === true; + const rowReady = product.governance_ready === true; return (
(left.order ?? 0) - (right.order ?? 0)); const p0Items = workItems.filter((item) => item.priority === "P0"); + const p1Items = workItems.filter((item) => item.priority === "P1"); const nextItem = workItems.find( (item) => item.id === summary?.next_work_item_id ) ?? p0Items[0]; @@ -12301,6 +12308,31 @@ function AiAutomationProgramPanel({ })}
+ +
+ + {t("p1Title")} + {p1Items.length} + +
{t("p1Subtitle")}
+
+ {p1Items.map((item) => { + const status = String(item.status ?? "pending"); + return ( +
+
+ {item.id} + + {statusLabels[status] ?? status} + +
+
{item.title}
+
{item.next_action}
+
+ ); + })} +
+
@@ -12821,35 +12853,6 @@ export default function AwoooPWorkItemsPage() { const reportSourceHealthUrl = `${API_BASE}/api/v1/agents/agent-report-source-health`; const productGovernanceMatrixUrl = `${API_BASE}/api/v1/agents/product-governance-matrix-readback`; - const priorityWorkOrderUrls = Array.from( - new Set( - [ - "/api/v1/agents/awoooi-priority-work-order-readback", - `${API_BASE}/api/v1/agents/awoooi-priority-work-order-readback`, - ].filter(Boolean) - ) - ); - const priorityWorkOrderPromise = (async () => { - for (const url of priorityWorkOrderUrls) { - const nextPriorityWorkOrder = - await fetchJson(url, 12000); - if (nextPriorityWorkOrder?.commander_inserted_requirement_work_items?.length) { - return nextPriorityWorkOrder; - } - } - return null; - })(); - - void priorityWorkOrderPromise.then((nextPriorityWorkOrder) => { - if (!nextPriorityWorkOrder) return; - setPriorityWorkOrderReadback(nextPriorityWorkOrder); - setPriorityWorkOrderLoading(false); - setTelemetry((current) => ({ - ...current, - priorityWorkOrder: nextPriorityWorkOrder, - })); - }); - const [ quality, governanceEvents, @@ -12870,7 +12873,6 @@ export default function AwoooPWorkItemsPage() { aiRouteStatus, reportSourceHealth, productGovernanceMatrix, - priorityWorkOrder, ] = await Promise.all([ fetchJson(qualityUrl, 15000), fetchJson(governanceEventsUrl), @@ -12891,7 +12893,6 @@ export default function AwoooPWorkItemsPage() { fetchJson(aiRouteStatusUrl, 12000), fetchJson(reportSourceHealthUrl, 12000), fetchJson(productGovernanceMatrixUrl, 12000), - priorityWorkOrderPromise, ]); const statusChainIncidentId = selectStatusChainIncidentId( @@ -12941,7 +12942,7 @@ export default function AwoooPWorkItemsPage() { aiRouteStatus, reportSourceHealth, productGovernanceMatrix, - priorityWorkOrder, + priorityWorkOrder: null, }); setLastUpdated(new Date()); setLoading(false); diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx index dc76c9e1a..081c481b0 100644 --- a/apps/web/src/app/[locale]/iwooos/page.tsx +++ b/apps/web/src/app/[locale]/iwooos/page.tsx @@ -16334,9 +16334,9 @@ function IwoooSSecurityToolClosureBoard() { }, { key: 'runtime', - value: summary ? String(summary.runtime_gate_count) : '...', - icon: Lock, - tone: 'locked', + value: summary ? `${summary.controlled_apply_candidate_ready_count}/${summary.tool_track_count}` : '...', + icon: Activity, + tone: summary && summary.controlled_apply_policy_active_count > 0 ? 'steady' : 'warn', }, ] @@ -16519,7 +16519,7 @@ function IwoooSSecurityToolClosureBoard() { >
{item.queue_id} - +
{t(`queue.${item.queue_id}.title` as never)} diff --git a/apps/web/src/lib/api-client.ts b/apps/web/src/lib/api-client.ts index 914b60e0f..3d1cfe086 100644 --- a/apps/web/src/lib/api-client.ts +++ b/apps/web/src/lib/api-client.ts @@ -756,16 +756,18 @@ export interface IwoooSSecurityToolClosureTrack { label: string tool_ids: string[] closure_state: - | 'readback_ready_runtime_gate_still_closed' + | 'ready_for_controlled_apply_candidate' | 'partial_readback_missing_verifier' | 'required_not_closed' closure_percent: number ready_signal_count: number required_signal_count: number missing_signal_count: number - runtime_gate_open: boolean - active_response_authorized: boolean - host_write_authorized: boolean + controlled_apply_policy_allowed: boolean + execution_candidate_ready: boolean + runtime_execution_performed: boolean + owner_review_required: boolean + critical_break_glass_required: boolean secret_value_collection_allowed: boolean next_executable_action: string required_verifier: string @@ -778,7 +780,7 @@ export interface IwoooSSecurityToolClosureQueueItem { state: string next_action: string ready_signal_count?: number - runtime_gate_open: boolean + controlled_apply_policy_allowed: boolean } export interface IwoooSSecurityToolClosureReadbackResponse { @@ -809,9 +811,10 @@ export interface IwoooSSecurityToolClosureReadbackResponse { runtime_detection_closed_count: number web_api_dast_closed_count: number normalized_detection_schema_closed_count: number - runtime_gate_count: number - host_write_authorized_count: number - active_response_authorized_count: number + controlled_apply_policy_active_count: number + controlled_apply_candidate_ready_count: number + owner_review_required_for_low_medium_high_count: number + runtime_execution_performed_count: number secret_value_collection_allowed_count: number } tool_tracks: IwoooSSecurityToolClosureTrack[] diff --git a/capabilities.json b/capabilities.json index 6f0baae17..1a3b02b47 100644 --- a/capabilities.json +++ b/capabilities.json @@ -1,9 +1,25 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "name": "OpenClaw Capabilities", - "version": "5.6.0", + "version": "6.0.0", "description": "OpenClaw AI Agent 允許調用的工具與操作權限定義", - "updated_at": "2026-04-10", + "updated_at": "2026-07-10", + + "governance_policy": { + "schema_version": "global_product_governance_v2", + "controlled_apply_default": true, + "controlled_risk_levels": ["low", "medium", "high"], + "critical_break_glass_required": true, + "required_receipts": [ + "target_selector", + "source_truth_diff", + "check_mode_or_dry_run", + "idempotent_execution", + "independent_post_verifier", + "rollback_or_no_write_terminal", + "durable_learning_writeback" + ] + }, "kubernetes": { "allowed_operations": [ @@ -11,21 +27,24 @@ "name": "RESTART_DEPLOYMENT", "command": "kubectl rollout restart deployment/{name} -n {namespace}", "risk_level": "medium", - "requires_approval": true, + "requires_approval": false, + "approval_mode": "controlled_automation", "description": "重啟 Deployment,觸發 Rolling Update" }, { "name": "DELETE_POD", "command": "kubectl delete pod {name} -n {namespace}", "risk_level": "medium", - "requires_approval": true, + "requires_approval": false, + "approval_mode": "controlled_automation", "description": "刪除 Pod,由 ReplicaSet 自動重建" }, { "name": "DELETE_PODS_BY_LABEL", "command": "kubectl delete pods -l {selector} -n {namespace}", "risk_level": "medium", - "requires_approval": true, + "requires_approval": false, + "approval_mode": "controlled_automation", "description": "依 Label 批量刪除 Pod" }, { diff --git a/docs/HARD_RULES.md b/docs/HARD_RULES.md index b3de5f344..5c9dcd33e 100644 --- a/docs/HARD_RULES.md +++ b/docs/HARD_RULES.md @@ -8,11 +8,11 @@ | 欄位 | 值 | |------|-----| -| **版本** | v2.7 | +| **版本** | v3.0 | | **建立日期** | 2026-03-20 (台北) | | **建立者** | Claude Code | -| **最後修改** | 2026-06-28 (台北) | -| **修改者** | Codex + ogt (統帥全面授權:非事故級 gate 全部轉受控自動執行) | +| **最後修改** | 2026-07-10 (台北) | +| **修改者** | Codex + ogt (全產品治理 v2:風險分級、runtime closure、舊 gate 退役) | ### 變更紀錄 @@ -37,6 +37,17 @@ | v2.6 | 2026-06-28 | Codex + ogt | 🔴🔴🔴 統帥全面授權:所有非事故級 hard gate / guard 預設打開為 AI controlled apply;`0 / false` 只能作 evidence,不得阻擋實作;真正硬阻擋只剩 secret、不可逆資料破壞、reboot / node drain / firewall cutover、攻擊型掃描、成本 / provider、force push / repo refs 破壞、raw secret volume 與未經 replay / shadow / canary 的核心替換 | | v2.7 | 2026-06-28 | Codex + ogt | 🔴🔴🔴 110 runner 壓力防再發:`.gitea/workflows` 不得把 push / pull_request 自動事件接到 `awoooi-ubuntu` / `awoooi-host` 或泛用 label;新增 source guard 驗證 | | v2.8 | 2026-06-28 | Codex + ogt | 🔴🔴🔴 non-110 runner readiness 加嚴:service 必須 target-match 已驗證 runner binary / config;不得用 Docker `latest`、錯目錄或 target mismatch 的 unit 冒充 ready | +| v3.0 | 2026-07-10 | Codex + ogt | 全產品治理 v2 生效:low/medium/high 預設 controlled apply、critical 才 break-glass;live runtime truth 優先;舊 owner/manual/read-only/Phase gate 必須有 expiry 與退出條件,且不得阻擋不相依工作 | + +--- + +## v3.0 規範優先序與舊 Gate 退役 + +本文件受 `~/.codex/AGENTS.md` 的 `global_product_governance_v2` 與 repo `GLOBAL_RULES.md` 的 `awoooi_product_engineering_rules_v3` 約束。後續章節若仍把 low/medium/high 預設停在 owner review、manual handoff、read-only、`runtime_write_gate=0`、`apply_allowed=false` 或未定義 Phase,該段只作歷史風險說明,不具阻擋效力。 + +所有有效 gate 必須提供 `policy_version`、`risk_class`、`expires_at`、`exit_condition`、`replacement_action` 與 `post_verifier`。缺上述欄位的 legacy gate 要轉成治理債工作項;production runtime receipt、Gitea deploy readback 與獨立 verifier 比舊 snapshot 優先。 + +真正完成是同一 run 的 Detect -> Normalize -> Correlate -> Decide -> Check -> Controlled Apply -> Verify -> Retry/Rollback -> Learn/Writeback;文件、UI、source、test、CD 或 metadata receipt 不得單獨關閉工作項。 --- diff --git a/product.awoooi.yaml b/product.awoooi.yaml index 3704b79e5..c581188ed 100644 --- a/product.awoooi.yaml +++ b/product.awoooi.yaml @@ -1,4 +1,4 @@ -schema_version: product_awoooi_manifest_v1 +schema_version: product_awoooi_manifest_v2 product: id: awoooi display_name: AWOOOI / AiOps @@ -15,15 +15,33 @@ runtime: production_url: https://awoooi.wooo.work delivery_workbench_api: /api/v1/agents/delivery-closure-workbench contracts: + governance_policy_ref: GLOBAL_RULES.md + governance_policy_version: awoooi_product_engineering_rules_v3 data_contract_ref: docs/evaluations/backup_dr_readiness_matrix_2026-06-04.json security_contract_ref: docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md runtime_inventory_ref: docs/evaluations/runtime_surface_inventory_2026-06-05.json operation_boundaries: read_only_manifest_allowed: true + controlled_apply_default: true + low_risk_controlled_apply_allowed: true + medium_risk_controlled_apply_allowed: true + high_risk_controlled_apply_allowed: true + critical_break_glass_required: true + target_selector_required: true + source_truth_diff_required: true + check_mode_or_dry_run_required: true + idempotency_key_required: true + rollback_or_no_write_terminal_required: true + independent_post_verifier_required: true + durable_learning_writeback_required: true github_api_allowed: false + github_cli_allowed: false repo_creation_allowed: false - refs_sync_allowed: false - workflow_trigger_allowed: false - host_or_k8s_write_allowed: false + gitea_branch_write_allowed: true + gitea_refs_sync_allowed: true + gitea_workflow_modification_allowed: true + gitea_workflow_trigger_allowed: true + host_or_k8s_controlled_write_allowed: true + critical_host_or_k8s_write_allowed: false secret_read_allowed: false raw_session_or_sqlite_read_allowed: false