enigma-memory 0.1.0
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/LICENSE +55 -0
- package/README.md +379 -0
- package/apps/cli/bin/enigma.mjs +1117 -0
- package/apps/desktop/src/app.js +1327 -0
- package/apps/desktop/src/index.html +1373 -0
- package/apps/desktop/src/styles.css +386 -0
- package/apps/gateway/bin/enigma-gateway.mjs +52 -0
- package/apps/gateway/src/server.mjs +1403 -0
- package/apps/native-host/README.md +265 -0
- package/apps/native-host/bin/enigma-native-host.mjs +499 -0
- package/apps/native-host/manifests/com.enigma.native_host.chrome.json +9 -0
- package/apps/native-host/manifests/com.enigma.native_host.edge.json +9 -0
- package/apps/native-host/manifests/com.enigma.native_host.firefox.json +9 -0
- package/apps/relay/bin/enigma-relay.mjs +52 -0
- package/apps/relay/src/server.mjs +1571 -0
- package/apps/verifier/bin/enigma-verify.mjs +240 -0
- package/deploy/docker-compose.production.example.yml +150 -0
- package/deploy/kubernetes/enigma-backend.example.yaml +580 -0
- package/package.json +196 -0
- package/packages/adapters/PACKAGE_CONTRACT.md +47 -0
- package/packages/adapters/src/index.js +183 -0
- package/packages/boundary/src/index.js +468 -0
- package/packages/connectors/src/index.js +533 -0
- package/packages/core/src/index.js +766 -0
- package/packages/enterprise/src/index.js +505 -0
- package/packages/importers/src/index.js +1083 -0
- package/packages/mcp-server/bin/enigma-mcp.mjs +19 -0
- package/packages/mcp-server/src/index.js +1185 -0
- package/packages/mesh/src/index.js +531 -0
- package/packages/metering/PACKAGE_CONTRACT.md +20 -0
- package/packages/metering/src/index.js +246 -0
- package/packages/optimizer/src/index.js +707 -0
- package/packages/passport/src/index.js +651 -0
- package/packages/settlement/PACKAGE_CONTRACT.md +28 -0
- package/packages/settlement/src/index.js +369 -0
- package/packages/storage/PACKAGE_CONTRACT.md +23 -0
- package/packages/storage/src/index.js +392 -0
- package/packages/vault/src/index.js +904 -0
- package/scripts/build-ai-orchestration-plan.mjs +248 -0
- package/scripts/build-cloudflare-pages-release-packet.mjs +287 -0
- package/scripts/build-cloudflare-token-policy.mjs +239 -0
- package/scripts/build-cloudflare-token-request.mjs +314 -0
- package/scripts/build-demo-assets.mjs +263 -0
- package/scripts/build-edge-backend-workers.mjs +353 -0
- package/scripts/build-goal-completion-audit.mjs +398 -0
- package/scripts/build-hosted-probe-worker.mjs +191 -0
- package/scripts/build-operator-acceptance-packet.mjs +254 -0
- package/scripts/build-operator-evidence-starter.mjs +525 -0
- package/scripts/build-production-backend-env-kit.mjs +384 -0
- package/scripts/build-production-dependency-report.mjs +369 -0
- package/scripts/build-production-handoff-packet.mjs +475 -0
- package/scripts/build-production-readiness-manifest.mjs +209 -0
- package/scripts/build-production-status-board.mjs +297 -0
- package/scripts/build-production-storage-migration.mjs +86 -0
- package/scripts/build-production-workplan.mjs +371 -0
- package/scripts/build-review-packet.mjs +594 -0
- package/scripts/check.mjs +419 -0
- package/scripts/cloudflare-ops.mjs +1672 -0
- package/scripts/cloudflare-secret-env.mjs +128 -0
- package/scripts/collect-hosted-backend-live-evidence.mjs +263 -0
- package/scripts/infrastructure-readiness.mjs +944 -0
- package/scripts/install-enigma-local.mjs +270 -0
- package/scripts/memory-optimization-benchmark.mjs +242 -0
- package/scripts/monitor-live-endpoints.mjs +358 -0
- package/scripts/release-audit.mjs +5588 -0
- package/scripts/release-provenance.mjs +250 -0
- package/scripts/run-backend-readiness-smoke.mjs +220 -0
- package/scripts/validate-backup-restore-drill.mjs +166 -0
- package/scripts/validate-cloudflare-credentials.mjs +112 -0
- package/scripts/validate-cloudflare-worker-inspect.mjs +179 -0
- package/scripts/validate-domain-tls.mjs +248 -0
- package/scripts/validate-hosted-backend-live.mjs +319 -0
- package/scripts/validate-incident-drill.mjs +221 -0
- package/scripts/validate-kms-custody.mjs +235 -0
- package/scripts/validate-legal-compliance-approval.mjs +274 -0
- package/scripts/validate-monitoring-alerting.mjs +263 -0
- package/scripts/validate-network-access-policy.mjs +285 -0
- package/scripts/validate-operator-acceptance.mjs +298 -0
- package/scripts/validate-production-manifests.mjs +325 -0
- package/scripts/validate-public-site-security.mjs +252 -0
- package/scripts/validate-security-threat-model.mjs +285 -0
- package/scripts/validate-service-settlement.mjs +256 -0
- package/scripts/validate-support-sla.mjs +190 -0
- package/scripts/validate-tenant-policy-approval.mjs +228 -0
- package/scripts/validate-usage-metering.mjs +224 -0
- package/scripts/validate-whitepaper-claims.mjs +166 -0
- package/specs/boundary-manifest-v1.schema.json +35 -0
- package/specs/capsule-v1.schema.json +55 -0
- package/specs/claim-boundary-manifest-v1.schema.json +22 -0
- package/specs/connector-profile-v1.schema.json +62 -0
- package/specs/deletion-tombstone-v1.schema.json +26 -0
- package/specs/enterprise-policy-v1.schema.json +59 -0
- package/specs/gateway-request-v1.schema.json +32 -0
- package/specs/goal-completion-audit-v1.schema.json +70 -0
- package/specs/hosted-backend-live-evidence-v1.schema.json +147 -0
- package/specs/import-report-v1.schema.json +214 -0
- package/specs/infrastructure-readiness-manifest-v1.schema.json +130 -0
- package/specs/memory-event-v1.schema.json +42 -0
- package/specs/mesh-node-v1.schema.json +126 -0
- package/specs/passport-v1.schema.json +50 -0
- package/specs/production-handoff-packet-v1.schema.json +149 -0
- package/specs/receipt-v1.schema.json +61 -0
- package/specs/relay-record-v1.schema.json +43 -0
- package/specs/state-checkpoint-v1.schema.json +37 -0
- package/specs/trust-bundle-v1.schema.json +56 -0
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EMPTY_MERKLE_ROOT,
|
|
3
|
+
MerkleSet,
|
|
4
|
+
canonicalize,
|
|
5
|
+
generateSigningKeyPair,
|
|
6
|
+
sha256Hex,
|
|
7
|
+
signPayload,
|
|
8
|
+
verifySignature
|
|
9
|
+
} from '../../core/src/index.js';
|
|
10
|
+
|
|
11
|
+
const ED25519 = 'Ed25519';
|
|
12
|
+
const NODE_SCHEMA = 'enigma.mesh_node.v1';
|
|
13
|
+
const CAPSULE_SCHEMA = 'enigma.capsule_manifest.v1';
|
|
14
|
+
const WITNESS_SCHEMA = 'enigma.witness_checkpoint.v1';
|
|
15
|
+
const RELAY_RECORD_SCHEMA = 'enigma.relay_record.v1';
|
|
16
|
+
const FEDERATION_GRANT_SCHEMA = 'enigma.federation_grant.v1';
|
|
17
|
+
const DEFAULT_NOW = '1970-01-01T00:00:00.000Z';
|
|
18
|
+
const DEFAULT_EXPIRY = '2100-01-01T00:00:00.000Z';
|
|
19
|
+
const ROOT_RE = /^sha256:[a-f0-9]{64}$/;
|
|
20
|
+
const HASH_RE = /^[a-f0-9]{64}$/;
|
|
21
|
+
const FORBIDDEN_PLAINTEXT_KEYS = new Set([
|
|
22
|
+
'plaintext',
|
|
23
|
+
'plaintextmemory',
|
|
24
|
+
'plainmemory',
|
|
25
|
+
'cleartext',
|
|
26
|
+
'rawmemory',
|
|
27
|
+
'memoryplaintext',
|
|
28
|
+
'memory',
|
|
29
|
+
'memories',
|
|
30
|
+
'text',
|
|
31
|
+
'content',
|
|
32
|
+
'body',
|
|
33
|
+
'prompt',
|
|
34
|
+
'prompttext',
|
|
35
|
+
'completion',
|
|
36
|
+
'message',
|
|
37
|
+
'messagetext',
|
|
38
|
+
'conversation',
|
|
39
|
+
'conversationtext',
|
|
40
|
+
'transcript',
|
|
41
|
+
'transcripttext'
|
|
42
|
+
]);
|
|
43
|
+
const RELAY_INPUT_KEYS = new Set([
|
|
44
|
+
'record_id',
|
|
45
|
+
'recordId',
|
|
46
|
+
'capsule_id',
|
|
47
|
+
'capsuleId',
|
|
48
|
+
'encrypted_payload_hash',
|
|
49
|
+
'encryptedPayloadHash',
|
|
50
|
+
'payload_hash',
|
|
51
|
+
'payloadHash',
|
|
52
|
+
'opaque_encrypted_record',
|
|
53
|
+
'opaqueEncryptedRecord',
|
|
54
|
+
'encrypted_payload',
|
|
55
|
+
'encryptedPayload',
|
|
56
|
+
'ciphertext',
|
|
57
|
+
'received_at',
|
|
58
|
+
'receivedAt',
|
|
59
|
+
'expires_at',
|
|
60
|
+
'expiresAt'
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
function isPlainRecord(value) {
|
|
64
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function requiredString(value, field) {
|
|
68
|
+
if (typeof value !== 'string' || value.length === 0) throw new TypeError(`${field} is required`);
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function normalizeKeyName(key) {
|
|
73
|
+
return String(key).toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const ENCRYPTED_PAYLOAD_RE = /^(age1|enc:|encrypted:|ciphertext:|jwe:|xchacha20poly1305:|aes256gcm:|sealed:)[A-Za-z0-9+/_=:.~-]+$/i;
|
|
77
|
+
const COMMITMENT_RE = /^(sha256:[a-f0-9]{64}|hmac-sha256:[a-f0-9]{64}|commitment:sha256:[a-f0-9]{64})$/i;
|
|
78
|
+
|
|
79
|
+
function isForbiddenPlaintextKey(key) {
|
|
80
|
+
return FORBIDDEN_PLAINTEXT_KEYS.has(normalizeKeyName(key));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function assertEncryptedEnvelopeOrCommitment(value, field) {
|
|
84
|
+
if (typeof value !== 'string' || value.length === 0) throw new TypeError(`${field} must be an encrypted envelope or commitment`);
|
|
85
|
+
if (!ENCRYPTED_PAYLOAD_RE.test(value) && !COMMITMENT_RE.test(value)) {
|
|
86
|
+
throw new Error(`${field} must be an explicit encrypted envelope or commitment`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function hasPlaintextLeak(value, seen = new WeakSet()) {
|
|
91
|
+
if (value === null || typeof value !== 'object') return false;
|
|
92
|
+
if (seen.has(value)) return false;
|
|
93
|
+
seen.add(value);
|
|
94
|
+
if (Array.isArray(value)) return value.some((item) => hasPlaintextLeak(item, seen));
|
|
95
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
96
|
+
if (isForbiddenPlaintextKey(key) && nested !== undefined && nested !== null && nested !== '') return true;
|
|
97
|
+
if (hasPlaintextLeak(nested, seen)) return true;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function assertNoPlaintext(value, context) {
|
|
103
|
+
if (hasPlaintextLeak(value)) throw new Error(`${context} contains plaintext memory fields`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function assertOnlyRelayFields(record) {
|
|
107
|
+
for (const key of Object.keys(record)) {
|
|
108
|
+
if (!RELAY_INPUT_KEYS.has(key)) throw new Error(`relay record field ${key} is not opaque encrypted metadata`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function rootFromValue(value) {
|
|
113
|
+
return `sha256:${sha256Hex(typeof value === 'string' ? value : canonicalize(value))}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function normalizeRoot(value, field, fallback = EMPTY_MERKLE_ROOT) {
|
|
117
|
+
if (value === undefined || value === null) return fallback;
|
|
118
|
+
if (typeof value !== 'string') throw new TypeError(`${field} must be a sha256 root`);
|
|
119
|
+
if (ROOT_RE.test(value)) return value;
|
|
120
|
+
if (HASH_RE.test(value)) return `sha256:${value}`;
|
|
121
|
+
throw new TypeError(`${field} must be a sha256 root`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function compareExpectedRoot(actual, expected, field, errors, message) {
|
|
125
|
+
if (expected === undefined) return;
|
|
126
|
+
try {
|
|
127
|
+
if (actual !== normalizeRoot(expected, field)) errors.push(message);
|
|
128
|
+
} catch {
|
|
129
|
+
errors.push(`${field} must be a sha256 root`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function normalizeHashableRoot(options, names, field) {
|
|
134
|
+
for (const name of names) {
|
|
135
|
+
if (options[name] !== undefined) return normalizeRoot(options[name], field);
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function normalizeEncryptedPayloadHash(options) {
|
|
141
|
+
const encrypted = options.encrypted_payload ?? options.encryptedPayload ?? options.opaque_encrypted_record ?? options.opaqueEncryptedRecord ?? options.ciphertext;
|
|
142
|
+
if (encrypted !== undefined) assertEncryptedEnvelopeOrCommitment(encrypted, 'encrypted_payload');
|
|
143
|
+
const explicit = normalizeHashableRoot(options, ['encrypted_payload_hash', 'encryptedPayloadHash', 'payload_hash', 'payloadHash'], 'encrypted_payload_hash');
|
|
144
|
+
if (explicit !== undefined) return explicit;
|
|
145
|
+
if (encrypted === undefined) throw new TypeError('encrypted_payload_hash is required');
|
|
146
|
+
return rootFromValue(encrypted);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function arrayOfStrings(value, field, fallback = []) {
|
|
150
|
+
if (value === undefined) return fallback;
|
|
151
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string' || item.length === 0)) {
|
|
152
|
+
throw new TypeError(`${field} must be an array of strings`);
|
|
153
|
+
}
|
|
154
|
+
return [...value];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function dateString(value, field, fallback) {
|
|
158
|
+
const string = requiredString(value ?? fallback, field);
|
|
159
|
+
if (Number.isNaN(Date.parse(string))) throw new TypeError(`${field} must be an ISO date string`);
|
|
160
|
+
return string;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function signaturePayload(record) {
|
|
164
|
+
const { signature, descriptor_hash, capsule_hash, witness_hash, grant_hash, ...unsigned } = record;
|
|
165
|
+
return unsigned;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function signerFrom(options, fallbackNode) {
|
|
169
|
+
const node = options.node ?? fallbackNode;
|
|
170
|
+
if (node?.privateKey) {
|
|
171
|
+
return {
|
|
172
|
+
key_id: node.signer?.key_id ?? node.trust_descriptor?.key?.key_id,
|
|
173
|
+
privateKey: node.privateKey,
|
|
174
|
+
publicKey: node.publicKey ?? node.trust_descriptor?.key?.public_key,
|
|
175
|
+
node_id: node.node_id
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
key_id: options.key_id ?? options.keyId ?? options.signer?.key_id,
|
|
180
|
+
privateKey: options.privateKey,
|
|
181
|
+
publicKey: options.publicKey,
|
|
182
|
+
node_id: options.node_id ?? options.nodeId
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function createSignature(unsigned, signer) {
|
|
187
|
+
if (signer.privateKey === undefined) throw new TypeError('privateKey is required');
|
|
188
|
+
const key_id = requiredString(signer.key_id, 'signer.key_id');
|
|
189
|
+
return { alg: ED25519, key_id, value: signPayload({ payload: unsigned, privateKey: signer.privateKey }) };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function resolvePublicKey(keyId, options = {}) {
|
|
193
|
+
if (options.publicKey !== undefined) return options.publicKey;
|
|
194
|
+
const node = options.node;
|
|
195
|
+
if (node?.signer?.key_id === keyId && node.publicKey !== undefined) return node.publicKey;
|
|
196
|
+
const descriptor = options.trustDescriptor ?? options.issuerDescriptor ?? options.witnessDescriptor ?? options.descriptor;
|
|
197
|
+
if (descriptor?.key?.key_id === keyId) return descriptor.key.public_key;
|
|
198
|
+
const publicKeys = options.publicKeys ?? options.keyring ?? options.keys;
|
|
199
|
+
if (publicKeys instanceof Map) return publicKeys.get(keyId);
|
|
200
|
+
if (typeof publicKeys === 'function') return publicKeys(keyId);
|
|
201
|
+
if (isPlainRecord(publicKeys)) return publicKeys[keyId];
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function verifySignedRecord(record, options, errors) {
|
|
206
|
+
if (!isPlainRecord(record.signature)) {
|
|
207
|
+
errors.push('signature missing');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (record.signature.alg !== ED25519 || typeof record.signature.key_id !== 'string' || typeof record.signature.value !== 'string') {
|
|
211
|
+
errors.push('signature malformed');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const expectedKeyId = options.expectedKeyId ?? options.signerKeyId ?? options.key_id ?? options.keyId;
|
|
215
|
+
if (expectedKeyId !== undefined && record.signature.key_id !== expectedKeyId) errors.push('signer mismatch');
|
|
216
|
+
const publicKey = resolvePublicKey(record.signature.key_id, options);
|
|
217
|
+
if (publicKey === undefined) {
|
|
218
|
+
errors.push(`missing public key for ${record.signature.key_id}`);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (!verifySignature({ payload: signaturePayload(record), signature: record.signature, publicKey })) errors.push('signature mismatch');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function verificationResult(errors, extra = {}) {
|
|
225
|
+
const ok = errors.length === 0;
|
|
226
|
+
return { ok, valid: ok, errors, ...extra };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
function createSignedDescriptor({ keyPair, node_id, role, capabilities, trust_roots, endpoints, created_at }) {
|
|
231
|
+
const unsigned = {
|
|
232
|
+
schema: NODE_SCHEMA,
|
|
233
|
+
node_id,
|
|
234
|
+
role,
|
|
235
|
+
key: { alg: ED25519, key_id: keyPair.key_id, public_key: keyPair.publicKey },
|
|
236
|
+
public_key: keyPair.publicKey,
|
|
237
|
+
capabilities,
|
|
238
|
+
trust_roots,
|
|
239
|
+
endpoints,
|
|
240
|
+
created_at
|
|
241
|
+
};
|
|
242
|
+
const signature = createSignature(unsigned, { key_id: keyPair.key_id, privateKey: keyPair.privateKey });
|
|
243
|
+
const signed = { ...unsigned, signature };
|
|
244
|
+
return { ...signed, descriptor_hash: rootFromValue(signed) };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function createMeshNode(options = {}) {
|
|
248
|
+
const keyPair = options.keyPair ?? options.signingKeyPair ?? generateSigningKeyPair(options.keyOptions ?? {});
|
|
249
|
+
const node_id = options.node_id ?? options.nodeId ?? `node_${sha256Hex(keyPair.publicKey).slice(0, 32)}`;
|
|
250
|
+
const created_at = dateString(options.created_at ?? options.createdAt, 'created_at', DEFAULT_NOW);
|
|
251
|
+
const capabilities = arrayOfStrings(options.capabilities, 'capabilities', ['capsule_manifest', 'witness_checkpoint', 'relay_store', 'federation_grant']);
|
|
252
|
+
const trust_roots = arrayOfStrings(options.trust_roots ?? options.trustRoots, 'trust_roots', []);
|
|
253
|
+
const endpoints = arrayOfStrings(options.endpoints, 'endpoints', []);
|
|
254
|
+
const role = options.role ?? 'mesh_node';
|
|
255
|
+
const trust_descriptor = createSignedDescriptor({ keyPair, node_id, role, capabilities, trust_roots, endpoints, created_at });
|
|
256
|
+
return {
|
|
257
|
+
schema: NODE_SCHEMA,
|
|
258
|
+
node_id,
|
|
259
|
+
signer: { alg: ED25519, key_id: keyPair.key_id },
|
|
260
|
+
publicKey: keyPair.publicKey,
|
|
261
|
+
privateKey: keyPair.privateKey,
|
|
262
|
+
trust_descriptor,
|
|
263
|
+
public_descriptor: trust_descriptor
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function createCapsuleManifest(options = {}) {
|
|
268
|
+
assertNoPlaintext(options, 'capsule manifest');
|
|
269
|
+
const signer = signerFrom(options);
|
|
270
|
+
const issuer = requiredString(options.issuer ?? signer.node_id, 'issuer');
|
|
271
|
+
const holder = requiredString(options.holder ?? options.holder_id ?? options.holderId, 'holder');
|
|
272
|
+
const ownerScope = options.owner_scope_hash ?? options.ownerScopeHash ?? rootFromValue(options.owner_scope ?? options.ownerScope ?? holder);
|
|
273
|
+
const encrypted_payload_hash = normalizeEncryptedPayloadHash(options);
|
|
274
|
+
const unsigned = {
|
|
275
|
+
schema: CAPSULE_SCHEMA,
|
|
276
|
+
capsule_id: options.capsule_id ?? options.capsuleId ?? `cap_${sha256Hex(canonicalize({ issuer, holder, ownerScope, encrypted_payload_hash })).slice(0, 32)}`,
|
|
277
|
+
encrypted_payload_hash,
|
|
278
|
+
receipt_log_root: normalizeRoot(options.receipt_log_root ?? options.receiptLogRoot, 'receipt_log_root'),
|
|
279
|
+
active_set_root: normalizeRoot(options.active_set_root ?? options.activeSetRoot ?? options.active_memory_root ?? options.activeMemoryRoot, 'active_set_root'),
|
|
280
|
+
owner_scope_hash: normalizeRoot(ownerScope, 'owner_scope_hash'),
|
|
281
|
+
issuer,
|
|
282
|
+
holder,
|
|
283
|
+
issued_at: dateString(options.issued_at ?? options.issuedAt, 'issued_at', DEFAULT_NOW),
|
|
284
|
+
expires_at: dateString(options.expires_at ?? options.expiresAt, 'expires_at', DEFAULT_EXPIRY)
|
|
285
|
+
};
|
|
286
|
+
const manifest = { ...unsigned, signature: createSignature(unsigned, signer) };
|
|
287
|
+
return { ...manifest, capsule_hash: rootFromValue(manifest) };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function verifyCapsuleManifest(manifest, options = {}) {
|
|
291
|
+
const errors = [];
|
|
292
|
+
if (!isPlainRecord(manifest)) return verificationResult(['capsule manifest must be an object']);
|
|
293
|
+
if (hasPlaintextLeak(manifest)) errors.push('plaintext memory fields present');
|
|
294
|
+
if (manifest.schema !== CAPSULE_SCHEMA) errors.push('schema mismatch');
|
|
295
|
+
for (const field of ['encrypted_payload_hash', 'receipt_log_root', 'active_set_root', 'owner_scope_hash']) {
|
|
296
|
+
if (!ROOT_RE.test(manifest[field] ?? '')) errors.push(`${field} must be a sha256 root`);
|
|
297
|
+
}
|
|
298
|
+
for (const field of ['capsule_id', 'issuer', 'holder', 'issued_at', 'expires_at']) {
|
|
299
|
+
if (typeof manifest[field] !== 'string' || manifest[field].length === 0) errors.push(`${field} is required`);
|
|
300
|
+
}
|
|
301
|
+
compareExpectedRoot(manifest.receipt_log_root, options.expectedReceiptLogRoot, 'expectedReceiptLogRoot', errors, 'receipt log root mismatch');
|
|
302
|
+
compareExpectedRoot(manifest.active_set_root, options.expectedActiveSetRoot, 'expectedActiveSetRoot', errors, 'active set root mismatch');
|
|
303
|
+
compareExpectedRoot(manifest.owner_scope_hash, options.expectedOwnerScopeHash, 'expectedOwnerScopeHash', errors, 'owner scope mismatch');
|
|
304
|
+
if (options.expectedIssuer !== undefined && manifest.issuer !== options.expectedIssuer) errors.push('issuer mismatch');
|
|
305
|
+
if (options.expectedHolder !== undefined && manifest.holder !== options.expectedHolder) errors.push('holder mismatch');
|
|
306
|
+
const now = options.now === undefined ? undefined : Date.parse(options.now);
|
|
307
|
+
if (now !== undefined && Number.isFinite(now) && Date.parse(manifest.expires_at) <= now) errors.push('capsule expired');
|
|
308
|
+
verifySignedRecord(manifest, options, errors);
|
|
309
|
+
return verificationResult(errors, { capsule_hash: rootFromValue(manifest) });
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function createWitnessCheckpoint(options = {}) {
|
|
313
|
+
assertNoPlaintext(options, 'witness checkpoint');
|
|
314
|
+
const signer = signerFrom(options);
|
|
315
|
+
const witness_node_id = requiredString(options.witness_node_id ?? options.witnessNodeId ?? signer.node_id, 'witness_node_id');
|
|
316
|
+
const subject_node_id = requiredString(options.subject_node_id ?? options.subjectNodeId ?? options.subject ?? 'subject', 'subject_node_id');
|
|
317
|
+
const receipt_log_root = normalizeRoot(options.receipt_log_root ?? options.receiptLogRoot, 'receipt_log_root');
|
|
318
|
+
const active_set_root = normalizeRoot(options.active_set_root ?? options.activeSetRoot ?? options.active_memory_root ?? options.activeMemoryRoot, 'active_set_root');
|
|
319
|
+
const epoch = options.epoch === undefined ? 0 : options.epoch;
|
|
320
|
+
if (!Number.isInteger(epoch) || epoch < 0) throw new TypeError('epoch must be a non-negative integer');
|
|
321
|
+
const previous_witness_hash = normalizeRoot(options.previous_witness_hash ?? options.previousWitnessHash ?? EMPTY_MERKLE_ROOT, 'previous_witness_hash');
|
|
322
|
+
const computed_checkpoint_root = rootFromValue({ subject_node_id, receipt_log_root, active_set_root, epoch, previous_witness_hash });
|
|
323
|
+
const suppliedCheckpointRoot = options.checkpoint_root ?? options.checkpointRoot;
|
|
324
|
+
const checkpoint_root = suppliedCheckpointRoot === undefined
|
|
325
|
+
? computed_checkpoint_root
|
|
326
|
+
: normalizeRoot(suppliedCheckpointRoot, 'checkpoint_root');
|
|
327
|
+
if (checkpoint_root !== computed_checkpoint_root) throw new Error('checkpoint_root does not match checkpoint contents');
|
|
328
|
+
const unsigned = {
|
|
329
|
+
schema: WITNESS_SCHEMA,
|
|
330
|
+
witness_checkpoint_id: options.witness_checkpoint_id ?? options.witnessCheckpointId ?? `wit_${sha256Hex(canonicalize({ witness_node_id, subject_node_id, checkpoint_root })).slice(0, 32)}`,
|
|
331
|
+
witness_node_id,
|
|
332
|
+
subject_node_id,
|
|
333
|
+
epoch,
|
|
334
|
+
checkpoint_root,
|
|
335
|
+
receipt_log_root,
|
|
336
|
+
active_set_root,
|
|
337
|
+
previous_witness_hash,
|
|
338
|
+
issued_at: dateString(options.issued_at ?? options.issuedAt, 'issued_at', DEFAULT_NOW)
|
|
339
|
+
};
|
|
340
|
+
const checkpoint = { ...unsigned, signature: createSignature(unsigned, signer) };
|
|
341
|
+
return { ...checkpoint, witness_hash: rootFromValue(checkpoint) };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export function verifyWitnessCheckpoint(checkpoint, options = {}) {
|
|
345
|
+
const errors = [];
|
|
346
|
+
if (!isPlainRecord(checkpoint)) return verificationResult(['witness checkpoint must be an object']);
|
|
347
|
+
if (hasPlaintextLeak(checkpoint)) errors.push('plaintext memory fields present');
|
|
348
|
+
if (checkpoint.schema !== WITNESS_SCHEMA) errors.push('schema mismatch');
|
|
349
|
+
for (const field of ['checkpoint_root', 'receipt_log_root', 'active_set_root', 'previous_witness_hash']) {
|
|
350
|
+
if (!ROOT_RE.test(checkpoint[field] ?? '')) errors.push(`${field} must be a sha256 root`);
|
|
351
|
+
}
|
|
352
|
+
if (!Number.isInteger(checkpoint.epoch) || checkpoint.epoch < 0) errors.push('epoch must be a non-negative integer');
|
|
353
|
+
const computedCheckpointRoot = rootFromValue({
|
|
354
|
+
subject_node_id: checkpoint.subject_node_id,
|
|
355
|
+
receipt_log_root: checkpoint.receipt_log_root,
|
|
356
|
+
active_set_root: checkpoint.active_set_root,
|
|
357
|
+
epoch: checkpoint.epoch,
|
|
358
|
+
previous_witness_hash: checkpoint.previous_witness_hash
|
|
359
|
+
});
|
|
360
|
+
if (checkpoint.checkpoint_root !== computedCheckpointRoot) errors.push('checkpoint root mismatch');
|
|
361
|
+
compareExpectedRoot(checkpoint.checkpoint_root, options.expectedCheckpointRoot, 'expectedCheckpointRoot', errors, 'checkpoint root mismatch');
|
|
362
|
+
compareExpectedRoot(checkpoint.receipt_log_root, options.expectedReceiptLogRoot, 'expectedReceiptLogRoot', errors, 'receipt log root mismatch');
|
|
363
|
+
compareExpectedRoot(checkpoint.active_set_root, options.expectedActiveSetRoot, 'expectedActiveSetRoot', errors, 'active set root mismatch');
|
|
364
|
+
compareExpectedRoot(checkpoint.previous_witness_hash, options.expectedPreviousWitnessHash, 'expectedPreviousWitnessHash', errors, 'previous witness hash mismatch');
|
|
365
|
+
if (options.minimumEpoch !== undefined && (!Number.isInteger(options.minimumEpoch) || checkpoint.epoch < options.minimumEpoch)) errors.push('epoch continuity mismatch');
|
|
366
|
+
if (options.expectedWitnessNodeId !== undefined && checkpoint.witness_node_id !== options.expectedWitnessNodeId) errors.push('witness node mismatch');
|
|
367
|
+
verifySignedRecord(checkpoint, options, errors);
|
|
368
|
+
return verificationResult(errors, { witness_hash: rootFromValue(checkpoint) });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function createRelayStore(options = {}) {
|
|
372
|
+
const node = options.node;
|
|
373
|
+
return {
|
|
374
|
+
schema: 'enigma.relay_store.v1',
|
|
375
|
+
store_id: options.store_id ?? options.storeId ?? `relay_${sha256Hex(canonicalize({ node_id: node?.node_id ?? 'local', created_at: options.created_at ?? DEFAULT_NOW })).slice(0, 32)}`,
|
|
376
|
+
node_id: options.node_id ?? options.nodeId ?? node?.node_id ?? 'local',
|
|
377
|
+
created_at: dateString(options.created_at ?? options.createdAt, 'created_at', DEFAULT_NOW),
|
|
378
|
+
records: new Map(),
|
|
379
|
+
node
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function pushRelayRecord(store, record = {}) {
|
|
384
|
+
if (!isPlainRecord(store) || !(store.records instanceof Map)) throw new TypeError('relay store is required');
|
|
385
|
+
if (!isPlainRecord(record)) throw new TypeError('relay record must be an object');
|
|
386
|
+
assertNoPlaintext(record, 'relay record');
|
|
387
|
+
assertOnlyRelayFields(record);
|
|
388
|
+
const encrypted_payload_hash = normalizeEncryptedPayloadHash(record);
|
|
389
|
+
const unsigned = {
|
|
390
|
+
schema: RELAY_RECORD_SCHEMA,
|
|
391
|
+
record_id: record.record_id ?? record.recordId ?? `rel_${sha256Hex(canonicalize({ store_id: store.store_id, encrypted_payload_hash })).slice(0, 32)}`,
|
|
392
|
+
store_id: store.store_id,
|
|
393
|
+
capsule_id: record.capsule_id ?? record.capsuleId ?? null,
|
|
394
|
+
encrypted_payload_hash,
|
|
395
|
+
payload_storage: 'hash_only',
|
|
396
|
+
received_at: dateString(record.received_at ?? record.receivedAt, 'received_at', DEFAULT_NOW),
|
|
397
|
+
expires_at: record.expires_at ?? record.expiresAt ? dateString(record.expires_at ?? record.expiresAt, 'expires_at') : null
|
|
398
|
+
};
|
|
399
|
+
const signer = store.node?.privateKey ? signerFrom({}, store.node) : null;
|
|
400
|
+
const relayRecord = signer ? { ...unsigned, signature: createSignature(unsigned, signer) } : unsigned;
|
|
401
|
+
store.records.set(relayRecord.record_id, Object.freeze({ ...relayRecord }));
|
|
402
|
+
return { ...relayRecord, relay_record_id: relayRecord.record_id };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export function pullRelayRecord(store, recordRef) {
|
|
406
|
+
if (!isPlainRecord(store) || !(store.records instanceof Map)) throw new TypeError('relay store is required');
|
|
407
|
+
const record_id = typeof recordRef === 'string' ? recordRef : recordRef?.record_id ?? recordRef?.recordId ?? recordRef?.relay_record_id;
|
|
408
|
+
if (typeof record_id !== 'string' || record_id.length === 0) throw new TypeError('record_id is required');
|
|
409
|
+
const record = store.records.get(record_id) ?? null;
|
|
410
|
+
if (record !== null && hasPlaintextLeak(record)) throw new Error('stored relay record contains plaintext memory fields');
|
|
411
|
+
return record === null ? null : { ...record, relay_record_id: record.record_id };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function createFederationGrant(options = {}) {
|
|
415
|
+
assertNoPlaintext(options, 'federation grant');
|
|
416
|
+
const signer = signerFrom(options);
|
|
417
|
+
const subjects = arrayOfStrings(options.subjects ?? options.subject_ids ?? options.subjectIds, 'subjects');
|
|
418
|
+
const operations = arrayOfStrings(options.operations, 'operations');
|
|
419
|
+
const issuer = requiredString(options.issuer ?? signer.node_id, 'issuer');
|
|
420
|
+
const holder = requiredString(options.holder ?? options.holder_id ?? options.holderId, 'holder');
|
|
421
|
+
const unsigned = {
|
|
422
|
+
schema: FEDERATION_GRANT_SCHEMA,
|
|
423
|
+
grant_id: options.grant_id ?? options.grantId ?? `fgr_${sha256Hex(canonicalize({ issuer, holder, subjects, operations, purpose: options.purpose })).slice(0, 32)}`,
|
|
424
|
+
issuer,
|
|
425
|
+
holder,
|
|
426
|
+
subjects: [...subjects].sort(),
|
|
427
|
+
operations: [...operations].sort(),
|
|
428
|
+
purpose: requiredString(options.purpose, 'purpose'),
|
|
429
|
+
issued_at: dateString(options.issued_at ?? options.issuedAt, 'issued_at', DEFAULT_NOW),
|
|
430
|
+
expires_at: dateString(options.expires_at ?? options.expiresAt, 'expires_at', DEFAULT_EXPIRY)
|
|
431
|
+
};
|
|
432
|
+
const grant = { ...unsigned, signature: createSignature(unsigned, signer) };
|
|
433
|
+
return { ...grant, grant_hash: rootFromValue(grant) };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export function verifyFederationGrant(grant, options = {}) {
|
|
437
|
+
const errors = [];
|
|
438
|
+
if (!isPlainRecord(grant)) return verificationResult(['federation grant must be an object']);
|
|
439
|
+
if (hasPlaintextLeak(grant)) errors.push('plaintext memory fields present');
|
|
440
|
+
if (grant.schema !== FEDERATION_GRANT_SCHEMA) errors.push('schema mismatch');
|
|
441
|
+
if (!Array.isArray(grant.subjects) || grant.subjects.some((item) => typeof item !== 'string' || item.length === 0)) errors.push('subjects must be strings');
|
|
442
|
+
if (!Array.isArray(grant.operations) || grant.operations.some((item) => typeof item !== 'string' || item.length === 0)) errors.push('operations must be strings');
|
|
443
|
+
for (const field of ['grant_id', 'issuer', 'holder', 'purpose', 'issued_at', 'expires_at']) {
|
|
444
|
+
if (typeof grant[field] !== 'string' || grant[field].length === 0) errors.push(`${field} is required`);
|
|
445
|
+
}
|
|
446
|
+
const expectedSubject = options.expectedSubject ?? options.subject;
|
|
447
|
+
if (expectedSubject !== undefined && !grant.subjects?.includes(expectedSubject)) errors.push('subject not granted');
|
|
448
|
+
const expectedOperation = options.expectedOperation ?? options.operation;
|
|
449
|
+
if (expectedOperation !== undefined && !grant.operations?.includes(expectedOperation)) errors.push('operation not granted');
|
|
450
|
+
if (options.expectedPurpose !== undefined && grant.purpose !== options.expectedPurpose) errors.push('purpose mismatch');
|
|
451
|
+
if (options.expectedIssuer !== undefined && grant.issuer !== options.expectedIssuer) errors.push('issuer mismatch');
|
|
452
|
+
if (options.expectedHolder !== undefined && grant.holder !== options.expectedHolder) errors.push('holder mismatch');
|
|
453
|
+
const now = options.now === undefined ? undefined : Date.parse(options.now);
|
|
454
|
+
if (now !== undefined && Number.isFinite(now) && Date.parse(grant.expires_at) <= now) errors.push('grant expired');
|
|
455
|
+
verifySignedRecord(grant, options, errors);
|
|
456
|
+
return verificationResult(errors, { grant_hash: rootFromValue(grant) });
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export function runMeshDemo(options = {}) {
|
|
460
|
+
const node = createMeshNode({ created_at: options.now ?? DEFAULT_NOW });
|
|
461
|
+
const holder = options.holder ?? 'holder:demo';
|
|
462
|
+
const activeSet = new MerkleSet(['mem:hmac-sha256:demo-active-address']);
|
|
463
|
+
const receiptLog = new MerkleSet(['sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa']);
|
|
464
|
+
const active_set_root = activeSet.root();
|
|
465
|
+
const receipt_log_root = receiptLog.root();
|
|
466
|
+
const encrypted_payload = 'age1-encrypted-demo-capsule-only';
|
|
467
|
+
const capsule = createCapsuleManifest({ node, issuer: node.node_id, holder, encrypted_payload, receipt_log_root, active_set_root, owner_scope: { holder, scope: 'demo' } });
|
|
468
|
+
const capsuleVerification = verifyCapsuleManifest(capsule, {
|
|
469
|
+
trustDescriptor: node.trust_descriptor,
|
|
470
|
+
expectedIssuer: node.node_id,
|
|
471
|
+
expectedHolder: holder,
|
|
472
|
+
expectedReceiptLogRoot: receipt_log_root,
|
|
473
|
+
expectedActiveSetRoot: active_set_root,
|
|
474
|
+
now: options.now ?? DEFAULT_NOW
|
|
475
|
+
});
|
|
476
|
+
const witness = createWitnessCheckpoint({ node, witness_node_id: node.node_id, subject_node_id: holder, receipt_log_root, active_set_root, epoch: 1 });
|
|
477
|
+
const witnessVerification = verifyWitnessCheckpoint(witness, {
|
|
478
|
+
trustDescriptor: node.trust_descriptor,
|
|
479
|
+
expectedWitnessNodeId: node.node_id,
|
|
480
|
+
expectedReceiptLogRoot: receipt_log_root,
|
|
481
|
+
expectedActiveSetRoot: active_set_root
|
|
482
|
+
});
|
|
483
|
+
const relayStore = createRelayStore({ node, created_at: options.now ?? DEFAULT_NOW });
|
|
484
|
+
const relayRecord = pushRelayRecord(relayStore, { capsule_id: capsule.capsule_id, opaque_encrypted_record: encrypted_payload });
|
|
485
|
+
let rejected_plaintext = false;
|
|
486
|
+
try {
|
|
487
|
+
pushRelayRecord(relayStore, { plaintext: 'demo memory must never cross the mesh', opaque_encrypted_record: encrypted_payload });
|
|
488
|
+
} catch {
|
|
489
|
+
rejected_plaintext = true;
|
|
490
|
+
}
|
|
491
|
+
const grant = createFederationGrant({ node, issuer: node.node_id, holder, subjects: [holder], operations: ['capsule.pull', 'witness.verify'], purpose: 'demo federation', expires_at: DEFAULT_EXPIRY });
|
|
492
|
+
const grantVerification = verifyFederationGrant(grant, {
|
|
493
|
+
trustDescriptor: node.trust_descriptor,
|
|
494
|
+
expectedIssuer: node.node_id,
|
|
495
|
+
expectedHolder: holder,
|
|
496
|
+
expectedSubject: holder,
|
|
497
|
+
expectedOperation: 'capsule.pull',
|
|
498
|
+
expectedPurpose: 'demo federation',
|
|
499
|
+
now: options.now ?? DEFAULT_NOW
|
|
500
|
+
});
|
|
501
|
+
const artifacts = { descriptor: node.trust_descriptor, capsule, witness, relayRecord, grant };
|
|
502
|
+
const no_plaintext_leakage = !hasPlaintextLeak(artifacts);
|
|
503
|
+
const ok = capsuleVerification.ok && witnessVerification.ok && grantVerification.ok && Boolean(relayRecord.record_id) && rejected_plaintext && no_plaintext_leakage;
|
|
504
|
+
return {
|
|
505
|
+
ok,
|
|
506
|
+
node: { node_id: node.node_id, trust_descriptor: node.trust_descriptor },
|
|
507
|
+
capsule: { ok: capsuleVerification.ok, capsule_id: capsule.capsule_id },
|
|
508
|
+
witness: { ok: witnessVerification.ok, witness_checkpoint_id: witness.witness_checkpoint_id },
|
|
509
|
+
relay: { record_id: relayRecord.record_id, rejected_plaintext },
|
|
510
|
+
federation: { ok: grantVerification.ok, grant_id: grant.grant_id },
|
|
511
|
+
capsule_verification_ok: capsuleVerification.ok,
|
|
512
|
+
witness_verification_ok: witnessVerification.ok,
|
|
513
|
+
relay_record_id: relayRecord.record_id,
|
|
514
|
+
federation_grant_verification_ok: grantVerification.ok,
|
|
515
|
+
no_plaintext_leakage
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
export default {
|
|
520
|
+
createMeshNode,
|
|
521
|
+
createCapsuleManifest,
|
|
522
|
+
verifyCapsuleManifest,
|
|
523
|
+
createWitnessCheckpoint,
|
|
524
|
+
verifyWitnessCheckpoint,
|
|
525
|
+
createRelayStore,
|
|
526
|
+
pushRelayRecord,
|
|
527
|
+
pullRelayRecord,
|
|
528
|
+
createFederationGrant,
|
|
529
|
+
verifyFederationGrant,
|
|
530
|
+
runMeshDemo
|
|
531
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# `@enigma-ai/enigma/metering`
|
|
2
|
+
|
|
3
|
+
Deterministic, content-minimized usage metering for the Enigma memory layer.
|
|
4
|
+
|
|
5
|
+
## Contract
|
|
6
|
+
|
|
7
|
+
- Produces `enigma.usage_event.v1` events and `enigma.usage_aggregate.v1` aggregates.
|
|
8
|
+
- Accepts token counts, memory baseline/optimized counts, provider/model identifiers, and public pricing inputs.
|
|
9
|
+
- Computes estimated memory credit from supplied counts only.
|
|
10
|
+
- Does not inspect or store prompts, completions, transcripts, provider responses, decrypted memory, credentials, or secrets.
|
|
11
|
+
- Does not claim provider invoice savings, token ROI/profit/equity, decentralized inference, provider-side deletion, model forgetting, or compliance certification.
|
|
12
|
+
|
|
13
|
+
## Public API
|
|
14
|
+
|
|
15
|
+
- `createUsageEvent(options)`
|
|
16
|
+
- `aggregateUsageEvents(options | events[])`
|
|
17
|
+
|
|
18
|
+
## Evidence boundary
|
|
19
|
+
|
|
20
|
+
A metering event is billing/settlement evidence for Enigma-side usage math. It is not proof that any cloud/provider invoice changed, any token has value, any inference backend is decentralized, or any provider deleted/forgot content.
|