enigma-memory 0.1.9 → 0.1.10

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.
@@ -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.9` package, runs:
63
+ The CI example installs Node 24, installs the published `enigma-memory@0.1.10` package, runs:
64
64
 
65
65
  ```sh
66
66
  npx --yes --package enigma-memory enigma setup --overwrite
@@ -10,6 +10,7 @@ The source package now has pure contract builders and validators in `packages/ho
10
10
  - tenant records;
11
11
  - hosted vault records;
12
12
  - API key metadata records;
13
+ - API key lifecycle packets for issue/rotate/revoke/audit readiness;
13
14
  - usage billing records;
14
15
  - dashboard summaries;
15
16
  - backup drill records;
@@ -18,7 +19,7 @@ The source package now has pure contract builders and validators in `packages/ho
18
19
 
19
20
  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.
20
21
 
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
+ The lifecycle packet public APIs are `buildCustomerLifecyclePacket(input)` / `validateCustomerLifecyclePacket(packet)` and `buildApiKeyLifecyclePacket(input)` / `validateApiKeyLifecyclePacket(packet)` under `enigma-memory/hosted-cloud`; the script commands below emit the same schemas for release evidence.
22
23
 
23
24
  The validators enforce hosted-cloud boundaries:
24
25
 
@@ -27,10 +28,11 @@ The validators enforce hosted-cloud boundaries:
27
28
  - raw memory, plaintext prompts, provider responses, transcripts, credential-looking values, token values, private keys, and API key secret material are rejected;
28
29
  - financial outcome claims, token ROI/profit claims, provider-side deletion claims, and model-forgetting claims are rejected;
29
30
  - API key contracts store identifiers, fingerprints, scopes, rotation refs, and timestamps only, not key material;
31
+ - API key lifecycle packets store issue/rotate/revoke/audit evidence refs, event refs, fingerprints, opaque subjects, readiness status, and operator approval refs only; they reject raw key material, provider payloads, customer memory, plaintext prompts, credentials, ROI claims, provider-deletion claims, and model-forgetting claims;
30
32
  - hosted vault contracts are opaque-record and plaintext-minimized contracts only;
31
33
  - billing records remain contract records until an external billing provider invoice flow is wired.
32
34
 
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.
35
+ 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. An API key lifecycle packet keeps `customer_api_keys_live:false` by default and can become live-ready only when every required issue/rotate/revoke/audit evidence ref is provided and an operator approval ref is supplied. That live-ready state is still evidence validation only: it does not issue a customer API key, create a secret, call an auth provider, rotate/revoke a provider credential, or prove provider-side deletion.
34
36
 
35
37
  ## Externally blocked before hosted cloud can be sold
36
38
 
@@ -53,6 +55,14 @@ A `provided` operator evidence ref means the contract can point to external evid
53
55
 
54
56
  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
57
 
58
+ ## API key lifecycle packet
59
+
60
+ `npm run production:hosted-api-key -- --tenant <id> --subject <opaque-subject-ref> --operation <issue|rotate|revoke|audit> --out <file>` builds `enigma.hosted_cloud.api_key_lifecycle_packet.v1` readiness evidence for one customer API key lifecycle operation. Operators may pass repeatable `--evidence-ref <phase=status:ref>` values for the phases required by that operation, plus `--operator-approval-ref <ref>` when a reviewed operator approval exists.
61
+
62
+ The command writes public-safe validation evidence only. It must not receive or print raw API keys, bearer tokens, credentials, provider response bodies, plaintext prompts, raw memory, customer content, financial ROI claims, provider deletion claims, or model forgetting claims. It creates no hosted account, customer API key, provider secret, KMS key, rotation job, revocation job, audit export, invoice, support ticket, Cloudflare resource, provider resource, or deployment.
63
+
64
+ The packet records lifecycle events, required evidence refs, missing evidence refs, malformed operation blockers, the operator approval ref, readiness status, `customer_api_keys_live`, and no-secret/no-plaintext guarantees. Missing evidence refs, blocked evidence refs, malformed operation surfaces, or absent operator approval keep `customer_api_keys_live:false`.
65
+
56
66
  ## Non-claims
57
67
 
58
68
  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.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.
3
+ This guide covers the public package imports for `enigma-memory@0.1.10`. The SDK runs locally by default: vaults, passports, context packs, receipts, relay/gateway demo state, storage contracts, metering artifacts, settlement artifacts, and hosted-cloud contract packets are package-level developer surfaces. They are not evidence of hosted Enigma cloud, live customer API key issuance, 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
 
@@ -157,6 +157,27 @@ const job = createPermissionlessMemoryJob({
157
157
  });
