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,209 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { writeFile, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import {
|
|
6
|
+
HOSTED_READINESS_REQUIRED_DEPENDENCIES,
|
|
7
|
+
normalizeDependencyEvidence,
|
|
8
|
+
readinessEvidenceRefs,
|
|
9
|
+
} from '../packages/adapters/src/index.js';
|
|
10
|
+
import { INFRASTRUCTURE_READINESS_MANIFEST_SCHEMA, validateInfrastructureReadinessManifest } from './infrastructure-readiness.mjs';
|
|
11
|
+
|
|
12
|
+
const GATEWAY_REQUIRED_DEPENDENCIES = Object.freeze([
|
|
13
|
+
'backend_host',
|
|
14
|
+
'dns_tls',
|
|
15
|
+
'durable_storage',
|
|
16
|
+
'kms_or_secret_custody',
|
|
17
|
+
'backup_restore',
|
|
18
|
+
'monitoring',
|
|
19
|
+
'siem_or_log_sink',
|
|
20
|
+
'operator_acceptance',
|
|
21
|
+
'runtime_auth',
|
|
22
|
+
'admin_auth',
|
|
23
|
+
'data_plane_auth',
|
|
24
|
+
'network_access_policy',
|
|
25
|
+
'kms_custody',
|
|
26
|
+
'tenant_policy_approval',
|
|
27
|
+
'usage_metering',
|
|
28
|
+
'service_settlement',
|
|
29
|
+
'monitoring_alerting',
|
|
30
|
+
'public_site_security',
|
|
31
|
+
'security_threat_model',
|
|
32
|
+
'legal_compliance_approval',
|
|
33
|
+
'support_sla',
|
|
34
|
+
'incident_drill',
|
|
35
|
+
'backup_restore_drill',
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
function parseArgs(argv = process.argv.slice(2)) {
|
|
39
|
+
const flags = new Map();
|
|
40
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
41
|
+
const arg = argv[index];
|
|
42
|
+
if (!arg.startsWith('--')) continue;
|
|
43
|
+
const eq = arg.indexOf('=');
|
|
44
|
+
if (eq !== -1) {
|
|
45
|
+
const key = arg.slice(2, eq);
|
|
46
|
+
const value = arg.slice(eq + 1);
|
|
47
|
+
if (flags.has(key)) flags.set(key, [...[].concat(flags.get(key)), value]);
|
|
48
|
+
else flags.set(key, value);
|
|
49
|
+
} else if (!argv[index + 1] || argv[index + 1].startsWith('--')) {
|
|
50
|
+
flags.set(arg.slice(2), true);
|
|
51
|
+
} else {
|
|
52
|
+
const key = arg.slice(2);
|
|
53
|
+
const value = argv[index + 1];
|
|
54
|
+
if (flags.has(key)) flags.set(key, [...[].concat(flags.get(key)), value]);
|
|
55
|
+
else flags.set(key, value);
|
|
56
|
+
index += 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return flags;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getFlag(flags, names, fallback = undefined) {
|
|
63
|
+
for (const name of names) if (flags.has(name)) return flags.get(name);
|
|
64
|
+
return fallback;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function firstEnv(env, names) {
|
|
68
|
+
for (const name of names) {
|
|
69
|
+
const value = env?.[name];
|
|
70
|
+
if (typeof value === 'string' && value.trim().length > 0) return value.trim();
|
|
71
|
+
}
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function firstConfig(flags, env, flagNames, envNames, fallback = undefined) {
|
|
76
|
+
const flagValue = getFlag(flags, flagNames);
|
|
77
|
+
if (typeof flagValue === 'string' && flagValue.trim().length > 0) return flagValue.trim();
|
|
78
|
+
return firstEnv(env, envNames) ?? fallback;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function maybeEndpoint(url, ref = undefined) {
|
|
82
|
+
if (url === undefined && ref === undefined) return undefined;
|
|
83
|
+
const endpoint = {};
|
|
84
|
+
if (url !== undefined) endpoint.url = url;
|
|
85
|
+
if (ref !== undefined) endpoint.ref = ref;
|
|
86
|
+
return endpoint;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function refEvidence(ref, provider = 'operator') {
|
|
90
|
+
if (ref === undefined) return undefined;
|
|
91
|
+
return { status: 'verified', provider, ref, observed_at: new Date(0).toISOString() };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function collectDependencyEvidence(flags, env) {
|
|
95
|
+
const evidence = {};
|
|
96
|
+
const put = (key, value, provider = 'operator') => {
|
|
97
|
+
const ref = typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
|
|
98
|
+
if (ref !== undefined) evidence[key] = refEvidence(ref, provider);
|
|
99
|
+
};
|
|
100
|
+
put('backend_host', firstConfig(flags, env, ['backend-host-ref', 'backendHostRef'], ['ENIGMA_BACKEND_HOST_REF', 'ENIGMA_RELAY_BACKEND_HOST_REF', 'ENIGMA_GATEWAY_BACKEND_HOST_REF', 'ENIGMA_RELAY_DEPLOYMENT_REF', 'ENIGMA_GATEWAY_DEPLOYMENT_REF']));
|
|
101
|
+
put('dns_tls', firstConfig(flags, env, ['dns-tls-ref', 'dnsTlsRef'], ['ENIGMA_DNS_TLS_REF', 'ENIGMA_RELAY_DNS_TLS_REF', 'ENIGMA_GATEWAY_DNS_TLS_REF', 'ENIGMA_TLS_REF']));
|
|
102
|
+
put('durable_storage', firstConfig(flags, env, ['durable-storage-ref', 'durableStorageRef'], ['ENIGMA_DURABLE_STORAGE_REF', 'ENIGMA_EXTERNAL_STORAGE_REF', 'ENIGMA_EXTERNAL_STORAGE_DSN_FILE', 'ENIGMA_RELAY_STORAGE_REF', 'ENIGMA_GATEWAY_STORAGE_REF']));
|
|
103
|
+
put('kms_or_secret_custody', firstConfig(flags, env, ['kms-ref', 'kmsRef', 'kms-or-secret-custody-ref'], ['ENIGMA_KMS_KEY_REF', 'ENIGMA_KMS_KEY_REF_FILE', 'ENIGMA_KMS_REF', 'ENIGMA_KMS_REF_FILE', 'ENIGMA_EXTERNAL_KMS_REF', 'ENIGMA_EXTERNAL_KMS_REF_FILE', 'ENIGMA_SECRETS_MANAGER_REF', 'ENIGMA_SECRETS_MANAGER_REF_FILE', 'ENIGMA_GATEWAY_SIGNER_REF', 'ENIGMA_GATEWAY_SIGNER_REF_FILE']));
|
|
104
|
+
put('backup_restore', firstConfig(flags, env, ['backup-ref', 'backupRestoreRef'], ['ENIGMA_BACKUP_TARGET_REF', 'ENIGMA_BACKUP_TARGET_URI_FILE', 'ENIGMA_RESTORE_TARGET_REF']));
|
|
105
|
+
put('monitoring', firstConfig(flags, env, ['monitoring-ref', 'monitoringRef'], ['ENIGMA_MONITORING_REF', 'ENIGMA_RELAY_MONITORING_REF', 'ENIGMA_GATEWAY_MONITORING_REF', 'ENIGMA_ALERTING_REF']));
|
|
106
|
+
put('siem_or_log_sink', firstConfig(flags, env, ['siem-ref', 'siemRef', 'log-sink-ref'], ['ENIGMA_SIEM_REF', 'ENIGMA_AUDIT_SINK_REF', 'ENIGMA_LOG_SINK_REF', 'ENIGMA_SIEM_EXPORT_ENDPOINT_FILE']));
|
|
107
|
+
put('runtime_auth', firstConfig(flags, env, ['runtime-auth-ref', 'runtimeAuthRef'], ['ENIGMA_RUNTIME_AUTH_REF', 'ENIGMA_RELAY_RUNTIME_AUTH_REF', 'ENIGMA_PAIRED_CLIENT_AUTH_REF']));
|
|
108
|
+
put('admin_auth', firstConfig(flags, env, ['admin-auth-ref', 'adminAuthRef'], ['ENIGMA_ADMIN_AUTH_REF', 'ENIGMA_GATEWAY_ADMIN_AUTH_REF']));
|
|
109
|
+
put('data_plane_auth', firstConfig(flags, env, ['data-plane-auth-ref', 'dataPlaneAuthRef'], ['ENIGMA_DATA_PLANE_AUTH_REF', 'ENIGMA_GATEWAY_DATA_PLANE_AUTH_REF']));
|
|
110
|
+
put('operator_acceptance', firstConfig(flags, env, ['operator-acceptance-ref', 'operatorAcceptanceRef'], ['ENIGMA_OPERATOR_ACCEPTANCE_REF', 'ENIGMA_OPERATOR_ACCEPTANCE_EVIDENCE_URI', 'ENIGMA_OPERATOR_ACCEPTANCE_EVIDENCE_URI_FILE']));
|
|
111
|
+
put('network_access_policy', firstConfig(flags, env, ['network-access-policy-ref', 'networkAccessPolicyRef'], ['ENIGMA_NETWORK_ACCESS_POLICY_REF', 'ENIGMA_NETWORK_POLICY_REF']));
|
|
112
|
+
put('kms_custody', firstConfig(flags, env, ['kms-custody-ref', 'kmsCustodyRef'], ['ENIGMA_KMS_CUSTODY_REF', 'ENIGMA_KEY_CUSTODY_REF']));
|
|
113
|
+
put('tenant_policy_approval', firstConfig(flags, env, ['tenant-policy-approval-ref', 'tenantPolicyApprovalRef'], ['ENIGMA_TENANT_POLICY_APPROVAL_REF', 'ENIGMA_TENANT_POLICY_REF']));
|
|
114
|
+
put('usage_metering', firstConfig(flags, env, ['usage-metering-ref', 'usageMeteringRef'], ['ENIGMA_USAGE_METERING_REF', 'ENIGMA_METERING_REF']));
|
|
115
|
+
put('service_settlement', firstConfig(flags, env, ['service-settlement-ref', 'serviceSettlementRef'], ['ENIGMA_SERVICE_SETTLEMENT_REF', 'ENIGMA_SETTLEMENT_REF']));
|
|
116
|
+
put('monitoring_alerting', firstConfig(flags, env, ['monitoring-alerting-ref', 'monitoringAlertingRef'], ['ENIGMA_MONITORING_ALERTING_REF', 'ENIGMA_ALERTING_EVIDENCE_REF']));
|
|
117
|
+
put('public_site_security', firstConfig(flags, env, ['public-site-security-ref', 'publicSiteSecurityRef'], ['ENIGMA_PUBLIC_SITE_SECURITY_REF', 'ENIGMA_SITE_SECURITY_REF']));
|
|
118
|
+
put('security_threat_model', firstConfig(flags, env, ['security-threat-model-ref', 'securityThreatModelRef'], ['ENIGMA_SECURITY_THREAT_MODEL_REF', 'ENIGMA_THREAT_MODEL_REF']));
|
|
119
|
+
put('legal_compliance_approval', firstConfig(flags, env, ['legal-compliance-ref', 'legalComplianceRef', 'legal-compliance-approval-ref', 'legalComplianceApprovalRef'], ['ENIGMA_LEGAL_COMPLIANCE_REF', 'ENIGMA_LEGAL_APPROVAL_REF', 'ENIGMA_LEGAL_COMPLIANCE_APPROVAL_REF']));
|
|
120
|
+
put('support_sla', firstConfig(flags, env, ['support-sla-ref', 'supportSlaRef'], ['ENIGMA_SUPPORT_SLA_REF']));
|
|
121
|
+
put('incident_drill', firstConfig(flags, env, ['incident-drill-ref', 'incidentDrillRef'], ['ENIGMA_INCIDENT_DRILL_REF']));
|
|
122
|
+
put('backup_restore_drill', firstConfig(flags, env, ['backup-restore-drill-ref', 'backupRestoreDrillRef'], ['ENIGMA_BACKUP_RESTORE_DRILL_REF']));
|
|
123
|
+
return evidence;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function asArray(value) {
|
|
127
|
+
if (value === undefined || value === true) return [];
|
|
128
|
+
return Array.isArray(value) ? value.map(String) : [String(value)];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function buildProductionReadinessManifest(options = {}) {
|
|
132
|
+
const flags = options.flags instanceof Map ? options.flags : parseArgs(options.argv ?? []);
|
|
133
|
+
const env = options.env ?? process.env;
|
|
134
|
+
const dependencyEvidence = collectDependencyEvidence(flags, env);
|
|
135
|
+
const normalizedEvidence = normalizeDependencyEvidence(dependencyEvidence, {
|
|
136
|
+
generated_at: options.generated_at ?? options.generatedAt ?? new Date(0).toISOString(),
|
|
137
|
+
required_keys: GATEWAY_REQUIRED_DEPENDENCIES,
|
|
138
|
+
});
|
|
139
|
+
const refs = {
|
|
140
|
+
...readinessEvidenceRefs(normalizedEvidence.evidence),
|
|
141
|
+
};
|
|
142
|
+
const relayRef = firstConfig(flags, env, ['relay-ref', 'relayRef'], ['ENIGMA_RELAY_REF', 'ENIGMA_RELAY_DEPLOYMENT_REF']);
|
|
143
|
+
const gatewayRef = firstConfig(flags, env, ['gateway-ref', 'gatewayRef'], ['ENIGMA_GATEWAY_REF', 'ENIGMA_GATEWAY_DEPLOYMENT_REF']);
|
|
144
|
+
if (relayRef !== undefined) refs.relay = relayRef;
|
|
145
|
+
if (gatewayRef !== undefined) refs.gateway = gatewayRef;
|
|
146
|
+
const operatorDecision = firstConfig(flags, env, ['operator-decision', 'operatorDecision'], ['ENIGMA_OPERATOR_DECISION', 'ENIGMA_OPERATOR_ACCEPTANCE_DECISION'], 'pending');
|
|
147
|
+
const operatorRef = refs.operator_acceptance ?? firstConfig(flags, env, ['operator-acceptance-ref', 'operatorAcceptanceRef'], ['ENIGMA_OPERATOR_ACCEPTANCE_REF', 'ENIGMA_OPERATOR_ACCEPTANCE_EVIDENCE_URI', 'ENIGMA_OPERATOR_ACCEPTANCE_EVIDENCE_URI_FILE']);
|
|
148
|
+
const externalBlockers = asArray(getFlag(flags, ['external-blocker', 'externalBlocker']));
|
|
149
|
+
for (const missing of normalizedEvidence.missing_keys) externalBlockers.push(`missing refs.${missing}`);
|
|
150
|
+
if (operatorDecision !== 'go') externalBlockers.push(`operator acceptance decision is ${operatorDecision}`);
|
|
151
|
+
const publicSiteUrl = firstConfig(flags, env, ['public-site-url', 'publicSiteUrl'], ['ENIGMA_PUBLIC_SITE_URL'], 'https://enigmamemory.com/');
|
|
152
|
+
const cloudflareProjectName = firstConfig(flags, env, ['cloudflare-project-name', 'cloudflareProjectName'], ['CLOUDFLARE_PAGES_PROJECT_NAME', 'ENIGMA_CLOUDFLARE_PAGES_PROJECT_NAME'], 'enigma-memory');
|
|
153
|
+
const cloudflarePagesUrl = firstConfig(flags, env, ['cloudflare-pages-url', 'cloudflarePagesUrl'], ['ENIGMA_CLOUDFLARE_PAGES_URL'], 'https://enigma-memory.pages.dev/');
|
|
154
|
+
const cloudflareAccountId = firstConfig(flags, env, ['cloudflare-account-id', 'cloudflareAccountId'], ['CLOUDFLARE_ACCOUNT_ID']);
|
|
155
|
+
const manifest = {
|
|
156
|
+
schema: INFRASTRUCTURE_READINESS_MANIFEST_SCHEMA,
|
|
157
|
+
name: firstConfig(flags, env, ['name'], ['ENIGMA_READINESS_MANIFEST_NAME'], 'Enigma production readiness manifest'),
|
|
158
|
+
mode: firstConfig(flags, env, ['mode'], ['ENIGMA_READINESS_MODE'], 'hosted-live'),
|
|
159
|
+
public_site: maybeEndpoint(publicSiteUrl),
|
|
160
|
+
cloudflare_pages: {
|
|
161
|
+
project_name: cloudflareProjectName,
|
|
162
|
+
project_url: cloudflarePagesUrl,
|
|
163
|
+
...(cloudflareAccountId === undefined ? {} : { account_id: cloudflareAccountId }),
|
|
164
|
+
},
|
|
165
|
+
relay: maybeEndpoint(
|
|
166
|
+
firstConfig(flags, env, ['relay-url', 'relayUrl'], ['ENIGMA_RELAY_READY_URL', 'ENIGMA_RELAY_PUBLIC_READY_URL'], 'https://relay.enigmamemory.com/readyz'),
|
|
167
|
+
relayRef
|
|
168
|
+
),
|
|
169
|
+
gateway: maybeEndpoint(
|
|
170
|
+
firstConfig(flags, env, ['gateway-url', 'gatewayUrl'], ['ENIGMA_GATEWAY_READY_URL', 'ENIGMA_GATEWAY_PUBLIC_READY_URL'], 'https://gateway.enigmamemory.com/readyz'),
|
|
171
|
+
gatewayRef
|
|
172
|
+
),
|
|
173
|
+
refs,
|
|
174
|
+
operator_acceptance: {
|
|
175
|
+
decision: operatorDecision,
|
|
176
|
+
...(operatorRef === undefined ? {} : { ref: operatorRef }),
|
|
177
|
+
},
|
|
178
|
+
external_blockers: [...new Set(externalBlockers)],
|
|
179
|
+
claim_boundary: [
|
|
180
|
+
'Generated readiness manifests contain public-safe refs only and never secret values.',
|
|
181
|
+
'This manifest is not hosted backend readiness until live /readyz checks, Cloudflare/project observation, operator acceptance, and external blockers are all clear.',
|
|
182
|
+
'Missing refs remain explicit blockers rather than silent defaults.',
|
|
183
|
+
],
|
|
184
|
+
};
|
|
185
|
+
return validateInfrastructureReadinessManifest(manifest);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function main() {
|
|
189
|
+
const flags = parseArgs();
|
|
190
|
+
const manifest = await buildProductionReadinessManifest({ flags });
|
|
191
|
+
const out = getFlag(flags, ['out']);
|
|
192
|
+
const text = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
193
|
+
if (out && out !== true) {
|
|
194
|
+
const path = resolve(String(out));
|
|
195
|
+
await mkdir(dirname(path), { recursive: true });
|
|
196
|
+
await writeFile(path, text, 'utf8');
|
|
197
|
+
process.stdout.write(`${JSON.stringify({ ok: true, out: path, schema: manifest.schema, external_blockers: manifest.external_blockers }, null, 2)}\n`);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
process.stdout.write(text);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const invokedPath = process.argv[1] ? resolve(process.argv[1]) : '';
|
|
204
|
+
if (invokedPath === fileURLToPath(import.meta.url)) {
|
|
205
|
+
main().catch((error) => {
|
|
206
|
+
process.stderr.write(`${error.name ?? 'Error'}: ${error.message}\n`);
|
|
207
|
+
process.exitCode = 1;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
export const PRODUCTION_STATUS_BOARD_SCHEMA = 'enigma.production_status_board.v1';
|
|
7
|
+
|
|
8
|
+
const SECRET_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,}|https?:\/\/[^\s/@]+:[^\s/@]+@|AKIA[0-9A-Z]{16})/iu;
|
|
9
|
+
const LOCAL_PATH_RE = /(?<![A-Za-z])(?:[A-Za-z]:[\\/]|\\\\[^\\/]+[\\/][^\\/]+|\/(?:Users|home)\/[^\s"']+)/u;
|
|
10
|
+
|
|
11
|
+
function parseArgs(argv = process.argv.slice(2)) {
|
|
12
|
+
const out = {
|
|
13
|
+
goalAudit: null,
|
|
14
|
+
dependencies: null,
|
|
15
|
+
workplan: null,
|
|
16
|
+
out: null,
|
|
17
|
+
help: false,
|
|
18
|
+
};
|
|
19
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
20
|
+
const token = argv[index];
|
|
21
|
+
const readValue = () => {
|
|
22
|
+
const value = argv[index + 1];
|
|
23
|
+
if (typeof value !== 'string' || value.length === 0 || value.startsWith('--')) throw new Error(`${token} requires a value`);
|
|
24
|
+
index += 1;
|
|
25
|
+
return value;
|
|
26
|
+
};
|
|
27
|
+
if (token === '--goal-audit' || token === '--goalAudit') out.goalAudit = readValue();
|
|
28
|
+
else if (token === '--dependencies') out.dependencies = readValue();
|
|
29
|
+
else if (token === '--workplan') out.workplan = readValue();
|
|
30
|
+
else if (token === '--out') out.out = readValue();
|
|
31
|
+
else if (token === '--help' || token === '-h') out.help = true;
|
|
32
|
+
else throw new Error(`Unknown production status board option: ${token}`);
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function usage() {
|
|
38
|
+
return 'Usage: node scripts/build-production-status-board.mjs --goal-audit <goal.json> --dependencies <dependencies.json> --workplan <workplan.json> [--out <file>]\n\nBuilds a public-safe launch status board from current goal, dependency, and workplan evidence.\n';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function readJson(path, label) {
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(await readFile(path, 'utf8'));
|
|
44
|
+
} catch {
|
|
45
|
+
throw new Error(`Unable to read ${label}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function assertPublicSafe(value, label = 'production status board') {
|
|
50
|
+
const text = JSON.stringify(value);
|
|
51
|
+
if (SECRET_RE.test(text)) throw new Error(`${label} contains secret-looking material`);
|
|
52
|
+
if (LOCAL_PATH_RE.test(text)) throw new Error(`${label} contains a local path`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function requireObject(value, label) {
|
|
56
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
57
|
+
assertPublicSafe(value, label);
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function limitedList(values, limit) {
|
|
62
|
+
return Array.isArray(values) ? values.slice(0, limit) : [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function uniqueLimitedList(values, limit) {
|
|
66
|
+
const out = [];
|
|
67
|
+
const seen = new Set();
|
|
68
|
+
for (const value of Array.isArray(values) ? values : []) {
|
|
69
|
+
if (!value || seen.has(value)) continue;
|
|
70
|
+
seen.add(value);
|
|
71
|
+
out.push(value);
|
|
72
|
+
if (out.length >= limit) break;
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function omittedCount(values, limit) {
|
|
78
|
+
return Math.max(0, (Array.isArray(values) ? values.length : 0) - limit);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function summarizePhaseDetails(details) {
|
|
82
|
+
if (details === null || typeof details !== 'object' || Array.isArray(details)) return null;
|
|
83
|
+
const summary = {};
|
|
84
|
+
for (const key of ['missing_ref_count', 'listed_missing_ref_count', 'blocker_listed_missing_ref_count', 'unlisted_missing_ref_count']) {
|
|
85
|
+
if (Number.isFinite(details[key])) summary[key] = details[key];
|
|
86
|
+
}
|
|
87
|
+
if (Array.isArray(details.missing_refs)) summary.missing_refs = limitedList(details.missing_refs, 30);
|
|
88
|
+
if (details.missing_ref_groups && typeof details.missing_ref_groups === 'object' && !Array.isArray(details.missing_ref_groups)) {
|
|
89
|
+
summary.missing_ref_groups = Object.fromEntries(Object.entries(details.missing_ref_groups)
|
|
90
|
+
.filter(([, refs]) => Array.isArray(refs))
|
|
91
|
+
.map(([group, refs]) => [group, limitedList(refs, 30)]));
|
|
92
|
+
}
|
|
93
|
+
if (Array.isArray(details.missing_endpoint_refs)) summary.missing_endpoint_refs = limitedList(details.missing_endpoint_refs, 10);
|
|
94
|
+
if (Array.isArray(details.hosted_state_blockers)) summary.hosted_state_blockers = limitedList(details.hosted_state_blockers, 10);
|
|
95
|
+
return Object.keys(summary).length > 0 ? summary : null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function summarizeGroup(group) {
|
|
99
|
+
return {
|
|
100
|
+
name: group.name,
|
|
101
|
+
ready: group.ready === true,
|
|
102
|
+
blocker_count: Number.isFinite(group.blocker_count) ? group.blocker_count : (Array.isArray(group.blockers) ? group.blockers.length : 0),
|
|
103
|
+
blockers: limitedList(group.blockers, 8),
|
|
104
|
+
omitted_blocker_count: omittedCount(group.blockers, 8),
|
|
105
|
+
next_command: typeof group.next_command === 'string' ? group.next_command : null,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function summarizePhase(phase) {
|
|
110
|
+
const details = summarizePhaseDetails(phase.details);
|
|
111
|
+
return {
|
|
112
|
+
id: phase.id,
|
|
113
|
+
title: phase.title,
|
|
114
|
+
ready: phase.ready === true,
|
|
115
|
+
owner: phase.owner,
|
|
116
|
+
prerequisites: Array.isArray(phase.prerequisites) ? phase.prerequisites : [],
|
|
117
|
+
blocker_count: Number.isFinite(phase.blocker_count) ? phase.blocker_count : (Array.isArray(phase.blockers) ? phase.blockers.length : 0),
|
|
118
|
+
blockers: limitedList(phase.blockers, 8),
|
|
119
|
+
omitted_blocker_count: omittedCount(phase.blockers, 8),
|
|
120
|
+
commands: limitedList(phase.commands, 5),
|
|
121
|
+
...(details ? { details } : {}),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function summarizeDeliverable(deliverable) {
|
|
126
|
+
return {
|
|
127
|
+
id: deliverable.id,
|
|
128
|
+
ok: deliverable.ok === true,
|
|
129
|
+
requirement: deliverable.requirement,
|
|
130
|
+
blocker_count: Array.isArray(deliverable.blockers) ? deliverable.blockers.length : 0,
|
|
131
|
+
blockers: limitedList(deliverable.blockers, 8),
|
|
132
|
+
omitted_blocker_count: omittedCount(deliverable.blockers, 8),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function groupMap(dependencies) {
|
|
137
|
+
return new Map((Array.isArray(dependencies.groups) ? dependencies.groups : []).map((group) => [group.name, group]));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function phaseMap(workplan) {
|
|
141
|
+
return new Map((Array.isArray(workplan.phases) ? workplan.phases : []).map((phase) => [phase.id, phase]));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function checkExecutionOrder(workplan) {
|
|
145
|
+
const phases = Array.isArray(workplan.phases) ? workplan.phases : [];
|
|
146
|
+
const ids = new Set(phases.map((phase) => phase.id));
|
|
147
|
+
const order = Array.isArray(workplan.execution_order) ? workplan.execution_order : [];
|
|
148
|
+
return {
|
|
149
|
+
present: order.length > 0,
|
|
150
|
+
covers_all_phases: order.length === ids.size && order.every((id) => ids.has(id)),
|
|
151
|
+
next_phase_order_index: workplan.next_phase_id === null ? null : order.indexOf(workplan.next_phase_id),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function inputFreshness(inputs, options = {}) {
|
|
156
|
+
const staleAfterSeconds = Number.isFinite(options.staleAfterSeconds) ? options.staleAfterSeconds : 900;
|
|
157
|
+
const asOfMs = typeof options.asOf === 'string' ? Date.parse(options.asOf) : NaN;
|
|
158
|
+
const entries = [
|
|
159
|
+
['goal_audit', inputs.goalAudit?.generated_at],
|
|
160
|
+
['dependencies', inputs.dependencies?.generated_at],
|
|
161
|
+
['workplan', inputs.workplan?.generated_at],
|
|
162
|
+
['workplan.dependencies', inputs.workplan?.evidence_inputs?.dependency_generated_at],
|
|
163
|
+
['workplan.operator_acceptance', inputs.workplan?.evidence_inputs?.operator_acceptance_generated_at],
|
|
164
|
+
['workplan.hosted_ref_catalog', inputs.workplan?.evidence_inputs?.hosted_ref_catalog_generated_at],
|
|
165
|
+
].map(([name, generatedAt]) => {
|
|
166
|
+
const time = typeof generatedAt === 'string' ? Date.parse(generatedAt) : NaN;
|
|
167
|
+
return {
|
|
168
|
+
name,
|
|
169
|
+
generated_at: typeof generatedAt === 'string' ? generatedAt : null,
|
|
170
|
+
valid: Number.isFinite(time),
|
|
171
|
+
epoch_ms: Number.isFinite(time) ? time : null,
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
const validTimes = entries.filter((entry) => entry.valid).map((entry) => entry.epoch_ms);
|
|
175
|
+
const latest = validTimes.length > 0 ? Math.max(...validTimes) : null;
|
|
176
|
+
const oldest = validTimes.length > 0 ? Math.min(...validTimes) : null;
|
|
177
|
+
const maxSkewSeconds = latest === null || oldest === null ? null : Math.round((latest - oldest) / 1000);
|
|
178
|
+
const latestAgeSeconds = latest === null || !Number.isFinite(asOfMs) ? null : Math.round((asOfMs - latest) / 1000);
|
|
179
|
+
return {
|
|
180
|
+
stale_after_seconds: staleAfterSeconds,
|
|
181
|
+
as_of: Number.isFinite(asOfMs) ? new Date(asOfMs).toISOString() : null,
|
|
182
|
+
valid_input_count: validTimes.length,
|
|
183
|
+
missing_or_invalid_inputs: entries.filter((entry) => !entry.valid).map((entry) => entry.name),
|
|
184
|
+
latest_generated_at: latest === null ? null : new Date(latest).toISOString(),
|
|
185
|
+
oldest_generated_at: oldest === null ? null : new Date(oldest).toISOString(),
|
|
186
|
+
max_skew_seconds: maxSkewSeconds,
|
|
187
|
+
latest_age_seconds: latestAgeSeconds,
|
|
188
|
+
stale: validTimes.length !== entries.length
|
|
189
|
+
|| !Number.isFinite(asOfMs)
|
|
190
|
+
|| (maxSkewSeconds !== null && maxSkewSeconds > staleAfterSeconds)
|
|
191
|
+
|| (latestAgeSeconds !== null && latestAgeSeconds > staleAfterSeconds),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function blockedExternalGroups(groups) {
|
|
196
|
+
return ['cloudflare_credentials', 'cloudflare_worker_permission', 'hosted_backend_live', 'operator_acceptance']
|
|
197
|
+
.map((name) => groups.get(name))
|
|
198
|
+
.filter(Boolean)
|
|
199
|
+
.map(summarizeGroup)
|
|
200
|
+
.filter((group) => group.ready !== true);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function buildProductionStatusBoard(inputs = {}, options = {}) {
|
|
204
|
+
const goalAudit = requireObject(inputs.goalAudit, 'goal audit');
|
|
205
|
+
const dependencies = requireObject(inputs.dependencies, 'dependency report');
|
|
206
|
+
const workplan = requireObject(inputs.workplan, 'production workplan');
|
|
207
|
+
if (goalAudit.schema !== 'enigma.goal_completion_audit.v1') throw new Error('goal audit schema mismatch');
|
|
208
|
+
if (dependencies.schema !== 'enigma.production_dependency_report.v1') throw new Error('dependency report schema mismatch');
|
|
209
|
+
if (workplan.schema !== 'enigma.production_workplan.v1') throw new Error('production workplan schema mismatch');
|
|
210
|
+
|
|
211
|
+
const groups = groupMap(dependencies);
|
|
212
|
+
const phases = phaseMap(workplan);
|
|
213
|
+
const blockedGroups = (Array.isArray(dependencies.groups) ? dependencies.groups : []).filter((group) => group.ready !== true).map(summarizeGroup);
|
|
214
|
+
const blockedPhases = (Array.isArray(workplan.phases) ? workplan.phases : []).filter((phase) => phase.ready !== true).map(summarizePhase);
|
|
215
|
+
const blockedDeliverables = (Array.isArray(goalAudit.deliverables) ? goalAudit.deliverables : []).filter((deliverable) => deliverable.ok !== true).map(summarizeDeliverable);
|
|
216
|
+
const nextPhase = workplan.next_phase_id ? phases.get(workplan.next_phase_id) : null;
|
|
217
|
+
const firstBlockedGroup = blockedGroups[0] ?? null;
|
|
218
|
+
const executionOrder = Array.isArray(workplan.execution_order) ? workplan.execution_order : [];
|
|
219
|
+
const localReady = ['static_site', 'release_gates', 'whitepaper_claims'].every((name) => groups.get(name)?.ready === true);
|
|
220
|
+
const generatedAt = options.generated_at ?? new Date().toISOString();
|
|
221
|
+
const freshness = inputFreshness({ goalAudit, dependencies, workplan }, { asOf: generatedAt });
|
|
222
|
+
const completeWithFreshEvidence = goalAudit.complete === true && dependencies.launch_ready === true && workplan.launch_ready === true && freshness.stale === false;
|
|
223
|
+
const launchReadyWithFreshEvidence = goalAudit.go_live_ready === true && dependencies.launch_ready === true && workplan.launch_ready === true && freshness.stale === false;
|
|
224
|
+
const report = {
|
|
225
|
+
schema: PRODUCTION_STATUS_BOARD_SCHEMA,
|
|
226
|
+
generated_at: generatedAt,
|
|
227
|
+
status: completeWithFreshEvidence ? 'ready' : 'blocked',
|
|
228
|
+
launch_ready: launchReadyWithFreshEvidence,
|
|
229
|
+
goal_complete: goalAudit.complete === true,
|
|
230
|
+
local_package_ready: localReady,
|
|
231
|
+
fresh_input_evidence: freshness.stale === false,
|
|
232
|
+
release_posture: goalAudit.release_posture ?? dependencies.release_posture ?? null,
|
|
233
|
+
ready_group_count: (Array.isArray(dependencies.groups) ? dependencies.groups : []).filter((group) => group.ready === true).length,
|
|
234
|
+
blocked_group_count: blockedGroups.length,
|
|
235
|
+
ready_phase_count: (Array.isArray(workplan.phases) ? workplan.phases : []).filter((phase) => phase.ready === true).length,
|
|
236
|
+
blocked_phase_count: blockedPhases.length,
|
|
237
|
+
blocked_deliverable_count: blockedDeliverables.length,
|
|
238
|
+
next_phase: nextPhase ? summarizePhase(nextPhase) : null,
|
|
239
|
+
next_phase_id: nextPhase?.id ?? null,
|
|
240
|
+
first_blocked_group: firstBlockedGroup,
|
|
241
|
+
execution_order: executionOrder,
|
|
242
|
+
execution_order_check: checkExecutionOrder(workplan),
|
|
243
|
+
input_freshness: freshness,
|
|
244
|
+
blocked_groups: blockedGroups,
|
|
245
|
+
blocked_phases: blockedPhases,
|
|
246
|
+
blocked_deliverables: blockedDeliverables,
|
|
247
|
+
external_blockers: blockedExternalGroups(groups),
|
|
248
|
+
immediate_operator_queue: uniqueLimitedList([
|
|
249
|
+
...(nextPhase?.commands ?? []),
|
|
250
|
+
...(firstBlockedGroup?.next_command ? [firstBlockedGroup.next_command] : []),
|
|
251
|
+
], 6),
|
|
252
|
+
evidence_inputs: {
|
|
253
|
+
goal_audit_generated_at: goalAudit.generated_at ?? null,
|
|
254
|
+
dependency_generated_at: dependencies.generated_at ?? null,
|
|
255
|
+
workplan_generated_at: workplan.generated_at ?? null,
|
|
256
|
+
workplan_dependency_generated_at: workplan.evidence_inputs?.dependency_generated_at ?? null,
|
|
257
|
+
workplan_operator_acceptance_generated_at: workplan.evidence_inputs?.operator_acceptance_generated_at ?? null,
|
|
258
|
+
workplan_hosted_ref_catalog_generated_at: workplan.evidence_inputs?.hosted_ref_catalog_generated_at ?? null,
|
|
259
|
+
},
|
|
260
|
+
claim_boundary: [
|
|
261
|
+
'This status board summarizes already-generated evidence; it does not create credentials, deploy infrastructure, approve operators, or certify launch readiness.',
|
|
262
|
+
'launch_ready:true requires the goal audit, dependency report, and workplan to all be ready with direct current evidence.',
|
|
263
|
+
'Cloudflare token values, account ids, credential-bearing URLs, raw memory, prompts, transcripts, provider responses, and private keys must never appear in this artifact.',
|
|
264
|
+
],
|
|
265
|
+
};
|
|
266
|
+
assertPublicSafe(report);
|
|
267
|
+
return report;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
271
|
+
const args = parseArgs(argv);
|
|
272
|
+
if (args.help) return { text: usage(), status: 0 };
|
|
273
|
+
if (!args.goalAudit) throw new Error('--goal-audit is required');
|
|
274
|
+
if (!args.dependencies) throw new Error('--dependencies is required');
|
|
275
|
+
if (!args.workplan) throw new Error('--workplan is required');
|
|
276
|
+
const report = buildProductionStatusBoard({
|
|
277
|
+
goalAudit: await readJson(args.goalAudit, 'goal audit'),
|
|
278
|
+
dependencies: await readJson(args.dependencies, 'dependency report'),
|
|
279
|
+
workplan: await readJson(args.workplan, 'production workplan'),
|
|
280
|
+
});
|
|
281
|
+
const json = `${JSON.stringify(report, null, 2)}\n`;
|
|
282
|
+
if (args.out) {
|
|
283
|
+
await mkdir(dirname(args.out), { recursive: true });
|
|
284
|
+
await writeFile(args.out, json, 'utf8');
|
|
285
|
+
}
|
|
286
|
+
return { text: json, status: report.launch_ready ? 0 : 1 };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
290
|
+
main().then(({ text, status }) => {
|
|
291
|
+
process.stdout.write(text);
|
|
292
|
+
process.exitCode = status;
|
|
293
|
+
}).catch((error) => {
|
|
294
|
+
process.stdout.write(`${JSON.stringify({ schema: PRODUCTION_STATUS_BOARD_SCHEMA, status: 'blocked', launch_ready: false, blockers: [error.message] }, null, 2)}\n`);
|
|
295
|
+
process.exitCode = 1;
|
|
296
|
+
});
|
|
297
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import {
|
|
6
|
+
assertProductionStorageSqlSafe,
|
|
7
|
+
buildPostgresMigration,
|
|
8
|
+
productionStorageContract,
|
|
9
|
+
} from '../packages/storage/src/index.js';
|
|
10
|
+
|
|
11
|
+
function parseArgs(argv = process.argv.slice(2)) {
|
|
12
|
+
const flags = new Map();
|
|
13
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
14
|
+
const arg = argv[index];
|
|
15
|
+
if (!arg.startsWith('--')) continue;
|
|
16
|
+
const eq = arg.indexOf('=');
|
|
17
|
+
if (eq !== -1) {
|
|
18
|
+
flags.set(arg.slice(2, eq), arg.slice(eq + 1));
|
|
19
|
+
} else if (!argv[index + 1] || argv[index + 1].startsWith('--')) {
|
|
20
|
+
flags.set(arg.slice(2), true);
|
|
21
|
+
} else {
|
|
22
|
+
flags.set(arg.slice(2), argv[index + 1]);
|
|
23
|
+
index += 1;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return flags;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getFlag(flags, names, fallback = undefined) {
|
|
30
|
+
for (const name of names) if (flags.has(name)) return flags.get(name);
|
|
31
|
+
return fallback;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function outputFormat(flags) {
|
|
35
|
+
const format = String(getFlag(flags, ['format'], 'json')).toLowerCase();
|
|
36
|
+
if (!['json', 'sql'].includes(format)) throw new Error('--format must be json or sql');
|
|
37
|
+
return format;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function buildProductionStorageMigrationArtifact(options = {}) {
|
|
41
|
+
const flags = options.flags instanceof Map ? options.flags : parseArgs(options.argv ?? []);
|
|
42
|
+
const schema = getFlag(flags, ['schema'], options.schema ?? 'enigma');
|
|
43
|
+
const migrationId = getFlag(flags, ['migration-id', 'migrationId'], options.migration_id ?? options.migrationId ?? '001_enigma_production_storage');
|
|
44
|
+
const migration = buildPostgresMigration({ schema, migration_id: migrationId });
|
|
45
|
+
assertProductionStorageSqlSafe(migration.sql);
|
|
46
|
+
const contract = productionStorageContract({ schema, migration_id: migrationId });
|
|
47
|
+
return {
|
|
48
|
+
ok: true,
|
|
49
|
+
schema: 'enigma.production_storage_migration_artifact.v1',
|
|
50
|
+
generated_at: options.generated_at ?? options.generatedAt ?? new Date(0).toISOString(),
|
|
51
|
+
format: outputFormat(flags),
|
|
52
|
+
contract,
|
|
53
|
+
migration,
|
|
54
|
+
claim_boundary: [
|
|
55
|
+
'This artifact is local SQL/migration material only; it does not connect to or mutate a database.',
|
|
56
|
+
'Operators must run it in controlled database change management and verify backups/restore before hosted_live_ready can be true.',
|
|
57
|
+
'The SQL schema stores hashes, opaque encrypted payload references, minimized decisions, SIEM events, and public-safe refs rather than raw memory or secrets.',
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function main() {
|
|
63
|
+
const flags = parseArgs();
|
|
64
|
+
const artifact = buildProductionStorageMigrationArtifact({ flags });
|
|
65
|
+
const format = artifact.format;
|
|
66
|
+
const out = getFlag(flags, ['out']);
|
|
67
|
+
const content = format === 'sql'
|
|
68
|
+
? `${artifact.migration.sql}\n`
|
|
69
|
+
: `${JSON.stringify(artifact, null, 2)}\n`;
|
|
70
|
+
if (out && out !== true) {
|
|
71
|
+
const path = resolve(String(out));
|
|
72
|
+
await mkdir(dirname(path), { recursive: true });
|
|
73
|
+
await writeFile(path, content, 'utf8');
|
|
74
|
+
process.stdout.write(`${JSON.stringify({ ok: true, out: path, schema: artifact.schema, format, table_count: artifact.contract.tables.length }, null, 2)}\n`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
process.stdout.write(content);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const invokedPath = process.argv[1] ? resolve(process.argv[1]) : '';
|
|
81
|
+
if (invokedPath === fileURLToPath(import.meta.url)) {
|
|
82
|
+
main().catch((error) => {
|
|
83
|
+
process.stderr.write(`${error.name ?? 'Error'}: ${error.message}\n`);
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
});
|
|
86
|
+
}
|