holosphere 2.0.0-alpha0 → 2.0.0-alpha10

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 (418) hide show
  1. package/CHANGELOG.md +473 -0
  2. package/FEATURES.md +431 -0
  3. package/LICENSE +29 -42
  4. package/LICENSE-AGPL.md +180 -0
  5. package/README.md +97 -16
  6. package/dist/2019-D2OG2idw.js +6680 -0
  7. package/dist/2019-D2OG2idw.js.map +1 -0
  8. package/dist/2019-EION3wKo.cjs +8 -0
  9. package/dist/2019-EION3wKo.cjs.map +1 -0
  10. package/dist/_commonjsHelpers-C37NGDzP.cjs +2 -0
  11. package/dist/_commonjsHelpers-C37NGDzP.cjs.map +1 -0
  12. package/dist/_commonjsHelpers-CUmg6egw.js +7 -0
  13. package/dist/_commonjsHelpers-CUmg6egw.js.map +1 -0
  14. package/dist/browser-BSniCNqO.js +3058 -0
  15. package/dist/browser-BSniCNqO.js.map +1 -0
  16. package/dist/browser-Cq59Ij19.cjs +2 -0
  17. package/dist/browser-Cq59Ij19.cjs.map +1 -0
  18. package/dist/cdn/holosphere.min.js +55 -0
  19. package/dist/cdn/holosphere.min.js.map +1 -0
  20. package/dist/cjs/holosphere.cjs +2 -0
  21. package/dist/cjs/holosphere.cjs.map +1 -0
  22. package/dist/esm/holosphere.js +53 -0
  23. package/dist/esm/holosphere.js.map +1 -0
  24. package/dist/index-DDGt_V9o.cjs +12 -0
  25. package/dist/index-DDGt_V9o.cjs.map +1 -0
  26. package/dist/index-DJXftyvB.js +39841 -0
  27. package/dist/index-DJXftyvB.js.map +1 -0
  28. package/dist/index-DMbdcMtK.cjs +18 -0
  29. package/dist/index-DMbdcMtK.cjs.map +1 -0
  30. package/dist/index-DeZ1xz_s.js +15104 -0
  31. package/dist/index-DeZ1xz_s.js.map +1 -0
  32. package/dist/indexeddb-storage-BFt6hMeF.js +176 -0
  33. package/dist/indexeddb-storage-BFt6hMeF.js.map +1 -0
  34. package/dist/indexeddb-storage-BK5tv4Sh.cjs +2 -0
  35. package/dist/indexeddb-storage-BK5tv4Sh.cjs.map +1 -0
  36. package/dist/memory-storage-C9HuoL2E.js +91 -0
  37. package/dist/memory-storage-C9HuoL2E.js.map +1 -0
  38. package/dist/memory-storage-Dao7jfYG.cjs +2 -0
  39. package/dist/memory-storage-Dao7jfYG.cjs.map +1 -0
  40. package/dist/secp256k1-BbKzbLtD.cjs +12 -0
  41. package/dist/secp256k1-BbKzbLtD.cjs.map +1 -0
  42. package/dist/secp256k1-CreY7Pcl.js +1890 -0
  43. package/dist/secp256k1-CreY7Pcl.js.map +1 -0
  44. package/docs/CONTRACTS.md +797 -0
  45. package/docs/FOSDEM_PROPOSAL.md +388 -0
  46. package/docs/LOCALFIRST.md +266 -0
  47. package/docs/api/ai_aggregation.js.html +333 -0
  48. package/docs/api/ai_breakdown.js.html +524 -0
  49. package/docs/api/ai_classifier.js.html +231 -0
  50. package/docs/api/ai_council.js.html +246 -0
  51. package/docs/api/ai_embeddings.js.html +304 -0
  52. package/docs/api/ai_federation-ai.js.html +338 -0
  53. package/docs/api/ai_h3-ai.js.html +970 -0
  54. package/docs/api/ai_index.js.html +124 -0
  55. package/docs/api/ai_json-ops.js.html +241 -0
  56. package/docs/api/ai_llm-service.js.html +239 -0
  57. package/docs/api/ai_nl-query.js.html +236 -0
  58. package/docs/api/ai_relationships.js.html +367 -0
  59. package/docs/api/ai_schema-extractor.js.html +235 -0
  60. package/docs/api/ai_spatial.js.html +307 -0
  61. package/docs/api/ai_tts.js.html +214 -0
  62. package/docs/api/content_social-protocols.js.html +180 -0
  63. package/docs/api/core_holosphere.js.html +757 -0
  64. package/docs/api/crypto_nostr-utils.js.html +306 -0
  65. package/docs/api/crypto_secp256k1.js.html +267 -0
  66. package/docs/api/data/search.json +1 -0
  67. package/docs/api/federation_discovery.js.html +337 -0
  68. package/docs/api/federation_handshake.js.html +478 -0
  69. package/docs/api/federation_hologram.js.html +1053 -0
  70. package/docs/api/federation_registry.js.html +389 -0
  71. package/docs/api/fonts/Inconsolata-Regular.ttf +0 -0
  72. package/docs/api/fonts/OpenSans-Regular.ttf +0 -0
  73. package/docs/api/fonts/WorkSans-Bold.ttf +0 -0
  74. package/docs/api/global.html +3 -0
  75. package/docs/api/hierarchical_upcast.js.html +128 -0
  76. package/docs/api/index.html +265 -0
  77. package/docs/api/index.js.html +1868 -0
  78. package/docs/api/lib_ai-methods.js.html +660 -0
  79. package/docs/api/lib_contract-methods.js.html +445 -0
  80. package/docs/api/lib_errors.js.html +56 -0
  81. package/docs/api/lib_federation-methods.js.html +348 -0
  82. package/docs/api/lib_index.js.html +33 -0
  83. package/docs/api/module-ai.html +5 -0
  84. package/docs/api/module-ai_aggregation-SmartAggregation.html +6 -0
  85. package/docs/api/module-ai_aggregation.SmartAggregation.html +3 -0
  86. package/docs/api/module-ai_aggregation.html +3 -0
  87. package/docs/api/module-ai_breakdown-TaskBreakdown.html +5 -0
  88. package/docs/api/module-ai_breakdown.TaskBreakdown.html +3 -0
  89. package/docs/api/module-ai_breakdown.html +3 -0
  90. package/docs/api/module-ai_classifier-Classifier.html +6 -0
  91. package/docs/api/module-ai_classifier.Classifier.html +3 -0
  92. package/docs/api/module-ai_classifier.html +3 -0
  93. package/docs/api/module-ai_council-Council.html +6 -0
  94. package/docs/api/module-ai_council.Council.html +3 -0
  95. package/docs/api/module-ai_council.html +3 -0
  96. package/docs/api/module-ai_embeddings-Embeddings.html +5 -0
  97. package/docs/api/module-ai_embeddings.Embeddings.html +3 -0
  98. package/docs/api/module-ai_embeddings.html +3 -0
  99. package/docs/api/module-ai_federation-ai-FederationAdvisor.html +6 -0
  100. package/docs/api/module-ai_federation-ai.FederationAdvisor.html +3 -0
  101. package/docs/api/module-ai_federation-ai.html +3 -0
  102. package/docs/api/module-ai_h3-ai-H3AI.html +6 -0
  103. package/docs/api/module-ai_h3-ai.H3AI.html +3 -0
  104. package/docs/api/module-ai_h3-ai.html +3 -0
  105. package/docs/api/module-ai_json-ops-JSONOps.html +5 -0
  106. package/docs/api/module-ai_json-ops.JSONOps.html +3 -0
  107. package/docs/api/module-ai_json-ops.html +3 -0
  108. package/docs/api/module-ai_llm-service-LLMService.html +5 -0
  109. package/docs/api/module-ai_llm-service.LLMService.html +3 -0
  110. package/docs/api/module-ai_llm-service.html +3 -0
  111. package/docs/api/module-ai_nl-query-NLQuery.html +5 -0
  112. package/docs/api/module-ai_nl-query.NLQuery.html +3 -0
  113. package/docs/api/module-ai_nl-query.html +3 -0
  114. package/docs/api/module-ai_relationships-RelationshipDiscovery.html +6 -0
  115. package/docs/api/module-ai_relationships.RelationshipDiscovery.html +3 -0
  116. package/docs/api/module-ai_relationships.html +3 -0
  117. package/docs/api/module-ai_schema-extractor-SchemaExtractor.html +5 -0
  118. package/docs/api/module-ai_schema-extractor.SchemaExtractor.html +3 -0
  119. package/docs/api/module-ai_schema-extractor.html +3 -0
  120. package/docs/api/module-ai_spatial-SpatialAnalysis.html +6 -0
  121. package/docs/api/module-ai_spatial.SpatialAnalysis.html +3 -0
  122. package/docs/api/module-ai_spatial.html +3 -0
  123. package/docs/api/module-ai_tts-TTS.html +5 -0
  124. package/docs/api/module-ai_tts.TTS.html +3 -0
  125. package/docs/api/module-ai_tts.html +3 -0
  126. package/docs/api/module-content_social-protocols.html +3 -0
  127. package/docs/api/module-core_holosphere-HoloSphere.html +6 -0
  128. package/docs/api/module-core_holosphere.HoloSphere.html +3 -0
  129. package/docs/api/module-core_holosphere.html +3 -0
  130. package/docs/api/module-crypto_nostr-utils.html +3 -0
  131. package/docs/api/module-crypto_secp256k1.html +3 -0
  132. package/docs/api/module-federation_hologram.html +3 -0
  133. package/docs/api/module-hierarchical_upcast.html +3 -0
  134. package/docs/api/module-holosphere-HoloSphereBase.html +3 -0
  135. package/docs/api/module-holosphere.html +3 -0
  136. package/docs/api/module-lib_ai-methods.html +3 -0
  137. package/docs/api/module-lib_contract-methods.html +3 -0
  138. package/docs/api/module-lib_errors-AuthorizationError.html +3 -0
  139. package/docs/api/module-lib_errors-ValidationError.html +3 -0
  140. package/docs/api/module-lib_errors.AuthorizationError.html +3 -0
  141. package/docs/api/module-lib_errors.ValidationError.html +3 -0
  142. package/docs/api/module-lib_errors.html +3 -0
  143. package/docs/api/module-lib_federation-methods.html +3 -0
  144. package/docs/api/module-lib_index.html +3 -0
  145. package/docs/api/module-schema_validator-ValidationError.html +3 -0
  146. package/docs/api/module-schema_validator.ValidationError.html +3 -0
  147. package/docs/api/module-schema_validator.html +3 -0
  148. package/docs/api/module-spatial_h3-operations.html +4 -0
  149. package/docs/api/module-storage_backend-factory.BackendFactory.html +3 -0
  150. package/docs/api/module-storage_backend-factory.html +3 -0
  151. package/docs/api/module-storage_backend-interface-StorageBackend.html +3 -0
  152. package/docs/api/module-storage_backend-interface.StorageBackend.html +3 -0
  153. package/docs/api/module-storage_backend-interface.html +3 -0
  154. package/docs/api/module-storage_backends_activitypub-backend-ActivityPubBackend.html +7 -0
  155. package/docs/api/module-storage_backends_activitypub-backend.ActivityPubBackend.html +3 -0
  156. package/docs/api/module-storage_backends_activitypub-backend.html +3 -0
  157. package/docs/api/module-storage_backends_activitypub_server-ActivityPubServer.html +8 -0
  158. package/docs/api/module-storage_backends_activitypub_server.ActivityPubServer.html +3 -0
  159. package/docs/api/module-storage_backends_activitypub_server.html +3 -0
  160. package/docs/api/module-storage_backends_gundb-backend-GunDBBackend.html +7 -0
  161. package/docs/api/module-storage_backends_gundb-backend.GunDBBackend.html +3 -0
  162. package/docs/api/module-storage_backends_gundb-backend.html +3 -0
  163. package/docs/api/module-storage_backends_nostr-backend-NostrBackend.html +8 -0
  164. package/docs/api/module-storage_backends_nostr-backend.NostrBackend.html +3 -0
  165. package/docs/api/module-storage_backends_nostr-backend.html +3 -0
  166. package/docs/api/module-storage_filesystem-storage-FileSystemStorage.html +5 -0
  167. package/docs/api/module-storage_filesystem-storage-browser-FileSystemStorage.html +3 -0
  168. package/docs/api/module-storage_filesystem-storage-browser.FileSystemStorage.html +3 -0
  169. package/docs/api/module-storage_filesystem-storage-browser.html +3 -0
  170. package/docs/api/module-storage_filesystem-storage.FileSystemStorage.html +3 -0
  171. package/docs/api/module-storage_filesystem-storage.html +3 -0
  172. package/docs/api/module-storage_global-tables.html +3 -0
  173. package/docs/api/module-storage_gun-async.html +3 -0
  174. package/docs/api/module-storage_gun-auth-GunAuth.html +5 -0
  175. package/docs/api/module-storage_gun-auth.GunAuth.html +3 -0
  176. package/docs/api/module-storage_gun-auth.html +3 -0
  177. package/docs/api/module-storage_gun-federation.html +3 -0
  178. package/docs/api/module-storage_gun-references-GunReferenceHandler.html +5 -0
  179. package/docs/api/module-storage_gun-references.GunReferenceHandler.html +3 -0
  180. package/docs/api/module-storage_gun-references.html +3 -0
  181. package/docs/api/module-storage_gun-schema-GunSchemaValidator.html +5 -0
  182. package/docs/api/module-storage_gun-schema.GunSchemaValidator.html +3 -0
  183. package/docs/api/module-storage_gun-schema.html +3 -0
  184. package/docs/api/module-storage_gun-wrapper.html +11 -0
  185. package/docs/api/module-storage_indexeddb-storage-IndexedDBStorage.html +5 -0
  186. package/docs/api/module-storage_indexeddb-storage.IndexedDBStorage.html +3 -0
  187. package/docs/api/module-storage_indexeddb-storage.html +3 -0
  188. package/docs/api/module-storage_key-storage-simple.html +3 -0
  189. package/docs/api/module-storage_key-storage.html +4 -0
  190. package/docs/api/module-storage_memory-storage-MemoryStorage.html +5 -0
  191. package/docs/api/module-storage_memory-storage.MemoryStorage.html +3 -0
  192. package/docs/api/module-storage_memory-storage.html +3 -0
  193. package/docs/api/module-storage_migration-MigrationTool.html +6 -0
  194. package/docs/api/module-storage_migration.MigrationTool.html +3 -0
  195. package/docs/api/module-storage_migration.html +3 -0
  196. package/docs/api/module-storage_nostr-async.html +18 -0
  197. package/docs/api/module-storage_nostr-client-LRUCache.html +3 -0
  198. package/docs/api/module-storage_nostr-client-NostrClient.html +7 -0
  199. package/docs/api/module-storage_nostr-client.NostrClient.html +15 -0
  200. package/docs/api/module-storage_nostr-client.html +6 -0
  201. package/docs/api/module-storage_nostr-wrapper.html +3 -0
  202. package/docs/api/module-storage_outbox-queue-OutboxQueue.html +4 -0
  203. package/docs/api/module-storage_outbox-queue.OutboxQueue.html +3 -0
  204. package/docs/api/module-storage_outbox-queue.html +3 -0
  205. package/docs/api/module-storage_persistent-storage-PersistentStorage.html +3 -0
  206. package/docs/api/module-storage_persistent-storage.html +4 -0
  207. package/docs/api/module-storage_sync-service-SyncService.html +5 -0
  208. package/docs/api/module-storage_sync-service.SyncService.html +3 -0
  209. package/docs/api/module-storage_sync-service.html +3 -0
  210. package/docs/api/module-storage_unified-storage.html +3 -0
  211. package/docs/api/module-subscriptions_manager.SubscriptionRegistry.html +3 -0
  212. package/docs/api/module-subscriptions_manager.html +3 -0
  213. package/docs/api/schema_validator.js.html +113 -0
  214. package/docs/api/scripts/core.js +726 -0
  215. package/docs/api/scripts/core.min.js +23 -0
  216. package/docs/api/scripts/resize.js +90 -0
  217. package/docs/api/scripts/search.js +265 -0
  218. package/docs/api/scripts/search.min.js +6 -0
  219. package/docs/api/scripts/third-party/Apache-License-2.0.txt +202 -0
  220. package/docs/api/scripts/third-party/fuse.js +9 -0
  221. package/docs/api/scripts/third-party/hljs-line-num-original.js +369 -0
  222. package/docs/api/scripts/third-party/hljs-line-num.js +1 -0
  223. package/docs/api/scripts/third-party/hljs-original.js +5171 -0
  224. package/docs/api/scripts/third-party/hljs.js +1 -0
  225. package/docs/api/scripts/third-party/popper.js +5 -0
  226. package/docs/api/scripts/third-party/tippy.js +1 -0
  227. package/docs/api/scripts/third-party/tocbot.js +672 -0
  228. package/docs/api/scripts/third-party/tocbot.min.js +1 -0
  229. package/docs/api/spatial_h3-operations.js.html +129 -0
  230. package/docs/api/storage_backend-factory.js.html +133 -0
  231. package/docs/api/storage_backend-interface.js.html +164 -0
  232. package/docs/api/storage_backends_activitypub-backend.js.html +298 -0
  233. package/docs/api/storage_backends_activitypub_server.js.html +678 -0
  234. package/docs/api/storage_backends_gundb-backend.js.html +878 -0
  235. package/docs/api/storage_backends_nostr-backend.js.html +254 -0
  236. package/docs/api/storage_filesystem-storage-browser.js.html +83 -0
  237. package/docs/api/storage_filesystem-storage.js.html +207 -0
  238. package/docs/api/storage_global-tables.js.html +116 -0
  239. package/docs/api/storage_gun-async.js.html +344 -0
  240. package/docs/api/storage_gun-auth.js.html +376 -0
  241. package/docs/api/storage_gun-federation.js.html +788 -0
  242. package/docs/api/storage_gun-references.js.html +212 -0
  243. package/docs/api/storage_gun-schema.js.html +309 -0
  244. package/docs/api/storage_gun-wrapper.js.html +645 -0
  245. package/docs/api/storage_indexeddb-storage.js.html +224 -0
  246. package/docs/api/storage_key-storage-simple.js.html +102 -0
  247. package/docs/api/storage_key-storage.js.html +171 -0
  248. package/docs/api/storage_memory-storage.js.html +128 -0
  249. package/docs/api/storage_migration.js.html +354 -0
  250. package/docs/api/storage_nostr-async.js.html +1076 -0
  251. package/docs/api/storage_nostr-client.js.html +1598 -0
  252. package/docs/api/storage_nostr-wrapper.js.html +218 -0
  253. package/docs/api/storage_outbox-queue.js.html +248 -0
  254. package/docs/api/storage_persistent-storage.js.html +160 -0
  255. package/docs/api/storage_sync-service.js.html +201 -0
  256. package/docs/api/storage_unified-storage.js.html +157 -0
  257. package/docs/api/styles/clean-jsdoc-theme-base.css +1159 -0
  258. package/docs/api/styles/clean-jsdoc-theme-dark.css +412 -0
  259. package/docs/api/styles/clean-jsdoc-theme-light.css +482 -0
  260. package/docs/api/styles/clean-jsdoc-theme-scrollbar.css +30 -0
  261. package/docs/api/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
  262. package/docs/api/styles/clean-jsdoc-theme.min.css +1 -0
  263. package/docs/api/subscriptions_manager.js.html +162 -0
  264. package/docs/contracts/api-interface.md +793 -0
  265. package/docs/data-model.md +476 -0
  266. package/docs/gun-async-usage.md +338 -0
  267. package/docs/plan.md +349 -0
  268. package/docs/quickstart.md +674 -0
  269. package/docs/research.md +362 -0
  270. package/docs/spec.md +244 -0
  271. package/docs/storage-backends.md +326 -0
  272. package/docs/tasks.md +947 -0
  273. package/examples/demo.html +47 -0
  274. package/examples/holosphere-widget.js +1242 -0
  275. package/examples/widget-demo.html +274 -0
  276. package/examples/widget.html +703 -0
  277. package/jsdoc.json +26 -0
  278. package/package.json +25 -7
  279. package/src/ai/aggregation.js +13 -2
  280. package/src/ai/breakdown.js +12 -2
  281. package/src/ai/classifier.js +14 -3
  282. package/src/ai/council.js +22 -7
  283. package/src/ai/embeddings.js +37 -15
  284. package/src/ai/federation-ai.js +13 -2
  285. package/src/ai/h3-ai.js +14 -2
  286. package/src/ai/index.js +16 -7
  287. package/src/ai/json-ops.js +18 -5
  288. package/src/ai/llm-service.js +62 -31
  289. package/src/ai/nl-query.js +12 -2
  290. package/src/ai/relationships.js +13 -2
  291. package/src/ai/schema-extractor.js +24 -10
  292. package/src/ai/spatial.js +13 -2
  293. package/src/ai/tts.js +25 -8
  294. package/src/cdn-entry.js +22 -0
  295. package/src/content/social-protocols.js +34 -25
  296. package/src/contracts/abis/Appreciative.json +1280 -0
  297. package/src/contracts/abis/AppreciativeFactory.json +101 -0
  298. package/src/contracts/abis/Bundle.json +1438 -0
  299. package/src/contracts/abis/BundleFactory.json +106 -0
  300. package/src/contracts/abis/Holon.json +881 -0
  301. package/src/contracts/abis/Holons.json +330 -0
  302. package/src/contracts/abis/Managed.json +1262 -0
  303. package/src/contracts/abis/ManagedFactory.json +149 -0
  304. package/src/contracts/abis/Membrane.json +261 -0
  305. package/src/contracts/abis/Splitter.json +1624 -0
  306. package/src/contracts/abis/SplitterFactory.json +220 -0
  307. package/src/contracts/abis/TestToken.json +321 -0
  308. package/src/contracts/abis/Zoned.json +1461 -0
  309. package/src/contracts/abis/ZonedFactory.json +154 -0
  310. package/src/contracts/chain-manager.js +403 -0
  311. package/src/contracts/deployer.js +500 -0
  312. package/src/contracts/event-listener.js +539 -0
  313. package/src/contracts/holon-contracts.js +359 -0
  314. package/src/contracts/index.js +82 -0
  315. package/src/contracts/networks.js +229 -0
  316. package/src/contracts/operations.js +687 -0
  317. package/src/contracts/queries.js +638 -0
  318. package/src/core/holosphere.js +487 -6
  319. package/src/crypto/nostr-utils.js +303 -0
  320. package/src/crypto/secp256k1.js +7 -2
  321. package/src/federation/handshake.js +475 -0
  322. package/src/federation/hologram.js +117 -3
  323. package/src/hierarchical/upcast.js +40 -25
  324. package/src/index.js +1501 -1909
  325. package/src/lib/ai-methods.js +657 -0
  326. package/src/lib/contract-methods.js +442 -0
  327. package/src/lib/errors.js +53 -0
  328. package/src/lib/federation-methods.js +345 -0
  329. package/src/lib/index.js +30 -0
  330. package/src/schema/validator.js +22 -3
  331. package/src/spatial/h3-operations.js +19 -3
  332. package/src/storage/backend-factory.js +7 -2
  333. package/src/storage/backend-interface.js +21 -2
  334. package/src/storage/backends/activitypub/server.js +25 -3
  335. package/src/storage/backends/activitypub-backend.js +25 -2
  336. package/src/storage/backends/gundb-backend.js +692 -50
  337. package/src/storage/backends/nostr-backend.js +116 -1
  338. package/src/storage/filesystem-storage-browser.js +42 -2
  339. package/src/storage/filesystem-storage.js +72 -5
  340. package/src/storage/global-tables.js +35 -3
  341. package/src/storage/gun-async.js +75 -15
  342. package/src/storage/gun-auth.js +373 -0
  343. package/src/storage/gun-federation.js +785 -0
  344. package/src/storage/gun-references.js +209 -0
  345. package/src/storage/gun-schema.js +306 -0
  346. package/src/storage/gun-wrapper.js +475 -54
  347. package/src/storage/indexeddb-storage.js +112 -13
  348. package/src/storage/key-storage-simple.js +32 -9
  349. package/src/storage/key-storage.js +45 -13
  350. package/src/storage/memory-storage.js +68 -2
  351. package/src/storage/migration.js +20 -7
  352. package/src/storage/nostr-async.js +412 -122
  353. package/src/storage/nostr-client.js +749 -76
  354. package/src/storage/nostr-wrapper.js +6 -2
  355. package/src/storage/outbox-queue.js +55 -18
  356. package/src/storage/persistent-storage.js +62 -14
  357. package/src/storage/sync-service.js +51 -17
  358. package/src/storage/unified-storage.js +154 -0
  359. package/src/subscriptions/manager.js +34 -17
  360. package/types/index.d.ts +133 -0
  361. package/vite.config.cdn.js +60 -0
  362. package/tests/unit/ai/aggregation.test.js +0 -295
  363. package/tests/unit/ai/breakdown.test.js +0 -446
  364. package/tests/unit/ai/classifier.test.js +0 -294
  365. package/tests/unit/ai/council.test.js +0 -262
  366. package/tests/unit/ai/embeddings.test.js +0 -384
  367. package/tests/unit/ai/federation-ai.test.js +0 -344
  368. package/tests/unit/ai/h3-ai.test.js +0 -458
  369. package/tests/unit/ai/index.test.js +0 -304
  370. package/tests/unit/ai/json-ops.test.js +0 -307
  371. package/tests/unit/ai/llm-service.test.js +0 -390
  372. package/tests/unit/ai/nl-query.test.js +0 -383
  373. package/tests/unit/ai/relationships.test.js +0 -311
  374. package/tests/unit/ai/schema-extractor.test.js +0 -384
  375. package/tests/unit/ai/spatial.test.js +0 -279
  376. package/tests/unit/ai/tts.test.js +0 -279
  377. package/tests/unit/content.test.js +0 -332
  378. package/tests/unit/contract/core.test.js +0 -88
  379. package/tests/unit/contract/crypto.test.js +0 -198
  380. package/tests/unit/contract/data.test.js +0 -223
  381. package/tests/unit/contract/federation.test.js +0 -181
  382. package/tests/unit/contract/hierarchical.test.js +0 -113
  383. package/tests/unit/contract/schema.test.js +0 -114
  384. package/tests/unit/contract/social.test.js +0 -217
  385. package/tests/unit/contract/spatial.test.js +0 -110
  386. package/tests/unit/contract/subscriptions.test.js +0 -128
  387. package/tests/unit/contract/utils.test.js +0 -159
  388. package/tests/unit/core.test.js +0 -152
  389. package/tests/unit/crypto.test.js +0 -328
  390. package/tests/unit/federation.test.js +0 -234
  391. package/tests/unit/gun-async.test.js +0 -252
  392. package/tests/unit/hierarchical.test.js +0 -399
  393. package/tests/unit/integration/scenario-01-geographic-storage.test.js +0 -74
  394. package/tests/unit/integration/scenario-02-federation.test.js +0 -76
  395. package/tests/unit/integration/scenario-03-subscriptions.test.js +0 -102
  396. package/tests/unit/integration/scenario-04-validation.test.js +0 -129
  397. package/tests/unit/integration/scenario-05-hierarchy.test.js +0 -125
  398. package/tests/unit/integration/scenario-06-social.test.js +0 -135
  399. package/tests/unit/integration/scenario-07-persistence.test.js +0 -130
  400. package/tests/unit/integration/scenario-08-authorization.test.js +0 -161
  401. package/tests/unit/integration/scenario-09-cross-dimensional.test.js +0 -139
  402. package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +0 -357
  403. package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +0 -410
  404. package/tests/unit/integration/scenario-12-capability-federated-read.test.js +0 -719
  405. package/tests/unit/performance/benchmark.test.js +0 -85
  406. package/tests/unit/schema.test.js +0 -213
  407. package/tests/unit/spatial.test.js +0 -158
  408. package/tests/unit/storage.test.js +0 -195
  409. package/tests/unit/subscriptions.test.js +0 -328
  410. package/tests/unit/test-data-permanence-debug.js +0 -197
  411. package/tests/unit/test-data-permanence.js +0 -340
  412. package/tests/unit/test-key-persistence-fixed.js +0 -148
  413. package/tests/unit/test-key-persistence.js +0 -172
  414. package/tests/unit/test-relay-permanence.js +0 -376
  415. package/tests/unit/test-second-node.js +0 -95
  416. package/tests/unit/test-simple-write.js +0 -89
  417. /package/{cleanup-test-data.js → scripts/cleanup-test-data.js} +0 -0
  418. /package/{test-ai-real-api.js → scripts/test-ai-real-api.js} +0 -0
