Migrate remaining in-repo workstream REST callers to /workplans
Point cleanup_stale_tasks, validate_repo_adr, custodian_cli, dashboard data loaders, extensions poll, MCP legacy resource, and consistency_check task queries at preferred /workplans routes. Add caller inventory evidence doc.
This commit is contained in:
parent
050cbbceb9
commit
b05ca2ca8c
10 changed files with 66 additions and 16 deletions
|
|
@ -241,14 +241,14 @@ def cmd_register(args: argparse.Namespace) -> None:
|
|||
print(f"==> Creating onboarding workstream '{ws_slug}' ...")
|
||||
# Check if it already exists
|
||||
existing_ws = next(
|
||||
(w for w in _api_get("/workstreams/") if w.get("slug") == ws_slug and w.get("status") == "active"),
|
||||
(w for w in _api_get("/workplans/") if w.get("slug") == ws_slug and w.get("status") == "active"),
|
||||
None,
|
||||
)
|
||||
if existing_ws:
|
||||
print(" Onboarding workstream already exists — skipping task creation.")
|
||||
if repo_id and not existing_ws.get("repo_id"):
|
||||
existing_owner = existing_ws.get("owner")
|
||||
_api_patch(f"/workstreams/{existing_ws['id']}/", {
|
||||
_api_patch(f"/workplans/{existing_ws['id']}/", {
|
||||
"repo_id": repo_id,
|
||||
"owner": repo_slug if existing_owner in (None, domain) else existing_owner,
|
||||
})
|
||||
|
|
@ -260,7 +260,7 @@ def cmd_register(args: argparse.Namespace) -> None:
|
|||
)
|
||||
else:
|
||||
try:
|
||||
ws = _api_post("/workstreams/", {
|
||||
ws = _api_post("/workplans/", {
|
||||
"topic_id": topic_id,
|
||||
"title": f"Repo Integration: {repo_slug}",
|
||||
"slug": ws_slug,
|
||||
|
|
@ -430,7 +430,7 @@ def cmd_create_workstream(args: argparse.Namespace) -> None:
|
|||
|
||||
slug = args.slug or re.sub(r"[^a-z0-9]+", "-", args.title.lower()).strip("-")
|
||||
|
||||
ws = _api_post("/workstreams/", {
|
||||
ws = _api_post("/workplans/", {
|
||||
"topic_id": topic_id,
|
||||
"title": args.title,
|
||||
"slug": slug,
|
||||
|
|
@ -460,7 +460,7 @@ def cmd_create_task(args: argparse.Namespace) -> None:
|
|||
# Resolve workstream: accept UUID or slug
|
||||
workstream_id = args.workstream
|
||||
if not _is_uuid(workstream_id):
|
||||
wss = _api_get("/workstreams/")
|
||||
wss = _api_get("/workplans/")
|
||||
match = next((w for w in wss if w.get("slug") == workstream_id), None)
|
||||
if not match:
|
||||
print(f"ERROR: No workstream found with slug '{workstream_id}'.")
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import urllib.error
|
|||
API_BASE = os.environ.get("API_BASE", "http://127.0.0.1:8000").rstrip("/")
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(f"{API_BASE}/workstreams", timeout=10) as resp:
|
||||
with urllib.request.urlopen(f"{API_BASE}/workplans", timeout=10) as resp:
|
||||
data = json.loads(resp.read())
|
||||
print(json.dumps(data))
|
||||
except urllib.error.URLError as e:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ if not ws_id:
|
|||
sys.exit(1)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(f"{API_BASE}/workstreams/{ws_id}", timeout=10) as resp:
|
||||
with urllib.request.urlopen(f"{API_BASE}/workplans/{ws_id}", timeout=10) as resp:
|
||||
data = json.loads(resp.read())
|
||||
print(json.dumps(data))
|
||||
except urllib.error.HTTPError as e:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const epState = (async function*() {
|
|||
try {
|
||||
const [re, rw, rt, rr] = await Promise.all([
|
||||
apiFetch("/extension-points/"),
|
||||
apiFetch("/workstreams/"),
|
||||
apiFetch("/workplans/"),
|
||||
apiFetch("/topics/"),
|
||||
apiFetch("/repos/"),
|
||||
]);
|
||||
|
|
|
|||
50
docs/evidence/workstream-caller-inventory-20260708.md
Normal file
50
docs/evidence/workstream-caller-inventory-20260708.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# Workstream caller inventory — 2026-07-08
|
||||
|
||||
Post `STATE-WP-0069` hunt. Live legacy-meter (7-day window) showed ~150k calls
|
||||
on legacy REST keys; most lacked `X-StateHub-Component` identity (`unknown` bucket).
|
||||
|
||||
## Migrated in this pass (REST → `/workplans/`)
|
||||
|
||||
| Caller | Repo | Was | Now |
|
||||
| --- | --- | --- | --- |
|
||||
| `scripts/consistency_check.py` | state-hub | `/workstreams/*` | `/workplans/*` (prior commit) |
|
||||
| `scripts/consistency_check.py` task queries | state-hub | `?workstream_id=` | `?workplan_id=` |
|
||||
| `scripts/cleanup_stale_tasks.py` | state-hub | `GET /workstreams/` | `GET /workplans/` |
|
||||
| `scripts/validate_repo_adr.py` | state-hub | `/workstreams/{id}`, list | `/workplans/*` |
|
||||
| `custodian_cli.py` | state-hub | CRUD/list `/workstreams/` | `/workplans/` |
|
||||
| `dashboard/src/data/workstreams.json.py` | state-hub | `GET /workstreams` | `GET /workplans` |
|
||||
| `dashboard/src/data/workstreams/[id].json.py` | state-hub | `GET /workstreams/{id}` | `GET /workplans/{id}` |
|
||||
| `dashboard/src/extensions.md` | state-hub | `apiFetch("/workstreams/")` | `apiFetch("/workplans/")` |
|
||||
| `mcp_server/server.py` legacy resource | state-hub | `_get("/workstreams")` | `_get("/workplans")` |
|
||||
| `context_resolvers/state_hub.py` | activity-core | index, detail, tasks | `/workplans/index`, `/workplans/{id}`, `?workplan_id=` |
|
||||
| `cya/shell_session.py` | can-you-assist | `GET /workstreams/?` | `GET /workplans/?` |
|
||||
|
||||
## Intentional legacy (keep until meter clears)
|
||||
|
||||
| Surface | Notes |
|
||||
| --- | --- |
|
||||
| `api/routers/workstreams.py` | Compat router; metered with Deprecation |
|
||||
| `api/routers/workstream_dependencies.py` | Legacy dependency aliases |
|
||||
| MCP tool names `create_workstream`, etc. | Aliases call `/workplans/` internally |
|
||||
| MCP param aliases `workstream_id` | Accepted on tools; REST uses `workplan_id` where updated |
|
||||
| `activity-core` progress sinks | Dual-write `workplan_id` + `workstream_id` on POST `/progress/` (wire compat) |
|
||||
| `railiance-platform/scripts/credential.py` | Dual-write scope fields |
|
||||
| Dashboard **routes** `/workstreams` | Nav URL compat only; `workstreams.md` already polls `/workplans/` |
|
||||
| `tests/test_legacy_meter.py`, `test_routers_core.py` | Regression coverage for legacy routes |
|
||||
|
||||
## Likely remaining production traffic sources
|
||||
|
||||
1. **Remote fix-consistency** on railiance01 / cluster nodes still on pre-`e0c954d` checkout.
|
||||
2. **Dashboard data loaders** on deployed hosts before this migration ships.
|
||||
3. **activity-core** k8s prompts still document `/workstreams/workplan-index` in YAML (docs only until rolled).
|
||||
4. **External MCP clients** (Claude Code `create_workstream` in `~/.claude.json`) — tool aliases hit `/workplans/` but old REST clients may remain.
|
||||
5. **POST bodies** with `workstream_id` on `/tasks/`, `/decisions/`, `/progress/` (metered separately).
|
||||
|
||||
## Next verification
|
||||
|
||||
```bash
|
||||
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.
|
||||
|
|
@ -276,7 +276,7 @@ def resource_workstreams(topic_slug: str) -> str:
|
|||
match = next((t for t in topics if t["slug"] == topic_slug), None)
|
||||
if not match:
|
||||
return json.dumps({"error": f"Topic '{topic_slug}' not found"})
|
||||
rows = _get("/workstreams", {"topic_id": match["id"]})
|
||||
rows = _get("/workplans", {"topic_id": match["id"]})
|
||||
return json.dumps(
|
||||
{
|
||||
"_deprecation": _legacy_mcp_deprecation(resource="state://workstreams/{topic_slug}"),
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ def main() -> int:
|
|||
|
||||
try:
|
||||
tasks = get("/tasks/?limit=500")
|
||||
workstreams = get("/workstreams/")
|
||||
workstreams = get("/workplans/")
|
||||
except urllib.error.URLError as e:
|
||||
print(f"[cleanup-stale] ERROR: API unreachable — {e}", file=sys.stderr)
|
||||
print("[cleanup-stale] Start the API with: cd ~/state-hub && make api", file=sys.stderr)
|
||||
|
|
|
|||
|
|
@ -936,7 +936,7 @@ def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = N
|
|||
# Continue to check drift even with mismatched repo
|
||||
|
||||
tasks = get_tasks_from_workplan(meta, body)
|
||||
db_tasks = _api_get(api_base, "/tasks", {"workstream_id": ws_id})
|
||||
db_tasks = _api_get(api_base, "/tasks", {"workplan_id": ws_id})
|
||||
file_task_statuses = [
|
||||
str(task.get("status", "")).strip()
|
||||
for task in tasks
|
||||
|
|
@ -2046,7 +2046,7 @@ def _write_custodian_brief(api_base: str, repo_slug: str, repo_path: str) -> boo
|
|||
for ws in workstreams:
|
||||
ws_title = ws.get("title", ws.get("slug", "?"))
|
||||
ws_id = ws["id"]
|
||||
tasks = _api_get(api_base, "/tasks", {"workstream_id": ws_id}) or []
|
||||
tasks = _api_get(api_base, "/tasks", {"workplan_id": ws_id}) or []
|
||||
if not isinstance(tasks, list):
|
||||
tasks = []
|
||||
|
||||
|
|
|
|||
|
|
@ -339,7 +339,7 @@ def check_api(api_base: str, metas: list[dict], domain_slug: str | None,
|
|||
file_ws_ids.add(ws_id)
|
||||
if meta.get("_active_file", True):
|
||||
active_file_ws_ids.add(ws_id)
|
||||
ws = _api_get(api_base, f"/workstreams/{ws_id}")
|
||||
ws = _api_get(api_base, f"/workplans/{ws_id}")
|
||||
if ws is None:
|
||||
report.add(Level.FAIL, "workstream-ref-exists",
|
||||
f"state_hub_workstream_id {ws_id} not found in DB (stale reference)",
|
||||
|
|
@ -373,7 +373,7 @@ def check_api(api_base: str, metas: list[dict], domain_slug: str | None,
|
|||
if t_domain not in domains_to_check:
|
||||
continue
|
||||
t_id = topic["id"]
|
||||
workstreams = _api_get(api_base, "/workstreams", {"topic_id": t_id})
|
||||
workstreams = _api_get(api_base, "/workplans", {"topic_id": t_id})
|
||||
if not isinstance(workstreams, list):
|
||||
report.add(Level.WARN, "orphan-detection",
|
||||
f"Could not fetch workstreams for topic {t_id[:8]}… (domain={t_domain})")
|
||||
|
|
|
|||
|
|
@ -971,7 +971,7 @@ class TestLifecycleRenormalization:
|
|||
return ws
|
||||
if path == "/tasks/task-1":
|
||||
return task
|
||||
if path == "/tasks" and params == {"workstream_id": "ws-1"}:
|
||||
if path == "/tasks" and params == {"workplan_id": "ws-1"}:
|
||||
return [task]
|
||||
if path == "/workplans/ws-1/dependencies":
|
||||
return []
|
||||
|
|
@ -1087,7 +1087,7 @@ class TestC12OrphanDbTasks:
|
|||
return ws
|
||||
if path == "/tasks/task-linked":
|
||||
return linked
|
||||
if path == "/tasks" and params == {"workstream_id": "ws-1"}:
|
||||
if path == "/tasks" and params == {"workplan_id": "ws-1"}:
|
||||
return [linked, orphan]
|
||||
if path == "/workplans/ws-1/dependencies":
|
||||
return []
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue