"""Project State Hub workplans/tasks into the graph-explorer payload. This is a *view* of State Hub coordination state, not Fabric topology. State Hub remains the authoring surface for workplans; Fabric only renders. """ from __future__ import annotations import json import os import re import urllib.request from datetime import datetime, timezone from typing import Any from urllib.parse import urlencode OPEN_WORKPLAN = frozenset({"proposed", "ready", "active", "blocked", "backlog"}) OPEN_TASK = frozenset({"todo", "progress", "wait"}) RESIDUAL_FLAVOR = "residual" WP_ID_RE = re.compile(r"\b([A-Z]{2,12}(?:-[A-Z]+)?-WP-[A-Z0-9-]+)\b") FLAVOR_COLORS = { "planning": "#6366f1", "implementation": "#1d4ed8", "refactoring": "#b45309", "extension": "#0f766e", "residual": "#64748b", } def is_residual_flavor(value: Any) -> bool: """True only for the closed flavor token. Titles are not consulted.""" return str(value or "").strip().lower() == RESIDUAL_FLAVOR def _flavor(record: dict[str, Any]) -> str | None: text = str(record.get("flavor") or "").strip().lower() return text or None def _indexed_depends_targets(workplan: dict[str, Any]) -> list[str]: """Hub-indexed workplan ids from depends_on stubs or id lists.""" raw = workplan.get("depends_on") if raw is None: raw = workplan.get("depends_on_workplans") if raw is None: return [] if not isinstance(raw, list): raw = [raw] targets: list[str] = [] seen: set[str] = set() for item in raw: if isinstance(item, dict): target = ( item.get("workplan_id") or item.get("workstream_id") or item.get("id") ) else: target = item if not target: continue key = str(target) if key in seen: continue seen.add(key) targets.append(key) return targets def coordination_graph_payload( workplans: list[dict[str, Any]], tasks: list[dict[str, Any]], *, include_residuals: bool = False, ) -> dict[str, Any]: """Build a GraphExplorerPayload of open workplans and their open tasks.""" open_all = [wp for wp in workplans if str(wp.get("status") or "") in OPEN_WORKPLAN] residual_open = sum(1 for wp in open_all if is_residual_flavor(_flavor(wp))) if include_residuals: open_wps = open_all else: open_wps = [wp for wp in open_all if not is_residual_flavor(_flavor(wp))] wp_by_id = {str(wp.get("id")): wp for wp in open_wps if wp.get("id")} slug_to_id = { str(wp.get("slug") or "").lower(): str(wp["id"]) for wp in open_wps if wp.get("slug") and wp.get("id") } record_to_id: dict[str, str] = {} for wp in open_wps: slug = str(wp.get("slug") or "") title = str(wp.get("title") or "") for token in WP_ID_RE.findall(slug.upper() + " " + title.upper()): record_to_id[token] = str(wp["id"]) if slug: record_to_id[slug.upper().replace("_", "-")] = str(wp["id"]) open_tasks = [ task for task in tasks if str(task.get("status") or "") in OPEN_TASK and str(task.get("workplan_id") or task.get("workstream_id") or "") in wp_by_id and (include_residuals or not is_residual_flavor(_flavor(task))) ] elements: list[dict[str, Any]] = [] for wp in open_wps: wp_id = str(wp["id"]) status = str(wp.get("status") or "unknown") flavor = _flavor(wp) elements.append( { "data": { "id": f"workplan:{wp_id}", "stableKey": f"workplan:{wp_id}", "kind": "Workplan", "layer": "workplan", "label": str(wp.get("slug") or wp.get("title") or wp_id)[:48], "name": wp.get("title"), "lifecycle": status, "status": status, "flavor": flavor, "nodeClass": flavor or "unspecified", "repo": wp.get("repo") or wp.get("slug"), "displayState": "show" if status != "blocked" else "highlight", "unresolved": status in {"blocked", "proposed"}, "chokepoint": 0, "color": FLAVOR_COLORS.get(flavor or "", "#1d4ed8"), } } ) belongs = 0 wait_edges = 0 for task in open_tasks: task_id = str(task["id"]) wp_id = str(task.get("workplan_id") or task.get("workstream_id")) status = str(task.get("status") or "todo") human = bool(task.get("needs_human")) flavor = _flavor(task) or _flavor(wp_by_id.get(wp_id) or {}) elements.append( { "data": { "id": f"task:{task_id}", "stableKey": f"task:{task_id}", "kind": "Task", "layer": "task", "label": str(task.get("title") or task_id)[:56], "name": task.get("title"), "lifecycle": status, "status": status, "flavor": flavor, "nodeClass": flavor or "unspecified", "needsHuman": human, "displayState": "highlight" if status == "wait" or human else "show", "unresolved": status == "wait" or human, } } ) elements.append( { "data": { "id": f"edge:belongs:{task_id}", "stableKey": f"edge:belongs:{task_id}", "kind": "Edge", "layer": "dependency", "displayState": "show", "source": f"task:{task_id}", "target": f"workplan:{wp_id}", "edgeType": "belongs_to", "edgeSource": "membership", "strength": "weak", "sourceLayer": "task", "targetLayer": "workplan", } } ) belongs += 1 if status == "wait" or human: wait_edges += 1 indexed_pairs: set[tuple[str, str]] = set() depends_count = 0 chokepoint: dict[str, int] = {str(wp["id"]): 0 for wp in open_wps} for wp in open_wps: src = str(wp["id"]) for dst_raw in _indexed_depends_targets(wp): dst = ( dst_raw if dst_raw in wp_by_id else record_to_id.get(dst_raw.upper()) or slug_to_id.get(dst_raw.lower()) ) if not dst or dst == src or dst not in wp_by_id: continue pair = (src, dst) if pair in indexed_pairs: continue indexed_pairs.add(pair) elements.append( { "data": { "id": f"edge:depends:{src}:{dst}", "stableKey": f"edge:depends:{src}:{dst}", "kind": "Edge", "layer": "dependency", "displayState": "show", "source": f"workplan:{src}", "target": f"workplan:{dst}", "edgeType": "depends_on", "edgeSource": "indexed", "strength": "strong", "sourceLayer": "workplan", "targetLayer": "workplan", } } ) depends_count += 1 chokepoint[dst] = chokepoint.get(dst, 0) + 1 cited = 0 for task in open_tasks: blob = " ".join( str(task.get(key) or "") for key in ("description", "blocking_reason", "intervention_note", "title") ) src_wp = str(task.get("workplan_id") or task.get("workstream_id")) for token in set(WP_ID_RE.findall(blob.upper())): dst = record_to_id.get(token) or slug_to_id.get(token.lower()) if not dst or dst == src_wp or dst not in wp_by_id: continue if (src_wp, dst) in indexed_pairs: continue elements.append( { "data": { "id": f"edge:cites:{task['id']}:{dst}", "stableKey": f"edge:cites:{task['id']}:{dst}", "kind": "Edge", "layer": "dependency", "displayState": "show", "source": f"workplan:{src_wp}", "target": f"workplan:{dst}", "edgeType": "waits_on", "edgeSource": "citation", "strength": "strong", "sourceLayer": "workplan", "targetLayer": "workplan", } } ) cited += 1 chokepoint[dst] = chokepoint.get(dst, 0) + 1 indexed_pairs.add((src_wp, dst)) for el in elements: node_id = el["data"]["id"] if not node_id.startswith("workplan:"): continue wp_id = node_id.split(":", 1)[1] el["data"]["chokepoint"] = int(chokepoint.get(wp_id) or 0) generated = datetime.now(timezone.utc).isoformat() return { "apiVersion": "railiance.fabric/v1alpha1", "kind": "GraphExplorerPayload", "manifest_id": "railiance-fabric.coordination-map", "generated_at": generated, "mode": "coordination", "metrics": { "open_workplans": len(open_wps), "open_tasks": len(open_tasks), "belongs_to_edges": belongs, "depends_on_edges": depends_count, "citation_edges": cited, "wait_or_human_tasks": wait_edges, "residual_open_workplans": residual_open, "include_residuals": include_residuals, }, "elements": elements, "hidden_elements": [], } def fetch_hub_lists(api_base: str) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: base = api_base.rstrip("/") workplans: list[dict[str, Any]] = [] for status in sorted(OPEN_WORKPLAN): workplans.extend(_get_json(f"{base}/workplans/?{urlencode({'status': status})}")) tasks = _get_json(f"{base}/tasks/") dep_rows = _get_json_optional(f"{base}/state/deps?include_residuals=true") by_id = { str(row.get("id")): row for row in dep_rows if isinstance(row, dict) and row.get("id") } for wp in workplans: extra = by_id.get(str(wp.get("id"))) if not extra: continue if extra.get("depends_on") and not wp.get("depends_on"): wp["depends_on"] = extra["depends_on"] if extra.get("flavor") and not wp.get("flavor"): wp["flavor"] = extra["flavor"] return workplans, tasks def _get_json(url: str) -> list[dict[str, Any]]: with urllib.request.urlopen(url, timeout=60) as response: payload = json.loads(response.read().decode()) if isinstance(payload, list): return payload raise RuntimeError(f"unexpected JSON from {url}") def _get_json_optional(url: str) -> list[dict[str, Any]]: try: return _get_json(url) except Exception: return [] def default_hub() -> str: return os.environ.get("STATE_HUB_URL") or os.environ.get("API_BASE") or "http://127.0.0.1:8000"