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.
- package/dist/{2019-D2OG2idw.js → 2019-CLMqIAfQ.js} +1722 -1668
- package/dist/{2019-D2OG2idw.js.map → 2019-CLMqIAfQ.js.map} +1 -1
- package/dist/2019-Cp3uYhyY.cjs +8 -0
- package/dist/{2019-EION3wKo.cjs.map → 2019-Cp3uYhyY.cjs.map} +1 -1
- package/dist/browser-D6cNVl0v.cjs +2 -0
- package/dist/{browser-Cq59Ij19.cjs.map → browser-D6cNVl0v.cjs.map} +1 -1
- package/dist/{browser-BSniCNqO.js → browser-nUQt1cnB.js} +2 -2
- package/dist/{browser-BSniCNqO.js.map → browser-nUQt1cnB.js.map} +1 -1
- package/dist/cjs/holosphere.cjs +1 -1
- package/dist/esm/holosphere.js +67 -50
- package/dist/{index-D-jZhliX.js → index-BN_uoxQK.js} +20324 -735
- package/dist/index-BN_uoxQK.js.map +1 -0
- package/dist/{index-Bl6rM1NW.js → index-CoAjtqsD.js} +2 -2
- package/dist/{index-Bl6rM1NW.js.map → index-CoAjtqsD.js.map} +1 -1
- package/dist/{index-Bwg3OzRM.cjs → index-Cp3tI53z.cjs} +3 -3
- package/dist/{index-Bwg3OzRM.cjs.map → index-Cp3tI53z.cjs.map} +1 -1
- package/dist/index-DJjGSwXG.cjs +13 -0
- package/dist/index-DJjGSwXG.cjs.map +1 -0
- package/dist/index-V8EHMYEY.cjs +29 -0
- package/dist/index-V8EHMYEY.cjs.map +1 -0
- package/dist/index-Z5TstN1e.js +11663 -0
- package/dist/index-Z5TstN1e.js.map +1 -0
- package/dist/indexeddb-storage-CZK5A7XH.cjs +2 -0
- package/dist/indexeddb-storage-CZK5A7XH.cjs.map +1 -0
- package/dist/{indexeddb-storage-5eiUNsHC.js → indexeddb-storage-bpA01pAU.js} +39 -2
- package/dist/indexeddb-storage-bpA01pAU.js.map +1 -0
- package/dist/{memory-storage-DMt36uZO.cjs → memory-storage-B1k8Jszd.cjs} +2 -2
- package/dist/{memory-storage-DMt36uZO.cjs.map → memory-storage-B1k8Jszd.cjs.map} +1 -1
- package/dist/{memory-storage-CI-gfmuG.js → memory-storage-BqhmytP_.js} +2 -2
- package/dist/{memory-storage-CI-gfmuG.js.map → memory-storage-BqhmytP_.js.map} +1 -1
- package/docs/FEDERATION.md +474 -0
- package/package.json +3 -1
- package/src/crypto/nostr-utils.js +7 -0
- package/src/crypto/secp256k1.js +104 -38
- package/src/federation/capabilities.js +162 -0
- package/src/federation/card-storage.js +376 -0
- package/src/federation/handshake.js +561 -9
- package/src/federation/hologram.js +194 -57
- package/src/federation/holon-registry.js +187 -0
- package/src/federation/index.js +68 -0
- package/src/federation/registry.js +164 -6
- package/src/federation/request-card.js +373 -0
- package/src/hierarchical/upcast.js +19 -3
- package/src/index.js +209 -75
- package/src/lib/federation-methods.js +527 -5
- package/src/storage/indexeddb-storage.js +41 -0
- package/src/storage/nostr-async.js +14 -5
- package/src/storage/nostr-client.js +471 -155
- package/src/storage/nostr-wrapper.js +6 -3
- package/dist/2019-EION3wKo.cjs +0 -8
- package/dist/_commonjsHelpers-C37NGDzP.cjs +0 -2
- package/dist/_commonjsHelpers-C37NGDzP.cjs.map +0 -1
- package/dist/_commonjsHelpers-CUmg6egw.js +0 -7
- package/dist/_commonjsHelpers-CUmg6egw.js.map +0 -1
- package/dist/browser-Cq59Ij19.cjs +0 -2
- package/dist/index-D-jZhliX.js.map +0 -1
- package/dist/index-Dc6Z8Aob.cjs +0 -18
- package/dist/index-Dc6Z8Aob.cjs.map +0 -1
- package/dist/indexeddb-storage-5eiUNsHC.js.map +0 -1
- package/dist/indexeddb-storage-FNFUVvTJ.cjs +0 -2
- package/dist/indexeddb-storage-FNFUVvTJ.cjs.map +0 -1
- package/dist/secp256k1-CEwJNcfV.js +0 -1890
- package/dist/secp256k1-CEwJNcfV.js.map +0 -1
- package/dist/secp256k1-CiEONUnj.cjs +0 -12
- 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.
|
|
7
|
-
*
|
|
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 -
|
|
89
|
-
* @param {string} options.authorPubKey - Source author's public key (
|
|
90
|
-
* @param {string} options.capability - Capability token (for
|
|
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
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
hologram.crossHolosphere = true;
|
|
118
|
-
hologram.target.authorPubKey = authorPubKey;
|
|
119
|
-
hologram._meta.sourcePubKey = authorPubKey;
|
|
120
|
-
}
|
|
144
|
+
return hologram;
|
|
145
|
+
}
|
|
121
146
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
312
|
+
// Verify capability (unified model - always verify unless explicitly skipped)
|
|
313
|
+
if (!skipCapabilityVerification) {
|
|
218
314
|
if (!capability) {
|
|
219
|
-
console.warn(`❌
|
|
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
|
|
330
|
+
console.warn(`❌ Capability verification failed for hologram: ${soul}`);
|
|
235
331
|
return null;
|
|
236
332
|
}
|
|
333
|
+
}
|
|
237
334
|
|
|
238
|
-
|
|
335
|
+
// UNIFIED MODEL: Always fetch with author filter if authorPubKey is present
|
|
336
|
+
if (authorPubKey) {
|
|
239
337
|
sourceData = await read(client, soul, {
|
|
240
|
-
authors: [
|
|
338
|
+
authors: [authorPubKey]
|
|
241
339
|
});
|
|
242
340
|
} else {
|
|
243
|
-
//
|
|
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'
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
494
|
-
const
|
|
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
|
+
};
|