Implement ops inventory probe evidence slice

This commit is contained in:
2026-06-05 23:16:40 +02:00
parent ee1f805c0b
commit 41d3e75a88
17 changed files with 1521 additions and 14 deletions

View File

@@ -0,0 +1,44 @@
from __future__ import annotations
from pathlib import Path
from activity_core.event_type_registry import parse_event_type_file
_EVENT_DIR = Path(__file__).parent.parent / "event-types"
_OPS_EVENT_TYPES = {
"ops-service-observed",
"ops-endpoint-verified",
"ops-access-path-checked",
"ops-backup-verified",
"ops-inventory-drift",
}
def test_ops_event_type_definitions_parse_and_expose_required_fields() -> None:
for type_id in _OPS_EVENT_TYPES:
path = _EVENT_DIR / f"{type_id}.md"
event_type = parse_event_type_file(path)
assert event_type.type_id == type_id
assert event_type.publisher == "activity-core"
assert event_type.status == "active"
assert event_type.attribute_schema["activity_core_run_id"]["required"] is True
assert event_type.attribute_schema["idempotency_key"]["required"] is True
assert event_type.attribute_schema["service_id"]["required"] is True
assert event_type.attribute_schema["observed_status"]["required"] is True
assert "raw response" in event_type.raw_md
assert "unredacted URL query strings" in event_type.raw_md
def test_endpoint_event_contract_captures_probe_result_fields() -> None:
event_type = parse_event_type_file(_EVENT_DIR / "ops-endpoint-verified.md")
for field in (
"endpoint_id",
"endpoint_url",
"expected_status",
"status_code",
"matched_expected_status",
"matched_expected_signal",
):
assert field in event_type.attribute_schema

View File

