diff --git a/apps/api/src/services/platform_operator_service.py b/apps/api/src/services/platform_operator_service.py index 21e0659f..e37181ea 100644 --- a/apps/api/src/services/platform_operator_service.py +++ b/apps/api/src/services/platform_operator_service.py @@ -47,6 +47,12 @@ from src.services.awooop_truth_chain_service import ( _summarize_mcp, fetch_truth_chain, ) +from src.services.ai_agent_result_capture_owner_release_approval_gate import ( + load_latest_ai_agent_result_capture_owner_release_approval_gate, +) +from src.services.ai_agent_result_capture_release_verifier_preflight_gate import ( + load_latest_ai_agent_result_capture_release_verifier_preflight_gate, +) from src.services.governance_km_stale_review_service import ( query_km_stale_owner_review_completion_queue, ) @@ -4075,6 +4081,217 @@ def _status_chain_ansible_dry_run_only( ) +def _compact_release_items( + items: Any, + *, + id_field: str, + summary_field: str, + limit: int = 5, +) -> list[dict[str, Any]]: + if not isinstance(items, list): + return [] + compacted: list[dict[str, Any]] = [] + for item in items: + if not isinstance(item, dict): + continue + compacted.append( + { + "id": item.get(id_field), + "display_name": item.get("display_name"), + "owner_agent": item.get("owner_agent"), + "status": item.get("status"), + "summary": item.get(summary_field), + "runtime_write_allowed": item.get("runtime_write_allowed", False), + } + ) + if len(compacted) >= limit: + break + return compacted + + +def _owner_release_package_bridge() -> dict[str, Any]: + try: + payload = load_latest_ai_agent_result_capture_owner_release_approval_gate() + except Exception as exc: # pragma: no cover - fail-closed read model guard + return { + "schema_version": "awooop_owner_release_package_bridge_v1", + "status": "snapshot_unavailable", + "source_schema_version": None, + "source_task_id": None, + "runtime_authority": "read_model_unavailable_no_write", + "packet_count": 0, + "approval_required_count": 0, + "blocked_count": 0, + "owner_release_approved_count": 0, + "maintenance_window_approved_count": 0, + "rollback_owner_confirmed_count": 0, + "operator_action_count": 0, + "packets": [], + "operator_actions": [], + "error": str(exc)[:160], + } + + rollups = payload.get("rollups") if isinstance(payload.get("rollups"), dict) else {} + status = ( + payload.get("program_status") + if isinstance(payload.get("program_status"), dict) + else {} + ) + return { + "schema_version": "awooop_owner_release_package_bridge_v1", + "status": "owner_review_required", + "source_schema_version": payload.get("schema_version"), + "source_task_id": status.get("current_task_id"), + "runtime_authority": status.get("runtime_authority"), + "packet_count": _safe_int(rollups.get("owner_release_approval_packet_count")), + "approval_required_count": _safe_int(rollups.get("approval_required_packet_count")), + "blocked_count": _safe_int(rollups.get("blocked_packet_count")), + "owner_release_approved_count": _safe_int(rollups.get("owner_release_approved_count")), + "maintenance_window_approved_count": _safe_int(rollups.get("maintenance_window_approved_count")), + "rollback_owner_confirmed_count": _safe_int(rollups.get("rollback_owner_confirmed_count")), + "operator_action_count": _safe_int(rollups.get("operator_action_count")), + "packets": _compact_release_items( + payload.get("owner_release_approval_packets"), + id_field="packet_id", + summary_field="approval_summary", + ), + "operator_actions": _compact_release_items( + payload.get("operator_actions"), + id_field="action_id", + summary_field="operator_instruction", + limit=3, + ), + } + + +def _release_verifier_package_bridge() -> dict[str, Any]: + try: + payload = load_latest_ai_agent_result_capture_release_verifier_preflight_gate() + except Exception as exc: # pragma: no cover - fail-closed read model guard + return { + "schema_version": "awooop_release_verifier_package_bridge_v1", + "status": "snapshot_unavailable", + "source_schema_version": None, + "source_task_id": None, + "runtime_authority": "read_model_unavailable_no_write", + "verifier_count": 0, + "rollback_count": 0, + "maintenance_hold_count": 0, + "live_apply_hold_count": 0, + "owner_release_authorized_count": 0, + "post_release_verifier_ready_count": 0, + "live_apply_release_pass_count": 0, + "operator_action_count": 0, + "verifier_gates": [], + "operator_actions": [], + "error": str(exc)[:160], + } + + rollups = payload.get("rollups") if isinstance(payload.get("rollups"), dict) else {} + status = ( + payload.get("program_status") + if isinstance(payload.get("program_status"), dict) + else {} + ) + return { + "schema_version": "awooop_release_verifier_package_bridge_v1", + "status": "preflight_review_required", + "source_schema_version": payload.get("schema_version"), + "source_task_id": status.get("current_task_id"), + "runtime_authority": status.get("runtime_authority"), + "verifier_count": _safe_int(rollups.get("release_verifier_preflight_count")), + "rollback_count": _safe_int(rollups.get("rollback_verifier_preflight_count")), + "maintenance_hold_count": _safe_int(rollups.get("maintenance_window_verifier_hold_count")), + "live_apply_hold_count": _safe_int(rollups.get("live_apply_verifier_hold_count")), + "owner_release_authorized_count": _safe_int(rollups.get("owner_release_authorized_count")), + "post_release_verifier_ready_count": _safe_int(rollups.get("post_release_verifier_ready_count")), + "live_apply_release_pass_count": _safe_int(rollups.get("live_apply_release_pass_count")), + "operator_action_count": _safe_int(rollups.get("operator_action_count")), + "verifier_gates": _compact_release_items( + payload.get("release_verifier_preflight_gates"), + id_field="readback_id", + summary_field="readback_summary", + ), + "operator_actions": _compact_release_items( + payload.get("operator_actions"), + id_field="action_id", + summary_field="operator_instruction", + limit=3, + ), + } + + +def _apply_gate_closure_tasks( + *, + source_ref: str, + safe_source_ref: str, + catalog_id: str, + owner_release_package: dict[str, Any], + verifier_package: dict[str, Any], +) -> list[dict[str, Any]]: + return [ + { + "key": "owner_release_packet_review", + "status": "ready_for_owner_review", + "owner_agent": "openclaw", + "source_asset_id": "agent-result-capture-owner-release-approval-gate:P2-131", + "work_item_id": f"owner-release-review:awoooi:{safe_source_ref}", + "summary": ( + f"packets={_safe_int(owner_release_package.get('packet_count'))}; " + f"approved={_safe_int(owner_release_package.get('owner_release_approved_count'))}" + ), + "next_step": "review_owner_release_packet_and_evidence_refs", + "runtime_write_allowed": False, + }, + { + "key": "maintenance_window_rollback_owner", + "status": "approval_required", + "owner_agent": "hermes", + "source_asset_id": "agent-result-capture-owner-release-approval-gate:P2-131", + "work_item_id": f"maintenance-rollback-review:awoooi:{safe_source_ref}", + "summary": ( + f"maintenance_approved={_safe_int(owner_release_package.get('maintenance_window_approved_count'))}; " + f"rollback_confirmed={_safe_int(owner_release_package.get('rollback_owner_confirmed_count'))}" + ), + "next_step": "collect_maintenance_window_and_rollback_owner", + "runtime_write_allowed": False, + }, + { + "key": "controlled_execution_authorization", + "status": "blocked_before_runtime_gate", + "owner_agent": "sre", + "source_asset_id": f"ansible-apply-candidate:{catalog_id}", + "work_item_id": f"controlled-execution-gate:awoooi:{safe_source_ref}", + "summary": f"incident={source_ref}; runtime_gate=0", + "next_step": "owner_release_required_before_controlled_apply", + "runtime_write_allowed": False, + }, + { + "key": "post_apply_verifier_preflight", + "status": "preflight_review_required", + "owner_agent": "openclaw", + "source_asset_id": "agent-result-capture-release-verifier-preflight-gate:P2-136", + "work_item_id": f"post-apply-verifier:awoooi:{safe_source_ref}", + "summary": ( + f"verifier={_safe_int(verifier_package.get('verifier_count'))}; " + f"ready={_safe_int(verifier_package.get('post_release_verifier_ready_count'))}" + ), + "next_step": "review_post_apply_verifier_before_any_apply", + "runtime_write_allowed": False, + }, + { + "key": "km_playbook_trust_writeback_plan", + "status": "blocked_until_verifier_passes", + "owner_agent": "hermes", + "source_asset_id": f"playbook-trust-update-candidate:{catalog_id}", + "work_item_id": f"km-playbook-writeback:awoooi:{safe_source_ref}", + "summary": "km_write=0; playbook_trust_write=0", + "next_step": "prepare_km_and_playbook_writeback_after_verified_execution", + "runtime_write_allowed": False, + }, + ] + + def _status_chain_ansible_apply_gate_handoff( *, ansible_dry_run_only: bool, @@ -4180,6 +4397,15 @@ def _status_chain_ansible_apply_gate_handoff( closure_total_count = len(closure_gates) closure_blocked_count = sum(1 for gate in closure_gates if gate["status"] == "blocked") closure_completion_percent = int(round((closure_ready_count / closure_total_count) * 100)) + owner_release_package = _owner_release_package_bridge() + verifier_package = _release_verifier_package_bridge() + closure_tasks = _apply_gate_closure_tasks( + source_ref=str(source_ref), + safe_source_ref=safe_source_ref, + catalog_id=str(catalog_id), + owner_release_package=owner_release_package, + verifier_package=verifier_package, + ) return { "schema_version": "awooop_automation_handoff_v1", @@ -4244,6 +4470,9 @@ def _status_chain_ansible_apply_gate_handoff( "status": "read_only", }, ], + "owner_release_package": owner_release_package, + "release_verifier_package": verifier_package, + "closure_tasks": closure_tasks, }, "candidate": { "catalog_id": catalog_id, diff --git a/apps/api/tests/test_awooop_operator_timeline_labels.py b/apps/api/tests/test_awooop_operator_timeline_labels.py index 63b093dd..60fd1cb6 100644 --- a/apps/api/tests/test_awooop_operator_timeline_labels.py +++ b/apps/api/tests/test_awooop_operator_timeline_labels.py @@ -1862,6 +1862,45 @@ def test_awooop_status_chain_does_not_treat_ansible_check_mode_as_repair() -> No assert closure["readback_assets"][0]["asset_id"] == ( "agent-result-capture-owner-approved-execution-rehearsal:P2-126" ) + owner_package = closure["owner_release_package"] + assert owner_package["schema_version"] == "awooop_owner_release_package_bridge_v1" + assert owner_package["source_schema_version"] == ( + "ai_agent_result_capture_owner_release_approval_gate_v1" + ) + assert owner_package["source_task_id"] == "P2-131" + assert owner_package["packet_count"] == 5 + assert owner_package["owner_release_approved_count"] == 0 + assert owner_package["maintenance_window_approved_count"] == 0 + assert owner_package["rollback_owner_confirmed_count"] == 0 + assert owner_package["packets"][0]["id"] == "approval_gate_result_capture_writer" + verifier_package = closure["release_verifier_package"] + assert verifier_package["schema_version"] == ( + "awooop_release_verifier_package_bridge_v1" + ) + assert verifier_package["source_schema_version"] == ( + "ai_agent_result_capture_release_verifier_preflight_gate_v1" + ) + assert verifier_package["source_task_id"] == "P2-136" + assert verifier_package["verifier_count"] == 5 + assert verifier_package["post_release_verifier_ready_count"] == 0 + assert verifier_package["live_apply_release_pass_count"] == 0 + assert verifier_package["verifier_gates"][0]["id"] == ( + "release_verifier_preflight_result_capture" + ) + assert [task["key"] for task in closure["closure_tasks"]] == [ + "owner_release_packet_review", + "maintenance_window_rollback_owner", + "controlled_execution_authorization", + "post_apply_verifier_preflight", + "km_playbook_trust_writeback_plan", + ] + assert {task["runtime_write_allowed"] for task in closure["closure_tasks"]} == {False} + assert closure["closure_tasks"][0]["source_asset_id"] == ( + "agent-result-capture-owner-release-approval-gate:P2-131" + ) + assert closure["closure_tasks"][3]["source_asset_id"] == ( + "agent-result-capture-release-verifier-preflight-gate:P2-136" + ) assert chain["execution"]["ansible"]["check_mode_total"] == 1 assert chain["execution"]["ansible"]["apply_total"] == 0 assert chain["execution"]["ansible"]["applied"] is False diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index d56c85b2..8c248c66 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -10180,7 +10180,11 @@ "manualInvestigation": "人工調查卡點並補證據", "reviewVerifier": "執行或審查修復後 verifier", "monitorRegression": "持續觀察是否復發", - "collectEvidenceOrWait": "補收證據或等待下一筆 recurrence" + "collectEvidenceOrWait": "補收證據或等待下一筆 recurrence", + "collectMaintenanceRollback": "補齊維護窗口與 rollback owner", + "ownerReleaseBeforeApply": "Owner 放行後才可進入受控套用", + "reviewPostApplyVerifier": "先審查套用後 verifier,再談執行", + "prepareKmPlaybookWriteback": "準備 verifier 通過後的 KM / PlayBook 回寫" }, "evidence": { "autoRepair": "Auto-repair", @@ -10217,6 +10221,17 @@ "closureBlockedReason": "阻擋:{reason}", "closureOwnerFields": "Owner 放行欄位", "closureReadbackAssets": "只讀回查資產", + "ownerReleasePackageTitle": "Owner 放行包", + "releaseVerifierPackageTitle": "Verifier 放行前檢", + "ownerReleasePacketValue": "packets {packets} · approved {approved}", + "maintenanceRollbackValue": "維護 {maintenance} · rollback {rollback}", + "operatorActionValue": "operator actions {actions}", + "verifierPackageValue": "verifier {verifier} · ready {ready}", + "liveApplyHoldValue": "live apply hold {holds} · passed {passed}", + "ownerAuthorizedValue": "owner authorized {authorized}", + "closureTaskBoardTitle": "閉環任務板", + "closureTaskOwner": "Owner:{owner}", + "runtimeWrite": "runtime_write={value}", "checklistTitle": "Owner 審查清單", "forbiddenTitle": "禁止動作", "gates": { @@ -10239,10 +10254,25 @@ "finalCandidateReadback": "最終候選回查", "releaseVerifierPreflight": "Verifier 放行前檢查" }, + "closureTasks": { + "ownerReleasePacketReview": "審 Owner 放行包", + "maintenanceRollbackOwner": "維護 / Rollback", + "controlledExecutionAuthorization": "受控執行授權", + "postApplyVerifierPreflight": "套用後 Verifier", + "kmPlaybookTrustWritebackPlan": "KM / PlayBook 回寫" + }, "closureStatuses": { "blockedBeforeOwnerRelease": "Owner 放行前受阻", "noWriteRehearsal": "無寫入演練", - "readOnly": "只讀" + "readOnly": "只讀", + "ownerReviewRequired": "需 Owner 審查", + "preflightReviewRequired": "需前檢審查", + "readyForOwnerReview": "可送 Owner 審查", + "approvalRequired": "需批准", + "blockedByPolicy": "政策阻擋", + "blockedBeforeRuntimeGate": "Runtime gate 前受阻", + "blockedUntilVerifierPasses": "Verifier 通過前受阻", + "snapshotUnavailable": "快照不可用" }, "statuses": { "passed": "已通過", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index d56c85b2..8c248c66 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -10180,7 +10180,11 @@ "manualInvestigation": "人工調查卡點並補證據", "reviewVerifier": "執行或審查修復後 verifier", "monitorRegression": "持續觀察是否復發", - "collectEvidenceOrWait": "補收證據或等待下一筆 recurrence" + "collectEvidenceOrWait": "補收證據或等待下一筆 recurrence", + "collectMaintenanceRollback": "補齊維護窗口與 rollback owner", + "ownerReleaseBeforeApply": "Owner 放行後才可進入受控套用", + "reviewPostApplyVerifier": "先審查套用後 verifier,再談執行", + "prepareKmPlaybookWriteback": "準備 verifier 通過後的 KM / PlayBook 回寫" }, "evidence": { "autoRepair": "Auto-repair", @@ -10217,6 +10221,17 @@ "closureBlockedReason": "阻擋:{reason}", "closureOwnerFields": "Owner 放行欄位", "closureReadbackAssets": "只讀回查資產", + "ownerReleasePackageTitle": "Owner 放行包", + "releaseVerifierPackageTitle": "Verifier 放行前檢", + "ownerReleasePacketValue": "packets {packets} · approved {approved}", + "maintenanceRollbackValue": "維護 {maintenance} · rollback {rollback}", + "operatorActionValue": "operator actions {actions}", + "verifierPackageValue": "verifier {verifier} · ready {ready}", + "liveApplyHoldValue": "live apply hold {holds} · passed {passed}", + "ownerAuthorizedValue": "owner authorized {authorized}", + "closureTaskBoardTitle": "閉環任務板", + "closureTaskOwner": "Owner:{owner}", + "runtimeWrite": "runtime_write={value}", "checklistTitle": "Owner 審查清單", "forbiddenTitle": "禁止動作", "gates": { @@ -10239,10 +10254,25 @@ "finalCandidateReadback": "最終候選回查", "releaseVerifierPreflight": "Verifier 放行前檢查" }, + "closureTasks": { + "ownerReleasePacketReview": "審 Owner 放行包", + "maintenanceRollbackOwner": "維護 / Rollback", + "controlledExecutionAuthorization": "受控執行授權", + "postApplyVerifierPreflight": "套用後 Verifier", + "kmPlaybookTrustWritebackPlan": "KM / PlayBook 回寫" + }, "closureStatuses": { "blockedBeforeOwnerRelease": "Owner 放行前受阻", "noWriteRehearsal": "無寫入演練", - "readOnly": "只讀" + "readOnly": "只讀", + "ownerReviewRequired": "需 Owner 審查", + "preflightReviewRequired": "需前檢審查", + "readyForOwnerReview": "可送 Owner 審查", + "approvalRequired": "需批准", + "blockedByPolicy": "政策阻擋", + "blockedBeforeRuntimeGate": "Runtime gate 前受阻", + "blockedUntilVerifierPasses": "Verifier 通過前受阻", + "snapshotUnavailable": "快照不可用" }, "statuses": { "passed": "已通過", diff --git a/apps/web/src/components/awooop/status-chain.tsx b/apps/web/src/components/awooop/status-chain.tsx index cf0fa2a7..dbeaaa82 100644 --- a/apps/web/src/components/awooop/status-chain.tsx +++ b/apps/web/src/components/awooop/status-chain.tsx @@ -166,6 +166,75 @@ export interface AwoooPStatusChain { asset_id?: string | null; status?: string | null; }>; + owner_release_package?: { + schema_version?: string | null; + status?: string | null; + source_schema_version?: string | null; + source_task_id?: string | null; + runtime_authority?: string | null; + packet_count?: number | null; + approval_required_count?: number | null; + blocked_count?: number | null; + owner_release_approved_count?: number | null; + maintenance_window_approved_count?: number | null; + rollback_owner_confirmed_count?: number | null; + operator_action_count?: number | null; + packets?: Array<{ + id?: string | null; + display_name?: string | null; + owner_agent?: string | null; + status?: string | null; + summary?: string | null; + runtime_write_allowed?: boolean | null; + }>; + operator_actions?: Array<{ + id?: string | null; + owner_agent?: string | null; + status?: string | null; + summary?: string | null; + runtime_write_allowed?: boolean | null; + }>; + }; + release_verifier_package?: { + schema_version?: string | null; + status?: string | null; + source_schema_version?: string | null; + source_task_id?: string | null; + runtime_authority?: string | null; + verifier_count?: number | null; + rollback_count?: number | null; + maintenance_hold_count?: number | null; + live_apply_hold_count?: number | null; + owner_release_authorized_count?: number | null; + post_release_verifier_ready_count?: number | null; + live_apply_release_pass_count?: number | null; + operator_action_count?: number | null; + verifier_gates?: Array<{ + id?: string | null; + display_name?: string | null; + owner_agent?: string | null; + status?: string | null; + summary?: string | null; + runtime_write_allowed?: boolean | null; + }>; + operator_actions?: Array<{ + id?: string | null; + owner_agent?: string | null; + status?: string | null; + summary?: string | null; + runtime_write_allowed?: boolean | null; + }>; + }; + closure_tasks?: Array<{ + key?: string | null; + status?: string | null; + owner_agent?: string | null; + source_asset_id?: string | null; + work_item_id?: string | null; + summary?: string | null; + next_step?: string | null; + runtime_write_allowed?: boolean | null; + }>; }; candidate?: { catalog_id?: string | null; @@ -312,6 +381,11 @@ export function AwoooPStatusChainPanel({ run_or_review_post_execution_verification: t("nextActions.reviewVerifier"), monitor_for_regression: t("nextActions.monitorRegression"), collect_evidence_or_wait: t("nextActions.collectEvidenceOrWait"), + review_owner_release_packet_and_evidence_refs: t("nextActions.reviewOwnerReleasePacket"), + collect_maintenance_window_and_rollback_owner: t("nextActions.collectMaintenanceRollback"), + owner_release_required_before_controlled_apply: t("nextActions.ownerReleaseBeforeApply"), + review_post_apply_verifier_before_any_apply: t("nextActions.reviewPostApplyVerifier"), + prepare_km_and_playbook_writeback_after_verified_execution: t("nextActions.prepareKmPlaybookWriteback"), }; return labels[key] ?? valueOrEmpty(value, emptyLabel); }; @@ -466,6 +540,9 @@ export function AwoooPStatusChainPanel({ const closureGates = closureReadiness?.gates ?? []; const closureReadbackAssets = closureReadiness?.readback_assets ?? []; const closureOwnerFields = closureReadiness?.required_owner_fields ?? []; + const closureOwnerPackage = closureReadiness?.owner_release_package; + const closureVerifierPackage = closureReadiness?.release_verifier_package; + const closureTasks = closureReadiness?.closure_tasks ?? []; const ownerReviewChecklist = automationHandoff?.owner_review_checklist ?? []; const forbiddenActions = automationHandoff?.forbidden_actions ?? []; const sourceToolchainTone: SourceFlowTone = sourceCorrelation @@ -603,9 +680,27 @@ export function AwoooPStatusChainPanel({ blocked_before_owner_release: t("applyGate.closureStatuses.blockedBeforeOwnerRelease"), no_write_rehearsal: t("applyGate.closureStatuses.noWriteRehearsal"), read_only: t("applyGate.closureStatuses.readOnly"), + owner_review_required: t("applyGate.closureStatuses.ownerReviewRequired"), + preflight_review_required: t("applyGate.closureStatuses.preflightReviewRequired"), + ready_for_owner_review: t("applyGate.closureStatuses.readyForOwnerReview"), + approval_required: t("applyGate.closureStatuses.approvalRequired"), + blocked_by_policy: t("applyGate.closureStatuses.blockedByPolicy"), + blocked_before_runtime_gate: t("applyGate.closureStatuses.blockedBeforeRuntimeGate"), + blocked_until_verifier_passes: t("applyGate.closureStatuses.blockedUntilVerifierPasses"), + snapshot_unavailable: t("applyGate.closureStatuses.snapshotUnavailable"), }; return labels[String(status ?? "")] ?? handoffStatusLabel(status); }; + const closureTaskLabel = (key: string | null | undefined) => { + const labels: Record = { + owner_release_packet_review: t("applyGate.closureTasks.ownerReleasePacketReview"), + maintenance_window_rollback_owner: t("applyGate.closureTasks.maintenanceRollbackOwner"), + controlled_execution_authorization: t("applyGate.closureTasks.controlledExecutionAuthorization"), + post_apply_verifier_preflight: t("applyGate.closureTasks.postApplyVerifierPreflight"), + km_playbook_trust_writeback_plan: t("applyGate.closureTasks.kmPlaybookTrustWritebackPlan"), + }; + return labels[String(key ?? "")] ?? valueOrEmpty(key, emptyLabel); + }; const handoffWorkItemHref = automationHandoff?.work_item_id ? `/awooop/work-items?project_id=awoooi&work_item_id=${encodeURIComponent(automationHandoff.work_item_id)}${automationHandoff.source_id ? `&incident_id=${encodeURIComponent(automationHandoff.source_id)}` : ""}` : null; @@ -917,6 +1012,117 @@ export function AwoooPStatusChainPanel({ ))} +
+
+
+
+

