holosphere 2.0.0-alpha11 → 2.0.0-alpha13

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 (65) hide show
  1. package/dist/{2019-D2OG2idw.js → 2019-CLMqIAfQ.js} +1722 -1668
  2. package/dist/{2019-D2OG2idw.js.map → 2019-CLMqIAfQ.js.map} +1 -1
  3. package/dist/2019-Cp3uYhyY.cjs +8 -0
  4. package/dist/{2019-EION3wKo.cjs.map → 2019-Cp3uYhyY.cjs.map} +1 -1
  5. package/dist/browser-D6cNVl0v.cjs +2 -0
  6. package/dist/{browser-Cq59Ij19.cjs.map → browser-D6cNVl0v.cjs.map} +1 -1
  7. package/dist/{browser-BSniCNqO.js → browser-nUQt1cnB.js} +2 -2
  8. package/dist/{browser-BSniCNqO.js.map → browser-nUQt1cnB.js.map} +1 -1
  9. package/dist/cjs/holosphere.cjs +1 -1
  10. package/dist/esm/holosphere.js +67 -50
  11. package/dist/{index-D-jZhliX.js → index-BN_uoxQK.js} +20324 -735
  12. package/dist/index-BN_uoxQK.js.map +1 -0
  13. package/dist/{index-Bl6rM1NW.js → index-CoAjtqsD.js} +2 -2
  14. package/dist/{index-Bl6rM1NW.js.map → index-CoAjtqsD.js.map} +1 -1
  15. package/dist/{index-Bwg3OzRM.cjs → index-Cp3tI53z.cjs} +3 -3
  16. package/dist/{index-Bwg3OzRM.cjs.map → index-Cp3tI53z.cjs.map} +1 -1
  17. package/dist/index-DJjGSwXG.cjs +13 -0
  18. package/dist/index-DJjGSwXG.cjs.map +1 -0
  19. package/dist/index-V8EHMYEY.cjs +29 -0
  20. package/dist/index-V8EHMYEY.cjs.map +1 -0
  21. package/dist/index-Z5TstN1e.js +11663 -0
  22. package/dist/index-Z5TstN1e.js.map +1 -0
  23. package/dist/indexeddb-storage-CZK5A7XH.cjs +2 -0
  24. package/dist/indexeddb-storage-CZK5A7XH.cjs.map +1 -0
  25. package/dist/{indexeddb-storage-5eiUNsHC.js → indexeddb-storage-bpA01pAU.js} +39 -2
  26. package/dist/indexeddb-storage-bpA01pAU.js.map +1 -0
  27. package/dist/{memory-storage-DMt36uZO.cjs → memory-storage-B1k8Jszd.cjs} +2 -2
  28. package/dist/{memory-storage-DMt36uZO.cjs.map → memory-storage-B1k8Jszd.cjs.map} +1 -1
  29. package/dist/{memory-storage-CI-gfmuG.js → memory-storage-BqhmytP_.js} +2 -2
  30. package/dist/{memory-storage-CI-gfmuG.js.map → memory-storage-BqhmytP_.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 +104 -38
  35. package/src/federation/capabilities.js +162 -0
  36. package/src/federation/card-storage.js +376 -0
  37. package/src/federation/handshake.js +561 -9
  38. package/src/federation/hologram.js +194 -57
  39. package/src/federation/holon-registry.js +187 -0
  40. package/src/federation/index.js +68 -0
  41. package/src/federation/registry.js +164 -6
  42. package/src/federation/request-card.js +373 -0
  43. package/src/hierarchical/upcast.js +19 -3
  44. package/src/index.js +209 -75
  45. package/src/lib/federation-methods.js +527 -5
  46. package/src/storage/indexeddb-storage.js +41 -0
  47. package/src/storage/nostr-async.js +14 -5
  48. package/src/storage/nostr-client.js +471 -155
  49. package/src/storage/nostr-wrapper.js +6 -3
  50. package/dist/2019-EION3wKo.cjs +0 -8
  51. package/dist/_commonjsHelpers-C37NGDzP.cjs +0 -2
  52. package/dist/_commonjsHelpers-C37NGDzP.cjs.map +0 -1
  53. package/dist/_commonjsHelpers-CUmg6egw.js +0 -7
  54. package/dist/_commonjsHelpers-CUmg6egw.js.map +0 -1
  55. package/dist/browser-Cq59Ij19.cjs +0 -2
  56. package/dist/index-D-jZhliX.js.map +0 -1
  57. package/dist/index-Dc6Z8Aob.cjs +0 -18
  58. package/dist/index-Dc6Z8Aob.cjs.map +0 -1
  59. package/dist/indexeddb-storage-5eiUNsHC.js.map +0 -1
  60. package/dist/indexeddb-storage-FNFUVvTJ.cjs +0 -2
  61. package/dist/indexeddb-storage-FNFUVvTJ.cjs.map +0 -1
  62. package/dist/secp256k1-CEwJNcfV.js +0 -1890
  63. package/dist/secp256k1-CEwJNcfV.js.map +0 -1
  64. package/dist/secp256k1-CiEONUnj.cjs +0 -12
  65. package/dist/secp256k1-CiEONUnj.cjs.map +0 -1
@@ -1,17 +1,23 @@
1
1
  /**
2
- * @fileoverview Federation and Hologram (Reference) Management.
2
+ * @fileoverview Unified Federation and Hologram (Reference) Management.
3
3
  *
4
4
  * Provides hologram (lightweight reference) creation, resolution, and management.
5
5
  * Holograms enable data to appear in multiple holons while maintaining a single
6
- * source of truth. Supports circular reference detection and cross-holosphere
7
- * federation with capability-based access control.
6
+ * source of truth.
7
+ *
8
+ * UNIFIED MODEL: All federation is capability-based. Every hologram includes:
9
+ * - authorPubKey: The public key of the data owner (can be self)
10
+ * - capability: A capability token (self-issued for same-author federation)
11
+ *
12
+ * This unifies "in-federation" (same author) and "cross-federation" (different authors)
13
+ * into a single consistent model.
8
14
  *
9
15
  * @module federation/hologram
10
16
  */
11
17
 
12
18
  import { buildPath, write, read, update } from '../storage/unified-storage.js';
13
- import { verifyCapability } from '../crypto/secp256k1.js';
14
- import { getCapabilityForAuthor } from './registry.js';
19
+ import { verifyCapability, issueCapability } from '../crypto/secp256k1.js';
20
+ import { getCapabilityForAuthor, storeInboundCapability } from './registry.js';
15
21
 
16
22
  /** @constant {number} Maximum depth for hologram resolution chain */
17
23
  const MAX_RESOLUTION_DEPTH = 10;
@@ -79,19 +85,38 @@ export async function wouldCreateCircularHologram(client, appname, sourceHolon,
79
85
  }
80
86
 
81
87
  /**
82
- * Create a hologram (lightweight reference)
88
+ * Create a hologram (lightweight reference) - UNIFIED MODEL
89
+ *
90
+ * All holograms now include authorPubKey and capability for consistency.
91
+ * This unifies "in-federation" (same author) and "cross-federation" (different authors).
92
+ *
83
93
  * @param {string} sourceHolon - Source holon ID
84
94
  * @param {string} targetHolon - Target holon ID
85
95
  * @param {string} lensName - Lens name
86
96
  * @param {string} dataId - Data ID
87
97
  * @param {string} appname - Application namespace
88
- * @param {Object} options - Optional cross-holosphere settings
89
- * @param {string} options.authorPubKey - Source author's public key (for cross-holosphere)
90
- * @param {string} options.capability - Capability token (for cross-holosphere)
91
- * @returns {Object} Hologram object
98
+ * @param {Object} options - Hologram options (authorPubKey and capability are required)
99
+ * @param {string} options.authorPubKey - Source author's public key (REQUIRED - can be self)
100
+ * @param {string} options.capability - Capability token (REQUIRED - self-issued for same author)
101
+ * @returns {Object} Hologram object with unified structure
102
+ * @throws {Error} If authorPubKey or capability is missing
92
103
  */
93
104
  export function createHologram(sourceHolon, targetHolon, lensName, dataId, appname, options = {}) {
94
- const { authorPubKey = null, capability = null } = options;
105
+ const { authorPubKey } = options;
106
+ let { capability } = options;
107
+
108
+ // Validate required fields for unified model
109
+ if (!authorPubKey) {
110
+ throw new Error('authorPubKey is required for hologram creation (unified model)');
111
+ }
112
+ if (!capability) {
113
+ throw new Error('capability is required for hologram creation (unified model)');
114
+ }
115
+
116
+ // Normalize capability - ensure it's the token string, not a capability object
117
+ if (typeof capability === 'object' && capability.token) {
118
+ capability = capability.token;
119
+ }
95
120
 
96
121
  const soul = buildPath(appname, sourceHolon, lensName, dataId);
97
122
 
@@ -104,32 +129,95 @@ export function createHologram(sourceHolon, targetHolon, lensName, dataId, appna
104
129
  holonId: sourceHolon,
105
130
  lensName,
106
131
  dataId,
132
+ authorPubKey, // Always present in unified model
107
133
  },
134
+ capability, // Always the token string (normalized above)
108
135
  _meta: {
109
136
  created: Date.now(),
110
137
  sourceHolon,
111
138
  source: sourceHolon, // Alias for compatibility
139
+ sourcePubKey: authorPubKey,
140
+ grantedAt: Date.now(),
112
141
  },
113
142
  };
114
143
 
115
- // Cross-holosphere enhancements
116
- if (authorPubKey) {
117
- hologram.crossHolosphere = true;
118
- hologram.target.authorPubKey = authorPubKey;
119
- hologram._meta.sourcePubKey = authorPubKey;
120
- }
144
+ return hologram;
145
+ }
121
146
 
