Add Core Hub production stabilization scheduled checks
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Container Image / build-and-push (push) Successful in 35s

Introduce the core-hub context resolver stabilization_check query, State Hub
progress summaries, activity-definition projections, and unit tests for the
CORE-WP-0007 post-cutover window.
This commit is contained in:
tegwick 2026-07-08 00:27:12 +02:00
parent c253139ee3
commit 151512a8fa
5 changed files with 383 additions and 1 deletions

View file

@ -284,6 +284,79 @@ data:
---
# ActivityDefinition: Phase 5 Stabilization Closeout Check
core-hub-stabilization-daily.md: |
---
id: "b4c8e2f1-6a3d-4e5b-9f0c-2d7e8a1b3c4d"
name: "Core Hub Stabilization Daily Check"
type: activity-definition
version: "1.0"
enabled: true
owner: core-hub
governance: core-hub
status: active
created: "2026-07-07"
trigger:
type: cron
cron_expression: "0 9 * * *"
timezone: Europe/Berlin
misfire_policy: skip
context_sources:
- type: core-hub
query: stabilization_check
required: true
params:
source: activity-core
closeout: false
base_url: "https://hub.coulomb.social"
window_start: "2026-07-03T00:00:00+00:00"
window_end: "2026-07-10T17:35:00+00:00"
min_widget_types: 26
evidence_sinks:
- type: state-hub-progress
event_type: core_hub_stabilization_check
author: activity-core
workstream_id: a8d66822-e435-4b1e-ad81-37a298d1795e
task_id: 16eb7ce3-b574-4667-a189-c14ff5d0502b
bind_to: context.core_hub_stabilization_check
---
# ActivityDefinition: Core Hub Stabilization Daily Check
core-hub-stabilization-closeout.md: |
---
id: "c5d9f3a2-7b4e-5f6c-0a1d-3e8f9b2c4d5e"
name: "Core Hub Stabilization Closeout Check"
type: activity-definition
version: "1.0"
enabled: true
owner: core-hub
governance: core-hub
status: active
created: "2026-07-07"
trigger:
type: scheduled
at: "2026-07-10T17:35:00+00:00"
timezone: UTC
context_sources:
- type: core-hub
query: stabilization_check
required: true
params:
source: activity-core
closeout: true
base_url: "https://hub.coulomb.social"
window_start: "2026-07-03T00:00:00+00:00"
window_end: "2026-07-10T17:35:00+00:00"
min_widget_types: 26
evidence_sinks:
- type: state-hub-progress
event_type: core_hub_stabilization_closeout
author: activity-core
workstream_id: a8d66822-e435-4b1e-ad81-37a298d1795e
task_id: 16eb7ce3-b574-4667-a189-c14ff5d0502b
bind_to: context.core_hub_stabilization_check
---
# ActivityDefinition: Core Hub Stabilization Closeout Check
ops-service-inventory-probes.md: |
---
id: "40d15a87-7ff6-4d8e-992c-37df15f95110"

View file

