@serfab/cadre-core 0.9.0 → 0.11.0

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 (193) hide show
  1. package/README.md +255 -253
  2. package/dist/bootstrap-peer-store-file.d.ts +13 -0
  3. package/dist/bootstrap-peer-store-file.js +42 -0
  4. package/dist/bootstrap-peer-store-file.js.map +1 -0
  5. package/dist/bootstrap-peer-store.d.ts +73 -0
  6. package/dist/bootstrap-peer-store.js +128 -0
  7. package/dist/bootstrap-peer-store.js.map +1 -0
  8. package/dist/cadre-node.d.ts +1121 -81
  9. package/dist/cadre-node.js +2276 -262
  10. package/dist/cadre-node.js.map +1 -1
  11. package/dist/control-authorization.d.ts +93 -0
  12. package/dist/control-authorization.js +57 -0
  13. package/dist/control-authorization.js.map +1 -0
  14. package/dist/control-cohort.d.ts +26 -0
  15. package/dist/control-cohort.js +26 -0
  16. package/dist/control-cohort.js.map +1 -1
  17. package/dist/control-database.d.ts +959 -54
  18. package/dist/control-database.js +1421 -148
  19. package/dist/control-database.js.map +1 -1
  20. package/dist/control-formation-recorder.d.ts +90 -10
  21. package/dist/control-formation-recorder.js +130 -14
  22. package/dist/control-formation-recorder.js.map +1 -1
  23. package/dist/control-schema.d.ts +1 -1
  24. package/dist/control-schema.js +651 -72
  25. package/dist/control-schema.js.map +1 -1
  26. package/dist/control-stream.d.ts +38 -4
  27. package/dist/control-stream.js +70 -4
  28. package/dist/control-stream.js.map +1 -1
  29. package/dist/control-write-retry.d.ts +161 -0
  30. package/dist/control-write-retry.js +412 -0
  31. package/dist/control-write-retry.js.map +1 -0
  32. package/dist/delegate-admission.d.ts +134 -0
  33. package/dist/delegate-admission.js +244 -0
  34. package/dist/delegate-admission.js.map +1 -0
  35. package/dist/device-token.d.ts +11 -11
  36. package/dist/device-token.js +14 -13
  37. package/dist/device-token.js.map +1 -1
  38. package/dist/ed25519-key.d.ts +26 -0
  39. package/dist/ed25519-key.js +56 -1
  40. package/dist/ed25519-key.js.map +1 -1
  41. package/dist/file-durable-slot.d.ts +20 -0
  42. package/dist/file-durable-slot.js +53 -0
  43. package/dist/file-durable-slot.js.map +1 -0
  44. package/dist/formation-approval.d.ts +157 -0
  45. package/dist/formation-approval.js +375 -0
  46. package/dist/formation-approval.js.map +1 -0
  47. package/dist/fs-atomic.d.ts +38 -0
  48. package/dist/fs-atomic.js +97 -0
  49. package/dist/fs-atomic.js.map +1 -0
  50. package/dist/identity-key.d.ts +49 -0
  51. package/dist/identity-key.js +81 -0
  52. package/dist/identity-key.js.map +1 -0
  53. package/dist/index.d.ts +17 -7
  54. package/dist/index.js +40 -6
  55. package/dist/index.js.map +1 -1
  56. package/dist/key-store-file.d.ts +2 -24
  57. package/dist/key-store-file.js +10 -77
  58. package/dist/key-store-file.js.map +1 -1
  59. package/dist/membership-connection-gater.d.ts +146 -0
  60. package/dist/membership-connection-gater.js +180 -0
  61. package/dist/membership-connection-gater.js.map +1 -0
  62. package/dist/node-local-snapshot.d.ts +109 -0
  63. package/dist/node-local-snapshot.js +182 -0
  64. package/dist/node-local-snapshot.js.map +1 -0
  65. package/dist/peer-addr-book.d.ts +92 -0
  66. package/dist/peer-addr-book.js +220 -0
  67. package/dist/peer-addr-book.js.map +1 -0
  68. package/dist/peer-authorization.d.ts +135 -18
  69. package/dist/peer-authorization.js +152 -21
  70. package/dist/peer-authorization.js.map +1 -1
  71. package/dist/peer-record.d.ts +21 -8
  72. package/dist/peer-record.js +26 -10
  73. package/dist/peer-record.js.map +1 -1
  74. package/dist/relay-addrs.d.ts +45 -0
  75. package/dist/relay-addrs.js +103 -0
  76. package/dist/relay-addrs.js.map +1 -0
  77. package/dist/relay-reservation.d.ts +266 -0
  78. package/dist/relay-reservation.js +613 -0
  79. package/dist/relay-reservation.js.map +1 -0
  80. package/dist/seed-bootstrap.d.ts +228 -56
  81. package/dist/seed-bootstrap.js +340 -198
  82. package/dist/seed-bootstrap.js.map +1 -1
  83. package/dist/seed-trust-policy.d.ts +47 -21
  84. package/dist/seed-trust-policy.js +24 -15
  85. package/dist/seed-trust-policy.js.map +1 -1
  86. package/dist/strand-addr-protocol.d.ts +42 -13
  87. package/dist/strand-addr-protocol.js +58 -52
  88. package/dist/strand-addr-protocol.js.map +1 -1
  89. package/dist/strand-backfill.d.ts +109 -0
  90. package/dist/strand-backfill.js +279 -0
  91. package/dist/strand-backfill.js.map +1 -0
  92. package/dist/strand-cohort.d.ts +4 -37
  93. package/dist/strand-cohort.js +4 -15
  94. package/dist/strand-cohort.js.map +1 -1
  95. package/dist/strand-database.d.ts +11 -16
  96. package/dist/strand-database.js +19 -7
  97. package/dist/strand-database.js.map +1 -1
  98. package/dist/strand-formation-manager.d.ts +36 -7
  99. package/dist/strand-formation-manager.js +115 -15
  100. package/dist/strand-formation-manager.js.map +1 -1
  101. package/dist/strand-formation-protocol.d.ts +103 -4
  102. package/dist/strand-formation-protocol.js +232 -4
  103. package/dist/strand-formation-protocol.js.map +1 -1
  104. package/dist/strand-instance-manager.d.ts +88 -19
  105. package/dist/strand-instance-manager.js +178 -34
  106. package/dist/strand-instance-manager.js.map +1 -1
  107. package/dist/strand-member-registry.d.ts +5 -5
  108. package/dist/strand-member-registry.js +18 -10
  109. package/dist/strand-member-registry.js.map +1 -1
  110. package/dist/strand-membership-writer.d.ts +484 -88
  111. package/dist/strand-membership-writer.js +751 -128
  112. package/dist/strand-membership-writer.js.map +1 -1
  113. package/dist/strand-solicitation.d.ts +99 -33
  114. package/dist/strand-solicitation.js +100 -52
  115. package/dist/strand-solicitation.js.map +1 -1
  116. package/dist/strand-transport-key.d.ts +40 -0
  117. package/dist/strand-transport-key.js +56 -0
  118. package/dist/strand-transport-key.js.map +1 -0
  119. package/dist/strand-wake-protocol.d.ts +9 -7
  120. package/dist/strand-wake-protocol.js +22 -46
  121. package/dist/strand-wake-protocol.js.map +1 -1
  122. package/dist/strand-watcher.d.ts +17 -2
  123. package/dist/strand-watcher.js +47 -3
  124. package/dist/strand-watcher.js.map +1 -1
  125. package/dist/trusted-owner-store-file.d.ts +12 -0
  126. package/dist/trusted-owner-store-file.js +39 -0
  127. package/dist/trusted-owner-store-file.js.map +1 -0
  128. package/dist/trusted-owner-store.d.ts +64 -0
  129. package/dist/trusted-owner-store.js +126 -0
  130. package/dist/trusted-owner-store.js.map +1 -0
  131. package/dist/types.d.ts +307 -44
  132. package/dist/types.js +13 -0
  133. package/dist/types.js.map +1 -1
  134. package/package.json +20 -8
  135. package/src/arachnode-stub.ts +159 -159
  136. package/src/bootstrap-peer-store-file.ts +44 -0
  137. package/src/bootstrap-peer-store.ts +172 -0
  138. package/src/cadre-node.ts +5158 -2981
  139. package/src/canonical-json.ts +30 -30
  140. package/src/control-authorization.ts +114 -0
  141. package/src/control-cohort.ts +130 -103
  142. package/src/control-database.ts +2406 -948
  143. package/src/control-formation-recorder.ts +305 -151
  144. package/src/control-schema.ts +651 -72
  145. package/src/control-stream.ts +205 -128
  146. package/src/control-write-retry.ts +483 -0
  147. package/src/delegate-admission.ts +302 -0
  148. package/src/device-token.ts +94 -89
  149. package/src/diagnostics/connection-path.ts +277 -277
  150. package/src/diagnostics/webrtc-turn-tracker.ts +222 -222
  151. package/src/ed25519-key.ts +66 -1
  152. package/src/enrollment.ts +199 -199
  153. package/src/file-durable-slot.ts +61 -0
  154. package/src/formation-approval.ts +604 -0
  155. package/src/fs-atomic.ts +107 -0
  156. package/src/hibernation-manager.ts +437 -437
  157. package/src/identity-key.ts +109 -0
  158. package/src/index.ts +137 -5
  159. package/src/key-store-file.ts +132 -203
  160. package/src/key-store.ts +99 -99
  161. package/src/membership-connection-gater.ts +204 -0
  162. package/src/node-local-snapshot.ts +260 -0
  163. package/src/peer-addr-book.ts +250 -0
  164. package/src/peer-authorization.ts +259 -81
  165. package/src/peer-record.ts +164 -146
  166. package/src/push-credentials.ts +84 -84
  167. package/src/push-node.ts +64 -64
  168. package/src/push-notifier-apns.ts +247 -247
  169. package/src/push-notifier-fcm.ts +220 -220
  170. package/src/push-notifier-shared.ts +28 -28
  171. package/src/push-notifier.ts +52 -52
  172. package/src/relay-addrs.ts +111 -0
  173. package/src/relay-reservation.ts +823 -0
  174. package/src/seed-bootstrap.ts +409 -209
  175. package/src/seed-trust-policy.ts +140 -107
  176. package/src/strand-addr-protocol.ts +441 -410
  177. package/src/strand-backfill.ts +366 -0
  178. package/src/strand-cohort.ts +38 -77
  179. package/src/strand-database.ts +202 -204
  180. package/src/strand-formation-manager.ts +491 -368
  181. package/src/strand-formation-protocol.ts +692 -391
  182. package/src/strand-instance-manager.ts +201 -46
  183. package/src/strand-member-key.ts +39 -39
  184. package/src/strand-member-registry.ts +182 -177
  185. package/src/strand-membership-writer.ts +938 -146
  186. package/src/strand-solicitation.ts +472 -407
  187. package/src/strand-transport-key.ts +58 -0
  188. package/src/strand-wake-payload.ts +32 -32
  189. package/src/strand-wake-protocol.ts +314 -331
  190. package/src/strand-watcher.ts +314 -251
  191. package/src/trusted-owner-store-file.ts +41 -0
  192. package/src/trusted-owner-store.ts +171 -0
  193. package/src/types.ts +1309 -1032
