diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 4ef528b9..66deb59f 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -12060,8 +12060,8 @@ "detail": "需要 AI 補齊、retry、rollback 或 break-glass 的審批" }, "handoff": { - "title": "接手", - "detail": "Gate 5、舊 HITL 證據與 AI 工作項補齊" + "title": "Action Packages", + "detail": "Gate 5 projections, historical evidence, and AI work item backfill" }, "verifier": { "title": "驗證", @@ -12074,7 +12074,7 @@ "detail": "找出 learning_recorded、execution_failed、AI retry / rollback 或逾時的審批。", "cta": "查看卡點", "meta": { - "needsHuman": "需要 AI 補齊", + "controlledAction": "AI action package", "executionFailed": "執行失敗 / 降級", "learningRecorded": "卡在學習紀錄" } @@ -12096,7 +12096,7 @@ "meta": { "gate5": "Gate 5 投影", "legacy": "Legacy HITL", - "manual": "AI 補齊" + "controlledAction": "AI backfill" } }, "guardrail": { @@ -12109,6 +12109,10 @@ "providerSwitch": "供應者切換" } } + }, + "controlledProof": { + "title": "Low / Medium / High default to AI controlled apply", + "detail": "AI action packages {packages}; Gate 5 {gate5}; historical HITL evidence {legacy}. Only critical / break-glass goes to incident-grade authorization; the rest is completed by AI selectors, check-mode, rollback, and verifiers." } }, "badges": { @@ -12159,7 +12163,7 @@ "openTickets": "Tickets", "empty": "無", "flowTitle": "處理流程", - "handoffTitle": "審批與 AI 受控接手", + "handoffTitle": "Approvals And AI Action Packages", "timelineEmpty": "尚未取得 Incident timeline。", "linkedExplanation": "此 Incident 已有受控決策 / timeline 關聯;若下方 AI 受控清單為空,代表它可能已完成、過期、拒絕,或已轉成 verifier / rollback / AI 補齊。", "unlinkedExplanation": "目前沒有對應 批准 id;這代表此 Incident不是等待批准的狀態,應從 Work Items / Runs 追下一步。", @@ -12167,12 +12171,16 @@ "yes": "需要 AI 補齊", "no": "不需 AI 補齊" }, + "controlledAction": { + "yes": "AI action package pending", + "no": "AI action package clear" + }, "metrics": { "approvals": "關聯審批", "stage": "目前階段", "repair": "修復狀態", "verification": "驗證", - "handoff": "AI 受控接手" + "handoff": "AI Action Package" }, "handoff": { "approvalIds": "Approval IDs", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index e28dce17..c4de46ea 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -12060,8 +12060,8 @@ "detail": "需要 AI 補齊、retry、rollback 或 break-glass 的審批" }, "handoff": { - "title": "接手", - "detail": "Gate 5、舊 HITL 證據與 AI 工作項補齊" + "title": "處置包", + "detail": "Gate 5、歷史證據與 AI 工作項補齊" }, "verifier": { "title": "驗證", @@ -12074,7 +12074,7 @@ "detail": "找出 learning_recorded、execution_failed、AI retry / rollback 或逾時的審批。", "cta": "查看卡點", "meta": { - "needsHuman": "需要 AI 補齊", + "controlledAction": "AI 處置包", "executionFailed": "執行失敗 / 降級", "learningRecorded": "卡在學習紀錄" } @@ -12096,7 +12096,7 @@ "meta": { "gate5": "Gate 5 投影", "legacy": "Legacy HITL", - "manual": "AI 補齊" + "controlledAction": "AI 補齊" } }, "guardrail": { @@ -12109,6 +12109,10 @@ "providerSwitch": "供應者切換" } } + }, + "controlledProof": { + "title": "低 / 中 / 高風險預設 AI controlled apply", + "detail": "AI 處置包 {packages};Gate 5 {gate5};既有 HITL 歷史證據 {legacy}。critical / break-glass 才進事故級授權,其餘由 AI 補齊 selector、check-mode、rollback 與 verifier。" } }, "badges": { @@ -12159,7 +12163,7 @@ "openTickets": "Tickets", "empty": "無", "flowTitle": "處理流程", - "handoffTitle": "審批與 AI 受控接手", + "handoffTitle": "審批與 AI 處置包", "timelineEmpty": "尚未取得 Incident timeline。", "linkedExplanation": "此 Incident 已有受控決策 / timeline 關聯;若下方 AI 受控清單為空,代表它可能已完成、過期、拒絕,或已轉成 verifier / rollback / AI 補齊。", "unlinkedExplanation": "目前沒有對應 批准 id;這代表此 Incident不是等待批准的狀態,應從 Work Items / Runs 追下一步。", @@ -12167,12 +12171,16 @@ "yes": "需要 AI 補齊", "no": "不需 AI 補齊" }, + "controlledAction": { + "yes": "AI 處置包待補齊", + "no": "AI 處置包已清空" + }, "metrics": { "approvals": "關聯審批", "stage": "目前階段", "repair": "修復狀態", "verification": "驗證", - "handoff": "AI 受控接手" + "handoff": "AI 處置包" }, "handoff": { "approvalIds": "Approval IDs", diff --git a/apps/web/src/app/[locale]/awooop/approvals/page.tsx b/apps/web/src/app/[locale]/awooop/approvals/page.tsx index 706108c7..85ad448b 100644 --- a/apps/web/src/app/[locale]/awooop/approvals/page.tsx +++ b/apps/web/src/app/[locale]/awooop/approvals/page.tsx @@ -366,7 +366,7 @@ function chainValues(approval: Approval): string[] { .map(lowerValue); } -function approvalNeedsHuman(approval: Approval): boolean { +function approvalNeedsControlledActionPackage(approval: Approval): boolean { const chain = approval.awooop_status_chain; const outcome = chain?.operator_outcome; return Boolean( @@ -1057,7 +1057,7 @@ function ApprovalDecisionRail({ }) { const t = useTranslations("awooop.approvals.decisionRail"); const projectQuery = encodeURIComponent(projectId); - const needsHuman = approvals.filter(approvalNeedsHuman); + const controlledActionPackages = approvals.filter(approvalNeedsControlledActionPackage); const executionFailed = approvals.filter(approvalHasExecutionFailure); const learningRecorded = approvals.filter(approvalHasLearningRecorded); const expired = approvals.filter(approvalIsExpired); @@ -1071,10 +1071,10 @@ function ApprovalDecisionRail({ const noEvidence = approvals.filter( (approval) => normalizeRemediationStatus(approval.remediation_summary) === "no_evidence" ); - const firstStuck = firstApproval([needsHuman, executionFailed, learningRecorded, expired]); - const stuckCount = uniqueApprovalCount([needsHuman, executionFailed, learningRecorded, expired]); + const firstStuck = firstApproval([controlledActionPackages, executionFailed, learningRecorded, expired]); + const stuckCount = uniqueApprovalCount([controlledActionPackages, executionFailed, learningRecorded, expired]); const evidenceCount = uniqueApprovalCount([mcpObserved, readOnly, noEvidence]); - const handoffCount = uniqueApprovalCount([needsHuman, gate5]) + legacyApprovals.length; + const handoffCount = uniqueApprovalCount([controlledActionPackages, gate5]) + legacyApprovals.length; const hasLoadIssue = Boolean(error || legacyError); const conclusionKey = stuckCount > 0 ? "blocked" @@ -1091,7 +1091,7 @@ function ApprovalDecisionRail({ icon: TriangleAlert, href: firstStuck ? approvalHref(firstStuck) : `/awooop/approvals?project_id=${projectQuery}`, meta: { - needsHuman: needsHuman.length, + controlledAction: controlledActionPackages.length, executionFailed: executionFailed.length, learningRecorded: learningRecorded.length, }, @@ -1117,7 +1117,7 @@ function ApprovalDecisionRail({ meta: { gate5: gate5.length, legacy: legacyApprovals.length, - manual: needsHuman.length, + controlledAction: controlledActionPackages.length, }, }, { @@ -1136,7 +1136,7 @@ function ApprovalDecisionRail({ const flow = [ { key: "request", value: approvals.length + legacyApprovals.length }, { key: "evidence", value: mcpObserved.length + readOnly.length }, - { key: "decision", value: needsHuman.length + expired.length }, + { key: "decision", value: controlledActionPackages.length + expired.length }, { key: "handoff", value: gate5.length + legacyApprovals.length }, { key: "verifier", value: executionFailed.length }, ]; @@ -1213,6 +1213,22 @@ function ApprovalDecisionRail({

{t("boundary")}

+
+
@@ -1719,7 +1735,9 @@ function FocusedIncidentApprovalPanel({ const topMcpTool = chain?.mcp?.top_tools?.[0]?.tool_name ?? "--"; const ansible = chain?.execution?.ansible; const outcome = chain?.operator_outcome; - const needsHuman = chain?.needs_human ?? outcome?.needs_human ?? false; + const needsControlledActionPackage = Boolean( + chain?.needs_human || outcome?.needs_human || outcome?.human_action_required + ); const title = timeline?.title ?? chain?.source_id ?? incidentId; const sourceCorrelation = chain?.source_refs?.correlation; @@ -1789,7 +1807,10 @@ function FocusedIncidentApprovalPanel({ [t("metrics.stage"), chain?.current_stage ?? "--"], [t("metrics.repair"), chain?.repair_state ?? "--"], [t("metrics.verification"), verifier?.status ?? chain?.verification ?? "--"], - [t("metrics.handoff"), needsHuman ? t("needsHuman.yes") : t("needsHuman.no")], + [ + t("metrics.handoff"), + needsControlledActionPackage ? t("controlledAction.yes") : t("controlledAction.no"), + ], ].map(([label, value]) => (

{label}