@serfab/cadre-core 0.0.2

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 (54) hide show
  1. package/README.md +252 -0
  2. package/dist/arachnode-stub.d.ts +60 -0
  3. package/dist/arachnode-stub.js +120 -0
  4. package/dist/arachnode-stub.js.map +1 -0
  5. package/dist/cadre-node.d.ts +252 -0
  6. package/dist/cadre-node.js +753 -0
  7. package/dist/cadre-node.js.map +1 -0
  8. package/dist/control-database.d.ts +62 -0
  9. package/dist/control-database.js +295 -0
  10. package/dist/control-database.js.map +1 -0
  11. package/dist/enrollment.d.ts +76 -0
  12. package/dist/enrollment.js +126 -0
  13. package/dist/enrollment.js.map +1 -0
  14. package/dist/hibernation-manager.d.ts +68 -0
  15. package/dist/hibernation-manager.js +212 -0
  16. package/dist/hibernation-manager.js.map +1 -0
  17. package/dist/index.d.ts +12 -0
  18. package/dist/index.js +24 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/seed-bootstrap.d.ts +164 -0
  21. package/dist/seed-bootstrap.js +554 -0
  22. package/dist/seed-bootstrap.js.map +1 -0
  23. package/dist/strand-database.d.ts +39 -0
  24. package/dist/strand-database.js +108 -0
  25. package/dist/strand-database.js.map +1 -0
  26. package/dist/strand-formation-manager.d.ts +87 -0
  27. package/dist/strand-formation-manager.js +168 -0
  28. package/dist/strand-formation-manager.js.map +1 -0
  29. package/dist/strand-instance-manager.d.ts +60 -0
  30. package/dist/strand-instance-manager.js +209 -0
  31. package/dist/strand-instance-manager.js.map +1 -0
  32. package/dist/strand-solicitation.d.ts +145 -0
  33. package/dist/strand-solicitation.js +180 -0
  34. package/dist/strand-solicitation.js.map +1 -0
  35. package/dist/strand-watcher.d.ts +70 -0
  36. package/dist/strand-watcher.js +136 -0
  37. package/dist/strand-watcher.js.map +1 -0
  38. package/dist/types.d.ts +484 -0
  39. package/dist/types.js +26 -0
  40. package/dist/types.js.map +1 -0
  41. package/package.json +67 -0
  42. package/src/arachnode-stub.ts +160 -0
  43. package/src/cadre-node.ts +923 -0
  44. package/src/control-database.ts +376 -0
  45. package/src/enrollment.ts +199 -0
  46. package/src/hibernation-manager.ts +268 -0
  47. package/src/index.ts +71 -0
  48. package/src/seed-bootstrap.ts +753 -0
  49. package/src/strand-database.ts +168 -0
  50. package/src/strand-formation-manager.ts +277 -0
  51. package/src/strand-instance-manager.ts +281 -0
  52. package/src/strand-solicitation.ts +318 -0
  53. package/src/strand-watcher.ts +197 -0
  54. package/src/types.ts +553 -0
