holosphere 2.0.0-alpha0 → 2.0.0-alpha2
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/LICENSE +162 -38
- package/dist/cjs/holosphere.cjs +2 -0
- package/dist/cjs/holosphere.cjs.map +1 -0
- package/dist/esm/holosphere.js +56 -0
- package/dist/esm/holosphere.js.map +1 -0
- package/dist/index-CDfIuXew.js +15974 -0
- package/dist/index-CDfIuXew.js.map +1 -0
- package/dist/index-ifOgtDvd.cjs +3 -0
- package/dist/index-ifOgtDvd.cjs.map +1 -0
- package/dist/indexeddb-storage-CMW4qRQS.js +96 -0
- package/dist/indexeddb-storage-CMW4qRQS.js.map +1 -0
- package/dist/indexeddb-storage-DLZOgetM.cjs +2 -0
- package/dist/indexeddb-storage-DLZOgetM.cjs.map +1 -0
- package/dist/memory-storage-DQzcAZlf.js +47 -0
- package/dist/memory-storage-DQzcAZlf.js.map +1 -0
- package/dist/memory-storage-DmePEP2q.cjs +2 -0
- package/dist/memory-storage-DmePEP2q.cjs.map +1 -0
- package/dist/secp256k1-CP0ZkpAx.cjs +13 -0
- package/dist/secp256k1-CP0ZkpAx.cjs.map +1 -0
- package/dist/secp256k1-vOXp40Fx.js +2281 -0
- package/dist/secp256k1-vOXp40Fx.js.map +1 -0
- package/docs/FOSDEM_PROPOSAL.md +388 -0
- package/docs/LOCALFIRST.md +266 -0
- package/docs/contracts/api-interface.md +793 -0
- package/docs/data-model.md +476 -0
- package/docs/gun-async-usage.md +338 -0
- package/docs/plan.md +349 -0
- package/docs/quickstart.md +674 -0
- package/docs/research.md +362 -0
- package/docs/spec.md +244 -0
- package/docs/storage-backends.md +326 -0
- package/docs/tasks.md +947 -0
- package/package.json +1 -1
- package/tests/unit/ai/aggregation.test.js +0 -295
- package/tests/unit/ai/breakdown.test.js +0 -446
- package/tests/unit/ai/classifier.test.js +0 -294
- package/tests/unit/ai/council.test.js +0 -262
- package/tests/unit/ai/embeddings.test.js +0 -384
- package/tests/unit/ai/federation-ai.test.js +0 -344
- package/tests/unit/ai/h3-ai.test.js +0 -458
- package/tests/unit/ai/index.test.js +0 -304
- package/tests/unit/ai/json-ops.test.js +0 -307
- package/tests/unit/ai/llm-service.test.js +0 -390
- package/tests/unit/ai/nl-query.test.js +0 -383
- package/tests/unit/ai/relationships.test.js +0 -311
- package/tests/unit/ai/schema-extractor.test.js +0 -384
- package/tests/unit/ai/spatial.test.js +0 -279
- package/tests/unit/ai/tts.test.js +0 -279
- package/tests/unit/content.test.js +0 -332
- package/tests/unit/contract/core.test.js +0 -88
- package/tests/unit/contract/crypto.test.js +0 -198
- package/tests/unit/contract/data.test.js +0 -223
- package/tests/unit/contract/federation.test.js +0 -181
- package/tests/unit/contract/hierarchical.test.js +0 -113
- package/tests/unit/contract/schema.test.js +0 -114
- package/tests/unit/contract/social.test.js +0 -217
- package/tests/unit/contract/spatial.test.js +0 -110
- package/tests/unit/contract/subscriptions.test.js +0 -128
- package/tests/unit/contract/utils.test.js +0 -159
- package/tests/unit/core.test.js +0 -152
- package/tests/unit/crypto.test.js +0 -328
- package/tests/unit/federation.test.js +0 -234
- package/tests/unit/gun-async.test.js +0 -252
- package/tests/unit/hierarchical.test.js +0 -399
- package/tests/unit/integration/scenario-01-geographic-storage.test.js +0 -74
- package/tests/unit/integration/scenario-02-federation.test.js +0 -76
- package/tests/unit/integration/scenario-03-subscriptions.test.js +0 -102
- package/tests/unit/integration/scenario-04-validation.test.js +0 -129
- package/tests/unit/integration/scenario-05-hierarchy.test.js +0 -125
- package/tests/unit/integration/scenario-06-social.test.js +0 -135
- package/tests/unit/integration/scenario-07-persistence.test.js +0 -130
- package/tests/unit/integration/scenario-08-authorization.test.js +0 -161
- package/tests/unit/integration/scenario-09-cross-dimensional.test.js +0 -139
- package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +0 -357
- package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +0 -410
- package/tests/unit/integration/scenario-12-capability-federated-read.test.js +0 -719
- package/tests/unit/performance/benchmark.test.js +0 -85
- package/tests/unit/schema.test.js +0 -213
- package/tests/unit/spatial.test.js +0 -158
- package/tests/unit/storage.test.js +0 -195
- package/tests/unit/subscriptions.test.js +0 -328
- package/tests/unit/test-data-permanence-debug.js +0 -197
- package/tests/unit/test-data-permanence.js +0 -340
- package/tests/unit/test-key-persistence-fixed.js +0 -148
- package/tests/unit/test-key-persistence.js +0 -172
- package/tests/unit/test-relay-permanence.js +0 -376
- package/tests/unit/test-second-node.js +0 -95
- package/tests/unit/test-simple-write.js +0 -89
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import HoloSphere from '../../../src/index.js';
|
|
3
|
-
|
|
4
|
-
describe('Integration: Scenario 9 - Cross-Dimensional Federation', () => {
|
|
5
|
-
let hs;
|
|
6
|
-
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
hs = new HoloSphere({ relays: [], appName: 'scenario-09' });
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('should complete cross-dimensional federation workflow', async () => {
|
|
12
|
-
// Step 1: Create data in geographic holon (San Francisco)
|
|
13
|
-
const geoHolon = await hs.toHolon(37.7749, -122.4194, 9);
|
|
14
|
-
await hs.write(geoHolon, 'discussions', {
|
|
15
|
-
id: 'thread-001',
|
|
16
|
-
topic: 'Urban Planning',
|
|
17
|
-
messages: 12
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
// Step 2: Create noospheric holon (concept space)
|
|
21
|
-
const conceptHolon = 'concept://urbanism/sustainable-cities';
|
|
22
|
-
await hs.write(conceptHolon, 'discussions', {
|
|
23
|
-
id: 'thread-002',
|
|
24
|
-
topic: 'Sustainability Frameworks',
|
|
25
|
-
messages: 8
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
// Step 3: Federate geographic ↔ noospheric (cross-dimensional link)
|
|
29
|
-
const fedSuccess = await hs.federate(geoHolon, conceptHolon, 'discussions', {
|
|
30
|
-
direction: 'bidirectional'
|
|
31
|
-
});
|
|
32
|
-
expect(fedSuccess).toBe(true);
|
|
33
|
-
|
|
34
|
-
// Step 4: Query geographic holon - includes noospheric federated data
|
|
35
|
-
const geoDiscussions = await hs.getFederatedData(geoHolon, 'discussions');
|
|
36
|
-
expect(Array.isArray(geoDiscussions)).toBe(true);
|
|
37
|
-
expect(geoDiscussions.length).toBeGreaterThanOrEqual(2);
|
|
38
|
-
|
|
39
|
-
const hasLocal = geoDiscussions.some(d => d.id === 'thread-001');
|
|
40
|
-
const hasFederated = geoDiscussions.some(d => d.id === 'thread-002');
|
|
41
|
-
expect(hasLocal).toBe(true);
|
|
42
|
-
expect(hasFederated).toBe(true);
|
|
43
|
-
|
|
44
|
-
// Step 5: Query noospheric holon - includes geographic federated data
|
|
45
|
-
const conceptDiscussions = await hs.getFederatedData(conceptHolon, 'discussions');
|
|
46
|
-
expect(Array.isArray(conceptDiscussions)).toBe(true);
|
|
47
|
-
expect(conceptDiscussions.length).toBeGreaterThanOrEqual(2);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('should validate noospheric holons identified by URI/URL', async () => {
|
|
51
|
-
const noosphericUris = [
|
|
52
|
-
'nostr://topic/climate',
|
|
53
|
-
'concept://philosophy/ethics',
|
|
54
|
-
'ap://community/developers',
|
|
55
|
-
'https://example.com/topic/blockchain'
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
for (const uri of noosphericUris) {
|
|
59
|
-
const success = await hs.write(uri, 'content', {
|
|
60
|
-
id: 'item-001',
|
|
61
|
-
text: 'Noospheric content'
|
|
62
|
-
});
|
|
63
|
-
expect(success).toBe(true);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should validate federation works across geographic and conceptual dimensions', async () => {
|
|
68
|
-
const geographic = await hs.toHolon(40.7128, -74.0060, 9); // New York
|
|
69
|
-
const conceptual = 'concept://technology/ai';
|
|
70
|
-
|
|
71
|
-
await hs.write(geographic, 'research', {
|
|
72
|
-
id: 'study-001',
|
|
73
|
-
title: 'AI in Urban Settings'
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
await hs.write(conceptual, 'research', {
|
|
77
|
-
id: 'study-002',
|
|
78
|
-
title: 'Machine Learning Ethics'
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// Inbound: geographic subscribes to receive data from conceptual
|
|
82
|
-
const fedSuccess = await hs.federate(geographic, conceptual, 'research', { direction: 'inbound' });
|
|
83
|
-
expect(fedSuccess).toBe(true);
|
|
84
|
-
|
|
85
|
-
// Geographic should see conceptual data (inbound federation)
|
|
86
|
-
const geoData = await hs.getFederatedData(geographic, 'research');
|
|
87
|
-
expect(geoData.some(d => d.id === 'study-002')).toBe(true);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('should validate bidirectional cross-dimensional linking', async () => {
|
|
91
|
-
const geo = await hs.toHolon(37.7749, -122.4194, 9);
|
|
92
|
-
const concept = 'concept://sustainability/green-energy';
|
|
93
|
-
|
|
94
|
-
await hs.write(geo, 'projects', { id: 'proj-001', name: 'Solar Panels' });
|
|
95
|
-
await hs.write(concept, 'projects', { id: 'proj-002', name: 'Wind Farms' });
|
|
96
|
-
|
|
97
|
-
await hs.federate(geo, concept, 'projects', { direction: 'bidirectional' });
|
|
98
|
-
|
|
99
|
-
// Both directions should work
|
|
100
|
-
const geoProjects = await hs.getFederatedData(geo, 'projects');
|
|
101
|
-
const conceptProjects = await hs.getFederatedData(concept, 'projects');
|
|
102
|
-
|
|
103
|
-
expect(geoProjects.some(p => p.id === 'proj-002')).toBe(true);
|
|
104
|
-
expect(conceptProjects.some(p => p.id === 'proj-001')).toBe(true);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('should validate unified query interface for both dimensions', async () => {
|
|
108
|
-
const geographic = await hs.toHolon(37.7749, -122.4194, 9);
|
|
109
|
-
const noospheric = 'nostr://topic/javascript';
|
|
110
|
-
|
|
111
|
-
// Same API for both dimensions
|
|
112
|
-
await hs.write(geographic, 'posts', { id: 'post-1', text: 'Geo post' });
|
|
113
|
-
await hs.write(noospheric, 'posts', { id: 'post-2', text: 'Noo post' });
|
|
114
|
-
|
|
115
|
-
const geoData = await hs.read(geographic, 'posts');
|
|
116
|
-
const nooData = await hs.read(noospheric, 'posts');
|
|
117
|
-
|
|
118
|
-
expect(Array.isArray(geoData)).toBe(true);
|
|
119
|
-
expect(Array.isArray(nooData)).toBe(true);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('should validate cross-dimensional navigation enabled', async () => {
|
|
123
|
-
const sanFrancisco = await hs.toHolon(37.7749, -122.4194, 9);
|
|
124
|
-
const techConcept = 'concept://technology/web3';
|
|
125
|
-
const socialProtocol = 'nostr://topic/decentralization';
|
|
126
|
-
|
|
127
|
-
// Create triangle of federations
|
|
128
|
-
await hs.write(sanFrancisco, 'ideas', { id: 'idea-1', source: 'geo' });
|
|
129
|
-
await hs.write(techConcept, 'ideas', { id: 'idea-2', source: 'concept' });
|
|
130
|
-
await hs.write(socialProtocol, 'ideas', { id: 'idea-3', source: 'social' });
|
|
131
|
-
|
|
132
|
-
await hs.federate(sanFrancisco, techConcept, 'ideas');
|
|
133
|
-
await hs.federate(techConcept, socialProtocol, 'ideas');
|
|
134
|
-
|
|
135
|
-
// Navigation across dimensions should work
|
|
136
|
-
const sfIdeas = await hs.getFederatedData(sanFrancisco, 'ideas');
|
|
137
|
-
expect(sfIdeas.length).toBeGreaterThanOrEqual(2);
|
|
138
|
-
});
|
|
139
|
-
});
|
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import HoloSphere from '../../../src/index.js';
|
|
3
|
-
|
|
4
|
-
describe('Integration: Scenario 10 - Cross-Holosphere Capability Tokens', () => {
|
|
5
|
-
// Two distinct private keys for two separate holospheres
|
|
6
|
-
const privateKeyA = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef';
|
|
7
|
-
const privateKeyB = 'fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210';
|
|
8
|
-
|
|
9
|
-
let holosphereA;
|
|
10
|
-
let holosphereB;
|
|
11
|
-
let publicKeyA;
|
|
12
|
-
let publicKeyB;
|
|
13
|
-
let testHolon;
|
|
14
|
-
|
|
15
|
-
beforeEach(async () => {
|
|
16
|
-
// Create two independent holospheres with different private keys
|
|
17
|
-
holosphereA = new HoloSphere({
|
|
18
|
-
relays: [],
|
|
19
|
-
appName: 'scenario-10-a',
|
|
20
|
-
privateKey: privateKeyA,
|
|
21
|
-
logLevel: 'ERROR'
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
holosphereB = new HoloSphere({
|
|
25
|
-
relays: [],
|
|
26
|
-
appName: 'scenario-10-b',
|
|
27
|
-
privateKey: privateKeyB,
|
|
28
|
-
logLevel: 'ERROR'
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// Get public keys
|
|
32
|
-
publicKeyA = holosphereA.client.publicKey;
|
|
33
|
-
publicKeyB = holosphereB.client.publicKey;
|
|
34
|
-
|
|
35
|
-
// Create a test holon (H3 cell)
|
|
36
|
-
testHolon = await holosphereA.toHolon(37.7749, -122.4194, 9);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
afterEach(async () => {
|
|
40
|
-
// Clean up any subscriptions or connections
|
|
41
|
-
if (holosphereA && holosphereA.client) {
|
|
42
|
-
await holosphereA.client.close?.();
|
|
43
|
-
}
|
|
44
|
-
if (holosphereB && holosphereB.client) {
|
|
45
|
-
await holosphereB.client.close?.();
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should have different public keys for different private keys', () => {
|
|
50
|
-
expect(publicKeyA).toBeDefined();
|
|
51
|
-
expect(publicKeyB).toBeDefined();
|
|
52
|
-
expect(publicKeyA).not.toBe(publicKeyB);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('should NOT allow holosphere B to see data written by holosphere A without capability', async () => {
|
|
56
|
-
// Step 1: Holosphere A writes private data
|
|
57
|
-
const secretData = {
|
|
58
|
-
id: 'secret-001',
|
|
59
|
-
content: 'This is private data from Holosphere A',
|
|
60
|
-
_creator: publicKeyA
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
await holosphereA.write(testHolon, 'private-docs', secretData);
|
|
64
|
-
|
|
65
|
-
// Step 2: Verify holosphere A can read its own data
|
|
66
|
-
const dataFromA = await holosphereA.read(testHolon, 'private-docs', 'secret-001');
|
|
67
|
-
expect(dataFromA).toBeDefined();
|
|
68
|
-
expect(dataFromA.content).toBe('This is private data from Holosphere A');
|
|
69
|
-
|
|
70
|
-
// Step 3: Holosphere B tries to read the same data
|
|
71
|
-
// Since they use different appNames and no federation, B should NOT see A's data
|
|
72
|
-
const dataFromB = await holosphereB.read(testHolon, 'private-docs', 'secret-001');
|
|
73
|
-
|
|
74
|
-
// B should not be able to see A's data (different namespace/holosphere)
|
|
75
|
-
expect(dataFromB).toBeNull();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('should allow access after capability token is granted', async () => {
|
|
79
|
-
// Step 1: Holosphere A writes data
|
|
80
|
-
const sharedData = {
|
|
81
|
-
id: 'shared-001',
|
|
82
|
-
content: 'This data will be shared via capability token',
|
|
83
|
-
_creator: publicKeyA
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
await holosphereA.write(testHolon, 'shared-docs', sharedData);
|
|
87
|
-
|
|
88
|
-
// Step 2: Verify initial state - B cannot see data
|
|
89
|
-
const initialReadByB = await holosphereB.read(testHolon, 'shared-docs', 'shared-001');
|
|
90
|
-
expect(initialReadByB).toBeNull();
|
|
91
|
-
|
|
92
|
-
// Step 3: Holosphere A issues a READ capability token to Holosphere B
|
|
93
|
-
const readCapabilityToken = await holosphereA.issueCapability(
|
|
94
|
-
['read'],
|
|
95
|
-
{ holonId: testHolon, lensName: 'shared-docs' },
|
|
96
|
-
publicKeyB,
|
|
97
|
-
{
|
|
98
|
-
expiresIn: 3600000, // 1 hour
|
|
99
|
-
issuerKey: privateKeyA
|
|
100
|
-
}
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
expect(typeof readCapabilityToken).toBe('string');
|
|
104
|
-
|
|
105
|
-
// Step 4: Verify the capability token is valid
|
|
106
|
-
const isValidToken = await holosphereA.verifyCapability(
|
|
107
|
-
readCapabilityToken,
|
|
108
|
-
'read',
|
|
109
|
-
{ holonId: testHolon, lensName: 'shared-docs' }
|
|
110
|
-
);
|
|
111
|
-
expect(isValidToken).toBe(true);
|
|
112
|
-
|
|
113
|
-
// Step 5: Holosphere B can now verify the token
|
|
114
|
-
const bCanVerify = await holosphereB.verifyCapability(
|
|
115
|
-
readCapabilityToken,
|
|
116
|
-
'read',
|
|
117
|
-
{ holonId: testHolon, lensName: 'shared-docs' }
|
|
118
|
-
);
|
|
119
|
-
expect(bCanVerify).toBe(true);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('should allow write access with write capability token', async () => {
|
|
123
|
-
// Step 1: Holosphere A creates initial data
|
|
124
|
-
await holosphereA.write(testHolon, 'collaborative', {
|
|
125
|
-
id: 'collab-001',
|
|
126
|
-
content: 'Initial content by A',
|
|
127
|
-
_creator: publicKeyA
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// Step 2: Holosphere A issues WRITE capability token to B
|
|
131
|
-
const writeCapabilityToken = await holosphereA.issueCapability(
|
|
132
|
-
['write', 'read'],
|
|
133
|
-
{ holonId: testHolon, lensName: 'collaborative' },
|
|
134
|
-
publicKeyB,
|
|
135
|
-
{
|
|
136
|
-
expiresIn: 3600000,
|
|
137
|
-
issuerKey: privateKeyA
|
|
138
|
-
}
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
// Step 3: Verify B has write permission with the token
|
|
142
|
-
const hasWritePermission = await holosphereB.verifyCapability(
|
|
143
|
-
writeCapabilityToken,
|
|
144
|
-
'write',
|
|
145
|
-
{ holonId: testHolon, lensName: 'collaborative' }
|
|
146
|
-
);
|
|
147
|
-
expect(hasWritePermission).toBe(true);
|
|
148
|
-
|
|
149
|
-
// Step 4: B writes new data using the capability token
|
|
150
|
-
await holosphereB.write(testHolon, 'collaborative', {
|
|
151
|
-
id: 'collab-002',
|
|
152
|
-
content: 'Content added by B with capability',
|
|
153
|
-
_creator: publicKeyB
|
|
154
|
-
}, { capability: writeCapabilityToken });
|
|
155
|
-
|
|
156
|
-
// Step 5: A can verify B's contribution was written
|
|
157
|
-
const bContribution = await holosphereB.read(testHolon, 'collaborative', 'collab-002');
|
|
158
|
-
expect(bContribution).toBeDefined();
|
|
159
|
-
expect(bContribution.content).toBe('Content added by B with capability');
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('should reject operations when capability token does not include required permission', async () => {
|
|
163
|
-
// Step 1: A writes data
|
|
164
|
-
await holosphereA.write(testHolon, 'restricted', {
|
|
165
|
-
id: 'restricted-001',
|
|
166
|
-
content: 'Restricted data',
|
|
167
|
-
_creator: publicKeyA
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
// Step 2: A issues READ-ONLY capability token to B
|
|
171
|
-
const readOnlyToken = await holosphereA.issueCapability(
|
|
172
|
-
['read'], // Only read, no write or delete
|
|
173
|
-
{ holonId: testHolon, lensName: 'restricted' },
|
|
174
|
-
publicKeyB,
|
|
175
|
-
{
|
|
176
|
-
expiresIn: 3600000,
|
|
177
|
-
issuerKey: privateKeyA
|
|
178
|
-
}
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
// Step 3: Verify B has read but NOT write permission
|
|
182
|
-
const hasRead = await holosphereB.verifyCapability(
|
|
183
|
-
readOnlyToken,
|
|
184
|
-
'read',
|
|
185
|
-
{ holonId: testHolon, lensName: 'restricted' }
|
|
186
|
-
);
|
|
187
|
-
expect(hasRead).toBe(true);
|
|
188
|
-
|
|
189
|
-
const hasWrite = await holosphereB.verifyCapability(
|
|
190
|
-
readOnlyToken,
|
|
191
|
-
'write',
|
|
192
|
-
{ holonId: testHolon, lensName: 'restricted' }
|
|
193
|
-
);
|
|
194
|
-
expect(hasWrite).toBe(false);
|
|
195
|
-
|
|
196
|
-
const hasDelete = await holosphereB.verifyCapability(
|
|
197
|
-
readOnlyToken,
|
|
198
|
-
'delete',
|
|
199
|
-
{ holonId: testHolon, lensName: 'restricted' }
|
|
200
|
-
);
|
|
201
|
-
expect(hasDelete).toBe(false);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('should reject capability tokens for wrong scope (different lens)', async () => {
|
|
205
|
-
// Issue token for 'documents' lens
|
|
206
|
-
const documentsToken = await holosphereA.issueCapability(
|
|
207
|
-
['read', 'write', 'delete'],
|
|
208
|
-
{ holonId: testHolon, lensName: 'documents' },
|
|
209
|
-
publicKeyB,
|
|
210
|
-
{
|
|
211
|
-
expiresIn: 3600000,
|
|
212
|
-
issuerKey: privateKeyA
|
|
213
|
-
}
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
// Token should be valid for 'documents' lens
|
|
217
|
-
const validForDocuments = await holosphereB.verifyCapability(
|
|
218
|
-
documentsToken,
|
|
219
|
-
'read',
|
|
220
|
-
{ holonId: testHolon, lensName: 'documents' }
|
|
221
|
-
);
|
|
222
|
-
expect(validForDocuments).toBe(true);
|
|
223
|
-
|
|
224
|
-
// Token should be INVALID for 'photos' lens
|
|
225
|
-
const validForPhotos = await holosphereB.verifyCapability(
|
|
226
|
-
documentsToken,
|
|
227
|
-
'read',
|
|
228
|
-
{ holonId: testHolon, lensName: 'photos' }
|
|
229
|
-
);
|
|
230
|
-
expect(validForPhotos).toBe(false);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it('should reject expired capability tokens', async () => {
|
|
234
|
-
// Issue a token that is already expired
|
|
235
|
-
const expiredToken = await holosphereA.issueCapability(
|
|
236
|
-
['read', 'write'],
|
|
237
|
-
{ holonId: testHolon, lensName: 'expired-test' },
|
|
238
|
-
publicKeyB,
|
|
239
|
-
{
|
|
240
|
-
expiresIn: -1000, // Already expired
|
|
241
|
-
issuerKey: privateKeyA
|
|
242
|
-
}
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
// Expired token should not be valid
|
|
246
|
-
const isValid = await holosphereB.verifyCapability(
|
|
247
|
-
expiredToken,
|
|
248
|
-
'read',
|
|
249
|
-
{ holonId: testHolon, lensName: 'expired-test' }
|
|
250
|
-
);
|
|
251
|
-
expect(isValid).toBe(false);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it('should allow delete access with delete capability token', async () => {
|
|
255
|
-
// Step 1: A creates data
|
|
256
|
-
await holosphereA.write(testHolon, 'deletable', {
|
|
257
|
-
id: 'to-delete-001',
|
|
258
|
-
content: 'This will be deleted by B',
|
|
259
|
-
_creator: publicKeyA
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
// Step 2: Verify data exists
|
|
263
|
-
const exists = await holosphereA.read(testHolon, 'deletable', 'to-delete-001');
|
|
264
|
-
expect(exists).toBeDefined();
|
|
265
|
-
|
|
266
|
-
// Step 3: B tries to delete without token - should fail
|
|
267
|
-
try {
|
|
268
|
-
await holosphereB.delete(testHolon, 'deletable', 'to-delete-001');
|
|
269
|
-
// If we reach here, the test should fail
|
|
270
|
-
expect(true).toBe(false);
|
|
271
|
-
} catch (err) {
|
|
272
|
-
// Expected: authorization error or no access
|
|
273
|
-
expect(err).toBeDefined();
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Step 4: A issues DELETE capability token to B
|
|
277
|
-
const deleteToken = await holosphereA.issueCapability(
|
|
278
|
-
['delete'],
|
|
279
|
-
{ holonId: testHolon, lensName: 'deletable' },
|
|
280
|
-
publicKeyB,
|
|
281
|
-
{
|
|
282
|
-
expiresIn: 3600000,
|
|
283
|
-
issuerKey: privateKeyA
|
|
284
|
-
}
|
|
285
|
-
);
|
|
286
|
-
|
|
287
|
-
// Step 5: Verify B has delete permission
|
|
288
|
-
const hasDeletePermission = await holosphereB.verifyCapability(
|
|
289
|
-
deleteToken,
|
|
290
|
-
'delete',
|
|
291
|
-
{ holonId: testHolon, lensName: 'deletable' }
|
|
292
|
-
);
|
|
293
|
-
expect(hasDeletePermission).toBe(true);
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
it('complete workflow: isolation -> capability grant -> access', async () => {
|
|
297
|
-
// === PHASE 1: ISOLATION ===
|
|
298
|
-
// A writes confidential data
|
|
299
|
-
const confidentialData = {
|
|
300
|
-
id: 'confidential-001',
|
|
301
|
-
title: 'Secret Project',
|
|
302
|
-
content: 'Top secret information only for authorized users',
|
|
303
|
-
_creator: publicKeyA
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
await holosphereA.write(testHolon, 'projects', confidentialData);
|
|
307
|
-
|
|
308
|
-
// A can see their own data
|
|
309
|
-
const aCanSee = await holosphereA.read(testHolon, 'projects', 'confidential-001');
|
|
310
|
-
expect(aCanSee).toBeDefined();
|
|
311
|
-
expect(aCanSee.title).toBe('Secret Project');
|
|
312
|
-
|
|
313
|
-
// B cannot see A's data (isolated)
|
|
314
|
-
const bCannotSee = await holosphereB.read(testHolon, 'projects', 'confidential-001');
|
|
315
|
-
expect(bCannotSee).toBeNull();
|
|
316
|
-
|
|
317
|
-
// === PHASE 2: CAPABILITY GRANT ===
|
|
318
|
-
// A decides to grant B read access via public key
|
|
319
|
-
const capabilityToken = await holosphereA.issueCapability(
|
|
320
|
-
['read'],
|
|
321
|
-
{ holonId: testHolon, lensName: 'projects' },
|
|
322
|
-
publicKeyB, // Grant to B's public key
|
|
323
|
-
{
|
|
324
|
-
expiresIn: 7200000, // 2 hours
|
|
325
|
-
issuerKey: privateKeyA
|
|
326
|
-
}
|
|
327
|
-
);
|
|
328
|
-
|
|
329
|
-
expect(capabilityToken).toBeDefined();
|
|
330
|
-
expect(typeof capabilityToken).toBe('string');
|
|
331
|
-
|
|
332
|
-
// === PHASE 3: VERIFIED ACCESS ===
|
|
333
|
-
// B can now verify they have read access
|
|
334
|
-
const bHasReadAccess = await holosphereB.verifyCapability(
|
|
335
|
-
capabilityToken,
|
|
336
|
-
'read',
|
|
337
|
-
{ holonId: testHolon, lensName: 'projects' }
|
|
338
|
-
);
|
|
339
|
-
expect(bHasReadAccess).toBe(true);
|
|
340
|
-
|
|
341
|
-
// B still cannot write (only read permission was granted)
|
|
342
|
-
const bHasWriteAccess = await holosphereB.verifyCapability(
|
|
343
|
-
capabilityToken,
|
|
344
|
-
'write',
|
|
345
|
-
{ holonId: testHolon, lensName: 'projects' }
|
|
346
|
-
);
|
|
347
|
-
expect(bHasWriteAccess).toBe(false);
|
|
348
|
-
|
|
349
|
-
// Token is properly scoped - cannot access other lenses
|
|
350
|
-
const bHasAccessToOtherLens = await holosphereB.verifyCapability(
|
|
351
|
-
capabilityToken,
|
|
352
|
-
'read',
|
|
353
|
-
{ holonId: testHolon, lensName: 'other-lens' }
|
|
354
|
-
);
|
|
355
|
-
expect(bHasAccessToOtherLens).toBe(false);
|
|
356
|
-
});
|
|
357
|
-
});
|