holosphere 1.1.20 → 2.0.0-alpha1

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 (147) hide show
  1. package/.env.example +36 -0
  2. package/.eslintrc.json +16 -0
  3. package/.prettierrc.json +7 -0
  4. package/LICENSE +162 -38
  5. package/README.md +483 -367
  6. package/bin/holosphere-activitypub.js +158 -0
  7. package/cleanup-test-data.js +204 -0
  8. package/examples/demo.html +1333 -0
  9. package/examples/example-bot.js +197 -0
  10. package/package.json +47 -87
  11. package/scripts/check-bundle-size.js +54 -0
  12. package/scripts/check-quest-ids.js +77 -0
  13. package/scripts/import-holons.js +578 -0
  14. package/scripts/publish-to-relay.js +101 -0
  15. package/scripts/read-example.js +186 -0
  16. package/scripts/relay-diagnostic.js +59 -0
  17. package/scripts/relay-example.js +179 -0
  18. package/scripts/resync-to-relay.js +245 -0
  19. package/scripts/revert-import.js +196 -0
  20. package/scripts/test-hybrid-mode.js +108 -0
  21. package/scripts/test-local-storage.js +63 -0
  22. package/scripts/test-nostr-direct.js +55 -0
  23. package/scripts/test-read-data.js +45 -0
  24. package/scripts/test-write-read.js +63 -0
  25. package/scripts/verify-import.js +95 -0
  26. package/scripts/verify-relay-data.js +139 -0
  27. package/src/ai/aggregation.js +319 -0
  28. package/src/ai/breakdown.js +511 -0
  29. package/src/ai/classifier.js +217 -0
  30. package/src/ai/council.js +228 -0
  31. package/src/ai/embeddings.js +279 -0
  32. package/src/ai/federation-ai.js +324 -0
  33. package/src/ai/h3-ai.js +955 -0
  34. package/src/ai/index.js +112 -0
  35. package/src/ai/json-ops.js +225 -0
  36. package/src/ai/llm-service.js +205 -0
  37. package/src/ai/nl-query.js +223 -0
  38. package/src/ai/relationships.js +353 -0
  39. package/src/ai/schema-extractor.js +218 -0
  40. package/src/ai/spatial.js +293 -0
  41. package/src/ai/tts.js +194 -0
  42. package/src/content/social-protocols.js +168 -0
  43. package/src/core/holosphere.js +273 -0
  44. package/src/crypto/secp256k1.js +259 -0
  45. package/src/federation/discovery.js +334 -0
  46. package/src/federation/hologram.js +1042 -0
  47. package/src/federation/registry.js +386 -0
  48. package/src/hierarchical/upcast.js +110 -0
  49. package/src/index.js +2669 -0
  50. package/src/schema/validator.js +91 -0
  51. package/src/spatial/h3-operations.js +110 -0
  52. package/src/storage/backend-factory.js +125 -0
  53. package/src/storage/backend-interface.js +142 -0
  54. package/src/storage/backends/activitypub/server.js +653 -0
  55. package/src/storage/backends/activitypub-backend.js +272 -0
  56. package/src/storage/backends/gundb-backend.js +233 -0
  57. package/src/storage/backends/nostr-backend.js +136 -0
  58. package/src/storage/filesystem-storage-browser.js +41 -0
  59. package/src/storage/filesystem-storage.js +138 -0
  60. package/src/storage/global-tables.js +81 -0
  61. package/src/storage/gun-async.js +281 -0
  62. package/src/storage/gun-wrapper.js +221 -0
  63. package/src/storage/indexeddb-storage.js +122 -0
  64. package/src/storage/key-storage-simple.js +76 -0
  65. package/src/storage/key-storage.js +136 -0
  66. package/src/storage/memory-storage.js +59 -0
  67. package/src/storage/migration.js +338 -0
  68. package/src/storage/nostr-async.js +811 -0
  69. package/src/storage/nostr-client.js +939 -0
  70. package/src/storage/nostr-wrapper.js +211 -0
  71. package/src/storage/outbox-queue.js +208 -0
  72. package/src/storage/persistent-storage.js +109 -0
  73. package/src/storage/sync-service.js +164 -0
  74. package/src/subscriptions/manager.js +142 -0
  75. package/test-ai-real-api.js +202 -0
  76. package/tests/unit/ai/aggregation.test.js +295 -0
  77. package/tests/unit/ai/breakdown.test.js +446 -0
  78. package/tests/unit/ai/classifier.test.js +294 -0
  79. package/tests/unit/ai/council.test.js +262 -0
  80. package/tests/unit/ai/embeddings.test.js +384 -0
  81. package/tests/unit/ai/federation-ai.test.js +344 -0
  82. package/tests/unit/ai/h3-ai.test.js +458 -0
  83. package/tests/unit/ai/index.test.js +304 -0
  84. package/tests/unit/ai/json-ops.test.js +307 -0
  85. package/tests/unit/ai/llm-service.test.js +390 -0
  86. package/tests/unit/ai/nl-query.test.js +383 -0
  87. package/tests/unit/ai/relationships.test.js +311 -0
  88. package/tests/unit/ai/schema-extractor.test.js +384 -0
  89. package/tests/unit/ai/spatial.test.js +279 -0
  90. package/tests/unit/ai/tts.test.js +279 -0
  91. package/tests/unit/content.test.js +332 -0
  92. package/tests/unit/contract/core.test.js +88 -0
  93. package/tests/unit/contract/crypto.test.js +198 -0
  94. package/tests/unit/contract/data.test.js +223 -0
  95. package/tests/unit/contract/federation.test.js +181 -0
  96. package/tests/unit/contract/hierarchical.test.js +113 -0
  97. package/tests/unit/contract/schema.test.js +114 -0
  98. package/tests/unit/contract/social.test.js +217 -0
  99. package/tests/unit/contract/spatial.test.js +110 -0
  100. package/tests/unit/contract/subscriptions.test.js +128 -0
  101. package/tests/unit/contract/utils.test.js +159 -0
  102. package/tests/unit/core.test.js +152 -0
  103. package/tests/unit/crypto.test.js +328 -0
  104. package/tests/unit/federation.test.js +234 -0
  105. package/tests/unit/gun-async.test.js +252 -0
  106. package/tests/unit/hierarchical.test.js +399 -0
  107. package/tests/unit/integration/scenario-01-geographic-storage.test.js +74 -0
  108. package/tests/unit/integration/scenario-02-federation.test.js +76 -0
  109. package/tests/unit/integration/scenario-03-subscriptions.test.js +102 -0
  110. package/tests/unit/integration/scenario-04-validation.test.js +129 -0
  111. package/tests/unit/integration/scenario-05-hierarchy.test.js +125 -0
  112. package/tests/unit/integration/scenario-06-social.test.js +135 -0
  113. package/tests/unit/integration/scenario-07-persistence.test.js +130 -0
  114. package/tests/unit/integration/scenario-08-authorization.test.js +161 -0
  115. package/tests/unit/integration/scenario-09-cross-dimensional.test.js +139 -0
  116. package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +357 -0
  117. package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +410 -0
  118. package/tests/unit/integration/scenario-12-capability-federated-read.test.js +719 -0
  119. package/tests/unit/performance/benchmark.test.js +85 -0
  120. package/tests/unit/schema.test.js +213 -0
  121. package/tests/unit/spatial.test.js +158 -0
  122. package/tests/unit/storage.test.js +195 -0
  123. package/tests/unit/subscriptions.test.js +328 -0
  124. package/tests/unit/test-data-permanence-debug.js +197 -0
  125. package/tests/unit/test-data-permanence.js +340 -0
  126. package/tests/unit/test-key-persistence-fixed.js +148 -0
  127. package/tests/unit/test-key-persistence.js +172 -0
  128. package/tests/unit/test-relay-permanence.js +376 -0
  129. package/tests/unit/test-second-node.js +95 -0
  130. package/tests/unit/test-simple-write.js +89 -0
  131. package/vite.config.js +49 -0
  132. package/vitest.config.js +20 -0
  133. package/FEDERATION.md +0 -213
  134. package/compute.js +0 -298
  135. package/content.js +0 -980
  136. package/federation.js +0 -1234
  137. package/global.js +0 -736
  138. package/hexlib.js +0 -335
  139. package/hologram.js +0 -183
  140. package/holosphere-bundle.esm.js +0 -33256
  141. package/holosphere-bundle.js +0 -33287
  142. package/holosphere-bundle.min.js +0 -39
  143. package/holosphere.d.ts +0 -601
  144. package/holosphere.js +0 -719
  145. package/node.js +0 -246
  146. package/schema.js +0 -139
  147. package/utils.js +0 -302
