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,256 @@
|
|
|
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
|
+
import {
|
|
6
|
+
OPERATOR_SERVICE_QUOTE_SCHEMA,
|
|
7
|
+
PERMISSIONLESS_MEMORY_JOB_SCHEMA,
|
|
8
|
+
SERVICE_SETTLEMENT_RECEIPT_SCHEMA,
|
|
9
|
+
SETTLEMENT_BATCH_SCHEMA,
|
|
10
|
+
verifyServiceSettlementReceipt,
|
|
11
|
+
} from '../packages/settlement/src/index.js';
|
|
12
|
+
|
|
13
|
+
export const SERVICE_SETTLEMENT_RESULT_SCHEMA = 'enigma.service_settlement_result.v1';
|
|
14
|
+
|
|
15
|
+
const HASH_RE = /^sha256:[0-9a-f]{64}$/;
|
|
16
|
+
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;
|
|
17
|
+
const FORBIDDEN_KEY_RE = /(?:^|_)(?:raw|plaintext|plain_text|prompt|prompts|message|messages|text|content|document|documents|transcript|response|responses|provider_response|response_body|credential|credentials|api_key|secret|password|private_key|seed|mnemonic)(?:$|_)/iu;
|
|
18
|
+
const SAFE_FIELD_NAMES = new Set(['raw_memory_on_chain', 'raw_memory_in_job', 'raw_memory_in_receipt', 'raw_memory_access_required']);
|
|
19
|
+
|
|
20
|
+
function isPlainObject(value) {
|
|
21
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function blocker(message, path) {
|
|
25
|
+
return { message, path };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function assertNoSensitivePayload(value, path = 'service_settlement') {
|
|
29
|
+
if (typeof value === 'string') {
|
|
30
|
+
if (!/\.(claim_boundary\[\d+\]|product_thesis|blockers\[\d+\]\.message)$/.test(path) && SECRET_VALUE_RE.test(value)) throw new Error(`${path} contains secret-looking data`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (Array.isArray(value)) {
|
|
34
|
+
value.forEach((item, index) => assertNoSensitivePayload(item, `${path}[${index}]`));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (!isPlainObject(value)) return;
|
|
38
|
+
for (const [key, child] of Object.entries(value)) {
|
|
39
|
+
const childPath = `${path}.${key}`;
|
|
40
|
+
if (FORBIDDEN_KEY_RE.test(key) && !SAFE_FIELD_NAMES.has(key) && !/^result\.blockers\[\d+\]\.message$/.test(childPath)) throw new Error(`${childPath} is not allowed in service settlement artifacts`);
|
|
41
|
+
assertNoSensitivePayload(child, childPath);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function finiteNonNegative(value) {
|
|
46
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function pushHash(blockers, value, path, label = path) {
|
|
50
|
+
if (!HASH_RE.test(String(value ?? ''))) blockers.push(blocker(`${label} must be sha256 hash`, path));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function pushString(blockers, value, path, label = path) {
|
|
54
|
+
if (typeof value !== 'string' || value.trim().length === 0) blockers.push(blocker(`${label} must be non-empty string`, path));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function validateJob(job, blockers, path) {
|
|
58
|
+
if (!isPlainObject(job)) {
|
|
59
|
+
blockers.push(blocker('job object is required', path));
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
if (job.schema !== PERMISSIONLESS_MEMORY_JOB_SCHEMA) blockers.push(blocker('job schema mismatch', `${path}.schema`));
|
|
63
|
+
if (!/^pjob_[a-f0-9]{32}$/.test(String(job.job_id ?? ''))) blockers.push(blocker('job_id must be deterministic pjob hash id', `${path}.job_id`));
|
|
64
|
+
pushHash(blockers, job.job_hash, `${path}.job_hash`, 'job_hash');
|
|
65
|
+
pushHash(blockers, job.memory_commitment_root, `${path}.memory_commitment_root`, 'memory_commitment_root');
|
|
66
|
+
pushHash(blockers, job.policy_hash, `${path}.policy_hash`, 'policy_hash');
|
|
67
|
+
pushHash(blockers, job.usage_event_hash, `${path}.usage_event_hash`, 'usage_event_hash');
|
|
68
|
+
pushString(blockers, job.tenant_id, `${path}.tenant_id`, 'tenant_id');
|
|
69
|
+
if (!finiteNonNegative(job.max_price?.amount) || job.max_price.amount <= 0) blockers.push(blocker('job max_price.amount must be positive number', `${path}.max_price.amount`));
|
|
70
|
+
pushString(blockers, job.max_price?.asset, `${path}.max_price.asset`, 'max_price.asset');
|
|
71
|
+
const boundary = job.access_boundary;
|
|
72
|
+
if (!isPlainObject(boundary)) blockers.push(blocker('job access_boundary is required', `${path}.access_boundary`));
|
|
73
|
+
else {
|
|
74
|
+
if (boundary.permissionless_submission !== true) blockers.push(blocker('job permissionless_submission must be true', `${path}.access_boundary.permissionless_submission`));
|
|
75
|
+
for (const field of ['raw_memory_on_chain', 'raw_memory_in_job', 'token_roi_claim', 'provider_invoice_savings_claim']) {
|
|
76
|
+
if (boundary[field] !== false) blockers.push(blocker(`job access_boundary.${field} must be false`, `${path}.access_boundary.${field}`));
|
|
77
|
+
}
|
|
78
|
+
if (boundary.centralized_or_byoc_hot_path !== true) blockers.push(blocker('job centralized_or_byoc_hot_path must be true', `${path}.access_boundary.centralized_or_byoc_hot_path`));
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function validateQuote(quote, job, blockers, path) {
|
|
84
|
+
if (!isPlainObject(quote)) {
|
|
85
|
+
blockers.push(blocker('quote object is required', path));
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
if (quote.schema !== OPERATOR_SERVICE_QUOTE_SCHEMA) blockers.push(blocker('quote schema mismatch', `${path}.schema`));
|
|
89
|
+
if (!/^quote_[a-f0-9]{32}$/.test(String(quote.quote_id ?? ''))) blockers.push(blocker('quote_id must be deterministic quote hash id', `${path}.quote_id`));
|
|
90
|
+
pushHash(blockers, quote.quote_hash, `${path}.quote_hash`, 'quote_hash');
|
|
91
|
+
pushString(blockers, quote.operator_id, `${path}.operator_id`, 'operator_id');
|
|
92
|
+
pushString(blockers, quote.service_kind, `${path}.service_kind`, 'service_kind');
|
|
93
|
+
if (!finiteNonNegative(quote.price?.amount) || quote.price.amount <= 0) blockers.push(blocker('quote price.amount must be positive number', `${path}.price.amount`));
|
|
94
|
+
pushString(blockers, quote.price?.asset, `${path}.price.asset`, 'price.asset');
|
|
95
|
+
if (isPlainObject(job)) {
|
|
96
|
+
if (quote.job_hash !== job.job_hash || quote.job_id !== job.job_id) blockers.push(blocker('quote must reference job', path));
|
|
97
|
+
if (quote.tenant_id !== job.tenant_id) blockers.push(blocker('quote tenant_id must match job', `${path}.tenant_id`));
|
|
98
|
+
if (quote.price?.asset !== job.max_price?.asset) blockers.push(blocker('quote asset must match job max_price asset', `${path}.price.asset`));
|
|
99
|
+
if (finiteNonNegative(quote.price?.amount) && finiteNonNegative(job.max_price?.amount) && quote.price.amount > job.max_price.amount) blockers.push(blocker('quote price exceeds job max_price', `${path}.price.amount`));
|
|
100
|
+
}
|
|
101
|
+
const boundary = quote.accountability_boundary;
|
|
102
|
+
if (!isPlainObject(boundary)) blockers.push(blocker('quote accountability_boundary is required', `${path}.accountability_boundary`));
|
|
103
|
+
else {
|
|
104
|
+
if (boundary.service_receipt_required !== true) blockers.push(blocker('quote service_receipt_required must be true', `${path}.accountability_boundary.service_receipt_required`));
|
|
105
|
+
for (const field of ['raw_memory_access_required', 'operator_controls_hot_path', 'token_profit_claim']) {
|
|
106
|
+
if (boundary[field] !== false) blockers.push(blocker(`quote accountability_boundary.${field} must be false`, `${path}.accountability_boundary.${field}`));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function validateReceipt(receipt, job, quote, blockers, path) {
|
|
113
|
+
if (!isPlainObject(receipt)) {
|
|
114
|
+
blockers.push(blocker('receipt object is required', path));
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
if (receipt.schema !== SERVICE_SETTLEMENT_RECEIPT_SCHEMA) blockers.push(blocker('receipt schema mismatch', `${path}.schema`));
|
|
118
|
+
if (!/^settle_[a-f0-9]{32}$/.test(String(receipt.settlement_receipt_id ?? ''))) blockers.push(blocker('settlement_receipt_id must be deterministic settle hash id', `${path}.settlement_receipt_id`));
|
|
119
|
+
pushHash(blockers, receipt.settlement_receipt_hash, `${path}.settlement_receipt_hash`, 'settlement_receipt_hash');
|
|
120
|
+
pushHash(blockers, receipt.job_hash, `${path}.job_hash`, 'receipt job_hash');
|
|
121
|
+
pushHash(blockers, receipt.quote_hash, `${path}.quote_hash`, 'receipt quote_hash');
|
|
122
|
+
pushHash(blockers, receipt.usage_event_hash, `${path}.usage_event_hash`, 'receipt usage_event_hash');
|
|
123
|
+
pushHash(blockers, receipt.memory_commitment_root, `${path}.memory_commitment_root`, 'receipt memory_commitment_root');
|
|
124
|
+
pushHash(blockers, receipt.policy_hash, `${path}.policy_hash`, 'receipt policy_hash');
|
|
125
|
+
if (!finiteNonNegative(receipt.settled_price?.amount)) blockers.push(blocker('settled_price.amount must be non-negative number', `${path}.settled_price.amount`));
|
|
126
|
+
pushString(blockers, receipt.settled_price?.asset, `${path}.settled_price.asset`, 'settled_price.asset');
|
|
127
|
+
pushString(blockers, receipt.settlement_ref, `${path}.settlement_ref`, 'settlement_ref');
|
|
128
|
+
pushString(blockers, receipt.service_receipt_ref, `${path}.service_receipt_ref`, 'service_receipt_ref');
|
|
129
|
+
const verification = verifyServiceSettlementReceipt({ job, quote, receipt });
|
|
130
|
+
for (const error of verification.errors ?? []) blockers.push(blocker(error, path));
|
|
131
|
+
const boundary = receipt.settlement_boundary;
|
|
132
|
+
if (!isPlainObject(boundary)) blockers.push(blocker('receipt settlement_boundary is required', `${path}.settlement_boundary`));
|
|
133
|
+
else {
|
|
134
|
+
if (boundary.permissionless_access !== true) blockers.push(blocker('receipt permissionless_access must be true', `${path}.settlement_boundary.permissionless_access`));
|
|
135
|
+
for (const field of ['raw_memory_on_chain', 'raw_memory_in_receipt', 'provider_invoice_savings_claim', 'token_roi_claim', 'token_profit_claim', 'model_forgetting_claim', 'provider_deletion_claim']) {
|
|
136
|
+
if (boundary[field] !== false) blockers.push(blocker(`receipt settlement_boundary.${field} must be false`, `${path}.settlement_boundary.${field}`));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function validateBatch(batch, receipts, blockers, path) {
|
|
143
|
+
if (batch === undefined || batch === null) return false;
|
|
144
|
+
if (!isPlainObject(batch)) {
|
|
145
|
+
blockers.push(blocker('batch must be object when supplied', path));
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
if (batch.schema !== SETTLEMENT_BATCH_SCHEMA) blockers.push(blocker('batch schema mismatch', `${path}.schema`));
|
|
149
|
+
if (!/^batch_[a-f0-9]{32}$/.test(String(batch.batch_id ?? ''))) blockers.push(blocker('batch_id must be deterministic batch hash id', `${path}.batch_id`));
|
|
150
|
+
pushHash(blockers, batch.batch_hash, `${path}.batch_hash`, 'batch_hash');
|
|
151
|
+
if (!Number.isSafeInteger(batch.receipt_count) || batch.receipt_count <= 0) blockers.push(blocker('batch receipt_count must be positive integer', `${path}.receipt_count`));
|
|
152
|
+
if (!Array.isArray(batch.receipt_hashes) || batch.receipt_hashes.length !== batch.receipt_count || batch.receipt_hashes.some((hash) => !HASH_RE.test(String(hash)))) blockers.push(blocker('batch receipt_hashes must match receipt_count and be sha256 hashes', `${path}.receipt_hashes`));
|
|
153
|
+
if (Array.isArray(receipts) && receipts.length > 0) {
|
|
154
|
+
for (const receipt of receipts) {
|
|
155
|
+
if (!batch.receipt_hashes?.includes(receipt.settlement_receipt_hash)) blockers.push(blocker('batch missing supplied receipt hash', `${path}.receipt_hashes`));
|
|
156
|
+
}
|
|
157
|
+
const expectedTotal = receipts.reduce((sum, receipt) => sum + (receipt.settled_price?.amount ?? 0), 0);
|
|
158
|
+
if (finiteNonNegative(batch.total_settled_amount) && Math.abs(batch.total_settled_amount - expectedTotal) > 1e-9) blockers.push(blocker('batch total_settled_amount must equal supplied receipts', `${path}.total_settled_amount`));
|
|
159
|
+
}
|
|
160
|
+
if (!Array.isArray(batch.claim_boundary) || batch.claim_boundary.length === 0) blockers.push(blocker('batch claim_boundary is required', `${path}.claim_boundary`));
|
|
161
|
+
if (Array.isArray(batch.claim_boundary) && !batch.claim_boundary.some((line) => /not investment|not.*profit|not.*provider invoice/i.test(line))) blockers.push(blocker('batch claim_boundary must reject investment/provider-invoice claims', `${path}.claim_boundary`));
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function normalizeInput(input) {
|
|
166
|
+
if (isPlainObject(input) && (input.job || input.quote || input.receipt || input.batch || input.receipts)) return input;
|
|
167
|
+
return { receipt: input };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function validateServiceSettlement(input, options = {}) {
|
|
171
|
+
assertNoSensitivePayload(input);
|
|
172
|
+
const normalized = normalizeInput(input);
|
|
173
|
+
const blockers = [];
|
|
174
|
+
const job = normalized.job;
|
|
175
|
+
const quote = normalized.quote;
|
|
176
|
+
const receipt = normalized.receipt;
|
|
177
|
+
const receipts = Array.isArray(normalized.receipts) ? normalized.receipts : (receipt ? [receipt] : []);
|
|
178
|
+
validateJob(job, blockers, 'job');
|
|
179
|
+
validateQuote(quote, job, blockers, 'quote');
|
|
180
|
+
validateReceipt(receipt, job, quote, blockers, 'receipt');
|
|
181
|
+
validateBatch(normalized.batch, receipts, blockers, 'batch');
|
|
182
|
+
const result = {
|
|
183
|
+
schema: SERVICE_SETTLEMENT_RESULT_SCHEMA,
|
|
184
|
+
generated_at: options.generated_at ?? options.generatedAt ?? new Date(0).toISOString(),
|
|
185
|
+
ok: blockers.length === 0,
|
|
186
|
+
status: blockers.length === 0 ? 'accepted' : 'blocked',
|
|
187
|
+
blockers,
|
|
188
|
+
checked: {
|
|
189
|
+
job_schema: job?.schema ?? null,
|
|
190
|
+
quote_schema: quote?.schema ?? null,
|
|
191
|
+
receipt_schema: receipt?.schema ?? null,
|
|
192
|
+
batch_schema: normalized.batch?.schema ?? null,
|
|
193
|
+
receipt_count: receipts.length,
|
|
194
|
+
max_price_amount: job?.max_price?.amount ?? null,
|
|
195
|
+
quote_price_amount: quote?.price?.amount ?? null,
|
|
196
|
+
settled_amount: receipt?.settled_price?.amount ?? null,
|
|
197
|
+
asset: receipt?.settled_price?.asset ?? quote?.price?.asset ?? job?.max_price?.asset ?? null,
|
|
198
|
+
invariant: finiteNonNegative(receipt?.settled_price?.amount) && finiteNonNegative(quote?.price?.amount) && finiteNonNegative(job?.max_price?.amount)
|
|
199
|
+
? receipt.settled_price.amount <= quote.price.amount && quote.price.amount <= job.max_price.amount
|
|
200
|
+
: false,
|
|
201
|
+
},
|
|
202
|
+
claim_boundary: [
|
|
203
|
+
'Service settlement validation checks hash-only Enigma job/quote/receipt linkage and price invariants; it is not investment, profit, yield, or provider invoice evidence.',
|
|
204
|
+
'A pass result does not claim token ROI, token profit, decentralized raw-memory inference, provider deletion, model forgetting, or compliance status.',
|
|
205
|
+
'Raw memory, prompts, completions, transcripts, provider responses, decrypted memory, credentials, private keys, and seed phrases are forbidden in service settlement artifacts.',
|
|
206
|
+
],
|
|
207
|
+
};
|
|
208
|
+
assertNoSensitivePayload(result, 'result');
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function parseArgs(argv = process.argv.slice(2)) {
|
|
213
|
+
const flags = new Map();
|
|
214
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
215
|
+
const arg = argv[index];
|
|
216
|
+
if (!arg.startsWith('--')) continue;
|
|
217
|
+
const eq = arg.indexOf('=');
|
|
218
|
+
if (eq !== -1) flags.set(arg.slice(2, eq), arg.slice(eq + 1));
|
|
219
|
+
else if (!argv[index + 1] || argv[index + 1].startsWith('--')) flags.set(arg.slice(2), true);
|
|
220
|
+
else {
|
|
221
|
+
flags.set(arg.slice(2), argv[index + 1]);
|
|
222
|
+
index += 1;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return flags;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function getFlag(flags, names) {
|
|
229
|
+
for (const name of names) if (flags.has(name)) return flags.get(name);
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function main() {
|
|
234
|
+
const flags = parseArgs();
|
|
235
|
+
const settlementPath = getFlag(flags, ['settlement', 'in']);
|
|
236
|
+
if (typeof settlementPath !== 'string' || settlementPath.trim() === '') throw new Error('--settlement <path> is required');
|
|
237
|
+
const input = JSON.parse(await readFile(resolve(settlementPath), 'utf8'));
|
|
238
|
+
const result = validateServiceSettlement(input, { generated_at: new Date().toISOString() });
|
|
239
|
+
const out = getFlag(flags, ['out']);
|
|
240
|
+
const text = `${JSON.stringify(result, null, 2)}\n`;
|
|
241
|
+
if (out && out !== true) {
|
|
242
|
+
const outPath = resolve(String(out));
|
|
243
|
+
await mkdir(dirname(outPath), { recursive: true });
|
|
244
|
+
await writeFile(outPath, text, 'utf8');
|
|
245
|
+
}
|
|
246
|
+
process.stdout.write(text);
|
|
247
|
+
process.exitCode = result.ok ? 0 : 1;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const invokedPath = process.argv[1] ? resolve(process.argv[1]) : '';
|
|
251
|
+
if (invokedPath === fileURLToPath(import.meta.url)) {
|
|
252
|
+
main().catch((error) => {
|
|
253
|
+
process.stderr.write(`${error.name ?? 'Error'}: ${error.message}\n`);
|
|
254
|
+
process.exitCode = 1;
|
|
255
|
+
});
|
|
256
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
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 SUPPORT_SLA_SCHEMA = 'enigma.support_sla.v1';
|
|
7
|
+
export const SUPPORT_SLA_RESULT_SCHEMA = 'enigma.support_sla_result.v1';
|
|
8
|
+
|
|
9
|
+
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;
|
|
10
|
+
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;
|
|
11
|
+
const REQUIRED_SEVERITIES = Object.freeze(['sev1', 'sev2', 'sev3']);
|
|
12
|
+
const STATUS_ACCEPTED = new Set(['approved', 'accepted', 'go', 'verified']);
|
|
13
|
+
|
|
14
|
+
function isPlainObject(value) {
|
|
15
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function nonEmptyString(value) {
|
|
19
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function nonNegative(value) {
|
|
23
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function assertNoSecrets(value, path = 'sla') {
|
|
27
|
+
if (typeof value === 'string') {
|
|
28
|
+
if (!/\.claim_boundary\[\d+\]$/.test(path) && SECRET_VALUE_RE.test(value)) throw new Error(`${path} contains secret or raw-memory-looking data`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
value.forEach((item, index) => assertNoSecrets(item, `${path}[${index}]`));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (!isPlainObject(value)) return;
|
|
36
|
+
for (const [key, child] of Object.entries(value)) {
|
|
37
|
+
if (SECRET_KEY_RE.test(key)) throw new Error(`${path}.${key} uses a forbidden field name`);
|
|
38
|
+
assertNoSecrets(child, `${path}.${key}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function blocker(message, path) {
|
|
43
|
+
return { message, path };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isoLike(value) {
|
|
47
|
+
return nonEmptyString(value) && !Number.isNaN(Date.parse(value));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function validateSupportSla(sla, options = {}) {
|
|
51
|
+
if (!isPlainObject(sla)) throw new Error('support SLA must be an object');
|
|
52
|
+
assertNoSecrets(sla);
|
|
53
|
+
const blockers = [];
|
|
54
|
+
if (sla.schema !== SUPPORT_SLA_SCHEMA) blockers.push(blocker('schema mismatch', 'schema'));
|
|
55
|
+
|
|
56
|
+
const metadata = sla.metadata;
|
|
57
|
+
if (!isPlainObject(metadata)) blockers.push(blocker('metadata is required', 'metadata'));
|
|
58
|
+
else {
|
|
59
|
+
for (const field of ['sla_id', 'environment', 'tenant', 'owner', 'approved_at', 'approval_ref', 'status']) {
|
|
60
|
+
if (!nonEmptyString(metadata[field])) blockers.push(blocker(`metadata.${field} is required`, `metadata.${field}`));
|
|
61
|
+
}
|
|
62
|
+
if (!STATUS_ACCEPTED.has(String(metadata.status ?? '').toLowerCase())) blockers.push(blocker('metadata.status must be approved/accepted/go/verified', 'metadata.status'));
|
|
63
|
+
if (!isoLike(metadata.approved_at)) blockers.push(blocker('metadata.approved_at must be ISO time', 'metadata.approved_at'));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const supportHours = sla.support_hours;
|
|
67
|
+
if (!isPlainObject(supportHours)) blockers.push(blocker('support_hours object is required', 'support_hours'));
|
|
68
|
+
else {
|
|
69
|
+
for (const field of ['timezone', 'coverage', 'holiday_policy_ref']) {
|
|
70
|
+
if (!nonEmptyString(supportHours[field])) blockers.push(blocker(`support_hours.${field} is required`, `support_hours.${field}`));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const severities = sla.severities;
|
|
75
|
+
if (!isPlainObject(severities)) blockers.push(blocker('severities object is required', 'severities'));
|
|
76
|
+
else {
|
|
77
|
+
for (const severity of REQUIRED_SEVERITIES) {
|
|
78
|
+
const item = severities[severity];
|
|
79
|
+
if (!isPlainObject(item)) blockers.push(blocker(`severity ${severity} is required`, `severities.${severity}`));
|
|
80
|
+
else {
|
|
81
|
+
for (const field of ['definition', 'response_seconds', 'update_seconds', 'resolution_target_seconds', 'escalation_ref']) {
|
|
82
|
+
if (field.endsWith('_seconds')) {
|
|
83
|
+
if (!nonNegative(item[field])) blockers.push(blocker(`severities.${severity}.${field} must be non-negative number`, `severities.${severity}.${field}`));
|
|
84
|
+
} else if (!nonEmptyString(item[field])) blockers.push(blocker(`severities.${severity}.${field} is required`, `severities.${severity}.${field}`));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const channels = sla.channels;
|
|
91
|
+
if (!Array.isArray(channels) || channels.length === 0) blockers.push(blocker('channels array is required', 'channels'));
|
|
92
|
+
else channels.forEach((channel, index) => {
|
|
93
|
+
if (!isPlainObject(channel)) blockers.push(blocker('channel must be object', `channels[${index}]`));
|
|
94
|
+
else {
|
|
95
|
+
for (const field of ['type', 'name', 'ref', 'owner']) {
|
|
96
|
+
if (!nonEmptyString(channel[field])) blockers.push(blocker(`channels[${index}].${field} is required`, `channels[${index}].${field}`));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const escalation = sla.escalation_matrix;
|
|
102
|
+
if (!Array.isArray(escalation) || escalation.length === 0) blockers.push(blocker('escalation_matrix array is required', 'escalation_matrix'));
|
|
103
|
+
else escalation.forEach((entry, index) => {
|
|
104
|
+
if (!isPlainObject(entry)) blockers.push(blocker('escalation entry must be object', `escalation_matrix[${index}]`));
|
|
105
|
+
else {
|
|
106
|
+
for (const field of ['level', 'owner', 'trigger', 'target_seconds', 'ref']) {
|
|
107
|
+
if (field === 'target_seconds') {
|
|
108
|
+
if (!nonNegative(entry[field])) blockers.push(blocker(`escalation_matrix[${index}].target_seconds must be non-negative number`, `escalation_matrix[${index}].target_seconds`));
|
|
109
|
+
} else if (!nonEmptyString(entry[field])) blockers.push(blocker(`escalation_matrix[${index}].${field} is required`, `escalation_matrix[${index}].${field}`));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const maintenance = sla.maintenance_window;
|
|
115
|
+
if (!isPlainObject(maintenance)) blockers.push(blocker('maintenance_window object is required', 'maintenance_window'));
|
|
116
|
+
else {
|
|
117
|
+
for (const field of ['cadence', 'window', 'notice_seconds', 'approval_ref']) {
|
|
118
|
+
if (field === 'notice_seconds') {
|
|
119
|
+
if (!nonNegative(maintenance[field])) blockers.push(blocker('maintenance_window.notice_seconds must be non-negative number', 'maintenance_window.notice_seconds'));
|
|
120
|
+
} else if (!nonEmptyString(maintenance[field])) blockers.push(blocker(`maintenance_window.${field} is required`, `maintenance_window.${field}`));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const result = {
|
|
125
|
+
schema: SUPPORT_SLA_RESULT_SCHEMA,
|
|
126
|
+
generated_at: options.generated_at ?? options.generatedAt ?? new Date(0).toISOString(),
|
|
127
|
+
ok: blockers.length === 0,
|
|
128
|
+
status: blockers.length === 0 ? 'accepted' : 'blocked',
|
|
129
|
+
blockers,
|
|
130
|
+
checked: {
|
|
131
|
+
severities: REQUIRED_SEVERITIES.length,
|
|
132
|
+
channels: Array.isArray(channels) ? channels.length : 0,
|
|
133
|
+
escalation_levels: Array.isArray(escalation) ? escalation.length : 0,
|
|
134
|
+
maintenance_window: isPlainObject(maintenance),
|
|
135
|
+
},
|
|
136
|
+
claim_boundary: [
|
|
137
|
+
'Support SLA validation checks declared evidence shape only; it does not staff a support team or page responders.',
|
|
138
|
+
'A pass result is evidence for the named SLA document and does not prove future response performance.',
|
|
139
|
+
'Secrets, raw memory, prompts, transcripts, provider responses, and decrypted content must remain outside SLA artifacts.',
|
|
140
|
+
],
|
|
141
|
+
};
|
|
142
|
+
assertNoSecrets(result, 'result');
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function parseArgs(argv = process.argv.slice(2)) {
|
|
147
|
+
const flags = new Map();
|
|
148
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
149
|
+
const arg = argv[index];
|
|
150
|
+
if (!arg.startsWith('--')) continue;
|
|
151
|
+
const eq = arg.indexOf('=');
|
|
152
|
+
if (eq !== -1) flags.set(arg.slice(2, eq), arg.slice(eq + 1));
|
|
153
|
+
else if (!argv[index + 1] || argv[index + 1].startsWith('--')) flags.set(arg.slice(2), true);
|
|
154
|
+
else {
|
|
155
|
+
flags.set(arg.slice(2), argv[index + 1]);
|
|
156
|
+
index += 1;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return flags;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function getFlag(flags, names) {
|
|
163
|
+
for (const name of names) if (flags.has(name)) return flags.get(name);
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function main() {
|
|
168
|
+
const flags = parseArgs();
|
|
169
|
+
const slaPath = getFlag(flags, ['sla', 'in']);
|
|
170
|
+
if (!nonEmptyString(slaPath)) throw new Error('--sla <path> is required');
|
|
171
|
+
const sla = JSON.parse(await readFile(resolve(String(slaPath)), 'utf8'));
|
|
172
|
+
const result = validateSupportSla(sla, { generated_at: new Date().toISOString() });
|
|
173
|
+
const out = getFlag(flags, ['out']);
|
|
174
|
+
const text = `${JSON.stringify(result, null, 2)}\n`;
|
|
175
|
+
if (out && out !== true) {
|
|
176
|
+
const outPath = resolve(String(out));
|
|
177
|
+
await mkdir(dirname(outPath), { recursive: true });
|
|
178
|
+
await writeFile(outPath, text, 'utf8');
|
|
179
|
+
}
|
|
180
|
+
process.stdout.write(text);
|
|
181
|
+
process.exitCode = result.ok ? 0 : 1;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const invokedPath = process.argv[1] ? resolve(process.argv[1]) : '';
|
|
185
|
+
if (invokedPath === fileURLToPath(import.meta.url)) {
|
|
186
|
+
main().catch((error) => {
|
|
187
|
+
process.stderr.write(`${error.name ?? 'Error'}: ${error.message}\n`);
|
|
188
|
+
process.exitCode = 1;
|
|
189
|
+
});
|
|
190
|
+
}
|