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

@ -25,6 +25,7 @@ from statehub_register import run_register as run_statehub_register
STATE_HUB_DIR = Path(__file__).resolve().parent
API_BASE = os.environ.get("API_BASE", "http://127.0.0.1:8000")
_LEGACY_METER_HEADERS = {"X-StateHub-Component": "state-hub.custodian-cli"}
TEMPLATE = STATE_HUB_DIR / "scripts" / "project_claude_md.template"
PATCH_CWD = STATE_HUB_DIR / "scripts" / "patch_mcp_cwd.py"
@ -81,7 +82,8 @@ _ONBOARDING_TASKS = [
def _api_get(path: str) -> object:
url = API_BASE.rstrip("/") + path
try:
with urllib.request.urlopen(url, timeout=10) as r:
req = urllib.request.Request(url, headers=_LEGACY_METER_HEADERS)
with urllib.request.urlopen(req, timeout=10) as r:
return json.loads(r.read())
except urllib.error.URLError as e:
print(f"ERROR: Cannot reach API at {API_BASE}: {e}")
@ -92,7 +94,11 @@ def _api_get(path: str) -> object:
def _api_post(path: str, body: dict) -> object:
url = API_BASE.rstrip("/") + path
data = json.dumps({k: v for k, v in body.items() if v is not None}).encode()
req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
req = urllib.request.Request(
url,
data=data,
headers={"Content-Type": "application/json", **_LEGACY_METER_HEADERS},
)
with urllib.request.urlopen(req, timeout=10) as r:
return json.loads(r.read())
@ -103,7 +109,7 @@ def _api_patch(path: str, body: dict) -> object:
req = urllib.request.Request(
url,
data=data,
headers={"Content-Type": "application/json"},
headers={"Content-Type": "application/json", **_LEGACY_METER_HEADERS},
method="PATCH",
)
with urllib.request.urlopen(req, timeout=10) as r:

View file

@ -6,11 +6,13 @@ import urllib.error
import urllib.request
API_BASE = os.environ.get("API_BASE", "http://127.0.0.1:8000").rstrip("/")
_HEADERS = {"X-StateHub-Component": "state-hub.dashboard.token-summary"}
def fetch(url: str):
try:
with urllib.request.urlopen(url, timeout=10) as resp:
req = urllib.request.Request(url, headers=_HEADERS)
with urllib.request.urlopen(req, timeout=10) as resp:
return json.loads(resp.read())
except urllib.error.URLError:
return None

View file

@ -6,9 +6,11 @@ import urllib.request
import urllib.error
API_BASE = os.environ.get("API_BASE", "http://127.0.0.1:8000").rstrip("/")
_HEADERS = {"X-StateHub-Component": "state-hub.dashboard.workstreams-list"}
try:
with urllib.request.urlopen(f"{API_BASE}/workplans", timeout=10) as resp:
req = urllib.request.Request(f"{API_BASE}/workplans", headers=_HEADERS)
with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read())
print(json.dumps(data))
except urllib.error.URLError as e:

View file

@ -7,6 +7,7 @@ import urllib.request
import urllib.error
API_BASE = os.environ.get("API_BASE", "http://127.0.0.1:8000").rstrip("/")
_HEADERS = {"X-StateHub-Component": "state-hub.dashboard.workplan-detail"}
ws_id = sys.argv[1] if len(sys.argv) > 1 else ""
@ -15,7 +16,8 @@ if not ws_id:
sys.exit(1)
try:
with urllib.request.urlopen(f"{API_BASE}/workplans/{ws_id}", timeout=10) as resp:
req = urllib.request.Request(f"{API_BASE}/workplans/{ws_id}", headers=_HEADERS)
with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read())
print(json.dumps(data))
except urllib.error.HTTPError as e:

View file

