enigma-memory 0.1.7 → 0.1.9

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.
@@ -1296,13 +1296,34 @@ export async function loadRelayStateFromFile(path, options = {}) {
1296
1296
  }
1297
1297
  }
1298
1298
 
1299
+ function retryableRelayRenameError(error) {
1300
+ return error?.code === 'EBUSY' || error?.code === 'EPERM' || error?.code === 'EACCES';
1301
+ }
1302
+
1303
+ async function delayRelayRenameRetry(ms) {
1304
+ await new Promise((resolve) => setTimeout(resolve, ms));
1305
+ }
1306
+
1307
+ async function renameRelayStateAtomically(tempPath, filePath) {
1308
+ const delays = [10, 25, 50, 100, 200];
1309
+ for (let attempt = 0; attempt <= delays.length; attempt += 1) {
1310
+ try {
1311
+ await rename(tempPath, filePath);
1312
+ return;
1313
+ } catch (error) {
1314
+ if (attempt === delays.length || !retryableRelayRenameError(error)) throw error;
1315
+ await delayRelayRenameRetry(delays[attempt]);
1316
+ }
1317
+ }
1318
+ }
1319
+
1299
1320
  export async function saveRelayStateToFile(state, path) {
1300
1321
  const filePath = assertString(path, 'relay state file path');
1301
1322
  const snapshot = serializeRelayState(state);
1302
1323
  const tempPath = join(dirname(filePath), `.${basename(filePath)}.${randomBytes(6).toString('hex')}.tmp`);
1303
1324
  try {
1304
1325
  await writeFile(tempPath, `${JSON.stringify(snapshot, null, 2)}\n`, 'utf8');
1305
- await rename(tempPath, filePath);
1326
+ await renameRelayStateAtomically(tempPath, filePath);
1306
1327
  } catch (error) {
1307
1328
  await unlink(tempPath).catch(() => undefined);
1308
1329
  throw error;
@@ -60,7 +60,7 @@ The example app prints ids, counts, roots, and verification status only. It does
60
60
 
61
61
  ## CLI and CI loop
62
62
 
63
- The CI example installs Node 24, installs the published `enigma-memory@0.1.7` package, runs:
63
+ The CI example installs Node 24, installs the published `enigma-memory@0.1.9` package, runs:
64
64
 
65
65
  ```sh
66
66
  npx --yes --package enigma-memory enigma setup --overwrite
@@ -13,10 +13,13 @@ The source package now has pure contract builders and validators in `packages/ho
13
13
  - usage billing records;
14
14
  - dashboard summaries;
15
15
  - backup drill records;
16
- - incident and SLA reference records.
16
+ - incident and SLA reference records;
17
+ - customer lifecycle packets that aggregate those surfaces for launch-readiness evidence.
17
18
 
18
19
  These functions are contract and validation code only. They do not call an auth provider, billing provider, cloud deployment, KMS, backup target, support desk, status page, SIEM, or model provider. They are safe to import as package code because they do not start servers, read user files, mutate deployment state, publish packages, or contact external accounts.
19
20
 
21
+ The lifecycle packet public API is `buildCustomerLifecyclePacket(input)` and `validateCustomerLifecyclePacket(packet)` under `enigma-memory/hosted-cloud`; the script command below emits the same schema for release evidence.
22
+
20
23
  The validators enforce hosted-cloud boundaries:
21
24
 
22
25
  - contract artifacts must include `operator_evidence_refs` for auth provider, billing provider, legal docs, data processing terms, support ownership, and external security review;
@@ -27,7 +30,7 @@ The validators enforce hosted-cloud boundaries:
27
30
  - hosted vault contracts are opaque-record and plaintext-minimized contracts only;
28
31
  - billing records remain contract records until an external billing provider invoice flow is wired.
29
32
 
30
- Every builder emits `readiness.contract_ready: true` and `readiness.integration_kind: "contract_validator_only"`. It also emits `readiness.hosted_cloud_sellable: false` because contract readiness is not provider wiring, legal approval, security review, or operator go-live approval.
33
+ Individual surface builders emit `readiness.contract_ready: true`, `readiness.integration_kind: "contract_validator_only"`, and `readiness.hosted_cloud_sellable: false` because contract readiness is not provider wiring, legal approval, security review, or operator go-live approval. A customer lifecycle packet may only mark `hosted_cloud_sellable: true` when every lifecycle surface has a provided evidence ref and an explicit operator go-live approval ref is supplied; the packet remains evidence validation, not live hosted SaaS or provider wiring.
31
34
 
32
35
  ## Externally blocked before hosted cloud can be sold
33
36
 
@@ -44,6 +47,12 @@ Hosted cloud remains blocked until an operator wires and records evidence for al
44
47
 
45
48
  A `provided` operator evidence ref means the contract can point to external evidence. It still does not by itself make hosted cloud sellable; an operator must complete the release checklist and issue go-live approval. A `blocked_external_dependency` ref is an explicit blocker, not fake evidence.
46
49
 
50
+ ## Customer lifecycle packet
51
+
52
+ `npm run production:hosted-customer -- --tenant <id> --domain <domain> --environment <env> --out <file>` builds `enigma.hosted_cloud.customer_lifecycle_packet.v1` readiness evidence for a tenant launch packet. Operators may pass repeatable `--evidence-ref <key=status:ref>` values and `--operator-go-live-ref <ref>` when real external evidence exists. The command writes public-safe validation evidence only: it creates no hosted account, tenant, vault, API key, invoice, support ticket, backup, Cloudflare resource, provider resource, secret, or deployment.
53
+
54
+ The lifecycle packet records lifecycle phases, required surfaces, external blockers, missing evidence refs, no-secret/no-plaintext guarantees, and the sellability gate. Missing surfaces, blocked evidence refs, or absent operator go-live approval keep `hosted_cloud_sellable:false`.
55
+
47
56
  ## Non-claims
48
57
 
49
58
  Hosted cloud collateral must not say or imply:
package/docs/sdk-api.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # SDK and API guide
2
2
 
3
- This guide covers the public package imports for `enigma-memory@0.1.7`. The SDK runs locally by default: vaults, passports, context packs, receipts, relay/gateway demo state, storage contracts, metering artifacts, and settlement artifacts are package-level developer surfaces. They are not evidence of hosted Enigma cloud, provider-side deletion, provider model forgetting, token ROI, invoice savings, compliance certification, or benchmark leadership.
3
+ This guide covers the public package imports for `enigma-memory@0.1.9`. The SDK runs locally by default: vaults, passports, context packs, receipts, relay/gateway demo state, storage contracts, metering artifacts, and settlement artifacts are package-level developer surfaces. They are not evidence of hosted Enigma cloud, provider-side deletion, provider model forgetting, token ROI, invoice savings, compliance certification, or benchmark leadership.
4
4
 
5
5
  ## Install and import style
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "enigma-memory",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "type": "module",
5
5
  "description": "Provider-agnostic AI memory passport and offline-verifiable proof layer.",
6
6
  "license": "Apache-2.0",
@@ -84,6 +84,7 @@
84
84
  "scripts/monitor-live-endpoints.mjs",
85
85
  "scripts/collect-hosted-backend-live-evidence.mjs",
86
86
  "scripts/build-hosted-probe-worker.mjs",
87
+ "scripts/build-hosted-customer-lifecycle.mjs",
87
88
  "scripts/build-edge-backend-workers.mjs",
88
89
  "scripts/validate-cloudflare-worker-inspect.mjs",
89
90
  "scripts/validate-production-manifests.mjs",
@@ -175,6 +176,7 @@
175
176
  "production:edge-deploy": "node scripts/deploy-edge-backend-workers.mjs",
176
177
  "production:edge-live": "node scripts/collect-edge-backend-bootstrap-evidence.mjs",
177
178
  "production:ref-draft": "node scripts/build-hosted-ref-draft.mjs",
179
+ "production:hosted-customer": "node scripts/build-hosted-customer-lifecycle.mjs",
178
180
  "production:storage-bootstrap": "node scripts/cloudflare-storage-bootstrap.mjs",
179
181
  "production:worker-inspect": "node scripts/validate-cloudflare-worker-inspect.mjs",
180
182
  "production:cloudflare-credentials": "node scripts/validate-cloudflare-credentials.mjs",
@@ -8,6 +8,7 @@ export const HOSTED_CLOUD_USAGE_BILLING_SCHEMA = 'enigma.hosted_cloud.usage_bill
8
8
  export const HOSTED_CLOUD_DASHBOARD_SCHEMA = 'enigma.hosted_cloud.dashboard_summary.v1';
9
9
  export const HOSTED_CLOUD_BACKUP_DRILL_SCHEMA = 'enigma.hosted_cloud.backup_drill.v1';
10
10
  export const HOSTED_CLOUD_INCIDENT_SLA_SCHEMA = 'enigma.hosted_cloud.incident_sla_refs.v1';
11
+ export const HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PACKET_SCHEMA = 'enigma.hosted_cloud.customer_lifecycle_packet.v1';
11
12
 
12
13
  export const HOSTED_CLOUD_EXTERNAL_BLOCKERS = Object.freeze([
13
14
  'auth_provider',
@@ -35,6 +36,48 @@ const BLOCKER_LABELS = Object.freeze({
35
36
  support_ownership: 'Support ownership is not assigned.',
36
37
  external_security_review: 'External security review is not complete.',
37
38
  });
39
+
40
+ export const HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PHASES = Object.freeze([
41
+ 'account',
42
+ 'tenant',
43
+ 'vault',
44
+ 'api_key',
45
+ 'billing',
46
+ 'dashboard',
47
+ 'backup',
48
+ 'incident_sla',
49
+ 'support',
50
+ 'monitoring',
51
+ 'legal',
52
+ 'security_review',
53
+ 'operator_go_live',
54
+ ]);
55
+ const CUSTOMER_LIFECYCLE_CONTRACT_PHASES = Object.freeze([
56
+ 'account',
57
+ 'tenant',
58
+ 'vault',
59
+ 'api_key',
60
+ 'billing',
61
+ 'dashboard',
62
+ 'backup',
63
+ 'incident_sla',
64
+ ]);
65
+ const CUSTOMER_LIFECYCLE_REQUIRED_EVIDENCE_PHASES = Object.freeze(HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PHASES.filter((phase) => phase !== 'operator_go_live'));
66
+ const CUSTOMER_LIFECYCLE_BLOCKER_LABELS = Object.freeze({
67
+ account: 'Hosted account contract evidence is not provided.',
68
+ tenant: 'Hosted tenant contract evidence is not provided.',
69
+ vault: 'Hosted vault contract evidence is not provided.',
70
+ api_key: 'Hosted API key metadata evidence is not provided.',
71
+ billing: 'Hosted billing evidence is not provided.',
72
+ dashboard: 'Hosted dashboard evidence is not provided.',
73
+ backup: 'Hosted backup drill evidence is not provided.',
74
+ incident_sla: 'Hosted incident/SLA evidence is not provided.',
75
+ support: 'Hosted support ownership evidence is not provided.',
76
+ monitoring: 'Hosted monitoring evidence is not provided.',
77
+ legal: 'Hosted legal approval evidence is not provided.',
78
+ security_review: 'Hosted security review evidence is not provided.',
79
+ operator_go_live: 'Explicit operator go-live approval is not provided.',
80
+ });
38
81
  const FORBIDDEN_KEY_RE = /(?:^|_)(?:raw_?memory|plaintext|plain_text|prompt|prompts|completion|completions|message_body|transcript|conversation|provider_?response|response_?body|credential|credentials|secret|password|private_?key|bearer|access_token|refresh_token|token_value|api_key_value|api_secret|token_?roi|token_?profit|roi_claim|profit_claim|provider_?deletion|provider_?erasure|model_?forgetting|model_?erasure)(?:$|_)/iu;
39
82
  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/@]+@|sk-[A-Za-z0-9_-]{16,}|AKIA[0-9A-Z]{16}|raw memory|private prompt|provider response|full transcript|decrypted memory)/iu;
40
83
  const FORBIDDEN_CLAIM_RE = /(?:token\s+(?:roi|profit|return|investment|price)|(?:roi|profit|return)\s+(?:from|on)\s+token|guaranteed\s+(?:savings|profit|return)|provider(?:-side|\s+side)?\s+(?:deletion|erasure)|model\s+(?:forgetting|forgot|erasure)|makes?\s+models?\s+forget|deleted\s+from\s+every\s+provider)/iu;
@@ -536,3 +579,406 @@ export function validateIncidentSlaRefs(contract) {
536
579
  requiredTrue(contract.incident_boundary.external_review_required, 'incident_boundary.external_review_required');
537
580
  return true;
538
581
  }
582
+
583
+ function lifecycleContractIdKey(phase) {
584
+ switch (phase) {
585
+ case 'account': return 'account_id';
586
+ case 'tenant': return 'tenant_id';
587
+ case 'vault': return 'vault_id';
588
+ case 'api_key': return 'api_key_id';
589
+ case 'billing': return 'billing_record_id';
590
+ case 'dashboard': return 'dashboard_id';
591
+ case 'backup': return 'backup_drill_id';
592
+ case 'incident_sla': return 'incident_sla_id';
593
+ default: throw new TypeError(`unknown lifecycle phase: ${phase}`);
594
+ }
595
+ }
596
+
597
+ function lifecycleContractSource(input, aliases) {
598
+ if (isPlainObject(input.contracts)) {
599
+ for (const alias of aliases) {
600
+ if (input.contracts[alias] !== undefined) return input.contracts[alias];
601
+ }
602
+ }
603
+ for (const alias of aliases) {
604
+ if (input[alias] !== undefined) return input[alias];
605
+ }
606
+ return undefined;
607
+ }
608
+
609
+ function buildOrValidateLifecycleContract(phase, source) {
610
+ if (source === undefined || source === null) return null;
611
+ if (!isPlainObject(source)) throw new TypeError(`contracts.${phase} must be an object`);
612
+ switch (phase) {
613
+ case 'account':
614
+ if (source.schema === HOSTED_CLOUD_USER_ACCOUNT_SCHEMA) {
615
+ validateUserAccountContract(source);
616
+ return source;
617
+ }
618
+ return buildUserAccountContract(source);
619
+ case 'tenant':
620
+ if (source.schema === HOSTED_CLOUD_TENANT_SCHEMA) {
621
+ validateTenantContract(source);
622
+ return source;
623
+ }
624
+ return buildTenantContract(source);
625
+ case 'vault':
626
+ if (source.schema === HOSTED_CLOUD_VAULT_SCHEMA) {
627
+ validateHostedVaultContract(source);
628
+ return source;
629
+ }
630
+ return buildHostedVaultContract(source);
631
+ case 'api_key':
632
+ if (source.schema === HOSTED_CLOUD_API_KEY_SCHEMA) {
633
+ validateApiKeyContract(source);
634
+ return source;
635
+ }
636
+ return buildApiKeyContract(source);
637
+ case 'billing':
638
+ if (source.schema === HOSTED_CLOUD_USAGE_BILLING_SCHEMA) {
639
+ validateUsageBillingRecord(source);
640
+ return source;
641
+ }
642
+ return buildUsageBillingRecord(source);
643
+ case 'dashboard':
644
+ if (source.schema === HOSTED_CLOUD_DASHBOARD_SCHEMA) {
645
+ validateDashboardSummary(source);
646
+ return source;
647
+ }
648
+ return buildDashboardSummary(source);
649
+ case 'backup':
650
+ if (source.schema === HOSTED_CLOUD_BACKUP_DRILL_SCHEMA) {
651
+ validateBackupDrillContract(source);
652
+ return source;
653
+ }
654
+ return buildBackupDrillContract(source);
655
+ case 'incident_sla':
656
+ if (source.schema === HOSTED_CLOUD_INCIDENT_SLA_SCHEMA) {
657
+ validateIncidentSlaRefs(source);
658
+ return source;
659
+ }
660
+ return buildIncidentSlaRefs(source);
661
+ default:
662
+ throw new TypeError(`unknown lifecycle phase: ${phase}`);
663
+ }
664
+ }
665
+
666
+ function lifecycleContractsFrom(input) {
667
+ return {
668
+ account: buildOrValidateLifecycleContract('account', lifecycleContractSource(input, ['account', 'user_account', 'userAccount', 'account_contract', 'accountContract', 'user_account_contract', 'userAccountContract'])),
669
+ tenant: buildOrValidateLifecycleContract('tenant', lifecycleContractSource(input, ['tenant', 'tenant_contract', 'tenantContract'])),
670
+ vault: buildOrValidateLifecycleContract('vault', lifecycleContractSource(input, ['vault', 'hosted_vault', 'hostedVault', 'vault_contract', 'vaultContract', 'hosted_vault_contract', 'hostedVaultContract'])),
671
+ api_key: buildOrValidateLifecycleContract('api_key', lifecycleContractSource(input, ['api_key', 'apiKey', 'api_key_contract', 'apiKeyContract'])),
672
+ billing: buildOrValidateLifecycleContract('billing', lifecycleContractSource(input, ['billing', 'usage_billing_record', 'usageBillingRecord', 'billing_record', 'billingRecord', 'billing_contract', 'billingContract'])),
673
+ dashboard: buildOrValidateLifecycleContract('dashboard', lifecycleContractSource(input, ['dashboard', 'dashboard_summary', 'dashboardSummary', 'dashboard_contract', 'dashboardContract'])),
674
+ backup: buildOrValidateLifecycleContract('backup', lifecycleContractSource(input, ['backup', 'backup_drill', 'backupDrill', 'backup_contract', 'backupContract', 'backup_drill_contract', 'backupDrillContract'])),
675
+ incident_sla: buildOrValidateLifecycleContract('incident_sla', lifecycleContractSource(input, ['incident_sla', 'incidentSla', 'incident_sla_refs', 'incidentSlaRefs', 'incident_sla_contract', 'incidentSlaContract'])),
676
+ };
677
+ }
678
+
679
+ function explicitLifecycleEvidenceRefs(input) {
680
+ const refs = input.required_evidence_refs ?? input.requiredEvidenceRefs ?? input.lifecycle_evidence_refs ?? input.lifecycleEvidenceRefs ?? input.evidence_refs ?? input.evidenceRefs ?? input.operator_evidence_refs ?? input.operatorEvidenceRefs;
681
+ if (refs === undefined || refs === null) return {};
682
+ if (!isPlainObject(refs)) throw new TypeError('required_evidence_refs must be an object');
683
+ return refs;
684
+ }
685
+
686
+ function requirePublicSafeLifecycleRef(value, name) {
687
+ const ref = requiredString(value, name);
688
+ assertNoForbiddenPayload(ref, name);
689
+ if (ref.startsWith('blocked:')) throw new TypeError(`${name} must be a public-safe evidence ref, not a blocker ref`);
690
+ return ref;
691
+ }
692
+
693
+ function lifecycleEvidenceRefFor(key, value) {
694
+ const fallback = { ref: `blocked:${key}`, status: BLOCKED, blocker: CUSTOMER_LIFECYCLE_BLOCKER_LABELS[key] };
695
+ if (value === undefined || value === null) return fallback;
696
+ if (typeof value === 'string') return { ref: requirePublicSafeLifecycleRef(value, `required_evidence_refs.${key}`), status: PROVIDED };
697
+ if (!isPlainObject(value)) throw new TypeError(`required_evidence_refs.${key} must be a string or object`);
698
+ const status = stringOrDefault(value.status, PROVIDED);
699
+ if (!EVIDENCE_STATUSES.has(status)) throw new TypeError(`required_evidence_refs.${key}.status is invalid`);
700
+ const ref = status === PROVIDED
701
+ ? requirePublicSafeLifecycleRef(value.ref, `required_evidence_refs.${key}.ref`)
702
+ : requiredString(value.ref, `required_evidence_refs.${key}.ref`);
703
+ assertNoForbiddenPayload(ref, `required_evidence_refs.${key}.ref`);
704
+ const evidence = { ref, status };
705
+ const owner = optionalString(value.owner, `required_evidence_refs.${key}.owner`);
706
+ const blocker = optionalString(value.blocker, `required_evidence_refs.${key}.blocker`);
707
+ if (owner) evidence.owner = owner;
708
+ if (status === BLOCKED) evidence.blocker = blocker ?? CUSTOMER_LIFECYCLE_BLOCKER_LABELS[key];
709
+ return evidence;
710
+ }
711
+
712
+ function contractEvidenceRef(phase, contract) {
713
+ if (!contract) return lifecycleEvidenceRefFor(phase, undefined);
714
+ const idKey = lifecycleContractIdKey(phase);
715
+ return {
716
+ ref: requiredString(contract.contract_hash, `contracts.${phase}.contract_hash`),
717
+ status: PROVIDED,
718
+ contract_schema: requiredString(contract.schema, `contracts.${phase}.schema`),
719
+ contract_id: requiredString(contract[idKey], `contracts.${phase}.${idKey}`),
720
+ };
721
+ }
722
+
723
+ function lifecycleEvidenceRefsFrom(input, contracts) {
724
+ const explicitRefs = explicitLifecycleEvidenceRefs(input);
725
+ const evidenceRefs = {};
726
+ for (const phase of CUSTOMER_LIFECYCLE_REQUIRED_EVIDENCE_PHASES) {
727
+ evidenceRefs[phase] = explicitRefs[phase] === undefined && CUSTOMER_LIFECYCLE_CONTRACT_PHASES.includes(phase)
728
+ ? contractEvidenceRef(phase, contracts[phase])
729
+ : lifecycleEvidenceRefFor(phase, explicitRefs[phase]);
730
+ }
731
+ return evidenceRefs;
732
+ }
733
+
734
+ function operatorGoLiveRefFromInput(input) {
735
+ const value = input.operator_go_live_ref ?? input.operatorGoLiveRef;
736
+ if (value === undefined || value === null) return null;
737
+ return requirePublicSafeLifecycleRef(value, 'operator_go_live_ref');
738
+ }
739
+
740
+ function operatorGoLiveRefFromPacket(packet) {
741
+ if (!Object.prototype.hasOwnProperty.call(packet, 'operator_go_live_ref')) throw new TypeError('operator_go_live_ref must be present');
742
+ if (packet.operator_go_live_ref === null) return null;
743
+ return requirePublicSafeLifecycleRef(packet.operator_go_live_ref, 'operator_go_live_ref');
744
+ }
745
+
746
+ function missingLifecycleEvidenceRefs(requiredEvidenceRefs) {
747
+ return CUSTOMER_LIFECYCLE_REQUIRED_EVIDENCE_PHASES
748
+ .filter((phase) => requiredEvidenceRefs[phase].status !== PROVIDED)
749
+ .map((phase) => ({
750
+ key: phase,
751
+ ref: requiredEvidenceRefs[phase].ref,
752
+ blocker: requiredEvidenceRefs[phase].blocker ?? CUSTOMER_LIFECYCLE_BLOCKER_LABELS[phase],
753
+ }));
754
+ }
755
+
756
+ function lifecycleContractExternalBlockers(contracts) {
757
+ if (!isPlainObject(contracts)) return [];
758
+ const blockers = [];
759
+ for (const phase of CUSTOMER_LIFECYCLE_CONTRACT_PHASES) {
760
+ const contract = contracts[phase];
761
+ if (!isPlainObject(contract) || !Array.isArray(contract.readiness?.external_blockers)) continue;
762
+ for (const blocker of contract.readiness.external_blockers) {
763
+ blockers.push({
764
+ key: `${phase}.${requiredString(blocker.key, `contracts.${phase}.readiness.external_blockers.key`)}`,
765
+ ref: requiredString(blocker.ref, `contracts.${phase}.readiness.external_blockers.ref`),
766
+ blocker: stringOrDefault(blocker.blocker, CUSTOMER_LIFECYCLE_BLOCKER_LABELS[phase]),
767
+ });
768
+ }
769
+ }
770
+ return blockers;
771
+ }
772
+
773
+ function customerLifecycleExternalBlockers(requiredEvidenceRefs, operatorGoLiveRef, contracts = null) {
774
+ const blockers = missingLifecycleEvidenceRefs(requiredEvidenceRefs).concat(lifecycleContractExternalBlockers(contracts));
775
+ if (operatorGoLiveRef === null) {
776
+ blockers.push({
777
+ key: 'operator_go_live',
778
+ ref: 'blocked:operator_go_live',
779
+ blocker: CUSTOMER_LIFECYCLE_BLOCKER_LABELS.operator_go_live,
780
+ });
781
+ }
782
+ return blockers;
783
+ }
784
+
785
+ function missingLifecycleSurfaceRefs(contracts) {
786
+ return CUSTOMER_LIFECYCLE_CONTRACT_PHASES
787
+ .filter((phase) => contracts[phase] === null)
788
+ .map((phase) => ({
789
+ key: phase,
790
+ ref: `blocked:${phase}`,
791
+ blocker: CUSTOMER_LIFECYCLE_BLOCKER_LABELS[phase],
792
+ }));
793
+ }
794
+
795
+ function customerLifecycleReadiness(requiredEvidenceRefs, operatorGoLiveRef, contracts = null) {
796
+ const missingEvidenceRefs = missingLifecycleEvidenceRefs(requiredEvidenceRefs);
797
+ const contractBlockers = lifecycleContractExternalBlockers(contracts);
798
+ const externalLifecycleBlockers = customerLifecycleExternalBlockers(requiredEvidenceRefs, operatorGoLiveRef, contracts);
799
+ const sellable = missingEvidenceRefs.length === 0 && contractBlockers.length === 0 && operatorGoLiveRef !== null;
800
+ return {
801
+ ...HOSTED_CLOUD_CONTRACT_READY,
802
+ status: sellable ? 'operator_approved_evidence_packet' : 'blocked_lifecycle_evidence_or_operator_go_live',
803
+ evidence_validation_only: true,
804
+ lifecycle_evidence_complete: missingEvidenceRefs.length === 0,
805
+ operator_go_live_approved: operatorGoLiveRef !== null,
806
+ external_wiring_ready: externalLifecycleBlockers.length === 0,
807
+ hosted_cloud_sellable: sellable,
808
+ selling_gate: sellable ? 'evidence_complete_operator_go_live_approved' : 'blocked_until_lifecycle_evidence_and_operator_go_live',
809
+ external_blockers: externalLifecycleBlockers,
810
+ missing_evidence_refs: missingEvidenceRefs,
811
+ };
812
+ }
813
+
814
+ function lifecyclePhaseRows(requiredEvidenceRefs, contracts, operatorGoLiveRef) {
815
+ return HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PHASES.map((phase) => {
816
+ if (phase === 'operator_go_live') {
817
+ return {
818
+ phase,
819
+ evidence_ref: operatorGoLiveRef ?? 'blocked:operator_go_live',
820
+ evidence_status: operatorGoLiveRef === null ? BLOCKED : PROVIDED,
821
+ ready: operatorGoLiveRef !== null,
822
+ blocker: operatorGoLiveRef === null ? CUSTOMER_LIFECYCLE_BLOCKER_LABELS.operator_go_live : undefined,
823
+ };
824
+ }
825
+ const evidence = requiredEvidenceRefs[phase];
826
+ const row = {
827
+ phase,
828
+ evidence_ref: evidence.ref,
829
+ evidence_status: evidence.status,
830
+ ready: evidence.status === PROVIDED,
831
+ };
832
+ if (evidence.status === BLOCKED) row.blocker = evidence.blocker ?? CUSTOMER_LIFECYCLE_BLOCKER_LABELS[phase];
833
+ if (CUSTOMER_LIFECYCLE_CONTRACT_PHASES.includes(phase) && contracts[phase]) {
834
+ row.contract_schema = contracts[phase].schema;
835
+ row.contract_id = contracts[phase][lifecycleContractIdKey(phase)];
836
+ row.contract_hash = contracts[phase].contract_hash;
837
+ }
838
+ return row;
839
+ });
840
+ }
841
+
842
+ function customerLifecycleSafetyGuarantees() {
843
+ return {
844
+ opaque_reference_only: true,
845
+ customer_content_absent: true,
846
+ sensitive_text_absent: true,
847
+ auth_material_absent: true,
848
+ provider_payloads_absent: true,
849
+ financial_outcome_claim_absent: true,
850
+ remote_erasure_claim_absent: true,
851
+ };
852
+ }
853
+
854
+ function validateCustomerLifecycleSafetyGuarantees(guarantees) {
855
+ if (!isPlainObject(guarantees)) throw new TypeError('public_safety_guarantees must be present');
856
+ requiredTrue(guarantees.opaque_reference_only, 'public_safety_guarantees.opaque_reference_only');
857
+ requiredTrue(guarantees.customer_content_absent, 'public_safety_guarantees.customer_content_absent');
858
+ requiredTrue(guarantees.sensitive_text_absent, 'public_safety_guarantees.sensitive_text_absent');
859
+ requiredTrue(guarantees.auth_material_absent, 'public_safety_guarantees.auth_material_absent');
860
+ requiredTrue(guarantees.provider_payloads_absent, 'public_safety_guarantees.provider_payloads_absent');
861
+ requiredTrue(guarantees.financial_outcome_claim_absent, 'public_safety_guarantees.financial_outcome_claim_absent');
862
+ requiredTrue(guarantees.remote_erasure_claim_absent, 'public_safety_guarantees.remote_erasure_claim_absent');
863
+ }
864
+
865
+ function assertSameLifecycleArray(actual, expected, name) {
866
+ if (!Array.isArray(actual)) throw new TypeError(`${name} must be an array`);
867
+ if (JSON.stringify(canonicalize(actual)) !== JSON.stringify(canonicalize(expected))) throw new TypeError(`${name} must match required_evidence_refs and operator_go_live_ref`);
868
+ }
869
+
870
+ function validateCustomerLifecycleEvidenceRefs(requiredEvidenceRefs) {
871
+ if (!isPlainObject(requiredEvidenceRefs)) throw new TypeError('required_evidence_refs must be present');
872
+ for (const phase of CUSTOMER_LIFECYCLE_REQUIRED_EVIDENCE_PHASES) {
873
+ if (!Object.prototype.hasOwnProperty.call(requiredEvidenceRefs, phase)) throw new TypeError(`required_evidence_refs.${phase} must be present`);
874
+ const evidence = lifecycleEvidenceRefFor(phase, requiredEvidenceRefs[phase]);
875
+ requiredEvidenceRefs[phase] = evidence;
876
+ }
877
+ return requiredEvidenceRefs;
878
+ }
879
+
880
+ function validateCustomerLifecycleContracts(contracts) {
881
+ if (!isPlainObject(contracts)) throw new TypeError('contracts must be present');
882
+ for (const phase of CUSTOMER_LIFECYCLE_CONTRACT_PHASES) {
883
+ if (!Object.prototype.hasOwnProperty.call(contracts, phase)) throw new TypeError(`contracts.${phase} must be present`);
884
+ const contract = contracts[phase];
885
+ if (contract === null) continue;
886
+ if (!isPlainObject(contract)) throw new TypeError(`contracts.${phase} must be an object or null`);
887
+ switch (phase) {
888
+ case 'account':
889
+ validateUserAccountContract(contract);
890
+ break;
891
+ case 'tenant':
892
+ validateTenantContract(contract);
893
+ break;
894
+ case 'vault':
895
+ validateHostedVaultContract(contract);
896
+ break;
897
+ case 'api_key':
898
+ validateApiKeyContract(contract);
899
+ break;
900
+ case 'billing':
901
+ validateUsageBillingRecord(contract);
902
+ break;
903
+ case 'dashboard':
904
+ validateDashboardSummary(contract);
905
+ break;
906
+ case 'backup':
907
+ validateBackupDrillContract(contract);
908
+ break;
909
+ case 'incident_sla':
910
+ validateIncidentSlaRefs(contract);
911
+ break;
912
+ default:
913
+ throw new TypeError(`unknown lifecycle phase: ${phase}`);
914
+ }
915
+ }
916
+ }
917
+
918
+ function validateCustomerLifecycleReadiness(readiness, expected) {
919
+ if (!isPlainObject(readiness)) throw new TypeError('readiness must be present');
920
+ requiredTrue(readiness.contract_ready, 'readiness.contract_ready');
921
+ if (readiness.integration_kind !== HOSTED_CLOUD_CONTRACT_READY.integration_kind) throw new TypeError('readiness.integration_kind must remain contract_validator_only');
922
+ requiredTrue(readiness.no_external_provider_calls, 'readiness.no_external_provider_calls');
923
+ requiredTrue(readiness.evidence_validation_only, 'readiness.evidence_validation_only');
924
+ if (readiness.status !== expected.status) throw new TypeError('readiness.status must match lifecycle evidence and operator go-live approval');
925
+ if (readiness.lifecycle_evidence_complete !== expected.lifecycle_evidence_complete) throw new TypeError('readiness.lifecycle_evidence_complete must match required_evidence_refs');
926
+ if (readiness.operator_go_live_approved !== expected.operator_go_live_approved) throw new TypeError('readiness.operator_go_live_approved must match operator_go_live_ref');
927
+ if (readiness.external_wiring_ready !== expected.external_wiring_ready) throw new TypeError('readiness.external_wiring_ready must match blockers');
928
+ if (readiness.hosted_cloud_sellable !== expected.hosted_cloud_sellable) throw new TypeError('readiness.hosted_cloud_sellable must match lifecycle evidence and operator go-live approval');
929
+ if (readiness.selling_gate !== expected.selling_gate) throw new TypeError('readiness.selling_gate must match lifecycle evidence and operator go-live approval');
930
+ assertSameLifecycleArray(readiness.external_blockers, expected.external_blockers, 'readiness.external_blockers');
931
+ assertSameLifecycleArray(readiness.missing_evidence_refs, expected.missing_evidence_refs, 'readiness.missing_evidence_refs');
932
+ }
933
+
934
+ export function buildCustomerLifecyclePacket(input = {}) {
935
+ if (!isPlainObject(input)) throw new TypeError('buildCustomerLifecyclePacket requires an options object');
936
+ assertNoForbiddenPayload(input, 'input');
937
+ const contracts = lifecycleContractsFrom(input);
938
+ const requiredEvidenceRefs = lifecycleEvidenceRefsFrom(input, contracts);
939
+ const operatorGoLiveRef = operatorGoLiveRefFromInput(input);
940
+ const missingSurfaceRefs = missingLifecycleSurfaceRefs(contracts);
941
+ const readiness = customerLifecycleReadiness(requiredEvidenceRefs, operatorGoLiveRef, contracts);
942
+ const body = {
943
+ schema: HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PACKET_SCHEMA,
944
+ packet_id: stringOrDefault(input.packet_id ?? input.packetId, undefined),
945
+ generated_at: isoTimestamp(input.generated_at ?? input.generatedAt, 'generated_at'),
946
+ contracts,
947
+ required_evidence_refs: requiredEvidenceRefs,
948
+ lifecycle_phases: lifecyclePhaseRows(requiredEvidenceRefs, contracts, operatorGoLiveRef),
949
+ missing_surface_refs: missingSurfaceRefs,
950
+ external_blockers: readiness.external_blockers,
951
+ missing_evidence_refs: readiness.missing_evidence_refs,
952
+ operator_go_live_ref: operatorGoLiveRef,
953
+ readiness,
954
+ hosted_cloud_sellable: readiness.hosted_cloud_sellable,
955
+ guarantees: customerLifecycleSafetyGuarantees(),
956
+ public_safety_guarantees: customerLifecycleSafetyGuarantees(),
957
+ };
958
+ const packet = withContractIdentity(body, 'hcclp', 'packet_id');
959
+ validateCustomerLifecyclePacket(packet);
960
+ return packet;
961
+ }
962
+
963
+ export function validateCustomerLifecyclePacket(packet) {
964
+ if (!isPlainObject(packet)) throw new TypeError('packet must be an object');
965
+ assertNoForbiddenPayload(packet, 'packet');
966
+ if (packet.schema !== HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PACKET_SCHEMA) throw new TypeError(`schema must be ${HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PACKET_SCHEMA}`);
967
+ requiredString(packet.packet_id, 'packet_id');
968
+ isoTimestamp(packet.generated_at, 'generated_at');
969
+ requiredString(packet.contract_hash, 'contract_hash');
970
+ validateCustomerLifecycleContracts(packet.contracts);
971
+ if (!isPlainObject(packet.required_evidence_refs)) throw new TypeError('required_evidence_refs must be present');
972
+ const requiredEvidenceRefs = validateCustomerLifecycleEvidenceRefs({ ...packet.required_evidence_refs });
973
+ const operatorGoLiveRef = operatorGoLiveRefFromPacket(packet);
974
+ const expectedReadiness = customerLifecycleReadiness(requiredEvidenceRefs, operatorGoLiveRef, packet.contracts);
975
+ const expectedPhases = lifecyclePhaseRows(requiredEvidenceRefs, packet.contracts, operatorGoLiveRef);
976
+ assertSameLifecycleArray(packet.lifecycle_phases, expectedPhases, 'lifecycle_phases');
977
+ assertSameLifecycleArray(packet.external_blockers, expectedReadiness.external_blockers, 'external_blockers');
978
+ assertSameLifecycleArray(packet.missing_surface_refs, missingLifecycleSurfaceRefs(packet.contracts), 'missing_surface_refs');
979
+ assertSameLifecycleArray(packet.missing_evidence_refs, expectedReadiness.missing_evidence_refs, 'missing_evidence_refs');
980
+ validateCustomerLifecycleReadiness(packet.readiness, expectedReadiness);
981
+ if (packet.hosted_cloud_sellable !== expectedReadiness.hosted_cloud_sellable) throw new TypeError('hosted_cloud_sellable must match readiness.hosted_cloud_sellable');
982
+ validateCustomerLifecycleSafetyGuarantees(packet.guarantees ?? packet.public_safety_guarantees);
983
+ return true;
984
+ }
@@ -17,7 +17,7 @@ import {
17
17
  const DEFAULT_BUNDLE = '.enigma/bundle.json';
18
18
  const JSONRPC_VERSION = '2.0';
19
19
  const MCP_PROTOCOL_VERSION = '2024-11-05';
20
- const SERVER_INFO = Object.freeze({ name: 'enigma-mcp-server', version: '0.1.7' });
20
+ const SERVER_INFO = Object.freeze({ name: 'enigma-mcp-server', version: '0.1.9' });
21
21
  const JSON_RPC_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
22
22
  const JSON_RPC_ERROR = Object.freeze({
23
23
  INVALID_REQUEST: -32600,
@@ -0,0 +1,493 @@
1
+ #!/usr/bin/env node
2
+ import { writeFile } from 'node:fs/promises';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { resolve } from 'node:path';
5
+
6
+ import {
7
+ HOSTED_CLOUD_API_KEY_SCHEMA,
8
+ HOSTED_CLOUD_BACKUP_DRILL_SCHEMA,
9
+ HOSTED_CLOUD_DASHBOARD_SCHEMA,
10
+ HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PACKET_SCHEMA,
11
+ HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PHASES,
12
+ HOSTED_CLOUD_EXTERNAL_BLOCKERS,
13
+ HOSTED_CLOUD_INCIDENT_SLA_SCHEMA,
14
+ HOSTED_CLOUD_TENANT_SCHEMA,
15
+ HOSTED_CLOUD_USAGE_BILLING_SCHEMA,
16
+ HOSTED_CLOUD_USER_ACCOUNT_SCHEMA,
17
+ HOSTED_CLOUD_VAULT_SCHEMA,
18
+ buildApiKeyContract,
19
+ buildBackupDrillContract,
20
+ buildDashboardSummary,
21
+ buildHostedVaultContract,
22
+ buildIncidentSlaRefs,
23
+ buildTenantContract,
24
+ buildUsageBillingRecord,
25
+ buildUserAccountContract,
26
+ buildCustomerLifecyclePacket,
27
+ validateCustomerLifecyclePacket,
28
+ } from '../packages/hosted-cloud/src/index.js';
29
+
30
+ export const HOSTED_CUSTOMER_LIFECYCLE_PACKET_SCHEMA = HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PACKET_SCHEMA;
31
+ export const HOSTED_CUSTOMER_LIFECYCLE_RELEASE_TARGET = '0.1.9';
32
+
33
+ const PROVIDED = 'provided';
34
+ const BLOCKED_MISSING = 'blocked_missing_evidence';
35
+ const BLOCKED_EXTERNAL = 'blocked_external_dependency';
36
+ const ALLOWED_STATUSES = new Set([PROVIDED, BLOCKED_MISSING, BLOCKED_EXTERNAL]);
37
+ const STATUS_ALIASES = Object.freeze({
38
+ blocked: BLOCKED_MISSING,
39
+ missing: BLOCKED_MISSING,
40
+ external: BLOCKED_EXTERNAL,
41
+ });
42
+
43
+ const CONTRACT_EVIDENCE = Object.freeze([
44
+ {
45
+ key: 'user_account',
46
+ label: 'Hosted user account contract',
47
+ schema: HOSTED_CLOUD_USER_ACCOUNT_SCHEMA,
48
+ contractKey: 'user_account',
49
+ idField: 'account_id',
50
+ },
51
+ {
52
+ key: 'tenant',
53
+ label: 'Hosted tenant contract',
54
+ schema: HOSTED_CLOUD_TENANT_SCHEMA,
55
+ contractKey: 'tenant',
56
+ idField: 'tenant_id',
57
+ },
58
+ {
59
+ key: 'hosted_vault',
60
+ label: 'Hosted vault contract',
61
+ schema: HOSTED_CLOUD_VAULT_SCHEMA,
62
+ contractKey: 'hosted_vault',
63
+ idField: 'vault_id',
64
+ },
65
+ {
66
+ key: 'api_key_metadata',
67
+ label: 'API key metadata contract',
68
+ schema: HOSTED_CLOUD_API_KEY_SCHEMA,
69
+ contractKey: 'api_key_metadata',
70
+ idField: 'api_key_id',
71
+ },
72
+ {
73
+ key: 'billing',
74
+ label: 'Usage billing contract',
75
+ schema: HOSTED_CLOUD_USAGE_BILLING_SCHEMA,
76
+ contractKey: 'billing',
77
+ idField: 'billing_record_id',
78
+ },
79
+ {
80
+ key: 'dashboard',
81
+ label: 'Dashboard summary contract',
82
+ schema: HOSTED_CLOUD_DASHBOARD_SCHEMA,
83
+ contractKey: 'dashboard',
84
+ idField: 'dashboard_id',
85
+ },
86
+ {
87
+ key: 'backup_drill',
88
+ label: 'Backup drill contract',
89
+ schema: HOSTED_CLOUD_BACKUP_DRILL_SCHEMA,
90
+ contractKey: 'backup_drill',
91
+ idField: 'backup_drill_id',
92
+ },
93
+ {
94
+ key: 'incident_sla_refs',
95
+ label: 'Incident and SLA refs contract',
96
+ schema: HOSTED_CLOUD_INCIDENT_SLA_SCHEMA,
97
+ contractKey: 'incident_sla_refs',
98
+ idField: 'incident_sla_id',
99
+ },
100
+ ]);
101
+ const CONTRACT_EVIDENCE_KEYS = new Set(CONTRACT_EVIDENCE.map(({ key }) => key));
102
+ const EXTERNAL_BLOCKER_KEYS = new Set(HOSTED_CLOUD_EXTERNAL_BLOCKERS);
103
+ const ADDITIONAL_LIFECYCLE_EVIDENCE_KEYS = Object.freeze(['monitoring']);
104
+ const EVIDENCE_KEY_ALIASES = Object.freeze({
105
+ account: 'user_account',
106
+ vault: 'hosted_vault',
107
+ api_key: 'api_key_metadata',
108
+ backup: 'backup_drill',
109
+ incident_sla: 'incident_sla_refs',
110
+ support: 'support_ownership',
111
+ legal: 'legal_docs',
112
+ security_review: 'external_security_review',
113
+ });
114
+ export const HOSTED_CUSTOMER_LIFECYCLE_EVIDENCE_KEYS = Object.freeze([
115
+ ...CONTRACT_EVIDENCE.map(({ key }) => key),
116
+ ...HOSTED_CLOUD_EXTERNAL_BLOCKERS,
117
+ ...ADDITIONAL_LIFECYCLE_EVIDENCE_KEYS,
118
+ ]);
119
+ const LIFECYCLE_EVIDENCE_KEY_SET = new Set([
120
+ ...HOSTED_CUSTOMER_LIFECYCLE_EVIDENCE_KEYS,
121
+ ...Object.keys(EVIDENCE_KEY_ALIASES),
122
+ ]);
123
+
124
+ 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/@]+@|sk-[A-Za-z0-9_-]{16,}|AKIA[0-9A-Z]{16}|\b(?:raw memory|plaintext prompts?|plain text prompts?|private prompts?|provider responses?|full transcript|decrypted memory|credentials?|secrets?|passwords?|private keys?|api key secret|api secrets?|access tokens?|refresh tokens?|token values?|credential material)\b)/iu;
125
+ const FORBIDDEN_CLAIM_RE = /(?:token\s+(?:roi|profit|return|investment|price)|financial\s+roi|roi\s+claim|(?:roi|profit|return)\s+(?:from|on)\s+token|guaranteed\s+(?:savings|profit|return)|provider(?:-side|\s+side)?\s+(?:deletion|erasure)|model\s+(?:forgetting|forgot|erasure)|makes?\s+models?\s+forget|deleted\s+from\s+every\s+provider)/iu;
126
+
127
+ function assertPublicSafeString(value, label) {
128
+ if (typeof value !== 'string' || value.trim() === '') throw new Error(`${label} must be a non-empty public ref`);
129
+ if (SECRET_VALUE_RE.test(value) || FORBIDDEN_CLAIM_RE.test(value)) {
130
+ throw new Error(`${label} contains non-public hosted-cloud material`);
131
+ }
132
+ return value.trim();
133
+ }
134
+
135
+ function normalizeStatus(status, key) {
136
+ const normalized = STATUS_ALIASES[status] ?? status;
137
+ if (!ALLOWED_STATUSES.has(normalized)) throw new Error('evidence status must be provided, blocked_missing_evidence, or blocked_external_dependency');
138
+ if (EXTERNAL_BLOCKER_KEYS.has(key) && normalized === BLOCKED_MISSING) return BLOCKED_EXTERNAL;
139
+ if (CONTRACT_EVIDENCE_KEYS.has(key) && normalized === BLOCKED_EXTERNAL) return BLOCKED_MISSING;
140
+ return normalized;
141
+ }
142
+
143
+ function defaultEvidenceRef(key) {
144
+ const status = EXTERNAL_BLOCKER_KEYS.has(key) ? BLOCKED_EXTERNAL : BLOCKED_MISSING;
145
+ return {
146
+ key,
147
+ status,
148
+ ref: `blocked:${key}`,
149
+ blocker: EXTERNAL_BLOCKER_KEYS.has(key)
150
+ ? `External ${key.replaceAll('_', ' ')} evidence is not provided.`
151
+ : `Public-safe ${key.replaceAll('_', ' ')} evidence is not provided.`,
152
+ };
153
+ }
154
+
155
+ function normalizeEvidenceRecord(key, record) {
156
+ const status = normalizeStatus(record.status ?? PROVIDED, key);
157
+ const ref = assertPublicSafeString(record.ref, 'evidence ref');
158
+ const output = { key, status, ref };
159
+ if (status !== PROVIDED) output.blocker = record.blocker ?? defaultEvidenceRef(key).blocker;
160
+ return output;
161
+ }
162
+
163
+ export function parseEvidenceRef(value) {
164
+ const raw = assertPublicSafeString(value, 'evidence ref argument');
165
+ const equalsIndex = raw.indexOf('=');
166
+ if (equalsIndex <= 0 || equalsIndex === raw.length - 1) throw new Error('evidence refs must use key=status:ref');
167
+ const rawKey = raw.slice(0, equalsIndex).trim();
168
+ const key = EVIDENCE_KEY_ALIASES[rawKey] ?? rawKey;
169
+ if (!LIFECYCLE_EVIDENCE_KEY_SET.has(rawKey) || !HOSTED_CUSTOMER_LIFECYCLE_EVIDENCE_KEYS.includes(key)) throw new Error(`evidence key must be one of: ${HOSTED_CUSTOMER_LIFECYCLE_EVIDENCE_KEYS.join(', ')}`);
170
+ const body = raw.slice(equalsIndex + 1).trim();
171
+ const colonIndex = body.indexOf(':');
172
+ if (colonIndex > 0) {
173
+ const possibleStatus = body.slice(0, colonIndex);
174
+ if (ALLOWED_STATUSES.has(possibleStatus) || STATUS_ALIASES[possibleStatus]) {
175
+ return normalizeEvidenceRecord(key, { status: possibleStatus, ref: body.slice(colonIndex + 1) });
176
+ }
177
+ }
178
+ return normalizeEvidenceRecord(key, { status: PROVIDED, ref: body });
179
+ }
180
+
181
+ function normalizeEvidenceRefs(records = []) {
182
+ const refs = Object.fromEntries(HOSTED_CUSTOMER_LIFECYCLE_EVIDENCE_KEYS.map((key) => [key, defaultEvidenceRef(key)]));
183
+ for (const rawRecord of records) {
184
+ const record = typeof rawRecord === 'string' ? parseEvidenceRef(rawRecord) : rawRecord;
185
+ refs[record.key] = normalizeEvidenceRecord(record.key, record);
186
+ }
187
+ return refs;
188
+ }
189
+
190
+ function operatorEvidenceRefsFrom(evidenceRefs) {
191
+ return Object.fromEntries(HOSTED_CLOUD_EXTERNAL_BLOCKERS.map((key) => {
192
+ const evidence = evidenceRefs[key];
193
+ if (evidence.status === PROVIDED) return [key, { status: PROVIDED, ref: evidence.ref }];
194
+ return [key, { status: BLOCKED_EXTERNAL, ref: evidence.ref, blocker: evidence.blocker }];
195
+ }));
196
+ }
197
+
198
+ function phaseFor(definition, evidence, contract) {
199
+ return {
200
+ phase: definition.key,
201
+ label: definition.label,
202
+ contract_schema: definition.schema,
203
+ evidence_ref: evidence.ref,
204
+ status: evidence.status === PROVIDED ? 'evidence_ref_provided' : evidence.status,
205
+ contract_id: contract?.[definition.idField] ?? null,
206
+ };
207
+ }
208
+
209
+ function lifecycleReadiness(evidenceRefs, operatorGoLiveRef) {
210
+ const missingEvidenceRefs = Object.values(evidenceRefs)
211
+ .filter((evidence) => evidence.status !== PROVIDED)
212
+ .map(({ key, status, ref, blocker }) => ({ key, status, ref, blocker }));
213
+ const allEvidenceProvided = missingEvidenceRefs.length === 0;
214
+ const operatorGoLiveProvided = typeof operatorGoLiveRef === 'string' && operatorGoLiveRef.length > 0;
215
+ const hostedCloudSellable = allEvidenceProvided && operatorGoLiveProvided;
216
+ const status = hostedCloudSellable
217
+ ? 'operator_approved_evidence_packet'
218
+ : allEvidenceProvided
219
+ ? 'blocked_operator_go_live_approval'
220
+ : 'blocked_missing_evidence';
221
+ return {
222
+ status,
223
+ hosted_cloud_sellable: hostedCloudSellable,
224
+ evidence_validation_only: true,
225
+ provider_wiring_performed: false,
226
+ no_external_provider_calls: true,
227
+ all_evidence_refs_provided: allEvidenceProvided,
228
+ operator_go_live_approval_ref: operatorGoLiveRef ?? 'blocked:operator_go_live_approval',
229
+ operator_go_live_approval_provided: operatorGoLiveProvided,
230
+ missing_evidence_refs: missingEvidenceRefs,
231
+ external_blockers: HOSTED_CLOUD_EXTERNAL_BLOCKERS
232
+ .map((key) => evidenceRefs[key])
233
+ .filter((evidence) => evidence.status !== PROVIDED)
234
+ .map(({ key, ref, blocker }) => ({ key, ref, blocker })),
235
+ };
236
+ }
237
+
238
+ function refFor(evidenceRefs, key) {
239
+ return evidenceRefs[key].ref;
240
+ }
241
+
242
+ function buildContractFragments({ tenantId, generatedAt, evidenceRefs }) {
243
+ const operator_evidence_refs = operatorEvidenceRefsFrom(evidenceRefs);
244
+ const base = { tenant_id: tenantId, generated_at: generatedAt, operator_evidence_refs };
245
+ const tenant = buildTenantContract({
246
+ ...base,
247
+ tenant_ref: refFor(evidenceRefs, 'tenant'),
248
+ policy_ref: refFor(evidenceRefs, 'tenant'),
249
+ retention_policy_ref: refFor(evidenceRefs, 'tenant'),
250
+ data_residency_ref: refFor(evidenceRefs, 'tenant'),
251
+ });
252
+ const userAccount = buildUserAccountContract({
253
+ ...base,
254
+ subject_ref: refFor(evidenceRefs, 'user_account'),
255
+ auth_provider_user_ref: refFor(evidenceRefs, 'auth_provider'),
256
+ });
257
+ const hostedVault = buildHostedVaultContract({
258
+ ...base,
259
+ vault_ref: refFor(evidenceRefs, 'hosted_vault'),
260
+ storage_ref: refFor(evidenceRefs, 'hosted_vault'),
261
+ kms_key_ref: refFor(evidenceRefs, 'hosted_vault'),
262
+ backup_policy_ref: refFor(evidenceRefs, 'backup_drill'),
263
+ retention_policy_ref: refFor(evidenceRefs, 'tenant'),
264
+ });
265
+ const apiKeyMetadata = buildApiKeyContract({
266
+ ...base,
267
+ subject_ref: refFor(evidenceRefs, 'user_account'),
268
+ key_fingerprint: `fingerprint:${refFor(evidenceRefs, 'api_key_metadata')}`,
269
+ issued_at: generatedAt,
270
+ rotation_ref: refFor(evidenceRefs, 'api_key_metadata'),
271
+ });
272
+ const billing = buildUsageBillingRecord({
273
+ ...base,
274
+ period_start: generatedAt,
275
+ period_end: generatedAt,
276
+ billing_provider_customer_ref: refFor(evidenceRefs, 'billing_provider'),
277
+ usage_aggregate_ref: refFor(evidenceRefs, 'billing'),
278
+ metered_event_count: 0,
279
+ billable_units: 0,
280
+ amount_due_minor_units: 0,
281
+ });
282
+ const incidentSlaRefs = buildIncidentSlaRefs({
283
+ ...base,
284
+ incident_response_ref: refFor(evidenceRefs, 'incident_sla_refs'),
285
+ sla_policy_ref: refFor(evidenceRefs, 'incident_sla_refs'),
286
+ support_owner_ref: refFor(evidenceRefs, 'support_ownership'),
287
+ escalation_policy_ref: refFor(evidenceRefs, 'incident_sla_refs'),
288
+ status_page_ref: refFor(evidenceRefs, 'incident_sla_refs'),
289
+ });
290
+ const backupDrill = buildBackupDrillContract({
291
+ ...base,
292
+ vault_ref: hostedVault.vault_ref,
293
+ performed_at: generatedAt,
294
+ backup_snapshot_ref: refFor(evidenceRefs, 'backup_drill'),
295
+ restore_evidence_ref: refFor(evidenceRefs, 'backup_drill'),
296
+ rpo_minutes: 0,
297
+ rto_minutes: 0,
298
+ });
299
+ const dashboard = buildDashboardSummary({
300
+ ...base,
301
+ account_count: 0,
302
+ active_api_key_count: 0,
303
+ hosted_vault_count: 0,
304
+ billing_period_ref: billing.billing_record_id,
305
+ open_incident_count: 0,
306
+ backup_drill_ref: backupDrill.backup_drill_id,
307
+ incident_sla_ref: incidentSlaRefs.incident_sla_id,
308
+ });
309
+ return {
310
+ user_account: userAccount,
311
+ tenant,
312
+ hosted_vault: hostedVault,
313
+ api_key_metadata: apiKeyMetadata,
314
+ billing,
315
+ dashboard,
316
+ backup_drill: backupDrill,
317
+ incident_sla_refs: incidentSlaRefs,
318
+ };
319
+ }
320
+
321
+ function canonicalContractsFrom(contracts) {
322
+ return {
323
+ account: contracts.user_account,
324
+ tenant: contracts.tenant,
325
+ vault: contracts.hosted_vault,
326
+ api_key: contracts.api_key_metadata,
327
+ billing: contracts.billing,
328
+ dashboard: contracts.dashboard,
329
+ backup: contracts.backup_drill,
330
+ incident_sla: contracts.incident_sla_refs,
331
+ };
332
+ }
333
+
334
+ function firstBlockedEvidence(evidenceRefs, keys) {
335
+ for (const key of keys) {
336
+ if (evidenceRefs[key].status !== PROVIDED) return evidenceRefs[key];
337
+ }
338
+ return null;
339
+ }
340
+
341
+ function providedLifecycleEvidence(evidence) {
342
+ return { status: PROVIDED, ref: evidence.ref };
343
+ }
344
+
345
+ function blockedLifecycleEvidence(evidence) {
346
+ return {
347
+ status: BLOCKED_EXTERNAL,
348
+ ref: evidence.ref,
349
+ blocker: evidence.blocker,
350
+ };
351
+ }
352
+
353
+ function coreEvidenceFor(evidenceRefs, phase, keys) {
354
+ const blocked = firstBlockedEvidence(evidenceRefs, keys);
355
+ if (blocked) return blockedLifecycleEvidence(blocked);
356
+ return providedLifecycleEvidence(evidenceRefs[keys[0]]);
357
+ }
358
+
359
+ function coreRequiredEvidenceRefs(evidenceRefs) {
360
+ const refs = {
361
+ account: coreEvidenceFor(evidenceRefs, 'account', ['user_account', 'auth_provider']),
362
+ tenant: coreEvidenceFor(evidenceRefs, 'tenant', ['tenant']),
363
+ vault: coreEvidenceFor(evidenceRefs, 'vault', ['hosted_vault']),
364
+ api_key: coreEvidenceFor(evidenceRefs, 'api_key', ['api_key_metadata']),
365
+ billing: coreEvidenceFor(evidenceRefs, 'billing', ['billing', 'billing_provider']),
366
+ dashboard: coreEvidenceFor(evidenceRefs, 'dashboard', ['dashboard']),
367
+ backup: coreEvidenceFor(evidenceRefs, 'backup', ['backup_drill']),
368
+ incident_sla: coreEvidenceFor(evidenceRefs, 'incident_sla', ['incident_sla_refs']),
369
+ support: coreEvidenceFor(evidenceRefs, 'support', ['support_ownership']),
370
+ monitoring: coreEvidenceFor(evidenceRefs, 'monitoring', ['monitoring']),
371
+ legal: coreEvidenceFor(evidenceRefs, 'legal', ['legal_docs', 'data_processing_terms']),
372
+ security_review: coreEvidenceFor(evidenceRefs, 'security_review', ['external_security_review']),
373
+ };
374
+ return Object.fromEntries(HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PHASES
375
+ .filter((phase) => phase !== 'operator_go_live')
376
+ .map((phase) => [phase, refs[phase]]));
377
+ }
378
+
379
+ function scriptBoundary() {
380
+ return {
381
+ public_safe_packet_only: true,
382
+ aggregates_contract_validators_only: true,
383
+ deploys_or_calls_external_providers: false,
384
+ sensitive_material_written: false,
385
+ customer_content_stored: false,
386
+ provider_wiring_required_elsewhere: true,
387
+ };
388
+ }
389
+
390
+ export function buildHostedCustomerLifecyclePacket(options = {}) {
391
+ const generatedAt = options.generatedAt ?? new Date().toISOString();
392
+ const tenantId = assertPublicSafeString(options.tenant ?? options.tenantId ?? 'blocked:tenant', 'tenant');
393
+ const domain = assertPublicSafeString(options.domain ?? 'blocked:domain', 'domain');
394
+ const environment = assertPublicSafeString(options.environment ?? 'blocked:environment', 'environment');
395
+ const operatorGoLiveRef = options.operatorGoLiveRef === undefined
396
+ ? null
397
+ : assertPublicSafeString(options.operatorGoLiveRef, 'operator go-live ref');
398
+ const evidenceRefs = normalizeEvidenceRefs(options.evidenceRefs ?? []);
399
+ const contractFragments = buildContractFragments({ tenantId, generatedAt, evidenceRefs });
400
+ const packet = buildCustomerLifecyclePacket({
401
+ generated_at: generatedAt,
402
+ contracts: canonicalContractsFrom(contractFragments),
403
+ required_evidence_refs: coreRequiredEvidenceRefs(evidenceRefs),
404
+ operator_go_live_ref: operatorGoLiveRef,
405
+ });
406
+ validateCustomerLifecyclePacket(packet);
407
+ return {
408
+ ...packet,
409
+ release_target: HOSTED_CUSTOMER_LIFECYCLE_RELEASE_TARGET,
410
+ tenant_id: tenantId,
411
+ domain,
412
+ environment,
413
+ evidence_refs: Object.fromEntries(Object.entries(evidenceRefs).map(([key, value]) => [key, { ...value }])),
414
+ operator_external_evidence_refs: operatorEvidenceRefsFrom(evidenceRefs),
415
+ boundary: scriptBoundary(),
416
+ };
417
+ }
418
+
419
+ export function parseArgs(argv) {
420
+ const args = {
421
+ tenant: undefined,
422
+ domain: undefined,
423
+ environment: undefined,
424
+ operatorGoLiveRef: undefined,
425
+ evidenceRefs: [],
426
+ out: undefined,
427
+ help: false,
428
+ };
429
+ for (let index = 0; index < argv.length; index += 1) {
430
+ const arg = argv[index];
431
+ if (arg === '--help' || arg === '-h') {
432
+ args.help = true;
433
+ continue;
434
+ }
435
+ const readValue = (name) => {
436
+ index += 1;
437
+ if (index >= argv.length || argv[index].startsWith('--')) throw new Error(`${name} requires a value`);
438
+ return argv[index];
439
+ };
440
+ if (arg === '--tenant') args.tenant = assertPublicSafeString(readValue('--tenant'), 'tenant');
441
+ else if (arg === '--domain') args.domain = assertPublicSafeString(readValue('--domain'), 'domain');
442
+ else if (arg === '--environment') args.environment = assertPublicSafeString(readValue('--environment'), 'environment');
443
+ else if (arg === '--operator-go-live-ref') args.operatorGoLiveRef = assertPublicSafeString(readValue('--operator-go-live-ref'), 'operator go-live ref');
444
+ else if (arg === '--evidence-ref') args.evidenceRefs.push(parseEvidenceRef(readValue('--evidence-ref')));
445
+ else if (arg === '--out') args.out = assertPublicSafeString(readValue('--out'), 'output file');
446
+ else throw new Error('unknown option; use --help');
447
+ }
448
+ return args;
449
+ }
450
+
451
+ export function usage() {
452
+ return `Usage: node scripts/build-hosted-customer-lifecycle.mjs [options]
453
+
454
+ Build a public-safe hosted customer lifecycle packet. This script validates local contract fragments only; it does not deploy, create accounts, call providers, or write secrets.
455
+
456
+ Options:
457
+ --tenant <id> Tenant id. Defaults to blocked:tenant.
458
+ --domain <domain> Customer domain. Defaults to blocked:domain.
459
+ --environment <env> Environment label. Defaults to blocked:environment.
460
+ --operator-go-live-ref <ref> Explicit operator approval evidence ref.
461
+ --evidence-ref <key=status:ref> Repeatable evidence ref. key=<ref> implies provided.
462
+ Status: provided, blocked_missing_evidence, blocked_external_dependency.
463
+ Keys: ${HOSTED_CUSTOMER_LIFECYCLE_EVIDENCE_KEYS.join(', ')}
464
+ --out <file> Also write the packet JSON to a file.
465
+ --help Show this help.
466
+ `;
467
+ }
468
+
469
+ export async function main(argv = process.argv.slice(2)) {
470
+ const args = parseArgs(argv);
471
+ if (args.help) {
472
+ process.stdout.write(usage());
473
+ return 0;
474
+ }
475
+ const packet = buildHostedCustomerLifecyclePacket(args);
476
+ const json = `${JSON.stringify(packet, null, 2)}\n`;
477
+ if (args.out) {
478
+ try {
479
+ await writeFile(args.out, json, 'utf8');
480
+ } catch {
481
+ throw new Error('failed to write lifecycle packet output');
482
+ }
483
+ }
484
+ process.stdout.write(json);
485
+ return 0;
486
+ }
487
+
488
+ if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
489
+ main().catch((error) => {
490
+ process.stderr.write(`Error: ${error?.message ?? 'failed to build lifecycle packet'}\n`);
491
+ process.exitCode = 1;
492
+ });
493
+ }
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'node:url';
6
6
 
7
7
  export const INSTALLER_ASSET_SCHEMA = 'enigma.installer_assets.v1';
8
8
  export const INSTALLER_ASSET_PACKAGE = 'enigma-memory';
9
- export const INSTALLER_ASSET_VERSION = '0.1.7';
9
+ export const INSTALLER_ASSET_VERSION = '0.1.9';
10
10
  export const INSTALLER_ASSET_GENERATED_AT = '1970-01-01T00:00:00.000Z';
11
11
 
12
12
  const SCRIPT_PATH = fileURLToPath(import.meta.url);
@@ -995,7 +995,7 @@ function buildSuiteReport(datasetRows, topK, options) {
995
995
  generated_at: options.generated_at ?? new Date().toISOString(),
996
996
  package: {
997
997
  name: 'enigma-memory',
998
- version: '0.1.7',
998
+ version: '0.1.9',
999
999
  },
1000
1000
  public_safe: true,
1001
1001
  top_k: topK,