betterwithage commited on
Commit
b16b90e
·
verified ·
1 Parent(s): cb5ec4e

deploy: Unified Operator Shell v4 (additive) — /operator + /api/sentra/v4/*

Browse files

Author: Yachay <yachay@szlholdings.dev>
DCO: Signed-off-by: Yachay <yachay@szlholdings.dev>
Change-class: ADDITIVE
Co-Authored-By: Perplexity Computer Agent

Files changed (4) hide show
  1. Dockerfile +9 -0
  2. operator_shell_v4.py +350 -0
  3. serve.py +19 -0
  4. 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, "&quot;")}" 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 => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[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>