from __future__ import annotations import json import urllib.error import urllib.parse import urllib.request from typing import Any DEFAULT_BASE_URL = "http://127.0.0.1:8000" TIMEOUT_SECONDS = 5 LIST_TIMEOUT_SECONDS = 20 # Observed status vocabulary (2026-07): "requested", "completed". No fixed # enum is published by the API (free-form string field) -- treat anything # not in this terminal set as still-open rather than assuming a closed list. TERMINAL_STATUSES = {"completed", "cancelled", "rejected", "resolved", "closed"} def _request( method: str, path: str, base_url: str, *, body: dict[str, Any] | None = None, params: dict[str, str] | None = None, timeout: int = TIMEOUT_SECONDS, ) -> dict[str, Any] | list[Any]: url = f"{base_url.rstrip('/')}{path}" if params: query = "&".join( f"{k}={urllib.parse.quote(v)}" for k, v in params.items() if v ) if query: url = f"{url}?{query}" data = json.dumps(body).encode("utf-8") if body is not None else None request = urllib.request.Request( url, data=data, method=method, headers={"Content-Type": "application/json", "User-Agent": "reuse-surface/0.1"}, ) with urllib.request.urlopen(request, timeout=timeout) as response: return json.loads(response.read().decode("utf-8")) def state_hub_reachable(base_url: str = DEFAULT_BASE_URL) -> bool: try: _request("GET", "/state/health", base_url) return True except (urllib.error.URLError, TimeoutError, OSError): return False def file_capability_request( *, title: str, description: str, requesting_domain: str, requesting_agent: str, capability_type: str = "reuse-surface-new-verdict", requesting_workplan_id: str | None = None, base_url: str = DEFAULT_BASE_URL, ) -> dict[str, Any] | None: """Files a State Hub capability request for a plan-check 'new' verdict. Returns the created request record, or None if the hub is unreachable (degrades gracefully -- plan-check never blocks on this).""" if not state_hub_reachable(base_url): return None body = { "title": title, "description": description, "capability_type": capability_type, "requesting_domain": requesting_domain, "requesting_agent": requesting_agent, "requesting_workplan_id": requesting_workplan_id, } try: return _request("POST", "/capability-requests/", base_url, body=body) except (urllib.error.URLError, TimeoutError, OSError): return None def list_open_capability_requests( base_url: str = DEFAULT_BASE_URL, ) -> list[dict[str, Any]] | None: """Returns capability requests whose status is not terminal (see TERMINAL_STATUSES), or None if the hub is unreachable. A request can carry a catalog_entry_id while still 'requested' (routed, not fulfilled), so status -- not catalog_entry_id presence -- is the open signal. The list endpoint can be slow (observed ~7s with a handful of rows); a longer timeout than the health check is used deliberately.""" if not state_hub_reachable(base_url): return None try: requests_ = _request( "GET", "/capability-requests/", base_url, timeout=LIST_TIMEOUT_SECONDS ) except (urllib.error.URLError, TimeoutError, OSError): return None if not isinstance(requests_, list): return [] return [r for r in requests_ if r.get("status") not in TERMINAL_STATUSES]