@ -1,4 +1,5 @@
from activity_core.context_resolvers import ( # noqa: F401
core_hub,
event_payload,
kaizen,
ops_inventory,

View file

@ -0,0 +1,193 @@
"""Core Hub production stabilization context adapter.
Registered as source type ``core-hub``.
Supported queries:
- stabilization_check: public production surface gates for the post-cutover
window before CORE-WP-0007-T02 operator sign-off.
"""
from __future__ import annotations
import os
from datetime import datetime, timezone
from typing import Any
import httpx
from activity_core.context_resolvers.base import CONTEXT_RESOLVER_REGISTRY, ContextResolver
from activity_core.context_resolvers.state_hub import _parse_iso_datetime, _utc_now
_DEFAULT_BASE_URL = "https://hub.coulomb.social"
_DEFAULT_TIMEOUT_SECONDS = 15.0
_PUBLIC_CHECKS: tuple[tuple[str, int], ...] = (
("/healthz", 200),
("/readyz", 200),
("/api/v2/widget-types", 200),
("/api/v2/hubs", 401),
)
class CoreHubContextResolver(ContextResolver):
"""Resolve lightweight Core Hub stabilization checks over public HTTP."""
def resolve(self, query: str, event: Any, params: dict[str, Any]) -> dict[str, Any]:
if query == "stabilization_check":
return _core_hub_stabilization_check(params)
return {}
def _bounded_int(
value: Any,
*,
default: int,
minimum: int,
maximum: int,
) -> int:
try:
parsed = int(value)
except (TypeError, ValueError):
return default
return max(minimum, min(maximum, parsed))
def _probe_status(
client: httpx.Client,
path: str,
expected_status: int,
) -> dict[str, Any]:
try:
response = client.get(path)
except Exception as exc: # pragma: no cover - network dependent
return {
"pass": False,
"path": path,
"expected_status": expected_status,
"error": str(exc),
}
return {
"pass": response.status_code == expected_status,
"path": path,
"expected_status": expected_status,
"status_code": response.status_code,
}
def _widget_type_count(client: httpx.Client, minimum: int) -> dict[str, Any]:
try:
response = client.get("/api/v2/widget-types")
except Exception as exc: # pragma: no cover - network dependent
return {
"pass": False,
"minimum": minimum,
"count": None,
"error": str(exc),
}
if response.status_code != 200:
return {
"pass": False,
"minimum": minimum,
"count": None,
"status_code": response.status_code,
}
try:
payload = response.json()
except ValueError as exc:
return {
"pass": False,
"minimum": minimum,
"count": None,
"error": f"invalid JSON: {exc}",
}
if isinstance(payload, list):
count = len(payload)
elif isinstance(payload, dict) and isinstance(payload.get("data"), list):
count = len(payload["data"])
else:
count = 0
return {
"pass": count >= minimum,
"minimum": minimum,
"count": count,
}
def _core_hub_stabilization_check(params: dict[str, Any]) -> dict[str, Any]:
closeout = bool(params.get("closeout"))
now = _utc_now()
window_start = _parse_iso_datetime(
params.get("window_start", "2026-07-03T00:00:00+00:00")
)
window_end = _parse_iso_datetime(
params.get("window_end", "2026-07-10T17:35:00+00:00")
)
within_window = bool(
window_start and window_end and window_start <= now <= window_end
)
if not within_window and not closeout:
return {
"kind": "core_hub_stabilization",
"skipped": True,
"reason": "outside_stabilization_window",
"closeout": closeout,
"overall_pass": True,
"operator_signoff_needed": False,
"base_url": str(params.get("base_url") or os.environ.get("CORE_HUB_BASE_URL") or _DEFAULT_BASE_URL),
"window": {
"start": window_start.isoformat() if window_start else None,
"end": window_end.isoformat() if window_end else None,
"within": within_window,
},
"checks": {},
}
base_url = str(
params.get("base_url")
or os.environ.get("CORE_HUB_BASE_URL")
or _DEFAULT_BASE_URL
).rstrip("/")
timeout_seconds = float(
params.get("timeout_seconds", _DEFAULT_TIMEOUT_SECONDS)
)
min_widget_types = _bounded_int(
params.get("min_widget_types", 26),
default=26,
minimum=1,
maximum=200,
)
checks: dict[str, Any] = {}
with httpx.Client(
base_url=base_url,
timeout=timeout_seconds,
headers={"Accept": "application/json", "User-Agent": "activity-core/core-hub-stabilization/0.1"},
follow_redirects=True,
) as client:
for path, expected_status in _PUBLIC_CHECKS:
key = path.strip("/").replace("/", "_").replace("-", "_") or "root"
checks[key] = _probe_status(client, path, expected_status)
checks["widget_types_count"] = _widget_type_count(client, min_widget_types)
overall_pass = all(section.get("pass") for section in checks.values())
result = {
"kind": "core_hub_stabilization",
"skipped": False,
"closeout": closeout,
"overall_pass": overall_pass,
"operator_signoff_needed": closeout and overall_pass,
"base_url": base_url,
"window": {
"start": window_start.isoformat() if window_start else None,
"end": window_end.isoformat() if window_end else None,
"within": within_window,
},
"checks": checks,
"source": str(params.get("source") or "activity-core"),
}
if closeout and not overall_pass:
raise RuntimeError(f"core hub stabilization closeout failed: {result}")
return result
CONTEXT_RESOLVER_REGISTRY["core-hub"] = CoreHubContextResolver

View file

@ -123,7 +123,11 @@ def _post_state_hub_progress(
"context_key": context_key,
}
if probe_result.get("checks") is not None:
if probe_result.get("kind") == "core_hub_stabilization":
compact = probe_result
summary = _core_hub_stabilization_summary_text(probe_result)
source_type = "core-hub"
elif probe_result.get("checks") is not None:
compact = probe_result
summary = _phase5_summary_text(probe_result)
source_type = "state-hub"
@ -503,6 +507,19 @@ def _compact_access_path(access_path: dict[str, Any]) -> dict[str, Any]:
}
def _core_hub_stabilization_summary_text(result: dict[str, Any]) -> str:
checks = result.get("checks") or {}
widget_types = checks.get("widget_types_count") or {}
status = "pass" if result.get("overall_pass") else "fail"
mode = "closeout" if result.get("closeout") else "daily"
return (
f"Core Hub stabilization {mode}: {status}; "
f"base={result.get('base_url', '?')}; "
f"widget_types={widget_types.get('count', '?')}/"
f"{widget_types.get('minimum', '?')}"
)
def _phase5_summary_text(result: dict[str, Any]) -> str:
checks = result.get("checks") or {}
totals = checks.get("totals") or {}

