holosphere 2.0.0-alpha21 → 2.0.0-alpha22

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 (69) hide show
  1. package/README.md +1 -2
  2. package/dist/cjs/holosphere.cjs +1 -1
  3. package/dist/esm/holosphere.js +43 -41
  4. package/dist/{index-COpLk9gL.cjs → index-B4xe-N5-.cjs} +2 -2
  5. package/dist/{index-COpLk9gL.cjs.map → index-B4xe-N5-.cjs.map} +1 -1
  6. package/dist/{index-D2WstuZJ.js → index-Bug_CGNq.js} +2 -2
  7. package/dist/{index-D2WstuZJ.js.map → index-Bug_CGNq.js.map} +1 -1
  8. package/dist/index-CaCPzdlv.cjs +29 -0
  9. package/dist/index-CaCPzdlv.cjs.map +1 -0
  10. package/dist/{index-B6-8KAQm.js → index-D76zMgwU.js} +2 -2
  11. package/dist/{index-B6-8KAQm.js.map → index-D76zMgwU.js.map} +1 -1
  12. package/dist/{index--QsHG_gD.cjs → index-DuOuk96g.cjs} +2 -2
  13. package/dist/{index--QsHG_gD.cjs.map → index-DuOuk96g.cjs.map} +1 -1
  14. package/dist/{index-BHptWysv.js → index-bYHRpACA.js} +2951 -7736
  15. package/dist/index-bYHRpACA.js.map +1 -0
  16. package/dist/{indexeddb-storage-kQ53UHEE.js → indexeddb-storage-BrIwr42m.js} +2 -2
  17. package/dist/{indexeddb-storage-kQ53UHEE.js.map → indexeddb-storage-BrIwr42m.js.map} +1 -1
  18. package/dist/{indexeddb-storage-wKG4mICM.cjs → indexeddb-storage-CFWfkdX9.cjs} +2 -2
  19. package/dist/{indexeddb-storage-wKG4mICM.cjs.map → indexeddb-storage-CFWfkdX9.cjs.map} +1 -1
  20. package/dist/{memory-storage-DnXCSbBl.js → memory-storage-BDQRj-2j.js} +2 -2
  21. package/dist/{memory-storage-DnXCSbBl.js.map → memory-storage-BDQRj-2j.js.map} +1 -1
  22. package/dist/{memory-storage-CGC8xM2G.cjs → memory-storage-bkatDnuR.cjs} +2 -2
  23. package/dist/{memory-storage-CGC8xM2G.cjs.map → memory-storage-bkatDnuR.cjs.map} +1 -1
  24. package/examples/demo.html +2 -29
  25. package/package.json +3 -8
  26. package/src/content/social-protocols.js +3 -59
  27. package/src/core/holosphere.js +16 -554
  28. package/src/crypto/nostr-utils.js +98 -1
  29. package/src/crypto/secp256k1.js +4 -393
  30. package/src/federation/discovery.js +7 -75
  31. package/src/federation/handshake.js +69 -202
  32. package/src/federation/hologram.js +222 -298
  33. package/src/federation/index.js +2 -9
  34. package/src/federation/registry.js +67 -1257
  35. package/src/federation/request-card.js +21 -35
  36. package/src/hierarchical/upcast.js +4 -9
  37. package/src/index.js +142 -296
  38. package/src/lib/federation-methods.js +370 -909
  39. package/src/storage/global-tables.js +1 -1
  40. package/src/storage/nostr-wrapper.js +9 -5
  41. package/src/subscriptions/manager.js +1 -1
  42. package/types/index.d.ts +145 -37
  43. package/bin/holosphere-activitypub.js +0 -158
  44. package/dist/2019-BzVkRcax.js +0 -6680
  45. package/dist/2019-BzVkRcax.js.map +0 -1
  46. package/dist/2019-C1hPR_Os.cjs +0 -8
  47. package/dist/2019-C1hPR_Os.cjs.map +0 -1
  48. package/dist/browser-BcmACE3G.js +0 -3058
  49. package/dist/browser-BcmACE3G.js.map +0 -1
  50. package/dist/browser-DaqYUTcG.cjs +0 -2
  51. package/dist/browser-DaqYUTcG.cjs.map +0 -1
  52. package/dist/index-BHptWysv.js.map +0 -1
  53. package/dist/index-CDlhzxT2.cjs +0 -29
  54. package/dist/index-CDlhzxT2.cjs.map +0 -1
  55. package/src/federation/capabilities.js +0 -46
  56. package/src/storage/backend-factory.js +0 -130
  57. package/src/storage/backend-interface.js +0 -161
  58. package/src/storage/backends/activitypub/server.js +0 -675
  59. package/src/storage/backends/activitypub-backend.js +0 -295
  60. package/src/storage/backends/gundb-backend.js +0 -875
  61. package/src/storage/backends/nostr-backend.js +0 -251
  62. package/src/storage/gun-async.js +0 -341
  63. package/src/storage/gun-auth.js +0 -373
  64. package/src/storage/gun-federation.js +0 -785
  65. package/src/storage/gun-references.js +0 -209
  66. package/src/storage/gun-schema.js +0 -306
  67. package/src/storage/gun-wrapper.js +0 -642
  68. package/src/storage/migration.js +0 -351
  69. package/src/storage/unified-storage.js +0 -161
package/src/index.js CHANGED
@@ -13,8 +13,7 @@
13
13
 
14
14
  import { HoloSphere as HoloSphereCore } from './core/holosphere.js';
15
15
  import * as spatial from './spatial/h3-operations.js';
