From 50d03b9f962192d6d33b9e3420e848f2b000bedc Mon Sep 17 00:00:00 2001 From: Oskar Kapala Date: Tue, 12 May 2026 18:01:37 +0200 Subject: [PATCH] Resolve merge conflicts --- .output.txt | 541 ++++++++++++++++++ docs/action-queue-system.md | 75 +++ docs/operator/approval-workflow.md | 27 + docs/operator/incident-remediation.md | 24 + docs/operator/reconcile-review.md | 18 +- scripts/executor/executor.py | 225 ++++++++ scripts/executor/test_actions.sh | 74 +++ scripts/supervisor/supervisor.py | 71 +++ .../0083f8ad-1f2b-47a4-81a8-81e59740879e.json | 24 + .../050add79-3265-4e35-bb88-41c368bbccda.json | 23 + .../actions/completed/resumable-task.json | 7 + tmp/homelab/actions/history.log | 10 + .../50d7cdab-2f12-449f-965a-0383e32babaa.json | 21 + .../5e239d96-ff3f-48a3-a71a-ad5aa6b7ff88.json | 20 + .../7cde5093-3394-43af-9391-321c50ac5362.json | 20 + .../a42e2183-ca22-4a50-97a7-eb53ab0e039a.json | 20 + .../aae83bcd-455f-4b59-bab0-7c7994116468.json | 21 + .../c2e6c844-6d96-4ea7-b924-5e33764e5493.json | 21 + .../c91a4171-e636-4194-a146-6e003d2f2586.json | 20 + .../e6d3f0d6-c294-4282-b9f4-a730f9cec9dc.json | 20 + .../f4c56df2-6775-484b-806e-cdecdcc19584.json | 20 + .../ff3da03c-fffa-49a7-985d-ed4589ab6856.json | 20 + .../2143ae5b-bcc6-410b-b925-e7def70fc013.json | 21 + .../240cbbc0-891e-4032-bf73-1fa40ff850b4.json | 21 + tmp/homelab/world/deployments/dep-001.json | 2 +- tmp/homelab/world/deployments/dep-002.json | 2 +- tmp/homelab/world/deployments/dep-003.json | 2 +- webui/index.html | 192 ++++++- webui/web.py | 68 +++ 29 files changed, 1615 insertions(+), 15 deletions(-) create mode 100644 .output.txt create mode 100644 docs/action-queue-system.md create mode 100644 docs/operator/approval-workflow.md create mode 100644 docs/operator/incident-remediation.md create mode 100644 scripts/executor/executor.py create mode 100644 scripts/executor/test_actions.sh create mode 100644 tmp/homelab/actions/completed/0083f8ad-1f2b-47a4-81a8-81e59740879e.json create mode 100644 tmp/homelab/actions/completed/050add79-3265-4e35-bb88-41c368bbccda.json create mode 100644 tmp/homelab/actions/completed/resumable-task.json create mode 100644 tmp/homelab/actions/history.log create mode 100644 tmp/homelab/actions/pending/50d7cdab-2f12-449f-965a-0383e32babaa.json create mode 100644 tmp/homelab/actions/pending/5e239d96-ff3f-48a3-a71a-ad5aa6b7ff88.json create mode 100644 tmp/homelab/actions/pending/7cde5093-3394-43af-9391-321c50ac5362.json create mode 100644 tmp/homelab/actions/pending/a42e2183-ca22-4a50-97a7-eb53ab0e039a.json create mode 100644 tmp/homelab/actions/pending/aae83bcd-455f-4b59-bab0-7c7994116468.json create mode 100644 tmp/homelab/actions/pending/c2e6c844-6d96-4ea7-b924-5e33764e5493.json create mode 100644 tmp/homelab/actions/pending/c91a4171-e636-4194-a146-6e003d2f2586.json create mode 100644 tmp/homelab/actions/pending/e6d3f0d6-c294-4282-b9f4-a730f9cec9dc.json create mode 100644 tmp/homelab/actions/pending/f4c56df2-6775-484b-806e-cdecdcc19584.json create mode 100644 tmp/homelab/actions/pending/ff3da03c-fffa-49a7-985d-ed4589ab6856.json create mode 100644 tmp/homelab/actions/rejected/2143ae5b-bcc6-410b-b925-e7def70fc013.json create mode 100644 tmp/homelab/actions/rejected/240cbbc0-891e-4032-bf73-1fa40ff850b4.json diff --git a/.output.txt b/.output.txt new file mode 100644 index 0000000..a9aecec --- /dev/null +++ b/.output.txt @@ -0,0 +1,541 @@ +Command finished with exit code 0. +Command output: +diff --git a/docs/operator/reconcile-review.md b/docs/operator/reconcile-review.md +index 8b99c6f..e56c333 100644 +--- a/docs/operator/reconcile-review.md ++++ b/docs/operator/reconcile-review.md +@@ -2,11 +2,13 @@ + + The system continuously monitors for drift between desired and actual state. + +-1. If a service is in RECONCILING state, check the Services view. +-2. Review the Recommendations view for automated or guarded actions. +-3. For 'safe' actions with high confidence, the system may act autonomously if enabled. +-4. For 'guarded' or 'dangerous' actions, an operator must manually approve the action. +-5. Risk Levels: +- - **Safe**: Minimal impact, high success rate. +- - **Guarded**: Potential brief service interruption. +- - **Dangerous**: Significant impact, potential data loss, or hardware interaction required. ++1. **Drift Detection**: When drift is detected, the supervisor generates a recommendation and a corresponding pending action. ++2. **Review**: Navigate to the **Recommendations** view for a high-level summary, or the **Action Queue** for the specific execution plan. ++3. **Approval**: For 'guarded' or 'dangerous' actions, click **Approve** in the Action Queue. ++4. **Execution**: Once approved, the action can be triggered manually by clicking **Execute**, or it will be picked up by the autonomous executor if the system is in `AUTONOMOUS` mode. ++5. **Observation**: Monitor the **Deployments** and **Topology** views to watch the reconciliation in real-time. ++ ++Risk Levels: ++- **Safe**: Minimal impact, high success rate. ++- **Guarded**: Potential brief service interruption. ++- **Dangerous**: Significant impact, potential data loss, or node-level disruption. +diff --git a/scripts/supervisor/supervisor.py b/scripts/supervisor/supervisor.py +index e58027b..ce5d162 100644 +--- a/scripts/supervisor/supervisor.py ++++ b/scripts/supervisor/supervisor.py +@@ -5,14 +5,19 @@ import yaml + import json + import time + import glob ++import uuid + from pathlib import Path + + # Configuration + WORLD_STATE_PATH = Path(os.getenv("HOMELAB_WORLD_ROOT", "/opt/homelab/world")) ++ACTIONS_ROOT = Path(os.getenv("HOMELAB_ACTIONS_ROOT", "/opt/homelab/actions")) + INVENTORY_PATH = Path("hosts") + EVENT_LOG = Path("/tmp/agent-events.log") + CHECKPOINT_FILE = Path("/tmp/supervisor-checkpoint.json") + ++# Action Queue Layout ++ACTION_DIRS = ["pending", "approved", "running", "completed", "failed", "rejected"] ++ + # Reconcile event types + RECONCILE_REQUIRED = "reconcile_required" + RECONCILE_RECOMMENDED = "reconcile_recommended" +@@ -24,6 +29,70 @@ STATE_DEGRADED = "degraded" + STATE_UNSTABLE = "unstable" + STATE_RECONCILING = "reconciling" + ++def ensure_action_dirs(): ++ """Ensure action queue directories exist.""" ++ for d in ACTION_DIRS: ++ (ACTIONS_ROOT / d).mkdir(parents=True, exist_ok=True) ++ ++def emit_action_proposal(recommendation): ++ """Convert recommendation to action proposal and save to pending/.""" ++ ensure_action_dirs() ++ ++ action_type_map = { ++ "redeploy": "redeploy_service", ++ "deploy": "redeploy_service", ++ "diagnostics": "collect_diagnostics", ++ "failover_review": "collect_diagnostics", ++ "review": "collect_diagnostics", ++ "delayed_deployment": "rerun_deployment_stage" ++ } ++ ++ action_type = action_type_map.get(recommendation["action"], "collect_diagnostics") ++ ++ risk_level_map = { ++ "redeploy_service": "guarded", ++ "rerun_healthcheck": "safe", ++ "rerun_deployment_stage": "guarded", ++ "collect_diagnostics": "safe" ++ } ++ risk_level = risk_level_map.get(action_type, "dangerous") ++ ++ # Dangerous always requires approval ++ # Guarded defaults to approval ++ approval_required = risk_level in ["dangerous", "guarded"] ++ ++ action_id = str(uuid.uuid4()) ++ action = { ++ "action_id": action_id, ++ "created_at": time.time(), ++ "proposed_by": "supervisor", ++ "correlation_id": str(uuid.uuid4()), # In a real system, link to drift ID ++ "node": recommendation["drift"].get("node"), ++ "service": recommendation["drift"].get("service"), ++ "action_type": action_type, ++ "risk_level": risk_level, ++ "confidence": 0.9, # Default confidence ++ "approval_required": approval_required, ++ "autonomous_eligible": False, # No autonomy yet ++ "status": "pending", ++ "payload": recommendation["drift"], ++ "rollback_reference": None ++ } ++ ++ file_path = ACTIONS_ROOT / "pending" / f"{action_id}.json" ++ try: ++ with open(file_path, "w") as f: ++ json.dump(action, f, indent=2) ++ ++ emit_event("action_created", f"Action proposed: {action_type} for {action.get('service') or action.get('node')}", { ++ "action_id": action_id, ++ "action_type": action_type, ++ "node": action.get("node"), ++ "service": action.get("service") ++ }) ++ except Exception as e: ++ print(f"Error emitting action proposal: {e}", file=sys.stderr) ++ + def emit_event(event_type, message, details=None): + """Emit reconciliation events using existing event system (append-only file).""" + event = { +@@ -278,6 +347,8 @@ def main(): + # Emit reconciliation events + for rec in recommendations: + emit_event(rec["type"], rec["message"], rec["drift"]) ++ # Proposed: Emit action proposals to action queue ++ emit_action_proposal(rec) + + # 6. Save checkpoint + save_checkpoint({ +diff --git a/tmp/homelab/world/deployments/dep-001.json b/tmp/homelab/world/deployments/dep-001.json +index 02db067..f70d7a8 100644 +--- a/tmp/homelab/world/deployments/dep-001.json ++++ b/tmp/homelab/world/deployments/dep-001.json +@@ -1 +1 @@ +-{"id": "dep-001", "service": "webapp", "status": "failed", "timestamp": 1778597957} ++{"id": "dep-001", "service": "webapp", "status": "failed", "timestamp": 1778600510} +diff --git a/tmp/homelab/world/deployments/dep-002.json b/tmp/homelab/world/deployments/dep-002.json +index e977aa0..1ee5a29 100644 +--- a/tmp/homelab/world/deployments/dep-002.json ++++ b/tmp/homelab/world/deployments/dep-002.json +@@ -1 +1 @@ +-{"id": "dep-002", "service": "webapp", "status": "failed", "timestamp": 1778597657} ++{"id": "dep-002", "service": "webapp", "status": "failed", "timestamp": 1778600210} +diff --git a/tmp/homelab/world/deployments/dep-003.json b/tmp/homelab/world/deployments/dep-003.json +index 66f10c9..f44385b 100644 +--- a/tmp/homelab/world/deployments/dep-003.json ++++ b/tmp/homelab/world/deployments/dep-003.json +@@ -1 +1 @@ +-{"id": "dep-003", "service": "webapp", "status": "failed", "timestamp": 1778597357} ++{"id": "dep-003", "service": "webapp", "status": "failed", "timestamp": 1778599910} +diff --git a/webui/index.html b/webui/index.html +index d720307..5c049c1 100644 +--- a/webui/index.html ++++ b/webui/index.html +@@ -216,9 +216,9 @@ + .label { color: var(--text-muted); font-size: 12px; margin-bottom: 4px; } + .value { font-weight: 500; margin-bottom: 12px; } + +- .risk-safe { color: var(--safe); } +- .risk-guarded { color: var(--guarded); } +- .risk-dangerous { color: var(--dangerous); } ++ .risk-safe { background: rgba(62, 175, 124, 0.1); color: var(--safe); } ++ .risk-guarded { background: rgba(230, 126, 34, 0.1); color: var(--guarded); } ++ .risk-dangerous { background: rgba(192, 57, 43, 0.1); color: var(--dangerous); } + + + +@@ -229,6 +229,9 @@ + ++ + +@@ -238,9 +241,15 @@ + ++ + ++ + +@@ -255,7 +264,16 @@ + +
+
+-
Dashboard
++
++
Dashboard
++ ++
+
+ +
+@@ -269,6 +287,10 @@ +
System Overview
+
+ ++
++
Pending Actions
++
++
+
+
Active Incidents
+
+@@ -276,6 +298,20 @@ +
+ + ++ ++ ++ + + + ++ ++ ++ + + + ++ ++ ++ + + +
+
Pending Actions
+
+
Active Incidents
@@ -276,6 +298,20 @@
+ + + + + + + + +