enigma-memory 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +8 -0
  2. package/apps/cli/bin/enigma.mjs +362 -10
  3. package/deploy/SIMULATION.md +152 -0
  4. package/deploy/docker-compose.local-production-simulation.yml +237 -0
  5. package/deploy/docker-compose.production.example.yml +19 -0
  6. package/deploy/kms-mock.mjs +64 -0
  7. package/deploy/nginx.local-production-simulation.conf +33 -0
  8. package/deploy/siem-mock.mjs +50 -0
  9. package/docs/benchmark-attestation-network.md +488 -0
  10. package/docs/benchmark-reproducibility.md +19 -2
  11. package/docs/blockchain-only-mechanisms.md +388 -0
  12. package/docs/client-connectors.md +512 -0
  13. package/docs/demo-proof-network.md +275 -0
  14. package/docs/developer-ecosystem.md +47 -4
  15. package/docs/developer-proof-quickstart.md +325 -0
  16. package/docs/enigma-memory-ready-conformance.md +376 -0
  17. package/docs/enterprise-proof-control-plane.md +365 -0
  18. package/docs/install-anywhere.md +517 -0
  19. package/docs/market-category-narrative.md +398 -0
  20. package/docs/memory-drive-health-model.md +649 -0
  21. package/docs/memory-drive-strategy.md +458 -0
  22. package/docs/memory-passport-standard.md +445 -0
  23. package/docs/novelty-invention-candidates.md +161 -0
  24. package/docs/privacy-ledger-model.md +229 -0
  25. package/docs/proof-network-build-notes.md +240 -0
  26. package/docs/proof-network-claim-boundaries.md +318 -0
  27. package/docs/proof-network-dashboard-spec.md +773 -0
  28. package/docs/proof-network-glossary.md +27 -0
  29. package/docs/proof-network-launch-plan.md +421 -0
  30. package/docs/proof-network-operator-protocol.md +432 -0
  31. package/docs/proof-network-roadmap.md +431 -0
  32. package/docs/proof-network-test-plan.md +216 -0
  33. package/docs/proof-network-threat-model.md +373 -0
  34. package/docs/proof-network.md +257 -0
  35. package/docs/sdk-api.md +132 -10
  36. package/docs/solana-devnet-acceptance.md +226 -0
  37. package/docs/solana-proof-rail.md +453 -0
  38. package/examples/ci/github-actions.yml +6 -3
  39. package/examples/proof-network-anchor.json +37 -0
  40. package/examples/proof-network-attestation.json +35 -0
  41. package/examples/proof-network-grant.json +27 -0
  42. package/examples/proof-network-packet.json +71 -0
  43. package/package.json +42 -3
  44. package/packages/mcp-server/src/index.js +1 -1
  45. package/packages/proof-network/src/index.js +570 -0
  46. package/scripts/build-hosted-api-key-lifecycle.mjs +1 -1
  47. package/scripts/build-hosted-customer-lifecycle.mjs +1 -1
  48. package/scripts/build-installer-assets.mjs +1 -1
  49. package/scripts/build-proof-network-packet.mjs +213 -0
  50. package/scripts/run-standard-memory-benchmarks.mjs +1 -1
  51. package/scripts/simulate-production-env.mjs +210 -0
  52. package/scripts/verify-registry-install.mjs +1 -0
  53. package/scripts/wait-for-backend-ready.mjs +101 -0
  54. package/specs/goal-completion-audit-v1.schema.json +1 -0
  55. package/specs/proof-network-anchor-batch-v1.schema.json +125 -0
  56. package/specs/proof-network-benchmark-attestation-v1.schema.json +103 -0
  57. package/specs/proof-network-capability-grant-v1.schema.json +132 -0
  58. package/specs/proof-network-packet-v1.schema.json +171 -0
package/README.md CHANGED
@@ -31,6 +31,14 @@ enigma setup --connect-installed --overwrite
31
31
 
32
32
  `--connect-installed` is the explicit client-config write path. It skips missing client configs instead of creating every default client config.
33
33
 
