Publish expanded LumynaX product platform package
Browse files- README.md +33 -1
- SMOKE_TESTS.md +2 -0
- marama_route/__init__.py +12 -0
- marama_route/cli.py +102 -0
- marama_route/platform.py +359 -0
- marama_route/ui.py +379 -28
- product_manifest.json +9 -0
- quickstart.py +18 -0
- sovereigncode/__init__.py +12 -0
- sovereigncode/cli.py +88 -1
- sovereigncode/platform.py +275 -0
- sovereigncode/ui.py +375 -4
README.md
CHANGED
|
@@ -37,6 +37,9 @@ python quickstart.py
|
|
| 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 ui --smoke
|
| 41 |
python -m sovereigncode.cli ui --port 8788 --open
|
| 42 |
```
|
|
@@ -97,6 +100,30 @@ py -3 -m tinyluminax.products.sovereigncode.cli plan-turn \
|
|
| 97 |
--registry products/lumynax-marama-route/configs/lumynax_model_registry.json
|
| 98 |
```
|
| 99 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
Denied training example:
|
| 101 |
|
| 102 |
```bash
|
|
@@ -128,6 +155,8 @@ py -3 -m tinyluminax.products.sovereigncode.cli ui --smoke
|
|
| 128 |
| LumynaX Runtime Adapter | Routes model calls to local GGUF, local API, or approved LumynaX model endpoints. |
|
| 129 |
| Audit Ledger | Stores decision records, prompt/output hashes, file diffs, and approval metadata. |
|
| 130 |
| Operator Console | Shows the plan, policy decision, diff, tests, and approval gate before external effects. |
|
|
|
|
|
|
|
| 131 |
|
| 132 |
## New Zealand Launch Shape
|
| 133 |
|
|
@@ -151,6 +180,9 @@ py -3 -m tinyluminax.products.sovereigncode.cli ui --smoke
|
|
| 151 |
| Personal profile capsule | `examples/capsule.personal-sovereignty-profile.json` |
|
| 152 |
| Personal-memory request | `examples/request.personal-memory-read.json` |
|
| 153 |
| Browser operator console | `python -m tinyluminax.products.sovereigncode.cli ui` |
|
|
|
|
|
|
|
|
|
|
| 154 |
| Product blueprint | `product_blueprint.md` |
|
| 155 |
|
| 156 |
## Source Grounding
|
|
@@ -159,7 +191,7 @@ The sovereignty model is inspired by the Data Capsule pattern described in the S
|
|
| 159 |
|
| 160 |
## Stage
|
| 161 |
|
| 162 |
-
This is an executable platform scaffold, not the final commercial application. The policy engine, router integration, CLI package, and browser operator console are working now; the tool broker, terminal loop, and full autonomous agent loop are the next implementation stage.
|
| 163 |
|
| 164 |
|
| 165 |
# AbteeX SovereignCode Product Blueprint
|
|
|
|
| 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
|
| 41 |
+
python -m sovereigncode.cli tool-check --capsule examples/capsule.restricted-nz-code.json --request examples/request.allowed-local-edit.json --tool-name workspace_reader --action read_context
|
| 42 |
+
python -m sovereigncode.cli opencode-config
|
| 43 |
python -m sovereigncode.cli ui --smoke
|
| 44 |
python -m sovereigncode.cli ui --port 8788 --open
|
| 45 |
```
|
|
|
|
| 100 |
--registry products/lumynax-marama-route/configs/lumynax_model_registry.json
|
| 101 |
```
|
| 102 |
|
| 103 |
+
Run the policy/tool matrix:
|
| 104 |
+
|
| 105 |
+
```bash
|
| 106 |
+
py -3 -m tinyluminax.products.sovereigncode.cli policy-matrix \
|
| 107 |
+
--capsule products/abx-sovereigncode/examples/capsule.restricted-nz-code.json \
|
| 108 |
+
--request products/abx-sovereigncode/examples/request.allowed-local-edit.json
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
Check one tool before execution:
|
| 112 |
+
|
| 113 |
+
```bash
|
| 114 |
+
py -3 -m tinyluminax.products.sovereigncode.cli tool-check \
|
| 115 |
+
--capsule products/abx-sovereigncode/examples/capsule.restricted-nz-code.json \
|
| 116 |
+
--request products/abx-sovereigncode/examples/request.allowed-local-edit.json \
|
| 117 |
+
--tool-name workspace_reader \
|
| 118 |
+
--action read_context
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
Emit an OpenCode-compatible workspace config:
|
| 122 |
+
|
| 123 |
+
```bash
|
| 124 |
+
py -3 -m tinyluminax.products.sovereigncode.cli opencode-config
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
Denied training example:
|
| 128 |
|
| 129 |
```bash
|
|
|
|
| 155 |
| LumynaX Runtime Adapter | Routes model calls to local GGUF, local API, or approved LumynaX model endpoints. |
|
| 156 |
| Audit Ledger | Stores decision records, prompt/output hashes, file diffs, and approval metadata. |
|
| 157 |
| Operator Console | Shows the plan, policy decision, diff, tests, and approval gate before external effects. |
|
| 158 |
+
| Policy Matrix | Evaluates common tool/action scenarios against the same Data Capsule. |
|
| 159 |
+
| Provider Exporter | Emits OpenCode-compatible workspace config pointing through MaramaRoute. |
|
| 160 |
|
| 161 |
## New Zealand Launch Shape
|
| 162 |
|
|
|
|
| 180 |
| Personal profile capsule | `examples/capsule.personal-sovereignty-profile.json` |
|
| 181 |
| Personal-memory request | `examples/request.personal-memory-read.json` |
|
| 182 |
| Browser operator console | `python -m tinyluminax.products.sovereigncode.cli ui` |
|
| 183 |
+
| Policy/tool matrix | `python -m tinyluminax.products.sovereigncode.cli policy-matrix` |
|
| 184 |
+
| Tool gate check | `python -m tinyluminax.products.sovereigncode.cli tool-check` |
|
| 185 |
+
| OpenCode workspace export | `python -m tinyluminax.products.sovereigncode.cli opencode-config` |
|
| 186 |
| Product blueprint | `product_blueprint.md` |
|
| 187 |
|
| 188 |
## Source Grounding
|
|
|
|
| 191 |
|
| 192 |
## Stage
|
| 193 |
|
| 194 |
+
This is an executable platform scaffold, not the final commercial application. The policy engine, router integration, CLI package, policy matrix, tool gate checks, capsule summaries, OpenCode config export, operator checklist, and browser operator console are working now; the live tool broker, terminal loop, and full autonomous agent loop are the next implementation stage.
|
| 195 |
|
| 196 |
|
| 197 |
# AbteeX SovereignCode Product Blueprint
|
SMOKE_TESTS.md
CHANGED
|
@@ -6,6 +6,8 @@ Run from the package root:
|
|
| 6 |
pip install -e .
|
| 7 |
python quickstart.py
|
| 8 |
python -m sovereigncode.cli ui --smoke
|
|
|
|
|
|
|
| 9 |
python -m sovereigncode.cli evaluate --capsule examples/capsule.personal-sovereignty-profile.json --request examples/request.personal-memory-read.json
|
| 10 |
```
|
| 11 |
|
|
|
|
| 6 |
pip install -e .
|
| 7 |
python quickstart.py
|
| 8 |
python -m sovereigncode.cli ui --smoke
|
| 9 |
+
python -m sovereigncode.cli policy-matrix --capsule examples/capsule.restricted-nz-code.json --request examples/request.allowed-local-edit.json
|
| 10 |
+
python -m sovereigncode.cli tool-check --capsule examples/capsule.restricted-nz-code.json --request examples/request.allowed-local-edit.json --tool-name workspace_reader --action read_context
|
| 11 |
python -m sovereigncode.cli evaluate --capsule examples/capsule.personal-sovereignty-profile.json --request examples/request.personal-memory-read.json
|
| 12 |
```
|
| 13 |
|
marama_route/__init__.py
CHANGED
|
@@ -6,6 +6,13 @@ from .gateway import (
|
|
| 6 |
route_chat_payload,
|
| 7 |
routing_request_from_chat_payload,
|
| 8 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
from .registry import ModelEndpoint, RoutingRequest, load_model_registry
|
| 10 |
from .router import RouteDecision, SovereignModelRouter
|
| 11 |
from .ui import smoke_ui as smoke_ui
|
|
@@ -17,8 +24,13 @@ __all__ = [
|
|
| 17 |
"SovereignModelRouter",
|
| 18 |
"build_chat_route_response",
|
| 19 |
"build_models_response",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
"load_model_registry",
|
| 21 |
"route_chat_payload",
|
|
|
|
| 22 |
"routing_request_from_chat_payload",
|
| 23 |
"smoke_ui",
|
| 24 |
]
|
|
|
|
| 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 .ui import smoke_ui as smoke_ui
|
|
|
|
| 24 |
"SovereignModelRouter",
|
| 25 |
"build_chat_route_response",
|
| 26 |
"build_models_response",
|
| 27 |
+
"build_opencode_provider_config",
|
| 28 |
+
"build_registry_analytics",
|
| 29 |
+
"catalog_models",
|
| 30 |
+
"compare_models",
|
| 31 |
"load_model_registry",
|
| 32 |
"route_chat_payload",
|
| 33 |
+
"route_scenario_matrix",
|
| 34 |
"routing_request_from_chat_payload",
|
| 35 |
"smoke_ui",
|
| 36 |
]
|
marama_route/cli.py
CHANGED
|
@@ -7,6 +7,13 @@ from pathlib import Path
|
|
| 7 |
from typing import Any
|
| 8 |
|
| 9 |
from .gateway import build_models_response, route_chat_payload
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
from .registry import RoutingRequest, load_model_registry
|
| 11 |
from .router import SovereignModelRouter
|
| 12 |
|
|
@@ -41,6 +48,55 @@ def _chat_dry_run(args: argparse.Namespace) -> int:
|
|
| 41 |
return 0 if selected is not None else 2
|
| 42 |
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
def _ui(args: argparse.Namespace) -> int:
|
| 45 |
from .ui import run_ui
|
| 46 |
|
|
@@ -79,6 +135,52 @@ def build_parser() -> argparse.ArgumentParser:
|
|
| 79 |
chat.add_argument("--request", type=Path, required=True, help="OpenAI chat request JSON.")
|
| 80 |
chat.set_defaults(handler=_chat_dry_run)
|
| 81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
ui = subparsers.add_parser(
|
| 83 |
"ui",
|
| 84 |
help="Launch the local MaramaRoute browser platform.",
|
|
|
|
| 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 |
|
|
|
|
| 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 |
|
|
|
|
| 135 |
chat.add_argument("--request", type=Path, required=True, help="OpenAI chat request JSON.")
|
| 136 |
chat.set_defaults(handler=_chat_dry_run)
|
| 137 |
|
| 138 |
+
catalog = subparsers.add_parser(
|
| 139 |
+
"catalog",
|
| 140 |
+
help="Search and filter the MaramaRoute model catalog.",
|
| 141 |
+
)
|
| 142 |
+
catalog.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 143 |
+
catalog.add_argument("--search", default="")
|
| 144 |
+
catalog.add_argument("--task", default="")
|
| 145 |
+
catalog.add_argument("--runtime", default="")
|
| 146 |
+
catalog.add_argument("--modality", default="")
|
| 147 |
+
catalog.add_argument("--jurisdiction", default="NZ")
|
| 148 |
+
catalog.add_argument("--min-context-tokens", type=int, default=0)
|
| 149 |
+
catalog.add_argument("--requires-json", action=argparse.BooleanOptionalAction, default=False)
|
| 150 |
+
catalog.add_argument("--requires-tools", action=argparse.BooleanOptionalAction, default=False)
|
| 151 |
+
catalog.add_argument("--requires-local", action=argparse.BooleanOptionalAction, default=False)
|
| 152 |
+
catalog.add_argument("--limit", type=int, default=25)
|
| 153 |
+
catalog.set_defaults(handler=_catalog)
|
| 154 |
+
|
| 155 |
+
compare = subparsers.add_parser(
|
| 156 |
+
"compare",
|
| 157 |
+
help="Compare routed fit for selected model ids.",
|
| 158 |
+
)
|
| 159 |
+
compare.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 160 |
+
compare.add_argument("--model", action="append", required=True, help="Model id, repeatable or comma-separated.")
|
| 161 |
+
compare.add_argument("--request", type=Path, default=None, help="Optional routing request JSON.")
|
| 162 |
+
compare.set_defaults(handler=_compare)
|
| 163 |
+
|
| 164 |
+
matrix = subparsers.add_parser(
|
| 165 |
+
"matrix",
|
| 166 |
+
help="Run the built-in sovereign routing scenario matrix.",
|
| 167 |
+
)
|
| 168 |
+
matrix.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 169 |
+
matrix.add_argument("--allow-blocked-exit-zero", action=argparse.BooleanOptionalAction, default=False)
|
| 170 |
+
matrix.set_defaults(handler=_matrix)
|
| 171 |
+
|
| 172 |
+
analytics = subparsers.add_parser("analytics", help="Summarise registry coverage.")
|
| 173 |
+
analytics.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 174 |
+
analytics.set_defaults(handler=_analytics)
|
| 175 |
+
|
| 176 |
+
opencode = subparsers.add_parser(
|
| 177 |
+
"opencode-config",
|
| 178 |
+
help="Emit an OpenCode-compatible MaramaRoute provider config.",
|
| 179 |
+
)
|
| 180 |
+
opencode.add_argument("--registry", type=Path, required=True, help="MaramaRoute model registry JSON.")
|
| 181 |
+
opencode.add_argument("--base-url", default="http://127.0.0.1:8787/v1")
|
| 182 |
+
opencode.set_defaults(handler=_opencode_config)
|
| 183 |
+
|
| 184 |
ui = subparsers.add_parser(
|
| 185 |
"ui",
|
| 186 |
help="Launch the local MaramaRoute browser platform.",
|
marama_route/platform.py
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import hashlib
|
| 4 |
+
import json
|
| 5 |
+
from collections import Counter
|
| 6 |
+
from typing import Any
|
| 7 |
+
|
| 8 |
+
from .gateway import build_models_response, route_chat_payload
|
| 9 |
+
from .registry import ModelEndpoint, RoutingRequest
|
| 10 |
+
from .router import SovereignModelRouter
|
| 11 |
+
|
| 12 |
+
DEFAULT_ROUTE_SCENARIOS: tuple[dict[str, Any], ...] = (
|
| 13 |
+
{
|
| 14 |
+
"name": "Restricted NZ code",
|
| 15 |
+
"prompt": "Refactor a private New Zealand Python service and return a JSON diff plan.",
|
| 16 |
+
"task_type": "code",
|
| 17 |
+
"modalities": ["text"],
|
| 18 |
+
"jurisdiction": "NZ",
|
| 19 |
+
"data_sensitivity": "restricted",
|
| 20 |
+
"min_context_tokens": 4096,
|
| 21 |
+
"requires_local": True,
|
| 22 |
+
"requires_json": True,
|
| 23 |
+
"requires_tools": False,
|
| 24 |
+
"max_fallbacks": 3,
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"name": "Personal memory",
|
| 28 |
+
"prompt": "Summarise local operator preferences without exposing raw personal notes.",
|
| 29 |
+
"task_type": "general",
|
| 30 |
+
"modalities": ["text"],
|
| 31 |
+
"jurisdiction": "NZ",
|
| 32 |
+
"data_sensitivity": "personal",
|
| 33 |
+
"min_context_tokens": 4096,
|
| 34 |
+
"requires_local": True,
|
| 35 |
+
"requires_json": False,
|
| 36 |
+
"requires_tools": False,
|
| 37 |
+
"max_fallbacks": 3,
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"name": "Vision document",
|
| 41 |
+
"prompt": "Read a scanned table image and extract structured rows.",
|
| 42 |
+
"task_type": "multimodal",
|
| 43 |
+
"modalities": ["text", "image"],
|
| 44 |
+
"jurisdiction": "NZ",
|
| 45 |
+
"data_sensitivity": "internal",
|
| 46 |
+
"min_context_tokens": 4096,
|
| 47 |
+
"requires_local": False,
|
| 48 |
+
"requires_json": True,
|
| 49 |
+
"requires_tools": False,
|
| 50 |
+
"max_fallbacks": 3,
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"name": "Reasoning brief",
|
| 54 |
+
"prompt": "Reason through a procurement risk register and produce a concise decision memo.",
|
| 55 |
+
"task_type": "reasoning",
|
| 56 |
+
"modalities": ["text"],
|
| 57 |
+
"jurisdiction": "NZ",
|
| 58 |
+
"data_sensitivity": "internal",
|
| 59 |
+
"min_context_tokens": 8192,
|
| 60 |
+
"requires_local": True,
|
| 61 |
+
"requires_json": False,
|
| 62 |
+
"requires_tools": False,
|
| 63 |
+
"max_fallbacks": 3,
|
| 64 |
+
},
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def build_registry_analytics(models: tuple[ModelEndpoint, ...]) -> dict[str, Any]:
|
| 69 |
+
runtimes = Counter(model.runtime for model in models)
|
| 70 |
+
families = Counter(model.family for model in models)
|
| 71 |
+
modalities = Counter(modality for model in models for modality in model.modalities)
|
| 72 |
+
tiers = Counter(str(model.sovereignty_tier) for model in models)
|
| 73 |
+
resident_nz = sum(1 for model in models if "NZ" in model.residency)
|
| 74 |
+
json_ready = sum(1 for model in models if model.supports_json)
|
| 75 |
+
tool_ready = sum(1 for model in models if model.supports_tools)
|
| 76 |
+
local_runtimes = sum(1 for model in models if _is_local_runtime(model.runtime))
|
| 77 |
+
context_values = [model.context_tokens for model in models]
|
| 78 |
+
return {
|
| 79 |
+
"model_count": len(models),
|
| 80 |
+
"resident_nz": resident_nz,
|
| 81 |
+
"local_runtimes": local_runtimes,
|
| 82 |
+
"json_ready": json_ready,
|
| 83 |
+
"tool_ready": tool_ready,
|
| 84 |
+
"max_context_tokens": max(context_values) if context_values else 0,
|
| 85 |
+
"avg_context_tokens": round(sum(context_values) / len(context_values), 2) if context_values else 0,
|
| 86 |
+
"runtimes": dict(sorted(runtimes.items())),
|
| 87 |
+
"families": dict(sorted(families.items())),
|
| 88 |
+
"modalities": dict(sorted(modalities.items())),
|
| 89 |
+
"sovereignty_tiers": dict(sorted(tiers.items())),
|
| 90 |
+
"top_models": [model_summary(model) for model in _top_models(models, limit=8)],
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def catalog_models(
|
| 95 |
+
models: tuple[ModelEndpoint, ...],
|
| 96 |
+
filters: dict[str, Any] | None = None,
|
| 97 |
+
) -> dict[str, Any]:
|
| 98 |
+
filters = filters or {}
|
| 99 |
+
search = str(filters.get("search") or "").strip().lower()
|
| 100 |
+
runtime = str(filters.get("runtime") or "").strip().lower()
|
| 101 |
+
family = str(filters.get("family") or "").strip().lower()
|
| 102 |
+
modality = str(filters.get("modality") or "").strip().lower()
|
| 103 |
+
task_type = str(filters.get("task_type") or "").strip().lower()
|
| 104 |
+
jurisdiction = str(filters.get("jurisdiction") or "").strip().upper()
|
| 105 |
+
min_context = int(filters.get("min_context_tokens") or 0)
|
| 106 |
+
limit = int(filters.get("limit") or 50)
|
| 107 |
+
requires_json = bool(filters.get("requires_json", False))
|
| 108 |
+
requires_tools = bool(filters.get("requires_tools", False))
|
| 109 |
+
requires_local = bool(filters.get("requires_local", False))
|
| 110 |
+
|
| 111 |
+
filtered: list[ModelEndpoint] = []
|
| 112 |
+
for model in models:
|
| 113 |
+
haystack = " ".join(
|
| 114 |
+
(
|
| 115 |
+
model.model_id,
|
| 116 |
+
model.repo_id,
|
| 117 |
+
model.family,
|
| 118 |
+
model.runtime,
|
| 119 |
+
" ".join(model.tags),
|
| 120 |
+
),
|
| 121 |
+
).lower()
|
| 122 |
+
if search and search not in haystack:
|
| 123 |
+
continue
|
| 124 |
+
if runtime and model.runtime.lower() != runtime:
|
| 125 |
+
continue
|
| 126 |
+
if family and model.family.lower() != family:
|
| 127 |
+
continue
|
| 128 |
+
if modality and modality not in {item.lower() for item in model.modalities}:
|
| 129 |
+
continue
|
| 130 |
+
if task_type and not _matches_task(model, task_type):
|
| 131 |
+
continue
|
| 132 |
+
if jurisdiction and jurisdiction not in model.residency:
|
| 133 |
+
continue
|
| 134 |
+
if min_context and model.context_tokens < min_context:
|
| 135 |
+
continue
|
| 136 |
+
if requires_json and not model.supports_json:
|
| 137 |
+
continue
|
| 138 |
+
if requires_tools and not model.supports_tools:
|
| 139 |
+
continue
|
| 140 |
+
if requires_local and not _is_local_runtime(model.runtime):
|
| 141 |
+
continue
|
| 142 |
+
filtered.append(model)
|
| 143 |
+
|
| 144 |
+
ranked = sorted(filtered, key=_catalog_sort_key, reverse=True)
|
| 145 |
+
return {
|
| 146 |
+
"ok": True,
|
| 147 |
+
"count": len(ranked),
|
| 148 |
+
"filters": filters,
|
| 149 |
+
"models": [model_summary(model) for model in ranked[:limit]],
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def compare_models(
|
| 154 |
+
models: tuple[ModelEndpoint, ...],
|
| 155 |
+
model_ids: list[str],
|
| 156 |
+
request_payload: dict[str, Any] | None = None,
|
| 157 |
+
) -> dict[str, Any]:
|
| 158 |
+
index = {model.model_id: model for model in models}
|
| 159 |
+
selected = [index[model_id] for model_id in model_ids if model_id in index]
|
| 160 |
+
missing = [model_id for model_id in model_ids if model_id not in index]
|
| 161 |
+
request = RoutingRequest.from_payload(request_payload or DEFAULT_ROUTE_SCENARIOS[0])
|
| 162 |
+
route_scores = SovereignModelRouter(tuple(selected)).route(request).scores if selected else {}
|
| 163 |
+
rows = []
|
| 164 |
+
for model in selected:
|
| 165 |
+
row = model_summary(model)
|
| 166 |
+
row["route_score"] = route_scores.get(model.model_id)
|
| 167 |
+
row["operator_score"] = _operator_score(model)
|
| 168 |
+
rows.append(row)
|
| 169 |
+
winner = max(rows, key=lambda item: (item.get("route_score") or -1, item["operator_score"]), default=None)
|
| 170 |
+
return {
|
| 171 |
+
"ok": bool(rows),
|
| 172 |
+
"missing": missing,
|
| 173 |
+
"request": request.to_dict(),
|
| 174 |
+
"winner": winner,
|
| 175 |
+
"models": rows,
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def route_scenario_matrix(
|
| 180 |
+
models: tuple[ModelEndpoint, ...],
|
| 181 |
+
scenarios: list[dict[str, Any]] | None = None,
|
| 182 |
+
) -> dict[str, Any]:
|
| 183 |
+
router = SovereignModelRouter(models)
|
| 184 |
+
rows = []
|
| 185 |
+
for scenario in scenarios or [dict(item) for item in DEFAULT_ROUTE_SCENARIOS]:
|
| 186 |
+
request = RoutingRequest.from_payload(scenario)
|
| 187 |
+
decision = router.route(request)
|
| 188 |
+
selected = decision.selected_model
|
| 189 |
+
rows.append(
|
| 190 |
+
{
|
| 191 |
+
"name": scenario.get("name", request.task_type),
|
| 192 |
+
"ok": selected is not None,
|
| 193 |
+
"task_type": request.task_type,
|
| 194 |
+
"sensitivity": request.data_sensitivity,
|
| 195 |
+
"selected_model": selected.model_id if selected else None,
|
| 196 |
+
"runtime": selected.runtime if selected else None,
|
| 197 |
+
"fallback_count": len(decision.fallback_models),
|
| 198 |
+
"rejected_count": len(decision.rejected),
|
| 199 |
+
"reasons": list(decision.reasons),
|
| 200 |
+
},
|
| 201 |
+
)
|
| 202 |
+
return {"ok": all(row["ok"] for row in rows), "scenarios": rows}
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def build_opencode_provider_config(
|
| 206 |
+
models: tuple[ModelEndpoint, ...],
|
| 207 |
+
*,
|
| 208 |
+
base_url: str = "http://127.0.0.1:8787/v1",
|
| 209 |
+
provider_id: str = "abteex-marama",
|
| 210 |
+
) -> dict[str, Any]:
|
| 211 |
+
route = SovereignModelRouter(models).route(RoutingRequest.from_payload(DEFAULT_ROUTE_SCENARIOS[0]))
|
| 212 |
+
default_model = route.selected_model or (_top_models(models, limit=1)[0] if models else None)
|
| 213 |
+
catalog = _top_models(models, limit=14)
|
| 214 |
+
model_entries = {
|
| 215 |
+
model.model_id: {
|
| 216 |
+
"name": model.model_id,
|
| 217 |
+
"context": model.context_tokens,
|
| 218 |
+
"modalities": list(model.modalities),
|
| 219 |
+
"residency": list(model.residency),
|
| 220 |
+
"runtime": model.runtime,
|
| 221 |
+
}
|
| 222 |
+
for model in catalog
|
| 223 |
+
}
|
| 224 |
+
return {
|
| 225 |
+
"$schema": "https://opencode.ai/config.json",
|
| 226 |
+
"provider": {
|
| 227 |
+
provider_id: {
|
| 228 |
+
"name": "AbteeX MaramaRoute",
|
| 229 |
+
"npm": "@ai-sdk/openai-compatible",
|
| 230 |
+
"options": {
|
| 231 |
+
"baseURL": base_url,
|
| 232 |
+
"apiKey": "${ABTEEX_MARAMA_API_KEY:-local-dev}",
|
| 233 |
+
},
|
| 234 |
+
"models": model_entries,
|
| 235 |
+
},
|
| 236 |
+
},
|
| 237 |
+
"model": f"{provider_id}/{default_model.model_id}" if default_model else "",
|
| 238 |
+
"small_model": f"{provider_id}/{catalog[-1].model_id}" if catalog else "",
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
def route_receipt(payload: dict[str, Any], route_result: dict[str, Any]) -> dict[str, Any]:
|
| 243 |
+
selected = route_result.get("route_decision", {}).get("selected_model")
|
| 244 |
+
receipt_payload = {
|
| 245 |
+
"request": payload,
|
| 246 |
+
"selected_model_id": selected.get("model_id") if isinstance(selected, dict) else None,
|
| 247 |
+
"rejected_count": len(route_result.get("route_decision", {}).get("rejected", [])),
|
| 248 |
+
}
|
| 249 |
+
digest = hashlib.sha256(
|
| 250 |
+
json.dumps(receipt_payload, sort_keys=True, default=str).encode("utf-8"),
|
| 251 |
+
).hexdigest()
|
| 252 |
+
return {
|
| 253 |
+
"receipt_id": f"marama-{digest[:16]}",
|
| 254 |
+
"request_hash": digest,
|
| 255 |
+
"selected_model": receipt_payload["selected_model_id"],
|
| 256 |
+
"prompt_retention": "not_stored_by_default",
|
| 257 |
+
"audit_fields": [
|
| 258 |
+
"request_hash",
|
| 259 |
+
"selected_model",
|
| 260 |
+
"fallback_models",
|
| 261 |
+
"rejected_count",
|
| 262 |
+
"residency",
|
| 263 |
+
"runtime",
|
| 264 |
+
],
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def route_or_chat_payload(payload: dict[str, Any], models: tuple[ModelEndpoint, ...]) -> dict[str, Any]:
|
| 269 |
+
if "messages" in payload:
|
| 270 |
+
result = route_chat_payload(payload, models)
|
| 271 |
+
selected = result["route_decision"]["selected_model"]
|
| 272 |
+
result = {"ok": selected is not None, "mode": "openai_chat_dry_run", **result}
|
| 273 |
+
else:
|
| 274 |
+
request = RoutingRequest.from_payload(payload)
|
| 275 |
+
decision = SovereignModelRouter(models).route(request)
|
| 276 |
+
result = {
|
| 277 |
+
"ok": decision.selected_model is not None,
|
| 278 |
+
"mode": "route",
|
| 279 |
+
"routing_request": request.to_dict(),
|
| 280 |
+
"route_decision": decision.to_dict(),
|
| 281 |
+
}
|
| 282 |
+
result["receipt"] = route_receipt(payload, result)
|
| 283 |
+
return result
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
def build_models_api(models: tuple[ModelEndpoint, ...]) -> dict[str, Any]:
|
| 287 |
+
response = build_models_response(models)
|
| 288 |
+
response["analytics"] = build_registry_analytics(models)
|
| 289 |
+
return response
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
def model_summary(model: ModelEndpoint) -> dict[str, Any]:
|
| 293 |
+
return {
|
| 294 |
+
"model_id": model.model_id,
|
| 295 |
+
"repo_id": model.repo_id,
|
| 296 |
+
"family": model.family,
|
| 297 |
+
"runtime": model.runtime,
|
| 298 |
+
"modalities": list(model.modalities),
|
| 299 |
+
"context_tokens": model.context_tokens,
|
| 300 |
+
"residency": list(model.residency),
|
| 301 |
+
"license_id": model.license_id,
|
| 302 |
+
"active_params_b": model.active_params_b,
|
| 303 |
+
"total_params_b": model.total_params_b,
|
| 304 |
+
"quality_rank": model.quality_rank,
|
| 305 |
+
"cost_rank": model.cost_rank,
|
| 306 |
+
"sovereignty_tier": model.sovereignty_tier,
|
| 307 |
+
"supports_json": model.supports_json,
|
| 308 |
+
"supports_tools": model.supports_tools,
|
| 309 |
+
"tags": list(model.tags),
|
| 310 |
+
"operator_score": _operator_score(model),
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
def scenario_presets() -> list[dict[str, Any]]:
|
| 315 |
+
return [dict(item) for item in DEFAULT_ROUTE_SCENARIOS]
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
def _top_models(models: tuple[ModelEndpoint, ...], *, limit: int) -> list[ModelEndpoint]:
|
| 319 |
+
return sorted(models, key=_catalog_sort_key, reverse=True)[:limit]
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
def _catalog_sort_key(model: ModelEndpoint) -> tuple[float, int, str]:
|
| 323 |
+
return (_operator_score(model), model.context_tokens, model.model_id)
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
def _operator_score(model: ModelEndpoint) -> float:
|
| 327 |
+
score = 0.0
|
| 328 |
+
if "NZ" in model.residency:
|
| 329 |
+
score += 25
|
| 330 |
+
if _is_local_runtime(model.runtime):
|
| 331 |
+
score += 15
|
| 332 |
+
score += model.sovereignty_tier * 10
|
| 333 |
+
score += max(0, 10 - model.quality_rank) * 3
|
| 334 |
+
score -= model.cost_rank
|
| 335 |
+
if model.supports_json:
|
| 336 |
+
score += 5
|
| 337 |
+
if model.supports_tools:
|
| 338 |
+
score += 5
|
| 339 |
+
if model.context_tokens >= 32768:
|
| 340 |
+
score += 6
|
| 341 |
+
elif model.context_tokens >= 8192:
|
| 342 |
+
score += 3
|
| 343 |
+
return round(score, 2)
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
def _matches_task(model: ModelEndpoint, task_type: str) -> bool:
|
| 347 |
+
tags = set(model.tags)
|
| 348 |
+
if task_type in tags or task_type in model.family.lower() or task_type in model.model_id.lower():
|
| 349 |
+
return True
|
| 350 |
+
if task_type == "code":
|
| 351 |
+
return "coder" in tags or "coder" in model.model_id.lower()
|
| 352 |
+
if task_type == "multimodal":
|
| 353 |
+
return "image" in model.modalities or "multimodal" in tags
|
| 354 |
+
return False
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
def _is_local_runtime(runtime: str) -> bool:
|
| 358 |
+
value = runtime.lower()
|
| 359 |
+
return value in {"llama_cpp", "gguf", "transformers", "sentence_transformers"} or "local" in value
|
marama_route/ui.py
CHANGED
|
@@ -6,9 +6,17 @@ from typing import Any
|
|
| 6 |
|
| 7 |
from ._ui_server import serve_dashboard
|
| 8 |
|
| 9 |
-
from .
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
PRODUCT_NAME = "LumynaX MaramaRoute"
|
| 14 |
PACKAGE_ROOT = Path(__file__).resolve().parent
|
|
@@ -49,24 +57,7 @@ def load_json_mapping(path: Path) -> dict[str, Any]:
|
|
| 49 |
|
| 50 |
|
| 51 |
def route_dashboard_payload(payload: dict[str, Any], registry_path: Path) -> dict[str, Any]:
|
| 52 |
-
|
| 53 |
-
if "messages" in payload:
|
| 54 |
-
result = route_chat_payload(payload, models)
|
| 55 |
-
selected = result["route_decision"]["selected_model"]
|
| 56 |
-
return {
|
| 57 |
-
"ok": selected is not None,
|
| 58 |
-
"mode": "openai_chat_dry_run",
|
| 59 |
-
**result,
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
request = RoutingRequest.from_payload(payload)
|
| 63 |
-
decision = SovereignModelRouter(models).route(request)
|
| 64 |
-
return {
|
| 65 |
-
"ok": decision.selected_model is not None,
|
| 66 |
-
"mode": "route",
|
| 67 |
-
"routing_request": request.to_dict(),
|
| 68 |
-
"route_decision": decision.to_dict(),
|
| 69 |
-
}
|
| 70 |
|
| 71 |
|
| 72 |
def build_dashboard_state(registry_path: Path) -> dict[str, Any]:
|
|
@@ -76,15 +67,19 @@ def build_dashboard_state(registry_path: Path) -> dict[str, Any]:
|
|
| 76 |
if default_openai_chat_request_path().exists()
|
| 77 |
else _fallback_chat_example()
|
| 78 |
)
|
| 79 |
-
|
| 80 |
-
resident_nz = sum(1 for model in models if "NZ" in model.residency)
|
| 81 |
return {
|
| 82 |
"registry_path": str(registry_path),
|
| 83 |
"model_count": len(models),
|
| 84 |
-
"resident_nz": resident_nz,
|
| 85 |
-
"runtimes": runtimes,
|
| 86 |
"example": example,
|
| 87 |
-
"models":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
}
|
| 89 |
|
| 90 |
|
|
@@ -98,7 +93,9 @@ def handle_api_request(
|
|
| 98 |
state = build_dashboard_state(registry_path)
|
| 99 |
return 200, {"ok": True, "product": PRODUCT_NAME, "model_count": state["model_count"]}
|
| 100 |
if method == "GET" and path == "/api/models":
|
| 101 |
-
return 200,
|
|
|
|
|
|
|
| 102 |
if method == "GET" and path == "/api/state":
|
| 103 |
return 200, {"ok": True, **build_dashboard_state(registry_path)}
|
| 104 |
if method == "POST" and path == "/api/route" and payload is not None:
|
|
@@ -107,6 +104,26 @@ def handle_api_request(
|
|
| 107 |
if not result["ok"]:
|
| 108 |
status = 422
|
| 109 |
return status, result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
return 404, {"ok": False, "error": "not_found"}
|
| 111 |
|
| 112 |
|
|
@@ -116,11 +133,17 @@ def smoke_ui(registry_path: Path | None = None) -> dict[str, Any]:
|
|
| 116 |
routed = route_dashboard_payload(state["example"], resolved_registry)
|
| 117 |
if not routed["ok"]:
|
| 118 |
raise RuntimeError("MaramaRoute UI smoke route did not select a model")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
return {
|
| 120 |
"ok": True,
|
| 121 |
"product": PRODUCT_NAME,
|
| 122 |
"model_count": state["model_count"],
|
| 123 |
"selected_model": routed["route_decision"]["selected_model"]["model_id"],
|
|
|
|
|
|
|
| 124 |
}
|
| 125 |
|
| 126 |
|
|
@@ -136,7 +159,7 @@ def run_ui(
|
|
| 136 |
if smoke:
|
| 137 |
print(json.dumps(smoke_ui(resolved_registry), indent=2, sort_keys=True))
|
| 138 |
return 0
|
| 139 |
-
html =
|
| 140 |
return serve_dashboard(
|
| 141 |
product_name=PRODUCT_NAME,
|
| 142 |
html=html,
|
|
@@ -152,6 +175,334 @@ def run_ui(
|
|
| 152 |
)
|
| 153 |
|
| 154 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
def build_dashboard_html(state: dict[str, Any]) -> str:
|
| 156 |
initial = json.dumps(state, sort_keys=True).replace("</", "<\\/")
|
| 157 |
return f"""<!doctype html>
|
|
|
|
| 6 |
|
| 7 |
from ._ui_server import serve_dashboard
|
| 8 |
|
| 9 |
+
from .platform import (
|
| 10 |
+
build_models_api,
|
| 11 |
+
build_opencode_provider_config,
|
| 12 |
+
build_registry_analytics,
|
| 13 |
+
catalog_models,
|
| 14 |
+
compare_models,
|
| 15 |
+
route_or_chat_payload,
|
| 16 |
+
route_scenario_matrix,
|
| 17 |
+
scenario_presets,
|
| 18 |
+
)
|
| 19 |
+
from .registry import load_model_registry
|
| 20 |
|
| 21 |
PRODUCT_NAME = "LumynaX MaramaRoute"
|
| 22 |
PACKAGE_ROOT = Path(__file__).resolve().parent
|
|
|
|
| 57 |
|
| 58 |
|
| 59 |
def route_dashboard_payload(payload: dict[str, Any], registry_path: Path) -> dict[str, Any]:
|
| 60 |
+
return route_or_chat_payload(payload, load_model_registry(registry_path))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
|
| 63 |
def build_dashboard_state(registry_path: Path) -> dict[str, Any]:
|
|
|
|
| 67 |
if default_openai_chat_request_path().exists()
|
| 68 |
else _fallback_chat_example()
|
| 69 |
)
|
| 70 |
+
analytics = build_registry_analytics(models)
|
|
|
|
| 71 |
return {
|
| 72 |
"registry_path": str(registry_path),
|
| 73 |
"model_count": len(models),
|
| 74 |
+
"resident_nz": analytics["resident_nz"],
|
| 75 |
+
"runtimes": sorted(analytics["runtimes"]),
|
| 76 |
"example": example,
|
| 77 |
+
"models": build_models_api(models),
|
| 78 |
+
"analytics": analytics,
|
| 79 |
+
"catalog": catalog_models(models, {"limit": 14})["models"],
|
| 80 |
+
"scenarios": scenario_presets(),
|
| 81 |
+
"matrix": route_scenario_matrix(models),
|
| 82 |
+
"opencode_config": build_opencode_provider_config(models),
|
| 83 |
}
|
| 84 |
|
| 85 |
|
|
|
|
| 93 |
state = build_dashboard_state(registry_path)
|
| 94 |
return 200, {"ok": True, "product": PRODUCT_NAME, "model_count": state["model_count"]}
|
| 95 |
if method == "GET" and path == "/api/models":
|
| 96 |
+
return 200, build_models_api(load_model_registry(registry_path))
|
| 97 |
+
if method == "GET" and path == "/api/analytics":
|
| 98 |
+
return 200, {"ok": True, **build_registry_analytics(load_model_registry(registry_path))}
|
| 99 |
if method == "GET" and path == "/api/state":
|
| 100 |
return 200, {"ok": True, **build_dashboard_state(registry_path)}
|
| 101 |
if method == "POST" and path == "/api/route" and payload is not None:
|
|
|
|
| 104 |
if not result["ok"]:
|
| 105 |
status = 422
|
| 106 |
return status, result
|
| 107 |
+
if method == "POST" and path == "/api/catalog" and payload is not None:
|
| 108 |
+
return 200, catalog_models(load_model_registry(registry_path), payload)
|
| 109 |
+
if method == "POST" and path == "/api/compare" and payload is not None:
|
| 110 |
+
model_ids = payload.get("model_ids") or payload.get("models") or []
|
| 111 |
+
if isinstance(model_ids, str):
|
| 112 |
+
model_ids = [item.strip() for item in model_ids.split(",") if item.strip()]
|
| 113 |
+
request_payload = payload.get("request") if isinstance(payload.get("request"), dict) else None
|
| 114 |
+
result = compare_models(load_model_registry(registry_path), list(model_ids), request_payload)
|
| 115 |
+
return (200 if result["ok"] else 422), result
|
| 116 |
+
if method == "POST" and path == "/api/matrix" and payload is not None:
|
| 117 |
+
scenarios = payload.get("scenarios") if isinstance(payload.get("scenarios"), list) else None
|
| 118 |
+
return 200, route_scenario_matrix(load_model_registry(registry_path), scenarios)
|
| 119 |
+
if method == "POST" and path == "/api/opencode-config" and payload is not None:
|
| 120 |
+
return 200, {
|
| 121 |
+
"ok": True,
|
| 122 |
+
"config": build_opencode_provider_config(
|
| 123 |
+
load_model_registry(registry_path),
|
| 124 |
+
base_url=str(payload.get("base_url") or "http://127.0.0.1:8787/v1"),
|
| 125 |
+
),
|
| 126 |
+
}
|
| 127 |
return 404, {"ok": False, "error": "not_found"}
|
| 128 |
|
| 129 |
|
|
|
|
| 133 |
routed = route_dashboard_payload(state["example"], resolved_registry)
|
| 134 |
if not routed["ok"]:
|
| 135 |
raise RuntimeError("MaramaRoute UI smoke route did not select a model")
|
| 136 |
+
catalog = catalog_models(load_model_registry(resolved_registry), {"task_type": "code", "limit": 3})
|
| 137 |
+
matrix = route_scenario_matrix(load_model_registry(resolved_registry))
|
| 138 |
+
if not catalog["models"] or not matrix["ok"]:
|
| 139 |
+
raise RuntimeError("MaramaRoute expanded UI smoke checks failed")
|
| 140 |
return {
|
| 141 |
"ok": True,
|
| 142 |
"product": PRODUCT_NAME,
|
| 143 |
"model_count": state["model_count"],
|
| 144 |
"selected_model": routed["route_decision"]["selected_model"]["model_id"],
|
| 145 |
+
"catalog_count": catalog["count"],
|
| 146 |
+
"scenario_count": len(matrix["scenarios"]),
|
| 147 |
}
|
| 148 |
|
| 149 |
|
|
|
|
| 159 |
if smoke:
|
| 160 |
print(json.dumps(smoke_ui(resolved_registry), indent=2, sort_keys=True))
|
| 161 |
return 0
|
| 162 |
+
html = build_expanded_dashboard_html(build_dashboard_state(resolved_registry))
|
| 163 |
return serve_dashboard(
|
| 164 |
product_name=PRODUCT_NAME,
|
| 165 |
html=html,
|
|
|
|
| 175 |
)
|
| 176 |
|
| 177 |
|
| 178 |
+
def build_expanded_dashboard_html(state: dict[str, Any]) -> str:
|
| 179 |
+
initial = json.dumps(state, sort_keys=True).replace("</", "<\\/")
|
| 180 |
+
html = """<!doctype html>
|
| 181 |
+
<html lang="en">
|
| 182 |
+
<head>
|
| 183 |
+
<meta charset="utf-8" />
|
| 184 |
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 185 |
+
<title>LumynaX MaramaRoute Console</title>
|
| 186 |
+
<style>
|
| 187 |
+
:root {
|
| 188 |
+
--paper: #fffefa;
|
| 189 |
+
--ink: #0a0a0b;
|
| 190 |
+
--muted: #726b62;
|
| 191 |
+
--rule: #ded6c8;
|
| 192 |
+
--amber: #e08a2c;
|
| 193 |
+
--amber-dark: #9a5416;
|
| 194 |
+
--green: #275f45;
|
| 195 |
+
--red: #8f2e24;
|
| 196 |
+
--panel: #f7f1e7;
|
| 197 |
+
--chalk: #fff7e8;
|
| 198 |
+
--mono: "Cascadia Code", "SFMono-Regular", Menlo, Consolas, monospace;
|
| 199 |
+
--serif: Georgia, "Iowan Old Style", "Palatino Linotype", serif;
|
| 200 |
+
}
|
| 201 |
+
* { box-sizing: border-box; }
|
| 202 |
+
body {
|
| 203 |
+
margin: 0;
|
| 204 |
+
background:
|
| 205 |
+
linear-gradient(90deg, rgba(10,10,11,.035) 1px, transparent 1px) 0 0 / 44px 44px,
|
| 206 |
+
linear-gradient(0deg, rgba(10,10,11,.025) 1px, transparent 1px) 0 0 / 44px 44px,
|
| 207 |
+
var(--paper);
|
| 208 |
+
color: var(--ink);
|
| 209 |
+
font-family: var(--serif);
|
| 210 |
+
}
|
| 211 |
+
header {
|
| 212 |
+
display: grid;
|
| 213 |
+
grid-template-columns: minmax(260px, 1fr) minmax(320px, 48vw);
|
| 214 |
+
gap: 24px;
|
| 215 |
+
align-items: end;
|
| 216 |
+
padding: 22px 28px 16px;
|
| 217 |
+
border-bottom: 1px solid var(--rule);
|
| 218 |
+
background: rgba(255,254,250,.92);
|
| 219 |
+
}
|
| 220 |
+
h1 {
|
| 221 |
+
margin: 0;
|
| 222 |
+
font-size: clamp(34px, 5vw, 68px);
|
| 223 |
+
line-height: .88;
|
| 224 |
+
letter-spacing: 0;
|
| 225 |
+
}
|
| 226 |
+
.subtitle, label, button, input, select, .metric, .pill, th, td {
|
| 227 |
+
font-family: var(--mono);
|
| 228 |
+
font-size: 12px;
|
| 229 |
+
letter-spacing: 0;
|
| 230 |
+
}
|
| 231 |
+
.subtitle { margin-top: 10px; color: var(--muted); }
|
| 232 |
+
.metrics { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 8px; }
|
| 233 |
+
.metric { border-left: 2px solid var(--amber); min-height: 60px; padding: 7px 8px; background: rgba(247,241,231,.72); }
|
| 234 |
+
.metric strong { display: block; font: 700 22px/1 var(--serif); color: var(--ink); overflow-wrap: anywhere; }
|
| 235 |
+
main { display: grid; grid-template-columns: minmax(340px, 440px) 1fr; min-height: calc(100vh - 118px); }
|
| 236 |
+
aside { border-right: 1px solid var(--rule); background: rgba(255,250,242,.95); padding: 18px; }
|
| 237 |
+
section { min-width: 0; }
|
| 238 |
+
.stack { display: grid; gap: 12px; }
|
| 239 |
+
.grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
|
| 240 |
+
.wide { grid-column: 1 / -1; }
|
| 241 |
+
.panel, textarea, input, select, pre, table { border: 1px solid var(--rule); border-radius: 6px; background: var(--paper); color: var(--ink); }
|
| 242 |
+
.panel { padding: 14px; }
|
| 243 |
+
.panel h2 { margin: 0 0 10px; font-size: 18px; line-height: 1.1; }
|
| 244 |
+
textarea {
|
| 245 |
+
width: 100%;
|
| 246 |
+
min-height: 270px;
|
| 247 |
+
resize: vertical;
|
| 248 |
+
padding: 12px;
|
| 249 |
+
font: 12px/1.45 var(--mono);
|
| 250 |
+
}
|
| 251 |
+
input, select { width: 100%; min-height: 34px; padding: 0 10px; }
|
| 252 |
+
button {
|
| 253 |
+
appearance: none;
|
| 254 |
+
border: 1px solid var(--ink);
|
| 255 |
+
background: var(--ink);
|
| 256 |
+
color: var(--paper);
|
| 257 |
+
border-radius: 4px;
|
| 258 |
+
min-height: 34px;
|
| 259 |
+
padding: 0 12px;
|
| 260 |
+
cursor: pointer;
|
| 261 |
+
white-space: nowrap;
|
| 262 |
+
}
|
| 263 |
+
button.secondary { background: transparent; color: var(--ink); border-color: var(--rule); }
|
| 264 |
+
.toolbar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
| 265 |
+
.content { padding: 18px; display: grid; gap: 12px; align-content: start; }
|
| 266 |
+
.status { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
|
| 267 |
+
.status .panel { min-height: 92px; }
|
| 268 |
+
.k { font: 12px/1.25 var(--mono); color: var(--muted); display: block; }
|
| 269 |
+
.v { display: block; margin-top: 8px; font-size: 18px; line-height: 1.12; overflow-wrap: anywhere; }
|
| 270 |
+
.ok { color: var(--green); }
|
| 271 |
+
.bad { color: var(--red); }
|
| 272 |
+
pre { margin: 0; padding: 14px; max-height: 520px; overflow: auto; font: 12px/1.45 var(--mono); }
|
| 273 |
+
table { width: 100%; border-collapse: collapse; overflow: hidden; }
|
| 274 |
+
th, td { padding: 9px 10px; text-align: left; border-bottom: 1px solid var(--rule); vertical-align: top; }
|
| 275 |
+
th { color: var(--muted); background: var(--panel); }
|
| 276 |
+
tr:last-child td { border-bottom: 0; }
|
| 277 |
+
.pills { display: flex; flex-wrap: wrap; gap: 6px; }
|
| 278 |
+
.pill { display: inline-flex; border: 1px solid var(--rule); border-radius: 999px; min-height: 24px; align-items: center; padding: 0 9px; background: var(--chalk); }
|
| 279 |
+
.split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 12px; }
|
| 280 |
+
@media (max-width: 1120px) {
|
| 281 |
+
header, main, .split { grid-template-columns: 1fr; }
|
| 282 |
+
aside { border-right: 0; border-bottom: 1px solid var(--rule); }
|
| 283 |
+
.metrics, .status { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
| 284 |
+
}
|
| 285 |
+
@media (max-width: 680px) {
|
| 286 |
+
header, aside, .content { padding: 14px; }
|
| 287 |
+
.grid, .metrics, .status { grid-template-columns: 1fr; }
|
| 288 |
+
}
|
| 289 |
+
</style>
|
| 290 |
+
</head>
|
| 291 |
+
<body>
|
| 292 |
+
<header>
|
| 293 |
+
<div>
|
| 294 |
+
<h1>LumynaX<br />MaramaRoute</h1>
|
| 295 |
+
<div class="subtitle">Sovereign router control surface for LumynaX model selection.</div>
|
| 296 |
+
</div>
|
| 297 |
+
<div class="metrics">
|
| 298 |
+
<div class="metric"><strong id="metricModels">0</strong>models</div>
|
| 299 |
+
<div class="metric"><strong id="metricNz">0</strong>NZ resident</div>
|
| 300 |
+
<div class="metric"><strong id="metricLocal">0</strong>local runtime</div>
|
| 301 |
+
<div class="metric"><strong id="metricJson">0</strong>JSON ready</div>
|
| 302 |
+
<div class="metric"><strong id="metricContext">0</strong>max ctx</div>
|
| 303 |
+
</div>
|
| 304 |
+
</header>
|
| 305 |
+
<main>
|
| 306 |
+
<aside class="stack">
|
| 307 |
+
<div class="toolbar">
|
| 308 |
+
<button id="routeBtn">Route</button>
|
| 309 |
+
<button class="secondary" id="matrixBtn">Matrix</button>
|
| 310 |
+
<button class="secondary" id="providerBtn">Provider</button>
|
| 311 |
+
</div>
|
| 312 |
+
<div>
|
| 313 |
+
<label for="preset">PRESET</label>
|
| 314 |
+
<select id="preset"></select>
|
| 315 |
+
</div>
|
| 316 |
+
<div>
|
| 317 |
+
<label for="request">REQUEST JSON</label>
|
| 318 |
+
<textarea id="request" spellcheck="false"></textarea>
|
| 319 |
+
</div>
|
| 320 |
+
<div class="panel stack">
|
| 321 |
+
<h2>Catalog</h2>
|
| 322 |
+
<div class="grid">
|
| 323 |
+
<div>
|
| 324 |
+
<label for="search">SEARCH</label>
|
| 325 |
+
<input id="search" value="qwen" />
|
| 326 |
+
</div>
|
| 327 |
+
<div>
|
| 328 |
+
<label for="task">TASK</label>
|
| 329 |
+
<select id="task">
|
| 330 |
+
<option value="">any</option>
|
| 331 |
+
<option value="code">code</option>
|
| 332 |
+
<option value="reasoning">reasoning</option>
|
| 333 |
+
<option value="multimodal">multimodal</option>
|
| 334 |
+
<option value="general">general</option>
|
| 335 |
+
</select>
|
| 336 |
+
</div>
|
| 337 |
+
<div>
|
| 338 |
+
<label for="runtime">RUNTIME</label>
|
| 339 |
+
<select id="runtime"></select>
|
| 340 |
+
</div>
|
| 341 |
+
<div>
|
| 342 |
+
<label for="context">MIN CONTEXT</label>
|
| 343 |
+
<input id="context" type="number" min="0" value="4096" />
|
| 344 |
+
</div>
|
| 345 |
+
</div>
|
| 346 |
+
<div class="toolbar">
|
| 347 |
+
<button class="secondary" id="catalogBtn">Filter</button>
|
| 348 |
+
<button class="secondary" id="compareBtn">Compare Top</button>
|
| 349 |
+
</div>
|
| 350 |
+
</div>
|
| 351 |
+
</aside>
|
| 352 |
+
<section class="content">
|
| 353 |
+
<div class="status">
|
| 354 |
+
<div class="panel"><span class="k">SELECTED</span><span class="v" id="selected">pending</span></div>
|
| 355 |
+
<div class="panel"><span class="k">MODE</span><span class="v" id="mode">ready</span></div>
|
| 356 |
+
<div class="panel"><span class="k">FALLBACKS</span><span class="v" id="fallbacks">0</span></div>
|
| 357 |
+
<div class="panel"><span class="k">RECEIPT</span><span class="v" id="receipt">none</span></div>
|
| 358 |
+
</div>
|
| 359 |
+
<div class="split">
|
| 360 |
+
<div class="panel">
|
| 361 |
+
<h2>Route Result</h2>
|
| 362 |
+
<pre id="output"></pre>
|
| 363 |
+
</div>
|
| 364 |
+
<div class="panel">
|
| 365 |
+
<h2>Scenario Matrix</h2>
|
| 366 |
+
<table>
|
| 367 |
+
<thead><tr><th>Scenario</th><th>Model</th><th>Rejected</th></tr></thead>
|
| 368 |
+
<tbody id="matrixRows"></tbody>
|
| 369 |
+
</table>
|
| 370 |
+
</div>
|
| 371 |
+
</div>
|
| 372 |
+
<div class="panel">
|
| 373 |
+
<h2>Model Catalog</h2>
|
| 374 |
+
<table>
|
| 375 |
+
<thead><tr><th>Model</th><th>Runtime</th><th>Fit</th><th>Caps</th></tr></thead>
|
| 376 |
+
<tbody id="catalogRows"></tbody>
|
| 377 |
+
</table>
|
| 378 |
+
</div>
|
| 379 |
+
<div class="panel">
|
| 380 |
+
<h2>Provider Config</h2>
|
| 381 |
+
<pre id="provider"></pre>
|
| 382 |
+
</div>
|
| 383 |
+
</section>
|
| 384 |
+
</main>
|
| 385 |
+
<script type="application/json" id="initial-state">__INITIAL__</script>
|
| 386 |
+
<script>
|
| 387 |
+
const state = JSON.parse(document.getElementById('initial-state').textContent);
|
| 388 |
+
const request = document.getElementById('request');
|
| 389 |
+
const preset = document.getElementById('preset');
|
| 390 |
+
const output = document.getElementById('output');
|
| 391 |
+
const provider = document.getElementById('provider');
|
| 392 |
+
const selected = document.getElementById('selected');
|
| 393 |
+
const mode = document.getElementById('mode');
|
| 394 |
+
const fallbacks = document.getElementById('fallbacks');
|
| 395 |
+
const receipt = document.getElementById('receipt');
|
| 396 |
+
const catalogRows = document.getElementById('catalogRows');
|
| 397 |
+
const matrixRows = document.getElementById('matrixRows');
|
| 398 |
+
const runtime = document.getElementById('runtime');
|
| 399 |
+
|
| 400 |
+
function writeJson(node, value) {
|
| 401 |
+
node.textContent = JSON.stringify(value, null, 2);
|
| 402 |
+
}
|
| 403 |
+
function safeJson(text) {
|
| 404 |
+
try { return [JSON.parse(text), null]; }
|
| 405 |
+
catch (error) { return [null, error.message]; }
|
| 406 |
+
}
|
| 407 |
+
function setMetric(id, value) { document.getElementById(id).textContent = value; }
|
| 408 |
+
setMetric('metricModels', state.analytics.model_count);
|
| 409 |
+
setMetric('metricNz', state.analytics.resident_nz);
|
| 410 |
+
setMetric('metricLocal', state.analytics.local_runtimes);
|
| 411 |
+
setMetric('metricJson', state.analytics.json_ready);
|
| 412 |
+
setMetric('metricContext', state.analytics.max_context_tokens);
|
| 413 |
+
|
| 414 |
+
runtime.innerHTML = '<option value="">any</option>' + Object.keys(state.analytics.runtimes).map(item => `<option value="${item}">${item}</option>`).join('');
|
| 415 |
+
preset.innerHTML = state.scenarios.map((item, index) => `<option value="${index}">${item.name}</option>`).join('');
|
| 416 |
+
preset.addEventListener('change', () => {
|
| 417 |
+
request.value = JSON.stringify(state.scenarios[Number(preset.value)], null, 2);
|
| 418 |
+
});
|
| 419 |
+
request.value = JSON.stringify(state.example, null, 2);
|
| 420 |
+
writeJson(output, {status: 'ready', registry: state.registry_path});
|
| 421 |
+
writeJson(provider, state.opencode_config);
|
| 422 |
+
|
| 423 |
+
function renderCatalog(rows) {
|
| 424 |
+
catalogRows.innerHTML = rows.map(row => {
|
| 425 |
+
const caps = [
|
| 426 |
+
row.residency.includes('NZ') ? 'NZ' : '',
|
| 427 |
+
row.supports_json ? 'JSON' : '',
|
| 428 |
+
row.supports_tools ? 'TOOLS' : '',
|
| 429 |
+
`${row.context_tokens} ctx`
|
| 430 |
+
].filter(Boolean).map(item => `<span class="pill">${item}</span>`).join(' ');
|
| 431 |
+
return `<tr><td>${row.model_id}<br><span class="k">${row.repo_id}</span></td><td>${row.runtime}</td><td>${row.operator_score}</td><td><div class="pills">${caps}</div></td></tr>`;
|
| 432 |
+
}).join('');
|
| 433 |
+
}
|
| 434 |
+
function renderMatrix(rows) {
|
| 435 |
+
matrixRows.innerHTML = rows.map(row => `<tr><td>${row.name}<br><span class="k">${row.sensitivity}</span></td><td class="${row.ok ? 'ok' : 'bad'}">${row.selected_model || 'none'}</td><td>${row.rejected_count}</td></tr>`).join('');
|
| 436 |
+
}
|
| 437 |
+
function setResult(data) {
|
| 438 |
+
writeJson(output, data);
|
| 439 |
+
const decision = data.route_decision || {};
|
| 440 |
+
const model = decision.selected_model || {};
|
| 441 |
+
selected.textContent = model.model_id || 'none';
|
| 442 |
+
selected.className = model.model_id ? 'v ok' : 'v bad';
|
| 443 |
+
mode.textContent = data.mode || 'result';
|
| 444 |
+
fallbacks.textContent = decision.fallback_models ? decision.fallback_models.length : 0;
|
| 445 |
+
receipt.textContent = data.receipt ? data.receipt.receipt_id : 'none';
|
| 446 |
+
}
|
| 447 |
+
async function postJson(path, payload) {
|
| 448 |
+
const response = await fetch(path, {
|
| 449 |
+
method: 'POST',
|
| 450 |
+
headers: {'Content-Type': 'application/json'},
|
| 451 |
+
body: JSON.stringify(payload)
|
| 452 |
+
});
|
| 453 |
+
return response.json();
|
| 454 |
+
}
|
| 455 |
+
async function route() {
|
| 456 |
+
const [payload, error] = safeJson(request.value);
|
| 457 |
+
if (error) { setResult({ok: false, error}); return; }
|
| 458 |
+
setResult(await postJson('/api/route', payload));
|
| 459 |
+
}
|
| 460 |
+
async function loadCatalog() {
|
| 461 |
+
const filters = {
|
| 462 |
+
search: document.getElementById('search').value,
|
| 463 |
+
task_type: document.getElementById('task').value,
|
| 464 |
+
runtime: runtime.value,
|
| 465 |
+
min_context_tokens: Number(document.getElementById('context').value || 0),
|
| 466 |
+
jurisdiction: 'NZ',
|
| 467 |
+
limit: 18
|
| 468 |
+
};
|
| 469 |
+
const data = await postJson('/api/catalog', filters);
|
| 470 |
+
renderCatalog(data.models);
|
| 471 |
+
writeJson(output, data);
|
| 472 |
+
}
|
| 473 |
+
async function compareTop() {
|
| 474 |
+
const ids = Array.from(catalogRows.querySelectorAll('tr')).slice(0, 4).map(row => row.cells[0].childNodes[0].textContent);
|
| 475 |
+
const data = await postJson('/api/compare', {model_ids: ids});
|
| 476 |
+
writeJson(output, data);
|
| 477 |
+
if (data.winner) {
|
| 478 |
+
selected.textContent = data.winner.model_id;
|
| 479 |
+
selected.className = 'v ok';
|
| 480 |
+
mode.textContent = 'compare';
|
| 481 |
+
}
|
| 482 |
+
}
|
| 483 |
+
async function matrix() {
|
| 484 |
+
const data = await postJson('/api/matrix', {scenarios: state.scenarios});
|
| 485 |
+
renderMatrix(data.scenarios);
|
| 486 |
+
writeJson(output, data);
|
| 487 |
+
}
|
| 488 |
+
async function providerConfig() {
|
| 489 |
+
const data = await postJson('/api/opencode-config', {base_url: 'http://127.0.0.1:8787/v1'});
|
| 490 |
+
writeJson(provider, data.config);
|
| 491 |
+
writeJson(output, data);
|
| 492 |
+
}
|
| 493 |
+
document.getElementById('routeBtn').addEventListener('click', route);
|
| 494 |
+
document.getElementById('catalogBtn').addEventListener('click', loadCatalog);
|
| 495 |
+
document.getElementById('compareBtn').addEventListener('click', compareTop);
|
| 496 |
+
document.getElementById('matrixBtn').addEventListener('click', matrix);
|
| 497 |
+
document.getElementById('providerBtn').addEventListener('click', providerConfig);
|
| 498 |
+
renderCatalog(state.catalog);
|
| 499 |
+
renderMatrix(state.matrix.scenarios);
|
| 500 |
+
</script>
|
| 501 |
+
</body>
|
| 502 |
+
</html>"""
|
| 503 |
+
return html.replace("__INITIAL__", initial)
|
| 504 |
+
|
| 505 |
+
|
| 506 |
def build_dashboard_html(state: dict[str, Any]) -> str:
|
| 507 |
initial = json.dumps(state, sort_keys=True).replace("</", "<\\/")
|
| 508 |
return f"""<!doctype html>
|
product_manifest.json
CHANGED
|
@@ -21,11 +21,18 @@
|
|
| 21 |
"lumynax_runtime_adapter",
|
| 22 |
"audit_ledger",
|
| 23 |
"human_review_gate",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
"opencode_compatible_provider"
|
| 25 |
],
|
| 26 |
"runtime_entrypoints": [
|
| 27 |
"python -m tinyluminax.products.sovereigncode.cli evaluate",
|
| 28 |
"python -m tinyluminax.products.sovereigncode.cli plan-turn",
|
|
|
|
|
|
|
|
|
|
| 29 |
"python -m tinyluminax.products.sovereigncode.cli ui"
|
| 30 |
],
|
| 31 |
"integration_surfaces": [
|
|
@@ -34,6 +41,8 @@
|
|
| 34 |
"OpenCode custom provider config",
|
| 35 |
"local CLI policy evaluator",
|
| 36 |
"governed coding-turn planner",
|
|
|
|
|
|
|
| 37 |
"browser operator console"
|
| 38 |
],
|
| 39 |
"brand_system": {
|
|
|
|
| 21 |
"lumynax_runtime_adapter",
|
| 22 |
"audit_ledger",
|
| 23 |
"human_review_gate",
|
| 24 |
+
"policy_matrix",
|
| 25 |
+
"tool_gate_check",
|
| 26 |
+
"capsule_summary",
|
| 27 |
+
"opencode_workspace_export",
|
| 28 |
"opencode_compatible_provider"
|
| 29 |
],
|
| 30 |
"runtime_entrypoints": [
|
| 31 |
"python -m tinyluminax.products.sovereigncode.cli evaluate",
|
| 32 |
"python -m tinyluminax.products.sovereigncode.cli plan-turn",
|
| 33 |
+
"python -m tinyluminax.products.sovereigncode.cli policy-matrix",
|
| 34 |
+
"python -m tinyluminax.products.sovereigncode.cli tool-check",
|
| 35 |
+
"python -m tinyluminax.products.sovereigncode.cli opencode-config",
|
| 36 |
"python -m tinyluminax.products.sovereigncode.cli ui"
|
| 37 |
],
|
| 38 |
"integration_surfaces": [
|
|
|
|
| 41 |
"OpenCode custom provider config",
|
| 42 |
"local CLI policy evaluator",
|
| 43 |
"governed coding-turn planner",
|
| 44 |
+
"policy matrix and tool gate checker",
|
| 45 |
+
"browser operator checklist",
|
| 46 |
"browser operator console"
|
| 47 |
],
|
| 48 |
"brand_system": {
|
quickstart.py
CHANGED
|
@@ -28,6 +28,24 @@ if __name__ == "__main__":
|
|
| 28 |
"ui",
|
| 29 |
"--smoke",
|
| 30 |
],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
[
|
| 32 |
"plan-turn",
|
| 33 |
"--capsule",
|
|
|
|
| 28 |
"ui",
|
| 29 |
"--smoke",
|
| 30 |
],
|
| 31 |
+
[
|
| 32 |
+
"policy-matrix",
|
| 33 |
+
"--capsule",
|
| 34 |
+
str(ROOT / "examples" / "capsule.restricted-nz-code.json"),
|
| 35 |
+
"--request",
|
| 36 |
+
str(ROOT / "examples" / "request.allowed-local-edit.json"),
|
| 37 |
+
],
|
| 38 |
+
[
|
| 39 |
+
"tool-check",
|
| 40 |
+
"--capsule",
|
| 41 |
+
str(ROOT / "examples" / "capsule.restricted-nz-code.json"),
|
| 42 |
+
"--request",
|
| 43 |
+
str(ROOT / "examples" / "request.allowed-local-edit.json"),
|
| 44 |
+
"--tool-name",
|
| 45 |
+
"workspace_reader",
|
| 46 |
+
"--action",
|
| 47 |
+
"read_context",
|
| 48 |
+
],
|
| 49 |
[
|
| 50 |
"plan-turn",
|
| 51 |
"--capsule",
|
sovereigncode/__init__.py
CHANGED
|
@@ -2,6 +2,13 @@ from __future__ import annotations
|
|
| 2 |
|
| 3 |
from .audit import AuditRecord, build_audit_record
|
| 4 |
from .planner import SovereignCodingTurnPlan, ToolGrant, plan_coding_turn
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
from .policy import (
|
| 6 |
DataCapsule,
|
| 7 |
PolicyDecision,
|
|
@@ -19,6 +26,11 @@ __all__ = [
|
|
| 19 |
"SovereigntyPolicyEngine",
|
| 20 |
"ToolGrant",
|
| 21 |
"build_audit_record",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
"plan_coding_turn",
|
| 23 |
"smoke_ui",
|
| 24 |
]
|
|
|
|
| 2 |
|
| 3 |
from .audit import AuditRecord, build_audit_record
|
| 4 |
from .planner import SovereignCodingTurnPlan, ToolGrant, plan_coding_turn
|
| 5 |
+
from .platform import (
|
| 6 |
+
build_capsule_summary,
|
| 7 |
+
build_opencode_workspace_config,
|
| 8 |
+
build_policy_matrix,
|
| 9 |
+
build_turn_brief,
|
| 10 |
+
check_tool_request,
|
| 11 |
+
)
|
| 12 |
from .policy import (
|
| 13 |
DataCapsule,
|
| 14 |
PolicyDecision,
|
|
|
|
| 26 |
"SovereigntyPolicyEngine",
|
| 27 |
"ToolGrant",
|
| 28 |
"build_audit_record",
|
| 29 |
+
"build_capsule_summary",
|
| 30 |
+
"build_opencode_workspace_config",
|
| 31 |
+
"build_policy_matrix",
|
| 32 |
+
"build_turn_brief",
|
| 33 |
+
"check_tool_request",
|
| 34 |
"plan_coding_turn",
|
| 35 |
"smoke_ui",
|
| 36 |
]
|
sovereigncode/cli.py
CHANGED
|
@@ -12,6 +12,13 @@ from marama_route import RoutingRequest, load_model_registry
|
|
| 12 |
|
| 13 |
from .audit import build_audit_record
|
| 14 |
from .planner import plan_coding_turn
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
from .policy import DataCapsule, SovereignRequest, SovereigntyPolicyEngine
|
| 16 |
|
| 17 |
|
|
@@ -43,10 +50,52 @@ def _plan_turn(args: argparse.Namespace) -> int:
|
|
| 43 |
routing_request = RoutingRequest.from_payload(_load_mapping(args.route_request))
|
| 44 |
models = load_model_registry(args.registry)
|
| 45 |
plan = plan_coding_turn(capsule, request, routing_request, models)
|
| 46 |
-
|
|
|
|
|
|
|
| 47 |
return 0 if plan.allowed or args.allow_blocked_exit_zero else 2
|
| 48 |
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
def _ui(args: argparse.Namespace) -> int:
|
| 51 |
from .ui import run_ui
|
| 52 |
|
|
@@ -106,6 +155,44 @@ def build_parser() -> argparse.ArgumentParser:
|
|
| 106 |
)
|
| 107 |
plan_turn.set_defaults(handler=_plan_turn)
|
| 108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
ui = subparsers.add_parser(
|
| 110 |
"ui",
|
| 111 |
help="Launch the local SovereignCode browser platform.",
|
|
|
|
| 12 |
|
| 13 |
from .audit import build_audit_record
|
| 14 |
from .planner import plan_coding_turn
|
| 15 |
+
from .platform import (
|
| 16 |
+
build_capsule_summary,
|
| 17 |
+
build_opencode_workspace_config,
|
| 18 |
+
build_policy_matrix,
|
| 19 |
+
build_turn_brief,
|
| 20 |
+
check_tool_request,
|
| 21 |
+
)
|
| 22 |
from .policy import DataCapsule, SovereignRequest, SovereigntyPolicyEngine
|
| 23 |
|
| 24 |
|
|
|
|
| 50 |
routing_request = RoutingRequest.from_payload(_load_mapping(args.route_request))
|
| 51 |
models = load_model_registry(args.registry)
|
| 52 |
plan = plan_coding_turn(capsule, request, routing_request, models)
|
| 53 |
+
payload = plan.to_dict()
|
| 54 |
+
payload["turn_brief"] = build_turn_brief(payload)
|
| 55 |
+
print(json.dumps(payload, indent=2, sort_keys=True))
|
| 56 |
return 0 if plan.allowed or args.allow_blocked_exit_zero else 2
|
| 57 |
|
| 58 |
|
| 59 |
+
def _policy_matrix(args: argparse.Namespace) -> int:
|
| 60 |
+
result = build_policy_matrix(_load_mapping(args.capsule), _load_mapping(args.request))
|
| 61 |
+
print(json.dumps(result, indent=2, sort_keys=True))
|
| 62 |
+
return 0 if result["blocked_count"] >= 0 else 2
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def _tool_check(args: argparse.Namespace) -> int:
|
| 66 |
+
tool_payload = {
|
| 67 |
+
"tool_name": args.tool_name,
|
| 68 |
+
"action": args.action,
|
| 69 |
+
"writes_files": args.writes_files,
|
| 70 |
+
"exports_data": args.exports_data,
|
| 71 |
+
"trains_model": args.trains_model,
|
| 72 |
+
"human_approved": args.human_approved,
|
| 73 |
+
}
|
| 74 |
+
result = check_tool_request(
|
| 75 |
+
_load_mapping(args.capsule),
|
| 76 |
+
_load_mapping(args.request),
|
| 77 |
+
tool_payload,
|
| 78 |
+
)
|
| 79 |
+
print(json.dumps(result, indent=2, sort_keys=True))
|
| 80 |
+
return 0 if result["ok"] or args.allow_blocked_exit_zero else 2
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def _capsule_summary(args: argparse.Namespace) -> int:
|
| 84 |
+
print(json.dumps(build_capsule_summary(_load_mapping(args.capsule)), indent=2, sort_keys=True))
|
| 85 |
+
return 0
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def _opencode_config(args: argparse.Namespace) -> int:
|
| 89 |
+
print(
|
| 90 |
+
json.dumps(
|
| 91 |
+
build_opencode_workspace_config(base_url=args.base_url, model=args.model),
|
| 92 |
+
indent=2,
|
| 93 |
+
sort_keys=True,
|
| 94 |
+
),
|
| 95 |
+
)
|
| 96 |
+
return 0
|
| 97 |
+
|
| 98 |
+
|
| 99 |
def _ui(args: argparse.Namespace) -> int:
|
| 100 |
from .ui import run_ui
|
| 101 |
|
|
|
|
| 155 |
)
|
| 156 |
plan_turn.set_defaults(handler=_plan_turn)
|
| 157 |
|
| 158 |
+
matrix = subparsers.add_parser(
|
| 159 |
+
"policy-matrix",
|
| 160 |
+
help="Evaluate the built-in tool/action matrix against a capsule.",
|
| 161 |
+
)
|
| 162 |
+
matrix.add_argument("--capsule", type=Path, required=True, help="Data Capsule JSON/YAML file.")
|
| 163 |
+
matrix.add_argument("--request", type=Path, required=True, help="Base sovereign request JSON/YAML file.")
|
| 164 |
+
matrix.set_defaults(handler=_policy_matrix)
|
| 165 |
+
|
| 166 |
+
tool_check = subparsers.add_parser(
|
| 167 |
+
"tool-check",
|
| 168 |
+
help="Check one tool/action request before execution.",
|
| 169 |
+
)
|
| 170 |
+
tool_check.add_argument("--capsule", type=Path, required=True, help="Data Capsule JSON/YAML file.")
|
| 171 |
+
tool_check.add_argument("--request", type=Path, required=True, help="Base sovereign request JSON/YAML file.")
|
| 172 |
+
tool_check.add_argument("--tool-name", default="workspace_reader")
|
| 173 |
+
tool_check.add_argument("--action", default="read_context")
|
| 174 |
+
tool_check.add_argument("--writes-files", action=argparse.BooleanOptionalAction, default=False)
|
| 175 |
+
tool_check.add_argument("--exports-data", action=argparse.BooleanOptionalAction, default=False)
|
| 176 |
+
tool_check.add_argument("--trains-model", action=argparse.BooleanOptionalAction, default=False)
|
| 177 |
+
tool_check.add_argument("--human-approved", action=argparse.BooleanOptionalAction, default=False)
|
| 178 |
+
tool_check.add_argument("--allow-blocked-exit-zero", action=argparse.BooleanOptionalAction, default=False)
|
| 179 |
+
tool_check.set_defaults(handler=_tool_check)
|
| 180 |
+
|
| 181 |
+
capsule_summary = subparsers.add_parser(
|
| 182 |
+
"capsule-summary",
|
| 183 |
+
help="Summarise Data Capsule controls and risk flags.",
|
| 184 |
+
)
|
| 185 |
+
capsule_summary.add_argument("--capsule", type=Path, required=True, help="Data Capsule JSON/YAML file.")
|
| 186 |
+
capsule_summary.set_defaults(handler=_capsule_summary)
|
| 187 |
+
|
| 188 |
+
opencode = subparsers.add_parser(
|
| 189 |
+
"opencode-config",
|
| 190 |
+
help="Emit an OpenCode-compatible SovereignCode workspace config.",
|
| 191 |
+
)
|
| 192 |
+
opencode.add_argument("--base-url", default="http://127.0.0.1:8787/v1")
|
| 193 |
+
opencode.add_argument("--model", default="lumynax-infused-qwen3-coder-30b-a3b-gguf")
|
| 194 |
+
opencode.set_defaults(handler=_opencode_config)
|
| 195 |
+
|
| 196 |
ui = subparsers.add_parser(
|
| 197 |
"ui",
|
| 198 |
help="Launch the local SovereignCode browser platform.",
|
sovereigncode/platform.py
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import Any
|
| 4 |
+
|
| 5 |
+
from .audit import build_audit_record
|
| 6 |
+
from .policy import DataCapsule, SovereignRequest, SovereigntyPolicyEngine
|
| 7 |
+
|
| 8 |
+
TOOL_SCENARIOS: tuple[dict[str, Any], ...] = (
|
| 9 |
+
{
|
| 10 |
+
"name": "Read workspace",
|
| 11 |
+
"action": "read_context",
|
| 12 |
+
"tool_name": "workspace_reader",
|
| 13 |
+
"writes_files": False,
|
| 14 |
+
"exports_data": False,
|
| 15 |
+
"trains_model": False,
|
| 16 |
+
"human_approved": False,
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"name": "Write file",
|
| 20 |
+
"action": "write_file",
|
| 21 |
+
"tool_name": "file_editor",
|
| 22 |
+
"writes_files": True,
|
| 23 |
+
"exports_data": False,
|
| 24 |
+
"trains_model": False,
|
| 25 |
+
"human_approved": False,
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"name": "Approved shell",
|
| 29 |
+
"action": "execute_shell",
|
| 30 |
+
"tool_name": "test_runner",
|
| 31 |
+
"writes_files": False,
|
| 32 |
+
"exports_data": False,
|
| 33 |
+
"trains_model": False,
|
| 34 |
+
"human_approved": True,
|
| 35 |
+
},
|
| 36 |
+
{
|
| 37 |
+
"name": "Network export",
|
| 38 |
+
"action": "network_export",
|
| 39 |
+
"tool_name": "external_api",
|
| 40 |
+
"writes_files": False,
|
| 41 |
+
"exports_data": True,
|
| 42 |
+
"trains_model": False,
|
| 43 |
+
"human_approved": True,
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"name": "Model training",
|
| 47 |
+
"action": "train_model",
|
| 48 |
+
"tool_name": "trainer",
|
| 49 |
+
"writes_files": False,
|
| 50 |
+
"exports_data": False,
|
| 51 |
+
"trains_model": True,
|
| 52 |
+
"human_approved": True,
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"name": "Publish commit",
|
| 56 |
+
"action": "commit",
|
| 57 |
+
"tool_name": "git",
|
| 58 |
+
"writes_files": False,
|
| 59 |
+
"exports_data": True,
|
| 60 |
+
"trains_model": False,
|
| 61 |
+
"human_approved": True,
|
| 62 |
+
},
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def build_policy_matrix(
|
| 67 |
+
capsule_payload: dict[str, Any],
|
| 68 |
+
request_payload: dict[str, Any],
|
| 69 |
+
scenarios: list[dict[str, Any]] | None = None,
|
| 70 |
+
) -> dict[str, Any]:
|
| 71 |
+
capsule = DataCapsule.from_payload(capsule_payload)
|
| 72 |
+
engine = SovereigntyPolicyEngine()
|
| 73 |
+
rows = []
|
| 74 |
+
for scenario in scenarios or [dict(item) for item in TOOL_SCENARIOS]:
|
| 75 |
+
merged = dict(request_payload)
|
| 76 |
+
merged.update({key: value for key, value in scenario.items() if key != "name"})
|
| 77 |
+
request = SovereignRequest.from_payload(merged)
|
| 78 |
+
decision = engine.evaluate(capsule, request)
|
| 79 |
+
audit = build_audit_record(capsule, request, decision)
|
| 80 |
+
rows.append(
|
| 81 |
+
{
|
| 82 |
+
"name": scenario.get("name", request.action),
|
| 83 |
+
"allowed": decision.allowed,
|
| 84 |
+
"action": request.action,
|
| 85 |
+
"tool_name": request.tool_name,
|
| 86 |
+
"writes_files": request.writes_files,
|
| 87 |
+
"exports_data": request.exports_data,
|
| 88 |
+
"trains_model": request.trains_model,
|
| 89 |
+
"human_approved": request.human_approved,
|
| 90 |
+
"reason_count": len(decision.reasons),
|
| 91 |
+
"obligation_count": len(decision.obligations),
|
| 92 |
+
"reasons": list(decision.reasons),
|
| 93 |
+
"obligations": list(decision.obligations),
|
| 94 |
+
"audit_hash": audit.request_hash,
|
| 95 |
+
},
|
| 96 |
+
)
|
| 97 |
+
return {
|
| 98 |
+
"ok": True,
|
| 99 |
+
"capsule_id": capsule.capsule_id,
|
| 100 |
+
"rows": rows,
|
| 101 |
+
"allowed_count": sum(1 for row in rows if row["allowed"]),
|
| 102 |
+
"blocked_count": sum(1 for row in rows if not row["allowed"]),
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def check_tool_request(
|
| 107 |
+
capsule_payload: dict[str, Any],
|
| 108 |
+
request_payload: dict[str, Any],
|
| 109 |
+
tool_payload: dict[str, Any],
|
| 110 |
+
) -> dict[str, Any]:
|
| 111 |
+
merged = dict(request_payload)
|
| 112 |
+
merged.update(tool_payload)
|
| 113 |
+
capsule = DataCapsule.from_payload(capsule_payload)
|
| 114 |
+
request = SovereignRequest.from_payload(merged)
|
| 115 |
+
decision = SovereigntyPolicyEngine().evaluate(capsule, request)
|
| 116 |
+
audit = build_audit_record(capsule, request, decision)
|
| 117 |
+
return {
|
| 118 |
+
"ok": decision.allowed,
|
| 119 |
+
"tool_name": request.tool_name,
|
| 120 |
+
"action": request.action,
|
| 121 |
+
"decision": decision.to_dict(),
|
| 122 |
+
"audit_record": audit.to_dict(),
|
| 123 |
+
"operator_gate": build_operator_gate(decision.to_dict(), request.to_dict()),
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def build_operator_gate(decision: dict[str, Any], request: dict[str, Any]) -> dict[str, Any]:
|
| 128 |
+
destructive = bool(
|
| 129 |
+
request.get("writes_files")
|
| 130 |
+
or request.get("exports_data")
|
| 131 |
+
or request.get("trains_model")
|
| 132 |
+
or request.get("action") in {"execute_shell", "publish", "commit", "network_export"}
|
| 133 |
+
)
|
| 134 |
+
return {
|
| 135 |
+
"requires_human_review": destructive or not decision.get("allowed", False),
|
| 136 |
+
"requires_visible_diff": bool(request.get("writes_files")),
|
| 137 |
+
"requires_export_manifest": bool(request.get("exports_data")),
|
| 138 |
+
"requires_training_consent": bool(request.get("trains_model")),
|
| 139 |
+
"next_gate": _next_gate(decision, request),
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def build_turn_brief(plan: dict[str, Any]) -> dict[str, Any]:
|
| 144 |
+
route = plan.get("route_decision") or {}
|
| 145 |
+
selected = route.get("selected_model") or {}
|
| 146 |
+
grants = plan.get("tool_grants") or []
|
| 147 |
+
blocked_grants = [grant for grant in grants if not grant.get("allowed")]
|
| 148 |
+
return {
|
| 149 |
+
"allowed": bool(plan.get("allowed")),
|
| 150 |
+
"selected_model": selected.get("model_id"),
|
| 151 |
+
"runtime": selected.get("runtime"),
|
| 152 |
+
"tool_grants": len(grants),
|
| 153 |
+
"blocked_tool_grants": len(blocked_grants),
|
| 154 |
+
"obligation_count": len(plan.get("obligations") or []),
|
| 155 |
+
"blocked_reasons": plan.get("blocked_reasons") or [],
|
| 156 |
+
"operator_checklist": build_operator_checklist(plan),
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def build_operator_checklist(plan: dict[str, Any]) -> list[dict[str, Any]]:
|
| 161 |
+
allowed = bool(plan.get("allowed"))
|
| 162 |
+
obligations = set(plan.get("obligations") or [])
|
| 163 |
+
route = plan.get("route_decision") or {}
|
| 164 |
+
selected = route.get("selected_model")
|
| 165 |
+
return [
|
| 166 |
+
{
|
| 167 |
+
"item": "policy_decision",
|
| 168 |
+
"status": "pass" if allowed else "blocked",
|
| 169 |
+
"detail": "Data Capsule policy permits the request" if allowed else "Policy blocked the request",
|
| 170 |
+
},
|
| 171 |
+
{
|
| 172 |
+
"item": "model_route",
|
| 173 |
+
"status": "pass" if selected else "blocked",
|
| 174 |
+
"detail": selected.get("model_id") if isinstance(selected, dict) else "No eligible model",
|
| 175 |
+
},
|
| 176 |
+
{
|
| 177 |
+
"item": "audit_record",
|
| 178 |
+
"status": "required",
|
| 179 |
+
"detail": "Immutable audit record must be persisted before external effects",
|
| 180 |
+
},
|
| 181 |
+
{
|
| 182 |
+
"item": "visible_diff",
|
| 183 |
+
"status": "required" if "show_diff_before_write_or_commit" in obligations else "not_required",
|
| 184 |
+
"detail": "Show file diff before writes or commits",
|
| 185 |
+
},
|
| 186 |
+
{
|
| 187 |
+
"item": "resident_runtime",
|
| 188 |
+
"status": "required" if "route_only_to_resident_runtime" in obligations else "not_required",
|
| 189 |
+
"detail": "Route high-impact data only to approved resident runtime",
|
| 190 |
+
},
|
| 191 |
+
]
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def build_opencode_workspace_config(
|
| 195 |
+
*,
|
| 196 |
+
base_url: str = "http://127.0.0.1:8787/v1",
|
| 197 |
+
provider_id: str = "abteex-marama",
|
| 198 |
+
model: str = "lumynax-infused-qwen3-coder-30b-a3b-gguf",
|
| 199 |
+
) -> dict[str, Any]:
|
| 200 |
+
return {
|
| 201 |
+
"$schema": "https://opencode.ai/config.json",
|
| 202 |
+
"provider": {
|
| 203 |
+
provider_id: {
|
| 204 |
+
"name": "AbteeX SovereignCode via MaramaRoute",
|
| 205 |
+
"npm": "@ai-sdk/openai-compatible",
|
| 206 |
+
"options": {
|
| 207 |
+
"baseURL": base_url,
|
| 208 |
+
"apiKey": "${ABTEEX_MARAMA_API_KEY:-local-dev}",
|
| 209 |
+
},
|
| 210 |
+
"models": {
|
| 211 |
+
model: {
|
| 212 |
+
"name": model,
|
| 213 |
+
"attachment": False,
|
| 214 |
+
"reasoning": True,
|
| 215 |
+
},
|
| 216 |
+
},
|
| 217 |
+
},
|
| 218 |
+
},
|
| 219 |
+
"model": f"{provider_id}/{model}",
|
| 220 |
+
"sovereigncode": {
|
| 221 |
+
"capsule_file": "products/abx-sovereigncode/examples/capsule.restricted-nz-code.json",
|
| 222 |
+
"audit_ledger": ".sovereigncode/audit.jsonl",
|
| 223 |
+
"require_human_review_for": ["write_files", "execute_shell", "network_export", "commit"],
|
| 224 |
+
},
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def build_capsule_summary(capsule_payload: dict[str, Any]) -> dict[str, Any]:
|
| 229 |
+
capsule = DataCapsule.from_payload(capsule_payload)
|
| 230 |
+
return {
|
| 231 |
+
"capsule_id": capsule.capsule_id,
|
| 232 |
+
"jurisdiction": capsule.jurisdiction,
|
| 233 |
+
"sensitivity": capsule.sensitivity,
|
| 234 |
+
"resident_regions": list(capsule.resident_regions),
|
| 235 |
+
"allowed_purposes": list(capsule.allowed_purposes),
|
| 236 |
+
"denied_purposes": list(capsule.denied_purposes),
|
| 237 |
+
"retention_days": capsule.retention_days,
|
| 238 |
+
"export_allowed": capsule.export_allowed,
|
| 239 |
+
"training_allowed": capsule.training_allowed,
|
| 240 |
+
"personal_detail_level": capsule.personal_detail_level,
|
| 241 |
+
"risk_flags": _capsule_risk_flags(capsule),
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def tool_scenarios() -> list[dict[str, Any]]:
|
| 246 |
+
return [dict(item) for item in TOOL_SCENARIOS]
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def _capsule_risk_flags(capsule: DataCapsule) -> list[str]:
|
| 250 |
+
flags = []
|
| 251 |
+
if capsule.sensitivity in {"personal", "restricted", "health", "iwi", "taonga"}:
|
| 252 |
+
flags.append("high_impact_sensitivity")
|
| 253 |
+
if not capsule.export_allowed:
|
| 254 |
+
flags.append("export_blocked")
|
| 255 |
+
if not capsule.training_allowed:
|
| 256 |
+
flags.append("training_blocked")
|
| 257 |
+
if capsule.retention_days <= 14:
|
| 258 |
+
flags.append("short_retention")
|
| 259 |
+
if capsule.personal_detail_level not in {"none", "anonymous"}:
|
| 260 |
+
flags.append("personal_detail_controls")
|
| 261 |
+
return flags
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
def _next_gate(decision: dict[str, Any], request: dict[str, Any]) -> str:
|
| 265 |
+
if not decision.get("allowed", False):
|
| 266 |
+
return "revise_request_or_capsule"
|
| 267 |
+
if request.get("writes_files"):
|
| 268 |
+
return "show_diff_before_write"
|
| 269 |
+
if request.get("exports_data"):
|
| 270 |
+
return "attach_export_manifest"
|
| 271 |
+
if request.get("trains_model"):
|
| 272 |
+
return "attach_training_consent"
|
| 273 |
+
if request.get("action") in {"execute_shell", "commit"}:
|
| 274 |
+
return "human_approval"
|
| 275 |
+
return "execute_with_audit"
|
sovereigncode/ui.py
CHANGED
|
@@ -16,6 +16,14 @@ except ModuleNotFoundError: # standalone HF package
|
|
| 16 |
|
| 17 |
from .audit import build_audit_record
|
| 18 |
from .planner import plan_coding_turn
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
from .policy import DataCapsule, SovereignRequest, SovereigntyPolicyEngine
|
| 20 |
|
| 21 |
PRODUCT_NAME = "AbteeX SovereignCode"
|
|
@@ -153,12 +161,20 @@ def build_dashboard_state(
|
|
| 153 |
else _fallback_personal_request()
|
| 154 |
)
|
| 155 |
models = load_model_registry(registry_path)
|
|
|
|
|
|
|
|
|
|
| 156 |
return {
|
| 157 |
-
"capsule":
|
| 158 |
-
"request":
|
| 159 |
-
"route_request":
|
| 160 |
"personal_capsule": personal_capsule,
|
| 161 |
"personal_request": personal_request,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
"registry_path": str(registry_path),
|
| 163 |
"model_count": len(models),
|
| 164 |
}
|
|
@@ -181,7 +197,27 @@ def handle_api_request(
|
|
| 181 |
return 200, {"ok": True, **state}
|
| 182 |
if method == "POST" and path in {"/api/evaluate", "/api/plan"} and payload is not None:
|
| 183 |
result = evaluate_dashboard_payload(payload, registry_path)
|
|
|
|
|
|
|
| 184 |
return (200 if result["ok"] else 422), result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
return 404, {"ok": False, "error": "not_found"}
|
| 186 |
|
| 187 |
|
|
@@ -213,11 +249,17 @@ def smoke_ui(
|
|
| 213 |
if not result["ok"]:
|
| 214 |
raise RuntimeError("SovereignCode UI smoke plan was blocked")
|
| 215 |
selected = result["plan"]["route_decision"]["selected_model"]["model_id"]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
return {
|
| 217 |
"ok": True,
|
| 218 |
"product": PRODUCT_NAME,
|
| 219 |
"model_count": state["model_count"],
|
| 220 |
"selected_model": selected,
|
|
|
|
|
|
|
| 221 |
}
|
| 222 |
|
| 223 |
|
|
@@ -257,7 +299,7 @@ def run_ui(
|
|
| 257 |
resolved_route,
|
| 258 |
resolved_registry,
|
| 259 |
)
|
| 260 |
-
html =
|
| 261 |
return serve_dashboard(
|
| 262 |
product_name=PRODUCT_NAME,
|
| 263 |
html=html,
|
|
@@ -274,6 +316,335 @@ def run_ui(
|
|
| 274 |
)
|
| 275 |
|
| 276 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
def build_dashboard_html(state: dict[str, Any]) -> str:
|
| 278 |
initial = json.dumps(state, sort_keys=True).replace("</", "<\\/")
|
| 279 |
return f"""<!doctype html>
|
|
|
|
| 16 |
|
| 17 |
from .audit import build_audit_record
|
| 18 |
from .planner import plan_coding_turn
|
| 19 |
+
from .platform import (
|
| 20 |
+
build_capsule_summary,
|
| 21 |
+
build_opencode_workspace_config,
|
| 22 |
+
build_policy_matrix,
|
| 23 |
+
build_turn_brief,
|
| 24 |
+
check_tool_request,
|
| 25 |
+
tool_scenarios,
|
| 26 |
+
)
|
| 27 |
from .policy import DataCapsule, SovereignRequest, SovereigntyPolicyEngine
|
| 28 |
|
| 29 |
PRODUCT_NAME = "AbteeX SovereignCode"
|
|
|
|
| 161 |
else _fallback_personal_request()
|
| 162 |
)
|
| 163 |
models = load_model_registry(registry_path)
|
| 164 |
+
capsule = load_json_mapping(capsule_path)
|
| 165 |
+
request = load_json_mapping(request_path)
|
| 166 |
+
route_request = load_json_mapping(route_request_path)
|
| 167 |
return {
|
| 168 |
+
"capsule": capsule,
|
| 169 |
+
"request": request,
|
| 170 |
+
"route_request": route_request,
|
| 171 |
"personal_capsule": personal_capsule,
|
| 172 |
"personal_request": personal_request,
|
| 173 |
+
"capsule_summary": build_capsule_summary(capsule),
|
| 174 |
+
"personal_capsule_summary": build_capsule_summary(personal_capsule),
|
| 175 |
+
"policy_matrix": build_policy_matrix(capsule, request),
|
| 176 |
+
"tool_scenarios": tool_scenarios(),
|
| 177 |
+
"opencode_config": build_opencode_workspace_config(),
|
| 178 |
"registry_path": str(registry_path),
|
| 179 |
"model_count": len(models),
|
| 180 |
}
|
|
|
|
| 197 |
return 200, {"ok": True, **state}
|
| 198 |
if method == "POST" and path in {"/api/evaluate", "/api/plan"} and payload is not None:
|
| 199 |
result = evaluate_dashboard_payload(payload, registry_path)
|
| 200 |
+
if "plan" in result:
|
| 201 |
+
result["turn_brief"] = build_turn_brief(result["plan"])
|
| 202 |
return (200 if result["ok"] else 422), result
|
| 203 |
+
if method == "POST" and path == "/api/policy-matrix" and payload is not None:
|
| 204 |
+
capsule = _mapping(payload.get("capsule")) or state["capsule"]
|
| 205 |
+
request = _mapping(payload.get("request")) or state["request"]
|
| 206 |
+
return 200, build_policy_matrix(capsule, request)
|
| 207 |
+
if method == "POST" and path == "/api/tool-check" and payload is not None:
|
| 208 |
+
capsule = _mapping(payload.get("capsule")) or state["capsule"]
|
| 209 |
+
request = _mapping(payload.get("request")) or state["request"]
|
| 210 |
+
tool = _mapping(payload.get("tool"))
|
| 211 |
+
result = check_tool_request(capsule, request, tool)
|
| 212 |
+
return (200 if result["ok"] else 422), result
|
| 213 |
+
if method == "POST" and path == "/api/opencode-config" and payload is not None:
|
| 214 |
+
return 200, {
|
| 215 |
+
"ok": True,
|
| 216 |
+
"config": build_opencode_workspace_config(
|
| 217 |
+
base_url=str(payload.get("base_url") or "http://127.0.0.1:8787/v1"),
|
| 218 |
+
model=str(payload.get("model") or "lumynax-infused-qwen3-coder-30b-a3b-gguf"),
|
| 219 |
+
),
|
| 220 |
+
}
|
| 221 |
return 404, {"ok": False, "error": "not_found"}
|
| 222 |
|
| 223 |
|
|
|
|
| 249 |
if not result["ok"]:
|
| 250 |
raise RuntimeError("SovereignCode UI smoke plan was blocked")
|
| 251 |
selected = result["plan"]["route_decision"]["selected_model"]["model_id"]
|
| 252 |
+
matrix = build_policy_matrix(state["capsule"], state["request"])
|
| 253 |
+
tool = check_tool_request(state["capsule"], state["request"], tool_scenarios()[0])
|
| 254 |
+
if matrix["blocked_count"] < 1 or not tool["ok"]:
|
| 255 |
+
raise RuntimeError("SovereignCode expanded UI smoke checks failed")
|
| 256 |
return {
|
| 257 |
"ok": True,
|
| 258 |
"product": PRODUCT_NAME,
|
| 259 |
"model_count": state["model_count"],
|
| 260 |
"selected_model": selected,
|
| 261 |
+
"matrix_rows": len(matrix["rows"]),
|
| 262 |
+
"tool_check": tool["ok"],
|
| 263 |
}
|
| 264 |
|
| 265 |
|
|
|
|
| 299 |
resolved_route,
|
| 300 |
resolved_registry,
|
| 301 |
)
|
| 302 |
+
html = build_expanded_dashboard_html(state)
|
| 303 |
return serve_dashboard(
|
| 304 |
product_name=PRODUCT_NAME,
|
| 305 |
html=html,
|
|
|
|
| 316 |
)
|
| 317 |
|
| 318 |
|
| 319 |
+
def build_expanded_dashboard_html(state: dict[str, Any]) -> str:
|
| 320 |
+
initial = json.dumps(state, sort_keys=True).replace("</", "<\\/")
|
| 321 |
+
html = """<!doctype html>
|
| 322 |
+
<html lang="en">
|
| 323 |
+
<head>
|
| 324 |
+
<meta charset="utf-8" />
|
| 325 |
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 326 |
+
<title>AbteeX SovereignCode Console</title>
|
| 327 |
+
<style>
|
| 328 |
+
:root {
|
| 329 |
+
--paper: #fffefa;
|
| 330 |
+
--ink: #0a0a0b;
|
| 331 |
+
--muted: #726b62;
|
| 332 |
+
--rule: #ded6c8;
|
| 333 |
+
--amber: #e08a2c;
|
| 334 |
+
--amber-dark: #9a5416;
|
| 335 |
+
--green: #275f45;
|
| 336 |
+
--red: #8f2e24;
|
| 337 |
+
--panel: #f7f1e7;
|
| 338 |
+
--chalk: #fff7e8;
|
| 339 |
+
--mono: "Cascadia Code", "SFMono-Regular", Menlo, Consolas, monospace;
|
| 340 |
+
--serif: Georgia, "Iowan Old Style", "Palatino Linotype", serif;
|
| 341 |
+
}
|
| 342 |
+
* { box-sizing: border-box; }
|
| 343 |
+
body {
|
| 344 |
+
margin: 0;
|
| 345 |
+
background:
|
| 346 |
+
linear-gradient(90deg, rgba(10,10,11,.035) 1px, transparent 1px) 0 0 / 42px 42px,
|
| 347 |
+
linear-gradient(0deg, rgba(10,10,11,.025) 1px, transparent 1px) 0 0 / 42px 42px,
|
| 348 |
+
var(--paper);
|
| 349 |
+
color: var(--ink);
|
| 350 |
+
font-family: var(--serif);
|
| 351 |
+
}
|
| 352 |
+
header {
|
| 353 |
+
display: grid;
|
| 354 |
+
grid-template-columns: minmax(260px, 1fr) minmax(320px, 48vw);
|
| 355 |
+
gap: 24px;
|
| 356 |
+
align-items: end;
|
| 357 |
+
padding: 22px 28px 16px;
|
| 358 |
+
border-bottom: 1px solid var(--rule);
|
| 359 |
+
background: rgba(255,254,250,.92);
|
| 360 |
+
}
|
| 361 |
+
h1 {
|
| 362 |
+
margin: 0;
|
| 363 |
+
font-size: clamp(34px, 5vw, 68px);
|
| 364 |
+
line-height: .88;
|
| 365 |
+
letter-spacing: 0;
|
| 366 |
+
}
|
| 367 |
+
.subtitle, label, button, select, .metric, .k, th, td {
|
| 368 |
+
font-family: var(--mono);
|
| 369 |
+
font-size: 12px;
|
| 370 |
+
letter-spacing: 0;
|
| 371 |
+
}
|
| 372 |
+
.subtitle { margin-top: 10px; color: var(--muted); }
|
| 373 |
+
.metrics { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 8px; }
|
| 374 |
+
.metric { border-left: 2px solid var(--amber); min-height: 60px; padding: 7px 8px; background: rgba(247,241,231,.72); }
|
| 375 |
+
.metric strong { display: block; font: 700 22px/1 var(--serif); color: var(--ink); overflow-wrap: anywhere; }
|
| 376 |
+
main { display: grid; grid-template-columns: minmax(360px, 480px) 1fr; min-height: calc(100vh - 118px); }
|
| 377 |
+
aside { border-right: 1px solid var(--rule); background: rgba(255,250,242,.95); padding: 18px; }
|
| 378 |
+
.content { padding: 18px; display: grid; gap: 12px; align-content: start; min-width: 0; }
|
| 379 |
+
.stack { display: grid; gap: 12px; }
|
| 380 |
+
.grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
|
| 381 |
+
.split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 12px; }
|
| 382 |
+
.panel, textarea, pre, table, select { border: 1px solid var(--rule); border-radius: 6px; background: var(--paper); color: var(--ink); }
|
| 383 |
+
.panel { padding: 14px; min-width: 0; }
|
| 384 |
+
.panel h2 { margin: 0 0 10px; font-size: 18px; line-height: 1.1; }
|
| 385 |
+
textarea {
|
| 386 |
+
width: 100%;
|
| 387 |
+
min-height: 150px;
|
| 388 |
+
resize: vertical;
|
| 389 |
+
padding: 12px;
|
| 390 |
+
font: 12px/1.45 var(--mono);
|
| 391 |
+
}
|
| 392 |
+
#routeRequest { min-height: 118px; }
|
| 393 |
+
select { width: 100%; min-height: 34px; padding: 0 10px; }
|
| 394 |
+
button {
|
| 395 |
+
appearance: none;
|
| 396 |
+
border: 1px solid var(--ink);
|
| 397 |
+
background: var(--ink);
|
| 398 |
+
color: var(--paper);
|
| 399 |
+
border-radius: 4px;
|
| 400 |
+
min-height: 34px;
|
| 401 |
+
padding: 0 12px;
|
| 402 |
+
cursor: pointer;
|
| 403 |
+
white-space: nowrap;
|
| 404 |
+
}
|
| 405 |
+
button.secondary { background: transparent; color: var(--ink); border-color: var(--rule); }
|
| 406 |
+
.toolbar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
| 407 |
+
.status { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; }
|
| 408 |
+
.status .panel { min-height: 92px; }
|
| 409 |
+
.k { color: var(--muted); display: block; line-height: 1.25; }
|
| 410 |
+
.v { display: block; margin-top: 8px; font-size: 18px; line-height: 1.12; overflow-wrap: anywhere; }
|
| 411 |
+
.ok { color: var(--green); }
|
| 412 |
+
.bad { color: var(--red); }
|
| 413 |
+
pre { margin: 0; padding: 14px; max-height: 520px; overflow: auto; font: 12px/1.45 var(--mono); }
|
| 414 |
+
table { width: 100%; border-collapse: collapse; overflow: hidden; }
|
| 415 |
+
th, td { padding: 9px 10px; text-align: left; border-bottom: 1px solid var(--rule); vertical-align: top; }
|
| 416 |
+
th { color: var(--muted); background: var(--panel); }
|
| 417 |
+
tr:last-child td { border-bottom: 0; }
|
| 418 |
+
.pill { display: inline-flex; border: 1px solid var(--rule); border-radius: 999px; min-height: 24px; align-items: center; padding: 0 9px; margin: 0 5px 5px 0; background: var(--chalk); font: 12px/1 var(--mono); }
|
| 419 |
+
@media (max-width: 1180px) {
|
| 420 |
+
header, main, .split { grid-template-columns: 1fr; }
|
| 421 |
+
aside { border-right: 0; border-bottom: 1px solid var(--rule); }
|
| 422 |
+
.metrics, .status { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
| 423 |
+
}
|
| 424 |
+
@media (max-width: 680px) {
|
| 425 |
+
header, aside, .content { padding: 14px; }
|
| 426 |
+
.grid, .metrics, .status { grid-template-columns: 1fr; }
|
| 427 |
+
}
|
| 428 |
+
</style>
|
| 429 |
+
</head>
|
| 430 |
+
<body>
|
| 431 |
+
<header>
|
| 432 |
+
<div>
|
| 433 |
+
<h1>AbteeX<br />SovereignCode</h1>
|
| 434 |
+
<div class="subtitle">Policy decision point, route plan, tool gates, audit receipt.</div>
|
| 435 |
+
</div>
|
| 436 |
+
<div class="metrics">
|
| 437 |
+
<div class="metric"><strong id="metricModels">0</strong>models</div>
|
| 438 |
+
<div class="metric"><strong id="metricRegion">NZ</strong>region</div>
|
| 439 |
+
<div class="metric"><strong id="metricSensitivity">-</strong>sensitivity</div>
|
| 440 |
+
<div class="metric"><strong id="metricRetention">0</strong>retention</div>
|
| 441 |
+
<div class="metric"><strong id="metricBlocked">0</strong>blocked tools</div>
|
| 442 |
+
</div>
|
| 443 |
+
</header>
|
| 444 |
+
<main>
|
| 445 |
+
<aside class="stack">
|
| 446 |
+
<div class="toolbar">
|
| 447 |
+
<button id="planBtn">Plan Turn</button>
|
| 448 |
+
<button class="secondary" id="evaluateBtn">Evaluate</button>
|
| 449 |
+
<button class="secondary" id="matrixBtn">Matrix</button>
|
| 450 |
+
<button class="secondary" id="providerBtn">Provider</button>
|
| 451 |
+
</div>
|
| 452 |
+
<div class="toolbar">
|
| 453 |
+
<button class="secondary" id="restrictedBtn">Restricted</button>
|
| 454 |
+
<button class="secondary" id="personalBtn">Personal</button>
|
| 455 |
+
</div>
|
| 456 |
+
<div>
|
| 457 |
+
<label for="toolScenario">TOOL GATE</label>
|
| 458 |
+
<select id="toolScenario"></select>
|
| 459 |
+
</div>
|
| 460 |
+
<div>
|
| 461 |
+
<label for="capsule">DATA CAPSULE</label>
|
| 462 |
+
<textarea id="capsule" spellcheck="false"></textarea>
|
| 463 |
+
</div>
|
| 464 |
+
<div>
|
| 465 |
+
<label for="request">SOVEREIGN REQUEST</label>
|
| 466 |
+
<textarea id="request" spellcheck="false"></textarea>
|
| 467 |
+
</div>
|
| 468 |
+
<div>
|
| 469 |
+
<label for="routeRequest">ROUTE REQUEST</label>
|
| 470 |
+
<textarea id="routeRequest" spellcheck="false"></textarea>
|
| 471 |
+
</div>
|
| 472 |
+
</aside>
|
| 473 |
+
<section class="content">
|
| 474 |
+
<div class="status">
|
| 475 |
+
<div class="panel"><span class="k">DECISION</span><span class="v" id="decision">pending</span></div>
|
| 476 |
+
<div class="panel"><span class="k">MODEL</span><span class="v" id="selected">pending</span></div>
|
| 477 |
+
<div class="panel"><span class="k">GRANTS</span><span class="v" id="grants">0</span></div>
|
| 478 |
+
<div class="panel"><span class="k">OBLIGATIONS</span><span class="v" id="obligations">0</span></div>
|
| 479 |
+
<div class="panel"><span class="k">NEXT GATE</span><span class="v" id="nextGate">ready</span></div>
|
| 480 |
+
</div>
|
| 481 |
+
<div class="split">
|
| 482 |
+
<div class="panel">
|
| 483 |
+
<h2>Turn Result</h2>
|
| 484 |
+
<pre id="output"></pre>
|
| 485 |
+
</div>
|
| 486 |
+
<div class="panel">
|
| 487 |
+
<h2>Operator Checklist</h2>
|
| 488 |
+
<table>
|
| 489 |
+
<thead><tr><th>Gate</th><th>Status</th><th>Detail</th></tr></thead>
|
| 490 |
+
<tbody id="checkRows"></tbody>
|
| 491 |
+
</table>
|
| 492 |
+
</div>
|
| 493 |
+
</div>
|
| 494 |
+
<div class="split">
|
| 495 |
+
<div class="panel">
|
| 496 |
+
<h2>Policy Matrix</h2>
|
| 497 |
+
<table>
|
| 498 |
+
<thead><tr><th>Tool</th><th>Decision</th><th>Reasons</th></tr></thead>
|
| 499 |
+
<tbody id="matrixRows"></tbody>
|
| 500 |
+
</table>
|
| 501 |
+
</div>
|
| 502 |
+
<div class="panel">
|
| 503 |
+
<h2>Capsule Summary</h2>
|
| 504 |
+
<div id="capsuleSummary"></div>
|
| 505 |
+
</div>
|
| 506 |
+
</div>
|
| 507 |
+
<div class="panel">
|
| 508 |
+
<h2>Provider Config</h2>
|
| 509 |
+
<pre id="provider"></pre>
|
| 510 |
+
</div>
|
| 511 |
+
</section>
|
| 512 |
+
</main>
|
| 513 |
+
<script type="application/json" id="initial-state">__INITIAL__</script>
|
| 514 |
+
<script>
|
| 515 |
+
const state = JSON.parse(document.getElementById('initial-state').textContent);
|
| 516 |
+
const capsule = document.getElementById('capsule');
|
| 517 |
+
const request = document.getElementById('request');
|
| 518 |
+
const routeRequest = document.getElementById('routeRequest');
|
| 519 |
+
const output = document.getElementById('output');
|
| 520 |
+
const provider = document.getElementById('provider');
|
| 521 |
+
const decision = document.getElementById('decision');
|
| 522 |
+
const selected = document.getElementById('selected');
|
| 523 |
+
const grants = document.getElementById('grants');
|
| 524 |
+
const obligations = document.getElementById('obligations');
|
| 525 |
+
const nextGate = document.getElementById('nextGate');
|
| 526 |
+
const matrixRows = document.getElementById('matrixRows');
|
| 527 |
+
const checkRows = document.getElementById('checkRows');
|
| 528 |
+
const capsuleSummary = document.getElementById('capsuleSummary');
|
| 529 |
+
const toolScenario = document.getElementById('toolScenario');
|
| 530 |
+
|
| 531 |
+
function writeJson(node, value) { node.textContent = JSON.stringify(value, null, 2); }
|
| 532 |
+
function safeJson(text) {
|
| 533 |
+
try { return [JSON.parse(text), null]; }
|
| 534 |
+
catch (error) { return [null, error.message]; }
|
| 535 |
+
}
|
| 536 |
+
function setMetric(id, value) { document.getElementById(id).textContent = value; }
|
| 537 |
+
setMetric('metricModels', state.model_count);
|
| 538 |
+
setMetric('metricRegion', state.capsule_summary.jurisdiction);
|
| 539 |
+
setMetric('metricSensitivity', state.capsule_summary.sensitivity);
|
| 540 |
+
setMetric('metricRetention', state.capsule_summary.retention_days);
|
| 541 |
+
setMetric('metricBlocked', state.policy_matrix.blocked_count);
|
| 542 |
+
toolScenario.innerHTML = state.tool_scenarios.map((item, index) => `<option value="${index}">${item.name}</option>`).join('');
|
| 543 |
+
|
| 544 |
+
function loadRestricted() {
|
| 545 |
+
capsule.value = JSON.stringify(state.capsule, null, 2);
|
| 546 |
+
request.value = JSON.stringify(state.request, null, 2);
|
| 547 |
+
routeRequest.value = JSON.stringify(state.route_request, null, 2);
|
| 548 |
+
renderCapsuleSummary(state.capsule_summary);
|
| 549 |
+
}
|
| 550 |
+
function loadPersonal() {
|
| 551 |
+
capsule.value = JSON.stringify(state.personal_capsule, null, 2);
|
| 552 |
+
request.value = JSON.stringify(state.personal_request, null, 2);
|
| 553 |
+
routeRequest.value = JSON.stringify(state.route_request, null, 2);
|
| 554 |
+
renderCapsuleSummary(state.personal_capsule_summary);
|
| 555 |
+
}
|
| 556 |
+
function readPayload(includeRoute) {
|
| 557 |
+
const payload = {capsule: JSON.parse(capsule.value), request: JSON.parse(request.value)};
|
| 558 |
+
if (includeRoute) payload.route_request = JSON.parse(routeRequest.value);
|
| 559 |
+
return payload;
|
| 560 |
+
}
|
| 561 |
+
function renderMatrix(matrix) {
|
| 562 |
+
matrixRows.innerHTML = matrix.rows.map(row => `<tr><td>${row.name}<br><span class="k">${row.tool_name}</span></td><td class="${row.allowed ? 'ok' : 'bad'}">${row.allowed ? 'allow' : 'block'}</td><td>${row.reason_count}</td></tr>`).join('');
|
| 563 |
+
setMetric('metricBlocked', matrix.blocked_count);
|
| 564 |
+
}
|
| 565 |
+
function renderChecklist(rows) {
|
| 566 |
+
checkRows.innerHTML = rows.map(row => `<tr><td>${row.item}</td><td>${row.status}</td><td>${row.detail}</td></tr>`).join('');
|
| 567 |
+
}
|
| 568 |
+
function renderCapsuleSummary(summary) {
|
| 569 |
+
capsuleSummary.innerHTML = `
|
| 570 |
+
<div class="pill">${summary.capsule_id}</div>
|
| 571 |
+
<div class="pill">${summary.jurisdiction}</div>
|
| 572 |
+
<div class="pill">${summary.sensitivity}</div>
|
| 573 |
+
<div class="pill">${summary.retention_days} days</div>
|
| 574 |
+
${(summary.risk_flags || []).map(item => `<div class="pill">${item}</div>`).join('')}
|
| 575 |
+
`;
|
| 576 |
+
}
|
| 577 |
+
function setResult(data) {
|
| 578 |
+
writeJson(output, data);
|
| 579 |
+
const plan = data.plan || {};
|
| 580 |
+
const brief = data.turn_brief || {};
|
| 581 |
+
const route = plan.route_decision || {};
|
| 582 |
+
const model = route.selected_model || {};
|
| 583 |
+
const allowed = data.ok === true;
|
| 584 |
+
decision.textContent = allowed ? 'allowed' : 'blocked';
|
| 585 |
+
decision.className = allowed ? 'v ok' : 'v bad';
|
| 586 |
+
selected.textContent = model.model_id || brief.selected_model || 'none';
|
| 587 |
+
selected.className = selected.textContent !== 'none' ? 'v ok' : 'v bad';
|
| 588 |
+
grants.textContent = plan.tool_grants ? plan.tool_grants.length : (brief.tool_grants || 0);
|
| 589 |
+
obligations.textContent = plan.obligations ? plan.obligations.length : (brief.obligation_count || 0);
|
| 590 |
+
if (brief.operator_checklist) renderChecklist(brief.operator_checklist);
|
| 591 |
+
nextGate.textContent = data.operator_gate ? data.operator_gate.next_gate : (allowed ? 'execute_with_audit' : 'revise_request');
|
| 592 |
+
}
|
| 593 |
+
async function postJson(path, payload) {
|
| 594 |
+
const response = await fetch(path, {
|
| 595 |
+
method: 'POST',
|
| 596 |
+
headers: {'Content-Type': 'application/json'},
|
| 597 |
+
body: JSON.stringify(payload)
|
| 598 |
+
});
|
| 599 |
+
return response.json();
|
| 600 |
+
}
|
| 601 |
+
async function submit(includeRoute) {
|
| 602 |
+
let payload;
|
| 603 |
+
try {
|
| 604 |
+
payload = readPayload(includeRoute);
|
| 605 |
+
} catch (error) {
|
| 606 |
+
setResult({ok: false, error: error.message});
|
| 607 |
+
return;
|
| 608 |
+
}
|
| 609 |
+
setResult(await postJson(includeRoute ? '/api/plan' : '/api/evaluate', payload));
|
| 610 |
+
}
|
| 611 |
+
async function matrix() {
|
| 612 |
+
const [cap, capErr] = safeJson(capsule.value);
|
| 613 |
+
const [req, reqErr] = safeJson(request.value);
|
| 614 |
+
if (capErr || reqErr) { setResult({ok: false, error: capErr || reqErr}); return; }
|
| 615 |
+
const data = await postJson('/api/policy-matrix', {capsule: cap, request: req});
|
| 616 |
+
renderMatrix(data);
|
| 617 |
+
writeJson(output, data);
|
| 618 |
+
}
|
| 619 |
+
async function toolCheck() {
|
| 620 |
+
const [cap, capErr] = safeJson(capsule.value);
|
| 621 |
+
const [req, reqErr] = safeJson(request.value);
|
| 622 |
+
if (capErr || reqErr) { setResult({ok: false, error: capErr || reqErr}); return; }
|
| 623 |
+
const tool = state.tool_scenarios[Number(toolScenario.value)];
|
| 624 |
+
setResult(await postJson('/api/tool-check', {capsule: cap, request: req, tool}));
|
| 625 |
+
}
|
| 626 |
+
async function providerConfig() {
|
| 627 |
+
const data = await postJson('/api/opencode-config', {base_url: 'http://127.0.0.1:8787/v1'});
|
| 628 |
+
writeJson(provider, data.config);
|
| 629 |
+
writeJson(output, data);
|
| 630 |
+
}
|
| 631 |
+
document.getElementById('planBtn').addEventListener('click', () => submit(true));
|
| 632 |
+
document.getElementById('evaluateBtn').addEventListener('click', () => submit(false));
|
| 633 |
+
document.getElementById('matrixBtn').addEventListener('click', matrix);
|
| 634 |
+
document.getElementById('providerBtn').addEventListener('click', providerConfig);
|
| 635 |
+
document.getElementById('restrictedBtn').addEventListener('click', loadRestricted);
|
| 636 |
+
document.getElementById('personalBtn').addEventListener('click', loadPersonal);
|
| 637 |
+
toolScenario.addEventListener('change', toolCheck);
|
| 638 |
+
loadRestricted();
|
| 639 |
+
renderMatrix(state.policy_matrix);
|
| 640 |
+
writeJson(provider, state.opencode_config);
|
| 641 |
+
writeJson(output, {status: 'ready', registry: state.registry_path});
|
| 642 |
+
</script>
|
| 643 |
+
</body>
|
| 644 |
+
</html>"""
|
| 645 |
+
return html.replace("__INITIAL__", initial)
|
| 646 |
+
|
| 647 |
+
|
| 648 |
def build_dashboard_html(state: dict[str, Any]) -> str:
|
| 649 |
initial = json.dumps(state, sort_keys=True).replace("</", "<\\/")
|
| 650 |
return f"""<!doctype html>
|