package/src/index.js CHANGED
@@ -1,22 +1,31 @@
1
1
  /**
2
- * HoloSphere - Holonic Geospatial Communication Infrastructure
3
- * Public API
2
+ * @fileoverview HoloSphere - Holonic Geospatial Communication Infrastructure
3
+ *
4
+ * This is the main entry point for the HoloSphere library, providing a comprehensive
5
+ * distributed P2P geospatial communication system. It combines H3 hexagonal indexing
6
+ * for spatial organization, Nostr protocol for distributed storage, smart contracts
7
+ * for fund distribution, and AI services for intelligent data processing.
8
+ *
9
+ * @module holosphere
10
+ * @author HoloSphere Team
11
+ * @license MIT
4
12
  */
5
13
 
6
14
  import { HoloSphere as HoloSphereCore } from './core/holosphere.js';
7
15
  import * as spatial from './spatial/h3-operations.js';
8
- import * as storage from './storage/nostr-wrapper.js';
16
+ import * as storage from './storage/unified-storage.js';
17
+ import * as nostrStorage from './storage/nostr-wrapper.js';
9
18
  import * as nostrAsync from './storage/nostr-async.js';
10
19
  import * as globalTables from './storage/global-tables.js';
11
20
  import * as schema from './schema/validator.js';
12
21
  import { ValidationError } from './schema/validator.js';
13
22
  import * as federation from './federation/hologram.js';
23
+ import * as handshake from './federation/handshake.js';
14
24
  import * as crypto from './crypto/secp256k1.js';
25
+ import * as nostrUtils from './crypto/nostr-utils.js';
15
26
  import * as social from './content/social-protocols.js';
16
27
  import * as subscriptions from './subscriptions/manager.js';
17
28
  import * as hierarchical from './hierarchical/upcast.js';
18
- import * as registry from './federation/registry.js';
19
- import * as discovery from './federation/discovery.js';
20
29
 
21
30
  // AI Module imports
22
31
  import { LLMService } from './ai/llm-service.js';
@@ -34,16 +43,72 @@ import { RelationshipDiscovery } from './ai/relationships.js';
34
43
  import { TaskBreakdown } from './ai/breakdown.js';
35
44
  import { H3AI } from './ai/h3-ai.js';
36
45
 
46
+ // Contracts Module imports
47
+ import { ChainManager } from './contracts/chain-manager.js';
48
+ import { ContractDeployer, ABIs as ContractABIs } from './contracts/deployer.js';
49
+ import { HolonContracts } from './contracts/holon-contracts.js';
50
+ import { ContractOperations } from './contracts/operations.js';
51
+ import { EventListener, SANKEY_EVENTS } from './contracts/event-listener.js';
52
+ import { ContractQueries } from './contracts/queries.js';
53
+ import * as networks from './contracts/networks.js';
54
+
55
+ // Mixin imports
56
+ import { withAIMethods } from './lib/ai-methods.js';
57
+ import { withContractMethods } from './lib/contract-methods.js';
58
+ import { withFederationMethods } from './lib/federation-methods.js';
59
+ import { AuthorizationError } from './lib/errors.js';
60
+
37
61
  /**
38
- * Main HoloSphere class with all methods
62
+ * Base HoloSphere class with core data operations.
63
+ * Extends HoloSphereCore with schema validation, caching, subscriptions, and AI capabilities.
64
+ *
65
+ * @class HoloSphereBase
66
+ * @extends HoloSphereCore
39
67
  */
