holosphere 2.0.0-alpha10 → 2.0.0-alpha12

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 (62) hide show
  1. package/dist/{2019-D2OG2idw.js → 2019-DQdDE6DG.js} +2 -2
  2. package/dist/{2019-D2OG2idw.js.map → 2019-DQdDE6DG.js.map} +1 -1
  3. package/dist/{2019-EION3wKo.cjs → 2019-Ew-DTDlI.cjs} +2 -2
  4. package/dist/{2019-EION3wKo.cjs.map → 2019-Ew-DTDlI.cjs.map} +1 -1
  5. package/dist/{browser-BSniCNqO.js → browser-CckFyRI9.js} +2 -2
  6. package/dist/{browser-BSniCNqO.js.map → browser-CckFyRI9.js.map} +1 -1
  7. package/dist/browser-D2qtVhH5.cjs +2 -0
  8. package/dist/{browser-Cq59Ij19.cjs.map → browser-D2qtVhH5.cjs.map} +1 -1
  9. package/dist/cjs/holosphere.cjs +1 -1
  10. package/dist/esm/holosphere.js +67 -50
  11. package/dist/index-9sqetkAn.cjs +29 -0
  12. package/dist/index-9sqetkAn.cjs.map +1 -0
  13. package/dist/index-C0ITDyFo.cjs +13 -0
  14. package/dist/index-C0ITDyFo.cjs.map +1 -0
  15. package/dist/{index-DeZ1xz_s.js → index-DrYM1LOY.js} +2 -2
  16. package/dist/{index-DeZ1xz_s.js.map → index-DrYM1LOY.js.map} +1 -1
  17. package/dist/{index-DJXftyvB.js → index-TDDyakLc.js} +20345 -805
  18. package/dist/index-TDDyakLc.js.map +1 -0
  19. package/dist/{index-DDGt_V9o.cjs → index-kyf1sjaC.cjs} +3 -3
  20. package/dist/{index-DDGt_V9o.cjs.map → index-kyf1sjaC.cjs.map} +1 -1
  21. package/dist/index-lbSQUoRz.js +11663 -0
  22. package/dist/index-lbSQUoRz.js.map +1 -0
  23. package/dist/{indexeddb-storage-BFt6hMeF.js → indexeddb-storage-CXhjqwhA.js} +2 -2
  24. package/dist/{indexeddb-storage-BFt6hMeF.js.map → indexeddb-storage-CXhjqwhA.js.map} +1 -1
  25. package/dist/{indexeddb-storage-BK5tv4Sh.cjs → indexeddb-storage-DFESDYIj.cjs} +2 -2
  26. package/dist/{indexeddb-storage-BK5tv4Sh.cjs.map → indexeddb-storage-DFESDYIj.cjs.map} +1 -1
  27. package/dist/{memory-storage-Dao7jfYG.cjs → memory-storage-D1tc1bjk.cjs} +2 -2
  28. package/dist/{memory-storage-Dao7jfYG.cjs.map → memory-storage-D1tc1bjk.cjs.map} +1 -1
  29. package/dist/{memory-storage-C9HuoL2E.js → memory-storage-DkewsdcM.js} +2 -2
  30. package/dist/{memory-storage-C9HuoL2E.js.map → memory-storage-DkewsdcM.js.map} +1 -1
  31. package/docs/FEDERATION.md +474 -0
  32. package/package.json +3 -1
  33. package/src/crypto/nostr-utils.js +7 -0
  34. package/src/crypto/secp256k1.js +46 -29
  35. package/src/federation/capabilities.js +162 -0
  36. package/src/federation/card-storage.js +304 -0
  37. package/src/federation/handshake.js +204 -7
  38. package/src/federation/hologram.js +167 -58
  39. package/src/federation/holon-registry.js +166 -0
  40. package/src/federation/index.js +68 -0
  41. package/src/federation/registry.js +110 -2
  42. package/src/federation/request-card.js +373 -0
  43. package/src/hierarchical/upcast.js +19 -3
  44. package/src/index.js +224 -95
  45. package/src/lib/federation-methods.js +527 -5
  46. package/src/storage/nostr-async.js +2 -2
  47. package/src/storage/nostr-client.js +370 -155
  48. package/src/storage/nostr-wrapper.js +1 -1
  49. package/dist/_commonjsHelpers-C37NGDzP.cjs +0 -2
  50. package/dist/_commonjsHelpers-C37NGDzP.cjs.map +0 -1
  51. package/dist/_commonjsHelpers-CUmg6egw.js +0 -7
  52. package/dist/_commonjsHelpers-CUmg6egw.js.map +0 -1
  53. package/dist/browser-Cq59Ij19.cjs +0 -2
  54. package/dist/cdn/holosphere.min.js +0 -55
  55. package/dist/cdn/holosphere.min.js.map +0 -1
  56. package/dist/index-DJXftyvB.js.map +0 -1
  57. package/dist/index-DMbdcMtK.cjs +0 -18
  58. package/dist/index-DMbdcMtK.cjs.map +0 -1
  59. package/dist/secp256k1-BbKzbLtD.cjs +0 -12
  60. package/dist/secp256k1-BbKzbLtD.cjs.map +0 -1
  61. package/dist/secp256k1-CreY7Pcl.js +0 -1890
  62. package/dist/secp256k1-CreY7Pcl.js.map +0 -1