package/README.md ADDED
@@ -0,0 +1,252 @@
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 Participation |
95
+ |---------|--------------|----------|-------------------|
96
+ | **transaction** | Ring Zulu only | Mobile devices, intermittent connectivity | Transaction verification, caching |
97
+ | **storage** | Ring Zulu + Storage Rings | Servers, NAS, always-on nodes | Full archival storage |
98
+
99
+ Both profiles participate in transaction consensus. The distinction is long-term storage commitment.
100
+
101
+ ## Strand Filtering
102
+
103
+ Mobile apps typically shouldn't participate in all strands. Use filters to control participation:
104
+
105
+ ```typescript
106
+ // Only participate in strands for a specific app
107
+ strandFilter: { mode: 'appId', appId: 'com.example.chat' }
108
+
109
+ // Only participate in one specific strand
110
+ strandFilter: { mode: 'strandId', strandId: 'strand-abc123' }
111
+
112
+ // Control network only, no strand participation
113
+ strandFilter: { mode: 'none' }
114
+
115
+ // Participate in all strands (default for servers)
116
+ strandFilter: { mode: 'all' }
117
+ ```
118
+
119
+ ## Enrolling New Devices
120
+
121
+ Adding a new device to your cadre uses the Seed Bootstrap API:
122
+
123
+ ```typescript
124
+ // On the new device: generate identity
125
+ const enrollment = new EnrollmentService();
126
+ const { peerId, privateKey } = await enrollment.createCadrePeer();
127
+ // Store privateKey securely, send peerId + multiaddrs to authority
128
+
129
+ // On authority device: authorize the new peer and create seed
130
+ await node.authorizePeer(newDevicePeerId, newDeviceMultiaddrs);
131
+ const seed = await node.createSeed();
132
+
133
+ // Deliver seed to new device (via protocol, API, or out-of-band)
134
+ await node.deliverSeed(newDeviceMultiaddr, seed);
135
+ // Or encode for QR/link: const encoded = node.encodeSeed(seed);
136
+
137
+ // On new device: apply seed to join cadre
138
+ const result = await newNode.applySeed(seed);
139
+ ```
140
+
141
+ For provider-hosted drones, use the helper:
142
+
143
+ ```typescript
144
+ // Get drone info from provider API
145
+ const droneInfo = await provider.createContainer(plan);
146
+
147
+ // One call: authorize + create seed
148
+ const { seed, encodedSeed } = await node.addDrone({
149
+ dronePeerId: droneInfo.peerId,
150
+ droneMultiaddrs: droneInfo.multiaddrs
151
+ });
152
+
153
+ // Send to provider for drone initialization
154
+ await provider.applySeed(droneInfo.containerId, encodedSeed);
155
+ ```
156
+
157
+ ## API Reference
158
+
159
+ ### CadreNode
160
+
161
+ The main entry point for cadre participation.
162
+
163
+ | Method | Description |
164
+ |--------|-------------|
165
+ | `start()` | Connect to control network and begin strand participation |
166
+ | `stop()` | Gracefully disconnect from all networks |
167
+ | `getStrands()` | Get all active strand instances |
168
+ | `getStrand(id)` | Get a specific strand instance |
169
+ | `addStrand(row)` | Manually add a strand (testing/direct API) |
170
+ | `removeStrand(id)` | Manually remove a strand |
171
+ | `getEnrollmentService()` | Access peer enrollment API |
172
+
173
+ ### Events
174
+
175
+ | Event | Payload | Description |
176
+ |-------|---------|-------------|
177
+ | `control:connected` | - | Connected to control network |
178
+ | `control:disconnected` | - | Disconnected from control network |
179
+ | `strand:started` | `{ strandId }` | Strand instance started |
180
+ | `strand:stopped` | `{ strandId }` | Strand instance stopped |
181
+ | `strand:error` | `{ strandId, error }` | Error in strand instance |
182
+
183
+ ## Related Documentation
184
+
185
+ - **[Cadre Architecture](../../docs/cadre-architecture.md)** - Deep dive into the cadre system design
186
+ - **[Strand Management](../../docs/strands.md)** - How strands connect multiple cadres
187
+ - **[Schema Guide](../../docs/schema-guide.md)** - Optimystic schema definitions
188
+
189
+ ## React Native Support
190
+
191
+ This package is compatible with React Native. For mobile apps, use the RN-specific storage provider:
192
+
193
+ ```typescript
194
+ import { CadreNode } from '@serfab/cadre-core';
195
+ import { RNRawStorage } from '@optimystic/db-p2p-storage-rn';
196
+ import { webSockets } from '@libp2p/websockets';
197
+ import { circuitRelayTransport } from '@libp2p/circuit-relay-v2';
198
+
199
+ const node = new CadreNode({
200
+ controlNetwork: {
201
+ partyId: 'your-unique-party-id',
202
+ bootstrapNodes: ['/dns4/relay.example.com/tcp/443/wss/p2p/12D3KooW...']
203
+ },
204
+ profile: 'transaction', // Mobile devices typically use transaction profile
205
+ strandFilter: { mode: 'sAppId', sAppId: 'com.example.myapp' },
206
+ storage: {
207
+ // RN storage provider creates strand-isolated AsyncStorage
208
+ provider: (strandId) => new RNRawStorage(strandId)
209
+ },
210
+ network: {
211
+ // TCP doesn't work in React Native — use WebSocket transport instead
212
+ transports: [webSockets(), circuitRelayTransport()],
213
+ listenAddrs: [] // RN nodes typically cannot listen
214
+ }
215
+ });
216
+ ```
217
+
218
+ ### Storage Provider Pattern
219
+
220
+ The `storage.provider` option accepts either:
221
+ - **An `IRawStorage` instance** - Shared storage for all strands
222
+ - **A factory function** `(strandId: string) => IRawStorage` - Creates isolated storage per strand (recommended)
223
+
224
+ Available storage implementations:
225
+ | Package | Environment | Description |
226
+ |---------|-------------|-------------|
227
+ | `@optimystic/db-p2p` | All | `MemoryRawStorage` - In-memory (testing only) |
228
+ | `@optimystic/db-p2p-storage-fs` | Node.js | `FileRawStorage` - File system storage |
229
+ | `@optimystic/db-p2p-storage-rn` | React Native | `RNRawStorage` - AsyncStorage-based |
230
+
231
+ ### React Native Considerations
232
+
233
+ 1. **Network Transport**: libp2p's TCP transport doesn't work in RN. Pass `transports: [webSockets(), circuitRelayTransport()]` in the `network` config (see example above).
234
+
235
+ 2. **Schema Loading**: The control schema is embedded in the package. Do not set `schemaPath` in React Native.
236
+
237
+ 3. **Profile**: Mobile devices should use `profile: 'transaction'` for battery/bandwidth efficiency.
238
+
239
+ 4. **Strand Filtering**: Always filter strands on mobile to avoid participating in unnecessary networks.
240
+
241
+ ## Related Packages
242
+
243
+ - **[@serfab/strand-proto](../strand-proto/)** - Strand initialization protocol
244
+ - **[@optimystic/db-core](https://github.com/gotchoices/optimystic)** - Distributed database core
245
+ - **[@optimystic/db-p2p](https://github.com/gotchoices/optimystic)** - libp2p integration for Optimystic
246
+ - **[@optimystic/db-p2p-storage-fs](https://github.com/gotchoices/optimystic)** - File system storage (Node.js)
247
+ - **[@optimystic/db-p2p-storage-rn](https://github.com/gotchoices/optimystic)** - AsyncStorage (React Native)
248
+
249
+ ## License
250
+
251
+ MIT
252
+
@@ -0,0 +1,60 @@
1
+ import type { NodeProfile, ArachnodeConfig } from './types.js';
2
+ /**
3
+ * Ring configuration for a storage node
4
+ */
5
+ export interface RingConfig {
6
+ /** Ring number (0 = full keyspace, higher = more partitions) */
7
+ ring: number;
8
+ /** Partition within the ring (depends on ring level) */
9
+ partition: number;
10
+ /** Keyspace range this node is responsible for */
11
+ keyspaceStart: Uint8Array;
12
+ keyspaceEnd: Uint8Array;
13
+ }
14
+ /**
15
+ * Stub implementation of Arachnode ring participation.
16
+ *
17
+ * Arachnode uses a concentric ring system where:
18
+ * - Ring Zulu (transaction ring): All nodes participate for transaction verification
19
+ * - Storage rings (0, 1, 2, 3...): Nodes join based on their storage capacity
20
+ * - Ring 0: Full keyspace (requires most storage)
21
+ * - Ring 1: 2 partitions
22
+ * - Ring 2: 4 partitions
23
+ * - Ring 3: 8 partitions
24
+ * - etc.
25
+ *
26
+ * This is a stub that will be replaced when arachnode is fully implemented.
27
+ */
28
+ export declare class ArachnodeStub {
29
+ private readonly profile;
30
+ private readonly config;
31
+ private ringConfig?;
32
+ private running;
33
+ constructor(profile: NodeProfile, config: ArachnodeConfig);
34
+ /**
35
+ * Start participating in rings
36
+ */
37
+ start(): Promise<void>;
38
+ /**
39
+ * Stop participating in rings
40
+ */
41
+ stop(): Promise<void>;
42
+ /**
43
+ * Get current ring configuration
44
+ */
45
+ getRingConfig(): RingConfig | undefined;
46
+ /**
47
+ * Check if participating in Ring Zulu
48
+ */
49
+ isInRingZulu(): boolean;
50
+ /**
51
+ * Check if participating in a storage ring
52
+ */
53
+ isInStorageRing(): boolean;
54
+ private calculateKeyspaceStart;
55
+ private calculateKeyspaceEnd;
56
+ }
57
+ /**
58
+ * Create an arachnode instance for a strand
59
+ */
60
+ export declare function createArachnodeStub(profile: NodeProfile, config?: Partial<ArachnodeConfig>): ArachnodeStub;
@@ -0,0 +1,120 @@
1
+ import debug from 'debug';
2
+ const log = debug('sereus:cadre:arachnode');
3
+ /**
4
+ * Stub implementation of Arachnode ring participation.
5
+ *
6
+ * Arachnode uses a concentric ring system where:
7
+ * - Ring Zulu (transaction ring): All nodes participate for transaction verification
8
+ * - Storage rings (0, 1, 2, 3...): Nodes join based on their storage capacity
9
+ * - Ring 0: Full keyspace (requires most storage)
10
+ * - Ring 1: 2 partitions
11
+ * - Ring 2: 4 partitions
12
+ * - Ring 3: 8 partitions
13
+ * - etc.
14
+ *
15
+ * This is a stub that will be replaced when arachnode is fully implemented.
16
+ */
17
+ export class ArachnodeStub {
18
+ constructor(profile, config) {
19
+ this.running = false;
20
+ this.profile = profile;
21
+ this.config = config;
22
+ log('ArachnodeStub created for profile=%s, config=%o', profile, config);
23
+ }
24
+ /**
25
+ * Start participating in rings
26
+ */
27
+ async start() {
28
+ if (this.running)
29
+ return;
30
+ this.running = true;
31
+ // All nodes participate in Ring Zulu
32
+ if (this.config.enableRingZulu) {
33
+ log('Joining Ring Zulu (transaction ring)');
34
+ // Stub: In real implementation, would register with transaction verification network
35
+ }
36
+ // Storage nodes join storage rings
37
+ if (this.profile === 'storage' && this.config.storageRing) {
38
+ const { ring, partition = 0 } = this.config.storageRing;
39
+ log('Joining storage ring %d, partition %d', ring, partition);
40
+ // Calculate keyspace range for this partition
41
+ // Stub: Using placeholder keyspace calculation
42
+ this.ringConfig = {
43
+ ring,
44
+ partition,
45
+ keyspaceStart: this.calculateKeyspaceStart(ring, partition),
46
+ keyspaceEnd: this.calculateKeyspaceEnd(ring, partition)
47
+ };
48
+ // Stub: In real implementation, would:
49
+ // 1. Register with the storage ring
50
+ // 2. Begin accepting block storage requests for our keyspace
51
+ // 3. Participate in replication with other ring members
52
+ }
53
+ log('ArachnodeStub started');
54
+ }
55
+ /**
56
+ * Stop participating in rings
57
+ */
58
+ async stop() {
59
+ if (!this.running)
60
+ return;
61
+ this.running = false;
62
+ if (this.ringConfig) {
63
+ log('Leaving storage ring %d, partition %d', this.ringConfig.ring, this.ringConfig.partition);
64
+ // Stub: In real implementation, would gracefully leave the ring
65
+ this.ringConfig = undefined;
66
+ }
67
+ if (this.config.enableRingZulu) {
68
+ log('Leaving Ring Zulu');
69
+ // Stub: In real implementation, would leave transaction ring
70
+ }
71
+ log('ArachnodeStub stopped');
72
+ }
73
+ /**
74
+ * Get current ring configuration
75
+ */
76
+ getRingConfig() {
77
+ return this.ringConfig;
78
+ }
79
+ /**
80
+ * Check if participating in Ring Zulu
81
+ */
82
+ isInRingZulu() {
83
+ return this.running && this.config.enableRingZulu;
84
+ }
85
+ /**
86
+ * Check if participating in a storage ring
87
+ */
88
+ isInStorageRing() {
89
+ return this.running && this.ringConfig !== undefined;
90
+ }
91
+ // Stub keyspace calculations
92
+ calculateKeyspaceStart(ring, partition) {
93
+ // Stub: Real implementation would calculate based on ring partition scheme
94
+ const numPartitions = Math.pow(2, ring);
95
+ const partitionSize = 256 / numPartitions;
96
+ const start = new Uint8Array(32);
97
+ start[0] = Math.floor(partition * partitionSize);
98
+ return start;
99
+ }
100
+ calculateKeyspaceEnd(ring, partition) {
101
+ // Stub: Real implementation would calculate based on ring partition scheme
102
+ const numPartitions = Math.pow(2, ring);
103
+ const partitionSize = 256 / numPartitions;
104
+ const end = new Uint8Array(32);
105
+ end[0] = Math.floor((partition + 1) * partitionSize) - 1;
106
+ end.fill(0xFF, 1); // Fill rest with max values
107
+ return end;
108
+ }
109
+ }
110
+ /**
111
+ * Create an arachnode instance for a strand
112
+ */
113
+ export function createArachnodeStub(profile, config) {
114
+ const fullConfig = {
115
+ enableRingZulu: config?.enableRingZulu ?? true,
116
+ storageRing: profile === 'storage' ? (config?.storageRing ?? { ring: 0 }) : undefined
117
+ };
118
+ return new ArachnodeStub(profile, fullConfig);
119
+ }
120
+ //# sourceMappingURL=arachnode-stub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arachnode-stub.js","sourceRoot":"","sources":["../src/arachnode-stub.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,252 @@
1
+ import type { Libp2p, PeerId } from '@libp2p/interface';
2
+ import type { CadreNodeConfig, StrandInstance, StrandConfig, SAppConfig, CadreNodeEvents, ControlNetworkSeed, ApplySeedResult, AddDroneOptions, AddPhoneOptions, DroneInitResult, InviteResult, CadreInvite, OpenInvitation, FormStrandResult, StrandFormationDisclosure } from './types.js';
3
+ import { type SAppIdLookup } from './strand-watcher.js';
4
+ import { EnrollmentService } from './enrollment.js';
5
+ import { ControlDatabase } from './control-database.js';
6
+ import { SeedBootstrapService } from './seed-bootstrap.js';
7
+ import { StrandSolicitationService, type StrandSolicitationServiceOptions } from './strand-solicitation.js';
8
+ type EventHandler<T> = (data: T) => void;
9
+ /**
10
+ * CadreNode is the main entry point for a cadre member.
11
+ * It manages:
12
+ * - Connection to the control network
13
+ * - Watching for strand changes
14
+ * - Starting/stopping strand instances
15
+ * - Strand hibernation lifecycle
16
+ * - Peer enrollment
17
+ */
18
+ export declare class CadreNode implements SAppIdLookup {
19
+ private readonly config;
20
+ private controlNode;
21
+ private controlDatabase;
22
+ private strandWatcher;
23
+ private strandManager;
24
+ private hibernationManager;
25
+ private enrollmentService;
26
+ private seedBootstrapService;
27
+ private strandSolicitationService;
28
+ private running;
29
+ private eventHandlers;
30
+ /** Map of strandId -> sAppConfig for sAppId filtering and management */
31
+ private sAppConfigs;
32
+ constructor(config: CadreNodeConfig);
33
+ /**
34
+ * SAppIdLookup implementation - get sAppId for a strand
35
+ */
36
+ getSAppId(strandId: string): string | undefined;
37
+ /**
38
+ * Get the peer ID of this node (available after start)
39
+ */
40
+ get peerId(): PeerId | undefined;
41
+ /**
42
+ * Get the multiaddrs of this node (available after start)
43
+ */
44
+ getMultiaddrs(): string[];
45
+ /**
46
+ * Check if the node is running
47
+ */
48
+ get isRunning(): boolean;
49
+ /**
50
+ * Get all strand instances
51
+ */
52
+ getStrands(): Map<string, StrandInstance>;
53
+ /**
54
+ * Get a specific strand instance
55
+ */
56
+ getStrand(strandId: string): StrandInstance | undefined;
57
+ /**
58
+ * Get the enrollment service for adding new peers
59
+ */
60
+ getEnrollmentService(): EnrollmentService;
61
+ /**
62
+ * Start the cadre node
63
+ */
64
+ start(): Promise<void>;
65
+ /**
66
+ * Stop the cadre node
67
+ */
68
+ stop(): Promise<void>;
69
+ /**
70
+ * Subscribe to events
71
+ */
72
+ on<K extends keyof CadreNodeEvents>(event: K, handler: EventHandler<CadreNodeEvents[K]>): void;
73
+ /**
74
+ * Unsubscribe from events
75
+ */
76
+ off<K extends keyof CadreNodeEvents>(event: K, handler: EventHandler<CadreNodeEvents[K]>): void;
77
+ private emit;
78
+ private createControlNode;
79
+ private createStrandQueryable;
80
+ /**
81
+ * Schedule self-registration in the CadrePeer table.
82
+ * This runs as a background task after the node is started.
83
+ */
84
+ private scheduleSelfRegistration;
85
+ /**
86
+ * Register this peer in the CadrePeer table.
87
+ * This allows other cadre members to discover this node.
88
+ */
89
+ private registerSelf;
90
+ private handleStrandAdded;
91
+ private handleStrandRemoved;
92
+ private cleanup;
93
+ private handleStrandIdle;
94
+ private handleStrandHibernate;
95
+ private handleStrandWake;
96
+ /**
97
+ * Add a strand with its sApp configuration.
98
+ * The hosting application must provide the sApp schema when creating a strand.
99
+ */
100
+ addStrand(config: StrandConfig): Promise<StrandInstance>;
101
+ /**
102
+ * Remove a strand
103
+ */
104
+ removeStrand(strandId: string): Promise<void>;
105
+ /**
106
+ * Record activity on a strand (resets hibernation timer)
107
+ */
108
+ recordStrandActivity(strandId: string): void;
109
+ /**
110
+ * Force wake a hibernating strand
111
+ */
112
+ wakeStrand(strandId: string): Promise<void>;
113
+ /**
114
+ * Get the control network node (for advanced use)
115
+ */
116
+ getControlNode(): Libp2p | null;
117
+ /**
118
+ * Get the control database (for advanced queries)
119
+ */
120
+ getControlDatabase(): ControlDatabase | null;
121
+ /**
122
+ * Force a poll of the strand watcher (for testing)
123
+ */
124
+ forceStrandPoll(): Promise<void>;
125
+ /**
126
+ * Get the sApp configuration for a strand
127
+ */
128
+ getSAppConfig(strandId: string): SAppConfig | undefined;
129
+ /**
130
+ * Initialize the seed bootstrap service with an authority key.
131
+ * Must be called before using seed-related methods that require signing.
132
+ *
133
+ * @param authorityPrivateKey - The authority's private key (base64url encoded)
134
+ */
135
+ initializeSeedBootstrap(authorityPrivateKey: string): void;
136
+ /**
137
+ * Enable the seed listener for receiving seeds via the /sereus/seed/1.0.0 protocol.
138
+ * This is for drone nodes that need to receive seeds without being an authority.
139
+ * Does not require an authority key.
140
+ */
141
+ enableSeedListener(): void;
142
+ /**
143
+ * Get the seed bootstrap service (for advanced use)
144
+ */
145
+ getSeedBootstrapService(): SeedBootstrapService | null;
146
+ /**
147
+ * Authorize a new peer to join the cadre.
148
+ * Signs the peer ID with the authority key and inserts into CadrePeer table.
149
+ *
150
+ * @param peerId - The peer ID to authorize
151
+ * @param multiaddrs - Optional multiaddrs for the peer
152
+ */
153
+ authorizePeer(peerId: string, multiaddrs?: string[]): Promise<void>;
154
+ /**
155
+ * Create a seed from the current control network state.
156
+ * The seed contains peer information and is signed by an authority.
157
+ */
158
+ createSeed(): Promise<ControlNetworkSeed>;
159
+ /**
160
+ * Apply a seed to populate the peer cache and enable connections.
161
+ * Validates the seed signature before applying.
162
+ */
163
+ applySeed(seed: ControlNetworkSeed): Promise<ApplySeedResult>;
164
+ /**
165
+ * Deliver a seed directly to a peer via the /sereus/seed/1.0.0 protocol.
166
+ */
167
+ deliverSeed(targetMultiaddr: string, seed: ControlNetworkSeed): Promise<{
168
+ accepted: boolean;
169
+ reason?: string;
170
+ }>;
171
+ /**
172
+ * Encode a seed for out-of-band delivery (e.g., QR code, copy/paste).
173
+ */
174
+ encodeSeed(seed: ControlNetworkSeed): string;
175
+ /**
176
+ * Decode a seed from base64url encoding.
177
+ */
178
+ decodeSeed(encoded: string): ControlNetworkSeed;
179
+ /**
180
+ * Get this node's circuit relay address for inclusion in seeds.
181
+ * Returns null if no relay address is available.
182
+ */
183
+ getRelayAddress(): Promise<string | null>;
184
+ /**
185
+ * Add a drone to the cadre (for phone/server adding provider-hosted node).
186
+ * Creates authorization and seed for drone initialization.
187
+ */
188
+ addDrone(options: AddDroneOptions): Promise<DroneInitResult>;
189
+ /**
190
+ * Create an invite for a phone to join the cadre.
191
+ * Use when a server wants to invite a NAT'd phone.
192
+ */
193
+ createInvite(token?: string, expiresIn?: number): Promise<InviteResult>;
194
+ /**
195
+ * Accept a phone connection using an invite.
196
+ * Call this when a phone dials in with an invite token.
197
+ */
198
+ acceptPhone(options: AddPhoneOptions, issuedInvite?: CadreInvite): Promise<void>;
199
+ /**
200
+ * Add a phone to the cadre with relay support.
201
+ * Use when both nodes are NAT'd (phone-to-phone).
202
+ */
203
+ addPhoneWithRelay(phonePeerId: string): Promise<DroneInitResult>;
204
+ /**
205
+ * Encode an invite for out-of-band delivery (QR, link, etc.).
206
+ */
207
+ encodeInvite(invite: CadreInvite): string;
208
+ /**
209
+ * Decode an invite from base64url encoding.
210
+ */
211
+ decodeInvite(encoded: string): CadreInvite;
212
+ /**
213
+ * Dial an authority from an invite (for phone joining via invite).
214
+ */
215
+ dialInvite(invite: CadreInvite): Promise<void>;
216
+ /**
217
+ * Initialize the strand solicitation service.
218
+ * This enables forming strands with other parties via open invitations.
219
+ *
220
+ * @param options - Configuration for the solicitation service
221
+ */
222
+ initializeStrandSolicitation(options?: StrandSolicitationServiceOptions): void;
223
+ /**
224
+ * Get the strand solicitation service (for advanced use)
225
+ */
226
+ getStrandSolicitationService(): StrandSolicitationService | null;
227
+ /**
228
+ * Create an open invitation for others to form strands with this party.
229
+ *
230
+ * @param sAppId - The sApp to use for formed strands
231
+ * @param expirationMs - How long the invitation is valid (ms from now)
232
+ * @returns The open invitation to share out-of-band
233
+ */
234
+ createOpenInvitation(sAppId: string, expirationMs?: number): Promise<OpenInvitation>;
235
+ /**
236
+ * Form a strand with a responder via an open invitation.
237
+ *
238
+ * @param invitation - The open invitation received out-of-band
239
+ * @param disclosure - Identity/context information to share with the responder
240
+ * @returns The member key and strand info if successful
241
+ */
242
+ formStrand(invitation: OpenInvitation, disclosure?: StrandFormationDisclosure): Promise<FormStrandResult>;
243
+ /**
244
+ * Encode an open invitation for out-of-band delivery (QR, link, etc.).
245
+ */
246
+ encodeInvitation(invitation: OpenInvitation): string;
247
+ /**
248
+ * Decode an open invitation from base64url encoding.
249
+ */
250
+ decodeInvitation(encoded: string): OpenInvitation;
251
+ }
252
+ export {};