the-custodian/tests/test_weekly_allocation_check.py
codex 9edd3151ac CUST-WP-0071-T05: hub delivery per weekly report and daily missed/failed-run check
The in-cluster runner posts one State Hub message (custodian-allocation-review
-> the-custodian) per newly retained week, records the message id in
receipts.jsonl, never reposts, and retries an undelivered week. New
weekly_allocation_check.py flags a failed latest receipt or a missing report
for the due ISO week (Monday 12:00 Europe/Berlin); security_flow_daily.py
notifies once per distinct flag set and stays inactive until a receipts
source is configured. Founder decision 2026-10-21 (T05 follow-up answers).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:40:12 +02:00

74 lines
3.8 KiB
Python

from datetime import datetime, timezone
import io
import json
from pathlib import Path
import sys
SCRIPTS = Path(__file__).resolve().parents[1] / "scripts"
sys.path.insert(0, str(SCRIPTS))
import security_flow_daily as daily # noqa: E402
import weekly_allocation_check as check # noqa: E402
UTC = timezone.utc
def test_due_week_turns_over_monday_noon_berlin():
assert check.due_week(datetime(2026, 10, 19, 9, 59, tzinfo=UTC)) == "2026-W42" # 11:59 CEST
assert check.due_week(datetime(2026, 10, 19, 10, 0, tzinfo=UTC)) == "2026-W43" # 12:00 CEST
assert check.due_week(datetime(2026, 11, 2, 10, 59, tzinfo=UTC)) == "2026-W44" # 11:59 CET
assert check.due_week(datetime(2026, 11, 2, 11, 0, tzinfo=UTC)) == "2026-W45"
assert check.due_week(datetime(2026, 10, 25, 20, 0, tzinfo=UTC)) == "2026-W43" # Sunday
def test_missing_failed_and_ok():
now = datetime(2026, 10, 19, 12, 0, tzinfo=UTC)
assert check.evaluate([], now)["flags"][0]["flag"] == "missing"
ok = [{"week": "2026-W43", "status": "retained", "run_at": "2026-10-19T06:00:30Z"}]
assert check.evaluate(ok, now)["status"] == "ok"
failed = ok + [{"week": "2026-W43", "status": "failed", "run_at": "2026-10-19T07:00:00Z", "error": "x"}]
assert [f["flag"] for f in check.evaluate(failed, now)["flags"]] == ["failed"]
only_failed = [failed[1]]
assert [f["flag"] for f in check.evaluate(only_failed, now)["flags"]] == ["failed", "missing"]
def test_inactive_without_source_and_file_and_hub_sources(tmp_path):
assert check.check(None, "http://hub")["status"] == "inactive"
receipts = tmp_path / "receipts.jsonl"
receipts.write_text(json.dumps({"week": "2026-W43", "status": "already-retained", "run_at": "a"}) + "\npartial{\n")
now = datetime(2026, 10, 20, tzinfo=UTC)
assert check.check(str(receipts), "http://hub", now)["status"] == "ok"
urls = []
def opener(url, timeout):
urls.append(url)
return io.BytesIO(json.dumps([{"body": json.dumps({"week": "2026-W42"}), "created_at": "z"}]).encode())
result = check.check("hub", "http://hub", now, opener)
assert "from_agent=custodian-allocation-review" in urls[0]
assert result["flags"][0] == {"flag": "missing", "week": "2026-W43",
"detail": "no retained weekly allocation report for the due ISO week"}
def test_daily_notice_is_deduplicated_and_cleared():
flagged = {"status": "flagged", "flags": [{"flag": "missing", "week": "2026-W43"}]}
state = {}
assert daily.allocation_notice(flagged, state) == {"WEEKLY_ALLOCATION": flagged}
state["allocation"] = flagged["flags"]
assert daily.allocation_notice(flagged, state) is None
assert daily.allocation_notice({"status": "ok"}, state) is None and "allocation" not in state
assert daily.allocation_notice({"status": "inactive"}, {}) is None
def test_daily_flow_posts_allocation_flag_once(tmp_path, monkeypatch):
report = {"READY": [], "UNBLOCKED": [], "ATTENDED": []}
receipts = tmp_path / "receipts.jsonl"
receipts.write_text(json.dumps({"week": "2020-W01", "status": "failed", "run_at": "x"}) + "\n")
monkeypatch.setattr(sys, "argv", ["d", "--apply", "--state-dir", str(tmp_path), "--allocation-receipts", str(receipts)])
monkeypatch.setattr(daily.subprocess, "run", lambda *a, **kw: None)
monkeypatch.setattr(daily.subprocess, "check_output", lambda *a, **kw: json.dumps(report))
sent = []
monkeypatch.setattr(daily, "post", lambda hub, to, payload, subject="Daily security flow": sent.append((to, subject, payload)) or "m1")
daily.main()
daily.main()
allocation = [s for s in sent if s[1] == "Weekly allocation assessment check"]
assert len(allocation) == 1 and allocation[0][0] == "the-custodian"
assert json.loads((tmp_path / "delivery.json").read_text())["allocation_message_id"] == "m1"