betterwithage Claude Opus 4.7 commited on
Commit
3fb5a36
·
verified ·
1 Parent(s): 4867bc1

deploy(hf): sync szl-holdings/a11oy@main derived COPY set

Browse files

Reusable Dockerfile-COPY-derived deploy from szl-holdings/a11oy main.
Files: 1061 Pruned: 0
Derived from Dockerfile COPY sources (NO hand-maintained allowlist).

Signed-off-by: SZL Holdings <noreply@szlholdings.ai>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

research/SOVEREIGN_MESH_RUNBOOK.md ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!--
2
+ SPDX-License-Identifier: Apache-2.0
3
+ (c) 2026 Lutar, Stephen P. - SZL Holdings - ORCID 0009-0001-0110-4173 - Doctrine v11
4
+ -->
5
+
6
+ # Sovereign Mesh Runbook — harness the 2-box GPU mesh behind ONE endpoint
7
+
8
+ **You are at the LAPTOP (`betterwithage`, RTX 5050) right now.** These are your hands —
9
+ the cloud agent cannot reach your LAN. Follow the PowerShell baby-steps in order.
10
+ Copy-paste one block at a time and read the "you should see" line before moving on.
11
+
12
+ **What we are building:** the cloud a11oy Space calls **ONE** endpoint
13
+ (`gateway.a-11-oy.com`, model `sovereign-llm`); a **LiteLLM** gateway load-balances
14
+ your two Windows GPU boxes (tower `omen` RTX 4060 Ti + this laptop `betterwithage`
15
+ RTX 5050), both running **Ollama**. Everything is durable (boot-persistent tasks) and
16
+ self-healing (restart policies + LiteLLM node cooldown). Cloudflare = public ingress,
17
+ Tailscale = private transport, Docker = the stateless sidecars only.
18
+
19
+ ```
20
+ a11oy Space (cloud)
21
+ | HTTPS + Bearer + CF-Access-Client-* (ONE endpoint)
22
+ v
23
+ Cloudflare edge (Access service token) -> gateway.a-11-oy.com
24
+ v (cloudflared, outbound-only tunnel)
25
+ LiteLLM :4000 (model "sovereign-llm")
26
+ | Tailscale (private, tailnet-only :11434)
27
+ +--> omen Ollama (llama3.1:8b)
28
+ +--> betterwithage Ollama (glm-4.7-flash + the #789 energy probe)
29
+ ```
30
+
31
+ > **HONESTY (Doctrine v11).** Nothing below "goes green" on faith. A node is only
32
+ > `wired`/`live` when a **real** call to it succeeds THIS request. Joules are
33
+ > **MEASURED** only from a real NVML delta (the #789 probe); otherwise the honest
34
+ > `MEASURED_SHARED_BOUNDED` / `UNAVAILABLE` empty-state stands. A truthful BLOCKED
35
+ > beats a fake green. **Ollama's `:11434` is never public.**
36
+
37
+ ---
38
+
39
+ ## 0. Prerequisites (one-time, on the laptop)
40
+
41
+ ```powershell
42
+ # Confirm the driver + GPU are visible (native, no Docker).
43
+ nvidia-smi
44
+ # you should see: your RTX 5050 listed, a driver version (want 535+), and power draw.
45
+
46
+ # Confirm Ollama, Tailscale, cloudflared, python are installed.
47
+ ollama --version
48
+ tailscale version
49
+ cloudflared --version
50
+ python --version # or: py --version
51
+ ```
52
+
53
+ If `nvidia-smi` shows the GPU but `power.draw` is `N/A` (some laptop GPUs do), the
54
+ #789 probe will honestly emit `UNAVAILABLE` rather than a fake joule — that is fine,
55
+ the mesh still works, you just do not get MEASURED joules from this box.
56
+
57
+ ---
58
+
59
+ ## Step (a) — pull glm-4.7-flash + start the #789 energy probe on the laptop
60
+
61
+ ```powershell
62
+ # 1. Pull the laptop's model.
63
+ ollama pull glm-4.7-flash
64
+
65
+ # 2. Make sure Ollama accepts the tunneled Host header + binds for the tailnet.
66
+ # (These are the machine-level env the persist script sets; safe to set now.)
67
+ [System.Environment]::SetEnvironmentVariable("OLLAMA_ORIGINS","*","Machine")
68
+ [System.Environment]::SetEnvironmentVariable("OLLAMA_HOST","0.0.0.0:11434","Machine")
69
+
70
+ # 3. Start the #789 per-inference energy probe in a LOOP (refreshes every 60s).
71
+ # Writes ~/.a11oy_ollama_energy.json which omen_joule_exporter.py merges as models[].
72
+ $env:OLLAMA_MODEL = "glm-4.7-flash:latest"
73
+ python box-scripts\ollama_energy_probe.py --loop 60
74
+ # you should see: a line per cycle with joules + a VERBATIM label
75
+ # (MEASURED_SHARED_BOUNDED by default; MEASURED only if you assert exclusivity;
76
+ # UNAVAILABLE if NVML/power.draw cannot be read — never a fabricated number).
77
+ ```
78
+
79
+ Leave that window running. (For the durable version, `box-scripts\laptop_persist.ps1`
80
+ registers Ollama + the exporter + the `laptop-szl` tunnel as AtStartup tasks — run it
81
+ **as Administrator** once you have verified the manual path works.)
82
+
83
+ > **Clean MEASURED (optional):** only if *nothing else* uses the GPU during the window:
84
+ > `$env:OLLAMA_GPU_EXCLUSIVE = "1"` before launching the probe. Otherwise keep the
85
+ > honest `MEASURED_SHARED_BOUNDED` upper bound — do not upgrade the label.
86
+
87
+ ---
88
+
89
+ ## Step (b) — start LiteLLM (the unified gateway)
90
+
91
+ Run LiteLLM on the **always-on** box. If the laptop is your always-on box for now,
92
+ run it here; normally it lives on `omen`. Two ways — pick ONE.
93
+
94
+ ### (b-native) Native Python — simplest, no Docker/WSL2 (RECOMMENDED to start)
95
+
96
+ ```powershell
97
+ pip install "litellm[proxy]"
98
+
99
+ # Bearer the Space must send (generate a real random key; keep it secret).
100
+ $env:LITELLM_MASTER_KEY = "sk-REPLACE-with-a-real-random-key"
101
+ # The shared pool model (must fit BOTH cards for true load-balancing).
102
+ $env:SOVEREIGN_POOL_MODEL = "llama3.1:8b"
103
+ # Tailscale MagicDNS names (or 100.x IPs from `tailscale ip -4` on each box).
104
+ $env:OMEN_OLLAMA_URL = "http://omen:11434"
105
+ $env:BWA_OLLAMA_URL = "http://betterwithage:11434"
106
+ $env:OMEN_MODEL = "llama3.1:8b"
107
+ $env:BWA_MODEL = "glm-4.7-flash:latest"
108
+
109
+ litellm --config box-scripts\litellm_config.yaml --port 4000
110
+ # you should see: "Uvicorn running on http://0.0.0.0:4000".
111
+ ```
112
+
113
+ Verify locally (new PowerShell window):
114
+
115
+ ```powershell
116
+ curl.exe -s http://localhost:4000/health/liveliness
117
+ # you should see: {"status":"healthy"...}
118
+
119
+ curl.exe -s http://localhost:4000/v1/models -H "Authorization: Bearer $env:LITELLM_MASTER_KEY"
120
+ # you should see: sovereign-llm, omen-llama, betterwithage-glm in the list.
121
+
122
+ curl.exe -s http://localhost:4000/v1/chat/completions `
123
+ -H "Authorization: Bearer $env:LITELLM_MASTER_KEY" `
124
+ -H "Content-Type: application/json" `
125
+ -d '{"model":"sovereign-llm","messages":[{"role":"user","content":"say ok"}]}'
126
+ # you should see: a real completion (proves LiteLLM reached a live Ollama node).
127
+ ```
128
+
129
+ ### (b-docker) Containerized sidecars — Ollama STILL native
130
+
131
+ > **WSL2 GPU passthrough is fragile.** Only the *sidecars* go in Docker; Ollama and
132
+ > the #789 probe stay native (Step a). If `dcgm-exporter` cannot get the GPU, comment
133
+ > it out and rely on the native probe for joules — the meter path does not need it.
134
+
135
+ ```powershell
136
+ # .env next to the compose file (NEVER commit it):
137
+ # LITELLM_MASTER_KEY=sk-... CF_TUNNEL_TOKEN_GPU=...(if using token mode)
138
+ # OMEN_OLLAMA_URL=http://host.docker.internal:11434 (native Ollama on THIS box)
139
+ # BWA_OLLAMA_URL=http://betterwithage:11434 (tailnet)
140
+ docker compose -f box-scripts\docker-compose.yml up -d litellm prometheus grafana
141
+ docker compose -f box-scripts\docker-compose.yml ps
142
+ # you should see: litellm (healthy). On the laptop, use docker-compose.laptop.yml
143
+ # which is telemetry-only (dcgm-exporter).
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Step (c) — verify meter2 `models[]` appears
149
+
150
+ The #789 probe (Step a) writes energy JSON; `omen_joule_exporter.py` (engine name
151
+ `betterwithage` on the laptop) merges it as top-level `models[]` and serves it on
152
+ `:9471`, tunneled as `meter2.a-11-oy.com`.
153
+
154
+ ```powershell
155
+ # Local exporter (if not already running via laptop_persist.ps1):
156
+ $env:OMEN_ENGINE_NAME = "betterwithage"
157
+ python box-scripts\omen_joule_exporter.py # serves 0.0.0.0:9471
158
+
159
+ # Local check:
160
+ curl.exe -s http://localhost:9471/ | python -m json.tool
161
+ # you should see: engines[] with engine "betterwithage" AND a top-level models[]
162
+ # entry for glm-4.7-flash with joules_per_token + a VERBATIM label
163
+ # (or an UNAVAILABLE null if NVML/power.draw is not readable — honest, not fake).
164
+
165
+ # Public check (through the laptop-szl tunnel):
166
+ curl.exe -s https://meter2.a-11-oy.com/ | python -m json.tool
167
+ # you should see: the same models[] payload.
168
+ ```
169
+
170
+ If `models[]` is missing: the probe is not running (Step a) or the energy JSON is
171
+ stale (>300s, `OLLAMA_ENERGY_MAX_AGE_S`) → it is intentionally surfaced as
172
+ `UNAVAILABLE`, never a stale/fake number.
173
+
174
+ ---
175
+
176
+ ## Step (d) — point the Space at the LiteLLM gateway
177
+
178
+ Expose LiteLLM publicly as `gateway.a-11-oy.com` via cloudflared (see
179
+ `box-scripts/cloudflared_ingress.example.yml` — the real `~/.cloudflared/config.yml`
180
+ is on-box, not committed). Then set the **Space secrets**:
181
+
182
+ | Space secret | Value | Effect |
183
+ |---|---|---|
184
+ | `SZL_LOCAL_LLM_URL` | `https://gateway.a-11-oy.com` | Points the sovereign-local path at the gateway (single endpoint). |
185
+ | `A11OY_SOVEREIGN_GATEWAY_URL` | `https://gateway.a-11-oy.com` | *Preferred.* Takes precedence over `SZL_LOCAL_LLM_URL`; the brain/anatomy use the unified LiteLLM endpoint. **Unset ⇒ falls back to `SZL_LOCAL_LLM_URL` (unchanged).** |
186
+ | `A11OY_SOVEREIGN_GATEWAY_KEY` | your `LITELLM_MASTER_KEY` | Bearer sent to the gateway. Secret is never logged/returned. (Fallback name: `SZL_LOCAL_LLM_KEY`.) |
187
+ | `SZL_LOCAL_LLM_MODEL` | `sovereign-llm` | The model name LiteLLM load-balances across both nodes. |
188
+
189
+ > Only **one** of `A11OY_SOVEREIGN_GATEWAY_URL` / `SZL_LOCAL_LLM_URL` is required.
190
+ > Set the gateway one to prefer the unified endpoint; both unset ⇒ honest stub.
191
+ > If Cloudflare Access is in front, also set the Space's `CF-Access-Client-Id` /
192
+ > `CF-Access-Client-Secret` (service token) — layered auth (bearer **and** Access).
193
+
194
+ ---
195
+
196
+ ## Step (e) — verify the Space wired to the mesh
197
+
198
+ ```powershell
199
+ # 1. Registry wired_count should increment (sovereign_local now wired).
200
+ curl.exe -s "https://szlholdings-a11oy.hf.space/api/a11oy/v1/llm/registry?probe=1" | python -m json.tool
201
+ # you should see: wired_count >= 1, "sovereign_local" in wired_model_ids, and its
202
+ # badge {wired:true, base_url: the gateway}. local_live:true ONLY if the node
203
+ # answered THIS request.
204
+
205
+ # 2. Sovereign health: live + served models THIS request.
206
+ curl.exe -s "https://szlholdings-a11oy.hf.space/api/a11oy/v1/llm/sovereign/health" | python -m json.tool
207
+ # you should see: env_present:true, live:true, served_models includes sovereign-llm.
208
+
209
+ # 3. Route a real prompt to the mesh.
210
+ curl.exe -s -X POST "https://szlholdings-a11oy.hf.space/api/a11oy/v1/llm/route" `
211
+ -H "Content-Type: application/json" `
212
+ -d '{"prompt":"one sentence: why sovereign inference matters","task_hint":"sovereign"}'
213
+ # you should see: routed_via "sovereign_local (...)", a REAL response, and a
214
+ # lambda_receipt (Λ = Conjecture 1, advisory). If the node is down you get an
215
+ # HONEST STUB — the tier selection + Λ + receipt are still real.
216
+ ```
217
+
218
+ `/brain/ask` (Wave 1/2) then routes its grounded prompt to `sovereign-llm` through the
219
+ same gateway when `A11OY_SOVEREIGN_GATEWAY_URL` (or `SZL_LOCAL_LLM_URL`) is set;
220
+ otherwise it honestly returns the retrieved subgraph with "no local model wired".
221
+
222
+ ---
223
+
224
+ ## Security (Doctrine v11 — non-negotiable)
225
+
226
+ 1. **Ollama is NEVER public.** `:11434` is served over the tailnet only. It is not in
227
+ any cloudflared ingress rule. Ollama has no built-in auth — the network IS its auth.
228
+ 2. **Layered auth on the ONE public endpoint** (`gateway.a-11-oy.com`):
229
+ - **LiteLLM bearer** (`master_key`) required on every request, **and**
230
+ - **Cloudflare Access service token** in front of the tunnel hostname, **and**
231
+ - **Tailnet ACL** (`box-scripts/tailscale_acl.json`, deny-by-default): only
232
+ `tag:llm-gateway` may reach `tag:llm-gpu:11434`. A leaked bearer alone, or a
233
+ leaked Access token alone, is insufficient.
234
+ 3. **Never use Tailscale Funnel** for Ollama (no funnel grant exists in the ACL).
235
+ 4. **Pin image tags** on GPU-facing containers; do not auto-update them unattended.
236
+ 5. **Never commit a key.** `LITELLM_MASTER_KEY`, tunnel tokens, and the gateway bearer
237
+ live only in the shell/`.env`/Space secrets — never in the tree.
238
+
239
+ ## Self-healing / durability
240
+
241
+ | Layer | Mechanism |
242
+ |---|---|
243
+ | Ollama (native Win) | `laptop_persist.ps1` / `omen_boot_persist.ps1` — AtStartup task, `RestartCount 999`, `-AllowStartIfOnBatteries`. |
244
+ | #789 probe + exporter | Same persist scripts (AtStartup, auto-restart). |
245
+ | Tailscale (Win) | "Run unattended" mode; Startup Type = Automatic. |
246
+ | cloudflared / litellm / prometheus / grafana / dcgm-exporter | `restart: unless-stopped` + healthchecks (docker-compose). |
247
+ | LiteLLM ↔ node failure | `cooldown_time: 30`, `num_retries: 2`, least-busy routing auto-skips a down node. |
248
+
249
+ ## Native-Windows fallback (the honest default)
250
+
251
+ WSL2 GPU-in-Docker is fragile (driver-version sensitivity, "restart Docker Desktop
252
+ fully" gotchas, silent CPU fallback). So the **model-serving path stays native**:
253
+ run Ollama + the #789 probe natively (Steps a, c), and containerize only the
254
+ stateless sidecars (LiteLLM / Prometheus / Grafana / cloudflared / dcgm-exporter).
255
+ If `dcgm-exporter` cannot acquire the GPU in its container, drop it and rely on the
256
+ native NVML probe for MEASURED joules — the mesh and the meter both still work.
257
+
258
+ ## Troubleshooting
259
+
260
+ - **`gpu2`/tunnel 403** → set `OLLAMA_ORIGINS=*` and `OLLAMA_HOST=0.0.0.0:11434` at
261
+ machine level (Step a), then restart Ollama.
262
+ - **LiteLLM 401 from the Space** → the Space's `A11OY_SOVEREIGN_GATEWAY_KEY` (bearer)
263
+ must equal `LITELLM_MASTER_KEY`; if Access is on, also send the CF service token.
264
+ - **`wired_count` did not increment** → `SZL_LOCAL_LLM_URL` / `A11OY_SOVEREIGN_GATEWAY_URL`
265
+ not set as a Space secret, or the gateway is unreachable (honest stub — not a bug).
266
+ - **`models[]` missing on meter2** → the #789 probe is not looping, or the reading is
267
+ stale/`UNAVAILABLE` (NVML/power.draw not readable). Honest empty-state, never faked.
268
+ </content>
static/3d/holographic.html CHANGED
@@ -34,9 +34,11 @@
34
  collapsible CATEGORY dropdowns. Replaces the old flat wall of ~84 pills so the flagship
35
  surfaces stand out and the rest group behind one-tap dropdowns. Accents are proof-teal
36
  (0x3af4c8) — NEVER purple (doctrine v11 palette). */
37
- .tabs{display:flex;flex-direction:column;gap:6px;padding:8px 12px;border-bottom:1px solid var(--line);
38
- background:#070d15;flex:0 0 auto}
39
- .navrow{display:flex;gap:6px;flex-wrap:wrap;align-items:center}
 
 
40
  .navlabel{font:9.5px ui-monospace,monospace;color:var(--para);letter-spacing:.6px;
41
  text-transform:uppercase;opacity:.7;margin-right:2px;flex:0 0 auto}
42
  .tab{font:11.5px ui-monospace,monospace;padding:6px 12px;border-radius:7px;border:1px solid var(--line);
@@ -60,9 +62,31 @@
60
  .catbtn[data-hasactive="true"]{box-shadow:0 0 0 1px var(--proof) inset}
61
  .catbtn .count{font-size:9px;color:var(--para);opacity:.7}
62
  /* Drop panel: the tabs of the currently-open category (only one open at a time). */
63
- .panel{display:none;gap:6px;flex-wrap:wrap;padding:8px;margin-top:2px;
64
- border:1px solid var(--line);border-radius:9px;background:#08111a}
65
  .panel[data-open="true"]{display:flex}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  #stage-wrap{position:relative;flex:1 1 auto;min-height:0;width:100%}
67
  #stage{position:absolute;inset:0}
68
  #stage canvas{display:block;width:100%;height:100%;touch-action:none}
@@ -85,13 +109,15 @@
85
  header .badge{font-size:9.5px;padding:2px 6px}
86
  #three-rev-badge,#cdn-badge{display:none}
87
  .tabs{padding:7px 10px;gap:5px}
 
 
88
  /* Featured + category rows each scroll horizontally so they never build a tall wall;
89
  the tapped-open panel wraps below, height-capped so it never shoves the stage off. */
90
- .navrow{flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:thin}
91
- .navrow::-webkit-scrollbar{height:4px}
92
- .navrow::-webkit-scrollbar-thumb{background:var(--line);border-radius:4px}
93
  .navlabel{position:sticky;left:0;background:#070d15;z-index:1}
94
  .tab,.catbtn{padding:7px 11px;font-size:11px;flex:0 0 auto}
 
95
  .panel[data-open="true"]{flex-wrap:wrap;max-height:34vh;overflow-y:auto}
96
  footer{font-size:9px;padding:5px 10px}
97
  /* Give the 3D stage a guaranteed usable share of the screen on phones (100dvh-safe). */
@@ -160,10 +186,21 @@
160
  <a href="/verify" class="badge" title="Public receipt verifier — paste a signed Khipu receipt / DSSE envelope for a PASS/FAIL panel">✓ verify</a>
161
  <a href="/" style="margin-left:auto;font:11px ui-monospace,monospace">← a11oy</a>
162
  </header>
163
- <nav class="tabs" id="tabs" role="tablist" aria-label="Estate surfaces">
164
- <div class="navrow" id="featured-row"><span class="navlabel">★ Featured</span></div>
165
- <div class="navrow" id="cat-row"><span class="navlabel">Categories</span></div>
166
- <div id="cat-panels"></div>
 
 
 
 
 
 
 
 
 
 
 
167
  </nav>
168
  <div id="stage-wrap">
169
  <div id="stage"></div>
@@ -219,6 +256,7 @@ import * as THREE from "three";
219
 
220
  // The 9 surface slots the other devs fill. Lazy-loaded per tab.
221
  const SURFACES = [
 
222
  { id: "atlas", cat: "map", flag: true, title: "Atlas", mod: "/static/3d/surfaces/atlas.js" },
223
  { id: "frontier", cat: "map", title: "Frontier", mod: "/static/3d/surfaces/frontier.js" },
224
  { id: "neuromorphic", cat: "more", title: "Neuromorphic", mod: "/static/3d/surfaces/neuromorphic.js" },
@@ -474,9 +512,32 @@ const catOf = (s) => s.cat || "more";
474
  const featuredRow = document.getElementById("featured-row");
475
  const catRow = document.getElementById("cat-row");
476
  const catPanels = document.getElementById("cat-panels");
 
 
 
 
 
477
  const _panels = {}; // cat -> drop-panel element
478
  const _catBtns = {}; // cat -> dropdown button
479
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
480
  // Short pill label: first 1-2 " · "-separated segments of the honest title, capped.
481
  // The FULL honest title is preserved verbatim in the tooltip (b.title) and in the
482
  // footer caption when the surface is selected — nothing is dropped, only de-cluttered.
@@ -504,6 +565,9 @@ function _makeTab(s, feat) {
504
  }
505
  b.addEventListener("click", () => {
506
  selectSurface(s.id);
 
 
 
507
  // On the mobile horizontal strips, keep the tapped tab in view.
508
  try { b.scrollIntoView({ behavior: "smooth", inline: "center", block: "nearest" }); } catch (_) {}
509
  });
@@ -537,21 +601,65 @@ CATS.forEach(([cat, label]) => {
537
  const panel = document.createElement("div");
538
  panel.className = "panel"; panel.dataset.cat = cat; panel.dataset.open = "false";
539
  items.forEach((s) => panel.appendChild(_makeTab(s, false)));
540
- btn.addEventListener("click", () => openCategory(cat, panel.dataset.open !== "true"));
 
 
 
541
  catRow.appendChild(btn);
542
  catPanels.appendChild(panel);
543
  _panels[cat] = panel; _catBtns[cat] = btn;
544
  });
545
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
546
  // Mark every tab button carrying `id` as selected (a flagship is in both the Featured row
547
  // and its category panel), auto-open the active surface's category, and proof-teal-flag the
548
  // owning dropdown button so the current location is always legible.
549
  function _highlight(id) {
550
  const def = SURFACES.find((s) => s.id === id);
551
  const cat = def ? catOf(def) : null;
 
 
 
 
552
  tabsEl.querySelectorAll("[data-id]").forEach((t) =>
553
  t.setAttribute("aria-selected", String(t.dataset.id === id)));
554
- if (cat && _panels[cat]) openCategory(cat, true);
555
  Object.keys(_catBtns).forEach((c) =>
556
  _catBtns[c].setAttribute("data-hasactive", String(c === cat)));
557
  }
 
34
  collapsible CATEGORY dropdowns. Replaces the old flat wall of ~84 pills so the flagship
35
  surfaces stand out and the rest group behind one-tap dropdowns. Accents are proof-teal
36
  (0x3af4c8) — NEVER purple (doctrine v11 palette). */
37
+ .tabs{display:flex;flex-direction:column;gap:0;padding:8px 12px;border-bottom:1px solid var(--line);
38
+ background:#070d15;flex:0 0 auto;position:relative;z-index:18}
39
+ .navrow{display:flex;gap:6px;flex-wrap:nowrap;align-items:center;overflow-x:auto;
40
+ -webkit-overflow-scrolling:touch;scrollbar-width:none;padding-bottom:2px}
41
+ .navrow::-webkit-scrollbar{display:none}
42
  .navlabel{font:9.5px ui-monospace,monospace;color:var(--para);letter-spacing:.6px;
43
  text-transform:uppercase;opacity:.7;margin-right:2px;flex:0 0 auto}
44
  .tab{font:11.5px ui-monospace,monospace;padding:6px 12px;border-radius:7px;border:1px solid var(--line);
 
62
  .catbtn[data-hasactive="true"]{box-shadow:0 0 0 1px var(--proof) inset}
63
  .catbtn .count{font-size:9px;color:var(--para);opacity:.7}
64
  /* Drop panel: the tabs of the currently-open category (only one open at a time). */
65
+ .panel{display:none;gap:6px;flex-wrap:wrap;padding:8px;margin-top:2px;max-height:180px;
66
+ overflow-y:auto;border:1px solid var(--line);border-radius:9px;background:#08111a}
67
  .panel[data-open="true"]{display:flex}
68
+ .navtools{display:grid;grid-template-columns:minmax(260px,1fr) minmax(210px,auto) auto auto;
69
+ gap:7px;align-items:center;min-height:34px}
70
+ .surface-search{width:100%;min-width:0;border:1px solid var(--line);border-radius:8px;
71
+ background:#08111a;color:var(--cream);padding:7px 10px;font:11.5px ui-monospace,monospace;
72
+ outline:none;letter-spacing:.2px}
73
+ .surface-search:focus{border-color:var(--proof);box-shadow:0 0 0 1px var(--proof)}
74
+ .surface-search::placeholder{color:#70818e}
75
+ .navmeta{font:10px ui-monospace,monospace;color:var(--para);white-space:nowrap}
76
+ .navmode{border:1px solid var(--line);border-radius:8px;background:#0a121b;color:var(--para);
77
+ padding:7px 10px;font:10.5px ui-monospace,monospace;cursor:pointer;white-space:nowrap}
78
+ .navmode:hover{border-color:var(--proof);color:var(--cream)}
79
+ .active-surface{display:flex;align-items:center;gap:7px;min-width:0;padding:0 8px;
80
+ border-left:1px solid var(--line);font:10px ui-monospace,monospace;color:var(--para)}
81
+ .active-surface span{font-size:9px;letter-spacing:.8px;color:var(--proof)}
82
+ .active-surface strong{font-weight:500;color:var(--cream);overflow:hidden;text-overflow:ellipsis;
83
+ white-space:nowrap;max-width:32vw}
84
+ .surface-browser{display:none;position:absolute;left:12px;right:12px;top:calc(100% + 1px);
85
+ padding:9px;border:1px solid var(--line);border-top-color:#20323e;border-radius:0 0 10px 10px;
86
+ background:rgba(7,13,21,.98);box-shadow:0 18px 40px rgba(0,0,0,.56);z-index:20}
87
+ .tabs[data-browse="true"] .surface-browser{display:flex;flex-direction:column;gap:7px}
88
+ #search-panel{position:absolute;left:12px;right:12px;top:calc(100% + 1px);z-index:22;
89
+ margin:0;box-shadow:0 18px 40px rgba(0,0,0,.56)}
90
  #stage-wrap{position:relative;flex:1 1 auto;min-height:0;width:100%}
91
  #stage{position:absolute;inset:0}
92
  #stage canvas{display:block;width:100%;height:100%;touch-action:none}
 
109
  header .badge{font-size:9.5px;padding:2px 6px}
110
  #three-rev-badge,#cdn-badge{display:none}
111
  .tabs{padding:7px 10px;gap:5px}
112
+ .navtools{grid-template-columns:minmax(0,1fr) auto}
113
+ .navmeta,.active-surface{display:none}
114
  /* Featured + category rows each scroll horizontally so they never build a tall wall;
115
  the tapped-open panel wraps below, height-capped so it never shoves the stage off. */
116
+ .navrow{flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none}
117
+ .navrow::-webkit-scrollbar{display:none}
 
118
  .navlabel{position:sticky;left:0;background:#070d15;z-index:1}
119
  .tab,.catbtn{padding:7px 11px;font-size:11px;flex:0 0 auto}
120
+ .surface-browser{left:8px;right:8px;max-height:52vh;overflow:auto}
121
  .panel[data-open="true"]{flex-wrap:wrap;max-height:34vh;overflow-y:auto}
122
  footer{font-size:9px;padding:5px 10px}
123
  /* Give the 3D stage a guaranteed usable share of the screen on phones (100dvh-safe). */
 
186
  <a href="/verify" class="badge" title="Public receipt verifier — paste a signed Khipu receipt / DSSE envelope for a PASS/FAIL panel">✓ verify</a>
187
  <a href="/" style="margin-left:auto;font:11px ui-monospace,monospace">← a11oy</a>
188
  </header>
189
+ <nav class="tabs" id="tabs" role="tablist" aria-label="Estate surfaces" data-browse="false">
190
+ <div class="navtools">
191
+ <input class="surface-search" id="surface-search" type="search"
192
+ placeholder="Find a surface (Ctrl/⌘ K)" autocomplete="off" spellcheck="false"
193
+ aria-label="Find an estate surface" aria-controls="search-panel"/>
194
+ <div class="active-surface" id="active-surface" aria-live="polite"><span>ACTIVE</span><strong>Loading surface</strong></div>
195
+ <span class="navmeta" id="surface-results-count">All surfaces indexed</span>
196
+ <button class="navmode" id="nav-mode" type="button" aria-expanded="false" aria-controls="surface-browser">Browse surfaces</button>
197
+ </div>
198
+ <div class="panel" id="search-panel" role="group" aria-label="Surface search results" data-open="false"></div>
199
+ <div class="surface-browser" id="surface-browser">
200
+ <div class="navrow" id="featured-row"><span class="navlabel">★ Featured</span></div>
201
+ <div class="navrow" id="cat-row"><span class="navlabel">Categories</span></div>
202
+ <div id="cat-panels"></div>
203
+ </div>
204
  </nav>
205
  <div id="stage-wrap">
206
  <div id="stage"></div>
 
256
 
257
  // The 9 surface slots the other devs fill. Lazy-loaded per tab.
258
  const SURFACES = [
259
+ { id: "integritycontrol", cat: "governance", short: "Integrity Control Plane", title: "Integrity Control Plane · read-only Waqay Security Loop + Claim Integrity manifests · PROPOSAL_ONLY boundary · effectors=0 · signatures honestly UNSIGNED until a real signer and external verifier exist", mod: "/static/3d/surfaces/integritycontrol.js" },
260
  { id: "atlas", cat: "map", flag: true, title: "Atlas", mod: "/static/3d/surfaces/atlas.js" },
261
  { id: "frontier", cat: "map", title: "Frontier", mod: "/static/3d/surfaces/frontier.js" },
262
  { id: "neuromorphic", cat: "more", title: "Neuromorphic", mod: "/static/3d/surfaces/neuromorphic.js" },
 
512
  const featuredRow = document.getElementById("featured-row");
513
  const catRow = document.getElementById("cat-row");
514
  const catPanels = document.getElementById("cat-panels");
515
+ const searchInput = document.getElementById("surface-search");
516
+ const searchPanel = document.getElementById("search-panel");
517
+ const searchCount = document.getElementById("surface-results-count");
518
+ const navMode = document.getElementById("nav-mode");
519
+ const activeSurface = document.querySelector("#active-surface strong");
520
  const _panels = {}; // cat -> drop-panel element
521
  const _catBtns = {}; // cat -> dropdown button
522
 
523
+ function _clearSearch() {
524
+ if (searchInput) searchInput.value = "";
525
+ if (searchPanel) { searchPanel.textContent = ""; searchPanel.dataset.open = "false"; }
526
+ if (searchCount) searchCount.textContent = SURFACES.length + " surfaces indexed";
527
+ }
528
+
529
+ // Command-center default: the graph owns the viewport. The complete registry remains one
530
+ // click away in an overlay drawer and every surface remains indexed by the search field.
531
+ function _setBrowser(open) {
532
+ const on = !!open;
533
+ tabsEl.dataset.browse = String(on);
534
+ if (navMode) {
535
+ navMode.setAttribute("aria-expanded", String(on));
536
+ navMode.textContent = on ? "Close browser" : "Browse surfaces";
537
+ }
538
+ if (!on) openCategory("", false);
539
+ }
540
+
541
  // Short pill label: first 1-2 " · "-separated segments of the honest title, capped.
542
  // The FULL honest title is preserved verbatim in the tooltip (b.title) and in the
543
  // footer caption when the surface is selected — nothing is dropped, only de-cluttered.
 
565
  }
566
  b.addEventListener("click", () => {
567
  selectSurface(s.id);
568
+ _clearSearch();
569
+ _setBrowser(false);
570
+ openCategory(catOf(s), false);
571
  // On the mobile horizontal strips, keep the tapped tab in view.
572
  try { b.scrollIntoView({ behavior: "smooth", inline: "center", block: "nearest" }); } catch (_) {}
573
  });
 
601
  const panel = document.createElement("div");
602
  panel.className = "panel"; panel.dataset.cat = cat; panel.dataset.open = "false";
603
  items.forEach((s) => panel.appendChild(_makeTab(s, false)));
604
+ btn.addEventListener("click", () => {
605
+ _clearSearch();
606
+ openCategory(cat, panel.dataset.open !== "true");
607
+ });
608
  catRow.appendChild(btn);
609
  catPanels.appendChild(panel);
610
  _panels[cat] = panel; _catBtns[cat] = btn;
611
  });
612
 
613
+ // Indexed surface finder: every surface stays reachable without a permanent wall of pills.
614
+ // Matching is deterministic over id, title and category; nothing is generated.
615
+ if (searchInput && searchPanel) {
616
+ const renderSearch = () => {
617
+ const q = searchInput.value.trim().toLowerCase();
618
+ searchPanel.textContent = "";
619
+ if (!q) { _clearSearch(); return; }
620
+ _setBrowser(false);
621
+ openCategory("", false);
622
+ const matches = SURFACES.filter((s) =>
623
+ (s.id + " " + s.title + " " + catOf(s)).toLowerCase().includes(q));
624
+ matches.slice(0, 36).forEach((s) => searchPanel.appendChild(_makeTab(s, false)));
625
+ searchPanel.dataset.open = "true";
626
+ if (searchCount) searchCount.textContent = matches.length + " match" + (matches.length === 1 ? "" : "es");
627
+ };
628
+ searchInput.addEventListener("input", renderSearch);
629
+ searchInput.addEventListener("keydown", (ev) => {
630
+ if (ev.key === "Escape") { _clearSearch(); _setBrowser(false); searchInput.blur(); }
631
+ if (ev.key === "Enter") {
632
+ const first = searchPanel.querySelector("[data-id]");
633
+ if (first) first.click();
634
+ }
635
+ });
636
+ document.addEventListener("keydown", (ev) => {
637
+ if ((ev.ctrlKey || ev.metaKey) && ev.key.toLowerCase() === "k") {
638
+ ev.preventDefault(); searchInput.focus(); searchInput.select();
639
+ }
640
+ });
641
+ _clearSearch();
642
+ }
643
+
644
+ if (navMode) {
645
+ navMode.addEventListener("click", () => {
646
+ _clearSearch();
647
+ _setBrowser(tabsEl.dataset.browse !== "true");
648
+ });
649
+ }
650
+
651
  // Mark every tab button carrying `id` as selected (a flagship is in both the Featured row
652
  // and its category panel), auto-open the active surface's category, and proof-teal-flag the
653
  // owning dropdown button so the current location is always legible.
654
  function _highlight(id) {
655
  const def = SURFACES.find((s) => s.id === id);
656
  const cat = def ? catOf(def) : null;
657
+ if (activeSurface) {
658
+ activeSurface.textContent = def ? _shortLabel(def) : id;
659
+ activeSurface.title = def ? def.title : id;
660
+ }
661
  tabsEl.querySelectorAll("[data-id]").forEach((t) =>
662
  t.setAttribute("aria-selected", String(t.dataset.id === id)));
 
663
  Object.keys(_catBtns).forEach((c) =>
664
  _catBtns[c].setAttribute("data-hasactive", String(c === cat)));
665
  }
static/3d/surfaces/_showcase.js CHANGED
@@ -308,6 +308,9 @@ export function createShowcase(ctx, opts = {}) {
308
  // fadeNear : number camera distance at full opacity (default 8)
309
  // fadeFar : number camera distance at min opacity (default 60)
310
  // minOpacity: number floor opacity for far labels (default 0.15)
 
 
 
311
  // container : DOM overlay host (default ctx.container)
312
  // ---------------------------------------------------------------------------
313
  export function createSceneLabels(ctx, opts = {}) {
@@ -329,6 +332,9 @@ export function createSceneLabels(ctx, opts = {}) {
329
  const fadeNear = opts.fadeNear != null ? opts.fadeNear : 8;
330
  const fadeFar = opts.fadeFar != null ? opts.fadeFar : 60;
331
  const minOpacity = opts.minOpacity != null ? opts.minOpacity : 0.15;
 
 
 
332
 
333
  const layer = document.createElement("div");
334
  layer.className = "szl-lbl-layer";
@@ -387,7 +393,18 @@ export function createSceneLabels(ctx, opts = {}) {
387
  canvas.addEventListener("pointerleave", _onLeave, { passive: true });
388
  }
389
 
390
- function _place(el, obj, text, isHover) {
 
 
 
 
 
 
 
 
 
 
 
391
  if (!obj) { el.style.display = "none"; return; }
392
  obj.getWorldPosition(proj);
393
  const dist = camera.position.distanceTo(proj);
@@ -399,24 +416,41 @@ export function createSceneLabels(ctx, opts = {}) {
399
  // fade with distance, size-capped (font stays fixed → size cap)
400
  let op = 1;
401
  if (dist > fadeNear) op = Math.max(minOpacity, 1 - (dist - fadeNear) / Math.max(1, fadeFar - fadeNear));
402
- el.textContent = text;
403
  el.style.left = x + "px";
404
  el.style.top = y + "px";
405
  el.style.opacity = String(isHover ? Math.max(op, 0.9) : op);
406
  el.style.display = "block";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
  }
408
 
409
  function update() {
410
  const objs = getObjects();
411
  // persistent top-N by weight
412
  if (topN) {
 
413
  const ranked = objs
414
  .map((o) => ({ o, w: getWeight(o) }))
415
  .sort((a, b) => b.w - a.w)
416
  .slice(0, topN);
417
  for (let i = 0; i < pool.length; i++) {
418
  const item = ranked[i];
419
- if (item && item.o) _place(pool[i], item.o, getText(item.o) || "", false);
420
  else pool[i].style.display = "none";
421
  }
422
  }
 
308
  // fadeNear : number camera distance at full opacity (default 8)
309
  // fadeFar : number camera distance at min opacity (default 60)
310
  // minOpacity: number floor opacity for far labels (default 0.15)
311
+ // avoidOverlap: boolean suppress lower-ranked colliding labels
312
+ // minGap : number collision padding in CSS pixels (default 8)
313
+ // maxLength : number persistent-label character cap (hover stays full)
314
  // container : DOM overlay host (default ctx.container)
315
  // ---------------------------------------------------------------------------
316
  export function createSceneLabels(ctx, opts = {}) {
 
332
  const fadeNear = opts.fadeNear != null ? opts.fadeNear : 8;
333
  const fadeFar = opts.fadeFar != null ? opts.fadeFar : 60;
334
  const minOpacity = opts.minOpacity != null ? opts.minOpacity : 0.15;
335
+ const avoidOverlap = opts.avoidOverlap === true;
336
+ const minGap = Math.max(0, opts.minGap != null ? opts.minGap : 8);
337
+ const maxLength = Math.max(12, opts.maxLength || 54);
338
 
339
  const layer = document.createElement("div");
340
  layer.className = "szl-lbl-layer";
 
393
  canvas.addEventListener("pointerleave", _onLeave, { passive: true });
394
  }
395
 
396
+ function _visibleText(text, isHover) {
397
+ const raw = String(text || "");
398
+ if (isHover || raw.length <= maxLength) return raw;
399
+ return raw.slice(0, maxLength - 1).replace(/\s+\S*$/, "") + "…";
400
+ }
401
+
402
+ function _overlaps(a, b) {
403
+ return !(a.right + minGap <= b.left || b.right + minGap <= a.left ||
404
+ a.bottom + minGap <= b.top || b.bottom + minGap <= a.top);
405
+ }
406
+
407
+ function _place(el, obj, text, isHover, occupied) {
408
  if (!obj) { el.style.display = "none"; return; }
409
  obj.getWorldPosition(proj);
410
  const dist = camera.position.distanceTo(proj);
 
416
  // fade with distance, size-capped (font stays fixed → size cap)
417
  let op = 1;
418
  if (dist > fadeNear) op = Math.max(minOpacity, 1 - (dist - fadeNear) / Math.max(1, fadeFar - fadeNear));
419
+ el.textContent = _visibleText(text, isHover);
420
  el.style.left = x + "px";
421
  el.style.top = y + "px";
422
  el.style.opacity = String(isHover ? Math.max(op, 0.9) : op);
423
  el.style.display = "block";
424
+ // Persistent labels are processed in descending importance. When two labels would
425
+ // collide, retain the higher-ranked one and suppress the lower-ranked label. Hover
426
+ // remains full-fidelity and is never suppressed.
427
+ if (avoidOverlap && occupied && !isHover && el.getBoundingClientRect) {
428
+ const er = el.getBoundingClientRect();
429
+ const lr = layer.getBoundingClientRect();
430
+ const box = {
431
+ left: er.left - lr.left, right: er.right - lr.left,
432
+ top: er.top - lr.top, bottom: er.bottom - lr.top,
433
+ };
434
+ if (occupied.some((prior) => _overlaps(box, prior))) {
435
+ el.style.display = "none";
436
+ return;
437
+ }
438
+ occupied.push(box);
439
+ }
440
  }
441
 
442
  function update() {
443
  const objs = getObjects();
444
  // persistent top-N by weight
445
  if (topN) {
446
+ const occupied = [];
447
  const ranked = objs
448
  .map((o) => ({ o, w: getWeight(o) }))
449
  .sort((a, b) => b.w - a.w)
450
  .slice(0, topN);
451
  for (let i = 0; i < pool.length; i++) {
452
  const item = ranked[i];
453
+ if (item && item.o) _place(pool[i], item.o, getText(item.o) || "", false, occupied);
454
  else pool[i].style.display = "none";
455
  }
456
  }
static/3d/surfaces/brain.js CHANGED
@@ -65,9 +65,9 @@ const LAYER_R = { "-1": 8.5, "0": 4.2, "1": 2.0, "2": 3.2, "3": 1.6 };
65
  // render budget (LOD): keep the estate core + this many top-connected leaders
66
  const FIELD_TOP = 480; // default top-connected field leaders
67
  const MAX_NODES = 1650; // hard cap on rendered nodes (mobile-safe)
68
- const LABEL_MESH_MAX = 220; // nodes drawn as individual meshes (labelable)
69
- const MAX_EDGES = 6500; // rendered edge cap
70
- const FIRE_MAX = 220; // simultaneous firing pulses
71
 
72
  let _stage = null, _THREE = null, _ctx = null, _group = null, _show = null;
73
  let _badge = null, _polls = [], _frameReg = false, _t0 = 0;
@@ -140,10 +140,12 @@ function _nodeColor(n) {
140
  }
141
  function _nodeRadius(n) {
142
  const deg = n.degree || 0;
143
- let r = 0.14 + 0.055 * Math.sqrt(deg);
144
- if (n.locked) r = Math.max(r, 0.42);
145
- if (n.kind === "estate") r = Math.max(r, 0.55);
146
- return Math.min(r, 0.9);
 
 
147
  }
148
 
149
  // -------------------------------------------------------------------------- //
@@ -152,6 +154,9 @@ function _nodeRadius(n) {
152
  function mount(ctx) {
153
  _ctx = ctx; _stage = ctx.stage; _THREE = ctx.THREE;
154
  _group = new _THREE.Group();
 
 
 
155
  _stage.scene.add(_group);
156
  _t0 = (typeof performance !== "undefined" ? performance.now() : Date.now());
157
  _sphereGeo = new _THREE.SphereGeometry(1, 10, 8);
@@ -173,7 +178,8 @@ function mount(ctx) {
173
  objects: () => _labelMeshes,
174
  text: (o) => (o.userData && o.userData.node && (o.userData.node.title || o.userData.node.id)) || "",
175
  weight: (o) => (o.userData && o.userData.node && o.userData.node.degree) || 0,
176
- topN: 12, hover: true, fadeNear: 10, fadeFar: 70,
 
177
  });
178
  }
179
 
 
65
  // render budget (LOD): keep the estate core + this many top-connected leaders
66
  const FIELD_TOP = 480; // default top-connected field leaders
67
  const MAX_NODES = 1650; // hard cap on rendered nodes (mobile-safe)
68
+ const LABEL_MESH_MAX = 144; // bounded interactive leaders; long tail stays instanced
69
+ const MAX_EDGES = 5200; // visual edge cap (the API still exposes the complete graph)
70
+ const FIRE_MAX = 160; // simultaneous firing pulses
71
 
72
  let _stage = null, _THREE = null, _ctx = null, _group = null, _show = null;
73
  let _badge = null, _polls = [], _frameReg = false, _t0 = 0;
 
140
  }
141
  function _nodeRadius(n) {
142
  const deg = n.degree || 0;
143
+ // Log degree prevents a handful of hubs from occluding the graph. Degree still controls
144
+ // size, but the visual cannot turn a popular node into an opaque wall.
145
+ let r = 0.08 + 0.035 * Math.log2(1 + deg);
146
+ if (n.locked) r = Math.max(r, 0.22);
147
+ if (n.kind === "estate") r = Math.max(r, 0.28);
148
+ return Math.min(r, 0.30);
149
  }
150
 
151
  // -------------------------------------------------------------------------- //
 
154
  function mount(ctx) {
155
  _ctx = ctx; _stage = ctx.stage; _THREE = ctx.THREE;
156
  _group = new _THREE.Group();
157
+ // Center the bounded graph in the viewport left by the compact evidence card.
158
+ _group.position.x = 1.0;
159
+ _group.scale.setScalar(0.66);
160
  _stage.scene.add(_group);
161
  _t0 = (typeof performance !== "undefined" ? performance.now() : Date.now());
162
  _sphereGeo = new _THREE.SphereGeometry(1, 10, 8);
 
178
  objects: () => _labelMeshes,
179
  text: (o) => (o.userData && o.userData.node && (o.userData.node.title || o.userData.node.id)) || "",
180
  weight: (o) => (o.userData && o.userData.node && o.userData.node.degree) || 0,
181
+ topN: 3, hover: true, fadeNear: 12, fadeFar: 55,
182
+ avoidOverlap: true, minGap: 10, maxLength: 38,
183
  });
184
  }
185
 
static/3d/surfaces/integritycontrol.js ADDED
@@ -0,0 +1,360 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Stephen P. Lutar Jr. / SZL Holdings
3
+ //
4
+ // Integrity Control Plane -- a compact, read-only view of two local contracts:
5
+ // GET /api/a11oy/v1/waqay/security-loop/manifest
6
+ // GET /api/a11oy/v1/claim-integrity/info
7
+ //
8
+ // This surface does not invoke an action route. A successful fetch means only that the
9
+ // contract manifest was read. It does NOT mean that a deployer, rollback mechanism, model,
10
+ // signer, repository writer, or any other effector is live. The view reports the API fields
11
+ // verbatim and treats any boundary mismatch as a violation rather than rounding it up.
12
+
13
+ import { createShowcase } from "./_showcase.js";
14
+
15
+ const ID = "integritycontrol";
16
+ const TITLE = "Integrity Control Plane · proposal boundaries";
17
+ const SECURITY_EP = "/api/a11oy/v1/waqay/security-loop/manifest";
18
+ const CLAIM_EP = "/api/a11oy/v1/claim-integrity/info";
19
+
20
+ const C_TEAL = 0x3af4c8;
21
+ const C_BLUE = 0x5b8dee;
22
+ const C_AMBER = 0xe8c074;
23
+ const C_RED = 0xff5964;
24
+ const C_DIM = 0x42505d;
25
+ const C_GRID = 0x1b3a44;
26
+
27
+ let _ctx = null;
28
+ let _stage = null;
29
+ let _THREE = null;
30
+ let _group = null;
31
+ let _show = null;
32
+ let _polls = [];
33
+ let _frameRegistered = false;
34
+ let _core = null;
35
+ let _ring = null;
36
+ let _nodes = [];
37
+ let _links = [];
38
+ let _boundaryPill = null;
39
+
40
+ const S = {
41
+ security: null,
42
+ claim: null,
43
+ fetch: { security: "init", claim: "init" },
44
+ mode: null,
45
+ effectors: null,
46
+ signing: null,
47
+ verdict: "AWAITING-CONTRACTS",
48
+ };
49
+
50
+ function _isObject(v) { return !!v && typeof v === "object" && !Array.isArray(v); }
51
+ function _upper(v) { return v == null ? null : String(v).trim().toUpperCase(); }
52
+ function _number(v) { return typeof v === "number" && Number.isFinite(v) ? v : null; }
53
+
54
+ // Pure boundary reducer. No fallback values are invented: a field is known only after the
55
+ // corresponding endpoint supplies it. Exported so contract tests can pin the reducer.
56
+ export function deriveIntegrityBoundary(security, claim) {
57
+ const sec = _isObject(security) ? security : null;
58
+ const clm = _isObject(claim) ? claim : null;
59
+ const complete = !!sec && !!clm;
60
+
61
+ const securityMode = sec ? _upper(sec.mode) : null;
62
+ const claimMode = clm ? _upper(clm.decision_state) : null;
63
+ const securityEffectors = sec ? _number(sec.effectors) : null;
64
+ const claimEffectors = clm ? _number(clm.effectors_enabled) : null;
65
+ const externalMutations = sec ? _upper(sec.external_mutations) : null;
66
+
67
+ const secReceipt = sec && _isObject(sec.receipt) ? sec.receipt : null;
68
+ const signatureDefault = secReceipt ? _upper(secReceipt.signature_default) : null;
69
+ const claimMissing = clm && Array.isArray(clm.not_implemented_here)
70
+ ? clm.not_implemented_here.map(_upper) : [];
71
+ const signingAbsent = !!signatureDefault && signatureDefault.includes("UNSIGNED") &&
72
+ claimMissing.includes("SIGNING");
73
+
74
+ const proposalOnly = complete && securityMode === "PROPOSAL_ONLY" &&
75
+ claimMode === "PROPOSAL_ONLY";
76
+ const zeroEffectors = complete && securityEffectors === 0 && claimEffectors === 0;
77
+ const mutationsDisabled = complete && externalMutations === "DISABLED";
78
+
79
+ let verdict = "AWAITING-CONTRACTS";
80
+ if (complete) {
81
+ verdict = proposalOnly && zeroEffectors && mutationsDisabled
82
+ ? "BOUNDARIES-INTACT"
83
+ : "BOUNDARY-VIOLATION";
84
+ }
85
+
86
+ return {
87
+ complete,
88
+ securityMode,
89
+ claimMode,
90
+ securityEffectors,
91
+ claimEffectors,
92
+ externalMutations,
93
+ signing: signingAbsent ? "UNSIGNED" : (complete ? "UNKNOWN" : null),
94
+ proposalOnly,
95
+ zeroEffectors,
96
+ mutationsDisabled,
97
+ verdict,
98
+ };
99
+ }
100
+
101
+ function _derive() {
102
+ const d = deriveIntegrityBoundary(S.security, S.claim);
103
+ S.mode = d.proposalOnly ? "PROPOSAL_ONLY" :
104
+ (d.complete ? `${d.securityMode || "UNKNOWN"} / ${d.claimMode || "UNKNOWN"}` : null);
105
+ S.effectors = d.zeroEffectors ? 0 :
106
+ (d.complete ? `${d.securityEffectors ?? "?"} / ${d.claimEffectors ?? "?"}` : null);
107
+ S.signing = d.signing;
108
+ S.verdict = d.verdict;
109
+ _paint();
110
+ }
111
+
112
+ export function mount(ctx) {
113
+ _ctx = ctx;
114
+ _stage = ctx.stage;
115
+ _THREE = ctx.THREE;
116
+ _group = new _THREE.Group();
117
+ _stage.scene.add(_group);
118
+ _stage.camera.position.set(0, 5.5, 16);
119
+ try {
120
+ if (_stage.controls && _stage.controls.target) {
121
+ _stage.controls.target.set(0, 1.5, 0);
122
+ _stage.controls.update();
123
+ }
124
+ } catch (_) {}
125
+ try { _stage.setBloom(true); } catch (_) {}
126
+
127
+ _buildScene();
128
+ _buildOverlay();
129
+ if (!_frameRegistered) { _stage.onFrame(_onFrame); _frameRegistered = true; }
130
+
131
+ _polls.push(ctx.live.poll(SECURITY_EP, 10000, (j) => {
132
+ S.security = j;
133
+ _derive();
134
+ }, {
135
+ onState: (m) => {
136
+ S.fetch.security = m.state;
137
+ if (m.state !== "live" && m.state !== "degraded") S.security = null;
138
+ _derive();
139
+ },
140
+ }));
141
+ _polls.push(ctx.live.poll(CLAIM_EP, 10000, (j) => {
142
+ S.claim = j;
143
+ _derive();
144
+ }, {
145
+ onState: (m) => {
146
+ S.fetch.claim = m.state;
147
+ if (m.state !== "live" && m.state !== "degraded") S.claim = null;
148
+ _derive();
149
+ },
150
+ }));
151
+
152
+ _paint();
153
+ return { id: ID, started: true };
154
+ }
155
+
156
+ function _buildScene() {
157
+ const THREE = _THREE;
158
+ const grid = new THREE.GridHelper(32, 32, C_GRID, 0x0f2027);
159
+ grid.material.opacity = 0.15;
160
+ grid.material.transparent = true;
161
+ _group.add(grid);
162
+
163
+ _core = new THREE.Mesh(
164
+ new THREE.IcosahedronGeometry(1.15, 1),
165
+ new THREE.MeshStandardMaterial({
166
+ color: C_DIM, emissive: C_DIM, emissiveIntensity: 0.2,
167
+ transparent: true, opacity: 0.78, wireframe: true,
168
+ }),
169
+ );
170
+ _core.position.y = 1.6;
171
+ _group.add(_core);
172
+
173
+ _ring = new THREE.Mesh(
174
+ new THREE.TorusGeometry(4.25, 0.035, 8, 96),
175
+ new THREE.MeshBasicMaterial({ color: C_BLUE, transparent: true, opacity: 0.38 }),
176
+ );
177
+ _ring.rotation.x = Math.PI / 2;
178
+ _ring.position.y = 1.6;
179
+ _group.add(_ring);
180
+
181
+ // Six points mirror the manifest's proposal-state topology. They are structural nodes,
182
+ // never active deployers. No per-node text is rendered, keeping the scene uncluttered.
183
+ const count = 6;
184
+ for (let i = 0; i < count; i += 1) {
185
+ const a = (i / count) * Math.PI * 2;
186
+ const node = new THREE.Mesh(
187
+ new THREE.OctahedronGeometry(0.28, 0),
188
+ new THREE.MeshStandardMaterial({
189
+ color: C_BLUE, emissive: C_BLUE, emissiveIntensity: 0.2,
190
+ transparent: true, opacity: 0.8,
191
+ }),
192
+ );
193
+ node.position.set(Math.cos(a) * 4.25, 1.6, Math.sin(a) * 4.25);
194
+ _group.add(node);
195
+ _nodes.push(node);
196
+
197
+ const linkGeo = new THREE.BufferGeometry().setFromPoints([
198
+ new THREE.Vector3(0, 1.6, 0), node.position.clone(),
199
+ ]);
200
+ const link = new THREE.Line(linkGeo, new THREE.LineBasicMaterial({
201
+ color: C_GRID, transparent: true, opacity: 0.28,
202
+ }));
203
+ _group.add(link);
204
+ _links.push(link);
205
+ }
206
+ }
207
+
208
+ function _buildOverlay() {
209
+ _show = createShowcase(_ctx, {
210
+ id: ID,
211
+ title: TITLE,
212
+ accent: "#3af4c8",
213
+ chips: [
214
+ {
215
+ label: "STRUCTURAL-ONLY",
216
+ text: "read-only manifests",
217
+ name: "honesty",
218
+ title: "Contract structure read from two same-origin endpoints; not an operational effector.",
219
+ },
220
+ ],
221
+ description:
222
+ "A compact view of the <b>Waqay Security Loop</b> and <b>Claim Integrity</b> " +
223
+ "contracts. It reads two same-origin GET endpoints and checks their control boundaries " +
224
+ "without invoking a deploy, rollback, write, model, signer, or repository action. " +
225
+ "<b>LIVE</b> here can only mean a manifest fetch succeeded; it never means effectors are live.",
226
+ citations:
227
+ "SZL-native clean-room control plane · read-only contract manifests · " +
228
+ "no copied third-party interface, branding, or implementation.",
229
+ plain: {
230
+ label: "◑ boundary meaning",
231
+ html: () =>
232
+ "<b>What this means:</b> The system exposes the rules that a future security workflow " +
233
+ "would have to obey. Today it may produce proposals only. The reported effector count " +
234
+ "must be zero, external mutations must be disabled, and signatures remain unsigned " +
235
+ "unless a real signer and independent verification are added. A manifest is a contract, " +
236
+ "not proof that production automation exists.",
237
+ },
238
+ });
239
+
240
+ _boundaryPill = document.createElement("span");
241
+ _boundaryPill.setAttribute("data-integrity-boundary", "awaiting");
242
+ Object.assign(_boundaryPill.style, {
243
+ display: "inline-block", font: "600 10.5px ui-monospace,monospace",
244
+ letterSpacing: ".35px", padding: "2px 8px", borderRadius: "5px",
245
+ color: "#111820", background: "#e8c074", border: "1px solid rgba(255,255,255,.12)",
246
+ });
247
+ _show.pills.appendChild(_boundaryPill);
248
+
249
+ _show.addField("control state", "mode");
250
+ _show.addField("effectors (security / claim)", "effectors");
251
+ _show.addField("external mutations", "mutations");
252
+ _show.addField("signature boundary", "signing");
253
+ _show.addField("contract endpoints", "feeds");
254
+ _show.addField("allowed output", "output");
255
+ }
256
+
257
+ function _field(key, value, color) {
258
+ if (!_show) return;
259
+ const el = _show.field(key);
260
+ if (!el) return;
261
+ el.textContent = value;
262
+ if (color) el.style.color = color;
263
+ }
264
+
265
+ function _paint() {
266
+ if (!_show) return;
267
+ const d = deriveIntegrityBoundary(S.security, S.claim);
268
+ const fetched = [S.fetch.security, S.fetch.claim].filter((v) => v === "live").length;
269
+
270
+ let color = "#e8c074";
271
+ let bg = "#e8c074";
272
+ let text = "AWAITING CONTRACTS";
273
+ if (d.verdict === "BOUNDARIES-INTACT") {
274
+ color = "#3af4c8";
275
+ bg = "#3af4c8";
276
+ text = "READ-ONLY BOUNDARIES INTACT";
277
+ } else if (d.verdict === "BOUNDARY-VIOLATION") {
278
+ color = "#ff5964";
279
+ bg = "#ff5964";
280
+ text = "BOUNDARY VIOLATION";
281
+ }
282
+
283
+ if (_boundaryPill) {
284
+ _boundaryPill.dataset.integrityBoundary = d.verdict.toLowerCase();
285
+ _boundaryPill.textContent = text;
286
+ _boundaryPill.style.background = bg;
287
+ }
288
+ if (_core && _core.material) {
289
+ const hex = d.verdict === "BOUNDARIES-INTACT" ? C_TEAL :
290
+ (d.verdict === "BOUNDARY-VIOLATION" ? C_RED : C_AMBER);
291
+ _core.material.color.setHex(hex);
292
+ _core.material.emissive.setHex(hex);
293
+ }
294
+ _nodes.forEach((node) => {
295
+ const hex = d.verdict === "BOUNDARY-VIOLATION" ? C_RED :
296
+ (d.verdict === "BOUNDARIES-INTACT" ? C_TEAL : C_DIM);
297
+ node.material.color.setHex(hex);
298
+ node.material.emissive.setHex(hex);
299
+ });
300
+
301
+ _field("mode", d.complete ? (d.proposalOnly ? "PROPOSAL_ONLY" :
302
+ `${d.securityMode || "UNKNOWN"} / ${d.claimMode || "UNKNOWN"}`) : "NO-LIVE-DATA", color);
303
+ _field("effectors", d.complete ?
304
+ `${d.securityEffectors ?? "?"} / ${d.claimEffectors ?? "?"}` : "NO-LIVE-DATA",
305
+ d.zeroEffectors ? "#3af4c8" : color);
306
+ _field("mutations", d.complete ? (d.externalMutations || "UNKNOWN") : "NO-LIVE-DATA",
307
+ d.mutationsDisabled ? "#3af4c8" : color);
308
+ _field("signing", d.signing || "NO-LIVE-DATA", d.signing === "UNSIGNED" ? "#e8c074" : color);
309
+ _field("feeds", `${fetched}/2 fetched`, fetched === 2 ? "#3af4c8" : "#e8c074");
310
+ _field("output", d.proposalOnly ? "proposal only · no action" : "NO ACTION", "#9fb1bf");
311
+ }
312
+
313
+ function _onFrame() {
314
+ const t = performance.now();
315
+ if (_ring) _ring.rotation.z = t * 0.00008;
316
+ if (_core) {
317
+ _core.rotation.x = t * 0.00017;
318
+ _core.rotation.y = t * 0.00023;
319
+ _core.material.emissiveIntensity = 0.22 + 0.08 * (0.5 + 0.5 * Math.sin(t * 0.002));
320
+ }
321
+ _nodes.forEach((node, i) => {
322
+ node.rotation.x = t * 0.00028 + i;
323
+ node.rotation.y = t * 0.00019 + i;
324
+ });
325
+ }
326
+
327
+ export function unmount() {
328
+ _polls.forEach((p) => { try { p.stop(); } catch (_) {} });
329
+ _polls = [];
330
+ try { if (_show) _show.destroy(); } catch (_) {}
331
+ try {
332
+ if (_group && _stage) {
333
+ _group.traverse((o) => {
334
+ if (o.geometry && o.geometry.dispose) o.geometry.dispose();
335
+ if (o.material) {
336
+ const mats = Array.isArray(o.material) ? o.material : [o.material];
337
+ mats.forEach((m) => { if (m && m.dispose) m.dispose(); });
338
+ }
339
+ });
340
+ _stage.scene.remove(_group);
341
+ }
342
+ } catch (_) {}
343
+
344
+ _ctx = _stage = _THREE = _group = _show = _core = _ring = _boundaryPill = null;
345
+ _nodes = [];
346
+ _links = [];
347
+ _frameRegistered = false;
348
+ S.security = S.claim = null;
349
+ S.fetch.security = S.fetch.claim = "init";
350
+ S.mode = S.effectors = S.signing = null;
351
+ S.verdict = "AWAITING-CONTRACTS";
352
+ }
353
+
354
+ export default {
355
+ id: ID,
356
+ title: TITLE,
357
+ endpoints: [SECURITY_EP, CLAIM_EP],
358
+ mount,
359
+ unmount,
360
+ };