enigma-memory 0.1.18 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/README.md +76 -24
  2. package/apps/cli/bin/enigma-desktop.mjs +140 -0
  3. package/apps/cli/bin/enigma-terminal.mjs +78 -0
  4. package/apps/cli/bin/enigma.mjs +1923 -285
  5. package/apps/desktop/electron-main.cjs +217 -0
  6. package/apps/desktop/package.json +12 -0
  7. package/apps/desktop/src/app.js +264 -7
  8. package/apps/desktop/src/index.html +3514 -1373
  9. package/apps/desktop/src/launch-electron.mjs +51 -0
  10. package/apps/desktop/src/server.mjs +2914 -0
  11. package/apps/desktop/src/styles.css +2972 -260
  12. package/apps/desktop/src/zk-browser-prove.mjs +53 -0
  13. package/apps/desktop/src/zk-state.mjs +1789 -0
  14. package/apps/gateway/bin/enigma-gateway.mjs +102 -5
  15. package/apps/gateway/src/server.mjs +271 -8
  16. package/apps/ios/EnigmaCore/Package.swift +12 -0
  17. package/apps/ios/EnigmaCore/Sources/EnigmaCore/EnigmaAPIClient.swift +227 -0
  18. package/apps/ios/EnigmaCore/Sources/EnigmaCore/Models.swift +278 -0
  19. package/apps/ios/EnigmaCore/Sources/EnigmaCore/PKCE.swift +96 -0
  20. package/apps/ios/EnigmaCore/Sources/EnigmaCore/PrivacyMinimizer.swift +187 -0
  21. package/apps/ios/EnigmaCore/Sources/EnigmaCore/ToolModels.swift +129 -0
  22. package/apps/ios/EnigmaCore/Tests/EnigmaCoreTests/EnigmaCoreTests.swift +42 -0
  23. package/apps/ios/EnigmaIOS/Enigma/AppModel.swift +346 -0
  24. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AccentColor.colorset/Contents.json +12 -0
  25. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/Contents.json +11 -0
  26. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/EnigmaAppIcon.png +0 -0
  27. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/Contents.json +3 -0
  28. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/LaunchBackground.colorset/Contents.json +12 -0
  29. package/apps/ios/EnigmaIOS/Enigma/ChatView.swift +181 -0
  30. package/apps/ios/EnigmaIOS/Enigma/CouncilView.swift +78 -0
  31. package/apps/ios/EnigmaIOS/Enigma/CreateView.swift +152 -0
  32. package/apps/ios/EnigmaIOS/Enigma/EnigmaApp.swift +52 -0
  33. package/apps/ios/EnigmaIOS/Enigma/Info.plist +52 -0
  34. package/apps/ios/EnigmaIOS/Enigma/NaturalLanguagePrivacyTagger.swift +26 -0
  35. package/apps/ios/EnigmaIOS/Enigma/OAuthClient.swift +321 -0
  36. package/apps/ios/EnigmaIOS/Enigma/OnboardingView.swift +105 -0
  37. package/apps/ios/EnigmaIOS/Enigma/PrivateVaultView.swift +275 -0
  38. package/apps/ios/EnigmaIOS/Enigma/SecureStore.swift +76 -0
  39. package/apps/ios/EnigmaIOS/Enigma/SettingsView.swift +60 -0
  40. package/apps/ios/EnigmaIOS/Enigma/Theme.swift +80 -0
  41. package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/project.pbxproj +211 -0
  42. package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/xcshareddata/xcschemes/Enigma.xcscheme +23 -0
  43. package/apps/native-host/README.md +19 -8
  44. package/apps/native-host/bin/enigma-native-host.mjs +229 -13
  45. package/apps/relay/bin/enigma-relay.mjs +103 -5
  46. package/apps/relay/src/federation-runtime.mjs +618 -0
  47. package/apps/relay/src/server.mjs +310 -9
  48. package/apps/verifier/bin/enigma-verify.mjs +327 -11
  49. package/cortex-v3/circuits/build/intent_vk_bytes.json +35 -0
  50. package/cortex-v3/circuits/build/sale_vk_bytes.json +35 -0
  51. package/cortex-v3/circuits/build/vk_bytes.json +32 -0
  52. package/cortex-v3/proving-assets.json +64 -0
  53. package/cortex-v3/zk/BUILD-CONTRACT.md +87 -0
  54. package/cortex-v3/zk/action-transition-vk.json +119 -0
  55. package/cortex-v3/zk/alias-adversarial.test.mjs +220 -0
  56. package/cortex-v3/zk/groth16-verify-child.mjs +17 -0
  57. package/cortex-v3/zk/intent-witness.mjs +365 -0
  58. package/cortex-v3/zk/intent-witness.test.mjs +485 -0
  59. package/cortex-v3/zk/proving-assets.mjs +203 -0
  60. package/cortex-v3/zk/sale-witness.mjs +783 -0
  61. package/cortex-v3/zk/sale-witness.test.mjs +784 -0
  62. package/cortex-v3/zk/sealed-sale-release-vk.json +119 -0
  63. package/cortex-v3/zk/settlement-evidence.mjs +722 -0
  64. package/cortex-v3/zk/setup-intent.mjs +688 -0
  65. package/cortex-v3/zk/setup-sale.mjs +666 -0
  66. package/cortex-v3/zk/setup.mjs +594 -0
  67. package/cortex-v3/zk/witness.mjs +184 -0
  68. package/cortex-v3/zk/zk-codec.mjs +232 -0
  69. package/cortex-v3/zk/zk-codec.test.mjs +293 -0
  70. package/cortex-v3/zk/zk-settle.mjs +370 -0
  71. package/cortex-v3/zk/zk-tree.mjs +256 -0
  72. package/cortex-v3/zk/zk-tree.test.mjs +419 -0
  73. package/deploy/docker-compose.local-production-simulation.yml +36 -0
  74. package/docs/browser-extension-install.md +8 -6
  75. package/docs/client-connectors.md +15 -11
  76. package/docs/developer-ecosystem.md +15 -13
  77. package/docs/enigma-memory-ready-conformance.md +11 -9
  78. package/docs/install-anywhere.md +61 -28
  79. package/docs/installers-and-desktop.md +8 -7
  80. package/docs/novelty-invention-candidates.md +161 -161
  81. package/docs/proof-network-claim-boundaries.md +320 -318
  82. package/examples/01-quickstart-agent/index.mjs +49 -0
  83. package/examples/01_agent_memory_quickstart.mjs +57 -0
  84. package/examples/02-multi-agent-swarm/index.mjs +57 -0
  85. package/examples/02_cross_model_passport.mjs +64 -0
  86. package/examples/03-langchain-memory/index.mjs +41 -0
  87. package/examples/03_poseidon_commitment_verification.mjs +71 -0
  88. package/examples/04-python-trading-agent/trader.py +49 -0
  89. package/examples/README.md +27 -0
  90. package/examples/ci/github-actions.yml +7 -2
  91. package/package.json +142 -11
  92. package/packages/adapters/PACKAGE_CONTRACT.md +1 -1
  93. package/packages/connectors/src/index.js +196 -4
  94. package/packages/connectors/swarm-router.mjs +168 -0
  95. package/packages/core/src/index.js +248 -1
  96. package/packages/core/src/version.mjs +7 -0
  97. package/packages/dev-tools/package.json +19 -0
  98. package/packages/dev-tools/src/index.js +4 -0
  99. package/packages/dev-tools/src/memory-benchmark-suite.js +112 -0
  100. package/packages/dev-tools/src/swarm-simulator.js +101 -0
  101. package/packages/dev-tools/src/vault-inspector.js +114 -0
  102. package/packages/dev-tools/src/vector-benchmark.js +100 -0
  103. package/packages/developer-platform/src/access-credentials.js +341 -0
  104. package/packages/developer-platform/src/http.js +132 -0
  105. package/packages/developer-platform/src/index.js +4 -0
  106. package/packages/developer-platform/src/usage-http.js +60 -0
  107. package/packages/developer-platform/src/usage.js +295 -0
  108. package/packages/enclave-runtime/attestation.mjs +159 -0
  109. package/packages/enclave-runtime/index.mjs +47 -0
  110. package/packages/enclave-runtime/session-manager.mjs +253 -0
  111. package/packages/enclave-runtime/zeroization-proof.mjs +227 -0
  112. package/packages/enigma-reflex/package.json +14 -0
  113. package/packages/enigma-reflex/src/index.js +204 -0
  114. package/packages/enigma-reflex/training/generate-dataset.mjs +40 -0
  115. package/packages/enigma-reflex/training/requirements.txt +8 -0
  116. package/packages/enigma-reflex/training/train.py +314 -0
  117. package/packages/enigma-weave/LICENSE +22 -0
  118. package/packages/enigma-weave/UPSTREAM.json +21 -0
  119. package/packages/enigma-weave/package.json +14 -0
  120. package/packages/enigma-weave/src/index.js +286 -0
  121. package/packages/hosted-cloud/src/index.js +80 -5
  122. package/packages/importers/src/index.js +432 -0
  123. package/packages/inference-runtime/src/browser.js +401 -0
  124. package/packages/inference-runtime/src/chat.js +265 -0
  125. package/packages/inference-runtime/src/code.js +407 -0
  126. package/packages/inference-runtime/src/contracts.js +162 -0
  127. package/packages/inference-runtime/src/http.js +232 -0
  128. package/packages/inference-runtime/src/image.js +186 -0
  129. package/packages/inference-runtime/src/index.js +10 -0
  130. package/packages/inference-runtime/src/model-router.js +320 -0
  131. package/packages/inference-runtime/src/platform.js +125 -0
  132. package/packages/inference-runtime/src/privacy.js +400 -0
  133. package/packages/inference-runtime/src/video.js +253 -0
  134. package/packages/mcp-server/README.md +22 -6
  135. package/packages/mcp-server/bin/enigma-mcp.mjs +2 -1
  136. package/packages/mcp-server/src/index.js +1418 -105
  137. package/packages/mcp-server/src/oauth.js +561 -0
  138. package/packages/mcp-server/src/private-handoff.js +84 -0
  139. package/packages/mcp-server/src/remote-http.js +273 -0
  140. package/packages/mcp-server/src/remote-policy.js +72 -0
  141. package/packages/mcp-server/swarm-bridge.mjs +361 -0
  142. package/packages/mesh/index.d.ts +283 -0
  143. package/packages/mesh/package.json +23 -0
  144. package/packages/mesh/src/crypto.js +189 -0
  145. package/packages/mesh/src/federation-packets.js +353 -0
  146. package/packages/mesh/src/gossip.js +311 -0
  147. package/packages/mesh/src/index.js +6 -0
  148. package/packages/mesh/src/protocol.js +255 -0
  149. package/packages/mesh/src/router.js +279 -0
  150. package/packages/mesh/src/transport.js +306 -0
  151. package/packages/passport/src/index.js +426 -1
  152. package/packages/private-economy/src/credits-http.js +100 -0
  153. package/packages/private-economy/src/credits.js +447 -0
  154. package/packages/private-economy/src/index.js +5 -0
  155. package/packages/private-economy/src/payments-http.js +120 -0
  156. package/packages/private-economy/src/payments.js +509 -0
  157. package/packages/private-economy/src/x402.js +346 -0
  158. package/packages/proof-network/PACKAGE_CONTRACT.md +21 -0
  159. package/packages/rag/index.d.ts +182 -0
  160. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/THIRD_PARTY_LICENSES.txt +207 -0
  161. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/config.json +25 -0
  162. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/onnx/model_quantized.onnx +0 -0
  163. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/sha256-manifest.json +28 -0
  164. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer.json +30686 -0
  165. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer_config.json +15 -0
  166. package/packages/rag/package.json +27 -0
  167. package/packages/rag/src/blinded-search.js +109 -0
  168. package/packages/rag/src/bm25.js +169 -0
  169. package/packages/rag/src/embeddings.js +459 -0
  170. package/packages/rag/src/hybrid.js +76 -0
  171. package/packages/rag/src/index.js +38 -0
  172. package/packages/rag/src/reranker.js +61 -0
  173. package/packages/rag/src/research.js +107 -0
  174. package/packages/rag/src/vector-store.js +430 -0
  175. package/packages/rag/src/verify-model-artifacts.mjs +4 -0
  176. package/packages/sdk/index.d.ts +760 -0
  177. package/packages/sdk/package.json +33 -0
  178. package/packages/sdk/python/README.md +24 -0
  179. package/packages/sdk/python/enigma_sdk.py +250 -0
  180. package/packages/sdk/python/pyproject.toml +34 -0
  181. package/packages/sdk/python/requirements.txt +1 -0
  182. package/packages/sdk/python/setup.py +20 -0
  183. package/packages/sdk/src/federation/capability-grant.js +389 -0
  184. package/packages/sdk/src/federation/federation-router.js +360 -0
  185. package/packages/sdk/src/federation/ghostmesh-bridge.js +497 -0
  186. package/packages/sdk/src/federation/index.js +3 -0
  187. package/packages/sdk/src/index.js +1796 -0
  188. package/packages/sdk/src/intelligence/contradiction.js +337 -0
  189. package/packages/sdk/src/intelligence/decision-engine.js +155 -0
  190. package/packages/sdk/src/intelligence/index.js +4 -0
  191. package/packages/sdk/src/intelligence/ontology.js +122 -0
  192. package/packages/sdk/src/intelligence/temporal.js +123 -0
  193. package/packages/sdk/src/market-client.js +142 -0
  194. package/packages/sdk/src/mesh-client.js +110 -0
  195. package/packages/sdk/src/middleware/index.js +3 -0
  196. package/packages/sdk/src/middleware/langchain.js +159 -0
  197. package/packages/sdk/src/middleware/llamaindex.js +101 -0
  198. package/packages/sdk/src/middleware/vercel-ai.js +112 -0
  199. package/packages/sdk/src/rag-client.js +85 -0
  200. package/packages/sdk/src/swarm-orchestrator.js +260 -0
  201. package/packages/settlement/PACKAGE_CONTRACT.md +1 -1
  202. package/packages/snapcompact/THIRD_PARTY_LICENSES.txt +40 -0
  203. package/packages/snapcompact/assets/8x13-latin1.bdf +3837 -0
  204. package/packages/snapcompact/index.d.ts +284 -0
  205. package/packages/snapcompact/package.json +25 -0
  206. package/packages/snapcompact/src/index.js +716 -0
  207. package/packages/storage/PACKAGE_CONTRACT.md +1 -1
  208. package/packages/terminal-console/animations.mjs +240 -0
  209. package/packages/terminal-console/auto-anchor.mjs +220 -0
  210. package/packages/terminal-console/banner.mjs +91 -0
  211. package/packages/terminal-console/commands.mjs +459 -0
  212. package/packages/terminal-console/delegation.mjs +152 -0
  213. package/packages/terminal-console/index.mjs +5 -0
  214. package/packages/terminal-console/outbox.mjs +143 -0
  215. package/packages/terminal-console/phantom-bridge.mjs +637 -0
  216. package/packages/terminal-console/repl.mjs +136 -0
  217. package/packages/terminal-console/signer-store.mjs +130 -0
  218. package/packages/terminal-console/solana-rpc.mjs +214 -0
  219. package/packages/terminal-console/solana-transport.mjs +189 -0
  220. package/packages/terminal-tui/dashboard.mjs +214 -0
  221. package/packages/terminal-tui/index.mjs +28 -0
  222. package/packages/terminal-tui/merkle-tree-renderer.mjs +268 -0
  223. package/packages/terminal-tui/telemetry-hud.mjs +137 -0
  224. package/packages/vault/index.d.ts +449 -0
  225. package/packages/vault/package.json +27 -0
  226. package/packages/vault/src/e2ee.mjs +393 -0
  227. package/packages/vault/src/enclave.js +481 -0
  228. package/packages/vault/src/erasure.js +207 -0
  229. package/packages/vault/src/index.js +1018 -155
  230. package/packages/vault/src/persistence.js +307 -0
  231. package/packages/vault/src/poseidon.js +354 -0
  232. package/packages/vault/src/receipt.js +459 -0
  233. package/scripts/benchmark-optical-context.mjs +166 -0
  234. package/scripts/bootstrap-enigma.mjs +502 -0
  235. package/scripts/build-edge-backend-workers.mjs +20 -5
  236. package/scripts/build-goal-completion-audit.mjs +72 -25
  237. package/scripts/build-hosted-api-key-lifecycle.mjs +26 -8
  238. package/scripts/build-hosted-customer-lifecycle.mjs +20 -3
  239. package/scripts/build-hosted-probe-worker.mjs +19 -4
  240. package/scripts/build-installer-assets.mjs +41 -21
  241. package/scripts/build-operator-evidence-starter.mjs +59 -1
  242. package/scripts/build-production-backend-env-kit.mjs +2 -0
  243. package/scripts/build-production-unblocker.mjs +3 -0
  244. package/scripts/check.mjs +17 -3
  245. package/scripts/collect-hosted-backend-live-evidence.mjs +49 -12
  246. package/scripts/install-enigma-local.mjs +18 -5
  247. package/scripts/release-audit.mjs +65 -109
  248. package/scripts/release-provenance.mjs +6 -0
  249. package/scripts/run-backend-readiness-smoke.mjs +112 -10
  250. package/scripts/scan-secrets.mjs +1 -0
  251. package/scripts/simulate-production-env.mjs +7 -2
  252. package/scripts/validate-hosted-backend-live.mjs +112 -1
  253. package/specs/antibody-pack-v1.schema.json +95 -0
  254. package/specs/antigen-envelope-v1.schema.json +81 -0
  255. package/specs/boundary-manifest-v1.schema.json +35 -35
  256. package/specs/capsule-v1.schema.json +55 -55
  257. package/specs/claim-boundary-manifest-v1.schema.json +22 -22
  258. package/specs/claim-ledger-v1.schema.json +291 -0
  259. package/specs/context-passport-v1.schema.json +59 -0
  260. package/specs/deletion-tombstone-v1.schema.json +26 -26
  261. package/specs/evidence-packet-v1.schema.json +177 -0
  262. package/specs/hosted-backend-live-evidence-v1.schema.json +72 -3
  263. package/specs/immune-scan-report-v1.schema.json +112 -0
  264. package/specs/lifecycle-receipt-log-v1.schema.json +67 -0
  265. package/specs/memory-atom-v1.schema.json +59 -0
  266. package/specs/memory-event-v1.schema.json +42 -42
  267. package/specs/passport-v1.schema.json +50 -50
  268. package/specs/proof-of-non-use-v1.schema.json +65 -0
  269. package/specs/quarantine-record-v1.schema.json +126 -0
  270. package/specs/receipt-v1.schema.json +61 -61
  271. package/specs/state-checkpoint-v1.schema.json +37 -37
  272. package/specs/trust-bundle-v1.schema.json +56 -56
  273. package/specs/trust-card-v1.schema.json +119 -0
  274. package/docs/proof-network-launch-plan.md +0 -421
  275. package/packages/metering/PACKAGE_CONTRACT.md +0 -20
  276. package/scripts/build-ai-orchestration-plan.mjs +0 -248