package/src/index.js CHANGED
@@ -21,6 +21,11 @@ import * as schema from './schema/validator.js';
21
21
  import { ValidationError } from './schema/validator.js';
22
22
  import * as federation from './federation/hologram.js';
23
23
  import * as handshake from './federation/handshake.js';
24
+ import * as holonRegistry from './federation/holon-registry.js';
25
+ import * as registry from './federation/registry.js';
26
+ import * as capabilities from './federation/capabilities.js';
27
+ import * as requestCard from './federation/request-card.js';
28
+ import * as cardStorage from './federation/card-storage.js';
24
29
  import * as crypto from './crypto/secp256k1.js';
25
30
  import * as nostrUtils from './crypto/nostr-utils.js';
26
31
  import * as social from './content/social-protocols.js';
@@ -254,6 +259,51 @@ class HoloSphereBase extends HoloSphereCore {
254
259
  return spatial.isValidH3(holonId);
255
260
  }
256
261
 
262
+ // === Holon Registry Operations ===
263
+
264
+ /**
265
+ * Register a holon with a public key.
266
+ * Maps a holonId (H3, chatId, concept) to a keypair owner.
267
+ *
268
+ * @param {string} holonId - Holon identifier to register
269
+ * @param {string} publicKey - 64-char hex public key (owner)
270
+ * @param {Object} [options={}] - Registration options
271
+ * @param {string} [options.alias] - Human-readable name
272
+ * @returns {Promise<boolean>} Success indicator
273
+ */
274
+ async registerHolon(holonId, publicKey, options = {}) {
275
+ return holonRegistry.registerHolon(this.client, this.config.appName, holonId, publicKey, options);
276
+ }
277
+
278
+ /**
279
+ * Look up a holon's registration.
280
+ *
281
+ * @param {string} holonId - Holon identifier
282
+ * @returns {Promise<Object|null>} Registry entry { holonId, publicKey, alias } or null
283
+ */
284
+ async lookupHolon(holonId) {
285
+ return holonRegistry.lookupHolon(this.client, this.config.appName, holonId);
286
+ }
287
+
288
+ /**
289
+ * Unregister a holon.
290
+ *
291
+ * @param {string} holonId - Holon identifier
292
+ * @returns {Promise<boolean>} Success indicator
293
+ */
294
+ async unregisterHolon(holonId) {
295
+ return holonRegistry.unregisterHolon(this.client, this.config.appName, holonId);
296
+ }
297
+
298
+ /**
299
+ * List all registered holons.
300
+ *
301
+ * @returns {Promise<Object[]>} Array of registry entries
302
+ */
303
+ async getRegisteredHolons() {
304
+ return holonRegistry.listRegisteredHolons(this.client, this.config.appName);
305
+ }
306
+
257
307
  // === Data Operations ===