@@ -0,0 +1,195 @@
from __future__ import annotations
import json
from typing import Any
import httpx
from activity_core.ops_evidence_sinks import persist_ops_inventory_evidence
class DummyResponse:
def __init__(self, payload: Any) -> None:
self.payload = payload
def raise_for_status(self) -> None:
return None
def json(self) -> Any:
return self.payload
def _payload(sinks: list[dict[str, Any]]) -> dict[str, Any]:
return {
"activity_id": "activity-1",
"run_id": "12345678-aaaa-bbbb-cccc-123456789abc",
"scheduled_for": "2026-06-05T10:15:00+00:00",
"version_used": 1,
"context_sources": [
{
"type": "ops-inventory",
"query": "probe_services",
"bind_to": "context.ops_probe",
"params": {"evidence_sinks": sinks},
}
],
"context": {
"ops_probe": {
"generated_at": "2026-06-05T10:15:01+00:00",
"inventory_path": "/tmp/service-inventory.yml",
"summary": {"ok": 1, "degraded": 0, "down": 0, "skipped": 1},
"services": [
{
"service_id": "state-hub",
"name": "State Hub",
"kind": "coordination-service",
"environment": "local",
"lifecycle_state": "observed",
"declared_health_status": "unknown",
"owner_repos": ["state-hub"],
"endpoint_count": 1,
"access_path_count": 1,
}
],
"endpoints": [
{
"service_id": "state-hub",
"service_name": "State Hub",
"endpoint_id": "state-hub-health",
"endpoint_type": "http",
"url": "http://user:pass@state-hub.test/health?token=secret",
"expected_status": 200,
"expected_signal_present": True,
"widget_ref": "ops:endpoint:state-hub-health",
"status": "ok",
"status_code": 200,
"matched_expected_status": True,
"matched_expected_signal": True,
"response_body": "secret response body",
"headers": {"Authorization": "Bearer secret"},
}
],
"access_paths": [
{
"service_id": "state-hub",
"service_name": "State Hub",
"access_path_id": "state-hub-access-1",
"access_path_type": "k8s",
"declared_status": "unknown",
"status": "skipped",
"reason": "unsupported_access_path_type",
}
],
}
},
}
def test_state_hub_progress_sink_posts_compact_probe_summary(monkeypatch) -> None:
posts: list[dict[str, Any]] = []
def fake_get(url: str, **kwargs: Any) -> DummyResponse:
assert url == "http://state-hub.test/progress/"
return DummyResponse([])
def fake_post(url: str, **kwargs: Any) -> DummyResponse:
posts.append({"url": url, **kwargs})
return DummyResponse({"id": "progress-1"})
monkeypatch.setattr(httpx, "get", fake_get)
monkeypatch.setattr(httpx, "post", fake_post)
result = persist_ops_inventory_evidence(
_payload([
{
"type": "state-hub-progress",
"state_hub_url": "http://state-hub.test",
"event_type": "ops_inventory_probe",
"workstream_id": "workstream-1",
"task_id": "task-1",
}
])
)
assert result == [
{
"type": "state-hub-progress",
"status": "posted",
"event_type": "ops_inventory_probe",
"progress_id": "progress-1",
"idempotency_key": "12345678-aaaa-bbbb-cccc-123456789abc:ops_probe:ops_inventory_probe",
"context_key": "ops_probe",
}
]
body = posts[0]["json"]
assert body["summary"] == "Ops inventory probe: 1 ok, 0 degraded, 0 down, 1 skipped"
assert body["workstream_id"] == "workstream-1"
assert body["task_id"] == "task-1"
assert body["detail"]["activity_core_run_id"] == _run_id()
assert body["detail"]["idempotency_key"] == result[0]["idempotency_key"]
assert body["detail"]["probe"]["endpoints"][0]["url"] == "http://state-hub.test/health"
serialized = json.dumps(body, sort_keys=True)
assert "secret response body" not in serialized
assert "Authorization" not in serialized
assert "user:pass" not in serialized
assert "token=secret" not in serialized
def test_state_hub_progress_sink_is_idempotent(monkeypatch) -> None:
idempotency_key = f"{_run_id()}:ops_probe:ops_inventory_probe"
def fake_get(url: str, **kwargs: Any) -> DummyResponse:
return DummyResponse([
{
"event_type": "ops_inventory_probe",
"detail": {"idempotency_key": idempotency_key},
}
])
def fake_post(url: str, **kwargs: Any) -> DummyResponse:
raise AssertionError("post should not be called")
monkeypatch.setattr(httpx, "get", fake_get)
monkeypatch.setattr(httpx, "post", fake_post)
result = persist_ops_inventory_evidence(
_payload([
{
"type": "state-hub-progress",
"state_hub_url": "http://state-hub.test",
}
])
)
assert result[0]["status"] == "exists"
assert result[0]["idempotency_key"] == idempotency_key
def test_inter_hub_sink_skips_cleanly_when_config_missing(monkeypatch) -> None:
monkeypatch.delenv("INTER_HUB_URL", raising=False)
monkeypatch.delenv("OPS_HUB_KEY", raising=False)
result = persist_ops_inventory_evidence(
_payload([{"type": "inter-hub-interaction-event"}])
)
assert result == [
{
"type": "inter-hub-interaction-event",
"status": "skipped",
"reason": "missing_inter_hub_config",
"missing": ["INTER_HUB_URL", "OPS_HUB_KEY", "widget_mapping"],
}
]
def test_no_evidence_sinks_returns_no_results() -> None:
payload = _payload([])
payload["context_sources"][0]["params"] = {}
assert persist_ops_inventory_evidence(payload) == []
def _run_id() -> str:
return "12345678-aaaa-bbbb-cccc-123456789abc"

View File

@@ -0,0 +1,283 @@
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
import httpx
import pytest
from activity_core.context_resolvers.ops_inventory import OpsInventoryContextResolver
class DummyResponse:
def __init__(self, status_code: int, text: str = "") -> None:
self.status_code = status_code
self.text = text
def _write_inventory(tmp_path: Path, services: str) -> Path:
path = tmp_path / "service-inventory.yml"
path.write_text(
f"""
version: 1
last_reviewed: "2026-06-05"
environments: []
hosts: []
clusters: []
services:
{services}
""",
encoding="utf-8",
)
return path
def test_probe_services_reports_ok_endpoint_and_skipped_access_path(
tmp_path,
monkeypatch,
) -> None:
inventory = _write_inventory(
tmp_path,
"""
- id: state-hub
name: State Hub
kind: coordination-service
lifecycle_state: observed
health_status: unknown
environment: local
owner_repos: [state-hub]
endpoints:
- id: state-hub-health
type: http
url: "http://127.0.0.1:8000/state/health"
expected_status: 200
expected_signal: "health response"
access_paths:
- type: k8s
target: local
status: unknown
""",
)
calls: list[dict[str, Any]] = []
def fake_get(url: str, **kwargs: Any) -> DummyResponse:
calls.append({"url": url, **kwargs})
return DummyResponse(200, "ok: health response")
monkeypatch.setattr(httpx, "get", fake_get)
result = OpsInventoryContextResolver().resolve(
"probe_services",
None,
{"inventory_path": str(inventory)},
)
assert result["summary"] == {"ok": 1, "degraded": 0, "down": 0, "skipped": 1}
assert result["services"][0]["service_id"] == "state-hub"
assert result["endpoints"][0]["status"] == "ok"
assert result["endpoints"][0]["matched_expected_status"] is True
assert result["endpoints"][0]["matched_expected_signal"] is True
assert result["access_paths"][0]["status"] == "skipped"
assert result["access_paths"][0]["reason"] == "unsupported_access_path_type"
assert calls == [
{
"url": "http://127.0.0.1:8000/state/health",
"timeout": 10.0,
"follow_redirects": False,
}
]
def test_probe_services_marks_status_mismatch_degraded(tmp_path, monkeypatch) -> None:
inventory = _write_inventory(
tmp_path,
"""
- id: gitea
name: Gitea
kind: application
lifecycle_state: observed
health_status: unknown
environment: coulombcore
owner_repos: [railiance-apps]
endpoints:
- id: gitea-registry
type: https
url: "https://gitea.coulomb.social/v2/"
expected_status: 401
expected_signal: "OCI registry auth challenge"
access_paths: []
""",
)
def fake_get(url: str, **kwargs: Any) -> DummyResponse:
return DummyResponse(200, "OCI registry auth challenge")
monkeypatch.setattr(httpx, "get", fake_get)
result = OpsInventoryContextResolver().resolve(
"probe_services",
None,
{"inventory_path": str(inventory)},
)
endpoint = result["endpoints"][0]
assert result["summary"] == {"ok": 0, "degraded": 1, "down": 0, "skipped": 0}
assert endpoint["status"] == "degraded"
assert endpoint["reason"] == "expected_status_mismatch"
assert endpoint["matched_expected_status"] is False
assert endpoint["matched_expected_signal"] is True
def test_probe_services_marks_signal_mismatch_degraded(tmp_path, monkeypatch) -> None:
inventory = _write_inventory(
tmp_path,
"""
- id: inter-hub
name: Inter-Hub
kind: governance-service
lifecycle_state: observed
health_status: unknown
environment: threephoenix-prod
owner_repos: [inter-hub]
endpoints:
- id: inter-hub-openapi
type: https
url: "https://hub.coulomb.social/api/v2/openapi.json"
expected_status: 200
expected_signal: "OpenAPI document"
access_paths: []
""",
)
def fake_get(url: str, **kwargs: Any) -> DummyResponse:
return DummyResponse(200, "{}")
monkeypatch.setattr(httpx, "get", fake_get)
result = OpsInventoryContextResolver().resolve(
"probe_services",
None,
{"inventory_path": str(inventory)},
)
endpoint = result["endpoints"][0]
assert result["summary"] == {"ok": 0, "degraded": 1, "down": 0, "skipped": 0}
assert endpoint["status"] == "degraded"
assert endpoint["reason"] == "expected_signal_missing"
assert endpoint["matched_expected_status"] is True
assert endpoint["matched_expected_signal"] is False
def test_probe_services_marks_network_error_down_and_sanitizes_output(
tmp_path,
monkeypatch,
) -> None:
inventory = _write_inventory(
tmp_path,
"""
- id: private-api
name: Private API
kind: application
lifecycle_state: observed
health_status: unknown
environment: local
owner_repos: [secret-repo]
endpoints:
- id: private-api-health
type: https
url: "https://user:pass@example.test/health?token=super-secret"
expected_status: 200
expected_signal: "secret response body"
access_paths: []
""",
)
def fake_get(url: str, **kwargs: Any) -> DummyResponse:
raise httpx.ConnectError("offline")
monkeypatch.setattr(httpx, "get", fake_get)
result = OpsInventoryContextResolver().resolve(
"probe_services",
None,
{"inventory_path": str(inventory)},
)
serialized = json.dumps(result, sort_keys=True)
endpoint = result["endpoints"][0]
assert result["summary"] == {"ok": 0, "degraded": 0, "down": 1, "skipped": 0}
assert endpoint["status"] == "down"
assert endpoint["url"] == "https://example.test/health"
assert "super-secret" not in serialized
assert "user:pass" not in serialized
assert "secret response body" not in serialized
def test_probe_services_skips_unsupported_and_network_disabled(
tmp_path,
monkeypatch,
) -> None:
inventory = _write_inventory(
tmp_path,
"""
- id: bridge
name: Ops Bridge
kind: bridge
lifecycle_state: observed
health_status: unknown
environment: local
owner_repos: [ops-bridge]
endpoints:
- id: bridge-ssh
type: ssh
url: "ssh://bridge.example"
- id: bridge-http
type: http
url: "http://bridge.example/health"
access_paths: []
""",
)
def fake_get(url: str, **kwargs: Any) -> DummyResponse:
raise AssertionError("network should be disabled")
monkeypatch.setattr(httpx, "get", fake_get)
result = OpsInventoryContextResolver().resolve(
"probe_services",
None,
{"inventory_path": str(inventory), "allow_network": False},
)
assert result["summary"] == {"ok": 0, "degraded": 0, "down": 0, "skipped": 2}
assert [entry["reason"] for entry in result["endpoints"]] == [
"kind_not_included",
"network_disabled",
]
def test_probe_services_missing_inventory_optional_and_required(tmp_path) -> None:
missing = tmp_path / "missing.yml"
resolver = OpsInventoryContextResolver()
optional = resolver.resolve(
"probe_services",
None,
{"inventory_path": str(missing), "required": False},
)
assert optional["status"] == "skipped"
assert optional["reason"] == "inventory_not_found"
assert optional["summary"] == {"ok": 0, "degraded": 0, "down": 0, "skipped": 1}
with pytest.raises(FileNotFoundError):
resolver.resolve(
"probe_services",
None,
{"inventory_path": str(missing), "required": True},
)
def test_unknown_query_returns_empty() -> None:
assert OpsInventoryContextResolver().resolve("unknown", None, {}) == {}

