STATE-WP-0069: schedule weekly legacy-meter review monitoring
Add legacy_meter_weekly_review state-hub resolver query, weekly activity definition (Mon 08:30 Berlin), Railiance runtime projection, and progress sink posting for STATE-WP-0069 retirement gating.
This commit is contained in:
parent
bad91099e8
commit
efa361541b
8 changed files with 267 additions and 1 deletions
35
activity-definitions/weekly-legacy-meter-review.md
Normal file
35
activity-definitions/weekly-legacy-meter-review.md
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
---
|
||||||
|
id: weekly-legacy-meter-review
|
||||||
|
name: Weekly Legacy-Meter Review
|
||||||
|
enabled: true
|
||||||
|
owner: custodian-agent
|
||||||
|
governance: custodian
|
||||||
|
status: active
|
||||||
|
trigger:
|
||||||
|
type: cron
|
||||||
|
cron_expression: "30 8 * * 1"
|
||||||
|
timezone: Europe/Berlin
|
||||||
|
misfire_policy: skip
|
||||||
|
context_sources:
|
||||||
|
- type: state-hub
|
||||||
|
query: legacy_meter_weekly_review
|
||||||
|
required: true
|
||||||
|
params:
|
||||||
|
days: 7
|
||||||
|
evidence_sinks:
|
||||||
|
- type: state-hub-progress
|
||||||
|
event_type: legacy_meter_weekly_review
|
||||||
|
author: activity-core
|
||||||
|
workplan_id: 923bb94a-d16c-422c-b81e-16328bd7b60c
|
||||||
|
bind_to: context.legacy_meter_weekly_review
|
||||||
|
---
|
||||||
|
|
||||||
|
# Weekly Legacy-Meter Review
|
||||||
|
|
||||||
|
Runs every Monday at 08:30 Berlin time. Fetches State Hub
|
||||||
|
`/legacy-meter/weekly-review` and posts a compact `legacy_meter_weekly_review`
|
||||||
|
progress event for STATE-WP-0069 retirement gating.
|
||||||
|
|
||||||
|
The run is observational: it records retirement candidates and window usage
|
||||||
|
counts. Interface removal remains gated on legacy-meter evidence per
|
||||||
|
`docs/workplan-terminology-transition.md`.
|
||||||
|
|
@ -293,6 +293,41 @@ data:
|
||||||
---
|
---
|
||||||
|
|
||||||
# ActivityDefinition: Phase 5 Stabilization Daily Check
|
# ActivityDefinition: Phase 5 Stabilization Daily Check
|
||||||
|
weekly-legacy-meter-review.md: |
|
||||||
|
---
|
||||||
|
id: weekly-legacy-meter-review
|
||||||
|
name: Weekly Legacy-Meter Review
|
||||||
|
type: activity-definition
|
||||||
|
version: "1.0"
|
||||||
|
enabled: true
|
||||||
|
owner: custodian
|
||||||
|
governance: custodian
|
||||||
|
status: active
|
||||||
|
created: "2026-07-08"
|
||||||
|
trigger:
|
||||||
|
type: cron
|
||||||
|
cron_expression: "30 8 * * 1"
|
||||||
|
timezone: Europe/Berlin
|
||||||
|
misfire_policy: skip
|
||||||
|
context_sources:
|
||||||
|
- type: state-hub
|
||||||
|
query: legacy_meter_weekly_review
|
||||||
|
required: true
|
||||||
|
params:
|
||||||
|
days: 7
|
||||||
|
evidence_sinks:
|
||||||
|
- type: state-hub-progress
|
||||||
|
event_type: legacy_meter_weekly_review
|
||||||
|
author: activity-core
|
||||||
|
workplan_id: 923bb94a-d16c-422c-b81e-16328bd7b60c
|
||||||
|
bind_to: context.legacy_meter_weekly_review
|
||||||
|
---
|
||||||
|
|
||||||
|
# ActivityDefinition: Weekly Legacy-Meter Review
|
||||||
|
|
||||||
|
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.
|
||||||
phase5-stabilization-closeout.md: |
|
phase5-stabilization-closeout.md: |
|
||||||
---
|
---
|
||||||
id: "e7d2b5a8-4c1f-4e9a-b6d3-8f2a1c4e6b09"
|
id: "e7d2b5a8-4c1f-4e9a-b6d3-8f2a1c4e6b09"
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,12 @@ def progress_event_types(args: argparse.Namespace) -> list[str | None]:
|
||||||
raw = args.progress_event_type
|
raw = args.progress_event_type
|
||||||
if raw is None:
|
if raw is None:
|
||||||
env_value = os.environ.get("AUTOMATION_STATUS_PROGRESS_EVENT_TYPES")
|
env_value = os.environ.get("AUTOMATION_STATUS_PROGRESS_EVENT_TYPES")
|
||||||
raw = env_value.split(",") if env_value else ["daily_triage", "schedule_miss", "ops_inventory_probe"]
|
raw = env_value.split(",") if env_value else [
|
||||||
|
"daily_triage",
|
||||||
|
"schedule_miss",
|
||||||
|
"ops_inventory_probe",
|
||||||
|
"legacy_meter_weekly_review",
|
||||||
|
]
|
||||||
values = [item.strip() for item in raw if item and item.strip()]
|
values = [item.strip() for item in raw if item and item.strip()]
|
||||||
return [None if item == "all" else item for item in values]
|
return [None if item == "all" else item for item in values]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ Supported queries:
|
||||||
- recently_on_scope_hourly: POST {STATE_HUB_URL}/recently-on-scope/hourly
|
- recently_on_scope_hourly: POST {STATE_HUB_URL}/recently-on-scope/hourly
|
||||||
- consistency_sweep_remote_all: POST {STATE_HUB_URL}/consistency/sweep/remote-all
|
- consistency_sweep_remote_all: POST {STATE_HUB_URL}/consistency/sweep/remote-all
|
||||||
- phase5_stabilization_check: hub-visible Phase 5 stabilization gates
|
- phase5_stabilization_check: hub-visible Phase 5 stabilization gates
|
||||||
|
- legacy_meter_weekly_review: GET {STATE_HUB_URL}/legacy-meter/weekly-review
|
||||||
|
|
||||||
No caching — state hub data is live operational state and must not be stale
|
No caching — state hub data is live operational state and must not be stale
|
||||||
within a single workflow run.
|
within a single workflow run.
|
||||||
|
|
@ -149,6 +150,8 @@ class StateHubContextResolver(ContextResolver):
|
||||||
return _validate_consistency_sweep_remote_all(result)
|
return _validate_consistency_sweep_remote_all(result)
|
||||||
if query == "phase5_stabilization_check":
|
if query == "phase5_stabilization_check":
|
||||||
return _phase5_stabilization_check(params)
|
return _phase5_stabilization_check(params)
|
||||||
|
if query == "legacy_meter_weekly_review":
|
||||||
|
return _legacy_meter_weekly_review(params)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -169,6 +172,42 @@ def _utc_now() -> datetime:
|
||||||
return datetime.now(timezone.utc)
|
return datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
|
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")
|
||||||
|
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)
|
||||||
|
return {
|
||||||
|
"kind": "legacy_meter_weekly_review",
|
||||||
|
"generated_at": raw.get("generated_at"),
|
||||||
|
"window_start": raw.get("window_start"),
|
||||||
|
"window_end": raw.get("window_end"),
|
||||||
|
"activity_core_handoff": raw.get("activity_core_handoff"),
|
||||||
|
"interface_count": len(interfaces),
|
||||||
|
"retirement_candidate_count": len(candidates),
|
||||||
|
"window_legacy_calls": window_calls,
|
||||||
|
"retirement_candidates": [
|
||||||
|
{
|
||||||
|
"interface_key": (item.get("interface") or {}).get("interface_key"),
|
||||||
|
"interface_kind": (item.get("interface") or {}).get("interface_kind"),
|
||||||
|
"replacement_ref": (item.get("interface") or {}).get("replacement_ref"),
|
||||||
|
"retirement_reason": item.get("retirement_reason"),
|
||||||
|
}
|
||||||
|
for item in candidates[:25]
|
||||||
|
],
|
||||||
|
"checks": {
|
||||||
|
"has_retirement_candidates": bool(candidates),
|
||||||
|
},
|
||||||
|
"overall_pass": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _phase5_stabilization_check(params: dict[str, Any]) -> dict[str, Any]:
|
def _phase5_stabilization_check(params: dict[str, Any]) -> dict[str, Any]:
|
||||||
closeout = bool(params.get("closeout"))
|
closeout = bool(params.get("closeout"))
|
||||||
now = _utc_now()
|
now = _utc_now()
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,10 @@ def _post_state_hub_progress(
|
||||||
compact = probe_result
|
compact = probe_result
|
||||||
summary = _core_hub_stabilization_summary_text(probe_result)
|
summary = _core_hub_stabilization_summary_text(probe_result)
|
||||||
source_type = "core-hub"
|
source_type = "core-hub"
|
||||||
|
elif probe_result.get("kind") == "legacy_meter_weekly_review":
|
||||||
|
compact = probe_result
|
||||||
|
summary = _legacy_meter_summary_text(probe_result)
|
||||||
|
source_type = "state-hub"
|
||||||
elif probe_result.get("checks") is not None:
|
elif probe_result.get("checks") is not None:
|
||||||
compact = probe_result
|
compact = probe_result
|
||||||
summary = _phase5_summary_text(probe_result)
|
summary = _phase5_summary_text(probe_result)
|
||||||
|
|
@ -518,6 +522,16 @@ def _core_hub_stabilization_summary_text(result: dict[str, Any]) -> str:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
return (
|
||||||
|
f"Legacy-meter weekly review: {candidates} retirement candidate(s) of "
|
||||||
|
f"{interfaces} registered interface(s); {calls} legacy call(s) in window"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _phase5_summary_text(result: dict[str, Any]) -> str:
|
def _phase5_summary_text(result: dict[str, Any]) -> str:
|
||||||
checks = result.get("checks") or {}
|
checks = result.get("checks") or {}
|
||||||
totals = checks.get("totals") or {}
|
totals = checks.get("totals") or {}
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,64 @@ def test_state_hub_progress_sink_posts_compact_probe_summary(monkeypatch) -> Non
|
||||||
assert "token=secret" not in serialized
|
assert "token=secret" not in serialized
|
||||||
|
|
||||||
|
|
||||||
|
def test_legacy_meter_weekly_review_sink_posts_progress(monkeypatch) -> None:
|
||||||
|
posts: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
def fake_get(url: str, **kwargs: Any) -> DummyResponse:
|
||||||
|
return DummyResponse([])
|
||||||
|
|
||||||
|
def fake_post(url: str, **kwargs: Any) -> DummyResponse:
|
||||||
|
posts.append({"url": url, **kwargs})
|
||||||
|
return DummyResponse({"id": "progress-legacy-meter-1"})
|
||||||
|
|
||||||
|
monkeypatch.setattr(httpx, "get", fake_get)
|
||||||
|
monkeypatch.setattr(httpx, "post", fake_post)
|
||||||
|
|
||||||
|
persist_ops_inventory_evidence(
|
||||||
|
{
|
||||||
|
"activity_id": "weekly-legacy-meter-review",
|
||||||
|
"run_id": "12345678-aaaa-bbbb-cccc-123456789abc",
|
||||||
|
"scheduled_for": "2026-07-08T06:30:00+00:00",
|
||||||
|
"version_used": 1,
|
||||||
|
"context_sources": [
|
||||||
|
{
|
||||||
|
"type": "state-hub",
|
||||||
|
"query": "legacy_meter_weekly_review",
|
||||||
|
"bind_to": "context.legacy_meter_weekly_review",
|
||||||
|
"params": {
|
||||||
|
"days": 7,
|
||||||
|
"evidence_sinks": [
|
||||||
|
{
|
||||||
|
"type": "state-hub-progress",
|
||||||
|
"state_hub_url": "http://state-hub.test",
|
||||||
|
"event_type": "legacy_meter_weekly_review",
|
||||||
|
"workplan_id": "923bb94a-d16c-422c-b81e-16328bd7b60c",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"context": {
|
||||||
|
"legacy_meter_weekly_review": {
|
||||||
|
"kind": "legacy_meter_weekly_review",
|
||||||
|
"interface_count": 12,
|
||||||
|
"retirement_candidate_count": 2,
|
||||||
|
"window_legacy_calls": 5,
|
||||||
|
"retirement_candidates": [],
|
||||||
|
"checks": {"has_retirement_candidates": True},
|
||||||
|
"overall_pass": True,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
body = posts[0]["json"]
|
||||||
|
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"
|
||||||
|
|
||||||
|
|
||||||
def test_state_hub_progress_prefers_workplan_id(monkeypatch) -> None:
|
def test_state_hub_progress_prefers_workplan_id(monkeypatch) -> None:
|
||||||
posts: list[dict[str, Any]] = []
|
posts: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,24 @@ def test_external_configmap_projects_disabled_ops_probe_definition(tmp_path) ->
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_external_configmap_projects_weekly_legacy_meter_review(tmp_path) -> None:
|
||||||
|
config = _by_kind_name("ConfigMap", "actcore-external-activity-definitions")
|
||||||
|
raw_definition = config["data"]["weekly-legacy-meter-review.md"]
|
||||||
|
definition_path = tmp_path / "weekly-legacy-meter-review.md"
|
||||||
|
definition_path.write_text(raw_definition, encoding="utf-8")
|
||||||
|
|
||||||
|
definition = parse_file(definition_path)
|
||||||
|
|
||||||
|
assert definition.id == "weekly-legacy-meter-review"
|
||||||
|
assert definition.enabled is True
|
||||||
|
assert definition.trigger_config["cron_expression"] == "30 8 * * 1"
|
||||||
|
source = definition.context_sources[0]
|
||||||
|
assert source["query"] == "legacy_meter_weekly_review"
|
||||||
|
assert source["params"]["evidence_sinks"][0]["event_type"] == (
|
||||||
|
"legacy_meter_weekly_review"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_external_configmap_projects_enabled_daily_wsjf_definition(tmp_path) -> None:
|
def test_external_configmap_projects_enabled_daily_wsjf_definition(tmp_path) -> None:
|
||||||
config = _by_kind_name("ConfigMap", "actcore-external-activity-definitions")
|
config = _by_kind_name("ConfigMap", "actcore-external-activity-definitions")
|
||||||
raw_definition = config["data"]["daily-statehub-wsjf-triage.md"]
|
raw_definition = config["data"]["daily-statehub-wsjf-triage.md"]
|
||||||
|
|
|
||||||
|
|
@ -706,6 +706,68 @@ def test_daily_triage_digest_is_curated_scalar_json(monkeypatch) -> None:
|
||||||
assert digest["ranked_suggestions"][0]["origin_ref"] == "issue-core-ingestion-api-key"
|
assert digest["ranked_suggestions"][0]["origin_ref"] == "issue-core-ingestion-api-key"
|
||||||
|
|
||||||
|
|
||||||
|
def test_legacy_meter_weekly_review_summarises_candidates(monkeypatch) -> None:
|
||||||
|
payloads = {
|
||||||
|
"/legacy-meter/weekly-review": {
|
||||||
|
"generated_at": "2026-07-08T08:30:00+00:00",
|
||||||
|
"window_start": "2026-07-01T08:30:00+00:00",
|
||||||
|
"window_end": "2026-07-08T08:30:00+00:00",
|
||||||
|
"activity_core_handoff": {
|
||||||
|
"activity_id": "statehub-legacy-interface-review",
|
||||||
|
"scheduler_owner": "activity-core",
|
||||||
|
},
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"interface": {
|
||||||
|
"interface_key": "rest_api:GET /workstreams/",
|
||||||
|
"interface_kind": "rest_api",
|
||||||
|
"replacement_ref": "GET /workplans/",
|
||||||
|
},
|
||||||
|
"window": {"calls": 2},
|
||||||
|
"retirement_candidate": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"interface": {
|
||||||
|
"interface_key": "mcp:create_workstream",
|
||||||
|
"interface_kind": "mcp_tool",
|
||||||
|
"replacement_ref": "create_workplan",
|
||||||
|
},
|
||||||
|
"window": {"calls": 0},
|
||||||
|
"retirement_candidate": True,
|
||||||
|
"retirement_reason": "no measured usage in review window",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"retirement_candidates": [
|
||||||
|
{
|
||||||
|
"interface": {
|
||||||
|
"interface_key": "mcp:create_workstream",
|
||||||
|
"interface_kind": "mcp_tool",
|
||||||
|
"replacement_ref": "create_workplan",
|
||||||
|
},
|
||||||
|
"window": {"calls": 0},
|
||||||
|
"retirement_candidate": True,
|
||||||
|
"retirement_reason": "no measured usage in review window",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def fake_get(url: str, **kwargs: Any) -> DummyResponse:
|
||||||
|
path = url.replace("http://state-hub.test", "")
|
||||||
|
return DummyResponse(payloads.get(path, {}))
|
||||||
|
|
||||||
|
monkeypatch.setenv("STATE_HUB_URL", "http://state-hub.test")
|
||||||
|
monkeypatch.setattr(httpx, "get", fake_get)
|
||||||
|
|
||||||
|
result = StateHubContextResolver().resolve("legacy_meter_weekly_review", None, {"days": 7})
|
||||||
|
|
||||||
|
assert result["kind"] == "legacy_meter_weekly_review"
|
||||||
|
assert result["interface_count"] == 2
|
||||||
|
assert result["retirement_candidate_count"] == 1
|
||||||
|
assert result["window_legacy_calls"] == 2
|
||||||
|
assert result["retirement_candidates"][0]["interface_key"] == "mcp:create_workstream"
|
||||||
|
|
||||||
|
|
||||||
def test_phase5_stabilization_check_passes(monkeypatch) -> None:
|
def test_phase5_stabilization_check_passes(monkeypatch) -> None:
|
||||||
payloads = {
|
payloads = {
|
||||||
"/state/health": {"status": "ok", "db": "connected"},
|
"/state/health": {"status": "ok", "db": "connected"},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue