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.
Files changed (105) hide show
  1. package/LICENSE +55 -0
  2. package/README.md +379 -0
  3. package/apps/cli/bin/enigma.mjs +1117 -0
  4. package/apps/desktop/src/app.js +1327 -0
  5. package/apps/desktop/src/index.html +1373 -0
  6. package/apps/desktop/src/styles.css +386 -0
  7. package/apps/gateway/bin/enigma-gateway.mjs +52 -0
  8. package/apps/gateway/src/server.mjs +1403 -0
  9. package/apps/native-host/README.md +265 -0
  10. package/apps/native-host/bin/enigma-native-host.mjs +499 -0
  11. package/apps/native-host/manifests/com.enigma.native_host.chrome.json +9 -0
  12. package/apps/native-host/manifests/com.enigma.native_host.edge.json +9 -0
  13. package/apps/native-host/manifests/com.enigma.native_host.firefox.json +9 -0
  14. package/apps/relay/bin/enigma-relay.mjs +52 -0
  15. package/apps/relay/src/server.mjs +1571 -0
  16. package/apps/verifier/bin/enigma-verify.mjs +240 -0
  17. package/deploy/docker-compose.production.example.yml +150 -0
  18. package/deploy/kubernetes/enigma-backend.example.yaml +580 -0
  19. package/package.json +196 -0
  20. package/packages/adapters/PACKAGE_CONTRACT.md +47 -0
  21. package/packages/adapters/src/index.js +183 -0
  22. package/packages/boundary/src/index.js +468 -0
  23. package/packages/connectors/src/index.js +533 -0
  24. package/packages/core/src/index.js +766 -0
  25. package/packages/enterprise/src/index.js +505 -0
  26. package/packages/importers/src/index.js +1083 -0
  27. package/packages/mcp-server/bin/enigma-mcp.mjs +19 -0
  28. package/packages/mcp-server/src/index.js +1185 -0
  29. package/packages/mesh/src/index.js +531 -0
  30. package/packages/metering/PACKAGE_CONTRACT.md +20 -0
  31. package/packages/metering/src/index.js +246 -0
  32. package/packages/optimizer/src/index.js +707 -0
  33. package/packages/passport/src/index.js +651 -0
  34. package/packages/settlement/PACKAGE_CONTRACT.md +28 -0
  35. package/packages/settlement/src/index.js +369 -0
  36. package/packages/storage/PACKAGE_CONTRACT.md +23 -0
  37. package/packages/storage/src/index.js +392 -0
  38. package/packages/vault/src/index.js +904 -0
  39. package/scripts/build-ai-orchestration-plan.mjs +248 -0
  40. package/scripts/build-cloudflare-pages-release-packet.mjs +287 -0
  41. package/scripts/build-cloudflare-token-policy.mjs +239 -0
  42. package/scripts/build-cloudflare-token-request.mjs +314 -0
  43. package/scripts/build-demo-assets.mjs +263 -0
  44. package/scripts/build-edge-backend-workers.mjs +353 -0
  45. package/scripts/build-goal-completion-audit.mjs +398 -0
  46. package/scripts/build-hosted-probe-worker.mjs +191 -0
  47. package/scripts/build-operator-acceptance-packet.mjs +254 -0
  48. package/scripts/build-operator-evidence-starter.mjs +525 -0
  49. package/scripts/build-production-backend-env-kit.mjs +384 -0
  50. package/scripts/build-production-dependency-report.mjs +369 -0
  51. package/scripts/build-production-handoff-packet.mjs +475 -0
  52. package/scripts/build-production-readiness-manifest.mjs +209 -0
  53. package/scripts/build-production-status-board.mjs +297 -0
  54. package/scripts/build-production-storage-migration.mjs +86 -0
  55. package/scripts/build-production-workplan.mjs +371 -0
  56. package/scripts/build-review-packet.mjs +594 -0
  57. package/scripts/check.mjs +419 -0
  58. package/scripts/cloudflare-ops.mjs +1672 -0
  59. package/scripts/cloudflare-secret-env.mjs +128 -0
  60. package/scripts/collect-hosted-backend-live-evidence.mjs +263 -0
  61. package/scripts/infrastructure-readiness.mjs +944 -0
  62. package/scripts/install-enigma-local.mjs +270 -0
  63. package/scripts/memory-optimization-benchmark.mjs +242 -0
  64. package/scripts/monitor-live-endpoints.mjs +358 -0
  65. package/scripts/release-audit.mjs +5588 -0
  66. package/scripts/release-provenance.mjs +250 -0
  67. package/scripts/run-backend-readiness-smoke.mjs +220 -0
  68. package/scripts/validate-backup-restore-drill.mjs +166 -0
  69. package/scripts/validate-cloudflare-credentials.mjs +112 -0
  70. package/scripts/validate-cloudflare-worker-inspect.mjs +179 -0
  71. package/scripts/validate-domain-tls.mjs +248 -0
  72. package/scripts/validate-hosted-backend-live.mjs +319 -0
  73. package/scripts/validate-incident-drill.mjs +221 -0
  74. package/scripts/validate-kms-custody.mjs +235 -0
  75. package/scripts/validate-legal-compliance-approval.mjs +274 -0
  76. package/scripts/validate-monitoring-alerting.mjs +263 -0
  77. package/scripts/validate-network-access-policy.mjs +285 -0
  78. package/scripts/validate-operator-acceptance.mjs +298 -0
  79. package/scripts/validate-production-manifests.mjs +325 -0
  80. package/scripts/validate-public-site-security.mjs +252 -0
  81. package/scripts/validate-security-threat-model.mjs +285 -0
  82. package/scripts/validate-service-settlement.mjs +256 -0
  83. package/scripts/validate-support-sla.mjs +190 -0
  84. package/scripts/validate-tenant-policy-approval.mjs +228 -0
  85. package/scripts/validate-usage-metering.mjs +224 -0
  86. package/scripts/validate-whitepaper-claims.mjs +166 -0
  87. package/specs/boundary-manifest-v1.schema.json +35 -0
  88. package/specs/capsule-v1.schema.json +55 -0
  89. package/specs/claim-boundary-manifest-v1.schema.json +22 -0
  90. package/specs/connector-profile-v1.schema.json +62 -0
  91. package/specs/deletion-tombstone-v1.schema.json +26 -0
  92. package/specs/enterprise-policy-v1.schema.json +59 -0
  93. package/specs/gateway-request-v1.schema.json +32 -0
  94. package/specs/goal-completion-audit-v1.schema.json +70 -0
  95. package/specs/hosted-backend-live-evidence-v1.schema.json +147 -0
  96. package/specs/import-report-v1.schema.json +214 -0
  97. package/specs/infrastructure-readiness-manifest-v1.schema.json +130 -0
  98. package/specs/memory-event-v1.schema.json +42 -0
  99. package/specs/mesh-node-v1.schema.json +126 -0
  100. package/specs/passport-v1.schema.json +50 -0
  101. package/specs/production-handoff-packet-v1.schema.json +149 -0
  102. package/specs/receipt-v1.schema.json +61 -0
  103. package/specs/relay-record-v1.schema.json +43 -0
  104. package/specs/state-checkpoint-v1.schema.json +37 -0
  105. package/specs/trust-bundle-v1.schema.json +56 -0