@@ -0,0 +1,286 @@
1
+ import { randomUUID } from 'node:crypto';
2
+
3
+ export const ENIGMA_WEAVE_VERSION = '1.0.0';
4
+ export const WEAVE_POLICIES = Object.freeze({
5
+ lean: Object.freeze({ maxCalls: 1, maxLatencyMs: 45_000 }),
6
+ balanced: Object.freeze({ maxCalls: 2, maxLatencyMs: 60_000 }),
7
+ strict: Object.freeze({ maxCalls: 3, maxLatencyMs: 120_000 }),
8
+ });
9
+ export const WEAVE_PIPELINES = Object.freeze({
10
+ general: Object.freeze({ name: 'general', direct: 'Answer directly and completely. Keep the result proportionate to the request.' }),
11
+ research: Object.freeze({ name: 'research', direct: 'Return a complete research synthesis separating evidence, inference, unknowns, and implications.' }),
12
+ 'code-review': Object.freeze({ name: 'code-review', direct: 'Return a senior engineering review with concrete failure modes, fixes, and regression checks.' }),
13
+ });
14
+
15
+ function positiveInteger(value, fallback, field) {
16
+ const candidate = value === undefined ? fallback : value;
17
+ if (!Number.isInteger(candidate) || candidate <= 0) throw new TypeError(`${field} must be a positive integer`);
18
+ return candidate;
19
+ }
20
+
21
+ function nonNegativeNumber(value, fallback, field) {
22
+ const candidate = value === undefined ? fallback : value;
23
+ if (!Number.isFinite(candidate) || candidate < 0) throw new TypeError(`${field} must be a non-negative number`);
24
+ return candidate;
25
+ }
26
+
27
+ function normalizeWorker(worker, index) {
28
+ if (!worker || typeof worker !== 'object') throw new TypeError(`worker ${index} must be an object`);
29
+ const id = String(worker.id || '').trim();
30
+ const model = String(worker.model || '').trim();
31
+ if (!id || !model) throw new TypeError(`worker ${index} requires id and model`);
32
+ return Object.freeze({
33
+ id,
34
+ model,
35
+ role: String(worker.role || id),
36
+ estimatedCredits: nonNegativeNumber(worker.estimatedCredits, 0, `worker ${id} estimatedCredits`),
37
+ retentionPolicy: String(worker.retentionPolicy || 'unknown'),
38
+ retentionEvidence: worker.retentionEvidence || null,
39
+ });
40
+ }
41
+
42
+ function pickWorker(workers, preferredRoles) {
43
+ for (const role of preferredRoles) {
44
+ const match = workers.find((worker) => worker.role === role || worker.id === role);
45
+ if (match) return match;
46
+ }
47
+ if (!workers[0]) throw new Error('Enigma Weave has no workers');
48
+ return workers[0];
49
+ }
50
+
51
+ export function assessWeaveTask(input, pipelineName = 'general') {
52
+ const text = `${pipelineName}\n${input}`.toLowerCase();
53
+ const signals = [
54
+ ['security', /\b(security|vulnerab|exploit|attack|ssrf|xss|csrf|injection|secret|credential)\b/],
55
+ ['identity', /\b(auth|authorization|permission|tenant|access control|token|session)\b/],
56
+ ['money', /\b(billing|payment|credit|wallet|spend|refund|invoice|double-spend)\b/],
57
+ ['privacy', /\b(privacy|pii|personal data|retention|redact|data leak)\b/],
58
+ ['concurrency', /\b(race|concurren|idempot|replay|transaction|atomic)\b/],
59
+ ['production-code', /\b(api|database|migration|deploy|production|code review|github|repo)\b/],
60
+ ].filter(([, expression]) => expression.test(text)).map(([signal]) => signal);
61
+ const research = pipelineName === 'research' || /\b(research|paper|benchmark|evidence|study|compare)\b/.test(text);
62
+ const codeReview = pipelineName === 'code-review' || signals.length > 0;
63
+ const simple = input.length < 180 && pipelineName === 'general'
64
+ && /^(name|list|say|give|what is|what's|define|translate|explain in one|one|short)\b/i.test(input.trim())
65
+ && !/\b(review|audit|investigate|compare|debug|implement|patch|design)\b/i.test(input);
66
+ if (codeReview) return { kind: 'code-review', complexity: 'high', risk: 'high', riskSignals: signals };
67
+ if (research) return { kind: 'research', complexity: 'medium', risk: 'medium', riskSignals: signals };
68
+ if (simple || input.length < 180) return { kind: 'general', complexity: 'low', risk: 'low', riskSignals: signals };
69
+ return { kind: 'general', complexity: 'medium', risk: 'medium', riskSignals: signals };
70
+ }
71
+
72
+ export function resolveWeaveLimits(input = {}) {
73
+ const policy = String(input.policy || 'balanced').toLowerCase();
74
+ const defaults = WEAVE_POLICIES[policy];
75
+ if (!defaults) throw new TypeError('policy must be lean, balanced, or strict');
76
+ const maxCalls = positiveInteger(input.maxCalls, defaults.maxCalls, 'maxCalls');
77
+ const maxLatencyMs = positiveInteger(input.maxLatencyMs, defaults.maxLatencyMs, 'maxLatencyMs');
78
+ const maxCredits = input.maxCredits === undefined ? null : nonNegativeNumber(input.maxCredits, 0, 'maxCredits');
79
+ return Object.freeze({ policy, maxCalls, maxLatencyMs, maxCredits });
80
+ }
81
+
82
+ function buildPlan(profile, policy, workers, pipeline) {
83
+ const directWorker = profile.kind === 'general'
84
+ ? pickWorker(workers, ['finalizer', 'drafter', 'critic'])
85
+ : pickWorker(workers, ['drafter', 'finalizer', 'critic']);
86
+ const steps = [{ id: 'direct', role: 'drafter', worker: directWorker, access: [], instruction: pipeline.direct }];
87
+ if (policy === 'lean') return steps;
88
+ const critic = pickWorker(workers, ['critic', 'finalizer', 'drafter']);
89
+ if (policy === 'balanced') {
90
+ steps.push({
91
+ id: 'verify',
92
+ role: 'editor',
93
+ worker: critic,
94
+ access: ['direct'],
95
+ instruction: 'Audit the direct answer for critical omissions or unsupported claims, then return the complete corrected final answer.',
96
+ });
97
+ return steps;
98
+ }
99
+ const finalizer = pickWorker(workers, ['finalizer', 'drafter', 'critic']);
100
+ steps.push(
101
+ { id: 'verify', role: 'critic', worker: critic, access: ['direct'], instruction: 'Adversarially audit the direct answer. Identify concrete errors, missing risks, and missing checks.' },
102
+ { id: 'final', role: 'finalizer', worker: finalizer, access: ['direct', 'verify'], instruction: 'Return the complete final answer, incorporating only valid verifier findings and removing weak claims.' },
103
+ );
104
+ return steps;
105
+ }
106
+
107
+ function shouldEscalate(input, profile, answer, policy) {
108
+ if (policy === 'lean') return false;
109
+ if (policy === 'strict' || profile.risk === 'high' || profile.kind === 'research') return true;
110
+ if (profile.risk === 'medium' && /\b(review|audit|investigate|research|compare|verify|debug|design)\b/i.test(input)) return true;
111
+ const text = String(answer || '').trim();
112
+ return !text || /^\s*(i can't|i cannot|sorry|unable to)/i.test(text);
113
+ }
114
+
115
+ function retentionDisclosure(trace) {
116
+ const routes = trace.map((item) => ({
117
+ step: item.id,
118
+ worker: item.workerId,
119
+ model: item.model,
120
+ retentionPolicy: item.retentionPolicy,
121
+ retentionEvidence: item.retentionEvidence,
122
+ status: item.status,
123
+ }));
124
+ const policies = new Set(routes.map((route) => route.retentionPolicy || 'unknown'));
125
+ return {
126
+ policy: policies.has('unknown') ? 'unknown' : policies.size === 1 ? [...policies][0] : 'mixed',
127
+ routes,
128
+ };
129
+ }
130
+
131
+ function publicPlan(steps) {
132
+ return steps.map((step) => ({ id: step.id, role: step.role, workerId: step.worker.id, model: step.worker.model, access: step.access }));
133
+ }
134
+
135
+ export function createEnigmaWeave(options = {}) {
136
+ if (typeof options.complete !== 'function') throw new TypeError('createEnigmaWeave requires complete(worker, request)');
137
+ const workers = (options.workers || []).map(normalizeWorker);
138
+ if (!workers.length) throw new TypeError('createEnigmaWeave requires workers');
139
+
140
+ return Object.freeze({
141
+ workers,
142
+ async run(input, runOptions = {}) {
143
+ if (typeof input !== 'string' || !input.trim()) throw new TypeError('input must be a non-empty string');
144
+ const pipeline = WEAVE_PIPELINES[runOptions.pipeline || 'general'];
145
+ if (!pipeline) throw new TypeError(`unknown pipeline: ${runOptions.pipeline}`);
146
+ const limits = resolveWeaveLimits(runOptions);
147
+ const profile = assessWeaveTask(input, pipeline.name);
148
+ const planned = buildPlan(profile, limits.policy, workers, pipeline);
149
+ const outputs = new Map();
150
+ const trace = [];
151
+ const startedAt = Date.now();
152
+ let estimatedCredits = 0;
153
+ let stopReason = null;
154
+ let limited = false;
155
+ runOptions.onEvent?.({ type: 'plan', pipeline: pipeline.name, policy: limits.policy, steps: publicPlan(planned), profile });
156
+
157
+ for (const step of planned) {
158
+ if (trace.length >= limits.maxCalls) { limited = true; stopReason = 'max-calls-reached'; break; }
159
+ if (Date.now() - startedAt >= limits.maxLatencyMs) { limited = true; stopReason = 'max-latency-reached'; break; }
160
+ if (limits.maxCredits !== null && estimatedCredits + step.worker.estimatedCredits > limits.maxCredits) {
161
+ limited = true;
162
+ stopReason = 'max-credits-reached';
163
+ break;
164
+ }
165
+ const accessed = Object.fromEntries(step.access.map((id) => [id, outputs.get(id)]));
166
+ const messages = [
167
+ { role: 'system', content: step.instruction },
168
+ { role: 'user', content: input },
169
+ ...(Object.keys(accessed).length ? [{ role: 'user', content: `Prior step outputs:\n${JSON.stringify(accessed)}` }] : []),
170
+ ];
171
+ const stepStartedAt = Date.now();
172
+ runOptions.onEvent?.({ type: 'step.started', id: step.id, workerId: step.worker.id, model: step.worker.model });
173
+ let result;
174
+ try {
175
+ result = await options.complete(step.worker, { messages, input, step: { id: step.id, role: step.role }, signal: runOptions.signal });
176
+ if (!result || typeof result.answer !== 'string') throw new Error(`worker ${step.worker.id} returned no answer`);
177
+ outputs.set(step.id, result.answer);
178
+ estimatedCredits += step.worker.estimatedCredits;
179
+ trace.push({
180
+ id: step.id,
181
+ workerId: step.worker.id,
182
+ model: step.worker.model,
183
+ status: 'succeeded',
184
+ elapsedMs: Date.now() - stepStartedAt,
185
+ usage: result.usage || null,
186
+ spentCredits: Number(result.spentCredits || 0),
187
+ retentionPolicy: result.retentionPolicy || step.worker.retentionPolicy,
188
+ retentionEvidence: result.retentionEvidence || step.worker.retentionEvidence,
189
+ });
190
+ runOptions.onEvent?.({ type: 'step.done', id: step.id, workerId: step.worker.id });
191
+ } catch (error) {
192
+ trace.push({
193
+ id: step.id,
194
+ workerId: step.worker.id,
195
+ model: step.worker.model,
196
+ status: 'failed',
197
+ elapsedMs: Date.now() - stepStartedAt,
198
+ usage: null,
199
+ spentCredits: 0,
200
+ retentionPolicy: step.worker.retentionPolicy,
201
+ retentionEvidence: step.worker.retentionEvidence,
202
+ error: error.code || 'worker_failed',
203
+ });
204
+ runOptions.onEvent?.({ type: 'step.failed', id: step.id, workerId: step.worker.id, code: error.code || 'worker_failed' });
205
+ if (step.id === 'direct') throw error;
206
+ break;
207
+ }
208
+ if (step.id === 'direct' && !shouldEscalate(input, profile, result.answer, limits.policy)) {
209
+ stopReason = 'direct-answer-accepted';
210
+ break;
211
+ }
212
+ }
213
+
214
+ const answer = outputs.get('final') || outputs.get('verify') || outputs.get('direct') || '';
215
+ const usage = trace.reduce((sum, item) => {
216
+ sum.promptTokens += Number(item.usage?.prompt_tokens || item.usage?.input_tokens || 0);
217
+ sum.completionTokens += Number(item.usage?.completion_tokens || item.usage?.output_tokens || 0);
218
+ sum.totalTokens += Number(item.usage?.total_tokens || 0);
219
+ sum.spentCredits += item.spentCredits;
220
+ return sum;
221
+ }, { promptTokens: 0, completionTokens: 0, totalTokens: 0, spentCredits: 0 });
222
+ const retention = retentionDisclosure(trace);
223
+ return {
224
+ pipeline: `${pipeline.name}:enigma-weave-${limits.policy}`,
225
+ answer,
226
+ trace,
227
+ metrics: { calls: trace.length, elapsedMs: Date.now() - startedAt, ...usage },
228
+ workflow: {
229
+ schema: 'enigma.weave_workflow.v1',
230
+ mode: 'adaptive',
231
+ policy: limits.policy,
232
+ router: 'local-policy',
233
+ profile,
234
+ plan: publicPlan(planned).slice(0, trace.length),
235
+ escalated: trace.length > 1,
236
+ budget: { limits, used: { calls: trace.length, estimatedCredits, latencyMs: Date.now() - startedAt }, limited, stopReason },
237
+ retention,
238
+ receipt: {
239
+ schema: 'enigma.weave_receipt.v1',
240
+ receiptId: randomUUID(),
241
+ stepCount: trace.length,
242
+ callCount: trace.length,
243
+ spentCredits: usage.spentCredits,
244
+ containsPromptOrOutput: false,
245
+ },
246
+ },
247
+ };
248
+ },
249
+ });
250
+ }
251
+
252
+ export function fuseWeaveCandidates(candidates, options = {}) {
253
+ if (!Array.isArray(candidates) || !candidates.length) throw new TypeError('candidates must be a non-empty array');
254
+ const valid = candidates.filter((candidate) => candidate && typeof candidate.answer === 'string' && candidate.answer.trim());
255
+ if (!valid.length) throw new Error('no usable candidate answer');
256
+ const ranked = valid.map((candidate, index) => ({
257
+ ...candidate,
258
+ index,
259
+ score: Number(candidate.score || 0) + Math.min(candidate.answer.length, 2_000) / 2_000,
260
+ })).sort((a, b) => b.score - a.score || a.index - b.index);
261
+ return options.returnAll ? ranked : ranked[0];
262
+ }
263
+
264
+ export async function evaluateEnigmaWeave(weave, dataset, options = {}) {
265
+ if (!weave || typeof weave.run !== 'function') throw new TypeError('weave must expose run()');
266
+ if (!Array.isArray(dataset) || !dataset.length) throw new TypeError('dataset must be a non-empty array');
267
+ const cases = [];
268
+ for (const item of dataset) {
269
+ const id = String(item.id || `case-${cases.length + 1}`);
270
+ const startedAt = Date.now();
271
+ try {
272
+ const result = await weave.run(String(item.input || ''), options);
273
+ const passed = typeof item.validate === 'function' ? await item.validate(result.answer, result) : Boolean(result.answer);
274
+ cases.push({ id, passed: Boolean(passed), elapsedMs: Date.now() - startedAt, calls: result.metrics.calls, receiptId: result.workflow.receipt.receiptId });
275
+ } catch (error) {
276
+ cases.push({ id, passed: false, elapsedMs: Date.now() - startedAt, calls: 0, error: error.code || 'evaluation_failed' });
277
+ }
278
+ }
279
+ return {
280
+ schema: 'enigma.weave_evaluation.v1',
281
+ total: cases.length,
282
+ passed: cases.filter((item) => item.passed).length,
283
+ failed: cases.filter((item) => !item.passed).length,
284
+ cases,
285
+ };
286
+ }
@@ -209,6 +209,14 @@ function hashValue(value) {
209
209
  return `${SHA256_PREFIX}${createHash('sha256').update(JSON.stringify(canonicalize(value))).digest('hex')}`;
210
210
  }
211
211
 
212
+ function validateContractHash(contract, name = 'contract') {
213
+ const actual = requiredString(contract.contract_hash, `${name}.contract_hash`);
214
+ const { contract_hash: _contractHash, ...canonicalFields } = contract;
215
+ const expected = hashValue(canonicalFields);
216
+ if (actual !== expected) throw new TypeError(`${name}.contract_hash must match canonical contract fields`);
217
+ return true;
218
+ }
219
+
212
220
  function contractId(prefix, body) {
213
221
  return `${prefix}_${hashValue(body).slice(SHA256_PREFIX.length, SHA256_PREFIX.length + 24)}`;
214
222
  }
@@ -316,7 +324,7 @@ function validateBase(contract, schema) {
316
324
  throw new TypeError('readiness.external_blockers must match operator_evidence_refs');
317
325
  }
318
326
  });
