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.
- package/README.md +76 -24
- package/apps/cli/bin/enigma-desktop.mjs +140 -0
- package/apps/cli/bin/enigma-terminal.mjs +78 -0
- package/apps/cli/bin/enigma.mjs +1923 -285
- package/apps/desktop/electron-main.cjs +217 -0
- package/apps/desktop/package.json +12 -0
- package/apps/desktop/src/app.js +264 -7
- package/apps/desktop/src/index.html +3514 -1373
- package/apps/desktop/src/launch-electron.mjs +51 -0
- package/apps/desktop/src/server.mjs +2914 -0
- package/apps/desktop/src/styles.css +2972 -260
- package/apps/desktop/src/zk-browser-prove.mjs +53 -0
- package/apps/desktop/src/zk-state.mjs +1789 -0
- package/apps/gateway/bin/enigma-gateway.mjs +102 -5
- package/apps/gateway/src/server.mjs +271 -8
- package/apps/ios/EnigmaCore/Package.swift +12 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/EnigmaAPIClient.swift +227 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/Models.swift +278 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/PKCE.swift +96 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/PrivacyMinimizer.swift +187 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/ToolModels.swift +129 -0
- package/apps/ios/EnigmaCore/Tests/EnigmaCoreTests/EnigmaCoreTests.swift +42 -0
- package/apps/ios/EnigmaIOS/Enigma/AppModel.swift +346 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AccentColor.colorset/Contents.json +12 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/Contents.json +11 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/EnigmaAppIcon.png +0 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/Contents.json +3 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/LaunchBackground.colorset/Contents.json +12 -0
- package/apps/ios/EnigmaIOS/Enigma/ChatView.swift +181 -0
- package/apps/ios/EnigmaIOS/Enigma/CouncilView.swift +78 -0
- package/apps/ios/EnigmaIOS/Enigma/CreateView.swift +152 -0
- package/apps/ios/EnigmaIOS/Enigma/EnigmaApp.swift +52 -0
- package/apps/ios/EnigmaIOS/Enigma/Info.plist +52 -0
- package/apps/ios/EnigmaIOS/Enigma/NaturalLanguagePrivacyTagger.swift +26 -0
- package/apps/ios/EnigmaIOS/Enigma/OAuthClient.swift +321 -0
- package/apps/ios/EnigmaIOS/Enigma/OnboardingView.swift +105 -0
- package/apps/ios/EnigmaIOS/Enigma/PrivateVaultView.swift +275 -0
- package/apps/ios/EnigmaIOS/Enigma/SecureStore.swift +76 -0
- package/apps/ios/EnigmaIOS/Enigma/SettingsView.swift +60 -0
- package/apps/ios/EnigmaIOS/Enigma/Theme.swift +80 -0
- package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/project.pbxproj +211 -0
- package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/xcshareddata/xcschemes/Enigma.xcscheme +23 -0
- package/apps/native-host/README.md +19 -8
- package/apps/native-host/bin/enigma-native-host.mjs +229 -13
- package/apps/relay/bin/enigma-relay.mjs +103 -5
- package/apps/relay/src/federation-runtime.mjs +618 -0
- package/apps/relay/src/server.mjs +310 -9
- package/apps/verifier/bin/enigma-verify.mjs +327 -11
- package/cortex-v3/circuits/build/intent_vk_bytes.json +35 -0
- package/cortex-v3/circuits/build/sale_vk_bytes.json +35 -0
- package/cortex-v3/circuits/build/vk_bytes.json +32 -0
- package/cortex-v3/proving-assets.json +64 -0
- package/cortex-v3/zk/BUILD-CONTRACT.md +87 -0
- package/cortex-v3/zk/action-transition-vk.json +119 -0
- package/cortex-v3/zk/alias-adversarial.test.mjs +220 -0
- package/cortex-v3/zk/groth16-verify-child.mjs +17 -0
- package/cortex-v3/zk/intent-witness.mjs +365 -0
- package/cortex-v3/zk/intent-witness.test.mjs +485 -0
- package/cortex-v3/zk/proving-assets.mjs +203 -0
- package/cortex-v3/zk/sale-witness.mjs +783 -0
- package/cortex-v3/zk/sale-witness.test.mjs +784 -0
- package/cortex-v3/zk/sealed-sale-release-vk.json +119 -0
- package/cortex-v3/zk/settlement-evidence.mjs +722 -0
- package/cortex-v3/zk/setup-intent.mjs +688 -0
- package/cortex-v3/zk/setup-sale.mjs +666 -0
- package/cortex-v3/zk/setup.mjs +594 -0
- package/cortex-v3/zk/witness.mjs +184 -0
- package/cortex-v3/zk/zk-codec.mjs +232 -0
- package/cortex-v3/zk/zk-codec.test.mjs +293 -0
- package/cortex-v3/zk/zk-settle.mjs +370 -0
- package/cortex-v3/zk/zk-tree.mjs +256 -0
- package/cortex-v3/zk/zk-tree.test.mjs +419 -0
- package/deploy/docker-compose.local-production-simulation.yml +36 -0
- package/docs/browser-extension-install.md +8 -6
- package/docs/client-connectors.md +15 -11
- package/docs/developer-ecosystem.md +15 -13
- package/docs/enigma-memory-ready-conformance.md +11 -9
- package/docs/install-anywhere.md +61 -28
- package/docs/installers-and-desktop.md +8 -7
- package/docs/novelty-invention-candidates.md +161 -161
- package/docs/proof-network-claim-boundaries.md +320 -318
- package/examples/01-quickstart-agent/index.mjs +49 -0
- package/examples/01_agent_memory_quickstart.mjs +57 -0
- package/examples/02-multi-agent-swarm/index.mjs +57 -0
- package/examples/02_cross_model_passport.mjs +64 -0
- package/examples/03-langchain-memory/index.mjs +41 -0
- package/examples/03_poseidon_commitment_verification.mjs +71 -0
- package/examples/04-python-trading-agent/trader.py +49 -0
- package/examples/README.md +27 -0
- package/examples/ci/github-actions.yml +7 -2
- package/package.json +142 -11
- package/packages/adapters/PACKAGE_CONTRACT.md +1 -1
- package/packages/connectors/src/index.js +196 -4
- package/packages/connectors/swarm-router.mjs +168 -0
- package/packages/core/src/index.js +248 -1
- package/packages/core/src/version.mjs +7 -0
- package/packages/dev-tools/package.json +19 -0
- package/packages/dev-tools/src/index.js +4 -0
- package/packages/dev-tools/src/memory-benchmark-suite.js +112 -0
- package/packages/dev-tools/src/swarm-simulator.js +101 -0
- package/packages/dev-tools/src/vault-inspector.js +114 -0
- package/packages/dev-tools/src/vector-benchmark.js +100 -0
- package/packages/developer-platform/src/access-credentials.js +341 -0
- package/packages/developer-platform/src/http.js +132 -0
- package/packages/developer-platform/src/index.js +4 -0
- package/packages/developer-platform/src/usage-http.js +60 -0
- package/packages/developer-platform/src/usage.js +295 -0
- package/packages/enclave-runtime/attestation.mjs +159 -0
- package/packages/enclave-runtime/index.mjs +47 -0
- package/packages/enclave-runtime/session-manager.mjs +253 -0
- package/packages/enclave-runtime/zeroization-proof.mjs +227 -0
- package/packages/enigma-reflex/package.json +14 -0
- package/packages/enigma-reflex/src/index.js +204 -0
- package/packages/enigma-reflex/training/generate-dataset.mjs +40 -0
- package/packages/enigma-reflex/training/requirements.txt +8 -0
- package/packages/enigma-reflex/training/train.py +314 -0
- package/packages/enigma-weave/LICENSE +22 -0
- package/packages/enigma-weave/UPSTREAM.json +21 -0
- package/packages/enigma-weave/package.json +14 -0
- package/packages/enigma-weave/src/index.js +286 -0
- package/packages/hosted-cloud/src/index.js +80 -5
- package/packages/importers/src/index.js +432 -0
- package/packages/inference-runtime/src/browser.js +401 -0
- package/packages/inference-runtime/src/chat.js +265 -0
- package/packages/inference-runtime/src/code.js +407 -0
- package/packages/inference-runtime/src/contracts.js +162 -0
- package/packages/inference-runtime/src/http.js +232 -0
- package/packages/inference-runtime/src/image.js +186 -0
- package/packages/inference-runtime/src/index.js +10 -0
- package/packages/inference-runtime/src/model-router.js +320 -0
- package/packages/inference-runtime/src/platform.js +125 -0
- package/packages/inference-runtime/src/privacy.js +400 -0
- package/packages/inference-runtime/src/video.js +253 -0
- package/packages/mcp-server/README.md +22 -6
- package/packages/mcp-server/bin/enigma-mcp.mjs +2 -1
- package/packages/mcp-server/src/index.js +1418 -105
- package/packages/mcp-server/src/oauth.js +561 -0
- package/packages/mcp-server/src/private-handoff.js +84 -0
- package/packages/mcp-server/src/remote-http.js +273 -0
- package/packages/mcp-server/src/remote-policy.js +72 -0
- package/packages/mcp-server/swarm-bridge.mjs +361 -0
- package/packages/mesh/index.d.ts +283 -0
- package/packages/mesh/package.json +23 -0
- package/packages/mesh/src/crypto.js +189 -0
- package/packages/mesh/src/federation-packets.js +353 -0
- package/packages/mesh/src/gossip.js +311 -0
- package/packages/mesh/src/index.js +6 -0
- package/packages/mesh/src/protocol.js +255 -0
- package/packages/mesh/src/router.js +279 -0
- package/packages/mesh/src/transport.js +306 -0
- package/packages/passport/src/index.js +426 -1
- package/packages/private-economy/src/credits-http.js +100 -0
- package/packages/private-economy/src/credits.js +447 -0
- package/packages/private-economy/src/index.js +5 -0
- package/packages/private-economy/src/payments-http.js +120 -0
- package/packages/private-economy/src/payments.js +509 -0
- package/packages/private-economy/src/x402.js +346 -0
- package/packages/proof-network/PACKAGE_CONTRACT.md +21 -0
- package/packages/rag/index.d.ts +182 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/THIRD_PARTY_LICENSES.txt +207 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/config.json +25 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/onnx/model_quantized.onnx +0 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/sha256-manifest.json +28 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer.json +30686 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer_config.json +15 -0
- package/packages/rag/package.json +27 -0
- package/packages/rag/src/blinded-search.js +109 -0
- package/packages/rag/src/bm25.js +169 -0
- package/packages/rag/src/embeddings.js +459 -0
- package/packages/rag/src/hybrid.js +76 -0
- package/packages/rag/src/index.js +38 -0
- package/packages/rag/src/reranker.js +61 -0
- package/packages/rag/src/research.js +107 -0
- package/packages/rag/src/vector-store.js +430 -0
- package/packages/rag/src/verify-model-artifacts.mjs +4 -0
- package/packages/sdk/index.d.ts +760 -0
- package/packages/sdk/package.json +33 -0
- package/packages/sdk/python/README.md +24 -0
- package/packages/sdk/python/enigma_sdk.py +250 -0
- package/packages/sdk/python/pyproject.toml +34 -0
- package/packages/sdk/python/requirements.txt +1 -0
- package/packages/sdk/python/setup.py +20 -0
- package/packages/sdk/src/federation/capability-grant.js +389 -0
- package/packages/sdk/src/federation/federation-router.js +360 -0
- package/packages/sdk/src/federation/ghostmesh-bridge.js +497 -0
- package/packages/sdk/src/federation/index.js +3 -0
- package/packages/sdk/src/index.js +1796 -0
- package/packages/sdk/src/intelligence/contradiction.js +337 -0
- package/packages/sdk/src/intelligence/decision-engine.js +155 -0
- package/packages/sdk/src/intelligence/index.js +4 -0
- package/packages/sdk/src/intelligence/ontology.js +122 -0
- package/packages/sdk/src/intelligence/temporal.js +123 -0
- package/packages/sdk/src/market-client.js +142 -0
- package/packages/sdk/src/mesh-client.js +110 -0
- package/packages/sdk/src/middleware/index.js +3 -0
- package/packages/sdk/src/middleware/langchain.js +159 -0
- package/packages/sdk/src/middleware/llamaindex.js +101 -0
- package/packages/sdk/src/middleware/vercel-ai.js +112 -0
- package/packages/sdk/src/rag-client.js +85 -0
- package/packages/sdk/src/swarm-orchestrator.js +260 -0
- package/packages/settlement/PACKAGE_CONTRACT.md +1 -1
- package/packages/snapcompact/THIRD_PARTY_LICENSES.txt +40 -0
- package/packages/snapcompact/assets/8x13-latin1.bdf +3837 -0
- package/packages/snapcompact/index.d.ts +284 -0
- package/packages/snapcompact/package.json +25 -0
- package/packages/snapcompact/src/index.js +716 -0
- package/packages/storage/PACKAGE_CONTRACT.md +1 -1
- package/packages/terminal-console/animations.mjs +240 -0
- package/packages/terminal-console/auto-anchor.mjs +220 -0
- package/packages/terminal-console/banner.mjs +91 -0
- package/packages/terminal-console/commands.mjs +459 -0
- package/packages/terminal-console/delegation.mjs +152 -0
- package/packages/terminal-console/index.mjs +5 -0
- package/packages/terminal-console/outbox.mjs +143 -0
- package/packages/terminal-console/phantom-bridge.mjs +637 -0
- package/packages/terminal-console/repl.mjs +136 -0
- package/packages/terminal-console/signer-store.mjs +130 -0
- package/packages/terminal-console/solana-rpc.mjs +214 -0
- package/packages/terminal-console/solana-transport.mjs +189 -0
- package/packages/terminal-tui/dashboard.mjs +214 -0
- package/packages/terminal-tui/index.mjs +28 -0
- package/packages/terminal-tui/merkle-tree-renderer.mjs +268 -0
- package/packages/terminal-tui/telemetry-hud.mjs +137 -0
- package/packages/vault/index.d.ts +449 -0
- package/packages/vault/package.json +27 -0
- package/packages/vault/src/e2ee.mjs +393 -0
- package/packages/vault/src/enclave.js +481 -0
- package/packages/vault/src/erasure.js +207 -0
- package/packages/vault/src/index.js +1018 -155
- package/packages/vault/src/persistence.js +307 -0
- package/packages/vault/src/poseidon.js +354 -0
- package/packages/vault/src/receipt.js +459 -0
- package/scripts/benchmark-optical-context.mjs +166 -0
- package/scripts/bootstrap-enigma.mjs +502 -0
- package/scripts/build-edge-backend-workers.mjs +20 -5
- package/scripts/build-goal-completion-audit.mjs +72 -25
- package/scripts/build-hosted-api-key-lifecycle.mjs +26 -8
- package/scripts/build-hosted-customer-lifecycle.mjs +20 -3
- package/scripts/build-hosted-probe-worker.mjs +19 -4
- package/scripts/build-installer-assets.mjs +41 -21
- package/scripts/build-operator-evidence-starter.mjs +59 -1
- package/scripts/build-production-backend-env-kit.mjs +2 -0
- package/scripts/build-production-unblocker.mjs +3 -0
- package/scripts/check.mjs +17 -3
- package/scripts/collect-hosted-backend-live-evidence.mjs +49 -12
- package/scripts/install-enigma-local.mjs +18 -5
- package/scripts/release-audit.mjs +65 -109
- package/scripts/release-provenance.mjs +6 -0
- package/scripts/run-backend-readiness-smoke.mjs +112 -10
- package/scripts/scan-secrets.mjs +1 -0
- package/scripts/simulate-production-env.mjs +7 -2
- package/scripts/validate-hosted-backend-live.mjs +112 -1
- package/specs/antibody-pack-v1.schema.json +95 -0
- package/specs/antigen-envelope-v1.schema.json +81 -0
- package/specs/boundary-manifest-v1.schema.json +35 -35
- package/specs/capsule-v1.schema.json +55 -55
- package/specs/claim-boundary-manifest-v1.schema.json +22 -22
- package/specs/claim-ledger-v1.schema.json +291 -0
- package/specs/context-passport-v1.schema.json +59 -0
- package/specs/deletion-tombstone-v1.schema.json +26 -26
- package/specs/evidence-packet-v1.schema.json +177 -0
- package/specs/hosted-backend-live-evidence-v1.schema.json +72 -3
- package/specs/immune-scan-report-v1.schema.json +112 -0
- package/specs/lifecycle-receipt-log-v1.schema.json +67 -0
- package/specs/memory-atom-v1.schema.json +59 -0
- package/specs/memory-event-v1.schema.json +42 -42
- package/specs/passport-v1.schema.json +50 -50
- package/specs/proof-of-non-use-v1.schema.json +65 -0
- package/specs/quarantine-record-v1.schema.json +126 -0
- package/specs/receipt-v1.schema.json +61 -61
- package/specs/state-checkpoint-v1.schema.json +37 -37
- package/specs/trust-bundle-v1.schema.json +56 -56
- package/specs/trust-card-v1.schema.json +119 -0
- package/docs/proof-network-launch-plan.md +0 -421
- package/packages/metering/PACKAGE_CONTRACT.md +0 -20
- package/scripts/build-ai-orchestration-plan.mjs +0 -248
package/apps/cli/bin/enigma.mjs
CHANGED
|
@@ -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 {
|
|
9
|
-
import {
|
|
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 {
|
|
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
|
|
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
|
|
339
|
-
|
|
340
|
-
|
|
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
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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
|
|
804
|
-
if (
|
|
805
|
-
|
|
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
|
-
|
|
854
|
-
const
|
|
855
|
-
|
|
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
|
|
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 = [
|
|
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: {
|
|
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
|
|
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
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
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, {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
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)
|
|
1583
|
-
|
|
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
|
|
1662
|
-
|
|
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
|
-
})
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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.
|
|
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
|
|
2044
|
-
const
|
|
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
|
|
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
|
-
|
|
2088
|
-
|
|
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
|
|
2093
|
-
const
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
const
|
|
2097
|
-
|
|
2098
|
-
|
|
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
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
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
|
|
2431
|
+
return 0;
|
|
2163
2432
|
}
|
|
2164
2433
|
|
|
2165
|
-
export async function
|
|
2166
|
-
const
|
|
2167
|
-
const
|
|
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
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
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
|
-
|
|
2223
|
-
if (vault) await persistState(bundlePath, vault, { passphrase: getFlag(flags, ['passphrase']) });
|
|
2852
|
+
|
|
2224
2853
|
const out = getFlag(flags, ['out']);
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
3539
|
+
let source;
|
|
2566
3540
|
try {
|
|
2567
|
-
|
|
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
|
-
|
|
2572
|
-
const
|
|
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
|
|
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
|
|
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
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
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
|
|
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
|
|
3137
|
-
'one-command
|
|
3138
|
-
'
|
|
3139
|
-
'
|
|
3140
|
-
'
|
|
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
|
-
|
|
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) {
|