Initial commit: Agent Governance System Phase 8

Phase 8 Production Hardening with complete governance infrastructure:

- Vault integration with tiered policies (T0-T4)
- DragonflyDB state management
- SQLite audit ledger
- Pipeline DSL and templates
- Promotion/revocation engine
- Checkpoint system for session persistence
- Health manager and circuit breaker for fault tolerance
- GitHub/Slack integrations
- Architectural test pipeline with bug watcher, suggestion engine, council review
- Multi-agent chaos testing framework

Test Results:
- Governance tests: 68/68 passing
- E2E workflow: 16/16 passing
- Phase 2 Vault: 14/14 passing
- Integration tests: 27/27 passing

Coverage: 57.6% average across 12 phases

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
profit
2026-01-23 22:07:06 -05:00
co-authored by Claude Opus 4.5
commit 77655c298c
338 changed files with 63491 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
# Preflight
> Pre-execution validation and checks
## Overview
This directory contains pre-execution validation and checks.
## Key Files
| File | Description |
|------|-------------|
| `preflight.py` | Python module |
| `dependency_check.py` | Python module |
| `inventory_check.py` | Python module |
| `sandbox_assert.py` | Python module |
| `__init__.py` | Python module |
## Interfaces / APIs
*Document any APIs, CLI commands, or interfaces here.*
## Status
** Not Started**
See [STATUS.md](./STATUS.md) for detailed progress tracking.
## Architecture Reference
Part of the [Agent Governance System](/opt/agent-governance/docs/ARCHITECTURE.md).
Parent: [Project Root](/opt/agent-governance)
---
*Last updated: 2026-01-23 23:25:09 UTC*
+35
View File
@@ -0,0 +1,35 @@
# Status: Preflight
## Current Phase
**COMPLETE**
## Tasks
| Status | Task | Updated |
|--------|------|---------|
| ☐ | *No tasks defined* | - |
## Dependencies
*No external dependencies.*
## Issues / Blockers
*No current issues or blockers.*
## Activity Log
### 2026-01-23 23:25:38 UTC
- **Phase**: COMPLETE
- **Action**: Pre-execution validation checks active
- **Details**: Phase updated to complete
### 2026-01-23 23:25:09 UTC
- **Phase**: NOT STARTED
- **Action**: Initialized
- **Details**: Status tracking initialized for this directory.
---
*Last updated: 2026-01-23 23:25:38 UTC*
+27
View File
@@ -0,0 +1,27 @@
"""
Preflight Check System
======================
Phase 3: Execution Pipeline - Preflight Validation
Components:
- inventory_check: Validates targets exist in allowed pools
- dependency_check: Verifies secrets and services available
- sandbox_assert: Ensures targets are within sandbox boundaries
- preflight: Unified gate combining all checks
"""
from .inventory_check import InventoryChecker, CheckResult, CheckStatus
from .dependency_check import DependencyChecker
from .sandbox_assert import SandboxAsserter, AssertionResult, AssertionStatus
from .preflight import PreflightGate
__all__ = [
"InventoryChecker",
"DependencyChecker",
"SandboxAsserter",
"PreflightGate",
"CheckResult",
"CheckStatus",
"AssertionResult",
"AssertionStatus",
]
+405
View File
@@ -0,0 +1,405 @@
#!/usr/bin/env python3
"""
Dependency Checker
==================
Verifies that all required secrets and resources are available before execution.
Part of Phase 3: Execution Pipeline - Preflight System.
"""
import json
import subprocess
import sys
from dataclasses import dataclass
from datetime import datetime, timezone
from enum import Enum
from typing import Optional
class CheckStatus(str, Enum):
PASS = "PASS"
FAIL = "FAIL"
WARN = "WARN"
SKIP = "SKIP"
@dataclass
class CheckResult:
check_name: str
status: CheckStatus
message: str
details: dict
timestamp: str
def to_dict(self) -> dict:
return {
"check_name": self.check_name,
"status": self.status.value,
"message": self.message,
"details": self.details,
"timestamp": self.timestamp
}
class DependencyChecker:
"""
Validates that all required dependencies are available.
Checks:
- Required Vault secrets exist
- Required services are reachable
- Required tools are installed
- Required credentials are valid
"""
# Standard dependencies by action type
DEPENDENCIES = {
"terraform": {
"secrets": ["api-keys/proxmox", "ssh/sandbox"],
"services": ["vault", "proxmox-api"],
"tools": ["terraform"]
},
"ansible": {
"secrets": ["ssh/sandbox", "inventory/proxmox"],
"services": ["vault"],
"tools": ["ansible-playbook"]
},
"docker": {
"secrets": ["registry/credentials"],
"services": ["vault", "docker-registry"],
"tools": ["docker"]
},
"generic": {
"secrets": [],
"services": ["vault", "dragonfly"],
"tools": []
}
}
# Service health endpoints
SERVICE_ENDPOINTS = {
"vault": ("https://127.0.0.1:8200/v1/sys/health", 200),
"dragonfly": ("redis://127.0.0.1:6379", None),
"proxmox-api": ("https://10.77.0.2:8006/api2/json/version", 200),
"docker-registry": ("https://registry.local/v2/", 200),
}
def __init__(self):
self.vault_token = self._get_vault_token()
def _get_vault_token(self) -> str:
with open("/opt/vault/init-keys.json") as f:
return json.load(f)["root_token"]
def _now(self) -> str:
return datetime.now(timezone.utc).isoformat()
def _vault_read(self, path: str) -> Optional[dict]:
"""Read from Vault KV"""
result = subprocess.run([
"curl", "-sk",
"-H", f"X-Vault-Token: {self.vault_token}",
f"https://127.0.0.1:8200/v1/secret/data/{path}"
], capture_output=True, text=True)
try:
data = json.loads(result.stdout)
if "data" in data and "data" in data["data"]:
return data["data"]["data"]
except:
pass
return None
def check_vault_secret(self, secret_path: str) -> CheckResult:
"""Check if a Vault secret exists and is readable"""
data = self._vault_read(secret_path)
if data:
# Don't expose actual secret values
keys = list(data.keys())
return CheckResult(
check_name=f"secret:{secret_path}",
status=CheckStatus.PASS,
message=f"Secret '{secret_path}' exists with keys: {keys}",
details={"path": secret_path, "keys": keys},
timestamp=self._now()
)
return CheckResult(
check_name=f"secret:{secret_path}",
status=CheckStatus.FAIL,
message=f"Secret '{secret_path}' not found or not readable",
details={"path": secret_path},
timestamp=self._now()
)
def check_service_health(self, service_name: str) -> CheckResult:
"""Check if a service is reachable"""
if service_name not in self.SERVICE_ENDPOINTS:
return CheckResult(
check_name=f"service:{service_name}",
status=CheckStatus.SKIP,
message=f"No health endpoint defined for '{service_name}'",
details={"service": service_name},
timestamp=self._now()
)
endpoint, expected_code = self.SERVICE_ENDPOINTS[service_name]
# Special handling for Redis/Dragonfly
if endpoint.startswith("redis://"):
try:
import redis
host = endpoint.replace("redis://", "").split(":")[0]
port = int(endpoint.split(":")[-1])
# Get password from Vault for dragonfly
password = None
if service_name == "dragonfly":
creds = self._vault_read("services/dragonfly")
if creds:
password = creds.get("password")
r = redis.Redis(host=host, port=port, password=password, socket_timeout=5)
r.ping()
return CheckResult(
check_name=f"service:{service_name}",
status=CheckStatus.PASS,
message=f"Service '{service_name}' is healthy (PING OK)",
details={"service": service_name, "endpoint": endpoint},
timestamp=self._now()
)
except Exception as e:
return CheckResult(
check_name=f"service:{service_name}",
status=CheckStatus.FAIL,
message=f"Service '{service_name}' is not reachable: {str(e)}",
details={"service": service_name, "endpoint": endpoint, "error": str(e)},
timestamp=self._now()
)
# HTTP health check
try:
result = subprocess.run([
"curl", "-sk", "-o", "/dev/null", "-w", "%{http_code}",
"--connect-timeout", "5",
endpoint
], capture_output=True, text=True, timeout=10)
status_code = int(result.stdout.strip())
if expected_code is None or status_code == expected_code:
return CheckResult(
check_name=f"service:{service_name}",
status=CheckStatus.PASS,
message=f"Service '{service_name}' is healthy (HTTP {status_code})",
details={"service": service_name, "endpoint": endpoint, "status_code": status_code},
timestamp=self._now()
)
else:
return CheckResult(
check_name=f"service:{service_name}",
status=CheckStatus.WARN,
message=f"Service '{service_name}' returned unexpected status {status_code}",
details={"service": service_name, "endpoint": endpoint, "status_code": status_code, "expected": expected_code},
timestamp=self._now()
)
except Exception as e:
return CheckResult(
check_name=f"service:{service_name}",
status=CheckStatus.FAIL,
message=f"Service '{service_name}' health check failed: {str(e)}",
details={"service": service_name, "endpoint": endpoint, "error": str(e)},
timestamp=self._now()
)
def check_tool_installed(self, tool_name: str) -> CheckResult:
"""Check if a tool is installed and available"""
result = subprocess.run(
["which", tool_name],
capture_output=True,
text=True
)
if result.returncode == 0:
path = result.stdout.strip()
return CheckResult(
check_name=f"tool:{tool_name}",
status=CheckStatus.PASS,
message=f"Tool '{tool_name}' found at {path}",
details={"tool": tool_name, "path": path},
timestamp=self._now()
)
return CheckResult(
check_name=f"tool:{tool_name}",
status=CheckStatus.FAIL,
message=f"Tool '{tool_name}' not found in PATH",
details={"tool": tool_name},
timestamp=self._now()
)
def check_vault_token_valid(self) -> CheckResult:
"""Check if the Vault token is valid"""
result = subprocess.run([
"curl", "-sk",
"-H", f"X-Vault-Token: {self.vault_token}",
"https://127.0.0.1:8200/v1/auth/token/lookup-self"
], capture_output=True, text=True)
try:
data = json.loads(result.stdout)
if "data" in data:
token_data = data["data"]
policies = token_data.get("policies", [])
ttl = token_data.get("ttl", 0)
if ttl > 0 or "root" in policies:
return CheckResult(
check_name="vault_token",
status=CheckStatus.PASS,
message=f"Vault token valid (TTL: {ttl}s, policies: {policies})",
details={"ttl": ttl, "policies": policies},
timestamp=self._now()
)
return CheckResult(
check_name="vault_token",
status=CheckStatus.WARN,
message=f"Vault token has no TTL (may be root token)",
details={"ttl": ttl, "policies": policies},
timestamp=self._now()
)
except:
pass
return CheckResult(
check_name="vault_token",
status=CheckStatus.FAIL,
message="Vault token invalid or expired",
details={},
timestamp=self._now()
)
def run_action_checks(self, action_type: str) -> list[CheckResult]:
"""Run all dependency checks for an action type"""
results = []
# Always check Vault token
results.append(self.check_vault_token_valid())
deps = self.DEPENDENCIES.get(action_type, self.DEPENDENCIES["generic"])
# Check secrets
for secret in deps.get("secrets", []):
results.append(self.check_vault_secret(secret))
# Check services
for service in deps.get("services", []):
results.append(self.check_service_health(service))
# Check tools
for tool in deps.get("tools", []):
results.append(self.check_tool_installed(tool))
return results
def preflight_report(self, action_type: str, extra_secrets: list[str] = None) -> dict:
"""Generate a full dependency preflight report"""
report = {
"report_type": "dependency_preflight",
"action_type": action_type,
"timestamp": self._now(),
"checks": [],
"summary": {
"total_checks": 0,
"passed": 0,
"failed": 0,
"warnings": 0,
"skipped": 0
},
"can_proceed": True
}
# Run standard checks
results = self.run_action_checks(action_type)
# Check any extra secrets
for secret in (extra_secrets or []):
results.append(self.check_vault_secret(secret))
for r in results:
report["checks"].append(r.to_dict())
report["summary"]["total_checks"] += 1
if r.status == CheckStatus.PASS:
report["summary"]["passed"] += 1
elif r.status == CheckStatus.FAIL:
report["summary"]["failed"] += 1
report["can_proceed"] = False
elif r.status == CheckStatus.WARN:
report["summary"]["warnings"] += 1
elif r.status == CheckStatus.SKIP:
report["summary"]["skipped"] += 1
return report
# =============================================================================
# CLI
# =============================================================================
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Dependency Preflight Checker")
parser.add_argument("action_type", choices=["terraform", "ansible", "docker", "generic"],
help="Type of action to check dependencies for")
parser.add_argument("--secret", action="append", dest="extra_secrets",
help="Additional secrets to check (can be repeated)")
parser.add_argument("--json", action="store_true", help="Output JSON")
args = parser.parse_args()
checker = DependencyChecker()
report = checker.preflight_report(args.action_type, args.extra_secrets)
if args.json:
print(json.dumps(report, indent=2))
else:
print("\n" + "=" * 60)
print("DEPENDENCY PREFLIGHT REPORT")
print("=" * 60)
print(f"Action Type: {report['action_type']}")
print(f"Timestamp: {report['timestamp']}")
print()
for check in report["checks"]:
status_icon = {
"PASS": "[OK]",
"FAIL": "[FAIL]",
"WARN": "[WARN]",
"SKIP": "[SKIP]"
}.get(check["status"], "[?]")
print(f" {status_icon} {check['check_name']}")
print(f" {check['message']}")
print("\n" + "-" * 60)
print("SUMMARY")
print("-" * 60)
s = report["summary"]
print(f" Total Checks: {s['total_checks']}")
print(f" Passed: {s['passed']}")
print(f" Failed: {s['failed']}")
print(f" Warnings: {s['warnings']}")
print(f" Skipped: {s['skipped']}")
print()
if report["can_proceed"]:
print("[OK] DEPENDENCY CHECK PASSED - All dependencies available")
else:
print("[FAIL] DEPENDENCY CHECK FAILED - Missing dependencies")
print("=" * 60)
sys.exit(0 if report["can_proceed"] else 1)
+330
View File
@@ -0,0 +1,330 @@
#!/usr/bin/env python3
"""
Inventory Validator
===================
Validates that target resources exist and are in allowed pools.
Part of Phase 3: Execution Pipeline - Preflight System.
"""
import json
import subprocess
import sys
from dataclasses import dataclass
from datetime import datetime, timezone
from enum import Enum
from typing import Optional
class CheckStatus(str, Enum):
PASS = "PASS"
FAIL = "FAIL"
WARN = "WARN"
SKIP = "SKIP"
@dataclass
class CheckResult:
check_name: str
status: CheckStatus
message: str
details: dict
timestamp: str
def to_dict(self) -> dict:
return {
"check_name": self.check_name,
"status": self.status.value,
"message": self.message,
"details": self.details,
"timestamp": self.timestamp
}
class InventoryChecker:
"""
Validates inventory targets against allowed pools.
Checks:
- Target exists in inventory
- Target is in an allowed pool for the agent's tier
- Target is not in a forbidden pool
"""
POOL_TIERS = {
"pve-sandbox": 1, # Tier 1+ can access
"pve-staging": 2, # Tier 2+ can access
"pve-prod": 3, # Tier 3+ with approval
}
FORBIDDEN_POOLS = {
0: ["pve-sandbox", "pve-staging", "pve-prod"], # Tier 0: no pools
1: ["pve-staging", "pve-prod"], # Tier 1: only sandbox
2: ["pve-prod"], # Tier 2: sandbox + staging
3: [], # Tier 3: all with approval
4: [], # Tier 4: all
}
def __init__(self):
self.vault_token = self._get_vault_token()
def _get_vault_token(self) -> str:
with open("/opt/vault/init-keys.json") as f:
return json.load(f)["root_token"]
def _now(self) -> str:
return datetime.now(timezone.utc).isoformat()
def _vault_read(self, path: str) -> Optional[dict]:
"""Read from Vault KV"""
result = subprocess.run([
"curl", "-sk",
"-H", f"X-Vault-Token: {self.vault_token}",
f"https://127.0.0.1:8200/v1/secret/data/{path}"
], capture_output=True, text=True)
try:
data = json.loads(result.stdout)
if "data" in data and "data" in data["data"]:
return data["data"]["data"]
except:
pass
return None
def get_inventory(self) -> dict:
"""Get infrastructure inventory from Vault"""
inventory = self._vault_read("inventory/proxmox")
if not inventory:
# Return mock inventory for testing
return {
"cluster": "pve-cluster-01",
"pools": {
"pve-sandbox": {
"nodes": ["sandbox-vm-01", "sandbox-vm-02", "sandbox-vm-03"],
"tier_required": 1
},
"pve-staging": {
"nodes": ["staging-vm-01", "staging-vm-02"],
"tier_required": 2
},
"pve-prod": {
"nodes": ["prod-vm-01", "prod-vm-02", "prod-db-01"],
"tier_required": 3
}
}
}
return inventory
def check_target_exists(self, target: str) -> CheckResult:
"""Check if target exists in any pool"""
inventory = self.get_inventory()
for pool_name, pool_data in inventory.get("pools", {}).items():
nodes = pool_data.get("nodes", [])
if target in nodes:
return CheckResult(
check_name="target_exists",
status=CheckStatus.PASS,
message=f"Target '{target}' found in pool '{pool_name}'",
details={"target": target, "pool": pool_name, "nodes": nodes},
timestamp=self._now()
)
return CheckResult(
check_name="target_exists",
status=CheckStatus.FAIL,
message=f"Target '{target}' not found in any inventory pool",
details={"target": target, "searched_pools": list(inventory.get("pools", {}).keys())},
timestamp=self._now()
)
def check_pool_access(self, target: str, agent_tier: int) -> CheckResult:
"""Check if agent tier has access to target's pool"""
inventory = self.get_inventory()
# Find which pool contains the target
target_pool = None
for pool_name, pool_data in inventory.get("pools", {}).items():
if target in pool_data.get("nodes", []):
target_pool = pool_name
break
if not target_pool:
return CheckResult(
check_name="pool_access",
status=CheckStatus.FAIL,
message=f"Target '{target}' not found in inventory",
details={"target": target},
timestamp=self._now()
)
# Check if pool is forbidden for this tier
forbidden = self.FORBIDDEN_POOLS.get(agent_tier, [])
if target_pool in forbidden:
return CheckResult(
check_name="pool_access",
status=CheckStatus.FAIL,
message=f"Tier {agent_tier} agents cannot access pool '{target_pool}'",
details={
"target": target,
"pool": target_pool,
"agent_tier": agent_tier,
"forbidden_pools": forbidden
},
timestamp=self._now()
)
return CheckResult(
check_name="pool_access",
status=CheckStatus.PASS,
message=f"Tier {agent_tier} has access to pool '{target_pool}'",
details={
"target": target,
"pool": target_pool,
"agent_tier": agent_tier
},
timestamp=self._now()
)
def check_not_production(self, target: str) -> CheckResult:
"""Warn if target is in production pool"""
inventory = self.get_inventory()
for pool_name, pool_data in inventory.get("pools", {}).items():
if target in pool_data.get("nodes", []):
if "prod" in pool_name.lower():
return CheckResult(
check_name="not_production",
status=CheckStatus.WARN,
message=f"Target '{target}' is in PRODUCTION pool '{pool_name}'",
details={
"target": target,
"pool": pool_name,
"warning": "Production access requires explicit approval"
},
timestamp=self._now()
)
return CheckResult(
check_name="not_production",
status=CheckStatus.PASS,
message=f"Target '{target}' is not in production",
details={"target": target, "pool": pool_name},
timestamp=self._now()
)
return CheckResult(
check_name="not_production",
status=CheckStatus.SKIP,
message=f"Target '{target}' not found in inventory",
details={"target": target},
timestamp=self._now()
)
def run_all_checks(self, target: str, agent_tier: int) -> list[CheckResult]:
"""Run all inventory checks"""
results = []
# Check 1: Target exists
results.append(self.check_target_exists(target))
# Check 2: Pool access
results.append(self.check_pool_access(target, agent_tier))
# Check 3: Production warning
results.append(self.check_not_production(target))
return results
def preflight_report(self, targets: list[str], agent_tier: int) -> dict:
"""Generate a full preflight report for multiple targets"""
report = {
"report_type": "inventory_preflight",
"agent_tier": agent_tier,
"timestamp": self._now(),
"targets": {},
"summary": {
"total_checks": 0,
"passed": 0,
"failed": 0,
"warnings": 0,
"skipped": 0
},
"can_proceed": True
}
for target in targets:
results = self.run_all_checks(target, agent_tier)
report["targets"][target] = [r.to_dict() for r in results]
for r in results:
report["summary"]["total_checks"] += 1
if r.status == CheckStatus.PASS:
report["summary"]["passed"] += 1
elif r.status == CheckStatus.FAIL:
report["summary"]["failed"] += 1
report["can_proceed"] = False
elif r.status == CheckStatus.WARN:
report["summary"]["warnings"] += 1
elif r.status == CheckStatus.SKIP:
report["summary"]["skipped"] += 1
return report
# =============================================================================
# CLI
# =============================================================================
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Inventory Preflight Checker")
parser.add_argument("targets", nargs="+", help="Target nodes to check")
parser.add_argument("--tier", type=int, default=1, help="Agent tier (0-4)")
parser.add_argument("--json", action="store_true", help="Output JSON")
args = parser.parse_args()
checker = InventoryChecker()
report = checker.preflight_report(args.targets, args.tier)
if args.json:
print(json.dumps(report, indent=2))
else:
print("\n" + "=" * 60)
print("INVENTORY PREFLIGHT REPORT")
print("=" * 60)
print(f"Agent Tier: {report['agent_tier']}")
print(f"Timestamp: {report['timestamp']}")
print()
for target, checks in report["targets"].items():
print(f"\nTarget: {target}")
print("-" * 40)
for check in checks:
status_icon = {
"PASS": "[OK]",
"FAIL": "[FAIL]",
"WARN": "[WARN]",
"SKIP": "[SKIP]"
}.get(check["status"], "[?]")
print(f" {status_icon} {check['check_name']}: {check['message']}")
print("\n" + "-" * 60)
print("SUMMARY")
print("-" * 60)
s = report["summary"]
print(f" Total Checks: {s['total_checks']}")
print(f" Passed: {s['passed']}")
print(f" Failed: {s['failed']}")
print(f" Warnings: {s['warnings']}")
print(f" Skipped: {s['skipped']}")
print()
if report["can_proceed"]:
print("[OK] PREFLIGHT PASSED - Can proceed with execution")
else:
print("[FAIL] PREFLIGHT FAILED - Cannot proceed")
print("=" * 60)
sys.exit(0 if report["can_proceed"] else 1)
+255
View File
@@ -0,0 +1,255 @@
#!/usr/bin/env python3
"""
Unified Preflight System
========================
Combines all preflight checks into a single gate.
Part of Phase 3: Execution Pipeline - Preflight System.
This is the main entry point for preflight validation.
All execution must pass through this gate.
"""
import json
import sys
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Optional
from inventory_check import InventoryChecker
from dependency_check import DependencyChecker
from sandbox_assert import SandboxAsserter, AssertionStatus
@dataclass
class PreflightGate:
"""
Unified preflight gate that blocks execution on any failure.
Gate Order:
1. Sandbox Assertion (CRITICAL - must pass first)
2. Inventory Validation
3. Dependency Check
If any check fails, execution is BLOCKED.
"""
def __init__(self):
self.inventory_checker = InventoryChecker()
self.dependency_checker = DependencyChecker()
self.sandbox_asserter = SandboxAsserter()
def _now(self) -> str:
return datetime.now(timezone.utc).isoformat()
def run_preflight(
self,
targets: list[str],
action_type: str,
agent_tier: int,
agent_id: str,
extra_secrets: list[str] = None,
allow_staging: bool = False,
production_override: bool = False
) -> dict:
"""
Run full preflight validation.
Args:
targets: List of target nodes
action_type: Type of action (terraform, ansible, docker, generic)
agent_tier: Agent's trust tier (0-4)
agent_id: Agent identifier for audit
extra_secrets: Additional secrets to validate
allow_staging: Allow staging targets
production_override: DANGEROUS: Allow production
Returns:
Comprehensive preflight report with gate decision
"""
start_time = datetime.now(timezone.utc)
report = {
"report_type": "unified_preflight",
"agent_id": agent_id,
"agent_tier": agent_tier,
"action_type": action_type,
"targets": targets,
"timestamp": self._now(),
"gates": {},
"gate_order": ["sandbox", "inventory", "dependencies"],
"failed_gates": [],
"can_proceed": True,
"decision": "PENDING",
"blocking_reason": None
}
# =====================================================================
# GATE 1: Sandbox Assertion (CRITICAL)
# =====================================================================
print("\n[GATE 1/3] Sandbox Assertion...")
sandbox_report = self.sandbox_asserter.assert_all_targets(
targets,
allow_staging=allow_staging,
production_override=production_override
)
report["gates"]["sandbox"] = sandbox_report
if not sandbox_report["can_proceed"]:
report["can_proceed"] = False
report["failed_gates"].append("sandbox")
report["decision"] = "BLOCKED"
report["blocking_reason"] = f"Sandbox assertion failed: {sandbox_report['blocked_targets']}"
print(f" [BLOCKED] Sandbox assertion failed")
# Short-circuit: Don't run other checks if sandbox fails
report["duration_ms"] = int((datetime.now(timezone.utc) - start_time).total_seconds() * 1000)
return report
else:
print(f" [PASS] All targets within sandbox boundaries")
# =====================================================================
# GATE 2: Inventory Validation
# =====================================================================
print("\n[GATE 2/3] Inventory Validation...")
inventory_report = self.inventory_checker.preflight_report(targets, agent_tier)
report["gates"]["inventory"] = inventory_report
if not inventory_report["can_proceed"]:
report["can_proceed"] = False
report["failed_gates"].append("inventory")
if report["decision"] == "PENDING":
report["decision"] = "BLOCKED"
report["blocking_reason"] = f"Inventory validation failed"
print(f" [BLOCKED] Inventory validation failed")
else:
print(f" [PASS] All targets validated in inventory")
# =====================================================================
# GATE 3: Dependency Check
# =====================================================================
print("\n[GATE 3/3] Dependency Check...")
dependency_report = self.dependency_checker.preflight_report(action_type, extra_secrets)
report["gates"]["dependencies"] = dependency_report
if not dependency_report["can_proceed"]:
report["can_proceed"] = False
report["failed_gates"].append("dependencies")
if report["decision"] == "PENDING":
report["decision"] = "BLOCKED"
report["blocking_reason"] = f"Dependency check failed"
print(f" [BLOCKED] Dependency check failed")
else:
print(f" [PASS] All dependencies available")
# =====================================================================
# Final Decision
# =====================================================================
if report["can_proceed"]:
report["decision"] = "APPROVED"
report["blocking_reason"] = None
report["duration_ms"] = int((datetime.now(timezone.utc) - start_time).total_seconds() * 1000)
return report
def print_report(self, report: dict):
"""Print a human-readable report"""
print("\n" + "=" * 70)
print("UNIFIED PREFLIGHT REPORT")
print("=" * 70)
print(f"Agent ID: {report['agent_id']}")
print(f"Agent Tier: {report['agent_tier']}")
print(f"Action Type: {report['action_type']}")
print(f"Targets: {report['targets']}")
print(f"Timestamp: {report['timestamp']}")
print(f"Duration: {report['duration_ms']}ms")
print()
print("GATE RESULTS:")
print("-" * 70)
for gate_name in report["gate_order"]:
gate = report["gates"].get(gate_name, {})
passed = gate.get("can_proceed", False)
status = "[PASS]" if passed else "[FAIL]"
print(f" {status} {gate_name.upper()}")
if gate_name == "sandbox":
summary = gate.get("summary", {})
print(f" Safe: {summary.get('safe', 0)}, Unsafe: {summary.get('unsafe', 0)}, Risk: {summary.get('max_risk_level', 0)}/4")
elif gate_name == "inventory":
summary = gate.get("summary", {})
print(f" Checks: {summary.get('total_checks', 0)}, Passed: {summary.get('passed', 0)}, Failed: {summary.get('failed', 0)}")
elif gate_name == "dependencies":
summary = gate.get("summary", {})
print(f" Checks: {summary.get('total_checks', 0)}, Passed: {summary.get('passed', 0)}, Failed: {summary.get('failed', 0)}")
print()
print("-" * 70)
print("DECISION")
print("-" * 70)
if report["can_proceed"]:
print(f" [APPROVED] Preflight checks passed - execution authorized")
else:
print(f" [BLOCKED] Preflight checks failed - execution denied")
print(f" Failed Gates: {report['failed_gates']}")
print(f" Reason: {report['blocking_reason']}")
print("=" * 70)
# =============================================================================
# CLI
# =============================================================================
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Unified Preflight Gate")
parser.add_argument("targets", nargs="+", help="Target nodes")
parser.add_argument("--action", "-a", default="generic",
choices=["terraform", "ansible", "docker", "generic"],
help="Action type")
parser.add_argument("--tier", "-t", type=int, default=1, help="Agent tier (0-4)")
parser.add_argument("--agent-id", default="cli-user", help="Agent identifier")
parser.add_argument("--secret", action="append", dest="extra_secrets",
help="Additional secrets to check")
parser.add_argument("--allow-staging", action="store_true",
help="Allow staging targets")
parser.add_argument("--production-override", action="store_true",
help="DANGEROUS: Allow production targets")
parser.add_argument("--json", action="store_true", help="Output JSON only")
parser.add_argument("--quiet", "-q", action="store_true", help="Quiet mode")
args = parser.parse_args()
gate = PreflightGate()
if not args.quiet:
print(f"\n{'='*70}")
print("PREFLIGHT CHECK INITIATED")
print(f"{'='*70}")
print(f"Agent: {args.agent_id} (Tier {args.tier})")
print(f"Action: {args.action}")
print(f"Targets: {args.targets}")
report = gate.run_preflight(
targets=args.targets,
action_type=args.action,
agent_tier=args.tier,
agent_id=args.agent_id,
extra_secrets=args.extra_secrets,
allow_staging=args.allow_staging,
production_override=args.production_override
)
if args.json:
print(json.dumps(report, indent=2))
elif not args.quiet:
gate.print_report(report)
sys.exit(0 if report["can_proceed"] else 1)
+416
View File
@@ -0,0 +1,416 @@
#!/usr/bin/env python3
"""
Sandbox Assertion
=================
Verifies that execution targets are within sandbox boundaries.
CRITICAL: This is a safety gate that prevents accidental production access.
Part of Phase 3: Execution Pipeline - Preflight System.
"""
import json
import subprocess
import sys
from dataclasses import dataclass
from datetime import datetime, timezone
from enum import Enum
from typing import Optional
class AssertionStatus(str, Enum):
SAFE = "SAFE" # Definitely sandbox
UNSAFE = "UNSAFE" # Definitely production
UNKNOWN = "UNKNOWN" # Cannot determine
OVERRIDE = "OVERRIDE" # Production with explicit approval
@dataclass
class AssertionResult:
target: str
status: AssertionStatus
environment: str
message: str
risk_level: int # 0=none, 1=low, 2=medium, 3=high, 4=critical
requires_approval: bool
details: dict
timestamp: str
def to_dict(self) -> dict:
return {
"target": self.target,
"status": self.status.value,
"environment": self.environment,
"message": self.message,
"risk_level": self.risk_level,
"requires_approval": self.requires_approval,
"details": self.details,
"timestamp": self.timestamp
}
class SandboxAsserter:
"""
Ensures execution stays within sandbox boundaries.
This is a CRITICAL safety component that:
- Identifies environment type from target name/IP/metadata
- Blocks production access by default
- Requires explicit approval for non-sandbox environments
- Logs all assertions for audit
"""
# Environment detection patterns
ENVIRONMENT_PATTERNS = {
"production": [
"prod", "prd", "production", "live", "primary",
"master", "main-db", "primary-db"
],
"staging": [
"staging", "stg", "stage", "preprod", "pre-prod",
"uat", "qa", "test-prod"
],
"sandbox": [
"sandbox", "sbx", "dev", "development", "local",
"test", "lab", "experiment", "demo"
]
}
# IP range classifications
IP_CLASSIFICATIONS = {
"10.77.0.0/24": "wireguard-internal",
"10.0.0.0/8": "internal-sandbox",
"172.16.0.0/12": "internal-sandbox",
"192.168.0.0/16": "internal-sandbox",
"0.0.0.0/0": "external-unknown"
}
# Risk levels by environment
RISK_LEVELS = {
"sandbox": 0,
"development": 0,
"staging": 2,
"production": 4,
"unknown": 3
}
def __init__(self):
self.vault_token = self._get_vault_token()
def _get_vault_token(self) -> str:
with open("/opt/vault/init-keys.json") as f:
return json.load(f)["root_token"]
def _now(self) -> str:
return datetime.now(timezone.utc).isoformat()
def _vault_read(self, path: str) -> Optional[dict]:
"""Read from Vault KV"""
result = subprocess.run([
"curl", "-sk",
"-H", f"X-Vault-Token: {self.vault_token}",
f"https://127.0.0.1:8200/v1/secret/data/{path}"
], capture_output=True, text=True)
try:
data = json.loads(result.stdout)
if "data" in data and "data" in data["data"]:
return data["data"]["data"]
except:
pass
return None
def _detect_environment_from_name(self, target: str) -> tuple[str, float]:
"""Detect environment from target name. Returns (env, confidence)"""
target_lower = target.lower()
for env, patterns in self.ENVIRONMENT_PATTERNS.items():
for pattern in patterns:
if pattern in target_lower:
# Higher confidence for exact matches
confidence = 0.95 if pattern == target_lower else 0.8
return env, confidence
return "unknown", 0.3
def _check_pool_classification(self, target: str) -> Optional[str]:
"""Check pool classification from inventory"""
inventory = self._vault_read("inventory/proxmox")
if not inventory:
return None
for pool_name, pool_data in inventory.get("pools", {}).items():
nodes = pool_data.get("nodes", [])
if target in nodes:
# Classify based on pool name
pool_lower = pool_name.lower()
if "prod" in pool_lower:
return "production"
elif "staging" in pool_lower or "stg" in pool_lower:
return "staging"
elif "sandbox" in pool_lower or "dev" in pool_lower:
return "sandbox"
return None
def assert_sandbox(self, target: str, allow_staging: bool = False,
production_override: bool = False) -> AssertionResult:
"""
Assert that target is within sandbox boundaries.
Args:
target: Target name/identifier
allow_staging: Allow staging environment (default: False)
production_override: Explicit production approval (default: False)
Returns:
AssertionResult with safety determination
"""
timestamp = self._now()
# Step 1: Check pool classification from Vault
pool_env = self._check_pool_classification(target)
# Step 2: Detect from name patterns
name_env, name_confidence = self._detect_environment_from_name(target)
# Step 3: Determine final environment
if pool_env:
environment = pool_env
confidence = 0.99 # Pool classification is authoritative
else:
environment = name_env
confidence = name_confidence
risk_level = self.RISK_LEVELS.get(environment, 3)
# Step 4: Make safety determination
if environment == "sandbox" or environment == "development":
return AssertionResult(
target=target,
status=AssertionStatus.SAFE,
environment=environment,
message=f"Target '{target}' is in SANDBOX environment - safe to proceed",
risk_level=risk_level,
requires_approval=False,
details={
"pool_classification": pool_env,
"name_detection": name_env,
"confidence": confidence
},
timestamp=timestamp
)
elif environment == "staging":
if allow_staging:
return AssertionResult(
target=target,
status=AssertionStatus.SAFE,
environment=environment,
message=f"Target '{target}' is in STAGING - allowed by flag",
risk_level=risk_level,
requires_approval=False,
details={
"pool_classification": pool_env,
"name_detection": name_env,
"confidence": confidence,
"staging_allowed": True
},
timestamp=timestamp
)
else:
return AssertionResult(
target=target,
status=AssertionStatus.UNSAFE,
environment=environment,
message=f"Target '{target}' is in STAGING - requires --allow-staging flag",
risk_level=risk_level,
requires_approval=True,
details={
"pool_classification": pool_env,
"name_detection": name_env,
"confidence": confidence,
"staging_allowed": False
},
timestamp=timestamp
)
elif environment == "production":
if production_override:
return AssertionResult(
target=target,
status=AssertionStatus.OVERRIDE,
environment=environment,
message=f"PRODUCTION OVERRIDE: Target '{target}' access with explicit approval",
risk_level=risk_level,
requires_approval=False, # Already approved
details={
"pool_classification": pool_env,
"name_detection": name_env,
"confidence": confidence,
"production_override": True,
"WARNING": "PRODUCTION ACCESS ENABLED"
},
timestamp=timestamp
)
else:
return AssertionResult(
target=target,
status=AssertionStatus.UNSAFE,
environment=environment,
message=f"BLOCKED: Target '{target}' is in PRODUCTION - requires explicit approval",
risk_level=risk_level,
requires_approval=True,
details={
"pool_classification": pool_env,
"name_detection": name_env,
"confidence": confidence,
"production_override": False,
"BLOCKED": "Production access denied"
},
timestamp=timestamp
)
else: # unknown
return AssertionResult(
target=target,
status=AssertionStatus.UNKNOWN,
environment=environment,
message=f"UNKNOWN: Cannot determine environment for '{target}'",
risk_level=risk_level,
requires_approval=True,
details={
"pool_classification": pool_env,
"name_detection": name_env,
"confidence": confidence,
"WARNING": "Unable to classify - treating as high risk"
},
timestamp=timestamp
)
def assert_all_targets(self, targets: list[str], allow_staging: bool = False,
production_override: bool = False) -> dict:
"""Assert sandbox for multiple targets"""
report = {
"report_type": "sandbox_assertion",
"timestamp": self._now(),
"settings": {
"allow_staging": allow_staging,
"production_override": production_override
},
"results": [],
"summary": {
"total": len(targets),
"safe": 0,
"unsafe": 0,
"unknown": 0,
"override": 0,
"max_risk_level": 0
},
"can_proceed": True,
"blocked_targets": []
}
for target in targets:
result = self.assert_sandbox(target, allow_staging, production_override)
report["results"].append(result.to_dict())
if result.status == AssertionStatus.SAFE:
report["summary"]["safe"] += 1
elif result.status == AssertionStatus.UNSAFE:
report["summary"]["unsafe"] += 1
report["can_proceed"] = False
report["blocked_targets"].append(target)
elif result.status == AssertionStatus.UNKNOWN:
report["summary"]["unknown"] += 1
report["can_proceed"] = False
report["blocked_targets"].append(target)
elif result.status == AssertionStatus.OVERRIDE:
report["summary"]["override"] += 1
report["summary"]["max_risk_level"] = max(
report["summary"]["max_risk_level"],
result.risk_level
)
return report
# =============================================================================
# CLI
# =============================================================================
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Sandbox Assertion Check")
parser.add_argument("targets", nargs="+", help="Target nodes to assert")
parser.add_argument("--allow-staging", action="store_true",
help="Allow staging environment targets")
parser.add_argument("--production-override", action="store_true",
help="DANGEROUS: Override production block with explicit approval")
parser.add_argument("--json", action="store_true", help="Output JSON")
args = parser.parse_args()
asserter = SandboxAsserter()
report = asserter.assert_all_targets(
args.targets,
allow_staging=args.allow_staging,
production_override=args.production_override
)
if args.json:
print(json.dumps(report, indent=2))
else:
print("\n" + "=" * 60)
print("SANDBOX ASSERTION REPORT")
print("=" * 60)
print(f"Timestamp: {report['timestamp']}")
print(f"Allow Staging: {report['settings']['allow_staging']}")
print(f"Production Override: {report['settings']['production_override']}")
print()
risk_icons = ["[SAFE]", "[LOW]", "[MED]", "[HIGH]", "[CRIT]"]
for result in report["results"]:
status_icon = {
"SAFE": "[OK]",
"UNSAFE": "[BLOCKED]",
"UNKNOWN": "[???]",
"OVERRIDE": "[OVERRIDE]"
}.get(result["status"], "[?]")
risk_icon = risk_icons[min(result["risk_level"], 4)]
print(f"Target: {result['target']}")
print(f" Status: {status_icon} {result['status']}")
print(f" Environment: {result['environment']}")
print(f" Risk Level: {risk_icon} ({result['risk_level']}/4)")
print(f" Message: {result['message']}")
if result["requires_approval"]:
print(f" !!! REQUIRES APPROVAL !!!")
print()
print("-" * 60)
print("SUMMARY")
print("-" * 60)
s = report["summary"]
print(f" Total Targets: {s['total']}")
print(f" Safe: {s['safe']}")
print(f" Unsafe (blocked): {s['unsafe']}")
print(f" Unknown: {s['unknown']}")
print(f" Override: {s['override']}")
print(f" Max Risk Level: {s['max_risk_level']}/4")
print()
if report["blocked_targets"]:
print(f"BLOCKED TARGETS: {report['blocked_targets']}")
print()
if report["can_proceed"]:
print("[OK] SANDBOX ASSERTION PASSED - All targets are safe")
else:
print("[BLOCKED] SANDBOX ASSERTION FAILED - Cannot proceed")
print(" Some targets are outside sandbox boundaries")
print("=" * 60)
sys.exit(0 if report["can_proceed"] else 1)