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,235 @@
1
+ #!/usr/bin/env node
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ export const KMS_CUSTODY_SCHEMA = 'enigma.kms_custody.v1';
7
+ export const KMS_CUSTODY_RESULT_SCHEMA = 'enigma.kms_custody_result.v1';
8
+
9
+ export const REQUIRED_CUSTODY_ITEMS = Object.freeze([
10
+ 'relay_signing_key',
11
+ 'witness_signing_key',
12
+ 'gateway_signing_key',
13
+ 'gateway_admin_bearer_hash',
14
+ 'gateway_data_plane_bearer_hash',
15
+ 'database_credential_ref',
16
+ 'siem_destination_credential_ref',
17
+ 'backup_encryption_key',
18
+ 'tls_certificate_key_ref',
19
+ ]);
20
+
21
+ const ACCEPTED_STATUSES = new Set(['approved', 'accepted', 'go', 'verified', 'active']);
22
+ const SECRET_VALUE_RE = /(?:Bearer\s+[A-Za-z0-9._~+/=-]{12,}|Basic\s+[A-Za-z0-9+/=-]{12,}|-----BEGIN [A-Z ]*PRIVATE KEY-----|https?:\/\/[^\s/@]+:[^\s/@]+@|raw memory|private prompt|full transcript|decrypted capsule|sk-[A-Za-z0-9_-]{16,}|AKIA[0-9A-Z]{16})/iu;
23
+ const SECRET_KEY_RE = /(?:password|passwd|pwd|token|api[_-]?key|private[_-]?key|secret|raw[_-]?memory|plaintext|plain[_-]?text|prompt|completion|transcript|embedding|provider[_-]?response)/iu;
24
+
25
+ function isPlainObject(value) {
26
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
27
+ }
28
+
29
+ function nonEmptyString(value) {
30
+ return typeof value === 'string' && value.trim().length > 0;
31
+ }
32
+
33
+ function positiveNumber(value) {
34
+ return typeof value === 'number' && Number.isFinite(value) && value > 0;
35
+ }
36
+
37
+ function isoLike(value) {
38
+ return nonEmptyString(value) && !Number.isNaN(Date.parse(value));
39
+ }
40
+
41
+ function blocker(message, path) {
42
+ return { message, path };
43
+ }
44
+
45
+ function assertNoSecrets(value, path = 'custody') {
46
+ if (typeof value === 'string') {
47
+ if (!/\.claim_boundary\[\d+\]$/.test(path) && SECRET_VALUE_RE.test(value)) throw new Error(`${path} contains secret or raw-memory-looking data`);
48
+ return;
49
+ }
50
+ if (Array.isArray(value)) {
51
+ value.forEach((item, index) => assertNoSecrets(item, `${path}[${index}]`));
52
+ return;
53
+ }
54
+ if (!isPlainObject(value)) return;
55
+ for (const [key, child] of Object.entries(value)) {
56
+ if (SECRET_KEY_RE.test(key)) throw new Error(`${path}.${key} uses a forbidden field name`);
57
+ assertNoSecrets(child, `${path}.${key}`);
58
+ }
59
+ }
60
+
61
+ function statusAccepted(value) {
62
+ return typeof value === 'string' && ACCEPTED_STATUSES.has(value.trim().toLowerCase());
63
+ }
64
+
65
+ function validateMetadata(metadata, blockers) {
66
+ if (!isPlainObject(metadata)) {
67
+ blockers.push(blocker('metadata is required', 'metadata'));
68
+ return;
69
+ }
70
+ for (const field of ['custody_id', 'environment', 'tenant', 'owner', 'approved_at', 'approval_ref', 'status']) {
71
+ if (!nonEmptyString(metadata[field])) blockers.push(blocker(`metadata.${field} is required`, `metadata.${field}`));
72
+ }
73
+ if (!statusAccepted(metadata.status)) blockers.push(blocker('metadata.status must be approved/accepted/go/verified/active', 'metadata.status'));
74
+ if (!isoLike(metadata.approved_at)) blockers.push(blocker('metadata.approved_at must be ISO time', 'metadata.approved_at'));
75
+ }
76
+
77
+ function validateProvider(provider, blockers) {
78
+ if (!isPlainObject(provider)) {
79
+ blockers.push(blocker('custody_provider object is required', 'custody_provider'));
80
+ return false;
81
+ }
82
+ for (const field of ['provider_ref', 'region', 'account_ref', 'access_model_ref', 'audit_log_ref', 'owner', 'status']) {
83
+ if (!nonEmptyString(provider[field])) blockers.push(blocker(`custody_provider.${field} is required`, `custody_provider.${field}`));
84
+ }
85
+ if (!statusAccepted(provider.status)) blockers.push(blocker('custody_provider.status must be approved/accepted/go/verified/active', 'custody_provider.status'));
86
+ return true;
87
+ }
88
+
89
+ function validateCustodyItems(items, blockers) {
90
+ if (!Array.isArray(items)) {
91
+ blockers.push(blocker('custody_items array is required', 'custody_items'));
92
+ return { checked: 0, requiredCovered: 0 };
93
+ }
94
+ const byId = new Map();
95
+ items.forEach((item, index) => {
96
+ const path = `custody_items[${index}]`;
97
+ if (!isPlainObject(item)) {
98
+ blockers.push(blocker('custody item must be object', path));
99
+ return;
100
+ }
101
+ for (const field of ['item_id', 'purpose', 'manager_ref', 'access_policy_ref', 'emergency_rotation_ref', 'owner', 'status']) {
102
+ if (!nonEmptyString(item[field])) blockers.push(blocker(`${path}.${field} is required`, `${path}.${field}`));
103
+ }
104
+ if (nonEmptyString(item.item_id)) byId.set(item.item_id, item);
105
+ if (!statusAccepted(item.status)) blockers.push(blocker(`${path}.status must be approved/accepted/go/verified/active`, `${path}.status`));
106
+ if (!positiveNumber(item.rotation_seconds)) blockers.push(blocker(`${path}.rotation_seconds must be positive number`, `${path}.rotation_seconds`));
107
+ if (!isoLike(item.last_rotated_at)) blockers.push(blocker(`${path}.last_rotated_at must be ISO time`, `${path}.last_rotated_at`));
108
+ if (!isoLike(item.next_rotation_at)) blockers.push(blocker(`${path}.next_rotation_at must be ISO time`, `${path}.next_rotation_at`));
109
+ if (item.value_exportable !== false) blockers.push(blocker(`${path}.value_exportable must be false`, `${path}.value_exportable`));
110
+ });
111
+ for (const itemId of REQUIRED_CUSTODY_ITEMS) {
112
+ if (!byId.has(itemId)) blockers.push(blocker(`custody_items must include ${itemId}`, 'custody_items'));
113
+ }
114
+ return { checked: items.length, requiredCovered: REQUIRED_CUSTODY_ITEMS.filter((itemId) => byId.has(itemId)).length };
115
+ }
116
+
117
+ function validateSigningControls(controls, blockers) {
118
+ if (!isPlainObject(controls)) {
119
+ blockers.push(blocker('signing_controls object is required', 'signing_controls'));
120
+ return false;
121
+ }
122
+ for (const field of ['algorithm_policy_ref', 'public_key_registry_ref', 'rotation_runbook_ref', 'verification_runbook_ref']) {
123
+ if (!nonEmptyString(controls[field])) blockers.push(blocker(`signing_controls.${field} is required`, `signing_controls.${field}`));
124
+ }
125
+ if (controls.public_key_published !== true) blockers.push(blocker('signing_controls.public_key_published must be true', 'signing_controls.public_key_published'));
126
+ return true;
127
+ }
128
+
129
+ function validateOperatorAccess(access, blockers) {
130
+ if (!isPlainObject(access)) {
131
+ blockers.push(blocker('operator_access object is required', 'operator_access'));
132
+ return false;
133
+ }
134
+ for (const field of ['least_privilege_ref', 'dual_control_ref', 'break_glass_approval_ref', 'audit_ref', 'review_ref']) {
135
+ if (!nonEmptyString(access[field])) blockers.push(blocker(`operator_access.${field} is required`, `operator_access.${field}`));
136
+ }
137
+ if (access.dual_control_required !== true) blockers.push(blocker('operator_access.dual_control_required must be true', 'operator_access.dual_control_required'));
138
+ if (!positiveNumber(access.review_cadence_seconds)) blockers.push(blocker('operator_access.review_cadence_seconds must be positive number', 'operator_access.review_cadence_seconds'));
139
+ return true;
140
+ }
141
+
142
+ function validateProhibitions(prohibitions, blockers) {
143
+ if (!isPlainObject(prohibitions)) {
144
+ blockers.push(blocker('prohibitions object is required', 'prohibitions'));
145
+ return false;
146
+ }
147
+ for (const field of ['material_in_source', 'material_in_chat', 'material_in_logs', 'artifact_contains_values', 'operator_can_export_values']) {
148
+ if (prohibitions[field] !== false) blockers.push(blocker(`prohibitions.${field} must be false`, `prohibitions.${field}`));
149
+ }
150
+ if (!nonEmptyString(prohibitions.enforcement_ref)) blockers.push(blocker('prohibitions.enforcement_ref is required', 'prohibitions.enforcement_ref'));
151
+ return true;
152
+ }
153
+
154
+ export function validateKmsCustody(custody, options = {}) {
155
+ if (!isPlainObject(custody)) throw new Error('KMS custody evidence must be an object');
156
+ assertNoSecrets(custody);
157
+ const blockers = [];
158
+ if (custody.schema !== KMS_CUSTODY_SCHEMA) blockers.push(blocker('schema mismatch', 'schema'));
159
+ validateMetadata(custody.metadata, blockers);
160
+ const providerOk = validateProvider(custody.custody_provider, blockers);
161
+ const items = validateCustodyItems(custody.custody_items, blockers);
162
+ const signingOk = validateSigningControls(custody.signing_controls, blockers);
163
+ const accessOk = validateOperatorAccess(custody.operator_access, blockers);
164
+ const prohibitionsOk = validateProhibitions(custody.prohibitions, blockers);
165
+
166
+ const result = {
167
+ schema: KMS_CUSTODY_RESULT_SCHEMA,
168
+ generated_at: options.generated_at ?? options.generatedAt ?? new Date(0).toISOString(),
169
+ ok: blockers.length === 0,
170
+ status: blockers.length === 0 ? 'accepted' : 'blocked',
171
+ blockers,
172
+ checked: {
173
+ provider: providerOk,
174
+ required_custody_items: REQUIRED_CUSTODY_ITEMS.length,
175
+ custody_items_covered: items.requiredCovered,
176
+ custody_items: items.checked,
177
+ signing_controls: signingOk,
178
+ operator_access: accessOk,
179
+ prohibitions: prohibitionsOk,
180
+ },
181
+ claim_boundary: [
182
+ 'KMS custody validation checks declared custody evidence shape only; it does not provision KMS, rotate keys, create credentials, or verify cloud IAM.',
183
+ 'A pass result requires references and prohibitions only; it must not include key material, credential values, bearer values, private keys, raw memory, prompts, transcripts, provider responses, or decrypted content.',
184
+ 'Custody evidence supports production readiness review but does not prove hosted/BYOC infrastructure is live by itself.',
185
+ ],
186
+ };
187
+ assertNoSecrets(result, 'result');
188
+ return result;
189
+ }
190
+
191
+ function parseArgs(argv = process.argv.slice(2)) {
192
+ const flags = new Map();
193
+ for (let index = 0; index < argv.length; index += 1) {
194
+ const arg = argv[index];
195
+ if (!arg.startsWith('--')) continue;
196
+ const eq = arg.indexOf('=');
197
+ if (eq !== -1) flags.set(arg.slice(2, eq), arg.slice(eq + 1));
198
+ else if (!argv[index + 1] || argv[index + 1].startsWith('--')) flags.set(arg.slice(2), true);
199
+ else {
200
+ flags.set(arg.slice(2), argv[index + 1]);
201
+ index += 1;
202
+ }
203
+ }
204
+ return flags;
205
+ }
206
+
207
+ function getFlag(flags, names) {
208
+ for (const name of names) if (flags.has(name)) return flags.get(name);
209
+ return undefined;
210
+ }
211
+
212
+ async function main() {
213
+ const flags = parseArgs();
214
+ const custodyPath = getFlag(flags, ['custody', 'in']);
215
+ if (!nonEmptyString(custodyPath)) throw new Error('--custody <path> is required');
216
+ const custody = JSON.parse(await readFile(resolve(String(custodyPath)), 'utf8'));
217
+ const result = validateKmsCustody(custody, { generated_at: new Date().toISOString() });
218
+ const out = getFlag(flags, ['out']);
219
+ const text = `${JSON.stringify(result, null, 2)}\n`;
220
+ if (out && out !== true) {
221
+ const outPath = resolve(String(out));
222
+ await mkdir(dirname(outPath), { recursive: true });
223
+ await writeFile(outPath, text, 'utf8');
224
+ }
225
+ process.stdout.write(text);
226
+ process.exitCode = result.ok ? 0 : 1;
227
+ }
228
+
229
+ const invokedPath = process.argv[1] ? resolve(process.argv[1]) : '';
230
+ if (invokedPath === fileURLToPath(import.meta.url)) {
231
+ main().catch((error) => {
232
+ process.stderr.write(`${error.name ?? 'Error'}: ${error.message}\n`);
233
+ process.exitCode = 1;
234
+ });
235
+ }
@@ -0,0 +1,274 @@
1
+ #!/usr/bin/env node
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ export const LEGAL_COMPLIANCE_APPROVAL_SCHEMA = 'enigma.legal_compliance_approval.v1';
7
+ export const LEGAL_COMPLIANCE_APPROVAL_RESULT_SCHEMA = 'enigma.legal_compliance_approval_result.v1';
8
+
9
+ export const REQUIRED_REVIEW_AREAS = Object.freeze([
10
+ 'privacy',
11
+ 'security',
12
+ 'marketing',
13
+ 'digital_asset_finance',
14
+ 'compliance',
15
+ 'data_retention',
16
+ 'incident_notification',
17
+ ]);
18
+
19
+ export const REQUIRED_NO_CLAIM_IDS = Object.freeze([
20
+ 'provider_deletion',
21
+ 'model_forgetting',
22
+ 'semantic_erasure',
23
+ 'imported_source_completeness',
24
+ 'token_roi_profit_equity',
25
+ 'compliance_status',
26
+ 'tamper_proof_hardware',
27
+ 'raw_compute_superiority',
28
+ 'guaranteed_discount_or_savings',
29
+ 'hosted_live_ready_without_operator_evidence',
30
+ 'unsupported_market_superlative',
31
+ ]);
32
+
33
+ const ACCEPTED_STATUSES = new Set(['approved', 'accepted', 'go', 'verified']);
34
+ const APPROVED_STATEMENTS = new Set(['approved', 'accepted', 'go', 'verified']);
35
+ const NO_CLAIM_STATUSES = new Set(['no_claim', 'no-claim', 'blocked', 'rejected']);
36
+ const ACCEPTED_DECISIONS = new Set(['approved', 'no_claim_only']);
37
+ const SECRET_VALUE_RE = /(?:Bearer\s+[A-Za-z0-9._~+/=-]{12,}|Basic\s+[A-Za-z0-9+/=-]{12,}|-----BEGIN [A-Z ]*PRIVATE KEY-----|https?:\/\/[^\s/@]+:[^\s/@]+@|raw memory|private prompt|full transcript|decrypted capsule|sk-[A-Za-z0-9_-]{16,}|AKIA[0-9A-Z]{16})/iu;
38
+ const SECRET_KEY_RE = /(?:password|passwd|pwd|token|api[_-]?key|private[_-]?key|secret|raw[_-]?memory|plaintext|plain[_-]?text|prompt|completion|transcript|embedding|provider[_-]?response)/iu;
39
+ const SAFE_FIELD_NAMES = new Set(['digital_asset_finance', 'token_roi_profit_equity', 'provider_deletion']);
40
+ const RISKY_APPROVED_CLAIM_RE = /(?:provider[-\s]?side\s+deletion|\bprovider\s+deletion\b|model\s+forgetting|semantic\s+erasure|complete\s+(?:import|source)|imported[-\s]?source\s+completeness|token\s+(?:roi|profit|equity|revenue\s*share)|(?:roi|profit|equity|revenue\s*share)\s+token|(?:SEC|SOC\s*2|HIPAA|GDPR|compliance)\s+(?:certified|approved|compliant|status)|tamper[-\s]?proof|raw\s+compute\s+superiority|(?:guarantee(?:d|s)?|prove(?:n)?|fixed)\s+(?:\d+(?:\.\d+)?%\s+)?(?:discount|savings?|cost\s+reduction)|\d+(?:\.\d+)?\s*[-–]\s*\d+(?:\.\d+)?%\s+(?:discount|cheaper|savings?)|hosted[_\s-]?live[_\s-]?ready|best\s+(?:in|on)\s+(?:the\s+)?world|better\s+than\s+anything|unmatched|unrivaled|never\s+been\s+done)/iu;
41
+
42
+ function isPlainObject(value) {
43
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
44
+ }
45
+
46
+ function nonEmptyString(value) {
47
+ return typeof value === 'string' && value.trim().length > 0;
48
+ }
49
+
50
+ function isoLike(value) {
51
+ return nonEmptyString(value) && !Number.isNaN(Date.parse(value));
52
+ }
53
+
54
+ function blocker(message, path) {
55
+ return { message, path };
56
+ }
57
+
58
+ function assertNoSecrets(value, path = 'approval') {
59
+ if (typeof value === 'string') {
60
+ if (!/\.claim_boundary\[\d+\]$/.test(path) && SECRET_VALUE_RE.test(value)) throw new Error(`${path} contains secret or raw-memory-looking data`);
61
+ return;
62
+ }
63
+ if (Array.isArray(value)) {
64
+ value.forEach((item, index) => assertNoSecrets(item, `${path}[${index}]`));
65
+ return;
66
+ }
67
+ if (!isPlainObject(value)) return;
68
+ for (const [key, child] of Object.entries(value)) {
69
+ if (SECRET_KEY_RE.test(key) && !SAFE_FIELD_NAMES.has(key)) throw new Error(`${path}.${key} uses a forbidden field name`);
70
+ assertNoSecrets(child, `${path}.${key}`);
71
+ }
72
+ }
73
+
74
+ function statusAccepted(value) {
75
+ return typeof value === 'string' && ACCEPTED_STATUSES.has(value.trim().toLowerCase());
76
+ }
77
+
78
+ function statementStatusApproved(value) {
79
+ return typeof value === 'string' && APPROVED_STATEMENTS.has(value.trim().toLowerCase());
80
+ }
81
+
82
+ function noClaimStatus(value) {
83
+ return typeof value === 'string' && NO_CLAIM_STATUSES.has(value.trim().toLowerCase());
84
+ }
85
+
86
+ function validateReviewAreas(reviewAreas, blockers) {
87
+ if (!isPlainObject(reviewAreas)) {
88
+ blockers.push(blocker('review_areas object is required', 'review_areas'));
89
+ return 0;
90
+ }
91
+ let checked = 0;
92
+ for (const area of REQUIRED_REVIEW_AREAS) {
93
+ const entry = reviewAreas[area];
94
+ checked += 1;
95
+ if (!isPlainObject(entry)) {
96
+ blockers.push(blocker(`review_areas.${area} is required`, `review_areas.${area}`));
97
+ continue;
98
+ }
99
+ for (const field of ['owner', 'evidence_ref', 'status']) {
100
+ if (!nonEmptyString(entry[field])) blockers.push(blocker(`review_areas.${area}.${field} is required`, `review_areas.${area}.${field}`));
101
+ }
102
+ if (!statusAccepted(entry.status)) blockers.push(blocker(`review_areas.${area}.status must be approved/accepted/go/verified`, `review_areas.${area}.status`));
103
+ }
104
+ return checked;
105
+ }
106
+
107
+ function validateReviewedStatements(statements, decision, blockers) {
108
+ if (!Array.isArray(statements)) {
109
+ blockers.push(blocker('reviewed_statements array is required', 'reviewed_statements'));
110
+ return { checked: 0, approved: 0, noClaim: 0 };
111
+ }
112
+ let approved = 0;
113
+ let noClaim = 0;
114
+ statements.forEach((statement, index) => {
115
+ const path = `reviewed_statements[${index}]`;
116
+ if (!isPlainObject(statement)) {
117
+ blockers.push(blocker('reviewed statement must be object', path));
118
+ return;
119
+ }
120
+ for (const field of ['statement_id', 'text', 'scope', 'evidence_ref', 'status']) {
121
+ if (!nonEmptyString(statement[field])) blockers.push(blocker(`${path}.${field} is required`, `${path}.${field}`));
122
+ }
123
+ const status = String(statement.status ?? '').trim().toLowerCase();
124
+ if (statementStatusApproved(status)) {
125
+ approved += 1;
126
+ if (RISKY_APPROVED_CLAIM_RE.test(String(statement.text ?? ''))) {
127
+ blockers.push(blocker('approved statement contains unsupported or compliance-sensitive overclaim', `${path}.text`));
128
+ }
129
+ } else if (noClaimStatus(status)) {
130
+ noClaim += 1;
131
+ } else {
132
+ blockers.push(blocker(`${path}.status must be approved/accepted/go/verified/no_claim/blocked/rejected`, `${path}.status`));
133
+ }
134
+ });
135
+ if (decision === 'approved' && approved === 0) blockers.push(blocker('approved decision requires at least one approved reviewed_statement', 'reviewed_statements'));
136
+ return { checked: statements.length, approved, noClaim };
137
+ }
138
+
139
+ function validateNoClaims(noClaims, blockers) {
140
+ if (!Array.isArray(noClaims)) {
141
+ blockers.push(blocker('no_claims array is required', 'no_claims'));
142
+ return 0;
143
+ }
144
+ const byId = new Map();
145
+ noClaims.forEach((entry, index) => {
146
+ const path = `no_claims[${index}]`;
147
+ if (!isPlainObject(entry)) {
148
+ blockers.push(blocker('no-claim entry must be object', path));
149
+ return;
150
+ }
151
+ for (const field of ['claim_id', 'decision', 'scope', 'evidence_ref']) {
152
+ if (!nonEmptyString(entry[field])) blockers.push(blocker(`${path}.${field} is required`, `${path}.${field}`));
153
+ }
154
+ if (String(entry.decision ?? '').trim().toLowerCase() !== 'no_claim') blockers.push(blocker(`${path}.decision must be no_claim`, `${path}.decision`));
155
+ if (nonEmptyString(entry.claim_id)) byId.set(entry.claim_id, entry);
156
+ });
157
+ for (const claimId of REQUIRED_NO_CLAIM_IDS) {
158
+ if (!byId.has(claimId)) blockers.push(blocker(`no_claims must include ${claimId}`, 'no_claims'));
159
+ }
160
+ return noClaims.length;
161
+ }
162
+
163
+ function validatePublicationControls(controls, blockers) {
164
+ if (!isPlainObject(controls)) {
165
+ blockers.push(blocker('publication_controls object is required', 'publication_controls'));
166
+ return false;
167
+ }
168
+ for (const field of ['publication_ref', 'claims_owner', 'withdrawal_path_ref', 'last_review_expires_at']) {
169
+ if (!nonEmptyString(controls[field])) blockers.push(blocker(`publication_controls.${field} is required`, `publication_controls.${field}`));
170
+ }
171
+ if (!isoLike(controls.last_review_expires_at)) blockers.push(blocker('publication_controls.last_review_expires_at must be ISO time', 'publication_controls.last_review_expires_at'));
172
+ if (!Array.isArray(controls.allowed_channels) || controls.allowed_channels.length === 0 || !controls.allowed_channels.every(nonEmptyString)) {
173
+ blockers.push(blocker('publication_controls.allowed_channels must be non-empty strings', 'publication_controls.allowed_channels'));
174
+ }
175
+ return true;
176
+ }
177
+
178
+ export function validateLegalComplianceApproval(approval, options = {}) {
179
+ if (!isPlainObject(approval)) throw new Error('legal compliance approval must be an object');
180
+ assertNoSecrets(approval);
181
+ const blockers = [];
182
+ if (approval.schema !== LEGAL_COMPLIANCE_APPROVAL_SCHEMA) blockers.push(blocker('schema mismatch', 'schema'));
183
+
184
+ const metadata = approval.metadata;
185
+ let decision = String(approval.decision ?? '').trim().toLowerCase();
186
+ if (!isPlainObject(metadata)) blockers.push(blocker('metadata is required', 'metadata'));
187
+ else {
188
+ for (const field of ['approval_id', 'environment', 'tenant', 'legal_owner', 'privacy_owner', 'reviewer', 'approved_at', 'approval_ref', 'status']) {
189
+ if (!nonEmptyString(metadata[field])) blockers.push(blocker(`metadata.${field} is required`, `metadata.${field}`));
190
+ }
191
+ if (!statusAccepted(metadata.status)) blockers.push(blocker('metadata.status must be approved/accepted/go/verified', 'metadata.status'));
192
+ if (!isoLike(metadata.approved_at)) blockers.push(blocker('metadata.approved_at must be ISO time', 'metadata.approved_at'));
193
+ }
194
+
195
+ if (!ACCEPTED_DECISIONS.has(decision)) {
196
+ blockers.push(blocker('decision must be approved or no_claim_only', 'decision'));
197
+ decision = 'blocked';
198
+ }
199
+
200
+ const reviewAreasChecked = validateReviewAreas(approval.review_areas, blockers);
201
+ const statements = validateReviewedStatements(approval.reviewed_statements, decision, blockers);
202
+ const noClaimsChecked = validateNoClaims(approval.no_claims, blockers);
203
+ validatePublicationControls(approval.publication_controls, blockers);
204
+
205
+ const result = {
206
+ schema: LEGAL_COMPLIANCE_APPROVAL_RESULT_SCHEMA,
207
+ generated_at: options.generated_at ?? options.generatedAt ?? new Date(0).toISOString(),
208
+ ok: blockers.length === 0,
209
+ decision: blockers.length === 0 ? decision : 'blocked',
210
+ blockers,
211
+ checked: {
212
+ review_areas: reviewAreasChecked,
213
+ reviewed_statements: statements.checked,
214
+ approved_statements: statements.approved,
215
+ no_claim_statements: statements.noClaim,
216
+ required_no_claims: REQUIRED_NO_CLAIM_IDS.length,
217
+ supplied_no_claims: noClaimsChecked,
218
+ publication_controls: isPlainObject(approval.publication_controls),
219
+ },
220
+ claim_boundary: [
221
+ 'Legal/compliance approval validation checks declared approval shape and overclaim blockers only; it is not legal advice.',
222
+ 'Approved statements must not claim provider deletion, model forgetting, compliance certification, token ROI, guaranteed savings, raw compute superiority, unsupported superlatives, or hosted live readiness without separate operator evidence.',
223
+ 'Secrets, raw memory, prompts, transcripts, provider responses, decrypted content, and credentials must remain outside legal/compliance artifacts.',
224
+ ],
225
+ };
226
+ assertNoSecrets(result, 'result');
227
+ return result;
228
+ }
229
+
230
+ function parseArgs(argv = process.argv.slice(2)) {
231
+ const flags = new Map();
232
+ for (let index = 0; index < argv.length; index += 1) {
233
+ const arg = argv[index];
234
+ if (!arg.startsWith('--')) continue;
235
+ const eq = arg.indexOf('=');
236
+ if (eq !== -1) flags.set(arg.slice(2, eq), arg.slice(eq + 1));
237
+ else if (!argv[index + 1] || argv[index + 1].startsWith('--')) flags.set(arg.slice(2), true);
238
+ else {
239
+ flags.set(arg.slice(2), argv[index + 1]);
240
+ index += 1;
241
+ }
242
+ }
243
+ return flags;
244
+ }
245
+
246
+ function getFlag(flags, names) {
247
+ for (const name of names) if (flags.has(name)) return flags.get(name);
248
+ return undefined;
249
+ }
250
+
251
+ async function main() {
252
+ const flags = parseArgs();
253
+ const approvalPath = getFlag(flags, ['approval', 'in']);
254
+ if (!nonEmptyString(approvalPath)) throw new Error('--approval <path> is required');
255
+ const approval = JSON.parse(await readFile(resolve(String(approvalPath)), 'utf8'));
256
+ const result = validateLegalComplianceApproval(approval, { generated_at: new Date().toISOString() });
257
+ const out = getFlag(flags, ['out']);
258
+ const text = `${JSON.stringify(result, null, 2)}\n`;
259
+ if (out && out !== true) {
260
+ const outPath = resolve(String(out));
261
+ await mkdir(dirname(outPath), { recursive: true });
262
+ await writeFile(outPath, text, 'utf8');
263
+ }
264
+ process.stdout.write(text);
265
+ process.exitCode = result.ok ? 0 : 1;
266
+ }
267
+
268
+ const invokedPath = process.argv[1] ? resolve(process.argv[1]) : '';
269
+ if (invokedPath === fileURLToPath(import.meta.url)) {
270
+ main().catch((error) => {
271
+ process.stderr.write(`${error.name ?? 'Error'}: ${error.message}\n`);
272
+ process.exitCode = 1;
273
+ });
274
+ }