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
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:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user