holosphere 1.1.19 → 2.0.0-alpha0

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 (146) hide show
  1. package/.env.example +36 -0
  2. package/.eslintrc.json +16 -0
  3. package/.prettierrc.json +7 -0
  4. package/README.md +476 -531
  5. package/bin/holosphere-activitypub.js +158 -0
  6. package/cleanup-test-data.js +204 -0
  7. package/examples/demo.html +1333 -0
  8. package/examples/example-bot.js +197 -0
  9. package/package.json +47 -87
  10. package/scripts/check-bundle-size.js +54 -0
  11. package/scripts/check-quest-ids.js +77 -0
  12. package/scripts/import-holons.js +578 -0
  13. package/scripts/publish-to-relay.js +101 -0
  14. package/scripts/read-example.js +186 -0
  15. package/scripts/relay-diagnostic.js +59 -0
  16. package/scripts/relay-example.js +179 -0
  17. package/scripts/resync-to-relay.js +245 -0
  18. package/scripts/revert-import.js +196 -0
  19. package/scripts/test-hybrid-mode.js +108 -0
  20. package/scripts/test-local-storage.js +63 -0
  21. package/scripts/test-nostr-direct.js +55 -0
  22. package/scripts/test-read-data.js +45 -0
  23. package/scripts/test-write-read.js +63 -0
  24. package/scripts/verify-import.js +95 -0
  25. package/scripts/verify-relay-data.js +139 -0
  26. package/src/ai/aggregation.js +319 -0
  27. package/src/ai/breakdown.js +511 -0
  28. package/src/ai/classifier.js +217 -0
  29. package/src/ai/council.js +228 -0
  30. package/src/ai/embeddings.js +279 -0
  31. package/src/ai/federation-ai.js +324 -0
  32. package/src/ai/h3-ai.js +955 -0
  33. package/src/ai/index.js +112 -0
  34. package/src/ai/json-ops.js +225 -0
  35. package/src/ai/llm-service.js +205 -0
  36. package/src/ai/nl-query.js +223 -0
  37. package/src/ai/relationships.js +353 -0
  38. package/src/ai/schema-extractor.js +218 -0
  39. package/src/ai/spatial.js +293 -0
  40. package/src/ai/tts.js +194 -0
  41. package/src/content/social-protocols.js +168 -0
  42. package/src/core/holosphere.js +273 -0
  43. package/src/crypto/secp256k1.js +259 -0
  44. package/src/federation/discovery.js +334 -0
  45. package/src/federation/hologram.js +1042 -0
  46. package/src/federation/registry.js +386 -0
  47. package/src/hierarchical/upcast.js +110 -0
  48. package/src/index.js +2669 -0
  49. package/src/schema/validator.js +91 -0
  50. package/src/spatial/h3-operations.js +110 -0
  51. package/src/storage/backend-factory.js +125 -0
  52. package/src/storage/backend-interface.js +142 -0
  53. package/src/storage/backends/activitypub/server.js +653 -0
  54. package/src/storage/backends/activitypub-backend.js +272 -0
  55. package/src/storage/backends/gundb-backend.js +233 -0
  56. package/src/storage/backends/nostr-backend.js +136 -0
  57. package/src/storage/filesystem-storage-browser.js +41 -0
  58. package/src/storage/filesystem-storage.js +138 -0
  59. package/src/storage/global-tables.js +81 -0
  60. package/src/storage/gun-async.js +281 -0
  61. package/src/storage/gun-wrapper.js +221 -0
  62. package/src/storage/indexeddb-storage.js +122 -0
  63. package/src/storage/key-storage-simple.js +76 -0
  64. package/src/storage/key-storage.js +136 -0
  65. package/src/storage/memory-storage.js +59 -0
  66. package/src/storage/migration.js +338 -0
  67. package/src/storage/nostr-async.js +811 -0
  68. package/src/storage/nostr-client.js +939 -0
  69. package/src/storage/nostr-wrapper.js +211 -0
  70. package/src/storage/outbox-queue.js +208 -0
  71. package/src/storage/persistent-storage.js +109 -0
  72. package/src/storage/sync-service.js +164 -0
  73. package/src/subscriptions/manager.js +142 -0
  74. package/test-ai-real-api.js +202 -0
  75. package/tests/unit/ai/aggregation.test.js +295 -0
  76. package/tests/unit/ai/breakdown.test.js +446 -0
  77. package/tests/unit/ai/classifier.test.js +294 -0
  78. package/tests/unit/ai/council.test.js +262 -0
  79. package/tests/unit/ai/embeddings.test.js +384 -0
  80. package/tests/unit/ai/federation-ai.test.js +344 -0
  81. package/tests/unit/ai/h3-ai.test.js +458 -0
  82. package/tests/unit/ai/index.test.js +304 -0
  83. package/tests/unit/ai/json-ops.test.js +307 -0
  84. package/tests/unit/ai/llm-service.test.js +390 -0
  85. package/tests/unit/ai/nl-query.test.js +383 -0
  86. package/tests/unit/ai/relationships.test.js +311 -0
  87. package/tests/unit/ai/schema-extractor.test.js +384 -0
  88. package/tests/unit/ai/spatial.test.js +279 -0
  89. package/tests/unit/ai/tts.test.js +279 -0
  90. package/tests/unit/content.test.js +332 -0
  91. package/tests/unit/contract/core.test.js +88 -0
  92. package/tests/unit/contract/crypto.test.js +198 -0
  93. package/tests/unit/contract/data.test.js +223 -0
  94. package/tests/unit/contract/federation.test.js +181 -0
  95. package/tests/unit/contract/hierarchical.test.js +113 -0
  96. package/tests/unit/contract/schema.test.js +114 -0
  97. package/tests/unit/contract/social.test.js +217 -0
  98. package/tests/unit/contract/spatial.test.js +110 -0
  99. package/tests/unit/contract/subscriptions.test.js +128 -0
  100. package/tests/unit/contract/utils.test.js +159 -0
  101. package/tests/unit/core.test.js +152 -0
  102. package/tests/unit/crypto.test.js +328 -0
  103. package/tests/unit/federation.test.js +234 -0
  104. package/tests/unit/gun-async.test.js +252 -0
  105. package/tests/unit/hierarchical.test.js +399 -0
  106. package/tests/unit/integration/scenario-01-geographic-storage.test.js +74 -0
  107. package/tests/unit/integration/scenario-02-federation.test.js +76 -0
  108. package/tests/unit/integration/scenario-03-subscriptions.test.js +102 -0
  109. package/tests/unit/integration/scenario-04-validation.test.js +129 -0
  110. package/tests/unit/integration/scenario-05-hierarchy.test.js +125 -0
  111. package/tests/unit/integration/scenario-06-social.test.js +135 -0
  112. package/tests/unit/integration/scenario-07-persistence.test.js +130 -0
  113. package/tests/unit/integration/scenario-08-authorization.test.js +161 -0
  114. package/tests/unit/integration/scenario-09-cross-dimensional.test.js +139 -0
  115. package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +357 -0
  116. package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +410 -0
  117. package/tests/unit/integration/scenario-12-capability-federated-read.test.js +719 -0
  118. package/tests/unit/performance/benchmark.test.js +85 -0
  119. package/tests/unit/schema.test.js +213 -0
  120. package/tests/unit/spatial.test.js +158 -0
  121. package/tests/unit/storage.test.js +195 -0
  122. package/tests/unit/subscriptions.test.js +328 -0
  123. package/tests/unit/test-data-permanence-debug.js +197 -0
  124. package/tests/unit/test-data-permanence.js +340 -0
  125. package/tests/unit/test-key-persistence-fixed.js +148 -0
  126. package/tests/unit/test-key-persistence.js +172 -0
  127. package/tests/unit/test-relay-permanence.js +376 -0
  128. package/tests/unit/test-second-node.js +95 -0
  129. package/tests/unit/test-simple-write.js +89 -0
  130. package/vite.config.js +49 -0
  131. package/vitest.config.js +20 -0
  132. package/FEDERATION.md +0 -213
  133. package/compute.js +0 -298
  134. package/content.js +0 -1022
  135. package/federation.js +0 -1234
  136. package/global.js +0 -736
  137. package/hexlib.js +0 -335
  138. package/hologram.js +0 -183
  139. package/holosphere-bundle.esm.js +0 -34549
  140. package/holosphere-bundle.js +0 -34580
  141. package/holosphere-bundle.min.js +0 -49
  142. package/holosphere.d.ts +0 -604
  143. package/holosphere.js +0 -739
  144. package/node.js +0 -246
  145. package/schema.js +0 -139
  146. package/utils.js +0 -302
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Nostr-based Federation Discovery Protocol
3
+ * Optional feature for automated federation handshake via Nostr events
4
+ */
5
+
6
+ import { issueCapability } from '../crypto/secp256k1.js';
7
+ import { addFederatedPartner, storeInboundCapability } from './registry.js';
8
+
9
+ // Custom event kinds for federation protocol
10
+ const FEDERATION_REQUEST_KIND = 30078;
11
+ const FEDERATION_ACCEPT_KIND = 30079;
12
+ const FEDERATION_DECLINE_KIND = 30080;
13
+
14
+ /**
15
+ * Send a federation request to another holosphere via Nostr
16
+ * @param {Object} client - NostrClient instance
17
+ * @param {string} appname - Application namespace
18
+ * @param {string} targetPubKey - Target holosphere's public key
19
+ * @param {Object} options - Request options
20
+ * @param {Object} options.scope - Requested scope { holonId, lensName }
21
+ * @param {string[]} options.permissions - Requested permissions
22
+ * @param {string} options.message - Optional message to include
23
+ * @param {number} options.expiresIn - Token expiration in ms (default: 30 days)
24
+ * @returns {Promise<Object>} Published event info
25
+ */
26
+ export async function sendFederationRequest(client, appname, targetPubKey, options = {}) {
27
+ const {
28
+ scope = { holonId: '*', lensName: '*' },
29
+ permissions = ['read'],
30
+ message = 'Federation request',
31
+ expiresIn = 30 * 24 * 60 * 60 * 1000, // 30 days
32
+ } = options;
33
+
34
+ // Issue capability we're offering them (access to our data)
35
+ const offeredCapability = await issueCapability(
36
+ permissions,
37
+ scope,
38
+ targetPubKey,
39
+ { issuerKey: client.privateKey, expiresIn }
40
+ );
41
+
42
+ const event = {
43
+ kind: FEDERATION_REQUEST_KIND,
44
+ created_at: Math.floor(Date.now() / 1000),
45
+ tags: [
46
+ ['d', `federation-request-${targetPubKey.slice(0, 16)}`],
47
+ ['p', targetPubKey],
48
+ ['holosphere', appname],
49
+ ['scope', JSON.stringify(scope)],
50
+ ['permissions', permissions.join(',')],
51
+ ],
52
+ content: JSON.stringify({
53
+ message,
54
+ offeredCapability,
55
+ requestedScope: scope,
56
+ requestedPermissions: permissions,
57
+ }),
58
+ };
59
+
60
+ const result = await client.publish(event);
61
+ return {
62
+ eventId: result.id || null,
63
+ targetPubKey,
64
+ scope,
65
+ permissions,
66
+ offeredCapability,
67
+ ...result,
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Subscribe to incoming federation requests
73
+ * @param {Object} client - NostrClient instance
74
+ * @param {Function} callback - Called with each request
75
+ * @returns {Promise<Object>} Subscription with unsubscribe method
76
+ */
77
+ export async function subscribeFederationRequests(client, callback) {
78
+ const filter = {
79
+ kinds: [FEDERATION_REQUEST_KIND],
80
+ '#p': [client.publicKey],
81
+ };
82
+
83
+ return client.subscribe(filter, (event) => {
84
+ try {
85
+ const content = JSON.parse(event.content);
86
+ const scopeTag = event.tags.find(t => t[0] === 'scope');
87
+ const permissionsTag = event.tags.find(t => t[0] === 'permissions');
88
+ const holosphereTag = event.tags.find(t => t[0] === 'holosphere');
89
+
90
+ const request = {
91
+ eventId: event.id,
92
+ requesterPubKey: event.pubkey,
93
+ appname: holosphereTag?.[1],
94
+ scope: scopeTag ? JSON.parse(scopeTag[1]) : { holonId: '*', lensName: '*' },
95
+ permissions: permissionsTag ? permissionsTag[1].split(',') : ['read'],
96
+ message: content.message,
97
+ offeredCapability: content.offeredCapability,
98
+ timestamp: event.created_at,
99
+ };
100
+
101
+ callback(request);
102
+ } catch (error) {
103
+ console.error('Failed to parse federation request:', error);
104
+ }
105
+ });
106
+ }
107
+
108
+ /**
109
+ * Accept a federation request
110
+ * @param {Object} client - NostrClient instance
111
+ * @param {string} appname - Application namespace
112
+ * @param {Object} request - Request object from subscription
113
+ * @param {Object} options - Acceptance options
114
+ * @param {Object} options.scope - Scope we're granting (defaults to requested)
115
+ * @param {string[]} options.permissions - Permissions we're granting
116
+ * @param {string} options.alias - Alias for this partner
117
+ * @param {number} options.expiresIn - Token expiration in ms
118
+ * @returns {Promise<Object>} Published acceptance event info
119
+ */
120
+ export async function acceptFederationRequest(client, appname, request, options = {}) {
121
+ const {
122
+ scope = request.scope,
123
+ permissions = request.permissions,
124
+ alias = null,
125
+ expiresIn = 30 * 24 * 60 * 60 * 1000,
126
+ } = options;
127
+
128
+ // Issue our capability to them
129
+ const grantedCapability = await issueCapability(
130
+ permissions,
131
+ scope,
132
+ request.requesterPubKey,
133
+ { issuerKey: client.privateKey, expiresIn }
134
+ );
135
+
136
+ // Store their offered capability in our registry
137
+ await addFederatedPartner(client, appname, request.requesterPubKey, {
138
+ alias,
139
+ addedVia: 'nostr_discovery',
140
+ inboundCapabilities: [{
141
+ token: request.offeredCapability,
142
+ scope: request.scope,
143
+ permissions: request.permissions,
144
+ expires: Date.now() + expiresIn,
145
+ }],
146
+ });
147
+
148
+ // Publish acceptance event
149
+ const event = {
150
+ kind: FEDERATION_ACCEPT_KIND,
151
+ created_at: Math.floor(Date.now() / 1000),
152
+ tags: [
153
+ ['d', `federation-accept-${request.requesterPubKey.slice(0, 16)}`],
154
+ ['p', request.requesterPubKey],
155
+ ['e', request.eventId], // Reference to original request
156
+ ['holosphere', appname],
157
+ ['scope', JSON.stringify(scope)],
158
+ ['permissions', permissions.join(',')],
159
+ ],
160
+ content: JSON.stringify({
161
+ acceptedScope: scope,
162
+ grantedCapability,
163
+ }),
164
+ };
165
+
166
+ const result = await client.publish(event);
167
+ return {
168
+ eventId: result.id || null,
169
+ requesterPubKey: request.requesterPubKey,
170
+ grantedCapability,
171
+ acceptedScope: scope,
172
+ ...result,
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Decline a federation request
178
+ * @param {Object} client - NostrClient instance
179
+ * @param {string} appname - Application namespace
180
+ * @param {Object} request - Request object from subscription
181
+ * @param {string} reason - Optional decline reason
182
+ * @returns {Promise<Object>} Published decline event info
183
+ */
184
+ export async function declineFederationRequest(client, appname, request, reason = '') {
185
+ const event = {
186
+ kind: FEDERATION_DECLINE_KIND,
187
+ created_at: Math.floor(Date.now() / 1000),
188
+ tags: [
189
+ ['d', `federation-decline-${request.requesterPubKey.slice(0, 16)}`],
190
+ ['p', request.requesterPubKey],
191
+ ['e', request.eventId],
192
+ ['holosphere', appname],
193
+ ],
194
+ content: JSON.stringify({ reason }),
195
+ };
196
+
197
+ const result = await client.publish(event);
198
+ return {
199
+ eventId: result.id || null,
200
+ requesterPubKey: request.requesterPubKey,
201
+ reason,
202
+ ...result,
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Subscribe to federation acceptances (responses to our requests)
208
+ * @param {Object} client - NostrClient instance
209
+ * @param {string} appname - Application namespace
210
+ * @param {Function} callback - Called with each acceptance
211
+ * @returns {Promise<Object>} Subscription with unsubscribe method
212
+ */
213
+ export async function subscribeFederationAcceptances(client, appname, callback) {
214
+ const filter = {
215
+ kinds: [FEDERATION_ACCEPT_KIND],
216
+ '#p': [client.publicKey],
217
+ };
218
+
219
+ return client.subscribe(filter, async (event) => {
220
+ try {
221
+ const content = JSON.parse(event.content);
222
+ const scopeTag = event.tags.find(t => t[0] === 'scope');
223
+ const permissionsTag = event.tags.find(t => t[0] === 'permissions');
224
+
225
+ // Store their granted capability in our registry
226
+ await storeInboundCapability(client, appname, event.pubkey, {
227
+ token: content.grantedCapability,
228
+ scope: scopeTag ? JSON.parse(scopeTag[1]) : content.acceptedScope,
229
+ permissions: permissionsTag ? permissionsTag[1].split(',') : ['read'],
230
+ expires: Date.now() + 30 * 24 * 60 * 60 * 1000, // Assume 30 day expiry
231
+ });
232
+
233
+ callback({
234
+ accepterPubKey: event.pubkey,
235
+ originalRequestId: event.tags.find(t => t[0] === 'e')?.[1],
236
+ acceptedScope: content.acceptedScope,
237
+ grantedCapability: content.grantedCapability,
238
+ timestamp: event.created_at,
239
+ });
240
+ } catch (error) {
241
+ console.error('Failed to parse federation acceptance:', error);
242
+ }
243
+ });
244
+ }
245
+
246
+ /**
247
+ * Subscribe to federation declines
248
+ * @param {Object} client - NostrClient instance
249
+ * @param {Function} callback - Called with each decline
250
+ * @returns {Promise<Object>} Subscription with unsubscribe method
251
+ */
252
+ export async function subscribeFederationDeclines(client, callback) {
253
+ const filter = {
254
+ kinds: [FEDERATION_DECLINE_KIND],
255
+ '#p': [client.publicKey],
256
+ };
257
+
258
+ return client.subscribe(filter, (event) => {
259
+ try {
260
+ const content = JSON.parse(event.content);
261
+
262
+ callback({
263
+ declinerPubKey: event.pubkey,
264
+ originalRequestId: event.tags.find(t => t[0] === 'e')?.[1],
265
+ reason: content.reason || '',
266
+ timestamp: event.created_at,
267
+ });
268
+ } catch (error) {
269
+ console.error('Failed to parse federation decline:', error);
270
+ }
271
+ });
272
+ }
273
+
274
+ /**
275
+ * Query for pending federation requests (requests we've sent that haven't been answered)
276
+ * @param {Object} client - NostrClient instance
277
+ * @param {Object} options - Query options
278
+ * @param {number} options.since - Only get requests since this timestamp (seconds)
279
+ * @returns {Promise<Object[]>} Array of pending requests
280
+ */
281
+ export async function getPendingFederationRequests(client, options = {}) {
282
+ const since = options.since || Math.floor((Date.now() - 30 * 24 * 60 * 60 * 1000) / 1000);
283
+
284
+ // Get our requests
285
+ const requestFilter = {
286
+ kinds: [FEDERATION_REQUEST_KIND],
287
+ authors: [client.publicKey],
288
+ since,
289
+ };
290
+ const requests = await client.query(requestFilter);
291
+
292
+ // Get all acceptances and declines to our requests
293
+ const responseFilter = {
294
+ kinds: [FEDERATION_ACCEPT_KIND, FEDERATION_DECLINE_KIND],
295
+ '#p': [client.publicKey],
296
+ since,
297
+ };
298
+ const responses = await client.query(responseFilter);
299
+
300
+ // Find request event IDs that have been responded to
301
+ const respondedRequestIds = new Set();
302
+ for (const response of responses) {
303
+ const eTag = response.tags.find(t => t[0] === 'e');
304
+ if (eTag) {
305
+ respondedRequestIds.add(eTag[1]);
306
+ }
307
+ }
308
+
309
+ // Filter to only pending requests
310
+ const pending = [];
311
+ for (const request of requests) {
312
+ if (!respondedRequestIds.has(request.id)) {
313
+ try {
314
+ const content = JSON.parse(request.content);
315
+ const pTag = request.tags.find(t => t[0] === 'p');
316
+ const scopeTag = request.tags.find(t => t[0] === 'scope');
317
+ const permissionsTag = request.tags.find(t => t[0] === 'permissions');
318
+
319
+ pending.push({
320
+ eventId: request.id,
321
+ targetPubKey: pTag?.[1],
322
+ scope: scopeTag ? JSON.parse(scopeTag[1]) : { holonId: '*', lensName: '*' },
323
+ permissions: permissionsTag ? permissionsTag[1].split(',') : ['read'],
324
+ message: content.message,
325
+ timestamp: request.created_at,
326
+ });
327
+ } catch (error) {
328
+ console.error('Failed to parse pending request:', error);
329
+ }
330
+ }
331
+ }
332
+
333
+ return pending;
334
+ }