@@ -0,0 +1,475 @@
1
+ #!/usr/bin/env node
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+ import { buildCloudflarePagesReleasePacket } from './build-cloudflare-pages-release-packet.mjs';
6
+ import { buildProductionReadinessManifest } from './build-production-readiness-manifest.mjs';
7
+ import {
8
+ assertNoSecretMaterial,
9
+ INFRASTRUCTURE_READINESS_SCHEMA,
10
+ runInfrastructureReadiness,
11
+ } from './infrastructure-readiness.mjs';
12
+ import { buildOperatorAcceptancePacket } from './build-operator-acceptance-packet.mjs';
13
+ import { validateOperatorAcceptancePacket } from './validate-operator-acceptance.mjs';
14
+ import { applyCloudflareSecretEnvFile } from './cloudflare-secret-env.mjs';
15
+ import { buildHostedProbeWorkerBundle } from './build-hosted-probe-worker.mjs';
16
+
17
+ export const PRODUCTION_HANDOFF_PACKET_SCHEMA = 'enigma.production_handoff_packet.v1';
18
+ const RELEASE_AUDIT_SCHEMA = 'enigma.release_audit.v1';
19
+ const COMPLETED_INFRASTRUCTURE_CHECK_NAMES = Object.freeze([
20
+ 'manifest.secret_scan',
21
+ 'manifest.schema',
22
+ 'readiness.contract',
23
+ 'hosted.required_refs',
24
+ 'operator_acceptance.decision',
25
+ 'external_blockers.manifest',
26
+ 'hosted.allow_localhost_boundary',
27
+ 'public_site.live',
28
+ 'relay.live',
29
+ 'gateway.live',
30
+ 'cloudflare.observation',
31
+ ]);
32
+
33
+ const SECRET_OUTPUT_RE = /(?:Bearer\s+[A-Za-z0-9._~+/=-]{12,}|Basic\s+[A-Za-z0-9+/=-]{12,}|-----BEGIN [A-Z ]*PRIVATE KEY-----|sk-[A-Za-z0-9_-]{16,}|AKIA[0-9A-Z]{16}|cf-[A-Za-z0-9_-]{12,})/iu;
34
+ const RAW_OUTPUT_RE = /(?:raw[\s_-]*memory|private[\s_-]*prompt|full[\s_-]*transcript|decrypted[\s_-]*(?:capsule|content)|provider[\s_-]*response|plain[\s_-]*text)/iu;
35
+
36
+ class UsageError extends Error {
37
+ constructor(message) {
38
+ super(message);
39
+ this.name = 'UsageError';
40
+ }
41
+ }
42
+
43
+ function requireText(name, value) {
44
+ if (typeof value !== 'string' || value.trim().length === 0) throw new UsageError(`${name} is required`);
45
+ return value.trim();
46
+ }
47
+
48
+ function assertNoUnsafeOutput(name, value) {
49
+ const text = JSON.stringify(value);
50
+ if (SECRET_OUTPUT_RE.test(text) || RAW_OUTPUT_RE.test(text)) throw new Error(`${name} appears to contain secret or raw-memory material`);
51
+ }
52
+
53
+ function assertNoUnsafeMaterial(name, value, path = name) {
54
+ if (typeof value === 'string') {
55
+ if (SECRET_OUTPUT_RE.test(value)) throw new Error(`${path} appears to contain secret material`);
56
+ if (!/\.claim_boundary\[\d+\]$/.test(path) && RAW_OUTPUT_RE.test(value)) throw new Error(`${path} appears to contain raw-memory material`);
57
+ return;
58
+ }
59
+ if (Array.isArray(value)) {
60
+ value.forEach((item, index) => assertNoUnsafeMaterial(name, item, `${path}[${index}]`));
61
+ return;
62
+ }
63
+ if (value !== null && typeof value === 'object') {
64
+ for (const [key, item] of Object.entries(value)) assertNoUnsafeMaterial(name, item, `${path}.${key}`);
65
+ }
66
+ }
67
+
68
+ function assertNoSecretOutput(name, value, path = name) {
69
+ if (typeof value === 'string') {
70
+ if (SECRET_OUTPUT_RE.test(value)) throw new Error(`${path} appears to contain secret material`);
71
+ return;
72
+ }
73
+ if (Array.isArray(value)) {
74
+ value.forEach((item, index) => assertNoSecretOutput(name, item, `${path}[${index}]`));
75
+ return;
76
+ }
77
+ if (value !== null && typeof value === 'object') {
78
+ for (const [key, item] of Object.entries(value)) assertNoSecretOutput(name, item, `${path}.${key}`);
79
+ }
80
+ }
81
+
82
+ function optionalText(value, fallback = null) {
83
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : fallback;
84
+ }
85
+
86
+ function shellArg(value) {
87
+ const text = requireText('command argument', value);
88
+ if (/^[A-Za-z0-9_./:@%+=,-]+$/u.test(text)) return text;
89
+ return `"${text.replace(/"/g, '\\"')}"`;
90
+ }
91
+
92
+ function parseArgs(argv) {
93
+ const out = {
94
+ site: null,
95
+ projectName: 'enigma-memory',
96
+ domain: 'enigmamemory.com',
97
+ liveUrl: 'https://enigmamemory.com/',
98
+ expectTitle: 'Enigma',
99
+ out: null,
100
+ envFile: null,
101
+ operatorAcceptancePacket: null,
102
+ infrastructureReadiness: null,
103
+ releaseAudit: null,
104
+ };
105
+ for (let index = 0; index < argv.length;) {
106
+ const token = argv[index];
107
+ if (token === '--help') return { help: true };
108
+ const readValue = (name) => {
109
+ if (index + 1 >= argv.length || argv[index + 1].startsWith('--')) throw new UsageError(`${name} requires a value`);
110
+ const value = argv[index + 1];
111
+ index += 2;
112
+ return value;
113
+ };
114
+ if (token === '--site') out.site = readValue(token);
115
+ else if (token === '--project-name') out.projectName = readValue(token);
116
+ else if (token === '--domain') out.domain = readValue(token);
117
+ else if (token === '--live-url') out.liveUrl = readValue(token);
118
+ else if (token === '--expect-title') out.expectTitle = readValue(token);
119
+ else if (token === '--cloudflare-env-file') out.envFile = readValue(token);
120
+ else if (token === '--operator-acceptance-packet' || token === '--operatorAcceptancePacket') out.operatorAcceptancePacket = readValue(token);
121
+ else if (token === '--infrastructure-readiness' || token === '--infrastructureReadiness') out.infrastructureReadiness = readValue(token);
122
+ else if (token === '--release-audit' || token === '--releaseAudit') out.releaseAudit = readValue(token);
123
+ else if (token === '--out') out.out = readValue(token);
124
+ else throw new UsageError(`unknown option ${token}`);
125
+ }
126
+ return out;
127
+ }
128
+
129
+ function usage() {
130
+ return `Usage: node scripts/build-production-handoff-packet.mjs --site <dir> [options]\n\nOptions:\n --project-name <name> Cloudflare Pages project name. Default: enigma-memory.\n --domain <host> Public domain. Default: enigmamemory.com.\n --live-url <url> Current public URL to observe. Default: https://enigmamemory.com/.\n --expect-title <text> Expected live title fragment. Default: Enigma.\n --infrastructure-readiness <file> Completed infrastructure readiness JSON to summarize.\n --operator-acceptance-packet <file> Completed operator packet JSON to validate and summarize.\n --release-audit <file> Completed enigma.release_audit.v1 JSON proving current release gates.\n --cloudflare-env-file <path> Optional local .env-style Cloudflare secret file; values are loaded but never printed.\n --out <file> Write packet JSON to a file and print the same JSON.`;
131
+ }
132
+
133
+ function summarizeInfrastructure(readiness) {
134
+ const checks = Array.isArray(readiness.checks) ? readiness.checks : [];
135
+ const failedChecks = checks.filter((check) => check?.ok !== true);
136
+ const hostedRefs = checks.find((check) => check.name === 'hosted.required_refs') ?? {};
137
+ return {
138
+ schema: readiness.schema,
139
+ ok: readiness.ok === true,
140
+ mode: readiness.mode,
141
+ contract_ready: readiness.readiness?.contract_ready === true,
142
+ hosted_live_ready: readiness.readiness?.hosted_live_ready === true,
143
+ public_live_ready: readiness.readiness?.public_live_ready === true,
144
+ cloudflare_observed: readiness.readiness?.cloudflare_observed === true,
145
+ check_count: checks.length,
146
+ failed_check_count: failedChecks.length,
147
+ failed_checks: failedChecks.slice(0, 20).map((check) => check?.name ?? 'unnamed-check'),
148
+ hosted_required_ref_count: hostedRefs.required_count ?? null,
149
+ hosted_required_ref_missing_count: hostedRefs.missing_count ?? null,
150
+ external_blocker_count: Array.isArray(readiness.external_blockers) ? readiness.external_blockers.length : null,
151
+ external_blockers: Array.isArray(readiness.external_blockers) ? readiness.external_blockers.slice(0, 40) : [],
152
+ };
153
+ }
154
+
155
+ function infrastructureBlockers(summary) {
156
+ const blockers = [];
157
+ if (summary.mode !== 'live') blockers.push(`mode is ${summary.mode}`);
158
+ if (summary.contract_ready !== true) blockers.push('contract_ready is false');
159
+ if (summary.public_live_ready !== true) blockers.push('public_live_ready is false');
160
+ if (summary.cloudflare_observed !== true) blockers.push('cloudflare_observed is false');
161
+ if (summary.hosted_live_ready !== true) blockers.push('hosted_live_ready is false');
162
+ if (typeof summary.hosted_required_ref_missing_count === 'number' && summary.hosted_required_ref_missing_count !== 0) blockers.push(`hosted missing refs: ${summary.hosted_required_ref_missing_count}`);
163
+ for (const name of summary.failed_checks ?? []) blockers.push(`failed check ${name}`);
164
+ for (const item of summary.external_blockers ?? []) blockers.push(item);
165
+ return blockers;
166
+ }
167
+
168
+ function summarizeReleaseAudit(audit) {
169
+ if (audit === null) {
170
+ return {
171
+ schema: RELEASE_AUDIT_SCHEMA,
172
+ ok: false,
173
+ provided_audit: false,
174
+ required_failed_count: null,
175
+ gate_count: null,
176
+ generated_at: null,
177
+ };
178
+ }
179
+ return {
180
+ schema: audit.schema,
181
+ ok: audit.ok === true,
182
+ provided_audit: true,
183
+ required_failed_count: Array.isArray(audit.required_failed) ? audit.required_failed.length : null,
184
+ gate_count: Array.isArray(audit.gates) ? audit.gates.length : null,
185
+ gate_names: Array.isArray(audit.gates) ? audit.gates.map((gate) => gate?.name).filter((name) => typeof name === 'string').slice(0, 80) : [],
186
+ generated_at: typeof audit.generated_at === 'string' ? audit.generated_at : null,
187
+ };
188
+ }
189
+
190
+ function summarizePages(packet) {
191
+ return {
192
+ schema: packet.schema,
193
+ local_artifact_ready: packet.local_artifact_ready === true,
194
+ automated_deploy_ready: packet.automated_deploy_ready === true,
195
+ credential_present: packet.credential_present === true,
196
+ artifact_file_count: packet.artifact?.file_count ?? null,
197
+ artifact_root_hash: packet.artifact?.root_hash ?? null,
198
+ security_blocker_count: packet.security?.blocker_count ?? null,
199
+ deployment_blockers: Array.isArray(packet.deployment_blockers) ? packet.deployment_blockers : [],
200
+ live_observation: packet.live_observation === null ? null : {
201
+ ok: packet.live_observation.ok === true,
202
+ live_url: packet.live_observation.live_url,
203
+ status: packet.live_observation.status,
204
+ title: packet.live_observation.title,
205
+ title_matched: packet.live_observation.title_matched === true,
206
+ blockers: packet.live_observation.blockers ?? [],
207
+ },
208
+ };
209
+ }
210
+
211
+ function buildNextActions({ projectName, domain, credentialsPresent, pages, infrastructure, operatorAcceptance, releaseAudit }) {
212
+ const actions = [];
213
+ const credentialsReady = credentialsPresent?.cloudflare_api_token === true && credentialsPresent?.cloudflare_account_id === true;
214
+ const pagesReady = pages?.automated_deploy_ready === true && pages?.live_observation?.ok === true;
215
+ if (!credentialsReady) {
216
+ actions.push({
217
+ id: 'create-cloudflare-token',
218
+ owner: 'operator',
219
+ command: 'Create or inject CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID with least-privilege Pages access; do not paste token into chat.',
220
+ evidence: 'npm run cloudflare:ops -- token verify --account-id <account-id>',
221
+ });
222
+ }
223
+ if (!pagesReady) {
224
+ actions.push({
225
+ id: 'deploy-current-static-site',
226
+ owner: 'operator-or-ai-with-token',
227
+ command: `npm run cloudflare:ops -- pages deploy --site <local-site-dir> --project-name ${shellArg(projectName)} --execute`,
228
+ evidence: `npm run cloudflare:ops -- pages verify --url ${shellArg(`https://${domain}/`)} --project-name ${shellArg(projectName)} --domain ${shellArg(domain)} --cloudflare-live required`,
229
+ });
230
+ }
231
+ if (infrastructure?.hosted_live_ready !== true) {
232
+ actions.push({
233
+ id: 'optional-standalone-worker-probe',
234
+ owner: 'operator-or-ai-with-token',
235
+ command: 'npm run production:hosted-probe -- --out-dir <dir> && npm run cloudflare:ops -- workers inspect-probe --name enigma-hosted-probe && npm run cloudflare:ops -- workers deploy-probe --script <dir>/worker.mjs --name enigma-hosted-probe --execute',
236
+ evidence: 'Cloudflare Worker /livez 200 and /readyz fail-closed until all refs and operator go exist; standalone edge probe only, not relay/gateway live evidence',
237
+ });
238
+ actions.push({
239
+ id: 'provision-hosted-backend',
240
+ owner: 'operator',
241
+ command: 'Deploy relay/gateway using deploy/docker-compose.production.example.yml or deploy/kubernetes/enigma-backend.example.yaml with all ENIGMA_*_REF evidence refs and secrets provided out-of-band.',
242
+ evidence: 'npm run infrastructure:readiness -- --manifest <completed-manifest.json> --live --cloudflare-live required',
243
+ }, {
244
+ id: 'validate-hosted-backend-live-evidence',
245
+ owner: 'operator-or-reviewer',
246
+ command: 'npm run production:hosted-live -- --evidence <hosted-backend-live.json>',
247
+ evidence: 'accepted enigma.hosted_backend_live_result.v1 with four public HTTPS relay/gateway /livez and /readyz probes, all hosted refs, and operator acceptance go',
248
+ });
249
+ }
250
+ if (operatorAcceptance?.ok !== true) {
251
+ actions.push({
252
+ id: 'generate-operator-evidence-starter',
253
+ owner: 'operator-or-reviewer',
254
+ command: `npm run production:evidence-starter -- --out-dir <evidence-dir> --domain ${shellArg(domain)} --tenant <tenant-id>`,
255
+ evidence: '<evidence-dir>/hosted-refs.template.json, hosted-backend-live.template.json, owner-approval-refs.template.json, evidence-refs.template.json, hosted-backend-live-collection.json/hosted-backend-live.json flow, acceptance-fill-plan.json, plus production:acceptance:packet and production:acceptance validation',
256
+ });
257
+ actions.push({
258
+ id: 'complete-operator-acceptance',
259
+ owner: 'operator',
260
+ command: 'npm run production:acceptance:packet -- --out <evidence-dir>/operator-acceptance-packet.json --owners-json <evidence-dir>/owner-approval-refs.json --evidence-refs <evidence-dir>/evidence-refs.json --readiness <evidence-dir>/infrastructure-readiness-live.json --manifest <evidence-dir>/infrastructure-readiness-manifest.json --storage <evidence-dir>/production-storage-migration.json --release-audit .enigma/release-audit-current.json --production-manifests <evidence-dir>/production-manifests.json --decision go --tenant <tenant-id> --target-regions <regions> --requested-go-live-date <date> --evidence-repository <evidence-repository> --packet-owner <operator> --validate && npm run production:acceptance -- --packet <evidence-dir>/operator-acceptance-packet.json',
261
+ evidence: '<evidence-dir>/operator-acceptance-packet.json accepted with decision go and zero blockers',
262
+ });
263
+ }
264
+ if (releaseAudit?.ok !== true) {
265
+ actions.push({
266
+ id: 'final-release-audit',
267
+ owner: 'reviewer',
268
+ command: 'npm run check && npm test && npm run release:audit',
269
+ evidence: 'release audit ok:true and required_failed:[]',
270
+ });
271
+ }
272
+ return actions;
273
+ }
274
+
275
+ async function readJsonInput(path, label) {
276
+ try {
277
+ return JSON.parse(await readFile(resolve(path), 'utf8'));
278
+ } catch (error) {
279
+ if (error instanceof SyntaxError) throw new UsageError(`${label} JSON is invalid`);
280
+ throw new UsageError(`${label} JSON could not be read`);
281
+ }
282
+ }
283
+
284
+ async function loadOperatorAcceptancePacket(input) {
285
+ if (input.operatorPacket && typeof input.operatorPacket === 'object') return input.operatorPacket;
286
+ if (input.operatorAcceptancePacket && typeof input.operatorAcceptancePacket === 'object') return input.operatorAcceptancePacket;
287
+ const packetPath = optionalText(input.operatorAcceptancePacketPath ?? input.operatorAcceptancePacket, null);
288
+ if (packetPath === null) return null;
289
+ return await readJsonInput(packetPath, 'operator acceptance packet');
290
+ }
291
+
292
+ async function loadInfrastructureReadiness(input) {
293
+ if (input.infrastructureReadiness && typeof input.infrastructureReadiness === 'object') return input.infrastructureReadiness;
294
+ const readinessPath = optionalText(input.infrastructureReadinessPath ?? input.infrastructureReadiness, null);
295
+ if (readinessPath === null) return null;
296
+ return await readJsonInput(readinessPath, 'infrastructure readiness');
297
+ }
298
+
299
+ async function loadReleaseAudit(input) {
300
+ if (input.releaseAudit && typeof input.releaseAudit === 'object') return input.releaseAudit;
301
+ const auditPath = optionalText(input.releaseAuditPath ?? input.releaseAudit, null);
302
+ if (auditPath === null) return null;
303
+ return await readJsonInput(auditPath, 'release audit');
304
+ }
305
+
306
+ function requireInfrastructureReadinessResult(value) {
307
+ if (value === null) return null;
308
+ assertNoSecretMaterial(value, { path: 'infrastructure_readiness' });
309
+ assertNoUnsafeMaterial('infrastructure readiness JSON', value);
310
+ if (value?.schema !== INFRASTRUCTURE_READINESS_SCHEMA) throw new UsageError(`infrastructure readiness JSON must use schema ${INFRASTRUCTURE_READINESS_SCHEMA}`);
311
+ if (value.ok !== true) throw new UsageError('infrastructure readiness JSON must have ok:true');
312
+ if (value.mode !== 'live') throw new UsageError('infrastructure readiness JSON must be live-mode evidence');
313
+ if (value.readiness?.contract_ready !== true) throw new UsageError('infrastructure readiness JSON must have readiness.contract_ready:true');
314
+ if (value.readiness?.public_live_ready !== true) throw new UsageError('infrastructure readiness JSON must have readiness.public_live_ready:true');
315
+ if (value.readiness?.cloudflare_observed !== true) throw new UsageError('infrastructure readiness JSON must have readiness.cloudflare_observed:true');
316
+ if (value.readiness?.hosted_live_ready !== true) throw new UsageError('infrastructure readiness JSON must have readiness.hosted_live_ready:true');
317
+ if (!Array.isArray(value.checks) || value.checks.length === 0) throw new UsageError('infrastructure readiness JSON must include passing checks');
318
+ const checkNames = new Set(value.checks.map((check) => check?.name).filter((name) => typeof name === 'string'));
319
+ const missingChecks = COMPLETED_INFRASTRUCTURE_CHECK_NAMES.filter((name) => !checkNames.has(name));
320
+ if (missingChecks.length > 0) throw new UsageError(`infrastructure readiness JSON missing completed live checks: ${missingChecks.join(', ')}`);
321
+ const failedChecks = value.checks.filter((check) => check?.ok !== true);
322
+ if (failedChecks.length > 0) throw new UsageError('infrastructure readiness JSON must not include failed checks');
323
+ const hostedRefs = value.checks.find((check) => check?.name === 'hosted.required_refs');
324
+ if (hostedRefs?.missing_count !== 0) throw new UsageError('infrastructure readiness JSON must have zero hosted.required_refs missing_count');
325
+ if (!Array.isArray(value.external_blockers) || value.external_blockers.length !== 0) throw new UsageError('infrastructure readiness JSON must have zero external_blockers');
326
+ return value;
327
+ }
328
+ function requireReleaseAuditResult(value) {
329
+ if (value === null) return null;
330
+ if (value?.schema !== RELEASE_AUDIT_SCHEMA) throw new UsageError(`release audit JSON must use schema ${RELEASE_AUDIT_SCHEMA}`);
331
+ if (value.ok !== true) throw new UsageError('release audit JSON must have ok:true');
332
+ if (!Array.isArray(value.required_failed)) throw new UsageError('release audit JSON must include required_failed array');
333
+ if (value.required_failed.length !== 0) throw new UsageError('release audit JSON must have zero required_failed gates');
334
+ if (!Array.isArray(value.gates) || value.gates.length === 0) throw new UsageError('release audit JSON must include gate evidence');
335
+ return value;
336
+ }
337
+
338
+ export async function buildProductionHandoffPacket(input = {}, options = {}) {
339
+ const env = options.env ?? process.env;
340
+ const generatedAt = options.generated_at ?? options.generatedAt ?? new Date().toISOString();
341
+ const site = resolve(requireText('--site', input.site));
342
+ const projectName = requireText('--project-name', input.projectName ?? 'enigma-memory');
343
+ const domain = optionalText(input.domain, 'enigmamemory.com');
344
+ const liveUrl = optionalText(input.liveUrl, domain ? `https://${domain}/` : null);
345
+ const expectTitle = optionalText(input.expectTitle, 'Enigma');
346
+
347
+ const pages = await buildCloudflarePagesReleasePacket({
348
+ site,
349
+ projectName,
350
+ domain,
351
+ liveUrl,
352
+ expectTitle,
353
+ }, {
354
+ env,
355
+ generated_at: generatedAt,
356
+ fetchImpl: options.fetchImpl ?? globalThis.fetch,
357
+ });
358
+
359
+ const providedInfrastructure = requireInfrastructureReadinessResult(await loadInfrastructureReadiness(input));
360
+ const infrastructure = providedInfrastructure ?? await runInfrastructureReadiness({
361
+ manifest: await buildProductionReadinessManifest({
362
+ env,
363
+ generated_at: generatedAt,
364
+ }),
365
+ }, {
366
+ env,
367
+ live: false,
368
+ cloudflareLive: 'off',
369
+ now: new Date(generatedAt),
370
+ fetchImpl: options.fetchImpl ?? globalThis.fetch,
371
+ });
372
+ const operatorPacket = await loadOperatorAcceptancePacket(input) ?? await buildOperatorAcceptancePacket({ generated_at: generatedAt });
373
+ assertNoSecretOutput('operator acceptance packet', operatorPacket);
374
+ const operatorAcceptance = validateOperatorAcceptancePacket(operatorPacket, { generated_at: generatedAt });
375
+ if (operatorAcceptance.ok === true) requireInfrastructureReadinessResult(operatorPacket.readiness);
376
+ const providedReleaseAudit = requireReleaseAuditResult(await loadReleaseAudit(input));
377
+
378
+ const hostedProbe = buildHostedProbeWorkerBundle({ generated_at: generatedAt });
379
+ const pageSummary = summarizePages(pages);
380
+ const infrastructureSummary = summarizeInfrastructure(infrastructure);
381
+ infrastructureSummary.provided_readiness = providedInfrastructure !== null;
382
+ assertNoUnsafeOutput('infrastructure readiness summary', infrastructureSummary);
383
+ const operatorSummary = {
384
+ schema: operatorAcceptance.schema,
385
+ ok: operatorAcceptance.ok === true,
386
+ decision: operatorAcceptance.decision,
387
+ blocker_count: operatorAcceptance.blockers.length,
388
+ warning_count: operatorAcceptance.warnings.length,
389
+ blocker_breakdown: operatorAcceptance.blocker_breakdown ?? {},
390
+ packet_id: operatorPacket.metadata?.packet_id ?? null,
391
+ provided_packet: (input.operatorPacket && typeof input.operatorPacket === 'object') || (input.operatorAcceptancePacket && typeof input.operatorAcceptancePacket === 'object') || typeof input.operatorAcceptancePacket === 'string' || typeof input.operatorAcceptancePacketPath === 'string',
392
+ };
393
+ assertNoUnsafeOutput('operator acceptance summary', operatorSummary);
394
+ const credentialSummary = {
395
+ cloudflare_api_token: typeof env.CLOUDFLARE_API_TOKEN === 'string' && env.CLOUDFLARE_API_TOKEN.length > 0,
396
+ cloudflare_account_id: typeof env.CLOUDFLARE_ACCOUNT_ID === 'string' && env.CLOUDFLARE_ACCOUNT_ID.length > 0,
397
+ };
398
+
399
+ const releaseAuditSummary = summarizeReleaseAudit(providedReleaseAudit);
400
+ assertNoUnsafeOutput('release audit summary', releaseAuditSummary);
401
+
402
+ const blockers = [
403
+ ...pageSummary.deployment_blockers.map((item) => `pages: ${item}`),
404
+ ...infrastructureBlockers(infrastructureSummary).map((item) => `infrastructure: ${item}`),
405
+ ...(operatorSummary.ok ? [] : [`operator_acceptance: ${operatorSummary.blocker_count} blockers`]),
406
+ ...(releaseAuditSummary.ok ? [] : ['release_audit: accepted current release audit evidence required']),
407
+ ];
408
+
409
+ const packet = {
410
+ schema: PRODUCTION_HANDOFF_PACKET_SCHEMA,
411
+ generated_at: generatedAt,
412
+ project: {
413
+ name: 'Enigma Memory',
414
+ domain,
415
+ cloudflare_pages_project: projectName,
416
+ local_site_path_redacted: true,
417
+ },
418
+ go_live_ready: pageSummary.automated_deploy_ready === true
419
+ && infrastructureSummary.hosted_live_ready === true
420
+ && operatorSummary.ok === true
421
+ && releaseAuditSummary.ok === true,
422
+ local_static_artifact_ready: pageSummary.local_artifact_ready === true,
423
+ credentials_present: credentialSummary,
424
+ pages: pageSummary,
425
+ infrastructure: infrastructureSummary,
426
+ operator_acceptance: operatorSummary,
427
+ release_audit: releaseAuditSummary,
428
+ hosted_probe_worker: {
429
+ schema: hostedProbe.schema,
430
+ ok: hostedProbe.ok,
431
+ worker_name: hostedProbe.worker_name,
432
+ source_hash: hostedProbe.validation.source_hash,
433
+ required_env_ref_count: hostedProbe.required_env_refs.length,
434
+ default_routes: hostedProbe.deployment_plan.default_routes,
435
+ mutates_cloudflare: hostedProbe.deployment_plan.mutates_cloudflare,
436
+ claim_boundary: hostedProbe.claim_boundary,
437
+ },
438
+ next_actions: buildNextActions({ projectName, domain, credentialsPresent: credentialSummary, pages: pageSummary, infrastructure: infrastructureSummary, operatorAcceptance: operatorSummary, releaseAudit: releaseAuditSummary }),
439
+ blockers: [...new Set(blockers)],
440
+ claim_boundary: [
441
+ 'This is a handoff packet for production operators and follow-on AI assistants.',
442
+ 'It summarizes current local artifacts and blockers without printing tokens, credentials, local paths, raw memory, personal contact data, or provider secrets.',
443
+ 'go_live_ready is false until Cloudflare deploy evidence, hosted backend live checks, completed operator acceptance, and release audit evidence are all current and direct.',
444
+ ],
445
+ };
446
+ assertNoUnsafeMaterial('production handoff packet', packet);
447
+ return packet;
448
+ }
449
+
450
+ export async function runCli(argv = process.argv.slice(2), options = {}) {
451
+ const parsed = parseArgs(argv);
452
+ if (parsed.help) return { text: usage() };
453
+ const secretEnv = await applyCloudflareSecretEnvFile(options.env ?? process.env, { path: parsed.envFile ?? undefined, includePath: false });
454
+ const packet = await buildProductionHandoffPacket(parsed, { ...options, env: secretEnv.env });
455
+ const json = `${JSON.stringify(packet, null, 2)}\n`;
456
+ if (SECRET_OUTPUT_RE.test(json)) throw new Error('handoff packet output appears to contain a secret');
457
+ if (parsed.out) {
458
+ const outPath = resolve(parsed.out);
459
+ await mkdir(dirname(outPath), { recursive: true });
460
+ await writeFile(outPath, json, 'utf8');
461
+ }
462
+ return { json: packet };
463
+ }
464
+
465
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
466
+ try {
467
+ const result = await runCli();
468
+ if (result.text) process.stdout.write(result.text);
469
+ else process.stdout.write(`${JSON.stringify(result.json, null, 2)}\n`);
470
+ } catch (error) {
471
+ const message = error instanceof Error ? error.message : String(error);
472
+ process.stdout.write(`${JSON.stringify({ schema: PRODUCTION_HANDOFF_PACKET_SCHEMA, ok: false, error: { code: error instanceof UsageError ? 'USAGE_ERROR' : 'HANDOFF_PACKET_ERROR', message } }, null, 2)}\n`);
473
+ process.exitCode = error instanceof UsageError ? 2 : 1;
474
+ }
475
+ }