{t("applyGate.ownerReleasePackageTitle")}

+

+ {valueOrEmpty(closureOwnerPackage?.source_task_id, emptyLabel)} +

+
+ + {closureStatusLabel(closureOwnerPackage?.status)} + +
+
+ + {t("applyGate.ownerReleasePacketValue", { + packets: closureOwnerPackage?.packet_count ?? 0, + approved: closureOwnerPackage?.owner_release_approved_count ?? 0, + })} + + + {t("applyGate.maintenanceRollbackValue", { + maintenance: closureOwnerPackage?.maintenance_window_approved_count ?? 0, + rollback: closureOwnerPackage?.rollback_owner_confirmed_count ?? 0, + })} + + + {t("applyGate.operatorActionValue", { + actions: closureOwnerPackage?.operator_action_count ?? 0, + })} + +
+
+
+
+
+

{t("applyGate.releaseVerifierPackageTitle")}

+

+ {valueOrEmpty(closureVerifierPackage?.source_task_id, emptyLabel)} +

+
+ + {closureStatusLabel(closureVerifierPackage?.status)} + +
+
+ + {t("applyGate.verifierPackageValue", { + verifier: closureVerifierPackage?.verifier_count ?? 0, + ready: closureVerifierPackage?.post_release_verifier_ready_count ?? 0, + })} + + + {t("applyGate.liveApplyHoldValue", { + holds: closureVerifierPackage?.live_apply_hold_count ?? 0, + passed: closureVerifierPackage?.live_apply_release_pass_count ?? 0, + })} + + + {t("applyGate.ownerAuthorizedValue", { + authorized: closureVerifierPackage?.owner_release_authorized_count ?? 0, + })} + +
+
+
+
+
+

{t("applyGate.closureTaskBoardTitle")}

+ + {closureTasks.length} + +
+
+
+ {(closureTasks.length ? closureTasks : [{ key: emptyLabel, status: emptyLabel, owner_agent: emptyLabel, summary: emptyLabel, source_asset_id: emptyLabel, next_step: emptyLabel, runtime_write_allowed: false }]).map((task, index) => ( +
+
+ + +
+

{closureTaskLabel(task.key)}

+

+ {closureStatusLabel(task.status)} +

+
+
+

+ {t("applyGate.closureTaskOwner", { owner: valueOrEmpty(task.owner_agent, emptyLabel) })} +

+

+ {valueOrEmpty(task.summary, emptyLabel)} +

+

+ {valueOrEmpty(task.source_asset_id, emptyLabel)} +

+

+ {nextActionLabel(task.next_step)} +

+ + {t("applyGate.runtimeWrite", { + value: boolValue(task.runtime_write_allowed, emptyLabel), + })} + +
+ ))} +