enigma-memory 0.1.15 → 0.1.16
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 +64 -84
- package/apps/cli/bin/enigma.mjs +30 -5
- package/apps/native-host/README.md +20 -0
- package/deploy/SIMULATION.md +34 -38
- package/docs/benchmark-attestation-network.md +2 -2
- package/docs/benchmark-reproducibility.md +21 -9
- package/docs/browser-extension-install.md +9 -6
- package/docs/client-connectors.md +22 -52
- package/docs/demo-proof-network.md +3 -3
- package/docs/developer-ecosystem.md +205 -223
- package/docs/developer-proof-quickstart.md +3 -3
- package/docs/enigma-memory-ready-conformance.md +1 -1
- package/docs/hosted-cloud-product.md +2 -0
- package/docs/install-anywhere.md +61 -66
- package/docs/memory-benchmarks.md +6 -3
- package/docs/proof-network-build-notes.md +2 -2
- package/docs/proof-network.md +32 -7
- package/docs/sdk-api.md +1 -1
- package/docs/solana-devnet-acceptance.md +1 -1
- package/docs/solana-proof-rail.md +1 -1
- package/package.json +7 -1
- package/packages/connectors/src/index.js +13 -0
- package/packages/mcp-server/README.md +22 -0
- package/packages/mcp-server/src/index.js +1 -1
- package/scripts/build-benchmark-proof-release.mjs +106 -5
- package/scripts/build-cloudflare-token-policy.mjs +6 -2
- package/scripts/build-hosted-api-key-lifecycle.mjs +1 -1
- package/scripts/build-hosted-customer-lifecycle.mjs +1 -1
- package/scripts/build-installer-assets.mjs +1 -1
- package/scripts/build-production-handoff-packet.mjs +1 -1
- package/scripts/build-production-unblocker.mjs +1 -1
- package/scripts/build-production-workplan.mjs +3 -1
- package/scripts/build-proof-network-packet.mjs +1 -1
- package/scripts/check.mjs +3 -1
- package/scripts/cloudflare-ops.mjs +35 -0
- package/scripts/collect-hosted-backend-live-evidence.mjs +44 -2
- package/scripts/run-memory-benchmarks.mjs +5 -0
- package/scripts/run-standard-memory-benchmarks.mjs +127 -3
- package/scripts/stage-cloudflare-pages-artifact.mjs +145 -0
|
@@ -167,8 +167,12 @@ export function buildCloudflareTokenPolicy(input = {}) {
|
|
|
167
167
|
const domain = optionalText(input.domain, 'enigmamemory.com');
|
|
168
168
|
const verificationCommands = [
|
|
169
169
|
'npm run cloudflare:ops -- token verify --account-id <account-id>',
|
|
170
|
-
'npm run cloudflare:pages:
|
|
171
|
-
'npm run
|
|
170
|
+
'npm run cloudflare:pages:stage',
|
|
171
|
+
'npm run cloudflare:pages:packet -- --site .enigma/cloudflare-pages/enigmamemory.com --project-name enigma-memory --domain enigmamemory.com --live-url https://enigmamemory.com/ --expect-title "Enigma"',
|
|
172
|
+
'npm run cloudflare:pages:dry-run',
|
|
173
|
+
'npm run cloudflare:pages:deploy',
|
|
174
|
+
'npm run cloudflare:ops -- --cloudflare-env-file <local-secret-file> pages verify --url https://enigmamemory.com/ --project-name enigma-memory --domain enigmamemory.com --cloudflare-live required',
|
|
175
|
+
'npm run production:handoff -- --site .enigma/cloudflare-pages/enigmamemory.com --project-name enigma-memory --domain enigmamemory.com --live-url https://enigmamemory.com/ --expect-title "Enigma"',
|
|
172
176
|
];
|
|
173
177
|
if (includeMode(mode, 'hosted-probe')) {
|
|
174
178
|
verificationCommands.splice(1, 0, 'npm run cloudflare:ops -- workers inspect-probe --name enigma-hosted-probe');
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from '../packages/hosted-cloud/src/index.js';
|
|
14
14
|
|
|
15
15
|
export const HOSTED_API_KEY_LIFECYCLE_PACKET_SCHEMA = HOSTED_CLOUD_API_KEY_LIFECYCLE_PACKET_SCHEMA;
|
|
16
|
-
export const HOSTED_API_KEY_LIFECYCLE_RELEASE_TARGET = '0.1.
|
|
16
|
+
export const HOSTED_API_KEY_LIFECYCLE_RELEASE_TARGET = '0.1.16';
|
|
17
17
|
|
|
18
18
|
const PROVIDED = 'provided';
|
|
19
19
|
const BLOCKED = 'blocked_external_dependency';
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
} from 'enigma-memory/hosted-cloud';
|
|
29
29
|
|
|
30
30
|
export const HOSTED_CUSTOMER_LIFECYCLE_PACKET_SCHEMA = HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PACKET_SCHEMA;
|
|
31
|
-
export const HOSTED_CUSTOMER_LIFECYCLE_RELEASE_TARGET = '0.1.
|
|
31
|
+
export const HOSTED_CUSTOMER_LIFECYCLE_RELEASE_TARGET = '0.1.16';
|
|
32
32
|
|
|
33
33
|
const PROVIDED = 'provided';
|
|
34
34
|
const BLOCKED_MISSING = 'blocked_missing_evidence';
|
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
|
|
7
7
|
export const INSTALLER_ASSET_SCHEMA = 'enigma.installer_assets.v1';
|
|
8
8
|
export const INSTALLER_ASSET_PACKAGE = 'enigma-memory';
|
|
9
|
-
export const INSTALLER_ASSET_VERSION = '0.1.
|
|
9
|
+
export const INSTALLER_ASSET_VERSION = '0.1.16';
|
|
10
10
|
export const INSTALLER_ASSET_GENERATED_AT = '1970-01-01T00:00:00.000Z';
|
|
11
11
|
|
|
12
12
|
const SCRIPT_PATH = fileURLToPath(import.meta.url);
|
|
@@ -224,7 +224,7 @@ function buildNextActions({ projectName, domain, credentialsPresent, pages, infr
|
|
|
224
224
|
actions.push({
|
|
225
225
|
id: 'deploy-current-static-site',
|
|
226
226
|
owner: 'operator-or-ai-with-token',
|
|
227
|
-
command: `npm run cloudflare:ops -- pages deploy --site
|
|
227
|
+
command: `npm run cloudflare:pages:stage && npm run cloudflare:ops -- pages deploy --site .enigma/cloudflare-pages/enigmamemory.com --project-name ${shellArg(projectName)} --execute`,
|
|
228
228
|
evidence: `npm run cloudflare:ops -- pages verify --url ${shellArg(`https://${domain}/`)} --project-name ${shellArg(projectName)} --domain ${shellArg(domain)} --cloudflare-live required`,
|
|
229
229
|
});
|
|
230
230
|
}
|
|
@@ -4,7 +4,7 @@ import { dirname, resolve } from 'node:path';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
|
|
6
6
|
export const PRODUCTION_UNBLOCKER_SCHEMA = 'enigma.production_unblocker.v1';
|
|
7
|
-
export const CURRENT_PUBLIC_PACKAGE_VERSION = '0.1.
|
|
7
|
+
export const CURRENT_PUBLIC_PACKAGE_VERSION = '0.1.15';
|
|
8
8
|
|
|
9
9
|
const STATUS_VALUES = Object.freeze([
|
|
10
10
|
'ready_now',
|
|
@@ -234,6 +234,8 @@ export function buildProductionWorkplan(inputs = {}, options = {}) {
|
|
|
234
234
|
const endpointRefs = missingHostedEndpointRefs(hosted);
|
|
235
235
|
const stateBlockers = hostedStateBlockers(hosted);
|
|
236
236
|
|
|
237
|
+
const finalDependencyCommand = 'npm run production:dependencies -- --goal-audit .enigma/goal-audit-current.json --release-audit .enigma/release-audit-current.json --worker-inspect .enigma/worker-inspect-result-current.json --whitepaper .enigma/whitepaper-claims-current.json --cloudflare-credentials .enigma/cloudflare-credentials-current.json --edge-deploy .enigma/edge-backend-deployment-current.json --edge-live .enigma/edge-backend-bootstrap-live-current.json --storage-bootstrap .enigma/cloudflare-storage-bootstrap-current.json';
|
|
238
|
+
|
|
237
239
|
const phases = [
|
|
238
240
|
makePhase({
|
|
239
241
|
id: 'cloudflare_credentials',
|
|
@@ -309,7 +311,7 @@ export function buildProductionWorkplan(inputs = {}, options = {}) {
|
|
|
309
311
|
owner: 'operator-or-reviewer',
|
|
310
312
|
prerequisites: ['cloudflare_credentials', 'cloudflare_worker_permission', 'hosted_backend_refs', 'operator_acceptance', 'release_gates'],
|
|
311
313
|
blockers: dependencyReport.launch_ready === true ? [] : ['launch_ready is false'],
|
|
312
|
-
commands: [release.next_command, staticSite.next_command, whitepaper.next_command, 'npm run production:goal-audit -- --site <public-site-dir> --domain enigmamemory.com --release-audit .enigma/release-audit-current.json',
|
|
314
|
+
commands: [release.next_command, staticSite.next_command, whitepaper.next_command, 'npm run production:goal-audit -- --site <public-site-dir> --domain enigmamemory.com --release-audit .enigma/release-audit-current.json', finalDependencyCommand],
|
|
313
315
|
evidence: [...release.evidence, ...staticSite.evidence, ...whitepaper.evidence],
|
|
314
316
|
details: { goal_complete: dependencyReport.goal_complete === true, launch_ready: dependencyReport.launch_ready === true },
|
|
315
317
|
}),
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
validateProofNetworkPacket,
|
|
16
16
|
} from '../packages/proof-network/src/index.js';
|
|
17
17
|
|
|
18
|
-
export const PROOF_NETWORK_PACKET_RELEASE_TARGET = '0.1.
|
|
18
|
+
export const PROOF_NETWORK_PACKET_RELEASE_TARGET = '0.1.16';
|
|
19
19
|
|
|
20
20
|
const HASH_RE = /^(?:sha256:)?[a-f0-9]{64}$/iu;
|
|
21
21
|
const SECRET_VALUE_RE = /(?:Bearer\s+[A-Za-z0-9._~+/=-]{12,}|Basic\s+[A-Za-z0-9+/=-]{12,}|-----BEGIN [A-Z ]*PRIVATE KEY-----|https?:\/\/[^\s/@]+:[^\s/@]+@|sk-[A-Za-z0-9_-]{16,}|AKIA[0-9A-Z]{16}|\b(?:raw[\s_-]*memory|plaintext[\s_-]*prompts?|plain[\s_-]*text[\s_-]*prompts?|private[\s_-]*prompts?|provider[\s_-]*responses?|full[\s_-]*transcript|decrypted[\s_-]*memory|credentials?|secrets?|passwords?|private[\s_-]*keys?|api[\s_-]*key[\s_-]*(?:secret|material|value)|api[\s_-]*secrets?|access[\s_-]*tokens?|refresh[\s_-]*tokens?|token[\s_-]*values?|credential[\s_-]*material|tenant[\s_-]*names?)\b)/iu;
|
package/scripts/check.mjs
CHANGED
|
@@ -3,7 +3,9 @@ import path from 'node:path';
|
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
4
4
|
|
|
5
5
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
6
|
-
const packageJsonPath =
|
|
6
|
+
const packageJsonPath = process.env.ENIGMA_CHECK_PACKAGE_JSON_OVERRIDE
|
|
7
|
+
? path.resolve(process.env.ENIGMA_CHECK_PACKAGE_JSON_OVERRIDE)
|
|
8
|
+
: path.join(root, 'package.json');
|
|
7
9
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
8
10
|
const productionRoots = [
|
|
9
11
|
'packages/adapters/src/',
|
|
@@ -6,6 +6,7 @@ import { dirname, join } from 'node:path';
|
|
|
6
6
|
import { pathToFileURL } from 'node:url';
|
|
7
7
|
import { promisify } from 'node:util';
|
|
8
8
|
import { applyCloudflareSecretEnvFileFromArgv, CloudflareSecretEnvError } from './cloudflare-secret-env.mjs';
|
|
9
|
+
import { PUBLIC_SITE_SECURITY_RESULT_SCHEMA, validatePublicSiteSecurity } from './validate-public-site-security.mjs';
|
|
9
10
|
|
|
10
11
|
const execFile = promisify(execFileCallback);
|
|
11
12
|
|
|
@@ -46,6 +47,8 @@ Commands:
|
|
|
46
47
|
|
|
47
48
|
pages deploy --site <dir> --project-name <name> [--execute]
|
|
48
49
|
Without --execute, prints the exact Wrangler deploy plan only.
|
|
50
|
+
Dry-run output includes local public-site security validation.
|
|
51
|
+
--execute refuses artifacts with local security blockers before invoking Wrangler.
|
|
49
52
|
With --execute, runs Wrangler through npm exec/npx without printing the token.
|
|
50
53
|
|
|
51
54
|
pages verify --url <https-url> --project-name <name> [--domain <host>] \\
|
|
@@ -264,6 +267,35 @@ function redactPlanOutput(plan) {
|
|
|
264
267
|
return redactOperationalPayload(plan);
|
|
265
268
|
}
|
|
266
269
|
|
|
270
|
+
function redactPublicSiteSecurityBlocker(entry) {
|
|
271
|
+
return {
|
|
272
|
+
message: redactOperationalText(entry?.message ?? ''),
|
|
273
|
+
...(entry?.path === undefined ? {} : { path: redactOperationalText(String(entry.path)) }),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function publicSiteSecurityDeploySummary(result) {
|
|
278
|
+
return {
|
|
279
|
+
schema: PUBLIC_SITE_SECURITY_RESULT_SCHEMA,
|
|
280
|
+
ok: result?.ok === true,
|
|
281
|
+
status: result?.status ?? 'blocked',
|
|
282
|
+
blocker_count: Array.isArray(result?.blockers) ? result.blockers.length : 0,
|
|
283
|
+
blockers: Array.isArray(result?.blockers) ? result.blockers.map(redactPublicSiteSecurityBlocker) : [],
|
|
284
|
+
checked: redactOperationalPayload(result?.checked ?? {}),
|
|
285
|
+
claimBoundary: result?.claim_boundary ?? [],
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function publicSiteSecurityErrorMessage(summary) {
|
|
290
|
+
const blockerText = summary.blockers
|
|
291
|
+
.slice(0, 5)
|
|
292
|
+
.map((entry) => (entry.path ? `${entry.path}: ${entry.message}` : entry.message))
|
|
293
|
+
.join('; ');
|
|
294
|
+
return blockerText.length > 0
|
|
295
|
+
? `public site security validation blocked Pages deploy: ${blockerText}`
|
|
296
|
+
: 'public site security validation blocked Pages deploy';
|
|
297
|
+
}
|
|
298
|
+
|
|
267
299
|
function parsePositiveInteger(value, name) {
|
|
268
300
|
const text = requireNonEmptyString(name, value);
|
|
269
301
|
if (!/^\d+$/.test(text)) throw new UsageError(`${name} must be a positive integer`);
|
|
@@ -1518,6 +1550,7 @@ export async function runCloudflareOpsCommand(command, {
|
|
|
1518
1550
|
|
|
1519
1551
|
if (command.kind === 'pages.deploy') {
|
|
1520
1552
|
const plan = buildWranglerPagesDeployPlan(command);
|
|
1553
|
+
const siteSecurity = publicSiteSecurityDeploySummary(await validatePublicSiteSecurity({ site: command.site }));
|
|
1521
1554
|
if (!command.execute) {
|
|
1522
1555
|
return {
|
|
1523
1556
|
json: {
|
|
@@ -1526,10 +1559,12 @@ export async function runCloudflareOpsCommand(command, {
|
|
|
1526
1559
|
dryRun: true,
|
|
1527
1560
|
execute: false,
|
|
1528
1561
|
plan: redactPlanOutput(plan),
|
|
1562
|
+
siteSecurity,
|
|
1529
1563
|
claimBoundary: 'Plan only; no Cloudflare Pages deployment was executed.',
|
|
1530
1564
|
},
|
|
1531
1565
|
};
|
|
1532
1566
|
}
|
|
1567
|
+
if (!siteSecurity.ok) throw new UsageError(publicSiteSecurityErrorMessage(siteSecurity));
|
|
1533
1568
|
const result = await execFileImpl(plan.command, plan.args, { shell: plan.usesShell === true, windowsHide: true, maxBuffer: 10 * 1024 * 1024, env });
|
|
1534
1569
|
return {
|
|
1535
1570
|
json: {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import https from 'node:https';
|
|
2
3
|
import { createHash } from 'node:crypto';
|
|
3
4
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
5
|
import { dirname, resolve } from 'node:path';
|
|
@@ -81,6 +82,42 @@ function redactProbeBody(value, path = 'probe.body') {
|
|
|
81
82
|
return value;
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
export function localSimulationLoopbackFetch(url, init = {}) {
|
|
86
|
+
const parsed = new URL(url);
|
|
87
|
+
const host = parsed.hostname.toLowerCase();
|
|
88
|
+
if (parsed.protocol !== 'https:' || (host !== 'sim.enigmamemory.com' && !host.endsWith('.sim.enigmamemory.com'))) {
|
|
89
|
+
throw new Error('--local-simulation-loopback only supports https://*.sim.enigmamemory.com simulation probes');
|
|
90
|
+
}
|
|
91
|
+
const request = {
|
|
92
|
+
hostname: '127.0.0.1',
|
|
93
|
+
port: parsed.port || 443,
|
|
94
|
+
path: `${parsed.pathname}${parsed.search}`,
|
|
95
|
+
method: init.method || 'GET',
|
|
96
|
+
headers: init.headers,
|
|
97
|
+
rejectUnauthorized: false,
|
|
98
|
+
servername: parsed.hostname,
|
|
99
|
+
};
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
const req = https.request(request, (res) => {
|
|
102
|
+
const chunks = [];
|
|
103
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
104
|
+
res.on('end', () => {
|
|
105
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
106
|
+
resolve({
|
|
107
|
+
ok: res.statusCode >= 200 && res.statusCode < 300,
|
|
108
|
+
status: res.statusCode,
|
|
109
|
+
statusText: res.statusMessage || '',
|
|
110
|
+
url,
|
|
111
|
+
redirected: false,
|
|
112
|
+
text: async () => text,
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
req.on('error', reject);
|
|
117
|
+
req.end();
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
84
121
|
async function fetchProbe(url, { fetchImpl = globalThis.fetch, observedAt }) {
|
|
85
122
|
if (typeof fetchImpl !== 'function') throw new Error('global fetch is not available in this Node runtime');
|
|
86
123
|
const response = await fetchImpl(url, {
|
|
@@ -189,6 +226,10 @@ function parseArgs(argv) {
|
|
|
189
226
|
}
|
|
190
227
|
if (!arg.startsWith('--')) throw new Error(`Unexpected argument: ${arg}`);
|
|
191
228
|
const name = arg.slice(2);
|
|
229
|
+
if (name === 'local-simulation-loopback') {
|
|
230
|
+
flags.set(name, true);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
192
233
|
const value = argv[index + 1];
|
|
193
234
|
if (!value || value.startsWith('--')) throw new Error(`${arg} requires a value`);
|
|
194
235
|
flags.set(name, value);
|
|
@@ -198,7 +239,7 @@ function parseArgs(argv) {
|
|
|
198
239
|
}
|
|
199
240
|
|
|
200
241
|
function usage() {
|
|
201
|
-
return `Usage: node scripts/collect-hosted-backend-live-evidence.mjs --relay-url <https-base> --gateway-url <https-base> --refs-json <refs.json> --domain <domain> --environment-id <id> --cloud-provider <provider> --region <region> --owner <owner> --operator-decision go --operator-packet-ref <ref> --operator-approved-at <iso> --operator-approved-by <name> [--out <collection.json>] [--evidence-out <evidence.json>]\n\nCollects public HTTPS /livez and /readyz evidence for relay and gateway, then validates it with validate-hosted-backend-live. It never sends credentials and does not deploy infrastructure.\n`;
|
|
242
|
+
return `Usage: node scripts/collect-hosted-backend-live-evidence.mjs --relay-url <https-base> --gateway-url <https-base> --refs-json <refs.json> --domain <domain> --environment-id <id> --cloud-provider <provider> --region <region> --owner <owner> --operator-decision go --operator-packet-ref <ref> --operator-approved-at <iso> --operator-approved-by <name> [--out <collection.json>] [--evidence-out <evidence.json>] [--local-simulation-loopback]\n\nCollects public HTTPS /livez and /readyz evidence for relay and gateway, then validates it with validate-hosted-backend-live. It never sends credentials and does not deploy infrastructure. The --local-simulation-loopback flag is restricted to https://*.sim.enigmamemory.com local simulation probes with self-signed TLS and must not be used as production evidence.\n`;
|
|
202
243
|
}
|
|
203
244
|
|
|
204
245
|
async function runCli(argv = process.argv.slice(2), { fetchImpl = globalThis.fetch } = {}) {
|
|
@@ -212,6 +253,7 @@ async function runCli(argv = process.argv.slice(2), { fetchImpl = globalThis.fet
|
|
|
212
253
|
const refs = await readJsonFile(refsPath);
|
|
213
254
|
const environment = await maybeReadJsonFile(readFlag(flags, 'environment-json'));
|
|
214
255
|
const operatorAcceptance = await maybeReadJsonFile(readFlag(flags, 'operator-acceptance-json'));
|
|
256
|
+
const selectedFetchImpl = flags.get('local-simulation-loopback') === true ? localSimulationLoopbackFetch : fetchImpl;
|
|
215
257
|
const collection = await collectHostedBackendLiveEvidence({
|
|
216
258
|
relayBaseUrl: readFlag(flags, 'relay-url'),
|
|
217
259
|
gatewayBaseUrl: readFlag(flags, 'gateway-url'),
|
|
@@ -233,7 +275,7 @@ async function runCli(argv = process.argv.slice(2), { fetchImpl = globalThis.fet
|
|
|
233
275
|
operatorApprovedAt: readFlag(flags, 'operator-approved-at'),
|
|
234
276
|
operatorApprovedBy: readFlag(flags, 'operator-approved-by'),
|
|
235
277
|
observed_at: readFlag(flags, 'observed-at') ?? new Date().toISOString(),
|
|
236
|
-
fetchImpl,
|
|
278
|
+
fetchImpl: selectedFetchImpl,
|
|
237
279
|
});
|
|
238
280
|
const collectionJson = `${JSON.stringify(collection, null, 2)}\n`;
|
|
239
281
|
const evidenceJson = `${JSON.stringify(collection.evidence, null, 2)}\n`;
|
|
@@ -832,6 +832,11 @@ export function runMemoryBenchmarkSuite(options = {}) {
|
|
|
832
832
|
credentials_required: false,
|
|
833
833
|
external_downloads_required: false,
|
|
834
834
|
external_provider_calls: false,
|
|
835
|
+
llm_answer_accuracy_scored: false,
|
|
836
|
+
provider_api_calls_made: false,
|
|
837
|
+
api_spend_possible: false,
|
|
838
|
+
mem0_adapter_run: false,
|
|
839
|
+
external_competitor_adapters_run: false,
|
|
835
840
|
raw_private_memory_plaintext_included: false,
|
|
836
841
|
provider_deletion_claim: false,
|
|
837
842
|
model_forgetting_claim: false,
|
|
@@ -37,6 +37,25 @@ export const STANDARD_MEMORY_BENCHMARK_METHODS = Object.freeze([
|
|
|
37
37
|
}),
|
|
38
38
|
]);
|
|
39
39
|
|
|
40
|
+
export const STANDARD_EXTERNAL_COMPETITOR_ADAPTERS = Object.freeze([
|
|
41
|
+
Object.freeze({
|
|
42
|
+
id: 'mem0',
|
|
43
|
+
name: 'Mem0',
|
|
44
|
+
status: 'not_run_requires_credentials_or_runtime',
|
|
45
|
+
target_type: 'external_adapter',
|
|
46
|
+
can_run_in_this_harness: false,
|
|
47
|
+
scores_included: false,
|
|
48
|
+
required_artifacts: Object.freeze([
|
|
49
|
+
'Mem0 platform credentials or open-source runtime',
|
|
50
|
+
'Pinned Mem0 SDK/package versions',
|
|
51
|
+
'Fixed extraction, update, retrieval, reset, model, and tool policy',
|
|
52
|
+
'Same reviewed dataset manifest, split, top-k, and scorer as Enigma rows',
|
|
53
|
+
]),
|
|
54
|
+
official_doc: 'https://docs.mem0.ai/',
|
|
55
|
+
boundary_reason: 'The standard runner has no Mem0 credentials, SDK/runtime, fixed memory loop, reset policy, model/tool environment, or reviewed adapter scorer, so no Mem0 score is produced.',
|
|
56
|
+
}),
|
|
57
|
+
]);
|
|
58
|
+
|
|
40
59
|
const LOCOMO_SOURCE_URL = 'https://raw.githubusercontent.com/snap-research/locomo/main/data/locomo10.json';
|
|
41
60
|
const LONGMEMEVAL_SOURCE_URLS = Object.freeze([
|
|
42
61
|
'https://huggingface.co/datasets/xiaowu0162/longmemeval-cleaned/resolve/main/longmemeval_oracle.json',
|
|
@@ -121,6 +140,8 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
121
140
|
} else if (arg === '--out') {
|
|
122
141
|
options.out = requiredFlagValue(argv, index, arg);
|
|
123
142
|
index += 1;
|
|
143
|
+
} else if (arg === '--dry-run') {
|
|
144
|
+
options.dry_run = true;
|
|
124
145
|
} else if (arg === '--help' || arg === '-h') {
|
|
125
146
|
options.help = true;
|
|
126
147
|
} else {
|
|
@@ -147,6 +168,100 @@ function optionalPositiveInteger(value, name) {
|
|
|
147
168
|
return positiveInteger(value, name);
|
|
148
169
|
}
|
|
149
170
|
|
|
171
|
+
function datasetPlanRows(options) {
|
|
172
|
+
const rows = [];
|
|
173
|
+
if (options.locomo !== undefined || options.locomoPath !== undefined) {
|
|
174
|
+
rows.push({
|
|
175
|
+
id: 'locomo',
|
|
176
|
+
label: 'LoCoMo',
|
|
177
|
+
local_file_name: publicFileName(options.locomo ?? options.locomoPath),
|
|
178
|
+
source_url: LOCOMO_SOURCE_URL,
|
|
179
|
+
license: 'CC BY-NC 4.0',
|
|
180
|
+
sample_limit: optionalPositiveInteger(options.max_locomo_qa ?? options.maxLocomoQa, 'max_locomo_qa') ?? null,
|
|
181
|
+
parser: 'conversation session turns as memory records; qa evidence labels score support only',
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if (options.longmemeval !== undefined || options.longmemevalPath !== undefined || options.longMemEvalPath !== undefined) {
|
|
185
|
+
rows.push({
|
|
186
|
+
id: 'longmemeval',
|
|
187
|
+
label: 'LongMemEval',
|
|
188
|
+
local_file_name: publicFileName(options.longmemeval ?? options.longmemevalPath ?? options.longMemEvalPath),
|
|
189
|
+
source_url: LONGMEMEVAL_SOURCE_URLS,
|
|
190
|
+
license: 'Review upstream Hugging Face dataset card and LongMemEval repository terms.',
|
|
191
|
+
sample_limit: optionalPositiveInteger(options.max_longmemeval_items ?? options.maxLongMemEvalItems, 'max_longmemeval_items') ?? null,
|
|
192
|
+
parser: 'haystack_sessions turns as memory records; answer-session labels score support only',
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return rows;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function offlineCommandBoundaries({ scoresIncluded, datasetFilesRead }) {
|
|
199
|
+
return {
|
|
200
|
+
deterministic_offline_runner: true,
|
|
201
|
+
dataset_files_read_from_local_disk: datasetFilesRead,
|
|
202
|
+
network_calls_made: false,
|
|
203
|
+
provider_api_calls_made: false,
|
|
204
|
+
api_spend_possible: false,
|
|
205
|
+
hosted_memory_service_called: false,
|
|
206
|
+
external_competitor_adapters_run: false,
|
|
207
|
+
mem0_adapter_run: false,
|
|
208
|
+
llm_used: false,
|
|
209
|
+
llm_answer_accuracy_scored: false,
|
|
210
|
+
retrieval_evidence_proxy_scored: scoresIncluded,
|
|
211
|
+
benchmark_scores_included: scoresIncluded,
|
|
212
|
+
raw_question_text_included: false,
|
|
213
|
+
raw_answer_text_included: false,
|
|
214
|
+
raw_conversation_text_included: false,
|
|
215
|
+
gold_labels_used_for_retrieval: false,
|
|
216
|
+
gold_labels_used_for_scoring: scoresIncluded,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function applesToApplesControls(topK) {
|
|
221
|
+
return {
|
|
222
|
+
same_top_k_for_all_methods: true,
|
|
223
|
+
top_k: topK,
|
|
224
|
+
same_parser_per_dataset: true,
|
|
225
|
+
same_local_records_per_dataset: true,
|
|
226
|
+
same_gold_evidence_labels_per_dataset_for_scoring_only: true,
|
|
227
|
+
local_deterministic_methods_only: true,
|
|
228
|
+
provider_runtime_fixed: false,
|
|
229
|
+
competitor_runtime_fixed: false,
|
|
230
|
+
answer_generator_fixed: false,
|
|
231
|
+
evaluator_model_fixed: false,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function buildStandardBenchmarkDryRunPlan(options = {}) {
|
|
236
|
+
const topK = optionalPositiveInteger(options.top_k ?? options.topK, 'top_k') ?? 5;
|
|
237
|
+
const datasets = datasetPlanRows(options);
|
|
238
|
+
if (datasets.length === 0) throw new Error('Provide --locomo <path> and/or --longmemeval <path>');
|
|
239
|
+
return {
|
|
240
|
+
schema: 'enigma.standard_memory_benchmark_plan.v1',
|
|
241
|
+
generated_at: options.generated_at ?? new Date().toISOString(),
|
|
242
|
+
package: {
|
|
243
|
+
name: 'enigma-memory',
|
|
244
|
+
version: '0.1.16',
|
|
245
|
+
},
|
|
246
|
+
public_safe: true,
|
|
247
|
+
dry_run: true,
|
|
248
|
+
top_k: topK,
|
|
249
|
+
datasets_planned: datasets,
|
|
250
|
+
local_methods: STANDARD_MEMORY_BENCHMARK_METHODS.map((method) => ({ ...method })),
|
|
251
|
+
external_competitor_adapters: STANDARD_EXTERNAL_COMPETITOR_ADAPTERS.map((adapter) => ({
|
|
252
|
+
...adapter,
|
|
253
|
+
required_artifacts: [...adapter.required_artifacts],
|
|
254
|
+
})),
|
|
255
|
+
command_boundaries: offlineCommandBoundaries({ scoresIncluded: false, datasetFilesRead: false }),
|
|
256
|
+
apples_to_apples_controls: applesToApplesControls(topK),
|
|
257
|
+
non_claims: [
|
|
258
|
+
'This dry run does not read dataset files and produces no benchmark score.',
|
|
259
|
+
'No provider APIs, hosted memory services, Mem0 runtime, competitor SDKs, LLM generators, or evaluator models are called.',
|
|
260
|
+
'A scored report requires a separate non-dry-run command against the exact local dataset files and hashes.',
|
|
261
|
+
],
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
150
265
|
function publicFileName(path) {
|
|
151
266
|
return path === undefined || path === null ? undefined : basename(String(path));
|
|
152
267
|
}
|
|
@@ -995,7 +1110,7 @@ function buildSuiteReport(datasetRows, topK, options) {
|
|
|
995
1110
|
generated_at: options.generated_at ?? new Date().toISOString(),
|
|
996
1111
|
package: {
|
|
997
1112
|
name: 'enigma-memory',
|
|
998
|
-
version: '0.1.
|
|
1113
|
+
version: '0.1.16',
|
|
999
1114
|
},
|
|
1000
1115
|
public_safe: true,
|
|
1001
1116
|
top_k: topK,
|
|
@@ -1010,6 +1125,8 @@ function buildSuiteReport(datasetRows, topK, options) {
|
|
|
1010
1125
|
'No provider APIs, hosted runtimes, competitor SDKs, or external accounts are called by this runner.',
|
|
1011
1126
|
'Rows are local deterministic methods only; no third-party competitor scores or benchmark-leadership claims are emitted.',
|
|
1012
1127
|
],
|
|
1128
|
+
command_boundaries: offlineCommandBoundaries({ scoresIncluded: true, datasetFilesRead: true }),
|
|
1129
|
+
apples_to_apples_controls: applesToApplesControls(topK),
|
|
1013
1130
|
benchmark_boundaries: {
|
|
1014
1131
|
official_dataset_files_required: true,
|
|
1015
1132
|
credentials_required: false,
|
|
@@ -1032,15 +1149,20 @@ function buildSuiteReport(datasetRows, topK, options) {
|
|
|
1032
1149
|
enigma_relevance_fallback: 'falls back to all local candidates only when no enhanced relevance signal exists, then applies deterministic local ranking and --top-k',
|
|
1033
1150
|
provider_api_used: false,
|
|
1034
1151
|
llm_used: false,
|
|
1152
|
+
gold_labels_used_for_retrieval: false,
|
|
1035
1153
|
},
|
|
1036
1154
|
local_methods: STANDARD_MEMORY_BENCHMARK_METHODS.map((method) => ({ ...method })),
|
|
1155
|
+
external_competitor_adapters: STANDARD_EXTERNAL_COMPETITOR_ADAPTERS.map((adapter) => ({
|
|
1156
|
+
...adapter,
|
|
1157
|
+
required_artifacts: [...adapter.required_artifacts],
|
|
1158
|
+
})),
|
|
1037
1159
|
datasets: datasetRows,
|
|
1038
1160
|
dataset_rows: datasetRows,
|
|
1039
1161
|
};
|
|
1040
1162
|
}
|
|
1041
1163
|
|
|
1042
1164
|
function usage() {
|
|
1043
|
-
return `Usage: node scripts/run-standard-memory-benchmarks.mjs [--locomo <path>] [--longmemeval <path>] [--max-locomo-qa <n>] [--max-longmemeval-items <n>] [--top-k <n>] [--out <path>]\n\nProduces schema ${STANDARD_MEMORY_BENCHMARK_SUITE_SCHEMA}. Raw question, answer, and conversation text are never written to the report. With --longmemeval and --max-longmemeval-items, the local top-level JSON array is streamed for hashing and only the requested sample items are parsed.`;
|
|
1165
|
+
return `Usage: node scripts/run-standard-memory-benchmarks.mjs [--locomo <path>] [--longmemeval <path>] [--max-locomo-qa <n>] [--max-longmemeval-items <n>] [--top-k <n>] [--out <path>] [--dry-run]\n\nProduces schema ${STANDARD_MEMORY_BENCHMARK_SUITE_SCHEMA}. Raw question, answer, and conversation text are never written to the report. With --longmemeval and --max-longmemeval-items, the local top-level JSON array is streamed for hashing and only the requested sample items are parsed. Use --dry-run to print a public-safe offline execution plan without reading dataset files or producing scores.`;
|
|
1044
1166
|
}
|
|
1045
1167
|
|
|
1046
1168
|
async function main() {
|
|
@@ -1049,7 +1171,9 @@ async function main() {
|
|
|
1049
1171
|
console.log(usage());
|
|
1050
1172
|
return;
|
|
1051
1173
|
}
|
|
1052
|
-
const report =
|
|
1174
|
+
const report = options.dry_run
|
|
1175
|
+
? buildStandardBenchmarkDryRunPlan(options)
|
|
1176
|
+
: await runStandardMemoryBenchmarkSuiteFromFiles(options);
|
|
1053
1177
|
const serialized = `${JSON.stringify(report, null, 2)}\n`;
|
|
1054
1178
|
if (options.out) {
|
|
1055
1179
|
const outPath = resolve(options.out);
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { validatePublicSiteSecurity } from './validate-public-site-security.mjs';
|
|
6
|
+
|
|
7
|
+
const STAGE_SCHEMA = 'enigma.cloudflare_pages_stage.v1';
|
|
8
|
+
const DEFAULT_HEADERS = Object.freeze([
|
|
9
|
+
['Permissions-Policy', 'camera=(), microphone=(), geolocation=()'],
|
|
10
|
+
['Content-Security-Policy', "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://api.fontshare.com; font-src 'self' https://fonts.gstatic.com https://api.fontshare.com; img-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"],
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
class UsageError extends Error {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'UsageError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseArgs(argv = process.argv.slice(2)) {
|
|
21
|
+
const out = { site: null, out: null, help: false };
|
|
22
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
23
|
+
const token = argv[i];
|
|
24
|
+
if (token === '--help') out.help = true;
|
|
25
|
+
else if (token === '--site') out.site = argv[++i] ?? null;
|
|
26
|
+
else if (token === '--out') out.out = argv[++i] ?? null;
|
|
27
|
+
else throw new UsageError(`unknown argument: ${token}`);
|
|
28
|
+
}
|
|
29
|
+
if (out.help) return out;
|
|
30
|
+
if (typeof out.site !== 'string' || out.site.trim().length === 0) throw new UsageError('--site is required');
|
|
31
|
+
if (typeof out.out !== 'string' || out.out.trim().length === 0) throw new UsageError('--out is required');
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function usage() {
|
|
36
|
+
return 'Usage: node scripts/stage-cloudflare-pages-artifact.mjs --site <dir> --out <dir>\n\nCopies a static Pages artifact to a local staging directory and overlays required Cloudflare security headers without mutating the source artifact.\n';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function headerName(line) {
|
|
40
|
+
const match = String(line).trim().match(/^([^:]+):\s*.+$/u);
|
|
41
|
+
return match ? match[1].trim().toLowerCase() : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function ensureRequiredHeaders(text) {
|
|
45
|
+
const normalized = String(text ?? '').replace(/\r\n/g, '\n');
|
|
46
|
+
const lines = normalized.length > 0 ? normalized.split('\n') : [];
|
|
47
|
+
let blockStart = -1;
|
|
48
|
+
let insertAt = -1;
|
|
49
|
+
const present = new Set();
|
|
50
|
+
|
|
51
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
52
|
+
const trimmed = lines[i].trim();
|
|
53
|
+
if (blockStart === -1) {
|
|
54
|
+
if (trimmed === '/*') {
|
|
55
|
+
blockStart = i;
|
|
56
|
+
insertAt = lines.length;
|
|
57
|
+
}
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (i > blockStart && lines[i] && !lines[i].startsWith(' ') && !lines[i].startsWith('\t')) {
|
|
61
|
+
insertAt = i;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
const name = headerName(lines[i]);
|
|
65
|
+
if (name) present.add(name);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const missing = DEFAULT_HEADERS.filter(([name]) => !present.has(name.toLowerCase()));
|
|
69
|
+
if (missing.length === 0) return normalized.endsWith('\n') ? normalized : `${normalized}\n`;
|
|
70
|
+
const additions = missing.map(([name, value]) => ` ${name}: ${value}`);
|
|
71
|
+
if (blockStart === -1) {
|
|
72
|
+
return [`/*`, ...additions, '', normalized].join('\n').replace(/\n*$/u, '\n');
|
|
73
|
+
}
|
|
74
|
+
const nextLines = [...lines];
|
|
75
|
+
nextLines.splice(insertAt, 0, ...additions);
|
|
76
|
+
return nextLines.join('\n').replace(/\n*$/u, '\n');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function publicPathLabel(value, placeholder) {
|
|
80
|
+
const text = String(value ?? '');
|
|
81
|
+
return /^(?:[A-Za-z]:[\\/]|\\\\|\/)/u.test(text) ? placeholder : text;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function safeErrorMessage(error) {
|
|
85
|
+
return String(error?.message ?? error).replace(/[A-Z]:\\[^\r\n"']+/gi, '<local-path>');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function stageCloudflarePagesArtifact(input = {}) {
|
|
89
|
+
const source = resolve(String(input.site ?? ''));
|
|
90
|
+
const out = resolve(String(input.out ?? ''));
|
|
91
|
+
const generatedAt = input.generated_at ?? input.generatedAt ?? new Date().toISOString();
|
|
92
|
+
if (source === out) throw new UsageError('--out must be different from --site');
|
|
93
|
+
await rm(out, { recursive: true, force: true });
|
|
94
|
+
await mkdir(dirname(out), { recursive: true });
|
|
95
|
+
await cp(source, out, { recursive: true, dereference: false, force: true, errorOnExist: false });
|
|
96
|
+
const headersPath = resolve(out, '_headers');
|
|
97
|
+
let headers = '';
|
|
98
|
+
try {
|
|
99
|
+
headers = await readFile(headersPath, 'utf8');
|
|
100
|
+
} catch {
|
|
101
|
+
headers = '';
|
|
102
|
+
}
|
|
103
|
+
await writeFile(headersPath, ensureRequiredHeaders(headers), 'utf8');
|
|
104
|
+
const security = await validatePublicSiteSecurity({ site: out }, { generated_at: generatedAt });
|
|
105
|
+
return {
|
|
106
|
+
schema: STAGE_SCHEMA,
|
|
107
|
+
generated_at: generatedAt,
|
|
108
|
+
source_site: '<source-public-site>',
|
|
109
|
+
staged_site: publicPathLabel(input.out, '<staged-public-site>'),
|
|
110
|
+
headers_overlay: {
|
|
111
|
+
file: '_headers',
|
|
112
|
+
ensured: DEFAULT_HEADERS.map(([name]) => name),
|
|
113
|
+
source_mutated: false,
|
|
114
|
+
},
|
|
115
|
+
security: {
|
|
116
|
+
schema: security.schema,
|
|
117
|
+
ok: security.ok,
|
|
118
|
+
status: security.status,
|
|
119
|
+
blocker_count: security.blockers.length,
|
|
120
|
+
blockers: security.blockers,
|
|
121
|
+
checked: security.checked,
|
|
122
|
+
},
|
|
123
|
+
ok: security.ok,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function main() {
|
|
128
|
+
try {
|
|
129
|
+
const args = parseArgs();
|
|
130
|
+
if (args.help) {
|
|
131
|
+
process.stdout.write(usage());
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
const result = await stageCloudflarePagesArtifact(args);
|
|
135
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
136
|
+
return result.ok ? 0 : 1;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
process.stderr.write(`${safeErrorMessage(error)}\n`);
|
|
139
|
+
return 1;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
144
|
+
process.exitCode = await main();
|
|
145
|
+
}
|