34
+ ## Enigma Proof Network
35
+
36
+ Enigma Proof Network is the public proof layer for AI memory: local tools can package privacy-preserving roots, refs, counts, signatures, scoped capability grants, revocations, and benchmark attestations without exposing raw memory, prompts, transcripts, completions, embeddings, tenant names, private keys, provider responses, or provider credentials.
37
+
38
+ The `enigma chain anchor|grant|revoke|attest|verify` commands are local planning and verification commands. They emit public-safe JSON with `transaction_submitted:false` and `raw_memory_on_chain:false`; they do not submit Solana transactions, deploy hosted SaaS, create accounts, or call external providers.
39
+
40
+ Start with the category narrative in [`docs/market-category-narrative.md`](docs/market-category-narrative.md), then read the technical overview in [`docs/proof-network.md`](docs/proof-network.md), use its [Solana role](docs/proof-network.md#solana-role) section for the Solana-ready anchoring boundary, and read [`docs/proof-network-faq.md`](docs/proof-network-faq.md) for claim boundaries.
41
+
34
42
  ## Install once, use everywhere
35
43
 
36
44
  Prerequisites:
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { createHash } from 'node:crypto';
3
3
  import { createServer as createHttpServer } from 'node:http';
4
- import { realpathSync } from 'node:fs';
5
- import { access, mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
4
+ import { constants as fsConstants, realpathSync } from 'node:fs';
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
8
  import { createVault, remember, recall, updateMemory, deleteMemory, exportBundle } from '../../../packages/vault/src/index.js';
@@ -30,6 +30,19 @@ import {
30
30
  createSettlementBatch,
31
31
  verifyServiceSettlementReceipt,
32
32
  } from '../../../packages/settlement/src/index.js';
33
+ import {
34
+ assertNoPrivateProofPayload,
35
+ createBenchmarkAttestation,
36
+ createCapabilityGrant,
37
+ createCapabilityRevocation,
38
+ createProofNetworkAnchorBatch,
39
+ sha256Json as proofNetworkSha256Json,
40
+ validateBenchmarkAttestation,
41
+ validateCapabilityGrant,
42
+ validateCapabilityRevocation,
43
+ validateProofNetworkAnchorBatch,
44
+ validateProofNetworkPacket,
45
+ } from '../../../packages/proof-network/src/index.js';
33
46
 
34
47
  const DEFAULT_BUNDLE = '.enigma/bundle.json';
35
48
  const DEFAULT_TEST_DRIVE_DIR = '.enigma/test-drive';
@@ -561,6 +574,86 @@ function minimumNodeMajor(range) {
561
574
  return match ? Number(match[1]) : 0;
562
575
  }
563
576
 
577
+ function npmUserAgentCheck(userAgent = process.env.npm_config_user_agent) {
578
+ const raw = typeof userAgent === 'string' ? userAgent.trim() : '';
579
+ const npmToken = raw.split(/\s+/).find((token) => token.startsWith('npm/'));
580
+ const version = npmToken ? npmToken.slice(4) : null;
581
+ return {
582
+ ok: true,
583
+ detected: version !== null,
584
+ name: version === null ? null : 'npm',
585
+ version,
586
+ source: version === null ? null : 'npm_config_user_agent',
587
+ };
588
+ }
589
+
590
+ async function statIfExists(path) {
591
+ try {
592
+ return await stat(path);
593
+ } catch (error) {
594
+ if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') return null;
595
+ throw error;
596
+ }
597
+ }
598
+
599
+ async function nearestExistingAncestor(path) {
600
+ let current = resolve(path);
601
+ for (;;) {
602
+ const stats = await statIfExists(current);
603
+ if (stats !== null) return { path: current, stats };
604
+ const parent = dirname(current);
605
+ if (parent === current) return null;
606
+ current = parent;
607
+ }
608
+ }
609
+
610
+ function publicParentDisplay(path, label) {
611
+ const value = String(path);
612
+ if (/^<[^>]+>$/.test(value)) return `<${label}>`;
613
+ const parent = dirname(value);
614
+ return parent === '' ? '.' : publicPathDisplay(parent, label);
615
+ }
616
+
617
+ async function writableVaultPathCheck(bundleInput, displayInput = bundleInput) {
618
+ const bundlePath = resolve(String(bundleInput));
619
+ const parentPath = dirname(bundlePath);
620
+ const targetStats = await statIfExists(bundlePath);
621
+ const nearest = await nearestExistingAncestor(parentPath);
622
+ let ok = false;
623
+ let reason = null;
624
+ let parentExists = false;
625
+ let nearestExistingParent = null;
626
+ if (targetStats?.isDirectory()) {
627
+ reason = 'target_is_directory';
628
+ } else if (nearest === null) {
629
+ reason = 'no_existing_parent';
630
+ } else if (!nearest.stats.isDirectory()) {
631
+ reason = 'nearest_parent_not_directory';
632
+ nearestExistingParent = '<existing-parent-path>';
633
+ } else {
634
+ parentExists = nearest.path === parentPath;
635
+ nearestExistingParent = parentExists ? publicParentDisplay(displayInput, 'bundle-dir') : '<existing-parent-dir>';
636
+ try {
637
+ await access(nearest.path, fsConstants.W_OK);
638
+ ok = true;
639
+ } catch {
640
+ reason = 'parent_not_writable';
641
+ }
642
+ }
643
+ return {
644
+ ok,
645
+ path: publicPathDisplay(displayInput, 'bundle-path'),
646
+ parent: publicParentDisplay(displayInput, 'bundle-dir'),
647
+ parent_exists: parentExists,
648
+ nearest_existing_parent: nearestExistingParent,
649
+ target_exists: targetStats !== null,
650
+ target_is_directory: targetStats?.isDirectory() === true,
651
+ writable: ok,
652
+ reason,
653
+ hint: ok ? null : 'Choose a writable --bundle path or create a writable parent directory.',
654
+ };
655
+ }
656
+
564
657
  async function schemaFiles() {
565
658
  return (await readdir(SPECS_URL)).filter((name) => name.endsWith('.schema.json')).sort();
566
659
  }
@@ -895,10 +988,19 @@ function setupNextCommands(bundleInput, exportDisplay, clients, writeConnectors)
895
988
  `enigma context --bundle ${commandPath(bundleInput)} --query "project context"`,
896
989
  `enigma verify --export ${commandPath(exportDisplay)}`,
897
990
  ];
898
- if (!writeConnectors) commands.push(`enigma connect ${primaryClient} --bundle ${commandPath(bundleInput)}`);
991
+ if (!writeConnectors) commands.push(`enigma connect ${primaryClient} --bundle ${commandPath(bundleInput)} --dry-run`);
899
992
  return commands;
900
993
  }
901
994
 