258
308
 
259
309
  /**
@@ -276,10 +326,14 @@ class HoloSphereBase extends HoloSphereCore {
276
326
  * @throws {AuthorizationError} If capability token is invalid
277
327
  */
278
328
  async write(holonId, lensName, data, options = {}) {
279
- if (!holonId || typeof holonId !== 'string') {
329
+ // Auto-convert to strings for convenience
330
+ if (holonId != null) holonId = String(holonId);
331
+ if (lensName != null) lensName = String(lensName);
332
+
333
+ if (!holonId) {
280
334
  throw new ValidationError('ValidationError: holonId must be a non-empty string');
281
335
  }
282
- if (!lensName || typeof lensName !== 'string') {
336
+ if (!lensName) {
283
337
  throw new ValidationError('ValidationError: lensName must be a non-empty string');
284
338
  }
285
339
  if (!data || typeof data !== 'object') {
@@ -296,6 +350,9 @@ class HoloSphereBase extends HoloSphereCore {
296
350
 
297
351
  if (!data.id) {
298
352
  data.id = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
353
+ } else {
354
+ // Auto-convert data.id to string for convenience
355
+ data.id = String(data.id);
299
356
  }
300
357
 
301
358
  // Add metadata
@@ -318,7 +375,7 @@ class HoloSphereBase extends HoloSphereCore {
318
375
  // OPTIMISTIC UPDATE: Store in write cache and clear from delete cache
319
376
  this._writeCache.set(path, { data: { ...data }, timestamp: Date.now() });
320
377
  this._deleteCache.delete(path);
321
- this._log('INFO', '📝 CACHE WRITE', {
378
+ this._log('DEBUG', '📝 CACHE WRITE', {
322
379
  path,
323
380
  dataId: data.id,
324
381
  cacheSize: this._writeCache.size,
@@ -353,7 +410,7 @@ class HoloSphereBase extends HoloSphereCore {
353
410
  */
354
411
  async _syncWriteToRelay(holonId, lensName, data, path, options) {
355
412
  const startTime = Date.now();
356
- this._log('INFO', '🔄 RELAY SYNC START', {
413
+ this._log('DEBUG', '🔄 RELAY SYNC START', {
357
414
  path,
358
415
  dataId: data.id,
359
416
  relays: this.client?.relays?.map(r => r.url) || ['local-only']
@@ -378,7 +435,7 @@ class HoloSphereBase extends HoloSphereCore {
378
435
  if (!this._metrics.totalWriteTime) this._metrics.totalWriteTime = 0;
379
436
  this._metrics.totalWriteTime += duration;
380
437
 
381
- this._log('INFO', '✅ RELAY SYNC SUCCESS', {
438
+ this._log('DEBUG', '✅ RELAY SYNC SUCCESS', {
382
439
  path,
383
440
  dataId: data.id,
384
441
  duration: `${duration}ms`,
@@ -479,6 +536,31 @@ class HoloSphereBase extends HoloSphereCore {
479
536
  return true;
480
537
  }
481
538
 
539
+ /**
540
+ * Resolve holonId to public key.
541
+ * If holonId is already a 64-char hex pubkey, returns it directly.
542
+ * Otherwise, looks up the holon registry.
543
+ *
544
+ * @private
545
+ * @param {string} holonId - Holon identifier
546
+ * @returns {Promise<string|null>} Public key or null if not found
547
+ */
548
+ async _resolveHolonToPubkey(holonId) {
549
+ return holonRegistry.resolveHolonToPubkey(this.client, this.config.appName, holonId);
550
+ }
551
+
552
+ /**
553
+ * Check if we have a stored capability for a given author and scope.
554
+ *
555
+ * @private
556
+ * @param {string} authorPubKey - Author's public key
557
+ * @param {Object} scope - Scope to check (holonId, lensName, dataId)
558
+ * @returns {Promise<Object|null>} Capability entry or null
559
+ */
560
+ async _getCapabilityForAuthor(authorPubKey, scope) {
561
+ return registry.getCapabilityForAuthor(this.client, this.config.appName, authorPubKey, scope);
562
+ }
563
+
482
564
  /**
483
565
  * Recursively resolves holograms (references) to their source data.
484
566
  * Handles circular reference detection and local overrides.
@@ -594,19 +676,66 @@ class HoloSphereBase extends HoloSphereCore {
594
676
  * @throws {AuthorizationError} If capability token is invalid
595
677
  */
596
678
  async read(holonId, lensName, dataId = null, options = {}) {
597
- if (!holonId || typeof holonId !== 'string') {
679
+ // Auto-convert to strings for convenience
680
+ if (holonId != null) holonId = String(holonId);
681
+ if (lensName != null) lensName = String(lensName);
682
+ if (dataId != null) dataId = String(dataId);
683
+
684
+ if (!holonId) {
598
685
  throw new ValidationError('ValidationError: holonId must be a non-empty string');
599
686
  }
600
- if (!lensName || typeof lensName !== 'string') {
687
+ if (!lensName) {
601
688
  throw new ValidationError('ValidationError: lensName must be a non-empty string');
602
689
  }
603
690
 
691
+ // Resolve holonId to public key (if registered)
692
+ const targetPubkey = await this._resolveHolonToPubkey(holonId);
693
+
694
+ // Determine if reading another author's data
695
+ // If holonId can't be resolved, treat as own data (backwards compatible for H3 holons)
696
+ const isOtherAuthor = targetPubkey && targetPubkey !== this.client.publicKey;
697
+ let readOptions = {};
698
+
699
+ // Explicit capability token takes precedence
604
700
  const capToken = options.capabilityToken || options.capability;
605
701
  if (capToken) {
606
702
  const authorized = await this.verifyCapability(capToken, 'read', { holonId, lensName, dataId });
607
703
  if (!authorized) {
608
704
  throw new AuthorizationError('AuthorizationError: Invalid capability token for read operation', 'read');
609
705
  }
706
+ // Use target author for reading
707
+ if (isOtherAuthor) {
708
+ readOptions.authors = [targetPubkey];
709
+ }
710
+ } else if (isOtherAuthor) {
711
+ // Auto-check capability for other author's data
712
+ const capability = await this._getCapabilityForAuthor(targetPubkey, { holonId, lensName, dataId });
713
+ if (!capability) {
714
+ this._log('DEBUG', 'No capability for author', { holonId, targetPubkey });
715
+ return dataId ? null : [];
716
+ }
717
+ readOptions.authors = [targetPubkey];
718
+ }
719
+
720
+ // Include federated authors to see holograms written by federation partners
721
+ // This enables visibility of holograms created during federation
722
+ if (!isOtherAuthor) {
723
+ try {
724
+ const federatedAuthors = await registry.getFederatedAuthorsForScope(
725
+ this.client,
726
+ this.config.appName,
727
+ { holonId, lensName, dataId },
728
+ 'read'
729
+ );
730
+ if (federatedAuthors.length > 0) {
731
+ const partnerPubkeys = federatedAuthors.map(f => f.pubKey);
732
+ // Include self + all federated partners
733
+ readOptions.authors = [this.client.publicKey, ...partnerPubkeys];
734
+ this._log('DEBUG', 'Including federated authors', { count: partnerPubkeys.length });
735
+ }
736
+ } catch (err) {
737
+ this._log('WARN', 'Failed to get federated authors', { error: err.message });
738
+ }
610
739
  }
611
740
 
612
741
  const startTime = Date.now();
@@ -617,18 +746,18 @@ class HoloSphereBase extends HoloSphereCore {
617
746
 
618
747
  // Check delete cache first - if deleted, return null immediately
619
748
  if (this._deleteCache.has(path)) {
620
- this._log('INFO', '🗑️ CACHE READ: Deleted item', {
749
+ this._log('DEBUG', '🗑️ CACHE READ: Deleted item', {
621
750
  path,
622
751
  source: 'delete-cache',
623
752
  deleteCacheSize: this._deleteCache.size
624
753
  });
625
754
  result = null;
626
755
  } else {
627
- // Check write cache for optimistic reads
628
- const cached = this._writeCache.get(path);
756
+ // Check write cache for optimistic reads (only for own data)
757
+ const cached = !isOtherAuthor ? this._writeCache.get(path) : null;
629
758
  if (cached) {
630
759
  const cacheAge = Date.now() - cached.timestamp;
631
- this._log('INFO', '⚡ CACHE HIT: Write cache', {
760
+ this._log('DEBUG', '⚡ CACHE HIT: Write cache', {
632
761
  path,
633
762
  source: 'write-cache',
634
763
  cacheAge: `${cacheAge}ms`,
@@ -636,9 +765,9 @@ class HoloSphereBase extends HoloSphereCore {
636
765
  });
637
766
  result = cached.data;
638
767
  } 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', {
768
+ this._log('DEBUG', '📖 CACHE MISS: Reading from storage', { path, authors: readOptions.authors });
769
+ result = await storage.read(this.client, path, readOptions);
770
+ this._log('DEBUG', '💾 STORAGE READ', {
642
771
  path,
643
772
  source: 'storage',
644
773
  found: !!result,
@@ -648,11 +777,11 @@ class HoloSphereBase extends HoloSphereCore {
648
777
  }
649
778
  } else {
650
779
  const path = storage.buildPath(this.config.appName, holonId, lensName);
651
- this._log('DEBUG', 'readAll', { holonId, lensName, path });
780
+ this._log('DEBUG', 'readAll', { holonId, lensName, path, authors: readOptions.authors });
652
781
 
653
782
  // 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);
783
+ const storageResult = await storage.readAll(this.client, path, readOptions);
784
+ result = isOtherAuthor ? storageResult : this._mergeWithWriteCache(storageResult, path);
656
785
  this._log('DEBUG', 'readAll result', { count: Array.isArray(result) ? result.length : 0 });
657
786
  }
658
787
 
@@ -782,7 +911,7 @@ class HoloSphereBase extends HoloSphereCore {
782
911
  // OPTIMISTIC UPDATE: Store in write cache and clear from delete cache
783
912
  this._writeCache.set(path, { data: { ...mergedData }, timestamp: Date.now() });
784
913
  this._deleteCache.delete(path);
785
- this._log('INFO', '✏️ CACHE UPDATE', {
914
+ this._log('DEBUG', '✏️ CACHE UPDATE', {
786
915
  path,
787
916
  dataId,
788
917
  updatedFields: Object.keys(updates),
@@ -808,7 +937,7 @@ class HoloSphereBase extends HoloSphereCore {
808
937
  */
809
938
  async _syncUpdateToRelay(holonId, lensName, dataId, mergedData, existingData, path) {
810
939
  const startTime = Date.now();
811
- this._log('INFO', '🔄 UPDATE SYNC START', {
940
+ this._log('DEBUG', '🔄 UPDATE SYNC START', {
812
941
  path,
813
942
  dataId,
814
943
  relays: this.client?.relays?.map(r => r.url) || ['local-only']
@@ -822,7 +951,7 @@ class HoloSphereBase extends HoloSphereCore {
822
951
  if (!this._metrics.totalWriteTime) this._metrics.totalWriteTime = 0;
823
952
  this._metrics.totalWriteTime += duration;
824
953
 
825
- this._log('INFO', '✅ UPDATE SYNC SUCCESS', {
954
+ this._log('DEBUG', '✅ UPDATE SYNC SUCCESS', {
826
955
  path,
827
956
  dataId,
828
957
  duration: `${duration}ms`,
@@ -865,13 +994,18 @@ class HoloSphereBase extends HoloSphereCore {
865
994
  * @throws {AuthorizationError} If not owner and no valid capability token
866
995
  */
867
996
  async delete(holonId, lensName, dataId, options = {}) {
868
- if (!holonId || typeof holonId !== 'string') {
997
+ // Auto-convert to strings for convenience
998
+ if (holonId != null) holonId = String(holonId);
999
+ if (lensName != null) lensName = String(lensName);
1000
+ if (dataId != null) dataId = String(dataId);
1001
+
1002
+ if (!holonId) {
869
1003
  throw new ValidationError('ValidationError: holonId must be a non-empty string');
870
1004
  }
871
- if (!lensName || typeof lensName !== 'string') {
1005
+ if (!lensName) {
872
1006
  throw new ValidationError('ValidationError: lensName must be a non-empty string');
873
1007
  }
874
- if (!dataId || typeof dataId !== 'string') {
1008
+ if (!dataId) {
875
1009
  throw new ValidationError('ValidationError: dataId must be a non-empty string');
876
1010
  }
877
1011
 
@@ -920,7 +1054,7 @@ class HoloSphereBase extends HoloSphereCore {
920
1054
  // OPTIMISTIC DELETE: Remove from write cache and add to delete cache
921
1055
  this._writeCache.delete(path);
922
1056
  this._deleteCache.add(path);
923
- this._log('INFO', '🗑️ CACHE DELETE', {
1057
+ this._log('DEBUG', '🗑️ CACHE DELETE', {
924
1058
  path,
925
1059
  dataId,
926
1060
  deleteCacheSize: this._deleteCache.size,
@@ -935,14 +1069,14 @@ class HoloSphereBase extends HoloSphereCore {
935
1069
 
936
1070
  // Delete from storage (background unless blocking)
937
1071
  const startTime = Date.now();
938
- this._log('INFO', '🔄 DELETE SYNC START', { path, dataId });
1072
+ this._log('DEBUG', '🔄 DELETE SYNC START', { path, dataId });
939
1073
 
940
1074
  const deletePromise = storage.deleteData(this.client, path).then(() => {
941
1075
  const duration = Date.now() - startTime;
942
1076
  this._metrics.deletes++;
943
1077
  // Clear from delete cache after successful sync
944
1078
  this._deleteCache.delete(path);
945
- this._log('INFO', '✅ DELETE SYNC SUCCESS', {
1079
+ this._log('DEBUG', '✅ DELETE SYNC SUCCESS', {
946
1080
  path,
947
1081
  dataId,
948
1082
  duration: `${duration}ms`,
@@ -983,6 +1117,10 @@ class HoloSphereBase extends HoloSphereCore {
983
1117
  * @returns {Promise<boolean>} True if write succeeded
984
1118
  */
985
1119
  async writeGlobal(table, data) {
1120
+ // Auto-convert to strings for convenience
1121
+ if (table != null) table = String(table);
1122
+ if (data?.id != null) data.id = String(data.id);
1123
+
986
1124
  return globalTables.writeGlobal(this.client, this.config.appName, table, data);
987
1125
  }
988
1126
 
@@ -994,6 +1132,10 @@ class HoloSphereBase extends HoloSphereCore {
994
1132
  * @returns {Promise<Object|Array|null>} Data object, array of objects, or null
995
1133
  */
996
1134
  async readGlobal(table, key = null) {
1135
+ // Auto-convert to strings for convenience
1136
+ if (table != null) table = String(table);
1137
+ if (key != null) key = String(key);
1138
+
997
1139
  return globalTables.readGlobal(this.client, this.config.appName, table, key);
998
1140
  }
999
1141
 
@@ -1006,6 +1148,10 @@ class HoloSphereBase extends HoloSphereCore {
1006
1148
  * @returns {Promise<boolean>} True if update succeeded
1007
1149
  */
1008
1150
  async updateGlobal(table, key, updates) {
1151
+ // Auto-convert to strings for convenience
1152
+ if (table != null) table = String(table);
1153
+ if (key != null) key = String(key);
1154
+
1009
1155
  return globalTables.updateGlobal(this.client, this.config.appName, table, key, updates);
1010
1156
  }
1011
1157
 
@@ -1017,6 +1163,10 @@ class HoloSphereBase extends HoloSphereCore {
1017
1163
  * @returns {Promise<boolean>} True if deletion succeeded
1018
1164
  */
1019
1165
  async deleteGlobal(table, key) {
1166
+ // Auto-convert to strings for convenience
1167
+ if (table != null) table = String(table);
1168
+ if (key != null) key = String(key);
1169
+
1020
1170
  return globalTables.deleteGlobal(this.client, this.config.appName, table, key);
1021
1171
  }
1022
1172
 
@@ -1027,6 +1177,9 @@ class HoloSphereBase extends HoloSphereCore {
1027
1177
  * @returns {Promise<Array>} Array of all data objects in the table
1028
1178
  */
1029
1179
  async getAllGlobal(table) {
1180
+ // Auto-convert to strings for convenience
1181
+ if (table != null) table = String(table);
1182
+
1030
1183
  return globalTables.getAllGlobal(this.client, this.config.appName, table);
1031
1184
  }
1032
1185
 
@@ -1037,6 +1190,9 @@ class HoloSphereBase extends HoloSphereCore {
1037
1190
  * @returns {Promise<boolean>} True if deletion succeeded
1038
1191
  */
1039
1192
  async deleteAllGlobal(table) {
1193
+ // Auto-convert to strings for convenience
1194
+ if (table != null) table = String(table);
1195
+
1040
1196
  return globalTables.deleteAllGlobal(this.client, this.config.appName, table);
1041
1197
  }
1042
1198
 
@@ -1061,6 +1217,10 @@ class HoloSphereBase extends HoloSphereCore {
1061
1217
  * @returns {Promise<boolean>} True if deletion succeeded
1062
1218
  */
1063
1219
  async deleteAll(holonId, lensName) {
1220
+ // Auto-convert to strings for convenience
1221
+ if (holonId != null) holonId = String(holonId);
1222
+ if (lensName != null) lensName = String(lensName);
1223
+
1064
1224
  const path = storage.buildPath(this.config.appName, holonId, lensName);
1065
1225
  return storage.deleteAll(this.client, path);
1066
1226
  }
@@ -1136,56 +1296,11 @@ class HoloSphereBase extends HoloSphereCore {
1136
1296
  }
1137
1297
 
1138
1298
  // === 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
- */
1154
- async federate(sourceHolon, targetHolon, lensName, options = {}) {
1155
- const { direction = 'outbound', mode = 'reference', filter = null } = options;
1156
-
1157
- // Validation
1158
- if (sourceHolon === targetHolon) {
1159
- throw new Error('Cannot federate a holon with itself');
1160
- }
1161
- if (!['inbound', 'outbound', 'bidirectional'].includes(direction)) {
1162
- throw new Error(`Invalid direction: ${direction}. Must be 'inbound', 'outbound', or 'bidirectional'`);
1163
- }
1164
-
1165
- // Store federation config
1166
- await federation.setupFederation(
1167
- this.client,
1168
- this.config.appName,
1169
- sourceHolon,
1170
- targetHolon,
1171
- lensName,
1172
- options
1173
- );
1174
-
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
- }
1182
-
1183
- this._metrics.federations = (this._metrics.federations || 0) + 1;
1184
- return true;
1185
- }
1299
+ // Note: federate() and unfederate() methods are provided by withFederationMethods mixin
1186
1300
 
1187
1301
  /**
1188
1302
  * Propagates existing data from one holon to another.
1303
+ * UNIFIED MODEL: Passes sourceAuthorPubKey for capability-based federation.
1189
1304
  *
1190
1305
  * @private
1191
1306
  * @param {string} fromHolon - Source holon H3 cell ID
@@ -1194,10 +1309,11 @@ class HoloSphereBase extends HoloSphereCore {
1194
1309
  * @param {Object} [options={}] - Propagation options
1195
1310
  * @param {string} [options.mode='reference'] - Mode: 'reference' or 'copy'
1196
1311
  * @param {Function} [options.filter] - Filter function to select data
1312
+ * @param {string} [options.sourceAuthorPubKey] - Source author's public key (defaults to client.publicKey)
1197
1313
  * @returns {Promise<void>}
1198
1314
  */
1199
1315
  async _propagateExistingData(fromHolon, toHolon, lensName, options = {}) {
1200
- const { mode = 'reference', filter = null } = options;
1316
+ const { mode = 'reference', filter = null, sourceAuthorPubKey } = options;
1201
1317
  const existingData = await this.read(fromHolon, lensName, null, { resolveHolograms: false });
1202
1318
  if (!existingData) return;
1203
1319
 
@@ -1207,6 +1323,7 @@ class HoloSphereBase extends HoloSphereCore {
1207
1323
  if (item.hologram === true) continue;
1208
1324
  // Apply filter if provided
1209
1325
  if (filter && !filter(item)) continue;
1326
+ // UNIFIED MODEL: Pass sourceAuthorPubKey for capability-based federation
1210
1327
  await federation.propagateData(
1211
1328
  this.client,
1212
1329
  this.config.appName,
@@ -1214,7 +1331,10 @@ class HoloSphereBase extends HoloSphereCore {
1214
1331
  fromHolon,
1215
1332
  toHolon,
1216
1333
  lensName,
1217
- mode
1334
+ mode,
1335
+ {
1336
+ sourceAuthorPubKey: sourceAuthorPubKey || this.client.publicKey,
1337
+ }
1218
1338
  );
1219
1339
  }
1220
1340
  }
@@ -1239,25 +1359,6 @@ class HoloSphereBase extends HoloSphereCore {
1239
1359
  return this._resolveHolograms(data);
1240
1360
  }
1241
1361
 
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
- */
1250
- async unfederate(sourceHolon, targetHolon, lensName) {
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
- }
1258
- return true;
1259
- }
1260
-
1261
1362
  /**
1262
1363
  * Updates local override values on a hologram.
1263
1364
  *
@@ -1369,6 +1470,7 @@ class HoloSphereBase extends HoloSphereCore {
1369
1470
  async propagateData(data, sourceHolon, targetHolon, lensName, options = {}) {
1370
1471
  // Extract mode from options, default to 'reference' for hologram creation
1371
1472
  const mode = options.mode || 'reference';
1473
+ // UNIFIED MODEL: Pass sourceAuthorPubKey for capability-based federation
1372
1474
  return federation.propagateData(
1373
1475
  this.client,
1374
1476
  this.config.appName,
@@ -1376,7 +1478,11 @@ class HoloSphereBase extends HoloSphereCore {
1376
1478
  sourceHolon,
1377
1479
  targetHolon,
1378
1480
  lensName,
1379
- mode
1481
+ mode,
1482
+ {
1483
+ sourceAuthorPubKey: options.sourceAuthorPubKey || this.client.publicKey,
1484
+ capability: options.capability,
1485
+ }
1380
1486
  );
1381
1487
  }
1382
1488
 
@@ -2224,10 +2330,33 @@ export {
2224
2330
  // Re-export types and utilities
2225
2331
  export { spatial, storage, schema, federation, handshake, crypto, nostrUtils, social, subscriptions, hierarchical };
2226
2332
 
2333
+ // Re-export federation submodules
2334
+ export { capabilities, requestCard, cardStorage, holonRegistry, registry };
2335
+
2227
2336
  // Re-export specific utilities used in tests
2228
2337
  export { matchScope } from './crypto/secp256k1.js';
2229
2338
  export { createHologram } from './federation/hologram.js';
2230
2339
 
2340
+ // Re-export federation card functions
2341
+ export {
2342
+ createFederationCard,
2343
+ getVisibleLenses,
2344
+ getLensConfigForHandshake,
2345
+ toggleLens,
2346
+ toggleCardExpansion,
2347
+ dismissCard,
2348
+ } from './federation/request-card.js';
2349
+
2350
+ // Re-export card storage functions
2351
+ export {
2352
+ saveCard,
2353
+ getCard,
2354
+ getDisplayableCards,
2355
+ dismissRequest,
2356
+ markResponseProcessed,
2357
+ isResponseProcessed,
2358
+ } from './federation/card-storage.js';
2359
+
2231
2360
  // Export AI factory function
2232
2361
  export { createAIServices } from './ai/index.js';
2233
2362