40
- export class HoloSphere extends HoloSphereCore {
68
+ class HoloSphereBase extends HoloSphereCore {
69
+ /**
70
+ * Creates a new HoloSphereBase instance.
71
+ *
72
+ * @param {Object} config - Configuration options
73
+ * @param {string} config.appName - Application namespace for data isolation
74
+ * @param {string} [config.backend='nostr'] - Storage backend type ('nostr', 'gundb', 'activitypub')
75
+ * @param {string[]} [config.relays] - Nostr relay URLs for distributed storage
76
+ * @param {string} [config.openaiKey] - OpenAI API key for AI services
77
+ * @param {Object} [config.aiOptions] - AI service configuration
78
+ * @param {string} [config.aiOptions.model] - OpenAI model to use
79
+ * @param {number} [config.aiOptions.temperature] - Temperature for AI responses
80
+ */
41
81
  constructor(config) {
42
82
  super(config);
83
+ /** @type {Map<string, {schema: Object, strict: boolean, timestamp: number}>} */
43
84
  this.schemas = new Map();
85
+ /** @type {Map<string, any>} */
86
+ this._cache = new Map();
87
+ /** @type {subscriptions.SubscriptionRegistry} */
44
88
  this.subscriptionRegistry = new subscriptions.SubscriptionRegistry();
45
89
 
90
+ /**
91
+ * Write cache for optimistic updates.
92
+ * Stores data immediately so reads return fresh data before relay sync completes.
93
+ * @type {Map<string, {data: Object, timestamp: number}>}
94
+ */
95
+ this._writeCache = new Map();
96
+
97
+ /**
98
+ * Pending write promises for deduplication.
99
+ * @type {Map<string, Promise>}
100
+ */
101
+ this._pendingWrites = new Map();
102
+
103
+ /**
104
+ * Delete cache for optimistic deletes.
105
+ * Tracks paths that have been deleted so reads don't return stale storage data.
106
+ * @type {Set<string>}
107
+ */
108
+ this._deleteCache = new Set();
109
+
46
110
  // Initialize AI services if openaiKey is provided or OPENAI_API_KEY env var is set
111
+ /** @type {Object|null} */
47
112
  this._ai = null;
48
113
  const openaiKey = config.openaiKey || this._getEnv('OPENAI_API_KEY');
49
114
  if (openaiKey) {
@@ -54,11 +119,18 @@ export class HoloSphere extends HoloSphereCore {
54
119
  };
55
120
  this._initializeAI(openaiKey, aiOptions);
56
121
  }
122
+
123
+ // Contracts module (lazy initialized)
124
+ /** @type {Object|null} */
125
+ this._contracts = null;
57
126
  }
58
127
 
59
128
  /**
60
- * Get environment variable (works in Node.js)
129
+ * Gets an environment variable value (Node.js only).
130
+ *
61
131
  * @private
132
+ * @param {string} name - Environment variable name
133
+ * @returns {string|undefined} Environment variable value or undefined
62
134
  */
63
135
  _getEnv(name) {
64
136
  if (typeof process !== 'undefined' && process.env) {
@@ -68,8 +140,11 @@ export class HoloSphere extends HoloSphereCore {
68
140
  }
69
141
 
70
142
  /**
71
- * Parse float from string, return undefined if invalid
143
+ * Parses a string value to a float.
144
+ *
72
145
  * @private
146
+ * @param {string|undefined|null} value - Value to parse
147
+ * @returns {number|undefined} Parsed float or undefined if invalid
73
148
  */
74
149
  _parseFloat(value) {
75
150
  if (value === undefined || value === null) return undefined;
@@ -78,28 +153,29 @@ export class HoloSphere extends HoloSphereCore {
78
153
  }
79
154
 
80
155
  /**
81
- * Initialize AI services
156
+ * Initializes AI services with the provided API key.
157
+ *
82
158
  * @private
159
+ * @param {string} apiKey - OpenAI API key
160
+ * @param {Object} [options={}] - AI configuration options
161
+ * @param {string} [options.model] - OpenAI model to use
162
+ * @param {number} [options.temperature] - Temperature for AI responses
83
163
  */
84
164
  _initializeAI(apiKey, options = {}) {
85
- // Build LLM options from config
86
165
  const llmOptions = {
87
166
  ...options.llm,
88
167
  model: options.model || options.llm?.model,
89
168
  temperature: options.temperature ?? options.llm?.temperature,
90
169
  };
91
170
 
92
- // Create shared LLM service
93
171
  this._ai = {
94
172
  llm: new LLMService(apiKey, llmOptions),
95
173
  };
96
174
 
97
- // Create OpenAI client for embeddings and TTS
98
175
  const OpenAI = require('openai').default;
99
176
  const openai = new OpenAI({ apiKey });
100
177
  this._ai.openai = openai;
101
178
 
102
- // Create all services
103
179
  this._ai.embeddings = new Embeddings(openai, this);
104
180
  this._ai.schemaExtractor = new SchemaExtractor(this._ai.llm);
105
181
  this._ai.jsonOps = new JSONOps(this._ai.llm);
@@ -116,41 +192,90 @@ export class HoloSphere extends HoloSphereCore {
116
192
  }
117
193
 
118
194
  /**
119
- * Check if AI services are initialized
120
- * @returns {boolean}
195
+ * Checks if AI services are initialized.
196
+ *
197
+ * @returns {boolean} True if AI services are available
121
198
  */
122
199
  hasAI() {
123
200
  return this._ai !== null;
124
201
  }
125
202
 
126
203
  /**
127
- * Get AI services object (for advanced usage)
128
- * @returns {Object|null}
204
+ * Gets the initialized AI services object.
205
+ *
206
+ * @returns {Object|null} AI services object containing llm, embeddings, etc., or null if not initialized
129
207
  */
130
208
  getAIServices() {
131
209
  return this._ai;
132
210
  }
133
211
 
134
212
  // === Spatial Operations ===
213
+
214
+ /**
215
+ * Converts geographic coordinates to an H3 holon ID.
216
+ *
217
+ * @param {number} lat - Latitude (-90 to 90)
218
+ * @param {number} lng - Longitude (-180 to 180)
219
+ * @param {number} resolution - H3 resolution level (0-15)
220
+ * @returns {Promise<string>} H3 cell ID representing the holon
221
+ */
135
222
  async toHolon(lat, lng, resolution) {
136
223
  return spatial.toHolon(lat, lng, resolution);
137
224
  }
138
225
 
226
+ /**
227
+ * Gets all parent holons at higher resolutions.
228
+ *
229
+ * @param {string} holonId - H3 cell ID
230
+ * @param {number} [maxResolution] - Maximum resolution to traverse up to
231
+ * @returns {Promise<string[]>} Array of parent H3 cell IDs
232
+ */
139
233
  async getParents(holonId, maxResolution) {
140
234
  return spatial.getParents(holonId, maxResolution);
141
235
  }
142
236
 
237
+ /**
238
+ * Gets all child holons at the next resolution level.
239
+ *
240
+ * @param {string} holonId - H3 cell ID
241
+ * @returns {Promise<string[]>} Array of child H3 cell IDs
242
+ */
143
243
  async getChildren(holonId) {
144
244
  return spatial.getChildren(holonId);
145
245
  }
146
246
 
247
+ /**
248
+ * Validates if a string is a valid H3 cell ID.
249
+ *
250
+ * @param {string} holonId - String to validate
251
+ * @returns {boolean} True if valid H3 cell ID
252
+ */
147
253
  isValidH3(holonId) {
148
254
  return spatial.isValidH3(holonId);
149
255
  }
150
256
 
151
257
  // === Data Operations ===
258
+
259
+ /**
260
+ * Writes data to a specific holon and lens.
261
+ * By default, write is optimistic (non-blocking) - returns immediately after caching locally.
262
+ * Data is synced to relays in the background.
263
+ *
264
+ * @param {string} holonId - H3 cell ID for the holon
265
+ * @param {string} lensName - Name of the lens (data category)
266
+ * @param {Object} data - Data object to write (must have or will be assigned an id)
267
+ * @param {Object} [options={}] - Write options
268
+ * @param {string} [options.capabilityToken] - Capability token for authorization
269
+ * @param {boolean} [options.validate=true] - Whether to validate against schema
270
+ * @param {boolean} [options.strict] - Override schema strict mode
271
+ * @param {boolean} [options.autoPropagate=true] - Whether to propagate to federated holons
272
+ * @param {Object} [options.propagationOptions] - Options for propagation
273
+ * @param {boolean} [options.blocking=false] - If true, wait for relay confirmation before returning
274
+ * @returns {Promise<boolean>} True if write succeeded (or queued for optimistic writes)
275
+ * @throws {ValidationError} If holonId, lensName, or data is invalid
276
+ * @throws {AuthorizationError} If capability token is invalid
277
+ */
152
278
  async write(holonId, lensName, data, options = {}) {
153
- // Validate inputs
154
279
  if (!holonId || typeof holonId !== 'string') {
155
280
  throw new ValidationError('ValidationError: holonId must be a non-empty string');
156
281
  }
@@ -161,212 +286,314 @@ export class HoloSphere extends HoloSphereCore {
161
286
  throw new ValidationError('ValidationError: data must be an object');
162
287
  }
163
288
 
164
- // Check authorization if capability token provided
165
289
  const capToken = options.capabilityToken || options.capability;
166
290
  if (capToken) {
167
- const authorized = await this.verifyCapability(
168
- capToken,
169
- 'write',
170
- { holonId, lensName }
171
- );
291
+ const authorized = await this.verifyCapability(capToken, 'write', { holonId, lensName });
172
292
  if (!authorized) {
173
293
  throw new AuthorizationError('AuthorizationError: Invalid capability token for write operation', 'write');
174
294
  }
175
295
  }
176
296
 
177
- // Auto-generate ID if not provided
178
297
  if (!data.id) {
179
298
  data.id = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
180
299
  }
181
300
 
182
- // Check if we're writing to an existing hologram location
301
+ // Add metadata
302
+ if (!data._meta) data._meta = {};
303
+ data._meta.createdAt = data._meta.createdAt || Date.now();
304
+ data._meta.updatedAt = Date.now();
305
+
183
306
  const path = storage.buildPath(this.config.appName, holonId, lensName, data.id);
184
- const existingData = await storage.read(this.client, path);
185
-
186
- // If existing data is a hologram, split the write between local overrides and source
187
- if (existingData && existingData.hologram === true && existingData.target) {
188
- // Fields that define the hologram structure (never overwrite these)
189
- const hologramStructureFields = ['hologram', 'soul', 'target', 'id', '_meta'];
190
- // Fields that exist in the hologram as local overrides
191
- const localOverrideFields = Object.keys(existingData).filter(k => !hologramStructureFields.includes(k));
192
-
193
- // Split data: local fields stay local, everything else goes to source
194
- const localData = {
195
- ...existingData, // Keep hologram structure
196
- };
197
- const sourceUpdates = {};
198
-
199
- for (const [key, value] of Object.entries(data)) {
200
- if (hologramStructureFields.includes(key)) {
201
- // Skip - don't overwrite hologram structure
202
- continue;
203
- } else if (key === '_hologram') {
204
- // Skip - this is resolved metadata, not real data
205
- continue;
206
- } else if (localOverrideFields.includes(key)) {
207
- // This field exists in the hologram - update locally
208
- localData[key] = value;
209
- } else {
210
- // This field doesn't exist in hologram - update source
211
- sourceUpdates[key] = value;
212
- }
307
+ this._log('DEBUG', 'write', { holonId, lensName, dataId: data.id, path, blocking: !!options.blocking });
308
+
309
+ // Validate synchronously if schema exists
310
+ if (options.validate !== false && this.schemas.has(lensName)) {
311
+ const schemaObj = this.schemas.get(lensName);
312
+ const strict = options.strict !== undefined ? options.strict : schemaObj.strict;
313
+ if (schemaObj.schema && typeof schemaObj.schema === 'object' && !schemaObj.schema.$ref) {
314
+ schema.validate(data, schemaObj.schema, lensName, strict);
213
315
  }
316
+ }
214
317
 
215
- // Schema validation for source updates (if schema exists)
216
- if (Object.keys(sourceUpdates).length > 0 && options.validate !== false && this.schemas.has(lensName)) {
217
- // Read current source data to merge with updates for validation
218
- const sourcePath = storage.buildPath(
219
- existingData.target.appname || this.config.appName,
220
- existingData.target.holonId,
221
- existingData.target.lensName,
222
- existingData.target.dataId
223
- );
224
- const currentSourceData = await storage.read(this.client, sourcePath);
225
- const mergedSourceData = { ...currentSourceData, ...sourceUpdates };
318
+ // OPTIMISTIC UPDATE: Store in write cache and clear from delete cache
319
+ this._writeCache.set(path, { data: { ...data }, timestamp: Date.now() });
320
+ this._deleteCache.delete(path);
321
+ this._log('INFO', '📝 CACHE WRITE', {
322
+ path,
323
+ dataId: data.id,
324
+ cacheSize: this._writeCache.size,
325
+ mode: options.blocking ? 'blocking' : 'optimistic'
326
+ });
226
327
 
227
- const schemaObj = this.schemas.get(lensName);
228
- const strict = options.strict !== undefined ? options.strict : schemaObj.strict;
328
+ // Queue background sync
329
+ const syncPromise = this._syncWriteToRelay(holonId, lensName, data, path, options);
229
330
 
230
- if (schemaObj.schema && typeof schemaObj.schema === 'object' && !schemaObj.schema.$ref) {
231
- schema.validate(mergedSourceData, schemaObj.schema, lensName, strict);
232
- }
233
- }
331
+ // If blocking mode, wait for relay confirmation
332
+ if (options.blocking) {
333
+ this._log('DEBUG', '⏳ BLOCKING: Waiting for relay confirmation', { path });
334
+ return syncPromise;
335
+ }
234
336
 
235
- // Update local hologram with its override fields
236
- const startTime = Date.now();
237
- await storage.write(this.client, path, localData);
238
-
239
- // Update source data with non-local fields (partial update, not replace)
240
- if (Object.keys(sourceUpdates).length > 0) {
241
- const sourceAppname = existingData.target.appname || this.config.appName;
242
- const sourceHolonId = existingData.target.holonId;
243
- const sourceLensName = existingData.target.lensName;
244
- const sourceDataId = existingData.target.dataId;
245
-
246
- const sourcePath = storage.buildPath(sourceAppname, sourceHolonId, sourceLensName, sourceDataId);
247
- await storage.update(this.client, sourcePath, sourceUpdates);
248
-
249
- // Refresh timestamps on all activeHolograms in source data's _meta
250
- await federation.refreshActiveHolograms(
251
- this.client,
252
- sourceAppname,
253
- sourceHolonId,
254
- sourceLensName,
255
- sourceDataId
256
- );
337
+ // Non-blocking: return immediately, sync happens in background
338
+ this._log('DEBUG', '⚡ OPTIMISTIC: Returning immediately, sync in background', { path });
339
+ return true;
340
+ }
341
+
342
+ /**
343
+ * Syncs a write operation to the relay in the background.
344
+ * Handles hologram writes, propagation, and error logging.
345
+ *
346
+ * @private
347
+ * @param {string} holonId - Holon ID
348
+ * @param {string} lensName - Lens name
349
+ * @param {Object} data - Data to write
350
+ * @param {string} path - Storage path
351
+ * @param {Object} options - Write options
352
+ * @returns {Promise<boolean>} True if write succeeded
353
+ */
354
+ async _syncWriteToRelay(holonId, lensName, data, path, options) {
355
+ const startTime = Date.now();
356
+ this._log('INFO', '🔄 RELAY SYNC START', {
357
+ path,
358
+ dataId: data.id,
359
+ relays: this.client?.relays?.map(r => r.url) || ['local-only']
360
+ });
361
+
362
+ try {
363
+ // Check for existing hologram
364
+ const existingData = await storage.read(this.client, path);
365
+
366
+ // Handle hologram writes
367
+ if (existingData && existingData.hologram === true && existingData.target) {
368
+ this._log('DEBUG', '🔗 Syncing hologram write', { path, target: existingData.target });
369
+ return this._syncHologramWrite(existingData, data, path, lensName, options);
257
370
  }
258
371
 
372
+ // Regular write to relay
373
+ await storage.write(this.client, path, data);
374
+
259
375
  const endTime = Date.now();
376
+ const duration = endTime - startTime;
260
377
  this._metrics.writes++;
261
378
  if (!this._metrics.totalWriteTime) this._metrics.totalWriteTime = 0;
262
- this._metrics.totalWriteTime += (endTime - startTime);
379
+ this._metrics.totalWriteTime += duration;
380
+
381
+ this._log('INFO', '✅ RELAY SYNC SUCCESS', {
382
+ path,
383
+ dataId: data.id,
384
+ duration: `${duration}ms`,
385
+ totalWrites: this._metrics.writes
386
+ });
387
+
388
+ // Clear from write cache after successful sync (optional - keeps cache smaller)
389
+ // this._writeCache.delete(path);
390
+
391
+ // Handle propagation in background
392
+ const { autoPropagate = true } = options;
393
+ if (autoPropagate && !data.hologram) {
394
+ this._log('DEBUG', '📤 Starting propagation', { path });
395
+ this.propagate(holonId, lensName, data, options.propagationOptions || {})
396
+ .catch(err => this._log('WARN', '⚠️ Propagation failed', { path, error: err.message }));
397
+ }
263
398
 
264
399
  return true;
400
+ } catch (error) {
401
+ const duration = Date.now() - startTime;
402
+ this._log('ERROR', '❌ RELAY SYNC FAILED', {
403
+ path,
404
+ dataId: data.id,
405
+ duration: `${duration}ms`,
406
+ error: error.message,
407
+ stack: error.stack?.split('\n')[1]?.trim()
408
+ });
409
+ // Keep data in write cache - will be retried or eventually consistent
410
+ this._log('WARN', '💾 Data retained in write cache for retry', { path, cacheSize: this._writeCache.size });
411
+ return false;
265
412
  }
413
+ }
266
414
 
267
- // Regular write (not a hologram) - proceed normally
268
- // Add metadata
269
- if (!data._meta) {
270
- data._meta = {};
415
+ /**
416
+ * Handles hologram write synchronization.
417
+ *
418
+ * @private
419
+ */
420
+ async _syncHologramWrite(existingData, data, path, lensName, options) {
421
+ const hologramStructureFields = ['hologram', 'soul', 'target', 'id', '_meta'];
422
+ const localOverrideFields = Object.keys(existingData).filter(k => !hologramStructureFields.includes(k));
423
+
424
+ const localData = { ...existingData };
425
+ const sourceUpdates = {};
426
+
427
+ for (const [key, value] of Object.entries(data)) {
428
+ if (hologramStructureFields.includes(key) || key === '_hologram') continue;
429
+ if (localOverrideFields.includes(key)) {
430
+ localData[key] = value;
431
+ } else {
432
+ sourceUpdates[key] = value;
433
+ }
271
434
  }
272
- data._meta.timestamp = Date.now();
273
435
 
274
- // Schema validation if exists
275
- if (options.validate !== false && this.schemas.has(lensName)) {
436
+ if (Object.keys(sourceUpdates).length > 0 && options.validate !== false && this.schemas.has(lensName)) {
437
+ const sourcePath = storage.buildPath(
438
+ existingData.target.appname || this.config.appName,
439
+ existingData.target.holonId,
440
+ existingData.target.lensName,
441
+ existingData.target.dataId
442
+ );
443
+ const currentSourceData = await storage.read(this.client, sourcePath);
444
+ const mergedSourceData = { ...currentSourceData, ...sourceUpdates };
445
+
276
446
  const schemaObj = this.schemas.get(lensName);
277
447
  const strict = options.strict !== undefined ? options.strict : schemaObj.strict;
278
448
 
279
- // If schema is a URI reference
280
- if (schemaObj.schema && typeof schemaObj.schema === 'object' && schemaObj.schema.$ref) {
281
- // URI schemas cannot be validated - skip validation in both modes
282
- // In a real implementation, URIs would be resolved first
283
- // For now, we allow writes through (no validation possible)
284
- } else if (schemaObj.schema && typeof schemaObj.schema === 'object') {
285
- // Validate if schema is an actual object
286
- schema.validate(data, schemaObj.schema, lensName, strict);
449
+ if (schemaObj.schema && typeof schemaObj.schema === 'object' && !schemaObj.schema.$ref) {
450
+ schema.validate(mergedSourceData, schemaObj.schema, lensName, strict);
287
451
  }
288
452
  }
289
453
 
290
- // Write to storage
291
454
  const startTime = Date.now();
292
- const success = await storage.write(this.client, path, data);
293
- const endTime = Date.now();
294
-
295
- if (success) {
296
- this._metrics.writes++;
297
- // Track average write time
298
- if (!this._metrics.totalWriteTime) this._metrics.totalWriteTime = 0;
299
- this._metrics.totalWriteTime += (endTime - startTime);
300
-
301
- // Refresh timestamps on all activeHolograms in the source data's _meta
302
- // This allows interfaces to detect updates automatically
303
- if (data._meta && Array.isArray(data._meta.activeHolograms) && data._meta.activeHolograms.length > 0) {
304
- const result = await federation.refreshActiveHolograms(
305
- this.client,
306
- this.config.appName,
307
- holonId,
308
- lensName,
309
- data.id,
310
- data // Pass the data we already have
311
- );
312
- // Update the written data with refreshed timestamps (already done in-place by refreshActiveHolograms)
313
- if (result.refreshed > 0) {
314
- // Re-write to persist the updated activeHolograms timestamps
315
- await storage.write(this.client, path, result.sourceData);
316
- }
317
- }
455
+ await storage.write(this.client, path, localData);
456
+
457
+ if (Object.keys(sourceUpdates).length > 0) {
458
+ const sourcePath = storage.buildPath(
459
+ existingData.target.appname || this.config.appName,
460
+ existingData.target.holonId,
461
+ existingData.target.lensName,
462
+ existingData.target.dataId
463
+ );
464
+ await storage.update(this.client, sourcePath, sourceUpdates);
465
+ await federation.refreshActiveHolograms(
466
+ this.client,
467
+ existingData.target.appname || this.config.appName,
468
+ existingData.target.holonId,
469
+ existingData.target.lensName,
470
+ existingData.target.dataId
471
+ );
318
472
  }
319
473
 
320
- return success;
474
+ const endTime = Date.now();
475
+ this._metrics.writes++;
476
+ if (!this._metrics.totalWriteTime) this._metrics.totalWriteTime = 0;
477
+ this._metrics.totalWriteTime += (endTime - startTime);
478
+
479
+ return true;
321
480
  }
322
481
 
323
482
  /**
324
- * Resolve holograms recursively
483
+ * Recursively resolves holograms (references) to their source data.
484
+ * Handles circular reference detection and local overrides.
485
+ *
325
486
  * @private
326
- * @param {*} data - Data that may contain holograms (object, array, or primitive)
327
- * @returns {Promise<*>} Resolved data
487
+ * @param {Object|Array|null} data - Data that may contain holograms
488
+ * @param {Set<string>} [visited=new Set()] - Set of visited paths for circular detection
489
+ * @returns {Promise<Object|Array|null>} Resolved data with holograms replaced by source data
328
490
  */
329
- async _resolveHolograms(data) {
330
- // Handle null/undefined
491
+ async _resolveHolograms(data, visited = new Set()) {
331
492
  if (!data) return data;
332
493
 
333
- // Handle arrays - resolve each item and filter out nulls
334
494
  if (Array.isArray(data)) {
335
495
  const resolved = [];
336
496
  for (const item of data) {
337
- const resolvedItem = await this._resolveHolograms(item);
338
- // Only add non-null items to preserve array integrity
339
- if (resolvedItem != null) {
497
+ const resolvedItem = await this._resolveHolograms(item, new Set());
498
+ if (resolvedItem !== null) {
340
499
  resolved.push(resolvedItem);
341
500
  }
342
501
  }
343
502
  return resolved;
344
503
  }
345
504
 
346
- // Handle objects
347
- if (typeof data === 'object') {
348
- // Check if this is a hologram - ONLY if explicitly marked with hologram:true
349
- // Having a 'soul' property alone does NOT make it a hologram (soul is used for metadata/linking)
350
- const isHologram = data.hologram === true;
505
+ if (data && typeof data === 'object') {
506
+ if (data.hologram === true && data.target) {
507
+ const sourcePath = storage.buildPath(
508
+ data.target.appname || this.config.appName,
509
+ data.target.holonId,
510
+ data.target.lensName,
511
+ data.target.dataId
512
+ );
513
+ this._log('DEBUG', 'resolving hologram', {
514
+ hologramId: data.id,
515
+ sourcePath,
516
+ targetHolon: data.target.holonId,
517
+ targetLens: data.target.lensName,
518
+ targetDataId: data.target.dataId
519
+ });
520
+
521
+ // Circular reference detection
522
+ if (visited.has(sourcePath)) {
523
+ this._log('WARN', 'Circular hologram reference detected', { sourcePath });
524
+ return null;
525
+ }
526
+ visited.add(sourcePath);
527
+
528
+ let resolveOptions = {};
529
+ if (data.target.authorPubKey) {
530
+ resolveOptions.authors = [data.target.authorPubKey];
531
+ resolveOptions.includeAuthor = true;
532
+ }
351
533
 
352
- if (isHologram) {
353
- const resolved = await federation.resolveHologram(this.client, data);
354
- // Recursively resolve the resolved data (in case it contains more holograms)
355
- return resolved ? await this._resolveHolograms(resolved) : null;
356
- }
534
+ const sourceData = await storage.read(this.client, sourcePath, resolveOptions);
535
+ this._log('DEBUG', 'hologram source fetched', { found: !!sourceData, sourcePath });
536
+ if (sourceData) {
537
+ // If source is also a hologram, recursively resolve it
538
+ let resolvedSource = sourceData;
539
+ if (sourceData.hologram === true && sourceData.target) {
540
+ resolvedSource = await this._resolveHolograms(sourceData, visited);
541
+ if (resolvedSource === null) {
542
+ return null; // Circular reference or unresolvable
543
+ }
544
+ }
357
545
 
358
- // Not a hologram, but might contain holograms in nested properties
359
- // For now, just return as-is to avoid deep traversal overhead
360
- // If needed, we can add deep resolution later
361
- return data;
362
- }
546
+ // Get local override fields from the hologram (excluding hologram structure fields)
547
+ const hologramStructureFields = ['hologram', 'soul', 'target', '_meta', 'id', 'capability', 'crossHolosphere'];
548
+ const localOverrides = {};
549
+ for (const [k, v] of Object.entries(data)) {
550
+ if (!hologramStructureFields.includes(k)) {
551
+ localOverrides[k] = v;
552
+ }
553
+ }
554
+
555
+ const merged = {
556
+ ...resolvedSource,
557
+ ...localOverrides,
558
+ _hologram: {
559
+ isHologram: true,
560
+ soul: data.soul,
561
+ sourceHolon: data.target.holonId,
562
+ source: data.target,
563
+ localOverrides: Object.keys(localOverrides),
564
+ crossHolosphere: data.crossHolosphere || false,
565
+ }
566
+ };
567
+
568
+ // Preserve source _meta but add hologram source info
569
+ if (resolvedSource._meta) {
570
+ merged._meta = { ...resolvedSource._meta, source: data.target.holonId };
571
+ } else {
572
+ merged._meta = { source: data.target.holonId };
573
+ }
363
574
 
364
- // Primitive value
575
+ return merged;
576
+ }
577
+ return null; // Source not found
578
+ }
579
+ }
365
580
  return data;
366
581
  }
367
582
 
583
+ /**
584
+ * Reads data from a specific holon and lens.
585
+ *
586
+ * @param {string} holonId - H3 cell ID for the holon
587
+ * @param {string} lensName - Name of the lens (data category)
588
+ * @param {string|null} [dataId=null] - Specific data ID, or null to read all
589
+ * @param {Object} [options={}] - Read options
590
+ * @param {string} [options.capabilityToken] - Capability token for authorization
591
+ * @param {boolean} [options.resolveHolograms=true] - Whether to resolve hologram references
592
+ * @returns {Promise<Object|Array|null>} Data object, array of objects, or null if not found
593
+ * @throws {ValidationError} If holonId or lensName is invalid
594
+ * @throws {AuthorizationError} If capability token is invalid
595
+ */
368
596
  async read(holonId, lensName, dataId = null, options = {}) {
369
- // Validate inputs
370
597
  if (!holonId || typeof holonId !== 'string') {
371
598
  throw new ValidationError('ValidationError: holonId must be a non-empty string');
372
599
  }
@@ -374,316 +601,569 @@ export class HoloSphere extends HoloSphereCore {
374
601
  throw new ValidationError('ValidationError: lensName must be a non-empty string');
375
602
  }
376
603
 
377
- const startTime = Date.now();
378
- this._metrics.reads++;
379
-
380
- // Build the scope for capability lookup
381
- const scope = { holonId, lensName };
382
- if (dataId) scope.dataId = dataId;
383
-
384
- // Get federated authors (partners who granted us read access)
385
- // Default: federated=true unless explicitly disabled
386
- const federated = options.federated !== false;
387
- let authors = [this.client.publicKey]; // Always include our own data
388
-
389
- if (federated) {
390
- try {
391
- const federatedAuthors = await registry.getFederatedAuthorsForScope(
392
- this.client,
393
- this.config.appName,
394
- scope,
395
- 'read'
396
- );
397
- // Add federated author pubKeys
398
- for (const { pubKey } of federatedAuthors) {
399
- if (!authors.includes(pubKey)) {
400
- authors.push(pubKey);
401
- }
402
- }
403
- } catch (error) {
404
- // Silently continue with just our own data if federation lookup fails
405
- console.debug('[read] Federation lookup failed, using local only:', error.message);
604
+ const capToken = options.capabilityToken || options.capability;
605
+ if (capToken) {
606
+ const authorized = await this.verifyCapability(capToken, 'read', { holonId, lensName, dataId });
607
+ if (!authorized) {
608
+ throw new AuthorizationError('AuthorizationError: Invalid capability token for read operation', 'read');
406
609
  }
407
610
  }
408
611
 
409
- // Query options including federated authors
410
- const queryOptions = {
411
- authors,
412
- includeAuthor: true, // Track which author each item came from
413
- };
612
+ const startTime = Date.now();
613
+ let result;
414
614
 
415
615
  if (dataId) {
416
616
  const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
417
- const data = await storage.read(this.client, path, queryOptions);
418
-
419
- // Filter out deleted items
420
- if (data && data._deleted) {
421
- const endTime = Date.now();
422
- if (!this._metrics.totalReadTime) this._metrics.totalReadTime = 0;
423
- this._metrics.totalReadTime += (endTime - startTime);
424
- return null;
425
- }
426
617
 
427
- // Always resolve holograms
428
- const resolved = await this._resolveHolograms(data);
429
-
430
- const endTime = Date.now();
431
- if (!this._metrics.totalReadTime) this._metrics.totalReadTime = 0;
432
- this._metrics.totalReadTime += (endTime - startTime);
433
- return resolved;
618
+ // Check delete cache first - if deleted, return null immediately
619
+ if (this._deleteCache.has(path)) {
620
+ this._log('INFO', '🗑️ CACHE READ: Deleted item', {
621
+ path,
622
+ source: 'delete-cache',
623
+ deleteCacheSize: this._deleteCache.size
624
+ });
625
+ result = null;
626
+ } else {
627
+ // Check write cache for optimistic reads
628
+ const cached = this._writeCache.get(path);
629
+ if (cached) {
630
+ const cacheAge = Date.now() - cached.timestamp;
631
+ this._log('INFO', '⚡ CACHE HIT: Write cache', {
632
+ path,
633
+ source: 'write-cache',
634
+ cacheAge: `${cacheAge}ms`,
635
+ writeCacheSize: this._writeCache.size
636
+ });
637
+ result = cached.data;
638
+ } else {
639
+ this._log('DEBUG', '📖 CACHE MISS: Reading from storage', { path });
640
+ result = await storage.read(this.client, path);
641
+ this._log('INFO', '💾 STORAGE READ', {
642
+ path,
643
+ source: 'storage',
644
+ found: !!result,
645
+ isHologram: result?.hologram === true
646
+ });
647
+ }
648
+ }
434
649
  } else {
435
650
  const path = storage.buildPath(this.config.appName, holonId, lensName);
436
- const result = await storage.readAll(this.client, path, {
437
- ...queryOptions,
438
- hybrid: this.config.hybridMode
439
- });
651
+ this._log('DEBUG', 'readAll', { holonId, lensName, path });
652
+
653
+ // For readAll, merge cached writes with storage results and filter deleted
654
+ const storageResult = await storage.readAll(this.client, path);
655
+ result = this._mergeWithWriteCache(storageResult, path);
656
+ this._log('DEBUG', 'readAll result', { count: Array.isArray(result) ? result.length : 0 });
657
+ }
440
658
 
441
- // Always resolve holograms in array results
442
- const resolved = await this._resolveHolograms(result);
659
+ const { resolveHolograms = true } = options;
660
+ if (resolveHolograms && result) {
661
+ this._log('DEBUG', 'resolving holograms', { itemCount: Array.isArray(result) ? result.length : 1 });
662
+ result = await this._resolveHolograms(result);
663
+ }
443
664
 
444
- // Filter out deleted items
445
- const filtered = Array.isArray(resolved)
446
- ? resolved.filter(item => !item || !item._deleted)
447
- : (resolved && resolved._deleted ? null : resolved);
665
+ const endTime = Date.now();
666
+ this._metrics.reads++;
667
+ if (!this._metrics.totalReadTime) this._metrics.totalReadTime = 0;
668
+ this._metrics.totalReadTime += (endTime - startTime);
448
669
 
449
- const endTime = Date.now();
450
- if (!this._metrics.totalReadTime) this._metrics.totalReadTime = 0;
451
- this._metrics.totalReadTime += (endTime - startTime);
452
- return filtered;
670
+ return result;
671
+ }
672
+
673
+ /**
674
+ * Merges storage results with write cache for readAll operations.
675
+ * Cached writes take precedence over storage data.
676
+ * Deleted items are filtered out.
677
+ *
678
+ * @private
679
+ * @param {Array} storageResult - Results from storage
680
+ * @param {string} pathPrefix - Path prefix for filtering cache
681
+ * @returns {Array} Merged results
682
+ */
683
+ _mergeWithWriteCache(storageResult, pathPrefix) {
684
+ const results = Array.isArray(storageResult) ? [...storageResult] : [];
685
+ const resultMap = new Map(results.map(item => [item.id, item]));
686
+
687
+ // Check for cached writes matching this path prefix
688
+ for (const [cachedPath, cached] of this._writeCache.entries()) {
689
+ if (cachedPath.startsWith(pathPrefix + '/')) {
690
+ // Cached write - update or add to results
691
+ const cachedData = cached.data;
692
+ if (cachedData.id) {
693
+ resultMap.set(cachedData.id, cachedData);
694
+ }
695
+ }
696
+ }
697
+
698
+ // Filter out deleted items
699
+ for (const deletedPath of this._deleteCache) {
700
+ if (deletedPath.startsWith(pathPrefix + '/')) {
701
+ // Extract the dataId from the path (last segment)
702
+ const dataId = deletedPath.split('/').pop();
703
+ resultMap.delete(dataId);
704
+ }
453
705
  }
706
+
707
+ return Array.from(resultMap.values());
454
708
  }
455
709
 
710
+ /**
711
+ * Updates existing data in a specific holon and lens.
712
+ * By default, update is optimistic (non-blocking) - returns immediately after caching merged data.
713
+ * Data is synced to relays in the background.
714
+ *
715
+ * @param {string} holonId - H3 cell ID for the holon
716
+ * @param {string} lensName - Name of the lens (data category)
717
+ * @param {string} dataId - ID of the data to update
718
+ * @param {Object} updates - Object containing fields to update
719
+ * @param {Object} [options={}] - Update options
720
+ * @param {string} [options.capabilityToken] - Capability token for authorization
721
+ * @param {boolean} [options.validate=true] - Whether to validate against schema
722
+ * @param {boolean} [options.strict] - Override schema strict mode
723
+ * @param {boolean} [options.blocking=false] - If true, wait for relay confirmation before returning
724
+ * @returns {Promise<boolean>} True if update succeeded, false if data not found
725
+ * @throws {ValidationError} If holonId, lensName, dataId, or updates is invalid
726
+ * @throws {AuthorizationError} If capability token is invalid
727
+ */
456
728
  async update(holonId, lensName, dataId, updates, options = {}) {
457
- // Check authorization if capability token provided
729
+ if (!holonId || typeof holonId !== 'string') {
730
+ throw new ValidationError('ValidationError: holonId must be a non-empty string');
731
+ }
732
+ if (!lensName || typeof lensName !== 'string') {
733
+ throw new ValidationError('ValidationError: lensName must be a non-empty string');
734
+ }
735
+ if (!dataId || typeof dataId !== 'string') {
736
+ throw new ValidationError('ValidationError: dataId must be a non-empty string');
737
+ }
738
+ if (!updates || typeof updates !== 'object') {
739
+ throw new ValidationError('ValidationError: updates must be an object');
740
+ }
741
+
458
742
  const capToken = options.capabilityToken || options.capability;
459
743
  if (capToken) {
460
- const authorized = await this.verifyCapability(
461
- capToken,
462
- 'write',
463
- { holonId, lensName }
464
- );
744
+ const authorized = await this.verifyCapability(capToken, 'write', { holonId, lensName, dataId });
465
745
  if (!authorized) {
466
746
  throw new AuthorizationError('AuthorizationError: Invalid capability token for update operation', 'write');
467
747
  }
468
748
  }
469
749
 
470
- // Read raw data (without resolving) to check if it's a hologram
471
750
  const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
472
- const rawData = await storage.read(this.client, path);
473
-
474
- // If this is a hologram, split updates between local overrides and source
475
- if (rawData && rawData.hologram === true && rawData.target) {
476
- // Fields that are stored in the hologram itself (local overrides)
477
- // These are fields that exist in the hologram beyond the core hologram structure
478
- const hologramStructureFields = ['hologram', 'soul', 'target', 'id', '_meta'];
479
- const localOverrideFields = Object.keys(rawData).filter(k => !hologramStructureFields.includes(k));
480
-
481
- // Split updates: local fields stay local, everything else goes to source
482
- const localUpdates = {};
483
- const sourceUpdates = {};
484
-
485
- for (const [key, value] of Object.entries(updates)) {
486
- if (hologramStructureFields.includes(key)) {
487
- // Skip hologram structure fields entirely
488
- continue;
489
- } else if (localOverrideFields.includes(key)) {
490
- // This field exists in the hologram - update locally
491
- localUpdates[key] = value;
492
- } else {
493
- // This field doesn't exist in hologram - update source
494
- sourceUpdates[key] = value;
495
- }
496
- }
497
751
 
498
- // Update local hologram overrides
499
- if (Object.keys(localUpdates).length > 0) {
500
- await storage.update(this.client, path, localUpdates);
501
- }
752
+ // Check write cache first, then storage
753
+ const cached = this._writeCache.get(path);
754
+ const existingData = cached ? cached.data : await storage.read(this.client, path);
755
+ const dataSource = cached ? 'write-cache' : 'storage';
502
756
 
503
- // Update source data
504
- if (Object.keys(sourceUpdates).length > 0) {
505
- const sourcePath = storage.buildPath(
506
- rawData.target.appname || this.config.appName,
507
- rawData.target.holonId,
508
- rawData.target.lensName,
509
- rawData.target.dataId
510
- );
511
- await storage.update(this.client, sourcePath, sourceUpdates);
512
- }
757
+ if (!existingData) {
758
+ this._log('DEBUG', '✏️ UPDATE: Data not found', { path, dataId });
759
+ return false;
760
+ }
513
761
 
514
- return true;
762
+ this._log('DEBUG', '✏️ UPDATE: Found data', { path, dataId, source: dataSource });
763
+
764
+ // Handle hologram updates - delegate to write() which is already optimistic
765
+ if (existingData.hologram === true && existingData.target) {
766
+ this._log('DEBUG', '🔗 Updating hologram', { path });
767
+ return this.write(holonId, lensName, { ...existingData, ...updates, id: dataId }, options);
515
768
  }
516
769
 
517
- // Regular data (not a hologram) - proceed with normal update
518
- // Schema validation if exists and updates data
770
+ const mergedData = { ...existingData, ...updates };
771
+ mergedData._meta = mergedData._meta || {};
772
+ mergedData._meta.updatedAt = Date.now();
773
+
519
774
  if (options.validate !== false && this.schemas.has(lensName)) {
520
- const existing = await this.read(holonId, lensName, dataId);
521
- const merged = { ...existing, ...updates };
522
775
  const schemaObj = this.schemas.get(lensName);
523
776
  const strict = options.strict !== undefined ? options.strict : schemaObj.strict;
524
-
525
- // If schema is a URI reference
526
- if (schemaObj.schema && typeof schemaObj.schema === 'object' && schemaObj.schema.$ref) {
527
- // URI schemas cannot be validated - skip validation in both modes
528
- // In a real implementation, URIs would be resolved first
529
- // For now, we allow updates through (no validation possible)
530
- } else if (schemaObj.schema && typeof schemaObj.schema === 'object') {
531
- schema.validate(merged, schemaObj.schema, lensName, strict);
777
+ if (schemaObj.schema && typeof schemaObj.schema === 'object' && !schemaObj.schema.$ref) {
778
+ schema.validate(mergedData, schemaObj.schema, lensName, strict);
532
779
  }
533
780
  }
534
781
 
535
- return storage.update(this.client, path, updates);
782
+ // OPTIMISTIC UPDATE: Store in write cache and clear from delete cache
783
+ this._writeCache.set(path, { data: { ...mergedData }, timestamp: Date.now() });
784
+ this._deleteCache.delete(path);
785
+ this._log('INFO', '✏️ CACHE UPDATE', {
786
+ path,
787
+ dataId,
788
+ updatedFields: Object.keys(updates),
789
+ cacheSize: this._writeCache.size,
790
+ mode: options.blocking ? 'blocking' : 'optimistic'
791
+ });
792
+
793
+ // Queue background sync
794
+ const syncPromise = this._syncUpdateToRelay(holonId, lensName, dataId, mergedData, existingData, path);
795
+
796
+ if (options.blocking) {
797
+ this._log('DEBUG', '⏳ BLOCKING: Waiting for update confirmation', { path });
798
+ return syncPromise;
799
+ }
800
+
801
+ return true;
802
+ }
803
+
804
+ /**
805
+ * Syncs an update operation to the relay in the background.
806
+ *
807
+ * @private
808
+ */
809
+ async _syncUpdateToRelay(holonId, lensName, dataId, mergedData, existingData, path) {
810
+ const startTime = Date.now();
811
+ this._log('INFO', '🔄 UPDATE SYNC START', {
812
+ path,
813
+ dataId,
814
+ relays: this.client?.relays?.map(r => r.url) || ['local-only']
815
+ });
816
+
817
+ try {
818
+ await storage.write(this.client, path, mergedData);
819
+ const duration = Date.now() - startTime;
820
+
821
+ this._metrics.writes++;
822
+ if (!this._metrics.totalWriteTime) this._metrics.totalWriteTime = 0;
823
+ this._metrics.totalWriteTime += duration;
824
+
825
+ this._log('INFO', '✅ UPDATE SYNC SUCCESS', {
826
+ path,
827
+ dataId,
828
+ duration: `${duration}ms`,
829
+ totalWrites: this._metrics.writes
830
+ });
831
+
832
+ if (existingData._meta && existingData._meta.activeHolograms) {
833
+ this._log('DEBUG', '🔗 Refreshing active holograms', { path });
834
+ federation.refreshActiveHolograms(this.client, this.config.appName, holonId, lensName, dataId)
835
+ .catch(err => this._log('WARN', '⚠️ Hologram refresh failed', { path, error: err.message }));
836
+ }
837
+
838
+ return true;
839
+ } catch (error) {
840
+ const duration = Date.now() - startTime;
841
+ this._log('ERROR', '❌ UPDATE SYNC FAILED', {
842
+ path,
843
+ dataId,
844
+ duration: `${duration}ms`,
845
+ error: error.message,
846
+ stack: error.stack?.split('\n')[1]?.trim()
847
+ });
848
+ this._log('WARN', '💾 Data retained in write cache for retry', { path, cacheSize: this._writeCache.size });
849
+ return false;
850
+ }
536
851
  }
537
852
 
853
+ /**
854
+ * Deletes data from a specific holon and lens.
855
+ * By default, delete is optimistic (non-blocking) - returns immediately after cache invalidation.
856
+ *
857
+ * @param {string} holonId - H3 cell ID for the holon
858
+ * @param {string} lensName - Name of the lens (data category)
859
+ * @param {string} dataId - ID of the data to delete
860
+ * @param {Object} [options={}] - Delete options
861
+ * @param {string} [options.capabilityToken] - Capability token for authorization
862
+ * @param {boolean} [options.blocking=false] - If true, wait for relay confirmation before returning
863
+ * @returns {Promise<boolean>} True if deletion succeeded, false if data not found
864
+ * @throws {ValidationError} If holonId, lensName, or dataId is invalid
865
+ * @throws {AuthorizationError} If not owner and no valid capability token
866
+ */
538
867
  async delete(holonId, lensName, dataId, options = {}) {
539
- // Read existing data to check ownership and get activeHolograms
540
- const existing = await this.read(holonId, lensName, dataId);
868
+ if (!holonId || typeof holonId !== 'string') {
869
+ throw new ValidationError('ValidationError: holonId must be a non-empty string');
870
+ }
871
+ if (!lensName || typeof lensName !== 'string') {
872
+ throw new ValidationError('ValidationError: lensName must be a non-empty string');
873
+ }
874
+ if (!dataId || typeof dataId !== 'string') {
875
+ throw new ValidationError('ValidationError: dataId must be a non-empty string');
876
+ }
877
+
878
+ const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
879
+
880
+ // Check write cache first, then storage
881
+ const cached = this._writeCache.get(path);
882
+ let existingData = cached ? cached.data : null;
883
+ let dataSource = cached ? 'write-cache' : null;
884
+
885
+ if (!existingData) {
886
+ existingData = await storage.read(this.client, path);
887
+ dataSource = existingData ? 'storage' : null;
888
+ }
541
889
 
542
- // If data doesn't exist, return false
543
- if (!existing) {
890
+ // Return false if data doesn't exist in cache or storage
891
+ if (!existingData) {
892
+ this._log('DEBUG', '🗑️ DELETE: Data not found', { path });
544
893
  return false;
545
894
  }
546
895
 
547
- // Check authorization
548
- const capToken = options.capabilityToken || options.capability;
549
- const creator = existing._creator || existing.owner;
550
- const currentUser = this.client.publicKey;
896
+ this._log('DEBUG', '🗑️ DELETE: Found data', { path, source: dataSource });
551
897
 
552
- // Authorization logic:
553
- // 1. If capability token provided, verify it
554
- // 2. Else if data has a creator/owner AND it's not the current user, deny
555
- // 3. Else allow (no owner or current user is owner)
898
+ // Check authorization: owner can delete, others need capability token
899
+ const dataOwner = existingData.owner || existingData._creator;
900
+ const isOwner = !dataOwner || dataOwner === this.client.publicKey;
556
901
 
557
- if (capToken) {
558
- const authorized = await this.verifyCapability(
559
- capToken,
560
- 'delete',
561
- { holonId, lensName }
562
- );
902
+ const capToken = options.capabilityToken || options.capability;
903
+ if (!isOwner) {
904
+ // Non-owner must provide a valid capability token
905
+ if (!capToken) {
906
+ throw new AuthorizationError('AuthorizationError: Capability token required for delete operation', 'delete');
907
+ }
908
+ const authorized = await this.verifyCapability(capToken, 'delete', { holonId, lensName, dataId });
563
909
  if (!authorized) {
564
- const error = new AuthorizationError('AuthorizationError: Invalid capability token for delete operation', 'delete');
565
- throw error;
910
+ throw new AuthorizationError('AuthorizationError: Invalid capability token for delete operation', 'delete');
566
911
  }
567
- } else if (creator && creator !== currentUser) {
568
- // Data has an owner and it's not the current user - require authorization
569
- const error = new AuthorizationError('AuthorizationError: Delete operation requires capability token', 'delete');
570
- throw error;
571
- }
572
- // else: no owner or current user is owner - allow deletion
573
-
574
- // Delete all holograms that point to this data
575
- if (existing._meta?.activeHolograms && Array.isArray(existing._meta.activeHolograms)) {
576
- console.info(`🗑️ Deleting ${existing._meta.activeHolograms.length} holograms for ${holonId}/${lensName}/${dataId}`);
577
- for (const hologramEntry of existing._meta.activeHolograms) {
578
- try {
579
- const hologramPath = storage.buildPath(
580
- this.config.appName,
581
- hologramEntry.targetHolon,
582
- lensName,
583
- dataId
584
- );
585
- await storage.deleteData(this.client, hologramPath);
586
- console.info(` ✓ Deleted hologram in ${hologramEntry.targetHolon}`);
587
- } catch (err) {
588
- console.warn(` ⚠️ Failed to delete hologram in ${hologramEntry.targetHolon}:`, err.message);
589
- }
912
+ } else if (capToken) {
913
+ // Owner provided a token - validate it anyway
914
+ const authorized = await this.verifyCapability(capToken, 'delete', { holonId, lensName, dataId });
915
+ if (!authorized) {
916
+ throw new AuthorizationError('AuthorizationError: Invalid capability token for delete operation', 'delete');
590
917
  }
591
918
  }
592
919
 
593
- const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
594
- return storage.deleteData(this.client, path);
920
+ // OPTIMISTIC DELETE: Remove from write cache and add to delete cache
921
+ this._writeCache.delete(path);
922
+ this._deleteCache.add(path);
923
+ this._log('INFO', '🗑️ CACHE DELETE', {
924
+ path,
925
+ dataId,
926
+ deleteCacheSize: this._deleteCache.size,
927
+ writeCacheSize: this._writeCache.size,
928
+ mode: options.blocking ? 'blocking' : 'optimistic'
929
+ });
930
+
931
+ if (existingData.hologram === true) {
932
+ this._log('DEBUG', '🔗 Deleting hologram', { path });
933
+ return this.deleteHologram(holonId, lensName, dataId, options);
934
+ }
935
+
936
+ // Delete from storage (background unless blocking)
937
+ const startTime = Date.now();
938
+ this._log('INFO', '🔄 DELETE SYNC START', { path, dataId });
939
+
940
+ const deletePromise = storage.deleteData(this.client, path).then(() => {
941
+ const duration = Date.now() - startTime;
942
+ this._metrics.deletes++;
943
+ // Clear from delete cache after successful sync
944
+ this._deleteCache.delete(path);
945
+ this._log('INFO', '✅ DELETE SYNC SUCCESS', {
946
+ path,
947
+ dataId,
948
+ duration: `${duration}ms`,
949
+ totalDeletes: this._metrics.deletes,
950
+ deleteCacheSize: this._deleteCache.size
951
+ });
952
+ return true;
953
+ }).catch(error => {
954
+ const duration = Date.now() - startTime;
955
+ this._log('ERROR', '❌ DELETE SYNC FAILED', {
956
+ path,
957
+ dataId,
958
+ duration: `${duration}ms`,
959
+ error: error.message
960
+ });
961
+ // Keep in delete cache so reads still return null
962
+ this._log('WARN', '💾 Path retained in delete cache', { path, deleteCacheSize: this._deleteCache.size });
963
+ return false;
964
+ });
965
+
966
+ if (options.blocking) {
967
+ this._log('DEBUG', '⏳ BLOCKING: Waiting for delete confirmation', { path });
968
+ return deletePromise;
969
+ }
970
+
971
+ // Non-blocking: return immediately
972
+ this._log('DEBUG', '⚡ OPTIMISTIC: Returning immediately, delete sync in background', { path });
973
+ return true;
595
974
  }
596
975
 
597
- // === Global Data Operations ===
976
+ // === Global Tables ===
977
+
978
+ /**
979
+ * Writes data to a global table (not holon-specific).
980
+ *
981
+ * @param {string} table - Name of the global table
982
+ * @param {Object} data - Data object to write (must have an id field)
983
+ * @returns {Promise<boolean>} True if write succeeded
984
+ */
598
985
  async writeGlobal(table, data) {
599
986
  return globalTables.writeGlobal(this.client, this.config.appName, table, data);
600
987
  }
601
988
 
989
+ /**
990
+ * Reads data from a global table.
991
+ *
992
+ * @param {string} table - Name of the global table
993
+ * @param {string|null} [key=null] - Specific key to read, or null to read all
994
+ * @returns {Promise<Object|Array|null>} Data object, array of objects, or null
995
+ */
602
996
  async readGlobal(table, key = null) {
603
997
  return globalTables.readGlobal(this.client, this.config.appName, table, key);
604
998
  }
605
999
 
1000
+ /**
1001
+ * Updates data in a global table.
1002
+ *
1003
+ * @param {string} table - Name of the global table
1004
+ * @param {string} key - Key of the data to update
1005
+ * @param {Object} updates - Object containing fields to update
1006
+ * @returns {Promise<boolean>} True if update succeeded
1007
+ */
606
1008
  async updateGlobal(table, key, updates) {
607
1009
  return globalTables.updateGlobal(this.client, this.config.appName, table, key, updates);
608
1010
  }
609
1011
 
1012
+ /**
1013
+ * Deletes data from a global table.
1014
+ *
1015
+ * @param {string} table - Name of the global table
1016
+ * @param {string} key - Key of the data to delete
1017
+ * @returns {Promise<boolean>} True if deletion succeeded
1018
+ */
610
1019
  async deleteGlobal(table, key) {
611
1020
  return globalTables.deleteGlobal(this.client, this.config.appName, table, key);
612
1021
  }
613
1022
 
1023
+ /**
1024
+ * Gets all data from a global table.
1025
+ *
1026
+ * @param {string} table - Name of the global table
1027
+ * @returns {Promise<Array>} Array of all data objects in the table
1028
+ */
614
1029
  async getAllGlobal(table) {
615
- return globalTables.readGlobal(this.client, this.config.appName, table);
1030
+ return globalTables.getAllGlobal(this.client, this.config.appName, table);
616
1031
  }
617
1032
 
1033
+ /**
1034
+ * Deletes all data from a global table.
1035
+ *
1036
+ * @param {string} table - Name of the global table
1037
+ * @returns {Promise<boolean>} True if deletion succeeded
1038
+ */
618
1039
  async deleteAllGlobal(table) {
619
1040
  return globalTables.deleteAllGlobal(this.client, this.config.appName, table);
620
1041
  }
621
1042
 
622
- // === Holon Data Operations ===
1043
+ // === Batch Operations ===
1044
+
1045
+ /**
1046
+ * Gets all data from a specific holon and lens.
1047
+ *
1048
+ * @param {string} holonId - H3 cell ID for the holon
1049
+ * @param {string} lensName - Name of the lens (data category)
1050
+ * @returns {Promise<Array|null>} Array of data objects or null
1051
+ */
623
1052
  async getAll(holonId, lensName) {
624
- return this.read(holonId, lensName);
1053
+ return this.read(holonId, lensName, null);
625
1054
  }
626
1055
 
1056
+ /**
1057
+ * Deletes all data from a specific holon and lens.
1058
+ *
1059
+ * @param {string} holonId - H3 cell ID for the holon
1060
+ * @param {string} lensName - Name of the lens (data category)
1061
+ * @returns {Promise<boolean>} True if deletion succeeded
1062
+ */
627
1063
  async deleteAll(holonId, lensName) {
628
1064
  const path = storage.buildPath(this.config.appName, holonId, lensName);
629
1065
  return storage.deleteAll(this.client, path);
630
1066
  }
631
1067
 
632
1068
  // === Schema Operations ===
633
- async setSchema(lensName, schemaObj, strict = false) {
634
- let resolvedSchema = schemaObj;
635
-
636
- // If schemaObj is a string (URI), store as reference
637
- if (typeof schemaObj === 'string') {
638
- // Store as URI reference - validation will happen when used
639
- resolvedSchema = { $ref: schemaObj };
640
- }
641
1069
 
642
- if (!resolvedSchema || (typeof resolvedSchema !== 'object' && typeof resolvedSchema !== 'boolean')) {
643
- throw new ValidationError('Schema must be a valid JSON Schema object or boolean');
1070
+ /**
1071
+ * Sets a JSON Schema for validating data in a specific lens.
1072
+ *
1073
+ * @param {string} lensName - Name of the lens to associate the schema with
1074
+ * @param {Object|string} schemaInput - JSON Schema object or URI reference
1075
+ * @param {boolean} [strict=false] - If true, validation will reject additional properties
1076
+ * @returns {Promise<void>}
1077
+ * @throws {ValidationError} If lensName is invalid or schema format is invalid
1078
+ */
1079
+ async setSchema(lensName, schemaInput, strict = false) {
1080
+ if (!lensName || typeof lensName !== 'string') {
1081
+ throw new ValidationError('ValidationError: lensName must be a non-empty string');
644
1082
  }
645
1083
 
646
- // Validate that it's a valid JSON Schema (basic check) - only for actual schemas, not refs
647
- if (typeof resolvedSchema === 'object' && !resolvedSchema.$ref) {
648
- if (!resolvedSchema.type && !resolvedSchema.properties && !resolvedSchema.items && resolvedSchema !== true && resolvedSchema !== false) {
649
- throw new ValidationError('ValidationError: Invalid JSON Schema format - must have type, properties, or items');
1084
+ // Handle null/undefined
1085
+ if (schemaInput == null) {
1086
+ throw new ValidationError('ValidationError: schema cannot be null or undefined');
1087
+ }
1088
+
1089
+ let schemaObj;
1090
+
1091
+ // Handle URI string - store as $ref
1092
+ if (typeof schemaInput === 'string') {
1093
+ schemaObj = { $ref: schemaInput };
1094
+ } else if (typeof schemaInput === 'object') {
1095
+ // Validate it looks like a JSON Schema (must have type or $ref or properties or items)
1096
+ const isValidSchema = schemaInput.$ref ||
1097
+ schemaInput.type ||
1098
+ schemaInput.properties ||
1099
+ schemaInput.items ||
1100
+ schemaInput.allOf ||
1101
+ schemaInput.anyOf ||
1102
+ schemaInput.oneOf ||
1103
+ schemaInput.$schema;
1104
+
1105
+ if (!isValidSchema) {
1106
+ throw new ValidationError('ValidationError: Invalid JSON Schema format');
650
1107
  }
1108
+ schemaObj = schemaInput;
1109
+ } else {
1110
+ throw new ValidationError('ValidationError: schema must be an object or URI string');
651
1111
  }
652
1112
 
653
- this.schemas.set(lensName, { schema: resolvedSchema, strict });
1113
+ this.schemas.set(lensName, { schema: schemaObj, strict, timestamp: Date.now() });
654
1114
  }
655
1115
 
1116
+ /**
1117
+ * Gets the JSON Schema for a specific lens.
1118
+ *
1119
+ * @param {string} lensName - Name of the lens
1120
+ * @returns {Promise<Object|null>} The schema object or null if not set
1121
+ */
656
1122
  async getSchema(lensName) {
657
1123
  const schemaObj = this.schemas.get(lensName);
658
1124
  return schemaObj ? schemaObj.schema : null;
659
1125
  }
660
1126
 
1127
+ /**
1128
+ * Clears the JSON Schema for a specific lens.
1129
+ *
1130
+ * @param {string} lensName - Name of the lens
1131
+ * @returns {Promise<void>}
1132
+ */
661
1133
  async clearSchema(lensName) {
662
1134
  this.schemas.delete(lensName);
663
- schema.clearSchemaCache(lensName);
1135
+ // Returns undefined for contract compliance
664
1136
  }
665
1137
 
666
1138
  // === Federation Operations ===
1139
+
1140
+ /**
1141
+ * Sets up federation between two holons for a specific lens.
1142
+ * Federation enables data sharing and synchronization between holons.
1143
+ *
1144
+ * @param {string} sourceHolon - Source holon H3 cell ID
1145
+ * @param {string} targetHolon - Target holon H3 cell ID
1146
+ * @param {string} lensName - Name of the lens to federate
1147
+ * @param {Object} [options={}] - Federation options
1148
+ * @param {string} [options.direction='outbound'] - Direction: 'inbound', 'outbound', or 'bidirectional'
1149
+ * @param {string} [options.mode='reference'] - Mode: 'reference' (hologram) or 'copy'
1150
+ * @param {Function} [options.filter] - Filter function to select which data to federate
1151
+ * @returns {Promise<boolean>} True if federation was set up successfully
1152
+ * @throws {Error} If trying to federate a holon with itself or invalid direction
1153
+ */
667
1154
  async federate(sourceHolon, targetHolon, lensName, options = {}) {
668
- // Validate self-federation
1155
+ const { direction = 'outbound', mode = 'reference', filter = null } = options;
1156
+
1157
+ // Validation
669
1158
  if (sourceHolon === targetHolon) {
670
1159
  throw new Error('Cannot federate a holon with itself');
671
1160
  }
672
-
673
- // Validate direction (if provided)
674
- if (options.direction && !['inbound', 'outbound', 'bidirectional'].includes(options.direction)) {
675
- throw new Error('Invalid federation direction - must be inbound, outbound, or bidirectional');
676
- }
677
-
678
- // Handle bidirectional as both inbound and outbound
679
- if (options.direction === 'bidirectional') {
680
- const { direction, ...restOptions } = options;
681
- const outboundResult = await this.federate(sourceHolon, targetHolon, lensName, { ...restOptions, direction: 'outbound' });
682
- const inboundResult = await this.federate(sourceHolon, targetHolon, lensName, { ...restOptions, direction: 'inbound' });
683
- return outboundResult && inboundResult;
1161
+ if (!['inbound', 'outbound', 'bidirectional'].includes(direction)) {
1162
+ throw new Error(`Invalid direction: ${direction}. Must be 'inbound', 'outbound', or 'bidirectional'`);
684
1163
  }
685
1164
 
686
- const result = await federation.setupFederation(
1165
+ // Store federation config
1166
+ await federation.setupFederation(
687
1167
  this.client,
688
1168
  this.config.appName,
689
1169
  sourceHolon,
@@ -692,133 +1172,100 @@ export class HoloSphere extends HoloSphereCore {
692
1172
  options
693
1173
  );
694
1174
 
695
- if (result) {
696
- if (!this._metrics.federations) this._metrics.federations = 0;
697
- this._metrics.federations++;
1175
+ // Actually propagate existing data based on direction
1176
+ if (direction === 'outbound' || direction === 'bidirectional') {
1177
+ await this._propagateExistingData(sourceHolon, targetHolon, lensName, { mode, filter });
1178
+ }
1179
+ if (direction === 'inbound' || direction === 'bidirectional') {
1180
+ await this._propagateExistingData(targetHolon, sourceHolon, lensName, { mode, filter });
1181
+ }
698
1182
 
699
- // By default, propagate existing data when federation is set up
700
- // Set propagateExisting: false to only federate future writes
701
- const propagateExisting = options.propagateExisting !== false;
702
-
703
- if (!propagateExisting) {
704
- return result;
705
- }
706
-
707
- // Propagate existing data based on direction
708
- // Default to 'outbound' (source -> target) if no direction specified
709
- const direction = options.direction || 'outbound';
710
- const mode = options.mode || 'reference';
711
- const filter = options.filter;
712
-
713
- if (direction === 'outbound') {
714
- // Source -> Target: propagate data from source to target
715
- // Check if receiver accepts this lens in their inbound
716
- const targetFederation = await this.getFederation(targetHolon);
717
- const receiverLensConfig = targetFederation?.lensConfig?.[sourceHolon];
718
- if (receiverLensConfig && receiverLensConfig.inbound && !receiverLensConfig.inbound.includes(lensName)) {
719
- console.log(`Skipping outbound propagation - ${targetHolon} doesn't accept lens '${lensName}' in inbound from ${sourceHolon}`);
720
- } else {
721
- // Read raw data to avoid resolving holograms
722
- const path = storage.buildPath(this.config.appName, sourceHolon, lensName);
723
- const sourceData = await storage.readAll(this.client, path);
724
- const sourceArray = Array.isArray(sourceData) ? sourceData : (sourceData ? [sourceData] : []);
725
-
726
- for (const item of sourceArray) {
727
- if (item.id === '_federation') continue; // Skip federation config
728
- // Skip items that are already holograms or from another source
729
- if (item.hologram === true) continue;
730
- if (item._meta && item._meta.source && item._meta.source !== sourceHolon) continue;
731
- if (filter && !filter(item)) continue; // Apply filter if provided
732
-
733
- await federation.propagateData(
734
- this.client,
735
- this.config.appName,
736
- item,
737
- sourceHolon,
738
- targetHolon,
739
- lensName,
740
- mode
741
- );
742
- }
743
- }
744
- }
1183
+ this._metrics.federations = (this._metrics.federations || 0) + 1;
1184
+ return true;
1185
+ }
745
1186
 
746
- if (direction === 'inbound') {
747
- // Target -> Source: propagate data from target to source
748
- // Check if receiver (source) accepts this lens in their inbound
749
- const sourceFederation = await this.getFederation(sourceHolon);
750
- const receiverLensConfig = sourceFederation?.lensConfig?.[targetHolon];
751
- if (receiverLensConfig && receiverLensConfig.inbound && !receiverLensConfig.inbound.includes(lensName)) {
752
- console.log(`Skipping inbound propagation - ${sourceHolon} doesn't accept lens '${lensName}' in inbound from ${targetHolon}`);
753
- } else {
754
- // Read raw data to avoid resolving holograms
755
- const path = storage.buildPath(this.config.appName, targetHolon, lensName);
756
- const targetData = await storage.readAll(this.client, path);
757
- const targetArray = Array.isArray(targetData) ? targetData : (targetData ? [targetData] : []);
758
-
759
- for (const item of targetArray) {
760
- if (item.id === '_federation') continue; // Skip federation config
761
- // Skip items that are already holograms or from another source
762
- if (item.hologram === true) continue;
763
- if (item._meta && item._meta.source && item._meta.source !== targetHolon) continue;
764
- if (filter && !filter(item)) continue; // Apply filter if provided
765
-
766
- await federation.propagateData(
767
- this.client,
768
- this.config.appName,
769
- item,
770
- targetHolon,
771
- sourceHolon,
772
- lensName,
773
- mode
774
- );
775
- }
776
- }
777
- }
1187
+ /**
1188
+ * Propagates existing data from one holon to another.
1189
+ *
1190
+ * @private
1191
+ * @param {string} fromHolon - Source holon H3 cell ID
1192
+ * @param {string} toHolon - Target holon H3 cell ID
1193
+ * @param {string} lensName - Name of the lens
1194
+ * @param {Object} [options={}] - Propagation options
1195
+ * @param {string} [options.mode='reference'] - Mode: 'reference' or 'copy'
1196
+ * @param {Function} [options.filter] - Filter function to select data
1197
+ * @returns {Promise<void>}
1198
+ */
1199
+ async _propagateExistingData(fromHolon, toHolon, lensName, options = {}) {
1200
+ const { mode = 'reference', filter = null } = options;
1201
+ const existingData = await this.read(fromHolon, lensName, null, { resolveHolograms: false });
1202
+ if (!existingData) return;
1203
+
1204
+ const items = Array.isArray(existingData) ? existingData : [existingData];
1205
+ for (const item of items) {
1206
+ // Skip holograms to avoid circular propagation
1207
+ if (item.hologram === true) continue;
1208
+ // Apply filter if provided
1209
+ if (filter && !filter(item)) continue;
1210
+ await federation.propagateData(
1211
+ this.client,
1212
+ this.config.appName,
1213
+ item,
1214
+ fromHolon,
1215
+ toHolon,
1216
+ lensName,
1217
+ mode
1218
+ );
778
1219
  }
779
-
780
- return result;
781
1220
  }
782
1221
 
1222
+ /**
1223
+ * Gets federated data from a holon, optionally resolving holograms.
1224
+ *
1225
+ * @param {string} holonId - H3 cell ID for the holon
1226
+ * @param {string} lensName - Name of the lens
1227
+ * @param {Object} [options={}] - Options
1228
+ * @param {boolean} [options.resolveHolograms=true] - Whether to resolve hologram references
1229
+ * @returns {Promise<Array|null>} Array of data objects or null
1230
+ */
783
1231
  async getFederatedData(holonId, lensName, options = {}) {
784
1232
  const { resolveHolograms = true } = options;
1233
+ const path = storage.buildPath(this.config.appName, holonId, lensName);
1234
+ const data = await storage.readAll(this.client, path);
785
1235
 
786
- // For getFederatedData, we just return the local data
787
- // The federation setup already propagates data (as holograms or copies)
788
- // So all federated data should already be accessible locally
789
-
790
- if (resolveHolograms) {
791
- // Use normal read which auto-resolves holograms
792
- const localData = await this.read(holonId, lensName);
793
- const localArray = Array.isArray(localData) ? localData : (localData ? [localData] : []);
794
-
795
- // Filter out federation config and nulls
796
- return localArray.filter(item => item != null && item.id !== '_federation');
797
- } else {
798
- // Read without resolving holograms - need to read raw data
799
- const path = storage.buildPath(this.config.appName, holonId, lensName);
800
- const rawData = await storage.readAll(this.client, path);
801
- const rawArray = Array.isArray(rawData) ? rawData : (rawData ? [rawData] : []);
1236
+ if (!data || !resolveHolograms) return data;
802
1237
 
803
- // Filter out federation config
804
- return rawArray.filter(item => item != null && item.id !== '_federation');
805
- }
1238
+ // Resolve holograms using existing method
1239
+ return this._resolveHolograms(data);
806
1240
  }
807
1241
 
1242
+ /**
1243
+ * Removes federation between two holons for a specific lens.
1244
+ *
1245
+ * @param {string} sourceHolon - Source holon H3 cell ID
1246
+ * @param {string} targetHolon - Target holon H3 cell ID
1247
+ * @param {string} lensName - Name of the lens
1248
+ * @returns {Promise<boolean>} Always returns true (idempotent operation)
1249
+ */
808
1250
  async unfederate(sourceHolon, targetHolon, lensName) {
809
- // Delete federation config (idempotent - always returns true)
810
- const path = storage.buildPath(this.config.appName, sourceHolon, lensName, '_federation');
811
- await storage.deleteData(this.client, path);
1251
+ // Remove federation config for this relationship - idempotent
1252
+ const configPath = storage.buildPath(this.config.appName, sourceHolon, lensName, '_federation');
1253
+ try {
1254
+ await storage.deleteData(this.client, configPath);
1255
+ } catch (e) {
1256
+ // Ignore errors - already unfederated or doesn't exist
1257
+ }
812
1258
  return true;
813
1259
  }
814
1260
 
815
1261
  /**
816
- * Update local overrides on a hologram (e.g., position, pinned status)
817
- * @param {string} holonId - Holon where the hologram lives
818
- * @param {string} lensName - Lens name (e.g., 'quests')
819
- * @param {string} dataId - Data ID of the hologram
820
- * @param {Object} overrides - Local fields to update (e.g., { x: 100, y: 200 })
821
- * @returns {Promise<boolean>} Success indicator
1262
+ * Updates local override values on a hologram.
1263
+ *
1264
+ * @param {string} holonId - H3 cell ID where the hologram exists
1265
+ * @param {string} lensName - Name of the lens
1266
+ * @param {string} dataId - ID of the hologram
1267
+ * @param {Object} overrides - Object containing local override values
1268
+ * @returns {Promise<boolean>} True if update succeeded
822
1269
  */
823
1270
  async updateHologramOverrides(holonId, lensName, dataId, overrides) {
824
1271
  return federation.updateHologramOverrides(
@@ -832,17 +1279,15 @@ export class HoloSphere extends HoloSphereCore {
832
1279
  }
833
1280
 
834
1281
  /**
835
- * Create a hologram (lightweight reference) for federation
836
- * @param {string} sourceHolon - Source holon ID where the original data lives
837
- * @param {string} lensName - Lens name (e.g., 'quests')
838
- * @param {Object} data - Data object (must have an 'id' property)
839
- * @param {string} [targetHolon] - Optional target holon ID (defaults to sourceHolon)
840
- * @returns {Object} Hologram object ready to be written to a holon
1282
+ * Creates a hologram (reference) object structure.
1283
+ *
1284
+ * @param {string} sourceHolon - Source holon H3 cell ID where the original data lives
1285
+ * @param {string} lensName - Name of the lens
1286
+ * @param {Object} data - Data object containing the id to reference
1287
+ * @param {string} [targetHolon=null] - Target holon for the hologram, defaults to sourceHolon
1288
+ * @returns {Object} Hologram object structure
841
1289
  */
842
1290
  createHologram(sourceHolon, lensName, data, targetHolon = null) {
843
- if (!data || !data.id) {
844
- throw new Error('createHologram: data must have an id property');
845
- }
846
1291
  return federation.createHologram(
847
1292
  sourceHolon,
848
1293
  targetHolon || sourceHolon,
@@ -853,29 +1298,31 @@ export class HoloSphere extends HoloSphereCore {
853
1298
  }
854
1299
 
855
1300
  /**
856
- * Get active holograms for a specific data item
857
- * Returns the activeHolograms array from the source data's _meta
858
- * @param {string} holonId - Source holon ID
859
- * @param {string} lensName - Lens name (e.g., 'quests')
860
- * @param {string} dataId - Data ID to get holograms for
861
- * @returns {Promise<Object[]>} Array of hologram info objects with timestamps
1301
+ * Gets all active holograms (references) pointing to a specific data item.
1302
+ *
1303
+ * @param {string} holonId - H3 cell ID of the source holon
1304
+ * @param {string} lensName - Name of the lens
1305
+ * @param {string} dataId - ID of the source data
1306
+ * @returns {Promise<Array>} Array of active hologram references
862
1307
  */
863
1308
  async getActiveHolograms(holonId, lensName, dataId) {
864
- const data = await this.read(holonId, lensName, dataId);
865
- if (!data || !data._meta || !Array.isArray(data._meta.activeHolograms)) {
1309
+ // Read the source data and return its activeHolograms list
1310
+ const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
1311
+ const sourceData = await storage.read(this.client, path);
1312
+ if (!sourceData || !sourceData._meta || !sourceData._meta.activeHolograms) {
866
1313
  return [];
867
1314
  }
868
- return data._meta.activeHolograms;
1315
+ return sourceData._meta.activeHolograms;
869
1316
  }
870
1317
 
871
1318
  /**
872
- * Remove a hologram reference from source data's _meta.activeHolograms
873
- * Call this when a hologram is deleted
874
- * @param {string} sourceHolon - Source holon ID
875
- * @param {string} lensName - Lens name
876
- * @param {string} dataId - Data ID
877
- * @param {string} targetHolon - Target holon where hologram lives
878
- * @returns {Promise<boolean>} Success indicator
1319
+ * Removes an active hologram reference from a source data item.
1320
+ *
1321
+ * @param {string} sourceHolon - Source holon H3 cell ID
1322
+ * @param {string} lensName - Name of the lens
1323
+ * @param {string} dataId - ID of the source data
1324
+ * @param {string} targetHolon - Target holon where the hologram exists
1325
+ * @returns {Promise<boolean>} True if removal succeeded
879
1326
  */
880
1327
  async removeActiveHologram(sourceHolon, lensName, dataId, targetHolon) {
881
1328
  return federation.removeActiveHologram(
@@ -889,12 +1336,13 @@ export class HoloSphere extends HoloSphereCore {
889
1336
  }
890
1337
 
891
1338
  /**
892
- * Delete a hologram and clean up activeHolograms on the original source
893
- * @param {string} holonId - Holon where the hologram lives
894
- * @param {string} lensName - Lens name
895
- * @param {string} dataId - Data ID of the hologram
896
- * @param {Object} options - Optional settings
897
- * @returns {Promise<Object>} Result with deletion info
1339
+ * Deletes a hologram and cleans up its references.
1340
+ *
1341
+ * @param {string} holonId - H3 cell ID where the hologram exists
1342
+ * @param {string} lensName - Name of the lens
1343
+ * @param {string} dataId - ID of the hologram to delete
1344
+ * @param {Object} [options={}] - Delete options
1345
+ * @returns {Promise<boolean>} True if deletion succeeded
898
1346
  */
899
1347
  async deleteHologram(holonId, lensName, dataId, options = {}) {
900
1348
  return federation.deleteHologram(
@@ -908,18 +1356,19 @@ export class HoloSphere extends HoloSphereCore {
908
1356
  }
909
1357
 
910
1358
  /**
911
- * Propagate data to a specific target holon (creates hologram or copy)
912
- * Use this for direct propagation; use propagate() for federation-based propagation
1359
+ * Propagates data from source holon to target holon.
1360
+ *
913
1361
  * @param {Object} data - Data to propagate
914
- * @param {string} sourceHolon - Source holon ID
915
- * @param {string} targetHolon - Target holon ID
916
- * @param {string} lensName - Lens name
917
- * @param {Object} options - Options
918
- * @param {string} options.mode - 'reference' (creates hologram) or 'copy' (copies data)
919
- * @returns {Promise<boolean>} Success indicator
1362
+ * @param {string} sourceHolon - Source holon H3 cell ID
1363
+ * @param {string} targetHolon - Target holon H3 cell ID
1364
+ * @param {string} lensName - Name of the lens
1365
+ * @param {Object} [options={}] - Propagation options
1366
+ * @param {string} [options.mode='reference'] - Mode: 'reference' creates hologram, 'copy' duplicates data
1367
+ * @returns {Promise<Object>} Result of propagation
920
1368
  */
921
1369
  async propagateData(data, sourceHolon, targetHolon, lensName, options = {}) {
922
- const { mode = 'reference' } = options;
1370
+ // Extract mode from options, default to 'reference' for hologram creation
1371
+ const mode = options.mode || 'reference';
923
1372
  return federation.propagateData(
924
1373
  this.client,
925
1374
  this.config.appName,
@@ -932,49 +1381,98 @@ export class HoloSphere extends HoloSphereCore {
932
1381
  }
933
1382
 
934
1383
  /**
935
- * Resolve a hologram to its actual data, merging local overrides
1384
+ * Resolves a hologram to its source data.
1385
+ *
936
1386
  * @param {Object} hologram - Hologram object to resolve
937
- * @returns {Promise<Object|null>} Resolved data or null
1387
+ * @param {Object} [options={}] - Resolution options
1388
+ * @returns {Promise<Object|null>} Resolved data or null if source not found
938
1389
  */
939
- async resolveHologram(hologram) {
940
- return federation.resolveHologram(this.client, hologram);
1390
+ async resolveHologram(hologram, options = {}) {
1391
+ return federation.resolveHologram(this.client, hologram, new Set(), [], { ...options, appname: this.config.appName });
941
1392
  }
942
1393
 
943
1394
  /**
944
- * Check if data is a hologram (unresolved reference)
945
- * @param {Object} data - Data to check
946
- * @returns {boolean} True if data is a hologram
1395
+ * Checks if an object is a hologram (unresolved reference).
1396
+ *
1397
+ * @param {Object} data - Data object to check
1398
+ * @returns {boolean} True if the object is a hologram
947
1399
  */
948
1400
  isHologram(data) {
949
1401
  return federation.isHologram(data);
950
1402
  }
951
1403
 
952
1404
  /**
953
- * Check if data was resolved from a hologram
954
- * @param {Object} data - Data to check
955
- * @returns {boolean} True if data was resolved from a hologram
1405
+ * Checks if an object is a resolved hologram (has _hologram metadata).
1406
+ *
1407
+ * @param {Object} data - Data object to check
1408
+ * @returns {boolean} True if the object is a resolved hologram
956
1409
  */
957
1410
  isResolvedHologram(data) {
958
1411
  return federation.isResolvedHologram(data);
959
1412
  }
960
1413
 
961
1414
  /**
962
- * Get hologram source information from resolved data
963
- * @param {Object} data - Resolved data
964
- * @returns {Object|null} Source info { soul, sourceHolon, localOverrides } or null
1415
+ * Gets the source information from a hologram or resolved hologram.
1416
+ *
1417
+ * @param {Object} data - Hologram or resolved hologram object
1418
+ * @returns {Object|null} Source information or null
965
1419
  */
966
1420
  getHologramSource(data) {
967
1421
  return federation.getHologramSource(data);
968
1422
  }
969
1423
 
970
1424
  /**
971
- * Cleanup circular holograms in a holon's lens
972
- * Detects and removes holograms that create circular references (A→B→A)
973
- * @param {string} holonId - Holon to clean up
974
- * @param {string} lensName - Lens name to check
975
- * @param {Object} options - Optional settings
976
- * @param {boolean} options.dryRun - If true, only report what would be deleted
977
- * @returns {Promise<Object>} Result with cleanup info
1425
+ * Updates platform-specific data for an active hologram entry.
1426
+ * Used by platform clients (Telegram, Discord, etc.) to store their message IDs.
1427
+ *
1428
+ * @param {string} sourceHolon - Source holon ID where the original data lives
1429
+ * @param {string} lensName - Lens name (e.g., 'quests', 'events')
1430
+ * @param {string} dataId - Data ID
1431
+ * @param {string} targetHolon - Target holon ID where the hologram exists
1432
+ * @param {string} platform - Platform name (e.g., 'telegram', 'discord')
1433
+ * @param {Object} platformData - Platform-specific data (e.g., { messageId: 123 })
1434
+ * @returns {Promise<boolean>} Success indicator
1435
+ */
1436
+ async updateHologramPlatform(sourceHolon, lensName, dataId, targetHolon, platform, platformData) {
1437
+ return federation.updateActiveHologramPlatform(
1438
+ this.client,
1439
+ this.config.appName,
1440
+ sourceHolon,
1441
+ lensName,
1442
+ dataId,
1443
+ targetHolon,
1444
+ platform,
1445
+ platformData
1446
+ );
1447
+ }
1448
+
1449
+ /**
1450
+ * Gets active holograms for a data item, optionally filtered by platform.
1451
+ *
1452
+ * @param {string} sourceHolon - Source holon ID
1453
+ * @param {string} lensName - Lens name
1454
+ * @param {string} dataId - Data ID
1455
+ * @param {string} [platform] - Optional platform filter (e.g., 'telegram')
1456
+ * @returns {Promise<Array>} Array of hologram entries with platform data
1457
+ */
1458
+ async getActiveHolograms(sourceHolon, lensName, dataId, platform = null) {
1459
+ return federation.getActiveHolograms(
1460
+ this.client,
1461
+ this.config.appName,
1462
+ sourceHolon,
1463
+ lensName,
1464
+ dataId,
1465
+ platform
1466
+ );
1467
+ }
1468
+
1469
+ /**
1470
+ * Cleans up circular hologram references in a holon.
1471
+ *
1472
+ * @param {string} holonId - H3 cell ID of the holon
1473
+ * @param {string} lensName - Name of the lens
1474
+ * @param {Object} [options={}] - Cleanup options
1475
+ * @returns {Promise<Object>} Cleanup results
978
1476
  */
979
1477
  async cleanupCircularHolograms(holonId, lensName, options = {}) {
980
1478
  return federation.cleanupCircularHolograms(
@@ -987,14 +1485,13 @@ export class HoloSphere extends HoloSphereCore {
987
1485
  }
988
1486
 
989
1487
  /**
990
- * Cleanup circular holograms for a specific list of IDs
991
- * Use this when you don't have an index but know which IDs to check
992
- * @param {string} holonId - Holon to clean up
993
- * @param {string} lensName - Lens name
1488
+ * Cleans up circular hologram references for specific data IDs.
1489
+ *
1490
+ * @param {string} holonId - H3 cell ID of the holon
1491
+ * @param {string} lensName - Name of the lens
994
1492
  * @param {string[]} dataIds - Array of data IDs to check
995
- * @param {Object} options - Optional settings
996
- * @param {boolean} options.dryRun - If true, only report what would be deleted
997
- * @returns {Promise<Object>} Result with cleanup info
1493
+ * @param {Object} [options={}] - Cleanup options
1494
+ * @returns {Promise<Object>} Cleanup results
998
1495
  */
999
1496
  async cleanupCircularHologramsByIds(holonId, lensName, dataIds, options = {}) {
1000
1497
  return federation.cleanupCircularHologramsByIds(
@@ -1008,130 +1505,59 @@ export class HoloSphere extends HoloSphereCore {
1008
1505
  }
1009
1506
 
1010
1507
  /**
1011
- * Propagate data to federated holons and optionally to parent holons
1012
- * @param {string} holonId - Source holon ID
1013
- * @param {string} lensName - Lens name (e.g., 'quests')
1014
- * @param {Object} data - Data or hologram to propagate
1015
- * @param {Object} options - Propagation options
1016
- * @param {boolean} options.useHolograms - Use holograms instead of copies (default: true)
1017
- * @param {boolean} options.propagateToParents - Propagate to parent holons for H3 holons (default: false)
1018
- * @param {number} options.maxParentLevels - Maximum parent levels to propagate (default: 3)
1019
- * @returns {Promise<Object>} Result with success/failure counts
1508
+ * Propagates data to all federated holons.
1509
+ *
1510
+ * @param {string} holonId - Source holon H3 cell ID
1511
+ * @param {string} lensName - Name of the lens
1512
+ * @param {Object} data - Data to propagate
1513
+ * @param {Object} [options={}] - Propagation options
1514
+ * @param {boolean} [options.useHolograms=true] - Whether to create holograms
1515
+ * @param {boolean} [options.resolveExisting=true] - Whether to resolve existing data
1516
+ * @returns {Promise<Array|undefined>} Array of propagation results or undefined if no targets
1020
1517
  */
1021
1518
  async propagate(holonId, lensName, data, options = {}) {
1022
- const {
1023
- useHolograms = true,
1024
- propagateToParents = false,
1025
- maxParentLevels = 3
1026
- } = options;
1027
-
1028
- const result = {
1029
- success: 0,
1030
- failed: 0,
1031
- targets: [],
1032
- parentPropagation: null
1033
- };
1034
-
1035
- // Get federation config for this holon
1036
1519
  const federationData = await this.getFederation(holonId);
1037
-
1038
- // Propagate to federated holons (outbound)
1039
- if (federationData && federationData.outbound) {
1040
- for (const targetHolon of federationData.outbound) {
1041
- // Check if sender has this lens configured for outbound to this target
1042
- const senderLensConfig = federationData.lensConfig?.[targetHolon];
1043
- if (senderLensConfig && senderLensConfig.outbound && !senderLensConfig.outbound.includes(lensName)) {
1044
- continue; // Skip - sender hasn't configured this lens for outbound to this target
1045
- }
1046
-
1047
- // Check if receiver accepts this lens in their inbound from sender
1048
- try {
1049
- const targetFederation = await this.getFederation(targetHolon);
1050
- const receiverLensConfig = targetFederation?.lensConfig?.[holonId];
1051
- if (receiverLensConfig && receiverLensConfig.inbound && !receiverLensConfig.inbound.includes(lensName)) {
1052
- console.log(`Skipping propagation to ${targetHolon} - lens '${lensName}' not in their inbound config for ${holonId}`);
1053
- continue; // Skip - receiver doesn't accept this lens from sender
1054
- }
1055
- } catch (error) {
1056
- console.warn(`Could not verify receiver's inbound config for ${targetHolon}, proceeding anyway:`, error);
1057
- }
1058
-
1059
- try {
1060
- const mode = useHolograms ? 'reference' : 'copy';
1061
- await federation.propagateData(
1062
- this.client,
1063
- this.config.appName,
1064
- data,
1065
- holonId,
1066
- targetHolon,
1067
- lensName,
1068
- mode
1069
- );
1070
- result.success++;
1071
- result.targets.push(targetHolon);
1072
- } catch (error) {
1073
- console.error(`Failed to propagate to ${targetHolon}:`, error);
1074
- result.failed++;
1075
- }
1076
- }
1077
- }
1078
-
1079
- // Propagate to parent holons (for H3/geographic holons)
1080
- if (propagateToParents && spatial.isValidH3(holonId)) {
1081
- const parents = spatial.getParents(holonId);
1082
- const targetParents = parents.slice(0, maxParentLevels);
1083
-
1084
- result.parentPropagation = {
1085
- success: 0,
1086
- failed: 0,
1087
- targets: []
1088
- };
1089
-
1090
- for (const parentHolon of targetParents) {
1091
- try {
1092
- const mode = useHolograms ? 'reference' : 'copy';
1093
- await federation.propagateData(
1094
- this.client,
1095
- this.config.appName,
1096
- data,
1097
- holonId,
1098
- parentHolon,
1099
- lensName,
1100
- mode
1101
- );
1102
- result.parentPropagation.success++;
1103
- result.parentPropagation.targets.push(parentHolon);
1104
- } catch (error) {
1105
- console.error(`Failed to propagate to parent ${parentHolon}:`, error);
1106
- result.parentPropagation.failed++;
1107
- }
1108
- }
1520
+ // getFederation returns an object with federated array, not an array directly
1521
+ const targets = federationData?.federated || federationData?.outbound || [];
1522
+ if (!targets || targets.length === 0) return;
1523
+
1524
+ const { useHolograms = true, resolveExisting = true } = options;
1525
+ const results = [];
1526
+
1527
+ for (const targetHolonId of targets) {
1528
+ const result = await this.propagateData(
1529
+ data,
1530
+ holonId,
1531
+ targetHolonId,
1532
+ lensName,
1533
+ { useHolograms, resolveExisting }
1534
+ );
1535
+ results.push({ parent: targetHolonId, ...result });
1109
1536
  }
1110
1537
 
1111
- return result;
1538
+ return results;
1112
1539
  }
1113
1540
 
1541
+ /**
1542
+ * Gets the federation configuration for a holon.
1543
+ *
1544
+ * @param {string} holonId - H3 cell ID of the holon
1545
+ * @returns {Promise<Object|null>} Federation configuration object or null
1546
+ * @throws {Error} If holonId is not provided
1547
+ */
1114
1548
  async getFederation(holonId) {
1115
1549
  if (!holonId) {
1116
1550
  throw new Error('getFederation: Missing holon ID');
1117
1551
  }
1118
- const data = await this.getGlobal('federation', holonId);
1552
+ const data = await this.readGlobal('federation', holonId);
1119
1553
  if (!data) return null;
1120
1554
 
1121
- // Ensure arrays exist (in case data from storage is malformed)
1122
- if (!Array.isArray(data.inbound)) {
1123
- data.inbound = [];
1124
- }
1125
- if (!Array.isArray(data.outbound)) {
1126
- data.outbound = [];
1127
- }
1128
- if (!data.lensConfig || typeof data.lensConfig !== 'object') {
1129
- data.lensConfig = {};
1130
- }
1555
+ if (!Array.isArray(data.inbound)) data.inbound = [];
1556
+ if (!Array.isArray(data.outbound)) data.outbound = [];
1557
+ if (!data.lensConfig || typeof data.lensConfig !== 'object') data.lensConfig = {};
1558
+ if (!data.partnerNames || typeof data.partnerNames !== 'object') data.partnerNames = {};
1131
1559
 
1132
- // Backwards compatibility: populate federated from inbound/outbound if it doesn't exist
1133
1560
  if (!Array.isArray(data.federated)) {
1134
- // Combine inbound and outbound into a unique set
1135
1561
  const allFederated = new Set([...data.inbound, ...data.outbound]);
1136
1562
  data.federated = Array.from(allFederated);
1137
1563
  }
@@ -1140,94 +1566,82 @@ export class HoloSphere extends HoloSphereCore {
1140
1566
  }
1141
1567
 
1142
1568
  /**
1143
- * Federate two holons at the holon level (manages multiple lenses)
1144
- * This is a higher-level method that manages federation metadata and lens-specific federations
1145
- * @param {string} sourceHolon - Source holon ID
1146
- * @param {string} targetHolon - Target holon ID
1147
- * @param {Object} options - Federation options
1148
- * @param {Object} options.lensConfig - Lens configuration {inbound: [], outbound: []}
1149
- * @returns {Promise<boolean>} Success indicator
1569
+ * Establishes a holon-level federation relationship.
1570
+ *
1571
+ * @param {string} sourceHolon - Source holon H3 cell ID
1572
+ * @param {string} targetHolon - Target holon H3 cell ID
1573
+ * @param {Object} [options={}] - Federation options
1574
+ * @param {Object} [options.lensConfig] - Lens configuration for federation
1575
+ * @param {string[]} [options.lensConfig.inbound] - Lenses for inbound federation
1576
+ * @param {string[]} [options.lensConfig.outbound] - Lenses for outbound federation
1577
+ * @param {string} [options.partnerName] - Human-readable name for the partner holon
1578
+ * @param {boolean} [options.skipPropagation=false] - Skip propagating existing data
1579
+ * @returns {Promise<boolean>} True if federation was established
1580
+ * @throws {Error} If trying to federate a holon with itself
1150
1581
  */
1151
1582
  async federateHolon(sourceHolon, targetHolon, options = {}) {
1152
- const { lensConfig = { inbound: [], outbound: [] } } = options;
1583
+ const { lensConfig = { inbound: [], outbound: [] }, partnerName = null, skipPropagation = false } = options;
1153
1584
 
1154
- // Validate self-federation
1155
1585
  if (sourceHolon === targetHolon) {
1156
1586
  throw new Error('Cannot federate a holon with itself');
1157
1587
  }
1158
1588
 
1159
- // Get or create federation record for source holon
1160
- let federationData = await this.getGlobal('federation', sourceHolon) || {
1589
+ let federationData = await this.readGlobal('federation', sourceHolon) || {
1161
1590
  id: sourceHolon,
1162
1591
  name: sourceHolon,
1163
1592
  federated: [],
1164
1593
  inbound: [],
1165
1594
  outbound: [],
1166
1595
  lensConfig: {},
1596
+ partnerNames: {},
1167
1597
  timestamp: Date.now()
1168
1598
  };
1169
1599
 
1170
- // Ensure arrays exist (in case data from storage is malformed)
1171
- if (!Array.isArray(federationData.federated)) {
1172
- federationData.federated = [];
1173
- }
1174
- if (!Array.isArray(federationData.inbound)) {
1175
- federationData.inbound = [];
1176
- }
1177
- if (!Array.isArray(federationData.outbound)) {
1178
- federationData.outbound = [];
1179
- }
1180
- if (!federationData.lensConfig || typeof federationData.lensConfig !== 'object') {
1181
- federationData.lensConfig = {};
1182
- }
1600
+ if (!Array.isArray(federationData.federated)) federationData.federated = [];
1601
+ if (!Array.isArray(federationData.inbound)) federationData.inbound = [];
1602
+ if (!Array.isArray(federationData.outbound)) federationData.outbound = [];
1603
+ if (!federationData.lensConfig || typeof federationData.lensConfig !== 'object') federationData.lensConfig = {};
1604
+ if (!federationData.partnerNames || typeof federationData.partnerNames !== 'object') federationData.partnerNames = {};
1183
1605
 
1184
- // Always add target to federated list (tracks all federation links)
1185
1606
  if (!federationData.federated.includes(targetHolon)) {
1186
1607
  federationData.federated.push(targetHolon);
1187
1608
  }
1188
1609
 
1189
- // Add target to inbound/outbound lists based on lensConfig
1190
- // If there are outbound lenses, add to outbound list
1610
+ // Store the partner's name if provided
1611
+ if (partnerName) {
1612
+ federationData.partnerNames[targetHolon] = partnerName;
1613
+ }
1614
+
1191
1615
  if (lensConfig.outbound && lensConfig.outbound.length > 0) {
1192
1616
  if (!federationData.outbound.includes(targetHolon)) {
1193
1617
  federationData.outbound.push(targetHolon);
1194
1618
  }
1195
1619
  } else {
1196
- // Remove from outbound if no outbound lenses
1197
1620
  federationData.outbound = federationData.outbound.filter(id => id !== targetHolon);
1198
1621
  }
1199
1622
 
1200
- // If there are inbound lenses, add to inbound list
1201
1623
  if (lensConfig.inbound && lensConfig.inbound.length > 0) {
1202
1624
  if (!federationData.inbound.includes(targetHolon)) {
1203
1625
  federationData.inbound.push(targetHolon);
1204
1626
  }
1205
1627
  } else {
1206
- // Remove from inbound if no inbound lenses
1207
1628
  federationData.inbound = federationData.inbound.filter(id => id !== targetHolon);
1208
1629
  }
1209
1630
 
1210
- // Store lens configuration for this target holon
1211
1631
  federationData.lensConfig[targetHolon] = {
1212
1632
  inbound: lensConfig.inbound || [],
1213
1633
  outbound: lensConfig.outbound || [],
1214
1634
  timestamp: Date.now()
1215
1635
  };
1216
1636
 
1217
- // Save federation metadata
1218
1637
  const success = await this.writeGlobal('federation', federationData);
1219
-
1220
- // Clear cache so getFederation returns fresh data immediately
1221
1638
  this.clearCache('federation');
1222
1639
 
1223
- // Setup lens-specific federations
1224
- if (success) {
1225
- // Federate each lens specified in inbound array (receive from target)
1640
+ // Only propagate existing data if skipPropagation is false
1641
+ if (success && !skipPropagation) {
1226
1642
  for (const lens of (lensConfig.inbound || [])) {
1227
1643
  await this.federate(targetHolon, sourceHolon, lens, { direction: 'outbound', mode: 'reference' });
1228
1644
  }
1229
-
1230
- // Federate each lens specified in outbound array (send to target)
1231
1645
  for (const lens of (lensConfig.outbound || [])) {
1232
1646
  await this.federate(sourceHolon, targetHolon, lens, { direction: 'outbound', mode: 'reference' });
1233
1647
  }
@@ -1237,45 +1651,33 @@ export class HoloSphere extends HoloSphereCore {
1237
1651
  }
1238
1652
 
1239
1653
  /**
1240
- * Unfederate two holons at the holon level
1241
- * @param {string} sourceHolon - Source holon ID
1242
- * @param {string} targetHolon - Target holon ID
1243
- * @returns {Promise<boolean>} Success indicator
1654
+ * Removes a holon-level federation relationship.
1655
+ *
1656
+ * @param {string} sourceHolon - Source holon H3 cell ID
1657
+ * @param {string} targetHolon - Target holon H3 cell ID
1658
+ * @returns {Promise<boolean>} True if unfederation succeeded, false if no federation existed
1244
1659
  */
1245
1660
  async unfederateHolon(sourceHolon, targetHolon) {
1246
- // Get federation record
1247
- const federationData = await this.getGlobal('federation', sourceHolon);
1248
- if (!federationData) {
1249
- return false;
1250
- }
1661
+ const federationData = await this.readGlobal('federation', sourceHolon);
1662
+ if (!federationData) return false;
1251
1663
 
1252
- // Get lens config for this target before removing
1253
1664
  const lensConfig = federationData.lensConfig?.[targetHolon];
1254
1665
 
1255
- // Remove target from federation lists
1256
1666
  federationData.federated = (federationData.federated || []).filter(id => id !== targetHolon);
1257
1667
  federationData.inbound = (federationData.inbound || []).filter(id => id !== targetHolon);
1258
1668
  federationData.outbound = (federationData.outbound || []).filter(id => id !== targetHolon);
1259
1669
 
1260
- // Remove lens config for this target
1261
1670
  if (federationData.lensConfig) {
1262
1671
  delete federationData.lensConfig[targetHolon];
1263
1672
  }
1264
1673
 
1265
- // Update federation metadata
1266
1674
  const success = await this.writeGlobal('federation', federationData);
1267
-
1268
- // Clear cache so getFederation returns fresh data immediately
1269
1675
  this.clearCache('federation');
1270
1676
 
1271
- // Unfederate lens-specific federations
1272
1677
  if (success && lensConfig) {
1273
- // Unfederate each lens that was in inbound array
1274
1678
  for (const lens of (lensConfig.inbound || [])) {
1275
1679
  await this.unfederate(targetHolon, sourceHolon, lens);
1276
1680
  }
1277
-
1278
- // Unfederate each lens that was in outbound array
1279
1681
  for (const lens of (lensConfig.outbound || [])) {
1280
1682
  await this.unfederate(sourceHolon, targetHolon, lens);
1281
1683
  }
@@ -1285,47 +1687,59 @@ export class HoloSphere extends HoloSphereCore {
1285
1687
  }
1286
1688
 
1287
1689
  /**
1288
- * Get the lens configuration for a specific federated holon
1289
- * @param {string} sourceHolon - Source holon ID
1290
- * @param {string} targetHolon - Target holon ID
1291
- * @returns {Promise<Object|null>} Lens config {inbound: [], outbound: []} or null
1690
+ * Gets the federation configuration between two holons.
1691
+ *
1692
+ * @param {string} sourceHolon - Source holon H3 cell ID
1693
+ * @param {string} targetHolon - Target holon H3 cell ID
1694
+ * @returns {Promise<Object|null>} Lens configuration or null
1292
1695
  */
1293
1696
  async getFederatedConfig(sourceHolon, targetHolon) {
1294
- const federationData = await this.getGlobal('federation', sourceHolon);
1295
- if (!federationData || !federationData.lensConfig) {
1296
- return null;
1297
- }
1697
+ const federationData = await this.readGlobal('federation', sourceHolon);
1698
+ if (!federationData || !federationData.lensConfig) return null;
1298
1699
  return federationData.lensConfig[targetHolon] || null;
1299
1700
  }
1300
1701
 
1301
1702
  // === Hierarchical Operations ===
1703
+
1704
+ /**
1705
+ * Performs hierarchical upcast aggregation.
1706
+ * Aggregates data from child holons up to parent holons in the H3 hierarchy.
1707
+ *
1708
+ * @param {string} holonId - Starting holon H3 cell ID
1709
+ * @param {string} lensName - Name of the lens
1710
+ * @param {string} dataId - ID of the data to upcast
1711
+ * @param {Object} [options={}] - Upcast options
1712
+ * @returns {Promise<Object>} Aggregation result
1713
+ */
1302
1714
  async upcast(holonId, lensName, dataId, options = {}) {
1303
- return hierarchical.upcast(this.client, this.config.appName, holonId, lensName, dataId, options);
1715
+ return hierarchical.upcast(this, holonId, lensName, dataId, options);
1304
1716
  }
1305
1717
 
1306
- // === Subscription Operations ===
1718
+ // === Subscriptions ===
1719
+
1720
+ /**
1721
+ * Subscribes to real-time updates for a holon and lens.
1722
+ *
1723
+ * @param {string} holonId - H3 cell ID for the holon
1724
+ * @param {string} lensName - Name of the lens
1725
+ * @param {Function} callback - Callback function called with updated data
1726
+ * @param {Object} [options={}] - Subscription options
1727
+ * @returns {{unsubscribe: Function}} Subscription object with unsubscribe method
1728
+ * @throws {TypeError} If callback is not a function
1729
+ */
1307
1730
  subscribe(holonId, lensName, callback, options = {}) {
1308
1731
  if (typeof callback !== 'function') {
1309
1732
  throw new TypeError('callback must be a function');
1310
1733
  }
1311
-
1312
1734
  const path = storage.buildPath(this.config.appName, holonId, lensName);
1313
-
1314
- // Default to real-time only mode (only receive new events, not historical)
1315
- const subscriptionOptions = {
1316
- realtimeOnly: true, // Only get events from subscription time forward
1317
- ...options
1318
- };
1319
-
1735
+ const subscriptionOptions = { realtimeOnly: true, ...options };
1320
1736
  const subscriptionId = `${holonId}-${lensName}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
1321
1737
  let innerSubscription = null;
1322
1738
  let unsubscribeCalled = false;
1323
1739
 
1324
- // Start async subscription setup in background
1325
1740
  subscriptions.createSubscription(this.client, path, callback, subscriptionOptions)
1326
1741
  .then(subscription => {
1327
1742
  innerSubscription = subscription;
1328
- // If unsubscribe was called before setup completed, clean up immediately
1329
1743
  if (unsubscribeCalled) {
1330
1744
  subscription.unsubscribe();
1331
1745
  } else {
@@ -1336,1334 +1750,512 @@ export class HoloSphere extends HoloSphereCore {
1336
1750
  this._log('ERROR', 'Subscription setup failed', { path, error: err.message });
1337
1751
  });
1338
1752
 
1339
- this._metrics.subscriptions++;
1340
-
1341
- // Return synchronous subscription object
1753
+ this._metrics.subscriptions = (this._metrics.subscriptions || 0) + 1;
1342
1754
  return {
1343
1755
  unsubscribe: () => {
1344
1756
  unsubscribeCalled = true;
1345
1757
  if (innerSubscription) {
1346
1758
  this.subscriptionRegistry.unregister(subscriptionId);
1347
1759
  }
1348
- },
1760
+ }
1349
1761
  };
1350
1762
  }
1351
1763
 
1352
1764
  /**
1353
- * Subscribe to global table changes
1354
- * @param {string} table - The global table name (e.g., 'federation')
1355
- * @param {string} key - The specific key within the table (e.g., holonId)
1356
- * @param {Function} callback - Callback function called on data changes
1357
- * @param {Object} options - Subscription options
1358
- * @returns {Object} Subscription object with unsubscribe method
1765
+ * Subscribes to real-time updates for a global table.
1766
+ *
1767
+ * @param {string} table - Name of the global table
1768
+ * @param {string} key - Key to subscribe to
1769
+ * @param {Function} callback - Callback function called with updated data
1770
+ * @param {Object} [options={}] - Subscription options
1771
+ * @returns {Promise<{unsubscribe: Function}>} Subscription object
1359
1772
  */
1360
1773
  async subscribeGlobal(table, key, callback, options = {}) {
1361
- if (typeof callback !== 'function') {
1362
- throw new TypeError('callback must be a function');
1363
- }
1364
-
1365
- // Build global table path: appname/table/key
1366
- const path = `${this.config.appName}/${table}/${key}`;
1367
-
1368
- // Default to real-time only mode
1369
- const subscriptionOptions = {
1370
- realtimeOnly: true,
1371
- ...options
1372
- };
1373
-
1374
- const subscription = await subscriptions.createSubscription(this.client, path, callback, subscriptionOptions);
1375
-
1376
- const subscriptionId = `global-${table}-${key}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
1377
- this.subscriptionRegistry.register(subscriptionId, subscription);
1378
- this._metrics.subscriptions++;
1379
-
1380
- return {
1381
- unsubscribe: () => {
1382
- this.subscriptionRegistry.unregister(subscriptionId);
1383
- },
1384
- };
1774
+ return globalTables.subscribeGlobal(
1775
+ this.client,
1776
+ this.config.appName,
1777
+ table,
1778
+ key,
1779
+ callback,
1780
+ options
1781
+ );
1385
1782
  }
1386
1783
 
1387
1784
  // === Crypto Operations ===
1785
+
1786
+ /**
1787
+ * Derives a public key from a private key.
1788
+ *
1789
+ * @param {string} privateKey - Hex-encoded private key
1790
+ * @returns {Promise<string>} Hex-encoded public key
1791
+ */
1388
1792
  async getPublicKey(privateKey) {
1389
1793
  return crypto.getPublicKey(privateKey);
1390
1794
  }
1391
1795
 
1796
+ /**
1797
+ * Signs content with a private key.
1798
+ *
1799
+ * @param {string|Object} content - Content to sign
1800
+ * @param {string} privateKey - Hex-encoded private key
1801
+ * @returns {Promise<string>} Hex-encoded signature
1802
+ */
1392
1803
  async sign(content, privateKey) {
1393
1804
  return crypto.sign(content, privateKey);
1394
1805
  }
1395
1806
 
1807
+ /**
1808
+ * Verifies a signature against content and public key.
1809
+ *
1810
+ * @param {string|Object} content - Original content
1811
+ * @param {string} signature - Hex-encoded signature
1812
+ * @param {string} publicKey - Hex-encoded public key
1813
+ * @returns {Promise<boolean>} True if signature is valid
1814
+ */
1396
1815
  async verify(content, signature, publicKey) {
1397
1816
  return crypto.verify(content, signature, publicKey);
1398
1817
  }
1399
1818
 
1819
+ /**
1820
+ * Issues a capability token for authorization.
1821
+ *
1822
+ * @param {string[]} permissions - Array of permissions ('read', 'write', 'delete')
1823
+ * @param {Object} scope - Scope of the capability (holonId, lensName, etc.)
1824
+ * @param {string} recipient - Public key of the recipient
1825
+ * @param {Object} [options] - Additional options
1826
+ * @returns {Promise<string>} Signed capability token
1827
+ */
1400
1828
  async issueCapability(permissions, scope, recipient, options) {
1401
1829
  return crypto.issueCapability(permissions, scope, recipient, options);
1402
1830
  }
1403
1831
 
1832
+ /**
1833
+ * Verifies a capability token.
1834
+ *
1835
+ * @param {string} token - Capability token to verify
1836
+ * @param {string} requiredPermission - Required permission to check
1837
+ * @param {Object} scope - Scope to verify against
1838
+ * @returns {Promise<boolean>} True if token is valid and has required permission
1839
+ */
1404
1840
  async verifyCapability(token, requiredPermission, scope) {
1405
1841
  return crypto.verifyCapability(token, requiredPermission, scope);
1406
1842
  }
1407
1843
 
1408
1844
  // === Social Protocol Operations ===
1845
+
1846
+ /**
1847
+ * Publishes a Nostr event to a holon.
1848
+ *
1849
+ * @param {Object} event - Nostr event object
1850
+ * @param {number} event.kind - Event kind
1851
+ * @param {string} event.content - Event content
1852
+ * @param {Array} [event.tags] - Event tags
1853
+ * @param {string} holonId - H3 cell ID for the holon
1854
+ * @param {string} [lensName='social'] - Name of the lens
1855
+ * @returns {Promise<boolean>} True if publish succeeded
1856
+ */
1409
1857
  async publishNostr(event, holonId, lensName = 'social') {
1410
- // Validate with throwing error (partial=true allows missing id/pubkey/sig)
1411
- social.validateNostrEvent(event, true, true);
1858
+ // Validate Nostr event format
1859
+ social.validateNostrEvent(event, true, true); // partial=true, throwOnError=true
1412
1860
 
1413
- // Sign the event if not already signed
1414
- if (!event.id || !event.pubkey || !event.sig) {
1415
- // Use nostr-tools to properly sign the event
1416
- const { finalizeEvent } = await import('nostr-tools');
1417
- const signedEvent = finalizeEvent(event, this.client.privateKey);
1861
+ const enrichedEvent = {
1862
+ ...event,
1863
+ tags: [...(event.tags || []), ['h', holonId], ['l', lensName]],
1864
+ };
1865
+ const signedEvent = nostrUtils.signEvent(enrichedEvent, this.client.privateKey);
1418
1866
 
1419
- // Update event with signature fields
1420
- event.id = signedEvent.id;
1421
- event.pubkey = signedEvent.pubkey;
1422
- event.sig = signedEvent.sig;
1423
- }
1867
+ // Add protocol field for querySocial filtering
1868
+ const eventWithProtocol = {
1869
+ ...signedEvent,
1870
+ protocol: 'nostr',
1871
+ };
1424
1872
 
1425
- const transformed = social.transformNostrEvent(event);
1426
- return this.write(holonId, lensName, transformed);
1873
+ const path = storage.buildPath(this.config.appName, holonId, lensName, signedEvent.id);
1874
+ await storage.write(this.client, path, eventWithProtocol);
1875
+ return true;
1427
1876
  }
1428
1877
 
1878
+ /**
1879
+ * Publishes an ActivityPub object to a holon.
1880
+ *
1881
+ * @param {Object} object - ActivityPub object
1882
+ * @param {string} object.type - ActivityPub type (Note, Article, etc.)
1883
+ * @param {string} [object.actor] - Actor ID (defaults to client public key)
1884
+ * @param {string} holonId - H3 cell ID for the holon
1885
+ * @param {string} [lensName='social'] - Name of the lens
1886
+ * @returns {Promise<boolean>} True if publish succeeded
1887
+ */
1429
1888
  async publishActivityPub(object, holonId, lensName = 'social') {
1430
- // Validate with throwing error
1431
- social.validateActivityPubObject(object, true);
1889
+ // Validate ActivityPub object format
1890
+ social.validateActivityPubObject(object, true); // throwOnError=true
1432
1891
 
1433
- const transformed = social.transformActivityPubObject(object);
1434
- return this.write(holonId, lensName, transformed);
1892
+ const activity = social.transformActivityPubObject({
1893
+ ...object,
1894
+ actor: object.actor || this.client.publicKey,
1895
+ });
1896
+ return this.write(holonId, lensName, activity);
1435
1897
  }
1436
1898
 
1899
+ /**
1900
+ * Queries social protocol data from a holon.
1901
+ *
1902
+ * @param {string} holonId - H3 cell ID for the holon
1903
+ * @param {Object} [options={}] - Query options
1904
+ * @param {string} [options.lens='social'] - Name of the lens
1905
+ * @param {string} [options.protocol] - Filter by protocol ('nostr', 'activitypub', 'all')
1906
+ * @param {string} [options.type] - Filter by content type
1907
+ * @param {number} [options.since] - Filter events after this timestamp
1908
+ * @param {number} [options.until] - Filter events before this timestamp
1909
+ * @returns {Promise<Array>} Array of matching social items
1910
+ */
1437
1911
  async querySocial(holonId, options = {}) {
1438
- const { protocol, accessLevel } = options;
1439
- let data = await this.read(holonId, 'social');
1440
-
1441
- if (protocol) {
1442
- data = social.filterByProtocol(data, protocol);
1443
- }
1444
-
1445
- if (accessLevel) {
1446
- data = social.filterByAccessLevel(data, accessLevel);
1447
- }
1448
-
1449
- // Parse tags back to array for Nostr events (stored as JSON string)
1450
- if (Array.isArray(data)) {
1451
- data = data.map(item => {
1452
- if (item.protocol === 'nostr' && typeof item.tags === 'string') {
1453
- return { ...item, tags: JSON.parse(item.tags) };
1454
- }
1455
- return item;
1456
- });
1457
- }
1458
-
1459
- return data;
1912
+ const lensName = options.lens || 'social';
1913
+ const data = await this.read(holonId, lensName);
1914
+
1915
+ if (!data) return [];
1916
+ const items = Array.isArray(data) ? data : [data];
1917
+
1918
+ return items.filter(item => {
1919
+ // 'all' means don't filter by protocol
1920
+ if (options.protocol && options.protocol !== 'all' && item.protocol !== options.protocol) return false;
1921
+ if (options.type && item.type !== options.type) return false;
1922
+ if (options.since && item.created_at < options.since) return false;
1923
+ if (options.until && item.created_at > options.until) return false;
1924
+ return true;
1925
+ });
1460
1926
  }
1461
1927
 
1928
+ /**
1929
+ * Verifies a Nostr event signature.
1930
+ *
1931
+ * @param {Object} event - Nostr event to verify
1932
+ * @param {string} event.id - Event ID
1933
+ * @param {string} event.pubkey - Author public key
1934
+ * @param {number} event.created_at - Creation timestamp
1935
+ * @param {number} event.kind - Event kind
1936
+ * @param {Array} event.tags - Event tags
1937
+ * @param {string} event.content - Event content
1938
+ * @param {string} event.sig - Event signature
1939
+ * @returns {Promise<boolean>} True if event signature is valid
1940
+ */
1462
1941
  async verifyNostrEvent(event) {
1463
- // Verify a Nostr event signature
1464
- const { verifyEvent } = await import('nostr-tools');
1465
-
1466
- // Parse tags if they're stored as a string
1467
- const eventToVerify = {
1468
- ...event,
1469
- tags: typeof event.tags === 'string' ? JSON.parse(event.tags) : event.tags
1470
- };
1471
-
1472
- return verifyEvent(eventToVerify);
1942
+ // Extract only standard Nostr event fields for verification
1943
+ // (nostr-tools can't serialize events with extra properties like 'protocol')
1944
+ const { id, pubkey, created_at, kind, tags, content, sig } = event;
1945
+ const standardEvent = { id, pubkey, created_at, kind, tags, content, sig };
1946
+ return nostrUtils.verifyEvent(standardEvent);
1473
1947
  }
1474
1948
 
1475
1949
  // === Metrics ===
1950
+
1951
+ /**
1952
+ * Gets performance metrics for this HoloSphere instance.
1953
+ *
1954
+ * @returns {Object} Metrics object
1955
+ * @returns {number} return.reads - Number of read operations
1956
+ * @returns {number} return.writes - Number of write operations
1957
+ * @returns {number} return.deletes - Number of delete operations
1958
+ * @returns {number} return.federations - Number of federation operations
1959
+ * @returns {number} return.subscriptions - Number of active subscriptions
1960
+ * @returns {number} return.avgReadTime - Average read time in milliseconds
1961
+ * @returns {number} return.avgWriteTime - Average write time in milliseconds
1962
+ */
1476
1963
  metrics() {
1477
- const baseMetrics = super.metrics();
1964
+ const reads = this._metrics.reads || 0;
1965
+ const writes = this._metrics.writes || 0;
1966
+ const totalReadTime = this._metrics.totalReadTime || 0;
1967
+ const totalWriteTime = this._metrics.totalWriteTime || 0;
1968
+
1478
1969
  return {
1479
- ...baseMetrics,
1970
+ reads,
1971
+ writes,
1972
+ deletes: this._metrics.deletes || 0,
1480
1973
  federations: this._metrics.federations || 0,
1481
- avgWriteTime: this._metrics.writes > 0
1482
- ? (this._metrics.totalWriteTime || 0) / this._metrics.writes
1483
- : 0,
1484
- avgReadTime: this._metrics.reads > 0
1485
- ? (this._metrics.totalReadTime || 0) / this._metrics.reads
1486
- : 0,
1974
+ subscriptions: this._metrics.subscriptions || 0,
1975
+ avgReadTime: reads > 0 ? totalReadTime / reads : 0,
1976
+ avgWriteTime: writes > 0 ? totalWriteTime / writes : 0,
1977
+ };
1978
+ }
1979
+
1980
+ /**
1981
+ * Returns the current status of optimistic caches for debugging.
1982
+ * Useful for understanding what data is pending sync or marked as deleted.
1983
+ *
1984
+ * @returns {Object} Cache status object
1985
+ * @returns {number} return.writeCacheSize - Number of items in write cache (pending sync)
1986
+ * @returns {number} return.deleteCacheSize - Number of items in delete cache (pending delete sync)
1987
+ * @returns {Array<Object>} return.writeCacheEntries - Array of write cache entries with path and age
1988
+ * @returns {Array<string>} return.deleteCachePaths - Array of paths marked as deleted
1989
+ */
1990
+ getCacheStatus() {
1991
+ const now = Date.now();
1992
+ const writeCacheEntries = [];
1993
+
1994
+ for (const [path, entry] of this._writeCache.entries()) {
1995
+ writeCacheEntries.push({
1996
+ path,
1997
+ dataId: entry.data?.id,
1998
+ age: `${now - entry.timestamp}ms`,
1999
+ timestamp: new Date(entry.timestamp).toISOString()
2000
+ });
2001
+ }
2002
+
2003
+ return {
2004
+ writeCacheSize: this._writeCache.size,
2005
+ deleteCacheSize: this._deleteCache.size,
2006
+ writeCacheEntries,
2007
+ deleteCachePaths: Array.from(this._deleteCache)
1487
2008
  };
1488
2009
  }
1489
2010
 
1490
- // === Functional Aliases ===
1491
- // Common CRUD-style aliases for data operations
2011
+ /**
2012
+ * Clears the optimistic caches. Useful for testing or resetting state.
2013
+ * Warning: This may cause inconsistencies if there are pending syncs.
2014
+ */
2015
+ clearCaches() {
2016
+ const writeSize = this._writeCache.size;
2017
+ const deleteSize = this._deleteCache.size;
2018
+ this._writeCache.clear();
2019
+ this._deleteCache.clear();
2020
+ this._log('WARN', '🧹 CACHES CLEARED', {
2021
+ writeCacheCleared: writeSize,
2022
+ deleteCacheCleared: deleteSize
2023
+ });
2024
+ }
2025
+
2026
+ // === Aliases ===
1492
2027
 
1493
2028
  /**
1494
- * Alias for write() - stores data in a holon/lens
1495
- * @alias write
2029
+ * Alias for {@link HoloSphereBase#write}.
2030
+ * @param {string} holonId - H3 cell ID for the holon
2031
+ * @param {string} lensName - Name of the lens
2032
+ * @param {Object} data - Data to write
2033
+ * @param {Object} [options] - Write options
2034
+ * @returns {Promise<boolean>}
1496
2035
  */
1497
2036
  async put(holonId, lensName, data, options = {}) {
1498
2037
  return this.write(holonId, lensName, data, options);
1499
2038
  }
1500
2039
 
1501
2040
  /**
1502
- * Alias for read() - retrieves data from a holon/lens
1503
- * @alias read
2041
+ * Alias for {@link HoloSphereBase#read}.
2042
+ * @param {string} holonId - H3 cell ID for the holon
2043
+ * @param {string} lensName - Name of the lens
2044
+ * @param {string|null} [dataId] - Specific data ID or null
2045
+ * @param {Object} [options] - Read options
2046
+ * @returns {Promise<Object|Array|null>}
1504
2047
  */
1505
2048
  async get(holonId, lensName, dataId = null, options = {}) {
1506
2049
  return this.read(holonId, lensName, dataId, options);
1507
2050
  }
1508
2051
 
1509
2052
  /**
1510
- * Alias for delete() - removes data from a holon/lens
1511
- * @alias delete
2053
+ * Alias for {@link HoloSphereBase#delete}.
2054
+ * @param {string} holonId - H3 cell ID for the holon
2055
+ * @param {string} lensName - Name of the lens
2056
+ * @param {string} dataId - Data ID to delete
2057
+ * @param {Object} [options] - Delete options
2058
+ * @returns {Promise<boolean>}
1512
2059
  */
1513
2060
  async remove(holonId, lensName, dataId, options = {}) {
1514
2061
  return this.delete(holonId, lensName, dataId, options);
1515
2062
  }
1516
2063
 
1517
2064
  /**
1518
- * Alias for writeGlobal() - stores data in global table
1519
- * @alias writeGlobal
2065
+ * Alias for {@link HoloSphereBase#writeGlobal}.
2066
+ * @param {string} table - Global table name
2067
+ * @param {Object} data - Data to write
2068
+ * @returns {Promise<boolean>}
1520
2069
  */
1521
2070
  async putGlobal(table, data) {
1522
2071
  return this.writeGlobal(table, data);
1523
2072
  }
1524
2073
 
1525
2074
  /**
1526
- * Alias for readGlobal() - retrieves data from global table
1527
- * @alias readGlobal
2075
+ * Alias for {@link HoloSphereBase#readGlobal}.
2076
+ * @param {string} table - Global table name
2077
+ * @param {string|null} [key] - Key to read
2078
+ * @returns {Promise<Object|Array|null>}
1528
2079
  */
1529
2080
  async getGlobal(table, key = null) {
1530
2081
  return this.readGlobal(table, key);
1531
2082
  }
1532
2083
 
1533
2084
  /**
1534
- * Alias for deleteGlobal() - removes data from global table
1535
- * @alias deleteGlobal
2085
+ * Alias for {@link HoloSphereBase#deleteGlobal}.
2086
+ * @param {string} table - Global table name
2087
+ * @param {string} key - Key to delete
2088
+ * @returns {Promise<boolean>}
1536
2089
  */
1537
2090
  async removeGlobal(table, key) {
1538
2091
  return this.deleteGlobal(table, key);
1539
2092
  }
1540
2093
 
1541
2094
  /**
1542
- * Alias for setSchema() - defines a schema for a lens
1543
- * @alias setSchema
2095
+ * Alias for {@link HoloSphereBase#setSchema}.
2096
+ * @param {string} lensName - Lens name
2097
+ * @param {Object|string} schemaObj - Schema object or URI
2098
+ * @param {boolean} [strict] - Strict mode
2099
+ * @returns {Promise<void>}
1544
2100
  */
1545
2101
  async defineSchema(lensName, schemaObj, strict = false) {
1546
2102
  return this.setSchema(lensName, schemaObj, strict);
1547
2103
  }
1548
2104
 
1549
2105
  /**
1550
- * Alias for getSchema() - retrieves schema for a lens
1551
- * @alias getSchema
2106
+ * Alias for {@link HoloSphereBase#getSchema}.
2107
+ * @param {string} lensName - Lens name
2108
+ * @returns {Promise<Object|null>}
1552
2109
  */
1553
2110
  async fetchSchema(lensName) {
1554
2111
  return this.getSchema(lensName);
1555
2112
  }
1556
2113
 
1557
2114
  /**
1558
- * Alias for clearSchema() - removes schema for a lens
1559
- * @alias clearSchema
2115
+ * Alias for {@link HoloSphereBase#clearSchema}.
2116
+ * @param {string} lensName - Lens name
2117
+ * @returns {Promise<void>}
1560
2118
  */
1561
2119
  async removeSchema(lensName) {
1562
2120
  return this.clearSchema(lensName);
1563
2121
  }
1564
2122
 
1565
2123
  /**
1566
- * Store data - more semantic alias for write
1567
- * @alias write
2124
+ * Alias for {@link HoloSphereBase#write}.
2125
+ * @param {string} holonId - H3 cell ID for the holon
2126
+ * @param {string} lensName - Name of the lens
2127
+ * @param {Object} data - Data to write
2128
+ * @param {Object} [options] - Write options
2129
+ * @returns {Promise<boolean>}
1568
2130
  */
1569
2131
  async store(holonId, lensName, data, options = {}) {
1570
2132
  return this.write(holonId, lensName, data, options);
1571
2133
  }
1572
2134
 
1573
2135
  /**
1574
- * Fetch data - more semantic alias for read
1575
- * @alias read
2136
+ * Alias for {@link HoloSphereBase#read}.
2137
+ * @param {string} holonId - H3 cell ID for the holon
2138
+ * @param {string} lensName - Name of the lens
2139
+ * @param {string|null} [dataId] - Specific data ID or null
2140
+ * @param {Object} [options] - Read options
2141
+ * @returns {Promise<Object|Array|null>}
1576
2142
  */
1577
2143
  async fetch(holonId, lensName, dataId = null, options = {}) {
1578
2144
  return this.read(holonId, lensName, dataId, options);
1579
2145
  }
1580
2146
 
1581
2147
  /**
1582
- * Save data - alternative alias for write
1583
- * @alias write
2148
+ * Alias for {@link HoloSphereBase#write}.
2149
+ * @param {string} holonId - H3 cell ID for the holon
2150
+ * @param {string} lensName - Name of the lens
2151
+ * @param {Object} data - Data to write
2152
+ * @param {Object} [options] - Write options
2153
+ * @returns {Promise<boolean>}
1584
2154
  */
1585
2155
  async save(holonId, lensName, data, options = {}) {
1586
2156
  return this.write(holonId, lensName, data, options);
1587
2157
  }
1588
2158
 
1589
2159
  /**
1590
- * Load data - alternative alias for read
1591
- * @alias read
2160
+ * Alias for {@link HoloSphereBase#read}.
2161
+ * @param {string} holonId - H3 cell ID for the holon
2162
+ * @param {string} lensName - Name of the lens
2163
+ * @param {string|null} [dataId] - Specific data ID or null
2164
+ * @param {Object} [options] - Read options
2165
+ * @returns {Promise<Object|Array|null>}
1592
2166
  */
1593
2167
  async load(holonId, lensName, dataId = null, options = {}) {
1594
2168
  return this.read(holonId, lensName, dataId, options);
1595
2169
  }
1596
2170
 
2171
+ // === Cache ===
2172
+
1597
2173
  /**
1598
- * Clear the internal cache (or specific entries matching a pattern)
1599
- * Useful after write operations when you need to immediately read fresh data
1600
- * @param {string} [pattern] - Optional pattern to match cache keys (clears all if not provided)
2174
+ * Clears the internal cache.
2175
+ *
2176
+ * @param {string|null} [pattern=null] - If provided, only clear cache entries matching this pattern
1601
2177
  */
1602
2178
  clearCache(pattern = null) {
1603
- if (this.client && this.client.clearCache) {
1604
- this.client.clearCache(pattern);
2179
+ if (pattern) {
2180
+ for (const key of this._cache.keys()) {
2181
+ if (key.includes(pattern)) {
2182
+ this._cache.delete(key);
2183
+ }
2184
+ }
2185
+ } else {
2186
+ this._cache.clear();
1605
2187
  }
1606
2188
  }
2189
+ }
1607
2190
 
1608
- // === AI Operations ===
1609
-
1610
- /**
1611
- * Check if AI is available and throw if not
1612
- * @private
1613
- */
1614
- _requireAI() {
1615
- if (!this._ai) {
1616
- throw new Error('AI services not initialized. Provide openaiKey in config.');
1617
- }
1618
- }
2191
+ /**
2192
+ * Main HoloSphere class - composed from base + all mixins
2193
+ */
2194
+ export const HoloSphere = withContractMethods(
2195
+ withFederationMethods(
2196
+ withAIMethods(HoloSphereBase)
2197
+ )
2198
+ );
1619
2199
 
1620
- // --- Core LLM Methods ---
2200
+ // Export error classes
2201
+ export { AuthorizationError };
2202
+ export { ValidationError };
1621
2203
 
1622
- /**
1623
- * Summarize text using AI
1624
- * @param {string} text - Text to summarize
1625
- * @param {Object} options - Options for summarization
1626
- * @returns {Promise<string>} Summary
1627
- */
1628
- async summarize(text, options = {}) {
1629
- this._requireAI();
1630
- return this._ai.llm.summarize(text, options);
1631
- }
2204
+ // Re-export AI module classes
2205
+ export {
2206
+ LLMService,
2207
+ SchemaExtractor,
2208
+ JSONOps,
2209
+ Embeddings,
2210
+ Council,
2211
+ TTS,
2212
+ VOICES,
2213
+ MODELS,
2214
+ NLQuery,
2215
+ Classifier,
2216
+ SpatialAnalysis,
2217
+ SmartAggregation,
2218
+ FederationAdvisor,
2219
+ RelationshipDiscovery,
2220
+ TaskBreakdown,
2221
+ H3AI,
2222
+ };
1632
2223
 
1633
- /**
1634
- * Analyze text from a specific perspective
1635
- * @param {string} text - Text to analyze
1636
- * @param {string} aspect - Perspective to analyze from
1637
- * @param {Object} options - Options
1638
- * @returns {Promise<string>} Analysis
1639
- */
1640
- async analyze(text, aspect, options = {}) {
1641
- this._requireAI();
1642
- return this._ai.llm.analyze(text, aspect, options);
1643
- }
2224
+ // Re-export types and utilities
2225
+ export { spatial, storage, schema, federation, handshake, crypto, nostrUtils, social, subscriptions, hierarchical };
1644
2226
 
1645
- /**
1646
- * Extract keywords from text
1647
- * @param {string} text - Text to extract keywords from
1648
- * @param {Object} options - Options
1649
- * @returns {Promise<string[]>} Keywords
1650
- */
1651
- async extractKeywords(text, options = {}) {
1652
- this._requireAI();
1653
- return this._ai.llm.extractKeywords(text, options);
1654
- }
2227
+ // Re-export specific utilities used in tests
2228
+ export { matchScope } from './crypto/secp256k1.js';
2229
+ export { createHologram } from './federation/hologram.js';
1655
2230
 
1656
- /**
1657
- * Categorize text into one of the provided categories
1658
- * @param {string} text - Text to categorize
1659
- * @param {string[]} categories - Available categories
1660
- * @param {Object} options - Options
1661
- * @returns {Promise<string>} Selected category
1662
- */
1663
- async categorize(text, categories, options = {}) {
1664
- this._requireAI();
1665
- return this._ai.llm.categorize(text, categories, options);
1666
- }
2231
+ // Export AI factory function
2232
+ export { createAIServices } from './ai/index.js';
1667
2233
 
1668
- /**
1669
- * Translate text to a target language
1670
- * @param {string} text - Text to translate
1671
- * @param {string} targetLanguage - Target language
1672
- * @param {Object} options - Options
1673
- * @returns {Promise<string>} Translated text
1674
- */
1675
- async translate(text, targetLanguage, options = {}) {
1676
- this._requireAI();
1677
- return this._ai.llm.translate(text, targetLanguage, options);
1678
- }
2234
+ // Export Contracts module classes for standalone use
2235
+ export {
2236
+ ChainManager,
2237
+ ContractDeployer,
2238
+ HolonContracts,
2239
+ ContractOperations,
2240
+ ContractABIs,
2241
+ EventListener,
2242
+ ContractQueries,
2243
+ SANKEY_EVENTS
2244
+ };
1679
2245
 
1680
- /**
1681
- * Generate questions based on text
1682
- * @param {string} text - Text to generate questions from
1683
- * @param {Object} options - Options
1684
- * @returns {Promise<string[]>} Generated questions
1685
- */
1686
- async generateQuestions(text, options = {}) {
1687
- this._requireAI();
1688
- return this._ai.llm.generateQuestions(text, options);
1689
- }
1690
-
1691
- // --- Schema Extraction ---
1692
-
1693
- /**
1694
- * Extract structured data from text based on a lens schema
1695
- * @param {string} text - Natural language text
1696
- * @param {string} lensName - Name of the lens to get schema from
1697
- * @param {Object} options - Options
1698
- * @returns {Promise<Object>} Extracted structured data
1699
- */
1700
- async extractToSchema(text, lensName, options = {}) {
1701
- this._requireAI();
1702
- const schemaObj = await this.getSchema(lensName);
1703
- if (!schemaObj) {
1704
- throw new Error(`No schema found for lens: ${lensName}`);
1705
- }
1706
- return this._ai.schemaExtractor.extractToSchema(text, schemaObj, options);
1707
- }
1708
-
1709
- // --- Fuzzy JSON Operations ---
1710
-
1711
- /**
1712
- * Semantically merge two JSON objects
1713
- * @param {Object} obj1 - First object
1714
- * @param {Object} obj2 - Second object
1715
- * @param {Object} options - Options
1716
- * @returns {Promise<Object>} Merged object
1717
- */
1718
- async jsonAdd(obj1, obj2, options = {}) {
1719
- this._requireAI();
1720
- return this._ai.jsonOps.add(obj1, obj2, options);
1721
- }
1722
-
1723
- /**
1724
- * Remove concepts from obj1 based on obj2
1725
- * @param {Object} obj1 - Base object
1726
- * @param {Object} obj2 - Object with concepts to remove
1727
- * @param {Object} options - Options
1728
- * @returns {Promise<Object>} Result
1729
- */
1730
- async jsonSubtract(obj1, obj2, options = {}) {
1731
- this._requireAI();
1732
- return this._ai.jsonOps.subtract(obj1, obj2, options);
1733
- }
1734
-
1735
- /**
1736
- * Union two objects with intelligent deduplication
1737
- * @param {Object} obj1 - First object
1738
- * @param {Object} obj2 - Second object
1739
- * @param {Object} options - Options
1740
- * @returns {Promise<Object>} United object
1741
- */
1742
- async jsonUnion(obj1, obj2, options = {}) {
1743
- this._requireAI();
1744
- return this._ai.jsonOps.union(obj1, obj2, options);
1745
- }
1746
-
1747
- /**
1748
- * Find semantic differences between two objects
1749
- * @param {Object} obj1 - First object
1750
- * @param {Object} obj2 - Second object
1751
- * @param {Object} options - Options
1752
- * @returns {Promise<Object>} Differences
1753
- */
1754
- async jsonDifference(obj1, obj2, options = {}) {
1755
- this._requireAI();
1756
- return this._ai.jsonOps.difference(obj1, obj2, options);
1757
- }
1758
-
1759
- /**
1760
- * Semantically concatenate objects/arrays
1761
- * @param {Object} obj1 - First object
1762
- * @param {Object} obj2 - Second object
1763
- * @param {Object} options - Options
1764
- * @returns {Promise<Object>} Concatenated result
1765
- */
1766
- async jsonConcatenate(obj1, obj2, options = {}) {
1767
- this._requireAI();
1768
- return this._ai.jsonOps.concatenate(obj1, obj2, options);
1769
- }
1770
-
1771
- // --- Embeddings & Semantic Search ---
1772
-
1773
- /**
1774
- * Generate embedding vector for text
1775
- * @param {string} text - Text to embed
1776
- * @returns {Promise<number[]>} Embedding vector
1777
- */
1778
- async embed(text) {
1779
- this._requireAI();
1780
- return this._ai.embeddings.embed(text);
1781
- }
1782
-
1783
- /**
1784
- * Perform semantic search in a holon/lens
1785
- * @param {string} query - Search query
1786
- * @param {string} holonId - Holon to search in
1787
- * @param {string} lensName - Lens to search in
1788
- * @param {Object} options - Search options
1789
- * @returns {Promise<Array>} Search results
1790
- */
1791
- async semanticSearch(query, holonId, lensName, options = {}) {
1792
- this._requireAI();
1793
- return this._ai.embeddings.semanticSearch(query, holonId, lensName, options);
1794
- }
1795
-
1796
- /**
1797
- * Store data with embedding for later semantic search
1798
- * @param {string} holonId - Holon ID
1799
- * @param {string} lensName - Lens name
1800
- * @param {Object} data - Data to store
1801
- * @param {string} textField - Field to generate embedding from
1802
- * @returns {Promise<boolean>} Success
1803
- */
1804
- async storeWithEmbedding(holonId, lensName, data, textField = 'description') {
1805
- this._requireAI();
1806
- return this._ai.embeddings.storeWithEmbedding(holonId, lensName, data, textField);
1807
- }
1808
-
1809
- // --- Multi-Perspective Council ---
1810
-
1811
- /**
1812
- * Ask the council of perspectives for wisdom
1813
- * @param {string} question - Question to ask
1814
- * @param {Object} options - Options
1815
- * @returns {Promise<Object>} Council responses
1816
- */
1817
- async askCouncil(question, options = {}) {
1818
- this._requireAI();
1819
- return this._ai.council.ask(question, options);
1820
- }
1821
-
1822
- /**
1823
- * Ask council with custom perspectives
1824
- * @param {string} question - Question to ask
1825
- * @param {string[]} perspectives - Custom perspectives
1826
- * @param {Object} options - Options
1827
- * @returns {Promise<Object>} Council responses
1828
- */
1829
- async askCouncilCustom(question, perspectives, options = {}) {
1830
- this._requireAI();
1831
- return this._ai.council.askCustom(question, perspectives, options);
1832
- }
1833
-
1834
- // --- Text-to-Speech ---
1835
-
1836
- /**
1837
- * Convert text to speech
1838
- * @param {string} text - Text to speak
1839
- * @param {string} voice - Voice to use
1840
- * @param {Object} options - TTS options
1841
- * @returns {Promise<Buffer>} Audio buffer
1842
- */
1843
- async textToSpeech(text, voice = 'nova', options = {}) {
1844
- this._requireAI();
1845
- return this._ai.tts.speak(text, voice, options);
1846
- }
1847
-
1848
- /**
1849
- * Convert text to speech as base64
1850
- * @param {string} text - Text to speak
1851
- * @param {string} voice - Voice to use
1852
- * @param {Object} options - TTS options
1853
- * @returns {Promise<string>} Base64 audio
1854
- */
1855
- async textToSpeechBase64(text, voice = 'nova', options = {}) {
1856
- this._requireAI();
1857
- return this._ai.tts.speakBase64(text, voice, options);
1858
- }
1859
-
1860
- // --- Natural Language Queries ---
1861
-
1862
- /**
1863
- * Query data using natural language
1864
- * @param {string} query - Natural language query
1865
- * @param {string} holonId - Holon to query (optional)
1866
- * @param {string} lensName - Lens to query (optional)
1867
- * @param {Object} options - Options
1868
- * @returns {Promise<Array>} Query results
1869
- */
1870
- async nlQuery(query, holonId = null, lensName = null, options = {}) {
1871
- this._requireAI();
1872
- return this._ai.nlQuery.execute(query, holonId, lensName, options);
1873
- }
1874
-
1875
- /**
1876
- * Parse natural language query to structured query
1877
- * @param {string} query - Natural language query
1878
- * @returns {Promise<Object>} Parsed query structure
1879
- */
1880
- async parseNLQuery(query) {
1881
- this._requireAI();
1882
- return this._ai.nlQuery.parse(query);
1883
- }
1884
-
1885
- // --- Auto-Classification ---
1886
-
1887
- /**
1888
- * Classify content to suggest the best lens
1889
- * @param {Object} content - Content to classify
1890
- * @param {Object} options - Options
1891
- * @returns {Promise<Object>} Classification result
1892
- */
1893
- async classifyToLens(content, options = {}) {
1894
- this._requireAI();
1895
- return this._ai.classifier.classifyToLens(content, options);
1896
- }
1897
-
1898
- /**
1899
- * Automatically store content in the best lens
1900
- * @param {string} holonId - Holon ID
1901
- * @param {Object} content - Content to store
1902
- * @param {Object} options - Options
1903
- * @returns {Promise<Object>} Storage result
1904
- */
1905
- async autoStore(holonId, content, options = {}) {
1906
- this._requireAI();
1907
- return this._ai.classifier.autoStore(holonId, content, options);
1908
- }
1909
-
1910
- /**
1911
- * Register a lens with the classifier
1912
- * @param {string} lensName - Lens name
1913
- * @param {string} description - Lens description
1914
- * @param {string[]} keywords - Keywords for this lens
1915
- */
1916
- registerLensForClassification(lensName, description, keywords = []) {
1917
- this._requireAI();
1918
- this._ai.classifier.registerLens(lensName, description, keywords);
1919
- }
1920
-
1921
- // --- Spatial Analysis ---
1922
-
1923
- /**
1924
- * AI-powered analysis of a geographic region
1925
- * @param {string} holonId - Holon to analyze
1926
- * @param {string} lensName - Lens to analyze (optional)
1927
- * @param {string} aspect - Aspect to focus on (optional)
1928
- * @param {Object} options - Options
1929
- * @returns {Promise<Object>} Analysis result
1930
- */
1931
- async analyzeRegion(holonId, lensName = null, aspect = null, options = {}) {
1932
- this._requireAI();
1933
- return this._ai.spatial.analyzeRegion(holonId, lensName, aspect, options);
1934
- }
1935
-
1936
- /**
1937
- * Compare data between two regions
1938
- * @param {string} holon1 - First holon
1939
- * @param {string} holon2 - Second holon
1940
- * @param {string} lensName - Lens to compare (optional)
1941
- * @returns {Promise<Object>} Comparison result
1942
- */
1943
- async compareRegions(holon1, holon2, lensName = null) {
1944
- this._requireAI();
1945
- return this._ai.spatial.compareRegions(holon1, holon2, lensName);
1946
- }
1947
-
1948
- /**
1949
- * Identify trends over time/space
1950
- * @param {string} holonId - Holon to analyze
1951
- * @param {string} lensName - Lens
1952
- * @param {Object} timeRange - Time range
1953
- * @returns {Promise<Object>} Trends
1954
- */
1955
- async spatialTrends(holonId, lensName, timeRange = null) {
1956
- this._requireAI();
1957
- return this._ai.spatial.spatialTrends(holonId, lensName, timeRange);
1958
- }
1959
-
1960
- // --- Smart Aggregation ---
1961
-
1962
- /**
1963
- * AI-summarized hierarchical aggregation up the holon tree
1964
- * @param {string} holonId - Starting holon
1965
- * @param {string} lensName - Lens to aggregate
1966
- * @param {Object} options - Options
1967
- * @returns {Promise<Object>} Aggregation result
1968
- */
1969
- async smartUpcast(holonId, lensName, options = {}) {
1970
- this._requireAI();
1971
- return this._ai.aggregation.smartUpcast(holonId, lensName, options);
1972
- }
1973
-
1974
- /**
1975
- * Generate comprehensive holon summary
1976
- * @param {string} holonId - Holon to summarize
1977
- * @returns {Promise<Object>} Summary
1978
- */
1979
- async generateHolonSummary(holonId) {
1980
- this._requireAI();
1981
- return this._ai.aggregation.generateHolonSummary(holonId);
1982
- }
1983
-
1984
- // --- Federation Advisor ---
1985
-
1986
- /**
1987
- * Get AI suggestions for federation partners
1988
- * @param {string} holonId - Holon to find partners for
1989
- * @param {Object} options - Options including candidateHolons
1990
- * @returns {Promise<Object>} Federation suggestions
1991
- */
1992
- async suggestFederations(holonId, options = {}) {
1993
- this._requireAI();
1994
- return this._ai.federationAdvisor.suggestFederations(holonId, options);
1995
- }
1996
-
1997
- /**
1998
- * Analyze federation health
1999
- * @param {string} holonId - Holon to analyze
2000
- * @returns {Promise<Object>} Health analysis
2001
- */
2002
- async analyzeFederationHealth(holonId) {
2003
- this._requireAI();
2004
- return this._ai.federationAdvisor.analyzeFederationHealth(holonId);
2005
- }
2006
-
2007
- /**
2008
- * Get federation optimization suggestions
2009
- * @param {string} holonId - Holon to optimize
2010
- * @returns {Promise<Object>} Optimization suggestions
2011
- */
2012
- async optimizeFederation(holonId) {
2013
- this._requireAI();
2014
- return this._ai.federationAdvisor.optimizeFederation(holonId);
2015
- }
2016
-
2017
- // --- Relationship Discovery ---
2018
-
2019
- /**
2020
- * Discover hidden relationships in data
2021
- * @param {string} holonId - Holon to analyze
2022
- * @param {string} lensName - Lens (optional)
2023
- * @returns {Promise<Object>} Discovered relationships
2024
- */
2025
- async discoverRelationships(holonId, lensName = null) {
2026
- this._requireAI();
2027
- return this._ai.relationships.discoverRelationships(holonId, lensName);
2028
- }
2029
-
2030
- /**
2031
- * Find semantically similar items
2032
- * @param {Object} item - Item to find similar items for
2033
- * @param {string} holonId - Holon to search in
2034
- * @param {string} lensName - Lens to search in (optional)
2035
- * @param {Object} options - Options
2036
- * @returns {Promise<Array>} Similar items
2037
- */
2038
- async findSimilar(item, holonId, lensName = null, options = {}) {
2039
- this._requireAI();
2040
- return this._ai.relationships.findSimilar(item, holonId, lensName, options);
2041
- }
2042
-
2043
- /**
2044
- * Build relationship graph
2045
- * @param {string} holonId - Holon
2046
- * @param {string} lensName - Lens
2047
- * @returns {Promise<Object>} Graph structure
2048
- */
2049
- async buildRelationshipGraph(holonId, lensName) {
2050
- this._requireAI();
2051
- return this._ai.relationships.buildGraph(holonId, lensName);
2052
- }
2053
-
2054
- /**
2055
- * Suggest connections for an item
2056
- * @param {Object} item - Item to find connections for
2057
- * @param {string} holonId - Holon
2058
- * @param {string} lensName - Lens
2059
- * @returns {Promise<Object>} Suggested connections
2060
- */
2061
- async suggestConnections(item, holonId, lensName) {
2062
- this._requireAI();
2063
- return this._ai.relationships.suggestConnections(item, holonId, lensName);
2064
- }
2065
-
2066
- // --- Task Breakdown ---
2067
-
2068
- /**
2069
- * Recursively break down a task/quest into subtasks
2070
- * @param {Object} item - The item to break down (must have id)
2071
- * @param {string} holonId - Holon where the item lives
2072
- * @param {string} lensName - Lens name (e.g., 'quests', 'tasks')
2073
- * @param {Object} options - Breakdown options
2074
- * @param {number} options.depth - Maximum recursion depth (default: 2)
2075
- * @param {number|Object} options.stepsPerLevel - Subtasks per level (default: {min:3, max:5})
2076
- * @param {boolean} options.useContext - Look at other items for context (default: true)
2077
- * @param {boolean} options.storeResults - Store generated subtasks (default: true)
2078
- * @returns {Promise<Object>} Breakdown result with tree structure
2079
- */
2080
- async breakdown(item, holonId, lensName, options = {}) {
2081
- this._requireAI();
2082
- return this._ai.taskBreakdown.breakdown(item, holonId, lensName, options);
2083
- }
2084
-
2085
- /**
2086
- * Suggest breakdown strategy for a task
2087
- * @param {Object} item - Item to analyze
2088
- * @returns {Promise<Object>} Suggested breakdown strategy
2089
- */
2090
- async suggestBreakdownStrategy(item) {
2091
- this._requireAI();
2092
- return this._ai.taskBreakdown.suggestStrategy(item);
2093
- }
2094
-
2095
- /**
2096
- * Flatten a breakdown tree into a list
2097
- * @param {Object} breakdownResult - Result from breakdown()
2098
- * @returns {Object[]} Flat list of all items
2099
- */
2100
- flattenBreakdown(breakdownResult) {
2101
- this._requireAI();
2102
- return this._ai.taskBreakdown.flatten(breakdownResult);
2103
- }
2104
-
2105
- /**
2106
- * Get items in dependency order for execution
2107
- * @param {Object} breakdownResult - Result from breakdown()
2108
- * @returns {Object[]} Items in dependency order
2109
- */
2110
- getBreakdownDependencyOrder(breakdownResult) {
2111
- this._requireAI();
2112
- return this._ai.taskBreakdown.getDependencyOrder(breakdownResult);
2113
- }
2114
-
2115
- /**
2116
- * Get progress on a breakdown tree
2117
- * @param {string} holonId - Holon ID
2118
- * @param {string} lensName - Lens name
2119
- * @param {string} itemId - Root item ID
2120
- * @returns {Promise<Object>} Progress summary
2121
- */
2122
- async getBreakdownProgress(holonId, lensName, itemId) {
2123
- this._requireAI();
2124
- return this._ai.taskBreakdown.getProgress(holonId, lensName, itemId);
2125
- }
2126
-
2127
- // --- H3 Geospatial AI ---
2128
-
2129
- /**
2130
- * Suggest optimal H3 resolution for a task/project
2131
- * @param {Object} item - Item to analyze
2132
- * @param {Object} options - Options including currentResolution
2133
- * @returns {Promise<Object>} Resolution recommendation
2134
- */
2135
- async suggestH3Resolution(item, options = {}) {
2136
- this._requireAI();
2137
- return this._ai.h3ai.suggestResolution(item, options);
2138
- }
2139
-
2140
- /**
2141
- * Analyze geographic distribution of data in a region
2142
- * @param {string} holonId - Parent holon to analyze
2143
- * @param {string} lensName - Lens to analyze
2144
- * @param {Object} options - Options
2145
- * @returns {Promise<Object>} Distribution analysis
2146
- */
2147
- async analyzeH3Distribution(holonId, lensName, options = {}) {
2148
- this._requireAI();
2149
- return this._ai.h3ai.analyzeDistribution(holonId, lensName, options);
2150
- }
2151
-
2152
- /**
2153
- * Find relevant data from neighboring H3 cells
2154
- * @param {string} holonId - Center holon
2155
- * @param {string} lensName - Lens to search
2156
- * @param {Object} options - Options including ringSize
2157
- * @returns {Promise<Object>} Neighbor analysis
2158
- */
2159
- async findH3NeighborRelevance(holonId, lensName, options = {}) {
2160
- this._requireAI();
2161
- return this._ai.h3ai.findNeighborRelevance(holonId, lensName, options);
2162
- }
2163
-
2164
- /**
2165
- * Suggest geographic expansion or contraction for an item
2166
- * @param {Object} item - Item to analyze
2167
- * @param {string} holonId - Current holon
2168
- * @param {string} lensName - Lens
2169
- * @param {Object} options - Options
2170
- * @returns {Promise<Object>} Scope suggestions
2171
- */
2172
- async suggestGeographicScope(item, holonId, lensName, options = {}) {
2173
- this._requireAI();
2174
- return this._ai.h3ai.suggestGeographicScope(item, holonId, lensName, options);
2175
- }
2176
-
2177
- /**
2178
- * Analyze coverage gaps in a region
2179
- * @param {string} holonId - Region to analyze
2180
- * @param {string} lensName - Lens
2181
- * @param {Object} options - Options including targetResolution
2182
- * @returns {Promise<Object>} Coverage analysis
2183
- */
2184
- async analyzeH3Coverage(holonId, lensName, options = {}) {
2185
- this._requireAI();
2186
- return this._ai.h3ai.analyzeCoverage(holonId, lensName, options);
2187
- }
2188
-
2189
- /**
2190
- * Find patterns across multiple H3 resolutions
2191
- * @param {string} holonId - Starting holon
2192
- * @param {string} lensName - Lens
2193
- * @param {Object} options - Options including levels
2194
- * @returns {Promise<Object>} Cross-resolution insights
2195
- */
2196
- async crossH3ResolutionInsights(holonId, lensName, options = {}) {
2197
- this._requireAI();
2198
- return this._ai.h3ai.crossResolutionInsights(holonId, lensName, options);
2199
- }
2200
-
2201
- /**
2202
- * Suggest item migration between holons based on geographic fit
2203
- * @param {Object} item - Item to analyze
2204
- * @param {string} holonId - Current location
2205
- * @param {string} lensName - Lens
2206
- * @param {Object} options - Options including searchRadius
2207
- * @returns {Promise<Object>} Migration suggestions
2208
- */
2209
- async suggestH3Migration(item, holonId, lensName, options = {}) {
2210
- this._requireAI();
2211
- return this._ai.h3ai.suggestMigration(item, holonId, lensName, options);
2212
- }
2213
-
2214
- /**
2215
- * Generate a geographic activity report for a region
2216
- * @param {string} holonId - Region to report on
2217
- * @param {Object} options - Options including lenses array
2218
- * @returns {Promise<Object>} Geographic report
2219
- */
2220
- async generateH3Report(holonId, options = {}) {
2221
- this._requireAI();
2222
- return this._ai.h3ai.generateGeographicReport(holonId, options);
2223
- }
2224
-
2225
- /**
2226
- * Find geographic clusters of related items
2227
- * @param {string} holonId - Region to analyze
2228
- * @param {string} lensName - Lens
2229
- * @param {Object} options - Options including clusterResolution
2230
- * @returns {Promise<Object>} Geographic clusters
2231
- */
2232
- async findH3Clusters(holonId, lensName, options = {}) {
2233
- this._requireAI();
2234
- return this._ai.h3ai.findGeographicClusters(holonId, lensName, options);
2235
- }
2236
-
2237
- /**
2238
- * Analyze geographic impact of an item
2239
- * @param {Object} item - Item to analyze
2240
- * @param {string} holonId - Item's holon
2241
- * @param {string} lensName - Lens
2242
- * @param {Object} options - Options
2243
- * @returns {Promise<Object>} Impact analysis
2244
- */
2245
- async analyzeH3Impact(item, holonId, lensName, options = {}) {
2246
- this._requireAI();
2247
- return this._ai.h3ai.analyzeGeographicImpact(item, holonId, lensName, options);
2248
- }
2249
-
2250
- // === Cross-Holosphere Federation ===
2251
-
2252
- /**
2253
- * Add a federated holosphere partner (manual key exchange)
2254
- * @param {string} pubKey - Partner's public key
2255
- * @param {Object} options - Federation options
2256
- * @param {string} options.alias - Human-readable name for this partner
2257
- * @param {Object[]} options.inboundCapabilities - Capabilities they've granted us
2258
- * @returns {Promise<boolean>} Success indicator
2259
- */
2260
- async addFederatedHolosphere(pubKey, options = {}) {
2261
- if (!pubKey || typeof pubKey !== 'string') {
2262
- throw new ValidationError('pubKey must be a valid public key string');
2263
- }
2264
-
2265
- return registry.addFederatedPartner(
2266
- this.client,
2267
- this.config.appName,
2268
- pubKey,
2269
- options
2270
- );
2271
- }
2272
-
2273
- /**
2274
- * Remove a federated holosphere partner
2275
- * @param {string} pubKey - Partner's public key
2276
- * @returns {Promise<boolean>} Success indicator
2277
- */
2278
- async removeFederatedHolosphere(pubKey) {
2279
- return registry.removeFederatedPartner(
2280
- this.client,
2281
- this.config.appName,
2282
- pubKey
2283
- );
2284
- }
2285
-
2286
- /**
2287
- * Get all federated holosphere public keys
2288
- * @returns {Promise<string[]>} Array of federated public keys
2289
- */
2290
- async getFederatedHolospheres() {
2291
- return registry.getFederatedAuthors(
2292
- this.client,
2293
- this.config.appName
2294
- );
2295
- }
2296
-
2297
- /**
2298
- * Get the full federation registry for this holosphere
2299
- * @returns {Promise<Object>} Federation registry
2300
- */
2301
- async getFederationRegistry() {
2302
- return registry.getFederationRegistry(
2303
- this.client,
2304
- this.config.appName
2305
- );
2306
- }
2307
-
2308
- /**
2309
- * Store a capability token received from another holosphere
2310
- * @param {string} partnerPubKey - Partner's public key
2311
- * @param {Object} capabilityInfo - Capability information
2312
- * @param {string} capabilityInfo.token - The capability token
2313
- * @param {Object} capabilityInfo.scope - Token scope
2314
- * @param {string[]} capabilityInfo.permissions - Granted permissions
2315
- * @param {number} capabilityInfo.expires - Expiration timestamp
2316
- * @returns {Promise<boolean>} Success indicator
2317
- */
2318
- async storeInboundCapability(partnerPubKey, capabilityInfo) {
2319
- return registry.storeInboundCapability(
2320
- this.client,
2321
- this.config.appName,
2322
- partnerPubKey,
2323
- capabilityInfo
2324
- );
2325
- }
2326
-
2327
- /**
2328
- * Read data from a federated source (cross-holosphere)
2329
- * @param {string} sourcePubKey - Source holosphere's public key
2330
- * @param {string} holonId - Holon ID
2331
- * @param {string} lensName - Lens name
2332
- * @param {string} dataId - Optional specific data ID
2333
- * @returns {Promise<Object|Object[]|null>} Data from federated source
2334
- */
2335
- async readFromFederatedSource(sourcePubKey, holonId, lensName, dataId = null) {
2336
- // Get capability for this source
2337
- const capabilityEntry = await registry.getCapabilityForAuthor(
2338
- this.client,
2339
- this.config.appName,
2340
- sourcePubKey,
2341
- { holonId, lensName, dataId }
2342
- );
2343
-
2344
- if (!capabilityEntry) {
2345
- throw new AuthorizationError('No valid capability for federated source', 'read');
2346
- }
2347
-
2348
- // Verify capability is still valid
2349
- const isValid = await this.verifyCapability(
2350
- capabilityEntry.token,
2351
- 'read',
2352
- { holonId, lensName, dataId }
2353
- );
2354
-
2355
- if (!isValid) {
2356
- throw new AuthorizationError('Capability verification failed', 'read');
2357
- }
2358
-
2359
- // Read with specific author
2360
- if (dataId) {
2361
- const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
2362
- return nostrAsync.nostrGet(this.client, path, 30000, {
2363
- authors: [sourcePubKey],
2364
- includeAuthor: true,
2365
- });
2366
- } else {
2367
- const path = storage.buildPath(this.config.appName, holonId, lensName);
2368
- return nostrAsync.nostrGetAll(this.client, path, 30000, {
2369
- authors: [sourcePubKey],
2370
- includeAuthor: true,
2371
- });
2372
- }
2373
- }
2374
-
2375
- /**
2376
- * Create a cross-holosphere hologram with embedded capability
2377
- * @param {string} sourcePubKey - Source holosphere's public key
2378
- * @param {string} sourceHolon - Source holon ID
2379
- * @param {string} lensName - Lens name
2380
- * @param {string} dataId - Data ID
2381
- * @param {string} targetHolon - Target holon ID (where hologram will live)
2382
- * @param {Object} options - Creation options
2383
- * @param {boolean} options.embedCapability - Whether to embed capability in hologram (default: true)
2384
- * @returns {Promise<Object>} Hologram object
2385
- */
2386
- async createCrossHolosphereHologram(sourcePubKey, sourceHolon, lensName, dataId, targetHolon, options = {}) {
2387
- const { embedCapability = true } = options;
2388
-
2389
- // Get capability for this source
2390
- const capabilityEntry = await registry.getCapabilityForAuthor(
2391
- this.client,
2392
- this.config.appName,
2393
- sourcePubKey,
2394
- { holonId: sourceHolon, lensName, dataId }
2395
- );
2396
-
2397
- if (!capabilityEntry) {
2398
- throw new AuthorizationError('No valid capability for cross-holosphere hologram', 'read');
2399
- }
2400
-
2401
- // Create hologram with embedded capability
2402
- const hologram = federation.createHologram(
2403
- sourceHolon,
2404
- targetHolon,
2405
- lensName,
2406
- dataId,
2407
- this.config.appName,
2408
- {
2409
- authorPubKey: sourcePubKey,
2410
- capability: embedCapability ? capabilityEntry.token : null,
2411
- }
2412
- );
2413
-
2414
- // Write hologram to target holon
2415
- const targetPath = storage.buildPath(this.config.appName, targetHolon, lensName, dataId);
2416
- await storage.write(this.client, targetPath, hologram);
2417
-
2418
- return hologram;
2419
- }
2420
-
2421
- /**
2422
- * Issue a capability token for federation with another holosphere
2423
- * @param {string} targetPubKey - Recipient's public key
2424
- * @param {Object} scope - Capability scope { holonId, lensName, dataId? }
2425
- * @param {string[]} permissions - Array of permissions ['read', 'write', 'delete']
2426
- * @param {Object} options - Additional options
2427
- * @param {number} options.expiresIn - Expiration in ms (default: 1 hour)
2428
- * @param {boolean} options.trackInRegistry - Store in registry (default: true)
2429
- * @returns {Promise<string>} Capability token
2430
- */
2431
- async issueCapabilityForFederation(targetPubKey, scope, permissions, options = {}) {
2432
- const { expiresIn = 3600000, trackInRegistry = true } = options;
2433
-
2434
- const token = await crypto.issueCapability(
2435
- permissions,
2436
- scope,
2437
- targetPubKey,
2438
- {
2439
- expiresIn,
2440
- issuerKey: this.client.privateKey,
2441
- }
2442
- );
2443
-
2444
- // Track in registry
2445
- if (trackInRegistry) {
2446
- // Ensure partner exists in registry
2447
- const partner = await registry.getFederatedPartner(
2448
- this.client,
2449
- this.config.appName,
2450
- targetPubKey
2451
- );
2452
-
2453
- if (!partner) {
2454
- await registry.addFederatedPartner(
2455
- this.client,
2456
- this.config.appName,
2457
- targetPubKey,
2458
- { addedVia: 'capability_issued' }
2459
- );
2460
- }
2461
-
2462
- await registry.storeOutboundCapability(
2463
- this.client,
2464
- this.config.appName,
2465
- targetPubKey,
2466
- {
2467
- tokenHash: await this._hashToken(token),
2468
- scope,
2469
- permissions,
2470
- expires: Date.now() + expiresIn,
2471
- }
2472
- );
2473
- }
2474
-
2475
- return token;
2476
- }
2477
-
2478
- /**
2479
- * Hash a token for storage (don't store full token)
2480
- * @private
2481
- */
2482
- async _hashToken(token) {
2483
- const { sha256 } = await import('@noble/hashes/sha256');
2484
- const { bytesToHex } = await import('@noble/hashes/utils');
2485
- const encoder = new TextEncoder();
2486
- return bytesToHex(sha256(encoder.encode(token)));
2487
- }
2488
-
2489
- // === Nostr Discovery Protocol (Optional) ===
2490
-
2491
- /**
2492
- * Send a federation request via Nostr
2493
- * @param {string} targetPubKey - Target holosphere's public key
2494
- * @param {Object} options - Request options
2495
- * @param {Object} options.scope - Requested scope { holonId, lensName }
2496
- * @param {string[]} options.permissions - Requested permissions
2497
- * @param {string} options.message - Optional message
2498
- * @returns {Promise<Object>} Published event info
2499
- */
2500
- async sendFederationRequest(targetPubKey, options = {}) {
2501
- return discovery.sendFederationRequest(
2502
- this.client,
2503
- this.config.appName,
2504
- targetPubKey,
2505
- options
2506
- );
2507
- }
2508
-
2509
- /**
2510
- * Subscribe to incoming federation requests
2511
- * @param {Function} callback - Called with each request
2512
- * @returns {Promise<Object>} Subscription with unsubscribe method
2513
- */
2514
- async subscribeFederationRequests(callback) {
2515
- return discovery.subscribeFederationRequests(this.client, callback);
2516
- }
2517
-
2518
- /**
2519
- * Accept a federation request
2520
- * @param {Object} request - Request object from subscription
2521
- * @param {Object} options - Acceptance options
2522
- * @returns {Promise<Object>} Published acceptance event info
2523
- */
2524
- async acceptFederationRequest(request, options = {}) {
2525
- return discovery.acceptFederationRequest(
2526
- this.client,
2527
- this.config.appName,
2528
- request,
2529
- options
2530
- );
2531
- }
2532
-
2533
- /**
2534
- * Decline a federation request
2535
- * @param {Object} request - Request object from subscription
2536
- * @param {string} reason - Optional decline reason
2537
- * @returns {Promise<Object>} Published decline event info
2538
- */
2539
- async declineFederationRequest(request, reason = '') {
2540
- return discovery.declineFederationRequest(
2541
- this.client,
2542
- this.config.appName,
2543
- request,
2544
- reason
2545
- );
2546
- }
2547
-
2548
- /**
2549
- * Subscribe to federation acceptances (responses to our requests)
2550
- * @param {Function} callback - Called with each acceptance
2551
- * @returns {Promise<Object>} Subscription with unsubscribe method
2552
- */
2553
- async subscribeFederationAcceptances(callback) {
2554
- return discovery.subscribeFederationAcceptances(
2555
- this.client,
2556
- this.config.appName,
2557
- callback
2558
- );
2559
- }
2560
-
2561
- /**
2562
- * Subscribe to federation declines
2563
- * @param {Function} callback - Called with each decline
2564
- * @returns {Promise<Object>} Subscription with unsubscribe method
2565
- */
2566
- async subscribeFederationDeclines(callback) {
2567
- return discovery.subscribeFederationDeclines(this.client, callback);
2568
- }
2569
-
2570
- /**
2571
- * Get pending federation requests (requests we've sent that haven't been answered)
2572
- * @param {Object} options - Query options
2573
- * @returns {Promise<Object[]>} Array of pending requests
2574
- */
2575
- async getPendingFederationRequests(options = {}) {
2576
- return discovery.getPendingFederationRequests(this.client, options);
2577
- }
2578
- }
2579
-
2580
- // Export error classes
2581
- export class AuthorizationError extends Error {
2582
- constructor(message, requiredPermission = null) {
2583
- super(message);
2584
- this.name = 'AuthorizationError';
2585
- this.requiredPermission = requiredPermission;
2586
- }
2587
- }
2588
-
2589
- export class HolosphereError extends Error {
2590
- constructor(message) {
2591
- super(message);
2592
- this.name = 'HolosphereError';
2593
- }
2594
- }
2595
-
2596
- // Re-export ValidationError from validator module
2597
- export { ValidationError } from './schema/validator.js';
2598
-
2599
- // Export utilities
2600
- export { nostrAsync };
2601
-
2602
- // Export hologram helper functions
2246
+ // Export network utilities
2603
2247
  export {
2604
- isHologram,
2605
- isResolvedHologram,
2606
- getHologramSource,
2607
- updateHologramOverrides,
2608
- addActiveHologram,
2609
- removeActiveHologram,
2610
- refreshActiveHolograms,
2611
- deleteHologram,
2612
- propagateData,
2613
- createHologram,
2614
- resolveHologram,
2615
- cleanupCircularHolograms,
2616
- cleanupCircularHologramsByIds
2617
- } from './federation/hologram.js';
2618
-
2619
- // Export federation registry functions
2620
- export {
2621
- getFederationRegistry,
2622
- addFederatedPartner,
2623
- removeFederatedPartner,
2624
- getFederatedAuthors,
2625
- getCapabilityForAuthor,
2626
- storeInboundCapability,
2627
- storeOutboundCapability,
2628
- cleanupExpiredCapabilities
2629
- } from './federation/registry.js';
2630
-
2631
- // Export Nostr discovery protocol functions
2632
- export {
2633
- sendFederationRequest,
2634
- subscribeFederationRequests,
2635
- acceptFederationRequest,
2636
- declineFederationRequest,
2637
- subscribeFederationAcceptances,
2638
- subscribeFederationDeclines,
2639
- getPendingFederationRequests
2640
- } from './federation/discovery.js';
2641
-
2642
- // Export scope matching utility
2643
- export { matchScope } from './crypto/secp256k1.js';
2644
-
2645
- // Export AI modules for standalone use
2646
- export {
2647
- LLMService,
2648
- SchemaExtractor,
2649
- JSONOps,
2650
- Embeddings,
2651
- Council,
2652
- TTS,
2653
- VOICES,
2654
- MODELS,
2655
- NLQuery,
2656
- Classifier,
2657
- SpatialAnalysis,
2658
- SmartAggregation,
2659
- FederationAdvisor,
2660
- RelationshipDiscovery,
2661
- TaskBreakdown,
2662
- H3AI
2663
- };
2664
-
2665
- // Export AI factory function
2666
- export { createAIServices } from './ai/index.js';
2248
+ NETWORKS,
2249
+ getNetwork,
2250
+ getNetworksByType,
2251
+ listNetworks,
2252
+ isNetworkSupported,
2253
+ getTxUrl,
2254
+ getAddressUrl
2255
+ } from './contracts/networks.js';
2256
+
2257
+ // Export contracts namespace
2258
+ export { networks };
2667
2259
 
2668
2260
  // Default export for backward compatibility
2669
2261
  export default HoloSphere;