feat: add host188 OpenClaw callback playbook

This commit is contained in:
ogt
2026-07-17 00:30:28 +08:00
parent e48494607a
commit 65c7174336
9 changed files with 1161 additions and 3 deletions

View File

@@ -0,0 +1,299 @@
from __future__ import annotations
import json
from pathlib import Path
import pytest
import yaml
from src.services.awooop_ansible_audit_service import (
_catalog_hints,
get_ansible_catalog_item,
)
from src.services.awooop_ansible_check_mode_service import (
build_ansible_apply_command,
build_ansible_check_mode_command,
)
from src.services.awooop_ansible_post_verifier import postconditions_for_catalog
from src.services.controlled_alert_target_router import resolve_typed_alert_target
ROOT = Path(__file__).resolve().parents[3]
PLAYBOOK = (
ROOT
/ "infra"
/ "ansible"
/ "playbooks"
/ "188-openclaw-callback-forwarder.yml"
)
SERVICE_REGISTRY = ROOT / "ops" / "config" / "service-registry.yaml"
MONITORING_REGISTRY = ROOT / "ops" / "monitoring" / "service-registry.yaml"
CATALOG_ID = "ansible:188-openclaw-callback-forwarder"
CANONICAL_ASSET_ID = "service:openclaw:host188"
EXPECTED_SHA = "ffc45311b6403b30cb6ae6e1aa83dae1a047e9c9"
EXPECTED_REMOTE = "ssh://git@192.168.0.110:2222/wooo/clawbot-v5.git"
def _flatten_tasks(tasks: list[dict]) -> list[dict]:
flattened: list[dict] = []
for task in tasks:
flattened.append(task)
for section in ("block", "rescue", "always"):
nested = task.get(section)
if isinstance(nested, list):
flattened.extend(_flatten_tasks(nested))
return flattened
def _incident() -> dict:
return {
"incident_id": "INC-OPENCLAW-CALLBACK-FORWARDER",
"project_id": "awoooi",
"alertname": "OpenClawDown",
"alert_category": "ai_agent",
"affected_services": ["openclaw"],
"signals": [
{
"alert_name": "OpenClawDown",
"labels": {"component": "openclaw", "host": "188"},
}
],
}
def test_openclaw_routes_only_to_exact_host188_compose_lane() -> None:
for target in ("openclaw", "clawbot", "clawbot.service"):
route = resolve_typed_alert_target(
alertname="OpenClawDown",
target_resource=target,
namespace="",
labels={"host": "188"},
alert_category="ai_agent",
)
assert route["resolution_status"] == "resolved"
assert route["target_kind"] == "docker_compose_container"
assert route["canonical_asset_id"] == CANONICAL_ASSET_ID
assert route["host"] == "192.168.0.188"
assert route["executor"] == "host_ansible_executor"
assert route["verifier"] == (
"openclaw_callback_forwarder_independent_verifier"
)
assert route["risk_class"] == "medium"
assert route["allowed_catalog_ids"] == [CATALOG_ID]
assert route["allowed_inventory_hosts"] == ["host_188"]
assert route["cross_domain_fallback_allowed"] is False
def test_openclaw_incident_selects_one_exact_bounded_catalog() -> None:
hints = _catalog_hints(_incident(), None)
assert hints["match_mode"] == "typed_domain_router_v2"
assert hints["decision_effect"] == "bounded_domain_candidate"
assert [row["catalog_id"] for row in hints["candidates"]] == [CATALOG_ID]
candidate = hints["candidates"][0]
assert candidate["inventory_hosts"] == ["host_188"]
assert candidate["canonical_asset_id"] == CANONICAL_ASSET_ID
assert candidate["typed_domain"] == "docker_compose_container"
assert candidate["supports_check_mode"] is True
assert candidate["auto_apply_enabled"] is True
assert candidate["approval_required"] is False
assert candidate["risk_level"] == "medium"
assert hints["cross_domain_fallback_allowed"] is False
def test_openclaw_catalog_and_independent_verifier_are_exact() -> None:
catalog = get_ansible_catalog_item(CATALOG_ID)
conditions = postconditions_for_catalog(CATALOG_ID)
assert catalog is not None
assert catalog["playbook_path"].endswith(
"188-openclaw-callback-forwarder.yml"
)
assert catalog["check_mode_playbook_path"] == catalog["playbook_path"]
assert catalog["canonical_asset_id"] == CANONICAL_ASSET_ID
assert catalog["inventory_hosts"] == ["host_188"]
assert catalog["domains"] == [
"docker_compose_container",
"openclaw",
"telegram_callback_forwarder",
"host_188",
]
assert catalog["catalog_revision"] == (
"2026-07-17-openclaw-callback-forwarder-v1"
)
assert catalog["cross_domain_fallback_allowed"] is False
assert {condition.condition_id for condition in conditions} == {
"host_188_openclaw_immutable_source_identity",
"host_188_openclaw_callback_dropin_enabled",
"host_188_clawbot_systemd_runtime_active",
"host_188_openclaw_local_health",
"host_188_awoooi_callback_receiver_public_health",
}
assert {condition.inventory_host for condition in conditions} == {"host_188"}
probes = "\n".join(condition.probe for condition in conditions)
assert EXPECTED_SHA in probes
assert EXPECTED_REMOTE in probes
assert "TELEGRAM_CALLBACK_FORWARD_ENABLED=true" in probes
assert "http://127.0.0.1:8088/health" in probes
assert "https://awoooi.wooo.work/api/v1/telegram/health" in probes
assert "--env-file" not in probes
def test_openclaw_playbook_is_check_first_bounded_and_rollback_capable() -> None:
payload = yaml.safe_load(PLAYBOOK.read_text(encoding="utf-8"))
source = PLAYBOOK.read_text(encoding="utf-8")
play = payload[0]
tasks = {task["name"]: task for task in _flatten_tasks(play["tasks"])}
assert play["hosts"] == "host_188"
assert play["gather_facts"] is False
assert play["become"] is True
assert play["vars"]["catalog_id"] == CATALOG_ID
assert play["vars"]["canonical_asset_id"] == CANONICAL_ASSET_ID
assert play["vars"]["expected_source_sha"] == EXPECTED_SHA
assert play["vars"]["expected_remote_url"] == EXPECTED_REMOTE
assert play["vars"]["allowed_working_directories"] == [
"/opt/openclaw",
"/home/ollama/clawbot-v5",
]
assert play["vars"]["callback_dropin_content"].splitlines() == [
"[Service]",
"Environment=TELEGRAM_CALLBACK_FORWARD_ENABLED=true",
]
working_directory = tasks[
"Resolve OpenClaw working directory from systemd truth"
]
assert working_directory["check_mode"] is False
assert working_directory["ansible.builtin.command"]["argv"] == [
"systemctl",
"show",
"clawbot.service",
"--property=WorkingDirectory",
"--value",
]
assert tasks["Fail closed when systemd source identity is outside host188 scope"][
"ansible.builtin.assert"
]["that"] == ["openclaw_workdir in allowed_working_directories"]
check_receipt = tasks["Publish check-mode source diff and no-write receipt"]
assert check_receipt["when"] == "ansible_check_mode"
assert check_receipt["ansible.builtin.debug"]["msg"][
"persistent_writes_performed"
] == 0
for key in ("trace_id", "run_id", "work_item_id"):
assert check_receipt["ansible.builtin.debug"]["msg"][key] == (
"{{ " + key + " }}"
)
for name in (
"Build only the immutable OpenClaw compose service with timeout",
"Rebuild only the restored OpenClaw compose service",
):
task = tasks[name]
argv = task["ansible.builtin.command"]["argv"]
assert argv[:4] == [
"/usr/bin/timeout",
"--signal=TERM",
"--kill-after=15",
"300",
]
assert "build" in argv
assert "up" not in argv
assert argv[-1] == "{{ compose_service }}"
assert task["args"]["chdir"] == "{{ openclaw_workdir }}"
assert task["no_log"] is True
for name in (
"Reload systemd and restart the owned service with callback flag",
"Reload systemd and restart restored clawbot.service contract",
):
assert tasks[name]["ansible.builtin.systemd"] == {
"name": "clawbot.service",
"state": "restarted",
"daemon_reload": True,
}
assert tasks[name]["no_log"] is True
rollback = tasks["Restore exact old source SHA after failed apply or verifier"]
assert rollback["ansible.builtin.command"]["argv"][-2:] == [
"--hard",
"{{ source_head_before.stdout | trim }}",
]
assert "old-source-sha" in source
assert "old-callback-dropin.conf" in source
assert "openclaw_callback_forwarder_rollback_v1" in source
assert "git merge --ff-only" not in source
assert "--ff-only" in source
assert "docker compose config" not in source
assert "--env-file" not in source
assert "- up" not in source
assert "docker compose down" not in source
assert "docker system prune" not in source
assert "force push" not in source
def test_openclaw_commands_carry_same_safe_controlled_correlation(
tmp_path: Path,
) -> None:
key = tmp_path / "ssh_mcp_key"
known_hosts = tmp_path / "known_hosts"
expected = {
"ansible_ssh_private_key_file": str(key),
"controlled_trace_id": "trace:openclaw:001",
"controlled_run_id": "run:openclaw:001",
"controlled_work_item_id": "P0-OPENCLAW-CALLBACK-001",
}
for builder in (build_ansible_check_mode_command, build_ansible_apply_command):
spec = builder(
playbook_path=(
"infra/ansible/playbooks/188-openclaw-callback-forwarder.yml"
),
inventory_hosts=("host_188",),
playbook_root=ROOT / "infra" / "ansible",
check_mode_ssh_key_path=key,
check_mode_known_hosts_path=known_hosts,
trace_id="trace:openclaw:001",
run_id="run:openclaw:001",
work_item_id="P0-OPENCLAW-CALLBACK-001",
)
assert json.loads(spec.command[-1]) == expected
with pytest.raises(ValueError, match="unsafe_controlled_execution_correlation"):
build_ansible_check_mode_command(
playbook_path=(
"infra/ansible/playbooks/188-openclaw-callback-forwarder.yml"
),
inventory_hosts=("host_188",),
playbook_root=ROOT / "infra" / "ansible",
check_mode_ssh_key_path=key,
check_mode_known_hosts_path=known_hosts,
trace_id="unsafe correlation",
)
def test_openclaw_source_and_monitoring_registries_use_exact_catalog() -> None:
registry = yaml.safe_load(SERVICE_REGISTRY.read_text(encoding="utf-8"))
service = next(row for row in registry["services"] if row["name"] == "openclaw")
assert service["canonical_id"] == CANONICAL_ASSET_ID
assert service["host"] == "192.168.0.188"
assert service["asset_domain"] == "docker_compose_container"
assert service["executor"] == "host_ansible_executor"
assert service["verifier"] == (
"openclaw_callback_forwarder_independent_verifier"
)
assert service["allowed_catalog_ids"] == [CATALOG_ID]
monitoring = yaml.safe_load(
MONITORING_REGISTRY.read_text(encoding="utf-8")
)
monitored = next(
row for row in monitoring["services"] if row["name"] == "openclaw"
)
assert monitored["host"] == "192.168.0.188"
assert monitored["auto_repair"] == {
"enabled": True,
"actions": [CATALOG_ID],
}