| from __future__ import annotations |
|
|
| import logging |
| import os |
| import re |
| import sys |
| import time |
| import urllib.parse |
| from contextlib import asynccontextmanager |
| from pathlib import Path |
| from typing import Any |
|
|
| from fastapi import FastAPI, HTTPException, Request |
| from fastapi.responses import FileResponse |
| from fastapi.staticfiles import StaticFiles |
|
|
| from key_os.facilities import kernel_facilities |
| from key_os.models import Receipt, ServiceSpec, utc_now |
| from key_os.probe import ProbeError, call_endpoint, enumerate_space |
| from key_os.routes import default_links, route_query |
| from key_os.security import contracts_report, redact, require_confirmation |
| from key_os.store import KeyOSStore |
|
|
| log = logging.getLogger("key_os") |
|
|
|
|
| from pydantic import BaseModel, Field |
|
|
| class ConnectRequest(BaseModel): |
| target: str = Field(..., description="Target Space ID (owner/name) or full URL to connect to", json_schema_extra={"example": "tostido/meshscale"}) |
|
|
| class ProbeRequest(BaseModel): |
| target: str = Field(..., description="Target Space ID or URL", json_schema_extra={"example": "http://127.0.0.1:7861"}) |
| path: str = Field(..., description="API endpoint path to query", json_schema_extra={"example": "/health"}) |
| method: str = Field("GET", description="HTTP method to use", json_schema_extra={"example": "GET"}) |
| query: dict[str, Any] | None = Field(None, description="Optional query parameters") |
| body: Any | None = Field(None, description="Optional request body to send as JSON") |
|
|
| class ReceiptRequest(BaseModel): |
| event_type: str = Field("operator_note", description="Event category / type", json_schema_extra={"example": "diagnostic_smoke"}) |
| subject: str = Field("key_os", description="Event subject or target key", json_schema_extra={"example": "local_server"}) |
| source: str = Field("operator", description="Creator or source identifier", json_schema_extra={"example": "codex_agent"}) |
| payload: dict[str, Any] = Field(default_factory=dict, description="Custom payload data") |
|
|
| class NotepadAddRequest(BaseModel): |
| type: str = Field("observation", description="Note type (observation, hypothesis, action, outcome, question, guide, issue)", json_schema_extra={"example": "issue"}) |
| title: str | None = Field(None, description="Title of the note (defaults to type if omitted)") |
| content: str = Field(..., description="The main text/content of the note", json_schema_extra={"example": "Local probes fail due to HF host checks."}) |
| tags: list[str] = Field(default_factory=list, description="Tags to organize the notes") |
| links: list[str] = Field(default_factory=list, description="Associated links or file paths") |
| confidence: float | None = Field(None, description="Confidence level between 0 and 1") |
| source: str = Field("operator", description="Source indicating who added the note", json_schema_extra={"example": "browser"}) |
|
|
| class NotepadClearRequest(BaseModel): |
| confirm: str = Field(..., description="Set to 'CONFIRM' to authorize deletion", json_schema_extra={"example": "CONFIRM"}) |
|
|
| class FactoryResetRequest(BaseModel): |
| confirm: str = Field(..., description="Set to 'CONFIRM' to authorize reset", json_schema_extra={"example": "CONFIRM"}) |
| clear_receipts: bool = Field(True, description="Whether to purge all receipts") |
| clear_notepad: bool = Field(False, description="Whether to purge the notepad") |
| clear_dispatch: bool = Field(True, description="Whether to clear dispatch state") |
|
|
| class SignalAckRequest(BaseModel): |
| state: str = Field(..., description="New acknowledgement state (new, seen, deferred, handled, blocked, superseded)", json_schema_extra={"example": "handled"}) |
| note: str | None = Field(None, description="Optional comment regarding the status change", json_schema_extra={"example": "fixed in 0.1.3"}) |
|
|
| class ContinuityCheckpointRequest(BaseModel): |
| subject: str = Field("codex_continuity", description="Optional subject annotation") |
| note: str = Field("Codex continuity checked.", description="Optional note text") |
| source: str = Field("operator", description="Optional source label") |
|
|
| class DispatchStartRequest(BaseModel): |
| seed: str = Field("status", description="The initial dispatch seed") |
| allow_mutating: bool = Field(True, description="Whether to permit mutating routes") |
| allow_external: bool = Field(True, description="Whether to permit external HTTP routes") |
| allow_destructive: bool = Field(True, description="Whether to permit destructive routes") |
| max_steps: int = Field(60, description="Max execution steps allowed") |
| step_delay: float = Field(0.45, description="Delay between execution steps in seconds") |
|
|
|
|
| ROOT = Path(__file__).parent |
| STATIC = ROOT / "static" |
| SERVICE_KEY_RE = re.compile(r"^[a-z0-9][a-z0-9_.-]{0,80}$") |
| RESERVED_SERVICE_KEYS = frozenset(facility.key for facility in kernel_facilities()) |
|
|
|
|
| def _bool_env(name: str, default: bool = False) -> bool: |
| value = os.environ.get(name) |
| if value is None: |
| return default |
| return value.strip().lower() in {"1", "true", "yes", "on"} |
|
|
|
|
| def _store() -> KeyOSStore: |
| return KeyOSStore(os.environ.get("KEY_OS_ROOT", "/data/key_os")) |
|
|
|
|
| def _links() -> dict[str, str]: |
| links = default_links() |
| if os.environ.get("KEY_OS_HOME_URL"): |
| links["home"] = os.environ["KEY_OS_HOME_URL"] |
| if os.environ.get("KEY_OS_BUCKET_URL"): |
| links["bucket"] = os.environ["KEY_OS_BUCKET_URL"] |
| if os.environ.get("KEY_OS_DOCS_URL"): |
| links["docs"] = os.environ["KEY_OS_DOCS_URL"] |
| return links |
|
|
|
|
| def _runtime_flags() -> dict[str, Any]: |
| return { |
| "operator_auth_required": False, |
| "operator_auth_configured": False, |
| "unauth_mutation_allowed": True, |
| } |
|
|
|
|
| def _boot_diagnostics() -> None: |
| """Log a clear summary of storage config at startup.""" |
| root = os.environ.get("KEY_OS_ROOT", "/data/key_os") |
| log.info("──────────────────────────────────────────────") |
| log.info(" KEY OS boot diagnostics") |
| log.info("──────────────────────────────────────────────") |
| log.info( |
| " ROOT : %s (writable=%s)", |
| root, |
| os.access(root, os.W_OK) if os.path.isdir(root) else "dir-missing", |
| ) |
| log.info(" AUTH : Wholesale Removed (Open Mutability)") |
| log.info("──────────────────────────────────────────────") |
|
|
|
|
| @asynccontextmanager |
| async def lifespan(app: FastAPI): |
| """Startup/shutdown lifecycle — runs after env is fully available.""" |
| logging.basicConfig( |
| level=logging.INFO, |
| format="%(levelname)s: %(name)s - %(message)s", |
| stream=sys.stderr, |
| ) |
| _boot_diagnostics() |
| yield |
|
|
|
|
| app = FastAPI( |
| title=os.environ.get("KEY_OS_NAME", "KEY OS"), |
| lifespan=lifespan, |
| ) |
| app.mount("/static", StaticFiles(directory=str(STATIC)), name="static") |
|
|
|
|
| @app.get("/") |
| async def index() -> FileResponse: |
| return FileResponse(STATIC / "index.html") |
|
|
|
|
| @app.get("/health") |
| async def health() -> dict[str, Any]: |
| return {"ok": True, "surface": "key_os", "time": utc_now()} |
|
|
|
|
| @app.get("/api/status") |
| async def status(detail: str = "compact") -> dict[str, Any]: |
| return _store().status(_links(), runtime_flags=_runtime_flags(), detail=detail) |
|
|
|
|
| @app.get("/api/os") |
| async def resource_os() -> dict[str, Any]: |
| return _store().resource_snapshot(_links(), runtime_flags=_runtime_flags()) |
|
|
|
|
| @app.get("/api/os/search") |
| async def resource_search(q: str = "", limit: int = 40) -> dict[str, Any]: |
| return _store().resource_search(q, limit=limit, links=_links(), runtime_flags=_runtime_flags()) |
|
|
|
|
| @app.get("/api/os/oculus") |
| async def oculus() -> dict[str, Any]: |
| return _store().status(_links(), runtime_flags=_runtime_flags()).get("oculus", {}) |
|
|
|
|
| @app.get("/api/continuity") |
| async def continuity() -> dict[str, Any]: |
| return _store().continuity() |
|
|
|
|
| @app.get("/api/signals") |
| async def signals() -> dict[str, Any]: |
| return _store().signals() |
|
|
|
|
| @app.get("/api/signals/{signal_id}") |
| async def signal_detail(signal_id: str) -> dict[str, Any]: |
| signal = _store().get_signal(signal_id) |
| if signal is None: |
| raise HTTPException(status_code=404, detail="signal not found") |
| return signal |
|
|
|
|
| @app.post("/api/signals/{signal_id}/ack") |
| async def signal_ack(signal_id: str, payload: SignalAckRequest) -> dict[str, Any]: |
| try: |
| return _store().acknowledge_signal(signal_id, payload.model_dump()) |
| except KeyError as exc: |
| raise HTTPException(status_code=404, detail="signal not found") from exc |
| except ValueError as exc: |
| raise HTTPException(status_code=400, detail=str(exc)) from exc |
|
|
|
|
| @app.post("/api/continuity/checkpoint") |
| async def continuity_checkpoint(payload: ContinuityCheckpointRequest) -> dict[str, Any]: |
| return _store().acknowledge_continuity(payload.model_dump()) |
|
|
|
|
| @app.get("/api/services") |
| async def services() -> dict[str, Any]: |
| custom_count = len(_store().load_services(include_kernel=False)) |
| items = [service.to_dict(redact_payload=True) for service in _store().load_services()] |
| groups: dict[str, list[dict[str, Any]]] = {} |
| for item in items: |
| groups.setdefault(str(item.get("group") or "custom"), []).append(item) |
| return {"count": len(items), "custom_count": custom_count, "groups": groups, "services": items} |
|
|
|
|
| @app.get("/api/facilities") |
| async def facilities() -> dict[str, Any]: |
| return _store().registry().manifest() |
|
|
|
|
| @app.get("/api/facilities/graph") |
| async def facility_graph() -> dict[str, Any]: |
| return _store().registry().graph() |
|
|
|
|
| @app.get("/api/facility/{key}") |
| async def facility_detail(key: str) -> dict[str, Any]: |
| return _store().registry().node_detail(key) |
|
|
|
|
| @app.post("/api/services") |
| async def register_service(request: Request) -> dict[str, Any]: |
| payload = await request.json() |
| key = str(payload.get("key") or "").strip().lower() |
| label = str(payload.get("label") or key).strip() |
| group = str(payload.get("group") or "custom").strip().lower() |
| route = str(payload.get("route") or "").strip() |
| if not key: |
| raise HTTPException(status_code=400, detail="service key is required") |
| if not SERVICE_KEY_RE.match(key): |
| raise HTTPException(status_code=400, detail="service key must match ^[a-z0-9][a-z0-9_.-]{0,80}$") |
| if key in RESERVED_SERVICE_KEYS: |
| raise HTTPException(status_code=409, detail=f"service key '{key}' is reserved by the kernel") |
| if group == "kernel": |
| raise HTTPException(status_code=400, detail="custom services may not use reserved group 'kernel'") |
| if not route: |
| raise HTTPException(status_code=400, detail="service route is required") |
| service = ServiceSpec( |
| key=key, |
| label=label, |
| group=group or "custom", |
| description=str(payload.get("description") or ""), |
| route=route, |
| method=str(payload.get("method") or "GET").upper(), |
| risk_class=str(payload.get("risk_class") or "inspect"), |
| tags=tuple(payload.get("tags") or ()), |
| input_schema=payload.get("input_schema") or {}, |
| output_schema=payload.get("output_schema") or {}, |
| payload=payload.get("payload") or {}, |
| ) |
| return {"ok": True, "service": _store().put_service(service).to_dict(redact_payload=True)} |
|
|
|
|
| @app.post("/api/route") |
| async def route(request: Request) -> dict[str, Any]: |
| payload = await request.json() |
| query = str(payload.get("query") or "") |
| report = _store().status(_links(), runtime_flags=_runtime_flags()) |
| return {"query": query, "route": route_query(query, report, _links()), "summary": report.get("kernel", {})} |
|
|
|
|
| def _is_self_target(url: str, request: Request) -> bool: |
| try: |
| parsed_target = urllib.parse.urlparse(url) |
| target_host = parsed_target.hostname or "" |
| target_port = parsed_target.port |
| |
| parsed_self = urllib.parse.urlparse(str(request.base_url)) |
| self_host = parsed_self.hostname or "" |
| self_port = parsed_self.port or 80 |
| |
| |
| is_target_local = target_host.lower() in {"localhost", "127.0.0.1", "::1"} or target_host.startswith("127.") |
| is_self_local = self_host.lower() in {"localhost", "127.0.0.1", "::1"} or self_host.startswith("127.") |
| |
| if is_target_local and is_self_local: |
| return (target_port or 80) == self_port |
| |
| return target_host.lower() == self_host.lower() and (target_port or 80) == self_port |
| except Exception: |
| return False |
|
|
|
|
| @app.post("/api/connect") |
| async def connect_space(payload: ConnectRequest, request: Request) -> dict[str, Any]: |
| """Surgeon's tools: quantify a parallel Space's entire API array. No token — |
| this is an observation surface, scoped to the HF constellation in probe.py.""" |
| target = payload.target.strip() |
| if not target: |
| raise HTTPException(status_code=400, detail="target space url or id is required") |
| |
| try: |
| from key_os.probe import normalize_target |
| normalized_target = normalize_target(target) |
| except ProbeError as exc: |
| raise HTTPException(status_code=400, detail=str(exc)) from exc |
|
|
| if _is_self_target(normalized_target, request): |
| import httpx |
| try: |
| transport = httpx.ASGITransport(app=app) |
| async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client: |
| t0 = time.perf_counter() |
| health_resp = await client.get("/health") |
| elapsed = round((time.perf_counter() - t0) * 1000, 1) |
| |
| spec_resp = await client.get("/openapi.json") |
| spec = spec_resp.json() if spec_resp.status_code == 200 else None |
| |
| if spec and "paths" in spec: |
| from key_os.probe import _parse_openapi |
| endpoints = _parse_openapi(spec) |
| info = spec.get("info") or {} |
| result = { |
| "target": target, |
| "base_url": normalized_target, |
| "fetched_at": utc_now(), |
| "health": { |
| "reachable": True, |
| "probe_path": "/health", |
| "status": health_resp.status_code, |
| "stage": "ok" if health_resp.status_code == 200 else "error", |
| "elapsed_ms": elapsed, |
| }, |
| "status": "connected", |
| "title": info.get("title"), |
| "version": info.get("version"), |
| "openapi": spec.get("openapi"), |
| "count": len(endpoints), |
| "endpoints": endpoints, |
| } |
| else: |
| result = { |
| "target": target, |
| "base_url": normalized_target, |
| "fetched_at": utc_now(), |
| "health": { |
| "reachable": True, |
| "probe_path": "/health", |
| "status": health_resp.status_code, |
| "stage": "ok" if health_resp.status_code == 200 else "error", |
| "elapsed_ms": elapsed, |
| }, |
| "status": "no_openapi", |
| "raw_status": spec_resp.status_code, |
| "message": f"No OpenAPI spec at {normalized_target}/openapi.json", |
| "endpoints": [], |
| } |
| except Exception as exc: |
| result = { |
| "target": target, |
| "base_url": normalized_target, |
| "fetched_at": utc_now(), |
| "health": {"reachable": False, "stage": "unreachable", "status": 0}, |
| "status": "unreachable", |
| "message": f"Self-connection error: {exc}", |
| "endpoints": [], |
| } |
| else: |
| try: |
| result = enumerate_space(target) |
| except ProbeError as exc: |
| raise HTTPException(status_code=400, detail=str(exc)) from exc |
|
|
| try: |
| _store().append_event_receipt( |
| "space_connect", |
| str(result.get("base_url") or target), |
| {"status": result.get("status"), "count": result.get("count")}, |
| source="probe", |
| ) |
| except Exception: |
| pass |
| return result |
|
|
|
|
| @app.post("/api/probe") |
| async def probe_space(payload: ProbeRequest, request: Request) -> dict[str, Any]: |
| """Fire a single request at a connected Space and return the raw result.""" |
| target = payload.target.strip() |
| path = payload.path.strip() |
| method = payload.method.upper() |
| if not target or not path: |
| raise HTTPException(status_code=400, detail="target and path are required") |
| |
| try: |
| from key_os.probe import normalize_target |
| normalized_target = normalize_target(target) |
| except ProbeError as exc: |
| raise HTTPException(status_code=400, detail=str(exc)) from exc |
|
|
| if _is_self_target(normalized_target, request): |
| import httpx |
| if not path.startswith("/"): |
| path = "/" + path |
| try: |
| transport = httpx.ASGITransport(app=app) |
| async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client: |
| t0 = time.perf_counter() |
| resp = await client.request( |
| method=method, |
| url=path, |
| params=payload.query, |
| json=payload.body, |
| timeout=10.0, |
| ) |
| elapsed = round((time.perf_counter() - t0) * 1000, 1) |
| |
| try: |
| body_json = resp.json() |
| except Exception: |
| body_json = None |
| |
| result = { |
| "ok": 200 <= resp.status_code < 400, |
| "status": resp.status_code, |
| "method": method, |
| "url": normalized_target + path, |
| "elapsed_ms": elapsed, |
| "content_type": resp.headers.get("content-type", ""), |
| "json": body_json, |
| "text": None if body_json is not None else resp.text[:20000], |
| "truncated": len(resp.text) > 20000, |
| } |
| except Exception as exc: |
| result = { |
| "ok": False, |
| "status": 0, |
| "method": method, |
| "url": normalized_target + path, |
| "error": f"{type(exc).__name__}: {exc}", |
| "elapsed_ms": 0.0, |
| } |
| else: |
| try: |
| result = call_endpoint( |
| target, |
| path, |
| method=method, |
| query=payload.query, |
| body=payload.body, |
| ) |
| except ProbeError as exc: |
| raise HTTPException(status_code=400, detail=str(exc)) from exc |
|
|
| try: |
| _store().append_event_receipt( |
| "space_probe", |
| f"{method} {path}", |
| {"target": target, "status": result.get("status"), "elapsed_ms": result.get("elapsed_ms")}, |
| source="probe", |
| ) |
| except Exception: |
| pass |
| return result |
|
|
|
|
| @app.get("/api/receipts") |
| async def receipts(limit: int = 50) -> dict[str, Any]: |
| items = _store().load_receipts(limit=limit) |
| return {"count": len(items), "receipts": items} |
|
|
|
|
| @app.post("/api/receipts") |
| async def write_receipt(payload: ReceiptRequest) -> dict[str, Any]: |
| receipt = Receipt( |
| event_type=payload.event_type, |
| subject=payload.subject, |
| source=payload.source, |
| payload=redact(payload.payload), |
| ) |
| _store().append_receipt(receipt) |
| return {"ok": True, "receipt": receipt.to_dict()} |
|
|
|
|
| @app.get("/api/notepad") |
| async def notepad(query: str = "", limit: int = 80, type: str = "") -> dict[str, Any]: |
| items = _store().notepad().list(query=query, limit=limit, note_type=type) |
| return {"schema": "key_os.research_notepad/v1", "count": len(items), "entries": items} |
|
|
|
|
| @app.get("/api/notepad/summary") |
| async def notepad_summary() -> dict[str, Any]: |
| return _store().notepad().summary() |
|
|
|
|
| @app.post("/api/notepad") |
| async def notepad_add(payload: NotepadAddRequest) -> dict[str, Any]: |
| entry = _store().notepad().add(payload.model_dump(), source=payload.source) |
| receipt = _store().append_event_receipt("notepad_add", str(entry.get("id")), entry, source="notepad") |
| return {"ok": True, "entry": entry, "receipt": receipt.to_dict()} |
|
|
|
|
| @app.post("/api/notepad/clear") |
| async def notepad_clear(payload: NotepadClearRequest) -> dict[str, Any]: |
| try: |
| require_confirmation("notepad_clear", payload.model_dump()) |
| except ValueError as exc: |
| raise HTTPException(status_code=400, detail=str(exc)) from exc |
| result = _store().notepad().clear() |
| receipt = _store().append_event_receipt("notepad_clear", "research_notepad", result, source="notepad") |
| return {**result, "receipt": receipt.to_dict()} |
|
|
|
|
| @app.get("/api/dispatch/state") |
| async def dispatch_state() -> dict[str, Any]: |
| store = _store() |
| state = store.dispatch().read() |
| state["plan"] = store.dispatch().plan(store.registry(), policy=state.get("policy") or {}) |
| return state |
|
|
|
|
| @app.post("/api/dispatch/start") |
| async def dispatch_start(payload: DispatchStartRequest) -> dict[str, Any]: |
| store = _store() |
| state = store.dispatch().start(payload.model_dump(), store.registry()) |
| receipt = store.append_event_receipt("dispatch_start", str(payload.seed), payload.model_dump(), source="dispatch") |
| return {"ok": True, "state": state, "receipt": receipt.to_dict()} |
|
|
|
|
| @app.post("/api/dispatch/stop") |
| async def dispatch_stop(request: Request) -> dict[str, Any]: |
| store = _store() |
| state = store.dispatch().stop() |
| receipt = store.append_event_receipt("dispatch_stop", "dispatcher", {}, source="dispatch") |
| return {"ok": True, "state": state, "receipt": receipt.to_dict()} |
|
|
|
|
| @app.get("/api/security/contracts") |
| async def security_contracts() -> dict[str, Any]: |
| return contracts_report() |
|
|
|
|
| @app.post("/api/factory-reset") |
| async def factory_reset(payload: FactoryResetRequest) -> dict[str, Any]: |
| try: |
| require_confirmation("factory_reset", payload.model_dump()) |
| except ValueError as exc: |
| raise HTTPException(status_code=400, detail=str(exc)) from exc |
| clear_receipts = payload.clear_receipts |
| clear_notepad = payload.clear_notepad |
| clear_dispatch = payload.clear_dispatch |
| return _store().factory_reset(clear_receipts=clear_receipts, clear_notepad=clear_notepad, clear_dispatch=clear_dispatch) |
|
|
|
|
| @app.post("/api/reset") |
| async def legacy_factory_reset(payload: FactoryResetRequest) -> dict[str, Any]: |
| return await factory_reset(payload) |
|
|