16
- import * as storage from './storage/unified-storage.js';
17
- import * as nostrStorage from './storage/nostr-wrapper.js';
16
+ import * as storage from './storage/nostr-wrapper.js';
18
17
  import * as nostrAsync from './storage/nostr-async.js';
19
18
  import * as globalTables from './storage/global-tables.js';
20
19
  import * as schema from './schema/validator.js';
@@ -23,7 +22,6 @@ import * as federation from './federation/hologram.js';
23
22
  import * as handshake from './federation/handshake.js';
24
23
  import * as holonRegistry from './federation/holon-registry.js';
25
24
  import * as registry from './federation/registry.js';
26
- import * as capabilities from './federation/capabilities.js';
27
25
  import * as requestCard from './federation/request-card.js';
28
26
  import * as cardStorage from './federation/card-storage.js';
29
27
  import * as crypto from './crypto/secp256k1.js';
@@ -78,7 +76,6 @@ class HoloSphereBase extends HoloSphereCore {
78
76
  *
79
77
  * @param {Object} config - Configuration options
80
78
  * @param {string} config.appName - Application namespace for data isolation
81
- * @param {string} [config.backend='nostr'] - Storage backend type ('nostr', 'gundb', 'activitypub')
82
79
  * @param {string[]} [config.relays] - Nostr relay URLs for distributed storage
83
80
  * @param {string} [config.openaiKey] - OpenAI API key for AI services
84
81
  * @param {Object} [config.aiOptions] - AI service configuration
@@ -177,6 +174,25 @@ class HoloSphereBase extends HoloSphereCore {
177
174
  return isNaN(parsed) ? undefined : parsed;
178
175
  }
179
176
 
177
+ /**
178
+ * Extracts holon ID from a hologram soul path.
179
+ * Soul paths follow the format: "AppName/Holons/<holonId>/lensName/dataId"
180
+ *
181
+ * @private
182
+ * @param {string|undefined} soul - The soul path from a hologram
183
+ * @returns {string|null} The extracted holon ID or null if not found
184
+ */
185
+ _extractHolonIdFromSoul(soul) {
186
+ if (!soul || typeof soul !== 'string') return null;
187
+ // Soul format: "AppName/Holons/<holonId>/lensName/dataId" or "Holons/<holonId>/..."
188
+ const parts = soul.split('/');
189
+ const holonsIndex = parts.indexOf('Holons');
190
+ if (holonsIndex !== -1 && parts.length > holonsIndex + 1) {
191
+ return parts[holonsIndex + 1];
192
+ }
193
+ return null;
194
+ }
195
+
180
196
  /**
181
197
  * Initializes AI services with the provided API key.
182
198
  *
@@ -335,17 +351,13 @@ class HoloSphereBase extends HoloSphereCore {
335
351
  * @param {string} lensName - Name of the lens (data category)
336
352
  * @param {Object} data - Data object to write (must have or will be assigned an id)
337
353
  * @param {Object} [options={}] - Write options
338
- * @param {string} [options.capabilityToken] - Capability token for authorization
339
354
  * @param {boolean} [options.validate=true] - Whether to validate against schema
340
355
  * @param {boolean} [options.strict] - Override schema strict mode
341
- * @param {boolean} [options.autoPropagate=true] - Whether to propagate to federated holons
342
- * @param {Object} [options.propagationOptions] - Options for propagation
343
356
  * @param {boolean} [options.blocking=false] - If true, wait for relay confirmation before returning
344
357
  * @param {string} [options.signingKey] - Private key to sign with (hex format). If not provided, uses holosphere's default key.
345
358
  * @param {boolean} [options.encrypt] - Whether to encrypt this data. Defaults to encryptByDefault config.
346
359
  * @returns {Promise<boolean>} True if write succeeded (or queued for optimistic writes)
347
360
  * @throws {ValidationError} If holonId, lensName, or data is invalid
348
- * @throws {AuthorizationError} If capability token is invalid
349
361
  */
350
362
  async write(holonId, lensName, data, options = {}) {
351
363
  // Auto-convert to strings for convenience
@@ -362,6 +374,19 @@ class HoloSphereBase extends HoloSphereCore {
362
374
  throw new ValidationError('ValidationError: data must be an object');
363
375
  }
364
376
 
377
+ // HOLOGRAM ROUTING: If writing to a hologram, route to source holon
378
+ if (data._hologram?.isHologram) {
379
+ const sourceHolon = data._hologram.sourceHolon || this._extractHolonIdFromSoul(data._hologram.soul);
380
+ if (sourceHolon && sourceHolon !== holonId) {
381
+ this._log('DEBUG', '🔀 HOLOGRAM WRITE: Routing to source holon', {
382
+ requestedHolon: holonId?.slice(0, 12) + '...',
383
+ sourceHolon: sourceHolon?.slice(0, 12) + '...',
384
+ lensName
385
+ });
386
+ holonId = sourceHolon;
387
+ }
388
+ }
389
+
365
390
  // Check write authorization BEFORE optimistic caching
366
391
  // Helper function to detect if a string is a pubkey (64-char hex)
367
392
  const isPubkeyHolon = typeof holonId === 'string' && /^[0-9a-f]{64}$/i.test(holonId);
@@ -379,78 +404,17 @@ class HoloSphereBase extends HoloSphereCore {
379
404
  // For pubkey-based holons, check if it's our own holon (matches effective writer's pubkey)
380
405
  // For non-pubkey holons (H3 cells, UUIDs), skip authorization for backwards compatibility
381
406
  const isOwnHolon = effectiveWriterPubKey && (holonId === effectiveWriterPubKey || !isPubkeyHolon);
382
- const capToken = options.capabilityToken || options.capability;
383
- const actingAsHolon = options.actingAs || options.actingAsHolon;
384
407
 
408
+ // SIMPLIFIED: Everyone can write everywhere (Nostr is open-write).
409
+ // Non-federated writes are simply invisible on read (filtered out).
410
+ // Only log for debugging; no capability checks needed.
385
411
  if (options.externalWriter) {
386
- // App-level authorization already validated (e.g., QR capability token).
387
- // Skip holosphere-level auth checks entirely.
388
- // The data will be visible to the holon owner because read() skips
389
- // author filtering for own-holon reads (d-tag path scopes the data).
390
- this._log('DEBUG', '✅ External writer — skipping auth', {
412
+ this._log('DEBUG', '✅ External writer', {
391
413
  writerPubKey: effectiveWriterPubKey?.slice(0, 12) + '...',
392
414
  holonId: holonId?.slice(0, 12) + '...'
393
415
  });
394
- // Fall through to actual write below (skip all auth checks)
395
416
  } else if (!isOwnHolon) {
396
- // Writing to non-own holon - check authorization
397
- // Priority: 1. Explicit capability token, 2. Membership-based access
398
-
399
- if (capToken) {
400
- // Explicit capability token provided - verify it
401
- const authorized = await this.verifyCapability(capToken, 'write', { holonId, lensName });
402
- if (!authorized) {
403
- this._log('WARN', '🚫 Write denied: Invalid capability token', { holonId, lensName });
404
- throw new AuthorizationError(
405
- 'Invalid capability token for write operation',
406
- 'write'
407
- );
408
- }
409
- this._log('DEBUG', '✅ Write authorized via capability token', { holonId, lensName });
410
- } else {
411
- // No capability token - check unified access control
412
- // Uses canAccess() which checks: 1. owner, 2. membership, 3. federation grants (unified)
413
- const writerPubKey = effectiveWriterPubKey;
414
-
415
- if (!writerPubKey) {
416
- this._log('WARN', '🚫 Write denied: No authenticated user', { holonId, lensName });
417
- throw new AuthorizationError(
418
- 'Authentication required for writing to federated holons',
419
- 'write'
420
- );
421
- }
422
-
423
- // Check unified access control
424
- const accessCheck = await this.canAccess(holonId, lensName, writerPubKey, 'write', { actingAsHolon });
425
-
426
- if (!accessCheck.allowed) {
427
- this._log('WARN', '🚫 Write denied: No write access', {
428
- holonId,
429
- lensName,
430
- reason: accessCheck.reason,
431
- via: accessCheck.via,
432
- writerPubKey: writerPubKey?.slice(0, 12) + '...'
433
- });
434
- throw new AuthorizationError(
435
- `Write access denied: ${accessCheck.reason}`,
436
- 'write'
437
- );
438
- }
439
-
440
- this._log('DEBUG', '✅ Write authorized via unified access control', {
441
- holonId,
442
- lensName,
443
- via: accessCheck.via,
444
- reason: accessCheck.reason,
445
- grant: accessCheck.grant ? 'yes' : 'no'
446
- });
447
- }
448
- } else if (capToken) {
449
- // Own holon with explicit token - validate it anyway
450
- const authorized = await this.verifyCapability(capToken, 'write', { holonId, lensName });
451
- if (!authorized) {
452
- throw new AuthorizationError('AuthorizationError: Invalid capability token for write operation', 'write');
453
- }
417
+ this._log('DEBUG', '📝 Writing to non-own holon (open write)', { holonId, lensName });
454
418
  }
455
419
 
456
420
  if (!data.id) {
@@ -578,14 +542,6 @@ class HoloSphereBase extends HoloSphereCore {
578
542
  // Clear from write cache after successful sync (optional - keeps cache smaller)
579
543
  // this._writeCache.delete(path);
580
544
 
581
- // Handle propagation in background
582
- const { autoPropagate = true } = options;
583
- if (autoPropagate && !data.hologram) {
584
- this._log('DEBUG', '📤 Starting propagation', { path });
585
- this.propagate(holonId, lensName, data, options.propagationOptions || {})
586
- .catch(err => this._log('WARN', '⚠️ Propagation failed', { path, error: err.message }));
587
- }
588
-
589
545
  return true;
590
546
  } catch (error) {
591
547
  const duration = Date.now() - startTime;
@@ -645,26 +601,8 @@ class HoloSphereBase extends HoloSphereCore {
645
601
  await storage.write(this.client, path, localData);
646
602
 
647
603
  if (Object.keys(sourceUpdates).length > 0) {
648
- // Verify write capability before updating source
649
- const capability = existingData.capability;
650
- if (!capability) {
651
- throw new Error('Hologram missing capability token for source update');
652
- }
653
-
654
- const hasWrite = await crypto.verifyCapability(
655
- capability,
656
- 'write',
657
- {
658
- holonId: existingData.target.holonId,
659
- lensName: existingData.target.lensName,
660
- dataId: existingData.target.dataId
661
- }
662
- );
663
-
664
- if (!hasWrite) {
665
- throw new Error('Write capability required to update source data through hologram');
666
- }
667
-
604
+ // SIMPLIFIED: Everyone can write everywhere (Nostr is open-write).
605
+ // Just update the source directly through the hologram.
668
606
  const sourcePath = storage.buildPath(
669
607
  existingData.target.appname || this.config.appName,
670
608
  existingData.target.holonId,
@@ -703,15 +641,14 @@ class HoloSphereBase extends HoloSphereCore {
703
641
  }
704
642
 
705
643
  /**
706
- * Check if we have a stored capability for a given author and scope.
644
+ * Check if a pubkey is in our federation.
707
645
  *
708
646
  * @private
709
- * @param {string} authorPubKey - Author's public key
710
- * @param {Object} scope - Scope to check (holonId, lensName, dataId)
711
- * @returns {Promise<Object|null>} Capability entry or null
647
+ * @param {string} pubKey - Public key to check
648
+ * @returns {Promise<boolean>} True if federated
712
649
  */
713
- async _getCapabilityForAuthor(authorPubKey, scope) {
714
- return registry.getCapabilityForAuthor(this.client, this.config.appName, authorPubKey, scope);
650
+ async _isFederatedWith(pubKey) {
651
+ return registry.isFederated(this.client, this.config.appName, pubKey);
715
652
  }
716
653
 
717
654
  /**
@@ -890,6 +827,9 @@ class HoloSphereBase extends HoloSphereCore {
890
827
  * hologram resolution (including capability verification and recursive
891
828
  * chain resolution) to federation.resolveHologram().
892
829
  *
830
+ * Lens holograms (lens: true) are expanded separately — they fetch all
831
+ * items from the source author's lens and merge them into the result set.
832
+ *
893
833
  * @private
894
834
  * @param {Object|Array|null} data - Data that may contain holograms
895
835
  * @returns {Promise<Object|Array|null>} Resolved data with holograms replaced by source data
@@ -899,16 +839,71 @@ class HoloSphereBase extends HoloSphereCore {
899
839
 
900
840
  if (Array.isArray(data)) {
901
841
  const resolved = [];
842
+ const lensHolograms = [];
843
+
844
+ // First pass: resolve item holograms, collect lens holograms
902
845
  for (const item of data) {
846
+ if (federation.isLensHologram(item)) {
847
+ lensHolograms.push(item);
848
+ continue;
849
+ }
903
850
  const resolvedItem = await this._resolveHolograms(item);
904
851
  if (resolvedItem !== null) {
905
852
  resolved.push(resolvedItem);
906
853
  }
907
854
  }
855
+
856
+ // Second pass: expand lens holograms
857
+ if (lensHolograms.length > 0) {
858
+ const existingIds = new Set(resolved.map(r => r.id));
859
+
860
+ for (const lh of lensHolograms) {
861
+ const target = lh.target || {};
862
+ const sourcePath = storage.buildPath(
863
+ target.app || this.config.appName,
864
+ target.holon,
865
+ target.lens
866
+ );
867
+ try {
868
+ const items = await storage.readAll(this.client, sourcePath, {
869
+ authors: [target.author],
870
+ });
871
+ if (Array.isArray(items)) {
872
+ for (const sourceItem of items) {
873
+ // Skip holograms in the source lens (no transitive expansion)
874
+ if (sourceItem.hologram) continue;
875
+ // Deduplicate by id — local items take precedence
876
+ if (sourceItem.id && !existingIds.has(sourceItem.id)) {
877
+ existingIds.add(sourceItem.id);
878
+ // Tag with hologram metadata so consumers know the provenance
879
+ resolved.push({
880
+ ...sourceItem,
881
+ _hologram: {
882
+ isHologram: true,
883
+ soul: lh.soul,
884
+ sourceHolon: target.holon,
885
+ sourcePubKey: target.author,
886
+ lensHologram: true,
887
+ },
888
+ });
889
+ }
890
+ }
891
+ }
892
+ } catch (err) {
893
+ this._log('WARN', '⚠️ Failed to expand lens hologram', {
894
+ soul: lh.soul,
895
+ error: err.message,
896
+ });
897
+ }
898
+ }
899
+ }
900
+
908
901
  return resolved;
909
902
  }
910
903
 
911
904
  if (data?.hologram === true && data.target) {
905
+ // Lens holograms return null from resolveHologram — they're handled in array context
906
+ if (data.lens === true) return null;
912
907
  return federation.resolveHologram(this.client, data, new Set(), [], {
913
908
  appname: this.config.appName,
914
909
  deleteCircular: false,
@@ -925,11 +920,9 @@ class HoloSphereBase extends HoloSphereCore {
925
920
  * @param {string} lensName - Name of the lens (data category)
926
921
  * @param {string|null} [dataId=null] - Specific data ID, or null to read all
927
922
  * @param {Object} [options={}] - Read options
928
- * @param {string} [options.capabilityToken] - Capability token for authorization
929
923
  * @param {boolean} [options.resolveHolograms=true] - Whether to resolve hologram references
930
924
  * @returns {Promise<Object|Array|null>} Data object, array of objects, or null if not found
931
925
  * @throws {ValidationError} If holonId or lensName is invalid
932
- * @throws {AuthorizationError} If capability token is invalid
933
926
  */
934
927
  async read(holonId, lensName, dataId = null, options = {}) {
935
928
  // Auto-convert to strings for convenience
@@ -990,40 +983,16 @@ class HoloSphereBase extends HoloSphereCore {
990
983
  if (options.forceRelay) readOptions.forceRelay = true;
991
984
  if (options.timeout) readOptions.timeout = options.timeout;
992
985
 
993
- // Explicit capability token takes precedence
994
- const capToken = options.capabilityToken || options.capability;
995
- if (capToken) {
996
- const authorized = await this.verifyCapability(capToken, 'read', { holonId, lensName, dataId });
997
- if (!authorized) {
998
- throw new AuthorizationError('AuthorizationError: Invalid capability token for read operation', 'read');
999
- }
1000
- // Use target author for reading
1001
- if (isOtherAuthor) {
1002
- readOptions.authors = [targetPubkey];
1003
- }
1004
- } else if (isOtherAuthor) {
1005
- // Auto-check capability for other author's data
1006
- this._log('DEBUG', '🔍 Looking up capability for federated author', {
1007
- holonId,
1008
- lensName,
1009
- dataId: dataId || '*',
1010
- targetPubkey: targetPubkey?.slice(0, 12) + '...',
1011
- myPubkey: this.client?.publicKey?.slice(0, 12) + '...'
1012
- });
1013
- const capability = await this._getCapabilityForAuthor(targetPubkey, { holonId, lensName, dataId });
1014
- if (!capability) {
1015
- this._log('WARN', '❌ No capability found for federated author - returning empty', {
986
+ if (isOtherAuthor) {
987
+ // Reading another author's data — check federation membership
988
+ const federated = await this._isFederatedWith(targetPubkey);
989
+ if (!federated) {
990
+ this._log('DEBUG', '🔍 Not federated with author — returning empty', {
1016
991
  holonId,
1017
- lensName,
1018
992
  targetPubkey: targetPubkey?.slice(0, 12) + '...'
1019
993
  });
1020
994
  return dataId ? null : [];
1021
995
  }
1022
- this._log('DEBUG', '✅ Capability found for federated author', {
1023
- holonId,
1024
- lensName,
1025
- targetPubkey: targetPubkey?.slice(0, 12) + '...'
1026
- });
1027
996
  readOptions.authors = [targetPubkey];
1028
997
  }
1029
998
 
@@ -1154,13 +1123,11 @@ class HoloSphereBase extends HoloSphereCore {
1154
1123
  * @param {string} dataId - ID of the data to update
1155
1124
  * @param {Object} updates - Object containing fields to update
1156
1125
  * @param {Object} [options={}] - Update options
1157
- * @param {string} [options.capabilityToken] - Capability token for authorization
1158
1126
  * @param {boolean} [options.validate=true] - Whether to validate against schema
1159
1127
  * @param {boolean} [options.strict] - Override schema strict mode
1160
1128
  * @param {boolean} [options.blocking=false] - If true, wait for relay confirmation before returning
1161
1129
  * @returns {Promise<boolean>} True if update succeeded, false if data not found
1162
1130
  * @throws {ValidationError} If holonId, lensName, dataId, or updates is invalid
1163
- * @throws {AuthorizationError} If capability token is invalid
1164
1131
  */
1165
1132
  async update(holonId, lensName, dataId, updates, options = {}) {
1166
1133
  if (!holonId || typeof holonId !== 'string') {
@@ -1176,38 +1143,8 @@ class HoloSphereBase extends HoloSphereCore {
1176
1143
  throw new ValidationError('ValidationError: updates must be an object');
1177
1144
  }
1178
1145
 
1179
- // Check write authorization BEFORE optimistic caching
1180
- // Helper function to detect if a string is a pubkey (64-char hex)
1181
- const isPubkeyHolon = typeof holonId === 'string' && /^[0-9a-f]{64}$/i.test(holonId);
1182
- // For pubkey-based holons, check if it's our own holon (our pubkey)
1183
- // For non-pubkey holons (H3 cells, UUIDs), skip authorization for backwards compatibility
1184
- const isOwnHolon = this.client && (holonId === this.client.publicKey || !isPubkeyHolon);
1185
- const capToken = options.capabilityToken || options.capability;
1186
-
1187
- if (!isOwnHolon) {
1188
- // Updating federated holon - require capability token
1189
- if (!capToken) {
1190
- this._log('WARN', '🚫 Update denied: No capability token for federated holon', { holonId, lensName, dataId });
1191
- throw new AuthorizationError(
1192
- 'Capability token required for writing to federated holons',
1193
- 'write'
1194
- );
1195
- }
1196
- const authorized = await this.verifyCapability(capToken, 'write', { holonId, lensName, dataId });
1197
- if (!authorized) {
1198
- this._log('WARN', '🚫 Update denied: Invalid capability token', { holonId, lensName, dataId });
1199
- throw new AuthorizationError(
1200
- 'Invalid capability token for update operation',
1201
- 'write'
1202
- );
1203
- }
1204
- } else if (capToken) {
1205
- // Own holon with explicit token - validate it anyway
1206
- const authorized = await this.verifyCapability(capToken, 'write', { holonId, lensName, dataId });
1207
- if (!authorized) {
1208
- throw new AuthorizationError('AuthorizationError: Invalid capability token for update operation', 'write');
1209
- }
1210
- }
1146
+ // SIMPLIFIED: Everyone can write everywhere (Nostr is open-write).
1147
+ // No capability checks needed non-federated updates are invisible on read.
1211
1148
 
1212
1149
  const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
1213
1150
 
@@ -1320,11 +1257,9 @@ class HoloSphereBase extends HoloSphereCore {
1320
1257
  * @param {string} lensName - Name of the lens (data category)
1321
1258
  * @param {string} dataId - ID of the data to delete
1322
1259
  * @param {Object} [options={}] - Delete options
1323
- * @param {string} [options.capabilityToken] - Capability token for authorization
1324
1260
  * @param {boolean} [options.blocking=false] - If true, wait for relay confirmation before returning
1325
1261
  * @returns {Promise<boolean>} True if deletion succeeded, false if data not found
1326
1262
  * @throws {ValidationError} If holonId, lensName, or dataId is invalid
1327
- * @throws {AuthorizationError} If not owner and no valid capability token
1328
1263
  */
1329
1264
  async delete(holonId, lensName, dataId, options = {}) {
1330
1265
  // Auto-convert to strings for convenience
@@ -1357,16 +1292,8 @@ class HoloSphereBase extends HoloSphereCore {
1357
1292
  // For pubkey-based holons, check if it's our own holon (matches effective deleter's pubkey)
1358
1293
  // For non-pubkey holons (H3 cells, UUIDs), skip authorization for backwards compatibility
1359
1294
  const isOwnHolon = effectiveDeleterPubKey && (holonId === effectiveDeleterPubKey || !isPubkeyHolon);
1360
- const capToken = options.capabilityToken || options.capability;
1361
-
1362
- if (!isOwnHolon && !capToken) {
1363
- // Deleting from federated holon without capability token - fail fast
1364
- this._log('WARN', '🚫 Delete denied: No capability token for federated holon', { holonId, lensName, dataId });
1365
- throw new AuthorizationError(
1366
- 'Capability token required for writing to federated holons',
1367
- 'delete'
1368
- );
1369
- }
1295
+ // SIMPLIFIED: Everyone can write/delete everywhere (Nostr is open-write).
1296
+ // Non-federated deletes are effectively invisible on read.
1370
1297
 
1371
1298
  const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
1372
1299
 
@@ -1376,7 +1303,6 @@ class HoloSphereBase extends HoloSphereCore {
1376
1303
  let dataSource = cached ? 'write-cache' : null;
1377
1304
 
1378
1305
  if (!existingData) {
1379
- // When signingKey is provided, read using the signer's pubkey as author
1380
1306
  const readOptions = {};
1381
1307
  if (effectiveDeleterPubKey && effectiveDeleterPubKey !== this.client?.publicKey) {
1382
1308
  readOptions.authors = [effectiveDeleterPubKey];
@@ -1385,7 +1311,6 @@ class HoloSphereBase extends HoloSphereCore {
1385
1311
  dataSource = existingData ? 'storage' : null;
1386
1312
  }
1387
1313
 
1388
- // Return false if data doesn't exist in cache or storage
1389
1314
  if (!existingData) {
1390
1315
  this._log('DEBUG', '🗑️ DELETE: Data not found', { path });
1391
1316
  return false;
@@ -1393,27 +1318,6 @@ class HoloSphereBase extends HoloSphereCore {
1393
1318
 
1394
1319
  this._log('DEBUG', '🗑️ DELETE: Found data', { path, source: dataSource });
1395
1320
 
1396
- // Check authorization: data owner can delete, others need capability token
1397
- const dataOwner = existingData.owner || existingData._creator;
1398
- const isDataOwner = !dataOwner || dataOwner === this.client.publicKey || dataOwner === effectiveDeleterPubKey;
1399
-
1400
- if (!isDataOwner) {
1401
- // Non-data-owner must provide a valid capability token
1402
- if (!capToken) {
1403
- throw new AuthorizationError('AuthorizationError: Capability token required for delete operation', 'delete');
1404
- }
1405
- const authorized = await this.verifyCapability(capToken, 'delete', { holonId, lensName, dataId });
1406
- if (!authorized) {
1407
- throw new AuthorizationError('AuthorizationError: Invalid capability token for delete operation', 'delete');
1408
- }
1409
- } else if (capToken) {
1410
- // Data owner provided a token - validate it anyway
1411
- const authorized = await this.verifyCapability(capToken, 'delete', { holonId, lensName, dataId });
1412
- if (!authorized) {
1413
- throw new AuthorizationError('AuthorizationError: Invalid capability token for delete operation', 'delete');
1414
- }
1415
- }
1416
-
1417
1321
  // OPTIMISTIC DELETE: Remove from write cache and add to delete cache
1418
1322
  this._writeCache.delete(path);
1419
1323
  this._deleteCache.add(path);
@@ -1779,7 +1683,6 @@ class HoloSphereBase extends HoloSphereCore {
1779
1683
  */
1780
1684
  async createHologram(sourceHolon, lensName, data, targetHolon = null) {
1781
1685
  const target = targetHolon || sourceHolon;
1782
- // Use createHologramWithCapability which auto-generates capability for self-operations
1783
1686
  return federation.createHologramWithCapability(
1784
1687
  this.client,
1785
1688
  sourceHolon,
@@ -1787,11 +1690,7 @@ class HoloSphereBase extends HoloSphereCore {
1787
1690
  lensName,
1788
1691
  data.id,
1789
1692
  this.config.appName,
1790
- {
1791
- sourceAuthorPubKey: this.client.publicKey,
1792
- targetAuthorPubKey: this.client.publicKey,
1793
- permissions: ['read'],
1794
- }
1693
+ { sourceAuthorPubKey: this.client.publicKey }
1795
1694
  );
1796
1695
  }
1797
1696
 
@@ -1867,7 +1766,6 @@ class HoloSphereBase extends HoloSphereCore {
1867
1766
  async propagateData(data, sourceHolon, targetHolon, lensName, options = {}) {
1868
1767
  // Extract mode from options, default to 'reference' for hologram creation
1869
1768
  const mode = options.mode || 'reference';
1870
- // UNIFIED MODEL: Pass sourceAuthorPubKey for capability-based federation
1871
1769
  return federation.propagateData(
1872
1770
  this.client,
1873
1771
  this.config.appName,
@@ -1878,7 +1776,6 @@ class HoloSphereBase extends HoloSphereCore {
1878
1776
  mode,
1879
1777
  {
1880
1778
  sourceAuthorPubKey: options.sourceAuthorPubKey || this.client.publicKey,
1881
- capability: options.capability,
1882
1779
  }
1883
1780
  );
1884
1781
  }
@@ -2078,12 +1975,11 @@ class HoloSphereBase extends HoloSphereCore {
2078
1975
  * @param {string[]} [options.lensConfig.inbound] - Lenses for inbound federation
2079
1976
  * @param {string[]} [options.lensConfig.outbound] - Lenses for outbound federation
2080
1977
  * @param {string} [options.partnerName] - Human-readable name for the partner holon
2081
- * @param {boolean} [options.skipPropagation=false] - Skip propagating existing data
2082
1978
  * @returns {Promise<boolean>} True if federation was established
2083
1979
  * @throws {Error} If trying to federate a holon with itself
2084
1980
  */
2085
1981
  async federateHolon(sourceHolon, targetHolon, options = {}) {
2086
- const { lensConfig = { inbound: [], outbound: [] }, partnerName = null, skipPropagation = false } = options;
1982
+ const { lensConfig = { inbound: [], outbound: [] }, partnerName = null } = options;
2087
1983
 
2088
1984
  if (sourceHolon === targetHolon) {
2089
1985
  throw new Error('Cannot federate a holon with itself');
@@ -2142,16 +2038,6 @@ class HoloSphereBase extends HoloSphereCore {
2142
2038
  const success = await this.writeGlobal('federation', federationData);
2143
2039
  this.clearCache('federation');
2144
2040
 
2145
- // Only propagate existing data if skipPropagation is false
2146
- if (success && !skipPropagation) {
2147
- for (const lens of (lensConfig.inbound || [])) {
2148
- await this.federate(targetHolon, sourceHolon, lens, { direction: 'outbound', mode: 'reference' });
2149
- }
2150
- for (const lens of (lensConfig.outbound || [])) {
2151
- await this.federate(sourceHolon, targetHolon, lens, { direction: 'outbound', mode: 'reference' });
2152
- }
2153
- }
2154
-
2155
2041
  return success;
2156
2042
  }
2157
2043
 
@@ -2176,9 +2062,15 @@ class HoloSphereBase extends HoloSphereCore {
2176
2062
  delete federationData.lensConfig[targetHolon];
2177
2063
  }
2178
2064
 
2065
+ if (federationData.partnerNames) {
2066
+ delete federationData.partnerNames[targetHolon];
2067
+ }
2068
+
2179
2069
  const success = await this.writeGlobal('federation', federationData);
2180
2070
  this.clearCache('federation');
2181
2071
 
2072
+ await registry.removeFederatedPartner(this.client, this.config.appName, targetHolon);
2073
+
2182
2074
  if (success && lensConfig) {
2183
2075
  for (const lens of (lensConfig.inbound || [])) {
2184
2076
  await this.unfederate(targetHolon, sourceHolon, lens);
@@ -2248,26 +2140,14 @@ class HoloSphereBase extends HoloSphereCore {
2248
2140
  const isPubkeyHolon = typeof holonId === 'string' && /^[0-9a-f]{64}$/i.test(holonId);
2249
2141
  const isOtherAuthor = isPubkeyHolon && holonId !== this.client.publicKey;
2250
2142
 
2251
- // For other-author subscriptions, do authorization check before creating subscription
2143
+ // For other-author subscriptions, check federation membership
2252
2144
  if (isOtherAuthor) {
2253
- const capToken = options.capabilityToken || options.capability;
2254
- if (capToken) {
2255
- const authorized = await this.verifyCapability(capToken, 'read', { holonId, lensName });
2256
- if (!authorized) {
2257
- this._log('WARN', '❌ Subscribe: invalid capability token - skipping', { holonId, lensName });
2258
- return;
2259
- }
2260
- } else {
2261
- const capability = await this._getCapabilityForAuthor(holonId, { holonId, lensName });
2262
- if (!capability) {
2263
- this._log('WARN', '❌ Subscribe: no capability for federated author - skipping', {
2264
- holonId, lensName, targetPubkey: holonId?.slice(0, 12) + '...'
2265
- });
2266
- return;
2267
- }
2268
- this._log('DEBUG', '✅ Subscribe: capability found for federated author', {
2145
+ const federated = await this._isFederatedWith(holonId);
2146
+ if (!federated) {
2147
+ this._log('DEBUG', '🔍 Subscribe: not federated with author - skipping', {
2269
2148
  holonId, lensName, targetPubkey: holonId?.slice(0, 12) + '...'
2270
2149
  });
2150
+ return;
2271
2151
  }
2272
2152
  }
2273
2153
 
@@ -2288,6 +2168,7 @@ class HoloSphereBase extends HoloSphereCore {
2288
2168
 
2289
2169
  // Post-setup enhancement: include federated authors
2290
2170
  // Own-data subscription is already active; this adds partner visibility
2171
+ // Create a second subscription for federated author updates
2291
2172
  if (!isOtherAuthor) {
2292
2173
  try {
2293
2174
  const fedAuthorsPromise = registry.getFederatedAuthors(
@@ -2299,7 +2180,17 @@ class HoloSphereBase extends HoloSphereCore {
2299
2180
  );
2300
2181
  const federatedAuthors = await Promise.race([fedAuthorsPromise, fedAuthorsTimeout]);
2301
2182
  if (federatedAuthors.length > 0) {
2302
- this._log('DEBUG', 'Subscribe: federated authors found post-setup', { count: federatedAuthors.length });
2183
+ this._log('DEBUG', 'Subscribe: creating subscription for federated authors', { count: federatedAuthors.length });
2184
+ // Create additional subscription for federated authors' updates
2185
+ const fedSubscription = await subscriptions.createSubscription(
2186
+ this.client, path, callback, { ...subscriptionOptions, authors: federatedAuthors }
2187
+ );
2188
+ // Store reference so it can be unsubscribed later
2189
+ if (!unsubscribeCalled) {
2190
+ this.subscriptionRegistry.register(`${subscriptionId}-fed`, fedSubscription);
2191
+ } else {
2192
+ fedSubscription.unsubscribe();
2193
+ }
2303
2194
  }
2304
2195
  } catch (err) {
2305
2196
  this._log('WARN', 'Failed to get federated authors for subscription', { error: err.message });
@@ -2316,6 +2207,8 @@ class HoloSphereBase extends HoloSphereCore {
2316
2207
  unsubscribeCalled = true;
2317
2208
  if (innerSubscription) {
2318
2209
  this.subscriptionRegistry.unregister(subscriptionId);
2210
+ // Also unsubscribe federated authors subscription if it exists
2211
+ this.subscriptionRegistry.unregister(`${subscriptionId}-fed`);
2319
2212
  }
2320
2213
  }
2321
2214
  };
@@ -2376,31 +2269,6 @@ class HoloSphereBase extends HoloSphereCore {
2376
2269
  return crypto.verify(content, signature, publicKey);
2377
2270
  }
2378
2271
 
2379
- /**
2380
- * Issues a capability token for authorization.
2381
- *
2382
- * @param {string[]} permissions - Array of permissions ('read', 'write', 'delete')
2383
- * @param {Object} scope - Scope of the capability (holonId, lensName, etc.)
2384
- * @param {string} recipient - Public key of the recipient
2385
- * @param {Object} [options] - Additional options
2386
- * @returns {Promise<string>} Signed capability token
2387
- */
2388
- async issueCapability(permissions, scope, recipient, options) {
2389
- return crypto.issueCapability(permissions, scope, recipient, options);
2390
- }
2391
-
2392
- /**
2393
- * Verifies a capability token.
2394
- *
2395
- * @param {string} token - Capability token to verify
2396
- * @param {string} requiredPermission - Required permission to check
2397
- * @param {Object} scope - Scope to verify against
2398
- * @returns {Promise<boolean>} True if token is valid and has required permission
2399
- */
2400
- async verifyCapability(token, requiredPermission, scope) {
2401
- return crypto.verifyCapability(token, requiredPermission, scope);
2402
- }
2403
-
2404
2272
  // === Social Protocol Operations ===
2405
2273
 
2406
2274
  /**
@@ -2435,34 +2303,13 @@ class HoloSphereBase extends HoloSphereCore {
2435
2303
  return true;
2436
2304
  }
2437
2305
 
2438
- /**
2439
- * Publishes an ActivityPub object to a holon.
2440
- *
2441
- * @param {Object} object - ActivityPub object
2442
- * @param {string} object.type - ActivityPub type (Note, Article, etc.)
2443
- * @param {string} [object.actor] - Actor ID (defaults to client public key)
2444
- * @param {string} holonId - H3 cell ID for the holon
2445
- * @param {string} [lensName='social'] - Name of the lens
2446
- * @returns {Promise<boolean>} True if publish succeeded
2447
- */
2448
- async publishActivityPub(object, holonId, lensName = 'social') {
2449
- // Validate ActivityPub object format
2450
- social.validateActivityPubObject(object, true); // throwOnError=true
2451
-
2452
- const activity = social.transformActivityPubObject({
2453
- ...object,
2454
- actor: object.actor || this.client.publicKey,
2455
- });
2456
- return this.write(holonId, lensName, activity);
2457
- }
2458
-
2459
2306
  /**
2460
2307
  * Queries social protocol data from a holon.
2461
2308
  *
2462
2309
  * @param {string} holonId - H3 cell ID for the holon
2463
2310
  * @param {Object} [options={}] - Query options
2464
2311
  * @param {string} [options.lens='social'] - Name of the lens
2465
- * @param {string} [options.protocol] - Filter by protocol ('nostr', 'activitypub', 'all')
2312
+ * @param {string} [options.protocol] - Filter by protocol ('nostr' or 'all')
2466
2313
  * @param {string} [options.type] - Filter by content type
2467
2314
  * @param {number} [options.since] - Filter events after this timestamp
2468
2315
  * @param {number} [options.until] - Filter events before this timestamp
@@ -2785,11 +2632,10 @@ export {
2785
2632
  export { spatial, storage, schema, federation, handshake, crypto, nostrUtils, social, subscriptions, hierarchical };
2786
2633
 
2787
2634
  // Re-export federation submodules
2788
- export { capabilities, requestCard, cardStorage, holonRegistry, registry };
2635
+ export { requestCard, cardStorage, holonRegistry, registry };
2789
2636
 
2790
2637
  // Re-export specific utilities used in tests
2791
- export { matchScope } from './crypto/secp256k1.js';
2792
- export { createHologram } from './federation/hologram.js';
2638
+ export { createHologram, createLensHologram, isLensHologram, sendShare, sendAck } from './federation/hologram.js';
2793
2639
 
2794
2640
  // Re-export lens encryption utilities
2795
2641
  export { LensKeyStore, buildLensPath, parseLensPath } from './crypto/key-store.js';