995
+ function doctorNextCommands(bundleDisplay, client) {
996
+ const clientId = client ?? DEFAULT_SETUP_CLIENTS[0];
997
+ return [
998
+ `enigma setup --bundle ${commandPath(bundleDisplay)}`,
999
+ `enigma doctor --bundle ${commandPath(bundleDisplay)} --client ${clientId}`,
1000
+ `enigma connect ${clientId} --bundle ${commandPath(bundleDisplay)}`,
1001
+ ];
1002
+ }
1003
+
902
1004
  async function setupDoctorChecks(flags, artifacts, clients, displays) {
903
1005
  const packageJson = await readPackageJson();
904
1006
  const requiredNodeMajor = minimumNodeMajor(packageJson.engines?.node);
@@ -925,12 +1027,15 @@ async function setupDoctorChecks(flags, artifacts, clients, displays) {
925
1027
  const doctor = await doctorConnectors({ ...connectorBaseOptions, clientId: client });
926
1028
  connectorClients.push(...doctor.clients);
927
1029
  }
1030
+ const vaultPath = await writableVaultPathCheck(artifacts.bundlePath, displays.bundle);
928
1031
  const checks = {
929
1032
  node: {
930
1033
  ok: requiredNodeMajor === 0 || currentNodeMajor >= requiredNodeMajor,
931
1034
  current: process.versions.node,
932
1035
  required: packageJson.engines?.node ?? null,
933
1036
  },
1037
+ npm: npmUserAgentCheck(),
1038
+ vault_path: vaultPath,
934
1039
  package_bins: {
935
1040
  ok: binEntries.every((entry) => entry.declared && entry.exists),
936
1041
  required: REQUIRED_PACKAGE_BINS,
@@ -1518,7 +1623,7 @@ function testDriveNextCommands(bundleDisplay, crossModelReportDisplay) {
1518
1623
  `enigma status --bundle ${quotedBundle}`,
1519
1624
  `enigma search --bundle ${quotedBundle} --query "local proof bundle"`,
1520
1625
  `enigma demo cross-model --bundle ${quotedBundle} --out ${quotedReport}`,
1521
- 'node scripts/run-memory-benchmarks.mjs',
1626
+ 'enigma setup --overwrite',
1522
1627
  ];
1523
1628
  }
1524
1629
 
@@ -1723,7 +1828,7 @@ export async function testDriveCommand(flags, io) {
1723
1828
  out_dir: outDirInput,
1724
1829
  bundle: bundleInput,
1725
1830
  install_command: `npm install -g ${packageJson.name ?? 'enigma-memory'}`,
1726
- release_target: '0.1.11',
1831
+ release_target: '0.1.13',
1727
1832
  artifacts_written: !dryRun,
1728
1833
  client_configs_written: false,
1729
1834
  client_config_write_required: false,
@@ -1829,10 +1934,11 @@ export async function doctorCommand(flags, io) {
1829
1934
  };
1830
1935
  }));
1831
1936
  const schemas = await schemaFiles();
1937
+ const bundleInput = pathFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE);
1832
1938
  const selectedClient = getFlag(flags, ['client']);
1833
1939
  const doctorOptions = selectedClient && selectedClient !== true
1834
- ? { ...connectorOptions(flags), clientId: String(selectedClient) }
1835
- : { ...connectorOptions(flags), clientId: undefined };
1940
+ ? { ...connectorOptions(flags), clientId: String(selectedClient), redactPaths: true }
1941
+ : { ...connectorOptions(flags), clientId: undefined, redactPaths: true };
1836
1942
  const connectorDoctor = await doctorConnectors(doctorOptions);
1837
1943
  const profile = getClientProfile(String(selectedClient && selectedClient !== true ? selectedClient : 'generic-mcp'), connectorOptions(flags));
1838
1944
  const checks = {
@@ -1841,6 +1947,7 @@ export async function doctorCommand(flags, io) {
1841
1947
  current: process.versions.node,
1842
1948
  required: packageJson.engines?.node ?? null,
1843
1949
  },
1950
+ npm: npmUserAgentCheck(),
1844
1951
  package_bins: {
1845
1952
  ok: binEntries.every((entry) => entry.declared && entry.exists),
1846
1953
  required: REQUIRED_PACKAGE_BINS,
@@ -1851,8 +1958,9 @@ export async function doctorCommand(flags, io) {
1851
1958
  bundle_default_path: {
1852
1959
  ok: DEFAULT_BUNDLE === '.enigma/bundle.json',
1853
1960
  path: DEFAULT_BUNDLE,
1854
- resolved: resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE))),
1961
+ resolved: publicPathDisplay(resolve(bundleInput), 'bundle-path'),
1855
1962
  },
1963
+ vault_path: await writableVaultPathCheck(resolve(bundleInput), publicPathDisplay(bundleInput, 'bundle-path')),
1856
1964
  schemas: {
1857
1965
  ok: schemas.length > 0,
1858
1966
  count: schemas.length,
@@ -1870,11 +1978,14 @@ export async function doctorCommand(flags, io) {
1870
1978
  ok,
1871
1979
  node: checks.node,
1872
1980
  package_bins: checks.package_bins,
1981
+ npm: checks.npm,
1982
+ vault_path: checks.vault_path,
1873
1983
  bundle_default_path: checks.bundle_default_path,
1874
1984
  schema_count: checks.schemas.count,
1875
1985
  schemas: checks.schemas,
1876
1986
  mcp_command_name: checks.mcp_command_name.command,
1877
1987
  connectors: checks.connectors,
1988
+ next_commands: doctorNextCommands(checks.vault_path.path, String(selectedClient && selectedClient !== true ? selectedClient : 'generic-mcp')),
1878
1989
  checks,
1879
1990
  }, io);
