diff --git a/src/psyc/cockpit/federation_routes.py b/src/psyc/cockpit/federation_routes.py index 90bdcb0..c1adab6 100644 --- a/src/psyc/cockpit/federation_routes.py +++ b/src/psyc/cockpit/federation_routes.py @@ -15,7 +15,7 @@ from fastapi.responses import HTMLResponse, JSONResponse, PlainTextResponse, Red from fastapi.templating import Jinja2Templates from psyc import db, log -from psyc.lines import discovery, federation, pulse, translog +from psyc.lines import discovery, federation, network_view, pulse, translog from psyc.result import Err @@ -30,6 +30,10 @@ _FEED_TTL = 60.0 _PUBLIC_PEERS_CACHE: Dict[str, Any] = {"ts": 0.0, "payload": None} _PUBLIC_PEERS_TTL = 60.0 +# And again for the public federation-network payload (signed JSON view). +_PUBLIC_NETWORK_CACHE: Dict[str, Any] = {"ts": 0.0, "payload": None} +_PUBLIC_NETWORK_TTL = 60.0 + def _admin_ok(request: Request) -> bool: return bool(request.session.get("admin_ok")) @@ -51,6 +55,14 @@ def _cached_public_peers() -> Any: return _PUBLIC_PEERS_CACHE["payload"] +def _cached_public_network() -> Dict[str, Any]: + now = time.time() + if _PUBLIC_NETWORK_CACHE["payload"] is None or (now - _PUBLIC_NETWORK_CACHE["ts"]) > _PUBLIC_NETWORK_TTL: + _PUBLIC_NETWORK_CACHE["payload"] = network_view.build_public_view() + _PUBLIC_NETWORK_CACHE["ts"] = now + return _PUBLIC_NETWORK_CACHE["payload"] + + def register(app: FastAPI, TEMPLATES: Jinja2Templates) -> None: """Mount all federation routes onto `app`.""" @@ -192,6 +204,16 @@ def register(app: FastAPI, TEMPLATES: Jinja2Templates) -> None: """ return JSONResponse(_cached_public_peers()) + @app.get("/federation/network") + def federation_network_public() -> JSONResponse: + """Signed federation-network attestation — for transitive-view fetchers. + + Mirrors /federation/peers/public in spirit but adds our outbound vouches + so a fetcher can reconstruct the local web-of-trust shape. Trusted peers + only — never unknown or blocked. Signal hashes are deliberately omitted. + """ + return JSONResponse(_cached_public_network()) + # ---------- public vouches + transparency log -------------------- @app.get("/federation/vouches")