From 5a9292319e12d9726f3686d8b048ed623a088edb Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 9 Jul 2026 00:31:55 +0200 Subject: [PATCH] Stop dual-writing workstream_id on State Hub progress POSTs Progress scope helpers and schedule-miss alerts now send workplan_id only. Legacy workstream_id in sink config still maps to workplan_id on the wire. --- src/activity_core/schedule_health.py | 1 - src/activity_core/state_hub_write.py | 5 ++--- tests/test_ops_evidence_sinks.py | 6 +++--- tests/test_report_sinks.py | 2 +- tests/test_schedule_health.py | 2 +- tests/test_state_hub_write.py | 6 +++--- 6 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/activity_core/schedule_health.py b/src/activity_core/schedule_health.py index 77b2244..ecfc65a 100644 --- a/src/activity_core/schedule_health.py +++ b/src/activity_core/schedule_health.py @@ -178,7 +178,6 @@ def post_missed_fire_alert( scope_id = workplan_id or workstream_id if scope_id: body["workplan_id"] = scope_id - body["workstream_id"] = scope_id # 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" diff --git a/src/activity_core/state_hub_write.py b/src/activity_core/state_hub_write.py index a4eae34..746aefe 100644 --- a/src/activity_core/state_hub_write.py +++ b/src/activity_core/state_hub_write.py @@ -23,13 +23,12 @@ IDEMPOTENCY_HEADER = "Idempotency-Key" def apply_progress_scope_fields(body: dict[str, Any], source: dict[str, Any]) -> None: """Copy topic/workplan/task/decision scope from a sink config into a progress body. - Prefers ``workplan_id``; dual-writes ``workstream_id`` for wire-compat until - legacy-meter retires the alias. + Prefers ``workplan_id``; accepts legacy ``workstream_id`` in sink config only + as a fallback source (maps to ``workplan_id`` on the wire). """ workplan_id = source.get("workplan_id") or source.get("workstream_id") if workplan_id: body["workplan_id"] = workplan_id - body["workstream_id"] = workplan_id for key in ("topic_id", "task_id", "decision_id"): if source.get(key): body[key] = source[key] diff --git a/tests/test_ops_evidence_sinks.py b/tests/test_ops_evidence_sinks.py index cbadfd6..4a063bc 100644 --- a/tests/test_ops_evidence_sinks.py +++ b/tests/test_ops_evidence_sinks.py @@ -124,7 +124,7 @@ def test_state_hub_progress_sink_posts_compact_probe_summary(monkeypatch) -> Non body = posts[0]["json"] assert body["summary"] == "Ops inventory probe: 1 ok, 0 degraded, 0 down, 1 skipped" assert body["workplan_id"] == "workstream-1" - assert body["workstream_id"] == "workstream-1" + assert "workstream_id" not in body assert body["task_id"] == "task-1" assert body["detail"]["activity_core_run_id"] == _run_id() assert body["detail"]["idempotency_key"] == result[0]["idempotency_key"] @@ -192,7 +192,7 @@ def test_legacy_meter_weekly_review_sink_posts_progress(monkeypatch) -> None: assert body["event_type"] == "legacy_meter_weekly_review" assert "2 retirement candidate(s)" in body["summary"] assert body["workplan_id"] == "923bb94a-d16c-422c-b81e-16328bd7b60c" - assert body["workstream_id"] == "923bb94a-d16c-422c-b81e-16328bd7b60c" + assert "workstream_id" not in body def test_state_hub_progress_prefers_workplan_id(monkeypatch) -> None: @@ -221,7 +221,7 @@ def test_state_hub_progress_prefers_workplan_id(monkeypatch) -> None: body = posts[0]["json"] assert body["workplan_id"] == "workplan-1" - assert body["workstream_id"] == "workplan-1" + assert "workstream_id" not in body def test_core_hub_stabilization_sink_posts_progress(monkeypatch) -> None: diff --git a/tests/test_report_sinks.py b/tests/test_report_sinks.py index e2c77fa..953ddb4 100644 --- a/tests/test_report_sinks.py +++ b/tests/test_report_sinks.py @@ -117,7 +117,7 @@ def test_state_hub_progress_sink_posts(monkeypatch) -> None: ] assert posts[0]["url"] == "http://state-hub.test/progress/" assert posts[0]["json"]["workplan_id"] == "workstream-1" - assert posts[0]["json"]["workstream_id"] == "workstream-1" + assert "workstream_id" not in posts[0]["json"] 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 diff --git a/tests/test_schedule_health.py b/tests/test_schedule_health.py index 9df8d3e..66cb8e7 100644 --- a/tests/test_schedule_health.py +++ b/tests/test_schedule_health.py @@ -98,7 +98,7 @@ def test_post_missed_fire_alert_dual_writes_workplan_scope(monkeypatch) -> None: assert result["status"] == "posted" body = posts[0]["json"] assert body["workplan_id"] == "wp-123" - assert body["workstream_id"] == "wp-123" + assert "workstream_id" not in body def test_no_interval_and_no_fire_is_not_flagged() -> None: diff --git a/tests/test_state_hub_write.py b/tests/test_state_hub_write.py index bc89b77..8042286 100644 --- a/tests/test_state_hub_write.py +++ b/tests/test_state_hub_write.py @@ -41,15 +41,15 @@ def test_apply_progress_scope_prefers_workplan_id() -> None: {"workplan_id": "wp-1", "workstream_id": "ws-legacy", "topic_id": "topic-1"}, ) assert body["workplan_id"] == "wp-1" - assert body["workstream_id"] == "wp-1" + assert "workstream_id" not in body assert body["topic_id"] == "topic-1" -def test_apply_progress_scope_dual_writes_legacy_workstream_id() -> None: +def test_apply_progress_scope_maps_legacy_workstream_config_to_workplan_id() -> None: body: dict[str, str] = {} apply_progress_scope_fields(body, {"workstream_id": "ws-1"}) assert body["workplan_id"] == "ws-1" - assert body["workstream_id"] == "ws-1" + assert "workstream_id" not in body def test_distinct_identities_get_distinct_keys() -> None: