From f41cf149779d8c793f34c9285fdf10c44fe80074 Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 9 Jul 2026 09:29:41 +0200 Subject: [PATCH] feat(legacy-meter): schedule 8h capture activity on railiance01 Add legacy-meter-8h-capture cron (0 */8 * * * UTC), pass hours through the state-hub resolver, and label progress summaries by window size. --- .../legacy-meter-8h-capture.md | 31 ++++++++++++++++ k8s/railiance/20-runtime.yaml | 35 +++++++++++++++++++ .../context_resolvers/state_hub.py | 11 +++++- src/activity_core/ops_evidence_sinks.py | 3 +- tests/test_state_hub_context_resolver.py | 26 ++++++++++++++ 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 activity-definitions/legacy-meter-8h-capture.md diff --git a/activity-definitions/legacy-meter-8h-capture.md b/activity-definitions/legacy-meter-8h-capture.md new file mode 100644 index 0000000..5e68814 --- /dev/null +++ b/activity-definitions/legacy-meter-8h-capture.md @@ -0,0 +1,31 @@ +--- +id: legacy-meter-8h-capture +name: Legacy-Meter 8h Capture +enabled: true +owner: custodian-agent +governance: custodian +status: active +trigger: + type: cron + cron_expression: "0 */8 * * *" + timezone: UTC + misfire_policy: skip +context_sources: + - type: state-hub + query: legacy_meter_weekly_review + required: true + params: + hours: 8 + evidence_sinks: + - type: state-hub-progress + event_type: legacy_meter_8h_capture + author: activity-core + workplan_id: 44e2e123-9934-4b5e-8b50-1bac548c5b70 + bind_to: context.legacy_meter_weekly_review +--- + +# Legacy-Meter 8h Capture + +Runs every eight hours UTC. Fetches State Hub `/legacy-meter/weekly-review?hours=8` +and posts `legacy_meter_8h_capture` progress for STATE-WP-0073 / STATE-WP-0070 +seven-window retirement gating. \ No newline at end of file diff --git a/k8s/railiance/20-runtime.yaml b/k8s/railiance/20-runtime.yaml index 1a6e490..84d971c 100644 --- a/k8s/railiance/20-runtime.yaml +++ b/k8s/railiance/20-runtime.yaml @@ -328,6 +328,41 @@ data: Railiance projection of the Custodian-owned definition in `activity-definitions/weekly-legacy-meter-review.md`. Posts `legacy_meter_weekly_review` progress for STATE-WP-0069 retirement gating. + legacy-meter-8h-capture.md: | + --- + id: legacy-meter-8h-capture + name: Legacy-Meter 8h Capture + type: activity-definition + version: "1.0" + enabled: true + owner: custodian + governance: custodian + status: active + created: "2026-07-09" + trigger: + type: cron + cron_expression: "0 */8 * * *" + timezone: UTC + misfire_policy: skip + context_sources: + - type: state-hub + query: legacy_meter_weekly_review + required: true + params: + hours: 8 + evidence_sinks: + - type: state-hub-progress + event_type: legacy_meter_8h_capture + author: activity-core + workplan_id: 44e2e123-9934-4b5e-8b50-1bac548c5b70 + bind_to: context.legacy_meter_weekly_review + --- + + # ActivityDefinition: Legacy-Meter 8h Capture + + Railiance projection of the Custodian-owned definition in + `activity-definitions/legacy-meter-8h-capture.md`. Posts + `legacy_meter_8h_capture` progress every 8h for STATE-WP-0073 retirement gating. phase5-stabilization-closeout.md: | --- id: "e7d2b5a8-4c1f-4e9a-b6d3-8f2a1c4e6b09" diff --git a/src/activity_core/context_resolvers/state_hub.py b/src/activity_core/context_resolvers/state_hub.py index 396f4c6..e3f8180 100644 --- a/src/activity_core/context_resolvers/state_hub.py +++ b/src/activity_core/context_resolvers/state_hub.py @@ -178,15 +178,24 @@ def _legacy_meter_weekly_review(params: dict[str, Any]) -> dict[str, Any]: """Fetch State Hub legacy-meter weekly review for activity-core scheduling.""" query_params = { key: params[key] - for key in ("days", "window_start", "window_end") + for key in ("days", "hours", "window_start", "window_end") if key in params } raw = _fetch_json("/legacy-meter/weekly-review", query_params or None) interfaces = raw.get("interfaces") or [] candidates = raw.get("retirement_candidates") or [] window_calls = sum(int((item.get("window") or {}).get("calls") or 0) for item in interfaces) + window_hours = params.get("hours") + window_days = params.get("days") + if window_hours is not None: + window_label = f"{window_hours}h" + elif window_days is not None: + window_label = f"{window_days}d" + else: + window_label = "7d" return { "kind": "legacy_meter_weekly_review", + "window_label": window_label, "generated_at": raw.get("generated_at"), "window_start": raw.get("window_start"), "window_end": raw.get("window_end"), diff --git a/src/activity_core/ops_evidence_sinks.py b/src/activity_core/ops_evidence_sinks.py index b0dd28c..837f25b 100644 --- a/src/activity_core/ops_evidence_sinks.py +++ b/src/activity_core/ops_evidence_sinks.py @@ -538,8 +538,9 @@ def _legacy_meter_summary_text(result: dict[str, Any]) -> str: candidates = result.get("retirement_candidate_count", 0) calls = result.get("window_legacy_calls", 0) interfaces = result.get("interface_count", 0) + window_label = result.get("window_label", "7d") return ( - f"Legacy-meter weekly review: {candidates} retirement candidate(s) of " + f"Legacy-meter {window_label} review: {candidates} retirement candidate(s) of " f"{interfaces} registered interface(s); {calls} legacy call(s) in window" ) diff --git a/tests/test_state_hub_context_resolver.py b/tests/test_state_hub_context_resolver.py index 313d7db..e6557e3 100644 --- a/tests/test_state_hub_context_resolver.py +++ b/tests/test_state_hub_context_resolver.py @@ -768,6 +768,32 @@ def test_legacy_meter_weekly_review_summarises_candidates(monkeypatch) -> None: assert result["retirement_candidates"][0]["interface_key"] == "mcp:create_workstream" +def test_legacy_meter_weekly_review_passes_hours_param(monkeypatch) -> None: + seen: dict[str, Any] = {} + + def fake_get(url: str, **kwargs: Any) -> DummyResponse: + seen["params"] = kwargs.get("params") + return DummyResponse( + { + "generated_at": "2026-07-09T08:00:00+00:00", + "window_start": "2026-07-09T00:00:00+00:00", + "window_end": "2026-07-09T08:00:00+00:00", + "interfaces": [], + "retirement_candidates": [], + } + ) + + monkeypatch.setenv("STATE_HUB_URL", "http://state-hub.test") + monkeypatch.setattr(httpx, "get", fake_get) + + result = StateHubContextResolver().resolve( + "legacy_meter_weekly_review", None, {"hours": 8} + ) + + assert seen["params"] == {"hours": 8} + assert result["window_label"] == "8h" + + def test_phase5_stabilization_check_passes(monkeypatch) -> None: payloads = { "/state/health": {"status": "ok", "db": "connected"},