View File

@@ -125,9 +125,15 @@ async def test_delete_schedule_removes_schedule(env: WorkflowEnvironment) -> Non
await upsert_schedule(env.client, defn)
await delete_schedule(env.client, defn.id)
schedules = await list_schedules(env.client)
ids = [s["schedule_id"] for s in schedules]
assert schedule_id(defn.id) not in ids, "Schedule should be gone after delete"
sid = schedule_id(defn.id)
ids: list[str] = []
for _ in range(10):
schedules = await list_schedules(env.client)
ids = [s["schedule_id"] for s in schedules]
if sid not in ids:
break
await asyncio.sleep(0.3)
assert sid not in ids, "Schedule should be gone after delete"
# ── T25e: delete_schedule is idempotent (no-op for non-existent schedule) ────

View File

@@ -1,5 +1,6 @@
import uuid
from activity_core.definition_parser import scan_and_parse
from activity_core.models import ActivityDefinition
from activity_core.sync_activity_definitions import _definition_uuid
@@ -41,3 +42,56 @@ def test_activity_definition_accepts_adr_style_context_source_without_name() ->
)
assert defn.context_sources[0].name == ""
def test_scan_and_parse_reads_external_activity_definition_dirs(
tmp_path,
monkeypatch,
) -> None:
repo_root = tmp_path / "activity-core"
external_root = tmp_path / "the-custodian"
definitions_dir = external_root / "activity-definitions"
repo_root.mkdir()
definitions_dir.mkdir(parents=True)
(definitions_dir / "ops-service-inventory-probes.md").write_text(
"""---
id: "40d15a87-7ff6-4d8e-992c-37df15f95110"
name: "Ops Service Inventory Probes"
enabled: false
owner: custodian
governance: custodian
status: proposed
trigger:
type: cron
cron_expression: "15 * * * *"
timezone: Europe/Berlin
misfire_policy: skip
context_sources:
- type: ops-inventory
query: probe_services
bind_to: context.ops_probe
params:
inventory_path: /tmp/service-inventory.yml
evidence_sinks:
- type: state-hub-progress
event_type: ops_inventory_probe
---
# Ops Service Inventory Probes
""",
encoding="utf-8",
)
monkeypatch.chdir(repo_root)
monkeypatch.setenv("ACTIVITY_DEFINITION_DIRS", str(external_root))
definitions = scan_and_parse()
assert len(definitions) == 1
definition = definitions[0]
assert definition.name == "Ops Service Inventory Probes"
assert definition.enabled is False
assert definition.context_sources[0]["type"] == "ops-inventory"
assert definition.context_sources[0]["params"]["evidence_sinks"][0]["type"] == (
"state-hub-progress"
)