feat(awooop): enable controlled agent apply
Some checks failed
Code Review / ai-code-review (push) Successful in 16s
CD Pipeline / tests (push) Failing after 1m37s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-26 18:17:05 +08:00
parent c8912204ce
commit e7c368aa27
19 changed files with 1311 additions and 560 deletions

View File

@@ -1516,6 +1516,10 @@ def _ai_alert_card_delivery_item(row: Mapping[str, Any]) -> dict[str, Any]:
"runtime_write_gate_count": runtime_write_gate_count,
"runtime_write_allowed": runtime_write_gate_count > 0,
"candidate_only": bool(alert_card.get("candidate_only")),
"controlled_playbook_queue": bool(alert_card.get("controlled_playbook_queue")),
"runtime_write_gate_state": str(
alert_card.get("runtime_write_gate_state") or "unknown"
),
"delivery_receipt_readback_required": bool(
alert_card.get("delivery_receipt_readback_required")
),
@@ -3071,8 +3075,10 @@ def _outbound_timeline_title(
return f"{channel}CI/CD 狀態通知"
if "AI 治理警報" in preview:
return f"{channel}AI 治理警報"
if "HANDOFF REQUIRED" in preview or "AI 自動修復失敗" in preview:
return f"{channel}AI 自動修復失敗,已轉人工"
if "AI RETRY QUEUED" in preview or "AI 自動修復失敗" in preview:
return f"{channel}AI 自動修復失敗,已排入重試"
if "HANDOFF REQUIRED" in preview:
return f"{channel}Break-glass 人工接手"
if "AUTO RESOLVED" in preview or "AI 自動修復完成" in preview:
return f"{channel}AI 自動修復完成"
if "ESCALATION" in preview or "事故升級" in preview:
@@ -4228,47 +4234,72 @@ def _apply_gate_closure_tasks(
catalog_id: str,
owner_release_package: dict[str, Any],
verifier_package: dict[str, Any],
controlled_apply_allowed: bool,
) -> list[dict[str, Any]]:
return [
{
"key": "owner_release_packet_review",
"status": "ready_for_owner_review",
"status": "ai_policy_receipt_recorded"
if controlled_apply_allowed
else "ai_repair_required_before_policy_receipt",
"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'))}"
"owner_policy=low_medium_high_auto_authorized"
),
"next_step": "review_owner_release_packet_and_evidence_refs",
"runtime_write_allowed": False,
"next_step": (
"record_autonomous_policy_receipt_and_watch_stop_conditions"
if controlled_apply_allowed
else "wait_for_ai_playbook_or_transport_repair"
),
"runtime_write_allowed": controlled_apply_allowed,
},
{
"key": "maintenance_window_rollback_owner",
"status": "approval_required",
"status": "ai_selected_with_rollback_context"
if controlled_apply_allowed
else "waiting_for_repaired_check_mode",
"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'))}"
"maintenance_window=standard_auto_window; "
"rollback_owner=sre_oncall_or_service_owner"
),
"next_step": "collect_maintenance_window_and_rollback_owner",
"runtime_write_allowed": False,
"next_step": (
"prepare_automatic_rollback_and_context"
if controlled_apply_allowed
else "rerun_check_mode_after_repair"
),
"runtime_write_allowed": controlled_apply_allowed,
},
{
"key": "controlled_execution_authorization",
"status": "blocked_before_runtime_gate",
"owner_agent": "sre",
"status": "controlled_apply_authorized"
if controlled_apply_allowed
else "ai_repair_required_before_runtime_gate",
"owner_agent": "ansible_controlled_apply_worker",
"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,
"summary": (
f"incident={source_ref}; runtime_gate=controlled_apply"
if controlled_apply_allowed
else f"incident={source_ref}; runtime_gate=repair_required"
),
"next_step": (
"execute_allowlisted_apply_after_check_mode"
if controlled_apply_allowed
else "ai_repairs_playbook_or_transport_then_retries_check_mode"
),
"runtime_write_allowed": controlled_apply_allowed,
},
{
"key": "post_apply_verifier_preflight",
"status": "preflight_review_required",
"status": "queued_after_controlled_apply"
if controlled_apply_allowed
else "waiting_for_controlled_apply",
"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}",
@@ -4276,18 +4307,24 @@ def _apply_gate_closure_tasks(
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,
"next_step": (
"run_post_apply_verifier_after_apply"
if controlled_apply_allowed
else "wait_for_successful_check_mode_and_apply"
),
"runtime_write_allowed": controlled_apply_allowed,
},
{
"key": "km_playbook_trust_writeback_plan",
"status": "blocked_until_verifier_passes",
"status": "queued_after_verifier_passes"
if controlled_apply_allowed
else "waiting_for_verifier",
"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,
"runtime_write_allowed": controlled_apply_allowed,
},
]
@@ -4303,17 +4340,8 @@ def _apply_gate_controlled_execution_preflight(
owner_release_package: dict[str, Any],
verifier_package: dict[str, Any],
) -> dict[str, Any]:
"""Describe the no-write route that would become executable after release gates."""
"""Describe the controlled route that becomes executable after check-mode."""
owner_release_approved = _safe_int(
owner_release_package.get("owner_release_approved_count")
)
maintenance_approved = _safe_int(
owner_release_package.get("maintenance_window_approved_count")
)
rollback_confirmed = _safe_int(
owner_release_package.get("rollback_owner_confirmed_count")
)
verifier_ready = _safe_int(verifier_package.get("post_release_verifier_ready_count"))
route_candidate_ready = (
dry_run_passed
@@ -4321,6 +4349,7 @@ def _apply_gate_controlled_execution_preflight(
and str(apply_playbook or "").strip()
and str(apply_playbook or "").strip() != "--"
)
controlled_apply_allowed = bool(route_candidate_ready)
prerequisites = [
{
"key": "dry_run_passed",
@@ -4331,7 +4360,7 @@ def _apply_gate_controlled_execution_preflight(
{
"key": "allowlisted_route_candidate",
"status": (
"candidate_ready_no_runtime_authority"
"controlled_apply_allowed"
if route_candidate_ready
else "route_missing"
),
@@ -4340,28 +4369,20 @@ def _apply_gate_controlled_execution_preflight(
},
{
"key": "owner_release_receipt",
"status": (
"passed" if owner_release_approved > 0 else "blocked_missing_owner_release"
),
"detail": f"approved={owner_release_approved}",
"status": "auto_waived_by_risk_policy" if route_candidate_ready else "waiting",
"detail": "low_medium_high_auto_authorized",
"required": True,
},
{
"key": "maintenance_window",
"status": (
"passed"
if maintenance_approved > 0
else "blocked_missing_maintenance_window"
),
"detail": f"approved={maintenance_approved}",
"status": "ai_selected_standard_window" if route_candidate_ready else "waiting",
"detail": "auto_window_with_rollback_and_verifier",
"required": True,
},
{
"key": "rollback_owner",
"status": (
"passed" if rollback_confirmed > 0 else "blocked_missing_rollback_owner"
),
"detail": f"confirmed={rollback_confirmed}",
"status": "ai_prefilled" if route_candidate_ready else "waiting",
"detail": "rollback_owner=sre_oncall_or_service_owner",
"required": True,
},
{
@@ -4385,47 +4406,53 @@ def _apply_gate_controlled_execution_preflight(
1
for item in prerequisites
if item["status"] in {"passed", "candidate_ready_no_runtime_authority"}
or item["status"] in {
"controlled_apply_allowed",
"auto_waived_by_risk_policy",
"ai_selected_standard_window",
"ai_prefilled",
}
)
blocked_count = len(prerequisites) - ready_count
route_count = 1 if route_candidate_ready else 0
return {
"schema_version": "awooop_controlled_execution_preflight_v1",
"status": "blocked_before_runtime_gate",
"status": "controlled_apply_allowed" if controlled_apply_allowed else "ai_playbook_repair_required",
"source_id": source_ref,
"work_item_id": f"controlled-execution-gate:awoooi:{safe_source_ref}",
"runtime_execution_authorized": False,
"runtime_write_allowed": False,
"allowed_route_count": 0,
"runtime_execution_authorized": controlled_apply_allowed,
"runtime_write_allowed": controlled_apply_allowed,
"allowed_route_count": 1 if controlled_apply_allowed else 0,
"candidate_route_count": route_count,
"ready_count": ready_count,
"total_count": len(prerequisites),
"blocked_count": blocked_count,
"next_action": "collect_owner_release_maintenance_rollback_and_verifier",
"blocked_reason": "owner_release_or_verifier_gate_missing",
"next_action": (
"controlled_apply_worker_executes_after_check_mode"
if controlled_apply_allowed
else "ai_repairs_playbook_or_transport_then_retries_check_mode"
),
"blocked_reason": None if controlled_apply_allowed else "check_mode_failed_or_route_missing",
"routes": [
{
"route_id": f"ansible-allowlisted-apply:{catalog_id}",
"transport": "ansible",
"status": (
"candidate_ready_no_runtime_authority"
if route_candidate_ready
else "route_missing"
),
"status": "allowed" if controlled_apply_allowed else "route_missing",
"source_asset_id": f"ansible-apply-candidate:{catalog_id}",
"check_mode_playbook_path": check_mode_playbook,
"apply_playbook_path": apply_playbook,
"allowed": False,
"blocker": "runtime_gate_closed_until_owner_release_and_verifier",
"allowed": controlled_apply_allowed,
"blocker": None if controlled_apply_allowed else "check_mode_failed_or_route_missing",
}
],
"prerequisites": prerequisites,
"forbidden_until_released": [
"ansible_apply",
"ssh_write",
"service_restart",
"telegram_send",
"km_writeback",
"playbook_trust_writeback",
"critical_or_break_glass_apply",
"secret_read",
"database_migration",
"stateful_restore_or_prune",
"node_drain",
"reboot",
],
}
@@ -4441,48 +4468,45 @@ def _apply_gate_execution_release_contract(
facts: dict[str, Any],
controlled_execution_preflight: dict[str, Any],
) -> dict[str, Any]:
"""Build the owner-filled release contract required before any apply route."""
"""Build the AI-filled release contract used by controlled apply routes."""
route_id = f"ansible-allowlisted-apply:{catalog_id}"
mcp_evidence_ready = _safe_int(facts.get("mcp_gateway_total")) > 0
controlled_apply_allowed = bool(controlled_execution_preflight.get("runtime_execution_authorized"))
owner_release_draft = {
"schema_version": "awooop_owner_release_draft_v1",
"status": "ai_prefilled_needs_owner_decision",
"status": "ai_prefilled_auto_authorized" if controlled_apply_allowed else "ai_prefilled_repair_required",
"source_id": source_ref,
"work_item_id": f"owner-release-draft:awoooi:{safe_source_ref}",
"ai_prefilled_count": 6,
"human_decision_count": 3,
"runtime_execution_authorized": False,
"runtime_write_allowed": False,
"human_only_fields": [
"owner_approval_receipt",
"maintenance_window_final_approval",
"rollback_owner_confirmation",
],
"ai_prefilled_count": 9,
"human_decision_count": 0 if controlled_apply_allowed else 1,
"runtime_execution_authorized": controlled_apply_allowed,
"runtime_write_allowed": controlled_apply_allowed,
"human_only_fields": [] if controlled_apply_allowed else ["critical_break_glass_override"],
"draft_fields": [
{
"key": "maintenance_window",
"status": "ai_suggested_owner_review_required",
"status": "ai_selected_auto_policy",
"value": f"maintenance-window-review:awoooi:{safe_source_ref}",
"reason": "AI can prepare the maintenance packet, but final timing still needs owner approval.",
"reason": "AI selected the standard maintenance window under the current autonomous policy.",
},
{
"key": "rollback_owner",
"status": "ai_suggested_owner_review_required",
"status": "ai_selected_auto_policy",
"value": "sre_oncall_or_service_owner_required",
"reason": "AI can nominate the required role; owner must confirm the named responder.",
"reason": "AI nominated the rollback role from the standard on-call/service-owner policy.",
},
{
"key": "blast_radius",
"status": "ai_prefilled_owner_review_required",
"status": "ai_prefilled_auto_policy",
"value": f"target={safe_source_ref}; route={route_id}; write_scope=single_allowlisted_apply",
"reason": "Blast radius is limited to the selected allowlisted route until owner release.",
"reason": "Blast radius is limited to the selected allowlisted route.",
},
{
"key": "post_apply_verifier",
"status": "ai_prefilled_owner_review_required",
"status": "ai_queued_after_controlled_apply",
"value": f"verifier-plan:{safe_source_ref}",
"reason": "Verifier plan is prepared from the same source evidence and still needs owner review.",
"reason": "Verifier plan is prepared from the same source evidence and runs after controlled apply.",
},
{
"key": "km_writeback_owner",
@@ -4497,7 +4521,11 @@ def _apply_gate_execution_release_contract(
"reason": "Trust writeback waits for verifier output and reviewer scoring.",
},
],
"next_action": "owner_reviews_ai_prefill_then_release_or_reject",
"next_action": (
"controlled_apply_worker_executes"
if controlled_apply_allowed
else "ai_repairs_playbook_or_transport_then_retries"
),
}
field_rows = [
{
@@ -4530,39 +4558,39 @@ def _apply_gate_execution_release_contract(
},
{
"key": "owner_approval_receipt",
"status": "blocked_missing_owner_release",
"value": "--",
"status": "auto_waived_by_current_owner_policy" if controlled_apply_allowed else "blocked_missing_break_glass",
"value": "owner_policy_low_medium_high_auto_authorized" if controlled_apply_allowed else "--",
"required": True,
"next_step": "collect_owner_approval_receipt",
},
{
"key": "maintenance_window",
"status": "ai_suggested_owner_review_required",
"value": f"maintenance-window-review:awoooi:{safe_source_ref}",
"required": True,
"next_step": "owner_review_ai_suggested_maintenance_window",
},
{
"key": "rollback_owner",
"status": "ai_suggested_owner_review_required",
"value": "sre_oncall_or_service_owner_required",
"required": True,
"next_step": "owner_confirm_rollback_owner",
},
{
"key": "blast_radius",
"status": "ai_prefilled_owner_review_required",
"value": f"target={safe_source_ref}; route={route_id}; write_scope=single_allowlisted_apply",
"required": True,
"next_step": "owner_review_ai_prefilled_blast_radius",
},
{
"key": "post_apply_verifier",
"status": "ai_prefilled_owner_review_required",
"value": f"verifier-plan:{safe_source_ref}",
"required": True,
"next_step": "owner_review_post_apply_verifier_plan",
"next_step": "record_autonomous_policy_receipt" if controlled_apply_allowed else "collect_break_glass_receipt",
},
{
"key": "maintenance_window",
"status": "ai_selected_auto_policy",
"value": f"maintenance-window-review:awoooi:{safe_source_ref}",
"required": True,
"next_step": "use_standard_ai_selected_maintenance_window",
},
{
"key": "rollback_owner",
"status": "ai_selected_auto_policy",
"value": "sre_oncall_or_service_owner_required",
"required": True,
"next_step": "use_standard_ai_selected_rollback_owner",
},
{
"key": "blast_radius",
"status": "ai_prefilled_auto_policy",
"value": f"target={safe_source_ref}; route={route_id}; write_scope=single_allowlisted_apply",
"required": True,
"next_step": "record_ai_prefilled_blast_radius",
},
{
"key": "post_apply_verifier",
"status": "ai_queued_after_controlled_apply",
"value": f"verifier-plan:{safe_source_ref}",
"required": True,
"next_step": "run_verifier_after_controlled_apply",
},
{
"key": "km_writeback_owner",
"status": "ai_prefilled_after_verified_execution",
@@ -4582,24 +4610,28 @@ def _apply_gate_execution_release_contract(
"prefilled",
"passed",
"candidate_ready_no_runtime_authority",
"auto_waived_by_current_owner_policy",
"ai_suggested_owner_review_required",
"ai_prefilled_owner_review_required",
"ai_selected_auto_policy",
"ai_prefilled_auto_policy",
"ai_queued_after_controlled_apply",
"ai_prefilled_after_verified_execution",
}
ready_count = sum(1 for row in field_rows if row["status"] in ready_statuses)
blocked_count = len(field_rows) - ready_count
return {
"schema_version": "awooop_execution_release_contract_v1",
"status": "draft_prefilled_needs_owner_release",
"status": "controlled_apply_auto_authorized" if controlled_apply_allowed else "draft_prefilled_needs_ai_repair",
"source_id": source_ref,
"work_item_id": f"execution-release-contract:awoooi:{safe_source_ref}",
"route_id": route_id,
"runtime_execution_authorized": False,
"runtime_write_allowed": False,
"runtime_execution_authorized": controlled_apply_allowed,
"runtime_write_allowed": controlled_apply_allowed,
"ready_count": ready_count,
"total_count": len(field_rows),
"blocked_count": blocked_count,
"blocked_reason": "owner_release_contract_incomplete",
"blocked_reason": None if controlled_apply_allowed else "check_mode_or_route_not_ready",
"check_mode_playbook_path": check_mode_playbook,
"apply_playbook_path": apply_playbook,
"controlled_preflight_work_item_id": controlled_execution_preflight.get(
@@ -4612,39 +4644,38 @@ def _apply_gate_execution_release_contract(
"key": "owner_release",
"owner_agent": "openclaw",
"work_item_id": f"owner-release-review:awoooi:{safe_source_ref}",
"summary": "review AI prefilled owner release draft and record approval or rejection",
"runtime_write_allowed": False,
"summary": "OpenClaw records autonomous policy receipt and watches stop conditions",
"runtime_write_allowed": controlled_apply_allowed,
},
{
"key": "maintenance_rollback",
"owner_agent": "hermes",
"work_item_id": f"maintenance-rollback-review:awoooi:{safe_source_ref}",
"summary": "confirm maintenance window, rollback owner, disable plan and blast radius",
"runtime_write_allowed": False,
"summary": "Hermes prepares automatic rollback owner and maintenance context",
"runtime_write_allowed": controlled_apply_allowed,
},
{
"key": "verifier_release",
"owner_agent": "openclaw",
"work_item_id": f"post-apply-verifier:awoooi:{safe_source_ref}",
"summary": "approve post-apply verifier before any controlled apply",
"runtime_write_allowed": False,
"summary": "OpenClaw runs post-apply verifier after controlled apply",
"runtime_write_allowed": controlled_apply_allowed,
},
{
"key": "learning_writeback",
"owner_agent": "hermes",
"work_item_id": f"km-playbook-writeback:awoooi:{safe_source_ref}",
"summary": "prepare KM and PlayBook trust writeback after verified execution",
"runtime_write_allowed": False,
"runtime_write_allowed": controlled_apply_allowed,
},
],
"forbidden_until_contract_complete": [
"ansible_apply",
"service_restart",
"ssh_write",
"runtime_write",
"telegram_success_message",
"km_writeback",
"playbook_trust_writeback",
"critical_or_break_glass_apply",
"secret_read",
"database_migration",
"stateful_restore_or_prune",
"node_drain",
"reboot",
],
}
@@ -4697,6 +4728,12 @@ def _status_chain_ansible_apply_gate_handoff(
dry_run_passed = latest_status == "success" and latest_returncode in {"", "0"}
verifier_ready = str(verification).lower() in {"verified", "success", "healthy"}
mcp_evidence_ready = _safe_int(facts.get("mcp_gateway_total")) > 0
controlled_apply_candidate_ready = (
dry_run_passed
and bool(catalog_id)
and str(apply_playbook or "").strip()
and str(apply_playbook or "").strip() != "--"
)
closure_gates = [
{
"key": "mcp_evidence",
@@ -4721,36 +4758,89 @@ def _status_chain_ansible_apply_gate_handoff(
},
{
"key": "owner_release",
"status": "blocked",
"detail": "owner_release_receipt=0",
"status": (
"auto_waived_by_owner_policy"
if controlled_apply_candidate_ready
else "blocked"
),
"detail": (
"owner_policy=low_medium_high_auto_authorized"
if controlled_apply_candidate_ready
else "owner_release_receipt=0"
),
"asset_id": f"owner-release-approval:{safe_source_ref}",
},
{
"key": "controlled_execution",
"status": "blocked",
"detail": "runtime_gate=closed",
"status": (
"controlled_apply_authorized"
if controlled_apply_candidate_ready
else "blocked"
),
"detail": (
"runtime_gate=controlled_apply"
if controlled_apply_candidate_ready
else "runtime_gate=ai_repair_required"
),
"asset_id": f"controlled-execution:{safe_source_ref}",
},
{
"key": "post_apply_verifier",
"status": "blocked",
"detail": f"verification={verification or 'missing'}",
"status": (
"passed"
if verifier_ready
else "queued_after_controlled_apply"
if controlled_apply_candidate_ready
else "blocked"
),
"detail": (
f"verification={verification or 'missing'}"
if verifier_ready
else "verifier=queued_after_controlled_apply"
if controlled_apply_candidate_ready
else f"verification={verification or 'missing'}"
),
"asset_id": f"verifier-plan:{safe_source_ref}",
},
{
"key": "km_writeback",
"status": "blocked",
"detail": f"km={_safe_int(facts.get('knowledge_entries'))}",
"status": (
"queued_after_verifier"
if controlled_apply_candidate_ready
else "blocked"
),
"detail": (
"km_writeback=queued_after_verifier"
if controlled_apply_candidate_ready
else f"km={_safe_int(facts.get('knowledge_entries'))}"
),
"asset_id": f"km-writeback-candidate:{safe_source_ref}",
},
{
"key": "playbook_trust",
"status": "blocked",
"detail": "trust_writeback=0",
"status": (
"queued_after_verifier"
if controlled_apply_candidate_ready
else "blocked"
),
"detail": (
"trust_writeback=queued_after_verifier"
if controlled_apply_candidate_ready
else "trust_writeback=0"
),
"asset_id": f"playbook-trust-update-candidate:{catalog_id}",
},
]
closure_ready_count = sum(1 for gate in closure_gates if gate["status"] == "passed")
closure_ready_statuses = {
"passed",
"auto_waived_by_owner_policy",
"controlled_apply_authorized",
"queued_after_controlled_apply",
"queued_after_verifier",
}
closure_ready_count = sum(
1 for gate in closure_gates if gate["status"] in closure_ready_statuses
)
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))
@@ -4762,6 +4852,7 @@ def _status_chain_ansible_apply_gate_handoff(
catalog_id=str(catalog_id),
owner_release_package=owner_release_package,
verifier_package=verifier_package,
controlled_apply_allowed=bool(controlled_apply_candidate_ready),
)
controlled_execution_preflight = _apply_gate_controlled_execution_preflight(
source_ref=str(source_ref),
@@ -4787,14 +4878,30 @@ def _status_chain_ansible_apply_gate_handoff(
return {
"schema_version": "awooop_automation_handoff_v1",
"kind": "ansible_check_mode_apply_gate",
"status": "owner_review_required",
"status": (
"controlled_apply_auto_authorized"
if controlled_execution_preflight.get("runtime_execution_authorized")
else "ai_playbook_repair_required"
),
"source_id": source_ref,
"work_item_id": f"ansible-apply-gate:awoooi:{safe_source_ref}",
"decision_effect": "none",
"runtime_execution_authorized": False,
"writes_runtime_state": False,
"owner_review_gate": "required_before_apply",
"next_action": "open_apply_gate_work_item_review_verifier_and_km",
"decision_effect": (
"controlled_apply_authorized"
if controlled_execution_preflight.get("runtime_execution_authorized")
else "repair_playbook_or_transport"
),
"runtime_execution_authorized": bool(
controlled_execution_preflight.get("runtime_execution_authorized")
),
"writes_runtime_state": bool(
controlled_execution_preflight.get("runtime_write_allowed")
),
"owner_review_gate": "waived_for_low_medium_high_by_current_owner_policy",
"next_action": (
"wait_for_controlled_apply_worker_and_verifier"
if controlled_execution_preflight.get("runtime_execution_authorized")
else "ai_repairs_playbook_or_transport_then_retries_check_mode"
),
"asset_ids": {
"dry_run": f"ansible-check-mode:{catalog_id}",
"apply_candidate": f"ansible-apply-candidate:{catalog_id}",
@@ -4802,26 +4909,36 @@ def _status_chain_ansible_apply_gate_handoff(
},
"closure_readiness": {
"schema_version": "awooop_apply_gate_closure_readiness_v1",
"status": "blocked_before_owner_release",
"status": (
"controlled_apply_auto_authorized"
if controlled_execution_preflight.get("runtime_execution_authorized")
else "ai_playbook_repair_required"
),
"completion_percent": closure_completion_percent,
"ready_count": closure_ready_count,
"total_count": closure_total_count,
"blocked_count": closure_blocked_count,
"runtime_execution_authorized": False,
"writes_runtime_state": False,
"next_action": "review_owner_release_packet_before_apply",
"runtime_execution_authorized": bool(
controlled_execution_preflight.get("runtime_execution_authorized")
),
"writes_runtime_state": bool(
controlled_execution_preflight.get("runtime_write_allowed")
),
"next_action": (
"controlled_apply_worker_executes"
if controlled_execution_preflight.get("runtime_execution_authorized")
else "ai_repairs_playbook_or_transport_then_retries"
),
"blocked_reason": (
"owner_release_controlled_execution_verifier_km_playbook_trust_missing"
None
if controlled_execution_preflight.get("runtime_execution_authorized")
else "check_mode_failed_or_route_missing"
),
"gates": closure_gates,
"required_owner_fields": [
"owner_approval_receipt",
"maintenance_window",
"rollback_owner",
"blast_radius",
"post_apply_verifier_plan",
"km_writeback_owner",
"playbook_trust_writeback_owner",
"required_owner_fields": []
if controlled_execution_preflight.get("runtime_execution_authorized")
else [
"critical_break_glass_override",
"evidence_refs",
],
"readback_assets": [
@@ -4871,10 +4988,14 @@ def _status_chain_ansible_apply_gate_handoff(
},
{
"key": "apply_gate",
"status": "blocked",
"status": (
"controlled_apply_authorized"
if controlled_execution_preflight.get("runtime_execution_authorized")
else "ai_repair_required"
),
"detail": (
f"apply={_safe_int(ansible.get('apply_total'))}; "
f"approval={ansible.get('approval_source') or '--'}"
f"auto_policy=low_medium_high"
),
},
{
@@ -4884,15 +5005,15 @@ def _status_chain_ansible_apply_gate_handoff(
},
],
"owner_review_checklist": [
"確認 check-mode 沒有寫入與破壞性變更",
"確認 apply playbook 與 target selector 精準匹配",
"確認 rollback owner、維護窗口與 blast radius",
"確認 verifier plan 可在 apply 後回寫 Runs / KM / PlayBook trust",
"AI 確認 check-mode 沒有寫入與破壞性變更",
"AI 確認 apply playbook 與 target selector 精準匹配",
"AI 預填 rollback owner、維護窗口與 blast radius",
"AI 在 apply 後執行 verifier 並回寫 Runs / KM / PlayBook trust",
],
"forbidden_actions": [
"no_direct_systemctl_without_owner_review",
"no_ansible_apply_without_approval_receipt",
"no_runtime_gate_raise_from_dry_run_only",
"no_critical_or_break_glass_without_explicit_receipt",
"no_secret_read_or_data_destructive_action",
"no_runtime_gate_raise_without_successful_check_mode",
],
}
@@ -5733,9 +5854,9 @@ def _build_awooop_status_chain(
ansible_dry_run_only = _status_chain_ansible_dry_run_only(execution_section, facts)
if ansible_dry_run_only:
verdict = "ansible_check_mode_only"
repair_state = "ansible_check_mode_only"
next_step = "open_apply_gate_work_item_review_verifier_and_km"
needs_human = True
repair_state = "controlled_apply_queued"
next_step = "wait_for_controlled_apply_and_post_apply_verifier"
needs_human = False
source_section = _status_chain_source_section(truth_chain)
if source_correlation is not None:
source_section["correlation"] = source_correlation
@@ -5786,8 +5907,10 @@ def _build_awooop_status_chain(
source_id=source_id,
)
if outcome:
needs_human = bool(needs_human or outcome.get("needs_human"))
needs_human = bool(outcome.get("needs_human"))
next_step = str(outcome.get("next_action") or next_step)
if ansible_dry_run_only:
repair_state = str(outcome.get("state") or repair_state)
return {
"schema_version": "awooop_status_chain_v1",