View file

@ -0,0 +1,98 @@
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any
import httpx
from activity_core.context_resolvers.core_hub import CoreHubContextResolver
class DummyResponse:
def __init__(self, payload: Any, status_code: int = 200) -> None:
self._payload = payload
self.status_code = status_code
self.text = str(payload)
def json(self) -> Any:
return self._payload
class DummyClient:
def __init__(self, responses: dict[str, tuple[Any, int]]) -> None:
self._responses = responses
def __enter__(self) -> DummyClient:
return self
def __exit__(self, *args: Any) -> None:
return None
def get(self, path: str) -> DummyResponse:
payload, status = self._responses.get(
path,
({}, 404),
)
return DummyResponse(payload, status)
def test_core_hub_stabilization_check_passes(monkeypatch) -> None:
widget_types = [{"slug": f"type-{index}"} for index in range(26)]
responses = {
"/healthz": ({}, 200),
"/readyz": ({}, 200),
"/api/v2/widget-types": (widget_types, 200),
"/api/v2/hubs": ({}, 401),
}
fixed_now = datetime(2026, 7, 7, 10, 0, tzinfo=timezone.utc)
monkeypatch.setattr(httpx, "Client", lambda **kwargs: DummyClient(responses))
monkeypatch.setattr(
"activity_core.context_resolvers.core_hub._utc_now",
lambda: fixed_now,
)
result = CoreHubContextResolver().resolve("stabilization_check", None, {})
assert result["overall_pass"] is True
assert result["skipped"] is False
assert result["checks"]["widget_types_count"]["pass"] is True
assert result["checks"]["api_v2_hubs"]["pass"] is True
def test_core_hub_stabilization_check_skips_outside_window(monkeypatch) -> None:
fixed_now = datetime(2026, 7, 12, 10, 0, tzinfo=timezone.utc)
monkeypatch.setattr(
"activity_core.context_resolvers.core_hub._utc_now",
lambda: fixed_now,
)
result = CoreHubContextResolver().resolve("stabilization_check", None, {})
assert result["skipped"] is True
assert result["reason"] == "outside_stabilization_window"
def test_core_hub_stabilization_closeout_raises_on_failure(monkeypatch) -> None:
responses = {
"/healthz": ({}, 500),
"/readyz": ({}, 200),
"/api/v2/widget-types": ([], 200),
"/api/v2/hubs": ({}, 401),
}
fixed_now = datetime(2026, 7, 7, 10, 0, tzinfo=timezone.utc)
monkeypatch.setattr(httpx, "Client", lambda **kwargs: DummyClient(responses))
monkeypatch.setattr(
"activity_core.context_resolvers.core_hub._utc_now",
lambda: fixed_now,
)
try:
CoreHubContextResolver().resolve(
"stabilization_check",
None,
{"closeout": True},
)
except RuntimeError as exc:
assert "closeout failed" in str(exc)
else:
raise AssertionError("expected closeout failure")