158
158
  ```
159
159
 
160
+ ### `enigma-memory/hosted-cloud`
161
+
162
+ The hosted-cloud module emits public-safe contract/readiness evidence only. Customer API key lifecycle packets model issue, rotate, revoke, and audit readiness with evidence refs, opaque subject refs, fingerprints, missing-evidence refs, readiness status, and operator approval refs. They never contain raw key material, provider payloads, plaintext prompts, raw memory, credentials, ROI claims, provider deletion claims, or model forgetting claims.
163
+
164
+ ```js
165
+ import {
166
+ buildApiKeyLifecyclePacket,
167
+ validateApiKeyLifecyclePacket,
168
+ } from 'enigma-memory/hosted-cloud';
169
+
170
+ const packet = buildApiKeyLifecyclePacket({
171
+ tenant_id: 'tenant-alpha',
172
+ subject_ref: 'subject-ref-alpha',
173
+ environment: 'production',
174
+ operation: 'audit',
175
+ });
176
+ const valid = validateApiKeyLifecyclePacket(packet);
177
+ ```
178
+
179
+ `customer_api_keys_live` stays `false` unless the issue/rotate/revoke/audit evidence refs are complete and an operator approval ref is supplied. Even then, the packet is readiness evidence validation only; it does not issue a key, create a secret, call KMS or auth providers, rotate or revoke a provider credential, or prove provider-side deletion.
180
+
160
181
  ## Verifying exported proof
161
182
 
162
183
  Package consumers should verify exported bundles through the CLI or MCP verifier rather than importing source-only verifier internals. Treat full bundles as private unless you have reviewed and removed local import key material before sharing:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "enigma-memory",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "description": "Provider-agnostic AI memory passport and offline-verifiable proof layer.",
6
6
  "license": "Apache-2.0",
@@ -85,6 +85,7 @@
85
85
  "scripts/collect-hosted-backend-live-evidence.mjs",
86
86
  "scripts/build-hosted-probe-worker.mjs",
87
87
  "scripts/build-hosted-customer-lifecycle.mjs",
88
+ "scripts/build-hosted-api-key-lifecycle.mjs",
88
89
  "scripts/build-edge-backend-workers.mjs",
89
90
  "scripts/validate-cloudflare-worker-inspect.mjs",
90
91
  "scripts/validate-production-manifests.mjs",
@@ -177,6 +178,7 @@
177
178
  "production:edge-live": "node scripts/collect-edge-backend-bootstrap-evidence.mjs",
178
179
  "production:ref-draft": "node scripts/build-hosted-ref-draft.mjs",
179
180
  "production:hosted-customer": "node scripts/build-hosted-customer-lifecycle.mjs",
181
+ "production:hosted-api-key": "node scripts/build-hosted-api-key-lifecycle.mjs",
180
182
  "production:storage-bootstrap": "node scripts/cloudflare-storage-bootstrap.mjs",
181
183
  "production:worker-inspect": "node scripts/validate-cloudflare-worker-inspect.mjs",
182
184
  "production:cloudflare-credentials": "node scripts/validate-cloudflare-credentials.mjs",
@@ -9,6 +9,7 @@ export const HOSTED_CLOUD_DASHBOARD_SCHEMA = 'enigma.hosted_cloud.dashboard_summ
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
11
  export const HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PACKET_SCHEMA = 'enigma.hosted_cloud.customer_lifecycle_packet.v1';
12
+ export const HOSTED_CLOUD_API_KEY_LIFECYCLE_PACKET_SCHEMA = 'enigma.hosted_cloud.api_key_lifecycle_packet.v1';
12
13
 
13
14
  export const HOSTED_CLOUD_EXTERNAL_BLOCKERS = Object.freeze([
14
15
  'auth_provider',
@@ -63,6 +64,23 @@ const CUSTOMER_LIFECYCLE_CONTRACT_PHASES = Object.freeze([
63
64
  'incident_sla',
64
65
  ]);
65
66
  const CUSTOMER_LIFECYCLE_REQUIRED_EVIDENCE_PHASES = Object.freeze(HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PHASES.filter((phase) => phase !== 'operator_go_live'));
67
+ export const HOSTED_CLOUD_API_KEY_LIFECYCLE_OPERATIONS = Object.freeze(['issue', 'rotate', 'revoke', 'audit']);
68
+ export const HOSTED_CLOUD_API_KEY_LIFECYCLE_PHASES = Object.freeze([
69
+ 'current_key_metadata',
70
+ 'next_key_metadata',
71
+ 'revoked_key_metadata',
72
+ 'issue_policy',
73
+ 'rotation_policy',
74
+ 'revocation_policy',
75
+ 'audit_log',
76
+ ]);
77
+ const API_KEY_LIFECYCLE_OPERATION_PHASES = Object.freeze({
78
+ issue: Object.freeze(['next_key_metadata', 'issue_policy', 'audit_log']),
79
+ rotate: Object.freeze(['current_key_metadata', 'next_key_metadata', 'rotation_policy', 'audit_log']),
80
+ revoke: Object.freeze(['current_key_metadata', 'revoked_key_metadata', 'revocation_policy', 'audit_log']),
81
+ audit: Object.freeze(['current_key_metadata', 'audit_log']),
82
+ });
83
+ const API_KEY_LIFECYCLE_KEY_SLOTS = Object.freeze(['current', 'next', 'revoked']);
66
84
  const CUSTOMER_LIFECYCLE_BLOCKER_LABELS = Object.freeze({
67
85
  account: 'Hosted account contract evidence is not provided.',
68
86
  tenant: 'Hosted tenant contract evidence is not provided.',
@@ -78,8 +96,19 @@ const CUSTOMER_LIFECYCLE_BLOCKER_LABELS = Object.freeze({
78
96
  security_review: 'Hosted security review evidence is not provided.',
79
97
  operator_go_live: 'Explicit operator go-live approval is not provided.',
80
98
  });
99
+ const API_KEY_LIFECYCLE_BLOCKER_LABELS = Object.freeze({
100
+ current_key_metadata: 'Current hosted API key metadata evidence is not provided.',
101
+ next_key_metadata: 'Next hosted API key metadata evidence is not provided.',
102
+ revoked_key_metadata: 'Revoked hosted API key metadata evidence is not provided.',
103
+ issue_policy: 'Hosted API key issue policy evidence is not provided.',
104
+ rotation_policy: 'Hosted API key rotation policy evidence is not provided.',
105
+ revocation_policy: 'Hosted API key revocation policy evidence is not provided.',
106
+ audit_log: 'Hosted API key lifecycle audit evidence is not provided.',
107
+ operator_approval: 'Explicit public-safe operator API key lifecycle approval is not provided.',
108
+ });
109
+ const API_KEY_SECRET_MATERIAL_KEY_RE = /(?:^|_)(?:raw_?api_?key|raw_?key|plaintext_?api_?key|plain_text_?api_?key|plaintext_?key|plain_text_?key|api_?key_?value|key_?value|key_?material|secret_?key|key_?secret|value)(?:$|_)/iu;
81
110
  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;
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;
111
+ 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;
83
112
  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;
84
113
 
85
114
  function isPlainObject(value) {
@@ -880,7 +909,7 @@ function validateCustomerLifecycleEvidenceRefs(requiredEvidenceRefs) {
880
909
  function validateCustomerLifecycleContracts(contracts) {
881
910
  if (!isPlainObject(contracts)) throw new TypeError('contracts must be present');
882
911
  for (const phase of CUSTOMER_LIFECYCLE_CONTRACT_PHASES) {
883
- if (!Object.prototype.hasOwnProperty.call(contracts, phase)) throw new TypeError(`contracts.${phase} must be present`);
912
+ if (!Object.prototype.hasOwnProperty.call(contracts, phase)) throw new TypeError(`contracts.${phase} must be present; missing_surface_refs must match contracts`);
884
913
  const contract = contracts[phase];
885
914
  if (contract === null) continue;
886
915
  if (!isPlainObject(contract)) throw new TypeError(`contracts.${phase} must be an object or null`);
@@ -982,3 +1011,392 @@ export function validateCustomerLifecyclePacket(packet) {
982
1011
  validateCustomerLifecycleSafetyGuarantees(packet.guarantees ?? packet.public_safety_guarantees);
983
1012
  return true;
984
1013
  }
1014
+
1015
+ function assertNoApiKeySecretMaterial(value, path = 'api_key_lifecycle') {
1016
+ if (Array.isArray(value)) {
1017
+ value.forEach((item, index) => assertNoApiKeySecretMaterial(item, `${path}[${index}]`));
1018
+ return;
1019
+ }
1020
+ if (!isPlainObject(value)) return;
1021
+ for (const [key, child] of Object.entries(value)) {
1022
+ const allowedBoundaryKey = key === 'key_material_boundary' || key === 'key_material_in_contract' || key === 'api_key_material_absent';
1023
+ const keyMetadataContext = /(?:^|\.)(?:current|next|revoked|current_key|currentKey|current_api_key|currentApiKey|next_key|nextKey|next_api_key|nextApiKey|revoked_key|revokedKey|revoked_api_key|revokedApiKey|key_contracts\.(?:current|next|revoked)|keyContracts\.(?:current|next|revoked)|key_metadata_contracts\.(?:current|next|revoked)|keyMetadataContracts\.(?:current|next|revoked)|contracts\.(?:current|next|revoked))$/u.test(path);
1024
+ const bareSecretKey = key === 'api_key' || key === 'apiKey' || (keyMetadataContext && key === 'key');
1025
+ if (!allowedBoundaryKey && (bareSecretKey || API_KEY_SECRET_MATERIAL_KEY_RE.test(key))) {
1026
+ throw new TypeError(`${path}.${key} is not allowed in API key lifecycle packets`);
1027
+ }
1028
+ assertNoApiKeySecretMaterial(child, `${path}.${key}`);
1029
+ }
1030
+ }
1031
+
1032
+ function apiKeyLifecycleOperation(input) {
1033
+ const operation = requiredString(input.operation, 'operation');
1034
+ if (!HOSTED_CLOUD_API_KEY_LIFECYCLE_OPERATIONS.includes(operation)) {
1035
+ throw new TypeError(`operation must be one of ${HOSTED_CLOUD_API_KEY_LIFECYCLE_OPERATIONS.join(', ')}`);
1036
+ }
1037
+ return operation;
1038
+ }
1039
+
1040
+ function apiKeyLifecycleRequiredPhases(operation) {
1041
+ return API_KEY_LIFECYCLE_OPERATION_PHASES[operation];
1042
+ }
1043
+
1044
+ function apiKeyLifecycleSlotAliases(slot) {
1045
+ switch (slot) {
1046
+ case 'current':
1047
+ return ['current', 'current_key', 'currentKey', 'current_api_key', 'currentApiKey', 'current_key_contract', 'currentKeyContract', 'current_api_key_contract', 'currentApiKeyContract'];
1048
+ case 'next':
1049
+ return ['next', 'next_key', 'nextKey', 'next_api_key', 'nextApiKey', 'next_key_contract', 'nextKeyContract', 'next_api_key_contract', 'nextApiKeyContract'];
1050
+ case 'revoked':
1051
+ return ['revoked', 'revoked_key', 'revokedKey', 'revoked_api_key', 'revokedApiKey', 'revoked_key_contract', 'revokedKeyContract', 'revoked_api_key_contract', 'revokedApiKeyContract'];
1052
+ default:
1053
+ throw new TypeError(`unknown API key lifecycle slot: ${slot}`);
1054
+ }
1055
+ }
1056
+
1057
+ function apiKeyLifecycleContractSource(input, slot) {
1058
+ const aliases = apiKeyLifecycleSlotAliases(slot);
1059
+ const contractCollections = [
1060
+ input.key_contracts,
1061
+ input.keyContracts,
1062
+ input.key_metadata_contracts,
1063
+ input.keyMetadataContracts,
1064
+ input.contracts,
1065
+ ];
1066
+ for (const collection of contractCollections) {
1067
+ if (!isPlainObject(collection)) continue;
1068
+ for (const alias of aliases) {
1069
+ if (collection[alias] !== undefined) return collection[alias];
1070
+ }
1071
+ }
1072
+ for (const alias of aliases) {
1073
+ if (input[alias] !== undefined) return input[alias];
1074
+ }
1075
+ return undefined;
1076
+ }
1077
+
1078
+ function validateApiKeyLifecycleContractTenantSubject(contract, tenantId, subjectRef, name) {
1079
+ if (contract.tenant_id !== tenantId) throw new TypeError(`${name}.tenant_id must match packet tenant_id`);
1080
+ if (contract.subject_ref !== subjectRef) throw new TypeError(`${name}.subject_ref must match packet subject_ref`);
1081
+ }
1082
+
1083
+ function buildOrValidateApiKeyLifecycleContract(slot, source, tenantId, subjectRef, generatedAt) {
1084
+ if (source === undefined || source === null) return null;
1085
+ if (!isPlainObject(source)) throw new TypeError(`key_contracts.${slot} must be an object`);
1086
+ const name = `key_contracts.${slot}`;
1087
+ if (source.schema === HOSTED_CLOUD_API_KEY_SCHEMA) {
1088
+ validateApiKeyContract(source);
1089
+ validateApiKeyLifecycleContractTenantSubject(source, tenantId, subjectRef, name);
1090
+ return source;
1091
+ }
1092
+ const contract = buildApiKeyContract({
1093
+ ...source,
1094
+ tenant_id: source.tenant_id ?? source.tenantId ?? tenantId,
1095
+ subject_ref: source.subject_ref ?? source.subjectRef ?? subjectRef,
1096
+ generated_at: source.generated_at ?? source.generatedAt ?? generatedAt,
1097
+ });
1098
+ validateApiKeyLifecycleContractTenantSubject(contract, tenantId, subjectRef, name);
1099
+ return contract;
1100
+ }
1101
+
1102
+ function apiKeyLifecycleContractsFrom(input, tenantId, subjectRef, generatedAt) {
1103
+ return Object.fromEntries(API_KEY_LIFECYCLE_KEY_SLOTS.map((slot) => [
1104
+ slot,
1105
+ buildOrValidateApiKeyLifecycleContract(slot, apiKeyLifecycleContractSource(input, slot), tenantId, subjectRef, generatedAt),
1106
+ ]));
1107
+ }
1108
+
1109
+ function explicitApiKeyLifecycleEvidenceRefs(input) {
1110
+ const refs = input.required_evidence_refs ?? input.requiredEvidenceRefs ?? input.api_key_evidence_refs ?? input.apiKeyEvidenceRefs ?? input.lifecycle_evidence_refs ?? input.lifecycleEvidenceRefs ?? input.evidence_refs ?? input.evidenceRefs;
1111
+ if (refs === undefined || refs === null) return {};
1112
+ if (!isPlainObject(refs)) throw new TypeError('required_evidence_refs must be an object');
1113
+ return refs;
1114
+ }
1115
+
1116
+ function apiKeyLifecycleEvidenceRefFor(phase, value) {
1117
+ const fallback = { ref: `blocked:${phase}`, status: BLOCKED, blocker: API_KEY_LIFECYCLE_BLOCKER_LABELS[phase] };
1118
+ if (value === undefined || value === null) return fallback;
1119
+ if (typeof value === 'string') return { ref: requirePublicSafeLifecycleRef(value, `required_evidence_refs.${phase}`), status: PROVIDED };
1120
+ if (!isPlainObject(value)) throw new TypeError(`required_evidence_refs.${phase} must be a string or object`);
1121
+ const status = stringOrDefault(value.status, PROVIDED);
1122
+ if (!EVIDENCE_STATUSES.has(status)) throw new TypeError(`required_evidence_refs.${phase}.status is invalid`);
1123
+ const ref = status === PROVIDED
1124
+ ? requirePublicSafeLifecycleRef(value.ref, `required_evidence_refs.${phase}.ref`)
1125
+ : requiredString(value.ref, `required_evidence_refs.${phase}.ref`);
1126
+ assertNoForbiddenPayload(ref, `required_evidence_refs.${phase}.ref`);
1127
+ assertNoApiKeySecretMaterial(ref, `required_evidence_refs.${phase}.ref`);
1128
+ const evidence = { ref, status };
1129
+ const owner = optionalString(value.owner, `required_evidence_refs.${phase}.owner`);
1130
+ const blocker = optionalString(value.blocker, `required_evidence_refs.${phase}.blocker`);
1131
+ const contractSchema = optionalString(value.contract_schema, `required_evidence_refs.${phase}.contract_schema`);
1132
+ const contractId = optionalString(value.contract_id, `required_evidence_refs.${phase}.contract_id`);
1133
+ if (owner) evidence.owner = owner;
1134
+ if (contractSchema) evidence.contract_schema = contractSchema;
1135
+ if (contractId) evidence.contract_id = contractId;
1136
+ if (status === BLOCKED) evidence.blocker = blocker ?? API_KEY_LIFECYCLE_BLOCKER_LABELS[phase];
1137
+ return evidence;
1138
+ }
1139
+
1140
+ function apiKeyLifecycleSlotForPhase(phase) {
1141
+ switch (phase) {
1142
+ case 'current_key_metadata':
1143
+ return 'current';
1144
+ case 'next_key_metadata':
1145
+ return 'next';
1146
+ case 'revoked_key_metadata':
1147
+ return 'revoked';
1148
+ default:
1149
+ return null;
1150
+ }
1151
+ }
1152
+
1153
+ function apiKeyLifecycleContractEvidenceRef(phase, contract) {
1154
+ if (!contract) return apiKeyLifecycleEvidenceRefFor(phase, undefined);
1155
+ return {
1156
+ ref: requiredString(contract.contract_hash, `key_contracts.${apiKeyLifecycleSlotForPhase(phase)}.contract_hash`),
1157
+ status: PROVIDED,
1158
+ contract_schema: requiredString(contract.schema, `key_contracts.${apiKeyLifecycleSlotForPhase(phase)}.schema`),
1159
+ contract_id: requiredString(contract.api_key_id, `key_contracts.${apiKeyLifecycleSlotForPhase(phase)}.api_key_id`),
1160
+ };
1161
+ }
1162
+
1163
+ function apiKeyLifecycleEvidenceRefsFrom(input, operation, keyContracts) {
1164
+ const explicitRefs = explicitApiKeyLifecycleEvidenceRefs(input);
1165
+ const requiredPhases = apiKeyLifecycleRequiredPhases(operation);
1166
+ const evidenceRefs = {};
1167
+ for (const phase of requiredPhases) {
1168
+ const slot = apiKeyLifecycleSlotForPhase(phase);
1169
+ evidenceRefs[phase] = explicitRefs[phase] === undefined && slot !== null
1170
+ ? apiKeyLifecycleContractEvidenceRef(phase, keyContracts[slot])
1171
+ : apiKeyLifecycleEvidenceRefFor(phase, explicitRefs[phase]);
1172
+ }
1173
+ return evidenceRefs;
1174
+ }
1175
+
1176
+ function apiKeyLifecycleOperatorApprovalRefFromInput(input) {
1177
+ const value = input.operator_approval_ref ?? input.operatorApprovalRef ?? input.operator_go_live_ref ?? input.operatorGoLiveRef;
1178
+ if (value === undefined || value === null) return null;
1179
+ return requirePublicSafeLifecycleRef(value, 'operator_approval_ref');
1180
+ }
1181
+
1182
+ function apiKeyLifecycleOperatorApprovalRefFromPacket(packet) {
1183
+ if (!Object.prototype.hasOwnProperty.call(packet, 'operator_approval_ref')) throw new TypeError('operator_approval_ref must be present');
1184
+ if (packet.operator_approval_ref === null) return null;
1185
+ return requirePublicSafeLifecycleRef(packet.operator_approval_ref, 'operator_approval_ref');
1186
+ }
1187
+
1188
+ function missingApiKeyLifecycleEvidenceRefs(requiredEvidenceRefs, operation) {
1189
+ return apiKeyLifecycleRequiredPhases(operation)
1190
+ .filter((phase) => requiredEvidenceRefs[phase].status !== PROVIDED)
1191
+ .map((phase) => ({
1192
+ key: phase,
1193
+ ref: requiredEvidenceRefs[phase].ref,
1194
+ blocker: requiredEvidenceRefs[phase].blocker ?? API_KEY_LIFECYCLE_BLOCKER_LABELS[phase],
1195
+ }));
1196
+ }
1197
+
1198
+ function apiKeyLifecycleExternalBlockers(requiredEvidenceRefs, operation, operatorApprovalRef) {
1199
+ const blockers = missingApiKeyLifecycleEvidenceRefs(requiredEvidenceRefs, operation);
1200
+ if (operatorApprovalRef === null) {
1201
+ blockers.push({
1202
+ key: 'operator_approval',
1203
+ ref: 'blocked:operator_approval',
1204
+ blocker: API_KEY_LIFECYCLE_BLOCKER_LABELS.operator_approval,
1205
+ });
1206
+ }
1207
+ return blockers;
1208
+ }
1209
+
1210
+ function apiKeyLifecycleReadiness(operation, requiredEvidenceRefs, operatorApprovalRef) {
1211
+ const missingEvidenceRefs = missingApiKeyLifecycleEvidenceRefs(requiredEvidenceRefs, operation);
1212
+ const externalLifecycleBlockers = apiKeyLifecycleExternalBlockers(requiredEvidenceRefs, operation, operatorApprovalRef);
1213
+ const evidenceApproved = missingEvidenceRefs.length === 0 && operatorApprovalRef !== null;
1214
+ return {
1215
+ ...HOSTED_CLOUD_CONTRACT_READY,
1216
+ operation,
1217
+ status: evidenceApproved ? 'operator_approved_api_key_lifecycle_evidence' : 'blocked_api_key_lifecycle_evidence_or_operator_approval',
1218
+ evidence_validation_only: true,
1219
+ no_provider_wiring: true,
1220
+ actual_key_issuance: false,
1221
+ lifecycle_evidence_complete: missingEvidenceRefs.length === 0,
1222
+ operator_approval_provided: operatorApprovalRef !== null,
1223
+ external_wiring_ready: externalLifecycleBlockers.length === 0,
1224
+ customer_api_keys_live: evidenceApproved,
1225
+ live_readiness_gate: evidenceApproved ? 'evidence_complete_operator_approved' : 'blocked_until_lifecycle_evidence_and_operator_approval',
1226
+ external_blockers: externalLifecycleBlockers,
1227
+ missing_evidence_refs: missingEvidenceRefs,
1228
+ };
1229
+ }
1230
+
1231
+ function apiKeyLifecycleKeyMetadataRefs(operation, requiredEvidenceRefs) {
1232
+ const requiredPhases = apiKeyLifecycleRequiredPhases(operation);
1233
+ return Object.fromEntries(API_KEY_LIFECYCLE_KEY_SLOTS.map((slot) => {
1234
+ const phase = `${slot}_key_metadata`;
1235
+ return [slot, requiredPhases.includes(phase) ? requiredEvidenceRefs[phase] : null];
1236
+ }));
1237
+ }
1238
+
1239
+ function apiKeyLifecycleEvents(operation, requiredEvidenceRefs, keyContracts) {
1240
+ return apiKeyLifecycleRequiredPhases(operation).map((phase) => {
1241
+ const evidence = requiredEvidenceRefs[phase];
1242
+ const event = {
1243
+ operation,
1244
+ phase,
1245
+ evidence_ref: evidence.ref,
1246
+ evidence_status: evidence.status,
1247
+ ready: evidence.status === PROVIDED,
1248
+ };
1249
+ if (evidence.status === BLOCKED) event.blocker = evidence.blocker ?? API_KEY_LIFECYCLE_BLOCKER_LABELS[phase];
1250
+ const slot = apiKeyLifecycleSlotForPhase(phase);
1251
+ if (slot !== null && keyContracts[slot]) {
1252
+ event.key_slot = slot;
1253
+ event.contract_schema = keyContracts[slot].schema;
1254
+ event.contract_id = keyContracts[slot].api_key_id;
1255
+ event.contract_hash = keyContracts[slot].contract_hash;
1256
+ }
1257
+ return event;
1258
+ });
1259
+ }
1260
+
1261
+ function apiKeyLifecycleSafetyGuarantees() {
1262
+ return {
1263
+ opaque_reference_only: true,
1264
+ customer_content_absent: true,
1265
+ sensitive_text_absent: true,
1266
+ auth_material_absent: true,
1267
+ api_key_material_absent: true,
1268
+ provider_payloads_absent: true,
1269
+ provider_wiring_absent: true,
1270
+ evidence_validation_only: true,
1271
+ actual_key_issuance_absent: true,
1272
+ financial_outcome_claim_absent: true,
1273
+ remote_erasure_claim_absent: true,
1274
+ };
1275
+ }
1276
+
1277
+ function validateApiKeyLifecycleSafetyGuarantees(guarantees) {
1278
+ if (!isPlainObject(guarantees)) throw new TypeError('public_safety_guarantees must be present');
1279
+ requiredTrue(guarantees.opaque_reference_only, 'public_safety_guarantees.opaque_reference_only');
1280
+ requiredTrue(guarantees.customer_content_absent, 'public_safety_guarantees.customer_content_absent');
1281
+ requiredTrue(guarantees.sensitive_text_absent, 'public_safety_guarantees.sensitive_text_absent');
1282
+ requiredTrue(guarantees.auth_material_absent, 'public_safety_guarantees.auth_material_absent');
1283
+ requiredTrue(guarantees.api_key_material_absent, 'public_safety_guarantees.api_key_material_absent');
1284
+ requiredTrue(guarantees.provider_payloads_absent, 'public_safety_guarantees.provider_payloads_absent');
1285
+ requiredTrue(guarantees.provider_wiring_absent, 'public_safety_guarantees.provider_wiring_absent');
1286
+ requiredTrue(guarantees.evidence_validation_only, 'public_safety_guarantees.evidence_validation_only');
1287
+ requiredTrue(guarantees.actual_key_issuance_absent, 'public_safety_guarantees.actual_key_issuance_absent');
1288
+ requiredTrue(guarantees.financial_outcome_claim_absent, 'public_safety_guarantees.financial_outcome_claim_absent');
1289
+ requiredTrue(guarantees.remote_erasure_claim_absent, 'public_safety_guarantees.remote_erasure_claim_absent');
1290
+ }
1291
+
1292
+ function validateApiKeyLifecycleContracts(keyContracts, tenantId, subjectRef) {
1293
+ if (!isPlainObject(keyContracts)) throw new TypeError('key_contracts must be present');
1294
+ for (const slot of API_KEY_LIFECYCLE_KEY_SLOTS) {
1295
+ if (!Object.prototype.hasOwnProperty.call(keyContracts, slot)) throw new TypeError(`key_contracts.${slot} must be present`);
1296
+ const contract = keyContracts[slot];
1297
+ if (contract === null) continue;
1298
+ if (!isPlainObject(contract)) throw new TypeError(`key_contracts.${slot} must be an object or null`);
1299
+ validateApiKeyContract(contract);
1300
+ validateApiKeyLifecycleContractTenantSubject(contract, tenantId, subjectRef, `key_contracts.${slot}`);
1301
+ }
1302
+ }
1303
+
1304
+ function validateApiKeyLifecycleEvidenceRefs(requiredEvidenceRefs, operation) {
1305
+ if (!isPlainObject(requiredEvidenceRefs)) throw new TypeError('required_evidence_refs must be present');
1306
+ const expectedPhases = apiKeyLifecycleRequiredPhases(operation);
1307
+ for (const phase of Object.keys(requiredEvidenceRefs)) {
1308
+ if (!expectedPhases.includes(phase)) throw new TypeError(`required_evidence_refs.${phase} is not required for ${operation}`);
1309
+ }
1310
+ const normalized = {};
1311
+ for (const phase of expectedPhases) {
1312
+ if (!Object.prototype.hasOwnProperty.call(requiredEvidenceRefs, phase)) throw new TypeError(`required_evidence_refs.${phase} must be present`);
1313
+ normalized[phase] = apiKeyLifecycleEvidenceRefFor(phase, requiredEvidenceRefs[phase]);
1314
+ }
1315
+ return normalized;
1316
+ }
1317
+
1318
+ function assertSameLifecycleObject(actual, expected, name) {
1319
+ if (!isPlainObject(actual)) throw new TypeError(`${name} must be an object`);
1320
+ if (JSON.stringify(canonicalize(actual)) !== JSON.stringify(canonicalize(expected))) throw new TypeError(`${name} must match API key lifecycle evidence`);
1321
+ }
1322
+
1323
+ function validateApiKeyLifecycleReadiness(readiness, expected) {
1324
+ if (!isPlainObject(readiness)) throw new TypeError('readiness must be present');
1325
+ requiredTrue(readiness.contract_ready, 'readiness.contract_ready');
1326
+ if (readiness.integration_kind !== HOSTED_CLOUD_CONTRACT_READY.integration_kind) throw new TypeError('readiness.integration_kind must remain contract_validator_only');
1327
+ requiredTrue(readiness.no_external_provider_calls, 'readiness.no_external_provider_calls');
1328
+ if (readiness.operation !== expected.operation) throw new TypeError('readiness.operation must match operation');
1329
+ if (readiness.status !== expected.status) throw new TypeError('readiness.status must match API key lifecycle evidence and operator approval');
1330
+ requiredTrue(readiness.evidence_validation_only, 'readiness.evidence_validation_only');
1331
+ requiredTrue(readiness.no_provider_wiring, 'readiness.no_provider_wiring');
1332
+ requiredFalse(readiness.actual_key_issuance, 'readiness.actual_key_issuance');
1333
+ if (readiness.lifecycle_evidence_complete !== expected.lifecycle_evidence_complete) throw new TypeError('readiness.lifecycle_evidence_complete must match required_evidence_refs');
1334
+ if (readiness.operator_approval_provided !== expected.operator_approval_provided) throw new TypeError('readiness.operator_approval_provided must match operator_approval_ref');
1335
+ if (readiness.external_wiring_ready !== expected.external_wiring_ready) throw new TypeError('readiness.external_wiring_ready must match blockers');
1336
+ if (readiness.customer_api_keys_live !== expected.customer_api_keys_live) throw new TypeError('readiness.customer_api_keys_live must match lifecycle evidence and operator approval');
1337
+ if (readiness.live_readiness_gate !== expected.live_readiness_gate) throw new TypeError('readiness.live_readiness_gate must match lifecycle evidence and operator approval');
1338
+ assertSameLifecycleArray(readiness.external_blockers, expected.external_blockers, 'readiness.external_blockers');
1339
+ assertSameLifecycleArray(readiness.missing_evidence_refs, expected.missing_evidence_refs, 'readiness.missing_evidence_refs');
1340
+ }
1341
+
1342
+ export function buildApiKeyLifecyclePacket(input = {}) {
1343
+ if (!isPlainObject(input)) throw new TypeError('buildApiKeyLifecyclePacket requires an options object');
1344
+ assertNoForbiddenPayload(input, 'input');
1345
+ assertNoApiKeySecretMaterial(input, 'input');
1346
+ const tenantId = requiredString(input.tenant_id ?? input.tenantId, 'tenant_id');
1347
+ const subjectRef = requiredString(input.subject_ref ?? input.subjectRef, 'subject_ref');
1348
+ const generatedAt = isoTimestamp(input.generated_at ?? input.generatedAt, 'generated_at');
1349
+ const operation = apiKeyLifecycleOperation(input);
1350
+ const keyContracts = apiKeyLifecycleContractsFrom(input, tenantId, subjectRef, generatedAt);
1351
+ const requiredEvidenceRefs = apiKeyLifecycleEvidenceRefsFrom(input, operation, keyContracts);
1352
+ const operatorApprovalRef = apiKeyLifecycleOperatorApprovalRefFromInput(input);
1353
+ const readiness = apiKeyLifecycleReadiness(operation, requiredEvidenceRefs, operatorApprovalRef);
1354
+ const body = {
1355
+ schema: HOSTED_CLOUD_API_KEY_LIFECYCLE_PACKET_SCHEMA,
1356
+ packet_id: stringOrDefault(input.packet_id ?? input.packetId, undefined),
1357
+ generated_at: generatedAt,
1358
+ tenant_id: tenantId,
1359
+ subject_ref: subjectRef,
1360
+ operation,
1361
+ key_contracts: keyContracts,
1362
+ key_metadata_refs: apiKeyLifecycleKeyMetadataRefs(operation, requiredEvidenceRefs),
1363
+ lifecycle_events: apiKeyLifecycleEvents(operation, requiredEvidenceRefs, keyContracts),
1364
+ required_evidence_refs: requiredEvidenceRefs,
1365
+ external_blockers: readiness.external_blockers,
1366
+ missing_evidence_refs: readiness.missing_evidence_refs,
1367
+ operator_approval_ref: operatorApprovalRef,
1368
+ readiness,
1369
+ customer_api_keys_live: readiness.customer_api_keys_live,
1370
+ guarantees: apiKeyLifecycleSafetyGuarantees(),
1371
+ public_safety_guarantees: apiKeyLifecycleSafetyGuarantees(),
1372
+ };
1373
+ const packet = withContractIdentity(body, 'hcaklp', 'packet_id');
1374
+ validateApiKeyLifecyclePacket(packet);
1375
+ return packet;
1376
+ }
1377
+
1378
+ export function validateApiKeyLifecyclePacket(packet) {
1379
+ if (!isPlainObject(packet)) throw new TypeError('packet must be an object');
1380
+ assertNoForbiddenPayload(packet, 'packet');
1381
+ assertNoApiKeySecretMaterial(packet, 'packet');
1382
+ if (packet.schema !== HOSTED_CLOUD_API_KEY_LIFECYCLE_PACKET_SCHEMA) throw new TypeError(`schema must be ${HOSTED_CLOUD_API_KEY_LIFECYCLE_PACKET_SCHEMA}`);
1383
+ requiredString(packet.packet_id, 'packet_id');
1384
+ isoTimestamp(packet.generated_at, 'generated_at');
1385
+ requiredString(packet.contract_hash, 'contract_hash');
1386
+ const tenantId = requiredString(packet.tenant_id, 'tenant_id');
1387
+ const subjectRef = requiredString(packet.subject_ref, 'subject_ref');
1388
+ const operation = apiKeyLifecycleOperation(packet);
1389
+ validateApiKeyLifecycleContracts(packet.key_contracts, tenantId, subjectRef);
1390
+ const requiredEvidenceRefs = validateApiKeyLifecycleEvidenceRefs(packet.required_evidence_refs, operation);
1391
+ const operatorApprovalRef = apiKeyLifecycleOperatorApprovalRefFromPacket(packet);
1392
+ const expectedReadiness = apiKeyLifecycleReadiness(operation, requiredEvidenceRefs, operatorApprovalRef);
1393
+ assertSameLifecycleObject(packet.key_metadata_refs, apiKeyLifecycleKeyMetadataRefs(operation, requiredEvidenceRefs), 'key_metadata_refs');
1394
+ assertSameLifecycleArray(packet.lifecycle_events, apiKeyLifecycleEvents(operation, requiredEvidenceRefs, packet.key_contracts), 'lifecycle_events');
1395
+ assertSameLifecycleArray(packet.external_blockers, expectedReadiness.external_blockers, 'external_blockers');
1396
+ assertSameLifecycleArray(packet.missing_evidence_refs, expectedReadiness.missing_evidence_refs, 'missing_evidence_refs');
1397
+ validateApiKeyLifecycleReadiness(packet.readiness, expectedReadiness);
1398
+ if (packet.customer_api_keys_live !== expectedReadiness.customer_api_keys_live) throw new TypeError('customer_api_keys_live must match readiness.customer_api_keys_live');
1399
+ validateApiKeyLifecycleSafetyGuarantees(packet.guarantees ?? packet.public_safety_guarantees);
1400
+ validateApiKeyLifecycleSafetyGuarantees(packet.public_safety_guarantees ?? packet.guarantees);
1401
+ return true;
1402
+ }
@@ -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.9' });
20
+ const SERVER_INFO = Object.freeze({ name: 'enigma-mcp-server', version: '0.1.10' });
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,274 @@
1
+ #!/usr/bin/env node
2
+ import { createHash } from 'node:crypto';
3
+ import { writeFile } from 'node:fs/promises';
4
+ import { isAbsolute, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ import {
8
+ HOSTED_CLOUD_API_KEY_LIFECYCLE_OPERATIONS,
9
+ HOSTED_CLOUD_API_KEY_LIFECYCLE_PACKET_SCHEMA,
10
+ HOSTED_CLOUD_API_KEY_LIFECYCLE_PHASES,
11
+ buildApiKeyLifecyclePacket,
12
+ validateApiKeyLifecyclePacket,
13
+ } from '../packages/hosted-cloud/src/index.js';
14
+
15
+ export const HOSTED_API_KEY_LIFECYCLE_PACKET_SCHEMA = HOSTED_CLOUD_API_KEY_LIFECYCLE_PACKET_SCHEMA;
16
+ export const HOSTED_API_KEY_LIFECYCLE_RELEASE_TARGET = '0.1.10';
17
+
18
+ const PROVIDED = 'provided';
19
+ const BLOCKED = 'blocked_external_dependency';
20
+ const ALLOWED_OPERATIONS = new Set(HOSTED_CLOUD_API_KEY_LIFECYCLE_OPERATIONS);
21
+ const ALLOWED_STATUSES = new Set([PROVIDED, BLOCKED, 'blocked_missing_evidence']);
22
+ const STATUS_ALIASES = Object.freeze({
23
+ blocked: BLOCKED,
24
+ missing: BLOCKED,
25
+ blocked_missing_evidence: BLOCKED,
26
+ });
27
+
28
+ const OPERATION_EVIDENCE_KEYS = Object.freeze({
29
+ issue: Object.freeze(['next_key_metadata', 'issue_policy', 'audit_log']),
30
+ rotate: Object.freeze(['current_key_metadata', 'next_key_metadata', 'rotation_policy', 'audit_log']),
31
+ revoke: Object.freeze(['current_key_metadata', 'revoked_key_metadata', 'revocation_policy', 'audit_log']),
32
+ audit: Object.freeze(['current_key_metadata', 'audit_log']),
33
+ });
34
+
35
+ const ALL_EVIDENCE_KEYS = Object.freeze(HOSTED_CLOUD_API_KEY_LIFECYCLE_PHASES);
36
+ const ALL_EVIDENCE_KEY_SET = new Set(ALL_EVIDENCE_KEYS);
37
+
38
+ 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[\s_-]*memory|plaintext[\s_-]*prompts?|plain[\s_-]*text[\s_-]*prompts?|private[\s_-]*prompts?|provider[\s_-]*responses?|full[\s_-]*transcript|decrypted[\s_-]*memory|credentials?|secrets?|passwords?|private[\s_-]*keys?|api[\s_-]*key[\s_-]*(?:secret|material|value)|api[\s_-]*secrets?|access[\s_-]*tokens?|refresh[\s_-]*tokens?|token[\s_-]*values?|credential[\s_-]*material)\b)/iu;
39
+ 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;
40
+ const ABSOLUTE_LOCAL_PATH_RE = /^(?:[A-Za-z]:[\\/]|\\\\|\/(?:Users|home|tmp|var|etc|mnt|Volumes)\b)/u;
41
+
42
+ function assertPublicSafeString(value, label) {
43
+ if (typeof value !== 'string' || value.trim() === '') throw new Error(`${label} must be a non-empty public ref`);
44
+ const normalized = value.trim();
45
+ if (SECRET_VALUE_RE.test(normalized) || FORBIDDEN_CLAIM_RE.test(normalized) || ABSOLUTE_LOCAL_PATH_RE.test(normalized)) {
46
+ throw new Error(`${label} contains non-public hosted-cloud material`);
47
+ }
48
+ return normalized;
49
+ }
50
+
51
+ function assertRelativeOutFile(value) {
52
+ const out = assertPublicSafeString(value, 'output file');
53
+ if (isAbsolute(out) || ABSOLUTE_LOCAL_PATH_RE.test(out)) throw new Error('output file must be a relative path');
54
+ return out;
55
+ }
56
+
57
+ function normalizeStatus(status) {
58
+ const normalized = STATUS_ALIASES[status] ?? status;
59
+ if (!ALLOWED_STATUSES.has(normalized)) throw new Error('evidence status must be provided, blocked_external_dependency, or blocked_missing_evidence');
60
+ return normalized;
61
+ }
62
+
63
+ function defaultEvidenceRef(key) {
64
+ return {
65
+ status: BLOCKED,
66
+ ref: `blocked:${key}`,
67
+ blocker: `Public-safe ${key.replaceAll('_', ' ')} evidence is not provided.`,
68
+ };
69
+ }
70
+
71
+ function normalizeEvidenceRecord(key, record) {
72
+ const status = normalizeStatus(record.status ?? PROVIDED);
73
+ const ref = assertPublicSafeString(record.ref, 'evidence ref');
74
+ const output = { status, ref };
75
+ if (status !== PROVIDED) output.blocker = record.blocker ?? defaultEvidenceRef(key).blocker;
76
+ return output;
77
+ }
78
+
79
+ export function parseEvidenceRef(value) {
80
+ const raw = assertPublicSafeString(value, 'evidence ref argument');
81
+ const equalsIndex = raw.indexOf('=');
82
+ if (equalsIndex <= 0 || equalsIndex === raw.length - 1) throw new Error('evidence refs must use key=status:ref');
83
+ const key = raw.slice(0, equalsIndex).trim();
84
+ if (!ALL_EVIDENCE_KEY_SET.has(key)) throw new Error(`evidence key must be one of: ${ALL_EVIDENCE_KEYS.join(', ')}`);
85
+ const body = raw.slice(equalsIndex + 1).trim();
86
+ const colonIndex = body.indexOf(':');
87
+ if (colonIndex > 0) {
88
+ const possibleStatus = body.slice(0, colonIndex);
89
+ if (ALLOWED_STATUSES.has(possibleStatus) || STATUS_ALIASES[possibleStatus]) {
90
+ return { key, ...normalizeEvidenceRecord(key, { status: possibleStatus, ref: body.slice(colonIndex + 1) }) };
91
+ }
92
+ }
93
+ return { key, ...normalizeEvidenceRecord(key, { status: PROVIDED, ref: body }) };
94
+ }
95
+
96
+ function evidenceRefsFor(operation, records = []) {
97
+ const requiredKeys = OPERATION_EVIDENCE_KEYS[operation];
98
+ const refs = Object.fromEntries(requiredKeys.map((key) => [key, defaultEvidenceRef(key)]));
99
+ for (const rawRecord of records) {
100
+ const record = typeof rawRecord === 'string' ? parseEvidenceRef(rawRecord) : rawRecord;
101
+ if (!requiredKeys.includes(record.key)) throw new Error(`evidence key ${record.key} is not required for ${operation}`);
102
+ refs[record.key] = normalizeEvidenceRecord(record.key, record);
103
+ }
104
+ return refs;
105
+ }
106
+
107
+ function providedRef(evidenceRefs, key) {
108
+ const evidence = evidenceRefs[key];
109
+ return evidence?.status === PROVIDED ? evidence.ref : null;
110
+ }
111
+
112
+ function fingerprintFromRef(ref, label) {
113
+ if (!ref) return `blocked:${label}`;
114
+ return `sha256:${createHash('sha256').update(`${label}\0${ref}`).digest('hex')}`;
115
+ }
116
+
117
+ function scopeList(scopes = []) {
118
+ if (!Array.isArray(scopes) || scopes.length === 0) return ['blocked:scope'];
119
+ return Object.freeze(scopes.map((scope, index) => assertPublicSafeString(scope, `scope ${index + 1}`)));
120
+ }
121
+
122
+
123
+ function keyInputsFor({ operation, tenantId, subjectRef, scopes, generatedAt, evidenceRefs }) {
124
+ const base = { tenant_id: tenantId, subject_ref: subjectRef, scopes, issued_at: generatedAt };
125
+ const currentRef = providedRef(evidenceRefs, 'current_key_metadata');
126
+ const nextRef = providedRef(evidenceRefs, 'next_key_metadata');
127
+ const revokedRef = providedRef(evidenceRefs, 'revoked_key_metadata');
128
+ const issueRef = providedRef(evidenceRefs, 'issue_policy') ?? nextRef;
129
+ const rotationRef = providedRef(evidenceRefs, 'rotation_policy') ?? currentRef;
130
+ const revokeRef = providedRef(evidenceRefs, 'revocation_policy') ?? revokedRef;
131
+ const inputs = {};
132
+
133
+ if (operation === 'issue') {
134
+ if (nextRef) inputs.next_key = { ...base, key_fingerprint: fingerprintFromRef(nextRef, 'issue_next_key'), rotation_ref: issueRef ?? 'blocked:issue_policy' };
135
+ return inputs;
136
+ }
137
+ if (operation === 'rotate') {
138
+ if (currentRef) inputs.current_key = { ...base, key_fingerprint: fingerprintFromRef(currentRef, 'rotate_current_key'), rotation_ref: rotationRef ?? 'blocked:rotation_policy' };
139
+ if (nextRef) inputs.next_key = { ...base, key_fingerprint: fingerprintFromRef(nextRef, 'rotate_next_key'), rotation_ref: rotationRef ?? 'blocked:rotation_policy' };
140
+ return inputs;
141
+ }
142
+ if (operation === 'revoke') {
143
+ if (currentRef) inputs.current_key = { ...base, key_fingerprint: fingerprintFromRef(currentRef, 'revoke_current_key'), rotation_ref: revokeRef ?? 'blocked:revocation_policy' };
144
+ if (revokedRef) inputs.revoked_key = { ...base, key_fingerprint: fingerprintFromRef(revokedRef, 'revoke_key'), rotation_ref: revokeRef ?? 'blocked:revocation_policy' };
145
+ return inputs;
146
+ }
147
+ if (currentRef) inputs.current_key = { ...base, key_fingerprint: fingerprintFromRef(currentRef, 'audit_key'), rotation_ref: providedRef(evidenceRefs, 'audit_log') ?? 'blocked:audit_log' };
148
+ return inputs;
149
+ }
150
+
151
+ function scriptBoundary() {
152
+ return {
153
+ public_safe_packet_only: true,
154
+ evidence_validation_only: true,
155
+ provider_wiring_performed: false,
156
+ deploys_or_calls_external_providers: false,
157
+ sensitive_material_written: false,
158
+ customer_api_key_material_written: false,
159
+ customer_content_written: false,
160
+ };
161
+ }
162
+
163
+ export function buildHostedApiKeyLifecyclePacket(options = {}) {
164
+ const generatedAt = options.generatedAt ?? new Date().toISOString();
165
+ const tenantId = assertPublicSafeString(options.tenant ?? options.tenantId ?? 'blocked:tenant', 'tenant');
166
+ const subjectRef = assertPublicSafeString(options.subject ?? options.subjectRef ?? 'blocked:subject', 'subject');
167
+ const operation = options.operation ?? 'audit';
168
+ if (!ALLOWED_OPERATIONS.has(operation)) throw new Error('operation must be issue, rotate, revoke, or audit');
169
+ const scopes = scopeList(options.scopes ?? options.scope ?? []);
170
+ const evidenceRefs = evidenceRefsFor(operation, options.evidenceRefs ?? []);
171
+ const operatorApprovalRef = options.operatorApprovalRef === undefined
172
+ ? null
173
+ : assertPublicSafeString(options.operatorApprovalRef, 'operator approval ref');
174
+ const packet = buildApiKeyLifecyclePacket({
175
+ tenant_id: tenantId,
176
+ subject_ref: subjectRef,
177
+ generated_at: generatedAt,
178
+ operation,
179
+ ...keyInputsFor({ operation, tenantId, subjectRef, scopes, generatedAt, evidenceRefs }),
180
+ required_evidence_refs: evidenceRefs,
181
+ operator_approval_ref: operatorApprovalRef,
182
+ });
183
+ validateApiKeyLifecyclePacket(packet);
184
+ return {
185
+ ...packet,
186
+ schema: HOSTED_CLOUD_API_KEY_LIFECYCLE_PACKET_SCHEMA,
187
+ release_target: HOSTED_API_KEY_LIFECYCLE_RELEASE_TARGET,
188
+ tenant_id: tenantId,
189
+ subject_ref: subjectRef,
190
+ operation,
191
+ scopes,
192
+ boundary: scriptBoundary(),
193
+ };
194
+ }
195
+
196
+ export function parseArgs(argv) {
197
+ const args = {
198
+ tenant: undefined,
199
+ subject: undefined,
200
+ operation: undefined,
201
+ scopes: [],
202
+ evidenceRefs: [],
203
+ operatorApprovalRef: undefined,
204
+ out: undefined,
205
+ help: false,
206
+ };
207
+ for (let index = 0; index < argv.length; index += 1) {
208
+ const arg = argv[index];
209
+ if (arg === '--help' || arg === '-h') {
210
+ args.help = true;
211
+ continue;
212
+ }
213
+ const readValue = (name) => {
214
+ index += 1;
215
+ if (index >= argv.length || argv[index].startsWith('--')) throw new Error(`${name} requires a value`);
216
+ return argv[index];
217
+ };
218
+ if (arg === '--tenant') args.tenant = assertPublicSafeString(readValue('--tenant'), 'tenant');
219
+ else if (arg === '--subject') args.subject = assertPublicSafeString(readValue('--subject'), 'subject');
220
+ else if (arg === '--operation') args.operation = assertPublicSafeString(readValue('--operation'), 'operation');
221
+ else if (arg === '--scope') args.scopes.push(assertPublicSafeString(readValue('--scope'), `scope ${args.scopes.length + 1}`));
222
+ else if (arg === '--evidence-ref') args.evidenceRefs.push(parseEvidenceRef(readValue('--evidence-ref')));
223
+ else if (arg === '--operator-approval-ref') args.operatorApprovalRef = assertPublicSafeString(readValue('--operator-approval-ref'), 'operator approval ref');
224
+ else if (arg === '--out') args.out = assertRelativeOutFile(readValue('--out'));
225
+ else throw new Error('unknown option; use --help');
226
+ }
227
+ return args;
228
+ }
229
+
230
+ export function usage() {
231
+ return `Usage: node scripts/build-hosted-api-key-lifecycle.mjs [options]
232
+
233
+ Build a public-safe hosted API key lifecycle evidence packet. The script validates metadata, fingerprints, and opaque refs only; it does not issue keys, call providers, create accounts, revoke provider credentials, or write secret material.
234
+
235
+ Options:
236
+ --tenant <id> Tenant id. Defaults to blocked:tenant.
237
+ --subject <ref> Subject ref. Defaults to blocked:subject.
238
+ --operation <issue|rotate|revoke|audit>
239
+ Lifecycle operation. Defaults to audit.
240
+ --scope <scope> Repeatable public-safe scope. Defaults to blocked:scope.
241
+ --evidence-ref <key=status:ref> Repeatable evidence ref. key=<ref> implies provided.
242
+ Status: provided, blocked_external_dependency, blocked_missing_evidence.
243
+ Keys: ${ALL_EVIDENCE_KEYS.join(', ')}
244
+ --operator-approval-ref <ref> Operator approval evidence ref.
245
+ --out <file> Also write the packet JSON to a relative file path.
246
+ --help Show this help.
247
+ `;
248
+ }
249
+
250
+ export async function main(argv = process.argv.slice(2)) {
251
+ const args = parseArgs(argv);
252
+ if (args.help) {
253
+ process.stdout.write(usage());
254
+ return 0;
255
+ }
256
+ const packet = buildHostedApiKeyLifecyclePacket(args);
257
+ const json = `${JSON.stringify(packet, null, 2)}\n`;
258
+ if (args.out) {
259
+ try {
260
+ await writeFile(args.out, json, 'utf8');
261
+ } catch {
262
+ throw new Error('failed to write API key lifecycle packet output');
263
+ }
264
+ }
265
+ process.stdout.write(json);
266
+ return 0;
267
+ }
268
+
269
+ if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
270
+ main().catch((error) => {
271
+ process.stderr.write(`Error: ${error?.message ?? 'failed to build API key lifecycle packet'}\n`);
272
+ process.exitCode = 1;
273
+ });
274
+ }
@@ -25,10 +25,10 @@ import {
25
25
  buildUserAccountContract,
26
26
  buildCustomerLifecyclePacket,
27
27
  validateCustomerLifecyclePacket,
28
- } from '../packages/hosted-cloud/src/index.js';
28
+ } from 'enigma-memory/hosted-cloud';
29
29
 
30
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';
31
+ export const HOSTED_CUSTOMER_LIFECYCLE_RELEASE_TARGET = '0.1.10';
32
32
 
33
33
  const PROVIDED = 'provided';
34
34
  const BLOCKED_MISSING = 'blocked_missing_evidence';
@@ -181,8 +181,11 @@ export function parseEvidenceRef(value) {
181
181
  function normalizeEvidenceRefs(records = []) {
182
182
  const refs = Object.fromEntries(HOSTED_CUSTOMER_LIFECYCLE_EVIDENCE_KEYS.map((key) => [key, defaultEvidenceRef(key)]));
183
183
  for (const rawRecord of records) {
184
- const record = typeof rawRecord === 'string' ? parseEvidenceRef(rawRecord) : rawRecord;
185
- refs[record.key] = normalizeEvidenceRecord(record.key, record);
184
+ const parsedRecord = typeof rawRecord === 'string' ? parseEvidenceRef(rawRecord) : rawRecord;
185
+ const key = EVIDENCE_KEY_ALIASES[parsedRecord.key] ?? parsedRecord.key;
186
+ if (!HOSTED_CUSTOMER_LIFECYCLE_EVIDENCE_KEYS.includes(key)) throw new Error(`evidence key must be one of: ${HOSTED_CUSTOMER_LIFECYCLE_EVIDENCE_KEYS.join(', ')}`);
187
+ const record = { ...parsedRecord, key };
188
+ refs[key] = normalizeEvidenceRecord(key, record);
186
189
  }
187
190
  return refs;
188
191
  }
@@ -195,46 +198,6 @@ function operatorEvidenceRefsFrom(evidenceRefs) {
195
198
  }));
196
199
  }
197
200
 
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
201
  function refFor(evidenceRefs, key) {
239
202
  return evidenceRefs[key].ref;
240
203
  }
@@ -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.9';
9
+ export const INSTALLER_ASSET_VERSION = '0.1.10';
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.9',
998
+ version: '0.1.10',
999
999
  },
1000
1000
  public_safe: true,
1001
1001
  top_k: topK,