holosphere 2.0.0-alpha21 → 2.0.0-alpha23

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 +61 -58
  4. package/dist/{index-B6-8KAQm.js → index-BEkCLOwI.js} +2 -2
  5. package/dist/{index-B6-8KAQm.js.map → index-BEkCLOwI.js.map} +1 -1
  6. package/dist/{index-D2WstuZJ.js → index-BEvX6DxG.js} +2 -2
  7. package/dist/{index-D2WstuZJ.js.map → index-BEvX6DxG.js.map} +1 -1
  8. package/dist/{index--QsHG_gD.cjs → index-BGTOiJ2Y.cjs} +2 -2
  9. package/dist/{index--QsHG_gD.cjs.map → index-BGTOiJ2Y.cjs.map} +1 -1
  10. package/dist/{index-COpLk9gL.cjs → index-BH1woZXL.cjs} +2 -2
  11. package/dist/{index-COpLk9gL.cjs.map → index-BH1woZXL.cjs.map} +1 -1
  12. package/dist/{index-BHptWysv.js → index-Cvxov2jv.js} +2970 -7753
  13. package/dist/index-Cvxov2jv.js.map +1 -0
  14. package/dist/index-vTKI_BAX.cjs +29 -0
  15. package/dist/index-vTKI_BAX.cjs.map +1 -0
  16. package/dist/{indexeddb-storage-wKG4mICM.cjs → indexeddb-storage-BmnCNnSg.cjs} +2 -2
  17. package/dist/{indexeddb-storage-wKG4mICM.cjs.map → indexeddb-storage-BmnCNnSg.cjs.map} +1 -1
  18. package/dist/{indexeddb-storage-kQ53UHEE.js → indexeddb-storage-MIFisaPy.js} +2 -2
  19. package/dist/{indexeddb-storage-kQ53UHEE.js.map → indexeddb-storage-MIFisaPy.js.map} +1 -1
  20. package/dist/{memory-storage-CGC8xM2G.cjs → memory-storage-BJjK3F4r.cjs} +2 -2
  21. package/dist/{memory-storage-CGC8xM2G.cjs.map → memory-storage-BJjK3F4r.cjs.map} +1 -1
  22. package/dist/{memory-storage-DnXCSbBl.js → memory-storage-DhHXdKQ-.js} +2 -2
  23. package/dist/{memory-storage-DnXCSbBl.js.map → memory-storage-DhHXdKQ-.js.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 +145 -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
@@ -11,10 +11,12 @@
11
11
  * @license MIT
12
12
  */
13
13
 
14
+ /** @constant {string} version - Package version (keep in sync with package.json) */
15
+ export const version = '2.0.0-alpha23';
16
+
14
17
  import { HoloSphere as HoloSphereCore } from './core/holosphere.js';
15
18
  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';
19
+ import * as storage from './storage/nostr-wrapper.js';
18
20
  import * as nostrAsync from './storage/nostr-async.js';
19
21
  import * as globalTables from './storage/global-tables.js';
20
22
  import * as schema from './schema/validator.js';
@@ -23,7 +25,6 @@ import * as federation from './federation/hologram.js';
23
25
  import * as handshake from './federation/handshake.js';
24
26
  import * as holonRegistry from './federation/holon-registry.js';
25
27
  import * as registry from './federation/registry.js';
26
- import * as capabilities from './federation/capabilities.js';
27
28
  import * as requestCard from './federation/request-card.js';
28
29
  import * as cardStorage from './federation/card-storage.js';
29
30
  import * as crypto from './crypto/secp256k1.js';
@@ -78,7 +79,6 @@ class HoloSphereBase extends HoloSphereCore {
78
79
  *
79
80
  * @param {Object} config - Configuration options
80
81
  * @param {string} config.appName - Application namespace for data isolation
81
- * @param {string} [config.backend='nostr'] - Storage backend type ('nostr', 'gundb', 'activitypub')
82
82
  * @param {string[]} [config.relays] - Nostr relay URLs for distributed storage
83
83
  * @param {string} [config.openaiKey] - OpenAI API key for AI services
84
84
  * @param {Object} [config.aiOptions] - AI service configuration
@@ -177,6 +177,25 @@ class HoloSphereBase extends HoloSphereCore {
177
177
  return isNaN(parsed) ? undefined : parsed;
178
178
  }
179
179
 
180
+ /**
181
+ * Extracts holon ID from a hologram soul path.
182
+ * Soul paths follow the format: "AppName/Holons/<holonId>/lensName/dataId"
183
+ *
184
+ * @private
185
+ * @param {string|undefined} soul - The soul path from a hologram
186
+ * @returns {string|null} The extracted holon ID or null if not found
187
+ */
188
+ _extractHolonIdFromSoul(soul) {
189
+ if (!soul || typeof soul !== 'string') return null;
190
+ // Soul format: "AppName/Holons/<holonId>/lensName/dataId" or "Holons/<holonId>/..."
191
+ const parts = soul.split('/');
192
+ const holonsIndex = parts.indexOf('Holons');
193
+ if (holonsIndex !== -1 && parts.length > holonsIndex + 1) {
194
+ return parts[holonsIndex + 1];
195
+ }
196
+ return null;
197
+ }
198
+
180
199
  /**
181
200
  * Initializes AI services with the provided API key.
182
201
  *
@@ -335,17 +354,13 @@ class HoloSphereBase extends HoloSphereCore {
335
354
  * @param {string} lensName - Name of the lens (data category)
336
355
  * @param {Object} data - Data object to write (must have or will be assigned an id)
337
356
  * @param {Object} [options={}] - Write options
338
- * @param {string} [options.capabilityToken] - Capability token for authorization
339
357
  * @param {boolean} [options.validate=true] - Whether to validate against schema
340
358
  * @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
359
  * @param {boolean} [options.blocking=false] - If true, wait for relay confirmation before returning
344
360
  * @param {string} [options.signingKey] - Private key to sign with (hex format). If not provided, uses holosphere's default key.
345
361
  * @param {boolean} [options.encrypt] - Whether to encrypt this data. Defaults to encryptByDefault config.
346
362
  * @returns {Promise<boolean>} True if write succeeded (or queued for optimistic writes)
347
363
  * @throws {ValidationError} If holonId, lensName, or data is invalid
348
- * @throws {AuthorizationError} If capability token is invalid
349
364
  */
350
365
  async write(holonId, lensName, data, options = {}) {
351
366
  // Auto-convert to strings for convenience
@@ -362,6 +377,19 @@ class HoloSphereBase extends HoloSphereCore {
362
377
  throw new ValidationError('ValidationError: data must be an object');
363
378
  }
364
379
 
380
+ // HOLOGRAM ROUTING: If writing to a hologram, route to source holon
381
+ if (data._hologram?.isHologram) {
382
+ const sourceHolon = data._hologram.sourceHolon || this._extractHolonIdFromSoul(data._hologram.soul);
383
+ if (sourceHolon && sourceHolon !== holonId) {
384
+ this._log('DEBUG', '🔀 HOLOGRAM WRITE: Routing to source holon', {
385
+ requestedHolon: holonId?.slice(0, 12) + '...',
386
+ sourceHolon: sourceHolon?.slice(0, 12) + '...',
387
+ lensName
388
+ });
389
+ holonId = sourceHolon;
390
+ }
391
+ }
392
+
365
393
  // Check write authorization BEFORE optimistic caching
366
394
  // Helper function to detect if a string is a pubkey (64-char hex)
367
395
  const isPubkeyHolon = typeof holonId === 'string' && /^[0-9a-f]{64}$/i.test(holonId);
@@ -379,78 +407,17 @@ class HoloSphereBase extends HoloSphereCore {
379
407
  // For pubkey-based holons, check if it's our own holon (matches effective writer's pubkey)
380
408
  // For non-pubkey holons (H3 cells, UUIDs), skip authorization for backwards compatibility
381
409
  const isOwnHolon = effectiveWriterPubKey && (holonId === effectiveWriterPubKey || !isPubkeyHolon);
382
- const capToken = options.capabilityToken || options.capability;
383
- const actingAsHolon = options.actingAs || options.actingAsHolon;
384
410
 
411
+ // SIMPLIFIED: Everyone can write everywhere (Nostr is open-write).
412
+ // Non-federated writes are simply invisible on read (filtered out).
413
+ // Only log for debugging; no capability checks needed.
385
414
  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', {
415
+ this._log('DEBUG', '✅ External writer', {
391
416
  writerPubKey: effectiveWriterPubKey?.slice(0, 12) + '...',
392
417
  holonId: holonId?.slice(0, 12) + '...'
393
418
  });
394
- // Fall through to actual write below (skip all auth checks)
395
419
  } 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
- }
420
+ this._log('DEBUG', '📝 Writing to non-own holon (open write)', { holonId, lensName });
454
421
  }
455
422
 
456
423
  if (!data.id) {
@@ -578,14 +545,6 @@ class HoloSphereBase extends HoloSphereCore {
578
545
  // Clear from write cache after successful sync (optional - keeps cache smaller)
579
546
  // this._writeCache.delete(path);
580
547
 
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
548
  return true;
590
549
  } catch (error) {
591
550
  const duration = Date.now() - startTime;
@@ -645,26 +604,8 @@ class HoloSphereBase extends HoloSphereCore {
645
604
  await storage.write(this.client, path, localData);
646
605
 
647
606
  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
-
607
+ // SIMPLIFIED: Everyone can write everywhere (Nostr is open-write).
608
+ // Just update the source directly through the hologram.
668
609
  const sourcePath = storage.buildPath(
669
610
  existingData.target.appname || this.config.appName,
670
611
  existingData.target.holonId,
@@ -703,15 +644,14 @@ class HoloSphereBase extends HoloSphereCore {
703
644
  }
704
645
 
705
646
  /**
706
- * Check if we have a stored capability for a given author and scope.
647
+ * Check if a pubkey is in our federation.
707
648
  *
708
649
  * @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
650
+ * @param {string} pubKey - Public key to check
651
+ * @returns {Promise<boolean>} True if federated
712
652
  */
713
- async _getCapabilityForAuthor(authorPubKey, scope) {
714
- return registry.getCapabilityForAuthor(this.client, this.config.appName, authorPubKey, scope);
653
+ async _isFederatedWith(pubKey) {
654
+ return registry.isFederated(this.client, this.config.appName, pubKey);
715
655
  }
716
656
 
717
657
  /**
@@ -890,6 +830,9 @@ class HoloSphereBase extends HoloSphereCore {
890
830
  * hologram resolution (including capability verification and recursive
891
831
  * chain resolution) to federation.resolveHologram().
892
832
  *
833
+ * Lens holograms (lens: true) are expanded separately — they fetch all
834
+ * items from the source author's lens and merge them into the result set.
835
+ *
893
836
  * @private
894
837
  * @param {Object|Array|null} data - Data that may contain holograms
895
838
  * @returns {Promise<Object|Array|null>} Resolved data with holograms replaced by source data
@@ -899,16 +842,71 @@ class HoloSphereBase extends HoloSphereCore {
899
842
 
900
843
  if (Array.isArray(data)) {
901
844
  const resolved = [];
845
+ const lensHolograms = [];
846
+
847
+ // First pass: resolve item holograms, collect lens holograms
902
848
  for (const item of data) {
849
+ if (federation.isLensHologram(item)) {
850
+ lensHolograms.push(item);
851
+ continue;
852
+ }
903
853
  const resolvedItem = await this._resolveHolograms(item);
904
854
  if (resolvedItem !== null) {
905
855
  resolved.push(resolvedItem);
906
856
  }
907
857
  }
858
+
859
+ // Second pass: expand lens holograms
860
+ if (lensHolograms.length > 0) {
861
+ const existingIds = new Set(resolved.map(r => r.id));
862
+
863
+ for (const lh of lensHolograms) {
864
+ const target = lh.target || {};
865
+ const sourcePath = storage.buildPath(
866
+ target.app || this.config.appName,
867
+ target.holon,
868
+ target.lens
869
+ );
870
+ try {
871
+ const items = await storage.readAll(this.client, sourcePath, {
872
+ authors: [target.author],
873
+ });
874
+ if (Array.isArray(items)) {
875
+ for (const sourceItem of items) {
876
+ // Skip holograms in the source lens (no transitive expansion)
877
+ if (sourceItem.hologram) continue;
878
+ // Deduplicate by id — local items take precedence
879
+ if (sourceItem.id && !existingIds.has(sourceItem.id)) {
880
+ existingIds.add(sourceItem.id);
881
+ // Tag with hologram metadata so consumers know the provenance
882
+ resolved.push({
883
+ ...sourceItem,
884
+ _hologram: {
885
+ isHologram: true,
886
+ soul: lh.soul,
887
+ sourceHolon: target.holon,
888
+ sourcePubKey: target.author,
889
+ lensHologram: true,
890
+ },
891
+ });
892
+ }
893
+ }
894
+ }
895
+ } catch (err) {
896
+ this._log('WARN', '⚠️ Failed to expand lens hologram', {
897
+ soul: lh.soul,
898
+ error: err.message,
899
+ });
900
+ }
901
+ }
902
+ }
903
+
908
904
  return resolved;
909
905
  }
910
906
 
911
907
  if (data?.hologram === true && data.target) {
908
+ // Lens holograms return null from resolveHologram — they're handled in array context
909
+ if (data.lens === true) return null;
912
910
  return federation.resolveHologram(this.client, data, new Set(), [], {
913
911
  appname: this.config.appName,
914
912
  deleteCircular: false,
@@ -925,11 +923,9 @@ class HoloSphereBase extends HoloSphereCore {
925
923
  * @param {string} lensName - Name of the lens (data category)
926
924
  * @param {string|null} [dataId=null] - Specific data ID, or null to read all
927
925
  * @param {Object} [options={}] - Read options
928
- * @param {string} [options.capabilityToken] - Capability token for authorization
929
926
  * @param {boolean} [options.resolveHolograms=true] - Whether to resolve hologram references
930
927
  * @returns {Promise<Object|Array|null>} Data object, array of objects, or null if not found
931
928
  * @throws {ValidationError} If holonId or lensName is invalid
932
- * @throws {AuthorizationError} If capability token is invalid
933
929
  */
934
930
  async read(holonId, lensName, dataId = null, options = {}) {
935
931
  // Auto-convert to strings for convenience
@@ -990,40 +986,16 @@ class HoloSphereBase extends HoloSphereCore {
990
986
  if (options.forceRelay) readOptions.forceRelay = true;
991
987
  if (options.timeout) readOptions.timeout = options.timeout;
992
988
 
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', {
989
+ if (isOtherAuthor) {
990
+ // Reading another author's data — check federation membership
991
+ const federated = await this._isFederatedWith(targetPubkey);
992
+ if (!federated) {
993
+ this._log('DEBUG', '🔍 Not federated with author — returning empty', {
1016
994
  holonId,
1017
- lensName,
1018
995
  targetPubkey: targetPubkey?.slice(0, 12) + '...'
1019
996
  });
1020
997
  return dataId ? null : [];
1021
998
  }
1022
- this._log('DEBUG', '✅ Capability found for federated author', {
1023
- holonId,
1024
- lensName,
1025
- targetPubkey: targetPubkey?.slice(0, 12) + '...'
1026
- });
1027
999
  readOptions.authors = [targetPubkey];
1028
1000
  }
1029
1001
 
@@ -1154,13 +1126,11 @@ class HoloSphereBase extends HoloSphereCore {
1154
1126
  * @param {string} dataId - ID of the data to update
1155
1127
  * @param {Object} updates - Object containing fields to update
1156
1128
  * @param {Object} [options={}] - Update options
1157
- * @param {string} [options.capabilityToken] - Capability token for authorization
1158
1129
  * @param {boolean} [options.validate=true] - Whether to validate against schema
1159
1130
  * @param {boolean} [options.strict] - Override schema strict mode
1160
1131
  * @param {boolean} [options.blocking=false] - If true, wait for relay confirmation before returning
1161
1132
  * @returns {Promise<boolean>} True if update succeeded, false if data not found
1162
1133
  * @throws {ValidationError} If holonId, lensName, dataId, or updates is invalid
1163
- * @throws {AuthorizationError} If capability token is invalid
1164
1134
  */
1165
1135
  async update(holonId, lensName, dataId, updates, options = {}) {
1166
1136
  if (!holonId || typeof holonId !== 'string') {
@@ -1176,38 +1146,8 @@ class HoloSphereBase extends HoloSphereCore {
1176
1146
  throw new ValidationError('ValidationError: updates must be an object');
1177
1147
  }
1178
1148
 
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
- }
1149
+ // SIMPLIFIED: Everyone can write everywhere (Nostr is open-write).
1150
+ // No capability checks needed non-federated updates are invisible on read.
1211
1151
 
1212
1152
  const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
1213
1153
 
@@ -1320,11 +1260,9 @@ class HoloSphereBase extends HoloSphereCore {
1320
1260
  * @param {string} lensName - Name of the lens (data category)
1321
1261
  * @param {string} dataId - ID of the data to delete
1322
1262
  * @param {Object} [options={}] - Delete options
1323
- * @param {string} [options.capabilityToken] - Capability token for authorization
1324
1263
  * @param {boolean} [options.blocking=false] - If true, wait for relay confirmation before returning
1325
1264
  * @returns {Promise<boolean>} True if deletion succeeded, false if data not found
1326
1265
  * @throws {ValidationError} If holonId, lensName, or dataId is invalid
1327
- * @throws {AuthorizationError} If not owner and no valid capability token
1328
1266
  */
1329
1267
  async delete(holonId, lensName, dataId, options = {}) {
1330
1268
  // Auto-convert to strings for convenience
@@ -1357,16 +1295,8 @@ class HoloSphereBase extends HoloSphereCore {
1357
1295
  // For pubkey-based holons, check if it's our own holon (matches effective deleter's pubkey)
1358
1296
  // For non-pubkey holons (H3 cells, UUIDs), skip authorization for backwards compatibility
1359
1297
  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
- }
1298
+ // SIMPLIFIED: Everyone can write/delete everywhere (Nostr is open-write).
1299
+ // Non-federated deletes are effectively invisible on read.
1370
1300
 
1371
1301
  const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
1372
1302
 
@@ -1376,7 +1306,6 @@ class HoloSphereBase extends HoloSphereCore {
1376
1306
  let dataSource = cached ? 'write-cache' : null;
1377
1307
 
1378
1308
  if (!existingData) {
1379
- // When signingKey is provided, read using the signer's pubkey as author
1380
1309
  const readOptions = {};
1381
1310
  if (effectiveDeleterPubKey && effectiveDeleterPubKey !== this.client?.publicKey) {
1382
1311
  readOptions.authors = [effectiveDeleterPubKey];
@@ -1385,7 +1314,6 @@ class HoloSphereBase extends HoloSphereCore {
1385
1314
  dataSource = existingData ? 'storage' : null;
1386
1315
  }
1387
1316
 
1388
- // Return false if data doesn't exist in cache or storage
1389
1317
  if (!existingData) {
1390
1318
  this._log('DEBUG', '🗑️ DELETE: Data not found', { path });
1391
1319
  return false;
@@ -1393,27 +1321,6 @@ class HoloSphereBase extends HoloSphereCore {
1393
1321
 
1394
1322
  this._log('DEBUG', '🗑️ DELETE: Found data', { path, source: dataSource });
1395
1323
 
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
1324
  // OPTIMISTIC DELETE: Remove from write cache and add to delete cache
1418
1325
  this._writeCache.delete(path);
1419
1326
  this._deleteCache.add(path);
@@ -1779,7 +1686,6 @@ class HoloSphereBase extends HoloSphereCore {
1779
1686
  */
1780
1687
  async createHologram(sourceHolon, lensName, data, targetHolon = null) {
1781
1688
  const target = targetHolon || sourceHolon;
1782
- // Use createHologramWithCapability which auto-generates capability for self-operations
1783
1689
  return federation.createHologramWithCapability(
1784
1690
  this.client,
1785
1691
  sourceHolon,
@@ -1787,11 +1693,7 @@ class HoloSphereBase extends HoloSphereCore {
1787
1693
  lensName,
1788
1694
  data.id,
1789
1695
  this.config.appName,
1790
- {
1791
- sourceAuthorPubKey: this.client.publicKey,
1792
- targetAuthorPubKey: this.client.publicKey,
1793
- permissions: ['read'],
1794
- }
1696
+ { sourceAuthorPubKey: this.client.publicKey }
1795
1697
  );
1796
1698
  }
1797
1699
 
@@ -1867,7 +1769,6 @@ class HoloSphereBase extends HoloSphereCore {
1867
1769
  async propagateData(data, sourceHolon, targetHolon, lensName, options = {}) {
1868
1770
  // Extract mode from options, default to 'reference' for hologram creation
1869
1771
  const mode = options.mode || 'reference';
1870
- // UNIFIED MODEL: Pass sourceAuthorPubKey for capability-based federation
1871
1772
  return federation.propagateData(
1872
1773
  this.client,
1873
1774
  this.config.appName,
@@ -1878,7 +1779,6 @@ class HoloSphereBase extends HoloSphereCore {
1878
1779
  mode,
1879
1780
  {
1880
1781
  sourceAuthorPubKey: options.sourceAuthorPubKey || this.client.publicKey,
1881
- capability: options.capability,
1882
1782
  }
1883
1783
  );
1884
1784
  }
@@ -2078,12 +1978,11 @@ class HoloSphereBase extends HoloSphereCore {
2078
1978
  * @param {string[]} [options.lensConfig.inbound] - Lenses for inbound federation
2079
1979
  * @param {string[]} [options.lensConfig.outbound] - Lenses for outbound federation
2080
1980
  * @param {string} [options.partnerName] - Human-readable name for the partner holon
2081
- * @param {boolean} [options.skipPropagation=false] - Skip propagating existing data
2082
1981
  * @returns {Promise<boolean>} True if federation was established
2083
1982
  * @throws {Error} If trying to federate a holon with itself
2084
1983
  */
2085
1984
  async federateHolon(sourceHolon, targetHolon, options = {}) {
2086
- const { lensConfig = { inbound: [], outbound: [] }, partnerName = null, skipPropagation = false } = options;
1985
+ const { lensConfig = { inbound: [], outbound: [] }, partnerName = null } = options;
2087
1986
 
2088
1987
  if (sourceHolon === targetHolon) {
2089
1988
  throw new Error('Cannot federate a holon with itself');
@@ -2142,16 +2041,6 @@ class HoloSphereBase extends HoloSphereCore {
2142
2041
  const success = await this.writeGlobal('federation', federationData);
2143
2042
  this.clearCache('federation');
2144
2043
 
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
2044
  return success;
2156
2045
  }
2157
2046
 
@@ -2176,9 +2065,15 @@ class HoloSphereBase extends HoloSphereCore {
2176
2065
  delete federationData.lensConfig[targetHolon];
2177
2066
  }
2178
2067
 
2068
+ if (federationData.partnerNames) {
2069
+ delete federationData.partnerNames[targetHolon];
2070
+ }
2071
+
2179
2072
  const success = await this.writeGlobal('federation', federationData);
2180
2073
  this.clearCache('federation');
2181
2074
 
2075
+ await registry.removeFederatedPartner(this.client, this.config.appName, targetHolon);
2076
+
2182
2077
  if (success && lensConfig) {
2183
2078
  for (const lens of (lensConfig.inbound || [])) {
2184
2079
  await this.unfederate(targetHolon, sourceHolon, lens);
@@ -2248,26 +2143,14 @@ class HoloSphereBase extends HoloSphereCore {
2248
2143
  const isPubkeyHolon = typeof holonId === 'string' && /^[0-9a-f]{64}$/i.test(holonId);
2249
2144
  const isOtherAuthor = isPubkeyHolon && holonId !== this.client.publicKey;
2250
2145
 
2251
- // For other-author subscriptions, do authorization check before creating subscription
2146
+ // For other-author subscriptions, check federation membership
2252
2147
  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', {
2148
+ const federated = await this._isFederatedWith(holonId);
2149
+ if (!federated) {
2150
+ this._log('DEBUG', '🔍 Subscribe: not federated with author - skipping', {
2269
2151
  holonId, lensName, targetPubkey: holonId?.slice(0, 12) + '...'
2270
2152
  });
2153
+ return;
2271
2154
  }
2272
2155
  }
2273
2156
 
@@ -2288,6 +2171,7 @@ class HoloSphereBase extends HoloSphereCore {
2288
2171
 
2289
2172
  // Post-setup enhancement: include federated authors
2290
2173
  // Own-data subscription is already active; this adds partner visibility
2174
+ // Create a second subscription for federated author updates
2291
2175
  if (!isOtherAuthor) {
2292
2176
  try {
2293
2177
  const fedAuthorsPromise = registry.getFederatedAuthors(
@@ -2299,7 +2183,17 @@ class HoloSphereBase extends HoloSphereCore {
2299
2183
  );
2300
2184
  const federatedAuthors = await Promise.race([fedAuthorsPromise, fedAuthorsTimeout]);
2301
2185
  if (federatedAuthors.length > 0) {
2302
- this._log('DEBUG', 'Subscribe: federated authors found post-setup', { count: federatedAuthors.length });
2186
+ this._log('DEBUG', 'Subscribe: creating subscription for federated authors', { count: federatedAuthors.length });
2187
+ // Create additional subscription for federated authors' updates
2188
+ const fedSubscription = await subscriptions.createSubscription(
2189
+ this.client, path, callback, { ...subscriptionOptions, authors: federatedAuthors }
2190
+ );
2191
+ // Store reference so it can be unsubscribed later
2192
+ if (!unsubscribeCalled) {
2193
+ this.subscriptionRegistry.register(`${subscriptionId}-fed`, fedSubscription);
2194
+ } else {
2195
+ fedSubscription.unsubscribe();
2196
+ }
2303
2197
  }
2304
2198
  } catch (err) {
2305
2199
  this._log('WARN', 'Failed to get federated authors for subscription', { error: err.message });
@@ -2316,6 +2210,8 @@ class HoloSphereBase extends HoloSphereCore {
2316
2210
  unsubscribeCalled = true;
2317
2211
  if (innerSubscription) {
2318
2212
  this.subscriptionRegistry.unregister(subscriptionId);
2213
+ // Also unsubscribe federated authors subscription if it exists
2214
+ this.subscriptionRegistry.unregister(`${subscriptionId}-fed`);
2319
2215
  }
2320
2216
  }
2321
2217
  };
@@ -2376,31 +2272,6 @@ class HoloSphereBase extends HoloSphereCore {
2376
2272
  return crypto.verify(content, signature, publicKey);
2377
2273
  }
2378
2274
 
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
2275
  // === Social Protocol Operations ===
2405
2276
 
2406
2277
  /**
@@ -2435,34 +2306,13 @@ class HoloSphereBase extends HoloSphereCore {
2435
2306
  return true;
2436
2307
  }
2437
2308
 
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
2309
  /**
2460
2310
  * Queries social protocol data from a holon.
2461
2311
  *
2462
2312
  * @param {string} holonId - H3 cell ID for the holon
2463
2313
  * @param {Object} [options={}] - Query options
2464
2314
  * @param {string} [options.lens='social'] - Name of the lens
2465
- * @param {string} [options.protocol] - Filter by protocol ('nostr', 'activitypub', 'all')
2315
+ * @param {string} [options.protocol] - Filter by protocol ('nostr' or 'all')
2466
2316
  * @param {string} [options.type] - Filter by content type
2467
2317
  * @param {number} [options.since] - Filter events after this timestamp
2468
2318
  * @param {number} [options.until] - Filter events before this timestamp
@@ -2785,11 +2635,10 @@ export {
2785
2635
  export { spatial, storage, schema, federation, handshake, crypto, nostrUtils, social, subscriptions, hierarchical };
2786
2636
 
2787
2637
  // Re-export federation submodules
2788
- export { capabilities, requestCard, cardStorage, holonRegistry, registry };
2638
+ export { requestCard, cardStorage, holonRegistry, registry };
2789
2639
 
2790
2640
  // Re-export specific utilities used in tests
2791
- export { matchScope } from './crypto/secp256k1.js';
2792
- export { createHologram } from './federation/hologram.js';
2641
+ export { createHologram, createLensHologram, isLensHologram, sendShare, sendAck } from './federation/hologram.js';
2793
2642
 
2794
2643
  // Re-export lens encryption utilities
2795
2644
  export { LensKeyStore, buildLensPath, parseLensPath } from './crypto/key-store.js';