diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 25c468067..b0cdf58ef 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -9182,16 +9182,21 @@ }, "managerSituation": { "eyebrow": "Manager overview", - "title": "StockPlatform P0", - "subtitle": "Blocker, impact, and next action are pinned to the first screen.", + "title": "P0 manager overview", + "titles": { + "reboot": "P0-006 reboot recovery SLO", + "stockplatform": "StockPlatform P0" + }, + "subtitle": "The active P0, blocker, impact, and next action are pinned to the first screen.", "loading": "Reading production readback.", - "head": "Deployed source", + "head": "Readiness / deployed", "decision": "Decision", "decisionValue": "Fix P0, no side lane", "decisionParallel": "P0 waits for data; advance {id}", "blockingSources": "Blocking sources", "noSourceNames": "Waiting for source contract readback", "states": { + "rebootSlo": "10-minute recovery SLO not proven", "sourceContract": "Source contract blocked", "preflight": "Recovery preflight blocked", "publicApi": "Public API blocked", @@ -9206,18 +9211,25 @@ "impact": { "label": "Impact", "value": "Public API not green", - "detail": "Source trust is not sufficient." + "detail": "Source trust is not sufficient.", + "rebootValue": "Reboot recovery is not closed", + "rebootDetail": "Fresh reboot window, service green, and backup readback are not all true." }, "evidence": { "label": "Evidence", "value": "{count} sources failed", "receipted": "receipt provided", - "missing": "receipt missing" + "missing": "receipt missing", + "rebootValue": "{count} SLO blockers", + "rebootReceipted": "timer/source readback exists", + "rebootMissing": "waiting for live SLO readback" }, "next": { "label": "Next step", "value": "Repair source contract", - "detail": "Run verifier immediately after repair." + "detail": "Run verifier immediately after repair.", + "rebootValue": "Collect public readback", + "rebootDetail": "Collect service data backup readback first; {count} backup blockers remain." }, "parallel": { "label": "Parallel mainline", @@ -13452,6 +13464,24 @@ "noSecret": "不讀 secret", "noHostWrite": "不寫主機", "postcheck": "postcheck 先行" + }, + "executionLabel": "runtime controlled apply chain", + "execution": { + "preflight": { + "label": "Preflight", + "value": "{ready} ready / {targets} targets", + "detail": "diff {diff}, check {check}, rollback {rollback}, verifier {verifier}" + }, + "review": { + "label": "Review packet", + "value": "{accepted}/{ready} accepted", + "detail": "supplement {supplement}; runtime gate {runtime}" + }, + "dryRun": { + "label": "Allowlisted dry-run", + "value": "{accepted}/{result} result", + "detail": "check-mode {check}, postcheck {verifier}, host write {writes}" + } } }, "commandRail": { diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 1485d76fe..2cf118967 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -9182,16 +9182,21 @@ }, "managerSituation": { "eyebrow": "管理者總覽", - "title": "StockPlatform P0", - "subtitle": "卡點、影響、下一步先放第一屏。", + "title": "P0 管理者總覽", + "titles": { + "reboot": "P0-006 重啟恢復 SLO", + "stockplatform": "StockPlatform P0" + }, + "subtitle": "目前 P0、卡點、影響、下一步先放第一屏。", "loading": "正在讀取正式 readback。", - "head": "正式部署", + "head": "Readiness / 部署", "decision": "決策", "decisionValue": "先修 P0,不開支線", "decisionParallel": "P0 等資料;並行推進 {id}", "blockingSources": "阻塞來源", "noSourceNames": "等待 source contract readback", "states": { + "rebootSlo": "10 分鐘恢復 SLO 未證明", "sourceContract": "來源合約卡住", "preflight": "修復預檢卡住", "publicApi": "公開 API 卡住", @@ -9206,18 +9211,25 @@ "impact": { "label": "影響", "value": "公開 API 未綠燈", - "detail": "資料來源可信度不足" + "detail": "資料來源可信度不足", + "rebootValue": "重啟後恢復未閉環", + "rebootDetail": "fresh reboot window、service green 與 backup readback 尚未同時成立" }, "evidence": { "label": "證據", "value": "{count} 個來源未通過", "receipted": "receipt 已收件", - "missing": "receipt 未到" + "missing": "receipt 未到", + "rebootValue": "{count} 個 SLO blocker", + "rebootReceipted": "timer/source readback 已存在", + "rebootMissing": "等待 live SLO readback" }, "next": { "label": "下一步", "value": "補來源合約", - "detail": "修完直接跑 verifier" + "detail": "修完直接跑 verifier", + "rebootValue": "補 public readback", + "rebootDetail": "先收 service data backup readback;目前 {count} 個 backup blocker" }, "parallel": { "label": "並行主線", @@ -13452,6 +13464,24 @@ "noSecret": "不讀 secret", "noHostWrite": "不寫主機", "postcheck": "postcheck 先行" + }, + "executionLabel": "runtime controlled apply 鏈", + "execution": { + "preflight": { + "label": "Preflight", + "value": "{ready} ready / {targets} targets", + "detail": "diff {diff}、check {check}、rollback {rollback}、verifier {verifier}" + }, + "review": { + "label": "Review packet", + "value": "{accepted}/{ready} accepted", + "detail": "supplement {supplement};runtime gate {runtime}" + }, + "dryRun": { + "label": "Allowlisted dry-run", + "value": "{accepted}/{result} result", + "detail": "check-mode {check}、postcheck {verifier}、host write {writes}" + } } }, "commandRail": { 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 2d912f84f..52c54e114 100644 --- a/apps/web/src/app/[locale]/awooop/work-items/page.tsx +++ b/apps/web/src/app/[locale]/awooop/work-items/page.tsx @@ -1087,6 +1087,9 @@ type PriorityWorkOrderResponse = { } | null; summary?: { active_p0_state?: string | null; + active_p0_workplan_id?: string | null; + active_p0_readiness_percent?: number | null; + active_p0_live_active_blockers?: string[] | null; next_executable_mainline_workplan_id?: string | null; next_executable_mainline_state?: string | null; parallel_mainline_execution_enabled?: boolean | null; @@ -1145,6 +1148,11 @@ type PriorityWorkOrderResponse = { controlled_cd_lane_live_gitea_actions_active_process_count?: number | null; controlled_cd_lane_live_metric_blocker_count?: number | null; controlled_cd_lane_live_metric_blockers?: string[] | null; + controlled_service_data_backup_blocker_count?: number | null; + controlled_service_data_backup_can_clear_blockers?: boolean | null; + controlled_service_data_backup_next_safe_action?: string | null; + windows99_verify_collection_blocker_count?: number | null; + windows99_verify_collection_blockers?: string[] | null; ai_loop_log_source_grouping_key_count?: number | null; ai_loop_log_source_grouping_keys?: string[] | null; ai_loop_log_source_tagging_contract_count?: number | null; @@ -1168,7 +1176,21 @@ type PriorityWorkOrderResponse = { stockplatform_source_contract_non_ok_source_count?: number | null; } | null; in_progress_or_blocked_in_priority_order?: Array<{ + id?: string | null; + title?: string | null; + status?: string | null; + next_action?: string | null; evidence?: { + active_blockers?: string[] | null; + live_slo_metric_present?: boolean | null; + live_slo_service_last_result?: string | null; + live_slo_timer_active?: boolean | null; + live_slo_timer_enabled?: boolean | null; + latest_live_slo_artifact?: string | null; + controlled_service_data_backup_blocker_count?: number | null; + controlled_service_data_backup_can_clear_blockers?: boolean | null; + windows99_verify_collection_blockers?: string[] | null; + drill_preflight_blocker_count?: number | null; ai_loop_current_blocker_log_source_tags?: AiLoopLogSourceTag[] | null; ai_loop_log_source_tagging_contract?: AiLoopLogSourceContract[] | null; ai_loop_current_blocker_resolved_by_production_readback?: boolean | null; @@ -8769,8 +8791,6 @@ function stockplatformCompactTone(value: string | boolean | null | undefined) { const STOCKPLATFORM_SOURCE_CONTRACT_FALLBACK_SOURCES = [ "raw.source_quality_observations.unit_sanity", - "core.margin_short_daily", - "ai.recommendations", ] as const; function shortSha(value: string | null | undefined) { @@ -8779,6 +8799,7 @@ function shortSha(value: string | null | undefined) { function stockplatformManagerStateKey(value: string | null | undefined) { const state = String(value ?? ""); + if (state.includes("reboot")) return "rebootSlo"; if (state.includes("source_contract")) return "sourceContract"; if (state.includes("preflight")) return "preflight"; if (state.includes("public_api")) return "publicApi"; @@ -8795,14 +8816,24 @@ function ManagerSituationBoard({ }) { const t = useTranslations("awooop.workItems.managerSituation"); const summary = priority?.summary; - const evidence = priority?.in_progress_or_blocked_in_priority_order?.find( + const activeState = + summary?.active_p0_state ?? + "blocked_reboot_auto_recovery_slo_not_ready"; + const activeWorkplan = + summary?.active_p0_workplan_id ?? + summary?.next_executable_mainline_workplan_id ?? + "P0-006"; + const activeItem = priority?.in_progress_or_blocked_in_priority_order?.find( + (item) => + item.id === activeWorkplan || + item.status === activeState || + item.status?.includes("reboot") + ); + const evidence = activeItem?.evidence ?? priority?.in_progress_or_blocked_in_priority_order?.find( (item) => item.evidence?.stockplatform_source_contract_readback_status || item.evidence?.stockplatform_public_api_runtime_status )?.evidence; - const activeWorkplan = - summary?.next_executable_mainline_workplan_id ?? - "P0-006-STOCKPLATFORM-PUBLIC-API-CONTROLLED-RECOVERY-PREFLIGHT"; const parallelOverlay = priority?.mainline_parallel_execution; const parallelEnabled = summary?.parallel_mainline_execution_enabled === true || @@ -8819,12 +8850,25 @@ function ManagerSituationBoard({ summary?.parallel_mainline_retry_readback_at ?? parallelOverlay?.retry_readback_at ?? "--"; - const activeState = - summary?.active_p0_state ?? - "blocked_stockplatform_public_api_controlled_recovery_preflight"; const activeStateLabel = t( `states.${stockplatformManagerStateKey(activeState)}` as never ); + const isRebootP0 = activeState.includes("reboot"); + const boardTitle = isRebootP0 + ? t("titles.reboot") + : activeState.includes("stockplatform") + ? t("titles.stockplatform") + : t("title"); + const activeBlockers = + summary?.active_p0_live_active_blockers ?? + evidence?.active_blockers ?? + []; + const activeBlockerCount = activeBlockers.length; + const readinessPercent = summary?.active_p0_readiness_percent ?? null; + const rebootEvidenceReady = + evidence?.live_slo_metric_present === true && + evidence?.live_slo_timer_enabled === true && + evidence?.live_slo_timer_active === true; const sourceContractStatus = summary?.stockplatform_source_contract_readback_status ?? evidence?.stockplatform_source_contract_readback_status ?? @@ -8838,8 +8882,12 @@ function ManagerSituationBoard({ evidence?.stockplatform_source_contract_receipt_provided ?? true; const blockingSources = - evidence?.stockplatform_source_contract_blocking_sources?.slice(0, 3) ?? - STOCKPLATFORM_SOURCE_CONTRACT_FALLBACK_SOURCES.map((source) => ({ source })); + isRebootP0 + ? activeBlockers.slice(0, 4).map((source) => ({ source })) + : ( + evidence?.stockplatform_source_contract_blocking_sources?.slice(0, 3) ?? + STOCKPLATFORM_SOURCE_CONTRACT_FALLBACK_SOURCES.map((source) => ({ source })) + ); const sourceNames = blockingSources .map((source) => source.source) .filter(Boolean) @@ -8862,24 +8910,32 @@ function ManagerSituationBoard({ key: "impact", icon: Database, label: t("cards.impact.label"), - value: t("cards.impact.value"), - detail: t("cards.impact.detail"), + value: isRebootP0 ? t("cards.impact.rebootValue") : t("cards.impact.value"), + detail: isRebootP0 ? t("cards.impact.rebootDetail") : t("cards.impact.detail"), tone: "border-[#c9d8ea] bg-[#eef5ff] text-[#1f5b9b]", }, { key: "evidence", icon: SearchCheck, label: t("cards.evidence.label"), - value: t("cards.evidence.value", { count: nonOkSourceCount }), - detail: receiptProvided ? t("cards.evidence.receipted") : t("cards.evidence.missing"), - tone: stockplatformCompactTone(sourceContractStatus), + value: isRebootP0 + ? t("cards.evidence.rebootValue", { count: activeBlockerCount }) + : t("cards.evidence.value", { count: nonOkSourceCount }), + detail: isRebootP0 + ? (rebootEvidenceReady ? t("cards.evidence.rebootReceipted") : t("cards.evidence.rebootMissing")) + : receiptProvided ? t("cards.evidence.receipted") : t("cards.evidence.missing"), + tone: isRebootP0 ? stockplatformCompactTone(activeState) : stockplatformCompactTone(sourceContractStatus), }, { key: "next", icon: ArrowRight, label: t("cards.next.label"), - value: t("cards.next.value"), - detail: t("cards.next.detail"), + value: isRebootP0 ? t("cards.next.rebootValue") : t("cards.next.value"), + detail: isRebootP0 + ? t("cards.next.rebootDetail", { + count: summary?.controlled_service_data_backup_blocker_count ?? evidence?.controlled_service_data_backup_blocker_count ?? 0, + }) + : t("cards.next.detail"), tone: "border-[#cbd7bf] bg-[#f4faef] text-[#3d6b24]", }, { @@ -8908,7 +8964,7 @@ function ManagerSituationBoard({ {t("eyebrow")}

- {t("title")} + {boardTitle}

{loading ? t("loading") : t("subtitle")} @@ -8919,7 +8975,7 @@ function ManagerSituationBoard({ {t("head")}

- {shortSha(deployedSha)} + {readinessPercent === null ? shortSha(deployedSha) : `${readinessPercent}%`}
@@ -9862,12 +9918,12 @@ export default function AwoooPWorkItemsPage() { diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx index 3bf0db86a..28dc06e1b 100644 --- a/apps/web/src/app/[locale]/iwooos/page.tsx +++ b/apps/web/src/app/[locale]/iwooos/page.tsx @@ -48,10 +48,13 @@ import { type IwoooSSecurityControlCoverageResponse, type IwoooSWazuhLiveMetadataGateItem, type IwoooSWazuhLiveMetadataGateResponse, + type IwoooSWazuhAllowlistedCheckModeDryRunResponse, type IwoooSWazuhManagerRegistryReviewerValidationResponse, type IwoooSWazuhManagedHostCoverageResponse, type IwoooSWazuhOwnerEvidencePreflightItem, type IwoooSWazuhOwnerEvidencePreflightResponse, + type IwoooSWazuhRuntimeControlledApplyPreflightResponse, + type IwoooSWazuhRuntimeGateOwnerReviewReadbackResponse, } from '@/lib/api-client' type PostureMetric = { @@ -1201,6 +1204,20 @@ const iwooosManagerFallback = { expectedHostScopeCount: 6, managerRegistryAcceptedCount: 6, reviewerValidationPassedCount: 1, + controlledApplyPreflightReadyCount: 1, + controlledApplyTargetSelectorCount: 6, + controlledApplySourceDiffCount: 1, + controlledApplyCheckModePlanCount: 1, + controlledApplyRollbackPlanCount: 1, + controlledApplyPostApplyVerifierCount: 1, + ownerReviewPacketReviewReadyCount: 1, + ownerReviewPacketAcceptedCount: 1, + ownerReviewSupplementRequiredCount: 0, + allowlistedDryRunPacketAcceptedCount: 1, + allowlistedDryRunResultRefCount: 1, + allowlistedDryRunPostVerifierCount: 1, + allowlistedDryRunCheckModeAllowedCount: 6, + allowlistedDryRunRuntimeWriteAllowedCount: 0, runtimeGateCount: 0, hostWriteAuthorizedCount: 0, activeResponseAuthorizedCount: 0, @@ -15545,17 +15562,31 @@ function IwoooSManagerCockpit() { const [coverage, setCoverage] = useState(null) const [managedHosts, setManagedHosts] = useState(null) const [registry, setRegistry] = useState(null) + const [controlledApply, setControlledApply] = useState(null) + const [ownerReview, setOwnerReview] = useState(null) + const [allowlistedDryRun, setAllowlistedDryRun] = useState(null) const [loaded, setLoaded] = useState(false) useEffect(() => { let mounted = true async function fetchCockpitReadback() { - const [runtimeResult, coverageResult, managedHostsResult, registryResult] = await Promise.allSettled([ + const [ + runtimeResult, + coverageResult, + managedHostsResult, + registryResult, + controlledApplyResult, + ownerReviewResult, + allowlistedDryRunResult, + ] = await Promise.allSettled([ apiClient.getIwoooSRuntimeSecurityReadback(), apiClient.getIwoooSSecurityControlCoverage(), apiClient.getIwoooSWazuhManagedHostCoverage(), apiClient.getIwoooSWazuhManagerRegistryReviewerValidation(), + apiClient.getIwoooSWazuhRuntimeControlledApplyPreflight(), + apiClient.getIwoooSWazuhRuntimeGateOwnerReviewReadback(), + apiClient.getIwoooSWazuhAllowlistedCheckModeDryRun(), ]) if (!mounted) return @@ -15563,6 +15594,9 @@ function IwoooSManagerCockpit() { if (coverageResult.status === 'fulfilled') setCoverage(coverageResult.value) if (managedHostsResult.status === 'fulfilled') setManagedHosts(managedHostsResult.value) if (registryResult.status === 'fulfilled') setRegistry(registryResult.value) + if (controlledApplyResult.status === 'fulfilled') setControlledApply(controlledApplyResult.value) + if (ownerReviewResult.status === 'fulfilled') setOwnerReview(ownerReviewResult.value) + if (allowlistedDryRunResult.status === 'fulfilled') setAllowlistedDryRun(allowlistedDryRunResult.value) setLoaded(true) } @@ -15594,17 +15628,79 @@ function IwoooSManagerCockpit() { registry?.summary.runtime_gate_count ?? managedHosts?.summary.runtime_gate_count ?? coverage?.summary.runtime_gate_count ?? + controlledApply?.summary.runtime_gate_count ?? + ownerReview?.summary.runtime_gate_count ?? + allowlistedDryRun?.summary.runtime_gate_count ?? iwooosManagerFallback.runtimeGateCount const hostWriteAuthorizedCount = registry?.summary.host_write_authorized_count ?? managedHosts?.summary.host_write_authorized_count ?? coverage?.summary.host_write_authorized_count ?? + controlledApply?.summary.host_write_authorized_count ?? + ownerReview?.summary.host_write_authorized_count ?? + allowlistedDryRun?.summary.host_write_authorized_count ?? iwooosManagerFallback.hostWriteAuthorizedCount const activeResponseAuthorizedCount = registry?.summary.active_response_authorized_count ?? managedHosts?.summary.active_response_authorized_count ?? coverage?.summary.active_response_authorized_count ?? + controlledApply?.summary.wazuh_active_response_authorized_count ?? + ownerReview?.summary.wazuh_active_response_authorized_count ?? + allowlistedDryRun?.summary.wazuh_active_response_authorized_count ?? iwooosManagerFallback.activeResponseAuthorizedCount + const controlledApplyPreflightReadyCount = + controlledApply?.summary.controlled_apply_preflight_ready_count ?? + runtime?.summary.wazuh_runtime_apply_preflight_ready_count ?? + iwooosManagerFallback.controlledApplyPreflightReadyCount + const controlledApplyTargetSelectorCount = + controlledApply?.summary.target_selector_count ?? + runtime?.summary.wazuh_runtime_apply_target_selector_count ?? + iwooosManagerFallback.controlledApplyTargetSelectorCount + const controlledApplySourceDiffCount = + controlledApply?.summary.source_of_truth_diff_count ?? + runtime?.summary.wazuh_runtime_apply_source_diff_count ?? + iwooosManagerFallback.controlledApplySourceDiffCount + const controlledApplyCheckModePlanCount = + controlledApply?.summary.check_mode_plan_count ?? + runtime?.summary.wazuh_runtime_apply_check_mode_plan_count ?? + iwooosManagerFallback.controlledApplyCheckModePlanCount + const controlledApplyRollbackPlanCount = + controlledApply?.summary.rollback_plan_count ?? + runtime?.summary.wazuh_runtime_apply_rollback_plan_count ?? + iwooosManagerFallback.controlledApplyRollbackPlanCount + const controlledApplyPostApplyVerifierCount = + controlledApply?.summary.post_apply_verifier_count ?? + runtime?.summary.wazuh_runtime_apply_post_apply_verifier_count ?? + iwooosManagerFallback.controlledApplyPostApplyVerifierCount + const ownerReviewPacketReviewReadyCount = + ownerReview?.summary.owner_review_packet_review_ready_count ?? + runtime?.summary.wazuh_runtime_owner_review_packet_review_ready_count ?? + iwooosManagerFallback.ownerReviewPacketReviewReadyCount + const ownerReviewPacketAcceptedCount = + ownerReview?.summary.owner_review_packet_accepted_count ?? + runtime?.summary.wazuh_runtime_owner_review_packet_accepted_count ?? + iwooosManagerFallback.ownerReviewPacketAcceptedCount + const ownerReviewSupplementRequiredCount = + ownerReview?.summary.owner_review_packet_supplement_required_count ?? + iwooosManagerFallback.ownerReviewSupplementRequiredCount + const allowlistedDryRunPacketAcceptedCount = + allowlistedDryRun?.summary.dry_run_packet_accepted_count ?? + runtime?.summary.wazuh_allowlisted_check_mode_dry_run_packet_accepted_count ?? + iwooosManagerFallback.allowlistedDryRunPacketAcceptedCount + const allowlistedDryRunResultRefCount = + allowlistedDryRun?.summary.dry_run_result_ref_count ?? + runtime?.summary.wazuh_allowlisted_check_mode_dry_run_result_ref_count ?? + iwooosManagerFallback.allowlistedDryRunResultRefCount + const allowlistedDryRunPostVerifierCount = + allowlistedDryRun?.summary.post_dry_run_verifier_count ?? + runtime?.summary.wazuh_allowlisted_check_mode_dry_run_post_verifier_count ?? + iwooosManagerFallback.allowlistedDryRunPostVerifierCount + const allowlistedDryRunCheckModeAllowedCount = + allowlistedDryRun?.target_selectors.filter(selector => selector.check_mode_allowed).length ?? + iwooosManagerFallback.allowlistedDryRunCheckModeAllowedCount + const allowlistedDryRunRuntimeWriteAllowedCount = + allowlistedDryRun?.target_selectors.filter(selector => selector.runtime_write_allowed).length ?? + iwooosManagerFallback.allowlistedDryRunRuntimeWriteAllowedCount const registryComplete = managerRegistryAcceptedCount >= expectedHostScopeCount && expectedHostScopeCount > 0 const runtimeLocked = runtimeGateCount === 0 && hostWriteAuthorizedCount === 0 && activeResponseAuthorizedCount === 0 @@ -15653,6 +15749,50 @@ function IwoooSManagerCockpit() { { key: 'activeResponse', value: activeResponseAuthorizedCount, tone: 'locked' as const }, { key: 'reviewer', value: reviewerValidationPassedCount, tone: reviewerValidationPassedCount > 0 ? 'steady' as const : 'warn' as const }, ] + const executionSteps: IwoooSManagerCockpitMetric[] = [ + { + key: 'preflight', + value: t('execution.preflight.value', { + ready: controlledApplyPreflightReadyCount, + targets: controlledApplyTargetSelectorCount, + }), + detail: t('execution.preflight.detail', { + diff: controlledApplySourceDiffCount, + check: controlledApplyCheckModePlanCount, + rollback: controlledApplyRollbackPlanCount, + verifier: controlledApplyPostApplyVerifierCount, + }), + icon: ListChecks, + tone: controlledApplyPreflightReadyCount > 0 && controlledApplyTargetSelectorCount >= expectedHostScopeCount ? 'steady' : 'warn', + }, + { + key: 'review', + value: t('execution.review.value', { + accepted: ownerReviewPacketAcceptedCount, + ready: ownerReviewPacketReviewReadyCount, + }), + detail: t('execution.review.detail', { + supplement: ownerReviewSupplementRequiredCount, + runtime: runtimeGateCount, + }), + icon: ClipboardCheck, + tone: ownerReviewPacketAcceptedCount > 0 && ownerReviewSupplementRequiredCount === 0 ? 'steady' : 'warn', + }, + { + key: 'dryRun', + value: t('execution.dryRun.value', { + accepted: allowlistedDryRunPacketAcceptedCount, + result: allowlistedDryRunResultRefCount, + }), + detail: t('execution.dryRun.detail', { + check: allowlistedDryRunCheckModeAllowedCount, + verifier: allowlistedDryRunPostVerifierCount, + writes: allowlistedDryRunRuntimeWriteAllowedCount, + }), + icon: FileCheck2, + tone: allowlistedDryRunPacketAcceptedCount > 0 && allowlistedDryRunRuntimeWriteAllowedCount === 0 ? 'steady' : 'locked', + }, + ] return (
+
+
+ {t('executionLabel')} +
+
+ {executionSteps.map(item => { + const Icon = item.icon + return ( +
+
+ + {t(`execution.${item.key}.label` as never)} + + +
+
+ {item.value} +
+
+ {item.detail} +
+
+ ) + })} +
+
+