enigma-memory 0.1.18 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/README.md +76 -24
  2. package/apps/cli/bin/enigma-desktop.mjs +140 -0
  3. package/apps/cli/bin/enigma-terminal.mjs +78 -0
  4. package/apps/cli/bin/enigma.mjs +1923 -285
  5. package/apps/desktop/electron-main.cjs +217 -0
  6. package/apps/desktop/package.json +12 -0
  7. package/apps/desktop/src/app.js +264 -7
  8. package/apps/desktop/src/index.html +3514 -1373
  9. package/apps/desktop/src/launch-electron.mjs +51 -0
  10. package/apps/desktop/src/server.mjs +2914 -0
  11. package/apps/desktop/src/styles.css +2972 -260
  12. package/apps/desktop/src/zk-browser-prove.mjs +53 -0
  13. package/apps/desktop/src/zk-state.mjs +1789 -0
  14. package/apps/gateway/bin/enigma-gateway.mjs +102 -5
  15. package/apps/gateway/src/server.mjs +271 -8
  16. package/apps/ios/EnigmaCore/Package.swift +12 -0
  17. package/apps/ios/EnigmaCore/Sources/EnigmaCore/EnigmaAPIClient.swift +227 -0
  18. package/apps/ios/EnigmaCore/Sources/EnigmaCore/Models.swift +278 -0
  19. package/apps/ios/EnigmaCore/Sources/EnigmaCore/PKCE.swift +96 -0
  20. package/apps/ios/EnigmaCore/Sources/EnigmaCore/PrivacyMinimizer.swift +187 -0
  21. package/apps/ios/EnigmaCore/Sources/EnigmaCore/ToolModels.swift +129 -0
  22. package/apps/ios/EnigmaCore/Tests/EnigmaCoreTests/EnigmaCoreTests.swift +42 -0
  23. package/apps/ios/EnigmaIOS/Enigma/AppModel.swift +346 -0
  24. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AccentColor.colorset/Contents.json +12 -0
  25. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/Contents.json +11 -0
  26. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/EnigmaAppIcon.png +0 -0
  27. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/Contents.json +3 -0
  28. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/LaunchBackground.colorset/Contents.json +12 -0
  29. package/apps/ios/EnigmaIOS/Enigma/ChatView.swift +181 -0
  30. package/apps/ios/EnigmaIOS/Enigma/CouncilView.swift +78 -0
  31. package/apps/ios/EnigmaIOS/Enigma/CreateView.swift +152 -0
  32. package/apps/ios/EnigmaIOS/Enigma/EnigmaApp.swift +52 -0
  33. package/apps/ios/EnigmaIOS/Enigma/Info.plist +52 -0
  34. package/apps/ios/EnigmaIOS/Enigma/NaturalLanguagePrivacyTagger.swift +26 -0
  35. package/apps/ios/EnigmaIOS/Enigma/OAuthClient.swift +321 -0
  36. package/apps/ios/EnigmaIOS/Enigma/OnboardingView.swift +105 -0
  37. package/apps/ios/EnigmaIOS/Enigma/PrivateVaultView.swift +275 -0
  38. package/apps/ios/EnigmaIOS/Enigma/SecureStore.swift +76 -0
  39. package/apps/ios/EnigmaIOS/Enigma/SettingsView.swift +60 -0
  40. package/apps/ios/EnigmaIOS/Enigma/Theme.swift +80 -0
  41. package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/project.pbxproj +211 -0
  42. package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/xcshareddata/xcschemes/Enigma.xcscheme +23 -0
  43. package/apps/native-host/README.md +19 -8
  44. package/apps/native-host/bin/enigma-native-host.mjs +229 -13
  45. package/apps/relay/bin/enigma-relay.mjs +103 -5
  46. package/apps/relay/src/federation-runtime.mjs +618 -0
  47. package/apps/relay/src/server.mjs +310 -9
  48. package/apps/verifier/bin/enigma-verify.mjs +327 -11
  49. package/cortex-v3/circuits/build/intent_vk_bytes.json +35 -0
  50. package/cortex-v3/circuits/build/sale_vk_bytes.json +35 -0
  51. package/cortex-v3/circuits/build/vk_bytes.json +32 -0
  52. package/cortex-v3/proving-assets.json +64 -0
  53. package/cortex-v3/zk/BUILD-CONTRACT.md +87 -0
  54. package/cortex-v3/zk/action-transition-vk.json +119 -0
  55. package/cortex-v3/zk/alias-adversarial.test.mjs +220 -0
  56. package/cortex-v3/zk/groth16-verify-child.mjs +17 -0
  57. package/cortex-v3/zk/intent-witness.mjs +365 -0
  58. package/cortex-v3/zk/intent-witness.test.mjs +485 -0
  59. package/cortex-v3/zk/proving-assets.mjs +203 -0
  60. package/cortex-v3/zk/sale-witness.mjs +783 -0
  61. package/cortex-v3/zk/sale-witness.test.mjs +784 -0
  62. package/cortex-v3/zk/sealed-sale-release-vk.json +119 -0
  63. package/cortex-v3/zk/settlement-evidence.mjs +722 -0
  64. package/cortex-v3/zk/setup-intent.mjs +688 -0
  65. package/cortex-v3/zk/setup-sale.mjs +666 -0
  66. package/cortex-v3/zk/setup.mjs +594 -0
  67. package/cortex-v3/zk/witness.mjs +184 -0
  68. package/cortex-v3/zk/zk-codec.mjs +232 -0
  69. package/cortex-v3/zk/zk-codec.test.mjs +293 -0
  70. package/cortex-v3/zk/zk-settle.mjs +370 -0
  71. package/cortex-v3/zk/zk-tree.mjs +256 -0
  72. package/cortex-v3/zk/zk-tree.test.mjs +419 -0
  73. package/deploy/docker-compose.local-production-simulation.yml +36 -0
  74. package/docs/browser-extension-install.md +8 -6
  75. package/docs/client-connectors.md +15 -11
  76. package/docs/developer-ecosystem.md +15 -13
  77. package/docs/enigma-memory-ready-conformance.md +11 -9
  78. package/docs/install-anywhere.md +61 -28
  79. package/docs/installers-and-desktop.md +8 -7
  80. package/docs/novelty-invention-candidates.md +161 -161
  81. package/docs/proof-network-claim-boundaries.md +320 -318
  82. package/examples/01-quickstart-agent/index.mjs +49 -0
  83. package/examples/01_agent_memory_quickstart.mjs +57 -0
  84. package/examples/02-multi-agent-swarm/index.mjs +57 -0
  85. package/examples/02_cross_model_passport.mjs +64 -0
  86. package/examples/03-langchain-memory/index.mjs +41 -0
  87. package/examples/03_poseidon_commitment_verification.mjs +71 -0
  88. package/examples/04-python-trading-agent/trader.py +49 -0
  89. package/examples/README.md +27 -0
  90. package/examples/ci/github-actions.yml +7 -2
  91. package/package.json +142 -11
  92. package/packages/adapters/PACKAGE_CONTRACT.md +1 -1
  93. package/packages/connectors/src/index.js +196 -4
  94. package/packages/connectors/swarm-router.mjs +168 -0
  95. package/packages/core/src/index.js +248 -1
  96. package/packages/core/src/version.mjs +7 -0
  97. package/packages/dev-tools/package.json +19 -0
  98. package/packages/dev-tools/src/index.js +4 -0
  99. package/packages/dev-tools/src/memory-benchmark-suite.js +112 -0
  100. package/packages/dev-tools/src/swarm-simulator.js +101 -0
  101. package/packages/dev-tools/src/vault-inspector.js +114 -0
  102. package/packages/dev-tools/src/vector-benchmark.js +100 -0
  103. package/packages/developer-platform/src/access-credentials.js +341 -0
  104. package/packages/developer-platform/src/http.js +132 -0
  105. package/packages/developer-platform/src/index.js +4 -0
  106. package/packages/developer-platform/src/usage-http.js +60 -0
  107. package/packages/developer-platform/src/usage.js +295 -0
  108. package/packages/enclave-runtime/attestation.mjs +159 -0
  109. package/packages/enclave-runtime/index.mjs +47 -0
  110. package/packages/enclave-runtime/session-manager.mjs +253 -0
  111. package/packages/enclave-runtime/zeroization-proof.mjs +227 -0
  112. package/packages/enigma-reflex/package.json +14 -0
  113. package/packages/enigma-reflex/src/index.js +204 -0
  114. package/packages/enigma-reflex/training/generate-dataset.mjs +40 -0
  115. package/packages/enigma-reflex/training/requirements.txt +8 -0
  116. package/packages/enigma-reflex/training/train.py +314 -0
  117. package/packages/enigma-weave/LICENSE +22 -0
  118. package/packages/enigma-weave/UPSTREAM.json +21 -0
  119. package/packages/enigma-weave/package.json +14 -0
  120. package/packages/enigma-weave/src/index.js +286 -0
  121. package/packages/hosted-cloud/src/index.js +80 -5
  122. package/packages/importers/src/index.js +432 -0
  123. package/packages/inference-runtime/src/browser.js +401 -0
  124. package/packages/inference-runtime/src/chat.js +265 -0
  125. package/packages/inference-runtime/src/code.js +407 -0
  126. package/packages/inference-runtime/src/contracts.js +162 -0
  127. package/packages/inference-runtime/src/http.js +232 -0
  128. package/packages/inference-runtime/src/image.js +186 -0
  129. package/packages/inference-runtime/src/index.js +10 -0
  130. package/packages/inference-runtime/src/model-router.js +320 -0
  131. package/packages/inference-runtime/src/platform.js +125 -0
  132. package/packages/inference-runtime/src/privacy.js +400 -0
  133. package/packages/inference-runtime/src/video.js +253 -0
  134. package/packages/mcp-server/README.md +22 -6
  135. package/packages/mcp-server/bin/enigma-mcp.mjs +2 -1
  136. package/packages/mcp-server/src/index.js +1418 -105
  137. package/packages/mcp-server/src/oauth.js +561 -0
  138. package/packages/mcp-server/src/private-handoff.js +84 -0
  139. package/packages/mcp-server/src/remote-http.js +273 -0
  140. package/packages/mcp-server/src/remote-policy.js +72 -0
  141. package/packages/mcp-server/swarm-bridge.mjs +361 -0
  142. package/packages/mesh/index.d.ts +283 -0
  143. package/packages/mesh/package.json +23 -0
  144. package/packages/mesh/src/crypto.js +189 -0
  145. package/packages/mesh/src/federation-packets.js +353 -0
  146. package/packages/mesh/src/gossip.js +311 -0
  147. package/packages/mesh/src/index.js +6 -0
  148. package/packages/mesh/src/protocol.js +255 -0
  149. package/packages/mesh/src/router.js +279 -0
  150. package/packages/mesh/src/transport.js +306 -0
  151. package/packages/passport/src/index.js +426 -1
  152. package/packages/private-economy/src/credits-http.js +100 -0
  153. package/packages/private-economy/src/credits.js +447 -0
  154. package/packages/private-economy/src/index.js +5 -0
  155. package/packages/private-economy/src/payments-http.js +120 -0
  156. package/packages/private-economy/src/payments.js +509 -0
  157. package/packages/private-economy/src/x402.js +346 -0
  158. package/packages/proof-network/PACKAGE_CONTRACT.md +21 -0
  159. package/packages/rag/index.d.ts +182 -0
  160. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/THIRD_PARTY_LICENSES.txt +207 -0
  161. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/config.json +25 -0
  162. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/onnx/model_quantized.onnx +0 -0
  163. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/sha256-manifest.json +28 -0
  164. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer.json +30686 -0
  165. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer_config.json +15 -0
  166. package/packages/rag/package.json +27 -0
  167. package/packages/rag/src/blinded-search.js +109 -0
  168. package/packages/rag/src/bm25.js +169 -0
  169. package/packages/rag/src/embeddings.js +459 -0
  170. package/packages/rag/src/hybrid.js +76 -0
  171. package/packages/rag/src/index.js +38 -0
  172. package/packages/rag/src/reranker.js +61 -0
  173. package/packages/rag/src/research.js +107 -0
  174. package/packages/rag/src/vector-store.js +430 -0
  175. package/packages/rag/src/verify-model-artifacts.mjs +4 -0
  176. package/packages/sdk/index.d.ts +760 -0
  177. package/packages/sdk/package.json +33 -0
  178. package/packages/sdk/python/README.md +24 -0
  179. package/packages/sdk/python/enigma_sdk.py +250 -0
  180. package/packages/sdk/python/pyproject.toml +34 -0
  181. package/packages/sdk/python/requirements.txt +1 -0
  182. package/packages/sdk/python/setup.py +20 -0
  183. package/packages/sdk/src/federation/capability-grant.js +389 -0
  184. package/packages/sdk/src/federation/federation-router.js +360 -0
  185. package/packages/sdk/src/federation/ghostmesh-bridge.js +497 -0
  186. package/packages/sdk/src/federation/index.js +3 -0
  187. package/packages/sdk/src/index.js +1796 -0
  188. package/packages/sdk/src/intelligence/contradiction.js +337 -0
  189. package/packages/sdk/src/intelligence/decision-engine.js +155 -0
  190. package/packages/sdk/src/intelligence/index.js +4 -0
  191. package/packages/sdk/src/intelligence/ontology.js +122 -0
  192. package/packages/sdk/src/intelligence/temporal.js +123 -0
  193. package/packages/sdk/src/market-client.js +142 -0
  194. package/packages/sdk/src/mesh-client.js +110 -0
  195. package/packages/sdk/src/middleware/index.js +3 -0
  196. package/packages/sdk/src/middleware/langchain.js +159 -0
  197. package/packages/sdk/src/middleware/llamaindex.js +101 -0
  198. package/packages/sdk/src/middleware/vercel-ai.js +112 -0
  199. package/packages/sdk/src/rag-client.js +85 -0
  200. package/packages/sdk/src/swarm-orchestrator.js +260 -0
  201. package/packages/settlement/PACKAGE_CONTRACT.md +1 -1
  202. package/packages/snapcompact/THIRD_PARTY_LICENSES.txt +40 -0
  203. package/packages/snapcompact/assets/8x13-latin1.bdf +3837 -0
  204. package/packages/snapcompact/index.d.ts +284 -0
  205. package/packages/snapcompact/package.json +25 -0
  206. package/packages/snapcompact/src/index.js +716 -0
  207. package/packages/storage/PACKAGE_CONTRACT.md +1 -1
  208. package/packages/terminal-console/animations.mjs +240 -0
  209. package/packages/terminal-console/auto-anchor.mjs +220 -0
  210. package/packages/terminal-console/banner.mjs +91 -0
  211. package/packages/terminal-console/commands.mjs +459 -0
  212. package/packages/terminal-console/delegation.mjs +152 -0
  213. package/packages/terminal-console/index.mjs +5 -0
  214. package/packages/terminal-console/outbox.mjs +143 -0
  215. package/packages/terminal-console/phantom-bridge.mjs +637 -0
  216. package/packages/terminal-console/repl.mjs +136 -0
  217. package/packages/terminal-console/signer-store.mjs +130 -0
  218. package/packages/terminal-console/solana-rpc.mjs +214 -0
  219. package/packages/terminal-console/solana-transport.mjs +189 -0
  220. package/packages/terminal-tui/dashboard.mjs +214 -0
  221. package/packages/terminal-tui/index.mjs +28 -0
  222. package/packages/terminal-tui/merkle-tree-renderer.mjs +268 -0
  223. package/packages/terminal-tui/telemetry-hud.mjs +137 -0
  224. package/packages/vault/index.d.ts +449 -0
  225. package/packages/vault/package.json +27 -0
  226. package/packages/vault/src/e2ee.mjs +393 -0
  227. package/packages/vault/src/enclave.js +481 -0
  228. package/packages/vault/src/erasure.js +207 -0
  229. package/packages/vault/src/index.js +1018 -155
  230. package/packages/vault/src/persistence.js +307 -0
  231. package/packages/vault/src/poseidon.js +354 -0
  232. package/packages/vault/src/receipt.js +459 -0
  233. package/scripts/benchmark-optical-context.mjs +166 -0
  234. package/scripts/bootstrap-enigma.mjs +502 -0
  235. package/scripts/build-edge-backend-workers.mjs +20 -5
  236. package/scripts/build-goal-completion-audit.mjs +72 -25
  237. package/scripts/build-hosted-api-key-lifecycle.mjs +26 -8
  238. package/scripts/build-hosted-customer-lifecycle.mjs +20 -3
  239. package/scripts/build-hosted-probe-worker.mjs +19 -4
  240. package/scripts/build-installer-assets.mjs +41 -21
  241. package/scripts/build-operator-evidence-starter.mjs +59 -1
  242. package/scripts/build-production-backend-env-kit.mjs +2 -0
  243. package/scripts/build-production-unblocker.mjs +3 -0
  244. package/scripts/check.mjs +17 -3
  245. package/scripts/collect-hosted-backend-live-evidence.mjs +49 -12
  246. package/scripts/install-enigma-local.mjs +18 -5
  247. package/scripts/release-audit.mjs +65 -109
  248. package/scripts/release-provenance.mjs +6 -0
  249. package/scripts/run-backend-readiness-smoke.mjs +112 -10
  250. package/scripts/scan-secrets.mjs +1 -0
  251. package/scripts/simulate-production-env.mjs +7 -2
  252. package/scripts/validate-hosted-backend-live.mjs +112 -1
  253. package/specs/antibody-pack-v1.schema.json +95 -0
  254. package/specs/antigen-envelope-v1.schema.json +81 -0
  255. package/specs/boundary-manifest-v1.schema.json +35 -35
  256. package/specs/capsule-v1.schema.json +55 -55
  257. package/specs/claim-boundary-manifest-v1.schema.json +22 -22
  258. package/specs/claim-ledger-v1.schema.json +291 -0
  259. package/specs/context-passport-v1.schema.json +59 -0
  260. package/specs/deletion-tombstone-v1.schema.json +26 -26
  261. package/specs/evidence-packet-v1.schema.json +177 -0
  262. package/specs/hosted-backend-live-evidence-v1.schema.json +72 -3
  263. package/specs/immune-scan-report-v1.schema.json +112 -0
  264. package/specs/lifecycle-receipt-log-v1.schema.json +67 -0
  265. package/specs/memory-atom-v1.schema.json +59 -0
  266. package/specs/memory-event-v1.schema.json +42 -42
  267. package/specs/passport-v1.schema.json +50 -50
  268. package/specs/proof-of-non-use-v1.schema.json +65 -0
  269. package/specs/quarantine-record-v1.schema.json +126 -0
  270. package/specs/receipt-v1.schema.json +61 -61
  271. package/specs/state-checkpoint-v1.schema.json +37 -37
  272. package/specs/trust-bundle-v1.schema.json +56 -56
  273. package/specs/trust-card-v1.schema.json +119 -0
  274. package/docs/proof-network-launch-plan.md +0 -421
  275. package/packages/metering/PACKAGE_CONTRACT.md +0 -20
  276. package/scripts/build-ai-orchestration-plan.mjs +0 -248
