feat(railiance): adopt state-hub edge relay beachhead for WP-0015
Deploy actcore-statehub-edge-relay, point STATE_HUB_URL at it, retire the bespoke state-hub bridge, and accept edge-relay queued write receipts in report and ops evidence sinks.
This commit is contained in:
parent
1a0636845a
commit
cdbe4de2bf
13 changed files with 256 additions and 136 deletions
|
|
@ -18,8 +18,10 @@ Supported queries:
|
|||
- 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
|
||||
within a single workflow run.
|
||||
When STATE_HUB_URL points at the state-hub edge relay, allowlisted GET reads may
|
||||
be served from a stale local cache during upstream outages (`X-StateHub-Edge-Cache:
|
||||
stale`). activity-core treats those as ordinary successful reads so workflows can
|
||||
continue with last-known hub state.
|
||||
Config: STATE_HUB_URL env var (default: http://127.0.0.1:8000).
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,11 @@ from typing import Any
|
|||
import httpx
|
||||
|
||||
from activity_core.context_resolvers.ops_inventory import _sanitize_url
|
||||
from activity_core.state_hub_write import apply_progress_scope_fields, idempotency_headers
|
||||
from activity_core.state_hub_write import (
|
||||
apply_progress_scope_fields,
|
||||
idempotency_headers,
|
||||
parse_state_hub_write_response,
|
||||
)
|
||||
|
||||
_DEFAULT_STATE_HUB_URL = "http://127.0.0.1:8000"
|
||||
_INTER_HUB_SINK_TYPES = {
|
||||
|
|
@ -161,8 +165,16 @@ def _post_state_hub_progress(
|
|||
headers=idempotency_headers(run_id, context_key, event_type),
|
||||
timeout=float(sink.get("timeout_seconds", 10.0)),
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
data = parse_state_hub_write_response(resp)
|
||||
if data.get("queued"):
|
||||
return {
|
||||
"type": "state-hub-progress",
|
||||
"status": "queued",
|
||||
"event_type": event_type,
|
||||
"outbox_id": data.get("outbox_id"),
|
||||
"idempotency_key": data.get("idempotency_key") or idempotency_key,
|
||||
"context_key": context_key,
|
||||
}
|
||||
return {
|
||||
"type": "state-hub-progress",
|
||||
"status": "posted",
|
||||
|
|
|
|||
|
|
@ -16,7 +16,11 @@ from activity_core.runtime_paths import (
|
|||
custodian_repo_root,
|
||||
resolve_runtime_path,
|
||||
)
|
||||
from activity_core.state_hub_write import apply_progress_scope_fields, idempotency_headers
|
||||
from activity_core.state_hub_write import (
|
||||
apply_progress_scope_fields,
|
||||
idempotency_headers,
|
||||
parse_state_hub_write_response,
|
||||
)
|
||||
|
||||
_DEFAULT_STATE_HUB_URL = "http://127.0.0.1:8000"
|
||||
|
||||
|
|
@ -154,8 +158,15 @@ def _post_state_hub_progress(
|
|||
headers=idempotency_headers(run_id, instruction_id, event_type),
|
||||
timeout=float(sink.get("timeout_seconds", 10.0)),
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
data = parse_state_hub_write_response(resp)
|
||||
if data.get("queued"):
|
||||
return {
|
||||
"type": "state-hub-progress",
|
||||
"status": "queued",
|
||||
"event_type": event_type,
|
||||
"outbox_id": data.get("outbox_id"),
|
||||
"idempotency_key": data.get("idempotency_key"),
|
||||
}
|
||||
return {
|
||||
"type": "state-hub-progress",
|
||||
"status": "posted",
|
||||
|
|
|
|||
|
|
@ -24,7 +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
|
||||
from activity_core.state_hub_write import idempotency_headers, parse_state_hub_write_response
|
||||
|
||||
_DEFAULT_STATE_HUB_URL = "http://127.0.0.1:8000"
|
||||
|
||||
|
|
@ -187,8 +187,14 @@ def post_missed_fire_alert(
|
|||
headers=idempotency_headers("schedule_miss", health.activity_id, last_fired),
|
||||
timeout=timeout_seconds,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
data = parse_state_hub_write_response(resp)
|
||||
if data.get("queued"):
|
||||
return {
|
||||
"type": "schedule-miss-alert",
|
||||
"status": "queued",
|
||||
"outbox_id": data.get("outbox_id"),
|
||||
"idempotency_key": data.get("idempotency_key"),
|
||||
}
|
||||
return {
|
||||
"type": "schedule-miss-alert",
|
||||
"status": "posted",
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@ write's identity. The guarantee lives on the write itself and does **not** depen
|
|||
on a live dedup read, so it holds even when the beachhead is serving offline.
|
||||
|
||||
activity-core does not implement the queue/cache (that is state-hub's beachhead);
|
||||
it only emits the key so the beachhead / State Hub can dedup on flush. The header
|
||||
passes untouched through the existing ``actcore-state-hub-bridge`` proxy and is
|
||||
ignored by State Hub versions that do not yet honour it.
|
||||
it only emits the key so the beachhead / State Hub can dedup on flush.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
IDEMPOTENCY_HEADER = "Idempotency-Key"
|
||||
|
||||
|
||||
|
|
@ -48,3 +48,13 @@ def idempotency_key(*parts: str | None) -> str:
|
|||
def idempotency_headers(*parts: str | None) -> dict[str, str]:
|
||||
"""Return the header dict to attach to a State Hub write."""
|
||||
return {IDEMPOTENCY_HEADER: idempotency_key(*parts)}
|
||||
|
||||
|
||||
def parse_state_hub_write_response(resp: httpx.Response) -> dict[str, Any]:
|
||||
"""Normalize a State Hub write response, including edge-relay queued receipts."""
|
||||
if resp.status_code == 202:
|
||||
data = resp.json()
|
||||
if data.get("queued"):
|
||||
return data
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue