fix(web): show approvals controlled automation proof
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 43s
E2E Health Check / e2e-health (push) Failing after 29s
CD Pipeline / build-and-deploy (push) Successful in 6m2s
CD Pipeline / post-deploy-checks (push) Successful in 1m41s
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 43s
E2E Health Check / e2e-health (push) Failing after 29s
CD Pipeline / build-and-deploy (push) Successful in 6m2s
CD Pipeline / post-deploy-checks (push) Successful in 1m41s
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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({
|
||||
<p className="mt-3 border border-[#d9b36f] bg-white px-3 py-2 text-xs leading-5 text-[#8a5a08]">
|
||||
{t("boundary")}
|
||||
</p>
|
||||
<div
|
||||
data-testid="approvals-controlled-automation-proof"
|
||||
className="mt-3 flex items-start gap-3 border border-[#9bc7a4] bg-[#f0faf2] px-3 py-3 text-xs leading-5 text-[#17602a]"
|
||||
>
|
||||
<ShieldCheck className="mt-0.5 h-4 w-4 shrink-0" aria-hidden="true" />
|
||||
<div className="min-w-0">
|
||||
<p className="font-semibold text-[#17602a]">{t("controlledProof.title")}</p>
|
||||
<p className="mt-1 text-[#255f33]">
|
||||
{t("controlledProof.detail", {
|
||||
packages: controlledActionPackages.length,
|
||||
gate5: gate5.length,
|
||||
legacy: legacyApprovals.length,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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]) => (
|
||||
<div key={label} className="min-w-0 bg-white px-4 py-3">
|
||||
<p className="text-xs font-semibold text-[#77736a]">{label}</p>
|
||||
|
||||
Reference in New Issue
Block a user