Publish expanded LumynaX product platform package
Browse files- MANIFEST.in +12 -0
- PYPI_RELEASE.md +30 -0
- README.md +32 -1
- configs/gateway.local.json +13 -13
- configs/service.local.json +14 -14
- marama_route/__init__.py +40 -40
- marama_route/_ui_server.py +121 -121
- marama_route/cli.py +233 -233
- marama_route/configs/gateway.local.json +13 -13
- marama_route/configs/service.local.json +14 -14
- marama_route/py.typed +0 -0
- marama_route/server.py +312 -312
- product_manifest.json +71 -56
- pyproject.toml +31 -6
- sovereigncode/__init__.py +41 -41
- sovereigncode/_ui_server.py +121 -121
- sovereigncode/configs/gateway.local.json +13 -13
- sovereigncode/configs/service.local.json +14 -14
- sovereigncode/ledger.py +64 -64
- sovereigncode/py.typed +0 -0
- sovereigncode/server.py +352 -352
MANIFEST.in
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
include README.md LICENSE requirements.txt SMOKE_TESTS.md PYPI_RELEASE.md quickstart.py product_manifest.json product_blueprint.md architecture.md
|
| 2 |
+
recursive-include sovereigncode *.py py.typed
|
| 3 |
+
recursive-include sovereigncode/configs *
|
| 4 |
+
recursive-include sovereigncode/examples *
|
| 5 |
+
recursive-include sovereigncode/integrations *
|
| 6 |
+
recursive-include sovereigncode/policy-packs *
|
| 7 |
+
recursive-include sovereigncode/schemas *
|
| 8 |
+
recursive-include marama_route *.py py.typed
|
| 9 |
+
recursive-include marama_route/configs *
|
| 10 |
+
recursive-include marama_route/examples *
|
| 11 |
+
recursive-include marama_route/integrations *
|
| 12 |
+
recursive-include marama_route/schemas *
|
PYPI_RELEASE.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# PyPI Release: AbteeX SovereignCode
|
| 2 |
+
|
| 3 |
+
Package: `abteex-sovereigncode`
|
| 4 |
+
Version: `0.4.0`
|
| 5 |
+
|
| 6 |
+
## Local Build
|
| 7 |
+
|
| 8 |
+
```bash
|
| 9 |
+
python -m build
|
| 10 |
+
python -m twine check dist/*
|
| 11 |
+
python quickstart.py
|
| 12 |
+
sovereigncode serve --smoke
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
## Publish
|
| 16 |
+
|
| 17 |
+
```bash
|
| 18 |
+
python -m twine upload dist/*
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
Required local credentials: `TWINE_USERNAME=__token__` and `TWINE_PASSWORD=<pypi-token>`.
|
| 22 |
+
The GitHub workflow can publish with PyPI trusted publishing when the PyPI project
|
| 23 |
+
is configured to trust `Aimaghsoodi/TinyLuminaX`.
|
| 24 |
+
|
| 25 |
+
## Runtime Surface
|
| 26 |
+
|
| 27 |
+
- CLI: `sovereigncode`
|
| 28 |
+
- Local UI/API: `sovereigncode serve --port 8788 --open`
|
| 29 |
+
- Policy API: `POST /v1/evaluate`, `POST /v1/plan-turn`, `POST /v1/tool-check`
|
| 30 |
+
- Audit: `GET /v1/audit` and `sovereigncode audit --limit 25`
|
README.md
CHANGED
|
@@ -21,10 +21,17 @@ language:
|
|
| 21 |
# AbteeX SovereignCode
|
| 22 |
<!-- abteex-sovereigncode-card:v3 -->
|
| 23 |
|
| 24 |
-
Standalone release package for `AbteeXAILab/sovereigncode`.
|
| 25 |
|
| 26 |
## Install
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
```bash
|
| 29 |
hf download AbteeXAILab/sovereigncode --local-dir sovereigncode --repo-type model
|
| 30 |
cd sovereigncode
|
|
@@ -35,6 +42,10 @@ python quickstart.py
|
|
| 35 |
## Included Runtime Commands
|
| 36 |
|
| 37 |
```bash
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
python -m sovereigncode.cli evaluate --capsule examples/capsule.restricted-nz-code.json --request examples/request.allowed-local-edit.json
|
| 39 |
python -m sovereigncode.cli plan-turn --capsule examples/capsule.restricted-nz-code.json --request examples/request.allowed-local-edit.json --route-request examples/request.code-restricted.json --registry configs/lumynax_model_registry.json
|
| 40 |
python -m sovereigncode.cli policy-matrix --capsule examples/capsule.restricted-nz-code.json --request examples/request.allowed-local-edit.json
|
|
@@ -83,6 +94,16 @@ The initial product scaffold includes:
|
|
| 83 |
|
| 84 |
## Quickstart
|
| 85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
From the repo root:
|
| 87 |
|
| 88 |
```bash
|
|
@@ -162,6 +183,16 @@ py -3 -m tinyluminax.products.sovereigncode.cli audit --limit 10
|
|
| 162 |
|
| 163 |
The service exposes `GET /health`, `GET /v1/audit`, `POST /v1/evaluate`, `POST /v1/plan-turn`, `POST /v1/tool-check`, `POST /v1/policy-matrix`, and the existing browser `/api/*` routes. It writes JSONL audit records to `.sovereigncode/audit.jsonl` by default.
|
| 164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
Smoke-check the UI routes without opening a browser:
|
| 166 |
|
| 167 |
```bash
|
|
|
|
| 21 |
# AbteeX SovereignCode
|
| 22 |
<!-- abteex-sovereigncode-card:v3 -->
|
| 23 |
|
| 24 |
+
Standalone release package for PyPI package `abteex-sovereigncode` and Hugging Face repo `AbteeXAILab/sovereigncode`.
|
| 25 |
|
| 26 |
## Install
|
| 27 |
|
| 28 |
+
```bash
|
| 29 |
+
pip install abteex-sovereigncode
|
| 30 |
+
sovereigncode serve --smoke
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
HF mirror install:
|
| 34 |
+
|
| 35 |
```bash
|
| 36 |
hf download AbteeXAILab/sovereigncode --local-dir sovereigncode --repo-type model
|
| 37 |
cd sovereigncode
|
|
|
|
| 42 |
## Included Runtime Commands
|
| 43 |
|
| 44 |
```bash
|
| 45 |
+
sovereigncode serve --smoke
|
| 46 |
+
sovereigncode audit --limit 5
|
| 47 |
+
sovereigncode opencode-config
|
| 48 |
+
sovereigncode ui --port 8788 --open
|
| 49 |
python -m sovereigncode.cli evaluate --capsule examples/capsule.restricted-nz-code.json --request examples/request.allowed-local-edit.json
|
| 50 |
python -m sovereigncode.cli plan-turn --capsule examples/capsule.restricted-nz-code.json --request examples/request.allowed-local-edit.json --route-request examples/request.code-restricted.json --registry configs/lumynax_model_registry.json
|
| 51 |
python -m sovereigncode.cli policy-matrix --capsule examples/capsule.restricted-nz-code.json --request examples/request.allowed-local-edit.json
|
|
|
|
| 94 |
|
| 95 |
## Quickstart
|
| 96 |
|
| 97 |
+
Install the standalone package:
|
| 98 |
+
|
| 99 |
+
```bash
|
| 100 |
+
pip install abteex-sovereigncode
|
| 101 |
+
sovereigncode serve --smoke
|
| 102 |
+
sovereigncode serve --port 8788 --open
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
The same package is mirrored on Hugging Face at `AbteeXAILab/sovereigncode` and can be installed from a downloaded snapshot with `pip install -e .`.
|
| 106 |
+
|
| 107 |
From the repo root:
|
| 108 |
|
| 109 |
```bash
|
|
|
|
| 183 |
|
| 184 |
The service exposes `GET /health`, `GET /v1/audit`, `POST /v1/evaluate`, `POST /v1/plan-turn`, `POST /v1/tool-check`, `POST /v1/policy-matrix`, and the existing browser `/api/*` routes. It writes JSONL audit records to `.sovereigncode/audit.jsonl` by default.
|
| 185 |
|
| 186 |
+
## PyPI Release Readiness
|
| 187 |
+
|
| 188 |
+
The generated standalone package includes `pyproject.toml`, `MANIFEST.in`, `SMOKE_TESTS.md`, `PYPI_RELEASE.md`, package data, console script entry points, and wheel/sdist validation support. The release gate is:
|
| 189 |
+
|
| 190 |
+
```bash
|
| 191 |
+
py -3 scripts/build_product_release_packages.py
|
| 192 |
+
py -3 scripts/verify_product_installs.py --product sovereigncode
|
| 193 |
+
py -3 scripts/publish_products_to_pypi.py --product sovereigncode --dry-run
|
| 194 |
+
```
|
| 195 |
+
|
| 196 |
Smoke-check the UI routes without opening a browser:
|
| 197 |
|
| 198 |
```bash
|
configs/gateway.local.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
| 1 |
-
{
|
| 2 |
-
"mode": "route_only",
|
| 3 |
-
"prompt_retention": "not_stored_by_default",
|
| 4 |
-
"default_timeout_seconds": 120,
|
| 5 |
-
"backends": {
|
| 6 |
-
"example-local-openai-compatible": {
|
| 7 |
-
"type": "openai_compatible",
|
| 8 |
-
"base_url": "http://127.0.0.1:8000/v1",
|
| 9 |
-
"api_key_env": "",
|
| 10 |
-
"model": "local-model-id"
|
| 11 |
-
}
|
| 12 |
-
}
|
| 13 |
-
}
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"mode": "route_only",
|
| 3 |
+
"prompt_retention": "not_stored_by_default",
|
| 4 |
+
"default_timeout_seconds": 120,
|
| 5 |
+
"backends": {
|
| 6 |
+
"example-local-openai-compatible": {
|
| 7 |
+
"type": "openai_compatible",
|
| 8 |
+
"base_url": "http://127.0.0.1:8000/v1",
|
| 9 |
+
"api_key_env": "",
|
| 10 |
+
"model": "local-model-id"
|
| 11 |
+
}
|
| 12 |
+
}
|
| 13 |
+
}
|
configs/service.local.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
| 1 |
-
{
|
| 2 |
-
"ledger_path": ".sovereigncode/audit.jsonl",
|
| 3 |
-
"default_region": "NZ",
|
| 4 |
-
"fail_closed": true,
|
| 5 |
-
"require_human_review_for": [
|
| 6 |
-
"write_files",
|
| 7 |
-
"execute_shell",
|
| 8 |
-
"network_export",
|
| 9 |
-
"commit",
|
| 10 |
-
"publish",
|
| 11 |
-
"train_model"
|
| 12 |
-
],
|
| 13 |
-
"marama_route_base_url": "http://127.0.0.1:8787/v1"
|
| 14 |
-
}
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"ledger_path": ".sovereigncode/audit.jsonl",
|
| 3 |
+
"default_region": "NZ",
|
| 4 |
+
"fail_closed": true,
|
| 5 |
+
"require_human_review_for": [
|
| 6 |
+
"write_files",
|
| 7 |
+
"execute_shell",
|
| 8 |
+
"network_export",
|
| 9 |
+
"commit",
|
| 10 |
+
"publish",
|
| 11 |
+
"train_model"
|
| 12 |
+
],
|
| 13 |
+
"marama_route_base_url": "http://127.0.0.1:8787/v1"
|
| 14 |
+
}
|
marama_route/__init__.py
CHANGED
|
@@ -1,40 +1,40 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
from .gateway import (
|
| 4 |
-
build_chat_route_response,
|
| 5 |
-
build_models_response,
|
| 6 |
-
route_chat_payload,
|
| 7 |
-
routing_request_from_chat_payload,
|
| 8 |
-
)
|
| 9 |
-
from .platform import (
|
| 10 |
-
build_opencode_provider_config,
|
| 11 |
-
build_registry_analytics,
|
| 12 |
-
catalog_models,
|
| 13 |
-
compare_models,
|
| 14 |
-
route_scenario_matrix,
|
| 15 |
-
)
|
| 16 |
-
from .registry import ModelEndpoint, RoutingRequest, load_model_registry
|
| 17 |
-
from .router import RouteDecision, SovereignModelRouter
|
| 18 |
-
from .server import handle_gateway_request, load_gateway_config, smoke_gateway
|
| 19 |
-
from .ui import smoke_ui as smoke_ui
|
| 20 |
-
|
| 21 |
-
__all__ = [
|
| 22 |
-
"ModelEndpoint",
|
| 23 |
-
"RouteDecision",
|
| 24 |
-
"RoutingRequest",
|
| 25 |
-
"SovereignModelRouter",
|
| 26 |
-
"build_chat_route_response",
|
| 27 |
-
"build_models_response",
|
| 28 |
-
"build_opencode_provider_config",
|
| 29 |
-
"build_registry_analytics",
|
| 30 |
-
"catalog_models",
|
| 31 |
-
"compare_models",
|
| 32 |
-
"handle_gateway_request",
|
| 33 |
-
"load_gateway_config",
|
| 34 |
-
"load_model_registry",
|
| 35 |
-
"route_chat_payload",
|
| 36 |
-
"route_scenario_matrix",
|
| 37 |
-
"routing_request_from_chat_payload",
|
| 38 |
-
"smoke_gateway",
|
| 39 |
-
"smoke_ui",
|
| 40 |
-
]
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from .gateway import (
|
| 4 |
+
build_chat_route_response,
|
| 5 |
+
build_models_response,
|
| 6 |
+
route_chat_payload,
|
| 7 |
+
routing_request_from_chat_payload,
|
| 8 |
+
)
|
| 9 |
+
from .platform import (
|
| 10 |
+
build_opencode_provider_config,
|
| 11 |
+
build_registry_analytics,
|
| 12 |
+
catalog_models,
|
| 13 |
+
compare_models,
|
| 14 |
+
route_scenario_matrix,
|
| 15 |
+
)
|
| 16 |
+
from .registry import ModelEndpoint, RoutingRequest, load_model_registry
|
| 17 |
+
from .router import RouteDecision, SovereignModelRouter
|
| 18 |
+
from .server import handle_gateway_request, load_gateway_config, smoke_gateway
|
| 19 |
+
from .ui import smoke_ui as smoke_ui
|
| 20 |
+
|
| 21 |
+
__all__ = [
|
| 22 |
+
"ModelEndpoint",
|
| 23 |
+
"RouteDecision",
|
| 24 |
+
"RoutingRequest",
|
| 25 |
+
"SovereignModelRouter",
|
| 26 |
+
"build_chat_route_response",
|
| 27 |
+
"build_models_response",
|
| 28 |
+
"build_opencode_provider_config",
|
| 29 |
+
"build_registry_analytics",
|
| 30 |
+
"catalog_models",
|
| 31 |
+
"compare_models",
|
| 32 |
+
"handle_gateway_request",
|
| 33 |
+
"load_gateway_config",
|
| 34 |
+
"load_model_registry",
|
| 35 |
+
"route_chat_payload",
|
| 36 |
+
"route_scenario_matrix",
|
| 37 |
+
"routing_request_from_chat_payload",
|
| 38 |
+
"smoke_gateway",
|
| 39 |
+
"smoke_ui",
|
| 40 |
+
]
|
marama_route/_ui_server.py
CHANGED
|
@@ -1,121 +1,121 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
import json
|
| 4 |
-
import socket
|
| 5 |
-
import webbrowser
|
| 6 |
-
from collections.abc import Callable
|
| 7 |
-
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
| 8 |
-
from typing import Any
|
| 9 |
-
from urllib.parse import urlparse
|
| 10 |
-
|
| 11 |
-
ApiHandler = Callable[[str, str, dict[str, Any] | None], tuple[int, dict[str, Any]]]
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
def find_available_port(host: str, preferred_port: int, *, attempts: int = 50) -> int:
|
| 15 |
-
start = preferred_port if preferred_port > 0 else 0
|
| 16 |
-
if start == 0:
|
| 17 |
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
|
| 18 |
-
probe.bind((host, 0))
|
| 19 |
-
return int(probe.getsockname()[1])
|
| 20 |
-
|
| 21 |
-
for port in range(start, start + attempts):
|
| 22 |
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
|
| 23 |
-
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
| 24 |
-
try:
|
| 25 |
-
probe.bind((host, port))
|
| 26 |
-
except OSError:
|
| 27 |
-
continue
|
| 28 |
-
return port
|
| 29 |
-
raise OSError(f"No available port found from {preferred_port} to {preferred_port + attempts - 1}")
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
def serve_dashboard(
|
| 33 |
-
*,
|
| 34 |
-
product_name: str,
|
| 35 |
-
html: str,
|
| 36 |
-
api_handler: ApiHandler,
|
| 37 |
-
host: str,
|
| 38 |
-
port: int,
|
| 39 |
-
open_browser: bool = False,
|
| 40 |
-
api_path_prefixes: tuple[str, ...] = ("/api/",),
|
| 41 |
-
api_exact_paths: tuple[str, ...] = (),
|
| 42 |
-
) -> int:
|
| 43 |
-
actual_port = find_available_port(host, port)
|
| 44 |
-
exact_paths = set(api_exact_paths)
|
| 45 |
-
|
| 46 |
-
def is_api_path(path: str) -> bool:
|
| 47 |
-
return path in exact_paths or any(path.startswith(prefix) for prefix in api_path_prefixes)
|
| 48 |
-
|
| 49 |
-
class Handler(BaseHTTPRequestHandler):
|
| 50 |
-
server_version = "AbteeXProductUI/0.1"
|
| 51 |
-
|
| 52 |
-
def do_GET(self) -> None: # noqa: N802 - stdlib handler method name
|
| 53 |
-
path = urlparse(self.path).path
|
| 54 |
-
if path == "/":
|
| 55 |
-
self._send_text(200, html, "text/html; charset=utf-8")
|
| 56 |
-
return
|
| 57 |
-
if is_api_path(path):
|
| 58 |
-
self._send_api("GET", path, None)
|
| 59 |
-
return
|
| 60 |
-
self._send_json(404, {"ok": False, "error": "not_found"})
|
| 61 |
-
|
| 62 |
-
def do_POST(self) -> None: # noqa: N802 - stdlib handler method name
|
| 63 |
-
path = urlparse(self.path).path
|
| 64 |
-
if not is_api_path(path):
|
| 65 |
-
self._send_json(404, {"ok": False, "error": "not_found"})
|
| 66 |
-
return
|
| 67 |
-
try:
|
| 68 |
-
length = int(self.headers.get("Content-Length", "0"))
|
| 69 |
-
raw = self.rfile.read(length).decode("utf-8") if length else "{}"
|
| 70 |
-
payload = json.loads(raw)
|
| 71 |
-
if not isinstance(payload, dict):
|
| 72 |
-
raise ValueError("JSON body must be an object")
|
| 73 |
-
self._send_api("POST", path, payload)
|
| 74 |
-
except Exception as exc: # defensive API boundary
|
| 75 |
-
self._send_json(400, {"ok": False, "error": str(exc)})
|
| 76 |
-
|
| 77 |
-
def log_message(self, format: str, *args: Any) -> None: # noqa: A002
|
| 78 |
-
return
|
| 79 |
-
|
| 80 |
-
def _send_api(
|
| 81 |
-
self,
|
| 82 |
-
method: str,
|
| 83 |
-
path: str,
|
| 84 |
-
payload: dict[str, Any] | None,
|
| 85 |
-
) -> None:
|
| 86 |
-
try:
|
| 87 |
-
status, response = api_handler(method, path, payload)
|
| 88 |
-
except Exception as exc: # defensive API boundary
|
| 89 |
-
status, response = 500, {"ok": False, "error": str(exc)}
|
| 90 |
-
self._send_json(status, response)
|
| 91 |
-
|
| 92 |
-
def _send_json(self, status: int, payload: dict[str, Any]) -> None:
|
| 93 |
-
body = json.dumps(payload, indent=2, sort_keys=True).encode("utf-8")
|
| 94 |
-
self.send_response(status)
|
| 95 |
-
self.send_header("Content-Type", "application/json; charset=utf-8")
|
| 96 |
-
self.send_header("Content-Length", str(len(body)))
|
| 97 |
-
self.send_header("Cache-Control", "no-store")
|
| 98 |
-
self.end_headers()
|
| 99 |
-
self.wfile.write(body)
|
| 100 |
-
|
| 101 |
-
def _send_text(self, status: int, body: str, content_type: str) -> None:
|
| 102 |
-
encoded = body.encode("utf-8")
|
| 103 |
-
self.send_response(status)
|
| 104 |
-
self.send_header("Content-Type", content_type)
|
| 105 |
-
self.send_header("Content-Length", str(len(encoded)))
|
| 106 |
-
self.send_header("Cache-Control", "no-store")
|
| 107 |
-
self.end_headers()
|
| 108 |
-
self.wfile.write(encoded)
|
| 109 |
-
|
| 110 |
-
server = ThreadingHTTPServer((host, actual_port), Handler)
|
| 111 |
-
url = f"http://{host}:{actual_port}/"
|
| 112 |
-
print(f"{product_name} UI listening on {url}")
|
| 113 |
-
if open_browser:
|
| 114 |
-
webbrowser.open(url)
|
| 115 |
-
try:
|
| 116 |
-
server.serve_forever()
|
| 117 |
-
except KeyboardInterrupt:
|
| 118 |
-
print(f"{product_name} UI stopped")
|
| 119 |
-
finally:
|
| 120 |
-
server.server_close()
|
| 121 |
-
return 0
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import socket
|
| 5 |
+
import webbrowser
|
| 6 |
+
from collections.abc import Callable
|
| 7 |
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
| 8 |
+
from typing import Any
|
| 9 |
+
from urllib.parse import urlparse
|
| 10 |
+
|
| 11 |
+
ApiHandler = Callable[[str, str, dict[str, Any] | None], tuple[int, dict[str, Any]]]
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def find_available_port(host: str, preferred_port: int, *, attempts: int = 50) -> int:
|
| 15 |
+
start = preferred_port if preferred_port > 0 else 0
|
| 16 |
+
if start == 0:
|
| 17 |
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
|
| 18 |
+
probe.bind((host, 0))
|
| 19 |
+
return int(probe.getsockname()[1])
|
| 20 |
+
|
| 21 |
+
for port in range(start, start + attempts):
|
| 22 |
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
|
| 23 |
+
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
| 24 |
+
try:
|
| 25 |
+
probe.bind((host, port))
|
| 26 |
+
except OSError:
|
| 27 |
+
continue
|
| 28 |
+
return port
|
| 29 |
+
raise OSError(f"No available port found from {preferred_port} to {preferred_port + attempts - 1}")
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def serve_dashboard(
|
| 33 |
+
*,
|
| 34 |
+
product_name: str,
|
| 35 |
+
html: str,
|
| 36 |
+
api_handler: ApiHandler,
|
| 37 |
+
host: str,
|
| 38 |
+
port: int,
|
| 39 |
+
open_browser: bool = False,
|
| 40 |
+
api_path_prefixes: tuple[str, ...] = ("/api/",),
|
| 41 |
+
api_exact_paths: tuple[str, ...] = (),
|
| 42 |
+
) -> int:
|
| 43 |
+
actual_port = find_available_port(host, port)
|
| 44 |
+
exact_paths = set(api_exact_paths)
|
| 45 |
+
|
| 46 |
+
def is_api_path(path: str) -> bool:
|
| 47 |
+
return path in exact_paths or any(path.startswith(prefix) for prefix in api_path_prefixes)
|
| 48 |
+
|
| 49 |
+
class Handler(BaseHTTPRequestHandler):
|
| 50 |
+
server_version = "AbteeXProductUI/0.1"
|
| 51 |
+
|
| 52 |
+
def do_GET(self) -> None: # noqa: N802 - stdlib handler method name
|
| 53 |
+
path = urlparse(self.path).path
|
| 54 |
+
if path == "/":
|
| 55 |
+
self._send_text(200, html, "text/html; charset=utf-8")
|
| 56 |
+
return
|
| 57 |
+
if is_api_path(path):
|
| 58 |
+
self._send_api("GET", path, None)
|
| 59 |
+
return
|
| 60 |
+
self._send_json(404, {"ok": False, "error": "not_found"})
|
| 61 |
+
|
| 62 |
+
def do_POST(self) -> None: # noqa: N802 - stdlib handler method name
|
| 63 |
+
path = urlparse(self.path).path
|
| 64 |
+
if not is_api_path(path):
|
| 65 |
+
self._send_json(404, {"ok": False, "error": "not_found"})
|
| 66 |
+
return
|
| 67 |
+
try:
|
| 68 |
+
length = int(self.headers.get("Content-Length", "0"))
|
| 69 |
+
raw = self.rfile.read(length).decode("utf-8") if length else "{}"
|
| 70 |
+
payload = json.loads(raw)
|
| 71 |
+
if not isinstance(payload, dict):
|
| 72 |
+
raise ValueError("JSON body must be an object")
|
| 73 |
+
self._send_api("POST", path, payload)
|
| 74 |
+
except Exception as exc: # defensive API boundary
|
| 75 |
+
self._send_json(400, {"ok": False, "error": str(exc)})
|
| 76 |
+
|
| 77 |
+
def log_message(self, format: str, *args: Any) -> None: # noqa: A002
|
| 78 |
+
return
|
| 79 |
+
|
| 80 |
+
def _send_api(
|
| 81 |
+
self,
|
| 82 |
+
method: str,
|
| 83 |
+
path: str,
|
| 84 |
+
payload: dict[str, Any] | None,
|
| 85 |
+
) -> None:
|
| 86 |
+
try:
|
| 87 |
+
status, response = api_handler(method, path, payload)
|
| 88 |
+
except Exception as exc: # defensive API boundary
|
| 89 |
+
status, response = 500, {"ok": False, "error": str(exc)}
|
| 90 |
+
self._send_json(status, response)
|
| 91 |
+
|
| 92 |
+
def _send_json(self, status: int, payload: dict[str, Any]) -> None:
|
| 93 |
+
body = json.dumps(payload, indent=2, sort_keys=True).encode("utf-8")
|
| 94 |
+
self.send_response(status)
|
| 95 |
+
self.send_header("Content-Type", "application/json; charset=utf-8")
|
| 96 |
+
self.send_header("Content-Length", str(len(body)))
|
| 97 |
+
self.send_header("Cache-Control", "no-store")
|
| 98 |
+
self.end_headers()
|
| 99 |
+
self.wfile.write(body)
|
| 100 |
+
|
| 101 |
+
def _send_text(self, status: int, body: str, content_type: str) -> None:
|
| 102 |
+
encoded = body.encode("utf-8")
|
| 103 |
+
self.send_response(status)
|
| 104 |
+
self.send_header("Content-Type", content_type)
|
| 105 |
+
self.send_header("Content-Length", str(len(encoded)))
|
| 106 |
+
self.send_header("Cache-Control", "no-store")
|
| 107 |
+
self.end_headers()
|
| 108 |
+
self.wfile.write(encoded)
|
| 109 |
+
|
| 110 |
+
server = ThreadingHTTPServer((host, actual_port), Handler)
|
| 111 |
+
url = f"http://{host}:{actual_port}/"
|
| 112 |
+
print(f"{product_name} UI listening on {url}")
|
| 113 |
+
if open_browser:
|
| 114 |
+
webbrowser.open(url)
|
| 115 |
+
try:
|
| 116 |
+
server.serve_forever()
|
| 117 |
+
except KeyboardInterrupt:
|
| 118 |
+
print(f"{product_name} UI stopped")
|
| 119 |
+
finally:
|
| 120 |
+
server.server_close()
|
| 121 |
+
return 0
|
marama_route/cli.py
CHANGED
|
@@ -1,233 +1,233 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
import argparse
|
| 4 |
-
import json
|
| 5 |
-
from collections.abc import Sequence
|
| 6 |
-
from pathlib import Path
|
| 7 |
-
from typing import Any
|
| 8 |
-
|
| 9 |
-
from .gateway import build_models_response, route_chat_payload
|
| 10 |
-
from .platform import (
|
| 11 |
-
build_opencode_provider_config,
|
| 12 |
-
build_registry_analytics,
|
| 13 |
-
catalog_models,
|
| 14 |
-
compare_models,
|
| 15 |
-
route_scenario_matrix,
|
| 16 |
-
)
|
| 17 |
-
from .registry import RoutingRequest, load_model_registry
|
| 18 |
-
from .router import SovereignModelRouter
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
def _load_json_mapping(path: Path) -> dict[str, Any]:
|
| 22 |
-
payload = json.loads(path.read_text(encoding="utf-8-sig"))
|
| 23 |
-
if not isinstance(payload, dict):
|
| 24 |
-
raise ValueError(f"Expected mapping in {path}")
|
| 25 |
-
return payload
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
def _route(args: argparse.Namespace) -> int:
|
| 29 |
-
models = load_model_registry(args.registry)
|
| 30 |
-
payload = _load_json_mapping(args.request)
|
| 31 |
-
decision = SovereignModelRouter(models).route(RoutingRequest.from_payload(payload))
|
| 32 |
-
print(json.dumps(decision.to_dict(), indent=2, sort_keys=True))
|
| 33 |
-
return 0 if decision.selected_model is not None else 2
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
def _models(args: argparse.Namespace) -> int:
|
| 37 |
-
models = load_model_registry(args.registry)
|
| 38 |
-
print(json.dumps(build_models_response(models), indent=2, sort_keys=True))
|
| 39 |
-
return 0
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
def _chat_dry_run(args: argparse.Namespace) -> int:
|
| 43 |
-
models = load_model_registry(args.registry)
|
| 44 |
-
payload = _load_json_mapping(args.request)
|
| 45 |
-
result = route_chat_payload(payload, models)
|
| 46 |
-
print(json.dumps(result, indent=2, sort_keys=True))
|
| 47 |
-
selected = result["route_decision"]["selected_model"]
|
| 48 |
-
return 0 if selected is not None else 2
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
def _catalog(args: argparse.Namespace) -> int:
|
| 52 |
-
models = load_model_registry(args.registry)
|
| 53 |
-
result = catalog_models(
|
| 54 |
-
models,
|
| 55 |
-
{
|
| 56 |
-
"search": args.search,
|
| 57 |
-
"task_type": args.task,
|
| 58 |
-
"runtime": args.runtime,
|
| 59 |
-
"modality": args.modality,
|
| 60 |
-
"jurisdiction": args.jurisdiction,
|
| 61 |
-
"min_context_tokens": args.min_context_tokens,
|
| 62 |
-
"requires_json": args.requires_json,
|
| 63 |
-
"requires_tools": args.requires_tools,
|
| 64 |
-
"requires_local": args.requires_local,
|
| 65 |
-
"limit": args.limit,
|
| 66 |
-
},
|
| 67 |
-
)
|
| 68 |
-
print(json.dumps(result, indent=2, sort_keys=True))
|
| 69 |
-
return 0
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
def _compare(args: argparse.Namespace) -> int:
|
| 73 |
-
models = load_model_registry(args.registry)
|
| 74 |
-
model_ids = [item.strip() for value in args.model for item in value.split(",") if item.strip()]
|
| 75 |
-
request = _load_json_mapping(args.request) if args.request else None
|
| 76 |
-
result = compare_models(models, model_ids, request)
|
| 77 |
-
print(json.dumps(result, indent=2, sort_keys=True))
|
| 78 |
-
return 0 if result["ok"] else 2
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
def _matrix(args: argparse.Namespace) -> int:
|
| 82 |
-
models = load_model_registry(args.registry)
|
| 83 |
-
result = route_scenario_matrix(models)
|
| 84 |
-
print(json.dumps(result, indent=2, sort_keys=True))
|
| 85 |
-
return 0 if result["ok"] or args.allow_blocked_exit_zero else 2
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
def _analytics(args: argparse.Namespace) -> int:
|
| 89 |
-
print(json.dumps(build_registry_analytics(load_model_registry(args.registry)), indent=2, sort_keys=True))
|
| 90 |
-
return 0
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
def _opencode_config(args: argparse.Namespace) -> int:
|
| 94 |
-
models = load_model_registry(args.registry)
|
| 95 |
-
result = build_opencode_provider_config(models, base_url=args.base_url)
|
| 96 |
-
print(json.dumps(result, indent=2, sort_keys=True))
|
| 97 |
-
return 0
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
def _ui(args: argparse.Namespace) -> int:
|
| 101 |
-
from .ui import run_ui
|
| 102 |
-
|
| 103 |
-
return run_ui(
|
| 104 |
-
registry_path=args.registry,
|
| 105 |
-
host=args.host,
|
| 106 |
-
port=args.port,
|
| 107 |
-
open_browser=args.open,
|
| 108 |
-
smoke=args.smoke,
|
| 109 |
-
)
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
def _serve(args: argparse.Namespace) -> int:
|
| 113 |
-
from .server import serve_gateway
|
| 114 |
-
|
| 115 |
-
return serve_gateway(
|
| 116 |
-
registry_path=args.registry,
|
| 117 |
-
config_path=args.config,
|
| 118 |
-
host=args.host,
|
| 119 |
-
port=args.port,
|
| 120 |
-
open_browser=args.open,
|
| 121 |
-
smoke=args.smoke,
|
| 122 |
-
)
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
def build_parser() -> argparse.ArgumentParser:
|
| 126 |
-
parser = argparse.ArgumentParser(
|
| 127 |
-
prog="lumynax-marama-route",
|
| 128 |
-
description="Route requests across LumynaX sovereign model releases.",
|
| 129 |
-
)
|
| 130 |
-
subparsers = parser.add_subparsers(dest="command")
|
| 131 |
-
route = subparsers.add_parser("route", help="Select a LumynaX model for a request.")
|
| 132 |
-
route.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 133 |
-
route.add_argument("--request", type=Path, required=True, help="Routing request JSON.")
|
| 134 |
-
route.set_defaults(handler=_route)
|
| 135 |
-
|
| 136 |
-
models = subparsers.add_parser(
|
| 137 |
-
"models",
|
| 138 |
-
help="Emit an OpenAI-compatible /v1/models response.",
|
| 139 |
-
)
|
| 140 |
-
models.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 141 |
-
models.set_defaults(handler=_models)
|
| 142 |
-
|
| 143 |
-
chat = subparsers.add_parser(
|
| 144 |
-
"chat-dry-run",
|
| 145 |
-
help="Route an OpenAI-compatible chat request without invoking a backend.",
|
| 146 |
-
)
|
| 147 |
-
chat.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 148 |
-
chat.add_argument("--request", type=Path, required=True, help="OpenAI chat request JSON.")
|
| 149 |
-
chat.set_defaults(handler=_chat_dry_run)
|
| 150 |
-
|
| 151 |
-
catalog = subparsers.add_parser(
|
| 152 |
-
"catalog",
|
| 153 |
-
help="Search and filter the MaramaRoute model catalog.",
|
| 154 |
-
)
|
| 155 |
-
catalog.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 156 |
-
catalog.add_argument("--search", default="")
|
| 157 |
-
catalog.add_argument("--task", default="")
|
| 158 |
-
catalog.add_argument("--runtime", default="")
|
| 159 |
-
catalog.add_argument("--modality", default="")
|
| 160 |
-
catalog.add_argument("--jurisdiction", default="NZ")
|
| 161 |
-
catalog.add_argument("--min-context-tokens", type=int, default=0)
|
| 162 |
-
catalog.add_argument("--requires-json", action=argparse.BooleanOptionalAction, default=False)
|
| 163 |
-
catalog.add_argument("--requires-tools", action=argparse.BooleanOptionalAction, default=False)
|
| 164 |
-
catalog.add_argument("--requires-local", action=argparse.BooleanOptionalAction, default=False)
|
| 165 |
-
catalog.add_argument("--limit", type=int, default=25)
|
| 166 |
-
catalog.set_defaults(handler=_catalog)
|
| 167 |
-
|
| 168 |
-
compare = subparsers.add_parser(
|
| 169 |
-
"compare",
|
| 170 |
-
help="Compare routed fit for selected model ids.",
|
| 171 |
-
)
|
| 172 |
-
compare.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 173 |
-
compare.add_argument("--model", action="append", required=True, help="Model id, repeatable or comma-separated.")
|
| 174 |
-
compare.add_argument("--request", type=Path, default=None, help="Optional routing request JSON.")
|
| 175 |
-
compare.set_defaults(handler=_compare)
|
| 176 |
-
|
| 177 |
-
matrix = subparsers.add_parser(
|
| 178 |
-
"matrix",
|
| 179 |
-
help="Run the built-in sovereign routing scenario matrix.",
|
| 180 |
-
)
|
| 181 |
-
matrix.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 182 |
-
matrix.add_argument("--allow-blocked-exit-zero", action=argparse.BooleanOptionalAction, default=False)
|
| 183 |
-
matrix.set_defaults(handler=_matrix)
|
| 184 |
-
|
| 185 |
-
analytics = subparsers.add_parser("analytics", help="Summarise registry coverage.")
|
| 186 |
-
analytics.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 187 |
-
analytics.set_defaults(handler=_analytics)
|
| 188 |
-
|
| 189 |
-
opencode = subparsers.add_parser(
|
| 190 |
-
"opencode-config",
|
| 191 |
-
help="Emit an OpenCode-compatible MaramaRoute provider config.",
|
| 192 |
-
)
|
| 193 |
-
opencode.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 194 |
-
opencode.add_argument("--base-url", default="http://127.0.0.1:8787/v1")
|
| 195 |
-
opencode.set_defaults(handler=_opencode_config)
|
| 196 |
-
|
| 197 |
-
ui = subparsers.add_parser(
|
| 198 |
-
"ui",
|
| 199 |
-
help="Launch the local MaramaRoute browser platform.",
|
| 200 |
-
)
|
| 201 |
-
ui.add_argument("--registry", type=Path, default=None, help="MaramaRoute model registry JSON.")
|
| 202 |
-
ui.add_argument("--host", type=str, default="127.0.0.1")
|
| 203 |
-
ui.add_argument("--port", type=int, default=8787)
|
| 204 |
-
ui.add_argument("--open", action=argparse.BooleanOptionalAction, default=False)
|
| 205 |
-
ui.add_argument("--smoke", action=argparse.BooleanOptionalAction, default=False)
|
| 206 |
-
ui.set_defaults(handler=_ui)
|
| 207 |
-
|
| 208 |
-
serve = subparsers.add_parser(
|
| 209 |
-
"serve",
|
| 210 |
-
help="Run the local OpenAI-compatible MaramaRoute gateway and browser console.",
|
| 211 |
-
)
|
| 212 |
-
serve.add_argument("--registry", type=Path, default=None, help="MaramaRoute model registry JSON.")
|
| 213 |
-
serve.add_argument("--config", type=Path, default=None, help="Gateway backend config JSON.")
|
| 214 |
-
serve.add_argument("--host", type=str, default="127.0.0.1")
|
| 215 |
-
serve.add_argument("--port", type=int, default=8787)
|
| 216 |
-
serve.add_argument("--open", action=argparse.BooleanOptionalAction, default=False)
|
| 217 |
-
serve.add_argument("--smoke", action=argparse.BooleanOptionalAction, default=False)
|
| 218 |
-
serve.set_defaults(handler=_serve)
|
| 219 |
-
return parser
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
def main(argv: Sequence[str] | None = None) -> int:
|
| 223 |
-
parser = build_parser()
|
| 224 |
-
args = parser.parse_args(argv)
|
| 225 |
-
handler = getattr(args, "handler", None)
|
| 226 |
-
if handler is None:
|
| 227 |
-
parser.print_help()
|
| 228 |
-
return 0
|
| 229 |
-
return int(handler(args))
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
if __name__ == "__main__":
|
| 233 |
-
raise SystemExit(main())
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import json
|
| 5 |
+
from collections.abc import Sequence
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
from typing import Any
|
| 8 |
+
|
| 9 |
+
from .gateway import build_models_response, route_chat_payload
|
| 10 |
+
from .platform import (
|
| 11 |
+
build_opencode_provider_config,
|
| 12 |
+
build_registry_analytics,
|
| 13 |
+
catalog_models,
|
| 14 |
+
compare_models,
|
| 15 |
+
route_scenario_matrix,
|
| 16 |
+
)
|
| 17 |
+
from .registry import RoutingRequest, load_model_registry
|
| 18 |
+
from .router import SovereignModelRouter
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _load_json_mapping(path: Path) -> dict[str, Any]:
|
| 22 |
+
payload = json.loads(path.read_text(encoding="utf-8-sig"))
|
| 23 |
+
if not isinstance(payload, dict):
|
| 24 |
+
raise ValueError(f"Expected mapping in {path}")
|
| 25 |
+
return payload
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _route(args: argparse.Namespace) -> int:
|
| 29 |
+
models = load_model_registry(args.registry)
|
| 30 |
+
payload = _load_json_mapping(args.request)
|
| 31 |
+
decision = SovereignModelRouter(models).route(RoutingRequest.from_payload(payload))
|
| 32 |
+
print(json.dumps(decision.to_dict(), indent=2, sort_keys=True))
|
| 33 |
+
return 0 if decision.selected_model is not None else 2
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _models(args: argparse.Namespace) -> int:
|
| 37 |
+
models = load_model_registry(args.registry)
|
| 38 |
+
print(json.dumps(build_models_response(models), indent=2, sort_keys=True))
|
| 39 |
+
return 0
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def _chat_dry_run(args: argparse.Namespace) -> int:
|
| 43 |
+
models = load_model_registry(args.registry)
|
| 44 |
+
payload = _load_json_mapping(args.request)
|
| 45 |
+
result = route_chat_payload(payload, models)
|
| 46 |
+
print(json.dumps(result, indent=2, sort_keys=True))
|
| 47 |
+
selected = result["route_decision"]["selected_model"]
|
| 48 |
+
return 0 if selected is not None else 2
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def _catalog(args: argparse.Namespace) -> int:
|
| 52 |
+
models = load_model_registry(args.registry)
|
| 53 |
+
result = catalog_models(
|
| 54 |
+
models,
|
| 55 |
+
{
|
| 56 |
+
"search": args.search,
|
| 57 |
+
"task_type": args.task,
|
| 58 |
+
"runtime": args.runtime,
|
| 59 |
+
"modality": args.modality,
|
| 60 |
+
"jurisdiction": args.jurisdiction,
|
| 61 |
+
"min_context_tokens": args.min_context_tokens,
|
| 62 |
+
"requires_json": args.requires_json,
|
| 63 |
+
"requires_tools": args.requires_tools,
|
| 64 |
+
"requires_local": args.requires_local,
|
| 65 |
+
"limit": args.limit,
|
| 66 |
+
},
|
| 67 |
+
)
|
| 68 |
+
print(json.dumps(result, indent=2, sort_keys=True))
|
| 69 |
+
return 0
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def _compare(args: argparse.Namespace) -> int:
|
| 73 |
+
models = load_model_registry(args.registry)
|
| 74 |
+
model_ids = [item.strip() for value in args.model for item in value.split(",") if item.strip()]
|
| 75 |
+
request = _load_json_mapping(args.request) if args.request else None
|
| 76 |
+
result = compare_models(models, model_ids, request)
|
| 77 |
+
print(json.dumps(result, indent=2, sort_keys=True))
|
| 78 |
+
return 0 if result["ok"] else 2
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def _matrix(args: argparse.Namespace) -> int:
|
| 82 |
+
models = load_model_registry(args.registry)
|
| 83 |
+
result = route_scenario_matrix(models)
|
| 84 |
+
print(json.dumps(result, indent=2, sort_keys=True))
|
| 85 |
+
return 0 if result["ok"] or args.allow_blocked_exit_zero else 2
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def _analytics(args: argparse.Namespace) -> int:
|
| 89 |
+
print(json.dumps(build_registry_analytics(load_model_registry(args.registry)), indent=2, sort_keys=True))
|
| 90 |
+
return 0
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def _opencode_config(args: argparse.Namespace) -> int:
|
| 94 |
+
models = load_model_registry(args.registry)
|
| 95 |
+
result = build_opencode_provider_config(models, base_url=args.base_url)
|
| 96 |
+
print(json.dumps(result, indent=2, sort_keys=True))
|
| 97 |
+
return 0
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def _ui(args: argparse.Namespace) -> int:
|
| 101 |
+
from .ui import run_ui
|
| 102 |
+
|
| 103 |
+
return run_ui(
|
| 104 |
+
registry_path=args.registry,
|
| 105 |
+
host=args.host,
|
| 106 |
+
port=args.port,
|
| 107 |
+
open_browser=args.open,
|
| 108 |
+
smoke=args.smoke,
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def _serve(args: argparse.Namespace) -> int:
|
| 113 |
+
from .server import serve_gateway
|
| 114 |
+
|
| 115 |
+
return serve_gateway(
|
| 116 |
+
registry_path=args.registry,
|
| 117 |
+
config_path=args.config,
|
| 118 |
+
host=args.host,
|
| 119 |
+
port=args.port,
|
| 120 |
+
open_browser=args.open,
|
| 121 |
+
smoke=args.smoke,
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def build_parser() -> argparse.ArgumentParser:
|
| 126 |
+
parser = argparse.ArgumentParser(
|
| 127 |
+
prog="lumynax-marama-route",
|
| 128 |
+
description="Route requests across LumynaX sovereign model releases.",
|
| 129 |
+
)
|
| 130 |
+
subparsers = parser.add_subparsers(dest="command")
|
| 131 |
+
route = subparsers.add_parser("route", help="Select a LumynaX model for a request.")
|
| 132 |
+
route.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 133 |
+
route.add_argument("--request", type=Path, required=True, help="Routing request JSON.")
|
| 134 |
+
route.set_defaults(handler=_route)
|
| 135 |
+
|
| 136 |
+
models = subparsers.add_parser(
|
| 137 |
+
"models",
|
| 138 |
+
help="Emit an OpenAI-compatible /v1/models response.",
|
| 139 |
+
)
|
| 140 |
+
models.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 141 |
+
models.set_defaults(handler=_models)
|
| 142 |
+
|
| 143 |
+
chat = subparsers.add_parser(
|
| 144 |
+
"chat-dry-run",
|
| 145 |
+
help="Route an OpenAI-compatible chat request without invoking a backend.",
|
| 146 |
+
)
|
| 147 |
+
chat.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 148 |
+
chat.add_argument("--request", type=Path, required=True, help="OpenAI chat request JSON.")
|
| 149 |
+
chat.set_defaults(handler=_chat_dry_run)
|
| 150 |
+
|
| 151 |
+
catalog = subparsers.add_parser(
|
| 152 |
+
"catalog",
|
| 153 |
+
help="Search and filter the MaramaRoute model catalog.",
|
| 154 |
+
)
|
| 155 |
+
catalog.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 156 |
+
catalog.add_argument("--search", default="")
|
| 157 |
+
catalog.add_argument("--task", default="")
|
| 158 |
+
catalog.add_argument("--runtime", default="")
|
| 159 |
+
catalog.add_argument("--modality", default="")
|
| 160 |
+
catalog.add_argument("--jurisdiction", default="NZ")
|
| 161 |
+
catalog.add_argument("--min-context-tokens", type=int, default=0)
|
| 162 |
+
catalog.add_argument("--requires-json", action=argparse.BooleanOptionalAction, default=False)
|
| 163 |
+
catalog.add_argument("--requires-tools", action=argparse.BooleanOptionalAction, default=False)
|
| 164 |
+
catalog.add_argument("--requires-local", action=argparse.BooleanOptionalAction, default=False)
|
| 165 |
+
catalog.add_argument("--limit", type=int, default=25)
|
| 166 |
+
catalog.set_defaults(handler=_catalog)
|
| 167 |
+
|
| 168 |
+
compare = subparsers.add_parser(
|
| 169 |
+
"compare",
|
| 170 |
+
help="Compare routed fit for selected model ids.",
|
| 171 |
+
)
|
| 172 |
+
compare.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 173 |
+
compare.add_argument("--model", action="append", required=True, help="Model id, repeatable or comma-separated.")
|
| 174 |
+
compare.add_argument("--request", type=Path, default=None, help="Optional routing request JSON.")
|
| 175 |
+
compare.set_defaults(handler=_compare)
|
| 176 |
+
|
| 177 |
+
matrix = subparsers.add_parser(
|
| 178 |
+
"matrix",
|
| 179 |
+
help="Run the built-in sovereign routing scenario matrix.",
|
| 180 |
+
)
|
| 181 |
+
matrix.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 182 |
+
matrix.add_argument("--allow-blocked-exit-zero", action=argparse.BooleanOptionalAction, default=False)
|
| 183 |
+
matrix.set_defaults(handler=_matrix)
|
| 184 |
+
|
| 185 |
+
analytics = subparsers.add_parser("analytics", help="Summarise registry coverage.")
|
| 186 |
+
analytics.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 187 |
+
analytics.set_defaults(handler=_analytics)
|
| 188 |
+
|
| 189 |
+
opencode = subparsers.add_parser(
|
| 190 |
+
"opencode-config",
|
| 191 |
+
help="Emit an OpenCode-compatible MaramaRoute provider config.",
|
| 192 |
+
)
|
| 193 |
+
opencode.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 194 |
+
opencode.add_argument("--base-url", default="http://127.0.0.1:8787/v1")
|
| 195 |
+
opencode.set_defaults(handler=_opencode_config)
|
| 196 |
+
|
| 197 |
+
ui = subparsers.add_parser(
|
| 198 |
+
"ui",
|
| 199 |
+
help="Launch the local MaramaRoute browser platform.",
|
| 200 |
+
)
|
| 201 |
+
ui.add_argument("--registry", type=Path, default=None, help="MaramaRoute model registry JSON.")
|
| 202 |
+
ui.add_argument("--host", type=str, default="127.0.0.1")
|
| 203 |
+
ui.add_argument("--port", type=int, default=8787)
|
| 204 |
+
ui.add_argument("--open", action=argparse.BooleanOptionalAction, default=False)
|
| 205 |
+
ui.add_argument("--smoke", action=argparse.BooleanOptionalAction, default=False)
|
| 206 |
+
ui.set_defaults(handler=_ui)
|
| 207 |
+
|
| 208 |
+
serve = subparsers.add_parser(
|
| 209 |
+
"serve",
|
| 210 |
+
help="Run the local OpenAI-compatible MaramaRoute gateway and browser console.",
|
| 211 |
+
)
|
| 212 |
+
serve.add_argument("--registry", type=Path, default=None, help="MaramaRoute model registry JSON.")
|
| 213 |
+
serve.add_argument("--config", type=Path, default=None, help="Gateway backend config JSON.")
|
| 214 |
+
serve.add_argument("--host", type=str, default="127.0.0.1")
|
| 215 |
+
serve.add_argument("--port", type=int, default=8787)
|
| 216 |
+
serve.add_argument("--open", action=argparse.BooleanOptionalAction, default=False)
|
| 217 |
+
serve.add_argument("--smoke", action=argparse.BooleanOptionalAction, default=False)
|
| 218 |
+
serve.set_defaults(handler=_serve)
|
| 219 |
+
return parser
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
def main(argv: Sequence[str] | None = None) -> int:
|
| 223 |
+
parser = build_parser()
|
| 224 |
+
args = parser.parse_args(argv)
|
| 225 |
+
handler = getattr(args, "handler", None)
|
| 226 |
+
if handler is None:
|
| 227 |
+
parser.print_help()
|
| 228 |
+
return 0
|
| 229 |
+
return int(handler(args))
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
if __name__ == "__main__":
|
| 233 |
+
raise SystemExit(main())
|
marama_route/configs/gateway.local.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
| 1 |
-
{
|
| 2 |
-
"mode": "route_only",
|
| 3 |
-
"prompt_retention": "not_stored_by_default",
|
| 4 |
-
"default_timeout_seconds": 120,
|
| 5 |
-
"backends": {
|
| 6 |
-
"example-local-openai-compatible": {
|
| 7 |
-
"type": "openai_compatible",
|
| 8 |
-
"base_url": "http://127.0.0.1:8000/v1",
|
| 9 |
-
"api_key_env": "",
|
| 10 |
-
"model": "local-model-id"
|
| 11 |
-
}
|
| 12 |
-
}
|
| 13 |
-
}
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"mode": "route_only",
|
| 3 |
+
"prompt_retention": "not_stored_by_default",
|
| 4 |
+
"default_timeout_seconds": 120,
|
| 5 |
+
"backends": {
|
| 6 |
+
"example-local-openai-compatible": {
|
| 7 |
+
"type": "openai_compatible",
|
| 8 |
+
"base_url": "http://127.0.0.1:8000/v1",
|
| 9 |
+
"api_key_env": "",
|
| 10 |
+
"model": "local-model-id"
|
| 11 |
+
}
|
| 12 |
+
}
|
| 13 |
+
}
|
marama_route/configs/service.local.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
| 1 |
-
{
|
| 2 |
-
"ledger_path": ".sovereigncode/audit.jsonl",
|
| 3 |
-
"default_region": "NZ",
|
| 4 |
-
"fail_closed": true,
|
| 5 |
-
"require_human_review_for": [
|
| 6 |
-
"write_files",
|
| 7 |
-
"execute_shell",
|
| 8 |
-
"network_export",
|
| 9 |
-
"commit",
|
| 10 |
-
"publish",
|
| 11 |
-
"train_model"
|
| 12 |
-
],
|
| 13 |
-
"marama_route_base_url": "http://127.0.0.1:8787/v1"
|
| 14 |
-
}
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"ledger_path": ".sovereigncode/audit.jsonl",
|
| 3 |
+
"default_region": "NZ",
|
| 4 |
+
"fail_closed": true,
|
| 5 |
+
"require_human_review_for": [
|
| 6 |
+
"write_files",
|
| 7 |
+
"execute_shell",
|
| 8 |
+
"network_export",
|
| 9 |
+
"commit",
|
| 10 |
+
"publish",
|
| 11 |
+
"train_model"
|
| 12 |
+
],
|
| 13 |
+
"marama_route_base_url": "http://127.0.0.1:8787/v1"
|
| 14 |
+
}
|
marama_route/py.typed
ADDED
|
File without changes
|
marama_route/server.py
CHANGED
|
@@ -1,312 +1,312 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
import json
|
| 4 |
-
import os
|
| 5 |
-
import tempfile
|
| 6 |
-
import urllib.error
|
| 7 |
-
import urllib.request
|
| 8 |
-
from pathlib import Path
|
| 9 |
-
from typing import Any
|
| 10 |
-
|
| 11 |
-
try: # repo package
|
| 12 |
-
from tinyluminax.products._ui_server import serve_dashboard
|
| 13 |
-
except ModuleNotFoundError: # standalone HF package
|
| 14 |
-
from ._ui_server import serve_dashboard
|
| 15 |
-
|
| 16 |
-
from .gateway import route_chat_payload
|
| 17 |
-
from .platform import build_models_api, route_or_chat_payload, route_receipt
|
| 18 |
-
from .registry import load_model_registry
|
| 19 |
-
from .ui import (
|
| 20 |
-
PRODUCT_NAME,
|
| 21 |
-
build_dashboard_state,
|
| 22 |
-
build_expanded_dashboard_html,
|
| 23 |
-
default_openai_chat_request_path,
|
| 24 |
-
default_registry_path,
|
| 25 |
-
handle_api_request,
|
| 26 |
-
load_json_mapping,
|
| 27 |
-
)
|
| 28 |
-
|
| 29 |
-
PACKAGE_ROOT = Path(__file__).resolve().parent
|
| 30 |
-
PACKAGE_PARENT = PACKAGE_ROOT.parent
|
| 31 |
-
|
| 32 |
-
DEFAULT_GATEWAY_CONFIG: dict[str, Any] = {
|
| 33 |
-
"mode": "route_only",
|
| 34 |
-
"prompt_retention": "not_stored_by_default",
|
| 35 |
-
"default_timeout_seconds": 120,
|
| 36 |
-
"backends": {},
|
| 37 |
-
}
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
def default_gateway_config_path() -> Path:
|
| 41 |
-
candidates = [
|
| 42 |
-
Path.cwd() / "products" / "lumynax-marama-route" / "configs" / "gateway.local.json",
|
| 43 |
-
Path.cwd() / "configs" / "gateway.local.json",
|
| 44 |
-
PACKAGE_ROOT / "configs" / "gateway.local.json",
|
| 45 |
-
PACKAGE_PARENT / "configs" / "gateway.local.json",
|
| 46 |
-
]
|
| 47 |
-
for path in candidates:
|
| 48 |
-
if path.exists():
|
| 49 |
-
return path
|
| 50 |
-
return candidates[0]
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
def default_route_request_path() -> Path:
|
| 54 |
-
candidates = [
|
| 55 |
-
Path.cwd() / "products" / "lumynax-marama-route" / "examples" / "request.code-restricted.json",
|
| 56 |
-
Path.cwd() / "examples" / "request.code-restricted.json",
|
| 57 |
-
PACKAGE_ROOT / "examples" / "request.code-restricted.json",
|
| 58 |
-
PACKAGE_PARENT / "examples" / "request.code-restricted.json",
|
| 59 |
-
]
|
| 60 |
-
for path in candidates:
|
| 61 |
-
if path.exists():
|
| 62 |
-
return path
|
| 63 |
-
return candidates[0]
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
def load_gateway_config(path: Path | None = None) -> dict[str, Any]:
|
| 67 |
-
config = dict(DEFAULT_GATEWAY_CONFIG)
|
| 68 |
-
config["backends"] = dict(DEFAULT_GATEWAY_CONFIG["backends"])
|
| 69 |
-
resolved = path or default_gateway_config_path()
|
| 70 |
-
if resolved.exists():
|
| 71 |
-
payload = json.loads(resolved.read_text(encoding="utf-8-sig"))
|
| 72 |
-
if not isinstance(payload, dict):
|
| 73 |
-
raise ValueError(f"Expected gateway config object in {resolved}")
|
| 74 |
-
config.update(payload)
|
| 75 |
-
config["backends"] = dict(payload.get("backends") or {})
|
| 76 |
-
config["config_path"] = str(resolved)
|
| 77 |
-
return config
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
def handle_gateway_request(
|
| 81 |
-
method: str,
|
| 82 |
-
path: str,
|
| 83 |
-
payload: dict[str, Any] | None,
|
| 84 |
-
registry_path: Path,
|
| 85 |
-
config_path: Path | None = None,
|
| 86 |
-
) -> tuple[int, dict[str, Any]]:
|
| 87 |
-
models = load_model_registry(registry_path)
|
| 88 |
-
config = load_gateway_config(config_path)
|
| 89 |
-
|
| 90 |
-
if path.startswith("/api/"):
|
| 91 |
-
return handle_api_request(method, path, payload, registry_path)
|
| 92 |
-
if method == "GET" and path in {"/health", "/v1/health"}:
|
| 93 |
-
return 200, {
|
| 94 |
-
"ok": True,
|
| 95 |
-
"product": PRODUCT_NAME,
|
| 96 |
-
"mode": config["mode"],
|
| 97 |
-
"model_count": len(models),
|
| 98 |
-
"configured_backends": len(config.get("backends") or {}),
|
| 99 |
-
"prompt_retention": config.get("prompt_retention", "not_stored_by_default"),
|
| 100 |
-
}
|
| 101 |
-
if method == "GET" and path == "/v1/models":
|
| 102 |
-
return 200, build_models_api(models)
|
| 103 |
-
if method == "POST" and path == "/v1/route" and payload is not None:
|
| 104 |
-
result = route_or_chat_payload(payload, models)
|
| 105 |
-
return (200 if result["ok"] else 422), result
|
| 106 |
-
if method == "POST" and path == "/v1/chat/completions" and payload is not None:
|
| 107 |
-
return chat_completion_gateway(payload, models, config)
|
| 108 |
-
return 404, {"ok": False, "error": "not_found"}
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
def chat_completion_gateway(
|
| 112 |
-
payload: dict[str, Any],
|
| 113 |
-
models: tuple[Any, ...],
|
| 114 |
-
config: dict[str, Any],
|
| 115 |
-
) -> tuple[int, dict[str, Any]]:
|
| 116 |
-
route_result = route_chat_payload(payload, models)
|
| 117 |
-
decision = route_result["route_decision"]
|
| 118 |
-
selected = decision.get("selected_model")
|
| 119 |
-
if not isinstance(selected, dict):
|
| 120 |
-
return 422, {"ok": False, "error": "no_eligible_model", **route_result}
|
| 121 |
-
|
| 122 |
-
receipt = route_receipt(payload, route_result)
|
| 123 |
-
dry_run = bool(
|
| 124 |
-
payload.get("dry_run")
|
| 125 |
-
or payload.get("marama_route_dry_run")
|
| 126 |
-
or config.get("mode", "route_only") == "route_only"
|
| 127 |
-
)
|
| 128 |
-
if dry_run:
|
| 129 |
-
response = dict(route_result["chat_completion_response"])
|
| 130 |
-
response["marama_route"] = dict(response["marama_route"])
|
| 131 |
-
response["marama_route"].update(
|
| 132 |
-
{
|
| 133 |
-
"backend_mode": "route_only",
|
| 134 |
-
"receipt": receipt,
|
| 135 |
-
"prompt_retention": config.get("prompt_retention", "not_stored_by_default"),
|
| 136 |
-
},
|
| 137 |
-
)
|
| 138 |
-
return 200, response
|
| 139 |
-
|
| 140 |
-
backend = _backend_for_model(selected["model_id"], config)
|
| 141 |
-
if backend is None:
|
| 142 |
-
return 424, {
|
| 143 |
-
"ok": False,
|
| 144 |
-
"error": "backend_not_configured",
|
| 145 |
-
"message": "Routing succeeded, but no live backend is configured for the selected model.",
|
| 146 |
-
"selected_model": selected["model_id"],
|
| 147 |
-
"required_config": {
|
| 148 |
-
"mode": "live",
|
| 149 |
-
"backends": {
|
| 150 |
-
selected["model_id"]: {
|
| 151 |
-
"type": "openai_compatible",
|
| 152 |
-
"base_url": "http://127.0.0.1:8000/v1",
|
| 153 |
-
"api_key_env": "OPTIONAL_ENV_NAME",
|
| 154 |
-
},
|
| 155 |
-
},
|
| 156 |
-
},
|
| 157 |
-
"receipt": receipt,
|
| 158 |
-
**route_result,
|
| 159 |
-
}
|
| 160 |
-
return _proxy_openai_chat_completion(payload, selected, backend, config, route_result, receipt)
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
def smoke_gateway(
|
| 164 |
-
*,
|
| 165 |
-
registry_path: Path | None = None,
|
| 166 |
-
config_path: Path | None = None,
|
| 167 |
-
) -> dict[str, Any]:
|
| 168 |
-
resolved_registry = registry_path or default_registry_path()
|
| 169 |
-
resolved_config = config_path or _temporary_route_only_config()
|
| 170 |
-
route_payload = load_json_mapping(default_route_request_path())
|
| 171 |
-
chat_payload = load_json_mapping(default_openai_chat_request_path())
|
| 172 |
-
chat_payload["dry_run"] = True
|
| 173 |
-
|
| 174 |
-
health_status, health = handle_gateway_request("GET", "/health", None, resolved_registry, resolved_config)
|
| 175 |
-
models_status, models = handle_gateway_request("GET", "/v1/models", None, resolved_registry, resolved_config)
|
| 176 |
-
route_status, route = handle_gateway_request("POST", "/v1/route", route_payload, resolved_registry, resolved_config)
|
| 177 |
-
chat_status, chat = handle_gateway_request(
|
| 178 |
-
"POST",
|
| 179 |
-
"/v1/chat/completions",
|
| 180 |
-
chat_payload,
|
| 181 |
-
resolved_registry,
|
| 182 |
-
resolved_config,
|
| 183 |
-
)
|
| 184 |
-
|
| 185 |
-
if health_status != 200 or models_status != 200 or route_status != 200 or chat_status != 200:
|
| 186 |
-
raise RuntimeError("MaramaRoute gateway smoke failed")
|
| 187 |
-
if chat.get("object") != "chat.completion" or chat["marama_route"]["selected_model"] is None:
|
| 188 |
-
raise RuntimeError("MaramaRoute gateway did not return a routed chat response")
|
| 189 |
-
return {
|
| 190 |
-
"ok": True,
|
| 191 |
-
"product": PRODUCT_NAME,
|
| 192 |
-
"mode": health["mode"],
|
| 193 |
-
"model_count": health["model_count"],
|
| 194 |
-
"route_selected_model": route["route_decision"]["selected_model"]["model_id"],
|
| 195 |
-
"chat_selected_model": chat["marama_route"]["selected_model"]["model_id"],
|
| 196 |
-
"configured_backends": health["configured_backends"],
|
| 197 |
-
}
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
def serve_gateway(
|
| 201 |
-
*,
|
| 202 |
-
registry_path: Path | None = None,
|
| 203 |
-
config_path: Path | None = None,
|
| 204 |
-
host: str = "127.0.0.1",
|
| 205 |
-
port: int = 8787,
|
| 206 |
-
open_browser: bool = False,
|
| 207 |
-
smoke: bool = False,
|
| 208 |
-
) -> int:
|
| 209 |
-
resolved_registry = registry_path or default_registry_path()
|
| 210 |
-
if smoke:
|
| 211 |
-
print(json.dumps(smoke_gateway(registry_path=resolved_registry, config_path=config_path), indent=2, sort_keys=True))
|
| 212 |
-
return 0
|
| 213 |
-
|
| 214 |
-
html = build_expanded_dashboard_html(build_dashboard_state(resolved_registry))
|
| 215 |
-
return serve_dashboard(
|
| 216 |
-
product_name=f"{PRODUCT_NAME} Gateway",
|
| 217 |
-
html=html,
|
| 218 |
-
api_handler=lambda method, path, request_payload: handle_gateway_request(
|
| 219 |
-
method,
|
| 220 |
-
path,
|
| 221 |
-
request_payload,
|
| 222 |
-
resolved_registry,
|
| 223 |
-
config_path,
|
| 224 |
-
),
|
| 225 |
-
host=host,
|
| 226 |
-
port=port,
|
| 227 |
-
open_browser=open_browser,
|
| 228 |
-
api_path_prefixes=("/api/", "/v1/"),
|
| 229 |
-
api_exact_paths=("/health",),
|
| 230 |
-
)
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
def _backend_for_model(model_id: str, config: dict[str, Any]) -> dict[str, Any] | None:
|
| 234 |
-
backends = config.get("backends")
|
| 235 |
-
if not isinstance(backends, dict):
|
| 236 |
-
return None
|
| 237 |
-
backend = backends.get(model_id) or backends.get("*")
|
| 238 |
-
return dict(backend) if isinstance(backend, dict) else None
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
def _proxy_openai_chat_completion(
|
| 242 |
-
payload: dict[str, Any],
|
| 243 |
-
selected: dict[str, Any],
|
| 244 |
-
backend: dict[str, Any],
|
| 245 |
-
config: dict[str, Any],
|
| 246 |
-
route_result: dict[str, Any],
|
| 247 |
-
receipt: dict[str, Any],
|
| 248 |
-
) -> tuple[int, dict[str, Any]]:
|
| 249 |
-
if str(backend.get("type") or "openai_compatible") != "openai_compatible":
|
| 250 |
-
return 424, {"ok": False, "error": "unsupported_backend_type", "backend": backend}
|
| 251 |
-
|
| 252 |
-
base_url = str(backend.get("base_url") or "").rstrip("/")
|
| 253 |
-
if not base_url:
|
| 254 |
-
return 424, {"ok": False, "error": "backend_base_url_missing", "selected_model": selected["model_id"]}
|
| 255 |
-
endpoint = f"{base_url}/chat/completions"
|
| 256 |
-
upstream_payload = dict(payload)
|
| 257 |
-
upstream_payload["model"] = str(backend.get("model") or selected["model_id"])
|
| 258 |
-
for key in ("route", "routing", "dry_run", "marama_route_dry_run"):
|
| 259 |
-
upstream_payload.pop(key, None)
|
| 260 |
-
|
| 261 |
-
headers = {"Content-Type": "application/json"}
|
| 262 |
-
api_key_env = str(backend.get("api_key_env") or "")
|
| 263 |
-
if api_key_env and os.getenv(api_key_env):
|
| 264 |
-
headers["Authorization"] = f"Bearer {os.environ[api_key_env]}"
|
| 265 |
-
headers.update({str(key): str(value) for key, value in dict(backend.get("headers") or {}).items()})
|
| 266 |
-
|
| 267 |
-
timeout = float(backend.get("timeout_seconds") or config.get("default_timeout_seconds") or 120)
|
| 268 |
-
request = urllib.request.Request(
|
| 269 |
-
endpoint,
|
| 270 |
-
data=json.dumps(upstream_payload).encode("utf-8"),
|
| 271 |
-
headers=headers,
|
| 272 |
-
method="POST",
|
| 273 |
-
)
|
| 274 |
-
try:
|
| 275 |
-
with urllib.request.urlopen(request, timeout=timeout) as response: # noqa: S310 - operator-configured local/remote backend
|
| 276 |
-
body = response.read().decode("utf-8")
|
| 277 |
-
payload_out = json.loads(body)
|
| 278 |
-
if not isinstance(payload_out, dict):
|
| 279 |
-
raise ValueError("upstream response was not a JSON object")
|
| 280 |
-
payload_out["marama_route"] = {
|
| 281 |
-
"dry_run": False,
|
| 282 |
-
"selected_model": selected,
|
| 283 |
-
"fallback_models": route_result["route_decision"]["fallback_models"],
|
| 284 |
-
"rejected_count": len(route_result["route_decision"]["rejected"]),
|
| 285 |
-
"receipt": receipt,
|
| 286 |
-
"backend_base_url": base_url,
|
| 287 |
-
"prompt_retention": config.get("prompt_retention", "not_stored_by_default"),
|
| 288 |
-
}
|
| 289 |
-
return int(response.status), payload_out
|
| 290 |
-
except urllib.error.HTTPError as exc:
|
| 291 |
-
return exc.code, {
|
| 292 |
-
"ok": False,
|
| 293 |
-
"error": "backend_http_error",
|
| 294 |
-
"status": exc.code,
|
| 295 |
-
"body": exc.read().decode("utf-8", errors="replace"),
|
| 296 |
-
"receipt": receipt,
|
| 297 |
-
**route_result,
|
| 298 |
-
}
|
| 299 |
-
except Exception as exc:
|
| 300 |
-
return 502, {
|
| 301 |
-
"ok": False,
|
| 302 |
-
"error": "backend_unavailable",
|
| 303 |
-
"message": str(exc),
|
| 304 |
-
"receipt": receipt,
|
| 305 |
-
**route_result,
|
| 306 |
-
}
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
def _temporary_route_only_config() -> Path:
|
| 310 |
-
path = Path(tempfile.gettempdir()) / "marama-route-smoke.gateway.json"
|
| 311 |
-
path.write_text(json.dumps(DEFAULT_GATEWAY_CONFIG, indent=2, sort_keys=True), encoding="utf-8")
|
| 312 |
-
return path
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import os
|
| 5 |
+
import tempfile
|
| 6 |
+
import urllib.error
|
| 7 |
+
import urllib.request
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
from typing import Any
|
| 10 |
+
|
| 11 |
+
try: # repo package
|
| 12 |
+
from tinyluminax.products._ui_server import serve_dashboard
|
| 13 |
+
except ModuleNotFoundError: # standalone HF package
|
| 14 |
+
from ._ui_server import serve_dashboard
|
| 15 |
+
|
| 16 |
+
from .gateway import route_chat_payload
|
| 17 |
+
from .platform import build_models_api, route_or_chat_payload, route_receipt
|
| 18 |
+
from .registry import load_model_registry
|
| 19 |
+
from .ui import (
|
| 20 |
+
PRODUCT_NAME,
|
| 21 |
+
build_dashboard_state,
|
| 22 |
+
build_expanded_dashboard_html,
|
| 23 |
+
default_openai_chat_request_path,
|
| 24 |
+
default_registry_path,
|
| 25 |
+
handle_api_request,
|
| 26 |
+
load_json_mapping,
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
PACKAGE_ROOT = Path(__file__).resolve().parent
|
| 30 |
+
PACKAGE_PARENT = PACKAGE_ROOT.parent
|
| 31 |
+
|
| 32 |
+
DEFAULT_GATEWAY_CONFIG: dict[str, Any] = {
|
| 33 |
+
"mode": "route_only",
|
| 34 |
+
"prompt_retention": "not_stored_by_default",
|
| 35 |
+
"default_timeout_seconds": 120,
|
| 36 |
+
"backends": {},
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def default_gateway_config_path() -> Path:
|
| 41 |
+
candidates = [
|
| 42 |
+
Path.cwd() / "products" / "lumynax-marama-route" / "configs" / "gateway.local.json",
|
| 43 |
+
Path.cwd() / "configs" / "gateway.local.json",
|
| 44 |
+
PACKAGE_ROOT / "configs" / "gateway.local.json",
|
| 45 |
+
PACKAGE_PARENT / "configs" / "gateway.local.json",
|
| 46 |
+
]
|
| 47 |
+
for path in candidates:
|
| 48 |
+
if path.exists():
|
| 49 |
+
return path
|
| 50 |
+
return candidates[0]
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def default_route_request_path() -> Path:
|
| 54 |
+
candidates = [
|
| 55 |
+
Path.cwd() / "products" / "lumynax-marama-route" / "examples" / "request.code-restricted.json",
|
| 56 |
+
Path.cwd() / "examples" / "request.code-restricted.json",
|
| 57 |
+
PACKAGE_ROOT / "examples" / "request.code-restricted.json",
|
| 58 |
+
PACKAGE_PARENT / "examples" / "request.code-restricted.json",
|
| 59 |
+
]
|
| 60 |
+
for path in candidates:
|
| 61 |
+
if path.exists():
|
| 62 |
+
return path
|
| 63 |
+
return candidates[0]
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def load_gateway_config(path: Path | None = None) -> dict[str, Any]:
|
| 67 |
+
config = dict(DEFAULT_GATEWAY_CONFIG)
|
| 68 |
+
config["backends"] = dict(DEFAULT_GATEWAY_CONFIG["backends"])
|
| 69 |
+
resolved = path or default_gateway_config_path()
|
| 70 |
+
if resolved.exists():
|
| 71 |
+
payload = json.loads(resolved.read_text(encoding="utf-8-sig"))
|
| 72 |
+
if not isinstance(payload, dict):
|
| 73 |
+
raise ValueError(f"Expected gateway config object in {resolved}")
|
| 74 |
+
config.update(payload)
|
| 75 |
+
config["backends"] = dict(payload.get("backends") or {})
|
| 76 |
+
config["config_path"] = str(resolved)
|
| 77 |
+
return config
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def handle_gateway_request(
|
| 81 |
+
method: str,
|
| 82 |
+
path: str,
|
| 83 |
+
payload: dict[str, Any] | None,
|
| 84 |
+
registry_path: Path,
|
| 85 |
+
config_path: Path | None = None,
|
| 86 |
+
) -> tuple[int, dict[str, Any]]:
|
| 87 |
+
models = load_model_registry(registry_path)
|
| 88 |
+
config = load_gateway_config(config_path)
|
| 89 |
+
|
| 90 |
+
if path.startswith("/api/"):
|
| 91 |
+
return handle_api_request(method, path, payload, registry_path)
|
| 92 |
+
if method == "GET" and path in {"/health", "/v1/health"}:
|
| 93 |
+
return 200, {
|
| 94 |
+
"ok": True,
|
| 95 |
+
"product": PRODUCT_NAME,
|
| 96 |
+
"mode": config["mode"],
|
| 97 |
+
"model_count": len(models),
|
| 98 |
+
"configured_backends": len(config.get("backends") or {}),
|
| 99 |
+
"prompt_retention": config.get("prompt_retention", "not_stored_by_default"),
|
| 100 |
+
}
|
| 101 |
+
if method == "GET" and path == "/v1/models":
|
| 102 |
+
return 200, build_models_api(models)
|
| 103 |
+
if method == "POST" and path == "/v1/route" and payload is not None:
|
| 104 |
+
result = route_or_chat_payload(payload, models)
|
| 105 |
+
return (200 if result["ok"] else 422), result
|
| 106 |
+
if method == "POST" and path == "/v1/chat/completions" and payload is not None:
|
| 107 |
+
return chat_completion_gateway(payload, models, config)
|
| 108 |
+
return 404, {"ok": False, "error": "not_found"}
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def chat_completion_gateway(
|
| 112 |
+
payload: dict[str, Any],
|
| 113 |
+
models: tuple[Any, ...],
|
| 114 |
+
config: dict[str, Any],
|
| 115 |
+
) -> tuple[int, dict[str, Any]]:
|
| 116 |
+
route_result = route_chat_payload(payload, models)
|
| 117 |
+
decision = route_result["route_decision"]
|
| 118 |
+
selected = decision.get("selected_model")
|
| 119 |
+
if not isinstance(selected, dict):
|
| 120 |
+
return 422, {"ok": False, "error": "no_eligible_model", **route_result}
|
| 121 |
+
|
| 122 |
+
receipt = route_receipt(payload, route_result)
|
| 123 |
+
dry_run = bool(
|
| 124 |
+
payload.get("dry_run")
|
| 125 |
+
or payload.get("marama_route_dry_run")
|
| 126 |
+
or config.get("mode", "route_only") == "route_only"
|
| 127 |
+
)
|
| 128 |
+
if dry_run:
|
| 129 |
+
response = dict(route_result["chat_completion_response"])
|
| 130 |
+
response["marama_route"] = dict(response["marama_route"])
|
| 131 |
+
response["marama_route"].update(
|
| 132 |
+
{
|
| 133 |
+
"backend_mode": "route_only",
|
| 134 |
+
"receipt": receipt,
|
| 135 |
+
"prompt_retention": config.get("prompt_retention", "not_stored_by_default"),
|
| 136 |
+
},
|
| 137 |
+
)
|
| 138 |
+
return 200, response
|
| 139 |
+
|
| 140 |
+
backend = _backend_for_model(selected["model_id"], config)
|
| 141 |
+
if backend is None:
|
| 142 |
+
return 424, {
|
| 143 |
+
"ok": False,
|
| 144 |
+
"error": "backend_not_configured",
|
| 145 |
+
"message": "Routing succeeded, but no live backend is configured for the selected model.",
|
| 146 |
+
"selected_model": selected["model_id"],
|
| 147 |
+
"required_config": {
|
| 148 |
+
"mode": "live",
|
| 149 |
+
"backends": {
|
| 150 |
+
selected["model_id"]: {
|
| 151 |
+
"type": "openai_compatible",
|
| 152 |
+
"base_url": "http://127.0.0.1:8000/v1",
|
| 153 |
+
"api_key_env": "OPTIONAL_ENV_NAME",
|
| 154 |
+
},
|
| 155 |
+
},
|
| 156 |
+
},
|
| 157 |
+
"receipt": receipt,
|
| 158 |
+
**route_result,
|
| 159 |
+
}
|
| 160 |
+
return _proxy_openai_chat_completion(payload, selected, backend, config, route_result, receipt)
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def smoke_gateway(
|
| 164 |
+
*,
|
| 165 |
+
registry_path: Path | None = None,
|
| 166 |
+
config_path: Path | None = None,
|
| 167 |
+
) -> dict[str, Any]:
|
| 168 |
+
resolved_registry = registry_path or default_registry_path()
|
| 169 |
+
resolved_config = config_path or _temporary_route_only_config()
|
| 170 |
+
route_payload = load_json_mapping(default_route_request_path())
|
| 171 |
+
chat_payload = load_json_mapping(default_openai_chat_request_path())
|
| 172 |
+
chat_payload["dry_run"] = True
|
| 173 |
+
|
| 174 |
+
health_status, health = handle_gateway_request("GET", "/health", None, resolved_registry, resolved_config)
|
| 175 |
+
models_status, models = handle_gateway_request("GET", "/v1/models", None, resolved_registry, resolved_config)
|
| 176 |
+
route_status, route = handle_gateway_request("POST", "/v1/route", route_payload, resolved_registry, resolved_config)
|
| 177 |
+
chat_status, chat = handle_gateway_request(
|
| 178 |
+
"POST",
|
| 179 |
+
"/v1/chat/completions",
|
| 180 |
+
chat_payload,
|
| 181 |
+
resolved_registry,
|
| 182 |
+
resolved_config,
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
if health_status != 200 or models_status != 200 or route_status != 200 or chat_status != 200:
|
| 186 |
+
raise RuntimeError("MaramaRoute gateway smoke failed")
|
| 187 |
+
if chat.get("object") != "chat.completion" or chat["marama_route"]["selected_model"] is None:
|
| 188 |
+
raise RuntimeError("MaramaRoute gateway did not return a routed chat response")
|
| 189 |
+
return {
|
| 190 |
+
"ok": True,
|
| 191 |
+
"product": PRODUCT_NAME,
|
| 192 |
+
"mode": health["mode"],
|
| 193 |
+
"model_count": health["model_count"],
|
| 194 |
+
"route_selected_model": route["route_decision"]["selected_model"]["model_id"],
|
| 195 |
+
"chat_selected_model": chat["marama_route"]["selected_model"]["model_id"],
|
| 196 |
+
"configured_backends": health["configured_backends"],
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
def serve_gateway(
|
| 201 |
+
*,
|
| 202 |
+
registry_path: Path | None = None,
|
| 203 |
+
config_path: Path | None = None,
|
| 204 |
+
host: str = "127.0.0.1",
|
| 205 |
+
port: int = 8787,
|
| 206 |
+
open_browser: bool = False,
|
| 207 |
+
smoke: bool = False,
|
| 208 |
+
) -> int:
|
| 209 |
+
resolved_registry = registry_path or default_registry_path()
|
| 210 |
+
if smoke:
|
| 211 |
+
print(json.dumps(smoke_gateway(registry_path=resolved_registry, config_path=config_path), indent=2, sort_keys=True))
|
| 212 |
+
return 0
|
| 213 |
+
|
| 214 |
+
html = build_expanded_dashboard_html(build_dashboard_state(resolved_registry))
|
| 215 |
+
return serve_dashboard(
|
| 216 |
+
product_name=f"{PRODUCT_NAME} Gateway",
|
| 217 |
+
html=html,
|
| 218 |
+
api_handler=lambda method, path, request_payload: handle_gateway_request(
|
| 219 |
+
method,
|
| 220 |
+
path,
|
| 221 |
+
request_payload,
|
| 222 |
+
resolved_registry,
|
| 223 |
+
config_path,
|
| 224 |
+
),
|
| 225 |
+
host=host,
|
| 226 |
+
port=port,
|
| 227 |
+
open_browser=open_browser,
|
| 228 |
+
api_path_prefixes=("/api/", "/v1/"),
|
| 229 |
+
api_exact_paths=("/health",),
|
| 230 |
+
)
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
def _backend_for_model(model_id: str, config: dict[str, Any]) -> dict[str, Any] | None:
|
| 234 |
+
backends = config.get("backends")
|
| 235 |
+
if not isinstance(backends, dict):
|
| 236 |
+
return None
|
| 237 |
+
backend = backends.get(model_id) or backends.get("*")
|
| 238 |
+
return dict(backend) if isinstance(backend, dict) else None
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
def _proxy_openai_chat_completion(
|
| 242 |
+
payload: dict[str, Any],
|
| 243 |
+
selected: dict[str, Any],
|
| 244 |
+
backend: dict[str, Any],
|
| 245 |
+
config: dict[str, Any],
|
| 246 |
+
route_result: dict[str, Any],
|
| 247 |
+
receipt: dict[str, Any],
|
| 248 |
+
) -> tuple[int, dict[str, Any]]:
|
| 249 |
+
if str(backend.get("type") or "openai_compatible") != "openai_compatible":
|
| 250 |
+
return 424, {"ok": False, "error": "unsupported_backend_type", "backend": backend}
|
| 251 |
+
|
| 252 |
+
base_url = str(backend.get("base_url") or "").rstrip("/")
|
| 253 |
+
if not base_url:
|
| 254 |
+
return 424, {"ok": False, "error": "backend_base_url_missing", "selected_model": selected["model_id"]}
|
| 255 |
+
endpoint = f"{base_url}/chat/completions"
|
| 256 |
+
upstream_payload = dict(payload)
|
| 257 |
+
upstream_payload["model"] = str(backend.get("model") or selected["model_id"])
|
| 258 |
+
for key in ("route", "routing", "dry_run", "marama_route_dry_run"):
|
| 259 |
+
upstream_payload.pop(key, None)
|
| 260 |
+
|
| 261 |
+
headers = {"Content-Type": "application/json"}
|
| 262 |
+
api_key_env = str(backend.get("api_key_env") or "")
|
| 263 |
+
if api_key_env and os.getenv(api_key_env):
|
| 264 |
+
headers["Authorization"] = f"Bearer {os.environ[api_key_env]}"
|
| 265 |
+
headers.update({str(key): str(value) for key, value in dict(backend.get("headers") or {}).items()})
|
| 266 |
+
|
| 267 |
+
timeout = float(backend.get("timeout_seconds") or config.get("default_timeout_seconds") or 120)
|
| 268 |
+
request = urllib.request.Request(
|
| 269 |
+
endpoint,
|
| 270 |
+
data=json.dumps(upstream_payload).encode("utf-8"),
|
| 271 |
+
headers=headers,
|
| 272 |
+
method="POST",
|
| 273 |
+
)
|
| 274 |
+
try:
|
| 275 |
+
with urllib.request.urlopen(request, timeout=timeout) as response: # noqa: S310 - operator-configured local/remote backend
|
| 276 |
+
body = response.read().decode("utf-8")
|
| 277 |
+
payload_out = json.loads(body)
|
| 278 |
+
if not isinstance(payload_out, dict):
|
| 279 |
+
raise ValueError("upstream response was not a JSON object")
|
| 280 |
+
payload_out["marama_route"] = {
|
| 281 |
+
"dry_run": False,
|
| 282 |
+
"selected_model": selected,
|
| 283 |
+
"fallback_models": route_result["route_decision"]["fallback_models"],
|
| 284 |
+
"rejected_count": len(route_result["route_decision"]["rejected"]),
|
| 285 |
+
"receipt": receipt,
|
| 286 |
+
"backend_base_url": base_url,
|
| 287 |
+
"prompt_retention": config.get("prompt_retention", "not_stored_by_default"),
|
| 288 |
+
}
|
| 289 |
+
return int(response.status), payload_out
|
| 290 |
+
except urllib.error.HTTPError as exc:
|
| 291 |
+
return exc.code, {
|
| 292 |
+
"ok": False,
|
| 293 |
+
"error": "backend_http_error",
|
| 294 |
+
"status": exc.code,
|
| 295 |
+
"body": exc.read().decode("utf-8", errors="replace"),
|
| 296 |
+
"receipt": receipt,
|
| 297 |
+
**route_result,
|
| 298 |
+
}
|
| 299 |
+
except Exception as exc:
|
| 300 |
+
return 502, {
|
| 301 |
+
"ok": False,
|
| 302 |
+
"error": "backend_unavailable",
|
| 303 |
+
"message": str(exc),
|
| 304 |
+
"receipt": receipt,
|
| 305 |
+
**route_result,
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
def _temporary_route_only_config() -> Path:
|
| 310 |
+
path = Path(tempfile.gettempdir()) / "marama-route-smoke.gateway.json"
|
| 311 |
+
path.write_text(json.dumps(DEFAULT_GATEWAY_CONFIG, indent=2, sort_keys=True), encoding="utf-8")
|
| 312 |
+
return path
|
product_manifest.json
CHANGED
|
@@ -1,62 +1,77 @@
|
|
| 1 |
-
{
|
| 2 |
-
"product_name": "AbteeX SovereignCode",
|
| 3 |
-
"slug": "abx-sovereigncode",
|
| 4 |
"publisher": "AbteeX AI Labs",
|
| 5 |
"family": "LumynaX sovereign products",
|
| 6 |
"stage": "local_runtime",
|
| 7 |
-
"
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
"
|
| 11 |
-
"
|
| 12 |
-
"
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
"
|
| 23 |
-
"
|
| 24 |
-
"
|
| 25 |
-
"
|
| 26 |
-
"
|
| 27 |
-
"
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
"
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
"
|
| 34 |
-
"
|
| 35 |
-
"
|
| 36 |
-
"
|
| 37 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
"python -m tinyluminax.products.sovereigncode.cli ui",
|
| 39 |
"python -m tinyluminax.products.sovereigncode.cli serve",
|
| 40 |
-
"python -m tinyluminax.products.sovereigncode.cli audit"
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
"
|
| 44 |
-
"OpenAI-compatible /v1/models via MaramaRoute",
|
| 45 |
-
"SovereignCode GET /health and GET /v1/audit",
|
| 46 |
-
"SovereignCode POST /v1/evaluate",
|
| 47 |
-
"SovereignCode POST /v1/plan-turn",
|
| 48 |
-
"SovereignCode POST /v1/tool-check",
|
| 49 |
-
"OpenCode custom provider config",
|
| 50 |
-
"local CLI policy evaluator",
|
| 51 |
-
"governed coding-turn planner",
|
| 52 |
-
"policy matrix and tool gate checker",
|
| 53 |
-
"browser operator checklist",
|
| 54 |
-
"browser operator console"
|
| 55 |
],
|
| 56 |
-
"
|
| 57 |
-
"
|
| 58 |
-
"
|
| 59 |
-
"
|
| 60 |
-
"
|
| 61 |
-
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"product_name": "AbteeX SovereignCode",
|
| 3 |
+
"slug": "abx-sovereigncode",
|
| 4 |
"publisher": "AbteeX AI Labs",
|
| 5 |
"family": "LumynaX sovereign products",
|
| 6 |
"stage": "local_runtime",
|
| 7 |
+
"package": {
|
| 8 |
+
"pypi_name": "abteex-sovereigncode",
|
| 9 |
+
"python_import": "sovereigncode",
|
| 10 |
+
"console_script": "sovereigncode",
|
| 11 |
+
"version": "0.4.0",
|
| 12 |
+
"release_artifacts": [
|
| 13 |
+
"wheel",
|
| 14 |
+
"sdist",
|
| 15 |
+
"huggingface_model_repo",
|
| 16 |
+
"github_source"
|
| 17 |
+
]
|
| 18 |
+
},
|
| 19 |
+
"positioning": "OpenCode-style local coding agent with Data Capsule sovereignty controls",
|
| 20 |
+
"target_region": "NZ",
|
| 21 |
+
"target_users": [
|
| 22 |
+
"New Zealand developers",
|
| 23 |
+
"regulated SMEs",
|
| 24 |
+
"local government teams",
|
| 25 |
+
"health-adjacent operators",
|
| 26 |
+
"Iwi and community data stewards",
|
| 27 |
+
"privacy-conscious individual developers"
|
| 28 |
+
],
|
| 29 |
+
"primary_modules": [
|
| 30 |
+
"data_capsule_policy",
|
| 31 |
+
"personal_detail_consent",
|
| 32 |
+
"tool_broker",
|
| 33 |
+
"lumynax_runtime_adapter",
|
| 34 |
+
"audit_ledger",
|
| 35 |
+
"human_review_gate",
|
| 36 |
+
"policy_matrix",
|
| 37 |
+
"tool_gate_check",
|
| 38 |
+
"capsule_summary",
|
| 39 |
+
"opencode_workspace_export",
|
| 40 |
+
"opencode_compatible_provider",
|
| 41 |
+
"policy_api_service",
|
| 42 |
+
"persistent_audit_ledger"
|
| 43 |
+
],
|
| 44 |
+
"runtime_entrypoints": [
|
| 45 |
+
"python -m tinyluminax.products.sovereigncode.cli evaluate",
|
| 46 |
+
"python -m tinyluminax.products.sovereigncode.cli plan-turn",
|
| 47 |
+
"python -m tinyluminax.products.sovereigncode.cli policy-matrix",
|
| 48 |
+
"python -m tinyluminax.products.sovereigncode.cli tool-check",
|
| 49 |
+
"python -m tinyluminax.products.sovereigncode.cli opencode-config",
|
| 50 |
"python -m tinyluminax.products.sovereigncode.cli ui",
|
| 51 |
"python -m tinyluminax.products.sovereigncode.cli serve",
|
| 52 |
+
"python -m tinyluminax.products.sovereigncode.cli audit",
|
| 53 |
+
"sovereigncode serve",
|
| 54 |
+
"sovereigncode serve --smoke",
|
| 55 |
+
"sovereigncode audit"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
],
|
| 57 |
+
"integration_surfaces": [
|
| 58 |
+
"OpenAI-compatible /v1/chat/completions via MaramaRoute",
|
| 59 |
+
"OpenAI-compatible /v1/models via MaramaRoute",
|
| 60 |
+
"SovereignCode GET /health and GET /v1/audit",
|
| 61 |
+
"SovereignCode POST /v1/evaluate",
|
| 62 |
+
"SovereignCode POST /v1/plan-turn",
|
| 63 |
+
"SovereignCode POST /v1/tool-check",
|
| 64 |
+
"OpenCode custom provider config",
|
| 65 |
+
"local CLI policy evaluator",
|
| 66 |
+
"governed coding-turn planner",
|
| 67 |
+
"policy matrix and tool gate checker",
|
| 68 |
+
"browser operator checklist",
|
| 69 |
+
"browser operator console"
|
| 70 |
+
],
|
| 71 |
+
"brand_system": {
|
| 72 |
+
"paper": "#fffefa",
|
| 73 |
+
"ink": "#0a0a0b",
|
| 74 |
+
"accent": "#e08a2c",
|
| 75 |
+
"muted": "#726b62"
|
| 76 |
+
}
|
| 77 |
+
}
|
pyproject.toml
CHANGED
|
@@ -4,19 +4,41 @@ build-backend = "setuptools.build_meta"
|
|
| 4 |
|
| 5 |
[project]
|
| 6 |
name = "abteex-sovereigncode"
|
| 7 |
-
version = "0.
|
| 8 |
-
description = "AbteeX SovereignCode: local-first
|
| 9 |
readme = "README.md"
|
| 10 |
requires-python = ">=3.11"
|
| 11 |
license = "Apache-2.0"
|
| 12 |
authors = [{ name = "AbteeX AI Labs" }]
|
| 13 |
-
|
|
|
|
| 14 |
dependencies = ["PyYAML>=6.0"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
[project.urls]
|
| 17 |
Homepage = "https://abteex.com"
|
| 18 |
-
Repository = "https://
|
|
|
|
| 19 |
Lumynax = "https://lumynax.com"
|
|
|
|
| 20 |
|
| 21 |
[project.scripts]
|
| 22 |
sovereigncode = "sovereigncode.cli:main"
|
|
@@ -26,6 +48,9 @@ where = ["."]
|
|
| 26 |
include = ["sovereigncode*", "marama_route*"]
|
| 27 |
namespaces = true
|
| 28 |
|
|
|
|
|
|
|
|
|
|
| 29 |
[tool.setuptools.package-data]
|
| 30 |
-
sovereigncode = ["configs/*", "examples/*", "integrations/*", "policy-packs/*", "schemas/*"]
|
| 31 |
-
marama_route = ["configs/*", "examples/*", "integrations/*", "schemas/*"]
|
|
|
|
| 4 |
|
| 5 |
[project]
|
| 6 |
name = "abteex-sovereigncode"
|
| 7 |
+
version = "0.4.0"
|
| 8 |
+
description = "AbteeX SovereignCode: local-first policy API, audit ledger, and coding-agent sovereignty controls."
|
| 9 |
readme = "README.md"
|
| 10 |
requires-python = ">=3.11"
|
| 11 |
license = "Apache-2.0"
|
| 12 |
authors = [{ name = "AbteeX AI Labs" }]
|
| 13 |
+
maintainers = [{ name = "AbteeX AI Labs" }]
|
| 14 |
+
keywords = ["lumynax", "sovereignty", "data-capsule", "coding-agent", "opencode", "new-zealand", "policy-api", "audit-ledger"]
|
| 15 |
dependencies = ["PyYAML>=6.0"]
|
| 16 |
+
classifiers = [
|
| 17 |
+
"Development Status :: 4 - Beta",
|
| 18 |
+
"Environment :: Console",
|
| 19 |
+
"Environment :: Web Environment",
|
| 20 |
+
"Intended Audience :: Developers",
|
| 21 |
+
"Intended Audience :: Information Technology",
|
| 22 |
+
"Operating System :: OS Independent",
|
| 23 |
+
"Programming Language :: Python :: 3",
|
| 24 |
+
"Programming Language :: Python :: 3.11",
|
| 25 |
+
"Programming Language :: Python :: 3.12",
|
| 26 |
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
| 27 |
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
| 28 |
+
"Topic :: System :: Systems Administration",
|
| 29 |
+
"Typing :: Typed",
|
| 30 |
+
]
|
| 31 |
+
|
| 32 |
+
[project.optional-dependencies]
|
| 33 |
+
release = ["build>=1.2", "twine>=5.1"]
|
| 34 |
+
dev = ["build>=1.2", "ruff>=0.6", "twine>=5.1"]
|
| 35 |
|
| 36 |
[project.urls]
|
| 37 |
Homepage = "https://abteex.com"
|
| 38 |
+
Repository = "https://github.com/Aimaghsoodi/TinyLuminaX"
|
| 39 |
+
"Hugging Face" = "https://huggingface.co/AbteeXAILab/sovereigncode"
|
| 40 |
Lumynax = "https://lumynax.com"
|
| 41 |
+
Issues = "https://github.com/Aimaghsoodi/TinyLuminaX/issues"
|
| 42 |
|
| 43 |
[project.scripts]
|
| 44 |
sovereigncode = "sovereigncode.cli:main"
|
|
|
|
| 48 |
include = ["sovereigncode*", "marama_route*"]
|
| 49 |
namespaces = true
|
| 50 |
|
| 51 |
+
[tool.setuptools]
|
| 52 |
+
include-package-data = true
|
| 53 |
+
|
| 54 |
[tool.setuptools.package-data]
|
| 55 |
+
sovereigncode = ["py.typed", "configs/*", "examples/*", "integrations/*", "policy-packs/*", "schemas/*"]
|
| 56 |
+
marama_route = ["py.typed", "configs/*", "examples/*", "integrations/*", "schemas/*"]
|
sovereigncode/__init__.py
CHANGED
|
@@ -1,41 +1,41 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
from .audit import AuditRecord, build_audit_record
|
| 4 |
-
from .ledger import AuditLedger
|
| 5 |
-
from .planner import SovereignCodingTurnPlan, ToolGrant, plan_coding_turn
|
| 6 |
-
from .platform import (
|
| 7 |
-
build_capsule_summary,
|
| 8 |
-
build_opencode_workspace_config,
|
| 9 |
-
build_policy_matrix,
|
| 10 |
-
build_turn_brief,
|
| 11 |
-
check_tool_request,
|
| 12 |
-
)
|
| 13 |
-
from .policy import (
|
| 14 |
-
DataCapsule,
|
| 15 |
-
PolicyDecision,
|
| 16 |
-
SovereignRequest,
|
| 17 |
-
SovereigntyPolicyEngine,
|
| 18 |
-
)
|
| 19 |
-
from .server import handle_service_request, smoke_service
|
| 20 |
-
from .ui import smoke_ui as smoke_ui
|
| 21 |
-
|
| 22 |
-
__all__ = [
|
| 23 |
-
"AuditRecord",
|
| 24 |
-
"AuditLedger",
|
| 25 |
-
"DataCapsule",
|
| 26 |
-
"PolicyDecision",
|
| 27 |
-
"SovereignRequest",
|
| 28 |
-
"SovereignCodingTurnPlan",
|
| 29 |
-
"SovereigntyPolicyEngine",
|
| 30 |
-
"ToolGrant",
|
| 31 |
-
"build_audit_record",
|
| 32 |
-
"build_capsule_summary",
|
| 33 |
-
"build_opencode_workspace_config",
|
| 34 |
-
"build_policy_matrix",
|
| 35 |
-
"build_turn_brief",
|
| 36 |
-
"check_tool_request",
|
| 37 |
-
"handle_service_request",
|
| 38 |
-
"plan_coding_turn",
|
| 39 |
-
"smoke_service",
|
| 40 |
-
"smoke_ui",
|
| 41 |
-
]
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from .audit import AuditRecord, build_audit_record
|
| 4 |
+
from .ledger import AuditLedger
|
| 5 |
+
from .planner import SovereignCodingTurnPlan, ToolGrant, plan_coding_turn
|
| 6 |
+
from .platform import (
|
| 7 |
+
build_capsule_summary,
|
| 8 |
+
build_opencode_workspace_config,
|
| 9 |
+
build_policy_matrix,
|
| 10 |
+
build_turn_brief,
|
| 11 |
+
check_tool_request,
|
| 12 |
+
)
|
| 13 |
+
from .policy import (
|
| 14 |
+
DataCapsule,
|
| 15 |
+
PolicyDecision,
|
| 16 |
+
SovereignRequest,
|
| 17 |
+
SovereigntyPolicyEngine,
|
| 18 |
+
)
|
| 19 |
+
from .server import handle_service_request, smoke_service
|
| 20 |
+
from .ui import smoke_ui as smoke_ui
|
| 21 |
+
|
| 22 |
+
__all__ = [
|
| 23 |
+
"AuditRecord",
|
| 24 |
+
"AuditLedger",
|
| 25 |
+
"DataCapsule",
|
| 26 |
+
"PolicyDecision",
|
| 27 |
+
"SovereignRequest",
|
| 28 |
+
"SovereignCodingTurnPlan",
|
| 29 |
+
"SovereigntyPolicyEngine",
|
| 30 |
+
"ToolGrant",
|
| 31 |
+
"build_audit_record",
|
| 32 |
+
"build_capsule_summary",
|
| 33 |
+
"build_opencode_workspace_config",
|
| 34 |
+
"build_policy_matrix",
|
| 35 |
+
"build_turn_brief",
|
| 36 |
+
"check_tool_request",
|
| 37 |
+
"handle_service_request",
|
| 38 |
+
"plan_coding_turn",
|
| 39 |
+
"smoke_service",
|
| 40 |
+
"smoke_ui",
|
| 41 |
+
]
|
sovereigncode/_ui_server.py
CHANGED
|
@@ -1,121 +1,121 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
import json
|
| 4 |
-
import socket
|
| 5 |
-
import webbrowser
|
| 6 |
-
from collections.abc import Callable
|
| 7 |
-
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
| 8 |
-
from typing import Any
|
| 9 |
-
from urllib.parse import urlparse
|
| 10 |
-
|
| 11 |
-
ApiHandler = Callable[[str, str, dict[str, Any] | None], tuple[int, dict[str, Any]]]
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
def find_available_port(host: str, preferred_port: int, *, attempts: int = 50) -> int:
|
| 15 |
-
start = preferred_port if preferred_port > 0 else 0
|
| 16 |
-
if start == 0:
|
| 17 |
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
|
| 18 |
-
probe.bind((host, 0))
|
| 19 |
-
return int(probe.getsockname()[1])
|
| 20 |
-
|
| 21 |
-
for port in range(start, start + attempts):
|
| 22 |
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
|
| 23 |
-
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
| 24 |
-
try:
|
| 25 |
-
probe.bind((host, port))
|
| 26 |
-
except OSError:
|
| 27 |
-
continue
|
| 28 |
-
return port
|
| 29 |
-
raise OSError(f"No available port found from {preferred_port} to {preferred_port + attempts - 1}")
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
def serve_dashboard(
|
| 33 |
-
*,
|
| 34 |
-
product_name: str,
|
| 35 |
-
html: str,
|
| 36 |
-
api_handler: ApiHandler,
|
| 37 |
-
host: str,
|
| 38 |
-
port: int,
|
| 39 |
-
open_browser: bool = False,
|
| 40 |
-
api_path_prefixes: tuple[str, ...] = ("/api/",),
|
| 41 |
-
api_exact_paths: tuple[str, ...] = (),
|
| 42 |
-
) -> int:
|
| 43 |
-
actual_port = find_available_port(host, port)
|
| 44 |
-
exact_paths = set(api_exact_paths)
|
| 45 |
-
|
| 46 |
-
def is_api_path(path: str) -> bool:
|
| 47 |
-
return path in exact_paths or any(path.startswith(prefix) for prefix in api_path_prefixes)
|
| 48 |
-
|
| 49 |
-
class Handler(BaseHTTPRequestHandler):
|
| 50 |
-
server_version = "AbteeXProductUI/0.1"
|
| 51 |
-
|
| 52 |
-
def do_GET(self) -> None: # noqa: N802 - stdlib handler method name
|
| 53 |
-
path = urlparse(self.path).path
|
| 54 |
-
if path == "/":
|
| 55 |
-
self._send_text(200, html, "text/html; charset=utf-8")
|
| 56 |
-
return
|
| 57 |
-
if is_api_path(path):
|
| 58 |
-
self._send_api("GET", path, None)
|
| 59 |
-
return
|
| 60 |
-
self._send_json(404, {"ok": False, "error": "not_found"})
|
| 61 |
-
|
| 62 |
-
def do_POST(self) -> None: # noqa: N802 - stdlib handler method name
|
| 63 |
-
path = urlparse(self.path).path
|
| 64 |
-
if not is_api_path(path):
|
| 65 |
-
self._send_json(404, {"ok": False, "error": "not_found"})
|
| 66 |
-
return
|
| 67 |
-
try:
|
| 68 |
-
length = int(self.headers.get("Content-Length", "0"))
|
| 69 |
-
raw = self.rfile.read(length).decode("utf-8") if length else "{}"
|
| 70 |
-
payload = json.loads(raw)
|
| 71 |
-
if not isinstance(payload, dict):
|
| 72 |
-
raise ValueError("JSON body must be an object")
|
| 73 |
-
self._send_api("POST", path, payload)
|
| 74 |
-
except Exception as exc: # defensive API boundary
|
| 75 |
-
self._send_json(400, {"ok": False, "error": str(exc)})
|
| 76 |
-
|
| 77 |
-
def log_message(self, format: str, *args: Any) -> None: # noqa: A002
|
| 78 |
-
return
|
| 79 |
-
|
| 80 |
-
def _send_api(
|
| 81 |
-
self,
|
| 82 |
-
method: str,
|
| 83 |
-
path: str,
|
| 84 |
-
payload: dict[str, Any] | None,
|
| 85 |
-
) -> None:
|
| 86 |
-
try:
|
| 87 |
-
status, response = api_handler(method, path, payload)
|
| 88 |
-
except Exception as exc: # defensive API boundary
|
| 89 |
-
status, response = 500, {"ok": False, "error": str(exc)}
|
| 90 |
-
self._send_json(status, response)
|
| 91 |
-
|
| 92 |
-
def _send_json(self, status: int, payload: dict[str, Any]) -> None:
|
| 93 |
-
body = json.dumps(payload, indent=2, sort_keys=True).encode("utf-8")
|
| 94 |
-
self.send_response(status)
|
| 95 |
-
self.send_header("Content-Type", "application/json; charset=utf-8")
|
| 96 |
-
self.send_header("Content-Length", str(len(body)))
|
| 97 |
-
self.send_header("Cache-Control", "no-store")
|
| 98 |
-
self.end_headers()
|
| 99 |
-
self.wfile.write(body)
|
| 100 |
-
|
| 101 |
-
def _send_text(self, status: int, body: str, content_type: str) -> None:
|
| 102 |
-
encoded = body.encode("utf-8")
|
| 103 |
-
self.send_response(status)
|
| 104 |
-
self.send_header("Content-Type", content_type)
|
| 105 |
-
self.send_header("Content-Length", str(len(encoded)))
|
| 106 |
-
self.send_header("Cache-Control", "no-store")
|
| 107 |
-
self.end_headers()
|
| 108 |
-
self.wfile.write(encoded)
|
| 109 |
-
|
| 110 |
-
server = ThreadingHTTPServer((host, actual_port), Handler)
|
| 111 |
-
url = f"http://{host}:{actual_port}/"
|
| 112 |
-
print(f"{product_name} UI listening on {url}")
|
| 113 |
-
if open_browser:
|
| 114 |
-
webbrowser.open(url)
|
| 115 |
-
try:
|
| 116 |
-
server.serve_forever()
|
| 117 |
-
except KeyboardInterrupt:
|
| 118 |
-
print(f"{product_name} UI stopped")
|
| 119 |
-
finally:
|
| 120 |
-
server.server_close()
|
| 121 |
-
return 0
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import socket
|
| 5 |
+
import webbrowser
|
| 6 |
+
from collections.abc import Callable
|
| 7 |
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
| 8 |
+
from typing import Any
|
| 9 |
+
from urllib.parse import urlparse
|
| 10 |
+
|
| 11 |
+
ApiHandler = Callable[[str, str, dict[str, Any] | None], tuple[int, dict[str, Any]]]
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def find_available_port(host: str, preferred_port: int, *, attempts: int = 50) -> int:
|
| 15 |
+
start = preferred_port if preferred_port > 0 else 0
|
| 16 |
+
if start == 0:
|
| 17 |
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
|
| 18 |
+
probe.bind((host, 0))
|
| 19 |
+
return int(probe.getsockname()[1])
|
| 20 |
+
|
| 21 |
+
for port in range(start, start + attempts):
|
| 22 |
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
|
| 23 |
+
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
| 24 |
+
try:
|
| 25 |
+
probe.bind((host, port))
|
| 26 |
+
except OSError:
|
| 27 |
+
continue
|
| 28 |
+
return port
|
| 29 |
+
raise OSError(f"No available port found from {preferred_port} to {preferred_port + attempts - 1}")
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def serve_dashboard(
|
| 33 |
+
*,
|
| 34 |
+
product_name: str,
|
| 35 |
+
html: str,
|
| 36 |
+
api_handler: ApiHandler,
|
| 37 |
+
host: str,
|
| 38 |
+
port: int,
|
| 39 |
+
open_browser: bool = False,
|
| 40 |
+
api_path_prefixes: tuple[str, ...] = ("/api/",),
|
| 41 |
+
api_exact_paths: tuple[str, ...] = (),
|
| 42 |
+
) -> int:
|
| 43 |
+
actual_port = find_available_port(host, port)
|
| 44 |
+
exact_paths = set(api_exact_paths)
|
| 45 |
+
|
| 46 |
+
def is_api_path(path: str) -> bool:
|
| 47 |
+
return path in exact_paths or any(path.startswith(prefix) for prefix in api_path_prefixes)
|
| 48 |
+
|
| 49 |
+
class Handler(BaseHTTPRequestHandler):
|
| 50 |
+
server_version = "AbteeXProductUI/0.1"
|
| 51 |
+
|
| 52 |
+
def do_GET(self) -> None: # noqa: N802 - stdlib handler method name
|
| 53 |
+
path = urlparse(self.path).path
|
| 54 |
+
if path == "/":
|
| 55 |
+
self._send_text(200, html, "text/html; charset=utf-8")
|
| 56 |
+
return
|
| 57 |
+
if is_api_path(path):
|
| 58 |
+
self._send_api("GET", path, None)
|
| 59 |
+
return
|
| 60 |
+
self._send_json(404, {"ok": False, "error": "not_found"})
|
| 61 |
+
|
| 62 |
+
def do_POST(self) -> None: # noqa: N802 - stdlib handler method name
|
| 63 |
+
path = urlparse(self.path).path
|
| 64 |
+
if not is_api_path(path):
|
| 65 |
+
self._send_json(404, {"ok": False, "error": "not_found"})
|
| 66 |
+
return
|
| 67 |
+
try:
|
| 68 |
+
length = int(self.headers.get("Content-Length", "0"))
|
| 69 |
+
raw = self.rfile.read(length).decode("utf-8") if length else "{}"
|
| 70 |
+
payload = json.loads(raw)
|
| 71 |
+
if not isinstance(payload, dict):
|
| 72 |
+
raise ValueError("JSON body must be an object")
|
| 73 |
+
self._send_api("POST", path, payload)
|
| 74 |
+
except Exception as exc: # defensive API boundary
|
| 75 |
+
self._send_json(400, {"ok": False, "error": str(exc)})
|
| 76 |
+
|
| 77 |
+
def log_message(self, format: str, *args: Any) -> None: # noqa: A002
|
| 78 |
+
return
|
| 79 |
+
|
| 80 |
+
def _send_api(
|
| 81 |
+
self,
|
| 82 |
+
method: str,
|
| 83 |
+
path: str,
|
| 84 |
+
payload: dict[str, Any] | None,
|
| 85 |
+
) -> None:
|
| 86 |
+
try:
|
| 87 |
+
status, response = api_handler(method, path, payload)
|
| 88 |
+
except Exception as exc: # defensive API boundary
|
| 89 |
+
status, response = 500, {"ok": False, "error": str(exc)}
|
| 90 |
+
self._send_json(status, response)
|
| 91 |
+
|
| 92 |
+
def _send_json(self, status: int, payload: dict[str, Any]) -> None:
|
| 93 |
+
body = json.dumps(payload, indent=2, sort_keys=True).encode("utf-8")
|
| 94 |
+
self.send_response(status)
|
| 95 |
+
self.send_header("Content-Type", "application/json; charset=utf-8")
|
| 96 |
+
self.send_header("Content-Length", str(len(body)))
|
| 97 |
+
self.send_header("Cache-Control", "no-store")
|
| 98 |
+
self.end_headers()
|
| 99 |
+
self.wfile.write(body)
|
| 100 |
+
|
| 101 |
+
def _send_text(self, status: int, body: str, content_type: str) -> None:
|
| 102 |
+
encoded = body.encode("utf-8")
|
| 103 |
+
self.send_response(status)
|
| 104 |
+
self.send_header("Content-Type", content_type)
|
| 105 |
+
self.send_header("Content-Length", str(len(encoded)))
|
| 106 |
+
self.send_header("Cache-Control", "no-store")
|
| 107 |
+
self.end_headers()
|
| 108 |
+
self.wfile.write(encoded)
|
| 109 |
+
|
| 110 |
+
server = ThreadingHTTPServer((host, actual_port), Handler)
|
| 111 |
+
url = f"http://{host}:{actual_port}/"
|
| 112 |
+
print(f"{product_name} UI listening on {url}")
|
| 113 |
+
if open_browser:
|
| 114 |
+
webbrowser.open(url)
|
| 115 |
+
try:
|
| 116 |
+
server.serve_forever()
|
| 117 |
+
except KeyboardInterrupt:
|
| 118 |
+
print(f"{product_name} UI stopped")
|
| 119 |
+
finally:
|
| 120 |
+
server.server_close()
|
| 121 |
+
return 0
|
sovereigncode/configs/gateway.local.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
| 1 |
-
{
|
| 2 |
-
"mode": "route_only",
|
| 3 |
-
"prompt_retention": "not_stored_by_default",
|
| 4 |
-
"default_timeout_seconds": 120,
|
| 5 |
-
"backends": {
|
| 6 |
-
"example-local-openai-compatible": {
|
| 7 |
-
"type": "openai_compatible",
|
| 8 |
-
"base_url": "http://127.0.0.1:8000/v1",
|
| 9 |
-
"api_key_env": "",
|
| 10 |
-
"model": "local-model-id"
|
| 11 |
-
}
|
| 12 |
-
}
|
| 13 |
-
}
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"mode": "route_only",
|
| 3 |
+
"prompt_retention": "not_stored_by_default",
|
| 4 |
+
"default_timeout_seconds": 120,
|
| 5 |
+
"backends": {
|
| 6 |
+
"example-local-openai-compatible": {
|
| 7 |
+
"type": "openai_compatible",
|
| 8 |
+
"base_url": "http://127.0.0.1:8000/v1",
|
| 9 |
+
"api_key_env": "",
|
| 10 |
+
"model": "local-model-id"
|
| 11 |
+
}
|
| 12 |
+
}
|
| 13 |
+
}
|
sovereigncode/configs/service.local.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
| 1 |
-
{
|
| 2 |
-
"ledger_path": ".sovereigncode/audit.jsonl",
|
| 3 |
-
"default_region": "NZ",
|
| 4 |
-
"fail_closed": true,
|
| 5 |
-
"require_human_review_for": [
|
| 6 |
-
"write_files",
|
| 7 |
-
"execute_shell",
|
| 8 |
-
"network_export",
|
| 9 |
-
"commit",
|
| 10 |
-
"publish",
|
| 11 |
-
"train_model"
|
| 12 |
-
],
|
| 13 |
-
"marama_route_base_url": "http://127.0.0.1:8787/v1"
|
| 14 |
-
}
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"ledger_path": ".sovereigncode/audit.jsonl",
|
| 3 |
+
"default_region": "NZ",
|
| 4 |
+
"fail_closed": true,
|
| 5 |
+
"require_human_review_for": [
|
| 6 |
+
"write_files",
|
| 7 |
+
"execute_shell",
|
| 8 |
+
"network_export",
|
| 9 |
+
"commit",
|
| 10 |
+
"publish",
|
| 11 |
+
"train_model"
|
| 12 |
+
],
|
| 13 |
+
"marama_route_base_url": "http://127.0.0.1:8787/v1"
|
| 14 |
+
}
|
sovereigncode/ledger.py
CHANGED
|
@@ -1,64 +1,64 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
import hashlib
|
| 4 |
-
import json
|
| 5 |
-
import threading
|
| 6 |
-
from dataclasses import dataclass, field
|
| 7 |
-
from datetime import UTC, datetime
|
| 8 |
-
from pathlib import Path
|
| 9 |
-
from typing import Any
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
def default_ledger_path() -> Path:
|
| 13 |
-
return Path.cwd() / ".sovereigncode" / "audit.jsonl"
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
@dataclass(slots=True)
|
| 17 |
-
class AuditLedger:
|
| 18 |
-
path: Path
|
| 19 |
-
_lock: threading.Lock = field(init=False, repr=False)
|
| 20 |
-
|
| 21 |
-
def __post_init__(self) -> None:
|
| 22 |
-
self._lock = threading.Lock()
|
| 23 |
-
|
| 24 |
-
def append(self, event: str, payload: dict[str, Any]) -> dict[str, Any]:
|
| 25 |
-
with self._lock:
|
| 26 |
-
self.path.parent.mkdir(parents=True, exist_ok=True)
|
| 27 |
-
sequence = self._line_count() + 1
|
| 28 |
-
record = {
|
| 29 |
-
"ledger_sequence": sequence,
|
| 30 |
-
"ledger_timestamp": datetime.now(UTC).isoformat(),
|
| 31 |
-
"event": event,
|
| 32 |
-
"payload": payload,
|
| 33 |
-
}
|
| 34 |
-
record["ledger_id"] = self._digest(record)
|
| 35 |
-
with self.path.open("a", encoding="utf-8", newline="\n") as stream:
|
| 36 |
-
stream.write(json.dumps(record, sort_keys=True, separators=(",", ":")))
|
| 37 |
-
stream.write("\n")
|
| 38 |
-
return {**record, "ledger_path": str(self.path)}
|
| 39 |
-
|
| 40 |
-
def tail(self, limit: int = 50) -> list[dict[str, Any]]:
|
| 41 |
-
if not self.path.exists():
|
| 42 |
-
return []
|
| 43 |
-
lines = self.path.read_text(encoding="utf-8-sig").splitlines()
|
| 44 |
-
records: list[dict[str, Any]] = []
|
| 45 |
-
for line in lines[-max(1, limit) :]:
|
| 46 |
-
try:
|
| 47 |
-
payload = json.loads(line)
|
| 48 |
-
except json.JSONDecodeError:
|
| 49 |
-
continue
|
| 50 |
-
if isinstance(payload, dict):
|
| 51 |
-
payload["ledger_path"] = str(self.path)
|
| 52 |
-
records.append(payload)
|
| 53 |
-
return records
|
| 54 |
-
|
| 55 |
-
def _line_count(self) -> int:
|
| 56 |
-
if not self.path.exists():
|
| 57 |
-
return 0
|
| 58 |
-
with self.path.open("r", encoding="utf-8-sig") as stream:
|
| 59 |
-
return sum(1 for line in stream if line.strip())
|
| 60 |
-
|
| 61 |
-
@staticmethod
|
| 62 |
-
def _digest(record: dict[str, Any]) -> str:
|
| 63 |
-
raw = json.dumps(record, sort_keys=True, default=str).encode("utf-8")
|
| 64 |
-
return f"sc-ledger-{hashlib.sha256(raw).hexdigest()[:24]}"
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import hashlib
|
| 4 |
+
import json
|
| 5 |
+
import threading
|
| 6 |
+
from dataclasses import dataclass, field
|
| 7 |
+
from datetime import UTC, datetime
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
from typing import Any
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def default_ledger_path() -> Path:
|
| 13 |
+
return Path.cwd() / ".sovereigncode" / "audit.jsonl"
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@dataclass(slots=True)
|
| 17 |
+
class AuditLedger:
|
| 18 |
+
path: Path
|
| 19 |
+
_lock: threading.Lock = field(init=False, repr=False)
|
| 20 |
+
|
| 21 |
+
def __post_init__(self) -> None:
|
| 22 |
+
self._lock = threading.Lock()
|
| 23 |
+
|
| 24 |
+
def append(self, event: str, payload: dict[str, Any]) -> dict[str, Any]:
|
| 25 |
+
with self._lock:
|
| 26 |
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
| 27 |
+
sequence = self._line_count() + 1
|
| 28 |
+
record = {
|
| 29 |
+
"ledger_sequence": sequence,
|
| 30 |
+
"ledger_timestamp": datetime.now(UTC).isoformat(),
|
| 31 |
+
"event": event,
|
| 32 |
+
"payload": payload,
|
| 33 |
+
}
|
| 34 |
+
record["ledger_id"] = self._digest(record)
|
| 35 |
+
with self.path.open("a", encoding="utf-8", newline="\n") as stream:
|
| 36 |
+
stream.write(json.dumps(record, sort_keys=True, separators=(",", ":")))
|
| 37 |
+
stream.write("\n")
|
| 38 |
+
return {**record, "ledger_path": str(self.path)}
|
| 39 |
+
|
| 40 |
+
def tail(self, limit: int = 50) -> list[dict[str, Any]]:
|
| 41 |
+
if not self.path.exists():
|
| 42 |
+
return []
|
| 43 |
+
lines = self.path.read_text(encoding="utf-8-sig").splitlines()
|
| 44 |
+
records: list[dict[str, Any]] = []
|
| 45 |
+
for line in lines[-max(1, limit) :]:
|
| 46 |
+
try:
|
| 47 |
+
payload = json.loads(line)
|
| 48 |
+
except json.JSONDecodeError:
|
| 49 |
+
continue
|
| 50 |
+
if isinstance(payload, dict):
|
| 51 |
+
payload["ledger_path"] = str(self.path)
|
| 52 |
+
records.append(payload)
|
| 53 |
+
return records
|
| 54 |
+
|
| 55 |
+
def _line_count(self) -> int:
|
| 56 |
+
if not self.path.exists():
|
| 57 |
+
return 0
|
| 58 |
+
with self.path.open("r", encoding="utf-8-sig") as stream:
|
| 59 |
+
return sum(1 for line in stream if line.strip())
|
| 60 |
+
|
| 61 |
+
@staticmethod
|
| 62 |
+
def _digest(record: dict[str, Any]) -> str:
|
| 63 |
+
raw = json.dumps(record, sort_keys=True, default=str).encode("utf-8")
|
| 64 |
+
return f"sc-ledger-{hashlib.sha256(raw).hexdigest()[:24]}"
|
sovereigncode/py.typed
ADDED
|
File without changes
|
sovereigncode/server.py
CHANGED
|
@@ -1,352 +1,352 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
import json
|
| 4 |
-
import os
|
| 5 |
-
import tempfile
|
| 6 |
-
from pathlib import Path
|
| 7 |
-
from typing import Any
|
| 8 |
-
|
| 9 |
-
try: # repo package
|
| 10 |
-
from tinyluminax.products._ui_server import serve_dashboard
|
| 11 |
-
except ModuleNotFoundError: # standalone HF package
|
| 12 |
-
from ._ui_server import serve_dashboard
|
| 13 |
-
|
| 14 |
-
try: # repo package
|
| 15 |
-
from tinyluminax.products.marama_route import RoutingRequest, load_model_registry
|
| 16 |
-
except ModuleNotFoundError: # standalone HF package
|
| 17 |
-
from marama_route import RoutingRequest, load_model_registry
|
| 18 |
-
|
| 19 |
-
from .audit import build_audit_record
|
| 20 |
-
from .ledger import AuditLedger, default_ledger_path
|
| 21 |
-
from .planner import plan_coding_turn
|
| 22 |
-
from .platform import (
|
| 23 |
-
build_capsule_summary,
|
| 24 |
-
build_policy_matrix,
|
| 25 |
-
build_turn_brief,
|
| 26 |
-
check_tool_request,
|
| 27 |
-
)
|
| 28 |
-
from .policy import DataCapsule, SovereignRequest, SovereigntyPolicyEngine
|
| 29 |
-
from .ui import (
|
| 30 |
-
PRODUCT_NAME,
|
| 31 |
-
build_dashboard_html,
|
| 32 |
-
build_dashboard_state,
|
| 33 |
-
default_capsule_path,
|
| 34 |
-
default_registry_path,
|
| 35 |
-
default_request_path,
|
| 36 |
-
default_route_request_path,
|
| 37 |
-
handle_api_request,
|
| 38 |
-
)
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
def handle_service_request(
|
| 42 |
-
method: str,
|
| 43 |
-
path: str,
|
| 44 |
-
payload: dict[str, Any] | None,
|
| 45 |
-
*,
|
| 46 |
-
capsule_path: Path,
|
| 47 |
-
request_path: Path,
|
| 48 |
-
route_request_path: Path,
|
| 49 |
-
registry_path: Path,
|
| 50 |
-
ledger_path: Path,
|
| 51 |
-
state: dict[str, Any] | None = None,
|
| 52 |
-
) -> tuple[int, dict[str, Any]]:
|
| 53 |
-
ledger = AuditLedger(ledger_path)
|
| 54 |
-
service_state = state or build_dashboard_state(capsule_path, request_path, route_request_path, registry_path)
|
| 55 |
-
|
| 56 |
-
if path.startswith("/api/"):
|
| 57 |
-
return handle_api_request(method, path, payload, registry_path, service_state)
|
| 58 |
-
if method == "GET" and path in {"/health", "/v1/health"}:
|
| 59 |
-
return 200, _health_payload(ledger, service_state)
|
| 60 |
-
if method == "GET" and path == "/v1/audit":
|
| 61 |
-
return 200, {"ok": True, "ledger_path": str(ledger.path), "records": ledger.tail()}
|
| 62 |
-
if method == "GET" and path == "/v1/capsule-summary":
|
| 63 |
-
return 200, {"ok": True, "summary": build_capsule_summary(service_state["capsule"])}
|
| 64 |
-
if method == "POST" and path == "/v1/evaluate":
|
| 65 |
-
result = evaluate_payload(payload or {}, service_state, ledger)
|
| 66 |
-
return (200 if result["decision"]["allowed"] else 422), result
|
| 67 |
-
if method == "POST" and path == "/v1/plan-turn":
|
| 68 |
-
result = plan_turn_payload(payload or {}, service_state, ledger)
|
| 69 |
-
return (200 if result["allowed"] else 422), result
|
| 70 |
-
if method == "POST" and path == "/v1/tool-check":
|
| 71 |
-
result = tool_check_payload(payload or {}, service_state, ledger)
|
| 72 |
-
return (200 if result["ok"] else 422), result
|
| 73 |
-
if method == "POST" and path == "/v1/policy-matrix":
|
| 74 |
-
return 200, policy_matrix_payload(payload or {}, service_state, ledger)
|
| 75 |
-
if method == "POST" and path == "/v1/capsule-summary":
|
| 76 |
-
capsule_payload = _mapping(payload, "capsule") or service_state["capsule"]
|
| 77 |
-
return 200, {"ok": True, "summary": build_capsule_summary(capsule_payload)}
|
| 78 |
-
return 404, {"ok": False, "error": "not_found"}
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
def evaluate_payload(
|
| 82 |
-
payload: dict[str, Any],
|
| 83 |
-
state: dict[str, Any],
|
| 84 |
-
ledger: AuditLedger,
|
| 85 |
-
) -> dict[str, Any]:
|
| 86 |
-
capsule = DataCapsule.from_payload(_mapping(payload, "capsule") or state["capsule"])
|
| 87 |
-
request = SovereignRequest.from_payload(_mapping(payload, "request") or state["request"])
|
| 88 |
-
decision = SovereigntyPolicyEngine().evaluate(capsule, request)
|
| 89 |
-
audit = build_audit_record(capsule, request, decision).to_dict()
|
| 90 |
-
ledger_record = ledger.append(
|
| 91 |
-
"policy_evaluate",
|
| 92 |
-
{
|
| 93 |
-
"capsule_id": capsule.capsule_id,
|
| 94 |
-
"request": request.to_dict(),
|
| 95 |
-
"decision": decision.to_dict(),
|
| 96 |
-
"audit_record": audit,
|
| 97 |
-
},
|
| 98 |
-
)
|
| 99 |
-
return {
|
| 100 |
-
"ok": decision.allowed,
|
| 101 |
-
"decision": decision.to_dict(),
|
| 102 |
-
"audit_record": audit,
|
| 103 |
-
"ledger_record": ledger_record,
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
def plan_turn_payload(
|
| 108 |
-
payload: dict[str, Any],
|
| 109 |
-
state: dict[str, Any],
|
| 110 |
-
ledger: AuditLedger,
|
| 111 |
-
) -> dict[str, Any]:
|
| 112 |
-
capsule = DataCapsule.from_payload(_mapping(payload, "capsule") or state["capsule"])
|
| 113 |
-
request = SovereignRequest.from_payload(_mapping(payload, "request") or state["request"])
|
| 114 |
-
route_request = RoutingRequest.from_payload(_mapping(payload, "route_request") or state["route_request"])
|
| 115 |
-
models = load_model_registry(Path(str(payload.get("registry_path") or state["registry_path"])))
|
| 116 |
-
plan = plan_coding_turn(capsule, request, route_request, models)
|
| 117 |
-
result = plan.to_dict()
|
| 118 |
-
result["turn_brief"] = build_turn_brief(result)
|
| 119 |
-
result["ledger_record"] = ledger.append(
|
| 120 |
-
"plan_turn",
|
| 121 |
-
{
|
| 122 |
-
"capsule_id": capsule.capsule_id,
|
| 123 |
-
"allowed": plan.allowed,
|
| 124 |
-
"policy_decision": result["policy_decision"],
|
| 125 |
-
"route_decision": result["route_decision"],
|
| 126 |
-
"audit_record": result["audit_record"],
|
| 127 |
-
"turn_brief": result["turn_brief"],
|
| 128 |
-
},
|
| 129 |
-
)
|
| 130 |
-
result["ok"] = plan.allowed
|
| 131 |
-
return result
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
def tool_check_payload(
|
| 135 |
-
payload: dict[str, Any],
|
| 136 |
-
state: dict[str, Any],
|
| 137 |
-
ledger: AuditLedger,
|
| 138 |
-
) -> dict[str, Any]:
|
| 139 |
-
tool_payload = _mapping(payload, "tool") or {
|
| 140 |
-
"tool_name": payload.get("tool_name", "workspace_reader"),
|
| 141 |
-
"action": payload.get("action", "read_context"),
|
| 142 |
-
"writes_files": bool(payload.get("writes_files", False)),
|
| 143 |
-
"exports_data": bool(payload.get("exports_data", False)),
|
| 144 |
-
"trains_model": bool(payload.get("trains_model", False)),
|
| 145 |
-
"human_approved": bool(payload.get("human_approved", False)),
|
| 146 |
-
}
|
| 147 |
-
result = check_tool_request(
|
| 148 |
-
_mapping(payload, "capsule") or state["capsule"],
|
| 149 |
-
_mapping(payload, "request") or state["request"],
|
| 150 |
-
tool_payload,
|
| 151 |
-
)
|
| 152 |
-
result["ledger_record"] = ledger.append(
|
| 153 |
-
"tool_check",
|
| 154 |
-
{
|
| 155 |
-
"tool": tool_payload,
|
| 156 |
-
"decision": result["decision"],
|
| 157 |
-
"audit_record": result["audit_record"],
|
| 158 |
-
"operator_gate": result["operator_gate"],
|
| 159 |
-
},
|
| 160 |
-
)
|
| 161 |
-
return result
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
def policy_matrix_payload(
|
| 165 |
-
payload: dict[str, Any],
|
| 166 |
-
state: dict[str, Any],
|
| 167 |
-
ledger: AuditLedger,
|
| 168 |
-
) -> dict[str, Any]:
|
| 169 |
-
scenarios = payload.get("scenarios") if isinstance(payload.get("scenarios"), list) else None
|
| 170 |
-
result = build_policy_matrix(
|
| 171 |
-
_mapping(payload, "capsule") or state["capsule"],
|
| 172 |
-
_mapping(payload, "request") or state["request"],
|
| 173 |
-
scenarios,
|
| 174 |
-
)
|
| 175 |
-
result["ledger_record"] = ledger.append(
|
| 176 |
-
"policy_matrix",
|
| 177 |
-
{
|
| 178 |
-
"capsule_id": result["capsule_id"],
|
| 179 |
-
"allowed_count": result["allowed_count"],
|
| 180 |
-
"blocked_count": result["blocked_count"],
|
| 181 |
-
},
|
| 182 |
-
)
|
| 183 |
-
return result
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
def smoke_service(
|
| 187 |
-
*,
|
| 188 |
-
capsule_path: Path | None = None,
|
| 189 |
-
request_path: Path | None = None,
|
| 190 |
-
route_request_path: Path | None = None,
|
| 191 |
-
registry_path: Path | None = None,
|
| 192 |
-
ledger_path: Path | None = None,
|
| 193 |
-
) -> dict[str, Any]:
|
| 194 |
-
resolved_capsule = capsule_path or default_capsule_path()
|
| 195 |
-
resolved_request = request_path or default_request_path()
|
| 196 |
-
resolved_route_request = route_request_path or default_route_request_path()
|
| 197 |
-
resolved_registry = registry_path or default_registry_path()
|
| 198 |
-
resolved_ledger = ledger_path or _temporary_ledger_path()
|
| 199 |
-
state = build_dashboard_state(resolved_capsule, resolved_request, resolved_route_request, resolved_registry)
|
| 200 |
-
|
| 201 |
-
health_status, health = handle_service_request(
|
| 202 |
-
"GET",
|
| 203 |
-
"/health",
|
| 204 |
-
None,
|
| 205 |
-
capsule_path=resolved_capsule,
|
| 206 |
-
request_path=resolved_request,
|
| 207 |
-
route_request_path=resolved_route_request,
|
| 208 |
-
registry_path=resolved_registry,
|
| 209 |
-
ledger_path=resolved_ledger,
|
| 210 |
-
state=state,
|
| 211 |
-
)
|
| 212 |
-
evaluate_status, evaluate = handle_service_request(
|
| 213 |
-
"POST",
|
| 214 |
-
"/v1/evaluate",
|
| 215 |
-
{},
|
| 216 |
-
capsule_path=resolved_capsule,
|
| 217 |
-
request_path=resolved_request,
|
| 218 |
-
route_request_path=resolved_route_request,
|
| 219 |
-
registry_path=resolved_registry,
|
| 220 |
-
ledger_path=resolved_ledger,
|
| 221 |
-
state=state,
|
| 222 |
-
)
|
| 223 |
-
plan_status, plan = handle_service_request(
|
| 224 |
-
"POST",
|
| 225 |
-
"/v1/plan-turn",
|
| 226 |
-
{},
|
| 227 |
-
capsule_path=resolved_capsule,
|
| 228 |
-
request_path=resolved_request,
|
| 229 |
-
route_request_path=resolved_route_request,
|
| 230 |
-
registry_path=resolved_registry,
|
| 231 |
-
ledger_path=resolved_ledger,
|
| 232 |
-
state=state,
|
| 233 |
-
)
|
| 234 |
-
tool_status, tool = handle_service_request(
|
| 235 |
-
"POST",
|
| 236 |
-
"/v1/tool-check",
|
| 237 |
-
{"tool_name": "workspace_reader", "action": "read_context"},
|
| 238 |
-
capsule_path=resolved_capsule,
|
| 239 |
-
request_path=resolved_request,
|
| 240 |
-
route_request_path=resolved_route_request,
|
| 241 |
-
registry_path=resolved_registry,
|
| 242 |
-
ledger_path=resolved_ledger,
|
| 243 |
-
state=state,
|
| 244 |
-
)
|
| 245 |
-
audit_status, audit = handle_service_request(
|
| 246 |
-
"GET",
|
| 247 |
-
"/v1/audit",
|
| 248 |
-
None,
|
| 249 |
-
capsule_path=resolved_capsule,
|
| 250 |
-
request_path=resolved_request,
|
| 251 |
-
route_request_path=resolved_route_request,
|
| 252 |
-
registry_path=resolved_registry,
|
| 253 |
-
ledger_path=resolved_ledger,
|
| 254 |
-
state=state,
|
| 255 |
-
)
|
| 256 |
-
|
| 257 |
-
if (health_status, evaluate_status, plan_status, tool_status, audit_status) != (200, 200, 200, 200, 200):
|
| 258 |
-
raise RuntimeError("SovereignCode service smoke failed")
|
| 259 |
-
if not evaluate["decision"]["allowed"] or not plan["allowed"] or not tool["ok"]:
|
| 260 |
-
raise RuntimeError("SovereignCode service smoke did not allow the governed local request")
|
| 261 |
-
return {
|
| 262 |
-
"ok": True,
|
| 263 |
-
"product": PRODUCT_NAME,
|
| 264 |
-
"capsule_id": state["capsule"]["capsule_id"],
|
| 265 |
-
"selected_model": plan["turn_brief"]["selected_model"],
|
| 266 |
-
"ledger_path": str(resolved_ledger),
|
| 267 |
-
"ledger_records": len(audit["records"]),
|
| 268 |
-
"tool_next_gate": tool["operator_gate"]["next_gate"],
|
| 269 |
-
}
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
def serve_service(
|
| 273 |
-
*,
|
| 274 |
-
capsule_path: Path | None = None,
|
| 275 |
-
request_path: Path | None = None,
|
| 276 |
-
route_request_path: Path | None = None,
|
| 277 |
-
registry_path: Path | None = None,
|
| 278 |
-
ledger_path: Path | None = None,
|
| 279 |
-
host: str = "127.0.0.1",
|
| 280 |
-
port: int = 8788,
|
| 281 |
-
open_browser: bool = False,
|
| 282 |
-
smoke: bool = False,
|
| 283 |
-
) -> int:
|
| 284 |
-
resolved_capsule = capsule_path or default_capsule_path()
|
| 285 |
-
resolved_request = request_path or default_request_path()
|
| 286 |
-
resolved_route_request = route_request_path or default_route_request_path()
|
| 287 |
-
resolved_registry = registry_path or default_registry_path()
|
| 288 |
-
resolved_ledger = ledger_path or default_ledger_path()
|
| 289 |
-
if smoke:
|
| 290 |
-
print(
|
| 291 |
-
json.dumps(
|
| 292 |
-
smoke_service(
|
| 293 |
-
capsule_path=resolved_capsule,
|
| 294 |
-
request_path=resolved_request,
|
| 295 |
-
route_request_path=resolved_route_request,
|
| 296 |
-
registry_path=resolved_registry,
|
| 297 |
-
ledger_path=ledger_path,
|
| 298 |
-
),
|
| 299 |
-
indent=2,
|
| 300 |
-
sort_keys=True,
|
| 301 |
-
),
|
| 302 |
-
)
|
| 303 |
-
return 0
|
| 304 |
-
|
| 305 |
-
state = build_dashboard_state(resolved_capsule, resolved_request, resolved_route_request, resolved_registry)
|
| 306 |
-
html = build_dashboard_html(state)
|
| 307 |
-
return serve_dashboard(
|
| 308 |
-
product_name=f"{PRODUCT_NAME} Service",
|
| 309 |
-
html=html,
|
| 310 |
-
api_handler=lambda method, path, request_payload: handle_service_request(
|
| 311 |
-
method,
|
| 312 |
-
path,
|
| 313 |
-
request_payload,
|
| 314 |
-
capsule_path=resolved_capsule,
|
| 315 |
-
request_path=resolved_request,
|
| 316 |
-
route_request_path=resolved_route_request,
|
| 317 |
-
registry_path=resolved_registry,
|
| 318 |
-
ledger_path=resolved_ledger,
|
| 319 |
-
state=state,
|
| 320 |
-
),
|
| 321 |
-
host=host,
|
| 322 |
-
port=port,
|
| 323 |
-
open_browser=open_browser,
|
| 324 |
-
api_path_prefixes=("/api/", "/v1/"),
|
| 325 |
-
api_exact_paths=("/health",),
|
| 326 |
-
)
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
def _health_payload(ledger: AuditLedger, state: dict[str, Any]) -> dict[str, Any]:
|
| 330 |
-
return {
|
| 331 |
-
"ok": True,
|
| 332 |
-
"product": PRODUCT_NAME,
|
| 333 |
-
"capsule_id": state["capsule"]["capsule_id"],
|
| 334 |
-
"ledger_path": str(ledger.path),
|
| 335 |
-
"ledger_records": len(ledger.tail()),
|
| 336 |
-
"service": "policy_api_audit_ledger",
|
| 337 |
-
}
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
def _mapping(payload: dict[str, Any] | None, key: str) -> dict[str, Any]:
|
| 341 |
-
if not isinstance(payload, dict):
|
| 342 |
-
return {}
|
| 343 |
-
value = payload.get(key)
|
| 344 |
-
return dict(value) if isinstance(value, dict) else {}
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
def _temporary_ledger_path() -> Path:
|
| 348 |
-
handle, raw_path = tempfile.mkstemp(prefix="sovereigncode-smoke-", suffix=".audit.jsonl")
|
| 349 |
-
os.close(handle)
|
| 350 |
-
path = Path(raw_path)
|
| 351 |
-
path.unlink(missing_ok=True)
|
| 352 |
-
return path
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import os
|
| 5 |
+
import tempfile
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
from typing import Any
|
| 8 |
+
|
| 9 |
+
try: # repo package
|
| 10 |
+
from tinyluminax.products._ui_server import serve_dashboard
|
| 11 |
+
except ModuleNotFoundError: # standalone HF package
|
| 12 |
+
from ._ui_server import serve_dashboard
|
| 13 |
+
|
| 14 |
+
try: # repo package
|
| 15 |
+
from tinyluminax.products.marama_route import RoutingRequest, load_model_registry
|
| 16 |
+
except ModuleNotFoundError: # standalone HF package
|
| 17 |
+
from marama_route import RoutingRequest, load_model_registry
|
| 18 |
+
|
| 19 |
+
from .audit import build_audit_record
|
| 20 |
+
from .ledger import AuditLedger, default_ledger_path
|
| 21 |
+
from .planner import plan_coding_turn
|
| 22 |
+
from .platform import (
|
| 23 |
+
build_capsule_summary,
|
| 24 |
+
build_policy_matrix,
|
| 25 |
+
build_turn_brief,
|
| 26 |
+
check_tool_request,
|
| 27 |
+
)
|
| 28 |
+
from .policy import DataCapsule, SovereignRequest, SovereigntyPolicyEngine
|
| 29 |
+
from .ui import (
|
| 30 |
+
PRODUCT_NAME,
|
| 31 |
+
build_dashboard_html,
|
| 32 |
+
build_dashboard_state,
|
| 33 |
+
default_capsule_path,
|
| 34 |
+
default_registry_path,
|
| 35 |
+
default_request_path,
|
| 36 |
+
default_route_request_path,
|
| 37 |
+
handle_api_request,
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def handle_service_request(
|
| 42 |
+
method: str,
|
| 43 |
+
path: str,
|
| 44 |
+
payload: dict[str, Any] | None,
|
| 45 |
+
*,
|
| 46 |
+
capsule_path: Path,
|
| 47 |
+
request_path: Path,
|
| 48 |
+
route_request_path: Path,
|
| 49 |
+
registry_path: Path,
|
| 50 |
+
ledger_path: Path,
|
| 51 |
+
state: dict[str, Any] | None = None,
|
| 52 |
+
) -> tuple[int, dict[str, Any]]:
|
| 53 |
+
ledger = AuditLedger(ledger_path)
|
| 54 |
+
service_state = state or build_dashboard_state(capsule_path, request_path, route_request_path, registry_path)
|
| 55 |
+
|
| 56 |
+
if path.startswith("/api/"):
|
| 57 |
+
return handle_api_request(method, path, payload, registry_path, service_state)
|
| 58 |
+
if method == "GET" and path in {"/health", "/v1/health"}:
|
| 59 |
+
return 200, _health_payload(ledger, service_state)
|
| 60 |
+
if method == "GET" and path == "/v1/audit":
|
| 61 |
+
return 200, {"ok": True, "ledger_path": str(ledger.path), "records": ledger.tail()}
|
| 62 |
+
if method == "GET" and path == "/v1/capsule-summary":
|
| 63 |
+
return 200, {"ok": True, "summary": build_capsule_summary(service_state["capsule"])}
|
| 64 |
+
if method == "POST" and path == "/v1/evaluate":
|
| 65 |
+
result = evaluate_payload(payload or {}, service_state, ledger)
|
| 66 |
+
return (200 if result["decision"]["allowed"] else 422), result
|
| 67 |
+
if method == "POST" and path == "/v1/plan-turn":
|
| 68 |
+
result = plan_turn_payload(payload or {}, service_state, ledger)
|
| 69 |
+
return (200 if result["allowed"] else 422), result
|
| 70 |
+
if method == "POST" and path == "/v1/tool-check":
|
| 71 |
+
result = tool_check_payload(payload or {}, service_state, ledger)
|
| 72 |
+
return (200 if result["ok"] else 422), result
|
| 73 |
+
if method == "POST" and path == "/v1/policy-matrix":
|
| 74 |
+
return 200, policy_matrix_payload(payload or {}, service_state, ledger)
|
| 75 |
+
if method == "POST" and path == "/v1/capsule-summary":
|
| 76 |
+
capsule_payload = _mapping(payload, "capsule") or service_state["capsule"]
|
| 77 |
+
return 200, {"ok": True, "summary": build_capsule_summary(capsule_payload)}
|
| 78 |
+
return 404, {"ok": False, "error": "not_found"}
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def evaluate_payload(
|
| 82 |
+
payload: dict[str, Any],
|
| 83 |
+
state: dict[str, Any],
|
| 84 |
+
ledger: AuditLedger,
|
| 85 |
+
) -> dict[str, Any]:
|
| 86 |
+
capsule = DataCapsule.from_payload(_mapping(payload, "capsule") or state["capsule"])
|
| 87 |
+
request = SovereignRequest.from_payload(_mapping(payload, "request") or state["request"])
|
| 88 |
+
decision = SovereigntyPolicyEngine().evaluate(capsule, request)
|
| 89 |
+
audit = build_audit_record(capsule, request, decision).to_dict()
|
| 90 |
+
ledger_record = ledger.append(
|
| 91 |
+
"policy_evaluate",
|
| 92 |
+
{
|
| 93 |
+
"capsule_id": capsule.capsule_id,
|
| 94 |
+
"request": request.to_dict(),
|
| 95 |
+
"decision": decision.to_dict(),
|
| 96 |
+
"audit_record": audit,
|
| 97 |
+
},
|
| 98 |
+
)
|
| 99 |
+
return {
|
| 100 |
+
"ok": decision.allowed,
|
| 101 |
+
"decision": decision.to_dict(),
|
| 102 |
+
"audit_record": audit,
|
| 103 |
+
"ledger_record": ledger_record,
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def plan_turn_payload(
|
| 108 |
+
payload: dict[str, Any],
|
| 109 |
+
state: dict[str, Any],
|
| 110 |
+
ledger: AuditLedger,
|
| 111 |
+
) -> dict[str, Any]:
|
| 112 |
+
capsule = DataCapsule.from_payload(_mapping(payload, "capsule") or state["capsule"])
|
| 113 |
+
request = SovereignRequest.from_payload(_mapping(payload, "request") or state["request"])
|
| 114 |
+
route_request = RoutingRequest.from_payload(_mapping(payload, "route_request") or state["route_request"])
|
| 115 |
+
models = load_model_registry(Path(str(payload.get("registry_path") or state["registry_path"])))
|
| 116 |
+
plan = plan_coding_turn(capsule, request, route_request, models)
|
| 117 |
+
result = plan.to_dict()
|
| 118 |
+
result["turn_brief"] = build_turn_brief(result)
|
| 119 |
+
result["ledger_record"] = ledger.append(
|
| 120 |
+
"plan_turn",
|
| 121 |
+
{
|
| 122 |
+
"capsule_id": capsule.capsule_id,
|
| 123 |
+
"allowed": plan.allowed,
|
| 124 |
+
"policy_decision": result["policy_decision"],
|
| 125 |
+
"route_decision": result["route_decision"],
|
| 126 |
+
"audit_record": result["audit_record"],
|
| 127 |
+
"turn_brief": result["turn_brief"],
|
| 128 |
+
},
|
| 129 |
+
)
|
| 130 |
+
result["ok"] = plan.allowed
|
| 131 |
+
return result
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def tool_check_payload(
|
| 135 |
+
payload: dict[str, Any],
|
| 136 |
+
state: dict[str, Any],
|
| 137 |
+
ledger: AuditLedger,
|
| 138 |
+
) -> dict[str, Any]:
|
| 139 |
+
tool_payload = _mapping(payload, "tool") or {
|
| 140 |
+
"tool_name": payload.get("tool_name", "workspace_reader"),
|
| 141 |
+
"action": payload.get("action", "read_context"),
|
| 142 |
+
"writes_files": bool(payload.get("writes_files", False)),
|
| 143 |
+
"exports_data": bool(payload.get("exports_data", False)),
|
| 144 |
+
"trains_model": bool(payload.get("trains_model", False)),
|
| 145 |
+
"human_approved": bool(payload.get("human_approved", False)),
|
| 146 |
+
}
|
| 147 |
+
result = check_tool_request(
|
| 148 |
+
_mapping(payload, "capsule") or state["capsule"],
|
| 149 |
+
_mapping(payload, "request") or state["request"],
|
| 150 |
+
tool_payload,
|
| 151 |
+
)
|
| 152 |
+
result["ledger_record"] = ledger.append(
|
| 153 |
+
"tool_check",
|
| 154 |
+
{
|
| 155 |
+
"tool": tool_payload,
|
| 156 |
+
"decision": result["decision"],
|
| 157 |
+
"audit_record": result["audit_record"],
|
| 158 |
+
"operator_gate": result["operator_gate"],
|
| 159 |
+
},
|
| 160 |
+
)
|
| 161 |
+
return result
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def policy_matrix_payload(
|
| 165 |
+
payload: dict[str, Any],
|
| 166 |
+
state: dict[str, Any],
|
| 167 |
+
ledger: AuditLedger,
|
| 168 |
+
) -> dict[str, Any]:
|
| 169 |
+
scenarios = payload.get("scenarios") if isinstance(payload.get("scenarios"), list) else None
|
| 170 |
+
result = build_policy_matrix(
|
| 171 |
+
_mapping(payload, "capsule") or state["capsule"],
|
| 172 |
+
_mapping(payload, "request") or state["request"],
|
| 173 |
+
scenarios,
|
| 174 |
+
)
|
| 175 |
+
result["ledger_record"] = ledger.append(
|
| 176 |
+
"policy_matrix",
|
| 177 |
+
{
|
| 178 |
+
"capsule_id": result["capsule_id"],
|
| 179 |
+
"allowed_count": result["allowed_count"],
|
| 180 |
+
"blocked_count": result["blocked_count"],
|
| 181 |
+
},
|
| 182 |
+
)
|
| 183 |
+
return result
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def smoke_service(
|
| 187 |
+
*,
|
| 188 |
+
capsule_path: Path | None = None,
|
| 189 |
+
request_path: Path | None = None,
|
| 190 |
+
route_request_path: Path | None = None,
|
| 191 |
+
registry_path: Path | None = None,
|
| 192 |
+
ledger_path: Path | None = None,
|
| 193 |
+
) -> dict[str, Any]:
|
| 194 |
+
resolved_capsule = capsule_path or default_capsule_path()
|
| 195 |
+
resolved_request = request_path or default_request_path()
|
| 196 |
+
resolved_route_request = route_request_path or default_route_request_path()
|
| 197 |
+
resolved_registry = registry_path or default_registry_path()
|
| 198 |
+
resolved_ledger = ledger_path or _temporary_ledger_path()
|
| 199 |
+
state = build_dashboard_state(resolved_capsule, resolved_request, resolved_route_request, resolved_registry)
|
| 200 |
+
|
| 201 |
+
health_status, health = handle_service_request(
|
| 202 |
+
"GET",
|
| 203 |
+
"/health",
|
| 204 |
+
None,
|
| 205 |
+
capsule_path=resolved_capsule,
|
| 206 |
+
request_path=resolved_request,
|
| 207 |
+
route_request_path=resolved_route_request,
|
| 208 |
+
registry_path=resolved_registry,
|
| 209 |
+
ledger_path=resolved_ledger,
|
| 210 |
+
state=state,
|
| 211 |
+
)
|
| 212 |
+
evaluate_status, evaluate = handle_service_request(
|
| 213 |
+
"POST",
|
| 214 |
+
"/v1/evaluate",
|
| 215 |
+
{},
|
| 216 |
+
capsule_path=resolved_capsule,
|
| 217 |
+
request_path=resolved_request,
|
| 218 |
+
route_request_path=resolved_route_request,
|
| 219 |
+
registry_path=resolved_registry,
|
| 220 |
+
ledger_path=resolved_ledger,
|
| 221 |
+
state=state,
|
| 222 |
+
)
|
| 223 |
+
plan_status, plan = handle_service_request(
|
| 224 |
+
"POST",
|
| 225 |
+
"/v1/plan-turn",
|
| 226 |
+
{},
|
| 227 |
+
capsule_path=resolved_capsule,
|
| 228 |
+
request_path=resolved_request,
|
| 229 |
+
route_request_path=resolved_route_request,
|
| 230 |
+
registry_path=resolved_registry,
|
| 231 |
+
ledger_path=resolved_ledger,
|
| 232 |
+
state=state,
|
| 233 |
+
)
|
| 234 |
+
tool_status, tool = handle_service_request(
|
| 235 |
+
"POST",
|
| 236 |
+
"/v1/tool-check",
|
| 237 |
+
{"tool_name": "workspace_reader", "action": "read_context"},
|
| 238 |
+
capsule_path=resolved_capsule,
|
| 239 |
+
request_path=resolved_request,
|
| 240 |
+
route_request_path=resolved_route_request,
|
| 241 |
+
registry_path=resolved_registry,
|
| 242 |
+
ledger_path=resolved_ledger,
|
| 243 |
+
state=state,
|
| 244 |
+
)
|
| 245 |
+
audit_status, audit = handle_service_request(
|
| 246 |
+
"GET",
|
| 247 |
+
"/v1/audit",
|
| 248 |
+
None,
|
| 249 |
+
capsule_path=resolved_capsule,
|
| 250 |
+
request_path=resolved_request,
|
| 251 |
+
route_request_path=resolved_route_request,
|
| 252 |
+
registry_path=resolved_registry,
|
| 253 |
+
ledger_path=resolved_ledger,
|
| 254 |
+
state=state,
|
| 255 |
+
)
|
| 256 |
+
|
| 257 |
+
if (health_status, evaluate_status, plan_status, tool_status, audit_status) != (200, 200, 200, 200, 200):
|
| 258 |
+
raise RuntimeError("SovereignCode service smoke failed")
|
| 259 |
+
if not evaluate["decision"]["allowed"] or not plan["allowed"] or not tool["ok"]:
|
| 260 |
+
raise RuntimeError("SovereignCode service smoke did not allow the governed local request")
|
| 261 |
+
return {
|
| 262 |
+
"ok": True,
|
| 263 |
+
"product": PRODUCT_NAME,
|
| 264 |
+
"capsule_id": state["capsule"]["capsule_id"],
|
| 265 |
+
"selected_model": plan["turn_brief"]["selected_model"],
|
| 266 |
+
"ledger_path": str(resolved_ledger),
|
| 267 |
+
"ledger_records": len(audit["records"]),
|
| 268 |
+
"tool_next_gate": tool["operator_gate"]["next_gate"],
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
def serve_service(
|
| 273 |
+
*,
|
| 274 |
+
capsule_path: Path | None = None,
|
| 275 |
+
request_path: Path | None = None,
|
| 276 |
+
route_request_path: Path | None = None,
|
| 277 |
+
registry_path: Path | None = None,
|
| 278 |
+
ledger_path: Path | None = None,
|
| 279 |
+
host: str = "127.0.0.1",
|
| 280 |
+
port: int = 8788,
|
| 281 |
+
open_browser: bool = False,
|
| 282 |
+
smoke: bool = False,
|
| 283 |
+
) -> int:
|
| 284 |
+
resolved_capsule = capsule_path or default_capsule_path()
|
| 285 |
+
resolved_request = request_path or default_request_path()
|
| 286 |
+
resolved_route_request = route_request_path or default_route_request_path()
|
| 287 |
+
resolved_registry = registry_path or default_registry_path()
|
| 288 |
+
resolved_ledger = ledger_path or default_ledger_path()
|
| 289 |
+
if smoke:
|
| 290 |
+
print(
|
| 291 |
+
json.dumps(
|
| 292 |
+
smoke_service(
|
| 293 |
+
capsule_path=resolved_capsule,
|
| 294 |
+
request_path=resolved_request,
|
| 295 |
+
route_request_path=resolved_route_request,
|
| 296 |
+
registry_path=resolved_registry,
|
| 297 |
+
ledger_path=ledger_path,
|
| 298 |
+
),
|
| 299 |
+
indent=2,
|
| 300 |
+
sort_keys=True,
|
| 301 |
+
),
|
| 302 |
+
)
|
| 303 |
+
return 0
|
| 304 |
+
|
| 305 |
+
state = build_dashboard_state(resolved_capsule, resolved_request, resolved_route_request, resolved_registry)
|
| 306 |
+
html = build_dashboard_html(state)
|
| 307 |
+
return serve_dashboard(
|
| 308 |
+
product_name=f"{PRODUCT_NAME} Service",
|
| 309 |
+
html=html,
|
| 310 |
+
api_handler=lambda method, path, request_payload: handle_service_request(
|
| 311 |
+
method,
|
| 312 |
+
path,
|
| 313 |
+
request_payload,
|
| 314 |
+
capsule_path=resolved_capsule,
|
| 315 |
+
request_path=resolved_request,
|
| 316 |
+
route_request_path=resolved_route_request,
|
| 317 |
+
registry_path=resolved_registry,
|
| 318 |
+
ledger_path=resolved_ledger,
|
| 319 |
+
state=state,
|
| 320 |
+
),
|
| 321 |
+
host=host,
|
| 322 |
+
port=port,
|
| 323 |
+
open_browser=open_browser,
|
| 324 |
+
api_path_prefixes=("/api/", "/v1/"),
|
| 325 |
+
api_exact_paths=("/health",),
|
| 326 |
+
)
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
def _health_payload(ledger: AuditLedger, state: dict[str, Any]) -> dict[str, Any]:
|
| 330 |
+
return {
|
| 331 |
+
"ok": True,
|
| 332 |
+
"product": PRODUCT_NAME,
|
| 333 |
+
"capsule_id": state["capsule"]["capsule_id"],
|
| 334 |
+
"ledger_path": str(ledger.path),
|
| 335 |
+
"ledger_records": len(ledger.tail()),
|
| 336 |
+
"service": "policy_api_audit_ledger",
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
|
| 340 |
+
def _mapping(payload: dict[str, Any] | None, key: str) -> dict[str, Any]:
|
| 341 |
+
if not isinstance(payload, dict):
|
| 342 |
+
return {}
|
| 343 |
+
value = payload.get(key)
|
| 344 |
+
return dict(value) if isinstance(value, dict) else {}
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
def _temporary_ledger_path() -> Path:
|
| 348 |
+
handle, raw_path = tempfile.mkstemp(prefix="sovereigncode-smoke-", suffix=".audit.jsonl")
|
| 349 |
+
os.close(handle)
|
| 350 |
+
path = Path(raw_path)
|
| 351 |
+
path.unlink(missing_ok=True)
|
| 352 |
+
return path
|