Spaces:
Running
Running
deploy: Unified Operator Shell v4 (additive) — /operator + /api/sentra/v4/*
Browse filesAuthor: Yachay <yachay@szlholdings.dev>
DCO: Signed-off-by: Yachay <yachay@szlholdings.dev>
Change-class: ADDITIVE
Co-Authored-By: Perplexity Computer Agent
- Dockerfile +9 -0
- operator_shell_v4.py +350 -0
- serve.py +19 -0
- web/operator.html +584 -0
Dockerfile
CHANGED
|
@@ -109,4 +109,13 @@ COPY szl_warhacker_aliases.py ./szl_warhacker_aliases.py
|
|
| 109 |
# self-contained kernel layer (9 living kernels + /api/sentra/v3/kernels/* lifecycle).
|
| 110 |
# Per-file COPY (no `COPY . .`) — without it `import szl_kernels_organ` fails.
|
| 111 |
COPY szl_kernels_organ.py ./szl_kernels_organ.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
CMD ["python", "serve.py"]
|
|
|
|
| 109 |
# self-contained kernel layer (9 living kernels + /api/sentra/v3/kernels/* lifecycle).
|
| 110 |
# Per-file COPY (no `COPY . .`) — without it `import szl_kernels_organ` fails.
|
| 111 |
COPY szl_kernels_organ.py ./szl_kernels_organ.py
|
| 112 |
+
# ADDITIVE (Unified Operator Shell v4, 2026-06-01, Yachay / Perplexity Computer
|
| 113 |
+
# Agent): explicit per-file COPY (this Dockerfile does not use `COPY . .`).
|
| 114 |
+
# serve.py imports operator_shell_v4 and calls .register(app, "sentra",
|
| 115 |
+
# web_dir="/app/web") -> /api/sentra/v4/* + /operator desktop cockpit. The shell
|
| 116 |
+
# HTML is served from /app/web/operator.html. operator_shell_v4 depends only on
|
| 117 |
+
# stdlib + fastapi (already installed) + the already-copied szl_dsse signing module.
|
| 118 |
+
# Without these COPYs the import fails and /operator falls through to the SPA shell.
|
| 119 |
+
COPY operator_shell_v4.py ./operator_shell_v4.py
|
| 120 |
+
COPY web/operator.html ./web/operator.html
|
| 121 |
CMD ["python", "serve.py"]
|
operator_shell_v4.py
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
# © 2026 Lutar, Stephen P. — SZL Holdings · ORCID 0009-0001-0110-4173 · Doctrine v11/v12
|
| 3 |
+
# Authored by Yachay (CTO). Co-Authored-By: Perplexity Computer Agent.
|
| 4 |
+
"""
|
| 5 |
+
operator_shell_v4 — the Unified Operator Shell v4 endpoint contract, registered
|
| 6 |
+
ADDITIVELY on each flagship's FastAPI app. ONE module, FOUR flagships.
|
| 7 |
+
|
| 8 |
+
Exposes EXACTLY (organ in {a11oy, sentra, amaru, killinchu}):
|
| 9 |
+
|
| 10 |
+
GET /api/<organ>/v4/inbox active inbox items only (noise rule)
|
| 11 |
+
GET /api/<organ>/v4/map/state current 3D scene state (discriminated by kind)
|
| 12 |
+
POST /api/<organ>/v4/command slash command -> DSSE receipt
|
| 13 |
+
GET /api/<organ>/v4/receipts recent successfully-signed receipts
|
| 14 |
+
GET /api/<organ>/v4/replay/{hash} reconstructed state at a frame
|
| 15 |
+
GET /api/<organ>/v4/stream SSE live updates (text/event-stream)
|
| 16 |
+
GET /api/<organ>/v4/healthz minimal health JSON
|
| 17 |
+
GET /<organ>/operator and /operator serve web/operator.html (desktop shell)
|
| 18 |
+
|
| 19 |
+
Honesty:
|
| 20 |
+
* Receipts are signed by the LIVE szl_dsse module (real ECDSA-P256 cosign over
|
| 21 |
+
DSSE PAE) when SZL_COSIGN_PRIVATE_PEM is present; otherwise an explicit
|
| 22 |
+
UNSIGNED envelope is returned (never fabricated).
|
| 23 |
+
* Per-organ keyid is carried as a receipt-metadata label; the cryptographic
|
| 24 |
+
keyid remains the real shared "szlholdings-cosign" during the transition.
|
| 25 |
+
* map/state and inbox surface ONLY live, state-changing items. Empty buffers
|
| 26 |
+
return empty arrays (the UI renders an honest IDLE / calm message).
|
| 27 |
+
|
| 28 |
+
Register from serve.py, BEFORE the SPA catch-all:
|
| 29 |
+
|
| 30 |
+
import operator_shell_v4 as _osh
|
| 31 |
+
_osh.register(app, "a11oy")
|
| 32 |
+
"""
|
| 33 |
+
from __future__ import annotations
|
| 34 |
+
|
| 35 |
+
import asyncio
|
| 36 |
+
import json
|
| 37 |
+
import os
|
| 38 |
+
import time
|
| 39 |
+
from datetime import datetime, timezone
|
| 40 |
+
from pathlib import Path
|
| 41 |
+
from typing import Any
|
| 42 |
+
|
| 43 |
+
from fastapi import Request
|
| 44 |
+
from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
|
| 45 |
+
|
| 46 |
+
try:
|
| 47 |
+
import szl_dsse as _dsse # the LIVE signing module
|
| 48 |
+
except Exception: # pragma: no cover
|
| 49 |
+
_dsse = None
|
| 50 |
+
|
| 51 |
+
ISO = lambda: datetime.now(timezone.utc).isoformat()
|
| 52 |
+
|
| 53 |
+
# --------------------------------------------------------------------------- #
|
| 54 |
+
# Sovereign LLM (Warhacker directive, founder 2026-06-01): the Cmd-K
|
| 55 |
+
# natural-language path routes to a LOCAL LLM ONLY (Qwen2.5-7B-Instruct AWQ via
|
| 56 |
+
# vLLM on the 4060 Ti tower). NEVER a cloud API. If the local endpoint is
|
| 57 |
+
# unreachable, return an honest deterministic stub + a signed fallback receipt.
|
| 58 |
+
# Commercial cloud routing (a11oy.code) is intentionally NOT in this path.
|
| 59 |
+
# --------------------------------------------------------------------------- #
|
| 60 |
+
LOCAL_LLM_BASE = os.environ.get("SZL_LOCAL_LLM_BASE", "http://local-llm:8000/v1")
|
| 61 |
+
LOCAL_LLM_MODEL = os.environ.get("SZL_LOCAL_LLM_MODEL", "Qwen2.5-7B-Instruct-AWQ")
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def _local_llm_nl_to_command(text: str) -> dict[str, Any]:
|
| 65 |
+
"""Map a natural-language phrase to a slash command using the LOCAL LLM only.
|
| 66 |
+
Returns {command, source, fallback}. On any failure -> deterministic stub
|
| 67 |
+
(keyword heuristic) with fallback=True so the caller signs a FALLBACK receipt."""
|
| 68 |
+
sys_prompt = ("You translate an operator phrase into ONE slash command from: "
|
| 69 |
+
"/sign /verify /inspect /replay /gate /khipu /filter /yuyay /track. "
|
| 70 |
+
"Reply with ONLY the command line, no prose.")
|
| 71 |
+
try:
|
| 72 |
+
import urllib.request
|
| 73 |
+
req = urllib.request.Request(
|
| 74 |
+
f"{LOCAL_LLM_BASE}/chat/completions",
|
| 75 |
+
data=json.dumps({"model": LOCAL_LLM_MODEL, "max_tokens": 32, "temperature": 0,
|
| 76 |
+
"messages": [{"role": "system", "content": sys_prompt},
|
| 77 |
+
{"role": "user", "content": text}]}).encode(),
|
| 78 |
+
headers={"Content-Type": "application/json"})
|
| 79 |
+
with urllib.request.urlopen(req, timeout=4) as r:
|
| 80 |
+
out = json.loads(r.read())
|
| 81 |
+
cmd = out["choices"][0]["message"]["content"].strip().splitlines()[0]
|
| 82 |
+
return {"command": cmd, "source": f"local:{LOCAL_LLM_MODEL}", "fallback": False}
|
| 83 |
+
except Exception as e:
|
| 84 |
+
# Honest deterministic stub — NEVER a cloud call.
|
| 85 |
+
t = text.lower()
|
| 86 |
+
guess = "/inspect " + text.strip()
|
| 87 |
+
for kw, c in (("sign", "/sign"), ("verif", "/verify"), ("replay", "/replay"),
|
| 88 |
+
("gate", "/gate"), ("filter", "/filter"), ("track", "/track")):
|
| 89 |
+
if kw in t:
|
| 90 |
+
guess = c + " " + text.strip()
|
| 91 |
+
break
|
| 92 |
+
return {"command": guess, "source": "deterministic-stub", "fallback": True,
|
| 93 |
+
"note": f"Local LLM offline ({LOCAL_LLM_BASE}) — falling back to deterministic stub."}
|
| 94 |
+
PER_ORGAN_KEYID = {"a11oy": "a11oy-cosign", "sentra": "sentra-cosign",
|
| 95 |
+
"amaru": "amaru-cosign", "killinchu": "killinchu-cosign"}
|
| 96 |
+
DOCTRINE = {"version": "v11", "counts": "749/14/163", "lean_sha": "c7c0ba17",
|
| 97 |
+
"numbers": {"declarations": 749, "axioms": 14, "sorries": 163}}
|
| 98 |
+
|
| 99 |
+
# In-process live event ring (real events appended by /command + organ hooks).
|
| 100 |
+
# Never pre-seeded with fake data — empty until something actually happens.
|
| 101 |
+
_RING: dict[str, list[dict]] = {}
|
| 102 |
+
_INBOX: dict[str, list[dict]] = {}
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def _ring(organ: str) -> list[dict]:
|
| 106 |
+
return _RING.setdefault(organ, [])
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def _now_minus(seconds: float) -> float:
|
| 110 |
+
return time.time() - seconds
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def _local_llm_online() -> bool:
|
| 114 |
+
"""Bounded local-only reachability probe (never a cloud call)."""
|
| 115 |
+
try:
|
| 116 |
+
import urllib.request
|
| 117 |
+
with urllib.request.urlopen(f"{LOCAL_LLM_BASE}/models", timeout=1.5) as r:
|
| 118 |
+
return r.status == 200
|
| 119 |
+
except Exception:
|
| 120 |
+
return False
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
# --------------------------------------------------------------------------- #
|
| 124 |
+
# Receipt signing (delegates to the live szl_dsse)
|
| 125 |
+
# --------------------------------------------------------------------------- #
|
| 126 |
+
def _sign_receipt(organ: str, action_verb: str, action_target: str, verdict: str = "pass") -> dict[str, Any]:
|
| 127 |
+
receipt = {
|
| 128 |
+
"organ": organ,
|
| 129 |
+
"keyid_label": PER_ORGAN_KEYID.get(organ, "szlholdings-cosign"),
|
| 130 |
+
"action_verb": action_verb,
|
| 131 |
+
"action_target": action_target,
|
| 132 |
+
"verdict": verdict,
|
| 133 |
+
"lean_sha": DOCTRINE["lean_sha"],
|
| 134 |
+
"doctrine": DOCTRINE["version"],
|
| 135 |
+
"doctrine_numbers": DOCTRINE["numbers"],
|
| 136 |
+
"ts": ISO(),
|
| 137 |
+
}
|
| 138 |
+
if _dsse is None:
|
| 139 |
+
return {"receipt": receipt, "dsse": {"signed": False,
|
| 140 |
+
"honesty": "szl_dsse module unavailable in this runtime; no signature."}}
|
| 141 |
+
signed = _dsse.sign_khipu_receipt(receipt)
|
| 142 |
+
env = signed.get("dsse", {})
|
| 143 |
+
sha = env.get("_pae_sha256")
|
| 144 |
+
signed["receipt"]["receipt_sha"] = ("sha256:" + sha) if sha else None
|
| 145 |
+
signed["receipt"]["keyid"] = (env.get("signatures") or [{}])[0].get("keyid", "szlholdings-cosign")
|
| 146 |
+
signed["receipt"]["signed"] = bool(env.get("signed"))
|
| 147 |
+
return signed
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
# --------------------------------------------------------------------------- #
|
| 151 |
+
# Per-organ map/state builders — surface ONLY live items (noise rule). These
|
| 152 |
+
# read from the live ring; if empty, return empty arrays so the UI renders IDLE.
|
| 153 |
+
# --------------------------------------------------------------------------- #
|
| 154 |
+
def _map_state(organ: str) -> dict[str, Any]:
|
| 155 |
+
ring = _ring(organ)
|
| 156 |
+
recent = [e for e in ring if e.get("ts_epoch", 0) > _now_minus(86400)] # last 24h
|
| 157 |
+
base = {"organ": organ, "lean_sha": DOCTRINE["lean_sha"], "ts": ISO()}
|
| 158 |
+
if organ == "a11oy":
|
| 159 |
+
knots = [{"receipt_sha": e.get("receipt_sha"), "ts": e.get("ts"),
|
| 160 |
+
"verdict": e.get("verdict", "pass"), "action_verb": e.get("action_verb"),
|
| 161 |
+
"action_target": e.get("action_target"), "t": (i + 1) / (len(recent) + 1)}
|
| 162 |
+
for i, e in enumerate(recent)]
|
| 163 |
+
# gates: only those that fired today
|
| 164 |
+
gates_fired = {}
|
| 165 |
+
for e in recent:
|
| 166 |
+
g = e.get("gate")
|
| 167 |
+
if g:
|
| 168 |
+
gates_fired[g] = {"id": g, "label": g, "last_eval_ts": e.get("ts"), "active": True}
|
| 169 |
+
return {**base, "kind": "khipu_spine", "knots": knots, "gates": list(gates_fired.values())}
|
| 170 |
+
if organ == "sentra":
|
| 171 |
+
sigs = {}
|
| 172 |
+
parts = []
|
| 173 |
+
for e in recent:
|
| 174 |
+
s = e.get("signature")
|
| 175 |
+
if s:
|
| 176 |
+
sigs.setdefault(s, {"id": s, "label": s, "activity": 0.0})
|
| 177 |
+
sigs[s]["activity"] = min(1.0, sigs[s]["activity"] + 0.2)
|
| 178 |
+
parts.append({"id": e.get("receipt_sha"), "verdict": e.get("verdict", "pass"),
|
| 179 |
+
"event_sha": e.get("receipt_sha")})
|
| 180 |
+
return {**base, "kind": "immune_cathedral", "signatures": list(sigs.values()),
|
| 181 |
+
"particles": parts[-30:], "core": {"label": "SZL stack"}}
|
| 182 |
+
if organ == "amaru":
|
| 183 |
+
# 13 axes from the most recent tick if present; PROVED formulas always visible
|
| 184 |
+
last = recent[-1] if recent else {}
|
| 185 |
+
axes = last.get("axes") or []
|
| 186 |
+
formulas = [{"id": f, "proved": True, "recent": False} for f in ("F1", "F11", "F12", "F18", "F19")]
|
| 187 |
+
for e in recent:
|
| 188 |
+
fid = e.get("formula")
|
| 189 |
+
if fid and fid not in ("F1", "F11", "F12", "F18", "F19"):
|
| 190 |
+
formulas.append({"id": fid, "proved": False, "recent": True})
|
| 191 |
+
return {**base, "kind": "yuyay_cortex", "axes": axes,
|
| 192 |
+
"lambda": last.get("lambda"), "chakras": last.get("chakras") or [],
|
| 193 |
+
"formulas": formulas, "in_flight": bool(last.get("in_flight"))}
|
| 194 |
+
if organ == "killinchu":
|
| 195 |
+
tracks = [{"id": e.get("track"), "lat": e.get("lat"), "lon": e.get("lon"),
|
| 196 |
+
"verdict": e.get("verdict", "warn"), "ts": e.get("ts")}
|
| 197 |
+
for e in ring if e.get("track") and e.get("ts_epoch", 0) > _now_minus(60)] # last 60s
|
| 198 |
+
return {**base, "kind": "killinchu_globe", "officers": last_officers(organ), "tracks": tracks}
|
| 199 |
+
return {**base, "kind": "unknown"}
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def last_officers(organ: str) -> list[dict]:
|
| 203 |
+
# 4 superhero orbital cards; activity drives orbit distance (busy=closer)
|
| 204 |
+
names = [("Sentra", "immune"), ("Amaru", "cortex"), ("a11oy", "governance"), ("Rosie", "aide")]
|
| 205 |
+
ring = _ring(organ)
|
| 206 |
+
out = []
|
| 207 |
+
for n, role in names:
|
| 208 |
+
recent = sum(1 for e in ring if e.get("officer") == n and e.get("ts_epoch", 0) > _now_minus(3600))
|
| 209 |
+
out.append({"name": n, "role": role, "activity": min(1.0, recent / 10.0)})
|
| 210 |
+
return out
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
# --------------------------------------------------------------------------- #
|
| 214 |
+
# Command handler — runs the verb, appends a real event, signs a receipt.
|
| 215 |
+
# --------------------------------------------------------------------------- #
|
| 216 |
+
def _handle_command(organ: str, command: str, args: dict) -> dict[str, Any]:
|
| 217 |
+
parts = command.strip().split()
|
| 218 |
+
if not parts:
|
| 219 |
+
return {"ok": False, "message": "empty command", "receipt": None}
|
| 220 |
+
aliases = {"s": "/sign", "v": "/verify", "i": "/inspect", "r": "/replay"}
|
| 221 |
+
nl_meta = None
|
| 222 |
+
if not command.strip().startswith("/") and parts[0] not in aliases:
|
| 223 |
+
# natural-language phrase -> resolve via LOCAL LLM only (founder directive)
|
| 224 |
+
nl_meta = _local_llm_nl_to_command(command.strip())
|
| 225 |
+
command = nl_meta["command"]
|
| 226 |
+
parts = command.strip().split()
|
| 227 |
+
verb = parts[0]
|
| 228 |
+
if verb in aliases:
|
| 229 |
+
verb = aliases[verb]
|
| 230 |
+
target = " ".join(parts[1:]) or args.get("target", "")
|
| 231 |
+
verb_clean = verb.lstrip("/")
|
| 232 |
+
|
| 233 |
+
if verb_clean == "verify":
|
| 234 |
+
# verify an existing receipt sha via szl_dsse if we have the envelope
|
| 235 |
+
ok = _dsse is not None
|
| 236 |
+
return {"ok": ok, "message": f"verify {target}: " + ("cosign-verifiable" if ok else "signing module unavailable"),
|
| 237 |
+
"receipt": None}
|
| 238 |
+
|
| 239 |
+
signed = _sign_receipt(organ, verb_clean, target, verdict="pass")
|
| 240 |
+
if nl_meta is not None:
|
| 241 |
+
signed["receipt"]["nl_route"] = nl_meta # records local LLM source or honest fallback
|
| 242 |
+
evt = {
|
| 243 |
+
"ts": signed["receipt"].get("ts"), "ts_epoch": time.time(),
|
| 244 |
+
"action_verb": verb_clean, "action_target": target,
|
| 245 |
+
"verdict": "pass", "receipt_sha": signed["receipt"].get("receipt_sha"),
|
| 246 |
+
"keyid": signed["receipt"].get("keyid"),
|
| 247 |
+
"signed": signed["receipt"].get("signed"),
|
| 248 |
+
}
|
| 249 |
+
if nl_meta is not None:
|
| 250 |
+
evt["nl_source"] = nl_meta.get("source")
|
| 251 |
+
evt["nl_fallback"] = nl_meta.get("fallback")
|
| 252 |
+
_ring(organ).append(evt)
|
| 253 |
+
msg = f"{verb_clean} → signed receipt"
|
| 254 |
+
if nl_meta and nl_meta.get("fallback"):
|
| 255 |
+
msg = nl_meta["note"] + f" Resolved → {command}; signed FALLBACK receipt."
|
| 256 |
+
elif nl_meta:
|
| 257 |
+
msg = f"local LLM → {command}; signed receipt."
|
| 258 |
+
return {"ok": True, "message": msg, "receipt": signed,
|
| 259 |
+
"map_delta": {"type": "receipt", "data": evt}}
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
# --------------------------------------------------------------------------- #
|
| 263 |
+
# SSE stream — emits real ring events as they arrive (heartbeat keeps alive).
|
| 264 |
+
# --------------------------------------------------------------------------- #
|
| 265 |
+
async def _stream(organ: str):
|
| 266 |
+
last = len(_ring(organ))
|
| 267 |
+
yield f"data: {json.dumps({'type':'hello','organ':organ,'ts':ISO()})}\n\n"
|
| 268 |
+
while True:
|
| 269 |
+
ring = _ring(organ)
|
| 270 |
+
if len(ring) > last:
|
| 271 |
+
for evt in ring[last:]:
|
| 272 |
+
yield f"data: {json.dumps({'type':'receipt','data':evt})}\n\n"
|
| 273 |
+
last = len(ring)
|
| 274 |
+
else:
|
| 275 |
+
yield f": heartbeat {ISO()}\n\n" # SSE comment heartbeat
|
| 276 |
+
await asyncio.sleep(2.0)
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
# --------------------------------------------------------------------------- #
|
| 280 |
+
# register
|
| 281 |
+
# --------------------------------------------------------------------------- #
|
| 282 |
+
def register(app, organ: str, web_dir: str | None = None) -> dict[str, Any]:
|
| 283 |
+
p = f"/api/{organ}/v4"
|
| 284 |
+
here = Path(web_dir) if web_dir else Path(__file__).resolve().parent / "web"
|
| 285 |
+
html = here / "operator.html"
|
| 286 |
+
|
| 287 |
+
@app.get(f"{p}/healthz")
|
| 288 |
+
async def _healthz():
|
| 289 |
+
return JSONResponse({"status": "ok", "service": organ, "shell": "operator-v4",
|
| 290 |
+
"doctrine": DOCTRINE["version"], "counts": DOCTRINE["counts"],
|
| 291 |
+
"lean_sha": DOCTRINE["lean_sha"], "keyid_label": PER_ORGAN_KEYID.get(organ),
|
| 292 |
+
"signing_available": (_dsse.signing_available() if _dsse else False),
|
| 293 |
+
"sovereign": True, # no cloud API in the demo path (founder directive)
|
| 294 |
+
"llm": {"mode": "local-only", "base": LOCAL_LLM_BASE, "model": LOCAL_LLM_MODEL,
|
| 295 |
+
"cloud": False}, "local_llm_online": _local_llm_online(),
|
| 296 |
+
"ts": ISO()})
|
| 297 |
+
|
| 298 |
+
@app.get(f"{p}/inbox")
|
| 299 |
+
async def _inbox():
|
| 300 |
+
return JSONResponse(_INBOX.get(organ, [])) # active items only; empty -> calm UI
|
| 301 |
+
|
| 302 |
+
@app.get(f"{p}/map/state")
|
| 303 |
+
async def _state():
|
| 304 |
+
return JSONResponse(_map_state(organ))
|
| 305 |
+
|
| 306 |
+
@app.post(f"{p}/command")
|
| 307 |
+
async def _command(req: Request):
|
| 308 |
+
body = await req.json()
|
| 309 |
+
return JSONResponse(_handle_command(organ, body.get("command", ""), body.get("args", {})))
|
| 310 |
+
|
| 311 |
+
@app.get(f"{p}/receipts")
|
| 312 |
+
async def _receipts(since: str | None = None, limit: int = 50):
|
| 313 |
+
ring = list(reversed(_ring(organ)))[: min(int(limit), 500)]
|
| 314 |
+
rows = [{"ts": e.get("ts"), "keyid": e.get("keyid"), "action_verb": e.get("action_verb"),
|
| 315 |
+
"action_target": e.get("action_target"), "verdict": e.get("verdict"),
|
| 316 |
+
"receipt_sha": e.get("receipt_sha"), "verify": bool(e.get("signed")),
|
| 317 |
+
"lean_sha": DOCTRINE["lean_sha"], "doctrine": DOCTRINE["version"]}
|
| 318 |
+
for e in ring if e.get("receipt_sha")]
|
| 319 |
+
return JSONResponse(rows)
|
| 320 |
+
|
| 321 |
+
@app.get(f"{p}/replay/{{chain_hash}}")
|
| 322 |
+
async def _replay(chain_hash: str, frame: int = 0):
|
| 323 |
+
ring = _ring(organ)
|
| 324 |
+
if frame < 0 or frame >= len(ring):
|
| 325 |
+
return JSONResponse({"error": "frame out of range", "frames": len(ring)}, status_code=404)
|
| 326 |
+
# reconstruct state at frame N (state up to and including that receipt)
|
| 327 |
+
sub = ring[: frame + 1]
|
| 328 |
+
evt = ring[frame]
|
| 329 |
+
return JSONResponse({"chain_hash": chain_hash, "frame": frame, "frames": len(ring),
|
| 330 |
+
"receipt": evt, "doctrine": DOCTRINE["version"], "lean_sha": DOCTRINE["lean_sha"],
|
| 331 |
+
"cumulative": len(sub)})
|
| 332 |
+
|
| 333 |
+
@app.get(f"{p}/stream")
|
| 334 |
+
async def _stream_route():
|
| 335 |
+
return StreamingResponse(_stream(organ), media_type="text/event-stream",
|
| 336 |
+
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"})
|
| 337 |
+
|
| 338 |
+
async def _serve_html():
|
| 339 |
+
if html.exists():
|
| 340 |
+
return FileResponse(str(html))
|
| 341 |
+
return JSONResponse({"error": "operator.html not deployed"}, status_code=404)
|
| 342 |
+
|
| 343 |
+
app.get(f"/{organ}/operator")(_serve_html)
|
| 344 |
+
app.get("/operator")(_serve_html)
|
| 345 |
+
|
| 346 |
+
return {"registered": True, "organ": organ, "base": p,
|
| 347 |
+
"routes": [f"{p}/inbox", f"{p}/map/state", f"{p}/command", f"{p}/receipts",
|
| 348 |
+
f"{p}/replay/{{hash}}", f"{p}/stream", f"{p}/healthz",
|
| 349 |
+
f"/{organ}/operator", "/operator"],
|
| 350 |
+
"signing_available": (_dsse.signing_available() if _dsse else False)}
|
serve.py
CHANGED
|
@@ -1568,6 +1568,25 @@ except Exception as _ue:
|
|
| 1568 |
print(f"[szl_unay] UNAY+Khipu-LMDB v2 NOT mounted ({_ue!r}); existing routes unaffected", file=_sysu.stderr)
|
| 1569 |
|
| 1570 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1571 |
@app.get("/{path:path}")
|
| 1572 |
async def catch_all(path: str):
|
| 1573 |
# Console routes — serve from CONSOLE_DIR
|
|
|
|
| 1568 |
print(f"[szl_unay] UNAY+Khipu-LMDB v2 NOT mounted ({_ue!r}); existing routes unaffected", file=_sysu.stderr)
|
| 1569 |
|
| 1570 |
|
| 1571 |
+
# ---------------------------------------------------------------------------
|
| 1572 |
+
# ADDITIVE (Unified Operator Shell v4, 2026-06-01, Yachay / Perplexity Computer
|
| 1573 |
+
# Agent): register the 7 v4 operator-shell endpoints + /operator desktop shell
|
| 1574 |
+
# BEFORE the SPA catch-all so they resolve LOCALLY. try/except-guarded: a missing
|
| 1575 |
+
# dep can NEVER take down the SPA or any existing route. Receipts sign live via
|
| 1576 |
+
# szl_dsse (cosign ECDSA-P256/DSSE). Doctrine v11 LOCKED 749/14/163 (public).
|
| 1577 |
+
# ---------------------------------------------------------------------------
|
| 1578 |
+
try:
|
| 1579 |
+
import operator_shell_v4 as _osh_v4
|
| 1580 |
+
_osh_v4_status = _osh_v4.register(app, "sentra", web_dir="/app/web")
|
| 1581 |
+
import sys as _osh_sys
|
| 1582 |
+
print(f"[sentra] Operator Shell v4 registered: {_osh_v4_status}", file=_osh_sys.stderr)
|
| 1583 |
+
except Exception as _osh_e:
|
| 1584 |
+
import traceback as _osh_tb, sys as _osh_sys
|
| 1585 |
+
print(f"[sentra] Operator Shell v4 NOT registered: {_osh_e!r}", file=_osh_sys.stderr)
|
| 1586 |
+
_osh_tb.print_exc()
|
| 1587 |
+
# --- end Operator Shell v4 ---
|
| 1588 |
+
|
| 1589 |
+
|
| 1590 |
@app.get("/{path:path}")
|
| 1591 |
async def catch_all(path: str):
|
| 1592 |
# Console routes — serve from CONSOLE_DIR
|
web/operator.html
ADDED
|
@@ -0,0 +1,584 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<!-- operator-shell — Unified Operator Shell · flagship: sentra
|
| 3 |
+
4-zone cockpit (Inbox · Map · Command Bar · Receipt Drawer · Footer).
|
| 4 |
+
Three.js r171 WebGL2 living scene. DESKTOP-FIRST (1280px+).
|
| 5 |
+
Sign: Yachay <yachay@szlholdings.dev> — DCO · ADDITIVE · SPDX: Apache-2.0
|
| 6 |
+
© 2026 Lutar, Stephen P. — SZL Holdings · ORCID 0009-0001-0110-4173 -->
|
| 7 |
+
<html lang="en">
|
| 8 |
+
<head>
|
| 9 |
+
<meta charset="utf-8" />
|
| 10 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 11 |
+
<title>Sentra — Operator Shell</title>
|
| 12 |
+
<style>/* operator-shell / shared / shell.css — 4-zone cockpit layout (DESKTOP-FIRST 1280px+)
|
| 13 |
+
* Sign: Yachay <yachay@szlholdings.dev> — DCO · ADDITIVE · SPDX: Apache-2.0 */
|
| 14 |
+
:root{
|
| 15 |
+
--bg-base:#0a0e14;--bg-elevated:#11161e;--bg-card:#161c26;--accent-gold:#e3b04b;
|
| 16 |
+
--accent-emerald:#10b981;--accent-amber:#f59e0b;--accent-rose:#f43f5e;
|
| 17 |
+
--text-primary:#f1f5f9;--text-secondary:#94a3b8;--text-muted:#475569;
|
| 18 |
+
--border-subtle:#1e293b;--khipu-knot:#cd8a3d;--font-mono:ui-monospace,Menlo,monospace;
|
| 19 |
+
--font-display:ui-sans-serif,Inter,system-ui;--radius-card:12px;--shadow-deep:0 10px 40px -10px rgba(0,0,0,.6);
|
| 20 |
+
}
|
| 21 |
+
*{box-sizing:border-box}
|
| 22 |
+
html,body{margin:0;height:100%;background:var(--bg-base);color:var(--text-primary);font-family:var(--font-display)}
|
| 23 |
+
.urgency-info,.verdict-pass{--u:var(--accent-emerald)}
|
| 24 |
+
.urgency-warn,.verdict-warn{--u:var(--accent-amber)}
|
| 25 |
+
.urgency-critical,.verdict-block{--u:var(--accent-rose)}
|
| 26 |
+
|
| 27 |
+
/* 4-zone grid: Inbox left, Map center, Drawer right, Footer bottom */
|
| 28 |
+
.shell{display:grid;grid-template-columns:340px 1fr 300px;grid-template-rows:1fr 36px;
|
| 29 |
+
grid-template-areas:"inbox map drawer" "footer footer footer";height:100vh;gap:1px;background:var(--border-subtle)}
|
| 30 |
+
.zone{background:var(--bg-base);overflow:hidden;position:relative}
|
| 31 |
+
.zone-inbox{grid-area:inbox;padding:14px;overflow-y:auto}
|
| 32 |
+
.zone-map{grid-area:map;background:radial-gradient(circle at 50% 40%,#0d1420,var(--bg-base))}
|
| 33 |
+
.zone-map canvas{width:100%;height:100%;display:block}
|
| 34 |
+
.zone-drawer{grid-area:drawer;padding:14px;overflow-y:auto;perspective:600px}
|
| 35 |
+
.zone-footer{grid-area:footer;background:var(--bg-elevated);display:flex;align-items:center;gap:18px;
|
| 36 |
+
padding:0 16px;font-family:var(--font-mono);font-size:12px;color:var(--text-secondary)}
|
| 37 |
+
|
| 38 |
+
/* Inbox */
|
| 39 |
+
.zone-inbox h2{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--text-muted);margin:0 0 12px}
|
| 40 |
+
.inbox-calm{color:var(--text-secondary);font-size:14px;line-height:1.6;padding:32px 8px;text-align:center;opacity:.8}
|
| 41 |
+
.inbox-card{background:var(--bg-card);border:1px solid var(--border-subtle);border-left:3px solid var(--u);
|
| 42 |
+
border-radius:var(--radius-card);padding:12px 14px;margin-bottom:10px;cursor:pointer;transition:transform .15s,box-shadow .15s;position:relative}
|
| 43 |
+
.inbox-card:hover{transform:translateY(-3px) scale(1.01);box-shadow:var(--shadow-deep)}
|
| 44 |
+
.inbox-card.flipped .ic-face{display:none}.inbox-card.flipped .ic-back{display:block}
|
| 45 |
+
.ic-back{display:none}.ic-back pre{font-family:var(--font-mono);font-size:11px;color:var(--text-secondary);white-space:pre-wrap;margin:0}
|
| 46 |
+
.ic-dot{position:absolute;top:14px;right:14px;width:8px;height:8px;border-radius:50%;background:var(--u);box-shadow:0 0 8px var(--u)}
|
| 47 |
+
.ic-title{font-weight:600;font-size:13px;margin-bottom:4px;padding-right:16px}
|
| 48 |
+
.ic-sub{font-size:12px;color:var(--text-secondary);line-height:1.4;margin-bottom:10px}
|
| 49 |
+
.ic-acts{display:flex;gap:8px}
|
| 50 |
+
.ic-act{background:transparent;border:1px solid var(--border-subtle);color:var(--text-secondary);
|
| 51 |
+
border-radius:7px;padding:5px 11px;font-size:12px;cursor:pointer;font-family:var(--font-mono)}
|
| 52 |
+
.ic-act.primary{background:var(--accent-gold);color:#0a0e14;border-color:var(--accent-gold);font-weight:600}
|
| 53 |
+
.ic-act:hover{border-color:var(--accent-gold)}
|
| 54 |
+
.inbox-more{text-align:center;color:var(--text-muted);font-size:12px;font-family:var(--font-mono);padding:6px}
|
| 55 |
+
|
| 56 |
+
/* Command bar (Cmd-K) */
|
| 57 |
+
.cmdk-overlay{position:fixed;inset:0;background:rgba(5,8,12,.6);backdrop-filter:blur(4px);z-index:1000;display:flex;justify-content:center;padding-top:14vh}
|
| 58 |
+
.cmdk-box{width:560px;max-width:92vw;height:fit-content;background:var(--bg-elevated);border:1px solid var(--border-subtle);
|
| 59 |
+
border-radius:14px;box-shadow:var(--shadow-deep);overflow:hidden}
|
| 60 |
+
.cmdk-input{width:100%;background:transparent;border:0;border-bottom:1px solid var(--border-subtle);
|
| 61 |
+
color:var(--text-primary);font-size:16px;padding:16px 18px;outline:none;font-family:var(--font-mono)}
|
| 62 |
+
.cmdk-list{max-height:46vh;overflow-y:auto;padding:6px}
|
| 63 |
+
.cmdk-group{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--text-muted);padding:8px 12px 4px}
|
| 64 |
+
.cmdk-row{display:flex;justify-content:space-between;padding:8px 12px;border-radius:8px;cursor:pointer}
|
| 65 |
+
.cmdk-row.sel,.cmdk-row:hover{background:var(--bg-card)}
|
| 66 |
+
.cmdk-cmd{font-family:var(--font-mono);font-size:13px;color:var(--accent-gold)}
|
| 67 |
+
.cmdk-desc{font-size:12px;color:var(--text-secondary);margin-left:14px;text-align:right}
|
| 68 |
+
|
| 69 |
+
/* Receipt drawer (z-tunnel) */
|
| 70 |
+
.zone-drawer h2{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--text-muted);margin:0 0 12px}
|
| 71 |
+
.rcpt-chip{display:flex;align-items:center;gap:8px;background:var(--bg-card);border:1px solid var(--border-subtle);
|
| 72 |
+
border-right:3px solid var(--u,var(--accent-emerald));border-radius:9px;padding:8px 10px;margin-bottom:8px;
|
| 73 |
+
font-family:var(--font-mono);font-size:11px;cursor:pointer;
|
| 74 |
+
transform:translateZ(calc(var(--z,0)*-6px)) scale(calc(1 - var(--z,0)*0.012));opacity:calc(1 - var(--z,0)*0.04);transition:transform .2s}
|
| 75 |
+
.rcpt-chip:hover{transform:translateZ(20px) scale(1.03);border-color:var(--accent-gold)}
|
| 76 |
+
.rc-time{color:var(--text-muted)}.rc-verb{color:var(--text-primary);flex:1}.rc-key{color:var(--text-secondary)}
|
| 77 |
+
.rc-sig.ok{color:var(--accent-emerald)}.rc-sig.pend{color:var(--text-muted)}
|
| 78 |
+
.drawer-idle{color:var(--text-muted);font-size:12px;padding:20px 4px;text-align:center}
|
| 79 |
+
|
| 80 |
+
/* Footer */
|
| 81 |
+
.ft-pill{width:10px;height:10px;border-radius:50%;margin-left:auto;box-shadow:0 0 8px currentColor}
|
| 82 |
+
.ft-knot{color:var(--khipu-knot)}.ft-lean{color:var(--text-muted)}
|
| 83 |
+
.ft-sov{color:var(--accent-gold);font-weight:600}.ft-sov.off{color:var(--accent-amber);opacity:.85}
|
| 84 |
+
.ft-doc{color:var(--text-secondary)}
|
| 85 |
+
|
| 86 |
+
/* Tablet degrade (NOT a priority) */
|
| 87 |
+
@media(max-width:1280px){.shell{grid-template-columns:300px 1fr}.zone-drawer{display:none}}
|
| 88 |
+
@media(max-width:768px){.shell{grid-template-columns:1fr;grid-template-areas:"map" "footer";grid-template-rows:1fr 36px}.zone-inbox{display:none}}
|
| 89 |
+
</style>
|
| 90 |
+
</head>
|
| 91 |
+
<body>
|
| 92 |
+
<div class="shell">
|
| 93 |
+
<section class="zone zone-inbox" aria-label="Inbox">
|
| 94 |
+
<h2>Inbox</h2>
|
| 95 |
+
<div id="inbox"></div>
|
| 96 |
+
</section>
|
| 97 |
+
<section class="zone zone-map" aria-label="Map — Immune Cathedral">
|
| 98 |
+
<canvas id="map"></canvas>
|
| 99 |
+
</section>
|
| 100 |
+
<section class="zone zone-drawer" aria-label="Receipts">
|
| 101 |
+
<h2>Receipts 🪢</h2>
|
| 102 |
+
<div id="drawer"></div>
|
| 103 |
+
</section>
|
| 104 |
+
<footer class="zone zone-footer" id="footer"></footer>
|
| 105 |
+
</div>
|
| 106 |
+
|
| 107 |
+
<script type="module">
|
| 108 |
+
import * as THREE from "https://esm.sh/three@0.171.0";
|
| 109 |
+
import { OrbitControls } from "https://esm.sh/three@0.171.0/examples/jsm/controls/OrbitControls.js";
|
| 110 |
+
/* =============================================================================
|
| 111 |
+
* operator-shell / shared / scenes.js
|
| 112 |
+
* Shared Three.js (r171+, WebGL2) living-scene modules for the Unified Operator
|
| 113 |
+
* Shell Map zone. One module per flagship body + shared 3D card stack & receipt
|
| 114 |
+
* ribbon helpers. Every scene is driven by GET /api/<organ>/v4/map/state and the
|
| 115 |
+
* live WS /api/<organ>/v4/stream — knots/particles/pulses tick on REAL DSSE
|
| 116 |
+
* receipts, never mocks. Empty buffers render an honest IDLE (never faked).
|
| 117 |
+
*
|
| 118 |
+
* Brand: Quechua names are brand, never translated. 🪢 = Khipu chain.
|
| 119 |
+
* Sign: Yachay <yachay@szlholdings.dev> — DCO · ADDITIVE
|
| 120 |
+
* SPDX-License-Identifier: Apache-2.0
|
| 121 |
+
* © 2026 Lutar, Stephen P. — SZL Holdings · ORCID 0009-0001-0110-4173
|
| 122 |
+
* ========================================================================== */
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
const TOKENS = {
|
| 127 |
+
bgBase: 0x0a0e14, emerald: 0x10b981, amber: 0xf59e0b, rose: 0xf43f5e,
|
| 128 |
+
gold: 0xe3b04b, knot: 0xcd8a3d, textMuted: 0x475569,
|
| 129 |
+
};
|
| 130 |
+
const VERDICT_COLOR = { pass: TOKENS.emerald, info: TOKENS.emerald, warn: TOKENS.amber, block: TOKENS.rose, ambiguous: TOKENS.amber, critical: TOKENS.rose };
|
| 131 |
+
const col = (v) => new THREE.Color(VERDICT_COLOR[v] ?? TOKENS.textMuted);
|
| 132 |
+
|
| 133 |
+
/* ---- Renderer factory (60fps target on RTX 4060 Ti, WebGL2) -------------- */
|
| 134 |
+
function makeStage(canvas) {
|
| 135 |
+
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true, powerPreference: "high-performance" });
|
| 136 |
+
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
| 137 |
+
const scene = new THREE.Scene();
|
| 138 |
+
const camera = new THREE.PerspectiveCamera(50, 1, 0.1, 2000);
|
| 139 |
+
camera.position.set(0, 0, 60);
|
| 140 |
+
const controls = new OrbitControls(camera, canvas);
|
| 141 |
+
controls.enableDamping = true; controls.dampingFactor = 0.08;
|
| 142 |
+
scene.add(new THREE.AmbientLight(0xffffff, 0.55));
|
| 143 |
+
const key = new THREE.PointLight(0xffffff, 1.1); key.position.set(30, 40, 60); scene.add(key);
|
| 144 |
+
function resize() {
|
| 145 |
+
const w = canvas.clientWidth || canvas.parentElement.clientWidth, h = canvas.clientHeight || canvas.parentElement.clientHeight;
|
| 146 |
+
renderer.setSize(w, h, false); camera.aspect = w / h; camera.updateProjectionMatrix();
|
| 147 |
+
}
|
| 148 |
+
window.addEventListener("resize", resize); resize();
|
| 149 |
+
return { renderer, scene, camera, controls, resize };
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
/* ---- IDLE placeholder: shown honestly when a buffer is empty ------------- */
|
| 153 |
+
function idleSprite(text) {
|
| 154 |
+
const c = document.createElement("canvas"); c.width = 512; c.height = 64;
|
| 155 |
+
const x = c.getContext("2d"); x.fillStyle = "#475569"; x.font = "28px ui-monospace, monospace";
|
| 156 |
+
x.textAlign = "center"; x.fillText(text, 256, 40);
|
| 157 |
+
const t = new THREE.CanvasTexture(c); const m = new THREE.SpriteMaterial({ map: t, transparent: true });
|
| 158 |
+
const s = new THREE.Sprite(m); s.scale.set(40, 5, 1); return s;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
/* =========================================================================
|
| 162 |
+
* a11oy — "The Khipu Spine": vertical glowing helix; each knot = a receipt.
|
| 163 |
+
* New receipts spawn at top, descend; lit by gate verdict. Lean SHA at base.
|
| 164 |
+
* Noise rule: only gates that fired today are surfaced.
|
| 165 |
+
* ====================================================================== */
|
| 166 |
+
function KhipuSpine(stage) {
|
| 167 |
+
const group = new THREE.Group(); stage.scene.add(group);
|
| 168 |
+
const ropeMat = new THREE.LineBasicMaterial({ color: TOKENS.knot, transparent: true, opacity: 0.5 });
|
| 169 |
+
const helixPts = []; for (let i = 0; i <= 200; i++) { const t = i / 200, a = t * Math.PI * 8, r = 6; helixPts.push(new THREE.Vector3(Math.cos(a) * r, 28 - t * 56, Math.sin(a) * r)); }
|
| 170 |
+
group.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(helixPts), ropeMat));
|
| 171 |
+
let knots = [], gates = [], idle = null;
|
| 172 |
+
const baseLabel = makeSpriteLabel("", TOKENS.gold); baseLabel.position.set(0, -30, 0); baseLabel.scale.set(24, 3, 1); group.add(baseLabel);
|
| 173 |
+
function setState(s) {
|
| 174 |
+
knots.forEach(k => group.remove(k.mesh)); knots = [];
|
| 175 |
+
gates.forEach(g => group.remove(g)); gates = [];
|
| 176 |
+
if (idle) { group.remove(idle); idle = null; }
|
| 177 |
+
baseLabel.material.map = textTex("Lean " + (s.lean_sha || "—"), TOKENS.gold); baseLabel.material.needsUpdate = true;
|
| 178 |
+
const ks = s.knots || [];
|
| 179 |
+
if (!ks.length) { idle = idleSprite("KHIPU SPINE · IDLE · no receipts today"); idle.position.y = 0; group.add(idle); }
|
| 180 |
+
ks.forEach(k => {
|
| 181 |
+
const t = k.t ?? 0.5, a = t * Math.PI * 8, r = 6;
|
| 182 |
+
const geo = new THREE.IcosahedronGeometry(0.9, 1);
|
| 183 |
+
const mesh = new THREE.Mesh(geo, new THREE.MeshStandardMaterial({ color: col(k.verdict), emissive: col(k.verdict), emissiveIntensity: 0.8 }));
|
| 184 |
+
mesh.position.set(Math.cos(a) * r, 28 - t * 56, Math.sin(a) * r);
|
| 185 |
+
mesh.userData = { receipt_sha: k.receipt_sha, kind: "knot" };
|
| 186 |
+
group.add(mesh); knots.push({ mesh, vy: 0 });
|
| 187 |
+
});
|
| 188 |
+
// Only gates that fired today (server already filters; we render what arrives)
|
| 189 |
+
(s.gates || []).forEach((g, i) => {
|
| 190 |
+
const ang = (i / Math.max(1, s.gates.length)) * Math.PI * 2;
|
| 191 |
+
const card = new THREE.Mesh(new THREE.PlaneGeometry(2.4, 1.2), new THREE.MeshBasicMaterial({ color: TOKENS.gold, transparent: true, opacity: g.active ? 0.9 : 0.35, side: THREE.DoubleSide }));
|
| 192 |
+
card.position.set(Math.cos(ang) * 16, (i % 5) * 4 - 8, Math.sin(ang) * 16);
|
| 193 |
+
card.userData = { gate: g.id, kind: "gate" }; group.add(card); gates.push(card);
|
| 194 |
+
});
|
| 195 |
+
}
|
| 196 |
+
function spawn(receipt) { // new receipt descends from top
|
| 197 |
+
const geo = new THREE.IcosahedronGeometry(1.1, 1);
|
| 198 |
+
const mesh = new THREE.Mesh(geo, new THREE.MeshStandardMaterial({ color: col(receipt.verdict), emissive: col(receipt.verdict), emissiveIntensity: 1.4 }));
|
| 199 |
+
mesh.position.set(0, 30, 0); mesh.userData = { receipt_sha: receipt.receipt_sha, kind: "knot" };
|
| 200 |
+
group.add(mesh); knots.push({ mesh, vy: -0.4, target: 28 - (receipt.t ?? 0.2) * 56 });
|
| 201 |
+
}
|
| 202 |
+
function tick(dt) {
|
| 203 |
+
group.rotation.y += dt * 0.08;
|
| 204 |
+
knots.forEach(k => { if (k.vy) { k.mesh.position.y += k.vy; if (k.mesh.position.y <= (k.target ?? -28)) k.vy = 0; } });
|
| 205 |
+
gates.forEach((g, i) => g.lookAt(stage.camera.position));
|
| 206 |
+
}
|
| 207 |
+
function ripple() { knots.forEach(k => k.mesh.material.emissiveIntensity = 1.6); setTimeout(() => knots.forEach(k => k.mesh.material.emissiveIntensity = 0.8), 240); }
|
| 208 |
+
return { group, setState, spawn, tick, ripple };
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
/* =========================================================================
|
| 212 |
+
* sentra — "The Immune Cathedral": geodesic sphere shell + small core.
|
| 213 |
+
* Particles fly in; malicious -> rose flare; benign -> green trail; ambiguous
|
| 214 |
+
* -> amber held in suspension. Noise rule: only signatures fired today shown.
|
| 215 |
+
* ====================================================================== */
|
| 216 |
+
function ImmuneCathedral(stage) {
|
| 217 |
+
const group = new THREE.Group(); stage.scene.add(group);
|
| 218 |
+
const shell = new THREE.Mesh(new THREE.IcosahedronGeometry(18, 2), new THREE.MeshBasicMaterial({ color: TOKENS.emerald, wireframe: true, transparent: true, opacity: 0.18 }));
|
| 219 |
+
group.add(shell);
|
| 220 |
+
const core = new THREE.Mesh(new THREE.IcosahedronGeometry(3, 1), new THREE.MeshStandardMaterial({ color: TOKENS.gold, emissive: TOKENS.gold, emissiveIntensity: 0.6 }));
|
| 221 |
+
group.add(core);
|
| 222 |
+
let sigs = [], parts = [], idle = null;
|
| 223 |
+
function setState(s) {
|
| 224 |
+
sigs.forEach(n => group.remove(n)); sigs = [];
|
| 225 |
+
if (idle) { group.remove(idle); idle = null; }
|
| 226 |
+
const sg = s.signatures || [];
|
| 227 |
+
if (!sg.length) { idle = idleSprite("IMMUNE CATHEDRAL · IDLE · no signatures fired today"); group.add(idle); }
|
| 228 |
+
sg.forEach((sig, i) => {
|
| 229 |
+
const phi = Math.acos(-1 + (2 * i) / Math.max(1, sg.length)), theta = Math.sqrt(sg.length * Math.PI) * phi;
|
| 230 |
+
const node = new THREE.Mesh(new THREE.SphereGeometry(0.6, 12, 12), new THREE.MeshStandardMaterial({ color: TOKENS.emerald, emissive: TOKENS.emerald, emissiveIntensity: 0.3 + (sig.activity || 0) }));
|
| 231 |
+
node.position.setFromSphericalCoords(18, phi, theta); node.userData = { signature: sig.id, kind: "signature" };
|
| 232 |
+
group.add(node); sigs.push(node);
|
| 233 |
+
});
|
| 234 |
+
}
|
| 235 |
+
function spawn(p) { // p.verdict: pass|block|ambiguous
|
| 236 |
+
const m = new THREE.Mesh(new THREE.SphereGeometry(0.35, 8, 8), new THREE.MeshBasicMaterial({ color: col(p.verdict) }));
|
| 237 |
+
const dir = new THREE.Vector3().randomDirection().multiplyScalar(34); m.position.copy(dir);
|
| 238 |
+
parts.push({ m, verdict: p.verdict, t: 0, sha: p.event_sha }); group.add(m);
|
| 239 |
+
}
|
| 240 |
+
function tick(dt) {
|
| 241 |
+
group.rotation.y += dt * 0.05; core.material.emissiveIntensity = 0.5 + 0.3 * Math.sin(performance.now() / 600);
|
| 242 |
+
for (let i = parts.length - 1; i >= 0; i--) {
|
| 243 |
+
const p = parts[i]; p.t += dt;
|
| 244 |
+
if (p.verdict === "block") { p.m.position.multiplyScalar(1.0 + dt * 0.4); p.m.material.color.set(TOKENS.rose); if (p.t > 1.5) { group.remove(p.m); parts.splice(i, 1); } }
|
| 245 |
+
else if (p.verdict === "ambiguous") { p.m.position.lerp(p.m.position.clone().normalize().multiplyScalar(18), dt); p.m.material.color.set(TOKENS.amber); }
|
| 246 |
+
else { p.m.position.lerp(new THREE.Vector3(0, 0, 0), dt * 0.9); if (p.m.position.length() < 3.2) { group.remove(p.m); parts.splice(i, 1); } }
|
| 247 |
+
}
|
| 248 |
+
}
|
| 249 |
+
function ripple() { shell.material.opacity = 0.5; setTimeout(() => shell.material.opacity = 0.18, 260); }
|
| 250 |
+
return { group, setState, spawn, tick, ripple };
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
/* =========================================================================
|
| 254 |
+
* amaru — "The 13-Axis Cortex": 3D radial spike chart, Λ pulsing at center.
|
| 255 |
+
* 7 chakra spheres orbit at heights. F1-F23 formula cards float as a
|
| 256 |
+
* constellation — only formulas with recent activity OR PROVED (F1,F11,F12,
|
| 257 |
+
* F18,F19) are shown (noise rule).
|
| 258 |
+
* ====================================================================== */
|
| 259 |
+
const PROVED = new Set(["F1", "F11", "F12", "F18", "F19"]);
|
| 260 |
+
function Cortex(stage) {
|
| 261 |
+
const group = new THREE.Group(); stage.scene.add(group);
|
| 262 |
+
const lambda = new THREE.Mesh(new THREE.IcosahedronGeometry(2.4, 2), new THREE.MeshStandardMaterial({ color: TOKENS.gold, emissive: TOKENS.gold, emissiveIntensity: 1.0 }));
|
| 263 |
+
group.add(lambda);
|
| 264 |
+
let spikes = [], chakras = [], formulas = [], idle = null;
|
| 265 |
+
function setState(s) {
|
| 266 |
+
spikes.forEach(x => group.remove(x)); spikes = [];
|
| 267 |
+
chakras.forEach(x => group.remove(x)); chakras = [];
|
| 268 |
+
formulas.forEach(x => group.remove(x)); formulas = [];
|
| 269 |
+
if (idle) { group.remove(idle); idle = null; }
|
| 270 |
+
const ax = s.axes || [];
|
| 271 |
+
if (!ax.length) { idle = idleSprite("13-AXIS CORTEX · IDLE · no tick"); group.add(idle); }
|
| 272 |
+
ax.forEach((a, i) => {
|
| 273 |
+
const ang = (i / Math.max(1, ax.length)) * Math.PI * 2, len = 6 + (a.value || 0) * 14;
|
| 274 |
+
const geo = new THREE.CylinderGeometry(0.15, 0.5, len, 6);
|
| 275 |
+
const mesh = new THREE.Mesh(geo, new THREE.MeshStandardMaterial({ color: a.color || TOKENS.emerald, emissive: a.color || TOKENS.emerald, emissiveIntensity: 0.5 }));
|
| 276 |
+
mesh.position.set(Math.cos(ang) * len / 2, 0, Math.sin(ang) * len / 2);
|
| 277 |
+
mesh.lookAt(0, 0, 0); mesh.rotateX(Math.PI / 2); mesh.userData = { axis: a.id || a.name, kind: "axis" };
|
| 278 |
+
group.add(mesh); spikes.push(mesh);
|
| 279 |
+
});
|
| 280 |
+
(s.chakras || []).forEach((c, i) => {
|
| 281 |
+
const ang = (i / 7) * Math.PI * 2;
|
| 282 |
+
const sph = new THREE.Mesh(new THREE.SphereGeometry(0.8, 16, 16), new THREE.MeshStandardMaterial({ color: TOKENS.rose, emissive: TOKENS.rose, emissiveIntensity: c.glow || 0.4 }));
|
| 283 |
+
sph.userData = { chakra: c.name, ang, h: c.height || (i - 3) * 3 }; group.add(sph); chakras.push(sph);
|
| 284 |
+
});
|
| 285 |
+
(s.formulas || []).forEach((f, i) => {
|
| 286 |
+
const proved = PROVED.has(f.id) || f.proved;
|
| 287 |
+
if (!proved && !f.recent) return; // noise rule
|
| 288 |
+
const ang = (i / Math.max(1, s.formulas.length)) * Math.PI * 2, R = 26;
|
| 289 |
+
const card = new THREE.Mesh(new THREE.PlaneGeometry(2.2, 1.1), new THREE.MeshBasicMaterial({ map: textTex(f.id, proved ? TOKENS.gold : TOKENS.emerald), transparent: true, opacity: proved ? 1.0 : 0.6, side: THREE.DoubleSide }));
|
| 290 |
+
card.position.set(Math.cos(ang) * R, (i % 6) * 4 - 12, Math.sin(ang) * R); card.userData = { formula: f.id, kind: "formula" };
|
| 291 |
+
group.add(card); formulas.push(card);
|
| 292 |
+
});
|
| 293 |
+
}
|
| 294 |
+
function tick(dt) {
|
| 295 |
+
const now = performance.now();
|
| 296 |
+
lambda.material.emissiveIntensity = 0.8 + 0.5 * Math.sin(now / 400);
|
| 297 |
+
lambda.rotation.y += dt * 0.5;
|
| 298 |
+
chakras.forEach((c, i) => { c.userData.ang += dt * 0.3; c.position.set(Math.cos(c.userData.ang) * 10, c.userData.h, Math.sin(c.userData.ang) * 10); });
|
| 299 |
+
formulas.forEach(f => f.lookAt(stage.camera.position));
|
| 300 |
+
group.rotation.y += dt * 0.04;
|
| 301 |
+
}
|
| 302 |
+
function ripple() { lambda.material.emissiveIntensity = 2.2; spikes.forEach(s => s.material.emissiveIntensity = 1.2); setTimeout(() => spikes.forEach(s => s.material.emissiveIntensity = 0.5), 280); }
|
| 303 |
+
return { group, setState, tick, ripple };
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
/* =========================================================================
|
| 307 |
+
* killinchu — "Earth + Command Orbit": Cesium globe preserved in the page;
|
| 308 |
+
* here we render the 4 superhero orbital cards (Sentra/Amaru/a11oy/Rosie)
|
| 309 |
+
* whose orbit distance is dynamic by activity (busy = closer). Tracks/threats
|
| 310 |
+
* on a globe proxy. Noise rule: only tracks updated in last 60s shown.
|
| 311 |
+
* (Used as an overlay layer above the Cesium canvas, or standalone proxy globe.)
|
| 312 |
+
* ====================================================================== */
|
| 313 |
+
function CommandOrbit(stage) {
|
| 314 |
+
const group = new THREE.Group(); stage.scene.add(group);
|
| 315 |
+
const globe = new THREE.Mesh(new THREE.SphereGeometry(10, 48, 48), new THREE.MeshStandardMaterial({ color: 0x123047, emissive: 0x0a1a28, emissiveIntensity: 0.4, wireframe: false }));
|
| 316 |
+
const grid = new THREE.Mesh(new THREE.SphereGeometry(10.05, 24, 24), new THREE.MeshBasicMaterial({ color: TOKENS.emerald, wireframe: true, transparent: true, opacity: 0.12 }));
|
| 317 |
+
group.add(globe); group.add(grid);
|
| 318 |
+
let officers = [], tracks = [], idle = null;
|
| 319 |
+
function setState(s) {
|
| 320 |
+
officers.forEach(o => group.remove(o.mesh)); officers = [];
|
| 321 |
+
tracks.forEach(t => group.remove(t)); tracks = [];
|
| 322 |
+
if (idle) { group.remove(idle); idle = null; }
|
| 323 |
+
(s.officers || []).forEach((o, i) => {
|
| 324 |
+
const ang = (i / Math.max(1, s.officers.length)) * Math.PI * 2;
|
| 325 |
+
const dist = 18 + (1 - (o.activity ?? 0.5)) * 14; // busy = closer
|
| 326 |
+
const card = new THREE.Mesh(new THREE.PlaneGeometry(4, 2.2), new THREE.MeshBasicMaterial({ map: textTex(o.name, TOKENS.gold), transparent: true, side: THREE.DoubleSide }));
|
| 327 |
+
card.userData = { officer: o.name, ang, dist, kind: "officer" }; group.add(card); officers.push({ mesh: card });
|
| 328 |
+
});
|
| 329 |
+
const tr = (s.tracks || []);
|
| 330 |
+
if (!tr.length && !(s.officers || []).length) { idle = idleSprite("COMMAND ORBIT · IDLE · no tracks in last 60s"); group.add(idle); }
|
| 331 |
+
tr.forEach(t => {
|
| 332 |
+
const dot = new THREE.Mesh(new THREE.SphereGeometry(0.3, 8, 8), new THREE.MeshBasicMaterial({ color: col(t.verdict || "warn") }));
|
| 333 |
+
const phi = (90 - (t.lat || 0)) * Math.PI / 180, theta = (t.lon || 0) * Math.PI / 180;
|
| 334 |
+
dot.position.setFromSphericalCoords(10.3, phi, theta); dot.userData = { track: t.id, kind: "track" };
|
| 335 |
+
group.add(dot); tracks.push(dot);
|
| 336 |
+
});
|
| 337 |
+
}
|
| 338 |
+
function tick(dt) {
|
| 339 |
+
globe.rotation.y += dt * 0.06; grid.rotation.y += dt * 0.06;
|
| 340 |
+
officers.forEach((o, i) => { const u = o.mesh.userData; u.ang += dt * 0.2; o.mesh.position.set(Math.cos(u.ang) * u.dist, Math.sin(i) * 4, Math.sin(u.ang) * u.dist); o.mesh.lookAt(stage.camera.position); });
|
| 341 |
+
}
|
| 342 |
+
function ripple() { grid.material.opacity = 0.4; setTimeout(() => grid.material.opacity = 0.12, 260); }
|
| 343 |
+
return { group, setState, tick, ripple };
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
/* ---- shared helpers: text texture + sprite label ------------------------ */
|
| 347 |
+
function textTex(text, color) {
|
| 348 |
+
const c = document.createElement("canvas"); c.width = 256; c.height = 128;
|
| 349 |
+
const x = c.getContext("2d"); x.clearRect(0, 0, 256, 128);
|
| 350 |
+
x.fillStyle = "#" + new THREE.Color(color).getHexString(); x.font = "bold 40px ui-monospace, monospace";
|
| 351 |
+
x.textAlign = "center"; x.textBaseline = "middle"; x.fillText(String(text), 128, 64);
|
| 352 |
+
const t = new THREE.CanvasTexture(c); t.needsUpdate = true; return t;
|
| 353 |
+
}
|
| 354 |
+
function makeSpriteLabel(text, color) {
|
| 355 |
+
const m = new THREE.SpriteMaterial({ map: textTex(text, color), transparent: true });
|
| 356 |
+
return new THREE.Sprite(m);
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
/* ---- click picking (returns userData of nearest mesh) ------------------- */
|
| 360 |
+
function pick(stage, group, ev) {
|
| 361 |
+
const rect = stage.renderer.domElement.getBoundingClientRect();
|
| 362 |
+
const m = new THREE.Vector2(((ev.clientX - rect.left) / rect.width) * 2 - 1, -((ev.clientY - rect.top) / rect.height) * 2 + 1);
|
| 363 |
+
const ray = new THREE.Raycaster(); ray.setFromCamera(m, stage.camera);
|
| 364 |
+
const hits = ray.intersectObjects(group.children, true);
|
| 365 |
+
return hits.length ? hits[0].object.userData : null;
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
/* ---- scene registry by organ ------------------------------------------- */
|
| 369 |
+
const SCENES = { a11oy: KhipuSpine, sentra: ImmuneCathedral, amaru: Cortex, killinchu: CommandOrbit };
|
| 370 |
+
|
| 371 |
+
/* =============================================================================
|
| 372 |
+
* operator-shell / shared / zones.js
|
| 373 |
+
* Shared zone renderers for the 4-zone Unified Operator Shell:
|
| 374 |
+
* Zone 1 INBOX — 3D-feel card stack of action-needed items
|
| 375 |
+
* Zone 3 COMMAND BAR — Cmd-K palette, single-letter aliases, fuzzy filter
|
| 376 |
+
* Zone 4 RECEIPT DRAWER — z-tunnel ribbon of successfully-signed receipts
|
| 377 |
+
* Footer — single line: doctrine · 🪢 depth · Lean SHA · status pill
|
| 378 |
+
* Noise principle: every element earns its pixel by being ALIVE. Empty inbox =
|
| 379 |
+
* one calm line. Zero-value stat cards are hidden, never shown as "0".
|
| 380 |
+
* Sign: Yachay <yachay@szlholdings.dev> — DCO · ADDITIVE · SPDX: Apache-2.0
|
| 381 |
+
* ========================================================================== */
|
| 382 |
+
|
| 383 |
+
const URGENCY_RANK = { critical: 0, warn: 1, info: 2 };
|
| 384 |
+
|
| 385 |
+
/* ---- Zone 1: INBOX card stack ------------------------------------------ */
|
| 386 |
+
function renderInbox(el, items, onCommand) {
|
| 387 |
+
el.innerHTML = "";
|
| 388 |
+
const active = (items || []).slice().sort((a, b) =>
|
| 389 |
+
(URGENCY_RANK[a.urgency] - URGENCY_RANK[b.urgency]) || (b.ts || "").localeCompare(a.ts || "") || (a.id || "").localeCompare(b.id || ""));
|
| 390 |
+
if (!active.length) {
|
| 391 |
+
const calm = document.createElement("div"); calm.className = "inbox-calm";
|
| 392 |
+
calm.textContent = "Nothing requires your attention. Map is breathing.";
|
| 393 |
+
el.appendChild(calm); return;
|
| 394 |
+
}
|
| 395 |
+
const visible = active.slice(0, 8), overflow = active.length - 8;
|
| 396 |
+
visible.forEach(item => {
|
| 397 |
+
const card = document.createElement("div");
|
| 398 |
+
card.className = `inbox-card urgency-${item.urgency}`;
|
| 399 |
+
card.tabIndex = 0;
|
| 400 |
+
const t = (item.title || "").slice(0, 80), st = (item.subtitle || "").slice(0, 120);
|
| 401 |
+
const acts = (item.actions || []).map(a =>
|
| 402 |
+
`<button class="ic-act${a.primary ? " primary" : ""}" data-cmd="${a.command.replace(/"/g, """)}" data-id="${item.id}">${a.label}</button>`).join("");
|
| 403 |
+
card.innerHTML =
|
| 404 |
+
`<div class="ic-face"><div class="ic-dot"></div><div class="ic-title">${esc(t)}</div>` +
|
| 405 |
+
`<div class="ic-sub">${esc(st)}</div><div class="ic-acts">${acts}</div></div>` +
|
| 406 |
+
`<div class="ic-back"><pre>${esc(JSON.stringify(item.evidence || {}, null, 2))}</pre></div>`;
|
| 407 |
+
card.addEventListener("click", e => { if (!e.target.classList.contains("ic-act")) card.classList.toggle("flipped"); });
|
| 408 |
+
card.querySelectorAll(".ic-act").forEach(b => b.addEventListener("click", e => {
|
| 409 |
+
e.stopPropagation(); onCommand(b.dataset.cmd.replace(/<id>/g, b.dataset.id));
|
| 410 |
+
}));
|
| 411 |
+
el.appendChild(card);
|
| 412 |
+
});
|
| 413 |
+
if (overflow > 0) { const m = document.createElement("div"); m.className = "inbox-more"; m.textContent = `+${overflow} more`; el.appendChild(m); }
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
/* ---- Zone 3: COMMAND BAR (Cmd-K) --------------------------------------- */
|
| 417 |
+
const ALIASES = { s: "/sign", v: "/verify", i: "/inspect", r: "/replay" };
|
| 418 |
+
function makeCommandBar(root, registry, onExecute) {
|
| 419 |
+
const overlay = document.createElement("div"); overlay.className = "cmdk-overlay"; overlay.hidden = true;
|
| 420 |
+
overlay.innerHTML = `<div class="cmdk-box"><input class="cmdk-input" placeholder="Type a command… s=sign v=verify i=inspect r=replay" />` +
|
| 421 |
+
`<div class="cmdk-list"></div></div>`;
|
| 422 |
+
root.appendChild(overlay);
|
| 423 |
+
const input = overlay.querySelector(".cmdk-input"), list = overlay.querySelector(".cmdk-list");
|
| 424 |
+
let sel = 0, flat = [];
|
| 425 |
+
function open() { overlay.hidden = false; input.value = ""; filter(""); input.focus(); }
|
| 426 |
+
function close() { overlay.hidden = true; }
|
| 427 |
+
function filter(q) {
|
| 428 |
+
const ql = q.trim().toLowerCase();
|
| 429 |
+
list.innerHTML = ""; flat = [];
|
| 430 |
+
const groups = registry(); // {Recent:[], Suggested:[], Common:[], Organ:[], Compliance:[]}
|
| 431 |
+
for (const [grp, cmds] of Object.entries(groups)) {
|
| 432 |
+
const matched = cmds.filter(c => !ql || c.cmd.toLowerCase().includes(ql) || (c.desc || "").toLowerCase().includes(ql));
|
| 433 |
+
if (!matched.length) continue;
|
| 434 |
+
const h = document.createElement("div"); h.className = "cmdk-group"; h.textContent = grp; list.appendChild(h);
|
| 435 |
+
matched.forEach(c => {
|
| 436 |
+
const row = document.createElement("div"); row.className = "cmdk-row"; row.dataset.cmd = c.cmd;
|
| 437 |
+
row.innerHTML = `<span class="cmdk-cmd">${esc(c.cmd)}</span><span class="cmdk-desc">${esc(c.desc || "")}</span>`;
|
| 438 |
+
row.addEventListener("click", () => exec(c.cmd)); list.appendChild(row); flat.push(row);
|
| 439 |
+
});
|
| 440 |
+
}
|
| 441 |
+
sel = 0; highlight();
|
| 442 |
+
}
|
| 443 |
+
function highlight() { flat.forEach((r, i) => r.classList.toggle("sel", i === sel)); flat[sel]?.scrollIntoView({ block: "nearest" }); }
|
| 444 |
+
function exec(raw) {
|
| 445 |
+
let cmd = raw.trim();
|
| 446 |
+
const parts = cmd.split(/\s+/); if (ALIASES[parts[0]]) { parts[0] = ALIASES[parts[0]]; cmd = parts.join(" "); }
|
| 447 |
+
close(); onExecute(cmd);
|
| 448 |
+
}
|
| 449 |
+
input.addEventListener("input", e => filter(e.target.value));
|
| 450 |
+
input.addEventListener("keydown", e => {
|
| 451 |
+
if (e.key === "ArrowDown") { sel = Math.min(sel + 1, flat.length - 1); highlight(); e.preventDefault(); }
|
| 452 |
+
else if (e.key === "ArrowUp") { sel = Math.max(sel - 1, 0); highlight(); e.preventDefault(); }
|
| 453 |
+
else if (e.key === "Enter") { const r = flat[sel]; exec(r ? r.dataset.cmd : input.value); }
|
| 454 |
+
else if (e.key === "Escape") close();
|
| 455 |
+
});
|
| 456 |
+
window.addEventListener("keydown", e => {
|
| 457 |
+
if ((e.key === "k" && (e.metaKey || e.ctrlKey)) || (e.key === "/" && document.activeElement.tagName !== "INPUT")) { e.preventDefault(); open(); }
|
| 458 |
+
});
|
| 459 |
+
overlay.addEventListener("click", e => { if (e.target === overlay) close(); });
|
| 460 |
+
return { open, close };
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
/* ---- Zone 4: RECEIPT DRAWER (z-tunnel ribbon) -------------------------- */
|
| 464 |
+
function renderReceipts(el, receipts, onInspect) {
|
| 465 |
+
el.innerHTML = "";
|
| 466 |
+
const ok = (receipts || []).filter(r => r.verify === undefined ? true : r.verify); // only successfully-signed surface here
|
| 467 |
+
if (!ok.length) { const d = document.createElement("div"); d.className = "drawer-idle"; d.textContent = "No signed receipts in window."; el.appendChild(d); return; }
|
| 468 |
+
ok.forEach((r, i) => {
|
| 469 |
+
const chip = document.createElement("div");
|
| 470 |
+
chip.className = `rcpt-chip verdict-${r.verdict || "pass"}`;
|
| 471 |
+
chip.style.setProperty("--z", i);
|
| 472 |
+
chip.innerHTML = `<span class="rc-time">${(r.ts || "").slice(11, 19)}</span>` +
|
| 473 |
+
`<span class="rc-verb">${esc(r.action_verb || "—")}</span>` +
|
| 474 |
+
`<span class="rc-key">${esc((r.keyid || "").replace("szlholdings-", ""))}</span>` +
|
| 475 |
+
`<span class="rc-sig ${r.verify ? "ok" : "pend"}">${r.verify ? "✓" : "·"}</span>`;
|
| 476 |
+
chip.addEventListener("click", () => onInspect(r));
|
| 477 |
+
el.appendChild(chip);
|
| 478 |
+
});
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
/* ---- Footer: single line ----------------------------------------------- */
|
| 482 |
+
function renderFooter(el, { doctrine, counts, depth, leanSha, status, sovereign }) {
|
| 483 |
+
const pill = { ok: "var(--accent-emerald)", warn: "var(--accent-amber)", down: "var(--accent-rose)" }[status] || "var(--accent-emerald)";
|
| 484 |
+
const sov = sovereign === false
|
| 485 |
+
? `<span class="ft-sov off" title="Local LLM offline — honest fallback">⚙️ Sovereign (LLM offline)</span>`
|
| 486 |
+
: `<span class="ft-sov" title="Runs entirely on the operator's hardware — no cloud API">⚙️ Sovereign</span>`;
|
| 487 |
+
el.innerHTML = `<span class="ft-knot">🪢 ${depth}</span>` +
|
| 488 |
+
sov +
|
| 489 |
+
`<span class="ft-doc">🪶 ${esc(doctrine)} LOCKED · ${esc(counts)}</span>` +
|
| 490 |
+
`<span class="ft-lean">⚖️ Lean ${esc(leanSha)}</span>` +
|
| 491 |
+
`<span class="ft-pill" style="background:${pill}"></span>`;
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
function esc(s) { return String(s).replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])); }
|
| 495 |
+
|
| 496 |
+
/* operator-shell / shared / api.js — thin client for the v4 endpoint contract.
|
| 497 |
+
* GET inbox · GET map/state · POST command · GET receipts · GET replay · WS stream · GET healthz
|
| 498 |
+
* Sign: Yachay <yachay@szlholdings.dev> — DCO · ADDITIVE · SPDX: Apache-2.0 */
|
| 499 |
+
function makeApi(organ, base = "") {
|
| 500 |
+
const p = `${base}/api/${organ}/v4`;
|
| 501 |
+
return {
|
| 502 |
+
organ,
|
| 503 |
+
inbox: () => fetch(`${p}/inbox`).then(r => r.json()),
|
| 504 |
+
mapState: () => fetch(`${p}/map/state`).then(r => r.json()),
|
| 505 |
+
command: (command, args = {}) => fetch(`${p}/command`, {
|
| 506 |
+
method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ command, args })
|
| 507 |
+
}).then(r => r.json()),
|
| 508 |
+
receipts: (since, limit = 50) => fetch(`${p}/receipts?` + new URLSearchParams({ ...(since ? { since } : {}), limit })).then(r => r.json()),
|
| 509 |
+
replay: (hash, frame = 0) => fetch(`${p}/replay/${hash}?frame=${frame}`).then(r => r.json()),
|
| 510 |
+
healthz: () => fetch(`${p}/healthz`).then(r => r.json()),
|
| 511 |
+
stream: (onMsg, onErr) => {
|
| 512 |
+
// SSE over the WS-labelled /stream route (EventSource = honest live updates)
|
| 513 |
+
const es = new EventSource(`${p}/stream`);
|
| 514 |
+
es.onmessage = e => { try { onMsg(JSON.parse(e.data)); } catch (_) {} };
|
| 515 |
+
es.onerror = e => { onErr && onErr(e); };
|
| 516 |
+
return es;
|
| 517 |
+
},
|
| 518 |
+
};
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
|
| 522 |
+
const ORGAN = "sentra";
|
| 523 |
+
const api = makeApi(ORGAN);
|
| 524 |
+
|
| 525 |
+
// Zone 2: Map — Three.js living scene for this flagship
|
| 526 |
+
const canvas = document.getElementById("map");
|
| 527 |
+
const stage = makeStage(canvas);
|
| 528 |
+
const scene = SCENES[ORGAN](stage);
|
| 529 |
+
let last = performance.now();
|
| 530 |
+
function loop(now){ const dt=(now-last)/1000; last=now; stage.controls.update(); scene.tick(dt);
|
| 531 |
+
stage.renderer.render(stage.scene, stage.camera); requestAnimationFrame(loop); }
|
| 532 |
+
requestAnimationFrame(loop);
|
| 533 |
+
canvas.addEventListener("click", e => { const u = pick(stage, scene.group, e);
|
| 534 |
+
if (u && (u.receipt_sha||u.signature||u.axis||u.formula||u.officer||u.track||u.gate))
|
| 535 |
+
runCommand(`/inspect ${u.receipt_sha||u.signature||u.axis||u.formula||u.officer||u.track||u.gate}`); });
|
| 536 |
+
|
| 537 |
+
// Command registry (Zone 3) — single-letter aliases + grouped, per-organ + compliance(killinchu)
|
| 538 |
+
function registry(){
|
| 539 |
+
const universal = [
|
| 540 |
+
{cmd:"/sign <action>", desc:"sign → DSSE receipt (s)"},
|
| 541 |
+
{cmd:"/verify <sha>", desc:"cosign verify (v)"},
|
| 542 |
+
{cmd:"/inspect <id>", desc:"drill into entity (i)"},
|
| 543 |
+
{cmd:"/replay <hash>", desc:"cognitive replay (r)"},
|
| 544 |
+
{cmd:"/healthz", desc:"flagship health"},
|
| 545 |
+
];
|
| 546 |
+
const perOrgan = [{"cmd": "/filter <text>", "desc": "mandatory dual-use filter"}, {"cmd": "/dual-use <text>", "desc": "dual-use classification"}, {"cmd": "/signature add <hash>", "desc": "add dual-use signature"}];
|
| 547 |
+
const compliance = [];
|
| 548 |
+
const g = {Suggested:universal.slice(0,2), Common:universal, "sentra":perOrgan};
|
| 549 |
+
if (compliance.length) g["Compliance"] = compliance;
|
| 550 |
+
return g;
|
| 551 |
+
}
|
| 552 |
+
const cmdk = makeCommandBar(document.body, registry, runCommand);
|
| 553 |
+
|
| 554 |
+
async function runCommand(cmd){
|
| 555 |
+
scene.ripple && scene.ripple(); // 3D ripple into the Map
|
| 556 |
+
const res = await api.command(cmd).catch(()=>({ok:false,message:"network"}));
|
| 557 |
+
await refresh(); // re-pull live state
|
| 558 |
+
return res;
|
| 559 |
+
}
|
| 560 |
+
|
| 561 |
+
// Zones 1 + 4 + Footer + Map state — all from live v4 endpoints
|
| 562 |
+
async function refresh(){
|
| 563 |
+
const [inbox, mapState, receipts, health] = await Promise.all([
|
| 564 |
+
api.inbox().catch(()=>[]), api.mapState().catch(()=>({})),
|
| 565 |
+
api.receipts(undefined,40).catch(()=>[]), api.healthz().catch(()=>({})),
|
| 566 |
+
]);
|
| 567 |
+
renderInbox(document.getElementById("inbox"), inbox, runCommand);
|
| 568 |
+
scene.setState(mapState||{});
|
| 569 |
+
renderReceipts(document.getElementById("drawer"), receipts, r => runCommand(`/inspect ${r.receipt_sha}`));
|
| 570 |
+
renderFooter(document.getElementById("footer"), {
|
| 571 |
+
doctrine: health.doctrine||"v11", counts: health.counts||"749/14/163",
|
| 572 |
+
depth: receipts.length, leanSha: health.lean_sha||"c7c0ba17",
|
| 573 |
+
status: health.status==="ok" ? "ok" : "warn",
|
| 574 |
+
sovereign: health.local_llm_online, // ⚙️ Sovereign badge; honest (offline) marker when local LLM unreachable
|
| 575 |
+
});
|
| 576 |
+
}
|
| 577 |
+
refresh();
|
| 578 |
+
|
| 579 |
+
// Live updates via SSE stream (real DSSE events)
|
| 580 |
+
api.stream(msg => { if (msg.type==="receipt") refresh(); }, () => {});
|
| 581 |
+
setInterval(refresh, 15000); // gentle reconcile
|
| 582 |
+
</script>
|
| 583 |
+
</body>
|
| 584 |
+
</html>
|