STATE-WP-0070: identity headers, archive 0069, phase-2 workplan
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Has been cancelled

Add X-StateHub-Component to fix-consistency and other State Hub REST callers for
legacy-meter attribution. Archive STATE-WP-0069; open STATE-WP-0070 for
meter-gated phase-2 retirement. Task POST bodies use workplan_id only.
This commit is contained in:
tegwick 2026-07-09 00:39:09 +02:00
parent 2f06751d4f
commit bb6bec9f10
10 changed files with 198 additions and 17 deletions

View file

@ -32,13 +32,15 @@ except Exception: # pragma: no cover — event publishing is optional
publish_event = None # type: ignore[assignment]
shutdown_publisher = None # type: ignore[assignment]
API = "http://127.0.0.1:8000"
API = os.environ.get("API_BASE", "http://127.0.0.1:8000").rstrip("/")
_LEGACY_METER_HEADERS = {"X-StateHub-Component": "state-hub.cleanup-stale-tasks"}
STALE_STATUSES = set(OPEN_TASK_STATUSES)
CLOSED_WS_STATUS = set(CLOSED_WORKSTREAM_STATUSES)
def get(path: str) -> list | dict:
with urllib.request.urlopen(f"{API}{path}") as r:
req = urllib.request.Request(f"{API}{path}", headers=_LEGACY_METER_HEADERS)
with urllib.request.urlopen(req) as r:
return json.loads(r.read())
@ -49,7 +51,7 @@ def _request(method: str, url: str, payload: dict) -> dict:
req = urllib.request.Request(
url,
data=data,
headers={"Content-Type": "application/json"},
headers={"Content-Type": "application/json", **_LEGACY_METER_HEADERS},
method=method,
)
try:

View file

@ -122,6 +122,13 @@ except ImportError:
# Constants
# ---------------------------------------------------------------------------
_LEGACY_METER_COMPONENT = "state-hub.fix-consistency"
def _legacy_meter_headers() -> dict[str, str]:
return {"X-StateHub-Component": _LEGACY_METER_COMPONENT}
_TASK_BLOCK_RE = re.compile(r"```task\s*\n(.*?)\n```", re.DOTALL)
_HEADING_RE = re.compile(r"^(#{1,4})\s+(.+?)$", re.MULTILINE)
_ARCHIVED_WP_RE = re.compile(r"^\d{6}-(.+\.md)$")
@ -566,7 +573,12 @@ def _api_get(
last_error: Exception | None = None
for attempt in range(_API_GET_RETRIES):
try:
with _httpx.Client(base_url=api_base, timeout=10.0, follow_redirects=True) as c:
with _httpx.Client(
base_url=api_base,
timeout=10.0,
follow_redirects=True,
headers=_legacy_meter_headers(),
) as c:
r = c.get(path, params=filtered if filtered else None)
r.raise_for_status()
return r.json()
@ -604,7 +616,12 @@ def _api_patch(api_base: str, path: str, body: dict) -> Any:
if not path.endswith("/"):
path += "/"
try:
with _httpx.Client(base_url=api_base, timeout=10.0, follow_redirects=True) as c:
with _httpx.Client(
base_url=api_base,
timeout=10.0,
follow_redirects=True,
headers=_legacy_meter_headers(),
) as c:
r = c.patch(path, json=body)
r.raise_for_status()
return r.json()
@ -620,7 +637,12 @@ def _api_put(api_base: str, path: str, body: dict) -> Any:
if not path.endswith("/"):
path += "/"
try:
with _httpx.Client(base_url=api_base, timeout=30.0, follow_redirects=True) as c:
with _httpx.Client(
base_url=api_base,
timeout=30.0,
follow_redirects=True,
headers=_legacy_meter_headers(),
) as c:
r = c.put(path, json=body)
r.raise_for_status()
return r.json()
@ -634,7 +656,12 @@ def _api_post(api_base: str, path: str, body: dict) -> Any:
if not path.endswith("/"):
path += "/"
try:
with _httpx.Client(base_url=api_base, timeout=10.0, follow_redirects=True) as c:
with _httpx.Client(
base_url=api_base,
timeout=10.0,
follow_redirects=True,
headers=_legacy_meter_headers(),
) as c:
r = c.post(path, json=body)
r.raise_for_status()
return r.json()
@ -2461,7 +2488,7 @@ def fix_repo(
if t_priority not in VALID_TASK_PRIORITIES:
t_priority = "medium"
t_data = _api_post(api_base, "/tasks", {
"workstream_id": new_ws_id,
"workplan_id": new_ws_id,
"title": str(task.get("title", t_id)).strip() or t_id,
"description": task.get("description") or None,
"status": t_status,
@ -2545,7 +2572,7 @@ def fix_repo(
if t_priority not in VALID_TASK_PRIORITIES:
t_priority = "medium"
t_data = _api_post(api_base, "/tasks", {
"workstream_id": ws_id,
"workplan_id": ws_id,
"title": str(task.get("title", t_id)).strip() or t_id,
"description": task.get("description") or None,
"status": t_status,

View file

@ -301,13 +301,22 @@ def check_files(workplans_dir: Path, report: Report) -> list[dict]:
# State-hub API checks
# ---------------------------------------------------------------------------
def _legacy_meter_headers() -> dict[str, str]:
return {"X-StateHub-Component": "state-hub.validate-repo-adr"}
def _api_get(api_base: str, path: str, params: dict | None = None) -> Any:
if not _HAS_HTTPX:
return None
if not path.endswith("/"):
path += "/"
try:
with _httpx.Client(base_url=api_base, timeout=10.0, follow_redirects=True) as c:
with _httpx.Client(
base_url=api_base,
timeout=10.0,
follow_redirects=True,
headers=_legacy_meter_headers(),
) as c:
r = c.get(path, params={k: v for k, v in (params or {}).items() if v is not None})
r.raise_for_status()
return r.json()