Spaces:
Running
Running
chore(sync): mirror static/3d kit + Dockerfile-COPY'd web/*.html to Space (hf-sync)
Browse filesAutomated 3D/web-page asset sync from szl-holdings/a11oy main via hf-sync.
web/*.html set parsed live from the Dockerfile COPY directives.
Added/updated: 53 file(s)
Deleted (gone from GitHub main): (none)
Keeps static/3d/** and every Dockerfile-COPY'd web/<page>.html identical
to GitHub main so an HF factory rebuild never drops a GitHub edit (closes
the chronic hf-module-drift red for the 3D kit + baked web pages).
- static/3d/surfaces/governance.js +706 -59
static/3d/surfaces/governance.js
CHANGED
|
@@ -1,77 +1,724 @@
|
|
| 1 |
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
-
// © 2026 Lutar, Stephen P. Jr. — SZL Holdings · Doctrine v11
|
| 3 |
//
|
| 4 |
-
// surfaces/governance.js — AI
|
| 5 |
//
|
| 6 |
-
// Leader/technique
|
| 7 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
//
|
| 9 |
-
//
|
| 10 |
-
//
|
| 11 |
-
//
|
| 12 |
-
//
|
| 13 |
-
//
|
| 14 |
-
//
|
| 15 |
-
//
|
| 16 |
-
// ctx.label the szl3d_label module (ctx.label.chip / .billboard / .legend)
|
| 17 |
-
// ctx.THREE the three module
|
| 18 |
-
// mount() attaches scene objects + starts polling. unmount() stops polls and removes
|
| 19 |
-
// DOM it added (the shell disposes the stage).
|
| 20 |
//
|
| 21 |
-
//
|
| 22 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
const ID = "governance";
|
| 25 |
-
const TITLE = "AI Governance";
|
| 26 |
-
const ENDPOINT = "/api/a11oy/v1/assurance/artifact";
|
| 27 |
-
const ACCENT = 0xb08fff;
|
| 28 |
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
});
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
return { id: ID, started: true };
|
| 68 |
}
|
| 69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
function unmount() {
|
| 71 |
-
try {
|
| 72 |
-
|
| 73 |
-
try { if (
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
}
|
| 76 |
|
| 77 |
-
export default { id: ID, title: TITLE, endpoints:
|
|
|
|
| 1 |
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
// © 2026 Lutar, Stephen P. Jr. — SZL Holdings · ORCID 0009-0001-0110-4173 · Doctrine v11
|
| 3 |
//
|
| 4 |
+
// surfaces/governance.js — AI GOVERNANCE / Assurance holographic surface (Dev5).
|
| 5 |
//
|
| 6 |
+
// Leader/technique modeled (we are MODELED ON, never claim to BE):
|
| 7 |
+
// GUAC v1.0 (OpenSSF) — Graph for Understanding Artifact Composition
|
| 8 |
+
// Sigstore / Rekor — append-only hash-chained Merkle transparency log
|
| 9 |
+
// SCITT (IETF) — Transparent Statement -> Ledger Entry -> Merkle proof
|
| 10 |
+
// Technique: 3D force-directed dependency knowledge graph + 3D Merkle hash-chain
|
| 11 |
+
// tree + compliance crosswalk heatmap.
|
| 12 |
//
|
| 13 |
+
// DOCTRINE v11 (LOCKED): WIRE TO LIVE DATA, never fabricate. Every value on screen
|
| 14 |
+
// traces to a real a11oy endpoint and carries its honesty label read straight off the
|
| 15 |
+
// JSON. The 5 assurance/forge routes are PENDING the Forge mesh deploy and currently
|
| 16 |
+
// 404 -> ctx.live.poll renders the honest NO-LIVE-DATA badge for each; this viz is
|
| 17 |
+
// built to the REAL data shapes the engines (artifact_behaviour_monitor.py,
|
| 18 |
+
// content_credentials.py, compliance_crosswalk.py, runtime_attestation.py,
|
| 19 |
+
// forge_governance.py, pq_signing.py) WILL return, so it lights up automatically on 200.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
//
|
| 21 |
+
// TEACHING POINT (doctrine): a SIGNATURE is NOT proof of safety — CVE-2026-45321
|
| 22 |
+
// (Mini Shai-Hulud): 84 @tanstack/* npm versions shipped VALID SLSA L3 / Sigstore
|
| 23 |
+
// provenance; provenance flagged ZERO; behaviour caught all 84. Λ = Conjecture 1
|
| 24 |
+
// (advisory governance, NOT proven trust / NOT proven safe).
|
| 25 |
+
//
|
| 26 |
+
// 0 runtime CDN: three via the page importmap (ctx.THREE); the SBOM force-graph reuses
|
| 27 |
+
// the repo-vendored 3d-force-graph UMD global at /vendor/3d-force-graph.min.js.
|
| 28 |
+
//
|
| 29 |
+
// CONTRACT: default-export { id, title, endpoints[], mount(ctx), unmount() }.
|
| 30 |
|
| 31 |
const ID = "governance";
|
| 32 |
+
const TITLE = "AI Governance · Assurance";
|
|
|
|
|
|
|
| 33 |
|
| 34 |
+
// The 5 gap routes (PENDING Forge mesh; 404 -> NO-LIVE-DATA until 200).
|
| 35 |
+
const EP = {
|
| 36 |
+
artifact: "/api/a11oy/v1/assurance/artifact", // artifact_behaviour_monitor.py
|
| 37 |
+
credential: "/api/a11oy/v1/assurance/credential", // content_credentials.py (C2PA)
|
| 38 |
+
compliance: "/api/a11oy/v1/assurance/compliance", // compliance_crosswalk.py + compliance.json
|
| 39 |
+
attest: "/api/a11oy/v1/assurance/attest", // runtime_attestation.py
|
| 40 |
+
ledger: "/api/a11oy/v1/forge/ledger", // forge_governance.py
|
| 41 |
+
};
|
| 42 |
+
const ENDPOINTS = Object.values(EP);
|
| 43 |
|
| 44 |
+
// Palette (matches the holographic shell tokens).
|
| 45 |
+
const C = {
|
| 46 |
+
accent: 0xb08fff, teal: 0x39d3c4, gold: 0xe8c074, red: 0xff6b6b,
|
| 47 |
+
green: 0x2fd07a, gray: 0x8a97a3, blue: 0x6fb1ff, cream: 0xeef3f6,
|
| 48 |
+
};
|
| 49 |
+
// Honest framework-status colors (IMPLEMENTED/PARTIAL/ROADMAP) for the heatmap.
|
| 50 |
+
const STATUS_COLOR = { IMPLEMENTED: 0x2fd07a, PARTIAL: 0xe8c074, ROADMAP: 0x8a97a3 };
|
| 51 |
|
| 52 |
+
let _stage = null, _THREE = null, _label = null, _live = null;
|
| 53 |
+
let _root = null; // THREE.Group holding everything we add to the scene
|
| 54 |
+
let _handles = []; // poll handles to stop on unmount
|
| 55 |
+
let _overlay = null, _graphPanel = null, _graph = null, _hud = {};
|
| 56 |
+
let _frameCb = null, _fgScript = null;
|
| 57 |
+
const _state = {}; // last meta per endpoint (for HUD)
|
| 58 |
+
|
| 59 |
+
// ---------------------------------------------------------------------------
|
| 60 |
+
// small helpers
|
| 61 |
+
// ---------------------------------------------------------------------------
|
| 62 |
+
function el(tag, css, txt) {
|
| 63 |
+
const e = document.createElement(tag);
|
| 64 |
+
if (css) e.style.cssText = css;
|
| 65 |
+
if (txt != null) e.textContent = txt;
|
| 66 |
+
return e;
|
| 67 |
+
}
|
| 68 |
+
function disposeObj(o) {
|
| 69 |
+
o.traverse?.((c) => {
|
| 70 |
+
if (c.geometry) c.geometry.dispose?.();
|
| 71 |
+
if (c.material) {
|
| 72 |
+
const m = c.material;
|
| 73 |
+
(Array.isArray(m) ? m : [m]).forEach((mm) => { mm.map?.dispose?.(); mm.dispose?.(); });
|
| 74 |
+
}
|
| 75 |
});
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
// ---------------------------------------------------------------------------
|
| 79 |
+
// mount
|
| 80 |
+
// ---------------------------------------------------------------------------
|
| 81 |
+
function mount(ctx) {
|
| 82 |
+
_stage = ctx.stage; _THREE = ctx.THREE; _label = ctx.label; _live = ctx.live;
|
| 83 |
+
const THREE = _THREE;
|
| 84 |
+
_root = new THREE.Group();
|
| 85 |
+
_stage.scene.add(_root);
|
| 86 |
+
|
| 87 |
+
_buildOverlay(ctx);
|
| 88 |
+
|
| 89 |
+
// --- the 7 in-scene scene demos (positioned around the estate) -----------
|
| 90 |
+
const merkle = buildMerkleTree(THREE); // Demo: Rekor-style 3D Merkle hash-chain tree
|
| 91 |
+
merkle.position.set(-7.5, 0, 0);
|
| 92 |
+
_root.add(merkle);
|
| 93 |
+
|
| 94 |
+
const helix = buildAttestationHelix(THREE); // Demo: attestation timeline helix
|
| 95 |
+
helix.position.set(7.5, -1, 0);
|
| 96 |
+
_root.add(helix);
|
| 97 |
+
|
| 98 |
+
const heatmap = buildComplianceHeatmap(THREE); // Demo: NIST/ISO/EU compliance heatmap (60/60/0)
|
| 99 |
+
heatmap.position.set(0, 4.2, -3);
|
| 100 |
+
_root.add(heatmap);
|
| 101 |
+
|
| 102 |
+
const ledger = buildLedgerChain(THREE); // Demo: Forge ledger hash-chain replay
|
| 103 |
+
ledger.position.set(0, -4.4, 0);
|
| 104 |
+
_root.add(ledger);
|
| 105 |
+
|
| 106 |
+
const axes = buildAttestationAxes(THREE); // Demo: 3-axis attestation (build/model/runtime)
|
| 107 |
+
axes.position.set(7.0, 4.0, -1);
|
| 108 |
+
_root.add(axes);
|
| 109 |
+
|
| 110 |
+
const pq = buildPqNode(THREE); // Demo: PQ hybrid Ed25519+ML-DSA node
|
| 111 |
+
pq.position.set(-7.5, 4.0, -1);
|
| 112 |
+
_root.add(pq);
|
| 113 |
+
|
| 114 |
+
const killSwitch = buildKillSwitch(THREE); // Demo: kill-switch indicator
|
| 115 |
+
killSwitch.position.set(0, 0, 6);
|
| 116 |
+
_root.add(killSwitch);
|
| 117 |
+
|
| 118 |
+
const callout = buildSigSafetyCallout(THREE); // Demo: signature ≠ safety teaching callout
|
| 119 |
+
callout.position.set(0, -8.2, 2);
|
| 120 |
+
_root.add(callout);
|
| 121 |
+
|
| 122 |
+
// doctrine billboards (every cluster is labelled honestly until its route is live)
|
| 123 |
+
_root.add(_label.billboard(THREE, "STRUCTURAL-ONLY", { text: "Merkle ledger", scale: 0.5, position: [-7.5, 4.4, 0] }));
|
| 124 |
+
_root.add(_label.billboard(THREE, "STRUCTURAL-ONLY", { text: "Attest helix", scale: 0.5, position: [7.5, 3.0, 0] }));
|
| 125 |
+
_root.add(_label.billboard(THREE, "MEASURED", { text: "Crosswalk 60/60/0", scale: 0.5, position: [0, 7.0, -3] }));
|
| 126 |
+
|
| 127 |
+
this_registerFrame(THREE, { merkle, helix, heatmap, ledger, axes, pq, killSwitch, callout });
|
| 128 |
+
|
| 129 |
+
// --- SBOM dependency force-graph (GUAC-style), reuses vendored ForceGraph3D
|
| 130 |
+
_initForceGraph();
|
| 131 |
+
|
| 132 |
+
// --- wire all 5 gap routes; each renders NO-LIVE-DATA honestly on 404 -----
|
| 133 |
+
startPolls(ctx);
|
| 134 |
|
| 135 |
return { id: ID, started: true };
|
| 136 |
}
|
| 137 |
|
| 138 |
+
// per-frame animation registered once with the stage.
|
| 139 |
+
function this_registerFrame(THREE, parts) {
|
| 140 |
+
let t = 0;
|
| 141 |
+
_frameCb = () => {
|
| 142 |
+
t += 0.016;
|
| 143 |
+
if (_root) _root.rotation.y = Math.sin(t * 0.08) * 0.06;
|
| 144 |
+
parts.helix.rotation.y += 0.006;
|
| 145 |
+
parts.merkle.rotation.y += 0.003;
|
| 146 |
+
parts.ledger.rotation.y -= 0.004;
|
| 147 |
+
// kill-switch pulse (armed=red glow throb; safe=steady green) — value set by poll
|
| 148 |
+
const ks = parts.killSwitch.userData;
|
| 149 |
+
if (ks.core) {
|
| 150 |
+
const armed = !!ks.armed;
|
| 151 |
+
const pulse = armed ? (0.5 + 0.5 * Math.sin(t * 6)) : 0.85;
|
| 152 |
+
ks.core.material.emissiveIntensity = (armed ? 0.6 : 0.3) + pulse * (armed ? 0.8 : 0.1);
|
| 153 |
+
}
|
| 154 |
+
// PQ node: two interlocked rings (Ed25519 + ML-DSA) counter-rotate
|
| 155 |
+
if (parts.pq.userData.ed) parts.pq.userData.ed.rotation.z += 0.01;
|
| 156 |
+
if (parts.pq.userData.ml) parts.pq.userData.ml.rotation.x -= 0.012;
|
| 157 |
+
// helix beads orbit on flowing
|
| 158 |
+
(parts.helix.userData.beads || []).forEach((b, i) => {
|
| 159 |
+
b.material.emissiveIntensity = 0.4 + 0.4 * Math.sin(t * 2 + i * 0.7);
|
| 160 |
+
});
|
| 161 |
+
};
|
| 162 |
+
_stage.onFrame(_frameCb);
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
// ===========================================================================
|
| 166 |
+
// DEMOS — 3D scene objects (built to the engine shapes; live values overlaid)
|
| 167 |
+
// ===========================================================================
|
| 168 |
+
|
| 169 |
+
// Demo 1: Rekor/SCITT-style 3D Merkle hash-chain tree (TubeGeometry branches).
|
| 170 |
+
// forge ledger uses a LINEAR khipu chain (prev_hash/entry_hash, no merkle); the
|
| 171 |
+
// Rekor inclusion proof is a binary Merkle tree — we render the canonical Merkle
|
| 172 |
+
// structure and color leaves by signature freshness once /forge/ledger is live.
|
| 173 |
+
function buildMerkleTree(THREE) {
|
| 174 |
+
const g = new THREE.Group(); g.userData.kind = "merkle";
|
| 175 |
+
const leafMat = () => new THREE.MeshStandardMaterial({
|
| 176 |
+
color: C.gray, emissive: C.gray, emissiveIntensity: 0.25, metalness: 0.4, roughness: 0.4,
|
| 177 |
+
});
|
| 178 |
+
const levels = 4; // 1 + 2 + 4 + 8 = 15 nodes
|
| 179 |
+
const nodes = [];
|
| 180 |
+
let y = 3.2;
|
| 181 |
+
const spread = 4.6;
|
| 182 |
+
const branchMat = new THREE.MeshStandardMaterial({ color: C.accent, emissive: C.accent, emissiveIntensity: 0.2, transparent: true, opacity: 0.55 });
|
| 183 |
+
const placed = [];
|
| 184 |
+
for (let lvl = 0; lvl < levels; lvl++) {
|
| 185 |
+
const count = Math.pow(2, lvl);
|
| 186 |
+
const row = [];
|
| 187 |
+
const w = spread * (lvl / (levels - 1));
|
| 188 |
+
for (let i = 0; i < count; i++) {
|
| 189 |
+
const x = count === 1 ? 0 : (-w + (2 * w) * (i / (count - 1)));
|
| 190 |
+
const isLeaf = lvl === levels - 1;
|
| 191 |
+
const geo = new THREE.OctahedronGeometry(isLeaf ? 0.28 : 0.36, 0);
|
| 192 |
+
const node = new THREE.Mesh(geo, leafMat());
|
| 193 |
+
node.position.set(x, y - lvl * 1.7, 0);
|
| 194 |
+
node.userData.leaf = isLeaf;
|
| 195 |
+
g.add(node); row.push(node); nodes.push(node);
|
| 196 |
+
}
|
| 197 |
+
placed.push(row);
|
| 198 |
+
}
|
| 199 |
+
// connect parents -> children with tube branches
|
| 200 |
+
for (let lvl = 0; lvl < levels - 1; lvl++) {
|
| 201 |
+
placed[lvl].forEach((parent, pi) => {
|
| 202 |
+
[2 * pi, 2 * pi + 1].forEach((ci) => {
|
| 203 |
+
const child = placed[lvl + 1][ci];
|
| 204 |
+
if (!child) return;
|
| 205 |
+
const curve = new THREE.LineCurve3(parent.position.clone(), child.position.clone());
|
| 206 |
+
const tube = new THREE.Mesh(new THREE.TubeGeometry(curve, 1, 0.03, 6, false), branchMat);
|
| 207 |
+
g.add(tube);
|
| 208 |
+
});
|
| 209 |
+
});
|
| 210 |
+
}
|
| 211 |
+
g.userData.leaves = placed[levels - 1];
|
| 212 |
+
g.userData.root = placed[0][0];
|
| 213 |
+
g.userData.allNodes = nodes;
|
| 214 |
+
return g;
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
// Demo 2: attestation timeline helix — SCITT-style. Each ring = a pipeline stage;
|
| 218 |
+
// beads = attestations (SLSA provenance, SBOM, test result) that lock into the ledger.
|
| 219 |
+
function buildAttestationHelix(THREE) {
|
| 220 |
+
const g = new THREE.Group(); g.userData.kind = "helix";
|
| 221 |
+
const turns = 3, perTurn = 12, n = turns * perTurn, R = 1.7, H = 6;
|
| 222 |
+
const pts = [];
|
| 223 |
+
for (let i = 0; i <= n; i++) {
|
| 224 |
+
const a = (i / perTurn) * Math.PI * 2;
|
| 225 |
+
const yy = -H / 2 + H * (i / n);
|
| 226 |
+
pts.push(new THREE.Vector3(Math.cos(a) * R, yy, Math.sin(a) * R));
|
| 227 |
+
}
|
| 228 |
+
const curve = new THREE.CatmullRomCurve3(pts);
|
| 229 |
+
const tube = new THREE.Mesh(
|
| 230 |
+
new THREE.TubeGeometry(curve, 200, 0.045, 8, false),
|
| 231 |
+
new THREE.MeshStandardMaterial({ color: C.teal, emissive: C.teal, emissiveIntensity: 0.3, transparent: true, opacity: 0.7 }),
|
| 232 |
+
);
|
| 233 |
+
g.add(tube);
|
| 234 |
+
const beads = [];
|
| 235 |
+
const stageColors = [C.gold, C.teal, C.accent, C.blue];
|
| 236 |
+
for (let i = 0; i < n; i += 3) {
|
| 237 |
+
const p = curve.getPointAt(i / n);
|
| 238 |
+
const bead = new THREE.Mesh(
|
| 239 |
+
new THREE.SphereGeometry(0.12, 12, 12),
|
| 240 |
+
new THREE.MeshStandardMaterial({ color: stageColors[(i / 3) % stageColors.length], emissive: stageColors[(i / 3) % stageColors.length], emissiveIntensity: 0.5 }),
|
| 241 |
+
);
|
| 242 |
+
bead.position.copy(p); g.add(bead); beads.push(bead);
|
| 243 |
+
}
|
| 244 |
+
g.userData.beads = beads;
|
| 245 |
+
return g;
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
// Demo 3: NIST / ISO / EU compliance crosswalk heatmap (HONEST 60/60/0).
|
| 249 |
+
// Rows = controls (compliance.json), cols = the 3 frameworks. Cell color =
|
| 250 |
+
// IMPLEMENTED/PARTIAL/ROADMAP; bar height = framework pct_implemented. We seed
|
| 251 |
+
// it with the STRUCTURAL frame from compliance.json and replace with live cells
|
| 252 |
+
// when /assurance/compliance is 200.
|
| 253 |
+
function buildComplianceHeatmap(THREE) {
|
| 254 |
+
const g = new THREE.Group(); g.userData.kind = "heatmap";
|
| 255 |
+
const frameworks = ["NIST", "ISO", "EU"];
|
| 256 |
+
const rows = 10, cols = 3, cw = 1.05, ch = 0.5;
|
| 257 |
+
const cells = [];
|
| 258 |
+
// STRUCTURAL seed mirroring compliance.json (NIST/ISO: 6 impl,3 part,1 road; EU: 0,9,1)
|
| 259 |
+
const seed = {
|
| 260 |
+
NIST: ["IMPLEMENTED", "IMPLEMENTED", "IMPLEMENTED", "IMPLEMENTED", "IMPLEMENTED", "IMPLEMENTED", "PARTIAL", "PARTIAL", "PARTIAL", "ROADMAP"],
|
| 261 |
+
ISO: ["IMPLEMENTED", "IMPLEMENTED", "IMPLEMENTED", "IMPLEMENTED", "IMPLEMENTED", "IMPLEMENTED", "PARTIAL", "PARTIAL", "PARTIAL", "ROADMAP"],
|
| 262 |
+
EU: ["PARTIAL", "PARTIAL", "PARTIAL", "PARTIAL", "PARTIAL", "PARTIAL", "PARTIAL", "PARTIAL", "PARTIAL", "ROADMAP"],
|
| 263 |
+
};
|
| 264 |
+
for (let c = 0; c < cols; c++) {
|
| 265 |
+
for (let r = 0; r < rows; r++) {
|
| 266 |
+
const status = seed[frameworks[c]][r];
|
| 267 |
+
const mesh = new THREE.Mesh(
|
| 268 |
+
new THREE.BoxGeometry(cw * 0.9, 0.12, ch * 0.9),
|
| 269 |
+
new THREE.MeshStandardMaterial({ color: STATUS_COLOR[status], emissive: STATUS_COLOR[status], emissiveIntensity: 0.3, metalness: 0.2, roughness: 0.6 }),
|
| 270 |
+
);
|
| 271 |
+
mesh.position.set((c - 1) * cw, 0, (r - rows / 2) * ch);
|
| 272 |
+
mesh.userData = { framework: frameworks[c], row: r };
|
| 273 |
+
g.add(mesh); cells.push(mesh);
|
| 274 |
+
}
|
| 275 |
+
}
|
| 276 |
+
// framework coverage pillars (height = pct_implemented; seeded 60/60/0)
|
| 277 |
+
const pct = { NIST: 60, ISO: 60, EU: 0 };
|
| 278 |
+
const pillars = {};
|
| 279 |
+
frameworks.forEach((f, c) => {
|
| 280 |
+
const h = Math.max(0.05, (pct[f] / 100) * 3.5);
|
| 281 |
+
const col = pct[f] >= 100 ? C.green : (pct[f] > 0 ? C.gold : C.gray);
|
| 282 |
+
const pil = new THREE.Mesh(
|
| 283 |
+
new THREE.BoxGeometry(0.5, h, 0.5),
|
| 284 |
+
new THREE.MeshStandardMaterial({ color: col, emissive: col, emissiveIntensity: 0.35, transparent: true, opacity: 0.85 }),
|
| 285 |
+
);
|
| 286 |
+
pil.position.set((c - 1) * cw, h / 2 + 0.2, (rows / 2) * ch + 0.9);
|
| 287 |
+
g.add(pil); pillars[f] = pil;
|
| 288 |
+
const bb = _label.billboard(THREE, "MEASURED", { text: `${f} ${pct[f]}%`, scale: 0.34, position: [(c - 1) * cw, h + 0.7, (rows / 2) * ch + 0.9] });
|
| 289 |
+
g.add(bb);
|
| 290 |
+
});
|
| 291 |
+
g.userData.cells = cells; g.userData.pillars = pillars; g.userData.frameworks = frameworks;
|
| 292 |
+
return g;
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
// Demo 4: Forge ledger hash-chain replay — linear khipu chain (prev_hash/entry_hash).
|
| 296 |
+
// Genesis = 64 zeros; ring color (safe-auto/gated/forbidden) + decision (ALLOW/DENY/
|
| 297 |
+
// BLOCKED). Populated from /forge/ledger entries[]; kill_switch drives the indicator.
|
| 298 |
+
function buildLedgerChain(THREE) {
|
| 299 |
+
const g = new THREE.Group(); g.userData.kind = "ledger";
|
| 300 |
+
const n = 8, gap = 1.25;
|
| 301 |
+
const blocks = [];
|
| 302 |
+
for (let i = 0; i < n; i++) {
|
| 303 |
+
const blk = new THREE.Mesh(
|
| 304 |
+
new THREE.BoxGeometry(0.7, 0.7, 0.7),
|
| 305 |
+
new THREE.MeshStandardMaterial({ color: C.gray, emissive: C.gray, emissiveIntensity: 0.25, metalness: 0.5, roughness: 0.4 }),
|
| 306 |
+
);
|
| 307 |
+
blk.position.set((i - (n - 1) / 2) * gap, 0, 0);
|
| 308 |
+
g.add(blk); blocks.push(blk);
|
| 309 |
+
if (i > 0) {
|
| 310 |
+
const link = new THREE.Mesh(
|
| 311 |
+
new THREE.CylinderGeometry(0.025, 0.025, gap - 0.7, 6),
|
| 312 |
+
new THREE.MeshStandardMaterial({ color: C.accent, emissive: C.accent, emissiveIntensity: 0.3 }),
|
| 313 |
+
);
|
| 314 |
+
link.rotation.z = Math.PI / 2;
|
| 315 |
+
link.position.set((i - (n - 1) / 2) * gap - gap / 2, 0, 0);
|
| 316 |
+
g.add(link);
|
| 317 |
+
}
|
| 318 |
+
}
|
| 319 |
+
// genesis marker (64 zeros)
|
| 320 |
+
blocks[0].material.color.setHex(C.blue); blocks[0].material.emissive.setHex(C.blue);
|
| 321 |
+
g.userData.blocks = blocks;
|
| 322 |
+
return g;
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
// Demo 5: 3-axis attestation (build / model / runtime) — runtime_attestation.py.
|
| 326 |
+
// Three orthogonal axes; a bar grows along each present axis. trust_level rises
|
| 327 |
+
// NONE -> BUILD-ONLY -> BUILD+MODEL -> ALL-THREE-AXES. More axes = more STRUCTURE,
|
| 328 |
+
// NOT more safety (lambda_note).
|
| 329 |
+
function buildAttestationAxes(THREE) {
|
| 330 |
+
const g = new THREE.Group(); g.userData.kind = "axes";
|
| 331 |
+
const defs = [
|
| 332 |
+
{ name: "build", dir: [1, 0, 0], color: C.green },
|
| 333 |
+
{ name: "model", dir: [0, 1, 0], color: C.gold },
|
| 334 |
+
{ name: "runtime", dir: [0, 0, 1], color: C.teal },
|
| 335 |
+
];
|
| 336 |
+
const bars = {};
|
| 337 |
+
defs.forEach((d) => {
|
| 338 |
+
const len = 1.6;
|
| 339 |
+
const bar = new THREE.Mesh(
|
| 340 |
+
new THREE.CylinderGeometry(0.06, 0.06, len, 8),
|
| 341 |
+
new THREE.MeshStandardMaterial({ color: d.color, emissive: d.color, emissiveIntensity: 0.35, transparent: true, opacity: 0.45 }),
|
| 342 |
+
);
|
| 343 |
+
// orient cylinder (default +Y) along dir
|
| 344 |
+
const v = new THREE.Vector3(...d.dir);
|
| 345 |
+
bar.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), v);
|
| 346 |
+
bar.position.copy(v.clone().multiplyScalar(len / 2));
|
| 347 |
+
g.add(bar); bars[d.name] = bar;
|
| 348 |
+
});
|
| 349 |
+
const hub = new THREE.Mesh(
|
| 350 |
+
new THREE.SphereGeometry(0.2, 16, 16),
|
| 351 |
+
new THREE.MeshStandardMaterial({ color: C.accent, emissive: C.accent, emissiveIntensity: 0.4 }),
|
| 352 |
+
);
|
| 353 |
+
g.add(hub);
|
| 354 |
+
g.userData.bars = bars;
|
| 355 |
+
return g;
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
// Demo 6: PQ hybrid-signature node — pq_signing.py (Ed25519 + ML-DSA, FIPS-204
|
| 359 |
+
// ML-DSA-65). Two interlocked rings; hybrid policy = BOTH must verify. ML-DSA is
|
| 360 |
+
// a STRUCTURAL STUB in-sandbox (real:false) until the real PQ signer lands -> gray.
|
| 361 |
+
function buildPqNode(THREE) {
|
| 362 |
+
const g = new THREE.Group(); g.userData.kind = "pq";
|
| 363 |
+
const core = new THREE.Mesh(
|
| 364 |
+
new THREE.IcosahedronGeometry(0.45, 0),
|
| 365 |
+
new THREE.MeshStandardMaterial({ color: C.accent, emissive: C.accent, emissiveIntensity: 0.4, metalness: 0.5, roughness: 0.3 }),
|
| 366 |
+
);
|
| 367 |
+
g.add(core);
|
| 368 |
+
const ed = new THREE.Mesh(
|
| 369 |
+
new THREE.TorusGeometry(0.95, 0.05, 8, 40),
|
| 370 |
+
new THREE.MeshStandardMaterial({ color: C.green, emissive: C.green, emissiveIntensity: 0.45 }), // Ed25519 = real
|
| 371 |
+
);
|
| 372 |
+
g.add(ed);
|
| 373 |
+
const ml = new THREE.Mesh(
|
| 374 |
+
new THREE.TorusGeometry(0.95, 0.05, 8, 40),
|
| 375 |
+
new THREE.MeshStandardMaterial({ color: C.gray, emissive: C.gray, emissiveIntensity: 0.3 }), // ML-DSA = STUB until real
|
| 376 |
+
);
|
| 377 |
+
ml.rotation.x = Math.PI / 2;
|
| 378 |
+
g.add(ml);
|
| 379 |
+
g.userData.core = core; g.userData.ed = ed; g.userData.ml = ml;
|
| 380 |
+
return g;
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
// Demo 7: kill-switch indicator — forge_governance.py kill_switch bool. Armed
|
| 384 |
+
// (true) -> gated actions forced to DENY, throbbing red; safe (false) -> steady green.
|
| 385 |
+
function buildKillSwitch(THREE) {
|
| 386 |
+
const g = new THREE.Group(); g.userData.kind = "killswitch";
|
| 387 |
+
const ring = new THREE.Mesh(
|
| 388 |
+
new THREE.TorusGeometry(0.7, 0.08, 10, 48),
|
| 389 |
+
new THREE.MeshStandardMaterial({ color: C.gray, emissive: C.gray, emissiveIntensity: 0.3 }),
|
| 390 |
+
);
|
| 391 |
+
g.add(ring);
|
| 392 |
+
const core = new THREE.Mesh(
|
| 393 |
+
new THREE.SphereGeometry(0.42, 24, 24),
|
| 394 |
+
new THREE.MeshStandardMaterial({ color: C.green, emissive: C.green, emissiveIntensity: 0.4, metalness: 0.3, roughness: 0.4 }),
|
| 395 |
+
);
|
| 396 |
+
g.add(core);
|
| 397 |
+
g.userData.core = core; g.userData.ring = ring; g.userData.armed = false;
|
| 398 |
+
return g;
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
// Demo 8: signature ≠ safety teaching callout (CVE-2026-45321). A green "valid
|
| 402 |
+
// signature" badge sitting ON a red behavioural-anomaly body — the whole point:
|
| 403 |
+
// a valid signature does NOT make the artifact safe.
|
| 404 |
+
function buildSigSafetyCallout(THREE) {
|
| 405 |
+
const g = new THREE.Group(); g.userData.kind = "callout";
|
| 406 |
+
const body = new THREE.Mesh(
|
| 407 |
+
new THREE.BoxGeometry(1.4, 1.0, 1.0),
|
| 408 |
+
new THREE.MeshStandardMaterial({ color: C.red, emissive: C.red, emissiveIntensity: 0.35, transparent: true, opacity: 0.7 }),
|
| 409 |
+
);
|
| 410 |
+
g.add(body);
|
| 411 |
+
// a green "signature valid" seal stuck on the malicious body
|
| 412 |
+
const seal = new THREE.Mesh(
|
| 413 |
+
new THREE.CylinderGeometry(0.32, 0.32, 0.1, 24),
|
| 414 |
+
new THREE.MeshStandardMaterial({ color: C.green, emissive: C.green, emissiveIntensity: 0.5 }),
|
| 415 |
+
);
|
| 416 |
+
seal.rotation.x = Math.PI / 2; seal.position.set(0, 0, 0.55);
|
| 417 |
+
g.add(seal);
|
| 418 |
+
g.add(_label.billboard(THREE, "STRUCTURAL-ONLY", { text: "signature ≠ safety · CVE-2026-45321", scale: 0.4, position: [0, 1.0, 0] }));
|
| 419 |
+
return g;
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
// ===========================================================================
|
| 423 |
+
// SBOM dependency knowledge graph (GUAC-style) — reuses vendored ForceGraph3D.
|
| 424 |
+
// Demo 9. Renders into its own DOM panel (the UMD lib owns a three.js canvas).
|
| 425 |
+
// Seeded STRUCTURAL; replaced with real nodes/edges when /assurance/artifact 200s.
|
| 426 |
+
// ===========================================================================
|
| 427 |
+
function _seedSbom() {
|
| 428 |
+
// STRUCTURAL placeholder graph (clearly labelled). Real SBOM arrives via the
|
| 429 |
+
// artifact route's dependency closure + behavioural verdict per node.
|
| 430 |
+
const root = { id: "szl/agentic-pinn-solver", group: "root", status: "ROADMAP", val: 8 };
|
| 431 |
+
const nodes = [root];
|
| 432 |
+
const links = [];
|
| 433 |
+
const deps = ["numpy", "fastapi", "starlette", "cryptography", "uvicorn", "pydantic", "ed25519", "rekor-client"];
|
| 434 |
+
deps.forEach((d, i) => {
|
| 435 |
+
const node = { id: d, group: "dep", status: "ROADMAP", val: 3 };
|
| 436 |
+
nodes.push(node);
|
| 437 |
+
links.push({ source: root.id, target: d, rel: "depends-on" });
|
| 438 |
+
if (i % 3 === 0) {
|
| 439 |
+
const sub = { id: d + "·sub", group: "transitive", status: "ROADMAP", val: 2 };
|
| 440 |
+
nodes.push(sub);
|
| 441 |
+
links.push({ source: d, target: sub.id, rel: "depends-on" });
|
| 442 |
+
}
|
| 443 |
+
});
|
| 444 |
+
return { nodes, links };
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
function _initForceGraph() {
|
| 448 |
+
if (!_graphPanel) return;
|
| 449 |
+
const ForceGraph3D = (typeof window !== "undefined") && window.ForceGraph3D;
|
| 450 |
+
if (!ForceGraph3D) {
|
| 451 |
+
// vendored lib not yet on the page — inject it same-origin (0 CDN), then build.
|
| 452 |
+
if (!_fgScript) {
|
| 453 |
+
_fgScript = el("script");
|
| 454 |
+
_fgScript.src = "/vendor/3d-force-graph.min.js";
|
| 455 |
+
_fgScript.onload = () => { try { _buildForceGraph(); } catch (_) {} };
|
| 456 |
+
_fgScript.onerror = () => { if (_hud.sbom) _hud.sbom.textContent = "SBOM graph: vendored lib unavailable"; };
|
| 457 |
+
document.head.appendChild(_fgScript);
|
| 458 |
+
}
|
| 459 |
+
return;
|
| 460 |
+
}
|
| 461 |
+
_buildForceGraph();
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
function _buildForceGraph() {
|
| 465 |
+
const ForceGraph3D = window.ForceGraph3D;
|
| 466 |
+
if (!ForceGraph3D || !_graphPanel || _graph) return;
|
| 467 |
+
const data = _seedSbom();
|
| 468 |
+
_graph = ForceGraph3D()(_graphPanel)
|
| 469 |
+
.backgroundColor("rgba(5,7,13,0)")
|
| 470 |
+
.graphData(data)
|
| 471 |
+
.nodeLabel((n) => `${n.id} · ${n.status}`)
|
| 472 |
+
.nodeColor((n) => "#" + (STATUS_COLOR[n.status] || C.gray).toString(16).padStart(6, "0"))
|
| 473 |
+
.nodeVal((n) => n.val || 3)
|
| 474 |
+
.nodeOpacity(0.92)
|
| 475 |
+
.linkColor(() => "rgba(176,143,255,0.4)")
|
| 476 |
+
.linkDirectionalParticles(1)
|
| 477 |
+
.linkDirectionalParticleSpeed(0.006)
|
| 478 |
+
.width(_graphPanel.clientWidth || 360)
|
| 479 |
+
.height(_graphPanel.clientHeight || 240)
|
| 480 |
+
.showNavInfo(false);
|
| 481 |
+
}
|
| 482 |
+
|
| 483 |
+
// update the SBOM graph from a live /assurance/artifact verdict (real shape).
|
| 484 |
+
function _updateSbomFromArtifact(json) {
|
| 485 |
+
if (!_graph) return;
|
| 486 |
+
// The artifact route returns a behavioural verdict (assess_live_certificate /
|
| 487 |
+
// assess_artifact). When a full dependency closure is present we render it; else
|
| 488 |
+
// we color the single certified artifact node by its verdict.
|
| 489 |
+
const verdict = json.behavioural_verdict || json.verdict;
|
| 490 |
+
const vColor = verdict === "ALLOW" ? "PARTIAL" : (verdict === "DENY" ? null : "ROADMAP");
|
| 491 |
+
const data = _graph.graphData();
|
| 492 |
+
if (data && data.nodes && data.nodes.length) {
|
| 493 |
+
data.nodes.forEach((n) => {
|
| 494 |
+
if (n.group === "root") {
|
| 495 |
+
// DENY -> red; ALLOW -> amber (PARTIAL, never green: signature ≠ safety);
|
| 496 |
+
n.status = vColor || "DENY";
|
| 497 |
+
n._deny = verdict === "DENY";
|
| 498 |
+
}
|
| 499 |
+
});
|
| 500 |
+
_graph.nodeColor((n) => n._deny ? "#ff6b6b" : ("#" + (STATUS_COLOR[n.status] || C.gray).toString(16).padStart(6, "0")));
|
| 501 |
+
_graph.refresh && _graph.refresh();
|
| 502 |
+
}
|
| 503 |
+
}
|
| 504 |
+
|
| 505 |
+
// ===========================================================================
|
| 506 |
+
// OVERLAY HUD — badges per route + legend + teaching callout + SBOM panel
|
| 507 |
+
// ===========================================================================
|
| 508 |
+
function _buildOverlay(ctx) {
|
| 509 |
+
_overlay = el("div", "position:absolute;left:14px;top:14px;z-index:5;display:flex;flex-direction:column;gap:9px;max-width:min(94%,440px);pointer-events:none");
|
| 510 |
+
|
| 511 |
+
const head = el("div", "font:600 14px ui-sans-serif,system-ui;color:#eef3f6;letter-spacing:.4px", TITLE);
|
| 512 |
+
_overlay.appendChild(head);
|
| 513 |
+
|
| 514 |
+
const sub = el("div", "font:10.5px ui-monospace,Menlo,monospace;color:#9fb1bf;line-height:1.5",
|
| 515 |
+
"modeled on GUAC v1.0 · Sigstore/Rekor · SCITT — knowledge graph + Merkle hash-chain + crosswalk");
|
| 516 |
+
_overlay.appendChild(sub);
|
| 517 |
+
|
| 518 |
+
// per-route live badges (each shows NO-LIVE-DATA until Forge meshes it)
|
| 519 |
+
const badgeWrap = el("div", "display:flex;flex-direction:column;gap:5px;pointer-events:auto");
|
| 520 |
+
_hud.badges = {};
|
| 521 |
+
const ROUTE_LABEL = {
|
| 522 |
+
artifact: "artifact (behaviour monitor)", credential: "credential (C2PA)",
|
| 523 |
+
compliance: "compliance (crosswalk)", attest: "attest (build/model/runtime)",
|
| 524 |
+
ledger: "forge ledger (hash-chain)",
|
| 525 |
+
};
|
| 526 |
+
Object.keys(EP).forEach((k) => {
|
| 527 |
+
const row = el("div", "display:flex;align-items:center;gap:8px");
|
| 528 |
+
const tag = el("span", "font:10px ui-monospace,monospace;color:#7d8a96;min-width:182px", ROUTE_LABEL[k]);
|
| 529 |
+
const badge = _live.createBadge();
|
| 530 |
+
_hud.badges[k] = badge;
|
| 531 |
+
row.appendChild(tag); row.appendChild(badge.el);
|
| 532 |
+
badgeWrap.appendChild(row);
|
| 533 |
+
});
|
| 534 |
+
_overlay.appendChild(badgeWrap);
|
| 535 |
+
|
| 536 |
+
// honesty legend (doctrine chips)
|
| 537 |
+
const legend = _label.legend(); legend.style.opacity = "0.9"; legend.style.pointerEvents = "auto";
|
| 538 |
+
_overlay.appendChild(legend);
|
| 539 |
+
|
| 540 |
+
// teaching callout (doctrine): signature ≠ safety
|
| 541 |
+
const teach = el("div",
|
| 542 |
+
"pointer-events:auto;margin-top:2px;padding:8px 10px;border:1px solid #3a2330;border-radius:8px;" +
|
| 543 |
+
"background:rgba(255,107,107,.08);color:#ffb4b4;font:10.5px ui-monospace,Menlo,monospace;line-height:1.55");
|
| 544 |
+
teach.innerHTML =
|
| 545 |
+
"<b style='color:#ff8f8f'>A signature is NOT proof of safety.</b><br>" +
|
| 546 |
+
"CVE-2026-45321 (Mini Shai-Hulud): 84 @tanstack/* npm versions shipped <i>valid</i> " +
|
| 547 |
+
"SLSA L3 / Sigstore provenance. Provenance flagged 0; behaviour caught all 84. " +
|
| 548 |
+
"<span style='color:#9fb1bf'>Λ = Conjecture 1 — advisory governance, NOT proven trust.</span>";
|
| 549 |
+
_overlay.appendChild(teach);
|
| 550 |
+
|
| 551 |
+
// status / scope line (filled live)
|
| 552 |
+
_hud.status = el("div", "font:10px ui-monospace,monospace;color:#7d8a96;line-height:1.5;pointer-events:auto");
|
| 553 |
+
_hud.status.textContent = "awaiting Forge mesh — all 5 assurance routes render NO-LIVE-DATA honestly until 200.";
|
| 554 |
+
_overlay.appendChild(_hud.status);
|
| 555 |
+
|
| 556 |
+
// SBOM graph panel (bottom-right), hosts the GUAC-style force-directed graph
|
| 557 |
+
_graphPanel = el("div",
|
| 558 |
+
"position:absolute;right:14px;bottom:42px;width:min(40vw,380px);height:min(34vh,260px);z-index:5;" +
|
| 559 |
+
"border:1px solid #1b2734;border-radius:10px;background:rgba(7,13,21,.55);overflow:hidden;pointer-events:auto");
|
| 560 |
+
const gh = el("div", "position:absolute;left:8px;top:6px;z-index:2;font:10px ui-monospace,monospace;color:#9fb1bf", "SBOM dependency graph · GUAC-style");
|
| 561 |
+
_hud.sbom = el("div", "position:absolute;left:8px;bottom:6px;z-index:2;font:9.5px ui-monospace,monospace;color:#7d8a96", "STRUCTURAL-ONLY · awaiting /assurance/artifact");
|
| 562 |
+
_graphPanel.appendChild(gh); _graphPanel.appendChild(_hud.sbom);
|
| 563 |
+
|
| 564 |
+
const host = ctx.container || document.body;
|
| 565 |
+
host.appendChild(_overlay);
|
| 566 |
+
host.appendChild(_graphPanel);
|
| 567 |
+
}
|
| 568 |
+
|
| 569 |
+
// ===========================================================================
|
| 570 |
+
// LIVE POLLS — wire each gap route; render honest state; light up on 200.
|
| 571 |
+
// ===========================================================================
|
| 572 |
+
function startPolls(ctx) {
|
| 573 |
+
// 1) artifact behaviour monitor -> SBOM node verdict + status
|
| 574 |
+
_handles.push(_live.poll(EP.artifact, 6000, (json, meta) => {
|
| 575 |
+
_state.artifact = meta;
|
| 576 |
+
_updateSbomFromArtifact(json);
|
| 577 |
+
const v = json.behavioural_verdict || json.verdict;
|
| 578 |
+
if (v && _hud.sbom) {
|
| 579 |
+
const safe = json.signature_alone_is_safety; // doctrine invariant: always false
|
| 580 |
+
_hud.sbom.textContent = `verdict ${v} · sig==safety:${safe === undefined ? "?" : safe} · ${json.fired_monitors ? json.fired_monitors.length : 0} fired`;
|
| 581 |
+
}
|
| 582 |
+
}, { badge: _hud.badges.artifact }));
|
| 583 |
+
|
| 584 |
+
// 2) C2PA credential -> trust hint colors the callout seal honestly
|
| 585 |
+
_handles.push(_live.poll(EP.credential, 7000, (json, meta) => {
|
| 586 |
+
_state.credential = meta;
|
| 587 |
+
// trust_hint ∈ {STRUCTURAL-ONLY, SELF_SIGNED, C2PA_TRUST_LIST, TAMPERED} — never "green"
|
| 588 |
+
const hint = json.trust_hint || (json.active_manifest && json.active_manifest.labels && json.active_manifest.labels.trust);
|
| 589 |
+
const callout = _root && _root.children.find((c) => c.userData && c.userData.kind === "callout");
|
| 590 |
+
if (callout) {
|
| 591 |
+
const seal = callout.children.find((c) => c.geometry && c.geometry.type === "CylinderGeometry");
|
| 592 |
+
if (seal) {
|
| 593 |
+
const col = hint === "TAMPERED" ? C.red : (hint === "C2PA_TRUST_LIST" ? C.green : C.gold);
|
| 594 |
+
seal.material.color.setHex(col); seal.material.emissive.setHex(col);
|
| 595 |
+
}
|
| 596 |
+
}
|
| 597 |
+
}, { badge: _hud.badges.credential }));
|
| 598 |
+
|
| 599 |
+
// 3) compliance crosswalk -> recolor heatmap cells + resize pillars (60/60/0 honest)
|
| 600 |
+
_handles.push(_live.poll(EP.compliance, 8000, (json, meta) => {
|
| 601 |
+
_state.compliance = meta;
|
| 602 |
+
_applyCompliance(json);
|
| 603 |
+
}, { badge: _hud.badges.compliance }));
|
| 604 |
+
|
| 605 |
+
// 4) runtime attestation -> grow the present build/model/runtime axis bars
|
| 606 |
+
_handles.push(_live.poll(EP.attest, 7000, (json, meta) => {
|
| 607 |
+
_state.attest = meta;
|
| 608 |
+
_applyAttest(json);
|
| 609 |
+
}, { badge: _hud.badges.attest }));
|
| 610 |
+
|
| 611 |
+
// 5) forge ledger -> color blocks by ring/decision, drive kill-switch indicator
|
| 612 |
+
_handles.push(_live.poll(EP.ledger, 6000, (json, meta) => {
|
| 613 |
+
_state.ledger = meta;
|
| 614 |
+
_applyLedger(json);
|
| 615 |
+
}, { badge: _hud.badges.ledger }));
|
| 616 |
+
|
| 617 |
+
// roll a compact status line as states change
|
| 618 |
+
_handles.forEach((h) => {});
|
| 619 |
+
_refreshStatus();
|
| 620 |
+
const si = setInterval(_refreshStatus, 2000);
|
| 621 |
+
_handles.push({ stop: () => clearInterval(si) });
|
| 622 |
+
}
|
| 623 |
+
|
| 624 |
+
function _refreshStatus() {
|
| 625 |
+
if (!_hud.status) return;
|
| 626 |
+
const live = Object.keys(_state).filter((k) => _state[k] && _state[k].state === "live");
|
| 627 |
+
const missing = Object.keys(EP).filter((k) => !_state[k] || _state[k].state === "missing" || _state[k].state === "init");
|
| 628 |
+
if (live.length === 0) {
|
| 629 |
+
_hud.status.textContent = `awaiting Forge mesh · ${missing.length}/5 routes NO-LIVE-DATA · viz lights up automatically on 200`;
|
| 630 |
+
_hud.status.style.color = "#7d8a96";
|
| 631 |
+
} else {
|
| 632 |
+
_hud.status.textContent = `LIVE: ${live.join(", ")} · ${missing.length} still awaiting Forge mesh`;
|
| 633 |
+
_hud.status.style.color = "#39d3c4";
|
| 634 |
+
}
|
| 635 |
+
}
|
| 636 |
+
|
| 637 |
+
function _applyCompliance(json) {
|
| 638 |
+
const heatmap = _root && _root.children.find((c) => c.userData && c.userData.kind === "heatmap");
|
| 639 |
+
if (!heatmap) return;
|
| 640 |
+
const cov = (json.coverage || json).frameworks || json.frameworks;
|
| 641 |
+
const crosswalk = json.crosswalk;
|
| 642 |
+
const FW = { NIST_AI_RMF: "NIST", ISO_IEC_42001: "ISO", EU_AI_ACT: "EU" };
|
| 643 |
+
// recolor cells from the real crosswalk[] (control x framework x status)
|
| 644 |
+
if (Array.isArray(crosswalk)) {
|
| 645 |
+
const byFw = { NIST: [], ISO: [], EU: [] };
|
| 646 |
+
crosswalk.forEach((c) => { const f = FW[c.framework]; if (f) byFw[f].push(c.status); });
|
| 647 |
+
heatmap.userData.cells.forEach((cell) => {
|
| 648 |
+
const arr = byFw[cell.userData.framework];
|
| 649 |
+
const st = arr && arr[cell.userData.row];
|
| 650 |
+
if (st && STATUS_COLOR[st]) { cell.material.color.setHex(STATUS_COLOR[st]); cell.material.emissive.setHex(STATUS_COLOR[st]); }
|
| 651 |
+
});
|
| 652 |
+
}
|
| 653 |
+
// resize coverage pillars to real pct_implemented (honest 60/60/0 expected)
|
| 654 |
+
if (cov) {
|
| 655 |
+
Object.entries(FW).forEach(([apiKey, shortKey]) => {
|
| 656 |
+
const entry = cov[apiKey];
|
| 657 |
+
const pil = heatmap.userData.pillars[shortKey];
|
| 658 |
+
if (entry && pil && typeof entry.pct_implemented === "number") {
|
| 659 |
+
const h = Math.max(0.05, (entry.pct_implemented / 100) * 3.5);
|
| 660 |
+
pil.scale.y = h / pil.geometry.parameters.height;
|
| 661 |
+
pil.position.y = h / 2 + 0.2;
|
| 662 |
+
const col = entry.pct_implemented >= 100 ? C.green : (entry.pct_implemented > 0 ? C.gold : C.gray);
|
| 663 |
+
pil.material.color.setHex(col); pil.material.emissive.setHex(col);
|
| 664 |
+
}
|
| 665 |
+
});
|
| 666 |
+
}
|
| 667 |
+
}
|
| 668 |
+
|
| 669 |
+
function _applyAttest(json) {
|
| 670 |
+
const axes = _root && _root.children.find((c) => c.userData && c.userData.kind === "axes");
|
| 671 |
+
if (!axes) return;
|
| 672 |
+
const present = json.axes_present || [];
|
| 673 |
+
["build", "model", "runtime"].forEach((name) => {
|
| 674 |
+
const bar = axes.userData.bars[name];
|
| 675 |
+
if (!bar) return;
|
| 676 |
+
const on = present.indexOf(name) >= 0;
|
| 677 |
+
bar.material.opacity = on ? 0.95 : 0.3;
|
| 678 |
+
bar.material.emissiveIntensity = on ? 0.55 : 0.2;
|
| 679 |
+
});
|
| 680 |
+
}
|
| 681 |
+
|
| 682 |
+
function _applyLedger(json) {
|
| 683 |
+
const ledger = _root && _root.children.find((c) => c.userData && c.userData.kind === "ledger");
|
| 684 |
+
const killSwitch = _root && _root.children.find((c) => c.userData && c.userData.kind === "killswitch");
|
| 685 |
+
const entries = json.entries || json.replay || [];
|
| 686 |
+
const RING = { "safe-auto": C.green, gated: C.gold, forbidden: C.red, unknown: C.gray };
|
| 687 |
+
if (ledger && Array.isArray(entries)) {
|
| 688 |
+
ledger.userData.blocks.forEach((blk, i) => {
|
| 689 |
+
const e = entries[i];
|
| 690 |
+
if (!e) return;
|
| 691 |
+
if (i === 0) return; // keep genesis blue
|
| 692 |
+
const col = e.decision === "BLOCKED" || e.decision === "DENY" ? C.red : (RING[e.ring] || C.gray);
|
| 693 |
+
blk.material.color.setHex(col); blk.material.emissive.setHex(col);
|
| 694 |
+
});
|
| 695 |
+
}
|
| 696 |
+
// kill-switch: forge_governance kill_switch bool -> armed indicator
|
| 697 |
+
if (killSwitch) {
|
| 698 |
+
const armed = !!json.kill_switch;
|
| 699 |
+
killSwitch.userData.armed = armed;
|
| 700 |
+
const col = armed ? C.red : C.green;
|
| 701 |
+
killSwitch.userData.core.material.color.setHex(col);
|
| 702 |
+
killSwitch.userData.core.material.emissive.setHex(col);
|
| 703 |
+
}
|
| 704 |
+
}
|
| 705 |
+
|
| 706 |
+
// ---------------------------------------------------------------------------
|
| 707 |
+
// unmount
|
| 708 |
+
// ---------------------------------------------------------------------------
|
| 709 |
function unmount() {
|
| 710 |
+
_handles.forEach((h) => { try { h.stop && h.stop(); } catch (_) {} });
|
| 711 |
+
_handles = [];
|
| 712 |
+
try { if (_frameCb && _stage && _stage.offFrame) _stage.offFrame(_frameCb); } catch (_) {}
|
| 713 |
+
_frameCb = null;
|
| 714 |
+
try { if (_graph && _graph._destructor) _graph._destructor(); } catch (_) {}
|
| 715 |
+
_graph = null;
|
| 716 |
+
try { if (_root && _stage) { disposeObj(_root); _stage.scene.remove(_root); } } catch (_) {}
|
| 717 |
+
_root = null;
|
| 718 |
+
[_overlay, _graphPanel].forEach((n) => { try { if (n && n.parentNode) n.parentNode.removeChild(n); } catch (_) {} });
|
| 719 |
+
_overlay = null; _graphPanel = null; _hud = {};
|
| 720 |
+
_stage = null; _THREE = null; _label = null; _live = null;
|
| 721 |
+
for (const k in _state) delete _state[k];
|
| 722 |
}
|
| 723 |
|
| 724 |
+
export default { id: ID, title: TITLE, endpoints: ENDPOINTS, mount, unmount };
|