1880
1991
  return ok ? 0 : 1;
@@ -2088,6 +2199,229 @@ function integerFlag(flags, names, label = names[0], fallback = undefined) {
2088
2199
  return number;
2089
2200
  }
2090
2201
 
2202
+ function flagValues(flags, names) {
2203
+ const values = [];
2204
+ for (const name of names) {
2205
+ const value = getFlag(flags, [name]);
2206
+ const entries = Array.isArray(value) ? value : [value];
2207
+ for (const entry of entries) {
2208
+ if (entry === undefined || entry === true || entry === '') continue;
2209
+ for (const item of String(entry).split(',')) {
2210
+ const trimmed = item.trim();
2211
+ if (trimmed) values.push(trimmed);
2212
+ }
2213
+ }
2214
+ }
2215
+ return values;
2216
+ }
2217
+
2218
+ function chainWriteOrPrint(flags, io, artifact, summary) {
2219
+ if (flags.has('out')) {
2220
+ const outPath = resolve(String(requireFlag(flags, ['out'])));
2221
+ return writeJson(outPath, artifact).then(() => {
2222
+ print({
2223
+ ok: true,
2224
+ path: publicPathDisplay(String(requireFlag(flags, ['out'])), 'proof-network-artifact'),
2225
+ transaction_submitted: false,
2226
+ raw_memory_on_chain: false,
2227
+ ...summary,
2228
+ }, io);
2229
+ return 0;
2230
+ });
2231
+ }
2232
+ print(artifact, io);
2233
+ return 0;
2234
+ }
2235
+
2236
+ async function sha256PublicFile(path) {
2237
+ const bytes = await readFile(path);
2238
+ try {
2239
+ assertNoPrivateProofPayload(JSON.parse(bytes.toString('utf8')));
2240
+ } catch (error) {
2241
+ if (error instanceof SyntaxError) return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
2242
+ throw new Error('Report file contains private proof payload markers.');
2243
+ }
2244
+ return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
2245
+ }
2246
+
2247
+ function scoreFlags(flags) {
2248
+ const scores = {};
2249
+ for (const value of flagValues(flags, ['score', 'scores', 'metric', 'metrics'])) {
2250
+ const eq = value.indexOf('=');
2251
+ if (eq <= 0) throw new Error('--score values must use name=value.');
2252
+ const key = value.slice(0, eq).trim();
2253
+ const raw = value.slice(eq + 1).trim();
2254
+ if (!key || !raw) throw new Error('--score values must use name=value.');
2255
+ const numeric = Number(raw);
2256
+ scores[key] = Number.isFinite(numeric) && raw !== '' ? numeric : raw;
2257
+ }
2258
+ return scores;
2259
+ }
2260
+
2261
+
2262
+ function assertChainArtifact(validate, artifact) {
2263
+ const result = chainValidationResult(validate, artifact);
2264
+ if (!result.ok) throw new Error(result.errors?.join('; ') || 'Invalid proof-network artifact.');
2265
+ return artifact;
2266
+ }
2267
+ function chainValidationResult(validate, artifact) {
2268
+ assertNoPrivateProofPayload(artifact);
2269
+ const result = validate(artifact);
2270
+ if (result === false) return { ok: false };
2271
+ if (result && typeof result === 'object') return { ok: result.ok !== false, ...result };
2272
+ return { ok: true };
2273
+ }
2274
+
2275
+ function chainArtifactValidator(artifact) {
2276
+ const schema = String(artifact?.schema ?? artifact?.type ?? artifact?.artifact_type ?? '');
2277
+ if (schema === 'enigma.proof_network.anchor_batch.v1') return [schema, validateProofNetworkAnchorBatch];
2278
+ if (schema === 'enigma.proof_network.capability_grant.v1') return [schema, validateCapabilityGrant];
2279
+ if (schema === 'enigma.proof_network.capability_revocation.v1') return [schema, validateCapabilityRevocation];
2280
+ if (schema === 'enigma.proof_network.benchmark_attestation.v1') return [schema, validateBenchmarkAttestation];
2281
+ if (schema === 'enigma.proof_network.packet.v1') return [schema, validateProofNetworkPacket];
2282
+ throw new Error(`Unsupported proof-network artifact schema: ${schema || 'missing'}.`);
2283
+ }
2284
+
2285
+ export async function chainAnchorCommand(flags, io) {
2286
+ const roots = flagValues(flags, ['root', 'roots', 'memory-root', 'memoryRoot', 'receipt-root', 'receiptRoot', 'context-root', 'contextRoot', 'memory-commitment-root', 'memoryCommitmentRoot']);
2287
+ if (roots.length === 0) throw new Error('Missing required --root.');
2288
+ const refs = flagValues(flags, ['ref', 'refs', 'public-ref', 'publicRef']);
2289
+ const publicChainRef = getFlag(flags, ['public-chain-ref', 'publicChainRef', 'chain-ref', 'chainRef'], 'solana:local-plan');
2290
+ const batch = createProofNetworkAnchorBatch({
2291
+ roots,
2292
+ root_count: roots.length,
2293
+ commitment_count: roots.length,
2294
+ refs,
2295
+ public_chain_ref: publicChainRef,
2296
+ authority_ref: getFlag(flags, ['authority', 'authority-ref', 'authorityRef']),
2297
+ batch_ref: getFlag(flags, ['batch-ref', 'batchRef']),
2298
+ created_at: getFlag(flags, ['created-at', 'createdAt']),
2299
+ transaction_submitted: false,
2300
+ raw_memory_on_chain: false,
2301
+ });
2302
+ assertChainArtifact(validateProofNetworkAnchorBatch, batch);
2303
+ return chainWriteOrPrint(flags, io, batch, {
2304
+ artifact_type: batch.schema,
2305
+ anchor_batch_id: batch.anchor_batch_id,
2306
+ anchor_batch_hash: batch.anchor_batch_hash ?? proofNetworkSha256Json(batch),
2307
+ });
2308
+ }
2309
+
2310
+ export async function chainGrantCommand(flags, io) {
2311
+ const resourceRefs = flagValues(flags, ['resource-root', 'resource-roots', 'resourceRoot', 'resourceRoots', 'resource-ref', 'resource-refs', 'resourceRef', 'resourceRefs', 'ref', 'refs']);
2312
+ const capability = requireFlag(flags, ['capability', 'capability-id', 'capabilityId'], 'capability');
2313
+ const scope = requireFlag(flags, ['scope', 'scope-ref', 'scopeRef', 'capability-scope', 'capabilityScope'], 'scope');
2314
+ const policyHash = getFlag(flags, ['policy-hash', 'policyHash'], proofNetworkSha256Json({ capability, scope, resource_refs: resourceRefs }));
2315
+ const grant = createCapabilityGrant({
2316
+ issuer_ref: getFlag(flags, ['issuer', 'issuer-ref', 'issuerRef'], 'issuer:local-cli'),
2317
+ subject_ref: requireFlag(flags, ['subject', 'subject-ref', 'subjectRef'], 'subject'),
2318
+ capability,
2319
+ scope,
2320
+ scopes: scope,
2321
+ capability_scope: scope,
2322
+ resource_roots: resourceRefs.length ? resourceRefs : [policyHash],
2323
+ policy_hash: policyHash,
2324
+ expires_at: requireFlag(flags, ['expires-at', 'expiresAt'], 'expires-at'),
2325
+ grant_ref: getFlag(flags, ['grant-ref', 'grantRef']),
2326
+ issued_at: getFlag(flags, ['issued-at', 'issuedAt', 'created-at', 'createdAt']),
2327
+ transaction_submitted: false,
2328
+ raw_memory_on_chain: false,
2329
+ });
2330
+ assertChainArtifact(validateCapabilityGrant, grant);
2331
+ return chainWriteOrPrint(flags, io, grant, {
2332
+ artifact_type: grant.schema,
2333
+ capability_grant_id: grant.capability_grant_id,
2334
+ capability_grant_hash: grant.capability_grant_hash ?? proofNetworkSha256Json(grant),
2335
+ });
2336
+ }
2337
+
2338
+ export async function chainRevokeCommand(flags, io) {
2339
+ const grantValue = getFlag(flags, ['grant']);
2340
+ let grantHash = getFlag(flags, ['grant-hash', 'grantHash']);
2341
+ let grantId = getFlag(flags, ['grant-id', 'grantId']);
2342
+ if (grantValue !== undefined && grantValue !== true && grantValue !== '') {
2343
+ const grantString = String(grantValue);
2344
+ if (grantString.startsWith('sha256:')) {
2345
+ grantHash = grantHash ?? grantString;
2346
+ } else {
2347
+ const grantArtifact = await readJson(resolve(grantString));
2348
+ const grantValidation = validateCapabilityGrant(grantArtifact);
2349
+ if (!grantValidation.ok) throw new Error(`Grant artifact is invalid: ${grantValidation.errors.join('; ')}`);
2350
+ grantHash = grantHash ?? grantArtifact.capability_grant_hash;
2351
+ grantId = grantId ?? grantArtifact.capability_grant_id;
2352
+ }
2353
+ }
2354
+ const nullifierValue = getFlag(flags, ['nullifier-root', 'nullifierRoot', 'nullifier-ref', 'nullifierRef', 'nullifier']);
2355
+ const revocation = createCapabilityRevocation({
2356
+ grant_id: grantId,
2357
+ grant_hash: grantHash ?? requireFlag(flags, ['grant-hash', 'grantHash'], 'grant-hash'),
2358
+ reason_ref: requireFlag(flags, ['reason', 'revocation-reason', 'revocationReason'], 'reason'),
2359
+ revocation_reason: getFlag(flags, ['reason', 'revocation-reason', 'revocationReason']),
2360
+ revocation_ref: getFlag(flags, ['revocation-ref', 'revocationRef']),
2361
+ nullifier_root: nullifierValue && String(nullifierValue).startsWith('sha256:') ? nullifierValue : undefined,
2362
+ nullifier_ref: nullifierValue && !String(nullifierValue).startsWith('sha256:') ? nullifierValue : undefined,
2363
+ revoked_at: getFlag(flags, ['revoked-at', 'revokedAt']),
2364
+ transaction_submitted: false,
2365
+ raw_memory_on_chain: false,
2366
+ });
2367
+ assertChainArtifact(validateCapabilityRevocation, revocation);
2368
+ return chainWriteOrPrint(flags, io, revocation, {
2369
+ artifact_type: revocation.schema,
2370
+ capability_revocation_id: revocation.capability_revocation_id,
2371
+ capability_revocation_hash: revocation.capability_revocation_hash ?? proofNetworkSha256Json(revocation),
2372
+ });
2373
+ }
2374
+
2375
+ export async function chainAttestCommand(flags, io) {
2376
+ const reportHash = getFlag(flags, ['report-hash', 'reportHash']);
2377
+ const reportFile = getFlag(flags, ['report-file', 'reportFile']);
2378
+ const resolvedReportHash = reportHash || (reportFile ? await sha256PublicFile(resolve(String(reportFile))) : undefined);
2379
+ if (!resolvedReportHash) throw new Error('Missing required --report-hash or --report-file.');
2380
+ const scores = scoreFlags(flags);
2381
+ const metricsHash = getFlag(flags, ['metrics-hash', 'metricsHash'], proofNetworkSha256Json({ scores }));
2382
+ const attestation = createBenchmarkAttestation({
2383
+ report_hash: resolvedReportHash,
2384
+ report_file_hash: resolvedReportHash,
2385
+ dataset_ref: requireFlag(flags, ['dataset-ref', 'datasetRef', 'dataset-manifest', 'datasetManifest'], 'dataset-ref'),
2386
+ runner_ref: requireFlag(flags, ['runner-ref', 'runnerRef'], 'runner-ref'),
2387
+ package_ref: requireFlag(flags, ['package-ref', 'packageRef'], 'package-ref'),
2388
+ metrics: scores,
2389
+ metrics_hash: metricsHash,
2390
+ attestation_ref: getFlag(flags, ['attestation-ref', 'attestationRef']),
2391
+ created_at: getFlag(flags, ['created-at', 'createdAt']),
2392
+ transaction_submitted: false,
2393
+ raw_memory_on_chain: false,
2394
+ });
2395
+ assertChainArtifact(validateBenchmarkAttestation, attestation);
2396
+ return chainWriteOrPrint(flags, io, attestation, {
2397
+ artifact_type: attestation.schema,
2398
+ benchmark_attestation_id: attestation.benchmark_attestation_id,
2399
+ benchmark_attestation_hash: attestation.benchmark_attestation_hash ?? proofNetworkSha256Json(attestation),
2400
+ });
2401
+ }
2402
+
2403
+ export async function chainVerifyCommand(flags, io, positionalFile = undefined) {
2404
+ const inPath = resolve(String(requireFileArg(flags, ['file', 'in'], positionalFile, 'file')));
2405
+ const artifact = await readJson(inPath);
2406
+ const [schema, validate] = chainArtifactValidator(artifact);
2407
+ let result;
2408
+ try {
2409
+ result = chainValidationResult(validate, artifact);
2410
+ } catch (error) {
2411
+ result = { ok: false, error: { code: 'PROOF_NETWORK_INVALID', message: error.message } };
2412
+ }
2413
+ print({
2414
+ ok: result.ok === true,
2415
+ artifact_type: schema,
2416
+ artifact_hash: proofNetworkSha256Json(artifact),
2417
+ transaction_submitted: false,
2418
+ raw_memory_on_chain: false,
2419
+ validation: result,
2420
+ }, io);
2421
+ return result.ok === true ? 0 : 1;
2422
+ }
2423
+
2424
+
2091
2425
  export async function meterEventCommand(flags, io) {
2092
2426
  const event = createUsageEvent({
2093
2427
  tenant_id: requireFlag(flags, ['tenant', 'tenant-id', 'tenantId'], 'tenant'),
@@ -2306,6 +2640,11 @@ function usage() {
2306
2640
  'settlement receipt',
2307
2641
  'settlement verify',
2308
2642
  'settlement batch',
2643
+ 'chain anchor',
2644
+ 'chain grant',
2645
+ 'chain revoke',
2646
+ 'chain attest',
2647
+ 'chain verify',
2309
2648
  ],
2310
2649
  connector_options: {
2311
2650
  '--bundle <path>': 'Absolute local Enigma vault bundle path rendered as ENIGMA_BUNDLE.',
@@ -2401,6 +2740,14 @@ function usage() {
2401
2740
  batch: 'enigma settlement batch --receipts <receipts.json> --batch-ref <ref> [--asset <asset>] [--out <file>]',
2402
2741
  boundary: 'Settlement artifacts contain commitment roots, capacity profiles, hashes, refs, prices, and claim boundaries only; no raw memory, prompts, provider responses, credentials, token ROI/profit, decentralization, or provider-invoice savings claim.',
2403
2742
  },
2743
+ chain: {
2744
+ anchor: 'enigma chain anchor --root <sha256:...> [--root <sha256:...>] [--ref <public-ref>] [--authority <public-authority-ref>] [--batch-ref <ref>] [--out <file>]',
2745
+ 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>]',
2746
+ revoke: 'enigma chain revoke --grant-hash <sha256:...> --reason <public-reason-code> [--revocation-ref <public-ref>] [--out <file>]',
2747
+ 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>]',
2748
+ verify: 'enigma chain verify --file <proof-artifact.json>',
2749
+ boundary: 'Proof Network chain commands are local planning commands only. They write public-safe hashes, roots, refs, counts, signatures, and booleans; they do not submit Solana transactions or put raw memory on chain.',
2750
+ },
2404
2751
  relay_gateway_options: {
2405
2752
  '--host <host>': 'Bind host. Defaults to 127.0.0.1.',
2406
2753
  '--port <port>': `Bind port. Defaults to ${DEFAULT_RELAY_PORT} for relay and ${DEFAULT_GATEWAY_PORT} for gateway.`,
@@ -2427,10 +2774,10 @@ export async function main(argv = process.argv.slice(2), io = { stdout: process.
2427
2774
  print(usage(), io);
2428
2775
  return 0;
2429
2776
  }
2430
- const twoPartCommands = ['boundary', 'mcp', 'mesh', 'enterprise', 'capsule', 'relay', 'gateway', 'connect', 'disconnect', 'import', 'native-host', 'meter', 'settlement', 'demo', 'passport'];
2777
+ const twoPartCommands = ['boundary', 'mcp', 'mesh', 'enterprise', 'capsule', 'relay', 'gateway', 'connect', 'disconnect', 'import', 'native-host', 'meter', 'settlement', 'chain', 'demo', 'passport'];
2431
2778
  const flags = parseArgs(twoPartCommands.includes(command) ? argv.slice(2) : argv.slice(1));
2432
2779
  const positionalFile = optionalPositional(argv[2]);
2433
- if ((flags.has('help') || argv.includes('-h')) && (command === 'setup' || command === 'test-drive' || command === 'search' || command === 'status' || (command === 'passport' && subcommand === 'status') || ((command === 'relay' || command === 'gateway') && (subcommand === 'serve' || subcommand === 'demo')) || (command === 'native-host' && (subcommand === 'manifest' || subcommand === 'install-plan')) || (command === 'demo' && subcommand === 'cross-model'))) {
2780
+ if ((command === 'chain' && (!subcommand || subcommand === '--help' || subcommand === '-h' || flags.has('help'))) || ((flags.has('help') || argv.includes('-h')) && (command === 'setup' || command === 'test-drive' || command === 'search' || command === 'status' || (command === 'passport' && subcommand === 'status') || ((command === 'relay' || command === 'gateway') && (subcommand === 'serve' || subcommand === 'demo')) || (command === 'native-host' && (subcommand === 'manifest' || subcommand === 'install-plan')) || (command === 'demo' && subcommand === 'cross-model')))) {
2434
2781
  print(usage(), io);
2435
2782
  return 0;
2436
2783
  }
@@ -2472,6 +2819,11 @@ export async function main(argv = process.argv.slice(2), io = { stdout: process.
2472
2819
  if (command === 'settlement' && subcommand === 'receipt') return await settlementReceiptCommand(flags, io);
2473
2820
  if (command === 'settlement' && subcommand === 'verify') return await settlementVerifyCommand(flags, io);
2474
2821
  if (command === 'settlement' && subcommand === 'batch') return await settlementBatchCommand(flags, io, positionalFile);
2822
+ if (command === 'chain' && subcommand === 'anchor') return await chainAnchorCommand(flags, io);
2823
+ if (command === 'chain' && subcommand === 'grant') return await chainGrantCommand(flags, io);
2824
+ if (command === 'chain' && subcommand === 'revoke') return await chainRevokeCommand(flags, io);
2825
+ if (command === 'chain' && subcommand === 'attest') return await chainAttestCommand(flags, io);
2826
+ if (command === 'chain' && subcommand === 'verify') return await chainVerifyCommand(flags, io, positionalFile);
2475
2827
  if (command === 'native-host' && subcommand === 'install-plan') return await nativeHostInstallPlanCommand(flags, io);
2476
2828
  if (command === 'mesh' && subcommand === 'demo') return await meshDemoCommand(flags, io);
2477
2829
  if (command === 'enterprise' && subcommand === 'demo') return await enterpriseDemoCommand(flags, io);
@@ -0,0 +1,152 @@
1
+ # Enigma Memory — Local Production Simulation
2
+
3
+ **CLAIM BOUNDARY:** This is a `local-simulation` environment. It uses
4
+ self-signed TLS, bind-mounted file "secrets", a mocked KMS, and a mocked SIEM.
5
+ It is **not** a production deployment and must not be used as go-live evidence.
6
+
7
+ ## Public-looking domain (local testing only)
8
+
9
+ The simulation is configured to answer on `sim.enigmamemory.com` so that the
10
+ hosted-backend live validator accepts the non-localhost HTTPS probe URLs. For
11
+ real go-live evidence the operator must point the chosen public domain at the
12
+ deployment's public IP address; the local simulation uses a hosts-file or local
13
+ DNS trick instead.
14
+
15
+ On the host running Docker, map the domain to the loopback interface:
16
+
17
+ ```text
18
+ 127.0.0.1 sim.enigmamemory.com relay.sim.enigmamemory.com gateway.sim.enigmamemory.com
19
+ ```
20
+
21
+ On Linux/macOS add that line to `/etc/hosts`; on Windows add it to
22
+ `C:\Windows\System32\drivers\etc\hosts`. Then the following commands work from
23
+ the host:
24
+
25
+ ```bash
26
+ curl -k https://sim.enigmamemory.com:8443/readyz
27
+ curl -k https://sim.enigmamemory.com:9443/readyz
28
+ ```
29
+
30
+ The tls-proxy service also declares `extra_hosts` entries for the same domain
31
+ names so that containers can resolve them locally when needed.
32
+
33
+ ## Quick start
34
+
35
+ 1. Generate secrets and a self-signed TLS certificate:
36
+
37
+ ```bash
38
+ node scripts/simulate-production-env.mjs
39
+ ```
40
+
41
+ Files are written to `deploy/secrets-simulation/` (gitignored).
42
+
43
+ 2. Start the simulation:
44
+
45
+ ```bash
46
+ docker compose -f deploy/docker-compose.local-production-simulation.yml up --build -d
47
+ ```
48
+
49
+ 3. Wait for the backend to become ready:
50
+
51
+ ```bash
52
+ node scripts/wait-for-backend-ready.mjs
53
+ ```
54
+
55
+ 4. Inspect readiness over HTTPS:
56
+
57
+ ```bash
58
+ curl -k https://localhost:8443/readyz
59
+ curl -k https://localhost:9443/readyz
60
+ curl -k https://sim.enigmamemory.com:8443/readyz
61
+ curl -k https://sim.enigmamemory.com:9443/readyz
62
+ ```
63
+
64
+ ## Stop
65
+
66
+ ```bash
67
+ docker compose -f deploy/docker-compose.local-production-simulation.yml down
68
+ ```
69
+
70
+ To also remove the Postgres volume and mock event data:
71
+
72
+ ```bash
73
+ docker compose -f deploy/docker-compose.local-production-simulation.yml down -v
74
+ ```
75
+
76
+ ## Ports and routes
77
+
78
+ | Service | Public port | Internal port | Health route | Notes |
79
+ |--------|-------------|---------------|--------------|-------|
80
+ | relay | `8443` (HTTPS, loopback) | `8787` | `/readyz`, `/livez` | TLS terminated by `tls-proxy`; also answers on `sim.enigmamemory.com` and `relay.sim.enigmamemory.com` |
81
+ | gateway | `9443` (HTTPS, loopback) | `8797` | `/readyz`, `/livez` | TLS terminated by `tls-proxy`; also answers on `sim.enigmamemory.com` and `gateway.sim.enigmamemory.com` |
82
+ | postgres | not exposed | `5432` | — | Used as the simulated durable store |
83
+ | kms-mock | not exposed | `3000` | `/healthz` | Serves a generated Ed25519 key ref |
84
+ | siem-mock | not exposed | `3000` | `/healthz` | Accepts `POST /events`, writes minimized metadata to `/data/siem-events.jsonl` |
85
+
86
+ ## Verify secret files
87
+
88
+ ```bash
89
+ node scripts/simulate-production-env.mjs --check
90
+ ```
91
+
92
+ This reports whether all required files in `deploy/secrets-simulation/` exist
93
+ and are non-empty.
94
+
95
+ ## Simulation-only behavior
96
+
97
+ - `ENIGMA_OPERATOR_ACCEPTANCE_DECISION` is set to `go` so the readiness
98
+ endpoints can turn green locally. This is **not** real operator acceptance.
99
+ - `ENIGMA_DISABLE_LOCAL_DEMO_FALLBACK` is `true`, but the "external" storage,
100
+ KMS, and SIEM are local mocks.
101
+ - The TLS certificate is self-signed; curl requires `-k`/`--insecure`.
102
+ - The certificate SAN list includes `localhost`, `127.0.0.1`,
103
+ `sim.enigmamemory.com`, `relay.sim.enigmamemory.com`,
104
+ `gateway.sim.enigmamemory.com`, and `*.sim.enigmamemory.com`.
105
+
106
+ ## Collect hosted backend live evidence
107
+
108
+ The simulation can be probed as if it were a public hosted deployment by
109
+ using the public-looking domain `sim.enigmamemory.com`. Because the domain
110
+ has no real DNS record, the collector resolves it to `127.0.0.1` locally and
111
+ accepts the self-signed certificate.
112
+
113
+ 1. Build a simulation operator acceptance packet:
114
+
115
+ ```bash
116
+ node scripts/build-operator-acceptance-packet.mjs \
117
+ --complete-fixture --decision go --packet-id sim-operator-acceptance \
118
+ --tenant enigma-sim --deployment-mode hosted --environment local-simulation \
119
+ --target-regions local --requested-go-live-date 2026-06-25 \
120
+ --evidence-repository https://github.com/enigma-memory/evidence/sim \
121
+ --packet-owner "Simulation Owner" \
122
+ --last-updated 2026-06-25T00:00:00.000Z \
123
+ --owners-json .enigma/sim-owner-overrides.json \
124
+ --evidence-refs .enigma/sim-evidence-overrides.json \
125
+ --out .enigma/sim-operator-acceptance.json --validate
126
+ ```
127
+
128
+ 2. Collect and validate live evidence:
129
+
130
+ ```bash
131
+ node .enigma/collect-sim-evidence.mjs \
132
+ --relay-url https://sim.enigmamemory.com:8443 \
133
+ --gateway-url https://sim.enigmamemory.com:9443 \
134
+ --refs-json .enigma/sim-hosted-refs.json \
135
+ --domain sim.enigmamemory.com --environment-id local-simulation \
136
+ --cloud-provider local --region local --owner enigma-sim \
137
+ --operator-decision go \
138
+ --operator-packet-ref .enigma/sim-operator-acceptance.json \
139
+ --operator-approved-at <iso8601> --operator-approved-by enigma-sim \
140
+ --out .enigma/hosted-backend-live-collection.json \
141
+ --evidence-out .enigma/hosted-backend-live-simulated.json
142
+
143
+ node scripts/validate-hosted-backend-live.mjs \
144
+ --evidence .enigma/hosted-backend-live-simulated.json
145
+ ```
146
+
147
+ The expected result is `status: accepted` with all four probes observed and no
148
+ blockers. The wrapper does not mutate DNS or deploy infrastructure and never
149
+ sends credentials.
150
+
151
+ Never commit `deploy/secrets-simulation/` or `*.pem` files. Both are
152
+ `.gitignore`d.