betterwithage Claude Opus 4.7 commited on
Commit
2c557ce
·
verified ·
1 Parent(s): 9d184e3

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

Browse files

Reusable Dockerfile-COPY-derived deploy from szl-holdings/a11oy main.
Files: 1035 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>

static/3d/holographic.html CHANGED
@@ -330,6 +330,7 @@ const SURFACES = [
330
  { id: "brainconsensus", cat: "brain", title: "Brain Consensus · honest corroboration of a brain grounding · measures how MANY distinct nodes support a query and how many distinct communities they span (cross-community agreement is stronger than one clique) → CORROBORATED/WEAK-CORROBORATION/SINGLE-SOURCE, single-source-risk flag when support collapses to one node/community, never CORROBORATED while that flag is set, MODELED corroboration honesty not a truth guarantee, unsigned SHA-256 receipt-on-write", mod: "/static/3d/surfaces/brainconsensus.js" },
331
  { id: "brainqueryaudit", cat: "brain", title: "Brain Query Audit · append-only hash-linked ledger of brain queries + the honest verdict each returned · POST appends {query, verdict, grounding_label} and mints an UNSIGNED SHA-256 receipt chained to the prior entry (tamper-evident); GET recomputes the whole chain → CHAIN-INTACT/CHAIN-BROKEN (never softened), ephemeral in-memory ledger labelled honestly, MODELED, receipt-on-write-not-on-read", mod: "/static/3d/surfaces/brainqueryaudit.js" },
332
  { id: "brainlineage", cat: "brain", title: "Brain Lineage · node-origin chain · how each knowledge-graph node ENTERED the graph, read VERBATIM from its OWN real origin fields (source/url → structural derivation → none) → TRACED/PARTIAL-LINEAGE/UNKNOWN-ORIGIN, a node with no source is UNKNOWN-ORIGIN never a fabricated source, aggregate never TRACED while any origin UNKNOWN, unsigned SHA-256 receipt-on-write (node-origin lineage, NOT per-answer provenance, NOT build/model attestation)", mod: "/static/3d/surfaces/brainlineage.js" },
 
333
  ];
334
 
335
  const stageEl = document.getElementById("stage");
 
330
  { id: "brainconsensus", cat: "brain", title: "Brain Consensus · honest corroboration of a brain grounding · measures how MANY distinct nodes support a query and how many distinct communities they span (cross-community agreement is stronger than one clique) → CORROBORATED/WEAK-CORROBORATION/SINGLE-SOURCE, single-source-risk flag when support collapses to one node/community, never CORROBORATED while that flag is set, MODELED corroboration honesty not a truth guarantee, unsigned SHA-256 receipt-on-write", mod: "/static/3d/surfaces/brainconsensus.js" },
331
  { id: "brainqueryaudit", cat: "brain", title: "Brain Query Audit · append-only hash-linked ledger of brain queries + the honest verdict each returned · POST appends {query, verdict, grounding_label} and mints an UNSIGNED SHA-256 receipt chained to the prior entry (tamper-evident); GET recomputes the whole chain → CHAIN-INTACT/CHAIN-BROKEN (never softened), ephemeral in-memory ledger labelled honestly, MODELED, receipt-on-write-not-on-read", mod: "/static/3d/surfaces/brainqueryaudit.js" },
332
  { id: "brainlineage", cat: "brain", title: "Brain Lineage · node-origin chain · how each knowledge-graph node ENTERED the graph, read VERBATIM from its OWN real origin fields (source/url → structural derivation → none) → TRACED/PARTIAL-LINEAGE/UNKNOWN-ORIGIN, a node with no source is UNKNOWN-ORIGIN never a fabricated source, aggregate never TRACED while any origin UNKNOWN, unsigned SHA-256 receipt-on-write (node-origin lineage, NOT per-answer provenance, NOT build/model attestation)", mod: "/static/3d/surfaces/brainlineage.js" },