package/README.md CHANGED
@@ -1,253 +1,255 @@
1
- # @serfab/cadre-core
2
-
3
- Core library for Sereus cadre nodes—the infrastructure that enables parties to manage their personal cluster of nodes participating in distributed strand networks.
4
-
5
- ## Why Sereus Cadre Core?
6
-
7
- In traditional distributed systems, users depend on centralized services to manage their data and identities. **Sereus** inverts this model: each user (or "party") controls their own **cadre**—a personal cluster of nodes ranging from always-on cloud servers to intermittently-connected mobile devices.
8
-
9
- The cadre architecture provides:
10
-
11
- - **Sovereignty**: Your data lives on your nodes, under your control
12
- - **Resilience**: Multiple nodes means no single point of failure
13
- - **Flexibility**: Mix cloud servers, home NAS, laptops, and phones
14
- - **Privacy**: Cryptographic authorization without central servers
15
-
16
- A cadre doesn't exist in isolation. Cadres participate in **strands**—shared data spaces where multiple parties collaborate. When you join a messaging app, your cadre joins that strand. When you share a document, you're creating a strand between your cadre and theirs.
17
-
18
- ## Architecture Overview
19
-
20
- ```
21
- ┌─────────────────────────────────────────────────────────────────────────┐
22
- │ Your Cadre │
23
- │ │
24
- │ ┌────────────────────────────────────────────────────────────────────┐ │
25
- │ │ Control Network │ │
26
- │ │ (Private Optimystic DB for cadre management) │ │
27
- │ │ │ │
28
- │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
29
- │ │ │ Phone │──│ Laptop │──│ Cloud │──│ NAS │ │ │
30
- │ │ │ (edge) │ │ (edge) │ │ (core) │ │ (core) │ │ │
31
- │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
32
- │ └────────────────────────────────────────────────────────────────────┘ │
33
- │ │ │
34
- │ ┌─────────────────────┼─────────────────────┐ │
35
- │ ▼ ▼ ▼ │
36
- │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
37
- │ │ Strand A │ │ Strand B │ │ Strand C │ │
38
- │ │ (Chat App) │ │ (Shared Docs) │ │ (Photo Backup) │ │
39
- │ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
40
- └─────────────────────────────────────────────────────────────────────────┘
41
- ```
42
-
43
- Each `CadreNode` instance:
44
- 1. **Connects to the control network** - A private Optimystic database shared only by your nodes
45
- 2. **Watches for strand changes** - Automatically detects when you join or leave strands
46
- 3. **Manages strand instances** - Spins up isolated libp2p networks for each strand
47
- 4. **Handles peer enrollment** - Cryptographically authorizes new devices to join your cadre
48
-
49
- ## Installation
50
-
51
- ```bash
52
- npm install @serfab/cadre-core
53
- ```
54
-
55
- ## Quick Start
56
-
57
- ```typescript
58
- import { CadreNode } from '@serfab/cadre-core';
59
- import { FileRawStorage } from '@optimystic/db-p2p-storage-fs';
60
-
61
- const node = new CadreNode({
62
- controlNetwork: {
63
- partyId: 'your-unique-party-id',
64
- bootstrapNodes: ['/ip4/192.168.1.100/tcp/4001/p2p/12D3KooW...']
65
- },
66
- profile: 'storage', // 'storage' for servers, 'transaction' for mobile
67
- storage: {
68
- // Storage provider factory - called with strandId for strand-isolated storage
69
- provider: (strandId) => new FileRawStorage(`/data/sereus/${strandId}`),
70
- quotaBytes: 10 * 1024 * 1024 * 1024 // 10 GB
71
- }
72
- });
73
-
74
- // Start the node
75
- await node.start();
76
- console.log('Node started with Peer ID:', node.peerId?.toString());
77
-
78
- // Listen for strand events
79
- node.on('strand:started', ({ strandId }) => {
80
- console.log('Joined strand:', strandId);
81
- });
82
-
83
- // Check active strands
84
- for (const [id, strand] of node.getStrands()) {
85
- console.log(`Strand ${id}: ${strand.status}, ${strand.connectedPeers} peers`);
86
- }
87
-
88
- // Graceful shutdown
89
- await node.stop();
90
- ```
91
-
92
- ## Node Profiles
93
-
94
- | Profile | Storage Role | Use Case | Ring Zulu |
95
- |---------|--------------|----------|-----------|
96
- | **transaction** | Arachnode disabled; transaction verification via FRET only | Mobile devices, intermittent connectivity | No |
97
- | **storage** | Ring Zulu + storage rings (storage rings not yet implemented — stub) | Servers, NAS, always-on nodes | Yes |
98
-
99
- Transaction-profile nodes verify transactions via FRET only, with Arachnode disabled. Storage-profile nodes additionally join Ring Zulu and (when implemented) the concentric storage rings.
100
-
101
- > **Note:** The storage-ring subsystem is currently a no-op stub (`arachnode-stub.ts`). See [Node Profiles in `docs/architecture.md`](../../docs/architecture.md#node-profiles) for the full design.
102
-
103
- ## Strand Filtering
104
-
105
- Mobile apps typically shouldn't participate in all strands. Use filters to control participation:
106
-
107
- ```typescript
108
- // Only participate in strands for a specific app
109
- strandFilter: { mode: 'sAppId', sAppId: 'com.example.chat' }
110
-
111
- // Only participate in one specific strand
112
- strandFilter: { mode: 'strandId', strandId: 'strand-abc123' }
113
-
114
- // Control network only, no strand participation
115
- strandFilter: { mode: 'none' }
116
-
117
- // Participate in all strands (default for servers)
118
- strandFilter: { mode: 'all' }
119
- ```
120
-
121
- ## Enrolling New Devices
122
-
123
- Adding a new device to your cadre uses the Seed Bootstrap API:
124
-
125
- ```typescript
126
- // On the new device: generate identity
127
- const enrollment = new EnrollmentService();
128
- const { peerId, privateKey } = await enrollment.createCadrePeer();
129
- // Store privateKey securely, send peerId + multiaddrs to owner
130
-
131
- // On owner device: authorize the new peer and create seed
132
- await node.authorizePeer(newDevicePeerId, newDeviceMultiaddrs);
133
- const seed = await node.createSeed();
134
-
135
- // Deliver seed to new device (via protocol, API, or out-of-band)
136
- await node.deliverSeed(newDeviceMultiaddr, seed);
137
- // Or encode for QR/link: const encoded = node.encodeSeed(seed);
138
-
139
- // On new device: apply seed to join cadre
140
- const result = await newNode.applySeed(seed);
141
- ```
142
-
143
- For provider-hosted drones, use the helper:
144
-
145
- ```typescript
146
- // Get drone info from provider API
147
- const droneInfo = await provider.createContainer(plan);
148
-
149
- // One call: authorize + create seed
150
- const { seed, encodedSeed } = await node.addDrone({
151
- dronePeerId: droneInfo.peerId,
152
- droneMultiaddrs: droneInfo.multiaddrs
153
- });
154
-
155
- // Send to provider for drone initialization
156
- await provider.initializeNode(droneInfo.containerId, encodedSeed);
157
- ```
158
-
159
- ## API Reference
160
-
161
- ### CadreNode
162
-
163
- The main entry point for cadre participation.
164
-
165
- | Method | Description |
166
- |--------|-------------|
167
- | `start()` | Connect to control network and begin strand participation |
168
- | `stop()` | Gracefully disconnect from all networks |
169
- | `getStrands()` | Get all active strand instances |
170
- | `getStrand(id)` | Get a specific strand instance |
171
- | `addStrand(row)` | Manually add a strand (testing/direct API) |
172
- | `removeStrand(id)` | Manually remove a strand |
173
- | `getEnrollmentService()` | Access peer enrollment API |
174
-
175
- ### Events
176
-
177
- | Event | Payload | Description |
178
- |-------|---------|-------------|
179
- | `control:connected` | - | Connected to control network |
180
- | `control:disconnected` | - | Disconnected from control network |
181
- | `strand:started` | `{ strandId }` | Strand instance started |
182
- | `strand:stopped` | `{ strandId }` | Strand instance stopped |
183
- | `strand:error` | `{ strandId, error }` | Error in strand instance |
184
-
185
- ## Related Documentation
186
-
187
- - **[Cadre Architecture](../../docs/architecture.md)** - Deep dive into the cadre system design
188
- - **[Strand Management](../../docs/strands.md)** - How strands connect multiple cadres
189
- - **[Schema Guide](../../docs/schema-guide.md)** - Optimystic schema definitions
190
-
191
- ## React Native Support
192
-
193
- This package is compatible with React Native. For mobile apps, use the RN-specific storage provider:
194
-
195
- ```typescript
196
- import { CadreNode } from '@serfab/cadre-core';
197
- import { RNRawStorage } from '@optimystic/db-p2p-storage-rn';
198
- import { webSockets } from '@libp2p/websockets';
199
- import { circuitRelayTransport } from '@libp2p/circuit-relay-v2';
200
-
201
- const node = new CadreNode({
202
- controlNetwork: {
203
- partyId: 'your-unique-party-id',
204
- bootstrapNodes: ['/dns4/relay.example.com/tcp/443/wss/p2p/12D3KooW...']
205
- },
206
- profile: 'transaction', // Mobile devices typically use transaction profile
207
- strandFilter: { mode: 'sAppId', sAppId: 'com.example.myapp' },
208
- storage: {
209
- // RN storage provider creates strand-isolated AsyncStorage
210
- provider: (strandId) => new RNRawStorage(strandId)
211
- },
212
- network: {
213
- // TCP doesn't work in React Native — use WebSocket transport instead
214
- transports: [webSockets(), circuitRelayTransport()],
215
- listenAddrs: [] // RN nodes typically cannot listen
216
- }
217
- });
218
- ```
219
-
220
- ### Storage Provider Pattern
221
-
222
- The `storage.provider` option accepts either:
223
- - **An `IRawStorage` instance** - Shared storage for all strands
224
- - **A factory function** `(strandId: string) => IRawStorage` - Creates isolated storage per strand (recommended)
225
-
226
- Available storage implementations:
227
- | Package | Environment | Description |
228
- |---------|-------------|-------------|
229
- | `@optimystic/db-p2p` | All | `MemoryRawStorage` - In-memory (testing only) |
230
- | `@optimystic/db-p2p-storage-fs` | Node.js | `FileRawStorage` - File system storage |
231
- | `@optimystic/db-p2p-storage-rn` | React Native | `RNRawStorage` - AsyncStorage-based |
232
-
233
- ### React Native Considerations
234
-
235
- 1. **Network Transport**: libp2p's TCP transport doesn't work in RN. Pass `transports: [webSockets(), circuitRelayTransport()]` in the `network` config (see example above).
236
-
237
- 2. **Schema Loading**: The control schema is embedded in the package. Do not set `schemaPath` in React Native.
238
-
239
- 3. **Profile**: Mobile devices should use `profile: 'transaction'` for battery/bandwidth efficiency.
240
-
241
- 4. **Strand Filtering**: Always filter strands on mobile to avoid participating in unnecessary networks.
242
-
243
- ## Related Packages
244
-
245
- - **[@optimystic/db-core](https://github.com/gotchoices/optimystic)** - Distributed database core
246
- - **[@optimystic/db-p2p](https://github.com/gotchoices/optimystic)** - libp2p integration for Optimystic
247
- - **[@optimystic/db-p2p-storage-fs](https://github.com/gotchoices/optimystic)** - File system storage (Node.js)
248
- - **[@optimystic/db-p2p-storage-rn](https://github.com/gotchoices/optimystic)** - AsyncStorage (React Native)
249
-
250
- ## License
251
-
252
- MIT
253
-
1
+ # @serfab/cadre-core
2
+
3
+ Core library for Sereus cadre nodes—the infrastructure that enables parties to manage their personal cluster of nodes participating in distributed strand networks.
4
+
5
+ ## Why Sereus Cadre Core?
6
+
7
+ In traditional distributed systems, users depend on centralized services to manage their data and identities. **Sereus** inverts this model: each user (or "party") controls their own **cadre**—a personal cluster of nodes ranging from always-on cloud servers to intermittently-connected mobile devices.
8
+
9
+ The cadre architecture provides:
10
+
11
+ - **Sovereignty**: Your data lives on your nodes, under your control
12
+ - **Resilience**: Multiple nodes means no single point of failure
13
+ - **Flexibility**: Mix cloud servers, home NAS, laptops, and phones
14
+ - **Privacy**: Cryptographic authorization without central servers
15
+
16
+ A cadre doesn't exist in isolation. Cadres participate in **strands**—shared data spaces where multiple parties collaborate. When you join a messaging app, your cadre joins that strand. When you share a document, you're creating a strand between your cadre and theirs.
17
+
18
+ ## Architecture Overview
19
+
20
+ ```
21
+ ┌─────────────────────────────────────────────────────────────────────────┐
22
+ │ Your Cadre │
23
+ │ │
24
+ │ ┌────────────────────────────────────────────────────────────────────┐ │
25
+ │ │ Control Network │ │
26
+ │ │ (Private Optimystic DB for cadre management) │ │
27
+ │ │ │ │
28
+ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
29
+ │ │ │ Phone │──│ Laptop │──│ Cloud │──│ NAS │ │ │
30
+ │ │ │ (edge) │ │ (edge) │ │ (core) │ │ (core) │ │ │
31
+ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
32
+ │ └────────────────────────────────────────────────────────────────────┘ │
33
+ │ │ │
34
+ │ ┌─────────────────────┼─────────────────────┐ │
35
+ │ ▼ ▼ ▼ │
36
+ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
37
+ │ │ Strand A │ │ Strand B │ │ Strand C │ │
38
+ │ │ (Chat App) │ │ (Shared Docs) │ │ (Photo Backup) │ │
39
+ │ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
40
+ └─────────────────────────────────────────────────────────────────────────┘
41
+ ```
42
+
43
+ Each `CadreNode` instance:
44
+ 1. **Connects to the control network** - A private Optimystic database shared only by your nodes
45
+ 2. **Watches for strand changes** - Automatically detects when you join or leave strands
46
+ 3. **Manages strand instances** - Spins up isolated libp2p networks for each strand
47
+ 4. **Handles peer enrollment** - Cryptographically authorizes new devices to join your cadre
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ npm install @serfab/cadre-core
53
+ ```
54
+
55
+ ## Quick Start
56
+
57
+ ```typescript
58
+ import { CadreNode } from '@serfab/cadre-core';
59
+ import { FileRawStorage } from '@optimystic/db-p2p-storage-fs';
60
+
61
+ const node = new CadreNode({
62
+ controlNetwork: {
63
+ partyId: 'your-unique-party-id',
64
+ bootstrapNodes: ['/ip4/192.168.1.100/tcp/4001/p2p/12D3KooW...']
65
+ },
66
+ profile: 'storage', // 'storage' for servers, 'transaction' for mobile
67
+ storage: {
68
+ // Storage provider factory - called with strandId for strand-isolated storage
69
+ provider: (strandId) => new FileRawStorage(`/data/sereus/${strandId}`),
70
+ quotaBytes: 10 * 1024 * 1024 * 1024 // 10 GB
71
+ }
72
+ });
73
+
74
+ // Start the node
75
+ await node.start();
76
+ console.log('Node started with Peer ID:', node.peerId?.toString());
77
+
78
+ // Listen for strand events
79
+ node.on('strand:started', ({ strandId }) => {
80
+ console.log('Joined strand:', strandId);
81
+ });
82
+
83
+ // Check active strands
84
+ for (const [id, strand] of node.getStrands()) {
85
+ console.log(`Strand ${id}: ${strand.status}, ${strand.connectedPeers} peers`);
86
+ }
87
+
88
+ // Graceful shutdown
89
+ await node.stop();
90
+ ```
91
+
92
+ ## Node Profiles
93
+
94
+ | Profile | Storage Role | Use Case | Ring Zulu |
95
+ |---------|--------------|----------|-----------|
96
+ | **transaction** | Arachnode disabled; transaction verification via FRET only | Mobile devices, intermittent connectivity | No |
97
+ | **storage** | Ring Zulu + storage rings (storage rings not yet implemented — stub) | Servers, NAS, always-on nodes | Yes |
98
+
99
+ Transaction-profile nodes verify transactions via FRET only, with Arachnode disabled. Storage-profile nodes additionally join Ring Zulu and (when implemented) the concentric storage rings.
100
+
101
+ > **Note:** The storage-ring subsystem is currently a no-op stub (`arachnode-stub.ts`). See [Node Profiles in `docs/architecture.md`](../../docs/architecture.md#node-profiles) for the full design.
102
+
103
+ ## Strand Filtering
104
+
105
+ Mobile apps typically shouldn't participate in all strands. Use filters to control participation:
106
+
107
+ ```typescript
108
+ // Only participate in strands for a specific app
109
+ strandFilter: { mode: 'sAppId', sAppId: 'com.example.chat' }
110
+
111
+ // Only participate in one specific strand
112
+ strandFilter: { mode: 'strandId', strandId: 'strand-abc123' }
113
+
114
+ // Control network only, no strand participation
115
+ strandFilter: { mode: 'none' }
116
+
117
+ // Participate in all strands (default for servers)
118
+ strandFilter: { mode: 'all' }
119
+ ```
120
+
121
+ ## Enrolling New Devices
122
+
123
+ Adding a new device to your cadre uses the Seed Bootstrap API:
124
+
125
+ ```typescript
126
+ // On the new device: generate identity
127
+ const enrollment = new EnrollmentService();
128
+ const { peerId, privateKey } = await enrollment.createCadrePeer();
129
+ // Store privateKey securely, send peerId + multiaddrs to owner
130
+
131
+ // On owner device: authorize the new peer and create seed
132
+ await node.authorizePeer(newDevicePeerId, newDeviceMultiaddrs);
133
+ const seed = await node.createSeed();
134
+
135
+ // Deliver seed to new device (via protocol, API, or out-of-band)
136
+ await node.deliverSeed(newDeviceMultiaddr, seed);
137
+ // Or encode for QR/link: const encoded = node.encodeSeed(seed);
138
+
139
+ // On new device: apply seed to join cadre
140
+ const result = await newNode.applySeed(seed);
141
+ ```
142
+
143
+ For provider-hosted drones, use the helper:
144
+
145
+ ```typescript
146
+ // Get drone info from provider API
147
+ const droneInfo = await provider.createContainer(plan);
148
+
149
+ // One call: authorize + create seed
150
+ const { seed, encodedSeed } = await node.addDrone({
151
+ dronePeerId: droneInfo.peerId,
152
+ droneMultiaddrs: droneInfo.multiaddrs
153
+ });
154
+
155
+ // Send to provider for drone initialization
156
+ await provider.initializeNode(droneInfo.containerId, encodedSeed);
157
+ ```
158
+
159
+ ## API Reference
160
+
161
+ ### CadreNode
162
+
163
+ The main entry point for cadre participation.
164
+
165
+ | Method | Description |
166
+ |--------|-------------|
167
+ | `start()` | Connect to control network and begin strand participation |
168
+ | `stop()` | Gracefully disconnect from all networks |
169
+ | `getStrands()` | Get all active strand instances |
170
+ | `getStrand(id)` | Get a specific strand instance |
171
+ | `addStrand(row)` | Manually add a strand (testing/direct API) |
172
+ | `stopStrand(id)` | Stop a strand on this node only (the shared `Strand` row stays; rediscovered on restart) |
173
+ | `publishStrand(id, type?, memberKey?)` | Owner-signed `Strand` row insert — makes the strand visible cadre-wide |
174
+ | `unpublishStrand(id)` | Owner-signed party-wide removal — deletes this party's `Strand` row; every node stops the strand. Destroys a closed strand's `MemberPrivateKey` |
175
+ | `getEnrollmentService()` | Access peer enrollment API |
176
+
177
+ ### Events
178
+
179
+ | Event | Payload | Description |
180
+ |-------|---------|-------------|
181
+ | `control:connected` | - | Connected to control network |
182
+ | `control:disconnected` | - | Disconnected from control network |
183
+ | `strand:started` | `{ strandId }` | Strand instance started |
184
+ | `strand:stopped` | `{ strandId }` | Strand instance stopped |
185
+ | `strand:error` | `{ strandId, error }` | Error in strand instance. A failed launch re-emits this on every retry (backing off up to 5 min), so treat it as a repeating signal, not a one-shot |
186
+
187
+ ## Related Documentation
188
+
189
+ - **[Cadre Architecture](../../docs/architecture.md)** - Deep dive into the cadre system design
190
+ - **[Strand Management](../../docs/strands.md)** - How strands connect multiple cadres
191
+ - **[Schema Guide](../../docs/schema-guide.md)** - Optimystic schema definitions
192
+
193
+ ## React Native Support
194
+
195
+ This package is compatible with React Native. For mobile apps, use the RN-specific storage provider:
196
+
197
+ ```typescript
198
+ import { CadreNode } from '@serfab/cadre-core';
199
+ import { RNRawStorage } from '@optimystic/db-p2p-storage-rn';
200
+ import { webSockets } from '@libp2p/websockets';
201
+ import { circuitRelayTransport } from '@libp2p/circuit-relay-v2';
202
+
203
+ const node = new CadreNode({
204
+ controlNetwork: {
205
+ partyId: 'your-unique-party-id',
206
+ bootstrapNodes: ['/dns4/relay.example.com/tcp/443/wss/p2p/12D3KooW...']
207
+ },
208
+ profile: 'transaction', // Mobile devices typically use transaction profile
209
+ strandFilter: { mode: 'sAppId', sAppId: 'com.example.myapp' },
210
+ storage: {
211
+ // RN storage provider creates strand-isolated AsyncStorage
212
+ provider: (strandId) => new RNRawStorage(strandId)
213
+ },
214
+ network: {
215
+ // TCP doesn't work in React Native — use WebSocket transport instead
216
+ transports: [webSockets(), circuitRelayTransport()],
217
+ listenAddrs: [] // RN nodes typically cannot listen
218
+ }
219
+ });
220
+ ```
221
+
222
+ ### Storage Provider Pattern
223
+
224
+ The `storage.provider` option accepts either:
225
+ - **An `IRawStorage` instance** - Shared storage for all strands
226
+ - **A factory function** `(strandId: string) => IRawStorage` - Creates isolated storage per strand (recommended)
227
+
228
+ Available storage implementations:
229
+ | Package | Environment | Description |
230
+ |---------|-------------|-------------|
231
+ | `@optimystic/db-p2p` | All | `MemoryRawStorage` - In-memory (testing only) |
232
+ | `@optimystic/db-p2p-storage-fs` | Node.js | `FileRawStorage` - File system storage |
233
+ | `@optimystic/db-p2p-storage-rn` | React Native | `RNRawStorage` - AsyncStorage-based |
234
+
235
+ ### React Native Considerations
236
+
237
+ 1. **Network Transport**: libp2p's TCP transport doesn't work in RN. Pass `transports: [webSockets(), circuitRelayTransport()]` in the `network` config (see example above).
238
+
239
+ 2. **Schema Loading**: The control schema is embedded in the package. Do not set `schemaPath` in React Native.
240
+
241
+ 3. **Profile**: Mobile devices should use `profile: 'transaction'` for battery/bandwidth efficiency.
242
+
243
+ 4. **Strand Filtering**: Always filter strands on mobile to avoid participating in unnecessary networks.
244
+
245
+ ## Related Packages
246
+
247
+ - **[@optimystic/db-core](https://github.com/gotchoices/optimystic)** - Distributed database core
248
+ - **[@optimystic/db-p2p](https://github.com/gotchoices/optimystic)** - libp2p integration for Optimystic
249
+ - **[@optimystic/db-p2p-storage-fs](https://github.com/gotchoices/optimystic)** - File system storage (Node.js)
250
+ - **[@optimystic/db-p2p-storage-rn](https://github.com/gotchoices/optimystic)** - AsyncStorage (React Native)
251
+
252
+ ## License
253
+
254
+ MIT
255
+
@@ -0,0 +1,13 @@
1
+ import type { BootstrapPeerStore } from './bootstrap-peer-store.js';
2
+ /**
3
+ * File-backed {@link BootstrapPeerStore}. Open via {@link open}, which loads the
4
+ * existing file and returns the cross-platform store over it; an absent, corrupt, or wrong-party file is a cold
5
+ * start (empty store) and structurally junk entries are dropped, while a
6
+ * present-but-unreadable file throws. Those rules and their reasoning live on
7
+ * `NodeLocalSnapshot.open` — including the in-process-only write serialisation,
8
+ * which two nodes sharing one directory for one party would defeat.
9
+ */
10
+ export declare const FileBootstrapPeerStore: {
11
+ /** Load (or cold-start) the party's retained dial targets from a file in `dir`. */
12
+ open(dir: string, partyId: string): Promise<BootstrapPeerStore>;
13
+ };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Node-only file-backed {@link BootstrapPeerStore}: one JSON file per party under
3
+ * a configured directory (the node's state directory — `cadre-cli` passes
4
+ * `ResolvedConfig.nodeStateDir`), holding the node-local, non-replicated
5
+ * cold-start dial targets a seed nominated.
6
+ *
7
+ * Nothing but the file itself lives here: the envelope, load policy, per-entry
8
+ * validation (shape only — never signatures; see the `bootstrap-peer-store.ts`
9
+ * module comment for why nothing persisted here is trust-bearing) and the
10
+ * serialised snapshot writes are cross-platform in
11
+ * `PersistentBootstrapPeerStore` / `node-local-snapshot.ts`, and the file is a
12
+ * `FileDurableSlot`. This module exists only so the `node:fs` edge stays out of
13
+ * the package's cross-platform default entry (`./index.js`) — import it from the
14
+ * dedicated subpath instead:
15
+ *
16
+ * ```ts
17
+ * import { FileBootstrapPeerStore } from '@serfab/cadre-core/bootstrap-peer-store-file';
18
+ * ```
19
+ *
20
+ * A React Native / browser entry graph never resolves this path, so the
21
+ * `node:fs` edge never reaches a bundler that cannot satisfy it (same isolation
22
+ * pattern as `trusted-owner-store-file.ts` / `key-store-file.ts`).
23
+ */
24
+ import { FileDurableSlot } from './file-durable-slot.js';
25
+ import { PersistentBootstrapPeerStore } from './bootstrap-peer-store.js';
26
+ /** Base name of the store file: `<dir>/bootstrap-peers.<encoded partyId>.json`. */
27
+ const SLOT_NAME = 'bootstrap-peers';
28
+ /**
29
+ * File-backed {@link BootstrapPeerStore}. Open via {@link open}, which loads the
30
+ * existing file and returns the cross-platform store over it; an absent, corrupt, or wrong-party file is a cold
31
+ * start (empty store) and structurally junk entries are dropped, while a
32
+ * present-but-unreadable file throws. Those rules and their reasoning live on
33
+ * `NodeLocalSnapshot.open` — including the in-process-only write serialisation,
34
+ * which two nodes sharing one directory for one party would defeat.
35
+ */
36
+ export const FileBootstrapPeerStore = {
37
+ /** Load (or cold-start) the party's retained dial targets from a file in `dir`. */
38
+ async open(dir, partyId) {
39
+ return PersistentBootstrapPeerStore.open(new FileDurableSlot(dir, SLOT_NAME, partyId), partyId);
40
+ }
41
+ };
42
+ //# sourceMappingURL=bootstrap-peer-store-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap-peer-store-file.js","sourceRoot":"","sources":["../src/bootstrap-peer-store-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AAGzE,mFAAmF;AACnF,MAAM,SAAS,GAAG,iBAAiB,CAAC;AAEpC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACrC,mFAAmF;IACnF,KAAK,CAAC,IAAI,CAAC,GAAW,EAAE,OAAe;QACtC,OAAO,4BAA4B,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;IACjG,CAAC;CACD,CAAC"}