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,594 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { execFile as execFileCallback } from 'node:child_process';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { promisify } from 'node:util';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const execFile = promisify(execFileCallback);
|
|
10
|
+
const PROJECT_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
11
|
+
const DEFAULT_OUT = '.enigma-review-packet';
|
|
12
|
+
const SCHEMA = 'enigma.review_packet.v1';
|
|
13
|
+
const COMMAND_TIMEOUT_MS = 10 * 60 * 1000;
|
|
14
|
+
const MAX_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
15
|
+
const RAW_MEMORY_SENTINEL = 'private launch-code phrase must not leave local memory';
|
|
16
|
+
const REVIEW_DOCS = Object.freeze([
|
|
17
|
+
'README.md',
|
|
18
|
+
'SECURITY.md',
|
|
19
|
+
'docs/release-evidence-2026-06-23.md',
|
|
20
|
+
'docs/security-threat-model.md',
|
|
21
|
+
'docs/public-api-reference.md',
|
|
22
|
+
'docs/operator-acceptance-packet.md',
|
|
23
|
+
'docs/release-provenance-and-sbom.md',
|
|
24
|
+
'launch/02_WHITEPAPER.md',
|
|
25
|
+
'docs/competitive-memory-analysis.md',
|
|
26
|
+
]);
|
|
27
|
+
const REVIEW_SOURCE_FILES = Object.freeze([
|
|
28
|
+
'apps/cli/bin/enigma.mjs',
|
|
29
|
+
'apps/gateway/src/server.mjs',
|
|
30
|
+
'apps/relay/src/server.mjs',
|
|
31
|
+
'packages/adapters/src/index.js',
|
|
32
|
+
'packages/adapters/PACKAGE_CONTRACT.md',
|
|
33
|
+
'packages/mcp-server/src/index.js',
|
|
34
|
+
'packages/metering/src/index.js',
|
|
35
|
+
'packages/metering/PACKAGE_CONTRACT.md',
|
|
36
|
+
'packages/optimizer/src/index.js',
|
|
37
|
+
'packages/settlement/src/index.js',
|
|
38
|
+
'packages/settlement/PACKAGE_CONTRACT.md',
|
|
39
|
+
'packages/storage/src/index.js',
|
|
40
|
+
'packages/storage/PACKAGE_CONTRACT.md',
|
|
41
|
+
'deploy/docker-compose.production.example.yml',
|
|
42
|
+
'deploy/kubernetes/enigma-backend.example.yaml',
|
|
43
|
+
'scripts/build-operator-acceptance-packet.mjs',
|
|
44
|
+
'scripts/build-production-readiness-manifest.mjs',
|
|
45
|
+
'scripts/build-production-storage-migration.mjs',
|
|
46
|
+
'scripts/cloudflare-secret-env.mjs',
|
|
47
|
+
'scripts/build-cloudflare-pages-release-packet.mjs',
|
|
48
|
+
'scripts/build-cloudflare-token-policy.mjs',
|
|
49
|
+
'scripts/build-cloudflare-token-request.mjs',
|
|
50
|
+
'scripts/build-production-handoff-packet.mjs',
|
|
51
|
+
'scripts/build-goal-completion-audit.mjs',
|
|
52
|
+
'scripts/run-backend-readiness-smoke.mjs',
|
|
53
|
+
'scripts/validate-hosted-backend-live.mjs',
|
|
54
|
+
'scripts/collect-hosted-backend-live-evidence.mjs',
|
|
55
|
+
'scripts/build-hosted-probe-worker.mjs',
|
|
56
|
+
'scripts/validate-production-manifests.mjs',
|
|
57
|
+
'scripts/validate-backup-restore-drill.mjs',
|
|
58
|
+
'scripts/validate-incident-drill.mjs',
|
|
59
|
+
'scripts/validate-kms-custody.mjs',
|
|
60
|
+
'scripts/validate-legal-compliance-approval.mjs',
|
|
61
|
+
'scripts/validate-monitoring-alerting.mjs',
|
|
62
|
+
'scripts/validate-network-access-policy.mjs',
|
|
63
|
+
'scripts/validate-operator-acceptance.mjs',
|
|
64
|
+
'scripts/validate-support-sla.mjs',
|
|
65
|
+
'scripts/validate-tenant-policy-approval.mjs',
|
|
66
|
+
'scripts/validate-usage-metering.mjs',
|
|
67
|
+
'scripts/validate-service-settlement.mjs',
|
|
68
|
+
'scripts/validate-public-site-security.mjs',
|
|
69
|
+
'scripts/validate-domain-tls.mjs',
|
|
70
|
+
'scripts/validate-security-threat-model.mjs',
|
|
71
|
+
'test/enigma-adapters.test.mjs',
|
|
72
|
+
'test/enigma-kms-custody.test.mjs',
|
|
73
|
+
'test/enigma-legal-compliance.test.mjs',
|
|
74
|
+
'test/enigma-metering.test.mjs',
|
|
75
|
+
'test/enigma-monitoring-alerting.test.mjs',
|
|
76
|
+
'test/enigma-network-access-policy.test.mjs',
|
|
77
|
+
'test/enigma-network.test.mjs',
|
|
78
|
+
'test/enigma-operator-acceptance.test.mjs',
|
|
79
|
+
'test/enigma-settlement.test.mjs',
|
|
80
|
+
'test/enigma-storage.test.mjs',
|
|
81
|
+
'test/enigma-tenant-policy-approval.test.mjs',
|
|
82
|
+
'test/enigma-usage-metering.test.mjs',
|
|
83
|
+
'test/enigma-service-settlement.test.mjs',
|
|
84
|
+
'test/enigma-public-site-security.test.mjs',
|
|
85
|
+
'test/enigma-cloudflare-pages-packet.test.mjs',
|
|
86
|
+
'test/enigma-cloudflare-secret-env.test.mjs',
|
|
87
|
+
'test/enigma-cloudflare-token-policy.test.mjs',
|
|
88
|
+
'test/enigma-cloudflare-token-request.test.mjs',
|
|
89
|
+
'test/enigma-production-handoff.test.mjs',
|
|
90
|
+
'test/enigma-goal-completion-audit.test.mjs',
|
|
91
|
+
'test/enigma-backend-readiness-smoke.test.mjs',
|
|
92
|
+
'test/enigma-hosted-backend-live.test.mjs',
|
|
93
|
+
'test/enigma-hosted-backend-collector.test.mjs',
|
|
94
|
+
'test/enigma-hosted-probe-worker.test.mjs',
|
|
95
|
+
'test/enigma-production-manifests.test.mjs',
|
|
96
|
+
'test/enigma-domain-tls.test.mjs',
|
|
97
|
+
'test/enigma-security-threat-model.test.mjs',
|
|
98
|
+
]);
|
|
99
|
+
const CLAIM_BOUNDARY = 'Local hand-review evidence only: this packet is not npm publication, live Cloudflare deployment, hosted/BYOC readiness, legal approval, signed provenance, compliance status, or proof that external providers deleted or forgot data.';
|
|
100
|
+
const SECRET_OR_LOCAL_ARTIFACT_PATH = /(?:^|\/)(?:\.env(?:\.|$)|env\.local$|secrets?|credentials?|tokens?|api[-_]?keys?|private[-_]?keys?|private[-_]?local[-_]?memory|\.enigma|enigma[-_]?bundle|vault[-_]?bundle|local[-_]?bundle|bundle\.json|raw[-_]?memory|logs?|npm-debug\.log|yarn-error\.log|pnpm-debug\.log)(?:\/|$|[._-])|\.(?:log|pem|key|p12|pfx|jks|keystore)(?:\.|$)/i;
|
|
101
|
+
const PRIVATE_OR_INTERNAL_COLLATERAL_PATH = /(?:^|\/)(?:00_LAUNCH_README|05_SOLANA_TOKEN_LAUNCH_RUNBOOK|09_PITCH_DECK_OUTLINE|10_INVESTOR_AND_PARTNER_MEMO|11_ENTERPRISE_SALES_PLAYBOOK|17_LAUNCH_CHECKLIST|REVIEW_PACKET_MANIFEST|release-audit|local-provenance|npm-pack-dry-run)(?:\.(?:html|json|md|txt)|$)/i;
|
|
102
|
+
const TEXT_FILE = /\.(?:css|csv|html|js|json|md|mjs|svg|txt|xml|yml|yaml)$/i;
|
|
103
|
+
|
|
104
|
+
function normalizeRel(rel) {
|
|
105
|
+
return String(rel).replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+/g, '/');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function npmInvocation(args) {
|
|
109
|
+
const label = ['npm', ...args].join(' ');
|
|
110
|
+
const npmExecPath = process.env.npm_execpath;
|
|
111
|
+
if (npmExecPath && npmExecPath.endsWith('.js')) {
|
|
112
|
+
return { command: process.execPath, args: [npmExecPath, ...args], label };
|
|
113
|
+
}
|
|
114
|
+
if (process.platform === 'win32') {
|
|
115
|
+
return { command: process.env.ComSpec ?? 'cmd.exe', args: ['/d', '/s', '/c', label], label };
|
|
116
|
+
}
|
|
117
|
+
return { command: 'npm', args, label };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function localOnlyEnv(extra = {}) {
|
|
121
|
+
const keep = ['PATH', 'Path', 'PATHEXT', 'SYSTEMROOT', 'SystemRoot', 'WINDIR', 'ComSpec', 'HOME', 'USERPROFILE', 'TMP', 'TEMP', 'TMPDIR'];
|
|
122
|
+
const env = {};
|
|
123
|
+
for (const key of keep) {
|
|
124
|
+
if (process.env[key]) env[key] = process.env[key];
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
...env,
|
|
128
|
+
CI: '1',
|
|
129
|
+
npm_config_audit: 'false',
|
|
130
|
+
npm_config_fund: 'false',
|
|
131
|
+
npm_config_update_notifier: 'false',
|
|
132
|
+
...extra,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function parseArgs(argv) {
|
|
137
|
+
const options = {};
|
|
138
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
139
|
+
const arg = argv[index];
|
|
140
|
+
if (arg === '--out') {
|
|
141
|
+
if (options.out !== undefined) throw new Error('--out may only be provided once');
|
|
142
|
+
const value = argv[index + 1];
|
|
143
|
+
if (!value) throw new Error('--out requires a directory path');
|
|
144
|
+
options.out = value;
|
|
145
|
+
index += 1;
|
|
146
|
+
} else if (arg === '--public-site') {
|
|
147
|
+
if (options.publicSite !== undefined) throw new Error('--public-site may only be provided once');
|
|
148
|
+
const value = argv[index + 1];
|
|
149
|
+
if (!value) throw new Error('--public-site requires a directory path');
|
|
150
|
+
options.publicSite = value;
|
|
151
|
+
index += 1;
|
|
152
|
+
} else {
|
|
153
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return options;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function packetTimestamp(now = new Date()) {
|
|
160
|
+
if (typeof now === 'string') return new Date(now).toISOString();
|
|
161
|
+
if (now instanceof Date) return now.toISOString();
|
|
162
|
+
throw new Error('now must be a Date or ISO timestamp string');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function resolveOutputDir(out = DEFAULT_OUT) {
|
|
166
|
+
const resolved = path.resolve(PROJECT_ROOT, out);
|
|
167
|
+
const projectRootLower = PROJECT_ROOT.toLowerCase();
|
|
168
|
+
const resolvedLower = resolved.toLowerCase();
|
|
169
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
170
|
+
const dangerousRoots = new Set([
|
|
171
|
+
path.parse(resolved).root.toLowerCase(),
|
|
172
|
+
PROJECT_ROOT.toLowerCase(),
|
|
173
|
+
home ? path.resolve(home).toLowerCase() : '',
|
|
174
|
+
].filter(Boolean));
|
|
175
|
+
if (dangerousRoots.has(resolvedLower)) throw new Error(`Refusing to replace unsafe review packet output: ${resolved}`);
|
|
176
|
+
if (projectRootLower.startsWith(`${resolvedLower}${path.sep}`)) throw new Error(`Refusing to place review packet at a parent of the project root: ${resolved}`);
|
|
177
|
+
return resolved;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function safePacketPath(packetRoot, rel) {
|
|
181
|
+
const normalized = normalizeRel(rel);
|
|
182
|
+
if (!normalized || normalized.startsWith('../') || path.isAbsolute(normalized)) throw new Error(`Unsafe packet path: ${rel}`);
|
|
183
|
+
const resolved = path.resolve(packetRoot, normalized);
|
|
184
|
+
if (resolved !== packetRoot && !resolved.startsWith(`${packetRoot}${path.sep}`)) throw new Error(`Unsafe packet path escapes output: ${rel}`);
|
|
185
|
+
return resolved;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function sourcePath(rel) {
|
|
189
|
+
const normalized = normalizeRel(rel);
|
|
190
|
+
if (!normalized || normalized.startsWith('../') || path.isAbsolute(normalized)) throw new Error(`Unsafe source path: ${rel}`);
|
|
191
|
+
const resolved = path.resolve(PROJECT_ROOT, normalized);
|
|
192
|
+
if (resolved !== PROJECT_ROOT && !resolved.startsWith(`${PROJECT_ROOT}${path.sep}`)) throw new Error(`Unsafe source path escapes project root: ${rel}`);
|
|
193
|
+
return resolved;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function shouldDenyCopy(rel) {
|
|
197
|
+
const normalized = normalizeRel(rel);
|
|
198
|
+
if (normalized === 'launch/token-metadata.template.json' || normalized === 'site/launch/token-metadata.template.json') return false;
|
|
199
|
+
if (normalized === 'evidence/local-provenance.json' || normalized === 'evidence/release-audit.json' || normalized === 'package/npm-pack-dry-run.json') return false;
|
|
200
|
+
return SECRET_OR_LOCAL_ARTIFACT_PATH.test(normalized) || PRIVATE_OR_INTERNAL_COLLATERAL_PATH.test(normalized);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function assertCopyContentAllowed(full, rel) {
|
|
204
|
+
if (!TEXT_FILE.test(rel)) return;
|
|
205
|
+
const text = fs.readFileSync(full, 'utf8');
|
|
206
|
+
if (text.includes(RAW_MEMORY_SENTINEL)) throw new Error(`${rel} contains the private raw-memory sentinel`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function copyFileIntoPacket(packetRoot, sourceFull, destRel) {
|
|
210
|
+
const normalizedDest = normalizeRel(destRel);
|
|
211
|
+
if (shouldDenyCopy(normalizedDest)) throw new Error(`Refusing to copy denied review packet path: ${normalizedDest}`);
|
|
212
|
+
assertCopyContentAllowed(sourceFull, normalizedDest);
|
|
213
|
+
const destFull = safePacketPath(packetRoot, normalizedDest);
|
|
214
|
+
fs.mkdirSync(path.dirname(destFull), { recursive: true });
|
|
215
|
+
fs.copyFileSync(sourceFull, destFull);
|
|
216
|
+
return normalizedDest;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function copyExistingDocs(packetRoot) {
|
|
220
|
+
const copied = [];
|
|
221
|
+
const skipped = [];
|
|
222
|
+
for (const rel of REVIEW_DOCS) {
|
|
223
|
+
const full = sourcePath(rel);
|
|
224
|
+
if (!fs.existsSync(full)) {
|
|
225
|
+
skipped.push(rel);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const destRel = `docs/${normalizeRel(rel)}`;
|
|
229
|
+
copied.push(copyFileIntoPacket(packetRoot, full, destRel));
|
|
230
|
+
}
|
|
231
|
+
return { copied, skipped };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function copyReviewSourceFiles(packetRoot) {
|
|
235
|
+
const copied = [];
|
|
236
|
+
const skipped = [];
|
|
237
|
+
for (const rel of REVIEW_SOURCE_FILES) {
|
|
238
|
+
const normalized = normalizeRel(rel);
|
|
239
|
+
const full = sourcePath(normalized);
|
|
240
|
+
if (!fs.existsSync(full)) {
|
|
241
|
+
skipped.push(normalized);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const destRel = `source/${normalized}`;
|
|
245
|
+
copied.push(copyFileIntoPacket(packetRoot, full, destRel));
|
|
246
|
+
}
|
|
247
|
+
return { copied, skipped };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function collectSourceFiles(root, dir = root, out = []) {
|
|
251
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
|
|
252
|
+
for (const entry of entries) {
|
|
253
|
+
const full = path.join(dir, entry.name);
|
|
254
|
+
const rel = normalizeRel(path.relative(root, full));
|
|
255
|
+
if (entry.isSymbolicLink()) {
|
|
256
|
+
out.push({ rel, denied: true, reason: 'symbolic links are not copied into review packets' });
|
|
257
|
+
} else if (entry.isDirectory()) {
|
|
258
|
+
if (shouldDenyCopy(`${rel}/`)) out.push({ rel: `${rel}/`, denied: true, reason: 'denied private/local artifact path' });
|
|
259
|
+
else collectSourceFiles(root, full, out);
|
|
260
|
+
} else if (entry.isFile()) {
|
|
261
|
+
if (shouldDenyCopy(rel)) out.push({ rel, denied: true, reason: 'denied private/local artifact path' });
|
|
262
|
+
else out.push({ rel, full, denied: false });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return out;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function copyPublicSite(packetRoot, publicSite) {
|
|
269
|
+
if (!publicSite) return { copied: [], denied: [], source: null };
|
|
270
|
+
const sourceRoot = path.resolve(PROJECT_ROOT, publicSite);
|
|
271
|
+
if (!fs.existsSync(sourceRoot) || !fs.statSync(sourceRoot).isDirectory()) throw new Error(`--public-site must point to an existing directory: ${publicSite}`);
|
|
272
|
+
const copied = [];
|
|
273
|
+
const denied = [];
|
|
274
|
+
for (const entry of collectSourceFiles(sourceRoot)) {
|
|
275
|
+
if (entry.denied) {
|
|
276
|
+
denied.push({ path: entry.rel, reason: entry.reason });
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
copied.push(copyFileIntoPacket(packetRoot, entry.full, `site/${entry.rel}`));
|
|
281
|
+
} catch (error) {
|
|
282
|
+
denied.push({ path: entry.rel, reason: error instanceof Error ? error.message : String(error) });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return { copied, denied, source: sourceRoot };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function publicSiteSummary(site) {
|
|
289
|
+
return {
|
|
290
|
+
provided: site.source !== null,
|
|
291
|
+
copied: site.copied,
|
|
292
|
+
denied_count: site.denied.length,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function displayOutputDir(packetRoot) {
|
|
297
|
+
const rel = normalizeRel(path.relative(PROJECT_ROOT, packetRoot));
|
|
298
|
+
if (!rel || rel.startsWith('../') || shouldDenyCopy(rel)) return '<review-packet-output>';
|
|
299
|
+
return rel;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function sha256File(full) {
|
|
303
|
+
const hash = createHash('sha256');
|
|
304
|
+
hash.update(fs.readFileSync(full));
|
|
305
|
+
return `sha256:${hash.digest('hex')}`;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function packetFiles(packetRoot) {
|
|
309
|
+
return collectSourceFiles(packetRoot)
|
|
310
|
+
.filter((entry) => !entry.denied && entry.rel !== 'REVIEW_PACKET_MANIFEST.json')
|
|
311
|
+
.map((entry) => ({
|
|
312
|
+
path: entry.rel,
|
|
313
|
+
bytes: fs.statSync(entry.full).size,
|
|
314
|
+
sha256: sha256File(entry.full),
|
|
315
|
+
}))
|
|
316
|
+
.sort((left, right) => left.path.localeCompare(right.path));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function parseJsonOrNull(text) {
|
|
320
|
+
try {
|
|
321
|
+
return JSON.parse(text);
|
|
322
|
+
} catch {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function summarizeJson(value) {
|
|
328
|
+
if (value === null || typeof value !== 'object') return {};
|
|
329
|
+
const summary = {};
|
|
330
|
+
for (const key of ['schema', 'ok', 'file_count', 'root_hash']) {
|
|
331
|
+
if (Object.hasOwn(value, key)) summary[key] = value[key];
|
|
332
|
+
}
|
|
333
|
+
if (value.counts && typeof value.counts === 'object') summary.counts = value.counts;
|
|
334
|
+
if (Array.isArray(value.required_failed)) summary.required_failed = value.required_failed;
|
|
335
|
+
if (Array.isArray(value.gates)) {
|
|
336
|
+
summary.gates = value.gates.map((gate) => ({ name: gate.name, ok: gate.ok, status: gate.status ?? null }));
|
|
337
|
+
}
|
|
338
|
+
if (Array.isArray(value) && value[0] && typeof value[0] === 'object') {
|
|
339
|
+
summary.entries = value.length;
|
|
340
|
+
summary.files = Array.isArray(value[0].files) ? value[0].files.length : undefined;
|
|
341
|
+
}
|
|
342
|
+
return summary;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function invokeCommand(commandRunner, command, args, options) {
|
|
346
|
+
if (commandRunner) return commandRunner(command, args, options);
|
|
347
|
+
return execFile(command, args, options);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function shellArgLabel(value) {
|
|
351
|
+
const text = String(value);
|
|
352
|
+
if (text.length === 0) return '""';
|
|
353
|
+
if (!/[\s"]/u.test(text)) return text;
|
|
354
|
+
return `"${text.replace(/"/gu, '\\"')}"`;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function commandSummaryLabel(command, args) {
|
|
358
|
+
const binary = command === process.execPath ? 'node' : (path.basename(command).toLowerCase().startsWith('npm') ? 'npm' : command);
|
|
359
|
+
return [binary, ...args].map(shellArgLabel).join(' ');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
async function runCommand({ name, command, args, outputRel, packetRoot, expectedJson = true, env = {}, commandRunner = null }) {
|
|
365
|
+
const started = Date.now();
|
|
366
|
+
const outputFull = safePacketPath(packetRoot, outputRel);
|
|
367
|
+
fs.mkdirSync(path.dirname(outputFull), { recursive: true });
|
|
368
|
+
const record = {
|
|
369
|
+
name,
|
|
370
|
+
command: commandSummaryLabel(command, args),
|
|
371
|
+
ok: false,
|
|
372
|
+
status: null,
|
|
373
|
+
signal: null,
|
|
374
|
+
duration_ms: 0,
|
|
375
|
+
output: normalizeRel(outputRel),
|
|
376
|
+
stdout_bytes: 0,
|
|
377
|
+
stderr_bytes: 0,
|
|
378
|
+
summary: {},
|
|
379
|
+
};
|
|
380
|
+
let stdout = '';
|
|
381
|
+
let stderr = '';
|
|
382
|
+
try {
|
|
383
|
+
const result = await invokeCommand(commandRunner, command, args, {
|
|
384
|
+
cwd: PROJECT_ROOT,
|
|
385
|
+
env: localOnlyEnv(env),
|
|
386
|
+
timeout: COMMAND_TIMEOUT_MS,
|
|
387
|
+
maxBuffer: MAX_OUTPUT_BYTES,
|
|
388
|
+
windowsHide: true,
|
|
389
|
+
});
|
|
390
|
+
stdout = result.stdout;
|
|
391
|
+
stderr = result.stderr;
|
|
392
|
+
record.ok = true;
|
|
393
|
+
record.status = 0;
|
|
394
|
+
} catch (error) {
|
|
395
|
+
stdout = String(error.stdout ?? '');
|
|
396
|
+
stderr = String(error.stderr ?? '');
|
|
397
|
+
record.status = Number.isInteger(error.code) ? error.code : null;
|
|
398
|
+
record.signal = error.signal ?? null;
|
|
399
|
+
record.error = {
|
|
400
|
+
code: error.killed ? 'COMMAND_TIMEOUT' : (error.code ?? 'COMMAND_FAILED'),
|
|
401
|
+
message: error.message,
|
|
402
|
+
};
|
|
403
|
+
} finally {
|
|
404
|
+
record.duration_ms = Date.now() - started;
|
|
405
|
+
record.stdout_bytes = Buffer.byteLength(stdout);
|
|
406
|
+
record.stderr_bytes = Buffer.byteLength(stderr);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const parsed = parseJsonOrNull(stdout);
|
|
410
|
+
if (parsed !== null) {
|
|
411
|
+
fs.writeFileSync(outputFull, `${JSON.stringify(parsed, null, 2)}\n`);
|
|
412
|
+
record.summary = summarizeJson(parsed);
|
|
413
|
+
} else if (expectedJson) {
|
|
414
|
+
const diagnostic = {
|
|
415
|
+
schema: 'enigma.review_packet.command_failure.v1',
|
|
416
|
+
command: record.command,
|
|
417
|
+
status: record.status,
|
|
418
|
+
signal: record.signal,
|
|
419
|
+
stdout_bytes: record.stdout_bytes,
|
|
420
|
+
stderr_bytes: record.stderr_bytes,
|
|
421
|
+
error: record.error ?? { code: 'INVALID_JSON', message: 'Command did not emit JSON on stdout' },
|
|
422
|
+
};
|
|
423
|
+
fs.writeFileSync(outputFull, `${JSON.stringify(diagnostic, null, 2)}\n`);
|
|
424
|
+
record.ok = false;
|
|
425
|
+
record.summary = { schema: diagnostic.schema };
|
|
426
|
+
record.error ??= diagnostic.error;
|
|
427
|
+
} else {
|
|
428
|
+
fs.writeFileSync(outputFull, stdout);
|
|
429
|
+
}
|
|
430
|
+
return record;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function writeProvenance(packetRoot, env, commandRunner = null) {
|
|
434
|
+
const outputRel = 'evidence/local-provenance.json';
|
|
435
|
+
const outputFull = safePacketPath(packetRoot, outputRel);
|
|
436
|
+
fs.mkdirSync(path.dirname(outputFull), { recursive: true });
|
|
437
|
+
const command = process.execPath;
|
|
438
|
+
const args = ['scripts/release-provenance.mjs', '--out', outputFull];
|
|
439
|
+
const started = Date.now();
|
|
440
|
+
const record = {
|
|
441
|
+
name: 'local-provenance',
|
|
442
|
+
command: ['node', 'scripts/release-provenance.mjs', '--out', outputRel].join(' '),
|
|
443
|
+
ok: false,
|
|
444
|
+
status: null,
|
|
445
|
+
signal: null,
|
|
446
|
+
duration_ms: 0,
|
|
447
|
+
output: outputRel,
|
|
448
|
+
stdout_bytes: 0,
|
|
449
|
+
stderr_bytes: 0,
|
|
450
|
+
summary: {},
|
|
451
|
+
};
|
|
452
|
+
let stdout = '';
|
|
453
|
+
let stderr = '';
|
|
454
|
+
try {
|
|
455
|
+
const result = await invokeCommand(commandRunner, command, args, {
|
|
456
|
+
cwd: PROJECT_ROOT,
|
|
457
|
+
env: localOnlyEnv(env),
|
|
458
|
+
timeout: COMMAND_TIMEOUT_MS,
|
|
459
|
+
maxBuffer: MAX_OUTPUT_BYTES,
|
|
460
|
+
windowsHide: true,
|
|
461
|
+
});
|
|
462
|
+
stdout = result.stdout;
|
|
463
|
+
stderr = result.stderr;
|
|
464
|
+
record.ok = true;
|
|
465
|
+
record.status = 0;
|
|
466
|
+
} catch (error) {
|
|
467
|
+
stdout = String(error.stdout ?? '');
|
|
468
|
+
stderr = String(error.stderr ?? '');
|
|
469
|
+
record.status = Number.isInteger(error.code) ? error.code : null;
|
|
470
|
+
record.signal = error.signal ?? null;
|
|
471
|
+
record.error = {
|
|
472
|
+
code: error.killed ? 'COMMAND_TIMEOUT' : (error.code ?? 'COMMAND_FAILED'),
|
|
473
|
+
message: error.message,
|
|
474
|
+
};
|
|
475
|
+
} finally {
|
|
476
|
+
record.duration_ms = Date.now() - started;
|
|
477
|
+
record.stdout_bytes = Buffer.byteLength(stdout);
|
|
478
|
+
record.stderr_bytes = Buffer.byteLength(stderr);
|
|
479
|
+
}
|
|
480
|
+
const provenance = fs.existsSync(outputFull) ? parseJsonOrNull(fs.readFileSync(outputFull, 'utf8')) : null;
|
|
481
|
+
if (provenance !== null) {
|
|
482
|
+
record.summary = summarizeJson(provenance);
|
|
483
|
+
} else {
|
|
484
|
+
const diagnostic = {
|
|
485
|
+
schema: 'enigma.review_packet.command_failure.v1',
|
|
486
|
+
command: record.command,
|
|
487
|
+
status: record.status,
|
|
488
|
+
signal: record.signal,
|
|
489
|
+
stdout_bytes: record.stdout_bytes,
|
|
490
|
+
stderr_bytes: record.stderr_bytes,
|
|
491
|
+
error: record.error ?? { code: 'MISSING_PROVENANCE_JSON', message: 'release-provenance did not write JSON evidence' },
|
|
492
|
+
};
|
|
493
|
+
fs.writeFileSync(outputFull, `${JSON.stringify(diagnostic, null, 2)}\n`);
|
|
494
|
+
record.ok = false;
|
|
495
|
+
record.summary = { schema: diagnostic.schema };
|
|
496
|
+
record.error ??= diagnostic.error;
|
|
497
|
+
}
|
|
498
|
+
return record;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async function writeReleaseAudit(packetRoot, env, commandRunner = null) {
|
|
502
|
+
return runCommand({
|
|
503
|
+
name: 'release-audit',
|
|
504
|
+
command: process.execPath,
|
|
505
|
+
args: ['scripts/release-audit.mjs'],
|
|
506
|
+
outputRel: 'evidence/release-audit.json',
|
|
507
|
+
packetRoot,
|
|
508
|
+
env: { ...env, ENIGMA_REVIEW_PACKET_EMBEDDED: '1' },
|
|
509
|
+
commandRunner,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async function writePackMetadata(packetRoot, env, commandRunner = null) {
|
|
514
|
+
const npm = npmInvocation(['pack', '--dry-run', '--json', '--ignore-scripts']);
|
|
515
|
+
return runCommand({
|
|
516
|
+
name: 'npm-pack-dry-run-json',
|
|
517
|
+
command: npm.command,
|
|
518
|
+
args: npm.args,
|
|
519
|
+
outputRel: 'package/npm-pack-dry-run.json',
|
|
520
|
+
packetRoot,
|
|
521
|
+
env,
|
|
522
|
+
commandRunner,
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function blockersFrom(commands, copyResults) {
|
|
527
|
+
const blockers = [];
|
|
528
|
+
for (const command of commands) {
|
|
529
|
+
if (!command.ok) blockers.push({ kind: 'command', name: command.name, detail: command.error?.message ?? `status ${command.status}` });
|
|
530
|
+
if (Array.isArray(command.summary.required_failed) && command.summary.required_failed.length > 0) {
|
|
531
|
+
blockers.push({ kind: 'release-audit', name: command.name, detail: `required gates failed: ${command.summary.required_failed.join(', ')}` });
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (copyResults.denied.length > 0) {
|
|
535
|
+
blockers.push({ kind: 'denied-site-copy', count: copyResults.denied.length, detail: 'denied public site paths were excluded from the review packet' });
|
|
536
|
+
}
|
|
537
|
+
return blockers.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function ensureCleanOutput(packetRoot) {
|
|
541
|
+
fs.rmSync(packetRoot, { recursive: true, force: true });
|
|
542
|
+
fs.mkdirSync(packetRoot, { recursive: true });
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
export async function buildReviewPacket(options = {}) {
|
|
546
|
+
const packetRoot = resolveOutputDir(options.out ?? DEFAULT_OUT);
|
|
547
|
+
const generatedAt = packetTimestamp(options.now ?? new Date());
|
|
548
|
+
ensureCleanOutput(packetRoot);
|
|
549
|
+
|
|
550
|
+
const sources = copyReviewSourceFiles(packetRoot);
|
|
551
|
+
const docs = copyExistingDocs(packetRoot);
|
|
552
|
+
const site = copyPublicSite(packetRoot, options.publicSite);
|
|
553
|
+
const env = options.env ?? {};
|
|
554
|
+
const commandRunner = options.commandRunner ?? null;
|
|
555
|
+
const commands = [
|
|
556
|
+
await writeProvenance(packetRoot, env, commandRunner),
|
|
557
|
+
await writeReleaseAudit(packetRoot, env, commandRunner),
|
|
558
|
+
await writePackMetadata(packetRoot, env, commandRunner),
|
|
559
|
+
];
|
|
560
|
+
const blockers = blockersFrom(commands, site);
|
|
561
|
+
const manifest = {
|
|
562
|
+
schema: SCHEMA,
|
|
563
|
+
generated_at: generatedAt,
|
|
564
|
+
claim_boundary: CLAIM_BOUNDARY,
|
|
565
|
+
output_dir: displayOutputDir(packetRoot),
|
|
566
|
+
directories: {
|
|
567
|
+
source: sources.copied.length > 0,
|
|
568
|
+
evidence: fs.existsSync(safePacketPath(packetRoot, 'evidence')),
|
|
569
|
+
docs: docs.copied.length > 0,
|
|
570
|
+
package: fs.existsSync(safePacketPath(packetRoot, 'package')),
|
|
571
|
+
site: site.copied.length > 0,
|
|
572
|
+
},
|
|
573
|
+
sources,
|
|
574
|
+
commands,
|
|
575
|
+
docs,
|
|
576
|
+
site: publicSiteSummary(site),
|
|
577
|
+
blockers,
|
|
578
|
+
files: packetFiles(packetRoot),
|
|
579
|
+
};
|
|
580
|
+
const manifestFull = safePacketPath(packetRoot, 'REVIEW_PACKET_MANIFEST.json');
|
|
581
|
+
fs.writeFileSync(manifestFull, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
582
|
+
return manifest;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
586
|
+
try {
|
|
587
|
+
const manifest = await buildReviewPacket(parseArgs(process.argv.slice(2)));
|
|
588
|
+
process.stdout.write(`${JSON.stringify({ ok: true, path: path.join(manifest.output_dir, 'REVIEW_PACKET_MANIFEST.json'), blockers: manifest.blockers.length }, null, 2)}\n`);
|
|
589
|
+
process.exitCode = manifest.blockers.length === 0 ? 0 : 1;
|
|
590
|
+
} catch (error) {
|
|
591
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
592
|
+
process.exitCode = 1;
|
|
593
|
+
}
|
|
594
|
+
}
|