333
+ { id: "brainexplain", cat: "brain", title: "Brain Explain · transparent explanation of WHY the brain retrieved what it did · MODELED descriptive trace over the REAL retrieval subgraph (which query terms matched which seed nodes, per-node ppr-vs-salience rationale, communities traversed, each node's OWN label VERBATIM) → EXPLAINABLE/PARTIALLY-EXPLAINABLE/OPAQUE (never invents a rationale; honest OPAQUE beats a fake one), unsigned SHA-256 receipt-on-write", mod: "/static/3d/surfaces/brainexplain.js" },
334
  ];
335
 
336
  const stageEl = document.getElementById("stage");
static/3d/surfaces/brainexplain.js ADDED
@@ -0,0 +1,405 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // © 2026 Lutar, Stephen P. — SZL Holdings · ORCID 0009-0001-0110-4173 · Doctrine v11
3
+ //
4
+ // surfaces/brainexplain.js — BRAIN EXPLAIN. A transparent, human-readable account of
5
+ // WHY the brain retrieved what it did for a query. It reads the MODELED explanation
6
+ // trace from the backend (GET /brain/explain?q=) — which query terms matched which
7
+ // seed nodes, why each supporting node ranked where it did (personalized PageRank vs
8
+ // baseline salience), which communities were traversed, and each supporting node's OWN
9
+ // honesty label VERBATIM — and renders the supporting nodes as a lattice of rank bars.
10
+ //
11
+ // HONESTY (Doctrine v11 — labels read VERBATIM, never upgraded):
12
+ // * the trace is MODELED — a derived account over a REAL retrieval subgraph; it
13
+ // DESCRIBES the retrieval and never invents a rationale.
14
+ // * each supporting node's basis is the honest reason it was included
15
+ // (direct-term-match / substring-match / vector-similarity / graph-traversal /
16
+ // unattributed) — an unattributed node is shown as such, not rationalized.
17
+ // * verdict EXPLAINABLE / PARTIALLY-EXPLAINABLE / OPAQUE; an OPAQUE retrieval is
18
+ // never shown as EXPLAINABLE. With no query-matched seed it is honestly OPAQUE.
19
+ // * Λ = Conjecture 1 → GREY, never green. locked-proven = exactly 8.
20
+ // * palette: lattice-blue 0x5b8dee · violet-blue 0x8a6bff · proof-teal 0x3af4c8
21
+ // · greys. PURPLE BANNED. 0 runtime CDN (three.js via ctx.THREE).
22
+ //
23
+ // Surface export shape: export default { id, title, endpoints, mount(ctx), unmount() }
24
+ // ctx = { stage, container, live, label, THREE, szl3d }
25
+
26
+ import { createShowcase } from "./_showcase.js";
27
+
28
+ const ID = "brainexplain";
29
+ const TITLE = "Brain Explain — why the brain retrieved this";
30
+
31
+ // same-origin a11oy endpoints (canonical a-11-oy.com in prod; relative here)
32
+ const EP_INFO = "/api/a11oy/v1/brain/explain/info";
33
+ const EP_EXPLAIN = "/api/a11oy/v1/brain/explain";
34
+ const EP_RECEIPT = "/api/a11oy/v1/brain/explain/receipt";
35
+
36
+ // palette (doctrine v11) — NO purple
37
+ const C_OK = 0x3af4c8; // proof-teal — a direct query-term match
38
+ const C_MID = 0x5b8dee; // lattice-blue — graph traversal / neutral
39
+ const C_WARN = 0x8a6bff; // violet-blue — a MODELED similarity proxy (attention)
40
+ const C_CONJ = 0x5a6570; // GREY — unattributed / conjecture floor — never green
41
+ const C_BASE = 0x1b3a44; // dim base grid
42
+
43
+ const MAX_BARS = 24;
44
+ const DEFAULT_Q = "brain graph";
45
+
46
+ // basis → bar colour (the honest reason a node is in the grounding set)
47
+ const BASIS_DIRECT = "direct-term-match";
48
+ const BASIS_SUBSTRING = "substring-match";
49
+ const BASIS_VECTOR = "vector-similarity";
50
+ const BASIS_TRAVERSAL = "graph-traversal";
51
+ const BASIS_UNATTRIB = "unattributed";
52
+
53
+ let _stage = null, _THREE = null, _ctx = null, _group = null, _show = null;
54
+ let _badge = null, _frameReg = false, _t0 = 0, _inFlight = null;
55
+
56
+ // scene objects
57
+ let _boxGeo = null;
58
+ let _meshes = []; // per supporting-node rank bar meshes
59
+
60
+ const S = {
61
+ label: "MODELED", state: "idle",
62
+ query: DEFAULT_Q,
63
+ verdict: "OPAQUE", verdictReason: null,
64
+ seedMatches: [], // [{id, matched_terms, node_label, ...}]
65
+ supporting: [], // supporting_nodes (each with basis/ppr/why/node_label)
66
+ communities: [], // communities_traversed
67
+ summary: null, // counts rollup
68
+ receipt: null, // unsigned SHA-256 digest (POST only)
69
+ note: null,
70
+ };
71
+
72
+ // -------------------------------------------------------------------------- //
73
+ // mount
74
+ // -------------------------------------------------------------------------- //
75
+ function mount(ctx) {
76
+ _ctx = ctx; _stage = ctx.stage; _THREE = ctx.THREE;
77
+ _group = new _THREE.Group();
78
+ _stage.scene.add(_group);
79
+ _t0 = (typeof performance !== "undefined" ? performance.now() : Date.now());
80
+ _boxGeo = new _THREE.BoxGeometry(1, 1, 1);
81
+
82
+ _buildOverlay(ctx);
83
+ if (ctx.live && ctx.live.createBadge) {
84
+ _badge = ctx.live.createBadge();
85
+ if (_show) _show.setBadge(_badge);
86
+ }
87
+
88
+ if (_show) {
89
+ _show.attachSceneLabels({
90
+ objects: () => _meshes,
91
+ text: (o) => (o.userData && o.userData.node && o.userData.node.id) || "",
92
+ weight: (o) => (o.userData && o.userData.node && o.userData.node.ppr) || 0,
93
+ topN: 8, hover: true, fadeNear: 9, fadeFar: 70,
94
+ });
95
+ }
96
+
97
+ _fetchExplain(S.query);
98
+
99
+ if (!_frameReg && _stage.onFrame) { _stage.onFrame(_animate); _frameReg = true; }
100
+ }
101
+
102
+ function _readLabel(j, fallback) {
103
+ const lbl = (j && j.label != null) ? j.label : (fallback || "MODELED");
104
+ return String(lbl).toUpperCase();
105
+ }
106
+
107
+ function _setBadge(state) {
108
+ S.state = state;
109
+ if (_badge && _badge.set) { try { _badge.set(state); } catch (_) {} }
110
+ }
111
+
112
+ // -------------------------------------------------------------------------- //
113
+ // data — GET the explanation trace for a query. PURE READ; mints nothing.
114
+ // -------------------------------------------------------------------------- //
115
+ function _fetchExplain(q) {
116
+ S.query = q;
117
+ _setBadge("loading");
118
+ S.state = "loading";
119
+ _paintOverlay();
120
+
121
+ const ctrl = ("AbortController" in window) ? new AbortController() : null;
122
+ if (_inFlight && _inFlight.abort) { try { _inFlight.abort(); } catch (_) {} }
123
+ _inFlight = ctrl;
124
+
125
+ const url = EP_EXPLAIN + "?q=" + encodeURIComponent(q || "") + "&k=12";
126
+ fetch(url, { headers: { accept: "application/json" },
127
+ signal: ctrl ? ctrl.signal : undefined })
128
+ .then((r) => (r.ok ? r.json() : Promise.reject(new Error("http " + r.status))))
129
+ .then((j) => { _inFlight = null; _onExplain(j); })
130
+ .catch((e) => {
131
+ if (e && e.name === "AbortError") return;
132
+ _inFlight = null;
133
+ S.state = "error"; _setBadge("error"); _paintOverlay();
134
+ });
135
+ }
136
+
137
+ // POST the query for a receipt (receipt-on-write) — same trace + unsigned SHA-256.
138
+ function _mintReceipt() {
139
+ _setBadge("loading");
140
+ S.state = "loading";
141
+ _paintOverlay();
142
+
143
+ const body = JSON.stringify({ q: S.query, k: 12 });
144
+ fetch(EP_RECEIPT, {
145
+ method: "POST",
146
+ headers: { "content-type": "application/json", accept: "application/json" },
147
+ body,
148
+ })
149
+ .then((r) => (r.ok ? r.json() : Promise.reject(new Error("http " + r.status))))
150
+ .then((j) => _onReceipt(j))
151
+ .catch(() => { S.state = "error"; _setBadge("error"); _paintOverlay(); });
152
+ }
153
+
154
+ function _ingest(j) {
155
+ S.label = _readLabel(j, "MODELED");
156
+ S.query = (j && j.query != null) ? String(j.query) : S.query;
157
+ S.verdict = String((j && j.verdict) || "OPAQUE").toUpperCase();
158
+ S.verdictReason = (j && j.verdict_reason) || null;
159
+ S.seedMatches = (j && j.seed_matches) || [];
160
+ S.supporting = (j && j.supporting_nodes) || [];
161
+ S.communities = (j && j.communities_traversed) || [];
162
+ S.summary = (j && j.summary) || null;
163
+ S.note = (j && (j.verdict_reason || j.error)) || null;
164
+ _rebuild();
165
+ }
166
+
167
+ function _onExplain(j) {
168
+ if (!j) { S.state = "error"; _setBadge("error"); _paintOverlay(); return; }
169
+ S.receipt = null;
170
+ _ingest(j);
171
+ S.state = "live"; _setBadge("live");
172
+ _paintOverlay();
173
+ }
174
+
175
+ function _onReceipt(j) {
176
+ if (!j) { S.state = "error"; _setBadge("error"); _paintOverlay(); return; }
177
+ _ingest(j);
178
+ S.receipt = j.receipt || null;
179
+ S.state = "live"; _setBadge("live");
180
+ _paintOverlay();
181
+ }
182
+
183
+ // -------------------------------------------------------------------------- //
184
+ // build — one bar per supporting node, height ∝ its personalized-PageRank rank.
185
+ // -------------------------------------------------------------------------- //
186
+ function _barColor(basis) {
187
+ if (basis === BASIS_DIRECT || basis === BASIS_SUBSTRING) return C_OK;
188
+ if (basis === BASIS_VECTOR) return C_WARN; // MODELED proxy → attention hue
189
+ if (basis === BASIS_UNATTRIB) return C_CONJ; // GREY — honestly unattributed
190
+ return C_MID; // graph-traversal / neutral
191
+ }
192
+
193
+ function _rebuild() {
194
+ if (!_group) return;
195
+ _clearScene();
196
+ const nodes = (S.supporting || []).slice(0, MAX_BARS);
197
+ if (!nodes.length) return;
198
+
199
+ // normalise ppr to [0,1] for bar height (descriptive — not a re-rank).
200
+ let maxPpr = 0;
201
+ nodes.forEach((n) => { const p = Number(n.ppr) || 0; if (p > maxPpr) maxPpr = p; });
202
+ const denom = maxPpr > 0 ? maxPpr : 1;
203
+
204
+ const n = nodes.length;
205
+ const spread = 12.0;
206
+ nodes.forEach((node, i) => {
207
+ const norm = Math.max(0, Math.min(1, (Number(node.ppr) || 0) / denom));
208
+ const h = 0.4 + 8.5 * norm;
209
+ const color = _barColor(node.basis);
210
+ const mat = new _THREE.MeshStandardMaterial({
211
+ color, emissive: color, emissiveIntensity: 0.22,
212
+ metalness: 0.12, roughness: 0.5, transparent: true, opacity: 0.95,
213
+ });
214
+ const mesh = new _THREE.Mesh(_boxGeo, mat);
215
+ mesh.scale.set(0.7, h, 0.7);
216
+ const x = (n === 1) ? 0 : (-spread / 2 + spread * (i / (n - 1)));
217
+ mesh.position.set(x, h / 2 - 3.0, 0);
218
+ mesh.userData = { node, baseGlow: 0.22 };
219
+ _meshes.push(mesh);
220
+ _group.add(mesh);
221
+ });
222
+ }
223
+
224
+ function _clearScene() {
225
+ _meshes.forEach((m) => {
226
+ if (m.material && m.material.dispose) m.material.dispose();
227
+ _group.remove(m);
228
+ });
229
+ _meshes = [];
230
+ }
231
+
232
+ // -------------------------------------------------------------------------- //
233
+ // animation: gentle rotation + soft breathing glow
234
+ // -------------------------------------------------------------------------- //
235
+ function _animate() {
236
+ if (!_group) return;
237
+ const now = (typeof performance !== "undefined" ? performance.now() : Date.now());
238
+ const t = (now - _t0) / 1000;
239
+ _group.rotation.y = Math.sin(t * 0.08) * 0.4;
240
+ const pulse = 0.5 + 0.5 * Math.sin(t * 1.5);
241
+ _meshes.forEach((m) => {
242
+ if (!m.material) return;
243
+ const base = m.userData.baseGlow || 0.22;
244
+ m.material.emissiveIntensity = base * (0.8 + 0.3 * pulse);
245
+ });
246
+ }
247
+
248
+ // -------------------------------------------------------------------------- //
249
+ // overlay (shared showcase helper)
250
+ // -------------------------------------------------------------------------- //
251
+ const _el = {};
252
+
253
+ function _buildOverlay(ctx) {
254
+ _show = createShowcase(ctx, {
255
+ id: ID, title: TITLE, accent: "#5b8dee", startExpanded: true,
256
+ chips: [{ label: "MODELED", text: "trace", name: "src" }],
257
+ legend: ["MODELED", "EXPLAINABLE", "PARTIALLY-EXPLAINABLE", "OPAQUE"],
258
+ description:
259
+ "<b>See WHY the brain retrieved what it did.</b> Type a query and the server reuses the " +
260
+ "SAME honest retrieval the brain already runs, then turns it into a deterministic, " +
261
+ "plain-language account: which query <b>terms matched</b> which seed nodes, why each " +
262
+ "supporting node <b>ranked</b> where it did (personalized PageRank vs its baseline " +
263
+ "salience), which <b>communities</b> the grounding traversed, and each node's OWN honesty " +
264
+ "<b>label</b> read verbatim. Each supporting node is a bar; its <b>basis</b> (direct term " +
265
+ "match / similarity proxy / graph traversal / unattributed) sets its colour. The trace is " +
266
+ "<b>MODELED</b> — it DESCRIBES the retrieval, never inventing a rationale. Verdict: " +
267
+ "EXPLAINABLE / PARTIALLY-EXPLAINABLE / <b>OPAQUE</b> (when retrieval returns too little to " +
268
+ "explain — no rationale is fabricated).",
269
+ citations:
270
+ "Trace is MODELED over the LIVE retrieval from /api/a11oy/v1/brain/explain (pure read — no " +
271
+ "signing on GET) — the same honest ranked retrieval as /brain/ask. A receipt is minted only " +
272
+ "on POST /api/a11oy/v1/brain/explain/receipt (unsigned SHA-256 content digest, " +
273
+ "receipt-on-write). Λ = Conjecture 1 (grey, never proven green); nothing here touches the " +
274
+ "locked-8.",
275
+ plain: { html: _plainHtml },
276
+ });
277
+
278
+ // query row: input + explain + receipt buttons
279
+ const wrap = document.createElement("div");
280
+ wrap.style.cssText = "display:flex;gap:6px;align-items:center;flex-wrap:wrap";
281
+ const input = document.createElement("input");
282
+ input.type = "text";
283
+ input.value = S.query;
284
+ input.placeholder = "query…";
285
+ input.style.cssText =
286
+ "font:600 12px ui-monospace,Menlo,monospace;padding:6px 9px;border-radius:8px;" +
287
+ "border:1px solid #1c2836;background:#0a1117;color:#c9d6df;flex:1;min-width:120px";
288
+ input.addEventListener("keydown", (e) => { if (e.key === "Enter") _fetchExplain(input.value); });
289
+ _el.input = input;
290
+ const expBtn = document.createElement("button");
291
+ expBtn.type = "button";
292
+ expBtn.textContent = "explain";
293
+ expBtn.style.cssText =
294
+ "font:600 12px ui-monospace,Menlo,monospace;padding:7px 13px;border-radius:8px;" +
295
+ "cursor:pointer;border:1px solid #3af4c8;background:#08201a;color:#3af4c8";
296
+ expBtn.addEventListener("click", () => _fetchExplain(input.value));
297
+ const rcptBtn = document.createElement("button");
298
+ rcptBtn.type = "button";
299
+ rcptBtn.textContent = "receipt";
300
+ rcptBtn.style.cssText =
301
+ "font:600 12px ui-monospace,Menlo,monospace;padding:7px 13px;border-radius:8px;" +
302
+ "cursor:pointer;border:1px solid #1c2836;background:#0a1117;color:#c9d6df";
303
+ rcptBtn.addEventListener("click", () => _mintReceipt());
304
+ wrap.appendChild(input); wrap.appendChild(expBtn); wrap.appendChild(rcptBtn);
305
+ _show.appendBody(wrap);
306
+
307
+ // KPI rows
308
+ _el.verdict = _show.addField("Verdict");
309
+ _el.seeds = _show.addField("Seed matches");
310
+ _el.support = _show.addField("Supporting nodes");
311
+ _el.direct = _show.addField("Direct-match nodes");
312
+ _el.unattrib = _show.addField("Unattributed nodes");
313
+ _el.comms = _show.addField("Communities traversed");
314
+ _el.receipt = _show.addField("Receipt (SHA-256)");
315
+
316
+ // per-node rationale list
317
+ _el.nodes = document.createElement("div");
318
+ _el.nodes.style.cssText =
319
+ "display:flex;flex-direction:column;gap:3px;font-size:10.5px;color:#9fb1bf;margin-top:2px";
320
+ _show.appendBody(_el.nodes);
321
+
322
+ // honest note (verbatim from the endpoint)
323
+ _el.note = document.createElement("div");
324
+ _el.note.style.cssText = "font-size:9.5px;color:#6b7a86;line-height:1.5;margin-top:2px";
325
+ _show.appendBody(_el.note);
326
+ }
327
+
328
+ function _fmt(n) { return (n == null) ? "—" : Number(n).toLocaleString("en-US"); }
329
+
330
+ function _paintOverlay() {
331
+ if (!_show) return;
332
+ _show.setChip("src", S.label || "MODELED", { text: "trace" });
333
+
334
+ const set = (k, v) => { if (_el[k]) _el[k].textContent = v; };
335
+ const loading = S.state === "loading";
336
+ const sm = S.summary || {};
337
+
338
+ set("verdict", loading ? "loading…" : (S.verdict || "OPAQUE"));
339
+ set("seeds", loading ? "…" : _fmt(sm.seed_count != null ? sm.seed_count : (S.seedMatches || []).length));
340
+ set("support", loading ? "…" : _fmt(sm.supporting_count != null ? sm.supporting_count : (S.supporting || []).length));
341
+ set("direct", loading ? "…" : _fmt(sm.direct_match_count));
342
+ set("unattrib", loading ? "…" : _fmt(sm.unattributed_count));
343
+ set("comms", loading ? "…" : _fmt(sm.community_count != null ? sm.community_count : (S.communities || []).length));
344
+ set("receipt", S.receipt ? String(S.receipt.content_sha256 || "").slice(0, 16) + "…"
345
+ : "— (POST to mint)");
346
+
347
+ // per-node rationale (rank · basis · verbatim label)
348
+ if (_el.nodes) {
349
+ _el.nodes.textContent = "";
350
+ (S.supporting || []).slice(0, 12).forEach((n) => {
351
+ const line = document.createElement("div");
352
+ line.style.cssText = "white-space:nowrap;overflow:hidden;text-overflow:ellipsis";
353
+ const terms = (n.matched_terms && n.matched_terms.length)
354
+ ? " [" + n.matched_terms.join(", ") + "]" : "";
355
+ line.textContent = "#" + _fmt(n.rank) + " · " + (n.id || "?") + " · " +
356
+ (n.basis || "?") + terms + " · " + (n.node_label || "UNLABELLED");
357
+ _el.nodes.appendChild(line);
358
+ });
359
+ }
360
+
361
+ if (_el.note) {
362
+ let note = S.note || "";
363
+ if (S.state === "error") note = "explain error — the brain-explain API did not respond.";
364
+ _el.note.textContent = note;
365
+ }
366
+
367
+ if (_show.refreshPlain) _show.refreshPlain();
368
+ }
369
+
370
+ function _plainHtml() {
371
+ return (
372
+ "This shows the brain's <b>reasoning receipt</b> for a search. When the brain retrieves " +
373
+ "knowledge for a query, this surface explains WHY: which of your search <b>words</b> literally " +
374
+ "matched which pieces of knowledge, why each supporting piece was ranked where it was (a " +
375
+ "measure called personalized PageRank vs how important the node is on its own), which " +
376
+ "<b>clusters</b> of the graph were walked, and what honesty <b>label</b> each piece carries — " +
377
+ "shown exactly as stored, never upgraded. That account is <b>MODELED</b>: it DESCRIBES the real " +
378
+ "retrieval and never makes up a reason. If the search matched too little to explain, it honestly " +
379
+ "says <b>OPAQUE</b> instead of inventing one. Label <b>" + (S.label || "MODELED") + "</b>."
380
+ );
381
+ }
382
+
383
+ // -------------------------------------------------------------------------- //
384
+ // unmount
385
+ // -------------------------------------------------------------------------- //
386
+ function unmount() {
387
+ try { if (_inFlight && _inFlight.abort) _inFlight.abort(); } catch (_) {}
388
+ _inFlight = null;
389
+ try { if (_show) _show.destroy(); } catch (_) {}
390
+ try { _clearScene(); } catch (_) {}
391
+ try { if (_boxGeo) _boxGeo.dispose(); } catch (_) {}
392
+ try { if (_group && _stage) _stage.scene.remove(_group); } catch (_) {}
393
+ _boxGeo = null;
394
+ _group = _show = _badge = null;
395
+ Object.keys(_el).forEach((k) => delete _el[k]);
396
+ _frameReg = false;
397
+ _stage = _THREE = _ctx = null;
398
+ S.label = "MODELED"; S.state = "idle";
399
+ S.query = DEFAULT_Q;
400
+ S.verdict = "OPAQUE"; S.verdictReason = null;
401
+ S.seedMatches = []; S.supporting = []; S.communities = [];
402
+ S.summary = null; S.note = null; S.receipt = null;
403
+ }
404
+
405
+ export default { id: ID, title: TITLE, endpoints: [EP_INFO, EP_EXPLAIN, EP_RECEIPT], mount, unmount };