319
- requiredString(contract.contract_hash, 'contract_hash');
327
+ validateContractHash(contract);
320
328
  return true;
321
329
  }
322
330
 
@@ -670,6 +678,19 @@ function lifecycleContractSource(input, aliases) {
670
678
  function buildOrValidateLifecycleContract(phase, source) {
671
679
  if (source === undefined || source === null) return null;
672
680
  if (!isPlainObject(source)) throw new TypeError(`contracts.${phase} must be an object`);
681
+ if (source.schema !== undefined) {
682
+ const expectedSchema = {
683
+ account: HOSTED_CLOUD_USER_ACCOUNT_SCHEMA,
684
+ tenant: HOSTED_CLOUD_TENANT_SCHEMA,
685
+ vault: HOSTED_CLOUD_VAULT_SCHEMA,
686
+ api_key: HOSTED_CLOUD_API_KEY_SCHEMA,
687
+ billing: HOSTED_CLOUD_USAGE_BILLING_SCHEMA,
688
+ dashboard: HOSTED_CLOUD_DASHBOARD_SCHEMA,
689
+ backup: HOSTED_CLOUD_BACKUP_DRILL_SCHEMA,
690
+ incident_sla: HOSTED_CLOUD_INCIDENT_SLA_SCHEMA,
691
+ }[phase];
692
+ if (source.schema !== expectedSchema) throw new TypeError(`contracts.${phase}.schema must be ${expectedSchema}`);
693
+ }
673
694
  switch (phase) {
674
695
  case 'account':
675
696
  if (source.schema === HOSTED_CLOUD_USER_ACCOUNT_SCHEMA) {
@@ -737,6 +758,29 @@ function lifecycleContractsFrom(input) {
737
758
  };
738
759
  }
739
760
 
761
+ function lifecycleTenantId(input, contracts) {
762
+ const explicitTenantId = input.tenant_id ?? input.tenantId;
763
+ const contractTenantIds = CUSTOMER_LIFECYCLE_CONTRACT_PHASES
764
+ .map((phase) => contracts[phase]?.tenant_id)
765
+ .filter((tenantId) => tenantId !== undefined && tenantId !== null);
766
+ const tenantId = requiredString(explicitTenantId ?? contractTenantIds[0], 'tenant_id');
767
+ for (const contractTenantId of contractTenantIds) {
768
+ if (requiredString(contractTenantId, 'contracts.tenant_id') !== tenantId) {
769
+ throw new TypeError('every tenant-bearing lifecycle contract must match packet tenant_id');
770
+ }
771
+ }
772
+ return tenantId;
773
+ }
774
+
775
+ function validateLifecycleTenantIdentity(contracts, tenantId) {
776
+ for (const phase of CUSTOMER_LIFECYCLE_CONTRACT_PHASES) {
777
+ const contract = contracts[phase];
778
+ if (contract !== null && contract.tenant_id !== tenantId) {
779
+ throw new TypeError(`contracts.${phase}.tenant_id must match packet tenant_id`);
780
+ }
781
+ }
782
+ }
783
+
740
784
  function explicitLifecycleEvidenceRefs(input) {
741
785
  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;
742
786
  if (refs === undefined || refs === null) return {};
@@ -1057,6 +1101,7 @@ export function buildCustomerLifecyclePacket(input = {}) {
1057
1101
  if (!isPlainObject(input)) throw new TypeError('buildCustomerLifecyclePacket requires an options object');
1058
1102
  assertNoForbiddenPayload(input, 'input');
1059
1103
  const contracts = lifecycleContractsFrom(input);
1104
+ const tenantId = lifecycleTenantId(input, contracts);
1060
1105
  const requiredEvidenceRefs = lifecycleEvidenceRefsFrom(input, contracts);
1061
1106
  const operatorGoLiveRef = operatorGoLiveRefFromInput(input);
1062
1107
  const missingSurfaceRefs = missingLifecycleSurfaceRefs(contracts);
@@ -1065,6 +1110,7 @@ export function buildCustomerLifecyclePacket(input = {}) {
1065
1110
  schema: HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PACKET_SCHEMA,
1066
1111
  packet_id: stringOrDefault(input.packet_id ?? input.packetId, undefined),
1067
1112
  generated_at: isoTimestamp(input.generated_at ?? input.generatedAt, 'generated_at'),
1113
+ tenant_id: tenantId,
1068
1114
  contracts,
1069
1115
  required_evidence_refs: requiredEvidenceRefs,
1070
1116
  lifecycle_phases: lifecyclePhaseRows(requiredEvidenceRefs, contracts, operatorGoLiveRef),
@@ -1089,8 +1135,9 @@ export function validateCustomerLifecyclePacket(packet) {
1089
1135
  if (packet.schema !== HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PACKET_SCHEMA) throw new TypeError(`schema must be ${HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PACKET_SCHEMA}`);
1090
1136
  requiredString(packet.packet_id, 'packet_id');
1091
1137
  isoTimestamp(packet.generated_at, 'generated_at');
1092
- requiredString(packet.contract_hash, 'contract_hash');
1138
+ const tenantId = requiredString(packet.tenant_id, 'tenant_id');
1093
1139
  validateCustomerLifecycleContracts(packet.contracts);
1140
+ validateLifecycleTenantIdentity(packet.contracts, tenantId);
1094
1141
  if (!isPlainObject(packet.required_evidence_refs)) throw new TypeError('required_evidence_refs must be present');
1095
1142
  const requiredEvidenceRefs = validateCustomerLifecycleEvidenceRefs({ ...packet.required_evidence_refs });
1096
1143
  const operatorGoLiveRef = operatorGoLiveRefFromPacket(packet);
@@ -1104,6 +1151,7 @@ export function validateCustomerLifecyclePacket(packet) {
1104
1151
  if (packet.hosted_cloud_sellable !== expectedReadiness.hosted_cloud_sellable) throw new TypeError('hosted_cloud_sellable must match readiness.hosted_cloud_sellable');
1105
1152
  assertSameLifecycleObject(packet.dashboard, customerLifecycleDashboard(packet.contracts, requiredEvidenceRefs, expectedReadiness, operatorGoLiveRef), 'dashboard');
1106
1153
  validateCustomerLifecycleSafetyGuarantees(packet.guarantees ?? packet.public_safety_guarantees);
1154
+ validateContractHash(packet, 'packet');
1107
1155
  return true;
1108
1156
  }
1109
1157
 
@@ -1179,6 +1227,9 @@ function buildOrValidateApiKeyLifecycleContract(slot, source, tenantId, subjectR
1179
1227
  if (source === undefined || source === null) return null;
1180
1228
  if (!isPlainObject(source)) throw new TypeError(`key_contracts.${slot} must be an object`);
1181
1229
  const name = `key_contracts.${slot}`;
1230
+ if (source.schema !== undefined && source.schema !== HOSTED_CLOUD_API_KEY_SCHEMA) {
1231
+ throw new TypeError(`${name}.schema must be ${HOSTED_CLOUD_API_KEY_SCHEMA}`);
1232
+ }
1182
1233
  if (source.schema === HOSTED_CLOUD_API_KEY_SCHEMA) {
1183
1234
  validateApiKeyContract(source);
1184
1235
  validateApiKeyLifecycleContractTenantSubject(source, tenantId, subjectRef, name);
@@ -1493,6 +1544,7 @@ export function validateApiKeyLifecyclePacket(packet) {
1493
1544
  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');
1494
1545
  validateApiKeyLifecycleSafetyGuarantees(packet.guarantees ?? packet.public_safety_guarantees);
1495
1546
  validateApiKeyLifecycleSafetyGuarantees(packet.public_safety_guarantees ?? packet.guarantees);
1547
+ validateContractHash(packet, 'packet');
1496
1548
  return true;
1497
1549
  }
1498
1550
 
@@ -1756,6 +1808,22 @@ function validateReadinessEvidenceRefs(packet) {
1756
1808
  return requiredEvidenceRefs;
1757
1809
  }
1758
1810
 
1811
+ function readinessPacketTenantId(input, customerLifecyclePacket, apiKeyLifecyclePacket) {
1812
+ const candidates = [
1813
+ input.tenant_id ?? input.tenantId,
1814
+ customerLifecyclePacket?.tenant_id,
1815
+ apiKeyLifecyclePacket?.tenant_id,
1816
+ ].filter((tenantId) => tenantId !== undefined && tenantId !== null);
1817
+ if (candidates.length === 0) return null;
1818
+ const tenantId = requiredString(candidates[0], 'tenant_id');
1819
+ for (const candidate of candidates.slice(1)) {
1820
+ if (requiredString(candidate, 'embedded packet tenant_id') !== tenantId) {
1821
+ throw new TypeError('customer and API key lifecycle packets must share one tenant_id');
1822
+ }
1823
+ }
1824
+ return tenantId;
1825
+ }
1826
+
1759
1827
  export function buildHostedCloudReadinessPacket(input = {}) {
1760
1828
  if (!isPlainObject(input)) throw new TypeError('buildHostedCloudReadinessPacket requires an options object');
1761
1829
  assertNoForbiddenPayload(input, 'input');
@@ -1773,6 +1841,7 @@ export function buildHostedCloudReadinessPacket(input = {}) {
1773
1841
  lifecycleSellable = providedLifecyclePacket.hosted_cloud_sellable;
1774
1842
  lifecycleEvidenceRefs = providedLifecyclePacket.required_evidence_refs;
1775
1843
  lifecycleRef = {
1844
+ tenant_id: providedLifecyclePacket.tenant_id,
1776
1845
  schema: providedLifecyclePacket.schema,
1777
1846
  packet_id: providedLifecyclePacket.packet_id,
1778
1847
  contract_hash: providedLifecyclePacket.contract_hash,
@@ -1785,12 +1854,14 @@ export function buildHostedCloudReadinessPacket(input = {}) {
1785
1854
  validateApiKeyLifecyclePacket(providedApiKeyPacket);
1786
1855
  apiKeyOperatorApprovalRef = providedApiKeyPacket.operator_approval_ref;
1787
1856
  apiKeyLifecycleRef = {
1857
+ tenant_id: providedApiKeyPacket.tenant_id,
1788
1858
  schema: providedApiKeyPacket.schema,
1789
1859
  packet_id: providedApiKeyPacket.packet_id,
1790
1860
  contract_hash: providedApiKeyPacket.contract_hash,
1791
1861
  operator_approval_ref: providedApiKeyPacket.operator_approval_ref,
1792
1862
  };
1793
1863
  }
1864
+ const tenantId = readinessPacketTenantId(input, providedLifecyclePacket, providedApiKeyPacket);
1794
1865
  const lifecycleExternalBlockers = (providedLifecyclePacket && isPlainObject(providedLifecyclePacket)) ? providedLifecyclePacket.external_blockers : [];
1795
1866
  const apiKeyExternalBlockers = (providedApiKeyPacket && isPlainObject(providedApiKeyPacket)) ? providedApiKeyPacket.external_blockers : [];
1796
1867
  const propagatedBlockers = [
@@ -1803,6 +1874,7 @@ export function buildHostedCloudReadinessPacket(input = {}) {
1803
1874
  schema: HOSTED_CLOUD_READINESS_PACKET_SCHEMA,
1804
1875
  readiness_id: stringOrDefault(input.readiness_id ?? input.readinessId, undefined),
1805
1876
  generated_at: generatedAt,
1877
+ tenant_id: tenantId,
1806
1878
  customer_lifecycle_packet: lifecycleRef,
1807
1879
  api_key_lifecycle_packet: apiKeyLifecycleRef,
1808
1880
  required_evidence_refs: requiredEvidenceRefs,
@@ -1828,21 +1900,24 @@ export function validateHostedCloudReadinessPacket(packet) {
1828
1900
  if (packet.schema !== HOSTED_CLOUD_READINESS_PACKET_SCHEMA) throw new TypeError(`schema must be ${HOSTED_CLOUD_READINESS_PACKET_SCHEMA}`);
1829
1901
  requiredString(packet.readiness_id, 'readiness_id');
1830
1902
  isoTimestamp(packet.generated_at, 'generated_at');
1831
- requiredString(packet.contract_hash, 'contract_hash');
1903
+ validateContractHash(packet, 'packet');
1904
+ const tenantId = packet.tenant_id === null ? null : requiredString(packet.tenant_id, 'tenant_id');
1832
1905
  if (!Object.prototype.hasOwnProperty.call(packet, 'customer_lifecycle_packet')) throw new TypeError('customer_lifecycle_packet must be present');
1833
1906
  if (packet.customer_lifecycle_packet !== null) {
1834
1907
  if (!isPlainObject(packet.customer_lifecycle_packet)) throw new TypeError('customer_lifecycle_packet must be an object or null');
1835
- requiredString(packet.customer_lifecycle_packet.schema, 'customer_lifecycle_packet.schema');
1908
+ if (packet.customer_lifecycle_packet.schema !== HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PACKET_SCHEMA) throw new TypeError(`customer_lifecycle_packet.schema must be ${HOSTED_CLOUD_CUSTOMER_LIFECYCLE_PACKET_SCHEMA}`);
1836
1909
  requiredString(packet.customer_lifecycle_packet.packet_id, 'customer_lifecycle_packet.packet_id');
1837
1910
  requiredString(packet.customer_lifecycle_packet.contract_hash, 'customer_lifecycle_packet.contract_hash');
1838
1911
  requiredBoolean(packet.customer_lifecycle_packet.hosted_cloud_sellable, 'customer_lifecycle_packet.hosted_cloud_sellable');
1912
+ if (requiredString(packet.customer_lifecycle_packet.tenant_id, 'customer_lifecycle_packet.tenant_id') !== tenantId) throw new TypeError('customer_lifecycle_packet.tenant_id must match readiness packet tenant_id');
1839
1913
  }
1840
1914
  if (!Object.prototype.hasOwnProperty.call(packet, 'api_key_lifecycle_packet')) throw new TypeError('api_key_lifecycle_packet must be present');
1841
1915
  if (packet.api_key_lifecycle_packet !== null) {
1842
1916
  if (!isPlainObject(packet.api_key_lifecycle_packet)) throw new TypeError('api_key_lifecycle_packet must be an object or null');
1843
- requiredString(packet.api_key_lifecycle_packet.schema, 'api_key_lifecycle_packet.schema');
1917
+ if (packet.api_key_lifecycle_packet.schema !== HOSTED_CLOUD_API_KEY_LIFECYCLE_PACKET_SCHEMA) throw new TypeError(`api_key_lifecycle_packet.schema must be ${HOSTED_CLOUD_API_KEY_LIFECYCLE_PACKET_SCHEMA}`);
1844
1918
  requiredString(packet.api_key_lifecycle_packet.packet_id, 'api_key_lifecycle_packet.packet_id');
1845
1919
  requiredString(packet.api_key_lifecycle_packet.contract_hash, 'api_key_lifecycle_packet.contract_hash');
1920
+ if (requiredString(packet.api_key_lifecycle_packet.tenant_id, 'api_key_lifecycle_packet.tenant_id') !== tenantId) throw new TypeError('api_key_lifecycle_packet.tenant_id must match readiness packet tenant_id');
1846
1921
  if (!Object.prototype.hasOwnProperty.call(packet.api_key_lifecycle_packet, 'operator_approval_ref')) throw new TypeError('api_key_lifecycle_packet.operator_approval_ref must be present');
1847
1922
  if (packet.api_key_lifecycle_packet.operator_approval_ref !== null) {
1848
1923
  requirePublicSafeLifecycleRef(packet.api_key_lifecycle_packet.operator_approval_ref, 'api_key_lifecycle_packet.operator_approval_ref');