122
- if (capability) {
123
- hologram.capability = capability;
124
- hologram._meta.grantedAt = Date.now();
147
+ /**
148
+ * Create a hologram with automatic capability issuance - UNIFIED MODEL
149
+ *
150
+ * This is the preferred method for creating holograms. It automatically issues
151
+ * a capability token (self-capability if same author, cross-capability otherwise).
152
+ *
153
+ * @param {Object} client - Nostr client instance (must have publicKey and optionally privateKey)
154
+ * @param {string} sourceHolon - Source holon ID
155
+ * @param {string} targetHolon - Target holon ID
156
+ * @param {string} lensName - Lens name
157
+ * @param {string} dataId - Data ID
158
+ * @param {string} appname - Application namespace
159
+ * @param {Object} options - Hologram options
160
+ * @param {string} [options.sourceAuthorPubKey] - Source author's public key (defaults to client.publicKey)
161
+ * @param {string} [options.targetAuthorPubKey] - Target author's public key (defaults to client.publicKey)
162
+ * @param {string} [options.capability] - Pre-issued capability (if not provided, one will be issued)
163
+ * @param {string[]} [options.permissions=['read']] - Permissions for auto-issued capability
164
+ * @param {number} [options.expiresIn] - Capability expiration (null for self-caps = no expiration)
165
+ * @returns {Promise<Object>} Hologram object with unified structure
166
+ */
167
+ export async function createHologramWithCapability(client, sourceHolon, targetHolon, lensName, dataId, appname, options = {}) {
168
+ const {
169
+ sourceAuthorPubKey = client.publicKey,
170
+ targetAuthorPubKey = client.publicKey,
171
+ capability: providedCapability = null,
172
+ permissions = ['read'],
173
+ expiresIn = null,
174
+ } = options;
175
+
176
+ let capability = providedCapability;
177
+
178
+ // Issue capability if not provided
179
+ if (!capability) {
180
+ const isSelfCapability = sourceAuthorPubKey === targetAuthorPubKey;
181
+
182
+ // Issue capability - for self-caps, no expiration by default
183
+ capability = await issueCapability(
184
+ permissions,
185
+ { holonId: sourceHolon, lensName, dataId },
186
+ targetAuthorPubKey,
187
+ {
188
+ expiresIn: expiresIn !== null ? expiresIn : (isSelfCapability ? 365 * 24 * 60 * 60 * 1000 : 3600000), // 1 year for self, 1 hour for cross
189
+ issuer: sourceAuthorPubKey,
190
+ issuerKey: client.privateKey,
191
+ }
192
+ );
193
+
194
+ // Store the capability in registry for cross-author federation
195
+ if (!isSelfCapability && client.privateKey) {
196
+ await storeInboundCapability(client, appname, sourceAuthorPubKey, {
197
+ token: capability,
198
+ scope: { holonId: sourceHolon, lensName, dataId },
199
+ permissions,
200
+ isSelfCapability: false,
201
+ });
202
+ }
125
203
  }
126
204
 
127
- return hologram;
205
+ return createHologram(sourceHolon, targetHolon, lensName, dataId, appname, {
206
+ authorPubKey: sourceAuthorPubKey,
207
+ capability,
208
+ });
128
209
  }
129
210
 
130
211
  /**
131
- * Resolve hologram to actual data, merging local overrides
132
- * Supports cross-holosphere holograms with capability token verification
212
+ * Resolve hologram to actual data, merging local overrides - UNIFIED MODEL
213
+ *
214
+ * In the unified model, ALL holograms have authorPubKey and capability.
215
+ * Resolution always:
216
+ * 1. Verifies the capability token
217
+ * 2. Fetches data with author filter
218
+ *
219
+ * This provides consistent security for both self and cross-author holograms.
220
+ *
133
221
  * @param {Object} client - Nostr client instance
134
222
  * @param {Object} hologram - Hologram object
135
223
  * @param {Set} visited - Visited souls (circular reference detection)
@@ -138,10 +226,11 @@ export function createHologram(sourceHolon, targetHolon, lensName, dataId, appna
138
226
  * @param {string} options.appname - Application namespace (for registry lookup)
139
227
  * @param {boolean} options.deleteCircular - If true, delete circular holograms when detected (default: true)
140
228
  * @param {string} options.hologramPath - Path of the hologram being resolved (for deletion)
229
+ * @param {boolean} options.skipCapabilityVerification - Skip capability check (for internal use only)
141
230
  * @returns {Promise<Object|null>} Resolved data with _hologram metadata, or null
142
231
  */
143
232
  export async function resolveHologram(client, hologram, visited = new Set(), chain = [], options = {}) {
144
- const { deleteCircular = true, hologramPath = null } = options;
233
+ const { deleteCircular = true, hologramPath = null, skipCapabilityVerification = false } = options;
145
234
 
146
235
  if (!hologram || !hologram.hologram) {
147
236
  return hologram; // Not a hologram, return as-is
@@ -192,31 +281,38 @@ export async function resolveHologram(client, hologram, visited = new Set(), cha
192
281
 
193
282
  let sourceData;
194
283
 
195
- // Handle cross-holosphere holograms
196
- if (hologram.crossHolosphere && target.authorPubKey) {
197
- // Get capability - try embedded first, then registry fallback
198
- let capability = hologram.capability;
284
+ // UNIFIED MODEL: All holograms have authorPubKey and capability
285
+ // Get capability - try embedded first, then registry fallback
286
+ let capability = hologram.capability;
287
+ const authorPubKey = target.authorPubKey;
199
288
 
200
- if (!capability && options.appname) {
201
- // Fallback to registry lookup
202
- const capEntry = await getCapabilityForAuthor(
203
- client,
204
- options.appname,
205
- target.authorPubKey,
206
- {
207
- holonId: target.holonId,
208
- lensName: target.lensName,
209
- dataId: target.dataId,
210
- }
211
- );
212
- if (capEntry) {
213
- capability = capEntry.token;
289
+ // Normalize capability - extract token string if it's a capability object
290
+ if (capability && typeof capability === 'object' && capability.token) {
291
+ capability = capability.token;
292
+ }
293
+
294
+ if (!capability && options.appname && authorPubKey) {
295
+ // Fallback to registry lookup
296
+ const capEntry = await getCapabilityForAuthor(
297
+ client,
298
+ options.appname,
299
+ authorPubKey,
300
+ {
301
+ holonId: target.holonId,
302
+ lensName: target.lensName,
303
+ dataId: target.dataId,
214
304
  }
305
+ );
306
+ if (capEntry) {
307
+ // Extract token string from capability entry
308
+ capability = capEntry.token || capEntry;
215
309
  }
310
+ }
216
311
 
217
- // Verify capability before resolving
312
+ // Verify capability (unified model - always verify unless explicitly skipped)
313
+ if (!skipCapabilityVerification) {
218
314
  if (!capability) {
219
- console.warn(`❌ Cross-holosphere hologram missing capability: ${soul}`);
315
+ console.warn(`❌ Hologram missing capability: ${soul}`);
220
316
  return null;
221
317
  }
222
318
 
@@ -231,16 +327,18 @@ export async function resolveHologram(client, hologram, visited = new Set(), cha
231
327
  );
232
328
 
233
329
  if (!isValid) {
234
- console.warn(`❌ Capability verification failed for cross-holosphere hologram: ${soul}`);
330
+ console.warn(`❌ Capability verification failed for hologram: ${soul}`);
235
331
  return null;
236
332
  }
333
+ }
237
334
 
238
- // Read from federated source (different author)
335
+ // UNIFIED MODEL: Always fetch with author filter if authorPubKey is present
336
+ if (authorPubKey) {
239
337
  sourceData = await read(client, soul, {
240
- authors: [target.authorPubKey]
338
+ authors: [authorPubKey]
241
339
  });
242
340
  } else {
243
- // Same-holosphere resolution (original behavior)
341
+ // Legacy fallback for holograms without authorPubKey
244
342
  sourceData = await read(client, soul);
245
343
  }
246
344
 
@@ -259,7 +357,7 @@ export async function resolveHologram(client, hologram, visited = new Set(), cha
259
357
  }
260
358
 
261
359
  /**
262
- * Merge hologram local overrides with source data
360
+ * Merge hologram local overrides with source data - UNIFIED MODEL
263
361
  * @private
264
362
  * @param {Object} hologram - Hologram object
265
363
  * @param {Object} sourceData - Source data
@@ -267,7 +365,7 @@ export async function resolveHologram(client, hologram, visited = new Set(), cha
267
365
  */
268
366
  function mergeHologramWithSource(hologram, sourceData) {
269
367
  // Extract local overrides from the hologram (exclude hologram-specific fields)
270
- const hologramOnlyFields = ['hologram', 'soul', 'target', '_meta', 'id', 'capability', 'crossHolosphere'];
368
+ const hologramOnlyFields = ['hologram', 'soul', 'target', '_meta', 'id', 'capability'];
271
369
  const localOverrides = {};
272
370
  const localOverrideKeys = [];
273
371
 
@@ -280,6 +378,8 @@ function mergeHologramWithSource(hologram, sourceData) {
280
378
 
281
379
  // Merge: source data + local overrides + hologram metadata
282
380
  const sourceHolon = hologram._meta?.sourceHolon || hologram._meta?.source || hologram.target?.holonId;
381
+ const sourcePubKey = hologram.target?.authorPubKey || hologram._meta?.sourcePubKey || null;
382
+
283
383
  const resolved = {
284
384
  ...sourceData, // Original data from source
285
385
  ...localOverrides, // Local overrides (x, y, pinned, etc.)
@@ -288,8 +388,7 @@ function mergeHologramWithSource(hologram, sourceData) {
288
388
  soul: hologram.soul,
289
389
  sourceHolon: sourceHolon,
290
390
  localOverrides: localOverrideKeys,
291
- crossHolosphere: hologram.crossHolosphere || false,
292
- sourcePubKey: hologram._meta?.sourcePubKey || null,
391
+ sourcePubKey: sourcePubKey, // Always present in unified model
293
392
  }
294
393
  };
295
394
 
@@ -340,14 +439,21 @@ export async function setupFederation(
340
439
  }
341
440
 
342
441
  /**
343
- * Propagate data to federated location
344
- * @param {Object} client - Nostr client instance
442
+ * Propagate data to federated location - UNIFIED MODEL
443
+ *
444
+ * In the unified model, all holograms require authorPubKey and capability.
445
+ * This function automatically issues capabilities for propagation.
446
+ *
447
+ * @param {Object} client - Nostr client instance (must have publicKey)
345
448
  * @param {string} appname - Application namespace
346
449
  * @param {Object} data - Data to propagate
347
450
  * @param {string} sourceHolon - Source holon ID
348
451
  * @param {string} targetHolon - Target holon ID
349
452
  * @param {string} lensName - Lens name
350
453
  * @param {string} mode - 'reference' or 'copy'
454
+ * @param {Object} options - Propagation options
455
+ * @param {string} [options.sourceAuthorPubKey] - Source author (defaults to client.publicKey)
456
+ * @param {string} [options.capability] - Pre-issued capability (auto-issued if not provided)
351
457
  * @returns {Promise<boolean>} Success indicator
352
458
  */
353
459
  export async function propagateData(
@@ -357,7 +463,8 @@ export async function propagateData(
357
463
  sourceHolon,
358
464
  targetHolon,
359
465
  lensName,
360
- mode = 'reference'
466
+ mode = 'reference',
467
+ options = {}
361
468
  ) {
362
469
  const dataId = data.id;
363
470
 
@@ -490,8 +597,38 @@ export async function propagateData(
490
597
  return false;
491
598
  }
492
599
 
493
- // Create hologram in target
494
- const hologram = createHologram(sourceHolon, targetHolon, lensName, dataId, appname);
600
+ // UNIFIED MODEL: Create hologram with capability
601
+ const sourceAuthorPubKey = options.sourceAuthorPubKey || client.publicKey;
602
+
603
+ // Determine targetAuthorPubKey - the person who should be able to read the hologram
604
+ // If targetHolon is a 64-char hex pubkey, use it directly as the target author
605
+ // Otherwise, this needs to be resolved via holon registry (TODO: add resolution)
606
+ const isPubkey = typeof targetHolon === 'string' && /^[0-9a-f]{64}$/i.test(targetHolon);
607
+ const targetAuthorPubKey = options.targetAuthorPubKey || (isPubkey ? targetHolon : client.publicKey);
608
+
609
+ console.log('[propagateData] Creating hologram with capability:', {
610
+ sourceHolon: sourceHolon?.slice(0, 12) + '...',
611
+ targetHolon: targetHolon?.slice(0, 12) + '...',
612
+ sourceAuthorPubKey: sourceAuthorPubKey?.slice(0, 12) + '...',
613
+ targetAuthorPubKey: targetAuthorPubKey?.slice(0, 12) + '...',
614
+ hasProvidedCapability: !!options.capability
615
+ });
616
+
617
+ const hologram = await createHologramWithCapability(
618
+ client,
619
+ sourceHolon,
620
+ targetHolon,
621
+ lensName,
622
+ dataId,
623
+ appname,
624
+ {
625
+ sourceAuthorPubKey,
626
+ targetAuthorPubKey,
627
+ capability: options.capability,
628
+ permissions: ['read'],
629
+ }
630
+ );
631
+
495
632
  const targetPath = buildPath(appname, targetHolon, lensName, dataId);
496
633
  const success = await write(client, targetPath, hologram);
497
634
 
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Holon Registry - Maps holonId to public key
3
+ *
4
+ * Every holon (H3 cell, telegram chat, concept) can be registered with a keypair.
5
+ * The holonId IS the public key (or resolves to one via this registry).
6
+ *
7
+ * When reading data, the system resolves holonId -> pubkey to determine
8
+ * whose data to read and what capabilities are needed.
9
+ */
10
+
11
+ import { writeGlobal, readGlobal, getAllGlobal } from '../storage/global-tables.js';
12
+
13
+ const HOLON_REGISTRY_TABLE = '_holon-registry';
14
+
15
+ /**
16
+ * Check if a string is a valid 64-character hex public key
17
+ * @param {string} str - String to check
18
+ * @returns {boolean} True if valid pubkey format
19
+ */
20
+ export function isPubkey(str) {
21
+ return typeof str === 'string' && /^[0-9a-f]{64}$/i.test(str);
22
+ }
23
+
24
+ /**
25
+ * Register a holon -> public key mapping
26
+ * @param {Object} client - NostrClient instance
27
+ * @param {string} appname - Application namespace
28
+ * @param {string} holonId - Holon identifier (H3, chatId, concept, etc.)
29
+ * @param {string} publicKey - 64-char hex public key
30
+ * @param {Object} options - Registration options
31
+ * @param {string} [options.alias] - Human-readable name
32
+ * @returns {Promise<boolean>} Success indicator
33
+ */
34
+ export async function registerHolon(client, appname, holonId, publicKey, options = {}) {
35
+ if (!isPubkey(publicKey)) {
36
+ throw new Error(`Invalid public key format: ${publicKey}`);
37
+ }
38
+
39
+ // Don't register if holonId is already a pubkey
40
+ if (isPubkey(holonId)) {
41
+ console.log('[HolonRegistry] registerHolon: holonId is already a pubkey, skipping registration');
42
+ return true; // No registration needed
43
+ }
44
+
45
+ const entry = {
46
+ id: holonId,
47
+ holonId: holonId,
48
+ publicKey: publicKey,
49
+ alias: options.alias || null,
50
+ createdAt: Date.now(),
51
+ updatedAt: Date.now(),
52
+ createdBy: client.publicKey,
53
+ };
54
+
55
+ console.log('[HolonRegistry] 📝 registerHolon:', {
56
+ holonId,
57
+ publicKey: publicKey?.slice(0, 12) + '...',
58
+ alias: options.alias
59
+ });
60
+
61
+ const result = await writeGlobal(client, appname, HOLON_REGISTRY_TABLE, entry);
62
+ console.log('[HolonRegistry] registerHolon result:', result);
63
+ return result;
64
+ }
65
+
66
+ /**
67
+ * Look up a holon's registration entry
68
+ * @param {Object} client - NostrClient instance
69
+ * @param {string} appname - Application namespace
70
+ * @param {string} holonId - Holon identifier
71
+ * @returns {Promise<Object|null>} Registry entry or null
72
+ */
73
+ export async function lookupHolon(client, appname, holonId) {
74
+ // If already a pubkey, return synthetic entry
75
+ if (isPubkey(holonId)) {
76
+ return {
77
+ holonId: holonId,
78
+ publicKey: holonId,
79
+ isDirect: true,
80
+ };
81
+ }
82
+
83
+ return readGlobal(client, appname, HOLON_REGISTRY_TABLE, holonId);
84
+ }
85
+
86
+ /**
87
+ * Resolve holonId to public key
88
+ * @param {Object} client - NostrClient instance
89
+ * @param {string} appname - Application namespace
90
+ * @param {string} holonId - Holon identifier
91
+ * @returns {Promise<string|null>} Public key or null if not found
92
+ */
93
+ export async function resolveHolonToPubkey(client, appname, holonId) {
94
+ // Direct pubkey
95
+ if (isPubkey(holonId)) {
96
+ console.log('[HolonRegistry] resolveHolonToPubkey: holonId is already a pubkey:', holonId?.slice(0, 12) + '...');
97
+ return holonId;
98
+ }
99
+
100
+ // Registry lookup
101
+ const entry = await lookupHolon(client, appname, holonId);
102
+
103
+ if (entry?.publicKey) {
104
+ console.log('[HolonRegistry] ✅ resolveHolonToPubkey: Found mapping', {
105
+ holonId,
106
+ publicKey: entry.publicKey?.slice(0, 12) + '...',
107
+ alias: entry.alias
108
+ });
109
+ } else {
110
+ console.log('[HolonRegistry] ❌ resolveHolonToPubkey: No mapping found for holonId:', holonId);
111
+ }
112
+
113
+ return entry?.publicKey || null;
114
+ }
115
+
116
+ /**
117
+ * Unregister a holon
118
+ * @param {Object} client - NostrClient instance
119
+ * @param {string} appname - Application namespace
120
+ * @param {string} holonId - Holon identifier
121
+ * @returns {Promise<boolean>} Success indicator
122
+ */
123
+ export async function unregisterHolon(client, appname, holonId) {
124
+ // Can't unregister a direct pubkey
125
+ if (isPubkey(holonId)) {
126
+ return false;
127
+ }
128
+
129
+ // Mark as deleted by writing a tombstone
130
+ const entry = {
131
+ id: holonId,
132
+ holonId: holonId,
133
+ publicKey: null,
134
+ _deleted: true,
135
+ deletedAt: Date.now(),
136
+ deletedBy: client.publicKey,
137
+ };
138
+
139
+ return writeGlobal(client, appname, HOLON_REGISTRY_TABLE, entry);
140
+ }
141
+
142
+ /**
143
+ * Update a holon's alias or metadata
144
+ * @param {Object} client - NostrClient instance
145
+ * @param {string} appname - Application namespace
146
+ * @param {string} holonId - Holon identifier
147
+ * @param {Object} updates - Fields to update
148
+ * @param {string} [updates.alias] - New alias
149
+ * @returns {Promise<boolean>} Success indicator
150
+ */
151
+ export async function updateHolon(client, appname, holonId, updates = {}) {
152
+ const existing = await lookupHolon(client, appname, holonId);
153
+ if (!existing || existing.isDirect) {
154
+ return false;
155
+ }
156
+
157
+ const entry = {
158
+ ...existing,
159
+ ...updates,
160
+ updatedAt: Date.now(),
161
+ };
162
+
163
+ return writeGlobal(client, appname, HOLON_REGISTRY_TABLE, entry);
164
+ }
165
+
166
+ /**
167
+ * List all registered holons
168
+ * @param {Object} client - NostrClient instance
169
+ * @param {string} appname - Application namespace
170
+ * @returns {Promise<Object[]>} Array of registry entries
171
+ */
172
+ export async function listRegisteredHolons(client, appname) {
173
+ const entries = await getAllGlobal(client, appname, HOLON_REGISTRY_TABLE);
174
+ return (entries || []).filter(e => !e._deleted);
175
+ }
176
+
177
+ /**
178
+ * Find holons by public key
179
+ * @param {Object} client - NostrClient instance
180
+ * @param {string} appname - Application namespace
181
+ * @param {string} publicKey - Public key to search for
182
+ * @returns {Promise<Object[]>} Array of holons with this pubkey
183
+ */
184
+ export async function findHolonsByPubkey(client, appname, publicKey) {
185
+ const all = await listRegisteredHolons(client, appname);
186
+ return all.filter(e => e.publicKey === publicKey);
187
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @fileoverview Federation Module Index
3
+ *
4
+ * Exports all federation-related functionality in a modular structure.
5
+ * The federation system is now split into smaller, focused components:
6
+ *
7
+ * - capabilities: Token issuance and verification
8
+ * - registry: Partner and capability storage
9
+ * - handshake: NIP-44 encrypted DM protocol
10
+ * - discovery: Nostr event-based discovery
11
+ * - hologram: Reference/hologram management
12
+ * - request-card: Card-based configuration UI
13
+ * - card-storage: Persistent card state management
14
+ * - holon-registry: HolonId to pubKey mapping
15
+ *
16
+ * @module federation
17
+ */
18
+
19
+ // Core federation components
20
+ export * from './registry.js';
21
+ export * from './hologram.js';
22
+ export * from './discovery.js';
23
+
24
+ // Handshake protocol
25
+ export * from './handshake.js';
26
+
27
+ // Capability management
28
+ export * from './capabilities.js';
29
+
30
+ // Card-based configuration
31
+ export * from './request-card.js';
32
+ export * from './card-storage.js';
33
+
34
+ // Holon registry
35
+ export * from './holon-registry.js';
36
+
37
+ // Named exports for convenience
38
+ import * as registry from './registry.js';
39
+ import * as hologram from './hologram.js';
40
+ import * as discovery from './discovery.js';
41
+ import * as handshake from './handshake.js';
42
+ import * as capabilities from './capabilities.js';
43
+ import * as requestCard from './request-card.js';
44
+ import * as cardStorage from './card-storage.js';
45
+ import * as holonRegistry from './holon-registry.js';
46
+
47
+ export {
48
+ registry,
49
+ hologram,
50
+ discovery,
51
+ handshake,
52
+ capabilities,
53
+ requestCard,
54
+ cardStorage,
55
+ holonRegistry,
56
+ };
57
+
58
+ // Default export with all modules
59
+ export default {
60
+ registry,
61
+ hologram,
62
+ discovery,
63
+ handshake,
64
+ capabilities,
65
+ requestCard,
66
+ cardStorage,
67
+ holonRegistry,
68
+ };