@ -47,4 +47,22 @@ python scripts/capture_legacy_meter_evidence.py --days 7
```
Expect `GET /workstreams/*` window counts to fall after deploy + one full
fix-consistency sweep cycle on all hosts.
fix-consistency sweep cycle on all hosts.
## Identity headers (2026-07-09)
Scripts now send `X-StateHub-Component` for legacy-meter attribution:
| Component key | Caller |
| --- | --- |
| `state-hub.fix-consistency` | `scripts/consistency_check.py` |
| `state-hub.custodian-cli` | `custodian_cli.py` |
| `state-hub.cleanup-stale-tasks` | `scripts/cleanup_stale_tasks.py` |
| `state-hub.validate-repo-adr` | `scripts/validate_repo_adr.py` |
| `state-hub.dashboard.workstreams-list` | dashboard data loader |
| `state-hub.dashboard.workplan-detail` | dashboard detail loader |
| `state-hub.dashboard.token-summary` | token summary loader |
**Remote deploy note:** railiance01 `~/state-hub` was at `050cbbc` (pre-migration);
`git pull` failed with permission denied on `.git/FETCH_HEAD` — operator action
required to update the sweep host.

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()

View file

@ -0,0 +1,113 @@
---
id: STATE-WP-0070
type: workplan
title: "Workplan terminology phase-2 legacy retirement"
domain: infotech
repo: state-hub
status: active
owner: codex
topic_slug: custodian
planning_priority: medium
planning_order: 70
created: "2026-07-09"
updated: "2026-07-09"
state_hub_workstream_id: "9aa92529-6ee8-4b3f-b573-78b0ef8d9788"
---
# STATE-WP-0070 — Workplan terminology phase-2 legacy retirement
**Parent:** `STATE-WP-0069` (finished 2026-07-08, archived).
**Prerequisite:** Caller migrations shipped (`b05ca2c+`), identity headers on
fix-consistency and dashboard loaders, activity-core progress dual-write stopped.
## Goal
Drive legacy-meter counts to zero per key, then retire remaining `/workstreams`
routes, MCP aliases, and internal dual-keys. All removals remain
**meter-gated** (seven consecutive zero-usage windows).
## Context
Live meter (2026-07-09) still shows ~150k weekly legacy GET calls — attributed
to `unknown` until identity headers deploy and remote fix-consistency hosts pull
latest `state-hub`. Evidence: `docs/evidence/legacy-meter-weekly-review-20260708.json`,
caller inventory: `docs/evidence/workstream-caller-inventory-20260708.md`.
## Task: Deploy caller migrations and verify meter attribution
```task
id: STATE-WP-0070-T01
status: progress
priority: high
state_hub_task_id: "04cb3a3a-fe52-480b-816b-37c8f8fbe5ee"
```
1. Pull `state-hub``b05ca2c` on all fix-consistency hosts (railiance01 sweep,
WSL workstation, cluster runners).
2. Run full `statehub fix-consistency --all` (or scheduled sweep).
3. Capture weekly review; confirm `X-StateHub-Component` buckets name
`state-hub.fix-consistency` instead of `unknown`.
4. Expect `GET /workstreams/*` window counts to fall sharply.
Done when a post-deploy capture shows attributed components and declining legacy
REST counts.
## Task: Retire remaining REST `/workstreams` routes per key
```task
id: STATE-WP-0070-T02
status: todo
priority: high
state_hub_task_id: "13122195-3b7e-4e22-a5e8-4ebe7e9a093f"
```
For each key in `docs/workplan-terminology-legacy-retirement-backlog.md` phase 12
still showing usage:
- `GET/POST/PATCH /workstreams/*`
- dependency and execution intent aliases
Return **410 Gone** (matching DELETE pattern) when legacy-meter shows seven
consecutive zero-usage windows. Update tests and capture evidence per retirement.
## Task: MCP alias removal (Phase 2)
```task
id: STATE-WP-0070-T03
status: todo
priority: medium
state_hub_task_id: "e2b9015a-d157-4677-b700-165f1cf7e2af"
```
Remove `create_workstream`, `update_workstream`, `list_workstreams`,
`update_workstream_status`, and `state://workstreams/{topic_slug}` when MCP
procedure keys hit zero usage for seven windows. Keep `TOOLS.md` migration notes.
## Task: Internal dual-key and param alias cleanup
```task
id: STATE-WP-0070-T04
status: todo
priority: low
state_hub_task_id: "18af98bf-c74e-47c8-948a-e713cff88bb8"
```
- Drop `open_workstreams` from `/state/summary` when meter clears.
- Remove `workstream_id` query/body aliases on preferred routes.
- Retire `flows/workstream.yaml` when no callers remain.
## Task: Closeout and fleet gate
```task
id: STATE-WP-0070-T05
status: todo
priority: medium
state_hub_task_id: "dcc81665-ef84-4c0a-a88f-d8afc0d1bbb9"
```
Re-run `scan_workstream_terminology.py`, grep budget check, full test suite,
archive this workplan, notify CUST-WP-0055 fleet gate complete.
Done when legacy-meter weekly review shows no active legacy REST keys and MCP
aliases are removed or marked retired.