Spaces:
Running
Running
deploy(hf): sync szl-holdings/a11oy@main derived COPY set
Browse filesReusable 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 +268 -0
- static/3d/holographic.html +122 -14
- static/3d/surfaces/_showcase.js +37 -3
- static/3d/surfaces/brain.js +14 -8
- static/3d/surfaces/integritycontrol.js +360 -0
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:
|
| 38 |
-
background:#070d15;flex:0 0 auto}
|
| 39 |
-
.navrow{display:flex;gap:6px;flex-wrap:
|
|
|
|
|
|
|
| 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:
|
| 91 |
-
.navrow::-webkit-scrollbar{
|
| 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="
|
| 165 |
-
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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", () =>
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 69 |
-
const MAX_EDGES =
|
| 70 |
-
const FIRE_MAX =
|
| 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 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
|
|
|
|
|
|
| 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:
|
|
|
|
| 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 |
+
};
|