feat(ACTIVITY-WP-0014): idempotency-keyed State Hub writes (T05, in-repo part)

Add activity_core/state_hub_write: every State Hub write (report-sink,
ops-evidence, schedule-miss) now sends a stable Idempotency-Key header derived
from run_id:instruction_id:event_type. Makes writes safe to buffer/replay under
the future state-hub beachhead without duplicate progress/triage events. The
read-based _progress_exists dedup is now best-effort (returns False on connection
error instead of hard-failing), so the guarantee lives on the keyed write rather
than a live read. Tests + runbook note. Endpoint adoption / proxy retirement stays
blocked on the state-hub beachhead capability.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 21:38:46 +02:00
parent f90591c5f1
commit 88fe359385
7 changed files with 181 additions and 19 deletions

View File

@@ -24,6 +24,7 @@ from uuid import UUID
import httpx
from activity_core.schedule_manager import schedule_id
from activity_core.state_hub_write import idempotency_headers
_DEFAULT_STATE_HUB_URL = "http://127.0.0.1:8000"
@@ -176,7 +177,14 @@ def post_missed_fire_alert(
if workstream_id:
body["workstream_id"] = workstream_id
resp = httpx.post(f"{base_url}/progress/", json=body, timeout=timeout_seconds)
# Dedup repeated alerts for the same missed window (same schedule + last fire).
last_fired = health.last_fired_at.isoformat() if health.last_fired_at else "none"
resp = httpx.post(
f"{base_url}/progress/",
json=body,
headers=idempotency_headers("schedule_miss", health.activity_id, last_fired),
timeout=timeout_seconds,
)
resp.raise_for_status()
data = resp.json()
return {