From 9a97983a64e0fa9f4032e760ab9dcda2e820a7ad Mon Sep 17 00:00:00 2001 From: Brandon Schneider Date: Mon, 18 May 2026 10:58:35 -0500 Subject: [PATCH] Add apiProvider service kind, credential gateway, and cupfox routing - TopologyNode.lean: apiProvider service kind with network-only requirement - server.py: OP_CREDENTIALS handler, RDS-backed wiki/fractal fold layers, /credentials HTTP endpoint via SurfaceHandler - cupfox-config.nix: Caddy routes for /api/credentials/ -> EC2 (100.69.1.43) and /git/ -> local Forgejo --- .../Semantics/Semantics/TopologyNode.lean | 5 + .../infra/embedded_surface/server.py | 108 +++++++++++++++--- cupfox-config.nix | 11 +- 3 files changed, 109 insertions(+), 15 deletions(-) diff --git a/0-Core-Formalism/lean/Semantics/Semantics/TopologyNode.lean b/0-Core-Formalism/lean/Semantics/Semantics/TopologyNode.lean index bb7a8420..a98a8d09 100644 --- a/0-Core-Formalism/lean/Semantics/Semantics/TopologyNode.lean +++ b/0-Core-Formalism/lean/Semantics/Semantics/TopologyNode.lean @@ -68,6 +68,7 @@ inductive ServiceKind where | substrateIndex | rgflowFilter | tardyInterpreter + | apiProvider deriving Repr, DecidableEq, Inhabited, BEq /-- Hardware requirements per service. -/ @@ -79,6 +80,7 @@ def serviceRequirements : ServiceKind → List HardwareCapability | .substrateIndex => [.storage, .compute] | .rgflowFilter => [.compute, .network] | .tardyInterpreter => [.compute] + | .apiProvider => [.network] /-- Thermodynamic cost to run a service (Q16.16 per tick). -/ def serviceCost : ServiceKind → Q16_16 @@ -89,6 +91,7 @@ def serviceCost : ServiceKind → Q16_16 | .substrateIndex => ofNat 2 | .rgflowFilter => ofNat 3 | .tardyInterpreter => ofNat 1 + | .apiProvider => ofNat 1 -- ═══════════════════════════════════════════════════════════════════════════ -- §3 Node State Machine @@ -236,5 +239,7 @@ def exampleFoxTopNode : TopologyNode := #eval canRunService exampleCoreNode ServiceKind.architect #eval canRunService exampleEdgeNode ServiceKind.architect #eval canRunService exampleEdgeNode ServiceKind.warden +#eval canRunService exampleEdgeNode ServiceKind.apiProvider +#eval serviceCost ServiceKind.apiProvider end Semantics.TopologyNode diff --git a/4-Infrastructure/infra/embedded_surface/server.py b/4-Infrastructure/infra/embedded_surface/server.py index 272ed2e6..78bb7fb6 100644 --- a/4-Infrastructure/infra/embedded_surface/server.py +++ b/4-Infrastructure/infra/embedded_surface/server.py @@ -40,6 +40,7 @@ OP_PLAN_ROUTE = 11 OP_WIKI = 12 OP_FRACTAL_FOLD = 13 OP_META_AUTOTYPE = 14 +OP_CREDENTIALS = 15 CODEC_NONE = 0 CODEC_ZLIB_TEST = 1 @@ -139,6 +140,7 @@ def primitive_payload() -> dict[str, Any]: "wiki", "fractal_fold", "meta_autotype", + "credentials", ] return { "node": PROFILE["node_id"], @@ -267,15 +269,25 @@ def handle_surface_op(op: int, payload: bytes) -> dict[str, Any]: request = json.loads(payload.decode("utf-8")) if payload else {"op": "recent"} if not isinstance(request, dict): raise ValueError("wiki payload must be a JSON object") - try: - from infra.ene_wiki_layer import ENEWikiLayer - except ModuleNotFoundError: - import sys + use_rds = os.environ.get("RS_USE_RDS", "").lower() in ("1", "true", "yes") + if use_rds: + try: + from infra.ene_rds_wiki_layer import ENERDSWikiLayer + except ModuleNotFoundError: + import sys + sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + from infra.ene_rds_wiki_layer import ENERDSWikiLayer - sys.path.insert(0, str(Path(__file__).resolve().parents[2])) - from infra.ene_wiki_layer import ENEWikiLayer + wiki = ENERDSWikiLayer() + else: + try: + from infra.ene_wiki_layer import ENEWikiLayer + except ModuleNotFoundError: + import sys + sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + from infra.ene_wiki_layer import ENEWikiLayer - wiki = ENEWikiLayer(STATE_DIR / "ene-wiki.db") + wiki = ENEWikiLayer(STATE_DIR / "ene-wiki.db") return wiki.handle_request(request) except Exception as exc: return {"ok": False, "op": "wiki", "error": str(exc)} @@ -284,15 +296,25 @@ def handle_surface_op(op: int, payload: bytes) -> dict[str, Any]: request = json.loads(payload.decode("utf-8")) if payload else {"op": "manifest"} if not isinstance(request, dict): raise ValueError("fractal_fold payload must be a JSON object") - try: - from infra.ene_fractal_fold import ENEFractalFold - except ModuleNotFoundError: - import sys + use_rds = os.environ.get("RS_USE_RDS", "").lower() in ("1", "true", "yes") + if use_rds: + try: + from infra.ene_rds_fractal_fold import ENERDSFractalFold + except ModuleNotFoundError: + import sys + sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + from infra.ene_rds_fractal_fold import ENERDSFractalFold - sys.path.insert(0, str(Path(__file__).resolve().parents[2])) - from infra.ene_fractal_fold import ENEFractalFold + fold = ENERDSFractalFold() + else: + try: + from infra.ene_fractal_fold import ENEFractalFold + except ModuleNotFoundError: + import sys + sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + from infra.ene_fractal_fold import ENEFractalFold - fold = ENEFractalFold(STATE_DIR / "ene-fractal-fold.db") + fold = ENEFractalFold(STATE_DIR / "ene-fractal-fold.db") return fold.handle_request(request) except Exception as exc: return {"ok": False, "op": "fractal_fold", "error": str(exc)} @@ -312,6 +334,50 @@ def handle_surface_op(op: int, payload: bytes) -> dict[str, Any]: return handle_request(request) except Exception as exc: return {"ok": False, "op": "meta_autotype", "error": str(exc)} + if op == OP_CREDENTIALS: + try: + request = json.loads(payload.decode("utf-8")) if payload else {} + if not isinstance(request, dict): + raise ValueError("credentials payload must be a JSON object") + try: + from credential_provider import ( + credential_status, + provider_manifest, + resolve_credential, + ) + except ModuleNotFoundError: + try: + from infra.credential_provider import ( + credential_status, + provider_manifest, + resolve_credential, + ) + except ModuleNotFoundError: + import sys + + sys.path.insert(0, str(Path(__file__).resolve().parent)) + from credential_provider import ( + credential_status, + provider_manifest, + resolve_credential, + ) + + action = request.get("action", "status") + if action == "status": + return credential_status() + if action == "manifest": + return provider_manifest() + if action == "resolve": + provider = request.get("provider", "") + if not provider: + return {"ok": False, "error": "missing provider name"} + cred = resolve_credential(provider) + if cred is None: + return {"ok": False, "error": f"provider {provider!r} not available"} + return {"ok": True, "provider": cred.provider, "value": cred.value} + return {"ok": False, "error": f"unknown credentials action {action!r}"} + except Exception as exc: + return {"ok": False, "op": "credentials", "error": str(exc)} return {"error": "unknown-op", "op": op} @@ -422,6 +488,20 @@ class SurfaceHandler(BaseHTTPRequestHandler): if self.path == "/ws": self.handle_ws() return + if self.path == "/credentials": + try: + from credential_provider import credential_status + except ModuleNotFoundError: + try: + from infra.credential_provider import credential_status + except ModuleNotFoundError: + import sys + + sys.path.insert(0, str(Path(__file__).resolve().parent)) + from credential_provider import credential_status + + self.send_json(credential_status()) + return self.send_json({"error": "not-found"}, HTTPStatus.NOT_FOUND) def handle_ws(self) -> None: diff --git a/cupfox-config.nix b/cupfox-config.nix index f39604b6..af82e368 100644 --- a/cupfox-config.nix +++ b/cupfox-config.nix @@ -52,13 +52,22 @@ in logFormat = "level INFO"; package = caddyWithPorkbun; extraConfig = '' - researchstack.info, cupfox.researchstack.info, http://100.126.151.57 { + researchstack.info, cupfox.researchstack.info, git.researchstack.info, http://100.126.151.57 { tls { dns porkbun { api_key {$PORKBUN_API_KEY} api_secret_key {$PORKBUN_SECRET_KEY} } } + + handle_path /api/credentials/* { + reverse_proxy http://100.69.1.43:8444 + } + + handle_path /git/* { + reverse_proxy http://127.0.0.1:3000 + } + root * /var/www/researchstack file_server }