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 36d089ac..07d5a6bd 100644 --- a/apps/api/src/services/awoooi_priority_work_order_readback.py +++ b/apps/api/src/services/awoooi_priority_work_order_readback.py @@ -44,6 +44,570 @@ _METRIC_LINE_RE = re.compile( r"^(?P[a-zA-Z_:][a-zA-Z0-9_:]*)(?:\{[^}]*\})?\s+" r"(?P[-+]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][-+]?[0-9]+)?)$" ) +_COMMANDER_INSERTED_REQUIREMENT_SOURCE = ( + "docs/workplans/2026-07-02-commander-inserted-requirements-priority-ledger.md" +) +_COMMANDER_INSERTED_REQUIREMENT_WORK_ITEMS: list[dict[str, Any]] = [ + { + "id": "CIR-P0-001", + "priority": "P0", + "order": 1, + "status": "in_progress", + "lane": "mainline_order", + "request": "不要偏離 / 只推進目前唯一目標 / 照順序推進", + "normalized_work_item": "所有插入需求先歸入正式工作項,再依 P0/P1/P2/P3 排序。", + "current_state": "台帳已建立;本 readback 將其產品化到 API 與前台。", + "acceptance": "priority work-order API 與 work-items 前台可讀回完整排序。", + "next_action": "每次推進固定回報 current P0 / action / evidence / next action。", + "mapped_workplan_id": "P0-006", + }, + { + "id": "CIR-P0-002", + "priority": "P0", + "order": 2, + "status": "in_progress", + "lane": "production_visible_result", + "request": "我要看到實作結果,不能只有文件或長文字。", + "normalized_work_item": "每個主線工作必須產出 commit、Gitea CD、production readback 或 verifier output。", + "current_state": "Gitea/CD/readback 已有基準;本輪補前台可見的插入需求面板。", + "acceptance": "正式環境 /zh-TW/awooop/work-items 可看到排序後工作項與 readback 計數。", + "next_action": "正常 commit、push Gitea main,追 CD 與 production UI/API readback。", + "mapped_workplan_id": "P0-006", + }, + { + "id": "CIR-P0-003", + "priority": "P0", + "order": 3, + "status": "done", + "lane": "github_freeze", + "request": "GitHub 申訴期間先做其他工作。", + "normalized_work_item": "GitHub freeze 下,所有 GitHub 工作轉回 Gitea/local/production truth。", + "current_state": "GitHub 停用有效;本輪未使用 GitHub、gh、GitHub API。", + "acceptance": "GitHub 相關項目維持 stopped / do_not_use,且不阻擋 Gitea 主線。", + "next_action": "只以 Gitea / Gitea SSH / production readback 作為 source-control truth。", + "mapped_workplan_id": "P0-003A", + }, + { + "id": "CIR-P0-004", + "priority": "P0", + "order": 4, + "status": "in_progress", + "lane": "gitea_source_truth", + "request": "全部專案推版與 repo truth 不能靠 GitHub。", + "normalized_work_item": "全產品 source-control readiness 改以 Gitea repo identity、SSH heads、backup health 為準。", + "current_state": "9 個 expected Gitea repos 已讀回 OK;跨產品 dev/prod CI baseline 仍需逐步補。", + "acceptance": "product governance matrix 顯示 canonical repo、branch、backup evidence、private visibility。", + "next_action": "把全產品 source truth 接到 product governance matrix。", + "mapped_workplan_id": "P0-003", + }, + { + "id": "CIR-P0-005", + "priority": "P0", + "order": 5, + "status": "done", + "lane": "account_safety", + "request": "說清楚 GitHub 帳號風險邊界。", + "normalized_work_item": "建立 account safety 邊界:不碰 GitHub,不要求 token,不讀 suspension 細節。", + "current_state": "freeze 已在 hard gates 與工作台帳中固定。", + "acceptance": "沒有 GitHub read/write/API/gh 操作,且未要求使用者提供 GitHub token。", + "next_action": "未來若要恢復 GitHub,先做風險評估與人工確認。", + "mapped_workplan_id": "P0-003A", + }, + { + "id": "CIR-P0-006", + "priority": "P0", + "order": 6, + "status": "blocked", + "lane": "secret_safety", + "request": "使用者貼入 runner token / 密碼類敏感值並授權直接操作。", + "normalized_work_item": "敏感值不入檔、不重印、不保存;runner registration 改 hidden prompt 或 metadata verifier。", + "current_state": "已遵守不保存、不讀 secret;secret exposure follow-up 不含明文。", + "acceptance": "secret_value_read=false,並只輸出 rotate / revoke / hidden-prompt checklist。", + "next_action": "需要 runner registration 時,只走 token-safe hidden prompt 或 non-secret verifier。", + "mapped_workplan_id": "P0-010", + }, + { + "id": "CIR-P0-007", + "priority": "P0", + "order": 7, + "status": "in_progress", + "lane": "runner_registration", + "request": "我沒辦法複製貼上 token。", + "normalized_work_item": "Runner 註冊需支援遠端 TTY hidden prompt,不要求把 token 貼給 Codex。", + "current_state": "token-safe registration script 已有 source;live 110 lane metadata 仍需 verifier。", + "acceptance": "不讀 `.runner` 內容,registration metadata 以 non-secret verifier 證明。", + "next_action": "補 110 drain staging artifacts 後只跑 readiness verifier。", + "mapped_workplan_id": "P0-010", + }, + { + "id": "CIR-P0-008", + "priority": "P0", + "order": 8, + "status": "done", + "lane": "repo_identity", + "request": "Gitea 恢復正常、所有專案儲存庫恢復、原本儲存庫不是新增。", + "normalized_work_item": "repo recovery 以原 Gitea repo identity、SSH heads、backup health 為準,不建立替代 repo。", + "current_state": "handoff truth 顯示 9 expected repos SSH heads OK、backup missing=0。", + "acceptance": "private repo public 404 只標 visibility/auth,不宣稱 history 消失。", + "next_action": "若再出現 404,先判斷 visibility/auth,再看 SSH heads 與 backup health。", + "mapped_workplan_id": "P0-003", + }, + { + "id": "CIR-P0-009", + "priority": "P0", + "order": 9, + "status": "in_progress", + "lane": "gitea_cd", + "request": "所有專案都沒辦法推版。", + "normalized_work_item": "先保 AWOOOI Gitea CD mainline green,再逐產品恢復 dev/prod CI baseline。", + "current_state": "AWOOOI Gitea CD 已恢復;跨產品 baseline 仍按 governance matrix 推進。", + "acceptance": "Gitea queue / deploy marker / production readback 必須跟最新 main 對齊。", + "next_action": "每個 production-visible 變更正常 push Gitea main 並追 CD。", + "mapped_workplan_id": "P0-004", + }, + { + "id": "CIR-P0-010", + "priority": "P0", + "order": 10, + "status": "in_progress", + "lane": "runner_pressure", + "request": "修復 Gitea、但不要開 legacy runner。", + "normalized_work_item": "188 non-110 runner 為主 CD lane;110 legacy/generic runner 維持 fail-closed。", + "current_state": "188 ready;110 live verifier 仍顯示 config / registration / staging blockers。", + "acceptance": "不恢復 generic runner,不解除 pressure guard,不改成 warn-only。", + "next_action": "補 110 drain staging artifacts 後只跑 readiness verifier。", + "mapped_workplan_id": "P0-010", + }, + { + "id": "CIR-P0-011", + "priority": "P0", + "order": 11, + "status": "in_progress", + "lane": "work_state_visibility", + "request": "所有已開始、進行中、已完成工作全部看清楚。", + "normalized_work_item": "工作狀態拆成 Done / In Progress / Blocked / Deferred,不再用單句完成度帶過。", + "current_state": "本 readback 補完整 status 與 priority counts。", + "acceptance": "API/UI 同時顯示總數、P0/P1/P2/P3、各工作狀態與 next action。", + "next_action": "後續 LOGBOOK 與 UI 都沿用 status/evidence/next-action 結構。", + "mapped_workplan_id": "P0-010", + }, + { + "id": "CIR-P0-012", + "priority": "P0", + "order": 12, + "status": "in_progress", + "lane": "mainline_sequence", + "request": "所有主線工作持續推進。", + "normalized_work_item": "主線順序固定:Gitea source truth -> CD/runner -> production readback -> product governance -> KM/PlayBook/RAG。", + "current_state": "目前仍在 Gitea/CD/production-visible readback 主線,不切 GitHub 或未授權 runtime 支線。", + "acceptance": "每次下一步都能映射到此 sequence 的一個節點。", + "next_action": "完成本面板 production readback 後回到下一個 P0 runtime closure item。", + "mapped_workplan_id": "P0-006", + }, + { + "id": "CIR-P0-013", + "priority": "P0", + "order": 13, + "status": "in_progress", + "lane": "gate_taxonomy", + "request": "所有硬閘和 guard 全部打開。", + "normalized_work_item": "非事故 gate 轉 evidence / ledger;事故級 secret、destructive、reboot、force-push 等仍 break-glass。", + "current_state": "gate taxonomy 已整理;UI/API 需顯示可自動處理方式與不可越線項。", + "acceptance": "readback 顯示 controlled apply 邊界,不把 0/false 當終局阻擋。", + "next_action": "後續 P0 修復補 target selector、diff、dry-run、rollback、verifier、writeback。", + "mapped_workplan_id": "P0-006", + }, + { + "id": "CIR-P0-014", + "priority": "P0", + "order": 14, + "status": "done", + "lane": "approval_semantics", + "request": "批准/繼續不能被誤解。", + "normalized_work_item": "批准等於沿目前排序繼續,不等於開新支線或解除事故級 hard gate。", + "current_state": "判讀規則已寫入台帳並由本 readback 對外顯示。", + "acceptance": "簡短批准不會觸發 GitHub、secret、force push、reboot 或 destructive operation。", + "next_action": "後續收到批准時,延續最新 P0 work item。", + "mapped_workplan_id": "P0-006", + }, + { + "id": "CIR-P0-015", + "priority": "P0", + "order": 15, + "status": "in_progress", + "lane": "context_convergence", + "request": "為什麼搞成這麼亂。", + "normalized_work_item": "多視窗、長上下文、重複 gate、GitHub freeze、Gitea recovery、runner pressure 收斂到同一台帳。", + "current_state": "本 API/UI 成為收斂入口;不靠散落聊天句子。", + "acceptance": "後續工作先看 work-order readback 與 LOGBOOK,不重讀 raw conversations。", + "next_action": "把此 readback 作為 work-items 前台的主線優先序來源。", + "mapped_workplan_id": "P0-010", + }, + { + "id": "CIR-P1-001", + "priority": "P1", + "order": 16, + "status": "pending", + "lane": "km_playbook_rag_mcp", + "request": "整合到 MCP、RAG、KM、PLAYBOOK、LOG。", + "normalized_work_item": "治理資料要沉澱到 KM / PlayBook / RAG / MCP / log,不只存在對話。", + "current_state": "已列入 P1;本輪先完成 API/UI readback。", + "acceptance": "每個工作項帶 work_item_id、evidence source、verifier、rollback、trust writeback。", + "next_action": "定義 work item schema 與 trust writeback 欄位。", + "mapped_workplan_id": "P1-006", + }, + { + "id": "CIR-P1-002", + "priority": "P1", + "order": 17, + "status": "pending", + "lane": "priority_matrix", + "request": "所有工作的優先順序要清楚。", + "normalized_work_item": "建立跨產品 priority matrix,區分 AWOOOI P0、Gitea/source-control P0、product governance P1、UI/UX P2。", + "current_state": "scorecard 部分存在;本台帳補 current thread priority order。", + "acceptance": "product runtime governance dashboard 能讀回 priority matrix。", + "next_action": "把本台帳映射到 existing product runtime governance dashboard。", + "mapped_workplan_id": "P0-010", + }, + { + "id": "CIR-P1-003", + "priority": "P1", + "order": 18, + "status": "in_progress", + "lane": "repo_governance", + "request": "所有儲存庫都不能被忽略。", + "normalized_work_item": "repo identity / backup health / private visibility 成為 product governance 第一層。", + "current_state": "Gitea P0 恢復 truth 已有 9 repos OK。", + "acceptance": "全產品 source-control readback rows 顯示 canonical repo / branch / backup evidence。", + "next_action": "補全產品 source-control readback rows。", + "mapped_workplan_id": "P0-003", + }, + { + "id": "CIR-P1-004", + "priority": "P1", + "order": 19, + "status": "pending", + "lane": "gitea_auth_paths", + "request": "Gitea 瀏覽器、API、SSH、runner token 的差別要清楚。", + "normalized_work_item": "Gitea 操作區分 public HTML readback、API token readback、SSH git readback、runner token registration。", + "current_state": "已區分 public HTML、API 401、SSH push 可用。", + "acceptance": "runbook 說明何時需要 token、何時不需要、且不得要求貼 token。", + "next_action": "建立 non-secret Gitea auth path runbook/readback。", + "mapped_workplan_id": "P0-003", + }, + { + "id": "CIR-P1-005", + "priority": "P1", + "order": 20, + "status": "deferred", + "lane": "local_console", + "request": "用 VMware Console 去 192.168.0.99 操作 110。", + "normalized_work_item": "VMware console 屬 break-glass / local console lane,只在 control path 無法修復且目標明確時使用。", + "current_state": "未作為本輪主線執行。", + "acceptance": "若需要,只列 non-secret console checklist,不輸入或保存密碼。", + "next_action": "保持 deferred,除非 SSH/Gitea control path 無法修復。", + "mapped_workplan_id": "P0-010", + }, + { + "id": "CIR-P1-006", + "priority": "P1", + "order": 21, + "status": "pending", + "lane": "sudo_auth_metadata", + "request": "sudo 為什麼不能用密碼。", + "normalized_work_item": "sudo/account/SSH auth 只做 metadata audit,不要求或記錄密碼。", + "current_state": "未在本輪 runtime 修改。", + "acceptance": "playbook 禁止收密碼,僅輸出 metadata check 與 authorized console SOP。", + "next_action": "補 sudo/auth metadata check playbook。", + "mapped_workplan_id": "P0-010", + }, + { + "id": "CIR-P1-007", + "priority": "P1", + "order": 22, + "status": "done", + "lane": "blocked_lane_fallback", + "request": "GitHub blocked 時先做其他工作。", + "normalized_work_item": "GitHub blocked 不等待,改推 Gitea / CD / product governance / backup / KM mainline。", + "current_state": "已照做:Gitea CD 已 success,且本輪續推 production-visible work item。", + "acceptance": "不等待 GitHub appeal,不把 GitHub 當下一步。", + "next_action": "下個 P0 仍照排序推進。", + "mapped_workplan_id": "P0-004", + }, + { + "id": "CIR-P2-001", + "priority": "P2", + "order": 23, + "status": "in_progress", + "lane": "reporting_cadence", + "request": "為什麼會偏離工作目標。", + "normalized_work_item": "每輪回報固定成 current P0 / action taken / evidence / next action。", + "current_state": "本 readback 提供產品化依據;仍需持續執行。", + "acceptance": "final / handoff 先列結果,再列下一步,不開旁支。", + "next_action": "每次 production readback 後更新同一格式。", + "mapped_workplan_id": "P0-006", + }, + { + "id": "CIR-P2-002", + "priority": "P2", + "order": 24, + "status": "deferred", + "lane": "observability", + "request": "production health readback degraded 要處理但不能亂重啟。", + "normalized_work_item": "SignOz component down 分流到 Observability P2,不阻擋本次 CD success。", + "current_state": "已觀察到 SignOz degraded;本輪不重啟服務。", + "acceptance": "observability lane 有獨立 readback,不蓋過 Gitea/CD 主線。", + "next_action": "排入 Observability P2,先補 readback 再決定 controlled action。", + "mapped_workplan_id": "P2-OBSERVABILITY", + }, + { + "id": "CIR-P0-RBT-001", + "priority": "P0", + "order": 26, + "status": "in_progress", + "lane": "reboot_slo", + "request": "主機重啟後 10 分鐘內全部恢復,且要自動判斷所有主機被重啟。", + "normalized_work_item": "建立 99/110/111/112/120/121/188 reboot detector、10 分鐘 SLO scorecard 與固定 triage order。", + "current_state": "部分已有 reboot recovery SLO alerts、scorecard、textfile;仍缺 fresh all-host reboot/drill proof。", + "acceptance": "最新 reboot SLO scorecard 可讀回;缺 fresh event 時標 awaiting_next_reboot_or_approved_drill。", + "next_action": "產生最新 reboot SLO scorecard readback,不宣稱未證明的 10 分鐘 SLA。", + "mapped_workplan_id": "P0-006-REBOOT-AUTO-RECOVERY-SLO-SCORECARD", + }, + { + "id": "CIR-P0-RBT-002", + "priority": "P0", + "order": 27, + "status": "pending", + "lane": "reboot_detection", + "request": "沒有偵測到主機重啟。", + "normalized_work_item": "boot_id、uptime、node exporter、Windows exporter、VMware VM power state 必須合併成 reboot event。", + "current_state": "Prometheus rule 有缺口告警;99/VMware/Windows 來源仍未完整閉環。", + "acceptance": "99/VMware/Windows probe source 與 textfile readback 存在,缺 99 時不得宣稱全主機 green。", + "next_action": "補 99/VMware/Windows probe source 與 textfile readback。", + "mapped_workplan_id": "P0-006-REBOOT-AUTO-RECOVERY-SLO-SCORECARD", + }, + { + "id": "CIR-P0-RBT-003", + "priority": "P0", + "order": 28, + "status": "pending", + "lane": "vmware_autostart", + "request": "192.168.0.99 VMware 要自動啟動,裡面 111/188/120/121/112 也自動啟動。", + "normalized_work_item": "Windows 99 VMware host autostart、guest VM autostart contract、VM 開機順序與 readback。", + "current_state": "尚未完成;不能用 Linux host green 代替。", + "acceptance": "non-secret verifier 只讀 `.vmx`、autostart policy、VMware service 狀態,不讀 Windows 密碼。", + "next_action": "建立 non-secret Windows/VMware autostart checklist 與 verifier。", + "mapped_workplan_id": "P0-006-REBOOT-AUTO-RECOVERY-SLO-SCORECARD", + }, + { + "id": "CIR-P0-RBT-004", + "priority": "P0", + "order": 29, + "status": "pending", + "lane": "windows_update_policy", + "request": "192.168.0.99 不可因 Windows Update 無預警重開。", + "normalized_work_item": "Windows Update reboot policy:active hours、no auto-restart、maintenance window、update notification audit。", + "current_state": "尚未完成;屬 99 Windows policy lane。", + "acceptance": "Windows Update policy verifier 可讀回;不得要求使用者貼密碼。", + "next_action": "建立 Windows Update policy verifier,等待 99 console 或授權遠端管理通道。", + "mapped_workplan_id": "P0-006-REBOOT-AUTO-RECOVERY-SLO-SCORECARD", + }, + { + "id": "CIR-P0-RBT-005", + "priority": "P0", + "order": 30, + "status": "pending", + "lane": "maintenance_fallback", + "request": "網站重啟後 502 嚴重影響體驗,要維護頁,外部雲端或專業做法。", + "normalized_work_item": "Public maintenance fallback:Nginx / edge / external static maintenance page / status page / fail-open UX。", + "current_state": "尚未完整落地;目前是需求缺口。", + "acceptance": "先有 no-write decision record、rollback、smoke verifier;不直接切 DNS 或 edge route。", + "next_action": "產生 public_maintenance_fallback decision record 與 no-write check-mode。", + "mapped_workplan_id": "P0-006", + }, + { + "id": "CIR-P0-RBT-006", + "priority": "P0", + "order": 31, + "status": "in_progress", + "lane": "telegram_reboot_alerts", + "request": "所有主機關機立刻 Telegram 告警,重啟後也要告警,其他告警一併完整思考。", + "normalized_work_item": "Down、shutdown suspected、reboot detected、reboot recovered、SLO missed、backup failed、CPU pressure 等告警矩陣。", + "current_state": "部分已有 Alertmanager rule 與 Telegram receipt 補強;仍缺完整 shutdown/up E2E receipt。", + "acceptance": "Alertmanager active/resolved 與 outbound receipt 能逐項讀回,不送測試 secret。", + "next_action": "建立 Telegram alert matrix 與 receipt verifier。", + "mapped_workplan_id": "P0-006", + }, + { + "id": "CIR-P0-RBT-007", + "priority": "P0", + "order": 32, + "status": "in_progress", + "lane": "backup_observability", + "request": "所有備份包含主機、DB、網站、服務、套件、工具、日誌都沒有監控告警。", + "normalized_work_item": "Backup observability coverage:job inventory、last success、freshness、offsite、restore drill、Telegram receipt。", + "current_state": "部分已有 backup health exporter / alert rules;全域 coverage 與 restore drill 未全綠。", + "acceptance": "host/DB/site/service config/package/tool/log 每列都有 metric、alert、last_success、restore_verifier。", + "next_action": "建立 backup coverage matrix 與 restore drill verifier。", + "mapped_workplan_id": "P0-005", + }, + { + "id": "CIR-P0-RBT-008", + "priority": "P0", + "order": 33, + "status": "in_progress", + "lane": "reboot_runbook_fixed_order", + "request": "每次重啟排查都不一樣,也不知道多久恢復,不符合 SLA。", + "normalized_work_item": "固定化 reboot runbook:fixed triage order、ETA、active blocker、remaining seconds、owner lane、next command。", + "current_state": "部分已有 scorecard / SOP;仍需所有回報統一格式。", + "acceptance": "SLO scorecard 強制輸出 current_phase、eta_or_wait_reason、active_blockers、next_safe_action。", + "next_action": "將 reboot SLO scorecard 補齊固定輸出欄位。", + "mapped_workplan_id": "P0-006-REBOOT-AUTO-RECOVERY-SLO-SCORECARD", + }, + { + "id": "CIR-P0-RBT-009", + "priority": "P0", + "order": 34, + "status": "pending", + "lane": "product_freshness_version", + "request": "所有產品、網站都要是最新版本;版本和數據是否最新要驗證。", + "normalized_work_item": "Product freshness/version matrix:source commit、deploy marker、runtime image、public health、data freshness、latest source availability。", + "current_state": "AWOOOI / StockPlatform 部分已在做;全產品未統一。", + "acceptance": "全產品表含 product、canonical repo、main SHA、deploy marker、public URL、data freshness、blocked reason。", + "next_action": "建立全產品 freshness/version readback 表。", + "mapped_workplan_id": "P0-006", + }, + { + "id": "CIR-P0-GIT-001", + "priority": "P0", + "order": 35, + "status": "in_progress", + "lane": "gitea_backup_restore", + "request": "Gitea 儲存庫都不見了?Gitea 沒完整備份嗎?", + "normalized_work_item": "Gitea repository identity、backup proof、restore drill 需比對 SSH heads、repo path、bundle backup、restore sample。", + "current_state": "已有 9 expected repos OK / backup health missing=0;仍需 restore drill 證明。", + "acceptance": "Gitea repo bundle backup readback 與 sample restore dry-run verifier 可讀回;禁止刪 repo / 改 visibility。", + "next_action": "補 Gitea repo bundle backup readback 與 sample restore dry-run verifier。", + "mapped_workplan_id": "P0-003", + }, + { + "id": "CIR-P0-CPU-001", + "priority": "P0", + "order": 36, + "status": "in_progress", + "lane": "cpu_pressure_automation", + "request": "110 / 188 CPU 負載持續過高,為什麼沒監控告警、沒主動修復。", + "normalized_work_item": "Sustained CPU pressure automation:Alertmanager -> controller -> evidence -> service playbook -> verifier -> KM writeback。", + "current_state": "110 已有 Host110SustainedModeratePressure、Gitea playbook、Stock/Postgres evidence;188 仍需同級 readback。", + "acceptance": "Stock/Postgres playbook 與 188 equivalent readback 可讀回,不以單次下降結案。", + "next_action": "接 postgres_hot_query_or_backup_export_playbook 並補 188 equivalent readback。", + "mapped_workplan_id": "P0-006", + }, + { + "id": "CIR-P0-CPU-002", + "priority": "P0", + "order": 37, + "status": "in_progress", + "lane": "alert_correlation", + "request": "噪音會影響真問題,要整合一起做。", + "normalized_work_item": "Alert noise / real issue correlation:backup aggregate noise、CPU pressure、Gitea queue、Stock freshness 分清主因與次因。", + "current_state": "部分已在 SOP 註記;仍需統一 correlation scorecard。", + "acceptance": "incident correlation readback 顯示 primary_blocker、secondary_noise、ignored_noise_reason、evidence_ref。", + "next_action": "建立 incident correlation readback。", + "mapped_workplan_id": "P0-006", + }, + { + "id": "CIR-P0-CD-001", + "priority": "P0", + "order": 38, + "status": "in_progress", + "lane": "gitea_cd_baseline", + "request": "所有專案都不能推版 / 要看到實作結果。", + "normalized_work_item": "Gitea-only CD baseline:每次 main push 要有 visible run、deploy marker、production readback;GitHub 不作解法。", + "current_state": "AWOOOI 最新 main 可推;全產品未全綠。", + "acceptance": "product governance matrix 接入各產品 Gitea CD readiness,不只報 AWOOOI。", + "next_action": "將 product governance matrix 接入各產品 Gitea CD readiness。", + "mapped_workplan_id": "P0-004", + }, + { + "id": "CIR-P1-AI-001", + "priority": "P1", + "order": 39, + "status": "in_progress", + "lane": "ai_controlled_repair_loop", + "request": "AI 專業在哪?要能主動發現、主動修復。", + "normalized_work_item": "AI controlled repair loop:detect -> classify -> candidate -> check-mode -> controlled apply -> post verifier -> KM / PlayBook trust。", + "current_state": "CPU / Gitea / Telegram receipt 已部分落地;全域 AI loop 未全部接上。", + "acceptance": "每個 P0 runbook 都有 candidate_action、controlled_apply_allowed、post_verifier、trust_writeback。", + "next_action": "將每個 P0 runbook 補齊 AI controlled repair loop 欄位。", + "mapped_workplan_id": "P1-006", + }, + { + "id": "CIR-P1-KM-001", + "priority": "P1", + "order": 40, + "status": "in_progress", + "lane": "sop_playbook_writeback", + "request": "修復過程、經驗完整沉澱進 SOP,整合到目前版本。", + "normalized_work_item": "所有 P0 修復必須同步 LOGBOOK、SOP、PlayBook、workplan ledger,不只留在對話。", + "current_state": "本台帳、LOGBOOK、SOP 已開始補;仍需 API/UI read model。", + "acceptance": "read-only API / governance UI row 顯示 last_updated / evidence_count。", + "next_action": "把本台帳轉成 read-only API / governance UI row。", + "mapped_workplan_id": "P1-006", + }, + { + "id": "CIR-P1-WORK-001", + "priority": "P1", + "order": 41, + "status": "in_progress", + "lane": "work_state_inventory", + "request": "所有已開始、進行中、已完成工作全部看清楚。", + "normalized_work_item": "工作狀態盤點:Done / In Progress / Blocked / Deferred / Next Action + evidence。", + "current_state": "本台帳已有初版;需納入 reboot/backup/VMware/maintenance/CPU。", + "acceptance": "Done/In Progress/Blocked/Deferred 包含 reboot、backup、VMware、maintenance、CPU 全列。", + "next_action": "更新工作狀態盤點並納入本節新 P0。", + "mapped_workplan_id": "P0-010", + }, + { + "id": "CIR-P2-OBS-001", + "priority": "P2", + "order": 42, + "status": "pending", + "lane": "observability_coverage", + "request": "其他還有哪些告警也要完整思考。", + "normalized_work_item": "Observability coverage expansion:SignOz/Sentry/Langfuse/Harbor/Registry/K3s/DB/backup/freshness/route/TLS 告警。", + "current_state": "多數 rule 分散存在;coverage matrix 不完整。", + "acceptance": "alert coverage matrix 區分 P0 actionable 與 P2 observability debt。", + "next_action": "建立 alert coverage matrix。", + "mapped_workplan_id": "P2-OBSERVABILITY", + }, + { + "id": "CIR-P2-UX-001", + "priority": "P2", + "order": 43, + "status": "pending", + "lane": "maintenance_ux", + "request": "維護頁外部雲端或其他專業做法評估。", + "normalized_work_item": "Maintenance UX 可先做 design / decision record;實際 DNS/edge cutover 需 controlled apply。", + "current_state": "尚未開始。", + "acceptance": "no-write design / rollback / smoke verifier 完成;不直接切 DNS。", + "next_action": "先出 no-write design / rollback / smoke verifier。", + "mapped_workplan_id": "P0-006", + }, + { + "id": "CIR-P3-001", + "priority": "P3", + "order": 44, + "status": "deferred", + "lane": "github_future_recovery", + "request": "GitHub appeal 後的未來恢復。", + "normalized_work_item": "只有未來明確要求恢復 GitHub,才先做風險評估與人工確認。", + "current_state": "等待外部狀態;freeze 仍有效。", + "acceptance": "未確認前不做任何 GitHub 操作。", + "next_action": "維持 stopped / do_not_use。", + "mapped_workplan_id": "P0-003A", + }, +] def load_latest_awoooi_priority_work_order_readback( @@ -62,6 +626,7 @@ def load_latest_awoooi_priority_work_order_readback( _require_mainline_consistency(payload, str(path)) _enrich_from_current_readbacks(payload) _require_mainline_consistency(payload, str(path)) + _apply_commander_inserted_requirement_work_items(payload) return payload @@ -2250,6 +2815,97 @@ def _set_rollups_and_summary( } +def _apply_commander_inserted_requirement_work_items( + payload: dict[str, Any], +) -> None: + priority_rank = {"P0": 0, "P1": 1, "P2": 2, "P3": 3} + ordered_source_items = sorted( + _COMMANDER_INSERTED_REQUIREMENT_WORK_ITEMS, + key=lambda item: ( + priority_rank.get(str(item["priority"]), 99), + _int(item.get("order")), + str(item["id"]), + ), + ) + items = [ + { + **item, + "order": index, + "source": _COMMANDER_INSERTED_REQUIREMENT_SOURCE, + "visible_surface": "/zh-TW/awooop/work-items", + } + for index, item in enumerate(ordered_source_items, start=1) + ] + priority_counts = { + priority: sum(1 for item in items if item["priority"] == priority) + for priority in ("P0", "P1", "P2", "P3") + } + status_counts = { + status: sum(1 for item in items if item["status"] == status) + for status in ("done", "in_progress", "pending", "blocked", "deferred") + } + next_item = next( + ( + item + for item in items + if item["status"] in {"in_progress", "pending", "blocked"} + ), + items[0], + ) + + payload["commander_inserted_requirement_work_items"] = items + + rollups = _dict(payload.setdefault("rollups", {})) + rollups["commander_inserted_requirement_work_item_count"] = len(items) + rollups["commander_inserted_requirement_p0_count"] = priority_counts["P0"] + rollups["commander_inserted_requirement_p1_count"] = priority_counts["P1"] + rollups["commander_inserted_requirement_p2_count"] = priority_counts["P2"] + rollups["commander_inserted_requirement_p3_count"] = priority_counts["P3"] + rollups["commander_inserted_requirement_done_count"] = status_counts["done"] + rollups["commander_inserted_requirement_in_progress_count"] = status_counts[ + "in_progress" + ] + rollups["commander_inserted_requirement_pending_count"] = status_counts[ + "pending" + ] + rollups["commander_inserted_requirement_blocked_count"] = status_counts[ + "blocked" + ] + rollups["commander_inserted_requirement_deferred_count"] = status_counts[ + "deferred" + ] + + summary = _dict(payload.setdefault("summary", {})) + summary["commander_inserted_requirement_work_item_count"] = len(items) + summary["commander_inserted_requirement_p0_count"] = priority_counts["P0"] + summary["commander_inserted_requirement_p1_count"] = priority_counts["P1"] + summary["commander_inserted_requirement_p2_count"] = priority_counts["P2"] + summary["commander_inserted_requirement_p3_count"] = priority_counts["P3"] + summary["commander_inserted_requirement_done_count"] = status_counts["done"] + summary["commander_inserted_requirement_in_progress_count"] = status_counts[ + "in_progress" + ] + summary["commander_inserted_requirement_pending_count"] = status_counts[ + "pending" + ] + summary["commander_inserted_requirement_blocked_count"] = status_counts[ + "blocked" + ] + summary["commander_inserted_requirement_deferred_count"] = status_counts[ + "deferred" + ] + summary["commander_inserted_requirement_next_id"] = str(next_item["id"]) + summary["commander_inserted_requirement_next_priority"] = str( + next_item["priority"] + ) + summary["commander_inserted_requirement_next_action"] = str( + next_item["next_action"] + ) + summary["commander_inserted_requirement_source"] = ( + _COMMANDER_INSERTED_REQUIREMENT_SOURCE + ) + + def _require_schema(payload: dict[str, Any], label: str) -> None: actual = payload.get("schema_version") if actual != _SCHEMA_VERSION: 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 235bbf22..a8db1f17 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 @@ -93,6 +93,19 @@ def test_awoooi_priority_work_order_readback_loader_returns_mainline_order(): assert payload["operation_boundaries"]["secret_or_runner_token_read"] is False assert payload["operation_boundaries"]["workflow_dispatch_performed"] is False assert payload["operation_boundaries"]["host_write_performed"] is False + inserted_items = payload["commander_inserted_requirement_work_items"] + assert len(inserted_items) == 43 + assert inserted_items[0]["id"] == "CIR-P0-001" + assert inserted_items[-1]["id"] == "CIR-P3-001" + assert [item["order"] for item in inserted_items] == list(range(1, 44)) + assert all(item["acceptance"] for item in inserted_items) + assert payload["summary"]["commander_inserted_requirement_work_item_count"] == 43 + assert payload["summary"]["commander_inserted_requirement_p0_count"] == 28 + assert payload["summary"]["commander_inserted_requirement_p1_count"] == 10 + assert payload["summary"]["commander_inserted_requirement_p2_count"] == 4 + assert payload["summary"]["commander_inserted_requirement_p3_count"] == 1 + assert payload["summary"]["commander_inserted_requirement_next_id"] == "CIR-P0-001" + assert payload["rollups"]["commander_inserted_requirement_blocked_count"] == 1 def test_awoooi_priority_work_order_readback_endpoint_returns_snapshot( @@ -165,6 +178,15 @@ def test_awoooi_priority_work_order_readback_endpoint_returns_snapshot( "P0-006-AI-LOOP-CURRENT-BLOCKER-EXECUTION-QUEUE" ) assert "do not reopen Harbor 502" in data["next_execution_order"][0] + assert data["summary"]["commander_inserted_requirement_work_item_count"] == 43 + assert data["summary"]["commander_inserted_requirement_next_id"] == "CIR-P0-001" + assert data["commander_inserted_requirement_work_items"][1]["id"] == ( + "CIR-P0-002" + ) + assert data["commander_inserted_requirement_work_items"][1]["priority"] == "P0" + assert data["commander_inserted_requirement_work_items"][1]["visible_surface"] == ( + "/zh-TW/awooop/work-items" + ) def test_controlled_cd_lane_live_metric_readback_falls_back_to_prometheus( diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index ab6a0861..37d339ba 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -9157,6 +9157,30 @@ "evidenceBoundary": "Evidence boundary" } }, + "commanderInsertedRequirements": { + "eyebrow": "Mainline priority", + "title": "Commander inserted requirement work items", + "subtitle": "Turns the requirements inserted during this run into ordered work items with P0/P1/P2/P3 priority, status, acceptance criteria, and next action.", + "total": "Total work items", + "next": "Current priority", + "nextAction": "Next action", + "acceptance": "Acceptance", + "rowNextAction": "Next action", + "loading": "Loading inserted requirement work items", + "empty": "No inserted requirement work items read back yet.", + "order": "order={order}", + "source": "source={source}", + "metrics": { + "active": "active" + }, + "statuses": { + "done": "Done", + "inProgress": "In progress", + "pending": "Not started", + "blocked": "Blocked", + "deferred": "Deferred" + } + }, "operatorSop": { "eyebrow": "操作 SOP 判讀", "title": "AI 受控卡點與自動化缺口接手面板", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 039f722b..6ec57f92 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -9157,6 +9157,30 @@ "evidenceBoundary": "證據邊界" } }, + "commanderInsertedRequirements": { + "eyebrow": "主線優先序", + "title": "統帥插入需求工作項", + "subtitle": "把本輪中途插入的要求收成正式工作項,依 P0/P1/P2/P3 排序,並顯示狀態、驗收條件與下一步。", + "total": "總工作項", + "next": "目前優先項", + "nextAction": "下一步", + "acceptance": "驗收條件", + "rowNextAction": "下一步", + "loading": "讀取插入需求工作項", + "empty": "尚未讀回插入需求工作項。", + "order": "order={order}", + "source": "source={source}", + "metrics": { + "active": "active" + }, + "statuses": { + "done": "已完成", + "inProgress": "進行中", + "pending": "未開始", + "blocked": "阻塞", + "deferred": "延後" + } + }, "operatorSop": { "eyebrow": "操作 SOP 判讀", "title": "AI 受控卡點與自動化缺口接手面板", 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 60fcce9d..cc8681a7 100644 --- a/apps/web/src/app/[locale]/awooop/work-items/page.tsx +++ b/apps/web/src/app/[locale]/awooop/work-items/page.tsx @@ -1026,8 +1026,38 @@ type AiLoopLogSourceContract = { purpose?: string | null; }; +type CommanderInsertedRequirementWorkItem = { + id?: string | null; + priority?: "P0" | "P1" | "P2" | "P3" | string | null; + order?: number | null; + status?: "done" | "in_progress" | "pending" | "blocked" | "deferred" | string | null; + lane?: string | null; + request?: string | null; + normalized_work_item?: string | null; + current_state?: string | null; + acceptance?: string | null; + next_action?: string | null; + mapped_workplan_id?: string | null; + visible_surface?: string | null; + source?: string | null; +}; + type PriorityWorkOrderResponse = { summary?: { + commander_inserted_requirement_work_item_count?: number | null; + commander_inserted_requirement_p0_count?: number | null; + commander_inserted_requirement_p1_count?: number | null; + commander_inserted_requirement_p2_count?: number | null; + commander_inserted_requirement_p3_count?: number | null; + commander_inserted_requirement_done_count?: number | null; + commander_inserted_requirement_in_progress_count?: number | null; + commander_inserted_requirement_pending_count?: number | null; + commander_inserted_requirement_blocked_count?: number | null; + commander_inserted_requirement_deferred_count?: number | null; + commander_inserted_requirement_next_id?: string | null; + commander_inserted_requirement_next_priority?: string | null; + commander_inserted_requirement_next_action?: string | null; + commander_inserted_requirement_source?: string | null; ai_loop_current_blocker_id?: string | null; ai_loop_current_blocker_log_source_tag_count?: number | null; ai_loop_current_blocker_log_source_tag_keys?: string[] | null; @@ -1102,6 +1132,7 @@ type PriorityWorkOrderResponse = { controlled_cd_lane_live_metric_blockers?: string[] | null; } | null; }>; + commander_inserted_requirement_work_items?: CommanderInsertedRequirementWorkItem[]; }; type Telemetry = { @@ -8400,6 +8431,235 @@ function AiLoopLogSourceTagsPanel({ ); } +function commanderPriorityTone(priority: string) { + switch (priority) { + case "P0": + return "border-[#f0c6a8] bg-[#fff8f1] text-[#9a4d16]"; + case "P1": + return "border-[#c9d8ea] bg-[#eef5ff] text-[#1f5b9b]"; + case "P2": + return "border-[#cbd7bf] bg-[#f4faef] text-[#3d6b24]"; + default: + return "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]"; + } +} + +function commanderStatusTone(status: string) { + switch (status) { + case "done": + return "border-[#b9d9c2] bg-[#f2fbf3] text-[#236332]"; + case "in_progress": + return "border-[#c9d8ea] bg-[#eef5ff] text-[#1f5b9b]"; + case "blocked": + return "border-[#f0c6a8] bg-[#fff8f1] text-[#9a4d16]"; + case "deferred": + return "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]"; + default: + return "border-[#e0ddd4] bg-white text-[#5f5b52]"; + } +} + +function CommanderInsertedRequirementsPanel({ + priority, + loading, +}: { + priority: PriorityWorkOrderResponse | null; + loading: boolean; +}) { + const t = useTranslations("awooop.workItems.commanderInsertedRequirements"); + const summary = priority?.summary; + const items = (priority?.commander_inserted_requirement_work_items ?? []) + .slice() + .sort((left, right) => (left.order ?? 0) - (right.order ?? 0)); + const total = + summary?.commander_inserted_requirement_work_item_count ?? items.length; + const nextId = summary?.commander_inserted_requirement_next_id ?? items[0]?.id ?? "--"; + const nextPriority = + summary?.commander_inserted_requirement_next_priority ?? + items[0]?.priority ?? + "--"; + const nextAction = + summary?.commander_inserted_requirement_next_action ?? + items[0]?.next_action ?? + "--"; + const source = summary?.commander_inserted_requirement_source ?? items[0]?.source ?? "--"; + const metrics = [ + { + key: "p0", + label: "P0", + value: summary?.commander_inserted_requirement_p0_count ?? 0, + tone: commanderPriorityTone("P0"), + }, + { + key: "p1", + label: "P1", + value: summary?.commander_inserted_requirement_p1_count ?? 0, + tone: commanderPriorityTone("P1"), + }, + { + key: "p2", + label: "P2", + value: summary?.commander_inserted_requirement_p2_count ?? 0, + tone: commanderPriorityTone("P2"), + }, + { + key: "status", + label: t("metrics.active"), + value: + (summary?.commander_inserted_requirement_in_progress_count ?? 0) + + (summary?.commander_inserted_requirement_pending_count ?? 0) + + (summary?.commander_inserted_requirement_blocked_count ?? 0), + tone: "border-[#e0ddd4] bg-white text-[#141413]", + }, + ]; + const statusLabels: Record = { + done: t("statuses.done"), + in_progress: t("statuses.inProgress"), + pending: t("statuses.pending"), + blocked: t("statuses.blocked"), + deferred: t("statuses.deferred"), + }; + + return ( +
+
+
+
+
+
+
+ {t("total")} +
+
+ {loading ? "--" : total} +
+
+
+ +
+ {metrics.map((metric) => ( +
+
{metric.label}
+
+ {loading ? "--" : metric.value} +
+
+ ))} +
+ +
+
+
+ {t("next")} +
+
+ {loading ? "--" : `${nextPriority}:${nextId}`} +
+
+
+
+ {t("nextAction")} +
+
+ {loading ? "--" : nextAction} +
+
+
+ +
+ {items.length > 0 ? ( + items.map((item) => { + const itemPriority = String(item.priority ?? "--"); + const itemStatus = String(item.status ?? "pending"); + return ( +
+
+
+ + {itemPriority} + + + {statusLabels[itemStatus] ?? itemStatus} + +
+
+ {loading ? "--" : item.id} +
+
+ {t("order", { order: item.order ?? 0 })} +
+
+
+
+ {loading ? "--" : item.normalized_work_item} +
+
+ {loading ? "--" : item.request} +
+
+ {loading ? "--" : item.mapped_workplan_id} +
+
+
+
+ {t("acceptance")} +
+
+ {loading ? "--" : item.acceptance} +
+
+ {t("rowNextAction")} +
+
+ {loading ? "--" : item.next_action} +
+
+
+ ); + }) + ) : ( +
+ {loading ? t("loading") : t("empty")} +
+ )} +
+ +
+ {t("source", { source })} +
+
+
+ ); +} + export default function AwoooPWorkItemsPage() { const t = useTranslations("awooop.workItems"); const locale = useLocale(); @@ -8687,6 +8947,11 @@ export default function AwoooPWorkItemsPage() { loading={loading && !telemetry.priorityWorkOrder} /> + +