@@ -0,0 +1,214 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import { ANSI, renderMerkleTree, stripAnsi } from './merkle-tree-renderer.mjs';
4
+ import { renderCallerTracker, renderMcpEventLog, renderZeroizationHud } from './telemetry-hud.mjs';
5
+
6
+ export const OFFICIAL_SOLANA_TOKEN_CA = 'EknDj8VZUHL6FhV9pSk3m6xP2U2sDzwjRDVstKeQpump';
7
+
8
+ function paint(value, code, color) {
9
+ return color ? `${code}${value}${ANSI.reset}` : String(value);
10
+ }
11
+
12
+ function countActive(bundle) {
13
+ if (Array.isArray(bundle?.active_memory_addresses)) return bundle.active_memory_addresses.length;
14
+ return (Array.isArray(bundle?.memory_objects) ? bundle.memory_objects : []).filter((memory) => !['deleted', 'tombstoned', 'inactive'].includes(String(memory?.state ?? 'active').toLowerCase())).length;
15
+ }
16
+
17
+ function countTombstones(bundle) {
18
+ return Array.isArray(bundle?.tombstones) ? bundle.tombstones.length : 0;
19
+ }
20
+
21
+ export function createDashboardSnapshot(bundle = {}) {
22
+ return {
23
+ activeMemories: countActive(bundle),
24
+ tombstones: countTombstones(bundle),
25
+ stateRoot: bundle?.vault?.active_set_root ?? bundle?.active_set_root ?? bundle?.merkle_root ?? '<not committed>',
26
+ receiptRoot: bundle?.vault?.receipt_log_root ?? bundle?.receipt_log_root ?? '<not committed>',
27
+ sequence: Number(bundle?.vault?.sequence ?? bundle?.sequence ?? 0),
28
+ vaultId: bundle?.vault?.vault_id ?? bundle?.vault_id ?? '<local vault>',
29
+ schema: bundle?.schema ?? '<unknown schema>',
30
+ };
31
+ }
32
+
33
+ function panel(title, lines, { color, width }) {
34
+ const inner = width - 4;
35
+ const top = `+-- ${title} ${'-'.repeat(Math.max(1, width - title.length - 7))}+`;
36
+ const body = lines.map((line) => {
37
+ const visible = stripAnsi(line).length;
38
+ return `| ${line}${' '.repeat(Math.max(0, inner - visible))} |`;
39
+ });
40
+ return [paint(top, ANSI.blue, color), ...body, paint(`+${'-'.repeat(width - 2)}+`, ANSI.blue, color)].join('\n');
41
+ }
42
+
43
+ function metric(label, value, colorCode, color) {
44
+ return `${paint(label, ANSI.dim, color)} ${paint(String(value).padStart(5), `${ANSI.bold}${colorCode}`, color)}`;
45
+ }
46
+
47
+ function solanaState(bundle) {
48
+ const state = bundle?.solana_anchor ?? bundle?.anchor ?? bundle?.solana ?? bundle?.chain?.solana;
49
+ if (!state || typeof state !== 'object') {
50
+ return {
51
+ available: false,
52
+ cluster: '<unavailable>',
53
+ programId: '<unavailable>',
54
+ escrows: '<unavailable>',
55
+ nullifiers: '<unavailable>',
56
+ slot: '<unavailable>',
57
+ };
58
+ }
59
+ const escrows = state?.escrows ?? state?.context_escrows ?? state?.accounts?.escrows;
60
+ const nullifiers = state?.nullifiers ?? state?.used_nullifiers ?? state?.accounts?.nullifiers;
61
+ const observedCount = (value) => Array.isArray(value) ? value.length : (Number.isFinite(Number(value?.count)) ? Number(value.count) : '<unavailable>');
62
+ return {
63
+ available: true,
64
+ cluster: state?.cluster ?? state?.network ?? '<unavailable>',
65
+ programId: state?.program_id ?? state?.programId ?? '<unavailable>',
66
+ escrows: observedCount(escrows),
67
+ nullifiers: observedCount(nullifiers),
68
+ slot: state?.slot ?? state?.last_slot ?? '<unavailable>',
69
+ };
70
+ }
71
+
72
+ export function renderSolanaExplorer(bundle, { color = true } = {}) {
73
+ const state = solanaState(bundle);
74
+ return [
75
+ `${paint('Roadmap', ANSI.dim, color)} ${paint('BUILDING TOWARD / IN DEVELOPMENT', ANSI.yellow, color)}`,
76
+ `Cluster ${state.cluster} Slot: ${state.slot}`,
77
+ `Program ${state.programId}`,
78
+ `Escrows ${state.escrows} context accounts Nullifiers: ${state.nullifiers}`,
79
+ `Token CA ${OFFICIAL_SOLANA_TOKEN_CA}`,
80
+ ];
81
+ }
82
+
83
+ function dashboardHeader(snapshot, { color, frame, mode }) {
84
+ const spinner = ['|', '/', '-', '\\'][frame % 4];
85
+ const title = mode === 'monitor' ? 'ENIGMA MEMORY // LIVE MCP MONITOR' : 'ENIGMA MEMORY // LOCAL CONTROL PLANE';
86
+ return [
87
+ paint(title, `${ANSI.bold}${ANSI.cyan}`, color),
88
+ `${paint(spinner, ANSI.green, color)} standalone local snapshot schema ${snapshot.schema}`,
89
+ ].join('\n');
90
+ }
91
+
92
+ export function renderDashboard(bundle, {
93
+ color = true,
94
+ width = 104,
95
+ frame = 0,
96
+ mode = 'dashboard',
97
+ bundleDisplay = '<local bundle>',
98
+ } = {}) {
99
+ if (!bundle || typeof bundle !== 'object' || Array.isArray(bundle)) throw new Error('Terminal dashboard requires a JSON bundle object.');
100
+ const safeWidth = Math.max(80, Math.min(140, Number(width) || 104));
101
+ const snapshot = createDashboardSnapshot(bundle);
102
+ const metrics = [
103
+ `${metric('ACTIVE MEMORIES', snapshot.activeMemories, ANSI.green, color)} ${metric('TOMBSTONES', snapshot.tombstones, ANSI.red, color)} ${metric('SEQUENCE', snapshot.sequence, ANSI.cyan, color)}`,
104
+ `Bundle ${bundleDisplay}`,
105
+ `Vault ${snapshot.vaultId}`,
106
+ `Merkle state root ${paint(snapshot.stateRoot, ANSI.cyan, color)}`,
107
+ `Receipt log root ${paint(snapshot.receiptRoot, ANSI.magenta, color)}`,
108
+ ];
109
+ const sections = [
110
+ dashboardHeader(snapshot, { color, frame, mode }),
111
+ panel('MEMORY STATE', metrics, { color, width: safeWidth }),
112
+ panel('REAL-TIME MCP EVENT LOGGER', renderMcpEventLog(bundle, { color, limit: mode === 'monitor' ? 10 : 6 }), { color, width: safeWidth }),
113
+ panel('MCP CALLER TRACKER', renderCallerTracker(bundle, { color }), { color, width: safeWidth }),
114
+ panel('ENCLAVE KEY ZEROIZATION HUD', renderZeroizationHud(bundle, { color }), { color, width: safeWidth }),
115
+ ];
116
+ if (mode !== 'monitor') sections.push(panel('SOLANA ANCHOR STATE EXPLORER', renderSolanaExplorer(bundle, { color }), { color, width: safeWidth }));
117
+ sections.push(paint('q / Ctrl-C to exit | refreshes from disk | no network or tokens required', ANSI.dim, color));
118
+ return `${sections.join('\n\n')}\n`;
119
+ }
120
+
121
+ export async function readBundle(bundlePath) {
122
+ const path = resolve(String(bundlePath));
123
+ let source;
124
+ try {
125
+ source = await readFile(path, 'utf8');
126
+ } catch (error) {
127
+ if (error?.code === 'ENOENT') throw new Error(`Bundle not found: ${bundlePath}`);
128
+ throw error;
129
+ }
130
+ try {
131
+ return JSON.parse(source);
132
+ } catch {
133
+ throw new Error(`Bundle is not valid JSON: ${bundlePath}`);
134
+ }
135
+ }
136
+
137
+ export async function renderDashboardFromFile(bundlePath, options = {}) {
138
+ const bundle = await readBundle(bundlePath);
139
+ return renderDashboard(bundle, { ...options, bundleDisplay: options.bundleDisplay ?? String(bundlePath) });
140
+ }
141
+
142
+ export async function renderTreeFromFile(bundlePath, options = {}) {
143
+ const bundle = await readBundle(bundlePath);
144
+ return renderMerkleTree(bundle, options);
145
+ }
146
+
147
+ function canAnimate(io) {
148
+ return io?.stdout?.isTTY === true && typeof io?.stdout?.write === 'function';
149
+ }
150
+
151
+ export async function runDashboard({
152
+ bundlePath,
153
+ once = false,
154
+ interval = 1000,
155
+ mode = 'dashboard',
156
+ color = true,
157
+ depth,
158
+ io = { stdout: process.stdout, stderr: process.stderr },
159
+ } = {}) {
160
+ if (!bundlePath) throw new Error('A bundle path is required.');
161
+ const refreshMs = Number(interval);
162
+ if (!Number.isInteger(refreshMs) || refreshMs < 100 || refreshMs > 60_000) throw new Error('--interval must be an integer from 100 to 60000 milliseconds.');
163
+
164
+ if (mode === 'tree') {
165
+ io.stdout.write(await renderTreeFromFile(bundlePath, { color, depth }));
166
+ return 0;
167
+ }
168
+
169
+ const initial = await renderDashboardFromFile(bundlePath, { color, mode, frame: 0 });
170
+ if (once || !canAnimate(io)) {
171
+ io.stdout.write(initial);
172
+ return 0;
173
+ }
174
+
175
+ io.stdout.write('\u001b[?25l\u001b[2J\u001b[H');
176
+ io.stdout.write(initial);
177
+ return new Promise((resolvePromise, rejectPromise) => {
178
+ let frame = 1;
179
+ let rendering = false;
180
+ let stopped = false;
181
+ const cleanup = (code = 0) => {
182
+ if (stopped) return;
183
+ stopped = true;
184
+ clearInterval(timer);
185
+ process.off('SIGINT', onSignal);
186
+ process.off('SIGTERM', onSignal);
187
+ if (io.stdin && typeof io.stdin.off === 'function') io.stdin.off('data', onInput);
188
+ io.stdout.write('\u001b[?25h\n');
189
+ resolvePromise(code);
190
+ };
191
+ const onSignal = () => cleanup(0);
192
+ const onInput = (chunk) => {
193
+ const value = String(chunk).toLowerCase();
194
+ if (value.includes('q') || value.includes('\u0003')) cleanup(0);
195
+ };
196
+ const timer = setInterval(async () => {
197
+ if (rendering || stopped) return;
198
+ rendering = true;
199
+ try {
200
+ const next = await renderDashboardFromFile(bundlePath, { color, mode, frame });
201
+ if (!stopped) io.stdout.write(`\u001b[H\u001b[2J${next}`);
202
+ frame += 1;
203
+ } catch (error) {
204
+ cleanup(1);
205
+ rejectPromise(error);
206
+ } finally {
207
+ rendering = false;
208
+ }
209
+ }, refreshMs);
210
+ process.once('SIGINT', onSignal);
211
+ process.once('SIGTERM', onSignal);
212
+ if (io.stdin && typeof io.stdin.on === 'function') io.stdin.on('data', onInput);
213
+ });
214
+ }
@@ -0,0 +1,28 @@
1
+ export {
2
+ ANSI,
3
+ buildMerkleTree,
4
+ createMerkleProof,
5
+ renderMerkleTree,
6
+ stripAnsi,
7
+ verifyMerkleProof,
8
+ } from './merkle-tree-renderer.mjs';
9
+
10
+ export {
11
+ collectCallerStats,
12
+ collectMcpEvents,
13
+ enclaveZeroizationSnapshot,
14
+ renderCallerTracker,
15
+ renderMcpEventLog,
16
+ renderZeroizationHud,
17
+ } from './telemetry-hud.mjs';
18
+
19
+ export {
20
+ OFFICIAL_SOLANA_TOKEN_CA,
21
+ createDashboardSnapshot,
22
+ readBundle,
23
+ renderDashboard,
24
+ renderDashboardFromFile,
25
+ renderSolanaExplorer,
26
+ renderTreeFromFile,
27
+ runDashboard,
28
+ } from './dashboard.mjs';
@@ -0,0 +1,268 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ export const ANSI = Object.freeze({
4
+ reset: '\u001b[0m',
5
+ bold: '\u001b[1m',
6
+ dim: '\u001b[2m',
7
+ red: '\u001b[38;2;244;63;94m',
8
+ green: '\u001b[38;2;16;185;129m',
9
+ yellow: '\u001b[38;2;251;191;36m',
10
+ blue: '\u001b[38;2;56;189;248m',
11
+ magenta: '\u001b[38;2;244;114;182m',
12
+ cyan: '\u001b[38;2;6;182;212m',
13
+ white: '\u001b[38;2;244;242;239m',
14
+ gray: '\u001b[38;2;125;122;118m',
15
+ amber: '\u001b[38;2;255;122;26m',
16
+ amberGlow: '\u001b[38;2;255;176;102m',
17
+ dimText: '\u001b[38;2;185;182;177m',
18
+ bgDark: '\u001b[48;2;8;5;2m',
19
+ });
20
+
21
+ export function stripAnsi(value) {
22
+ return String(value).replace(/\u001b\[[0-9;]*m/gu, '');
23
+ }
24
+
25
+ function paint(value, code, color) {
26
+ return color ? `${code}${value}${ANSI.reset}` : String(value);
27
+ }
28
+
29
+ function sha256(value) {
30
+ return `sha256:${createHash('sha256').update(value).digest('hex')}`;
31
+ }
32
+
33
+ function digestBytes(hash) {
34
+ const hex = String(hash).replace(/^sha256:/u, '');
35
+ return /^[0-9a-f]{64}$/iu.test(hex) ? Buffer.from(hex, 'hex') : createHash('sha256').update(String(hash)).digest();
36
+ }
37
+
38
+ function canonicalLeafHash(leaf) {
39
+ return sha256(JSON.stringify({
40
+ memory_addr: leaf.memoryAddress,
41
+ status: leaf.status,
42
+ commitment: leaf.commitment,
43
+ }));
44
+ }
45
+
46
+ function combineHashes(left, right) {
47
+ return sha256(Buffer.concat([digestBytes(left), digestBytes(right)]));
48
+ }
49
+
50
+ function normalizeTombstone(entry) {
51
+ const memoryAddress = entry?.memory_addr ?? entry?.memoryAddress ?? entry?.address ?? entry?.id;
52
+ if (!memoryAddress) return null;
53
+ return {
54
+ memoryAddress: String(memoryAddress),
55
+ status: 'tombstoned',
56
+ commitment: entry?.tombstone_hash ?? entry?.commitment ?? entry?.event_hash ?? entry?.receipt_hash ?? memoryAddress,
57
+ source: entry,
58
+ };
59
+ }
60
+
61
+ function leafRecords(bundle) {
62
+ const activeAddresses = new Set(Array.isArray(bundle?.active_memory_addresses) ? bundle.active_memory_addresses.map(String) : []);
63
+ const tombstones = new Map();
64
+ for (const entry of Array.isArray(bundle?.tombstones) ? bundle.tombstones : []) {
65
+ const normalized = normalizeTombstone(entry);
66
+ if (normalized) tombstones.set(normalized.memoryAddress, normalized);
67
+ }
68
+
69
+ const records = [];
70
+ const seen = new Set();
71
+ for (const memory of Array.isArray(bundle?.memory_objects) ? bundle.memory_objects : []) {
72
+ const memoryAddress = String(memory?.memory_addr ?? memory?.memoryAddress ?? memory?.memory_id ?? `memory-${records.length + 1}`);
73
+ const tombstoned = tombstones.has(memoryAddress) || memory?.state === 'tombstoned' || memory?.state === 'deleted';
74
+ const status = tombstoned ? 'tombstoned' : (activeAddresses.size === 0 || activeAddresses.has(memoryAddress) || memory?.state === 'active' ? 'active' : 'inactive');
75
+ records.push({
76
+ memoryAddress,
77
+ status,
78
+ commitment: memory?.content_hash ?? memory?.content_commitment ?? memory?.commitment ?? memoryAddress,
79
+ source: memory,
80
+ });
81
+ seen.add(memoryAddress);
82
+ }
83
+ for (const [memoryAddress, tombstone] of tombstones) {
84
+ if (!seen.has(memoryAddress)) records.push(tombstone);
85
+ }
86
+ return records;
87
+ }
88
+
89
+ function aggregateStatus(children) {
90
+ const statuses = new Set(children.map((child) => child.status));
91
+ return statuses.size === 1 ? children[0].status : 'mixed';
92
+ }
93
+
94
+ /**
95
+ * Builds a deterministic visualization Merkle tree over public memory commitments.
96
+ * The computed visualization root is intentionally kept separate from the vault's
97
+ * committed active-set root because vault implementations may use a different
98
+ * canonical leaf encoding.
99
+ */
100
+ export function buildMerkleTree(bundle = {}) {
101
+ const records = leafRecords(bundle);
102
+ const effectiveRecords = records.length > 0 ? records : [{
103
+ memoryAddress: '<empty-active-set>',
104
+ status: 'empty',
105
+ commitment: 'empty',
106
+ source: null,
107
+ }];
108
+ const leaves = effectiveRecords.map((record, index) => ({
109
+ ...record,
110
+ type: 'leaf',
111
+ level: 0,
112
+ index,
113
+ hash: canonicalLeafHash(record),
114
+ children: [],
115
+ leafStart: index,
116
+ leafEnd: index,
117
+ }));
118
+ const levels = [leaves];
119
+ let current = leaves;
120
+ let level = 1;
121
+ while (current.length > 1) {
122
+ const next = [];
123
+ for (let index = 0; index < current.length; index += 2) {
124
+ const left = current[index];
125
+ const right = current[index + 1] ?? left;
126
+ next.push({
127
+ type: 'internal',
128
+ level,
129
+ index: next.length,
130
+ hash: combineHashes(left.hash, right.hash),
131
+ status: aggregateStatus([left, right]),
132
+ children: [left, right],
133
+ duplicatedRight: left === right,
134
+ leafStart: left.leafStart,
135
+ leafEnd: right.leafEnd,
136
+ });
137
+ }
138
+ levels.push(next);
139
+ current = next;
140
+ level += 1;
141
+ }
142
+ const committedRoot = bundle?.vault?.active_set_root ?? bundle?.active_set_root ?? bundle?.merkle_root ?? null;
143
+ return {
144
+ leaves,
145
+ levels,
146
+ root: current[0],
147
+ computedRoot: current[0].hash,
148
+ committedRoot,
149
+ activeCount: records.filter((record) => record.status === 'active').length,
150
+ tombstoneCount: records.filter((record) => record.status === 'tombstoned').length,
151
+ };
152
+ }
153
+
154
+ export function createMerkleProof(tree, target = 0) {
155
+ let leafIndex = typeof target === 'number'
156
+ ? target
157
+ : tree.leaves.findIndex((leaf) => leaf.memoryAddress === target);
158
+ if (!Number.isInteger(leafIndex) || leafIndex < 0 || leafIndex >= tree.leaves.length) {
159
+ throw new Error(`Merkle proof target not found: ${target}`);
160
+ }
161
+ const originalIndex = leafIndex;
162
+ const steps = [];
163
+ for (let level = 0; level < tree.levels.length - 1; level += 1) {
164
+ const nodes = tree.levels[level];
165
+ const siblingIndex = leafIndex % 2 === 0 ? leafIndex + 1 : leafIndex - 1;
166
+ const sibling = nodes[siblingIndex] ?? nodes[leafIndex];
167
+ steps.push({
168
+ level,
169
+ position: leafIndex % 2 === 0 ? 'right' : 'left',
170
+ hash: sibling.hash,
171
+ duplicated: siblingIndex >= nodes.length,
172
+ });
173
+ leafIndex = Math.floor(leafIndex / 2);
174
+ }
175
+ return {
176
+ leafIndex: originalIndex,
177
+ memoryAddress: tree.leaves[originalIndex].memoryAddress,
178
+ leafHash: tree.leaves[originalIndex].hash,
179
+ steps,
180
+ expectedRoot: tree.computedRoot,
181
+ };
182
+ }
183
+
184
+ export function verifyMerkleProof(proof) {
185
+ let computed = proof.leafHash;
186
+ for (const step of proof.steps) {
187
+ computed = step.position === 'left'
188
+ ? combineHashes(step.hash, computed)
189
+ : combineHashes(computed, step.hash);
190
+ }
191
+ return { verified: computed === proof.expectedRoot, computedRoot: computed };
192
+ }
193
+
194
+ function shortHash(hash, size = 14) {
195
+ if (!hash) return '<not-committed>';
196
+ const text = String(hash);
197
+ const prefix = text.startsWith('sha256:') ? 'sha256:' : '';
198
+ const digest = prefix ? text.slice(prefix.length) : text;
199
+ return digest.length <= size ? text : `${prefix}${digest.slice(0, size)}...${digest.slice(-6)}`;
200
+ }
201
+
202
+ function shortAddress(address, size = 24) {
203
+ const text = String(address);
204
+ return text.length <= size ? text : `${text.slice(0, size - 9)}...${text.slice(-6)}`;
205
+ }
206
+
207
+ function statusBadge(status, color) {
208
+ if (status === 'active') return paint('[A] ACTIVE', ANSI.green, color);
209
+ if (status === 'tombstoned') return paint('[X] TOMBSTONED', ANSI.red, color);
210
+ if (status === 'inactive') return paint('[-] INACTIVE', ANSI.yellow, color);
211
+ if (status === 'mixed') return paint('[M] MIXED', ANSI.yellow, color);
212
+ return paint('[.] EMPTY', ANSI.gray, color);
213
+ }
214
+
215
+ function nodeOnProofPath(node, proof) {
216
+ return proof.leafIndex >= node.leafStart && proof.leafIndex <= node.leafEnd;
217
+ }
218
+
219
+ function renderNode(node, proof, options, prefix = '', isLast = true, depth = 0, root = false) {
220
+ const connector = root ? '' : `${prefix}${isLast ? '`-- ' : '|-- '}`;
221
+ const pathMark = nodeOnProofPath(node, proof) ? paint(' *PROOF', ANSI.magenta, options.color) : '';
222
+ const kind = node.type === 'leaf' ? 'LEAF' : (root ? 'ROOT' : 'NODE');
223
+ const label = node.type === 'leaf' ? ` ${shortAddress(node.memoryAddress)}` : ` L${node.level}:${node.index}`;
224
+ const line = `${connector}${paint(kind, node.type === 'leaf' ? ANSI.cyan : ANSI.blue, options.color)}${label} ${shortHash(node.hash)} ${statusBadge(node.status, options.color)}${pathMark}`;
225
+ const lines = [line];
226
+ if (node.children.length === 0) return lines;
227
+ if (depth >= options.depth - 1) {
228
+ const nextPrefix = root ? '' : `${prefix}${isLast ? ' ' : '| '}`;
229
+ lines.push(`${nextPrefix}\`-- ... ${node.leafEnd - node.leafStart + 1} leaves below (increase --depth)`);
230
+ return lines;
231
+ }
232
+ const nextPrefix = root ? '' : `${prefix}${isLast ? ' ' : '| '}`;
233
+ node.children.forEach((child, index) => {
234
+ lines.push(...renderNode(child, proof, options, nextPrefix, index === node.children.length - 1, depth + 1, false));
235
+ });
236
+ return lines;
237
+ }
238
+
239
+ export function renderMerkleTree(bundle, { depth = Number.POSITIVE_INFINITY, color = true, proofAddress } = {}) {
240
+ const parsedDepth = depth === Number.POSITIVE_INFINITY ? depth : Number(depth);
241
+ if (!(parsedDepth === Number.POSITIVE_INFINITY || (Number.isInteger(parsedDepth) && parsedDepth >= 1 && parsedDepth <= 64))) {
242
+ throw new Error('--depth must be an integer from 1 to 64.');
243
+ }
244
+ const tree = buildMerkleTree(bundle);
245
+ const target = proofAddress ?? tree.leaves.find((leaf) => leaf.status === 'active')?.memoryAddress ?? 0;
246
+ const proof = createMerkleProof(tree, target);
247
+ const verification = verifyMerkleProof(proof);
248
+ const options = { depth: parsedDepth, color };
249
+ const lines = [
250
+ paint('ENIGMA MEMORY // MERKLE STATE TREE', `${ANSI.bold}${ANSI.cyan}`, color),
251
+ `${paint('Committed active-set root', ANSI.dim, color)} ${tree.committedRoot ?? '<not present in bundle>'}`,
252
+ `${paint('Visualization root', ANSI.dim, color)} ${tree.computedRoot}`,
253
+ `${paint('Leaves', ANSI.dim, color)} ${tree.leaves.length} ${paint('Active', ANSI.green, color)} ${tree.activeCount} ${paint('Tombstoned', ANSI.red, color)} ${tree.tombstoneCount}`,
254
+ '',
255
+ ...renderNode(tree.root, proof, options, '', true, 0, true),
256
+ '',
257
+ paint('PROOF VERIFICATION PATH', `${ANSI.bold}${ANSI.magenta}`, color),
258
+ `Target ${shortAddress(proof.memoryAddress, 48)}`,
259
+ `Leaf ${proof.leafHash}`,
260
+ ];
261
+ if (proof.steps.length === 0) lines.push('Step 0 single-leaf tree (no siblings required)');
262
+ proof.steps.forEach((step, index) => {
263
+ lines.push(`Step ${index + 1} ${step.position.toUpperCase().padEnd(5)} sibling ${shortHash(step.hash, 24)}${step.duplicated ? ' (duplicated)' : ''}`);
264
+ });
265
+ lines.push(`Result ${verification.verified ? paint('VERIFIED', ANSI.green, color) : paint('FAILED', ANSI.red, color)} -> ${verification.computedRoot}`);
266
+ lines.push(paint('Proof verifies the visualization tree encoding; the vault root above remains the canonical state commitment.', ANSI.dim, color));
267
+ return `${lines.join('\n')}\n`;
268
+ }
@@ -0,0 +1,137 @@
1
+ import { ANSI } from './merkle-tree-renderer.mjs';
2
+
3
+ function paint(value, code, color) {
4
+ return color ? `${code}${value}${ANSI.reset}` : String(value);
5
+ }
6
+
7
+ function asArray(value) {
8
+ return Array.isArray(value) ? value : [];
9
+ }
10
+
11
+ function eventPayload(entry) {
12
+ return entry?.event && typeof entry.event === 'object' ? entry.event : entry;
13
+ }
14
+
15
+ function eventIdentity(event, index) {
16
+ return event?.event_id ?? event?.id ?? event?.call_id ?? `${event?.timestamp ?? 'undated'}:${event?.operation ?? event?.tool ?? index}`;
17
+ }
18
+
19
+ function explicitMcpEvents(bundle) {
20
+ return [
21
+ ...asArray(bundle?.mcp_events),
22
+ ...asArray(bundle?.telemetry?.mcp_events),
23
+ ...asArray(bundle?.mcp?.events),
24
+ ...asArray(bundle?.swarm?.events),
25
+ ...asArray(bundle?.mcp_swarm?.events),
26
+ ];
27
+ }
28
+
29
+ function looksLikeMcpEvent(entry) {
30
+ const event = eventPayload(entry);
31
+ const source = `${event?.source ?? ''} ${event?.protocol ?? ''} ${event?.transport ?? ''} ${event?.actor_id ?? ''}`.toLowerCase();
32
+ const operation = String(event?.operation ?? event?.type ?? '').toLowerCase();
33
+ return source.includes('mcp') || operation.startsWith('mcp_') || operation === 'tool_call' || operation === 'tools/call';
34
+ }
35
+
36
+ export function collectMcpEvents(bundle = {}, { limit = 8 } = {}) {
37
+ const candidates = [...explicitMcpEvents(bundle), ...asArray(bundle?.events).filter(looksLikeMcpEvent)];
38
+ const seen = new Set();
39
+ const normalized = [];
40
+ candidates.forEach((entry, index) => {
41
+ const event = eventPayload(entry) ?? {};
42
+ const id = String(eventIdentity(event, index));
43
+ if (seen.has(id)) return;
44
+ seen.add(id);
45
+ normalized.push({
46
+ id,
47
+ timestamp: event.timestamp ?? event.created_at ?? entry?.timestamp ?? null,
48
+ caller: String(event.caller ?? event.caller_id ?? event.client_id ?? event.agent_id ?? event.actor_id ?? event.subject_id ?? 'anonymous-local'),
49
+ tool: String(event.tool ?? event.tool_name ?? event.method ?? event.operation ?? event.type ?? 'mcp_event'),
50
+ status: String(event.status ?? event.result?.status ?? (event.error ? 'error' : 'ok')),
51
+ });
52
+ });
53
+ normalized.sort((a, b) => String(a.timestamp ?? '').localeCompare(String(b.timestamp ?? '')));
54
+ return normalized.slice(-Math.max(0, Number(limit) || 0));
55
+ }
56
+
57
+ export function collectCallerStats(events) {
58
+ const counts = new Map();
59
+ for (const event of events) counts.set(event.caller, (counts.get(event.caller) ?? 0) + 1);
60
+ return [...counts.entries()]
61
+ .map(([caller, calls]) => ({ caller, calls }))
62
+ .sort((a, b) => b.calls - a.calls || a.caller.localeCompare(b.caller));
63
+ }
64
+
65
+ function zeroizationAttestations(bundle) {
66
+ return [
67
+ ...asArray(bundle?.enclave_attestations),
68
+ ...asArray(bundle?.zeroization_attestations),
69
+ ...asArray(bundle?.enclave?.attestations),
70
+ ...asArray(bundle?.enclave_runtime?.attestations),
71
+ ];
72
+ }
73
+
74
+ function actionOf(attestation) {
75
+ return String(attestation?.payload?.action ?? attestation?.action ?? attestation?.event ?? '').toUpperCase();
76
+ }
77
+
78
+ export function enclaveZeroizationSnapshot(bundle = {}) {
79
+ const attestations = zeroizationAttestations(bundle);
80
+ const zeroized = attestations.filter((entry) => actionOf(entry) === 'KEY_ZEROIZED');
81
+ const batches = attestations.filter((entry) => actionOf(entry) === 'BATCH_KEYS_ZEROIZED');
82
+ const batchKeys = batches.reduce((total, entry) => total + Number(entry?.payload?.batchSize ?? entry?.batchSize ?? 0), 0);
83
+ const records = [
84
+ ...asArray(bundle?.enclave?.records),
85
+ ...asArray(bundle?.enclave_runtime?.records),
86
+ ];
87
+ const activeKeys = records.filter((record) => !['zeroized', 'destroyed', 'tombstoned'].includes(String(record?.status ?? record?.state ?? '').toLowerCase())).length;
88
+ const explicitStatus = bundle?.enclave?.zeroization_status ?? bundle?.enclave_runtime?.zeroization_status ?? null;
89
+ let status = 'UNAVAILABLE';
90
+ if (zeroized.length + batchKeys > 0 || String(explicitStatus).toLowerCase() === 'zeroized') status = 'REPORTED / UNVERIFIED';
91
+ else if (activeKeys > 0) status = 'KEYS ACTIVE';
92
+ return {
93
+ status,
94
+ zeroizedKeys: zeroized.length + batchKeys,
95
+ activeKeys,
96
+ attestationCount: zeroized.length + batches.length,
97
+ provider: bundle?.enclave?.provider ?? bundle?.enclave_runtime?.provider ?? '<unavailable>',
98
+ };
99
+ }
100
+
101
+ function short(value, size) {
102
+ const text = String(value ?? '');
103
+ return text.length <= size ? text : `${text.slice(0, size - 3)}...`;
104
+ }
105
+
106
+ function shortTime(value) {
107
+ if (!value) return '--:--:--';
108
+ const date = new Date(value);
109
+ return Number.isNaN(date.getTime()) ? short(value, 8) : date.toISOString().slice(11, 19);
110
+ }
111
+
112
+ export function renderMcpEventLog(bundle, { color = true, limit = 6 } = {}) {
113
+ const events = collectMcpEvents(bundle, { limit });
114
+ if (events.length === 0) return [paint('No MCP calls observed in this local bundle snapshot.', ANSI.dim, color)];
115
+ return events.map((event) => {
116
+ const statusColor = event.status.toLowerCase() === 'error' ? ANSI.red : ANSI.green;
117
+ return `${paint(shortTime(event.timestamp), ANSI.gray, color)} ${paint(short(event.status.toUpperCase(), 7).padEnd(7), statusColor, color)} ${short(event.caller, 20).padEnd(20)} ${short(event.tool, 31)}`;
118
+ });
119
+ }
120
+
121
+ export function renderCallerTracker(bundle, { color = true, limit = 5 } = {}) {
122
+ const events = collectMcpEvents(bundle, { limit: Number.MAX_SAFE_INTEGER });
123
+ const callers = collectCallerStats(events).slice(0, limit);
124
+ if (callers.length === 0) return [paint('Callers: none observed', ANSI.dim, color)];
125
+ return callers.map((caller, index) => `${paint(`#${index + 1}`, ANSI.cyan, color)} ${short(caller.caller, 35).padEnd(35)} ${String(caller.calls).padStart(4)} calls`);
126
+ }
127
+
128
+ export function renderZeroizationHud(bundle, { color = true } = {}) {
129
+ const snapshot = enclaveZeroizationSnapshot(bundle);
130
+ const statusColor = snapshot.status === 'REPORTED / UNVERIFIED' || snapshot.status === 'KEYS ACTIVE' ? ANSI.yellow : ANSI.gray;
131
+ return [
132
+ `Status ${paint(snapshot.status, `${ANSI.bold}${statusColor}`, color)}`,
133
+ `Key records ${snapshot.activeKeys} active / ${snapshot.zeroizedKeys} zeroized`,
134
+ `Reports ${snapshot.attestationCount} Provider: ${snapshot.provider}`,
135
+ paint('Action/status fields are unverified reports; no hardware-RAM erasure claim.', ANSI.dim, color),
136
+ ];
137
+ }