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>
74 lines
3.8 KiB
Python
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"
|