@@ -0,0 +1,129 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import HoloSphere from '../../../src/index.js';
3
+
4
+ describe('Integration: Scenario 4 - Schema Validation (Strict and Permissive)', () => {
5
+ let hs;
6
+
7
+ beforeEach(() => {
8
+ hs = new HoloSphere({ relays: [], appName: 'scenario-04' });
9
+ });
10
+
11
+ it('should complete schema validation workflow', async () => {
12
+ // Step 1: Define schema for temperature data
13
+ const temperatureSchema = {
14
+ type: 'object',
15
+ properties: {
16
+ id: { type: 'string' },
17
+ celsius: { type: 'number', minimum: -50, maximum: 60 },
18
+ timestamp: { type: 'number' }
19
+ },
20
+ required: ['id', 'celsius']
21
+ };
22
+
23
+ const holon = await hs.toHolon(37.7749, -122.4194, 9);
24
+
25
+ // Step 2: Set schema in permissive mode (default)
26
+ await hs.setSchema('temperature', temperatureSchema, false);
27
+
28
+ // Step 3: Write invalid data - succeeds with warning logged
29
+ const permissiveResult = await hs.write(holon, 'temperature', {
30
+ id: 'bad-sensor',
31
+ celsius: 'very hot' // Invalid: string instead of number
32
+ });
33
+ expect(permissiveResult).toBe(true);
34
+
35
+ // Step 4: Enable strict mode
36
+ await hs.setSchema('temperature', temperatureSchema, true);
37
+
38
+ // Step 5: Write invalid data - throws ValidationError
39
+ await expect(
40
+ hs.write(holon, 'temperature', {
41
+ id: 'bad-sensor-2',
42
+ celsius: 'freezing'
43
+ }, { strict: true })
44
+ ).rejects.toThrow('ValidationError');
45
+
46
+ // Step 6: Write valid data - succeeds
47
+ const validResult = await hs.write(holon, 'temperature', {
48
+ id: 'good-sensor',
49
+ celsius: 22.5,
50
+ timestamp: Date.now()
51
+ }, { strict: true });
52
+ expect(validResult).toBe(true);
53
+ });
54
+
55
+ it('should validate schemas support URI references', async () => {
56
+ // URI schemas can be set and will skip validation
57
+ await expect(
58
+ hs.setSchema('test', 'test://schemas/valid.json')
59
+ ).resolves.toBeUndefined();
60
+ });
61
+
62
+ it('should validate permissive mode logs but allows invalid data', async () => {
63
+ const holon = await hs.toHolon(37.7749, -122.4194, 9);
64
+ const testSchema = {
65
+ type: 'object',
66
+ properties: {
67
+ id: { type: 'string' },
68
+ value: { type: 'number' }
69
+ },
70
+ required: ['id', 'value']
71
+ };
72
+ await hs.setSchema('test', testSchema, false);
73
+
74
+ const result = await hs.write(holon, 'test', {
75
+ id: 'invalid',
76
+ value: 'not-a-number'
77
+ });
78
+
79
+ expect(result).toBe(true);
80
+ });
81
+
82
+ it('should validate strict mode blocks invalid data with ValidationError', async () => {
83
+ const holon = await hs.toHolon(37.7749, -122.4194, 9);
84
+ const strictSchema = {
85
+ type: 'object',
86
+ properties: {
87
+ id: { type: 'string' },
88
+ value: { type: 'number' }
89
+ },
90
+ required: ['id', 'value']
91
+ };
92
+ await hs.setSchema('strict-test', strictSchema, true);
93
+
94
+ await expect(
95
+ hs.write(holon, 'strict-test', {
96
+ id: 'bad',
97
+ value: 'invalid'
98
+ }, { strict: true })
99
+ ).rejects.toThrow('ValidationError');
100
+ });
101
+
102
+ it('should validate valid data succeeds in both modes', async () => {
103
+ const holon = await hs.toHolon(37.7749, -122.4194, 9);
104
+ const tempSchema = {
105
+ type: 'object',
106
+ properties: {
107
+ id: { type: 'string' },
108
+ celsius: { type: 'number' },
109
+ timestamp: { type: 'number' }
110
+ },
111
+ required: ['id', 'celsius']
112
+ };
113
+ const validData = {
114
+ id: 'sensor-001',
115
+ celsius: 25.5,
116
+ timestamp: Date.now()
117
+ };
118
+
119
+ // Permissive mode
120
+ await hs.setSchema('temp1', tempSchema, false);
121
+ const permissive = await hs.write(holon, 'temp1', validData);
122
+ expect(permissive).toBe(true);
123
+
124
+ // Strict mode
125
+ await hs.setSchema('temp2', tempSchema, true);
126
+ const strict = await hs.write(holon, 'temp2', validData, { strict: true });
127
+ expect(strict).toBe(true);
128
+ });
129
+ });
@@ -0,0 +1,125 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import HoloSphere from '../../../src/index.js';
3
+
4
+ describe('Integration: Scenario 5 - Multi-Scale Hierarchical Queries', () => {
5
+ let hs;
6
+
7
+ beforeEach(() => {
8
+ hs = new HoloSphere({ relays: [], appName: 'scenario-05' });
9
+ });
10
+
11
+ it('should complete multi-scale hierarchical query workflow', async () => {
12
+ // Step 1: Create data at high resolution (local street level, res 12)
13
+ const localHolon = await hs.toHolon(37.7749, -122.4194, 12);
14
+ await hs.write(localHolon, 'events', {
15
+ id: 'meetup-001',
16
+ name: 'JavaScript Meetup',
17
+ attendees: 25
18
+ });
19
+
20
+ // Step 2: Upcast to parent hierarchy (propagate to neighborhood, city, region)
21
+ const upcastSuccess = await hs.upcast(localHolon, 'events', 'meetup-001', {
22
+ maxLevel: 3
23
+ });
24
+ expect(upcastSuccess).toBe(true);
25
+
26
+ // Step 3: Query at different scales
27
+ const parents = await hs.getParents(localHolon);
28
+ expect(parents.length).toBeGreaterThan(0);
29
+
30
+ // Find neighborhood level (estimate resolution 9)
31
+ const neighborhood = parents.find(p => {
32
+ // Get resolution somehow or just use first parent
33
+ return true;
34
+ });
35
+
36
+ if (neighborhood) {
37
+ const neighborhoodEvents = await hs.read(neighborhood, 'events');
38
+ expect(Array.isArray(neighborhoodEvents)).toBe(true);
39
+ // Should include upcast data
40
+ }
41
+
42
+ // City level (lower resolution parent)
43
+ if (parents.length > 1) {
44
+ const city = parents[1];
45
+ const cityEvents = await hs.read(city, 'events');
46
+ expect(Array.isArray(cityEvents)).toBe(true);
47
+ }
48
+
49
+ // Regional level
50
+ if (parents.length > 2) {
51
+ const region = parents[2];
52
+ const regionEvents = await hs.read(region, 'events');
53
+ expect(Array.isArray(regionEvents)).toBe(true);
54
+ }
55
+ });
56
+
57
+ it('should validate hierarchical parent-child relationships maintained', async () => {
58
+ const child = await hs.toHolon(37.7749, -122.4194, 10);
59
+ const parents = await hs.getParents(child);
60
+
61
+ expect(parents.length).toBeGreaterThan(0);
62
+
63
+ // Each parent should be valid H3
64
+ parents.forEach(parent => {
65
+ expect(hs.isValidH3(parent)).toBe(true);
66
+ });
67
+ });
68
+
69
+ it('should validate data propagates up hierarchy (upcast)', async () => {
70
+ const local = await hs.toHolon(40.7128, -74.0060, 11);
71
+
72
+ await hs.write(local, 'alerts', {
73
+ id: 'alert-001',
74
+ type: 'weather',
75
+ severity: 'high'
76
+ });
77
+
78
+ await hs.upcast(local, 'alerts', 'alert-001', { maxLevel: 2 });
79
+
80
+ const parents = await hs.getParents(local);
81
+ if (parents.length > 0) {
82
+ const parentData = await hs.read(parents[0], 'alerts');
83
+ expect(Array.isArray(parentData)).toBe(true);
84
+ }
85
+ });
86
+
87
+ it('should validate multi-scale queries work at different resolutions', async () => {
88
+ // Create data at resolution 12
89
+ const res12 = await hs.toHolon(37.7749, -122.4194, 12);
90
+ await hs.write(res12, 'data', { id: 'item-1', value: 'high-res' });
91
+
92
+ // Get data at resolution 9 (parent)
93
+ const parents = await hs.getParents(res12);
94
+ const res9 = parents.find(p => hs.isValidH3(p));
95
+
96
+ if (res9) {
97
+ // Should be able to query at this resolution
98
+ const data = await hs.read(res9, 'data');
99
+ expect(Array.isArray(data)).toBe(true);
100
+ }
101
+
102
+ // Resolution 7 (grandparent)
103
+ if (parents.length > 1) {
104
+ const res7 = parents[1];
105
+ const data = await hs.read(res7, 'data');
106
+ expect(Array.isArray(data)).toBe(true);
107
+ }
108
+ });
109
+
110
+ it('should validate upcast with different operation modes', async () => {
111
+ const holon = await hs.toHolon(37.7749, -122.4194, 10);
112
+
113
+ // Summarize mode
114
+ await hs.write(holon, 'counts', { id: 'count-1', value: 5 });
115
+ await hs.upcast(holon, 'counts', 'count-1', { operation: 'summarize' });
116
+
117
+ // Aggregate mode
118
+ await hs.write(holon, 'totals', { id: 'total-1', amount: 100 });
119
+ await hs.upcast(holon, 'totals', 'total-1', { operation: 'aggregate' });
120
+
121
+ // Concatenate mode (default)
122
+ await hs.write(holon, 'lists', { id: 'list-1', items: [1, 2, 3] });
123
+ await hs.upcast(holon, 'lists', 'list-1', { operation: 'concatenate' });
124
+ });
125
+ });
@@ -0,0 +1,135 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import HoloSphere from '../../../src/index.js';
3
+
4
+ describe('Integration: Scenario 6 - Cross-Protocol Social Content', () => {
5
+ let hs;
6
+
7
+ beforeEach(() => {
8
+ hs = new HoloSphere({ relays: [], appName: 'scenario-06' });
9
+ });
10
+
11
+ it('should complete cross-protocol social content workflow', async () => {
12
+ // Step 1: Publish Nostr event to geographic holon
13
+ const holon = await hs.toHolon(37.7749, -122.4194, 9);
14
+
15
+ const nostrEvent = {
16
+ kind: 1, // Text note
17
+ content: 'Building with HoloSphere!',
18
+ tags: [['t', 'holosphere'], ['t', 'geospatial']],
19
+ created_at: Math.floor(Date.now() / 1000)
20
+ };
21
+
22
+ const nostrSuccess = await hs.publishNostr(nostrEvent, holon);
23
+ expect(nostrSuccess).toBe(true);
24
+
25
+ // Step 2: Publish ActivityPub object to noospheric holon
26
+ const apObject = {
27
+ '@context': 'https://www.w3.org/ns/activitystreams',
28
+ type: 'Note',
29
+ content: 'Exploring federated social on HoloSphere',
30
+ published: new Date().toISOString()
31
+ };
32
+
33
+ const apSuccess = await hs.publishActivityPub(
34
+ apObject,
35
+ 'ap://community/holosphere-devs'
36
+ );
37
+ expect(apSuccess).toBe(true);
38
+
39
+ // Step 3: Query all social content (unified interface)
40
+ const allSocial = await hs.querySocial(holon, { protocol: 'all' });
41
+ expect(Array.isArray(allSocial)).toBe(true);
42
+ expect(allSocial.length).toBeGreaterThanOrEqual(1);
43
+
44
+ // Step 4: Filter by protocol
45
+ const nostrOnly = await hs.querySocial(holon, { protocol: 'nostr' });
46
+ expect(Array.isArray(nostrOnly)).toBe(true);
47
+ expect(nostrOnly.length).toBeGreaterThanOrEqual(1);
48
+
49
+ // Step 5: Verify cryptographic signature
50
+ if (nostrOnly.length > 0) {
51
+ const event = nostrOnly[0];
52
+ expect(event).toHaveProperty('sig');
53
+ expect(event).toHaveProperty('pubkey');
54
+
55
+ // Use verifyNostrEvent for proper Nostr signature verification
56
+ const isValid = await hs.verifyNostrEvent(event);
57
+ expect(isValid).toBe(true);
58
+ }
59
+ });
60
+
61
+ it('should validate Nostr and ActivityPub unified under common interface', async () => {
62
+ const holon = await hs.toHolon(40.7128, -74.0060, 9);
63
+
64
+ // Publish both types
65
+ await hs.publishNostr({
66
+ kind: 1,
67
+ content: 'Nostr post',
68
+ tags: [],
69
+ created_at: Math.floor(Date.now() / 1000)
70
+ }, holon);
71
+
72
+ await hs.publishActivityPub({
73
+ '@context': 'https://www.w3.org/ns/activitystreams',
74
+ type: 'Note',
75
+ content: 'ActivityPub note'
76
+ }, holon);
77
+
78
+ // Query should return both
79
+ const all = await hs.querySocial(holon, { protocol: 'all' });
80
+ expect(all.length).toBeGreaterThanOrEqual(2);
81
+ });
82
+
83
+ it('should validate protocol-specific content validation', async () => {
84
+ const holon = await hs.toHolon(37.7749, -122.4194, 9);
85
+
86
+ // Invalid Nostr event should be rejected
87
+ await expect(
88
+ hs.publishNostr({ invalid: 'event' }, holon)
89
+ ).rejects.toThrow('ValidationError');
90
+
91
+ // Invalid ActivityPub object should be rejected
92
+ await expect(
93
+ hs.publishActivityPub({ invalid: 'object' }, holon)
94
+ ).rejects.toThrow('ValidationError');
95
+ });
96
+
97
+ it('should validate cryptographic signature verification', async () => {
98
+ const holon = await hs.toHolon(37.7749, -122.4194, 9);
99
+
100
+ const event = {
101
+ kind: 1,
102
+ content: 'Signed content',
103
+ tags: [],
104
+ created_at: Math.floor(Date.now() / 1000)
105
+ };
106
+
107
+ await hs.publishNostr(event, holon);
108
+
109
+ const published = await hs.querySocial(holon, { protocol: 'nostr' });
110
+ if (published.length > 0) {
111
+ const signedEvent = published[0];
112
+ expect(signedEvent).toHaveProperty('sig');
113
+ expect(signedEvent).toHaveProperty('pubkey');
114
+ }
115
+ });
116
+
117
+ it('should validate filter by protocol and access level', async () => {
118
+ const holon = await hs.toHolon(37.7749, -122.4194, 9);
119
+
120
+ await hs.publishNostr({
121
+ kind: 1,
122
+ content: 'Public post',
123
+ tags: [],
124
+ created_at: Math.floor(Date.now() / 1000)
125
+ }, holon);
126
+
127
+ // Filter by protocol
128
+ const nostrPosts = await hs.querySocial(holon, { protocol: 'nostr' });
129
+ expect(Array.isArray(nostrPosts)).toBe(true);
130
+
131
+ // Filter by access level
132
+ const publicPosts = await hs.querySocial(holon, { accessLevel: 'public' });
133
+ expect(Array.isArray(publicPosts)).toBe(true);
134
+ });
135
+ });
@@ -0,0 +1,130 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import HoloSphere from '../../../src/index.js';
3
+
4
+ describe('Integration: Scenario 7 - Offline Persistence and Recovery', () => {
5
+ let hs;
6
+
7
+ beforeEach(() => {
8
+ hs = new HoloSphere({
9
+ appName: 'scenario-07',
10
+ radisk: true // Enabled by default
11
+ });
12
+ });
13
+
14
+ it('should complete offline persistence and recovery workflow', async () => {
15
+ const holon = await hs.toHolon(37.7749, -122.4194, 9);
16
+
17
+ // Step 1: Write data to persistent storage
18
+ await hs.write(holon, 'notes', {
19
+ id: 'note-001',
20
+ text: 'This persists across sessions'
21
+ });
22
+
23
+ // Verify write succeeded
24
+ const written = await hs.read(holon, 'notes', 'note-001');
25
+ expect(written.text).toBe('This persists across sessions');
26
+
27
+ // Step 2: Simulate app restart (create new instance)
28
+ const hs2 = new HoloSphere({
29
+ appName: 'scenario-07',
30
+ radisk: true
31
+ });
32
+
33
+ // Step 3: Read previously written data - recovered from disk
34
+ const recovered = await hs2.read(holon, 'notes', 'note-001');
35
+ expect(recovered).not.toBe(null);
36
+ expect(recovered.text).toBe('This persists across sessions');
37
+
38
+ // Step 4: Offline operation - write without network
39
+ await hs2.write(holon, 'notes', {
40
+ id: 'note-002',
41
+ text: 'Written while offline'
42
+ });
43
+
44
+ // Step 5: Data available immediately from local storage
45
+ const offline = await hs2.read(holon, 'notes', 'note-002');
46
+ expect(offline).not.toBe(null);
47
+ expect(offline.text).toBe('Written while offline');
48
+ });
49
+
50
+ it('should validate radisk persistence enabled by default', () => {
51
+ const hs = new HoloSphere({ relays: [], appName: 'test-radisk' });
52
+ // Should initialize without error with radisk enabled
53
+ expect(hs).toBeDefined();
54
+ });
55
+
56
+ it.skip('should validate data persists across app restarts (requires persistent storage)', async () => {
57
+ const appName = 'persistence-test-' + Date.now();
58
+ const holon = await hs.toHolon(40.7128, -74.0060, 9);
59
+
60
+ // First instance
61
+ const hs1 = new HoloSphere({ appName, radisk: true });
62
+ await hs1.write(holon, 'persistent', {
63
+ id: 'persistent-001',
64
+ value: 'must persist'
65
+ });
66
+
67
+ // Second instance (simulated restart)
68
+ const hs2 = new HoloSphere({ appName, radisk: true });
69
+ const recovered = await hs2.read(holon, 'persistent', 'persistent-001');
70
+
71
+ expect(recovered).not.toBe(null);
72
+ expect(recovered.value).toBe('must persist');
73
+ });
74
+
75
+ it('should validate offline writes succeed and are recoverable', async () => {
76
+ const holon = await hs.toHolon(37.7749, -122.4194, 9);
77
+
78
+ // Write while potentially offline (radisk handles this)
79
+ const success = await hs.write(holon, 'offline-data', {
80
+ id: 'offline-001',
81
+ timestamp: Date.now(),
82
+ status: 'offline-write'
83
+ });
84
+
85
+ expect(success).toBe(true);
86
+
87
+ // Verify immediately available
88
+ const data = await hs.read(holon, 'offline-data', 'offline-001');
89
+ expect(data).not.toBe(null);
90
+ expect(data.status).toBe('offline-write');
91
+ });
92
+
93
+ it.skip('should validate no data loss during crashes (requires persistent storage)', async () => {
94
+ const appName = 'crash-test-' + Date.now();
95
+ const holon = await hs.toHolon(37.7749, -122.4194, 9);
96
+
97
+ const hs1 = new HoloSphere({ appName, radisk: true });
98
+
99
+ // Write multiple items
100
+ await hs1.write(holon, 'crash-test', { id: 'item-1', data: 'A' });
101
+ await hs1.write(holon, 'crash-test', { id: 'item-2', data: 'B' });
102
+ await hs1.write(holon, 'crash-test', { id: 'item-3', data: 'C' });
103
+
104
+ // Simulate crash and restart
105
+ const hs2 = new HoloSphere({ appName, radisk: true });
106
+
107
+ // All data should be recoverable
108
+ const item1 = await hs2.read(holon, 'crash-test', 'item-1');
109
+ const item2 = await hs2.read(holon, 'crash-test', 'item-2');
110
+ const item3 = await hs2.read(holon, 'crash-test', 'item-3');
111
+
112
+ expect(item1?.data).toBe('A');
113
+ expect(item2?.data).toBe('B');
114
+ expect(item3?.data).toBe('C');
115
+ });
116
+
117
+ it.skip('should validate persistence works with updates (requires persistent storage)', async () => {
118
+ const appName = 'update-persist-' + Date.now();
119
+ const holon = await hs.toHolon(37.7749, -122.4194, 9);
120
+
121
+ const hs1 = new HoloSphere({ appName, radisk: true });
122
+ await hs1.write(holon, 'updatable', { id: 'item-1', version: 1 });
123
+ await hs1.update(holon, 'updatable', 'item-1', { version: 2 });
124
+
125
+ const hs2 = new HoloSphere({ appName, radisk: true });
126
+ const recovered = await hs2.read(holon, 'updatable', 'item-1');
127
+
128
+ expect(recovered.version).toBe(2); // Updated version persisted
129
+ });
130
+ });
@@ -0,0 +1,161 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import HoloSphere from '../../../src/index.js';
3
+
4
+ describe('Integration: Scenario 8 - Authorization with Capability Tokens', () => {
5
+ let hs;
6
+ let userAPrivateKey;
7
+ let userAPublicKey;
8
+ let userBPublicKey;
9
+
10
+ beforeEach(() => {
11
+ hs = new HoloSphere({ relays: [], appName: 'scenario-08' });
12
+
13
+ // Mock keys (in real implementation, would use proper key generation)
14
+ userAPrivateKey = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef';
15
+ userAPublicKey = '04' + '0123456789abcdef'.repeat(8);
16
+ userBPublicKey = '04' + 'fedcba9876543210'.repeat(8);
17
+ });
18
+
19
+ it('should complete authorization with capability tokens workflow', async () => {
20
+ const holon = await hs.toHolon(37.7749, -122.4194, 9);
21
+
22
+ // Step 1: User A creates data
23
+ await hs.write(holon, 'documents', {
24
+ id: 'doc-001',
25
+ title: 'User A Document',
26
+ _creator: userAPublicKey
27
+ });
28
+
29
+ // Step 2: User B attempts to delete without authorization
30
+ try {
31
+ await hs.delete(holon, 'documents', 'doc-001');
32
+ // Should throw, but if not, fail the test
33
+ expect(true).toBe(false);
34
+ } catch (err) {
35
+ expect(err.constructor.name).toBe('AuthorizationError');
36
+ expect(err.requiredPermission).toBe('delete');
37
+ }
38
+
39
+ // Step 3: User A issues capability token to User B
40
+ const token = await hs.issueCapability(
41
+ ['delete'],
42
+ { holonId: holon, lensName: 'documents' },
43
+ userBPublicKey,
44
+ { expiresIn: 3600000, issuerKey: userAPrivateKey }
45
+ );
46
+
47
+ expect(typeof token).toBe('string');
48
+
49
+ // Step 4: User B deletes with valid token
50
+ const deleted = await hs.delete(holon, 'documents', 'doc-001', {
51
+ capability: token
52
+ });
53
+ expect(deleted).toBe(true);
54
+
55
+ // Step 5: Verify data is deleted
56
+ const gone = await hs.read(holon, 'documents', 'doc-001');
57
+ expect(gone).toBe(null);
58
+ });
59
+
60
+ it('should validate delete without capability token is rejected', async () => {
61
+ const holon = await hs.toHolon(40.7128, -74.0060, 9);
62
+
63
+ await hs.write(holon, 'protected', {
64
+ id: 'protected-001',
65
+ owner: userAPublicKey
66
+ });
67
+
68
+ await expect(
69
+ hs.delete(holon, 'protected', 'protected-001')
70
+ ).rejects.toThrow('AuthorizationError');
71
+ });
72
+
73
+ it('should validate capability tokens grant specific permissions', async () => {
74
+ const holon = await hs.toHolon(37.7749, -122.4194, 9);
75
+
76
+ // Issue token with only 'read' permission
77
+ const readToken = await hs.issueCapability(
78
+ ['read'],
79
+ { holonId: holon, lensName: 'test' },
80
+ userBPublicKey,
81
+ { issuerKey: userAPrivateKey }
82
+ );
83
+
84
+ // Verify token has 'read' permission
85
+ const hasRead = await hs.verifyCapability(
86
+ readToken,
87
+ 'read',
88
+ { holonId: holon, lensName: 'test' }
89
+ );
90
+ expect(hasRead).toBe(true);
91
+
92
+ // Verify token does NOT have 'delete' permission
93
+ const hasDelete = await hs.verifyCapability(
94
+ readToken,
95
+ 'delete',
96
+ { holonId: holon, lensName: 'test' }
97
+ );
98
+ expect(hasDelete).toBe(false);
99
+ });
100
+
101
+ it('should validate token expiration is enforced', async () => {
102
+ const holon = await hs.toHolon(37.7749, -122.4194, 9);
103
+
104
+ // Issue expired token
105
+ const expiredToken = await hs.issueCapability(
106
+ ['delete'],
107
+ { holonId: holon, lensName: 'test' },
108
+ userBPublicKey,
109
+ { expiresIn: -1000, issuerKey: userAPrivateKey }
110
+ );
111
+
112
+ // Expired token should not be valid
113
+ const isValid = await hs.verifyCapability(
114
+ expiredToken,
115
+ 'delete',
116
+ { holonId: holon, lensName: 'test' }
117
+ );
118
+ expect(isValid).toBe(false);
119
+ });
120
+
121
+ it('should validate data creators can delete their own data', async () => {
122
+ const holon = await hs.toHolon(37.7749, -122.4194, 9);
123
+
124
+ await hs.write(holon, 'my-data', {
125
+ id: 'my-item-001',
126
+ content: 'My content'
127
+ });
128
+
129
+ // Owner should be able to delete without capability token
130
+ const deleted = await hs.delete(holon, 'my-data', 'my-item-001');
131
+ expect(deleted).toBe(true);
132
+ });
133
+
134
+ it('should validate capability tokens work across different lenses', async () => {
135
+ const holon = await hs.toHolon(37.7749, -122.4194, 9);
136
+
137
+ // Token for lens 'documents'
138
+ const docToken = await hs.issueCapability(
139
+ ['delete'],
140
+ { holonId: holon, lensName: 'documents' },
141
+ userBPublicKey,
142
+ { issuerKey: userAPrivateKey }
143
+ );
144
+
145
+ // Should be valid for 'documents' lens
146
+ const validForDocs = await hs.verifyCapability(
147
+ docToken,
148
+ 'delete',
149
+ { holonId: holon, lensName: 'documents' }
150
+ );
151
+ expect(validForDocs).toBe(true);
152
+
153
+ // Should NOT be valid for 'photos' lens
154
+ const validForPhotos = await hs.verifyCapability(
155
+ docToken,
156
+ 'delete',
157
+ { holonId: holon, lensName: 'photos' }
158
+ );
159
+ expect(validForPhotos).toBe(false);
160
+ });
161
+ });