feat(awooop): surface commander inserted work items
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 53s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 53s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
This commit is contained in:
@@ -44,6 +44,570 @@ _METRIC_LINE_RE = re.compile(
|
||||
r"^(?P<name>[a-zA-Z_:][a-zA-Z0-9_:]*)(?:\{[^}]*\})?\s+"
|
||||
r"(?P<value>[-+]?(?:[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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 受控卡點與自動化缺口接手面板",
|
||||
|
||||
@@ -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 受控卡點與自動化缺口接手面板",
|
||||
|
||||
@@ -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<string, string> = {
|
||||
done: t("statuses.done"),
|
||||
in_progress: t("statuses.inProgress"),
|
||||
pending: t("statuses.pending"),
|
||||
blocked: t("statuses.blocked"),
|
||||
deferred: t("statuses.deferred"),
|
||||
};
|
||||
|
||||
return (
|
||||
<section
|
||||
className="border border-[#e0ddd4] bg-white"
|
||||
data-testid="commander-inserted-requirement-work-items"
|
||||
>
|
||||
<div className="p-4">
|
||||
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<ListChecks className="h-5 w-5 shrink-0 text-[#d97757]" aria-hidden="true" />
|
||||
<div className="min-w-0">
|
||||
<p className="text-[11px] font-semibold uppercase tracking-[0.08em] text-[#9a968c]">
|
||||
{t("eyebrow")}
|
||||
</p>
|
||||
<h3 className="mt-1 text-base font-semibold tracking-normal text-[#141413]">
|
||||
{t("title")}
|
||||
</h3>
|
||||
<p className="mt-2 max-w-3xl text-xs leading-5 text-[#77736a]">
|
||||
{t("subtitle")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border border-[#d8d3c7] bg-[#faf9f3] px-3 py-2 text-right">
|
||||
<div className="text-[11px] font-semibold text-[#77736a]">
|
||||
{t("total")}
|
||||
</div>
|
||||
<div className="mt-1 font-mono text-xl font-semibold text-[#141413]">
|
||||
{loading ? "--" : total}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 grid gap-2 sm:grid-cols-2 xl:grid-cols-4">
|
||||
{metrics.map((metric) => (
|
||||
<div key={metric.key} className={cn("border px-3 py-2", metric.tone)}>
|
||||
<div className="text-[11px] font-semibold">{metric.label}</div>
|
||||
<div className="mt-1 font-mono text-lg font-semibold text-[#141413]">
|
||||
{loading ? "--" : metric.value}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-3 grid gap-2 border border-[#c9d8ea] bg-[#eef5ff] p-3 md:grid-cols-[minmax(0,0.85fr)_minmax(0,2fr)]">
|
||||
<div className="min-w-0">
|
||||
<div className="text-[11px] font-semibold text-[#1f5b9b]">
|
||||
{t("next")}
|
||||
</div>
|
||||
<div className="mt-1 break-words font-mono text-sm font-semibold text-[#141413]">
|
||||
{loading ? "--" : `${nextPriority}:${nextId}`}
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="text-[11px] font-semibold text-[#1f5b9b]">
|
||||
{t("nextAction")}
|
||||
</div>
|
||||
<div className="mt-1 break-words text-xs leading-5 text-[#141413]">
|
||||
{loading ? "--" : nextAction}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 grid gap-2">
|
||||
{items.length > 0 ? (
|
||||
items.map((item) => {
|
||||
const itemPriority = String(item.priority ?? "--");
|
||||
const itemStatus = String(item.status ?? "pending");
|
||||
return (
|
||||
<div
|
||||
key={item.id ?? item.order}
|
||||
className="grid gap-3 border border-[#e0ddd4] bg-[#faf9f3] p-3 lg:grid-cols-[minmax(112px,0.45fr)_minmax(0,1.6fr)_minmax(0,1.1fr)]"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
<span
|
||||
className={cn(
|
||||
"border px-2 py-0.5 font-mono text-[11px] font-semibold",
|
||||
commanderPriorityTone(itemPriority)
|
||||
)}
|
||||
>
|
||||
{itemPriority}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"border px-2 py-0.5 text-[11px] font-semibold",
|
||||
commanderStatusTone(itemStatus)
|
||||
)}
|
||||
>
|
||||
{statusLabels[itemStatus] ?? itemStatus}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2 break-all font-mono text-xs font-semibold text-[#141413]">
|
||||
{loading ? "--" : item.id}
|
||||
</div>
|
||||
<div className="mt-1 font-mono text-[10px] text-[#77736a]">
|
||||
{t("order", { order: item.order ?? 0 })}
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="break-words text-xs font-semibold leading-5 text-[#141413]">
|
||||
{loading ? "--" : item.normalized_work_item}
|
||||
</div>
|
||||
<div className="mt-2 break-words text-[11px] leading-5 text-[#77736a]">
|
||||
{loading ? "--" : item.request}
|
||||
</div>
|
||||
<div className="mt-2 break-words font-mono text-[10px] leading-4 text-[#5f5b52]">
|
||||
{loading ? "--" : item.mapped_workplan_id}
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 border border-[#d8d3c7] bg-white px-3 py-2">
|
||||
<div className="text-[11px] font-semibold text-[#77736a]">
|
||||
{t("acceptance")}
|
||||
</div>
|
||||
<div className="mt-1 break-words text-[11px] leading-5 text-[#141413]">
|
||||
{loading ? "--" : item.acceptance}
|
||||
</div>
|
||||
<div className="mt-2 text-[11px] font-semibold text-[#77736a]">
|
||||
{t("rowNextAction")}
|
||||
</div>
|
||||
<div className="mt-1 break-words text-[11px] leading-5 text-[#141413]">
|
||||
{loading ? "--" : item.next_action}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="border border-[#d8d3c7] bg-[#faf9f3] px-3 py-6 text-center text-xs text-[#77736a]">
|
||||
{loading ? t("loading") : t("empty")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-3 break-words font-mono text-[10px] leading-4 text-[#9a968c]">
|
||||
{t("source", { source })}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AwoooPWorkItemsPage() {
|
||||
const t = useTranslations("awooop.workItems");
|
||||
const locale = useLocale();
|
||||
@@ -8687,6 +8947,11 @@ export default function AwoooPWorkItemsPage() {
|
||||
loading={loading && !telemetry.priorityWorkOrder}
|
||||
/>
|
||||
|
||||
<CommanderInsertedRequirementsPanel
|
||||
priority={telemetry.priorityWorkOrder}
|
||||
loading={loading && !telemetry.priorityWorkOrder}
|
||||
/>
|
||||
|
||||
<OperatorSopRail
|
||||
telemetry={telemetry}
|
||||
workItems={workItems}
|
||||
|
||||
Reference in New Issue
Block a user