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
@@ -5,14 +5,44 @@ import { constants as fsConstants, realpathSync } from 'node:fs';
5
5
  import { access, mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
6
6
  import { dirname, isAbsolute, join, resolve } from 'node:path';
7
7
  import { pathToFileURL } from 'node:url';
8
- import { createVault, remember, recall, updateMemory, deleteMemory, exportBundle, decryptKeyring, KEYRING_ENCRYPTED_TYPE } from '../../../packages/vault/src/index.js';
9
- import { createPassport, compileContextPack, createMemoryDriveHealthReport } from '../../../packages/passport/src/index.js';
8
+ import { ENIGMA_VERSION } from '../../../packages/core/src/version.mjs';
9
+ import {
10
+ createVault,
11
+ remember,
12
+ recall,
13
+ updateMemory,
14
+ deleteMemory,
15
+ exportBundle,
16
+ importBundle,
17
+ rotateContentKey,
18
+ sealContentKeyEpoch,
19
+ resetVaultContentKeys,
20
+ } from '../../../packages/vault/src/index.js';
21
+ import {
22
+ readJsonFileWithRevision,
23
+ readFileRevision,
24
+ withBundleFileLock,
25
+ writeJsonFileAtomic,
26
+ } from '../../../packages/vault/src/persistence.js';
27
+ import { createPassport, compileContextPack, createContextPassport, createProofOfNonUse, createMemoryDriveHealthReport } from '../../../packages/passport/src/index.js';
10
28
  import { runBoundarySimulation } from '../../../packages/boundary/src/index.js';
11
29
  import { startStdioServer } from '../../../packages/mcp-server/src/index.js';
12
- import { runMeshDemo } from '../../../packages/mesh/src/index.js';
30
+ import { runMeshDemo, MeshGossipNode, WebSocketTransport, generateMeshIdentity } from '../../../packages/mesh/src/index.js';
13
31
  import { runEnterpriseDemo } from '../../../packages/enterprise/src/index.js';
14
32
  import { connectClient, disconnectClient, doctorConnectors, getClientProfile, planConnectWizard, renderMcpConfig, supportedClients } from '../../../packages/connectors/src/index.js';
15
- import { exportEnigmaCapsule, importChatGptExport, importClaudeMemory, importEnigmaCapsule, importLangGraphStore, importLettaAgentFile, importMem0Export, importZepGraphitiExport } from '../../../packages/importers/src/index.js';
33
+ import {
34
+ exportEnigmaCapsule,
35
+ importChatGptExport,
36
+ importClaudeMemory,
37
+ importEnigmaCapsule,
38
+ importLangGraphStore,
39
+ importLettaAgentFile,
40
+ importMem0Export,
41
+ importZepGraphitiExport,
42
+ loadPersistedImporterArtifact,
43
+ persistEnigmaCapsule,
44
+ persistImportReport,
45
+ } from '../../../packages/importers/src/index.js';
16
46
  import * as relayServer from '../../relay/src/server.mjs';
17
47
  import * as gatewayServer from '../../gateway/src/server.mjs';
18
48
  import { verifyBundle } from '../../verifier/bin/enigma-verify.mjs';
@@ -47,6 +77,9 @@ import {
47
77
  validateRegistryBatch,
48
78
  validateProofNetworkPacket,
49
79
  } from '../../../packages/proof-network/src/index.js';
80
+ import { runDashboard as runTerminalDashboard } from '../../../packages/terminal-tui/index.mjs';
81
+ import { createAttestation, EnclaveSessionManager, verifyZeroizationReceipt } from '../../../packages/enclave-runtime/index.mjs';
82
+ import { swarmGraphFromBundle, swarmStatusFromBundle } from '../../../packages/connectors/swarm-router.mjs';
50
83
 
51
84
  const DEFAULT_BUNDLE = '.enigma/bundle.json';
52
85
  const DEFAULT_TEST_DRIVE_DIR = '.enigma/test-drive';
@@ -114,7 +147,7 @@ const IMPORTERS = Object.freeze({
114
147
  zep_graphiti_export: importZepGraphitiExport,
115
148
  });
116
149
 
117
- export const REQUIRED_PACKAGE_BINS = Object.freeze(['enigma', 'enigma-verify', 'enigma-mcp', 'enigma-relay', 'enigma-gateway', 'enigma-native-host']);
150
+ export const REQUIRED_PACKAGE_BINS = Object.freeze(['enigma', 'enigma-terminal', 'enigma-shell', 'enigma-verify', 'enigma-mcp', 'enigma-relay', 'enigma-gateway', 'enigma-native-host']);
118
151
 
119
152
  function setFlagValue(flags, name, value) {
120
153
  if (!flags.has(name)) {
@@ -220,6 +253,100 @@ async function writeJson(path, value) {
220
253
  await mkdir(dirname(path), { recursive: true });
221
254
  await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
222
255
  }
256
+ export async function resolvePassphrase(flags) {
257
+ let hasExplicitFlag = false;
258
+ let explicitFlagVal;
259
+ let hasExplicitFile = false;
260
+ let explicitFileVal;
261
+
262
+ if (flags) {
263
+ const rawFlag = getFlag(flags, ['passphrase', 'keyring-passphrase', 'keyringPassphrase']);
264
+ if (rawFlag !== undefined) {
265
+ hasExplicitFlag = true;
266
+ if (rawFlag === true || rawFlag === '') {
267
+ throw new Error('Passphrase flag was provided but value is empty.');
268
+ }
269
+ explicitFlagVal = String(lastFlagValue(rawFlag));
270
+ if (explicitFlagVal.trim() === '') {
271
+ throw new Error('Passphrase flag was provided but value is empty.');
272
+ }
273
+ }
274
+
275
+ const rawFile = getFlag(flags, ['passphrase-file', 'passphraseFile']);
276
+ if (rawFile !== undefined) {
277
+ hasExplicitFile = true;
278
+ if (rawFile === true || rawFile === '') {
279
+ throw new Error('Passphrase file flag was provided but path is empty.');
280
+ }
281
+ explicitFileVal = String(lastFlagValue(rawFile));
282
+ if (explicitFileVal.trim() === '') {
283
+ throw new Error('Passphrase file flag was provided but path is empty.');
284
+ }
285
+ }
286
+ }
287
+
288
+ // 1. Conflict detection
289
+ if (hasExplicitFlag && hasExplicitFile) {
290
+ throw new Error('Cannot specify both --passphrase and --passphrase-file.');
291
+ }
292
+
293
+ // 2. Explicit direct CLI flag outranks everything
294
+ if (hasExplicitFlag) {
295
+ return explicitFlagVal;
296
+ }
297
+
298
+ // 3. Explicit CLI passphrase-file outranks ambient environment variables
299
+ if (hasExplicitFile) {
300
+ const filePath = resolve(explicitFileVal);
301
+ let raw;
302
+ try {
303
+ raw = await readFile(filePath, 'utf8');
304
+ } catch (err) {
305
+ throw new Error(`Cannot read passphrase file at '${filePath}': ${err.message}`);
306
+ }
307
+ const clean = raw.replace(/\r?\n$/u, '');
308
+ if (clean.length === 0 || clean.trim().length === 0) {
309
+ throw new Error(`Passphrase file at '${filePath}' is empty.`);
310
+ }
311
+ return clean;
312
+ }
313
+
314
+ // 4. Ambient environment variable
315
+ const rawEnv = process.env.ENIGMA_PASSPHRASE ?? process.env.ENIGMA_KEYRING_PASSPHRASE;
316
+ if (rawEnv !== undefined) {
317
+ if (rawEnv === '' || String(rawEnv).trim() === '') {
318
+ throw new Error('ENIGMA_PASSPHRASE environment variable is set but empty.');
319
+ }
320
+ return String(rawEnv);
321
+ }
322
+
323
+ return undefined;
324
+ }
325
+ function insecureDevMode(flags = {}) {
326
+ return booleanFlag(flags, ['insecure-dev', 'insecureDev'], false)
327
+ || ['1', 'true'].includes(String(process.env.ENIGMA_INSECURE_DEV_MODE ?? '').toLowerCase());
328
+ }
329
+
330
+ export async function resolveVaultCredentials(flags = {}) {
331
+ const passphrase = await resolvePassphrase(flags);
332
+ const insecure = insecureDevMode(flags);
333
+ if (passphrase === undefined && !insecure) {
334
+ throw new Error('Persistent vault access requires --passphrase, --passphrase-file, or ENIGMA_PASSPHRASE. Use --insecure-dev only for disposable local development.');
335
+ }
336
+ const activeOnlyRaw = getFlag(flags, ['active-epoch-only', 'activeEpochOnly']);
337
+ const allowOldRaw = getFlag(flags, ['allow-old-epochs', 'allowOldEpochs']);
338
+ if (activeOnlyRaw !== undefined && allowOldRaw !== undefined) {
339
+ throw new Error('Cannot combine --active-epoch-only with --allow-old-epochs.');
340
+ }
341
+ let allowOldEpochReads;
342
+ if (activeOnlyRaw !== undefined) allowOldEpochReads = !booleanFlag(flags, ['active-epoch-only', 'activeEpochOnly'], true);
343
+ if (allowOldRaw !== undefined) allowOldEpochReads = booleanFlag(flags, ['allow-old-epochs', 'allowOldEpochs'], true);
344
+ return {
345
+ passphrase,
346
+ insecureDevMode: insecure,
347
+ allowOldEpochReads,
348
+ };
349
+ }
223
350
 
224
351
  async function fileExists(path) {
225
352
  try {
@@ -304,7 +431,8 @@ async function buildQuickstartArtifacts(flags, { bundleInput = DEFAULT_BUNDLE, o
304
431
  ensureDistinctOutputPaths(paths.outputs.map((output) => output.path));
305
432
  if (checkExisting) await assertCanWriteQuickstartOutputs(paths.outputs, overwrite);
306
433
 
307
- const passphrase = getFlag(flags, ['passphrase']);
434
+ const credentials = await resolveVaultCredentials(flags);
435
+ const { passphrase } = credentials;
308
436
  const vault = createVault({
309
437
  subjectId: String(getFlag(flags, ['subject', 'subject-id'], 'local-user')),
310
438
  displayName: String(getFlag(flags, ['display-name', 'name'], 'Local user')),
@@ -335,13 +463,18 @@ async function buildQuickstartArtifacts(flags, { bundleInput = DEFAULT_BUNDLE, o
335
463
  const verifyReport = verifyBundle(bundle);
336
464
 
337
465
  if (write) {
338
- await writeJson(paths.bundlePath, bundle);
339
- await writeJson(paths.contextPackPath, contextPack);
340
- await writeJson(paths.exportPath, bundle);
466
+ await withBundleFileLock(paths.bundlePath, async () => {
467
+ const expectedRevision = await fileExists(paths.bundlePath)
468
+ ? (await readFileRevision(paths.bundlePath)).revision
469
+ : null;
470
+ await writeJsonFileAtomic(paths.bundlePath, bundle, { expectedRevision, resetMonotonicHead: overwrite });
471
+ });
472
+ await writeJson(paths.contextPackPath, publicContextPackSummary(contextPack));
473
+ await writeJsonFileAtomic(paths.exportPath, bundle, { resetMonotonicHead: overwrite });
341
474
  await writeJson(paths.verifyReportPath, verifyReport);
342
475
  }
343
476
 
344
- return { ...paths, vault, passport, contextPack, bundle, verifyReport };
477
+ return { ...paths, vault, passport, contextPack, bundle, verifyReport, credentials };
345
478
  }
346
479
 
347
480
  async function crossModelMemoryTextFromFlags(flags) {
@@ -659,6 +792,41 @@ async function writableVaultPathCheck(bundleInput, displayInput = bundleInput) {
659
792
  };
660
793
  }
661
794
 
795
+ async function bundleInitializedCheck(bundlePath, vaultPath) {
796
+ if (vaultPath.target_exists !== true) {
797
+ return {
798
+ ok: false,
799
+ bundle: vaultPath.path,
800
+ target_exists: false,
801
+ schema: null,
802
+ reason: 'bundle_missing',
803
+ hint: 'Run quickstart or setup before using doctor as the final green check.',
804
+ };
805
+ }
806
+ try {
807
+ const bundle = await readJson(bundlePath);
808
+ const schema = typeof bundle?.schema === 'string' ? bundle.schema : null;
809
+ const schemaOk = schema === 'enigma.vault_bundle.v1';
810
+ return {
811
+ ok: schemaOk,
812
+ bundle: vaultPath.path,
813
+ target_exists: true,
814
+ schema: schemaOk ? schema : null,
815
+ reason: schemaOk ? null : 'bundle_schema_mismatch',
816
+ hint: schemaOk ? null : 'Run quickstart or setup with --overwrite to recreate the local Enigma bundle.',
817
+ };
818
+ } catch {
819
+ return {
820
+ ok: false,
821
+ bundle: vaultPath.path,
822
+ target_exists: true,
823
+ schema: null,
824
+ reason: 'bundle_json_invalid',
825
+ hint: 'Run quickstart or setup with --overwrite to recreate the local Enigma bundle.',
826
+ };
827
+ }
828
+ }
829
+
662
830
  async function schemaFiles() {
663
831
  return (await readdir(SPECS_URL)).filter((name) => name.endsWith('.schema.json')).sort();
664
832
  }
@@ -686,16 +854,28 @@ function publicCapsuleImportResult(result) {
686
854
  }
687
855
 
688
856
  async function ensureBundle(bundlePath, flags = {}) {
689
- const existed = await fileExists(bundlePath);
690
- if (existed) return { created: false, bundle: await readJson(bundlePath) };
691
- const passphrase = getFlag(flags, ['passphrase']);
692
- const vault = createVault({
693
- subjectId: String(getFlag(flags, ['subject', 'subject-id'], 'local-user')),
694
- displayName: String(getFlag(flags, ['display-name', 'name'], 'Local user')),
695
- passphrase,
857
+ const credentials = await resolveVaultCredentials(flags);
858
+ return withBundleFileLock(bundlePath, async () => {
859
+ if (await fileExists(bundlePath)) {
860
+ const state = await loadState(bundlePath, credentials);
861
+ return { ...state, credentials, created: false, bundle: state.stored };
862
+ }
863
+ const vault = createVault({
864
+ subjectId: String(getFlag(flags, ['subject', 'subject-id'], 'local-user')),
865
+ displayName: String(getFlag(flags, ['display-name', 'name'], 'Local user')),
866
+ passphrase: credentials.passphrase,
867
+ });
868
+ const bundle = await persistState(bundlePath, vault, { ...credentials, lockHeld: true, expectedRevision: null });
869
+ return {
870
+ created: true,
871
+ bundle,
872
+ stored: bundle,
873
+ vault,
874
+ passport: createPassport({ vault }),
875
+ revision: bundle.__revision,
876
+ credentials,
877
+ };
696
878
  });
697
- const bundle = await persistState(bundlePath, vault, { passphrase });
698
- return { created: true, bundle };
699
879
  }
700
880
 
701
881
  function listenAddress(server) {
@@ -800,68 +980,64 @@ async function serveHttpCommand({ flags, io, service, createServer, defaultPort
800
980
  return 0;
801
981
  }
802
982
 
803
- function reviveVault(stored, options = {}) {
804
- if (stored.schema !== 'enigma.vault_bundle.v1') {
805
- if (stored.vault?.schema === 'enigma.vault.local.v1') return stored.vault;
806
- throw new Error(`Unsupported Enigma bundle schema: ${stored.schema ?? '<missing>'}`);
807
- }
808
- const keyring = stored.keyring;
809
- let vaultKey;
810
- let addressKey;
811
- let privateKey;
812
- if (keyring?.type === KEYRING_ENCRYPTED_TYPE) {
813
- if (!options.passphrase) throw new Error('Encrypted keyring requires passphrase');
814
- const decrypted = decryptKeyring(keyring, options.passphrase);
815
- vaultKey = decrypted.vault_key_b64;
816
- addressKey = decrypted.address_key_b64;
817
- privateKey = decrypted.privateKey;
818
- } else {
819
- vaultKey = keyring?.vault_key_b64;
820
- addressKey = keyring?.address_key_b64;
821
- privateKey = keyring?.privateKey;
983
+ function assertPersistentVaultCredentials(options = {}) {
984
+ if (options.passphrase === undefined && options.insecureDevMode !== true) {
985
+ throw new Error('Persistent vault access requires credentials unless explicit insecure-dev mode is enabled.');
822
986
  }
823
- const signingKeyPair = privateKey
824
- ? { key_id: keyring?.signer?.key_id, publicKey: keyring?.publicKey, privateKey }
825
- : undefined;
826
- const vault = createVault({
827
- vault_id: stored.vault?.vault_id,
828
- tenant_id: stored.vault?.tenant_id,
829
- subject_id: stored.vault?.subject_id,
830
- actor_id: stored.vault?.actor_id,
831
- policy_id: stored.vault?.policy_id,
832
- vault_key: vaultKey,
833
- address_key: addressKey,
834
- signingKeyPair,
835
- now: stored.vault?.created_at,
836
- });
837
- if (keyring?.signer) vault.signer = keyring.signer;
838
- vault.created_at = stored.vault?.created_at ?? vault.created_at;
839
- vault.updated_at = stored.vault?.updated_at ?? vault.updated_at;
840
- vault.sequence = Number.isInteger(stored.vault?.sequence) ? stored.vault.sequence : 0;
841
- vault.events = Array.isArray(stored.events) ? stored.events : [];
842
- vault.receipts = Array.isArray(stored.receipts) ? stored.receipts : [];
843
- vault.memories = new Map((stored.memory_objects ?? []).map((record) => [record.memory_addr, { ...record }]));
844
- vault.activeAddresses = new Set(stored.active_memory_addresses ?? []);
845
- vault.tombstones = new Map((stored.tombstones ?? []).map((tombstone) => [tombstone.memory_addr, tombstone]));
846
- const roots = vault.__computeRoots();
847
- vault.active_set_root = roots.active_set_root;
848
- vault.receipt_log_root = roots.receipt_log_root;
849
- return vault;
850
987
  }
851
988
 
852
989
  async function loadState(bundlePath, options = {}) {
853
- const stored = await readJson(bundlePath);
854
- const vault = reviveVault(stored, options);
855
- return { stored, vault, passport: createPassport({ vault }) };
990
+ assertPersistentVaultCredentials(options);
991
+ const { value: stored, revision } = await readJsonFileWithRevision(bundlePath, options);
992
+ const imported = importBundle({
993
+ bundle: stored,
994
+ passphrase: options.passphrase,
995
+ strict: true,
996
+ insecureDevMode: options.insecureDevMode === true,
997
+ allowOldEpochReads: options.allowOldEpochReads,
998
+ recordImport: false,
999
+ });
1000
+ const vault = imported.vault;
1001
+ return { stored, revision, vault, passport: createPassport({ vault }), verified: imported.verified };
856
1002
  }
857
1003
 
858
1004
  async function persistState(bundlePath, vault, options = {}) {
1005
+ if (options.lockHeld !== true) {
1006
+ return withBundleFileLock(bundlePath, async () => {
1007
+ const expectedRevision = Object.prototype.hasOwnProperty.call(options, 'expectedRevision')
1008
+ ? options.expectedRevision
1009
+ : (await fileExists(bundlePath) ? (await readJsonFileWithRevision(bundlePath, options)).revision : null);
1010
+ return persistState(bundlePath, vault, { ...options, lockHeld: true, expectedRevision });
1011
+ });
1012
+ }
1013
+ assertPersistentVaultCredentials(options);
859
1014
  const exported = exportBundle({ vault, includePlaintext: false, passphrase: options.passphrase });
860
1015
  const bundle = exported.bundle ?? exported;
861
- await writeJson(bundlePath, bundle);
1016
+ const persisted = await writeJsonFileAtomic(bundlePath, bundle, {
1017
+ ...(Object.prototype.hasOwnProperty.call(options, 'expectedRevision')
1018
+ ? { expectedRevision: options.expectedRevision }
1019
+ : {}),
1020
+ checkpointProvider: options.checkpointProvider,
1021
+ requireExternalCheckpoint: options.requireExternalCheckpoint,
1022
+ minimumSequence: options.minimumSequence,
1023
+ });
1024
+ Object.defineProperty(bundle, '__revision', { value: persisted.revision, enumerable: false });
862
1025
  return bundle;
863
1026
  }
864
1027
 
1028
+ async function withVaultTransaction(bundlePath, options, operation) {
1029
+ return withBundleFileLock(bundlePath, async () => {
1030
+ const state = await loadState(bundlePath, options);
1031
+ const result = await operation(state);
1032
+ const bundle = await persistState(bundlePath, state.vault, {
1033
+ ...options,
1034
+ lockHeld: true,
1035
+ expectedRevision: state.revision,
1036
+ });
1037
+ return { ...state, result, bundle };
1038
+ });
1039
+ }
1040
+
865
1041
 
866
1042
  async function initCommand(flags, io) {
867
1043
  const bundleInput = pathFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE);
@@ -907,6 +1083,9 @@ async function initCommand(flags, io) {
907
1083
  context_pack: displays.context_pack,
908
1084
  export: displays.export,
909
1085
  verify_report: displays.verify_report,
1086
+ keyring_type: artifacts.bundle.keyring?.type,
1087
+ vault_credentials_required: artifacts.bundle.keyring?.type === 'local_secret_encrypted',
1088
+ insecure_evaluation_mode: artifacts.bundle.keyring?.type === 'local_secret',
910
1089
  subject_id: artifacts.bundle.vault?.subject_id,
911
1090
  client_configs_written: anyConnectorWritePerformed,
912
1091
  client_config_write_requested: connectorWritesRequested,
@@ -1065,8 +1244,10 @@ function publicSetupError(error, rawDisplays, publicDisplays) {
1065
1244
  return new Error(message);
1066
1245
  }
1067
1246
 
1247
+ const PUBLIC_PASSPHRASE_FILE_FLAG = '--passphrase-file "<passphrase-file>"';
1248
+
1068
1249
  function oneCommandInstallConnect(bundleDisplay = DEFAULT_BUNDLE, outDirDisplay = dirname(bundleDisplay)) {
1069
- const parts = ['npm install -g enigma-memory && enigma setup'];
1250
+ const parts = [`npm install -g enigma-memory && enigma setup ${PUBLIC_PASSPHRASE_FILE_FLAG}`];
1070
1251
  if (bundleDisplay !== DEFAULT_BUNDLE) parts.push(`--bundle ${commandPath(bundleDisplay)}`);
1071
1252
  if (outDirDisplay !== dirname(bundleDisplay)) parts.push(`--out-dir ${commandPath(outDirDisplay)}`);
1072
1253
  const base = parts.join(' ');
@@ -1095,7 +1276,7 @@ function setupNextCommands(bundleInput, exportDisplay, clients, writeConnectors)
1095
1276
  }
1096
1277
 
1097
1278
  function initExecuteCommand(bundleDisplay, outDirDisplay, requestedSelection, connectRequested, overwrite) {
1098
- let command = `enigma init --bundle ${commandPath(bundleDisplay)} --out-dir ${commandPath(outDirDisplay)}`;
1279
+ let command = `enigma init --bundle ${commandPath(bundleDisplay)} --out-dir ${commandPath(outDirDisplay)} ${PUBLIC_PASSPHRASE_FILE_FLAG}`;
1099
1280
  for (const client of requestedSelection.explicit_clients) command += ` --client ${client}`;
1100
1281
  if (requestedSelection.auto) command += ' --client auto';
1101
1282
  if (connectRequested) command += ' --connect';
@@ -1113,14 +1294,28 @@ function doctorNextCommands(bundleDisplay, client) {
1113
1294
  const clientId = client ?? DEFAULT_SETUP_CLIENTS[0];
1114
1295
  const bundle = commandPath(bundleDisplay);
1115
1296
  return [
1297
+ `enigma quickstart --bundle ${bundle} ${PUBLIC_PASSPHRASE_FILE_FLAG} --overwrite`,
1298
+ `enigma setup --bundle ${bundle} ${PUBLIC_PASSPHRASE_FILE_FLAG} --overwrite`,
1299
+ `enigma doctor --bundle ${bundle} --client ${clientId}`,
1116
1300
  `enigma status --bundle ${bundle}`,
1117
1301
  `enigma drive health --bundle ${bundle}`,
1118
- `enigma setup --bundle ${bundle}`,
1119
- `enigma doctor --bundle ${bundle} --client ${clientId}`,
1120
1302
  `enigma connect ${clientId} --bundle ${bundle}`,
1121
1303
  ];
1122
1304
  }
1123
1305
 
1306
+ function doctorFirstRunHint(_bundleDisplay, client) {
1307
+ const clientId = client ?? DEFAULT_SETUP_CLIENTS[0];
1308
+ const bundle = commandPath('<bundle-path>');
1309
+ return {
1310
+ bundle: '<bundle-path>',
1311
+ commands: [
1312
+ `enigma quickstart --bundle ${bundle} ${PUBLIC_PASSPHRASE_FILE_FLAG} --overwrite`,
1313
+ `enigma setup --bundle ${bundle} ${PUBLIC_PASSPHRASE_FILE_FLAG} --overwrite`,
1314
+ `enigma doctor --bundle ${bundle} --client ${clientId}`,
1315
+ ],
1316
+ };
1317
+ }
1318
+
1124
1319
  async function setupDoctorChecks(flags, artifacts, clients, displays) {
1125
1320
  const packageJson = await readPackageJson();
1126
1321
  const requiredNodeMajor = minimumNodeMajor(packageJson.engines?.node);
@@ -1320,6 +1515,9 @@ export async function setupCommand(flags, io) {
1320
1515
  connect_installed: connectInstalled,
1321
1516
  bundle: displays.bundle,
1322
1517
  context_pack: displays.context_pack,
1518
+ keyring_type: artifacts.bundle.keyring?.type,
1519
+ vault_credentials_required: artifacts.bundle.keyring?.type === 'local_secret_encrypted',
1520
+ insecure_evaluation_mode: artifacts.bundle.keyring?.type === 'local_secret',
1323
1521
  export: displays.export,
1324
1522
  verify_report: displays.verify_report,
1325
1523
  memory_source: setupMemorySource(flags),
@@ -1344,7 +1542,16 @@ export async function setupCommand(flags, io) {
1344
1542
  connect_plans: Object.fromEntries(connectors.map((connector) => [connector.client_id, connector.connect_plan])),
1345
1543
  next_commands: setupNextCommands(displays.bundle, displays.export, clients, connectorWritesRequested && (!connectInstalled || anyConnectorWritePerformed)),
1346
1544
  checks: doctor.checks,
1347
- claim_boundaries: { ...SETUP_CLAIM_BOUNDARIES, hosted_saas_live: false, raw_memory_printed: false, solana_required: false, browser_extension_required: false },
1545
+ claim_boundaries: {
1546
+ ...SETUP_CLAIM_BOUNDARIES,
1547
+ vault_credentials_required: artifacts.bundle.keyring?.type === 'local_secret_encrypted',
1548
+ keyring_encrypted: artifacts.bundle.keyring?.type === 'local_secret_encrypted',
1549
+ insecure_evaluation_mode: artifacts.bundle.keyring?.type === 'local_secret',
1550
+ hosted_saas_live: false,
1551
+ raw_memory_printed: false,
1552
+ solana_required: false,
1553
+ browser_extension_required: false,
1554
+ },
1348
1555
  }, io);
1349
1556
  return ok ? 0 : 1;
1350
1557
  }
@@ -1369,6 +1576,9 @@ export async function quickstartCommand(flags, io) {
1369
1576
  context_pack: displays.context_pack,
1370
1577
  export: displays.export,
1371
1578
  verify_report: displays.verify_report,
1579
+ keyring_type: artifacts.bundle.keyring?.type,
1580
+ vault_credentials_required: artifacts.bundle.keyring?.type === 'local_secret_encrypted',
1581
+ insecure_evaluation_mode: artifacts.bundle.keyring?.type === 'local_secret',
1372
1582
  memory_count: Array.isArray(artifacts.bundle.memory_objects) ? artifacts.bundle.memory_objects.length : 0,
1373
1583
  receipt_count: Array.isArray(artifacts.bundle.receipts) ? artifacts.bundle.receipts.length : 0,
1374
1584
  context_item_count: Array.isArray(artifacts.contextPack.memories) ? artifacts.contextPack.memories.length : 0,
@@ -1380,6 +1590,9 @@ export async function quickstartCommand(flags, io) {
1380
1590
  claim_boundaries: {
1381
1591
  local_only: true,
1382
1592
  provider_credentials_required: false,
1593
+ vault_credentials_required: artifacts.bundle.keyring?.type === 'local_secret_encrypted',
1594
+ keyring_encrypted: artifacts.bundle.keyring?.type === 'local_secret_encrypted',
1595
+ insecure_evaluation_mode: artifacts.bundle.keyring?.type === 'local_secret',
1383
1596
  provider_native_memory_canonical: false,
1384
1597
  provider_deletion_proof: false,
1385
1598
  model_forgetting_proof: false,
@@ -1439,8 +1652,10 @@ export async function crossModelDemoCommand(flags, io) {
1439
1652
  let vault;
1440
1653
  let passport;
1441
1654
  let demoMemoryAddr;
1655
+ let loadedRevision = null;
1442
1656
 
1443
- const passphrase = getFlag(flags, ['passphrase']);
1657
+ const credentials = await resolveVaultCredentials(flags);
1658
+ const { passphrase } = credentials;
1444
1659
  if (!bundleWasSupplied) {
1445
1660
  vault = createVault({
1446
1661
  subjectId: 'cross-model-demo-user',
@@ -1459,12 +1674,10 @@ export async function crossModelDemoCommand(flags, io) {
1459
1674
  demoMemoryAddr = remembered.memory_addr;
1460
1675
  bundleCreated = true;
1461
1676
  } else {
1462
- const existed = await fileExists(bundlePath);
1463
- if (!existed) {
1464
- await ensureBundle(bundlePath, flags);
1465
- bundleCreated = true;
1466
- }
1467
- ({ vault, passport } = await loadState(bundlePath, { passphrase }));
1677
+ const state = await ensureBundle(bundlePath, flags);
1678
+ bundleCreated = state.created;
1679
+ loadedRevision = state.revision;
1680
+ ({ vault, passport } = state);
1468
1681
  const remembered = remember({
1469
1682
  vault,
1470
1683
  passport,
@@ -1482,7 +1695,7 @@ export async function crossModelDemoCommand(flags, io) {
1482
1695
  const receiptCountBeforeProfiles = Array.isArray(vault.receipts) ? vault.receipts.length : 0;
1483
1696
  const profiles = buildCrossModelProfileSummaries({ vault, passport, demoMemoryAddr, limit });
1484
1697
 
1485
- const bundle = await persistState(bundlePath, vault, { passphrase: getFlag(flags, ['passphrase']) });
1698
+ const bundle = await persistState(bundlePath, vault, { ...credentials, expectedRevision: loadedRevision });
1486
1699
  const report = {
1487
1700
  ok: true,
1488
1701
  schema: 'enigma.cross_model_demo.v1',
@@ -1511,82 +1724,121 @@ export async function crossModelDemoCommand(flags, io) {
1511
1724
 
1512
1725
  async function rememberCommand(flags, io) {
1513
1726
  const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
1514
- const { vault, passport } = await loadState(bundlePath, { passphrase: getFlag(flags, ['passphrase']) });
1727
+ const credentials = await resolveVaultCredentials(flags);
1728
+ const text = await memoryTextFromFlags(flags);
1515
1729
  const importance = parseImportanceFlag(flags);
1516
- const result = remember({
1730
+ const { result } = await withVaultTransaction(bundlePath, credentials, ({ vault, passport }) => remember({
1517
1731
  vault,
1518
1732
  passport,
1519
- text: await memoryTextFromFlags(flags),
1733
+ text,
1520
1734
  importance,
1521
1735
  purpose: getFlag(flags, ['purpose'], 'user_memory'),
1522
1736
  purpose_tags: parseList(getFlag(flags, ['tags'])),
1523
1737
  metadata: parseJson(getFlag(flags, ['metadata']), {}),
1524
- });
1525
- await persistState(bundlePath, vault, { passphrase: getFlag(flags, ['passphrase']) });
1738
+ }));
1526
1739
  print({ ok: true, memory_addr: result.memory_addr, receipt_id: result.receipt?.receipt_id }, io);
1527
1740
  return 0;
1528
1741
  }
1529
1742
 
1743
+ async function recallCommand(flags, io) {
1744
+ const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
1745
+ const credentials = await resolveVaultCredentials(flags);
1746
+ const memoryAddr = requireFlag(flags, ['id', 'memory-addr'], 'id');
1747
+ const { result } = await withVaultTransaction(bundlePath, credentials, ({ vault, passport }) => recall({
1748
+ vault,
1749
+ passport,
1750
+ memory_addr: memoryAddr,
1751
+ purpose: getFlag(flags, ['purpose'], 'cli_recall'),
1752
+ }));
1753
+ print({
1754
+ ok: true,
1755
+ memory_addr: result.memory_addr,
1756
+ content: result.content,
1757
+ memory: result.memory,
1758
+ receipt: result.receipt,
1759
+ receipt_id: result.receipt?.receipt_id,
1760
+ }, io);
1761
+ return 0;
1762
+ }
1763
+
1530
1764
  async function updateCommand(flags, io) {
1531
1765
  const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
1532
- const { vault, passport } = await loadState(bundlePath, { passphrase: getFlag(flags, ['passphrase']) });
1533
- const result = updateMemory({
1766
+ const credentials = await resolveVaultCredentials(flags);
1767
+ const { result } = await withVaultTransaction(bundlePath, credentials, ({ vault, passport }) => updateMemory({
1534
1768
  vault,
1535
1769
  passport,
1536
1770
  memory_addr: requireFlag(flags, ['id', 'memory-addr'], 'id'),
1537
1771
  text: requireFlag(flags, ['text', 'memory'], 'text'),
1538
1772
  metadata: parseJson(getFlag(flags, ['metadata']), {}),
1539
- });
1540
- await persistState(bundlePath, vault, { passphrase: getFlag(flags, ['passphrase']) });
1773
+ }));
1541
1774
  print({ ok: true, old_memory_addr: result.old_memory_addr, memory_addr: result.memory_addr, receipt_id: result.receipt?.receipt_id }, io);
1542
1775
  return 0;
1543
1776
  }
1544
1777
 
1545
1778
  async function deleteCommand(flags, io) {
1546
1779
  const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
1547
- const { vault, passport } = await loadState(bundlePath, { passphrase: getFlag(flags, ['passphrase']) });
1548
- const result = deleteMemory({
1780
+ const credentials = await resolveVaultCredentials(flags);
1781
+ const { result } = await withVaultTransaction(bundlePath, credentials, ({ vault, passport }) => deleteMemory({
1549
1782
  vault,
1550
1783
  passport,
1551
1784
  memory_addr: requireFlag(flags, ['id', 'memory-addr'], 'id'),
1552
1785
  reason: getFlag(flags, ['reason'], 'user_delete'),
1553
- });
1554
- await persistState(bundlePath, vault, { passphrase: getFlag(flags, ['passphrase']) });
1786
+ }));
1555
1787
  print({ ok: true, memory_addr: result.memory_addr, receipt_id: result.receipt?.receipt_id }, io);
1556
1788
  return 0;
1557
1789
  }
1558
1790
 
1559
1791
  async function contextCommand(flags, io) {
1560
1792
  const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
1561
- const { vault, passport } = await loadState(bundlePath, { passphrase: getFlag(flags, ['passphrase']) });
1793
+ const credentials = await resolveVaultCredentials(flags);
1562
1794
  const query = getFlag(flags, ['query', 'q'], '');
1563
1795
  const optimize = getFlag(flags, ['optimize']) === true
1564
1796
  || getFlag(flags, ['optimize']) === 'true'
1565
1797
  || String(query).trim().length > 0;
1566
1798
  const includeUnrelated = booleanFlag(flags, ['include-unrelated', 'includeUnrelated', 'no-strict-relevance', 'noStrictRelevance'], false);
1567
1799
  const strictRelevance = String(query).trim().length > 0 && !includeUnrelated;
1568
- const pack = compileContextPack({
1569
- vault,
1570
- passport,
1571
- query,
1572
- purpose: getFlag(flags, ['purpose'], 'local_context'),
1573
- limit: Number(getFlag(flags, ['limit'], 8)),
1574
- optimize,
1575
- strict_relevance: strictRelevance,
1576
- max_estimated_tokens: parseOptionalNumber(getFlag(flags, ['max-estimated-tokens', 'maxEstimatedTokens'])),
1577
- price_per_million_tokens: parseOptionalNumber(getFlag(flags, ['price-per-million-tokens', 'pricePerMillionTokens'])),
1578
- currency: getFlag(flags, ['currency']),
1800
+ const withProof = booleanFlag(flags, ['proof', 'with-proof', 'withProof'], false);
1801
+ const { result: output } = await withVaultTransaction(bundlePath, credentials, ({ vault, passport }) => {
1802
+ const pack = compileContextPack({
1803
+ vault,
1804
+ passport,
1805
+ query,
1806
+ purpose: getFlag(flags, ['purpose'], 'local_context'),
1807
+ limit: Number(getFlag(flags, ['limit'], 8)),
1808
+ optimize,
1809
+ strict_relevance: strictRelevance,
1810
+ max_estimated_tokens: parseOptionalNumber(getFlag(flags, ['max-estimated-tokens', 'maxEstimatedTokens'])),
1811
+ price_per_million_tokens: parseOptionalNumber(getFlag(flags, ['price-per-million-tokens', 'pricePerMillionTokens'])),
1812
+ currency: getFlag(flags, ['currency']),
1813
+ });
1814
+ return withProof
1815
+ ? {
1816
+ ok: true,
1817
+ schema: 'enigma.context_proof_bundle.v1',
1818
+ context_pack_ref: contextPackPublicDigest(pack),
1819
+ context_pack_summary: publicContextPackSummary(pack),
1820
+ context_passport: createContextPassport({ contextPack: pack, passport, vault, query, now: getFlag(flags, ['now']) }),
1821
+ proof_of_non_use: createProofOfNonUse({ contextPack: pack, passport, vault, query, now: getFlag(flags, ['now']) }),
1822
+ claim_boundaries: {
1823
+ local_enigma_vault_only: true,
1824
+ provider_deletion_claim: false,
1825
+ model_forgetting_claim: false,
1826
+ raw_memory_printed: false,
1827
+ },
1828
+ }
1829
+ : pack;
1579
1830
  });
1580
- await persistState(bundlePath, vault, { passphrase: getFlag(flags, ['passphrase']) });
1581
1831
  const out = getFlag(flags, ['out']);
1582
- if (out && out !== true) await writeJson(resolve(String(out)), pack);
1583
- print(pack, io);
1832
+ if (out && out !== true) {
1833
+ const persistedOutput = withProof ? output : publicContextPackSummary(output);
1834
+ await writeJson(resolve(String(out)), persistedOutput);
1835
+ }
1836
+ print(output, io);
1584
1837
  return 0;
1585
1838
  }
1586
1839
 
1587
1840
 
1588
1841
  function memorySearchReport({ bundlePath, vault, query, limit, includeContent = false, includeUnrelated = false, now = '2026-01-01T00:00:00.000Z' }) {
1589
- const roots = vault.__computeRoots();
1590
1842
  const queryTokens = searchTokensFrom(query);
1591
1843
  const { candidates, byAddress } = searchCandidates(vault, queryTokens, includeUnrelated);
1592
1844
  const plan = createMemoryOptimizationPlan({
@@ -1603,6 +1855,17 @@ function memorySearchReport({ bundlePath, vault, query, limit, includeContent =
1603
1855
  return planIndex.get(left.address) - planIndex.get(right.address);
1604
1856
  })
1605
1857
  .slice(0, limit);
1858
+ const auditReceiptByAddress = new Map(selectedItems.map((item) => {
1859
+ const audited = recall({
1860
+ vault,
1861
+ memory_addr: item.address,
1862
+ operation: 'search_read',
1863
+ purpose: 'cli_search',
1864
+ now,
1865
+ });
1866
+ return [item.address, audited.receipt];
1867
+ }));
1868
+ const roots = vault.__computeRoots();
1606
1869
  const accessReceipts = selectedItems.map((item, index) => createMemoryAccessReceipt({
1607
1870
  item,
1608
1871
  plan,
@@ -1626,6 +1889,7 @@ function memorySearchReport({ bundlePath, vault, query, limit, includeContent =
1626
1889
  score: hit.score,
1627
1890
  tier: item.tier,
1628
1891
  receipt_ids: searchResultReceiptIds(vault, item.address),
1892
+ read_receipt_id: auditReceiptByAddress.get(item.address)?.receipt_id,
1629
1893
  access_receipt_ref: accessReceipt?.access_receipt_ref,
1630
1894
  access_receipt_id: accessReceipt?.receipt_id,
1631
1895
  access_receipt_refs: accessReceipt?.access_receipt_ref ? [accessReceipt.access_receipt_ref] : [],
@@ -1658,8 +1922,8 @@ async function searchCommand(flags, io) {
1658
1922
  if (limit < 0) throw new Error('--limit must be non-negative.');
1659
1923
  const includeContent = getFlag(flags, ['include-content', 'includeContent']) === true || getFlag(flags, ['include-content', 'includeContent']) === 'true';
1660
1924
  const includeUnrelated = booleanFlag(flags, ['include-unrelated', 'includeUnrelated', 'no-strict-relevance', 'noStrictRelevance'], false);
1661
- const { vault } = await loadState(bundlePath, { passphrase: getFlag(flags, ['passphrase']) });
1662
- print(memorySearchReport({
1925
+ const credentials = await resolveVaultCredentials(flags);
1926
+ const { result: report } = await withVaultTransaction(bundlePath, credentials, ({ vault }) => memorySearchReport({
1663
1927
  bundlePath,
1664
1928
  vault,
1665
1929
  query,
@@ -1667,7 +1931,8 @@ async function searchCommand(flags, io) {
1667
1931
  includeContent,
1668
1932
  includeUnrelated,
1669
1933
  now: getFlag(flags, ['now'], '2026-01-01T00:00:00.000Z'),
1670
- }), io);
1934
+ }));
1935
+ print(report, io);
1671
1936
  return 0;
1672
1937
  }
1673
1938
 
@@ -1709,10 +1974,47 @@ function passportStatusReport({ bundlePath, stored = {}, vault, passport }) {
1709
1974
 
1710
1975
  async function statusCommand(flags, io) {
1711
1976
  const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
1712
- const { stored, vault, passport } = await loadState(bundlePath, { passphrase: getFlag(flags, ['passphrase']) });
1977
+ const credentials = await resolveVaultCredentials(flags);
1978
+ const { stored, vault, passport } = await loadState(bundlePath, credentials);
1713
1979
  print(passportStatusReport({ bundlePath, stored, vault, passport }), io);
1714
1980
  return 0;
1715
1981
  }
1982
+ async function rotateVaultKeyCommand(flags, io) {
1983
+ const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
1984
+ const credentials = await resolveVaultCredentials(flags);
1985
+ const { result } = await withVaultTransaction(bundlePath, credentials, ({ vault }) => rotateContentKey({
1986
+ vault,
1987
+ key_id: getFlag(flags, ['key-id', 'keyId']),
1988
+ now: getFlag(flags, ['now']),
1989
+ }));
1990
+ print({ ok: true, operation: 'key_rotate', ...result }, io);
1991
+ return 0;
1992
+ }
1993
+
1994
+ async function sealVaultKeyCommand(flags, io) {
1995
+ const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
1996
+ const credentials = await resolveVaultCredentials(flags);
1997
+ const epochValue = getFlag(flags, ['epoch']);
1998
+ const { result } = await withVaultTransaction(bundlePath, credentials, ({ vault }) => sealContentKeyEpoch({
1999
+ vault,
2000
+ epoch: epochValue === undefined ? undefined : Number(epochValue),
2001
+ now: getFlag(flags, ['now']),
2002
+ }));
2003
+ print({ ok: true, operation: 'key_seal', ...result }, io);
2004
+ return 0;
2005
+ }
2006
+
2007
+ async function resetVaultKeysCommand(flags, io) {
2008
+ const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
2009
+ const credentials = await resolveVaultCredentials(flags);
2010
+ const { result } = await withVaultTransaction(bundlePath, credentials, ({ vault }) => resetVaultContentKeys({
2011
+ vault,
2012
+ key_id: getFlag(flags, ['key-id', 'keyId']),
2013
+ now: getFlag(flags, ['now']),
2014
+ }));
2015
+ print({ ok: true, operation: 'key_reset', ...result }, io);
2016
+ return 0;
2017
+ }
1716
2018
  async function readOptionalJsonInput(flags, names) {
1717
2019
  const path = getFlag(flags, names);
1718
2020
  if (!path) return null;
@@ -1722,7 +2024,8 @@ async function readOptionalJsonInput(flags, names) {
1722
2024
 
1723
2025
  async function driveHealthCommand(flags, io) {
1724
2026
  const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
1725
- const { vault, passport } = await loadState(bundlePath, { passphrase: getFlag(flags, ['passphrase']) });
2027
+ const credentials = await resolveVaultCredentials(flags);
2028
+ const { vault, passport } = await loadState(bundlePath, credentials);
1726
2029
  const benchmarkSummary = await readOptionalJsonInput(flags, ['benchmark-summary', 'benchmarkSummary']);
1727
2030
  const connectorSummary = await readOptionalJsonInput(flags, ['connector-summary', 'connectorSummary']);
1728
2031
  const replicas = await readOptionalJsonInput(flags, ['replicas']);
@@ -1785,29 +2088,35 @@ function firstActiveMemoryAddress(vault) {
1785
2088
  return first.value;
1786
2089
  }
1787
2090
 
1788
- function testDriveNextCommands(bundleDisplay, crossModelReportDisplay) {
2091
+ function testDriveCredentialFlag(insecureEvaluationMode) {
2092
+ return insecureEvaluationMode ? '--insecure-dev' : PUBLIC_PASSPHRASE_FILE_FLAG;
2093
+ }
2094
+
2095
+ function testDriveNextCommands(bundleDisplay, crossModelReportDisplay, insecureEvaluationMode) {
1789
2096
  const quotedBundle = commandPath(bundleDisplay);
1790
2097
  const quotedReport = commandPath(crossModelReportDisplay);
2098
+ const credentialFlag = testDriveCredentialFlag(insecureEvaluationMode);
1791
2099
  return [
1792
- `enigma status --bundle ${quotedBundle}`,
1793
- `enigma drive health --bundle ${quotedBundle}`,
1794
- `enigma search --bundle ${quotedBundle} --query "local proof bundle"`,
1795
- `enigma demo cross-model --bundle ${quotedBundle} --out ${quotedReport}`,
1796
- 'enigma setup --overwrite',
2100
+ `enigma status --bundle ${quotedBundle} ${credentialFlag}`,
2101
+ `enigma drive health --bundle ${quotedBundle} ${credentialFlag}`,
2102
+ `enigma search --bundle ${quotedBundle} ${credentialFlag} --query "local proof bundle"`,
2103
+ `enigma demo cross-model --bundle ${quotedBundle} ${credentialFlag} --out ${quotedReport}`,
2104
+ `enigma setup ${credentialFlag} --overwrite`,
1797
2105
  ];
1798
2106
  }
1799
2107
 
1800
- function testDriveFlowCommands({ bundleDisplay, outDirInput, crossModelReportDisplay, overwrite }) {
2108
+ function testDriveFlowCommands({ bundleDisplay, outDirInput, crossModelReportDisplay, overwrite, insecureEvaluationMode }) {
1801
2109
  const overwriteSuffix = overwrite ? ' --overwrite' : '';
1802
2110
  const quotedBundle = commandPath(bundleDisplay);
1803
2111
  const quotedOutDir = commandPath(outDirInput);
1804
2112
  const quotedReport = commandPath(crossModelReportDisplay);
2113
+ const credentialFlag = testDriveCredentialFlag(insecureEvaluationMode);
1805
2114
  return [
1806
- `enigma quickstart --bundle ${quotedBundle} --out-dir ${quotedOutDir}${overwriteSuffix}`,
1807
- `enigma status --bundle ${quotedBundle}`,
1808
- `enigma drive health --bundle ${quotedBundle}`,
1809
- `enigma search --bundle ${quotedBundle} --query "local proof bundle"`,
1810
- `enigma demo cross-model --bundle ${quotedBundle} --out ${quotedReport}`,
2115
+ `enigma quickstart --bundle ${quotedBundle} --out-dir ${quotedOutDir} ${credentialFlag}${overwriteSuffix}`,
2116
+ `enigma status --bundle ${quotedBundle} ${credentialFlag}`,
2117
+ `enigma drive health --bundle ${quotedBundle} ${credentialFlag}`,
2118
+ `enigma search --bundle ${quotedBundle} ${credentialFlag} --query "local proof bundle"`,
2119
+ `enigma demo cross-model --bundle ${quotedBundle} ${credentialFlag} --out ${quotedReport}`,
1811
2120
  ];
1812
2121
  }
1813
2122
 
@@ -1843,10 +2152,15 @@ function testDriveBenchmarkPointers() {
1843
2152
  ];
1844
2153
  }
1845
2154
 
1846
- function testDriveClaimBoundaries() {
2155
+ function testDriveClaimBoundaries(keyringType) {
2156
+ const encrypted = keyringType === 'local_secret_encrypted';
1847
2157
  return {
1848
2158
  local_only: true,
1849
- credentials_required: false,
2159
+ credentials_required: encrypted,
2160
+ vault_credentials_required: encrypted,
2161
+ keyring_encrypted: encrypted,
2162
+ insecure_evaluation_only: !encrypted,
2163
+ at_rest_confidentiality: encrypted,
1850
2164
  external_provider_calls: false,
1851
2165
  client_config_writes_performed: false,
1852
2166
  plaintext_memory_echoed: false,
@@ -1927,7 +2241,32 @@ export async function testDriveCommand(flags, io) {
1927
2241
  demoMemoryAddr,
1928
2242
  limit: crossModelLimit,
1929
2243
  });
1930
- const finalExport = exportBundle({ vault: artifacts.vault, includePlaintext: false });
2244
+ const rawStatusSummary = passportStatusReport({
2245
+ bundlePath: outputs.bundlePath,
2246
+ vault: artifacts.vault,
2247
+ passport: artifacts.passport,
2248
+ stored: {
2249
+ owner: {
2250
+ subject_id: artifacts.vault.subject_id,
2251
+ display_name: artifacts.vault.display_name,
2252
+ },
2253
+ },
2254
+ });
2255
+ const statusSummary = publicTestDriveStatusSummary(rawStatusSummary, bundleInput);
2256
+ const rawSearchSummary = memorySearchReport({
2257
+ bundlePath: outputs.bundlePath,
2258
+ vault: artifacts.vault,
2259
+ query: 'local proof bundle',
2260
+ limit: 3,
2261
+ includeContent: false,
2262
+ now: getFlag(flags, ['now'], '2026-01-01T00:00:00.000Z'),
2263
+ });
2264
+ const searchSummary = publicTestDriveSearchSummary(rawSearchSummary, bundleInput);
2265
+ const finalExport = exportBundle({
2266
+ vault: artifacts.vault,
2267
+ includePlaintext: false,
2268
+ passphrase: artifacts.credentials.passphrase,
2269
+ });
1931
2270
  const finalBundle = finalExport.bundle ?? finalExport;
1932
2271
  const finalVerifyReport = verifyBundle(finalBundle);
1933
2272
  const crossModelReport = {
@@ -1951,27 +2290,6 @@ export async function testDriveCommand(flags, io) {
1951
2290
  out_written: !dryRun,
1952
2291
  claim_boundaries: { ...CROSS_MODEL_CLAIM_BOUNDARIES },
1953
2292
  };
1954
- const rawStatusSummary = passportStatusReport({
1955
- bundlePath: outputs.bundlePath,
1956
- vault: artifacts.vault,
1957
- passport: artifacts.passport,
1958
- stored: {
1959
- owner: {
1960
- subject_id: artifacts.vault.subject_id,
1961
- display_name: artifacts.vault.display_name,
1962
- },
1963
- },
1964
- });
1965
- const statusSummary = publicTestDriveStatusSummary(rawStatusSummary, bundleInput);
1966
- const rawSearchSummary = memorySearchReport({
1967
- bundlePath: outputs.bundlePath,
1968
- vault: artifacts.vault,
1969
- query: 'local proof bundle',
1970
- limit: 3,
1971
- includeContent: false,
1972
- now: getFlag(flags, ['now'], '2026-01-01T00:00:00.000Z'),
1973
- });
1974
- const searchSummary = publicTestDriveSearchSummary(rawSearchSummary, bundleInput);
1975
2293
 
1976
2294
  if (!dryRun) {
1977
2295
  await writeJson(outputs.bundlePath, finalBundle);
@@ -1986,8 +2304,9 @@ export async function testDriveCommand(flags, io) {
1986
2304
  outDirInput,
1987
2305
  crossModelReportDisplay: outputs.crossModelReportDisplay,
1988
2306
  overwrite,
2307
+ insecureEvaluationMode: artifacts.credentials.insecureDevMode,
1989
2308
  });
1990
- const nextCommands = testDriveNextCommands(bundleInput, outputs.crossModelReportDisplay);
2309
+ const nextCommands = testDriveNextCommands(bundleInput, outputs.crossModelReportDisplay, artifacts.credentials.insecureDevMode);
1991
2310
  const files = testDriveFileSummaries(outputs.artifacts, !dryRun);
1992
2311
  const packageJson = await readPackageJson();
1993
2312
  const ok = finalVerifyReport.ok === true && crossModelReport.ok === true && statusSummary.ok === true && searchSummary.ok === true;
@@ -1999,12 +2318,15 @@ export async function testDriveCommand(flags, io) {
1999
2318
  out_dir: outDirInput,
2000
2319
  bundle: bundleInput,
2001
2320
  install_command: `npm install -g ${packageJson.name ?? 'enigma-memory'}`,
2002
- release_target: '0.1.18',
2321
+ release_target: packageJson.version ?? '0.1.20',
2003
2322
  artifacts_written: !dryRun,
2004
2323
  client_configs_written: false,
2005
2324
  client_config_write_required: false,
2006
2325
  memory_plaintext_echoed: false,
2007
2326
  provider_credentials_required: false,
2327
+ keyring_type: finalBundle.keyring?.type,
2328
+ vault_credentials_required: finalBundle.keyring?.type === 'local_secret_encrypted',
2329
+ insecure_evaluation_mode: finalBundle.keyring?.type === 'local_secret',
2008
2330
  hosted_saas_live: false,
2009
2331
  files,
2010
2332
  files_written: dryRun ? [] : files.map((file) => file.path),
@@ -2024,6 +2346,9 @@ export async function testDriveCommand(flags, io) {
2024
2346
  client_selection: publicSetupClientSelection(selection),
2025
2347
  client_configs_written: false,
2026
2348
  provider_credentials_required: false,
2349
+ keyring_type: finalBundle.keyring?.type,
2350
+ vault_credentials_required: finalBundle.keyring?.type === 'local_secret_encrypted',
2351
+ insecure_evaluation_mode: finalBundle.keyring?.type === 'local_secret',
2027
2352
  memory_plaintext_echoed: false,
2028
2353
  memory_count: Array.isArray(finalBundle.memory_objects) ? finalBundle.memory_objects.length : 0,
2029
2354
  receipt_count: Array.isArray(finalBundle.receipts) ? finalBundle.receipts.length : 0,
@@ -2033,19 +2358,17 @@ export async function testDriveCommand(flags, io) {
2033
2358
  status_summary: statusSummary,
2034
2359
  search_summary: searchSummary,
2035
2360
  cross_model_summary: crossModelReport,
2036
- claim_boundaries: testDriveClaimBoundaries(),
2361
+ claim_boundaries: testDriveClaimBoundaries(finalBundle.keyring?.type),
2037
2362
  }, io);
2038
2363
  return ok ? 0 : 1;
2039
2364
  }
2040
2365
 
2041
2366
  async function exportCommand(flags, io) {
2042
2367
  const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
2043
- const { vault } = await loadState(bundlePath, { passphrase: getFlag(flags, ['passphrase']) });
2044
- const exported = exportBundle({ vault, includePlaintext: false, passphrase: getFlag(flags, ['passphrase']) });
2045
- const bundle = exported.bundle ?? exported;
2046
- await writeJson(bundlePath, bundle);
2368
+ const credentials = await resolveVaultCredentials(flags);
2369
+ const { bundle } = await withVaultTransaction(bundlePath, credentials, () => ({ operation: 'export' }));
2047
2370
  const out = resolve(String(getFlag(flags, ['out'], 'enigma-export.json')));
2048
- await writeJson(out, bundle);
2371
+ await writeJsonFileAtomic(out, bundle);
2049
2372
  print({ ok: true, export: out, receipt_count: Array.isArray(bundle.receipts) ? bundle.receipts.length : 0 }, io);
2050
2373
  return 0;
2051
2374
  }
@@ -2083,88 +2406,368 @@ export async function meshDemoCommand(_flags, io) {
2083
2406
  print(runMeshDemo(), io);
2084
2407
  return 0;
2085
2408
  }
2086
-
2087
- export async function enterpriseDemoCommand(_flags, io) {
2088
- print(runEnterpriseDemo(), io);
2409
+ export async function meshStatusCommand(flags, io) {
2410
+ const node = new MeshGossipNode({
2411
+ scope: String(getFlag(flags, ['scope'], 'enigma.global')),
2412
+ });
2413
+ print(node.getStatus(), io);
2089
2414
  return 0;
2090
2415
  }
2091
2416
 
2092
- export async function doctorCommand(flags, io) {
2093
- const packageJson = await readPackageJson();
2094
- const requiredNodeMajor = minimumNodeMajor(packageJson.engines?.node);
2095
- const currentNodeMajor = nodeMajor(process.versions.node);
2096
- const binMap = packageJson.bin && typeof packageJson.bin === 'object' && !Array.isArray(packageJson.bin) ? packageJson.bin : {};
2097
- const binEntries = await Promise.all(REQUIRED_PACKAGE_BINS.map(async (name) => {
2098
- const target = binMap[name];
2099
- const declared = typeof target === 'string' && target.length > 0;
2100
- return {
2101
- name,
2102
- target: declared ? target : null,
2103
- declared,
2104
- exists: declared ? await fileExists(packageFileUrl(target)) : false,
2105
- };
2106
- }));
2107
- const schemas = await schemaFiles();
2108
- const bundleInput = pathFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE);
2109
- const selectedClient = getFlag(flags, ['client']);
2110
- const doctorOptions = selectedClient && selectedClient !== true
2111
- ? { ...connectorOptions(flags), clientId: String(selectedClient), redactPaths: true }
2112
- : { ...connectorOptions(flags), clientId: undefined, redactPaths: true };
2113
- const connectorDoctor = await doctorConnectors(doctorOptions);
2114
- const profile = getClientProfile(String(selectedClient && selectedClient !== true ? selectedClient : 'generic-mcp'), connectorOptions(flags));
2115
- const checks = {
2116
- node: {
2117
- ok: requiredNodeMajor === 0 || currentNodeMajor >= requiredNodeMajor,
2118
- current: process.versions.node,
2119
- required: packageJson.engines?.node ?? null,
2120
- },
2121
- npm: npmUserAgentCheck(),
2122
- package_bins: {
2123
- ok: binEntries.every((entry) => entry.declared && entry.exists),
2124
- required: REQUIRED_PACKAGE_BINS,
2125
- entries: binEntries,
2126
- missing: binEntries.filter((entry) => !entry.declared).map((entry) => entry.name),
2127
- missing_targets: binEntries.filter((entry) => entry.declared && !entry.exists).map((entry) => entry.name),
2128
- },
2129
- bundle_default_path: {
2130
- ok: DEFAULT_BUNDLE === '.enigma/bundle.json',
2131
- path: DEFAULT_BUNDLE,
2132
- resolved: publicPathDisplay(resolve(bundleInput), 'bundle-path'),
2133
- },
2134
- vault_path: await writableVaultPathCheck(resolve(bundleInput), publicPathDisplay(bundleInput, 'bundle-path')),
2135
- schemas: {
2136
- ok: schemas.length > 0,
2137
- count: schemas.length,
2138
- files: schemas,
2139
- },
2140
- mcp_command_name: {
2141
- ok: profile.command === 'enigma-mcp',
2142
- command: profile.command,
2143
- expected: 'enigma-mcp',
2144
- },
2145
- connectors: connectorDoctor,
2146
- };
2147
- const ok = Object.values(checks).every((check) => check.ok !== false);
2417
+ export async function meshAnnounceCommand(flags, io) {
2418
+ const node = new MeshGossipNode({
2419
+ scope: String(getFlag(flags, ['scope'], 'enigma.global')),
2420
+ });
2421
+ const packet = await node.announce({
2422
+ agentName: String(getFlag(flags, ['name'], 'enigma-cli-agent')),
2423
+ });
2148
2424
  print({
2149
- ok,
2150
- node: checks.node,
2151
- package_bins: checks.package_bins,
2152
- npm: checks.npm,
2153
- vault_path: checks.vault_path,
2154
- bundle_default_path: checks.bundle_default_path,
2155
- schema_count: checks.schemas.count,
2156
- schemas: checks.schemas,
2157
- mcp_command_name: checks.mcp_command_name.command,
2158
- connectors: checks.connectors,
2159
- next_commands: doctorNextCommands(checks.vault_path.path, String(selectedClient && selectedClient !== true ? selectedClient : 'generic-mcp')),
2160
- checks,
2425
+ ok: true,
2426
+ action: 'ANNOUNCE_BROADCAST',
2427
+ destinationHex: node.identity.destinationHex,
2428
+ publicKeyHex: node.identity.publicKey.toString('hex'),
2429
+ packetSize: packet.length,
2161
2430
  }, io);
2162
- return ok ? 0 : 1;
2431
+ return 0;
2163
2432
  }
2164
2433
 
2165
- export async function installCommand(flags, io) {
2166
- const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
2167
- const bundleState = await ensureBundle(bundlePath, flags);
2434
+ export async function meshSendCommand(flags, io) {
2435
+ const to = requireFlag(flags, ['to', 'destination']);
2436
+ const recipientPub = requireFlag(flags, ['recipient-pub', 'recipientPub']);
2437
+ const memory = requireFlag(flags, ['memory', 'content', 'message']);
2438
+ const node = new MeshGossipNode({
2439
+ scope: String(getFlag(flags, ['scope'], 'enigma.global')),
2440
+ });
2441
+ const result = await node.sendEncryptedCapsule(
2442
+ to,
2443
+ Buffer.from(recipientPub, 'hex'),
2444
+ memory
2445
+ );
2446
+ print({ ok: true, ...result }, io);
2447
+ return 0;
2448
+ }
2449
+
2450
+ export async function meshListenCommand(flags, io) {
2451
+ const port = Number(getFlag(flags, ['port'], 9190));
2452
+ const node = new MeshGossipNode({
2453
+ scope: String(getFlag(flags, ['scope'], 'enigma.global')),
2454
+ transports: [new WebSocketTransport({ port })],
2455
+ });
2456
+ await node.start();
2457
+ print({
2458
+ ok: true,
2459
+ status: 'LISTENING',
2460
+ nodeId: node.nodeId,
2461
+ destinationHex: node.identity.destinationHex,
2462
+ port,
2463
+ }, io);
2464
+ return 0;
2465
+ }
2466
+
2467
+ export async function marketStatusCommand(flags, io) {
2468
+ const bundlePath = getFlag(flags, ['bundle', 'b'], DEFAULT_BUNDLE);
2469
+ const statePath = join(dirname(resolve(bundlePath)), 'zk-tree-state.json');
2470
+ let localStateAvailable = false;
2471
+ try {
2472
+ await access(statePath, fsConstants.F_OK);
2473
+ localStateAvailable = true;
2474
+ } catch {}
2475
+
2476
+ print({
2477
+ ok: true,
2478
+ market: {
2479
+ mode: 'local_standalone',
2480
+ protocol: 'Sealed Sale (ZKCP Fair Exchange)',
2481
+ circuit: 'sealed_sale_release (87,422 R1CS constraints)',
2482
+ onChainProgram: 'E18KZR1Jq8fG3FFACpoHsE21ME3t1ftKPEwXL7cFWkoZ',
2483
+ settlement: 'Commitments-Only Settle -> Envelope Reveal Claim',
2484
+ localStatePath: statePath,
2485
+ localStateAvailable,
2486
+ rpcChecked: false,
2487
+ transactionSubmitted: false,
2488
+ honestyNote: 'Localnet/Devnet demo ceremony; nullifiers prove protocol-level non-use, not physical deletion.',
2489
+ },
2490
+ }, io);
2491
+ return 0;
2492
+ }
2493
+
2494
+ export async function marketListCommand(flags, io) {
2495
+ const bundlePath = getFlag(flags, ['bundle', 'b'], DEFAULT_BUNDLE);
2496
+ const statePath = join(dirname(resolve(bundlePath)), 'zk-tree-state.json');
2497
+
2498
+ let state;
2499
+ try {
2500
+ const { readZkState } = await import('../../desktop/src/zk-state.mjs');
2501
+ state = readZkState(statePath);
2502
+ } catch (err) {
2503
+ if (err.code === 'ENOENT') {
2504
+ print({
2505
+ ok: true,
2506
+ statePath,
2507
+ count: 0,
2508
+ listings: [],
2509
+ note: `No local ZK tree state file found at ${statePath}. Ingest a memory with --saleable to create a listing.`,
2510
+ }, io);
2511
+ return 0;
2512
+ }
2513
+ throw err;
2514
+ }
2515
+
2516
+ if (!state) {
2517
+ print({
2518
+ ok: true,
2519
+ statePath,
2520
+ count: 0,
2521
+ listings: [],
2522
+ note: `No local ZK tree state file found at ${statePath}. Ingest a memory with --saleable to create a listing.`,
2523
+ }, io);
2524
+ return 0;
2525
+ }
2526
+
2527
+ const rawNotes = Array.isArray(state.notes) ? state.notes : [];
2528
+ const saleableNotes = rawNotes.filter((n) => n && n.saleable === true);
2529
+
2530
+ const listings = saleableNotes.map((note) => {
2531
+ const noteIdx = Number(note.index);
2532
+ const rawLeaf = (Array.isArray(state.noteLeaves) && noteIdx >= 0 && noteIdx < state.noteLeaves.length)
2533
+ ? state.noteLeaves[noteIdx]
2534
+ : undefined;
2535
+
2536
+ let leafCommitmentHex = null;
2537
+ let leafCommitmentDecimal = null;
2538
+ if (rawLeaf !== undefined) {
2539
+ try {
2540
+ const leafBigInt = BigInt(rawLeaf);
2541
+ leafCommitmentDecimal = leafBigInt.toString(10);
2542
+ leafCommitmentHex = '0x' + leafBigInt.toString(16).padStart(64, '0');
2543
+ } catch {}
2544
+ }
2545
+
2546
+ return {
2547
+ index: note.index,
2548
+ scope: note.scope,
2549
+ saleable: true,
2550
+ leafCommitmentDecimal,
2551
+ leafCommitmentHex,
2552
+ appendSig: note.appendSig || null,
2553
+ appendedAt: note.appendedAt || null,
2554
+ };
2555
+ });
2556
+
2557
+ print({
2558
+ ok: true,
2559
+ statePath,
2560
+ count: listings.length,
2561
+ listings,
2562
+ honestyNote: 'Local ZK tree state saleable notes only; secret openings remain in host custody.',
2563
+ }, io);
2564
+ return 0;
2565
+ }
2566
+
2567
+ const ZKCP_FIELD_R = BigInt('0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001');
2568
+ const ZKCP_HEX32_RE = /^0x[0-9a-fA-F]{64}$/;
2569
+
2570
+ function parseZkcpHex32(val, fieldName) {
2571
+ if (typeof val !== 'string' || !ZKCP_HEX32_RE.test(val)) {
2572
+ throw new Error(`Field ${fieldName} must be a 32-byte hex string (0x followed by 64 hex characters); got ${val}`);
2573
+ }
2574
+ return val.toLowerCase();
2575
+ }
2576
+
2577
+ function parseZkcpLimb(val, index) {
2578
+ if (typeof val !== 'string' || !/^(0|[1-9][0-9]*)$/.test(val)) {
2579
+ throw new Error(`Chunk [${index}] must be a canonical decimal string; got ${JSON.stringify(val)}`);
2580
+ }
2581
+ const n = BigInt(val);
2582
+ if (n < 0n || n >= ZKCP_FIELD_R) {
2583
+ throw new Error(`Chunk [${index}] value is not a canonical BN254 scalar field element (must be 0 <= x < r)`);
2584
+ }
2585
+ return n;
2586
+ }
2587
+
2588
+ export async function marketVerifyCommand(flags, io) {
2589
+ const listingPath = getFlag(flags, ['listing', 'file']);
2590
+ if (!listingPath) {
2591
+ throw new Error('Missing required flag: --listing <path_to_listing.json>');
2592
+ }
2593
+
2594
+ let listing;
2595
+ try {
2596
+ const content = await readFile(resolve(listingPath), 'utf8');
2597
+ listing = JSON.parse(content);
2598
+ } catch (err) {
2599
+ throw new Error(`Failed to read listing file at ${listingPath}: ${err.message}`);
2600
+ }
2601
+
2602
+ if (!listing || typeof listing !== 'object') {
2603
+ throw new Error('Listing payload must be a JSON object');
2604
+ }
2605
+
2606
+ const termsHash = parseZkcpHex32(listing.saleTermsHashHex || listing.saleTermsHash, 'saleTermsHash');
2607
+ const keyCommit = parseZkcpHex32(listing.keyCommitHex || listing.keyCommit, 'keyCommit');
2608
+ const envelopeCommit = parseZkcpHex32(listing.envelopeCommitHex || listing.envelopeCommit, 'envelopeCommit');
2609
+ const expectedCipherCommit = parseZkcpHex32(listing.cipherCommitHex || listing.cipherCommit, 'cipherCommit');
2610
+ const chunks = listing.ciphertextChunks || listing.ciphertext;
2611
+
2612
+ if (!Array.isArray(chunks) || chunks.length !== 32) {
2613
+ throw new Error(`Listing ciphertextChunks must be an array of exactly 32 BN254 limbs; got ${Array.isArray(chunks) ? chunks.length : typeof chunks}`);
2614
+ }
2615
+
2616
+ const parsedLimbs = chunks.map((c, idx) => parseZkcpLimb(c, idx));
2617
+
2618
+ const { poseidon } = await import('../../../cortex-v3/zk/zk-tree.mjs');
2619
+ let acc = 0n;
2620
+ for (const limb of parsedLimbs) {
2621
+ acc = poseidon([acc, limb]);
2622
+ }
2623
+ const computedCipherCommit = '0x' + acc.toString(16).padStart(64, '0');
2624
+
2625
+ if (computedCipherCommit.toLowerCase() !== expectedCipherCommit.toLowerCase()) {
2626
+ print({
2627
+ ok: false,
2628
+ verified: false,
2629
+ error: 'Cipher fold mismatch: computed Poseidon fold of ciphertext chunks does not match listing cipherCommit',
2630
+ computedCipherCommit,
2631
+ expectedCipherCommit,
2632
+ }, io);
2633
+ return 1;
2634
+ }
2635
+
2636
+ print({
2637
+ ok: true,
2638
+ offlineCipherFoldValid: true,
2639
+ formatChecks: {
2640
+ listingFormatValid: true,
2641
+ canonicalCommitments: true,
2642
+ chunkCount: 32,
2643
+ canonicalDecimalLimbs: true,
2644
+ cipherFoldMatch: true,
2645
+ },
2646
+ saleTermsHash: termsHash,
2647
+ keyCommit,
2648
+ envelopeCommit,
2649
+ cipherCommit: computedCipherCommit,
2650
+ honestyNote: 'Offline cryptographic cipher fold verification against listing commitments. Does not attest to on-chain receipt existence without RPC connection.',
2651
+ }, io);
2652
+ return 0;
2653
+ }
2654
+ export async function marketDemoCommand(flags, io) {
2655
+ print({
2656
+ ok: true,
2657
+ mode: 'demo_fixture',
2658
+ fixture: {
2659
+ saleTermsHash: '0x4a91f48bc29124a919024fba901249b910294bca',
2660
+ priceSol: '0.015 SOL',
2661
+ status: 'DEMO_FIXTURE',
2662
+ },
2663
+ honestyNote: 'Local fixture demonstration only; not an on-chain transaction.',
2664
+ }, io);
2665
+ return 0;
2666
+ }
2667
+ export async function zkStatusCommand(flags, io) {
2668
+ print({
2669
+ ok: true,
2670
+ zkEngine: {
2671
+ status: 'ONLINE',
2672
+ curves: ['BN254 (alt_bn128 BE)', 'BabyJubjub'],
2673
+ prover: 'Groth16 SnarkJS WASM (Client-Side)',
2674
+ verifierProgram: 'E18KZR1Jq8fG3FFACpoHsE21ME3t1ftKPEwXL7cFWkoZ',
2675
+ trees: ['Note Sparse Merkle Tree (SMT)', 'Nullifier Indexed Merkle Tree (IMT)'],
2676
+ honesty: 'Localnet/Devnet demo ceremony; nullifiers prove non-use, not deletion.',
2677
+ },
2678
+ }, io);
2679
+ return 0;
2680
+ }
2681
+ export async function enterpriseDemoCommand(_flags, io) {
2682
+ print(runEnterpriseDemo(), io);
2683
+ return 0;
2684
+ }
2685
+
2686
+ export async function doctorCommand(flags, io) {
2687
+ const packageJson = await readPackageJson();
2688
+ const requiredNodeMajor = minimumNodeMajor(packageJson.engines?.node);
2689
+ const currentNodeMajor = nodeMajor(process.versions.node);
2690
+ const binMap = packageJson.bin && typeof packageJson.bin === 'object' && !Array.isArray(packageJson.bin) ? packageJson.bin : {};
2691
+ const binEntries = await Promise.all(REQUIRED_PACKAGE_BINS.map(async (name) => {
2692
+ const target = binMap[name];
2693
+ const declared = typeof target === 'string' && target.length > 0;
2694
+ return {
2695
+ name,
2696
+ target: declared ? target : null,
2697
+ declared,
2698
+ exists: declared ? await fileExists(packageFileUrl(target)) : false,
2699
+ };
2700
+ }));
2701
+ const schemas = await schemaFiles();
2702
+ const bundleInput = pathFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE);
2703
+ const resolvedBundlePath = resolve(bundleInput);
2704
+ const publicBundlePath = publicPathDisplay(bundleInput, 'bundle-path');
2705
+ const vaultPath = await writableVaultPathCheck(resolvedBundlePath, publicBundlePath);
2706
+ const selectedClient = getFlag(flags, ['client']);
2707
+ const doctorOptions = selectedClient && selectedClient !== true
2708
+ ? { ...connectorOptions(flags), clientId: String(selectedClient), redactPaths: true }
2709
+ : { ...connectorOptions(flags), clientId: undefined, redactPaths: true };
2710
+ const connectorDoctor = await doctorConnectors(doctorOptions);
2711
+ const profile = getClientProfile(String(selectedClient && selectedClient !== true ? selectedClient : 'generic-mcp'), connectorOptions(flags));
2712
+ const checks = {
2713
+ node: {
2714
+ ok: requiredNodeMajor === 0 || currentNodeMajor >= requiredNodeMajor,
2715
+ current: process.versions.node,
2716
+ required: packageJson.engines?.node ?? null,
2717
+ },
2718
+ npm: npmUserAgentCheck(),
2719
+ package_bins: {
2720
+ ok: binEntries.every((entry) => entry.declared && entry.exists),
2721
+ required: REQUIRED_PACKAGE_BINS,
2722
+ entries: binEntries,
2723
+ missing: binEntries.filter((entry) => !entry.declared).map((entry) => entry.name),
2724
+ missing_targets: binEntries.filter((entry) => entry.declared && !entry.exists).map((entry) => entry.name),
2725
+ },
2726
+ bundle_default_path: {
2727
+ ok: DEFAULT_BUNDLE === '.enigma/bundle.json',
2728
+ path: DEFAULT_BUNDLE,
2729
+ resolved: publicPathDisplay(resolvedBundlePath, 'bundle-path'),
2730
+ },
2731
+ vault_path: vaultPath,
2732
+ bundle_initialized: await bundleInitializedCheck(resolvedBundlePath, vaultPath),
2733
+ schemas: {
2734
+ ok: schemas.length > 0,
2735
+ count: schemas.length,
2736
+ files: schemas,
2737
+ },
2738
+ mcp_command_name: {
2739
+ ok: profile.command === 'enigma-mcp',
2740
+ command: profile.command,
2741
+ expected: 'enigma-mcp',
2742
+ },
2743
+ connectors: connectorDoctor,
2744
+ };
2745
+ const ok = Object.values(checks).every((check) => check.ok !== false);
2746
+ const doctorClient = String(selectedClient && selectedClient !== true ? selectedClient : 'generic-mcp');
2747
+ const firstRunHint = doctorFirstRunHint(checks.vault_path.path, doctorClient);
2748
+ print({
2749
+ ok,
2750
+ node: checks.node,
2751
+ package_bins: checks.package_bins,
2752
+ npm: checks.npm,
2753
+ vault_path: checks.vault_path,
2754
+ bundle_initialized: checks.bundle_initialized,
2755
+ bundle_default_path: checks.bundle_default_path,
2756
+ schema_count: checks.schemas.count,
2757
+ schemas: checks.schemas,
2758
+ mcp_command_name: checks.mcp_command_name.command,
2759
+ connectors: checks.connectors,
2760
+ first_run_hint: firstRunHint,
2761
+ fresh_install_hint: firstRunHint,
2762
+ next_commands: doctorNextCommands(checks.vault_path.path, doctorClient),
2763
+ checks,
2764
+ }, io);
2765
+ return ok ? 0 : 1;
2766
+ }
2767
+
2768
+ export async function installCommand(flags, io) {
2769
+ const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
2770
+ const bundleState = await ensureBundle(bundlePath, flags);
2168
2771
  const selectedClient = getFlag(flags, ['client']);
2169
2772
  const clients = selectedClient && selectedClient !== true ? [String(selectedClient)] : supportedClients;
2170
2773
  const snippets = {};
@@ -2213,23 +2816,65 @@ export async function importCommand(source, flags, io, positionalFile = undefine
2213
2816
  const now = getFlag(flags, ['now']);
2214
2817
  if (now && now !== true) options.now = String(now);
2215
2818
  const bundlePath = resolve(String(getFlag(flags, ['bundle'], DEFAULT_BUNDLE)));
2216
- let vault;
2217
- if (getFlag(flags, ['write-vault'], false) === true) {
2218
- await ensureBundle(bundlePath, flags);
2219
- ({ vault } = await loadState(bundlePath, { passphrase: getFlag(flags, ['passphrase']) }));
2220
- options.vault = vault;
2819
+ const accepted = (result) => result.memory_candidates?.length > 0 || result.ok === true;
2820
+ const preflightReport = importer(input, options);
2821
+ let report = preflightReport;
2822
+ let vaultWritten = false;
2823
+
2824
+ if (getFlag(flags, ['write-vault'], false) === true && accepted(preflightReport)) {
2825
+ const credentials = await resolveVaultCredentials(flags);
2826
+ report = await withBundleFileLock(bundlePath, async () => {
2827
+ let vault;
2828
+ let revision = null;
2829
+ if (await fileExists(bundlePath)) {
2830
+ const state = await loadState(bundlePath, credentials);
2831
+ vault = state.vault;
2832
+ revision = state.revision;
2833
+ } else {
2834
+ vault = createVault({
2835
+ subjectId: String(getFlag(flags, ['subject', 'subject-id'], 'local-user')),
2836
+ displayName: String(getFlag(flags, ['display-name', 'name'], 'Local user')),
2837
+ passphrase: credentials.passphrase,
2838
+ });
2839
+ }
2840
+
2841
+ const imported = importer(input, { ...options, vault });
2842
+ if (!accepted(imported)) return imported;
2843
+ await persistState(bundlePath, vault, {
2844
+ ...credentials,
2845
+ lockHeld: true,
2846
+ expectedRevision: revision,
2847
+ });
2848
+ vaultWritten = true;
2849
+ return imported;
2850
+ });
2221
2851
  }
2222
- const report = importer(input, options);
2223
- if (vault) await persistState(bundlePath, vault, { passphrase: getFlag(flags, ['passphrase']) });
2852
+
2224
2853
  const out = getFlag(flags, ['out']);
2225
- if (out && out !== true) await writeJson(resolve(String(out)), report);
2226
- print({ ...report, source_file: file, bundle: vault ? bundlePath : undefined, out: out && out !== true ? resolve(String(out)) : undefined }, io);
2227
- return report.memory_candidates?.length > 0 || report.ok === true ? 0 : 1;
2854
+ let persistedReport;
2855
+ if (out && out !== true) {
2856
+ const passphrase = await resolvePassphrase(flags);
2857
+ if (!passphrase) throw new Error('Persisted import reports require --passphrase, --passphrase-file, or ENIGMA_PASSPHRASE.');
2858
+ persistedReport = await persistImportReport(report, {
2859
+ path: resolve(String(out)),
2860
+ passphrase,
2861
+ now: options.now,
2862
+ });
2863
+ }
2864
+ print({
2865
+ ...publicCapsuleImportResult(report),
2866
+ source_file: file,
2867
+ bundle: vaultWritten ? bundlePath : undefined,
2868
+ out: persistedReport?.path,
2869
+ }, io);
2870
+ return accepted(report) ? 0 : 1;
2228
2871
  }
2229
2872
 
2230
2873
  export async function capsuleExportCommand(flags, io, positionalFile = undefined) {
2231
2874
  const file = resolve(requireFileArg(flags, ['file', 'report'], positionalFile, 'file'));
2232
- const input = await readJson(file);
2875
+ const passphrase = await resolvePassphrase(flags);
2876
+ if (!passphrase) throw new Error('Capsule export requires --passphrase, --passphrase-file, or ENIGMA_PASSPHRASE.');
2877
+ const input = await loadPersistedImporterArtifact({ path: file, passphrase });
2233
2878
  const now = getFlag(flags, ['now']);
2234
2879
  const capsule = exportEnigmaCapsule({
2235
2880
  reports: Array.isArray(input) ? input : (input.reports ?? input.report ?? input),
@@ -2237,7 +2882,11 @@ export async function capsuleExportCommand(flags, io, positionalFile = undefined
2237
2882
  now: now && now !== true ? String(now) : undefined,
2238
2883
  });
2239
2884
  const out = resolve(String(getFlag(flags, ['out'], 'enigma-capsule.json')));
2240
- await writeJson(out, capsule);
2885
+ await persistEnigmaCapsule(capsule, {
2886
+ path: out,
2887
+ passphrase,
2888
+ now: now && now !== true ? String(now) : undefined,
2889
+ });
2241
2890
  print({
2242
2891
  ok: capsule.schema === 'enigma.import_capsule.v1',
2243
2892
  schema: capsule.schema,
@@ -2251,20 +2900,61 @@ export async function capsuleExportCommand(flags, io, positionalFile = undefined
2251
2900
 
2252
2901
  export async function capsuleImportCommand(flags, io, positionalFile = undefined) {
2253
2902
  const file = resolve(requireFileArg(flags, ['file', 'capsule'], positionalFile, 'file'));
2254
- const capsule = await readJson(file);
2903
+ const passphrase = await resolvePassphrase(flags);
2904
+ if (!passphrase) throw new Error('Capsule import requires --passphrase, --passphrase-file, or ENIGMA_PASSPHRASE.');
2905
+ const capsule = await loadPersistedImporterArtifact({ path: file, passphrase });
2255
2906
  const bundlePath = resolve(String(getFlag(flags, ['bundle'], DEFAULT_BUNDLE)));
2256
- let vault;
2257
2907
  const options = {};
2908
+ const trustDescriptorPath = getFlag(flags, ['trust-descriptor', 'trustDescriptor']);
2909
+ const trustBundlePath = getFlag(flags, ['trust-bundle', 'trustBundle']);
2910
+ if (trustDescriptorPath && trustBundlePath) {
2911
+ throw new Error('Capsule import accepts one trust source: --trust-descriptor or --trust-bundle.');
2912
+ }
2913
+ if (!trustDescriptorPath && !trustBundlePath) {
2914
+ throw new Error('Capsule import requires --trust-descriptor or --trust-bundle.');
2915
+ }
2916
+ if (trustDescriptorPath) options.trustDescriptor = await readJson(resolve(String(trustDescriptorPath)));
2917
+ if (trustBundlePath) options.trustBundle = await readJson(resolve(String(trustBundlePath)));
2258
2918
  const now = getFlag(flags, ['now']);
2259
2919
  if (now && now !== true) options.now = String(now);
2260
- if (getFlag(flags, ['write-vault'], false) === true) {
2261
- await ensureBundle(bundlePath, flags);
2262
- ({ vault } = await loadState(bundlePath, { passphrase: getFlag(flags, ['passphrase']) }));
2263
- options.vault = vault;
2264
- }
2265
- const result = importEnigmaCapsule(capsule, options);
2266
- if (vault) await persistState(bundlePath, vault, { passphrase: getFlag(flags, ['passphrase']) });
2267
- print({ ...publicCapsuleImportResult(result), source_file: file, bundle: vault ? bundlePath : undefined }, io);
2920
+
2921
+ const verifiedResult = importEnigmaCapsule(capsule, options);
2922
+ const writeVault = getFlag(flags, ['write-vault'], false) === true;
2923
+ if (!writeVault || !verifiedResult.ok) {
2924
+ print({ ...publicCapsuleImportResult(verifiedResult), source_file: file }, io);
2925
+ return verifiedResult.ok ? 0 : 1;
2926
+ }
2927
+
2928
+ const credentials = await resolveVaultCredentials(flags);
2929
+ const result = await withBundleFileLock(bundlePath, async () => {
2930
+ let vault;
2931
+ let revision = null;
2932
+ if (await fileExists(bundlePath)) {
2933
+ const state = await loadState(bundlePath, credentials);
2934
+ vault = state.vault;
2935
+ revision = state.revision;
2936
+ } else {
2937
+ vault = createVault({
2938
+ subjectId: String(getFlag(flags, ['subject', 'subject-id'], 'local-user')),
2939
+ displayName: String(getFlag(flags, ['display-name', 'name'], 'Local user')),
2940
+ passphrase: credentials.passphrase,
2941
+ });
2942
+ }
2943
+
2944
+ const imported = importEnigmaCapsule(capsule, { ...options, vault });
2945
+ if (!imported.ok) return imported;
2946
+ await persistState(bundlePath, vault, {
2947
+ ...credentials,
2948
+ lockHeld: true,
2949
+ expectedRevision: revision,
2950
+ });
2951
+ return imported;
2952
+ });
2953
+ print({
2954
+ ...publicCapsuleImportResult(result),
2955
+ source_file: file,
2956
+ bundle: result.ok ? bundlePath : undefined,
2957
+ }, io);
2268
2958
  return result.ok ? 0 : 1;
2269
2959
  }
2270
2960
 
@@ -2467,15 +3157,179 @@ function chainArtifactValidator(artifact) {
2467
3157
 
2468
3158
  const SOLANA_SUBMIT_CLUSTERS = new Set(['devnet', 'testnet', 'mainnet-beta', 'localnet']);
2469
3159
  const SOLANA_MEMO_PROGRAM_ID = 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr';
3160
+ const SOLANA_RPC_CLUSTERS = Object.freeze({
3161
+ devnet: 'https://api.devnet.solana.com',
3162
+ testnet: 'https://api.testnet.solana.com',
3163
+ 'mainnet-beta': 'https://api.mainnet-beta.solana.com',
3164
+ localnet: 'http://127.0.0.1:8899',
3165
+ });
3166
+ const SOLANA_PUBLIC_CLUSTER_GENESIS_HASHES = Object.freeze({
3167
+ devnet: 'EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG',
3168
+ testnet: '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY',
3169
+ 'mainnet-beta': '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d',
3170
+ });
3171
+
3172
+ const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
3173
+ const BASE58_MAP = new Map();
3174
+ for (let i = 0; i < BASE58_ALPHABET.length; i += 1) BASE58_MAP.set(BASE58_ALPHABET[i], BigInt(i));
3175
+
3176
+ function decodeBase58Utf8(str) {
3177
+ if (typeof str !== 'string' || str.length === 0 || str.length > 100000) return null;
3178
+ let num = 0n;
3179
+ for (let i = 0; i < str.length; i += 1) {
3180
+ const val = BASE58_MAP.get(str[i]);
3181
+ if (val === undefined) return null;
3182
+ num = num * 58n + val;
3183
+ }
3184
+ const bytes = [];
3185
+ while (num > 0n) {
3186
+ bytes.push(Number(num & 0xffn));
3187
+ num >>= 8n;
3188
+ }
3189
+ for (let i = 0; i < str.length && str[i] === '1'; i += 1) {
3190
+ bytes.push(0);
3191
+ }
3192
+ bytes.reverse();
3193
+ return Buffer.from(bytes).toString('utf8');
3194
+ }
3195
+
3196
+ function decodeBase58Buffer(str) {
3197
+ if (typeof str !== 'string' || str.length === 0 || str.length > 100000) return null;
3198
+ let num = 0n;
3199
+ for (let i = 0; i < str.length; i += 1) {
3200
+ const val = BASE58_MAP.get(str[i]);
3201
+ if (val === undefined) return null;
3202
+ num = num * 58n + val;
3203
+ }
3204
+ const bytes = [];
3205
+ while (num > 0n) {
3206
+ bytes.push(Number(num & 0xffn));
3207
+ num >>= 8n;
3208
+ }
3209
+ for (let i = 0; i < str.length && str[i] === '1'; i += 1) {
3210
+ bytes.push(0);
3211
+ }
3212
+ bytes.reverse();
3213
+ const result = Buffer.from(bytes);
3214
+ if (result.length < 32) {
3215
+ return Buffer.concat([Buffer.alloc(32 - result.length), result]);
3216
+ }
3217
+ return result.length === 32 ? result : result.subarray(result.length - 32);
3218
+ }
3219
+
3220
+ function encodeBase58Buffer(buffer) {
3221
+ if (!Buffer.isBuffer(buffer) || buffer.length === 0) return '';
3222
+ let leadingZeros = 0;
3223
+ for (let i = 0; i < buffer.length && buffer[i] === 0; i += 1) {
3224
+ leadingZeros += 1;
3225
+ }
3226
+ let num = BigInt('0x' + (buffer.toString('hex') || '0'));
3227
+ let encoded = '';
3228
+ while (num > 0n) {
3229
+ const rem = Number(num % 58n);
3230
+ num = num / 58n;
3231
+ encoded = BASE58_ALPHABET[rem] + encoded;
3232
+ }
3233
+ return '1'.repeat(leadingZeros) + encoded;
3234
+ }
3235
+ function parseStrictSolanaMemoRef(raw) {
3236
+ if (typeof raw !== 'string' || raw.length === 0 || raw.length > 16384) return null;
3237
+ let parsed;
3238
+ try {
3239
+ parsed = JSON.parse(raw);
3240
+ } catch {
3241
+ return null;
3242
+ }
3243
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return null;
3244
+ if (parsed.memo_ref && typeof parsed.memo_ref === 'object' && !Array.isArray(parsed.memo_ref)) parsed = parsed.memo_ref;
3245
+ const keys = Object.keys(parsed).sort();
3246
+ const expectedKeys = ['artifact_hash', 'artifact_type', 'cluster', 'proof_commitment', 'protocol', 'rail', 'v'];
3247
+ if (keys.length !== expectedKeys.length) return null;
3248
+ for (let i = 0; i < expectedKeys.length; i += 1) {
3249
+ if (keys[i] !== expectedKeys[i]) return null;
3250
+ }
3251
+ if (parsed.v !== 1) return null;
3252
+ if (parsed.protocol !== 'enigma-proof-network') return null;
3253
+ if (parsed.rail !== 'solana-memo') return null;
3254
+ if (typeof parsed.cluster !== 'string' || !SOLANA_SUBMIT_CLUSTERS.has(parsed.cluster)) return null;
3255
+ if (typeof parsed.artifact_type !== 'string' || !parsed.artifact_type.startsWith('enigma.proof_network.')) return null;
3256
+ if (typeof parsed.artifact_hash !== 'string' || !/^sha256:[a-f0-9]{64}$/u.test(parsed.artifact_hash)) return null;
3257
+ if (typeof parsed.proof_commitment !== 'string' || !/^sha256:[a-f0-9]{64}$/u.test(parsed.proof_commitment)) return null;
3258
+ return parsed;
3259
+ }
3260
+
3261
+ function isVaultBundle(value) {
3262
+ return value && typeof value === 'object' && value.schema === 'enigma.vault_bundle.v1';
3263
+ }
3264
+
3265
+ export function anchorBatchFromVaultBundle(bundle, cluster, flags) {
3266
+ const verifyReport = verifyBundle(bundle);
3267
+ if (verifyReport.ok !== true) throw new Error('Local vault bundle verification failed; refusing to derive a Solana anchor.');
3268
+
3269
+ const vault = bundle.vault ?? {};
3270
+ const roots = [vault.active_set_root, vault.receipt_log_root]
3271
+ .filter((root) => typeof root === 'string' && root.startsWith('sha256:'));
3272
+ if (roots.length === 0) throw new Error('Local vault bundle has no anchorable commitment roots.');
3273
+
3274
+ const bundleRef = vault.vault_id ? `enigma://vault/${vault.vault_id}` : 'enigma://vault/local';
3275
+ const refs = [
3276
+ `${bundleRef}/active_set_root`,
3277
+ `${bundleRef}/receipt_log_root`,
3278
+ ...flagValues(flags, ['ref', 'refs', 'public-ref', 'publicRef']),
3279
+ ];
3280
+ const input = {
3281
+ roots,
3282
+ root_count: roots.length,
3283
+ commitment_count: roots.length,
3284
+ refs,
3285
+ public_chain_ref: getFlag(flags, ['public-chain-ref', 'publicChainRef', 'chain-ref', 'chainRef'], `solana:${cluster}:spl-memo`),
3286
+ authority_ref: getFlag(flags, ['authority', 'authority-ref', 'authorityRef'], 'authority:local-enigma-cli'),
3287
+ batch_ref: getFlag(flags, ['batch-ref', 'batchRef'], `${bundleRef}/anchor-batch/${cluster}`),
3288
+ cluster_ref: solanaClusterRef(cluster),
3289
+ transaction_submitted: false,
3290
+ raw_memory_on_chain: false,
3291
+ };
3292
+ const createdAt = getFlag(flags, ['created-at', 'createdAt']) ?? bundle.exported_at;
3293
+ if (createdAt !== undefined) input.created_at = createdAt;
3294
+ const batch = createProofNetworkAnchorBatch(input);
3295
+ const validation = chainValidationResult(validateProofNetworkAnchorBatch, batch);
3296
+ if (validation.ok !== true) throw new Error('Derived local Solana anchor batch failed proof-network validation.');
3297
+ return { artifact: batch, schema: batch.schema, validation, source_schema: bundle.schema };
3298
+ }
3299
+
3300
+ export function normalizeSolanaLocalArtifact(source, cluster, flags) {
3301
+ const candidate = source?.bundle && isVaultBundle(source.bundle) ? source.bundle : source;
3302
+ if (isVaultBundle(candidate)) return anchorBatchFromVaultBundle(candidate, cluster, flags);
3303
+
3304
+ assertNoPrivateProofPayload(candidate);
3305
+ const [schema, validate] = chainArtifactValidator(candidate);
3306
+ const validation = chainValidationResult(validate, candidate);
3307
+ if (validation.ok !== true) throw new Error(validation.errors?.join('; ') || 'Invalid proof-network artifact.');
3308
+ assertArtifactClusterMatchesSubmit(candidate, cluster);
3309
+ return { artifact: candidate, schema, validation, source_schema: schema };
3310
+ }
2470
3311
 
2471
3312
  function solanaSubmitCluster(flags) {
2472
- const cluster = String(requireFlag(flags, ['cluster'], 'cluster'));
3313
+ let cluster = String(requireFlag(flags, ['cluster'], 'cluster'));
3314
+ if (cluster.startsWith('solana:')) cluster = cluster.slice(7);
2473
3315
  if (!SOLANA_SUBMIT_CLUSTERS.has(cluster)) {
2474
3316
  throw new Error('--cluster must be one of devnet, testnet, mainnet-beta, or localnet.');
2475
3317
  }
2476
3318
  return cluster;
2477
3319
  }
2478
3320
 
3321
+ function solanaClusterRef(cluster) {
3322
+ return `solana:${cluster}`;
3323
+ }
3324
+
3325
+ function assertArtifactClusterMatchesSubmit(artifact, cluster) {
3326
+ const expected = solanaClusterRef(cluster);
3327
+ const actual = artifact?.cluster_ref ?? artifact?.clusterRef;
3328
+ if (actual !== undefined && actual !== expected) {
3329
+ throw new Error(`Proof artifact cluster_ref ${actual} does not match requested Solana cluster ${expected}.`);
3330
+ }
3331
+ }
3332
+
2479
3333
  function createSolanaProofMemoRef(schema, artifact, cluster) {
2480
3334
  const artifactHash = proofNetworkSha256Json(artifact);
2481
3335
  const proofCommitment = proofNetworkSha256Json({
@@ -2535,6 +3389,123 @@ function solanaSubmitRpcUrl(flags, cluster, clusterApiUrl) {
2535
3389
  return clusterApiUrl(cluster);
2536
3390
  }
2537
3391
 
3392
+ function solanaLocalnetRpcPolicy(rpcUrl, mode = 'execute') {
3393
+ let parsed;
3394
+ try {
3395
+ parsed = new URL(rpcUrl);
3396
+ } catch {
3397
+ throw new Error('Solana --rpc must be a valid HTTP(S) URL.');
3398
+ }
3399
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
3400
+ throw new Error('Solana --rpc must be a valid HTTP(S) URL.');
3401
+ }
3402
+ const host = parsed.hostname.toLowerCase();
3403
+ const loopback = host === 'localhost' || host === '127.0.0.1' || host === '[::1]' || host === '::1';
3404
+ if (!loopback) {
3405
+ throw new Error(`Solana localnet ${mode} requires a loopback RPC endpoint; use devnet, testnet, or mainnet-beta for public RPC endpoints.`);
3406
+ }
3407
+ return 'localnet-loopback';
3408
+ }
3409
+
3410
+ async function verifySolanaRpcGenesis(connection, cluster, rpcUrl) {
3411
+ const localnetPolicy = cluster === 'localnet' ? solanaLocalnetRpcPolicy(rpcUrl, 'execute mode') : undefined;
3412
+ let genesisHash;
3413
+ try {
3414
+ genesisHash = await connection.getGenesisHash();
3415
+ } catch {
3416
+ throw new Error('Unable to verify Solana RPC genesis hash before submission.');
3417
+ }
3418
+
3419
+ if (cluster === 'localnet') {
3420
+ return {
3421
+ genesis_hash: genesisHash,
3422
+ genesis_policy: localnetPolicy,
3423
+ };
3424
+ }
3425
+
3426
+ const expected = SOLANA_PUBLIC_CLUSTER_GENESIS_HASHES[cluster];
3427
+ if (genesisHash !== expected) {
3428
+ throw new Error(`Solana RPC genesis hash ${genesisHash} does not match requested cluster ${cluster} (${expected}); refusing to submit a mislabeled memo.`);
3429
+ }
3430
+ return {
3431
+ genesis_hash: genesisHash,
3432
+ expected_genesis_hash: expected,
3433
+ genesis_policy: 'public-cluster-genesis',
3434
+ };
3435
+ }
3436
+
3437
+ function solanaVerifyRpcUrl(flags, cluster) {
3438
+ const rpc = getFlag(flags, ['rpc']);
3439
+ const custom = rpc !== undefined && rpc !== true && rpc !== '';
3440
+ const url = custom
3441
+ ? String(lastFlagValue(rpc))
3442
+ : SOLANA_RPC_CLUSTERS[cluster];
3443
+ let parsed;
3444
+ try {
3445
+ parsed = new URL(url);
3446
+ } catch {
3447
+ throw new Error('Solana --rpc must be a valid HTTP(S) URL.');
3448
+ }
3449
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') throw new Error('Solana --rpc must be a valid HTTP(S) URL.');
3450
+ if (cluster === 'localnet') solanaLocalnetRpcPolicy(url, 'verification');
3451
+ return { url, label: custom ? '<custom-rpc>' : `${cluster}:default` };
3452
+ }
3453
+
3454
+ function redactSolanaRpcError(error) {
3455
+ const message = String(error?.message ?? error ?? 'Solana RPC request failed')
3456
+ .replace(/[a-z][a-z0-9+.-]*:\/\/[^\s/@]+:[^\s/@]+@[^\s)]+/giu, '<custom-rpc>')
3457
+ .replace(/https?:\/\/[^\s)]+/giu, '<rpc>')
3458
+ .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/giu, 'Bearer <redacted>')
3459
+ .replace(/Basic\s+[A-Za-z0-9+/=-]+/giu, 'Basic <redacted>')
3460
+ .replace(/(?:api[_-]?key|token|password|secret)=?[^\s,;)]*/giu, '$1=<redacted>')
3461
+ .replace(/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/gu, '<private-key-redacted>');
3462
+ return message.slice(0, 240);
3463
+ }
3464
+
3465
+ async function solanaRpcCall({ url, method, params = [], fetchFn = globalThis.fetch, timeoutMs = 10000 }) {
3466
+ if (typeof fetchFn !== 'function') throw new Error('Solana verification requires a fetch-capable runtime.');
3467
+ let timer;
3468
+ const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
3469
+ try {
3470
+ if (controller) timer = setTimeout(() => controller.abort(), timeoutMs);
3471
+ const response = await fetchFn(url, {
3472
+ method: 'POST',
3473
+ headers: { 'Content-Type': 'application/json' },
3474
+ body: JSON.stringify({ jsonrpc: '2.0', id: `enigma-verify-${method}`, method, params }),
3475
+ signal: controller?.signal,
3476
+ });
3477
+ if (response?.ok === false) throw new Error(`HTTP ${response.status}`);
3478
+ const payload = await response.json();
3479
+ if (payload?.error) throw new Error(payload.error.message || `RPC ${method} failed`);
3480
+ return payload?.result;
3481
+ } catch (error) {
3482
+ throw new Error(redactSolanaRpcError(error));
3483
+ } finally {
3484
+ if (timer) clearTimeout(timer);
3485
+ }
3486
+ }
3487
+
3488
+ function verifySolanaGenesisHash(genesisHash, cluster, rpcUrl) {
3489
+ const localnetPolicy = cluster === 'localnet' ? solanaLocalnetRpcPolicy(rpcUrl, 'verification') : undefined;
3490
+ if (cluster === 'localnet') return { genesis_hash: genesisHash ?? null, genesis_policy: localnetPolicy };
3491
+ const expected = SOLANA_PUBLIC_CLUSTER_GENESIS_HASHES[cluster];
3492
+ if (genesisHash !== expected) {
3493
+ return {
3494
+ verified: false,
3495
+ genesis_hash: genesisHash ?? null,
3496
+ expected_genesis_hash: expected,
3497
+ genesis_policy: 'public-cluster-genesis',
3498
+ failure_code: 'RPC_GENESIS_MISMATCH',
3499
+ };
3500
+ }
3501
+ return {
3502
+ verified: true,
3503
+ genesis_hash: genesisHash,
3504
+ expected_genesis_hash: expected,
3505
+ genesis_policy: 'public-cluster-genesis',
3506
+ };
3507
+ }
3508
+
2538
3509
  async function submitSolanaMemoTransaction(flags, cluster, memoRef) {
2539
3510
  const keypairPath = resolve(String(requireFlag(flags, ['keypair'], 'keypair')));
2540
3511
  const secretKey = await readSolanaKeypair(keypairPath);
@@ -2545,7 +3516,9 @@ async function submitSolanaMemoTransaction(flags, cluster, memoRef) {
2545
3516
  } catch {
2546
3517
  throw new Error('Solana --keypair could not be loaded as a signer.');
2547
3518
  }
2548
- const connection = new Connection(solanaSubmitRpcUrl(flags, cluster, clusterApiUrl), 'confirmed');
3519
+ const rpcUrl = solanaSubmitRpcUrl(flags, cluster, clusterApiUrl);
3520
+ const connection = new Connection(rpcUrl, 'confirmed');
3521
+ const genesis = await verifySolanaRpcGenesis(connection, cluster, rpcUrl);
2549
3522
  const memoBytes = Buffer.from(JSON.stringify(memoRef), 'utf8');
2550
3523
  const transaction = new Transaction().add(new TransactionInstruction({
2551
3524
  keys: [],
@@ -2553,7 +3526,8 @@ async function submitSolanaMemoTransaction(flags, cluster, memoRef) {
2553
3526
  data: memoBytes,
2554
3527
  }));
2555
3528
  try {
2556
- return await sendAndConfirmTransaction(connection, transaction, [payer], { commitment: 'confirmed' });
3529
+ const signature = await sendAndConfirmTransaction(connection, transaction, [payer], { commitment: 'confirmed' });
3530
+ return { signature, ...genesis };
2557
3531
  } catch {
2558
3532
  throw new Error('Solana submission failed before a public-safe transaction signature was returned.');
2559
3533
  }
@@ -2562,16 +3536,14 @@ async function submitSolanaMemoTransaction(flags, cluster, memoRef) {
2562
3536
  export async function chainSubmitSolanaCommand(flags, io, positionalFile = undefined) {
2563
3537
  const inPath = resolve(String(requireFileArg(flags, ['file', 'in'], positionalFile, 'file')));
2564
3538
  const cluster = solanaSubmitCluster(flags);
2565
- let artifact;
3539
+ let source;
2566
3540
  try {
2567
- artifact = await readJson(inPath);
3541
+ source = await readJson(inPath);
2568
3542
  } catch {
2569
3543
  throw new Error('Unable to read a valid proof artifact JSON file.');
2570
3544
  }
2571
- assertNoPrivateProofPayload(artifact);
2572
- const [schema, validate] = chainArtifactValidator(artifact);
2573
- const result = chainValidationResult(validate, artifact);
2574
- if (result.ok !== true) throw new Error(result.errors?.join('; ') || 'Invalid proof-network artifact.');
3545
+ const normalized = normalizeSolanaLocalArtifact(source, cluster, flags);
3546
+ const { artifact, schema, validation: result, source_schema } = normalized;
2575
3547
  const memoRef = createSolanaProofMemoRef(schema, artifact, cluster);
2576
3548
  const execute = booleanFlag(flags, ['execute'], false);
2577
3549
  if (!execute) {
@@ -2585,6 +3557,7 @@ export async function chainSubmitSolanaCommand(flags, io, positionalFile = undef
2585
3557
  transaction_submitted: false,
2586
3558
  raw_memory_on_chain: false,
2587
3559
  artifact_type: schema,
3560
+ source_schema,
2588
3561
  artifact_hash: memoRef.artifact_hash,
2589
3562
  proof_commitment: memoRef.proof_commitment,
2590
3563
  memo_program: SOLANA_MEMO_PROGRAM_ID,
@@ -2599,7 +3572,7 @@ export async function chainSubmitSolanaCommand(flags, io, positionalFile = undef
2599
3572
  }, io);
2600
3573
  return 0;
2601
3574
  }
2602
- const signature = await submitSolanaMemoTransaction(flags, cluster, memoRef);
3575
+ const submission = await submitSolanaMemoTransaction(flags, cluster, memoRef);
2603
3576
  print({
2604
3577
  ok: true,
2605
3578
  command: 'chain submit-solana',
@@ -2609,8 +3582,11 @@ export async function chainSubmitSolanaCommand(flags, io, positionalFile = undef
2609
3582
  rpc_endpoint: solanaSubmitRpcLabel(flags, cluster),
2610
3583
  transaction_submitted: true,
2611
3584
  raw_memory_on_chain: false,
2612
- signature,
3585
+ signature: submission.signature,
3586
+ rpc_genesis_hash: submission.genesis_hash,
3587
+ rpc_genesis_policy: submission.genesis_policy,
2613
3588
  artifact_type: schema,
3589
+ source_schema,
2614
3590
  artifact_hash: memoRef.artifact_hash,
2615
3591
  proof_commitment: memoRef.proof_commitment,
2616
3592
  memo_program: SOLANA_MEMO_PROGRAM_ID,
@@ -2620,11 +3596,398 @@ export async function chainSubmitSolanaCommand(flags, io, positionalFile = undef
2620
3596
  return 0;
2621
3597
  }
2622
3598
 
3599
+ function solanaInstructionProgramId(instruction, message) {
3600
+ if (typeof instruction?.programId === 'string') return instruction.programId;
3601
+ if (instruction?.programId && typeof instruction.programId.toString === 'function') return instruction.programId.toString();
3602
+ if (instruction?.programIdIndex !== undefined) {
3603
+ const key = message?.accountKeys?.[instruction.programIdIndex];
3604
+ return typeof key === 'string' ? key : key?.pubkey?.toString?.() ?? null;
3605
+ }
3606
+ return null;
3607
+ }
3608
+
3609
+ function memoTextFromInstruction(instruction) {
3610
+ if (typeof instruction?.parsed === 'string') return instruction.parsed;
3611
+ if (instruction?.parsed && typeof instruction.parsed === 'object') {
3612
+ const info = instruction.parsed.info ?? instruction.parsed;
3613
+ if (typeof info.memo === 'string') return info.memo;
3614
+ if (typeof info.data === 'string') return info.data;
3615
+ }
3616
+ const data = instruction?.data;
3617
+ if (Array.isArray(data) && typeof data[0] === 'string') {
3618
+ if (data[1] === 'base64') {
3619
+ try { return Buffer.from(data[0], 'base64').toString('utf8'); } catch { return null; }
3620
+ }
3621
+ return decodeBase58Utf8(data[0]);
3622
+ }
3623
+ if (typeof data === 'string') return data.trim().startsWith('{') ? data : decodeBase58Utf8(data);
3624
+ if (data instanceof Uint8Array || Buffer.isBuffer(data)) return Buffer.from(data).toString('utf8');
3625
+ return null;
3626
+ }
3627
+
3628
+ function extractSolanaMemoRefs(transaction) {
3629
+ const message = transaction?.transaction?.message ?? transaction?.message;
3630
+ const instructions = Array.isArray(message?.instructions) ? message.instructions : [];
3631
+ const protocolRefs = [];
3632
+ let memoInstructionCount = 0;
3633
+ for (const instruction of instructions) {
3634
+ if (solanaInstructionProgramId(instruction, message) !== SOLANA_MEMO_PROGRAM_ID) continue;
3635
+ memoInstructionCount += 1;
3636
+ const candidate = parseStrictSolanaMemoRef(memoTextFromInstruction(instruction));
3637
+ if (candidate) protocolRefs.push(candidate);
3638
+ }
3639
+ return { memo_instruction_count: memoInstructionCount, protocol_refs: protocolRefs };
3640
+ }
3641
+
3642
+ function solanaTransactionSignatures(transaction) {
3643
+ const value = transaction?.transaction?.signatures ?? transaction?.signatures;
3644
+ return Array.isArray(value) ? value.map((item) => String(item)).filter(Boolean) : [];
3645
+ }
3646
+
3647
+ function validateSolanaSignature(value) {
3648
+ const signature = String(value ?? '').trim();
3649
+ if (!/^[1-9A-HJ-NP-Za-km-z]{32,100}$/u.test(signature)) throw new Error('Solana --signature must be a base58 transaction signature.');
3650
+ return signature;
3651
+ }
3652
+
3653
+ function safeObservedMemoRef(ref) {
3654
+ if (!ref) return null;
3655
+ return {
3656
+ v: ref.v,
3657
+ protocol: ref.protocol,
3658
+ rail: ref.rail,
3659
+ cluster: SOLANA_SUBMIT_CLUSTERS.has(ref.cluster) ? ref.cluster : null,
3660
+ artifact_type: typeof ref.artifact_type === 'string' && /^enigma\.[a-z0-9_.-]{2,120}$/u.test(ref.artifact_type) ? ref.artifact_type : null,
3661
+ artifact_hash: /^sha256:[a-f0-9]{64}$/u.test(ref.artifact_hash) ? ref.artifact_hash : null,
3662
+ proof_commitment: /^sha256:[a-f0-9]{64}$/u.test(ref.proof_commitment) ? ref.proof_commitment : null,
3663
+ };
3664
+ }
3665
+
3666
+ function solanaBlockTimeIso(value) {
3667
+ if (!Number.isFinite(value)) return null;
3668
+ const date = new Date(value * 1000);
3669
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
3670
+ }
3671
+
3672
+ function solanaPayer(transaction) {
3673
+ const key = transaction?.transaction?.message?.accountKeys?.[0];
3674
+ const value = typeof key === 'string' ? key : key?.pubkey;
3675
+ const payer = typeof value === 'string' ? value : value?.toString?.() ?? '';
3676
+ return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/u.test(payer) ? payer : null;
3677
+ }
3678
+
3679
+ function solanaVerificationBase({ cluster, rpcEndpoint, signature, normalized, source }) {
3680
+ const memoRef = createSolanaProofMemoRef(normalized.schema, normalized.artifact, cluster);
3681
+ return {
3682
+ ok: false,
3683
+ command: 'chain verify-solana',
3684
+ chain: 'solana',
3685
+ cluster,
3686
+ rpc_endpoint: rpcEndpoint,
3687
+ signature: signature ?? null,
3688
+ transaction_source: source,
3689
+ transaction_observed: false,
3690
+ verified_on_chain: false,
3691
+ verification_status: 'not_verified',
3692
+ slot: null,
3693
+ block_time: null,
3694
+ confirmation_status: null,
3695
+ confirmation_depth: null,
3696
+ slot_depth: null,
3697
+ transaction_success: null,
3698
+ memo_program: SOLANA_MEMO_PROGRAM_ID,
3699
+ artifact_type: memoRef.artifact_type,
3700
+ source_schema: normalized.source_schema,
3701
+ artifact_hash: memoRef.artifact_hash,
3702
+ proof_commitment: memoRef.proof_commitment,
3703
+ raw_memory_on_chain: false,
3704
+ local_validation: normalized.validation,
3705
+ claim_boundary: 'Verification proves only that this Solana transaction contains the selected public commitment. It does not prove factual truth, absence of model hallucinations, provider deletion, or physical deletion by a third party.',
3706
+ };
3707
+ }
3708
+
3709
+ function solanaMemoMatchReport(observed, expected, { uniqueMemo, transactionSuccess }) {
3710
+ return {
3711
+ unique_memo: uniqueMemo,
3712
+ transaction_success: transactionSuccess === true,
3713
+ version: observed?.v === expected.v,
3714
+ protocol: observed?.protocol === expected.protocol,
3715
+ rail: observed?.rail === expected.rail,
3716
+ cluster: observed?.cluster === expected.cluster,
3717
+ artifact_type: observed?.artifact_type === expected.artifact_type,
3718
+ artifact_hash: observed?.artifact_hash === expected.artifact_hash,
3719
+ proof_commitment: observed?.proof_commitment === expected.proof_commitment,
3720
+ };
3721
+ }
3722
+
3723
+ async function solanaConfirmationInfo({ rpcUrl, signature, transactionSlot, fetchFn }) {
3724
+ let status = null;
3725
+ let currentSlot = null;
3726
+ try {
3727
+ const result = await solanaRpcCall({ url: rpcUrl, method: 'getSignatureStatuses', params: [[signature], { searchTransactionHistory: true }], fetchFn });
3728
+ status = Array.isArray(result) ? result[0] : result?.value?.[0] ?? null;
3729
+ } catch {
3730
+ // Confirmation depth is supplementary; a node may omit it while retaining the transaction.
3731
+ }
3732
+ try {
3733
+ currentSlot = await solanaRpcCall({ url: rpcUrl, method: 'getSlot', params: [{ commitment: 'confirmed' }], fetchFn });
3734
+ } catch {
3735
+ // Keep depth unknown rather than turning a valid memo into a false negative.
3736
+ }
3737
+ return {
3738
+ confirmation_status: status?.confirmationStatus ?? null,
3739
+ confirmation_depth: Number.isInteger(status?.confirmations) ? status.confirmations : null,
3740
+ slot_depth: Number.isInteger(currentSlot) && Number.isInteger(transactionSlot) && currentSlot >= transactionSlot ? currentSlot - transactionSlot : null,
3741
+ };
3742
+ }
3743
+
3744
+ async function legacyVerifySolanaCommand(flags, io, positionalFile = undefined) {
3745
+ const inPath = resolve(String(requireFileArg(flags, ['file', 'in', 'artifact'], positionalFile, 'file')));
3746
+ const signature = String(requireFlag(flags, ['signature', 'sig', 'tx', 'txid'], 'signature'));
3747
+ const cluster = solanaSubmitCluster(flags);
3748
+ let artifact;
3749
+ try {
3750
+ artifact = await readJson(inPath);
3751
+ } catch {
3752
+ throw new Error('Unable to read a valid proof artifact JSON file.');
3753
+ }
3754
+ assertNoPrivateProofPayload(artifact);
3755
+ const [schema, validate] = chainArtifactValidator(artifact);
3756
+ const result = chainValidationResult(validate, artifact);
3757
+ if (result.ok !== true) throw new Error(result.errors?.join('; ') || 'Invalid proof-network artifact.');
3758
+ assertArtifactClusterMatchesSubmit(artifact, cluster);
3759
+ const expectedMemoRef = createSolanaProofMemoRef(schema, artifact, cluster);
3760
+
3761
+ const { Connection, clusterApiUrl } = await loadSolanaWeb3();
3762
+ const rpcUrl = solanaSubmitRpcUrl(flags, cluster, clusterApiUrl);
3763
+ const connection = new Connection(rpcUrl, 'confirmed');
3764
+ const genesis = await verifySolanaRpcGenesis(connection, cluster, rpcUrl);
3765
+
3766
+ let tx;
3767
+ try {
3768
+ tx = await connection.getParsedTransaction(signature, {
3769
+ maxSupportedTransactionVersion: 0,
3770
+ commitment: 'confirmed',
3771
+ });
3772
+ } catch {
3773
+ throw new Error('Failed to query Solana transaction confirmation.');
3774
+ }
3775
+ if (!tx) {
3776
+ throw new Error(`Solana transaction ${signature} was not found on cluster ${cluster}.`);
3777
+ }
3778
+
3779
+ const signatures = Array.isArray(tx.transaction?.signatures) ? tx.transaction.signatures : [];
3780
+ if (!signatures.includes(signature)) {
3781
+ throw new Error(`Solana transaction response does not contain the requested signature ${signature}.`);
3782
+ }
3783
+
3784
+ if (!tx.meta || tx.meta.err !== null) {
3785
+ throw new Error(`Solana transaction ${signature} missing metadata or failed on-chain; its memo is not valid committed state.`);
3786
+ }
3787
+
3788
+ let foundMemoRef = null;
3789
+ const instructions = tx.transaction?.message?.instructions || [];
3790
+ for (const ix of instructions) {
3791
+ const progId = ix.programId ? ix.programId.toString() : (ix.programIdIndex !== undefined ? tx.transaction.message.accountKeys[ix.programIdIndex]?.pubkey?.toString() : null);
3792
+ if (progId === SOLANA_MEMO_PROGRAM_ID) {
3793
+ let raw = null;
3794
+ if (typeof ix.parsed === 'string') {
3795
+ raw = ix.parsed;
3796
+ } else if (typeof ix.data === 'string') {
3797
+ raw = decodeBase58Utf8(ix.data);
3798
+ }
3799
+ if (raw) {
3800
+ const candidate = parseStrictSolanaMemoRef(raw);
3801
+ if (candidate) {
3802
+ foundMemoRef = candidate;
3803
+ break;
3804
+ }
3805
+ }
3806
+ }
3807
+ }
3808
+
3809
+ if (!foundMemoRef) {
3810
+ throw new Error(`Solana transaction ${signature} does not contain a valid Enigma SPL Memo instruction.`);
3811
+ }
3812
+
3813
+ const isVerified = (
3814
+ foundMemoRef.v === expectedMemoRef.v &&
3815
+ foundMemoRef.protocol === expectedMemoRef.protocol &&
3816
+ foundMemoRef.rail === expectedMemoRef.rail &&
3817
+ foundMemoRef.cluster === expectedMemoRef.cluster &&
3818
+ foundMemoRef.artifact_type === expectedMemoRef.artifact_type &&
3819
+ foundMemoRef.artifact_hash === expectedMemoRef.artifact_hash &&
3820
+ foundMemoRef.proof_commitment === expectedMemoRef.proof_commitment
3821
+ );
3822
+
3823
+ const blockTimeIso = tx.blockTime ? new Date(tx.blockTime * 1000).toISOString() : null;
3824
+ const payer = tx.transaction?.message?.accountKeys?.[0]?.pubkey ? tx.transaction.message.accountKeys[0].pubkey.toString() : (tx.transaction?.message?.accountKeys?.[0] ? tx.transaction.message.accountKeys[0].toString() : 'unknown');
3825
+
3826
+ print({
3827
+ ok: isVerified,
3828
+ command: 'chain verify-solana',
3829
+ chain: 'solana',
3830
+ cluster,
3831
+ signature,
3832
+ verified_on_chain: isVerified,
3833
+ slot: Number(tx.slot),
3834
+ block_time: blockTimeIso,
3835
+ payer,
3836
+ fee_lamports: Number(tx.meta?.fee ?? 0),
3837
+ memo_program: SOLANA_MEMO_PROGRAM_ID,
3838
+ artifact_type: expectedMemoRef.artifact_type,
3839
+ artifact_hash: expectedMemoRef.artifact_hash,
3840
+ proof_commitment: expectedMemoRef.proof_commitment,
3841
+ rpc_genesis_hash: genesis.genesis_hash,
3842
+ rpc_genesis_policy: genesis.genesis_policy,
3843
+ }, io);
3844
+
3845
+ return isVerified ? 0 : 1;
3846
+ }
3847
+
3848
+ export async function chainVerifySolanaCommand(flags, io, positionalFile = undefined) {
3849
+ const sourceFlag = getFlag(flags, ['file', 'in', 'artifact', 'anchor-file', 'anchor', 'bundle', 'export']);
3850
+ const sourcePath = sourceFlag !== undefined && sourceFlag !== true && sourceFlag !== ''
3851
+ ? String(lastFlagValue(sourceFlag))
3852
+ : (sourceFlag === undefined && positionalFile ? String(positionalFile) : undefined);
3853
+ if (!sourcePath) throw new Error('Missing required --file <anchor-batch.json|bundle.json|export.json>.');
3854
+ const cluster = solanaSubmitCluster(flags);
3855
+ let source;
3856
+ try {
3857
+ source = await readJson(resolve(sourcePath));
3858
+ } catch {
3859
+ throw new Error('Unable to read a valid local anchor, bundle, or export JSON file.');
3860
+ }
3861
+ const normalized = normalizeSolanaLocalArtifact(source, cluster, flags);
3862
+ const rpc = solanaVerifyRpcUrl(flags, cluster);
3863
+ const fetchFn = io?.solanaFetch ?? globalThis.fetch;
3864
+ const signatureFlag = getFlag(flags, ['signature', 'sig', 'tx', 'txid']);
3865
+ const transactionFile = getFlag(flags, ['transaction-file', 'tx-file', 'transactionFile', 'txFile']);
3866
+ const transactionInline = getFlag(flags, ['transaction', 'transaction-json', 'transactionJson', 'transaction-object', 'transactionObject', 'tx-object', 'txObject']);
3867
+ if (transactionFile !== undefined && transactionInline !== undefined) throw new Error('Use either --transaction-file or --transaction, not both.');
3868
+
3869
+ let transaction = null;
3870
+ let transactionSource = 'rpc';
3871
+ if (transactionFile !== undefined) {
3872
+ if (transactionFile === true || transactionFile === '') throw new Error('Missing required --transaction-file.');
3873
+ try { transaction = await readJson(resolve(String(transactionFile))); } catch { throw new Error('Unable to read a valid Solana transaction JSON object.'); }
3874
+ transactionSource = 'input';
3875
+ } else if (transactionInline !== undefined) {
3876
+ if (transactionInline === true || transactionInline === '') throw new Error('Missing required --transaction JSON object.');
3877
+ try { transaction = JSON.parse(String(transactionInline)); } catch { throw new Error('Unable to parse --transaction as a JSON object.'); }
3878
+ transactionSource = 'input';
3879
+ }
3880
+
3881
+ let signature = signatureFlag !== undefined && signatureFlag !== true && signatureFlag !== '' ? validateSolanaSignature(lastFlagValue(signatureFlag)) : undefined;
3882
+ if (!signature && sourceFlag !== undefined && positionalFile) signature = validateSolanaSignature(positionalFile);
3883
+ if (!signature && transaction) {
3884
+ const inferred = solanaTransactionSignatures(transaction)[0] ?? transaction?.signature;
3885
+ if (inferred) signature = validateSolanaSignature(inferred);
3886
+ }
3887
+ if (!signature) throw new Error('Provide --signature <transaction-signature> or a transaction object containing a signature.');
3888
+
3889
+ const report = solanaVerificationBase({
3890
+ cluster,
3891
+ rpcEndpoint: rpc.label,
3892
+ signature,
3893
+ normalized,
3894
+ source: transactionSource,
3895
+ });
3896
+ let genesis;
3897
+ try {
3898
+ const genesisHash = await solanaRpcCall({ url: rpc.url, method: 'getGenesisHash', fetchFn });
3899
+ genesis = verifySolanaGenesisHash(genesisHash, cluster, rpc.url);
3900
+ } catch (error) {
3901
+ print({ ...report, verification_status: 'rpc_error', error: { code: 'SOLANA_RPC_ERROR', message: redactSolanaRpcError(error) } }, io);
3902
+ return 1;
3903
+ }
3904
+ Object.assign(report, {
3905
+ rpc_genesis_hash: genesis.genesis_hash,
3906
+ rpc_genesis_policy: genesis.genesis_policy,
3907
+ network_verified: genesis.verified !== false,
3908
+ });
3909
+ if (genesis.verified === false) {
3910
+ Object.assign(report, { verification_status: 'network_mismatch', failure_code: genesis.failure_code });
3911
+ print(report, io);
3912
+ return 1;
3913
+ }
3914
+
3915
+ if (!transaction) {
3916
+ try {
3917
+ try {
3918
+ transaction = await solanaRpcCall({
3919
+ url: rpc.url,
3920
+ method: 'getTransaction',
3921
+ params: [signature, { encoding: 'jsonParsed', commitment: 'confirmed', maxSupportedTransactionVersion: 0 }],
3922
+ fetchFn,
3923
+ });
3924
+ } catch (firstError) {
3925
+ // Older validators reject maxSupportedTransactionVersion; retry with the legacy-compatible shape.
3926
+ try {
3927
+ transaction = await solanaRpcCall({
3928
+ url: rpc.url,
3929
+ method: 'getTransaction',
3930
+ params: [signature, { encoding: 'jsonParsed', commitment: 'confirmed' }],
3931
+ fetchFn,
3932
+ });
3933
+ } catch {
3934
+ throw firstError;
3935
+ }
3936
+ }
3937
+ } catch (error) {
3938
+ print({ ...report, verification_status: 'rpc_error', error: { code: 'SOLANA_RPC_ERROR', message: redactSolanaRpcError(error) } }, io);
3939
+ return 1;
3940
+ }
3941
+ }
3942
+ if (transaction?.result && !transaction.transaction && transaction.result.transaction) transaction = transaction.result;
3943
+ if (!transaction) {
3944
+ print({ ...report, verification_status: 'not_found', network_verified: true }, io);
3945
+ return 1;
3946
+ }
3947
+ const txSignatures = solanaTransactionSignatures(transaction);
3948
+ if (txSignatures.length > 0 && !txSignatures.includes(signature)) {
3949
+ throw new Error(`Solana transaction response does not contain the requested signature ${signature}.`);
3950
+ }
3951
+
3952
+ const transactionSuccess = transaction?.meta?.err === null;
3953
+ if (!transactionSuccess) throw new Error(`Solana transaction ${signature} missing metadata or failed on-chain; its memo is not valid committed state.`);
3954
+ const extracted = extractSolanaMemoRefs(transaction);
3955
+ const uniqueMemo = extracted.protocol_refs.length === 1;
3956
+ if (!uniqueMemo) throw new Error(`Solana transaction ${signature} does not contain a valid Enigma SPL Memo instruction (exactly one is required).`);
3957
+ const expectedMemoRef = createSolanaProofMemoRef(normalized.schema, normalized.artifact, cluster);
3958
+ const observed = safeObservedMemoRef(uniqueMemo ? extracted.protocol_refs[0] : null);
3959
+ const matches = solanaMemoMatchReport(observed, expectedMemoRef, { uniqueMemo, transactionSuccess });
3960
+ const isVerified = Object.values(matches).every(Boolean);
3961
+ const slot = Number.isInteger(transaction?.slot) ? transaction.slot : null;
3962
+ const confirmation = await solanaConfirmationInfo({ rpcUrl: rpc.url, signature, transactionSlot: slot, fetchFn });
3963
+ Object.assign(report, {
3964
+ ok: isVerified,
3965
+ transaction_observed: true,
3966
+ verified_on_chain: isVerified,
3967
+ verification_status: isVerified ? 'verified' : (transactionSuccess ? 'mismatch' : 'transaction_failed'),
3968
+ failure_code: isVerified ? null : (transactionSuccess ? 'MEMO_COMMITMENT_MISMATCH' : 'TRANSACTION_FAILED'),
3969
+ slot,
3970
+ block_time: solanaBlockTimeIso(transaction?.blockTime),
3971
+ confirmation_status: confirmation.confirmation_status,
3972
+ confirmation_depth: confirmation.confirmation_depth,
3973
+ slot_depth: confirmation.slot_depth,
3974
+ transaction_success: transactionSuccess,
3975
+ payer: solanaPayer(transaction),
3976
+ fee_lamports: Number.isFinite(transaction?.meta?.fee) ? transaction.meta.fee : null,
3977
+ memo_instruction_count: extracted.memo_instruction_count,
3978
+ observed_memo_ref: observed,
3979
+ matches,
3980
+ });
3981
+ print(report, io);
3982
+ return isVerified ? 0 : 1;
3983
+ }
3984
+
2623
3985
  export async function chainAnchorCommand(flags, io) {
2624
3986
  const roots = flagValues(flags, ['root', 'roots', 'memory-root', 'memoryRoot', 'receipt-root', 'receiptRoot', 'context-root', 'contextRoot', 'memory-commitment-root', 'memoryCommitmentRoot']);
2625
3987
  if (roots.length === 0) throw new Error('Missing required --root.');
2626
3988
  const refs = flagValues(flags, ['ref', 'refs', 'public-ref', 'publicRef']);
2627
- const publicChainRef = getFlag(flags, ['public-chain-ref', 'publicChainRef', 'chain-ref', 'chainRef'], 'solana:local-plan');
3989
+ const cluster = getFlag(flags, ['cluster']) === undefined ? undefined : solanaSubmitCluster(flags);
3990
+ const publicChainRef = getFlag(flags, ['public-chain-ref', 'publicChainRef', 'chain-ref', 'chainRef'], cluster ? `solana:${cluster}:spl-memo` : 'solana:local-plan');
2628
3991
  const batch = createProofNetworkAnchorBatch({
2629
3992
  roots,
2630
3993
  root_count: roots.length,
@@ -2634,6 +3997,7 @@ export async function chainAnchorCommand(flags, io) {
2634
3997
  authority_ref: getFlag(flags, ['authority', 'authority-ref', 'authorityRef']),
2635
3998
  batch_ref: getFlag(flags, ['batch-ref', 'batchRef']),
2636
3999
  created_at: getFlag(flags, ['created-at', 'createdAt']),
4000
+ cluster_ref: cluster ? solanaClusterRef(cluster) : undefined,
2637
4001
  transaction_submitted: false,
2638
4002
  raw_memory_on_chain: false,
2639
4003
  });
@@ -2642,9 +4006,29 @@ export async function chainAnchorCommand(flags, io) {
2642
4006
  artifact_type: batch.schema,
2643
4007
  anchor_batch_id: batch.anchor_batch_id,
2644
4008
  anchor_batch_hash: batch.anchor_batch_hash ?? proofNetworkSha256Json(batch),
4009
+ solana_cluster: cluster,
2645
4010
  });
2646
4011
  }
2647
4012
 
4013
+ export async function chainAnchorBundleCommand(flags, io) {
4014
+ const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
4015
+ let bundle;
4016
+ try {
4017
+ bundle = await readJson(bundlePath);
4018
+ } catch {
4019
+ throw new Error('Unable to read a valid Enigma bundle JSON file.');
4020
+ }
4021
+
4022
+ const cluster = solanaSubmitCluster(flags);
4023
+ const { artifact: batch } = anchorBatchFromVaultBundle(bundle, cluster, flags);
4024
+ return chainWriteOrPrint(flags, io, batch, {
4025
+ artifact_type: batch.schema,
4026
+ anchor_batch_id: batch.anchor_batch_id,
4027
+ anchor_batch_hash: batch.anchor_batch_hash ?? proofNetworkSha256Json(batch),
4028
+ source_bundle_verified: true,
4029
+ solana_cluster: cluster,
4030
+ });
4031
+ }
2648
4032
  export async function chainGrantCommand(flags, io) {
2649
4033
  const resourceRefs = flagValues(flags, ['resource-root', 'resource-roots', 'resourceRoot', 'resourceRoots', 'resource-ref', 'resource-refs', 'resourceRef', 'resourceRefs', 'ref', 'refs']);
2650
4034
  const capability = requireFlag(flags, ['capability', 'capability-id', 'capabilityId'], 'capability');
@@ -2673,6 +4057,7 @@ export async function chainGrantCommand(flags, io) {
2673
4057
  });
2674
4058
  }
2675
4059
 
4060
+
2676
4061
  export async function chainRevokeCommand(flags, io) {
2677
4062
  const grantValue = getFlag(flags, ['grant']);
2678
4063
  let grantHash = getFlag(flags, ['grant-hash', 'grantHash']);
@@ -2988,34 +4373,143 @@ export async function settlementBatchCommand(flags, io, positionalFile) {
2988
4373
  return 0;
2989
4374
  }
2990
4375
 
4376
+ function terminalColorEnabled(flags) {
4377
+ return !booleanFlag(flags, ['no-color', 'noColor'], false);
4378
+ }
4379
+
4380
+ export async function tuiCommand(flags, io, mode = 'dashboard') {
4381
+ const bundlePath = pathFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE);
4382
+ return runTerminalDashboard({
4383
+ bundlePath,
4384
+ once: booleanFlag(flags, ['once'], false),
4385
+ interval: getFlag(flags, ['interval'], 1000),
4386
+ mode,
4387
+ color: terminalColorEnabled(flags),
4388
+ io,
4389
+ });
4390
+ }
4391
+
4392
+ export async function treeCommand(flags, io) {
4393
+ const bundlePath = pathFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE);
4394
+ const depth = parseOptionalNumber(getFlag(flags, ['depth']));
4395
+ return runTerminalDashboard({
4396
+ bundlePath,
4397
+ once: true,
4398
+ interval: 1000,
4399
+ mode: 'tree',
4400
+ depth,
4401
+ color: terminalColorEnabled(flags),
4402
+ io,
4403
+ });
4404
+ }
4405
+
4406
+ export async function monitorCommand(flags, io) {
4407
+ return tuiCommand(flags, io, 'monitor');
4408
+ }
4409
+
4410
+ async function optionalBundleBytes(flags) {
4411
+ const value = lastFlagValue(getFlag(flags, ['bundle', 'file']));
4412
+ if (value === undefined) return undefined;
4413
+ if (value === true || value === '') throw new Error('Missing required --bundle.');
4414
+ return readFile(resolve(String(value)));
4415
+ }
4416
+
4417
+ export async function enclaveCycleCommand(flags, io) {
4418
+ const bundle = await optionalBundleBytes(flags);
4419
+ const purpose = lastFlagValue(getFlag(flags, ['purpose']));
4420
+ if (purpose === true || purpose === '') throw new Error('Missing required --purpose.');
4421
+ const manager = new EnclaveSessionManager();
4422
+ const session = manager.initializeSession(bundle === undefined ? {} : { bundle });
4423
+ if (purpose !== undefined) manager.deriveSessionKey(session.session_id, String(purpose));
4424
+ const receipt = manager.zeroizeSession(session.session_id);
4425
+ const verification = verifyZeroizationReceipt(receipt, {
4426
+ expectedSessionId: session.session_id,
4427
+ expectedPublicKey: manager.publicKeySpkiBase64,
4428
+ });
4429
+ if (!verification.valid) throw new Error(`Atomic enclave cycle receipt verification failed: ${verification.errors.join(', ')}`);
4430
+ const receiptOut = lastFlagValue(getFlag(flags, ['receipt-out', 'receiptOut']));
4431
+ if (receiptOut === true || receiptOut === '') throw new Error('Missing required --receipt-out.');
4432
+ if (receiptOut !== undefined) {
4433
+ const outPath = resolve(String(receiptOut));
4434
+ await writeJson(outPath, receipt);
4435
+ print({ ok: true, verified: true, path: outPath, session_id: receipt.session_id, receipt_hash: receipt.receipt_hash }, io);
4436
+ } else {
4437
+ print(receipt, io);
4438
+ }
4439
+ return 0;
4440
+ }
4441
+
4442
+ export async function enclaveAttestCommand(flags, io) {
4443
+ const bundle = await optionalBundleBytes(flags);
4444
+ print(createAttestation(bundle === undefined ? {} : { bundle }), io);
4445
+ return 0;
4446
+ }
4447
+
4448
+ export async function enclaveVerifyZeroizationCommand(flags, io, positionalFile) {
4449
+ const receiptPath = resolve(String(requireFileArg(flags, ['receipt'], positionalFile, 'receipt')));
4450
+ const result = verifyZeroizationReceipt(await readJson(receiptPath));
4451
+ print(result, io);
4452
+ return result.valid ? 0 : 1;
4453
+ }
4454
+
4455
+ export async function swarmStatusCommand(flags, io) {
4456
+ const bundlePath = pathFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE);
4457
+ print(await swarmStatusFromBundle(bundlePath), io);
4458
+ return 0;
4459
+ }
4460
+
4461
+ export async function swarmGraphCommand(flags, io) {
4462
+ const bundlePath = pathFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE);
4463
+ print(await swarmGraphFromBundle(bundlePath), io);
4464
+ return 0;
4465
+ }
4466
+
2991
4467
  function humanUsage() {
2992
4468
  return `Enigma Memory CLI — local-first AI Memory Passport
2993
4469
 
2994
4470
  Usage: enigma <command> [options]
2995
4471
 
2996
- Quick start:
2997
- npm install -g enigma-memory
2998
- enigma setup --bundle "$HOME/.enigma/bundle.json" --client auto --connect-installed --overwrite
2999
- echo "Your memory text" > memory.txt
3000
- enigma remember --bundle "$HOME/.enigma/bundle.json" --text-file memory.txt
3001
- enigma search --bundle "$HOME/.enigma/bundle.json" --query "your topic"
3002
- enigma context --bundle "$HOME/.enigma/bundle.json" --query "your topic" --out "$HOME/.enigma/context-pack.json"
3003
- enigma verify --export "$HOME/.enigma/export.json"
4472
+ One-command harness install:
4473
+ npx --yes enigma-memory@latest
4474
+
4475
+ Pinned GitHub source before registry publication:
4476
+ npx --yes --package=github:Enigma-Memory/enigma-cortex-enterprise#<commit> enigma-memory
4477
+
4478
+ The bootstrap installs every Enigma command, creates an encrypted local vault,
4479
+ and opens the desktop with lexical embeddings. Add --no-launch for install-only
4480
+ or --terminal to open the interactive terminal instead.
4481
+
4482
+ After installation:
4483
+ enigma-terminal --bundle "$HOME/.enigma/bundle.json"
4484
+ enigma passport status --bundle "$HOME/.enigma/bundle.json" --passphrase-file "$HOME/.enigma/passphrase"
3004
4485
 
3005
4486
  Windows CMD uses %USERPROFILE% instead of $HOME:
3006
4487
  enigma remember --bundle "%USERPROFILE%\\.enigma\\bundle.json" --text-file memory.txt
3007
4488
 
3008
4489
  Common commands:
3009
- init, setup, quickstart, test-drive First-run / demo paths
4490
+ init, setup, start, quickstart, test-drive First-run / demo paths
3010
4491
  remember, recall, update, delete Memory operations
3011
4492
  search, context Retrieval / context packs
3012
4493
  status, drive health, doctor Health and diagnostics
4494
+ vault rotate|seal|reset Content-key lifecycle
3013
4495
  connect <client>, disconnect <client> MCP client connectors
3014
4496
  export, import <source>, capsule ... Migration and packaging
3015
4497
  verify Verify an exported bundle
4498
+ tui, dashboard, tree, monitor Live local terminal observability
4499
+ enclave cycle|attest|verify-zeroization Atomic local key lifecycle
4500
+ swarm status|graph Local-only metadata simulator
4501
+ chain anchor-bundle|submit-solana Public-safe Solana proof anchoring
4502
+ chain verify-solana Verify an on-chain SPL Memo anchor
3016
4503
  mcp serve, relay demo|serve, Local service demos
3017
4504
  gateway demo|serve
3018
4505
 
4506
+ Security Modes & At-Rest Confidentiality:
4507
+ • Encrypted Default: Persistent commands require ENIGMA_PASSPHRASE,
4508
+ --passphrase <value>, or --passphrase-file <path>, and use AES-256-GCM
4509
+ keyring encryption with scrypt and authenticated bundle metadata.
4510
+ • Explicit Insecure Development: --insecure-dev permits a plaintext keyring only
4511
+ for disposable local evaluation; persistent commands otherwise fail closed.
4512
+
3019
4513
  If no AI clients are installed, setup still creates the local bundle and
3020
4514
  skips connector writes. Connect later with enigma connect <client> or the
3021
4515
  copy-paste MCP snippets in docs/client-connectors.md.
@@ -3030,8 +4524,18 @@ function usage() {
3030
4524
  'init',
3031
4525
  'setup',
3032
4526
  'quickstart',
4527
+ 'start',
3033
4528
  'test-drive',
3034
4529
  'demo cross-model',
4530
+ 'tui',
4531
+ 'dashboard',
4532
+ 'tree',
4533
+ 'monitor',
4534
+ 'enclave cycle',
4535
+ 'enclave attest',
4536
+ 'enclave verify-zeroization',
4537
+ 'swarm status',
4538
+ 'swarm graph',
3035
4539
  'doctor',
3036
4540
  'install',
3037
4541
  'connect <client>',
@@ -3044,6 +4548,9 @@ function usage() {
3044
4548
  'search',
3045
4549
  'status',
3046
4550
  'passport status',
4551
+ 'vault rotate',
4552
+ 'vault seal',
4553
+ 'vault reset',
3047
4554
  'export',
3048
4555
  'import <source>',
3049
4556
  'capsule export',
@@ -3068,15 +4575,36 @@ function usage() {
3068
4575
  'settlement verify',
3069
4576
  'settlement batch',
3070
4577
  'chain anchor',
4578
+ 'chain anchor-bundle',
3071
4579
  'chain grant',
3072
4580
  'chain revoke',
3073
4581
  'chain attest',
3074
4582
  'chain verify',
3075
4583
  'chain submit-solana',
4584
+ 'chain verify-solana',
3076
4585
  'chain register',
3077
4586
  'chain registry',
3078
4587
  'drive health',
3079
4588
  ],
4589
+ terminal_tui_options: {
4590
+ 'enigma tui --bundle <path> [--once]': 'Render the full local terminal dashboard; --once exits after one CI-safe snapshot.',
4591
+ 'enigma dashboard --bundle <path> [--once]': 'Alias for the full local terminal dashboard.',
4592
+ 'enigma tree --bundle <path> [--depth <n>]': 'Render the ANSI Merkle tree, leaf statuses, and a proof verification path.',
4593
+ 'enigma monitor --bundle <path> [--interval <ms>]': 'Run the live MCP event and caller monitor; non-TTY output automatically renders once.',
4594
+ '--no-color': 'Disable ANSI colors for logs that require plain text.',
4595
+ boundary: 'Reads the local bundle only. Tombstones mean cryptographic logical tombstoning and active-set de-indexing; the TUI makes no hardware-RAM erasure, provider-deletion, or model-forgetting claim.',
4596
+ },
4597
+ enclave_options: {
4598
+ cycle: 'enigma enclave cycle [--bundle <path>] [--purpose <label>] [--receipt-out <path>]',
4599
+ attest: 'enigma enclave attest [--bundle <path>]',
4600
+ verify_zeroization: 'enigma enclave verify-zeroization --receipt <path>',
4601
+ boundary: 'cycle creates, optionally derives, and zeroizes ephemeral keys atomically within one process; no session key or active session is persisted cross-process. It performs best-effort overwrite of Enigma-retained Node Buffer instances with post-overwrite byte verification and a signed/hashed receipt. PCRs are simulated. State nullification means active-session removal and Enigma key-handle invalidation only; it does not prove hardware-RAM zeroization, runtime/GC/cache/external-copy erasure, physical-media sanitization, unrecoverability, or DoD/NIST compliance.',
4602
+ },
4603
+ swarm_options: {
4604
+ status: 'enigma swarm status [--bundle <path>]',
4605
+ graph: 'enigma swarm graph [--bundle <path>]',
4606
+ boundary: 'Local-only simulator and metadata graph. Raw memory content is redacted by default; no network transmission or decentralization claim.',
4607
+ },
3080
4608
  connector_options: {
3081
4609
  '--bundle <path>': 'Absolute local Enigma vault bundle path rendered as ENIGMA_BUNDLE.',
3082
4610
  '--config <path>': 'Client MCP JSON config path to read/write.',
@@ -3120,6 +4648,8 @@ function usage() {
3120
4648
  '--memory-file <path>': 'Read local memory text from a file without echoing plaintext. Alias: --text-file.',
3121
4649
  '--memory-text <text>': 'Inline demo-only memory text. Avoid for private content because argv can be logged.',
3122
4650
  '--overwrite': 'Replace existing local first-run artifacts.',
4651
+ '--passphrase-file <path>': 'Required encrypted-vault credential file; keeps the passphrase out of shell history and process arguments.',
4652
+ '--insecure-dev': 'INSECURE, EVALUATION ONLY: permit a disposable plaintext-keyring vault. Never use for normal or persistent data.',
3123
4653
  },
3124
4654
  setup_options: {
3125
4655
  '--bundle <path>': 'Bundle JSON to create. Defaults to .enigma/bundle.json.',
@@ -3131,13 +4661,20 @@ function usage() {
3131
4661
  '--overwrite': 'Replace existing local setup artifacts.',
3132
4662
  '--dry-run': 'Plan setup without writing local artifacts or client configs.',
3133
4663
  '--write-connectors': 'Also write selected client MCP config files. Defaults to false.',
4664
+ '--passphrase-file <path>': 'Required encrypted-vault credential file; keeps the passphrase out of shell history and process arguments.',
4665
+ '--insecure-dev': 'INSECURE, EVALUATION ONLY: permit a disposable plaintext-keyring vault. Never use for normal or persistent data.',
3134
4666
  },
3135
4667
  install_options: {
3136
- 'one-command installed clients': 'npm install -g enigma-memory && enigma setup --client auto --connect-installed --overwrite',
3137
- 'one-command Claude Desktop': 'npm install -g enigma-memory && enigma setup --client claude-desktop --write-connectors --overwrite',
3138
- 'one-command Cursor': 'npm install -g enigma-memory && enigma setup --client cursor --write-connectors --overwrite',
3139
- 'one-command Kimi Code': 'npm install -g enigma-memory && enigma setup --client kimi-code --write-connectors --overwrite',
3140
- 'one-command VS Code Cline': 'npm install -g enigma-memory && enigma setup --client vscode-cline --write-connectors --overwrite',
4668
+ 'one-command full harness': 'npx --yes enigma-memory@latest',
4669
+ 'one-command pinned source': 'npx --yes --package=github:Enigma-Memory/enigma-cortex-enterprise#<commit> enigma-memory',
4670
+ 'bootstrap behavior': 'Installs the exact executing package, generates a secure passphrase file only for a new vault, initializes encrypted local state, and opens the lexical desktop without a neural model download.',
4671
+ 'bootstrap install only': 'npx --yes enigma-memory@latest --no-launch',
4672
+ 'bootstrap terminal': 'npx --yes enigma-memory@latest --terminal',
4673
+ 'one-command installed clients': `npm install -g enigma-memory && enigma setup ${PUBLIC_PASSPHRASE_FILE_FLAG} --client auto --connect-installed --overwrite`,
4674
+ 'one-command Claude Desktop': `npm install -g enigma-memory && enigma setup ${PUBLIC_PASSPHRASE_FILE_FLAG} --client claude-desktop --write-connectors --overwrite`,
4675
+ 'one-command Cursor': `npm install -g enigma-memory && enigma setup ${PUBLIC_PASSPHRASE_FILE_FLAG} --client cursor --write-connectors --overwrite`,
4676
+ 'one-command Kimi Code': `npm install -g enigma-memory && enigma setup ${PUBLIC_PASSPHRASE_FILE_FLAG} --client kimi-code --write-connectors --overwrite`,
4677
+ 'one-command VS Code Cline': `npm install -g enigma-memory && enigma setup ${PUBLIC_PASSPHRASE_FILE_FLAG} --client vscode-cline --write-connectors --overwrite`,
3141
4678
  '--client <id>': 'Limit generated MCP snippets to one supported client.',
3142
4679
  '--out <path>': 'Write generated MCP snippets to a JSON file for review without hand-editing client config JSON.',
3143
4680
  },
@@ -3149,6 +4686,8 @@ function usage() {
3149
4686
  '--memory-file <path>': 'Read local memory text from a file. Alias: --text-file.',
3150
4687
  '--memory-text <text>': 'Inline demo memory text for non-private demos only.',
3151
4688
  '--overwrite': 'Replace existing quickstart output files.',
4689
+ '--passphrase-file <path>': 'Required encrypted-vault credential file; keeps the passphrase out of shell history and process arguments.',
4690
+ '--insecure-dev': 'INSECURE, EVALUATION ONLY: permit a disposable plaintext-keyring vault. Never use for normal or persistent data.',
3152
4691
  },
3153
4692
  test_drive_options: {
3154
4693
  '--out-dir <path>': `Isolated demo directory. Defaults to ${DEFAULT_TEST_DRIVE_DIR}.`,
@@ -3163,6 +4702,13 @@ function usage() {
3163
4702
  '--out <path>': 'Write the same public-safe JSON report to a local file.',
3164
4703
  '--limit <n>': 'Maximum active memories per generated profile context pack. Defaults to 1 for the same-memory demo story.',
3165
4704
  },
4705
+ capsule_options: {
4706
+ export: 'enigma capsule export --file <encrypted-import-report> --passphrase-file <path> --out <encrypted-capsule>',
4707
+ import: 'enigma capsule import --file <encrypted-capsule> --passphrase-file <path> (--trust-descriptor <json> | --trust-bundle <json>) [--write-vault --bundle <path>]',
4708
+ '--trust-descriptor <path>': 'Required external issuer descriptor used to verify the capsule signature. Mutually exclusive with --trust-bundle.',
4709
+ '--trust-bundle <path>': 'Required external issuer trust bundle used to verify the capsule signature. Mutually exclusive with --trust-descriptor.',
4710
+ boundary: 'The trust source must be supplied independently of the encrypted capsule; embedded descriptor metadata is never accepted as its own trust anchor.',
4711
+ },
3166
4712
  native_host: {
3167
4713
  bin: 'enigma-native-host',
3168
4714
  host_name: 'com.enigma.native_host',
@@ -3204,6 +4750,7 @@ function usage() {
3204
4750
  },
3205
4751
  chain: {
3206
4752
  anchor: 'enigma chain anchor --root <sha256:...> [--root <sha256:...>] [--ref <public-ref>] [--authority <public-authority-ref>] [--batch-ref <ref>] [--out <file>]',
4753
+ anchor_bundle: 'enigma chain anchor-bundle --bundle <bundle.json> --cluster <devnet|testnet|mainnet-beta|localnet> [--out <anchor-batch.json>] then enigma chain submit-solana --file <anchor-batch.json> [--execute --keypair <solana-cli-keypair.json>]',
3207
4754
  grant: 'enigma chain grant --subject <public-subject-ref> --capability <capability-id> --scope <scope-id> [--resource-ref <sha256:...>] [--policy-hash <sha256:...>] --expires-at <iso> [--grant-ref <public-ref>] [--out <file>]',
3208
4755
  revoke: 'enigma chain revoke --grant-hash <sha256:...> --reason <public-reason-code> [--revocation-ref <public-ref>] [--out <file>]',
3209
4756
  attest: 'enigma chain attest (--report-hash <sha256:...> | --report-file <report.json>) --dataset-ref <sha256:...> --runner-ref <public-runner-ref> --package-ref <public-package-ref> [--score name=value] [--out <file>]',
@@ -3211,6 +4758,7 @@ function usage() {
3211
4758
  register: 'enigma chain register --entry-type <anchor_batch|benchmark_attestation|connector_conformance|health_report|operator_receipt|settlement_job> (--artifact-hash <sha256:...> | --artifact-file <artifact.json>) --artifact-schema-ref <schema-id> [--digest-ref <sha256:...>] [--signer <public-ref>] [--registry-ref <public-ref>] [--entry-ref <public-ref>] [--entry-count <n>] [--out <file>]',
3212
4759
  registry: 'enigma chain registry --entry <registry-entry.json> [--entry <registry-entry.json>] [--registry-ref <public-ref>] [--out <file>]',
3213
4760
  submit_solana: 'enigma chain submit-solana --file <proof-artifact.json> --cluster <devnet|testnet|mainnet-beta|localnet> [--rpc <url>] [--execute --keypair <solana-cli-64-byte-keypair.json>]',
4761
+ verify_solana: 'enigma chain verify-solana --signature <tx-signature> --file <anchor-batch|bundle|export.json> --cluster <devnet|testnet|mainnet-beta|localnet> [--rpc <url>] [--transaction-file <tx.json> | --transaction <json>]',
3214
4762
  boundary: 'Proof Network chain commands default to local planning and dry-run validation. submit-solana only submits a Solana Memo transaction when --execute is passed; it carries compact public-safe commitment/ref JSON, never raw memory or artifact bodies.',
3215
4763
  },
3216
4764
  memory_drive_health: {
@@ -3248,9 +4796,52 @@ function usage() {
3248
4796
  };
3249
4797
  }
3250
4798
 
4799
+ export async function devCommand(subcommand, flags, io) {
4800
+ const { runVectorBenchmark, inspectVaultBundle, simulateSwarmNetwork, runMemoryBenchmarkSuite } = await import('../../../packages/dev-tools/src/index.js');
4801
+ if (subcommand === 'bench') {
4802
+ const items = integerFlag(flags, ['items', 'n'], 'items', 100);
4803
+ const queries = integerFlag(flags, ['queries', 'q'], 'queries', 50);
4804
+ const report = await runVectorBenchmark({
4805
+ ...io.devToolOptions?.vectorBenchmark,
4806
+ itemCount: items,
4807
+ queryCount: queries,
4808
+ });
4809
+ print(report, io);
4810
+ return 0;
4811
+ }
4812
+ if (subcommand === 'bench-suite') {
4813
+ const size = integerFlag(flags, ['size', 's'], 'size', 30);
4814
+ const report = await runMemoryBenchmarkSuite({
4815
+ ...io.devToolOptions?.memoryBenchmark,
4816
+ corpusSize: size,
4817
+ });
4818
+ print(report, io);
4819
+ return 0;
4820
+ }
4821
+ if (subcommand === 'inspect') {
4822
+ const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
4823
+ const credentials = await resolveVaultCredentials(flags);
4824
+ const report = await inspectVaultBundle(bundlePath, { passphrase: credentials.passphrase });
4825
+ print(report, io);
4826
+ return 0;
4827
+ }
4828
+ if (subcommand === 'sim-swarm') {
4829
+ const nodes = integerFlag(flags, ['nodes', 'n'], 'nodes', 4);
4830
+ const messages = integerFlag(flags, ['messages', 'm'], 'messages', 8);
4831
+ const report = await simulateSwarmNetwork({ nodeCount: nodes, messageCount: messages });
4832
+ print(report, io);
4833
+ return 0;
4834
+ }
4835
+ throw new Error(`Unknown dev subcommand: "${subcommand}". Available: bench, bench-suite, inspect, sim-swarm`);
4836
+ }
4837
+
3251
4838
  export async function main(argv = process.argv.slice(2), io = { stdout: process.stdout, stderr: process.stderr }) {
3252
4839
  const command = argv[0];
3253
4840
  const subcommand = argv[1];
4841
+ if (command === '--version' || command === '-v' || command === 'version') {
4842
+ io.stdout.write(`enigma v${ENIGMA_VERSION}\n`);
4843
+ return 0;
4844
+ }
3254
4845
  if (!command || command === '--help' || command === '-h' || command === '--help-human') {
3255
4846
  if (command === '--help-human') {
3256
4847
  io.stdout.write(`${humanUsage()}\n`);
@@ -3259,14 +4850,15 @@ export async function main(argv = process.argv.slice(2), io = { stdout: process.
3259
4850
  }
3260
4851
  return 0;
3261
4852
  }
3262
- const twoPartCommands = ['boundary', 'mcp', 'mesh', 'enterprise', 'capsule', 'relay', 'gateway', 'connect', 'disconnect', 'import', 'native-host', 'meter', 'settlement', 'chain', 'demo', 'passport', 'drive'];
4853
+ const twoPartCommands = ['dev', 'boundary', 'mcp', 'mesh', 'market', 'zk', 'enterprise', 'capsule', 'relay', 'gateway', 'connect', 'disconnect', 'import', 'native-host', 'meter', 'settlement', 'chain', 'demo', 'passport', 'drive', 'enclave', 'swarm', 'vault'];
3263
4854
  const flags = parseArgs(twoPartCommands.includes(command) ? argv.slice(2) : argv.slice(1));
3264
4855
  const positionalFile = optionalPositional(argv[2]);
3265
4856
  if (command === 'help') {
3266
4857
  io.stdout.write(`${humanUsage()}\n`);
3267
4858
  return 0;
3268
4859
  }
3269
- if ((command === 'chain' && (!subcommand || subcommand === '--help' || subcommand === '-h' || flags.has('help'))) || ((flags.has('help') || argv.includes('-h')) && (command === 'init' || command === 'setup' || command === 'test-drive' || command === 'search' || command === 'status' || (command === 'passport' && subcommand === 'status') || ((command === 'relay' || command === 'gateway') && (subcommand === 'serve' || subcommand === 'demo')) || (command === 'native-host' && (subcommand === 'manifest' || subcommand === 'install-plan')) || (command === 'demo' && subcommand === 'cross-model') || (command === 'drive' && subcommand === 'health')))) {
4860
+ const helpRequested = flags.has('help') || argv.includes('-h') || subcommand === '--help' || subcommand === '-h';
4861
+ if (helpRequested || ((command === 'chain' || command === 'enclave' || command === 'swarm') && !subcommand)) {
3270
4862
  print(usage(), io);
3271
4863
  return 0;
3272
4864
  }
@@ -3274,11 +4866,22 @@ export async function main(argv = process.argv.slice(2), io = { stdout: process.
3274
4866
  if (command === 'init') return await initCommand(flags, io);
3275
4867
  if (command === 'setup') return await setupCommand(flags, io);
3276
4868
  if (command === 'quickstart') return await quickstartCommand(flags, io);
4869
+ if (command === 'start') return await quickstartCommand(flags, io);
3277
4870
  if (command === 'test-drive') return await testDriveCommand(flags, io);
3278
4871
  if (command === 'demo' && subcommand === 'cross-model') return await crossModelDemoCommand(flags, io);
4872
+ if (command === 'tui') return await tuiCommand(flags, io);
4873
+ if (command === 'dashboard') return await tuiCommand(flags, io);
4874
+ if (command === 'tree') return await treeCommand(flags, io);
4875
+ if (command === 'monitor') return await monitorCommand(flags, io);
4876
+ if (command === 'enclave' && subcommand === 'cycle') return await enclaveCycleCommand(flags, io);
4877
+ if (command === 'enclave' && subcommand === 'attest') return await enclaveAttestCommand(flags, io);
4878
+ if (command === 'enclave' && subcommand === 'verify-zeroization') return await enclaveVerifyZeroizationCommand(flags, io, positionalFile);
4879
+ if (command === 'swarm' && subcommand === 'status') return await swarmStatusCommand(flags, io);
4880
+ if (command === 'swarm' && subcommand === 'graph') return await swarmGraphCommand(flags, io);
3279
4881
  if (command === 'doctor') return await doctorCommand(flags, io);
3280
4882
  if (command === 'install') return await installCommand(flags, io);
3281
4883
  if (command === 'connect') return await connectCommand(subcommand, flags, io);
4884
+ if (command === 'dev') return await devCommand(subcommand, flags, io);
3282
4885
  if (command === 'disconnect') return await disconnectCommand(subcommand, flags, io);
3283
4886
  if (command === 'remember') return await rememberCommand(flags, io);
3284
4887
  if (command === 'recall') return await recallCommand(flags, io);
@@ -3289,6 +4892,9 @@ export async function main(argv = process.argv.slice(2), io = { stdout: process.
3289
4892
  if (command === 'status') return await statusCommand(flags, io);
3290
4893
  if (command === 'passport' && subcommand === 'status') return await statusCommand(flags, io);
3291
4894
  if (command === 'drive' && subcommand === 'health') return await driveHealthCommand(flags, io);
4895
+ if (command === 'vault' && subcommand === 'rotate') return await rotateVaultKeyCommand(flags, io);
4896
+ if (command === 'vault' && subcommand === 'seal') return await sealVaultKeyCommand(flags, io);
4897
+ if (command === 'vault' && subcommand === 'reset') return await resetVaultKeysCommand(flags, io);
3292
4898
  if (command === 'export') return await exportCommand(flags, io);
3293
4899
  if (command === 'import') return await importCommand(subcommand, flags, io, positionalFile);
3294
4900
  if (command === 'capsule' && subcommand === 'export') return await capsuleExportCommand(flags, io, positionalFile);
@@ -3309,16 +4915,48 @@ export async function main(argv = process.argv.slice(2), io = { stdout: process.
3309
4915
  if (command === 'settlement' && subcommand === 'receipt') return await settlementReceiptCommand(flags, io);
3310
4916
  if (command === 'settlement' && subcommand === 'verify') return await settlementVerifyCommand(flags, io);
3311
4917
  if (command === 'settlement' && subcommand === 'batch') return await settlementBatchCommand(flags, io, positionalFile);
4918
+ if (command === 'solana') {
4919
+ if (subcommand === 'anchor-bundle') return await chainAnchorBundleCommand(flags, io);
4920
+ if (subcommand === 'submit' || subcommand === 'submit-solana') return await chainSubmitSolanaCommand(flags, io, positionalFile);
4921
+ if (subcommand === 'verify-anchor' || subcommand === 'verify-solana' || subcommand === 'verify') return await chainVerifySolanaCommand(flags, io, positionalFile);
4922
+ if (!subcommand || subcommand === 'status' || subcommand === 'health') {
4923
+ const { probeSolanaRpc, formatSolanaStatusReport } = await import('../../../packages/terminal-console/solana-rpc.mjs');
4924
+ const cluster = getFlag(flags, ['cluster'], 'mainnet-beta');
4925
+ const rpcUrl = getFlag(flags, ['rpc']);
4926
+ const probe = await probeSolanaRpc({ cluster, rpcUrl });
4927
+ const out = formatSolanaStatusReport(probe, { color: !getFlag(flags, ['no-color', 'noColor']) });
4928
+ io.stdout.write('\n' + out + '\n');
4929
+ return probe.ok ? 0 : 1;
4930
+ }
4931
+ if (subcommand === 'stream' || subcommand === 'pulse' || subcommand === 'heartbeat') {
4932
+ const { runSolanaHeartbeatStream } = await import('../../../packages/terminal-console/animations.mjs');
4933
+ const cluster = getFlag(flags, ['cluster'], 'mainnet-beta');
4934
+ const rpcUrl = getFlag(flags, ['rpc']);
4935
+ const samples = getFlag(flags, ['samples', 'n']) ? Number(getFlag(flags, ['samples', 'n'])) : (getFlag(flags, ['once']) ? 1 : undefined);
4936
+ return await runSolanaHeartbeatStream(io, { cluster, rpcUrl, samples, color: !getFlag(flags, ['no-color', 'noColor']) });
4937
+ }
4938
+ }
3312
4939
  if (command === 'chain' && subcommand === 'anchor') return await chainAnchorCommand(flags, io);
4940
+ if (command === 'chain' && subcommand === 'anchor-bundle') return await chainAnchorBundleCommand(flags, io);
3313
4941
  if (command === 'chain' && subcommand === 'grant') return await chainGrantCommand(flags, io);
3314
4942
  if (command === 'chain' && subcommand === 'revoke') return await chainRevokeCommand(flags, io);
3315
4943
  if (command === 'chain' && subcommand === 'attest') return await chainAttestCommand(flags, io);
3316
4944
  if (command === 'chain' && subcommand === 'verify') return await chainVerifyCommand(flags, io, positionalFile);
3317
4945
  if (command === 'chain' && subcommand === 'submit-solana') return await chainSubmitSolanaCommand(flags, io, positionalFile);
4946
+ if (command === 'chain' && (subcommand === 'verify-solana' || subcommand === 'verify-anchor')) return await chainVerifySolanaCommand(flags, io, positionalFile);
3318
4947
  if (command === 'chain' && subcommand === 'register') return await chainRegisterCommand(flags, io);
3319
4948
  if (command === 'chain' && subcommand === 'registry') return await chainRegistryCommand(flags, io, positionalFile);
3320
4949
  if (command === 'native-host' && subcommand === 'install-plan') return await nativeHostInstallPlanCommand(flags, io);
4950
+ if (command === 'mesh' && (subcommand === 'status' || !subcommand)) return await meshStatusCommand(flags, io);
4951
+ if (command === 'mesh' && subcommand === 'announce') return await meshAnnounceCommand(flags, io);
4952
+ if (command === 'mesh' && subcommand === 'send') return await meshSendCommand(flags, io);
4953
+ if (command === 'mesh' && subcommand === 'listen') return await meshListenCommand(flags, io);
3321
4954
  if (command === 'mesh' && subcommand === 'demo') return await meshDemoCommand(flags, io);
4955
+ if (command === 'market' && (subcommand === 'status' || !subcommand)) return await marketStatusCommand(flags, io);
4956
+ if (command === 'market' && subcommand === 'list') return await marketListCommand(flags, io);
4957
+ if (command === 'market' && subcommand === 'verify') return await marketVerifyCommand(flags, io);
4958
+ if (command === 'market' && subcommand === 'demo') return await marketDemoCommand(flags, io);
4959
+ if (command === 'zk' && (subcommand === 'status' || !subcommand)) return await zkStatusCommand(flags, io);
3322
4960
  if (command === 'enterprise' && subcommand === 'demo') return await enterpriseDemoCommand(flags, io);
3323
4961
  throw new Error(`Unknown command: ${[command, subcommand].filter(Boolean).join(' ')}`);
3324
4962
  } catch (error) {