generated from coulomb/repo-seed
Add automation status surface
This commit is contained in:
@@ -573,6 +573,47 @@ def test_resilient_recovery_against_real_2026_06_26_fixture():
|
||||
assert all("rank" in rec and "candidate" in rec for rec in result.report["recommendations"])
|
||||
|
||||
|
||||
|
||||
class _MetadataBadLLM:
|
||||
def __init__(self) -> None:
|
||||
self.call_count = 0
|
||||
self.last_response_metadata: dict[str, Any] | None = None
|
||||
|
||||
def complete(
|
||||
self,
|
||||
prompt: str,
|
||||
model: str = "",
|
||||
config: dict | None = None,
|
||||
) -> str:
|
||||
self.call_count += 1
|
||||
self.last_response_metadata = {
|
||||
"finish_reason": "length",
|
||||
"usage": {"input_tokens": 1100, "output_tokens": 1200},
|
||||
}
|
||||
return ("x" * 9000) + "{"
|
||||
|
||||
|
||||
def test_invalid_report_preserves_response_metadata_and_long_preview():
|
||||
llm = _MetadataBadLLM()
|
||||
instr = _instr(
|
||||
id="daily-triage-report",
|
||||
prompt="Report.",
|
||||
trusted_fields=[],
|
||||
report_sinks=[{"type": "working-memory", "path": "/tmp"}],
|
||||
)
|
||||
|
||||
result = execute_instruction_with_audit(instr, _Event(), {}, llm)
|
||||
|
||||
assert llm.call_count == 2
|
||||
assert result.output_validated is False
|
||||
assert result.llm_response_metadata == {
|
||||
"finish_reason": "length",
|
||||
"usage": {"input_tokens": 1100, "output_tokens": 1200},
|
||||
}
|
||||
assert result.report["llm_response_metadata"] == result.llm_response_metadata
|
||||
assert len(result.report["raw_output_preview"]) > 4000
|
||||
|
||||
|
||||
def test_execute_instruction_with_audit_preserves_invalid_report_with_sinks(
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
|
||||
184
tests/test_automation_status.py
Normal file
184
tests/test_automation_status.py
Normal file
@@ -0,0 +1,184 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from activity_core import automation_status as status
|
||||
|
||||
ACTIVITY_ID = "00000000-0000-0000-0000-000000000123"
|
||||
|
||||
|
||||
def _window():
|
||||
return status.resolve_window(
|
||||
"2026-06-26",
|
||||
"2026-06-29",
|
||||
"Europe/Berlin",
|
||||
)
|
||||
|
||||
|
||||
def _definition(enabled: bool = True):
|
||||
return {
|
||||
"id": ACTIVITY_ID,
|
||||
"name": "Daily Check",
|
||||
"enabled": enabled,
|
||||
"trigger_type": "cron",
|
||||
"trigger_config": {
|
||||
"trigger_type": "cron",
|
||||
"cron_expression": "0 9 * * *",
|
||||
"timezone": "Europe/Berlin",
|
||||
"misfire_policy": "skip",
|
||||
},
|
||||
"source": "test",
|
||||
}
|
||||
|
||||
|
||||
def test_friday_shortcut_resolves_to_previous_friday_start() -> None:
|
||||
now = datetime(2026, 6, 29, 12, 0, tzinfo=ZoneInfo("Europe/Berlin"))
|
||||
|
||||
window = status.resolve_window("friday", None, "Europe/Berlin", now=now)
|
||||
|
||||
assert window["since"].isoformat() == "2026-06-26T00:00:00+02:00"
|
||||
assert window["until"].isoformat() == "2026-06-29T12:00:00+02:00"
|
||||
|
||||
|
||||
def test_expected_fires_for_simple_cron_window() -> None:
|
||||
fires = status.expected_fires(_definition(), _window())
|
||||
|
||||
assert fires == [
|
||||
"2026-06-26T09:00:00+02:00",
|
||||
"2026-06-27T09:00:00+02:00",
|
||||
"2026-06-28T09:00:00+02:00",
|
||||
"2026-06-29T09:00:00+02:00",
|
||||
]
|
||||
|
||||
|
||||
def test_completed_when_expected_run_exists() -> None:
|
||||
run = {
|
||||
"run_id": "run-1",
|
||||
"activity_id": ACTIVITY_ID,
|
||||
"scheduled_for": "2026-06-26T07:00:00+00:00",
|
||||
"fired_at": "2026-06-26T07:00:10+00:00",
|
||||
"tasks_spawned": 1,
|
||||
}
|
||||
|
||||
report = status.classify_activity(
|
||||
_definition(),
|
||||
_window(),
|
||||
[run],
|
||||
[{"source": "state_hub_progress", "run_id": "run-1", "output_validated": True}],
|
||||
None,
|
||||
["2026-06-26T09:00:00+02:00"],
|
||||
runs_available=True,
|
||||
)
|
||||
|
||||
assert report["status"] == "completed"
|
||||
|
||||
|
||||
def test_validation_failure_wins_over_completed_run() -> None:
|
||||
run = {"run_id": "run-1", "activity_id": ACTIVITY_ID, "scheduled_for": None, "fired_at": "2026-06-26T07:00:10+00:00"}
|
||||
|
||||
report = status.classify_activity(
|
||||
_definition(),
|
||||
_window(),
|
||||
[run],
|
||||
[{"source": "working_memory", "run_id": "run-1", "output_validated": False}],
|
||||
None,
|
||||
["2026-06-26T09:00:00+02:00"],
|
||||
runs_available=True,
|
||||
)
|
||||
|
||||
assert report["status"] == "validation_failed"
|
||||
|
||||
|
||||
def test_missed_when_expected_fire_has_no_run_and_runs_available() -> None:
|
||||
report = status.classify_activity(
|
||||
_definition(),
|
||||
_window(),
|
||||
[],
|
||||
[],
|
||||
None,
|
||||
["2026-06-26T09:00:00+02:00"],
|
||||
runs_available=True,
|
||||
)
|
||||
|
||||
assert report["status"] == "missed"
|
||||
|
||||
|
||||
def test_disabled_schedule_is_not_counted_as_missed() -> None:
|
||||
report = status.classify_activity(
|
||||
_definition(enabled=False),
|
||||
_window(),
|
||||
[],
|
||||
[],
|
||||
None,
|
||||
["2026-06-26T09:00:00+02:00"],
|
||||
runs_available=True,
|
||||
)
|
||||
|
||||
assert report["status"] == "disabled"
|
||||
|
||||
|
||||
def test_scheduled_definition_reports_one_shot_schedule_id() -> None:
|
||||
definition = {
|
||||
"id": ACTIVITY_ID,
|
||||
"name": "One Shot",
|
||||
"enabled": True,
|
||||
"trigger_type": "scheduled",
|
||||
"trigger_config": {
|
||||
"trigger_type": "scheduled",
|
||||
"at": "2026-06-26T09:00:00+02:00",
|
||||
"timezone": "Europe/Berlin",
|
||||
},
|
||||
"source": "test",
|
||||
}
|
||||
|
||||
report = status.classify_activity(
|
||||
definition,
|
||||
_window(),
|
||||
[],
|
||||
[],
|
||||
None,
|
||||
["2026-06-26T09:00:00+02:00"],
|
||||
runs_available=False,
|
||||
)
|
||||
|
||||
assert status.automation_schedule_id(_definition()) == f"activity-schedule-{ACTIVITY_ID}"
|
||||
assert report["schedule_id"] == f"activity-schedule-{ACTIVITY_ID}-once"
|
||||
|
||||
|
||||
def test_partial_source_availability_is_unknown_not_missed() -> None:
|
||||
report = status.classify_activity(
|
||||
_definition(),
|
||||
_window(),
|
||||
[],
|
||||
[],
|
||||
None,
|
||||
["2026-06-26T09:00:00+02:00"],
|
||||
runs_available=False,
|
||||
)
|
||||
|
||||
assert report["status"] == "unknown"
|
||||
assert "missed-run verdict is unknown" in report["warnings"][0]
|
||||
|
||||
|
||||
def test_working_memory_frontmatter_evidence(tmp_path: Path) -> None:
|
||||
note = tmp_path / "daily-triage-2026-06-26-run.md"
|
||||
note.write_text(
|
||||
"---\n"
|
||||
"source: activity-core\n"
|
||||
f"activity_id: {ACTIVITY_ID}\n"
|
||||
"activity_core_run_id: run-1\n"
|
||||
"scheduled_for: 2026-06-26T07:00:00+00:00\n"
|
||||
"output_validated: false\n"
|
||||
"created: 2026-06-26T07:01:00+00:00\n"
|
||||
"---\n"
|
||||
"body\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
evidence, source = status.load_working_memory_evidence(str(tmp_path), _window())
|
||||
|
||||
assert source["status"] == "ok"
|
||||
assert evidence[0]["run_id"] == "run-1"
|
||||
assert evidence[0]["output_validated"] is False
|
||||
@@ -13,7 +13,12 @@ def test_llm_connect_client_forwards_run_config(monkeypatch) -> None:
|
||||
pass
|
||||
|
||||
def json(self) -> dict:
|
||||
return {"content": '{"summary":"ok","recommendations":[]}'}
|
||||
return {
|
||||
"content": '{"summary":"ok","recommendations":[]}',
|
||||
"finish_reason": "stop",
|
||||
"usage": {"input_tokens": 10, "output_tokens": 20},
|
||||
"raw_response": {"provider_blob": "not persisted"},
|
||||
}
|
||||
|
||||
def fake_post(url: str, json: dict, timeout: float) -> Response:
|
||||
captured["url"] = url
|
||||
@@ -50,3 +55,7 @@ def test_llm_connect_client_forwards_run_config(monkeypatch) -> None:
|
||||
"timeout_seconds": 42,
|
||||
},
|
||||
}
|
||||
assert client.last_response_metadata == {
|
||||
"finish_reason": "stop",
|
||||
"usage": {"input_tokens": 10, "output_tokens": 20},
|
||||
}
|
||||
|
||||
@@ -93,12 +93,21 @@ def test_external_configmap_projects_enabled_daily_wsjf_definition(tmp_path) ->
|
||||
assert definition.trigger_config["cron_expression"] == "20 7 * * *"
|
||||
assert definition.trigger_config["timezone"] == "Europe/Berlin"
|
||||
assert instruction["id"] == "daily-triage-report"
|
||||
assert instruction["max_tokens"] == 1800
|
||||
assert "most 7 recommendations" in instruction["prompt"]
|
||||
assert "fewer well-formed" in instruction["prompt"]
|
||||
assert instruction["output_schema"] == (
|
||||
"/etc/activity-core/schemas/daily-triage-report.json"
|
||||
)
|
||||
assert instruction["report_sinks"][0]["type"] == "working-memory"
|
||||
assert instruction["report_sinks"][1]["event_type"] == "daily_triage"
|
||||
|
||||
schema = _by_kind_name("ConfigMap", "actcore-report-schemas")
|
||||
daily_schema = yaml.safe_load(schema["data"]["daily-triage-report.json"])
|
||||
recommendations = daily_schema["properties"]["recommendations"]
|
||||
assert recommendations["maxItems"] == 7
|
||||
assert recommendations["items"]["properties"]["rank"]["maximum"] == 7
|
||||
|
||||
|
||||
def test_ops_inventory_configmap_contains_probeable_inventory() -> None:
|
||||
config = _by_kind_name("ConfigMap", "actcore-ops-service-inventory")
|
||||
|
||||
@@ -37,6 +37,10 @@ def _payload(sinks: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
"output_validated": True,
|
||||
"review_required": False,
|
||||
"validation_error": None,
|
||||
"llm_response_metadata": {
|
||||
"finish_reason": "stop",
|
||||
"usage": {"output_tokens": 50},
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
@@ -62,6 +66,8 @@ def test_working_memory_sink_writes_idempotently(tmp_path) -> None:
|
||||
assert "output_validated: true" in text
|
||||
assert "review_required: false" in text
|
||||
assert "model: test-model" in text
|
||||
assert "LLM response metadata:" in text
|
||||
assert '"finish_reason": "stop"' in text
|
||||
assert "State Hub has loose ends." in text
|
||||
|
||||
|
||||
@@ -113,6 +119,10 @@ def test_state_hub_progress_sink_posts(monkeypatch) -> None:
|
||||
assert posts[0]["json"]["detail"]["activity_core_run_id"] == payload_run_id()
|
||||
assert posts[0]["json"]["detail"]["output_validated"] is True
|
||||
assert posts[0]["json"]["detail"]["review_required"] is False
|
||||
assert posts[0]["json"]["detail"]["llm_response_metadata"] == {
|
||||
"finish_reason": "stop",
|
||||
"usage": {"output_tokens": 50},
|
||||
}
|
||||
|
||||
|
||||
def test_state_hub_progress_includes_prior_working_memory_path(
|
||||
|
||||
Reference in New Issue
Block a user