@xnetjs/sync 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.
@@ -0,0 +1,3203 @@
1
+ import { DID, ContentId, PolicyEvaluator, AuthDecision } from '@xnetjs/core';
2
+ import { UnifiedSignature, SignatureWire, SecurityLevel, EncryptedData, WrappedKey } from '@xnetjs/crypto';
3
+ import { PQKeyRegistry, HybridKeyBundle } from '@xnetjs/identity';
4
+
5
+ /**
6
+ * Lamport clock utilities for total ordering in distributed systems.
7
+ *
8
+ * Lamport timestamps provide a simple, single-integer logical clock that:
9
+ * - Guarantees total ordering of events (with tie-breaker)
10
+ * - Requires no coordination between nodes
11
+ * - Is trivial to merge (max + 1)
12
+ *
13
+ * Combined with author DID as tie-breaker, this gives deterministic
14
+ * ordering across all nodes without the complexity of vector clocks.
15
+ */
16
+
17
+ /**
18
+ * A Lamport timestamp with author for deterministic tie-breaking.
19
+ *
20
+ * Two changes are ordered by:
21
+ * 1. Lamport time (lower = earlier)
22
+ * 2. Author DID string comparison (deterministic tie-breaker)
23
+ */
24
+ interface LamportTimestamp {
25
+ /** Logical time - increments on each change */
26
+ time: number;
27
+ /** Author DID - used for deterministic tie-breaking */
28
+ author: DID;
29
+ }
30
+ /**
31
+ * A Lamport clock that tracks the current logical time.
32
+ * Each author maintains their own clock instance.
33
+ */
34
+ interface LamportClock {
35
+ /** Current logical time */
36
+ time: number;
37
+ /** The author's DID */
38
+ author: DID;
39
+ }
40
+ /**
41
+ * Create a new Lamport clock for an author.
42
+ * Starts at time 0; first tick will produce time 1.
43
+ */
44
+ declare function createLamportClock(author: DID): LamportClock;
45
+ /**
46
+ * Tick the clock and return a new timestamp.
47
+ * This should be called when creating a new change.
48
+ *
49
+ * @param clock - The clock to tick
50
+ * @returns A tuple of [newClock, timestamp]
51
+ */
52
+ declare function tick(clock: LamportClock): [LamportClock, LamportTimestamp];
53
+ /**
54
+ * Update the clock after receiving a change from another node.
55
+ * Sets our time to max(ourTime, receivedTime) so next tick is greater.
56
+ *
57
+ * @param clock - Our local clock
58
+ * @param receivedTime - The Lamport time from the received change
59
+ * @returns Updated clock
60
+ */
61
+ declare function receive(clock: LamportClock, receivedTime: number): LamportClock;
62
+ /**
63
+ * Compare two Lamport timestamps for ordering.
64
+ *
65
+ * @returns
66
+ * -1 if a < b (a happened before b)
67
+ * 1 if a > b (a happened after b)
68
+ * 0 if a === b (same timestamp - should be rare)
69
+ */
70
+ declare function compareLamportTimestamps(a: LamportTimestamp, b: LamportTimestamp): -1 | 0 | 1;
71
+ /**
72
+ * Check if timestamp a is strictly before timestamp b.
73
+ */
74
+ declare function isBefore(a: LamportTimestamp, b: LamportTimestamp): boolean;
75
+ /**
76
+ * Check if timestamp a is strictly after timestamp b.
77
+ */
78
+ declare function isAfter(a: LamportTimestamp, b: LamportTimestamp): boolean;
79
+ /**
80
+ * Serialize a Lamport timestamp to a string for storage/sorting.
81
+ * Format: {time-padded-16-digits}-{author}
82
+ *
83
+ * The padding ensures lexicographic string sorting matches numeric sorting.
84
+ */
85
+ declare function serializeTimestamp(ts: LamportTimestamp): string;
86
+ /**
87
+ * Parse a serialized Lamport timestamp.
88
+ */
89
+ declare function parseTimestamp(serialized: string): LamportTimestamp;
90
+ /**
91
+ * Get the maximum Lamport time from a list of timestamps.
92
+ * Useful for initializing a clock after loading existing changes.
93
+ */
94
+ declare function maxTime(timestamps: LamportTimestamp[]): number;
95
+
96
+ /**
97
+ * Change types for xNet sync primitives
98
+ *
99
+ * Change<T> is the universal unit of sync for both Yjs (CRDT) and
100
+ * event-sourced (records) data. It replaces SignedUpdate and RecordOperation
101
+ * with a single, generic type.
102
+ */
103
+
104
+ /**
105
+ * Current protocol version for Change<T>.
106
+ *
107
+ * Version history:
108
+ * - 3: Multi-level cryptography with hybrid signatures (Ed25519 + ML-DSA)
109
+ * - 2: V2 compact format with abbreviated field names
110
+ * - 1: Initial versioned protocol (adds protocolVersion field)
111
+ * - 0/undefined: Legacy unversioned changes (backward compat)
112
+ */
113
+ declare const CURRENT_PROTOCOL_VERSION = 3;
114
+ /**
115
+ * A signed change with chain linkage and Lamport ordering.
116
+ * Generic T allows different payload types for different use cases:
117
+ * - YjsUpdate for rich text documents
118
+ * - RecordPayload for database operations
119
+ *
120
+ * Changes can optionally be part of a transaction batch, which groups
121
+ * related changes that should be applied atomically. This is important for:
122
+ * - Multi-node operations (move task between projects)
123
+ * - Undo/redo grouping
124
+ * - Audit trails ("user did X" as a single action)
125
+ * - Future blockchain integration (batch = transaction)
126
+ */
127
+ interface Change<T = unknown> {
128
+ /**
129
+ * Protocol version of this change.
130
+ * - undefined: Legacy change (protocol v0, backward compat)
131
+ * - 1+: Versioned change with protocol version
132
+ *
133
+ * Used for version negotiation and migration paths.
134
+ */
135
+ protocolVersion?: number;
136
+ /** Unique change ID (nanoid) */
137
+ id: string;
138
+ /** Change type (e.g., 'yjs-update', 'create-item', 'update-item') */
139
+ type: string;
140
+ /** The actual change data */
141
+ payload: T;
142
+ /** Content-addressed hash of this change */
143
+ hash: ContentId;
144
+ /** Hash of the previous change in the chain (null for first) */
145
+ parentHash: ContentId | null;
146
+ /** DID of the author */
147
+ authorDID: DID;
148
+ /** Ed25519 signature of the hash */
149
+ signature: Uint8Array;
150
+ /** Wall clock timestamp (milliseconds) - for display, not ordering */
151
+ wallTime: number;
152
+ /** Lamport timestamp for ordering */
153
+ lamport: LamportTimestamp;
154
+ /**
155
+ * Groups changes that should be treated as a single atomic operation.
156
+ * All changes with the same batchId were created in one transaction.
157
+ * For undo/redo, the entire batch should be undone/redone together.
158
+ */
159
+ batchId?: string;
160
+ /**
161
+ * Position of this change within the batch (0-indexed).
162
+ * Ensures deterministic ordering when replaying.
163
+ */
164
+ batchIndex?: number;
165
+ /**
166
+ * Total number of changes in the batch.
167
+ * Receivers can wait for all changes before committing.
168
+ */
169
+ batchSize?: number;
170
+ }
171
+ /**
172
+ * An unsigned change (before hash and signature are computed).
173
+ * Used as input to signChange().
174
+ */
175
+ interface UnsignedChange<T = unknown> {
176
+ protocolVersion?: number;
177
+ id: string;
178
+ type: string;
179
+ payload: T;
180
+ parentHash: ContentId | null;
181
+ authorDID: DID;
182
+ wallTime: number;
183
+ lamport: LamportTimestamp;
184
+ batchId?: string;
185
+ batchIndex?: number;
186
+ batchSize?: number;
187
+ }
188
+ /**
189
+ * Options for creating a change
190
+ */
191
+ interface CreateChangeOptions<T> {
192
+ id: string;
193
+ type: string;
194
+ payload: T;
195
+ parentHash: ContentId | null;
196
+ authorDID: DID;
197
+ lamport: LamportTimestamp;
198
+ wallTime?: number;
199
+ batchId?: string;
200
+ batchIndex?: number;
201
+ batchSize?: number;
202
+ }
203
+ /**
204
+ * Create an unsigned change from options
205
+ */
206
+ declare function createUnsignedChange<T>(options: CreateChangeOptions<T>): UnsignedChange<T>;
207
+ /**
208
+ * Generate a unique batch ID for grouping changes in a transaction.
209
+ */
210
+ declare function createBatchId(): string;
211
+ /**
212
+ * Compute the hash of an unsigned change.
213
+ * The hash is computed over a canonical JSON representation with sorted keys.
214
+ *
215
+ * Version handling:
216
+ * - protocolVersion 0/undefined (legacy): hash without protocolVersion field
217
+ * - protocolVersion 1+: include protocolVersion in hash computation
218
+ */
219
+ declare function computeChangeHash<T>(unsigned: UnsignedChange<T>): ContentId;
220
+ /**
221
+ * Sign an unsigned change, producing a fully signed Change<T>.
222
+ *
223
+ * @param unsigned - The change to sign
224
+ * @param signingKey - Ed25519 private key (32 bytes)
225
+ * @returns Signed change with hash and signature
226
+ */
227
+ declare function signChange<T>(unsigned: UnsignedChange<T>, signingKey: Uint8Array): Change<T>;
228
+ /**
229
+ * Verify a change's signature against a public key.
230
+ *
231
+ * Protocol version handling:
232
+ * - Accepts changes with protocolVersion <= CURRENT_PROTOCOL_VERSION
233
+ * - Logs warning for future versions but still attempts verification
234
+ * - Never rejects based on version alone (graceful degradation)
235
+ *
236
+ * @param change - The change to verify
237
+ * @param publicKey - Ed25519 public key (32 bytes)
238
+ * @returns true if the signature is valid
239
+ */
240
+ declare function verifyChange<T>(change: Change<T>, publicKey: Uint8Array): boolean;
241
+ /**
242
+ * Verify that a change's hash is correct (not tampered).
243
+ * This re-computes the hash from the change data and compares.
244
+ *
245
+ * Handles both legacy (no protocolVersion) and versioned changes.
246
+ */
247
+ declare function verifyChangeHash<T>(change: Change<T>): boolean;
248
+ /**
249
+ * Create a unique change ID.
250
+ * Uses crypto.randomUUID for uniqueness.
251
+ */
252
+ declare function createChangeId(): string;
253
+
254
+ /**
255
+ * Hash chain utilities for managing linked changes.
256
+ *
257
+ * Changes form a hash chain where each change references its parent via
258
+ * parentHash. This enables:
259
+ * - Detecting forks (two changes with the same parent)
260
+ * - Validating chain integrity
261
+ * - Finding chain heads (latest changes)
262
+ */
263
+
264
+ /**
265
+ * Result of validating a chain of changes
266
+ */
267
+ interface ChainValidationResult {
268
+ /** Whether the chain is valid */
269
+ valid: boolean;
270
+ /** Error message if invalid */
271
+ error?: string;
272
+ /** Whether a fork was detected */
273
+ forkDetected?: boolean;
274
+ /** Hash where the fork occurred */
275
+ forkPoint?: ContentId;
276
+ }
277
+ /**
278
+ * Information about a fork in the chain
279
+ */
280
+ interface Fork<T = unknown> {
281
+ /** The common ancestor hash where the fork occurred */
282
+ commonAncestor: ContentId;
283
+ /** Changes in the first branch (by Lamport timestamp) */
284
+ branch1: Change<T>[];
285
+ /** Changes in the second branch (by Lamport timestamp) */
286
+ branch2: Change<T>[];
287
+ }
288
+ /**
289
+ * Validate that a list of changes forms a valid hash chain.
290
+ * Checks:
291
+ * - All hashes are computed correctly (not tampered)
292
+ * - Parent references exist in the chain (or are null for roots)
293
+ *
294
+ * @param changes - The changes to validate
295
+ * @returns Validation result
296
+ */
297
+ declare function validateChain<T>(changes: Change<T>[]): ChainValidationResult;
298
+ /**
299
+ * Detect if there are any forks in the chain.
300
+ * A fork occurs when two different changes have the same parent.
301
+ *
302
+ * @param changes - The changes to check
303
+ * @returns Fork detection result
304
+ */
305
+ declare function detectFork<T>(changes: Change<T>[]): {
306
+ hasFork: boolean;
307
+ forkPoints: ContentId[];
308
+ };
309
+ /**
310
+ * Get the head(s) of the chain - changes that are not parents of any other change.
311
+ * Multiple heads indicate a fork that needs resolution.
312
+ *
313
+ * @param changes - The changes to analyze
314
+ * @returns Array of head changes
315
+ */
316
+ declare function getChainHeads<T>(changes: Change<T>[]): Change<T>[];
317
+ /**
318
+ * Get the root(s) of the chain - changes with no parent (parentHash is null).
319
+ *
320
+ * @param changes - The changes to analyze
321
+ * @returns Array of root changes
322
+ */
323
+ declare function getChainRoots<T>(changes: Change<T>[]): Change<T>[];
324
+ /**
325
+ * Get the full ancestry of a change (all changes leading up to it).
326
+ *
327
+ * @param change - The change to get ancestry for
328
+ * @param allChanges - All available changes
329
+ * @returns Array of ancestor changes, from oldest to newest (excluding the input change)
330
+ */
331
+ declare function getAncestry<T>(change: Change<T>, allChanges: Change<T>[]): Change<T>[];
332
+ /**
333
+ * Find the common ancestor of two changes.
334
+ * Returns null if they have no common ancestor.
335
+ *
336
+ * @param a - First change
337
+ * @param b - Second change
338
+ * @param allChanges - All available changes
339
+ * @returns The common ancestor change, or null
340
+ */
341
+ declare function findCommonAncestor<T>(a: Change<T>, b: Change<T>, allChanges: Change<T>[]): Change<T> | null;
342
+ /**
343
+ * Get detailed fork information.
344
+ *
345
+ * @param changes - The changes to analyze
346
+ * @returns Array of Fork objects describing each fork
347
+ */
348
+ declare function getForks<T>(changes: Change<T>[]): Fork<T>[];
349
+ /**
350
+ * Sort changes in topological order (parents before children).
351
+ * Within the same level, sort by Lamport timestamp.
352
+ *
353
+ * @param changes - The changes to sort
354
+ * @returns Sorted changes array
355
+ */
356
+ declare function topologicalSort<T>(changes: Change<T>[]): Change<T>[];
357
+
358
+ /**
359
+ * Feature flag system for xNet protocol capabilities.
360
+ *
361
+ * Features are capabilities that can be negotiated between peers.
362
+ * Each feature has a protocol version where it was introduced, and may
363
+ * have dependencies on other features.
364
+ *
365
+ * Usage:
366
+ * ```typescript
367
+ * import { FEATURES, getEnabledFeatures, isFeatureEnabled } from '@xnetjs/sync'
368
+ *
369
+ * // Get features for a protocol version
370
+ * const features = getEnabledFeatures(2) // ['node-changes', 'yjs-updates', ...]
371
+ *
372
+ * // Check if a feature is enabled in a negotiated set
373
+ * if (isFeatureEnabled('schema-versioning', negotiatedFeatures)) {
374
+ * // Use schema versioning
375
+ * }
376
+ * ```
377
+ */
378
+ /**
379
+ * All known feature flag names.
380
+ * Defined as a union type to allow forward-references in FeatureConfig.
381
+ */
382
+ type FeatureFlag = 'node-changes' | 'yjs-updates' | 'signed-yjs-envelopes' | 'batch-changes' | 'lamport-ordering' | 'hash-chains' | 'schema-versioning' | 'capability-negotiation' | 'peer-scoring' | 'schema-lenses' | 'unknown-preservation' | 'schema-inheritance' | 'federated-queries' | 'compressed-payloads';
383
+ /**
384
+ * Feature configuration for a protocol capability.
385
+ */
386
+ interface FeatureConfig {
387
+ /** Protocol version when this feature was introduced */
388
+ since: number;
389
+ /** Whether this feature is required (cannot be disabled) */
390
+ required: boolean;
391
+ /** Human-readable description */
392
+ description: string;
393
+ /** Features this one depends on (must be enabled if this is enabled) */
394
+ requires?: FeatureFlag[];
395
+ /** Features that conflict with this one (cannot both be enabled) */
396
+ conflicts?: FeatureFlag[];
397
+ }
398
+ /**
399
+ * Registry of all supported features.
400
+ *
401
+ * Features are organized by when they were introduced:
402
+ * - Protocol v1: Core sync primitives
403
+ * - Protocol v2: Schema versioning and capability negotiation
404
+ * - Protocol v3+: Future extensions
405
+ */
406
+ declare const FEATURES: Record<FeatureFlag, FeatureConfig>;
407
+ /**
408
+ * Array of all feature flag names.
409
+ */
410
+ declare const ALL_FEATURES: FeatureFlag[];
411
+ /**
412
+ * Get all features enabled at a given protocol version.
413
+ *
414
+ * @param protocolVersion - The protocol version to check
415
+ * @returns Array of feature flags enabled at this version
416
+ *
417
+ * @example
418
+ * ```typescript
419
+ * const v1Features = getEnabledFeatures(1)
420
+ * // ['node-changes', 'yjs-updates', 'signed-yjs-envelopes', ...]
421
+ *
422
+ * const v2Features = getEnabledFeatures(2)
423
+ * // [...v1Features, 'schema-versioning', 'capability-negotiation', ...]
424
+ * ```
425
+ */
426
+ declare function getEnabledFeatures(protocolVersion: number): FeatureFlag[];
427
+ /**
428
+ * Check if a feature is enabled in a negotiated feature set.
429
+ *
430
+ * @param feature - The feature to check
431
+ * @param enabledFeatures - The set of enabled features (from negotiation)
432
+ * @returns true if the feature is in the enabled set
433
+ *
434
+ * @example
435
+ * ```typescript
436
+ * const negotiated = ['node-changes', 'yjs-updates', 'schema-versioning']
437
+ * isFeatureEnabled('schema-versioning', negotiated) // true
438
+ * isFeatureEnabled('federated-queries', negotiated) // false
439
+ * ```
440
+ */
441
+ declare function isFeatureEnabled(feature: FeatureFlag, enabledFeatures: readonly FeatureFlag[] | readonly string[]): boolean;
442
+ /**
443
+ * Get required features for a protocol version.
444
+ * These features cannot be disabled during negotiation.
445
+ *
446
+ * @param protocolVersion - The protocol version to check
447
+ * @returns Array of required feature flags
448
+ */
449
+ declare function getRequiredFeatures(protocolVersion: number): FeatureFlag[];
450
+ /**
451
+ * Get optional features for a protocol version.
452
+ * These features can be enabled or disabled during negotiation.
453
+ *
454
+ * @param protocolVersion - The protocol version to check
455
+ * @returns Array of optional feature flags
456
+ */
457
+ declare function getOptionalFeatures(protocolVersion: number): FeatureFlag[];
458
+ /**
459
+ * Get the minimum protocol version required for a feature.
460
+ *
461
+ * @param feature - The feature to check
462
+ * @returns The protocol version when this feature was introduced
463
+ */
464
+ declare function getFeatureVersion(feature: FeatureFlag): number;
465
+ /**
466
+ * Check if a feature is available at a given protocol version.
467
+ *
468
+ * @param feature - The feature to check
469
+ * @param protocolVersion - The protocol version
470
+ * @returns true if the feature is available at this version
471
+ */
472
+ declare function isFeatureAvailable(feature: FeatureFlag, protocolVersion: number): boolean;
473
+ /**
474
+ * Result of validating a feature set.
475
+ */
476
+ interface FeatureValidationResult {
477
+ valid: boolean;
478
+ errors: FeatureValidationError[];
479
+ warnings: FeatureValidationWarning[];
480
+ }
481
+ interface FeatureValidationError {
482
+ type: 'missing-dependency' | 'conflict' | 'version-mismatch' | 'missing-required';
483
+ feature: FeatureFlag;
484
+ message: string;
485
+ relatedFeature?: FeatureFlag;
486
+ }
487
+ interface FeatureValidationWarning {
488
+ type: 'deprecated' | 'experimental';
489
+ feature: FeatureFlag;
490
+ message: string;
491
+ }
492
+ /**
493
+ * Get the dependencies of a feature (features it requires).
494
+ *
495
+ * @param feature - The feature to check
496
+ * @returns Array of required features, or empty array if none
497
+ */
498
+ declare function getFeatureDependencies(feature: FeatureFlag): FeatureFlag[];
499
+ /**
500
+ * Get features that conflict with a given feature.
501
+ *
502
+ * @param feature - The feature to check
503
+ * @returns Array of conflicting features, or empty array if none
504
+ */
505
+ declare function getFeatureConflicts(feature: FeatureFlag): FeatureFlag[];
506
+ /**
507
+ * Get all transitive dependencies of a feature (recursive).
508
+ *
509
+ * @param feature - The feature to check
510
+ * @param visited - Set of already-visited features (for cycle detection)
511
+ * @returns Array of all required features (direct and transitive)
512
+ */
513
+ declare function getAllDependencies(feature: FeatureFlag, visited?: Set<FeatureFlag>): FeatureFlag[];
514
+ /**
515
+ * Validate a set of features for consistency.
516
+ *
517
+ * Checks for:
518
+ * - Missing required dependencies
519
+ * - Conflicting features
520
+ * - Required features that are missing
521
+ * - Version compatibility issues
522
+ *
523
+ * @param features - Array of features to validate
524
+ * @param protocolVersion - The protocol version context
525
+ * @returns Validation result with errors and warnings
526
+ */
527
+ declare function validateFeatureSet(features: readonly FeatureFlag[], protocolVersion?: number): FeatureValidationResult;
528
+ /**
529
+ * Compute the intersection of two feature sets.
530
+ * Used during negotiation to find common features.
531
+ *
532
+ * @param local - Local peer's features
533
+ * @param remote - Remote peer's features
534
+ * @returns Features present in both sets
535
+ */
536
+ declare function intersectFeatures(local: readonly FeatureFlag[], remote: readonly FeatureFlag[] | readonly string[]): FeatureFlag[];
537
+ /**
538
+ * Compute the difference between two feature sets.
539
+ * Returns features in `a` that are not in `b`.
540
+ *
541
+ * @param a - First feature set
542
+ * @param b - Second feature set
543
+ * @returns Features in a but not in b
544
+ */
545
+ declare function diffFeatures(a: readonly FeatureFlag[], b: readonly FeatureFlag[] | readonly string[]): FeatureFlag[];
546
+ /**
547
+ * Add all dependencies to a feature set (closure).
548
+ * Ensures the set is valid by including all required dependencies.
549
+ *
550
+ * @param features - Initial feature set
551
+ * @returns Feature set with all dependencies included
552
+ */
553
+ declare function addDependencies(features: readonly FeatureFlag[]): FeatureFlag[];
554
+
555
+ /**
556
+ * Version negotiation protocol for xNet peers.
557
+ *
558
+ * When two peers connect, they exchange capability information and
559
+ * negotiate a common set of features they both support. This allows:
560
+ * - Older clients to work with newer servers (backward compatibility)
561
+ * - Newer clients to work with older servers (graceful degradation)
562
+ * - Feature-specific behavior based on negotiated capabilities
563
+ *
564
+ * Usage:
565
+ * ```typescript
566
+ * import { VersionNegotiator, createLocalCapabilities } from '@xnetjs/sync'
567
+ *
568
+ * const negotiator = new VersionNegotiator()
569
+ * const local = createLocalCapabilities('did:key:z...', ['node-changes', 'yjs-updates'])
570
+ *
571
+ * // On receiving remote capabilities:
572
+ * const session = negotiator.negotiate(local, remoteCapabilities)
573
+ * if (session.success) {
574
+ * if (session.canUse('schema-versioning')) {
575
+ * // Use schema versioning
576
+ * }
577
+ * }
578
+ * ```
579
+ */
580
+
581
+ /**
582
+ * Capabilities advertised by a peer during connection.
583
+ */
584
+ interface PeerCapabilities {
585
+ /** Peer's DID */
586
+ peerId: string;
587
+ /** Maximum protocol version supported */
588
+ protocolVersion: number;
589
+ /** Minimum protocol version supported */
590
+ minProtocolVersion: number;
591
+ /** Features enabled on this peer */
592
+ features: FeatureFlag[];
593
+ /** Package version string (e.g., '0.5.0') */
594
+ packageVersion: string;
595
+ /** Schema IRIs this peer understands (optional) */
596
+ schemas?: string[];
597
+ }
598
+ /**
599
+ * Result of a successful negotiation.
600
+ */
601
+ interface NegotiatedSession {
602
+ success: true;
603
+ /** Remote peer's ID */
604
+ peerId: string;
605
+ /** Agreed protocol version (highest common version) */
606
+ agreedVersion: number;
607
+ /** Features both peers support */
608
+ commonFeatures: FeatureFlag[];
609
+ /** Warnings about degraded functionality */
610
+ warnings: NegotiationWarning[];
611
+ /**
612
+ * Check if a feature can be used in this session.
613
+ */
614
+ canUse(feature: FeatureFlag): boolean;
615
+ }
616
+ /**
617
+ * Result of a failed negotiation.
618
+ */
619
+ interface NegotiationFailure {
620
+ success: false;
621
+ /** Error code */
622
+ error: 'incompatible-versions' | 'missing-required-features' | 'invalid-capabilities';
623
+ /** Human-readable message */
624
+ message: string;
625
+ /** Local protocol version */
626
+ localVersion: number;
627
+ /** Remote protocol version */
628
+ remoteVersion: number;
629
+ /** Suggestion for resolving the issue */
630
+ suggestion: 'upgrade-client' | 'upgrade-hub' | 'upgrade-both' | 'contact-support';
631
+ }
632
+ /**
633
+ * Warning about potential issues in a negotiated session.
634
+ */
635
+ interface NegotiationWarning {
636
+ type: 'degraded-features' | 'version-mismatch' | 'unknown-features';
637
+ message: string;
638
+ /** Features affected by this warning */
639
+ affectedFeatures?: FeatureFlag[];
640
+ }
641
+ /**
642
+ * Result type for negotiation (success or failure).
643
+ */
644
+ type NegotiationResult = NegotiatedSession | NegotiationFailure;
645
+ /**
646
+ * Create local capabilities for negotiation.
647
+ *
648
+ * @param peerId - Local peer's DID
649
+ * @param features - Features to advertise (defaults to all enabled at current version)
650
+ * @param options - Additional options
651
+ * @returns PeerCapabilities for this peer
652
+ */
653
+ declare function createLocalCapabilities(peerId: string, features?: FeatureFlag[], options?: {
654
+ packageVersion?: string;
655
+ minProtocolVersion?: number;
656
+ schemas?: string[];
657
+ }): PeerCapabilities;
658
+ /**
659
+ * Parse capabilities from a handshake message.
660
+ * Handles missing or malformed fields gracefully.
661
+ *
662
+ * @param message - Raw handshake message
663
+ * @returns Parsed capabilities or null if invalid
664
+ */
665
+ declare function parseCapabilities(message: unknown): PeerCapabilities | null;
666
+ /**
667
+ * Handles version negotiation between peers.
668
+ *
669
+ * The negotiation process:
670
+ * 1. Find highest common protocol version
671
+ * 2. Check version ranges overlap
672
+ * 3. Find common feature set
673
+ * 4. Verify required features are present
674
+ * 5. Generate warnings for degraded functionality
675
+ */
676
+ declare class VersionNegotiator {
677
+ /**
678
+ * Negotiate capabilities between local and remote peers.
679
+ *
680
+ * @param local - Local peer's capabilities
681
+ * @param remote - Remote peer's capabilities
682
+ * @returns Negotiated session or failure result
683
+ */
684
+ negotiate(local: PeerCapabilities, remote: PeerCapabilities): NegotiationResult;
685
+ /**
686
+ * Validate that a set of capabilities is well-formed.
687
+ *
688
+ * @param capabilities - Capabilities to validate
689
+ * @returns Validation result with errors if any
690
+ */
691
+ validateCapabilities(capabilities: PeerCapabilities): {
692
+ valid: boolean;
693
+ errors: string[];
694
+ };
695
+ /**
696
+ * Create a failure result with appropriate suggestion.
697
+ */
698
+ private createFailure;
699
+ /**
700
+ * Generate warnings about degraded functionality.
701
+ */
702
+ private generateWarnings;
703
+ }
704
+ /**
705
+ * Default negotiator instance for convenience.
706
+ */
707
+ declare const defaultNegotiator: VersionNegotiator;
708
+
709
+ /**
710
+ * Sync provider interfaces and types.
711
+ *
712
+ * A SyncProvider is responsible for connecting to the sync network,
713
+ * broadcasting changes to peers, and receiving changes from peers.
714
+ * Different implementations can use different transports (WebRTC, WebSocket, etc.)
715
+ */
716
+
717
+ /**
718
+ * Connection status of a sync provider.
719
+ */
720
+ type SyncStatus = 'disconnected' | 'connecting' | 'synced' | 'syncing' | 'error';
721
+ /**
722
+ * Information about a connected peer.
723
+ */
724
+ interface PeerInfo {
725
+ /** Unique peer identifier */
726
+ id: string;
727
+ /** Human-readable name (if available) */
728
+ name?: string;
729
+ /** When the peer connected */
730
+ connectedAt: number;
731
+ /** Last activity timestamp */
732
+ lastSeen: number;
733
+ /** Peer's advertised capabilities (after handshake) */
734
+ capabilities?: PeerCapabilities;
735
+ /** Negotiated session with this peer */
736
+ negotiatedSession?: NegotiatedSession;
737
+ }
738
+ /**
739
+ * Events emitted by sync providers.
740
+ */
741
+ interface SyncProviderEvents<T = unknown> {
742
+ /** Fired when sync status changes */
743
+ 'status-change': (status: SyncStatus) => void;
744
+ /** Fired when a single change is received from a peer */
745
+ 'change-received': (change: Change<T>, peerId: string) => void;
746
+ /** Fired when multiple changes are synced */
747
+ 'changes-synced': (changes: Change<T>[]) => void;
748
+ /** Fired when a peer connects */
749
+ 'peer-connected': (peer: PeerInfo) => void;
750
+ /** Fired when a peer disconnects */
751
+ 'peer-disconnected': (peerId: string) => void;
752
+ /** Fired on error */
753
+ error: (error: Error) => void;
754
+ /**
755
+ * Fired when a change with an unknown type is received.
756
+ * The change is still stored in the change log for forward compatibility,
757
+ * but cannot be processed by the current version.
758
+ */
759
+ 'unknown-change-type': (change: Change<unknown>, peerId: string) => void;
760
+ /**
761
+ * Fired when capability negotiation completes with a peer.
762
+ * Includes the negotiated session with common features.
763
+ */
764
+ 'negotiation-complete': (peerId: string, session: NegotiatedSession) => void;
765
+ /**
766
+ * Fired when capability negotiation fails with a peer.
767
+ * The peer will be disconnected after this event.
768
+ */
769
+ 'negotiation-failed': (peerId: string, error: string, suggestion: string) => void;
770
+ /**
771
+ * Fired when operating with degraded features due to peer compatibility.
772
+ * Includes warnings about unavailable features.
773
+ */
774
+ 'capability-degraded': (peerId: string, warnings: string[]) => void;
775
+ }
776
+ /**
777
+ * Event listener type for type-safe event handling.
778
+ */
779
+ type SyncEventListener<T, E extends keyof SyncProviderEvents<T>> = SyncProviderEvents<T>[E];
780
+ /**
781
+ * Base interface for all sync providers.
782
+ *
783
+ * Implementations include:
784
+ * - y-webrtc provider for Yjs documents
785
+ * - Custom provider for event-sourced records
786
+ * - Hybrid providers that support both
787
+ */
788
+ interface SyncProvider<T = unknown> {
789
+ /** Current sync status */
790
+ readonly status: SyncStatus;
791
+ /** List of connected peer IDs */
792
+ readonly peers: string[];
793
+ /** Detailed information about connected peers */
794
+ readonly peerInfo: Map<string, PeerInfo>;
795
+ /** Local peer's capabilities */
796
+ readonly localCapabilities: PeerCapabilities;
797
+ /**
798
+ * Check if a feature can be used with a specific peer.
799
+ * Returns false if not negotiated or feature unavailable.
800
+ */
801
+ canUseFeature(peerId: string, feature: FeatureFlag): boolean;
802
+ /**
803
+ * Get the negotiated session for a peer.
804
+ * Returns undefined if not yet negotiated.
805
+ */
806
+ getNegotiatedSession(peerId: string): NegotiatedSession | undefined;
807
+ /**
808
+ * Connect to the sync network.
809
+ * This may involve signaling servers, DHT lookups, etc.
810
+ */
811
+ connect(): Promise<void>;
812
+ /**
813
+ * Disconnect from the sync network.
814
+ * Closes all peer connections gracefully.
815
+ */
816
+ disconnect(): Promise<void>;
817
+ /**
818
+ * Broadcast a change to all connected peers.
819
+ *
820
+ * @param change - The change to broadcast
821
+ */
822
+ broadcast(change: Change<T>): Promise<void>;
823
+ /**
824
+ * Request changes from a specific peer.
825
+ * Used for initial sync or catching up.
826
+ *
827
+ * @param peerId - The peer to request from
828
+ * @param since - Optional hash to request changes since
829
+ * @returns Array of changes
830
+ */
831
+ requestChanges(peerId: string, since?: string): Promise<Change<T>[]>;
832
+ /**
833
+ * Request changes from all connected peers.
834
+ * Useful for catching up after reconnection.
835
+ *
836
+ * @param since - Optional hash to request changes since
837
+ * @returns Array of unique changes (deduplicated)
838
+ */
839
+ requestChangesFromAll(since?: string): Promise<Change<T>[]>;
840
+ /**
841
+ * Subscribe to an event.
842
+ *
843
+ * @param event - Event name
844
+ * @param listener - Event listener
845
+ */
846
+ on<E extends keyof SyncProviderEvents<T>>(event: E, listener: SyncProviderEvents<T>[E]): void;
847
+ /**
848
+ * Unsubscribe from an event.
849
+ *
850
+ * @param event - Event name
851
+ * @param listener - Event listener to remove
852
+ */
853
+ off<E extends keyof SyncProviderEvents<T>>(event: E, listener: SyncProviderEvents<T>[E]): void;
854
+ /**
855
+ * Subscribe to an event for a single occurrence.
856
+ *
857
+ * @param event - Event name
858
+ * @param listener - Event listener
859
+ */
860
+ once<E extends keyof SyncProviderEvents<T>>(event: E, listener: SyncProviderEvents<T>[E]): void;
861
+ }
862
+ /**
863
+ * Options for creating a sync provider.
864
+ */
865
+ interface SyncProviderOptions {
866
+ /** Signaling server URL(s) */
867
+ signalingServers?: string[];
868
+ /** Room/topic name for peer discovery */
869
+ room: string;
870
+ /** Connection timeout in milliseconds */
871
+ timeout?: number;
872
+ /** Whether to auto-reconnect on disconnect */
873
+ autoReconnect?: boolean;
874
+ /** Maximum reconnection attempts */
875
+ maxReconnectAttempts?: number;
876
+ /** Reconnection delay in milliseconds */
877
+ reconnectDelay?: number;
878
+ /** Local peer's DID for capability advertisement */
879
+ localDID?: string;
880
+ /** Features to advertise (defaults to all enabled at current protocol version) */
881
+ enabledFeatures?: FeatureFlag[];
882
+ /** Minimum protocol version to accept from peers */
883
+ minProtocolVersion?: number;
884
+ /** Whether to reject peers with incompatible versions (default: false, just warn) */
885
+ strictVersionCheck?: boolean;
886
+ /** Package version string for debugging */
887
+ packageVersion?: string;
888
+ }
889
+ /**
890
+ * Abstract base class for sync providers with common functionality.
891
+ * Implementations can extend this to reduce boilerplate.
892
+ */
893
+ declare abstract class BaseSyncProvider<T = unknown> implements SyncProvider<T> {
894
+ protected _status: SyncStatus;
895
+ protected _peers: Map<string, PeerInfo>;
896
+ protected _listeners: Map<string, Set<(...args: unknown[]) => unknown>>;
897
+ /** Local capabilities for negotiation */
898
+ protected _localCapabilities: PeerCapabilities;
899
+ /** Version negotiator instance */
900
+ protected _negotiator: VersionNegotiator;
901
+ /** Provider options */
902
+ protected _options: SyncProviderOptions;
903
+ constructor(options: SyncProviderOptions);
904
+ get status(): SyncStatus;
905
+ get peers(): string[];
906
+ get peerInfo(): Map<string, PeerInfo>;
907
+ get localCapabilities(): PeerCapabilities;
908
+ /**
909
+ * Check if a feature can be used with a specific peer.
910
+ */
911
+ canUseFeature(peerId: string, feature: FeatureFlag): boolean;
912
+ /**
913
+ * Get the negotiated session for a peer.
914
+ */
915
+ getNegotiatedSession(peerId: string): NegotiatedSession | undefined;
916
+ abstract connect(): Promise<void>;
917
+ abstract disconnect(): Promise<void>;
918
+ abstract broadcast(change: Change<T>): Promise<void>;
919
+ abstract requestChanges(peerId: string, since?: string): Promise<Change<T>[]>;
920
+ requestChangesFromAll(since?: string): Promise<Change<T>[]>;
921
+ on<E extends keyof SyncProviderEvents<T>>(event: E, listener: SyncProviderEvents<T>[E]): void;
922
+ off<E extends keyof SyncProviderEvents<T>>(event: E, listener: SyncProviderEvents<T>[E]): void;
923
+ once<E extends keyof SyncProviderEvents<T>>(event: E, listener: SyncProviderEvents<T>[E]): void;
924
+ protected emit<E extends keyof SyncProviderEvents<T>>(event: E, ...args: Parameters<SyncProviderEvents<T>[E]>): void;
925
+ protected setStatus(status: SyncStatus): void;
926
+ protected addPeer(id: string, name?: string): void;
927
+ protected removePeer(id: string): void;
928
+ protected updatePeerLastSeen(id: string): void;
929
+ /**
930
+ * Negotiate capabilities with a peer.
931
+ * Called when a peer connects and sends their capabilities.
932
+ *
933
+ * @param peerId - The peer's ID
934
+ * @param remoteCapabilities - The peer's advertised capabilities
935
+ * @returns Negotiation result (success or failure)
936
+ */
937
+ protected negotiateWithPeer(peerId: string, remoteCapabilities: PeerCapabilities): NegotiationResult;
938
+ /**
939
+ * Get features available with all connected peers.
940
+ * Returns the intersection of all negotiated feature sets.
941
+ */
942
+ getCommonFeatures(): FeatureFlag[];
943
+ /**
944
+ * Check if a feature can be used with all connected peers.
945
+ */
946
+ canUseFeatureWithAll(feature: FeatureFlag): boolean;
947
+ }
948
+
949
+ /**
950
+ * Signed Yjs Envelopes - Per-update signing and verification for Yjs sync messages
951
+ *
952
+ * Every outgoing Yjs update is wrapped in a signed envelope containing the author's DID
953
+ * and a multi-level signature (Ed25519 and/or ML-DSA-65) over the BLAKE3 hash of the
954
+ * update bytes plus metadata.
955
+ *
956
+ * V2 introduces:
957
+ * - Multi-level signature support (Level 0, 1, 2)
958
+ * - Document ID binding
959
+ * - Wire format with compact JSON encoding
960
+ *
961
+ * V1 is retained for backward compatibility.
962
+ */
963
+
964
+ /**
965
+ * A signed envelope wrapping a Yjs update (V1 format).
966
+ * @deprecated Use SignedYjsEnvelopeV2 for new code
967
+ */
968
+ interface SignedYjsEnvelopeV1 {
969
+ /** Raw Yjs update bytes */
970
+ update: Uint8Array;
971
+ /** Author's DID (did:key:...) */
972
+ authorDID: string;
973
+ /** Ed25519 signature over BLAKE3(update) */
974
+ signature: Uint8Array;
975
+ /** Wall clock timestamp (for ordering/debugging) */
976
+ timestamp: number;
977
+ /** Yjs clientID this author uses in this session */
978
+ clientId: number;
979
+ }
980
+ /**
981
+ * A signed envelope wrapping a Yjs update (V2 format with multi-level signatures).
982
+ */
983
+ interface SignedYjsEnvelopeV2 {
984
+ /** Wire format version */
985
+ v: 2;
986
+ /** Raw Yjs update bytes */
987
+ update: Uint8Array;
988
+ /** Envelope metadata */
989
+ meta: {
990
+ /** Author's DID (did:key:...) */
991
+ authorDID: DID;
992
+ /** Yjs clientID this author uses in this session */
993
+ clientId: number;
994
+ /** Wall clock timestamp (ms since epoch) */
995
+ timestamp: number;
996
+ /** Document ID this update applies to */
997
+ docId: string;
998
+ };
999
+ /** Multi-level signature over BLAKE3(update + meta) */
1000
+ signature: UnifiedSignature;
1001
+ }
1002
+ /**
1003
+ * Wire format for SignedYjsEnvelopeV2 (compact JSON).
1004
+ */
1005
+ interface SignedYjsEnvelopeWire {
1006
+ v: 2;
1007
+ /** Base64-encoded update bytes */
1008
+ u: string;
1009
+ /** Metadata */
1010
+ m: {
1011
+ /** Author DID */
1012
+ a: string;
1013
+ /** Client ID */
1014
+ c: number;
1015
+ /** Timestamp */
1016
+ t: number;
1017
+ /** Document ID */
1018
+ d: string;
1019
+ };
1020
+ /** Signature (wire format) */
1021
+ s: SignatureWire;
1022
+ }
1023
+ /**
1024
+ * Union type for all envelope versions.
1025
+ * Use `isV2Envelope()` to check the version.
1026
+ */
1027
+ type SignedYjsEnvelope = SignedYjsEnvelopeV1 | SignedYjsEnvelopeV2;
1028
+ /**
1029
+ * Result of envelope verification (V1 format).
1030
+ * @deprecated Use EnvelopeVerificationResult for V2
1031
+ */
1032
+ interface EnvelopeVerifyResult {
1033
+ valid: boolean;
1034
+ reason?: 'invalid_signature' | 'did_resolution_failed' | 'update_too_large';
1035
+ }
1036
+ /**
1037
+ * Result of envelope verification (V2 format with details).
1038
+ */
1039
+ interface EnvelopeVerificationResult {
1040
+ valid: boolean;
1041
+ level: SecurityLevel;
1042
+ errors: string[];
1043
+ authorDID: DID;
1044
+ clientId: number;
1045
+ }
1046
+ /**
1047
+ * Options for creating a signed envelope.
1048
+ */
1049
+ interface CreateEnvelopeOptions {
1050
+ /** Security level (default: 0 for Ed25519-only) */
1051
+ level?: SecurityLevel;
1052
+ }
1053
+ /**
1054
+ * Options for envelope verification.
1055
+ */
1056
+ interface VerifyEnvelopeOptions {
1057
+ /** PQ key registry for Level 1/2 verification */
1058
+ registry?: PQKeyRegistry;
1059
+ /** Expected document ID (optional, for extra validation) */
1060
+ expectedDocId?: string;
1061
+ /** Maximum age in ms (optional, for freshness check) */
1062
+ maxAge?: number;
1063
+ /** Minimum security level required */
1064
+ minLevel?: SecurityLevel;
1065
+ /** Verification policy */
1066
+ policy?: 'strict' | 'permissive';
1067
+ }
1068
+ /**
1069
+ * Check if an envelope is V2 format.
1070
+ */
1071
+ declare function isV2Envelope(envelope: SignedYjsEnvelope): envelope is SignedYjsEnvelopeV2;
1072
+ /**
1073
+ * Check if an envelope is V1 format.
1074
+ */
1075
+ declare function isV1Envelope(envelope: SignedYjsEnvelope): envelope is SignedYjsEnvelopeV1;
1076
+ /**
1077
+ * Sign a Yjs update using V1 format (Ed25519 only).
1078
+ *
1079
+ * @deprecated Use signYjsUpdateV2() for new code
1080
+ *
1081
+ * @param update - Raw Yjs update bytes
1082
+ * @param authorDID - Author's DID (did:key:...)
1083
+ * @param privateKey - Ed25519 private key (32 bytes)
1084
+ * @param clientId - Yjs clientID for this session
1085
+ * @returns SignedYjsEnvelopeV1 ready for transmission
1086
+ */
1087
+ declare function signYjsUpdateV1(update: Uint8Array, authorDID: string, privateKey: Uint8Array, clientId: number): SignedYjsEnvelopeV1;
1088
+ /**
1089
+ * Verify a V1 SignedYjsEnvelope.
1090
+ *
1091
+ * @deprecated Use verifyYjsEnvelopeV2() for V2 envelopes
1092
+ */
1093
+ declare function verifyYjsEnvelopeV1(envelope: SignedYjsEnvelopeV1): EnvelopeVerifyResult;
1094
+ /**
1095
+ * Sign a Yjs update using V2 format with multi-level signatures.
1096
+ *
1097
+ * @param update - Raw Yjs update bytes
1098
+ * @param docId - Document ID this update applies to
1099
+ * @param clientId - Yjs clientID for this session
1100
+ * @param keyBundle - Key bundle for signing
1101
+ * @param options - Signing options
1102
+ * @returns SignedYjsEnvelopeV2 ready for transmission
1103
+ *
1104
+ * @example
1105
+ * ```typescript
1106
+ * const envelope = signYjsUpdateV2(update, 'doc-1', doc.clientID, keyBundle, { level: 1 })
1107
+ * ws.send(encode({ type: 'sync-update', room, envelope: serializeYjsEnvelope(envelope) }))
1108
+ * ```
1109
+ */
1110
+ declare function signYjsUpdateV2(update: Uint8Array, docId: string, clientId: number, keyBundle: HybridKeyBundle, options?: CreateEnvelopeOptions): SignedYjsEnvelopeV2;
1111
+ /**
1112
+ * Sign multiple updates in a batch using V2 format.
1113
+ */
1114
+ declare function signYjsUpdateBatch(updates: Uint8Array[], docId: string, clientId: number, keyBundle: HybridKeyBundle, options?: CreateEnvelopeOptions): SignedYjsEnvelopeV2[];
1115
+ /**
1116
+ * Verify a V2 SignedYjsEnvelope with multi-level signature support.
1117
+ *
1118
+ * @param envelope - The envelope to verify
1119
+ * @param options - Verification options
1120
+ * @returns Verification result with detailed errors
1121
+ */
1122
+ declare function verifyYjsEnvelopeV2(envelope: SignedYjsEnvelopeV2, options?: VerifyEnvelopeOptions): Promise<EnvelopeVerificationResult>;
1123
+ /**
1124
+ * Quick verification returning just boolean.
1125
+ */
1126
+ declare function verifyYjsEnvelopeQuick(envelope: SignedYjsEnvelopeV2, options?: VerifyEnvelopeOptions): Promise<boolean>;
1127
+ /**
1128
+ * Serialize V2 envelope to wire format.
1129
+ */
1130
+ declare function serializeYjsEnvelope(envelope: SignedYjsEnvelopeV2): SignedYjsEnvelopeWire;
1131
+ /**
1132
+ * Deserialize V2 envelope from wire format.
1133
+ */
1134
+ declare function deserializeYjsEnvelope(wire: SignedYjsEnvelopeWire): SignedYjsEnvelopeV2;
1135
+ /**
1136
+ * Calculate envelope size in bytes (approximate).
1137
+ */
1138
+ declare function envelopeSize(envelope: SignedYjsEnvelopeV2): number;
1139
+ /**
1140
+ * Sign a Yjs update, creating a SignedYjsEnvelope.
1141
+ *
1142
+ * This is the main API for signing updates. It creates a V1 envelope
1143
+ * for backward compatibility when using simple Ed25519 keys, or a V2
1144
+ * envelope when using a HybridKeyBundle.
1145
+ *
1146
+ * @overload V1 signature (legacy)
1147
+ * @param update - Raw Yjs update bytes
1148
+ * @param authorDID - Author's DID (did:key:...)
1149
+ * @param privateKey - Ed25519 private key (32 bytes)
1150
+ * @param clientId - Yjs clientID for this session
1151
+ */
1152
+ declare function signYjsUpdate(update: Uint8Array, authorDID: string, privateKey: Uint8Array, clientId: number): SignedYjsEnvelopeV1;
1153
+ /**
1154
+ * @overload V2 signature (multi-level)
1155
+ * @param update - Raw Yjs update bytes
1156
+ * @param docId - Document ID this update applies to
1157
+ * @param clientId - Yjs clientID for this session
1158
+ * @param keyBundle - Key bundle for signing
1159
+ * @param options - Signing options
1160
+ */
1161
+ declare function signYjsUpdate(update: Uint8Array, docId: string, clientId: number, keyBundle: HybridKeyBundle, options?: CreateEnvelopeOptions): SignedYjsEnvelopeV2;
1162
+ /**
1163
+ * Verify a SignedYjsEnvelope (auto-detects version).
1164
+ *
1165
+ * For V1 envelopes, returns a simple valid/reason result.
1166
+ * For V2 envelopes, returns a detailed verification result.
1167
+ *
1168
+ * @param envelope - The envelope to verify
1169
+ * @param options - Verification options (V2 only)
1170
+ */
1171
+ declare function verifyYjsEnvelope(envelope: SignedYjsEnvelopeV1): EnvelopeVerifyResult;
1172
+ declare function verifyYjsEnvelope(envelope: SignedYjsEnvelopeV2, options?: VerifyEnvelopeOptions): Promise<EnvelopeVerificationResult>;
1173
+ /**
1174
+ * Type guard to check if a message contains a signed envelope.
1175
+ */
1176
+ declare function hasSignedEnvelope(msg: unknown): msg is {
1177
+ envelope: SignedYjsEnvelope;
1178
+ [key: string]: unknown;
1179
+ };
1180
+ /**
1181
+ * Check if a message is a legacy unsigned sync update.
1182
+ */
1183
+ declare function isLegacyUpdate(msg: unknown): msg is {
1184
+ data: Uint8Array;
1185
+ [key: string]: unknown;
1186
+ };
1187
+
1188
+ /**
1189
+ * Yjs Update Size and Rate Limits
1190
+ *
1191
+ * Constants and utilities for preventing DoS via oversized or high-frequency Yjs updates.
1192
+ */
1193
+ /** Maximum size of a single Yjs update (1MB) */
1194
+ declare const MAX_YJS_UPDATE_SIZE = 1048576;
1195
+ /** Maximum updates per second per connection */
1196
+ declare const MAX_YJS_UPDATES_PER_SECOND = 30;
1197
+ /** Maximum updates per minute per connection (sustained rate) */
1198
+ declare const MAX_YJS_UPDATES_PER_MINUTE = 600;
1199
+ /** Maximum document size (full state, 50MB) */
1200
+ declare const MAX_YJS_DOC_SIZE = 52428800;
1201
+ /** Chunk size for large initial syncs (256KB) */
1202
+ declare const YJS_SYNC_CHUNK_SIZE = 262144;
1203
+ /** Burst allowance above per-second limit */
1204
+ declare const YJS_RATE_BURST_ALLOWANCE = 10;
1205
+ /**
1206
+ * Rate limiter configuration.
1207
+ */
1208
+ interface RateLimiterConfig {
1209
+ /** Maximum updates per second */
1210
+ maxPerSecond: number;
1211
+ /** Maximum updates per minute */
1212
+ maxPerMinute: number;
1213
+ /** Burst allowance above maxPerSecond */
1214
+ burstAllowance: number;
1215
+ }
1216
+ /**
1217
+ * Default rate limiter configuration.
1218
+ */
1219
+ declare const DEFAULT_RATE_LIMITER_CONFIG: RateLimiterConfig;
1220
+ /**
1221
+ * Extended config for rate limiter with cleanup options.
1222
+ */
1223
+ interface YjsRateLimiterOptions extends Partial<RateLimiterConfig> {
1224
+ /** How long until an entry is considered stale (default: 2 minutes) */
1225
+ staleThresholdMs?: number;
1226
+ /** Cleanup interval in ms (default: 30 seconds). Set to 0 to disable auto-cleanup. */
1227
+ cleanupIntervalMs?: number;
1228
+ }
1229
+ /**
1230
+ * Rate limiter for Yjs updates.
1231
+ *
1232
+ * Tracks per-peer update frequency and enforces both per-second and per-minute limits.
1233
+ *
1234
+ * @example
1235
+ * ```typescript
1236
+ * const limiter = new YjsRateLimiter()
1237
+ *
1238
+ * if (!limiter.allow(peerId)) {
1239
+ * // Reject update, rate limit exceeded
1240
+ * return { ok: false, reason: 'rate_exceeded' }
1241
+ * }
1242
+ * ```
1243
+ */
1244
+ declare class YjsRateLimiter {
1245
+ private secondWindows;
1246
+ private minuteWindows;
1247
+ private config;
1248
+ private cleanupInterval;
1249
+ private staleThresholdMs;
1250
+ constructor(options?: YjsRateLimiterOptions);
1251
+ /** Start periodic cleanup of stale entries. */
1252
+ startCleanup(intervalMs?: number): void;
1253
+ /** Stop periodic cleanup. */
1254
+ stopCleanup(): void;
1255
+ /** Remove entries whose windows have expired. Returns count removed. */
1256
+ cleanupStale(): number;
1257
+ /** Get number of tracked peers. */
1258
+ get peerCount(): number;
1259
+ /**
1260
+ * Check if a peer can send another update.
1261
+ *
1262
+ * @param peerId - Peer identifier
1263
+ * @returns true if allowed, false if rate-limited
1264
+ */
1265
+ allow(peerId: string): boolean;
1266
+ /**
1267
+ * Get current rate info for a peer (for debugging/monitoring).
1268
+ */
1269
+ getInfo(peerId: string): {
1270
+ perSecond: number;
1271
+ perMinute: number;
1272
+ } | undefined;
1273
+ /**
1274
+ * Reset state for a disconnected peer.
1275
+ */
1276
+ remove(peerId: string): void;
1277
+ /**
1278
+ * Clear all state.
1279
+ */
1280
+ clear(): void;
1281
+ /**
1282
+ * Stop cleanup and clear all state.
1283
+ */
1284
+ destroy(): void;
1285
+ }
1286
+ /**
1287
+ * Check if an update exceeds the size limit.
1288
+ */
1289
+ declare function isUpdateTooLarge(update: Uint8Array, maxSize?: number): boolean;
1290
+ /**
1291
+ * Check if a document state exceeds the size limit.
1292
+ */
1293
+ declare function isDocumentTooLarge(state: Uint8Array, maxSize?: number): boolean;
1294
+ /**
1295
+ * Calculate number of chunks needed for a large update.
1296
+ */
1297
+ declare function calculateChunkCount(totalSize: number, chunkSize?: number): number;
1298
+ /**
1299
+ * Split a large update into chunks.
1300
+ */
1301
+ declare function chunkUpdate(update: Uint8Array, chunkSize?: number): Uint8Array[];
1302
+ /**
1303
+ * Reassemble chunks into a single update.
1304
+ */
1305
+ declare function reassembleChunks(chunks: Uint8Array[]): Uint8Array;
1306
+
1307
+ /**
1308
+ * Yjs State Integrity - Hash-at-Rest for Yjs document state
1309
+ *
1310
+ * Provides utilities to hash Yjs state on persist and verify on load,
1311
+ * detecting storage-level corruption before it propagates.
1312
+ */
1313
+ /**
1314
+ * Compute a BLAKE3 hash of Yjs state bytes.
1315
+ *
1316
+ * @param state - Yjs state bytes (from Y.encodeStateAsUpdate)
1317
+ * @returns Hex-encoded BLAKE3 hash
1318
+ */
1319
+ declare function hashYjsState(state: Uint8Array): string;
1320
+ /**
1321
+ * Verify that Yjs state bytes match an expected hash.
1322
+ *
1323
+ * @param state - Yjs state bytes to verify
1324
+ * @param expectedHash - Expected hex-encoded BLAKE3 hash
1325
+ * @returns true if hashes match, false if corrupted
1326
+ */
1327
+ declare function verifyYjsStateIntegrity(state: Uint8Array, expectedHash: string): boolean;
1328
+ /**
1329
+ * Error thrown when Yjs state is corrupted (hash mismatch).
1330
+ */
1331
+ declare class YjsIntegrityError extends Error {
1332
+ readonly docId: string;
1333
+ readonly expectedHash: string;
1334
+ readonly actualHash: string;
1335
+ constructor(docId: string, expectedHash: string, actualHash: string);
1336
+ }
1337
+ /**
1338
+ * Persisted Yjs document state with integrity hash.
1339
+ */
1340
+ interface PersistedDocState {
1341
+ /** The raw Yjs state (Y.encodeStateAsUpdate) */
1342
+ state: Uint8Array;
1343
+ /** BLAKE3 hex hash of state bytes */
1344
+ hash: string;
1345
+ /** When this was persisted */
1346
+ persistedAt: number;
1347
+ /** Number of updates merged since last snapshot */
1348
+ updateCount: number;
1349
+ }
1350
+ /**
1351
+ * Create a PersistedDocState from Yjs state bytes.
1352
+ *
1353
+ * @param state - Yjs state bytes
1354
+ * @param updateCount - Number of updates in this state (default: 0)
1355
+ * @returns PersistedDocState with computed hash
1356
+ */
1357
+ declare function createPersistedDocState(state: Uint8Array, updateCount?: number): PersistedDocState;
1358
+ /**
1359
+ * Verify a PersistedDocState's integrity.
1360
+ *
1361
+ * @param docId - Document ID (for error reporting)
1362
+ * @param record - The persisted state record
1363
+ * @throws YjsIntegrityError if hash doesn't match
1364
+ */
1365
+ declare function verifyPersistedDocState(docId: string, record: PersistedDocState): void;
1366
+ /**
1367
+ * Safely load Yjs state, verifying integrity if hash is present.
1368
+ *
1369
+ * @param docId - Document ID
1370
+ * @param record - The persisted state record (may be legacy without hash)
1371
+ * @returns The state bytes
1372
+ * @throws YjsIntegrityError if hash exists and doesn't match
1373
+ */
1374
+ declare function loadVerifiedState(docId: string, record: Partial<PersistedDocState> & {
1375
+ state: Uint8Array;
1376
+ }): Uint8Array;
1377
+ /**
1378
+ * Constants for compaction thresholds.
1379
+ */
1380
+ /** Compact (re-encode and re-hash) after this many incremental updates */
1381
+ declare const COMPACTION_UPDATE_THRESHOLD = 100;
1382
+ /** Compact after this much time (ms) since last compaction */
1383
+ declare const COMPACTION_TIME_THRESHOLD = 3600000;
1384
+ /**
1385
+ * Check if a document should be compacted.
1386
+ *
1387
+ * @param updateCount - Number of updates since last compaction
1388
+ * @param persistedAt - When the state was last persisted
1389
+ * @returns true if compaction is recommended
1390
+ */
1391
+ declare function shouldCompact(updateCount: number, persistedAt: number): boolean;
1392
+
1393
+ /**
1394
+ * Yjs Peer Scoring - Track Yjs-specific misbehavior and auto-block repeat offenders
1395
+ *
1396
+ * Extends the concept of peer scoring with Yjs-specific metrics.
1397
+ * Peers that repeatedly send invalid signatures, oversized updates, or exceed
1398
+ * rate limits accumulate negative scores and are eventually auto-blocked.
1399
+ */
1400
+ /**
1401
+ * Yjs-specific peer metrics for tracking violations.
1402
+ */
1403
+ interface YjsPeerMetrics {
1404
+ /** Invalid Ed25519 signatures on Yjs envelopes */
1405
+ invalidSignatures: number;
1406
+ /** Updates exceeding size limit */
1407
+ oversizedUpdates: number;
1408
+ /** Updates exceeding rate limit */
1409
+ rateExceeded: number;
1410
+ /** Unsigned updates when signing required */
1411
+ unsignedUpdates: number;
1412
+ /** Updates from unattested clientIDs (Tier 3) */
1413
+ unattestedClientIds: number;
1414
+ /** Updates rejected by authorization policy */
1415
+ unauthorizedUpdates: number;
1416
+ /** Total valid updates (for ratio calculations) */
1417
+ validUpdates: number;
1418
+ /** First seen timestamp */
1419
+ firstSeen: number;
1420
+ /** Last violation timestamp */
1421
+ lastViolation: number;
1422
+ }
1423
+ /**
1424
+ * Violation types for peer scoring.
1425
+ */
1426
+ type YjsViolationType = 'invalidSignature' | 'oversizedUpdate' | 'rateExceeded' | 'unsignedUpdate' | 'unattestedClientId' | 'unauthorizedUpdate';
1427
+ /**
1428
+ * Action to take based on peer score.
1429
+ */
1430
+ type PeerAction = 'allow' | 'warn' | 'throttle' | 'block';
1431
+ /**
1432
+ * Optional telemetry collector interface for sync operations.
1433
+ * Compatible with @xnetjs/telemetry TelemetryCollector.
1434
+ */
1435
+ interface SyncTelemetry {
1436
+ reportPerformance(metricName: string, durationMs: number, codeNamespace?: string): void;
1437
+ reportUsage(metricName: string, value: number): void;
1438
+ reportCrash(error: Error, context?: {
1439
+ codeNamespace?: string;
1440
+ }): void;
1441
+ reportSecurityEvent(eventName: string, severity: 'low' | 'medium' | 'high' | 'critical'): void;
1442
+ }
1443
+ /**
1444
+ * Configuration for Yjs peer scoring.
1445
+ */
1446
+ interface YjsScoringConfig {
1447
+ /** Points deducted per violation type */
1448
+ penalties: {
1449
+ invalidSignature: number;
1450
+ oversizedUpdate: number;
1451
+ rateExceeded: number;
1452
+ unsignedUpdate: number;
1453
+ unattestedClientId: number;
1454
+ unauthorizedUpdate: number;
1455
+ };
1456
+ /** Score thresholds */
1457
+ thresholds: {
1458
+ warn: number;
1459
+ throttle: number;
1460
+ block: number;
1461
+ };
1462
+ /** Score recovery rate (points per tick of good behavior) */
1463
+ recoveryRate: number;
1464
+ /** Immediate block after N invalid signatures */
1465
+ instantBlockAfter: number;
1466
+ /**
1467
+ * Optional telemetry collector for security events.
1468
+ * When provided, YjsPeerScorer will report:
1469
+ * - Security events for violations (invalid signatures, rate limits, etc.)
1470
+ * - Usage metrics for peer actions (block, throttle, warn)
1471
+ * - Performance metrics for score calculations
1472
+ */
1473
+ telemetry?: SyncTelemetry;
1474
+ }
1475
+ /**
1476
+ * Default scoring configuration.
1477
+ */
1478
+ declare const DEFAULT_YJS_SCORING_CONFIG: YjsScoringConfig;
1479
+ /**
1480
+ * Peer scorer for Yjs-specific violations.
1481
+ *
1482
+ * Peers start at score 100. Violations deduct points based on severity.
1483
+ * Score thresholds trigger different actions (warn, throttle, block).
1484
+ *
1485
+ * @example
1486
+ * ```typescript
1487
+ * const scorer = new YjsPeerScorer()
1488
+ *
1489
+ * // On violation:
1490
+ * const action = scorer.penalize(peerId, 'invalidSignature')
1491
+ * if (action === 'block') {
1492
+ * ws.close(4403, 'Blocked due to repeated violations')
1493
+ * }
1494
+ *
1495
+ * // On valid update:
1496
+ * scorer.recordValid(peerId)
1497
+ *
1498
+ * // Periodic recovery:
1499
+ * setInterval(() => scorer.tick(), 60_000)
1500
+ * ```
1501
+ */
1502
+ declare class YjsPeerScorer {
1503
+ private metrics;
1504
+ private scores;
1505
+ readonly config: YjsScoringConfig;
1506
+ private telemetry?;
1507
+ constructor(config?: Partial<YjsScoringConfig>);
1508
+ /**
1509
+ * Record a violation for a peer.
1510
+ *
1511
+ * @param peerId - Peer identifier
1512
+ * @param reason - Type of violation
1513
+ * @returns Action to take based on new score
1514
+ */
1515
+ penalize(peerId: string, reason: YjsViolationType): PeerAction;
1516
+ /**
1517
+ * Record a valid update (for ratio tracking + score recovery consideration).
1518
+ */
1519
+ recordValid(peerId: string): void;
1520
+ /**
1521
+ * Get current score for a peer.
1522
+ * New peers start at 100.
1523
+ */
1524
+ getScore(peerId: string): number;
1525
+ /**
1526
+ * Get action for a given score.
1527
+ */
1528
+ getAction(score: number): PeerAction;
1529
+ /**
1530
+ * Get current action for a peer.
1531
+ */
1532
+ getPeerAction(peerId: string): PeerAction;
1533
+ /**
1534
+ * Recover scores over time (called periodically).
1535
+ * Only recovers if no recent violations.
1536
+ *
1537
+ * @param recoveryWindow - Time in ms that must pass without violations (default: 60s)
1538
+ */
1539
+ tick(recoveryWindow?: number): void;
1540
+ /**
1541
+ * Get metrics for a peer (for debugging/monitoring).
1542
+ */
1543
+ getMetrics(peerId: string): YjsPeerMetrics | undefined;
1544
+ /**
1545
+ * Get all peer IDs with metrics.
1546
+ */
1547
+ getAllPeerIds(): string[];
1548
+ /**
1549
+ * Get all metrics (for monitoring endpoint).
1550
+ */
1551
+ getAllMetrics(): Map<string, YjsPeerMetrics>;
1552
+ /**
1553
+ * Remove all state for a disconnected peer.
1554
+ */
1555
+ remove(peerId: string): void;
1556
+ /**
1557
+ * Clear all state.
1558
+ */
1559
+ clear(): void;
1560
+ /**
1561
+ * Get violation ratio for a peer.
1562
+ * Returns the ratio of violations to total operations.
1563
+ */
1564
+ getViolationRatio(peerId: string): number;
1565
+ private getOrCreateMetrics;
1566
+ }
1567
+
1568
+ /**
1569
+ * Yjs authorization primitives: encrypted-at-rest state and peer auth gate.
1570
+ */
1571
+
1572
+ interface EncryptedYjsState {
1573
+ nodeId: string;
1574
+ version: 1;
1575
+ encryptedState: Uint8Array;
1576
+ nonce: Uint8Array;
1577
+ stateVector: Uint8Array;
1578
+ stateHash: Uint8Array;
1579
+ checkpointedAt: number;
1580
+ updatesSinceCheckpoint: number;
1581
+ }
1582
+ declare class YjsStateIntegrityError extends Error {
1583
+ constructor(message?: string);
1584
+ }
1585
+ declare function encryptYjsState(state: Uint8Array, nodeId: string, contentKey: Uint8Array, options?: {
1586
+ stateVector?: Uint8Array;
1587
+ checkpointedAt?: number;
1588
+ updatesSinceCheckpoint?: number;
1589
+ }): EncryptedYjsState;
1590
+ declare function decryptYjsState(encrypted: EncryptedYjsState, contentKey: Uint8Array): Uint8Array;
1591
+ declare function serializeEncryptedYjsState(state: EncryptedYjsState): Uint8Array;
1592
+ declare function deserializeEncryptedYjsState(bytes: Uint8Array): EncryptedYjsState;
1593
+ interface YjsAuthDecision {
1594
+ allowed: boolean;
1595
+ authorDID: DID;
1596
+ cached: boolean;
1597
+ }
1598
+ interface YjsAuthGateOptions {
1599
+ cacheTTL?: number;
1600
+ now?: () => number;
1601
+ }
1602
+ declare class YjsAuthGate {
1603
+ private readonly evaluator;
1604
+ private readonly nodeId;
1605
+ private static readonly DEFAULT_CACHE_TTL;
1606
+ private readonly peerAuthCache;
1607
+ private readonly cacheTTL;
1608
+ private readonly now;
1609
+ constructor(evaluator: PolicyEvaluator, nodeId: string, options?: YjsAuthGateOptions);
1610
+ canApplyUpdate(envelope: Pick<SignedYjsEnvelopeV2, 'meta'>): Promise<YjsAuthDecision>;
1611
+ invalidatePeer(did: DID): void;
1612
+ invalidateAll(): void;
1613
+ }
1614
+ interface YjsCheckpointerOptions {
1615
+ maxUpdates?: number;
1616
+ maxAgeMs?: number;
1617
+ now?: () => number;
1618
+ }
1619
+ declare class YjsCheckpointer {
1620
+ private readonly maxUpdates;
1621
+ private readonly maxAgeMs;
1622
+ private readonly now;
1623
+ constructor(options?: YjsCheckpointerOptions);
1624
+ shouldCheckpoint(state: Pick<EncryptedYjsState, 'updatesSinceCheckpoint' | 'checkpointedAt'>): boolean;
1625
+ checkpoint(input: {
1626
+ state: Uint8Array;
1627
+ nodeId: string;
1628
+ contentKey: Uint8Array;
1629
+ stateVector?: Uint8Array;
1630
+ }): EncryptedYjsState;
1631
+ }
1632
+ declare function toEncryptedData(state: EncryptedYjsState): EncryptedData;
1633
+
1634
+ /**
1635
+ * Yjs authorized sync manager and provider.
1636
+ */
1637
+
1638
+ type GrantLikeNode = {
1639
+ schemaId?: string;
1640
+ properties?: Record<string, unknown>;
1641
+ };
1642
+ type GrantEvent = {
1643
+ node?: GrantLikeNode | null;
1644
+ };
1645
+ interface YDocLike {
1646
+ emit?: (event: string, args: unknown[]) => void;
1647
+ }
1648
+ interface YDocCodec<TDoc extends YDocLike = YDocLike> {
1649
+ createDoc(nodeId: string): TDoc;
1650
+ applyUpdate(doc: TDoc, update: Uint8Array, origin?: string): void;
1651
+ encodeStateAsUpdate(doc: TDoc): Uint8Array;
1652
+ encodeStateVector(doc: TDoc): Uint8Array;
1653
+ }
1654
+ type AuthorizedRoom<TDoc extends YDocLike = YDocLike> = {
1655
+ nodeId: string;
1656
+ doc: TDoc;
1657
+ contentKey: Uint8Array;
1658
+ authGate: YjsAuthGate;
1659
+ authorizedPeers: Set<DID>;
1660
+ };
1661
+ type AuthorizedDoc<TDoc extends YDocLike = YDocLike> = {
1662
+ doc: TDoc;
1663
+ nodeId: string;
1664
+ mode: 'read' | 'write';
1665
+ contentKey: Uint8Array;
1666
+ room?: AuthorizedRoom<TDoc>;
1667
+ release: () => void;
1668
+ };
1669
+ interface AuthorizedStateAdapter {
1670
+ getDocumentContent(nodeId: string): Promise<Uint8Array | null>;
1671
+ setDocumentContent(nodeId: string, content: Uint8Array): Promise<void>;
1672
+ }
1673
+ interface GrantEventStore {
1674
+ subscribe(listener: (event: GrantEvent) => void): () => void;
1675
+ }
1676
+ interface ContentKeyProvider {
1677
+ getOrUnwrap(nodeId: string): Promise<Uint8Array>;
1678
+ }
1679
+ interface RecipientKeyResolver {
1680
+ resolveBatch(dids: DID[]): Promise<Map<DID, Uint8Array>>;
1681
+ }
1682
+ interface AuthorizedSyncManagerOptions<TDoc extends YDocLike = YDocLike> {
1683
+ authorDID: DID;
1684
+ evaluator: PolicyEvaluator;
1685
+ adapter: AuthorizedStateAdapter;
1686
+ store: GrantEventStore;
1687
+ keyProvider: ContentKeyProvider;
1688
+ ydoc: YDocCodec<TDoc>;
1689
+ publicKeyResolver?: RecipientKeyResolver;
1690
+ onRotateContentKey?: (input: {
1691
+ nodeId: string;
1692
+ recipients: DID[];
1693
+ wrappedKeys: Record<string, WrappedKey>;
1694
+ contentKey: Uint8Array;
1695
+ }) => Promise<void>;
1696
+ }
1697
+ declare class AuthorizedSyncManager<TDoc extends YDocLike = YDocLike> {
1698
+ private readonly options;
1699
+ private readonly rooms;
1700
+ constructor(options: AuthorizedSyncManagerOptions<TDoc>);
1701
+ acquire(nodeId: string, mode?: 'read' | 'write'): Promise<AuthorizedDoc<TDoc>>;
1702
+ release(nodeId: string): void;
1703
+ private joinRoom;
1704
+ private wireRevocationEvents;
1705
+ private handlePeerRevocation;
1706
+ }
1707
+ interface AuthorizedYjsSyncProviderOptions<TDoc extends YDocLike = YDocLike> {
1708
+ nodeId: string;
1709
+ doc: TDoc;
1710
+ ydoc: Pick<YDocCodec<TDoc>, 'applyUpdate'>;
1711
+ authGate: YjsAuthGate;
1712
+ peerScorer?: YjsPeerScorer;
1713
+ rateLimiter?: {
1714
+ allow(peerId: DID): boolean;
1715
+ };
1716
+ verifyEnvelope?: (envelope: SignedYjsEnvelopeV2) => Promise<EnvelopeVerificationResult>;
1717
+ onRejected?: (input: {
1718
+ peerId: DID;
1719
+ reason: 'rate-exceeded' | 'invalid-signature' | 'unauthorized';
1720
+ }) => void;
1721
+ }
1722
+ declare class AuthorizedYjsSyncProvider<TDoc extends YDocLike = YDocLike> {
1723
+ private readonly nodeId;
1724
+ private readonly doc;
1725
+ private readonly ydoc;
1726
+ private readonly authGate;
1727
+ private readonly peerScorer;
1728
+ private readonly rateLimiter?;
1729
+ private readonly verifyEnvelope;
1730
+ private readonly onRejected?;
1731
+ constructor(options: AuthorizedYjsSyncProviderOptions<TDoc>);
1732
+ handleRemoteUpdate(envelope: SignedYjsEnvelopeV2): Promise<boolean>;
1733
+ }
1734
+ declare class AuthorizedYjsError extends Error {
1735
+ readonly code: 'PERMISSION_DENIED';
1736
+ readonly decision: AuthDecision;
1737
+ constructor(code: 'PERMISSION_DENIED', decision: AuthDecision);
1738
+ }
1739
+
1740
+ /**
1741
+ * ClientID-to-DID Binding - Cryptographically bind Yjs clientIDs to DIDs
1742
+ *
1743
+ * Yjs assigns random integer clientIDs that have no cryptographic binding to
1744
+ * the author's identity. This module provides signed attestations that bind
1745
+ * a clientID to a DID for verifiable author attribution.
1746
+ *
1747
+ * V2 introduces multi-level signature support (Ed25519 and/or ML-DSA-65).
1748
+ */
1749
+
1750
+ /**
1751
+ * A signed attestation binding a Yjs clientID to a DID (V1 format).
1752
+ * @deprecated Use ClientIdAttestationV2 for new code
1753
+ */
1754
+ interface ClientIdAttestationV1 {
1755
+ /** The Yjs clientID being attested */
1756
+ clientId: number;
1757
+ /** The DID claiming this clientID */
1758
+ did: string;
1759
+ /** Ed25519 signature over BLAKE3(payload) */
1760
+ signature: Uint8Array;
1761
+ /** Session expiry (Unix timestamp seconds) */
1762
+ expiresAt: number;
1763
+ /** Room this attestation applies to */
1764
+ room: string;
1765
+ }
1766
+ /**
1767
+ * A signed attestation binding a Yjs clientID to a DID (V2 format with multi-level signatures).
1768
+ */
1769
+ interface ClientIdAttestationV2 {
1770
+ /** Wire format version */
1771
+ v: 2;
1772
+ /** The Yjs clientID being attested */
1773
+ clientId: number;
1774
+ /** The DID claiming this clientID */
1775
+ did: DID;
1776
+ /** When this binding was created (ms since epoch) */
1777
+ timestamp: number;
1778
+ /** Session expiry (Unix timestamp ms) */
1779
+ expiresAt?: number;
1780
+ /** Room this attestation applies to */
1781
+ room: string;
1782
+ /** Multi-level signature */
1783
+ signature: UnifiedSignature;
1784
+ }
1785
+ /**
1786
+ * Wire format for ClientIdAttestationV2.
1787
+ */
1788
+ interface ClientIdAttestationWire {
1789
+ v: 2;
1790
+ c: number;
1791
+ d: string;
1792
+ t: number;
1793
+ e?: number;
1794
+ r: string;
1795
+ s: SignatureWire;
1796
+ }
1797
+ /**
1798
+ * Union type for all attestation versions.
1799
+ */
1800
+ type ClientIdAttestation = ClientIdAttestationV1 | ClientIdAttestationV2;
1801
+ /**
1802
+ * Result of attestation verification (V1 format).
1803
+ */
1804
+ interface AttestationVerifyResult {
1805
+ valid: boolean;
1806
+ reason?: 'expired' | 'invalid_signature' | 'did_resolution_failed';
1807
+ }
1808
+ /**
1809
+ * Result of attestation verification (V2 format with details).
1810
+ */
1811
+ interface AttestationVerificationResult {
1812
+ valid: boolean;
1813
+ expired: boolean;
1814
+ level: SecurityLevel;
1815
+ errors: string[];
1816
+ }
1817
+ /**
1818
+ * Options for creating an attestation.
1819
+ */
1820
+ interface CreateAttestationOptions {
1821
+ /** Expiration time in ms (optional) */
1822
+ expiresInMs?: number;
1823
+ /** Security level (default: 0 for Ed25519-only) */
1824
+ level?: SecurityLevel;
1825
+ }
1826
+ /**
1827
+ * Options for verifying an attestation.
1828
+ */
1829
+ interface VerifyAttestationOptions {
1830
+ /** PQ key registry for Level 1/2 verification */
1831
+ registry?: PQKeyRegistry;
1832
+ /** Minimum security level required */
1833
+ minLevel?: SecurityLevel;
1834
+ }
1835
+ /**
1836
+ * Check if an attestation is V2 format.
1837
+ */
1838
+ declare function isV2Attestation(attestation: ClientIdAttestation): attestation is ClientIdAttestationV2;
1839
+ /**
1840
+ * Check if an attestation is V1 format.
1841
+ */
1842
+ declare function isV1Attestation(attestation: ClientIdAttestation): attestation is ClientIdAttestationV1;
1843
+ /**
1844
+ * Create a signed clientID attestation (V1 format).
1845
+ *
1846
+ * @deprecated Use createClientIdAttestationV2() for new code
1847
+ */
1848
+ declare function createClientIdAttestationV1(clientId: number, did: string, privateKey: Uint8Array, room: string, ttlSeconds?: number): ClientIdAttestationV1;
1849
+ /**
1850
+ * Verify a V1 clientID attestation.
1851
+ *
1852
+ * @deprecated Use verifyClientIdAttestationV2() for V2 attestations
1853
+ */
1854
+ declare function verifyClientIdAttestationV1(attestation: ClientIdAttestationV1): AttestationVerifyResult;
1855
+ /**
1856
+ * Create a signed clientID attestation (V2 format with multi-level signatures).
1857
+ *
1858
+ * @param clientId - The Yjs clientID to attest
1859
+ * @param room - The room this attestation applies to
1860
+ * @param keyBundle - Key bundle for signing
1861
+ * @param options - Creation options
1862
+ * @returns Signed attestation
1863
+ *
1864
+ * @example
1865
+ * ```typescript
1866
+ * const attestation = createClientIdAttestationV2(doc.clientID, 'xnet-doc-abc', keyBundle)
1867
+ * ws.send({ type: 'clientid-bind', room, attestation: serializeAttestation(attestation) })
1868
+ * ```
1869
+ */
1870
+ declare function createClientIdAttestationV2(clientId: number, room: string, keyBundle: HybridKeyBundle, options?: CreateAttestationOptions): ClientIdAttestationV2;
1871
+ /**
1872
+ * Verify a V2 clientID attestation with multi-level signature support.
1873
+ *
1874
+ * @param attestation - The attestation to verify
1875
+ * @param options - Verification options
1876
+ * @returns Verification result with detailed errors
1877
+ */
1878
+ declare function verifyClientIdAttestationV2(attestation: ClientIdAttestationV2, options?: VerifyAttestationOptions): Promise<AttestationVerificationResult>;
1879
+ /**
1880
+ * Serialize V2 attestation to wire format.
1881
+ */
1882
+ declare function serializeClientIdAttestation(attestation: ClientIdAttestationV2): ClientIdAttestationWire;
1883
+ /**
1884
+ * Deserialize V2 attestation from wire format.
1885
+ */
1886
+ declare function deserializeClientIdAttestation(wire: ClientIdAttestationWire): ClientIdAttestationV2;
1887
+ /**
1888
+ * Create a signed clientID attestation.
1889
+ *
1890
+ * @overload V1 signature (legacy)
1891
+ */
1892
+ declare function createClientIdAttestation(clientId: number, did: string, privateKey: Uint8Array, room: string, ttlSeconds?: number): ClientIdAttestationV1;
1893
+ /**
1894
+ * @overload V2 signature (multi-level)
1895
+ */
1896
+ declare function createClientIdAttestation(clientId: number, room: string, keyBundle: HybridKeyBundle, options?: CreateAttestationOptions): ClientIdAttestationV2;
1897
+ /**
1898
+ * Verify a clientID attestation (auto-detects version).
1899
+ */
1900
+ declare function verifyClientIdAttestation(attestation: ClientIdAttestationV1): AttestationVerifyResult;
1901
+ declare function verifyClientIdAttestation(attestation: ClientIdAttestationV2, options?: VerifyAttestationOptions): Promise<AttestationVerificationResult>;
1902
+ /**
1903
+ * Interface for a per-room clientID map.
1904
+ */
1905
+ interface ClientIdMap {
1906
+ /** Look up DID by clientId */
1907
+ getOwner(clientId: number): string | undefined;
1908
+ /** Look up clientId by DID */
1909
+ getClientId(did: string): number | undefined;
1910
+ /** Register a verified attestation */
1911
+ register(attestation: ClientIdAttestation): void;
1912
+ /** Remove expired bindings */
1913
+ cleanup(): void;
1914
+ /** Get all active bindings */
1915
+ getAll(): Array<{
1916
+ clientId: number;
1917
+ did: string;
1918
+ expiresAt: number;
1919
+ }>;
1920
+ /** Check if a clientID is attested */
1921
+ has(clientId: number): boolean;
1922
+ /** Get the number of active bindings */
1923
+ size(): number;
1924
+ }
1925
+ /**
1926
+ * Implementation of ClientIdMap for tracking clientID->DID bindings.
1927
+ *
1928
+ * @example
1929
+ * ```typescript
1930
+ * const map = new ClientIdMapImpl()
1931
+ *
1932
+ * // On receiving attestation:
1933
+ * const result = verifyClientIdAttestation(attestation)
1934
+ * if (result.valid) {
1935
+ * map.register(attestation)
1936
+ * }
1937
+ *
1938
+ * // On receiving update:
1939
+ * const owner = map.getOwner(envelope.clientId)
1940
+ * if (owner && owner !== envelope.authorDID) {
1941
+ * // ClientID claimed by different DID - reject!
1942
+ * }
1943
+ * ```
1944
+ */
1945
+ declare class ClientIdMapImpl implements ClientIdMap {
1946
+ private byClientId;
1947
+ private byDid;
1948
+ getOwner(clientId: number): string | undefined;
1949
+ getClientId(did: string): number | undefined;
1950
+ register(attestation: ClientIdAttestation): void;
1951
+ cleanup(): void;
1952
+ getAll(): Array<{
1953
+ clientId: number;
1954
+ did: string;
1955
+ expiresAt: number;
1956
+ }>;
1957
+ has(clientId: number): boolean;
1958
+ size(): number;
1959
+ /**
1960
+ * Get the count of non-expired entries without mutating state.
1961
+ * This is useful for accurate counts but slower than size().
1962
+ */
1963
+ activeCount(): number;
1964
+ /**
1965
+ * Clear all bindings.
1966
+ */
1967
+ clear(): void;
1968
+ }
1969
+ /**
1970
+ * Check if an update's clientID matches the expected owner.
1971
+ *
1972
+ * @param clientId - The clientID from the update
1973
+ * @param authorDID - The DID from the envelope
1974
+ * @param map - The clientID map
1975
+ * @returns true if valid (matches or no binding exists), false if mismatch
1976
+ */
1977
+ declare function validateClientIdOwnership(clientId: number, authorDID: string, map: ClientIdMap): boolean;
1978
+
1979
+ /**
1980
+ * YjsChange - Wrap Yjs updates in the Change<T> envelope for hash chain integration
1981
+ *
1982
+ * This provides the strongest security tier: Yjs updates become entries in the
1983
+ * same per-node hash chain as NodeChanges. This gives rich text the same
1984
+ * guarantees as structured data — signed, hashed, chain-linked, with a full
1985
+ * audit trail of who typed what and when.
1986
+ *
1987
+ * See: docs/plans/plan03_4_1YjsSecurity/08-hash-chain-integration.md
1988
+ */
1989
+
1990
+ /**
1991
+ * Change type identifier for Yjs changes.
1992
+ */
1993
+ declare const YJS_CHANGE_TYPE = "yjs-update";
1994
+ /**
1995
+ * Payload for a YjsChange - contains the batched Yjs update.
1996
+ */
1997
+ interface YjsUpdatePayload {
1998
+ /** The node this update belongs to */
1999
+ nodeId: string;
2000
+ /** Batched Yjs update bytes (one or more merged updates) */
2001
+ update: Uint8Array;
2002
+ /** Yjs clientID for this author (verified via Step 07) */
2003
+ clientId: number;
2004
+ /** Number of individual Yjs updates batched into this change */
2005
+ updateCount: number;
2006
+ }
2007
+ /**
2008
+ * A Yjs update wrapped in the Change<T> envelope.
2009
+ * Gets: id, hash, parentHash, signature, authorDID, lamport, wallTime
2010
+ */
2011
+ type YjsChange = Change<YjsUpdatePayload>;
2012
+ /**
2013
+ * Unsigned version of YjsChange (before signing).
2014
+ */
2015
+ type UnsignedYjsChange = UnsignedChange<YjsUpdatePayload>;
2016
+ /**
2017
+ * Options for creating a YjsChange.
2018
+ */
2019
+ interface CreateYjsChangeOptions {
2020
+ /** The node this update belongs to */
2021
+ nodeId: string;
2022
+ /** Batched Yjs update bytes */
2023
+ update: Uint8Array;
2024
+ /** Yjs clientID for this author */
2025
+ clientId: number;
2026
+ /** Number of individual updates in this batch */
2027
+ updateCount: number;
2028
+ /** Author's DID */
2029
+ authorDID: string;
2030
+ /** Author's Ed25519 private key for signing */
2031
+ privateKey: Uint8Array;
2032
+ /** Hash of the previous change in the chain (null for first) */
2033
+ parentHash: ContentId | null;
2034
+ /** Lamport timestamp for ordering */
2035
+ lamport: LamportTimestamp;
2036
+ /** Optional wall time (defaults to Date.now()) */
2037
+ wallTime?: number;
2038
+ }
2039
+ /**
2040
+ * Create an unsigned YjsChange.
2041
+ *
2042
+ * @param options - Change options
2043
+ * @returns Unsigned change ready for signing
2044
+ */
2045
+ declare function createUnsignedYjsChange(options: Omit<CreateYjsChangeOptions, 'privateKey'>): UnsignedYjsChange;
2046
+ /**
2047
+ * Create a signed YjsChange.
2048
+ *
2049
+ * @param options - Change options including private key
2050
+ * @returns Signed YjsChange with hash and signature
2051
+ *
2052
+ * @example
2053
+ * ```typescript
2054
+ * const change = createYjsChange({
2055
+ * nodeId: 'page-123',
2056
+ * update: Y.encodeStateAsUpdate(doc),
2057
+ * clientId: doc.clientID,
2058
+ * updateCount: 5,
2059
+ * authorDID: identity.did,
2060
+ * privateKey: identity.privateKey,
2061
+ * parentHash: lastChangeHash,
2062
+ * lamport: { time: 42, did: identity.did }
2063
+ * })
2064
+ * ```
2065
+ */
2066
+ declare function createYjsChange(options: CreateYjsChangeOptions): YjsChange;
2067
+ /**
2068
+ * Type guard to check if a change is a YjsChange.
2069
+ *
2070
+ * @param change - Any change object
2071
+ * @returns true if the change is a YjsChange
2072
+ */
2073
+ declare function isYjsChange(change: Change<unknown>): change is YjsChange;
2074
+ /**
2075
+ * Type guard to check if a change is a NodeChange (not a YjsChange).
2076
+ * NodeChanges have 'properties' and 'schemaId' in their payload.
2077
+ */
2078
+ declare function isNodeChange(change: Change<unknown>): boolean;
2079
+ /**
2080
+ * Extract the nodeId from any change (works for both NodeChange and YjsChange).
2081
+ */
2082
+ declare function getChangeNodeId(change: Change<unknown>): string | undefined;
2083
+
2084
+ /**
2085
+ * YjsBatcher - Batch Yjs updates for efficient hash chain integration
2086
+ *
2087
+ * Individual keystrokes generate ~5 updates/sec. Wrapping each in a full
2088
+ * Change<T> is wasteful. Instead, batch updates over a configurable window.
2089
+ *
2090
+ * With 2-second batching:
2091
+ * - Updates/sec: 5 → 0.5
2092
+ * - Overhead/sec: 1KB → 100B
2093
+ * - Signature ops/sec: 5 → 0.5
2094
+ *
2095
+ * See: docs/plans/plan03_4_1YjsSecurity/08-hash-chain-integration.md
2096
+ */
2097
+ /**
2098
+ * Configuration for the YjsBatcher.
2099
+ */
2100
+ interface YjsBatcherConfig {
2101
+ /** Batch window in milliseconds (default: 2000) */
2102
+ batchWindowMs: number;
2103
+ /** Max updates per batch before flush (default: 50) */
2104
+ maxBatchSize: number;
2105
+ /** Flush on paragraph break / Enter key (default: true) */
2106
+ flushOnParagraph: boolean;
2107
+ }
2108
+ /**
2109
+ * Default configuration for YjsBatcher.
2110
+ */
2111
+ declare const DEFAULT_BATCHER_CONFIG: YjsBatcherConfig;
2112
+ /**
2113
+ * Callback invoked when a batch is flushed.
2114
+ *
2115
+ * @param mergedUpdate - The merged Yjs update bytes (from Y.mergeUpdates)
2116
+ * @param updateCount - Number of individual updates in this batch
2117
+ */
2118
+ type BatchFlushCallback = (mergedUpdate: Uint8Array, updateCount: number) => void;
2119
+ /**
2120
+ * Function that merges multiple Yjs updates into one.
2121
+ * This is typically Y.mergeUpdates from the 'yjs' package.
2122
+ */
2123
+ type MergeUpdatesFn = (updates: Uint8Array[]) => Uint8Array;
2124
+ /**
2125
+ * YjsBatcher collects Yjs updates and flushes them in batches.
2126
+ *
2127
+ * This reduces the overhead of wrapping each update in a signed Change<T>
2128
+ * while still maintaining a full audit trail.
2129
+ *
2130
+ * @example
2131
+ * ```typescript
2132
+ * import * as Y from 'yjs'
2133
+ *
2134
+ * const batcher = new YjsBatcher(
2135
+ * (mergedUpdate, count) => {
2136
+ * // Create a YjsChange from the merged update
2137
+ * const change = createYjsChange({
2138
+ * nodeId,
2139
+ * update: mergedUpdate,
2140
+ * updateCount: count,
2141
+ * ...
2142
+ * })
2143
+ * // Append to hash chain
2144
+ * store.appendChange(change)
2145
+ * },
2146
+ * { batchWindowMs: 2000 },
2147
+ * Y.mergeUpdates // Pass the merge function
2148
+ * )
2149
+ *
2150
+ * // On each local Yjs update:
2151
+ * doc.on('update', (update, origin) => {
2152
+ * if (origin !== 'remote') {
2153
+ * batcher.add(update)
2154
+ * }
2155
+ * })
2156
+ *
2157
+ * // On component unmount:
2158
+ * batcher.destroy()
2159
+ * ```
2160
+ */
2161
+ declare class YjsBatcher {
2162
+ private pendingUpdates;
2163
+ private flushTimer;
2164
+ private config;
2165
+ private onFlush;
2166
+ private mergeUpdates;
2167
+ private destroyed;
2168
+ /**
2169
+ * Create a new YjsBatcher.
2170
+ *
2171
+ * @param onFlush - Callback invoked when a batch is flushed
2172
+ * @param config - Optional configuration overrides
2173
+ * @param mergeUpdates - Function to merge updates (required - use Y.mergeUpdates from yjs)
2174
+ */
2175
+ constructor(onFlush: BatchFlushCallback, config?: Partial<YjsBatcherConfig>, mergeUpdates?: MergeUpdatesFn);
2176
+ /**
2177
+ * Add an update to the current batch.
2178
+ *
2179
+ * @param update - The Yjs update bytes
2180
+ * @param isParagraphBreak - Whether this update is a paragraph break (Enter key)
2181
+ */
2182
+ add(update: Uint8Array, isParagraphBreak?: boolean): void;
2183
+ /**
2184
+ * Force flush the current batch.
2185
+ * Safe to call even if there are no pending updates.
2186
+ */
2187
+ flush(): void;
2188
+ /**
2189
+ * Check if there are pending updates.
2190
+ */
2191
+ hasPending(): boolean;
2192
+ /**
2193
+ * Get the number of pending updates.
2194
+ */
2195
+ pendingCount(): number;
2196
+ /**
2197
+ * Destroy the batcher. Flushes any remaining updates.
2198
+ * After destroy, add() calls will be ignored.
2199
+ */
2200
+ destroy(): void;
2201
+ /**
2202
+ * Check if the batcher has been destroyed.
2203
+ */
2204
+ isDestroyed(): boolean;
2205
+ /**
2206
+ * Reset or start the flush timer.
2207
+ */
2208
+ private resetTimer;
2209
+ }
2210
+
2211
+ /**
2212
+ * Serializer types for version-specific change encoding.
2213
+ *
2214
+ * Each protocol version may have a different wire format for changes.
2215
+ * Serializers handle encoding and decoding for their specific version.
2216
+ */
2217
+
2218
+ /**
2219
+ * Serialized change format.
2220
+ * Can be either binary (Uint8Array) or JSON-compatible object.
2221
+ */
2222
+ type SerializedChange = Uint8Array | Record<string, unknown>;
2223
+ /**
2224
+ * Result of deserializing a change.
2225
+ */
2226
+ interface DeserializeResult<T = unknown> {
2227
+ success: true;
2228
+ change: Change<T>;
2229
+ }
2230
+ interface DeserializeError {
2231
+ success: false;
2232
+ error: string;
2233
+ /** Raw data that failed to deserialize */
2234
+ rawData?: unknown;
2235
+ }
2236
+ type DeserializeOutcome<T = unknown> = DeserializeResult<T> | DeserializeError;
2237
+ /**
2238
+ * Interface for version-specific change serializers.
2239
+ *
2240
+ * Each serializer handles:
2241
+ * - Encoding changes for network transmission
2242
+ * - Decoding changes from network
2243
+ * - Validating change format
2244
+ */
2245
+ interface ChangeSerializer {
2246
+ /**
2247
+ * Protocol version this serializer handles.
2248
+ */
2249
+ readonly version: number;
2250
+ /**
2251
+ * Human-readable name for this serializer.
2252
+ */
2253
+ readonly name: string;
2254
+ /**
2255
+ * Serialize a change for transmission.
2256
+ *
2257
+ * @param change - The change to serialize
2258
+ * @returns Serialized data (binary or JSON object)
2259
+ */
2260
+ serialize<T>(change: Change<T>): SerializedChange;
2261
+ /**
2262
+ * Deserialize a change from received data.
2263
+ *
2264
+ * @param data - Raw data (binary or JSON object)
2265
+ * @returns Deserialized change or error
2266
+ */
2267
+ deserialize<T = unknown>(data: SerializedChange): DeserializeOutcome<T>;
2268
+ /**
2269
+ * Check if this serializer can handle the given data.
2270
+ * Used for format detection.
2271
+ *
2272
+ * @param data - Raw data to check
2273
+ * @returns true if this serializer can deserialize the data
2274
+ */
2275
+ canDeserialize(data: unknown): boolean;
2276
+ }
2277
+ /**
2278
+ * Options for serialization.
2279
+ */
2280
+ interface SerializeOptions {
2281
+ /**
2282
+ * Whether to use binary encoding (more compact) or JSON (more readable).
2283
+ * Default: true for production, false for debugging.
2284
+ */
2285
+ binary?: boolean;
2286
+ /**
2287
+ * Whether to include optional fields with default values.
2288
+ * Default: false (omit default values).
2289
+ */
2290
+ includeDefaults?: boolean;
2291
+ }
2292
+ /**
2293
+ * Registry for managing multiple serializer versions.
2294
+ */
2295
+ interface SerializerRegistry {
2296
+ /**
2297
+ * Get serializer for a specific version.
2298
+ */
2299
+ get(version: number): ChangeSerializer | undefined;
2300
+ /**
2301
+ * Get the default (latest) serializer.
2302
+ */
2303
+ getDefault(): ChangeSerializer;
2304
+ /**
2305
+ * Register a serializer.
2306
+ */
2307
+ register(serializer: ChangeSerializer): void;
2308
+ /**
2309
+ * Get all registered versions.
2310
+ */
2311
+ getVersions(): number[];
2312
+ /**
2313
+ * Auto-detect serializer for incoming data.
2314
+ */
2315
+ detect(data: unknown): ChangeSerializer | undefined;
2316
+ }
2317
+
2318
+ /**
2319
+ * V1 Serializer - Original change format.
2320
+ *
2321
+ * Protocol v1 uses JSON encoding with the following characteristics:
2322
+ * - protocolVersion field may be missing (legacy) or 1
2323
+ * - Signature is base64 encoded
2324
+ * - All fields use full names (no abbreviations)
2325
+ *
2326
+ * Wire format (JSON):
2327
+ * {
2328
+ * protocolVersion?: 1,
2329
+ * id: string,
2330
+ * type: string,
2331
+ * payload: T,
2332
+ * hash: string,
2333
+ * parentHash: string | null,
2334
+ * authorDID: string,
2335
+ * signature: string (base64),
2336
+ * wallTime: number,
2337
+ * lamport: { time: number, did: string },
2338
+ * batchId?: string,
2339
+ * batchIndex?: number,
2340
+ * batchSize?: number
2341
+ * }
2342
+ */
2343
+
2344
+ /**
2345
+ * V1 Serializer implementation.
2346
+ */
2347
+ declare class V1Serializer implements ChangeSerializer {
2348
+ readonly version = 1;
2349
+ readonly name = "V1 JSON Serializer";
2350
+ serialize<T>(change: Change<T>): SerializedChange;
2351
+ deserialize<T = unknown>(data: SerializedChange): DeserializeOutcome<T>;
2352
+ canDeserialize(data: unknown): boolean;
2353
+ }
2354
+ /**
2355
+ * Default V1 serializer instance.
2356
+ */
2357
+ declare const v1Serializer: V1Serializer;
2358
+
2359
+ /**
2360
+ * V2 Serializer - Enhanced change format with schema versioning.
2361
+ *
2362
+ * Protocol v2 extends v1 with:
2363
+ * - Required protocolVersion field (always 2)
2364
+ * - Schema version in payload (_sv field)
2365
+ * - Abbreviated field names for compact transmission
2366
+ * - Support for binary payload compression (future)
2367
+ *
2368
+ * Wire format (JSON, abbreviated):
2369
+ * {
2370
+ * v: 2, // protocolVersion
2371
+ * i: string, // id
2372
+ * t: string, // type
2373
+ * p: { ..., _sv?: string }, // payload with optional schema version
2374
+ * h: string, // hash
2375
+ * ph: string | null, // parentHash
2376
+ * a: string, // authorDID
2377
+ * s: string, // signature (base64)
2378
+ * w: number, // wallTime
2379
+ * l: { t: number, a: string }, // lamport { time, author }
2380
+ * bi?: string, // batchId
2381
+ * bx?: number, // batchIndex
2382
+ * bs?: number // batchSize
2383
+ * }
2384
+ */
2385
+
2386
+ /**
2387
+ * V2 Serializer implementation.
2388
+ * Uses abbreviated field names for more compact wire format.
2389
+ */
2390
+ declare class V2Serializer implements ChangeSerializer {
2391
+ readonly version = 2;
2392
+ readonly name = "V2 Compact Serializer";
2393
+ serialize<T>(change: Change<T>): SerializedChange;
2394
+ deserialize<T = unknown>(data: SerializedChange): DeserializeOutcome<T>;
2395
+ canDeserialize(data: unknown): boolean;
2396
+ /**
2397
+ * Add schema version to a payload.
2398
+ * Used when serializing node changes with schema versioning.
2399
+ */
2400
+ static addSchemaVersion<T extends Record<string, unknown>>(payload: T, schemaVersion: string): T & {
2401
+ _sv: string;
2402
+ };
2403
+ /**
2404
+ * Extract schema version from a payload.
2405
+ */
2406
+ static getSchemaVersion(payload: unknown): string | undefined;
2407
+ }
2408
+ /**
2409
+ * Default V2 serializer instance.
2410
+ */
2411
+ declare const v2Serializer: V2Serializer;
2412
+
2413
+ /**
2414
+ * Serializer registry and exports.
2415
+ *
2416
+ * This module provides:
2417
+ * - Version-specific serializers (V1, V2, V3)
2418
+ * - A registry for managing serializers
2419
+ * - Auto-detection for incoming data
2420
+ * - Helper function for selecting serializer by version
2421
+ *
2422
+ * V3 is the default for new changes (multi-level signature support).
2423
+ */
2424
+
2425
+ /**
2426
+ * Default serializer registry implementation.
2427
+ */
2428
+ declare class DefaultSerializerRegistry implements SerializerRegistry {
2429
+ private serializers;
2430
+ private defaultVersion;
2431
+ constructor(defaultVersion?: number);
2432
+ get(version: number): ChangeSerializer | undefined;
2433
+ getDefault(): ChangeSerializer;
2434
+ register(serializer: ChangeSerializer): void;
2435
+ getVersions(): number[];
2436
+ detect(data: unknown): ChangeSerializer | undefined;
2437
+ }
2438
+ /**
2439
+ * Default serializer registry instance.
2440
+ */
2441
+ declare const serializerRegistry: DefaultSerializerRegistry;
2442
+ /**
2443
+ * Get a serializer for a specific protocol version.
2444
+ *
2445
+ * @param version - Protocol version number
2446
+ * @returns Serializer for that version, or undefined if not found
2447
+ */
2448
+ declare function getSerializer(version: number): ChangeSerializer | undefined;
2449
+ /**
2450
+ * Get the default serializer (for current protocol version).
2451
+ */
2452
+ declare function getDefaultSerializer(): ChangeSerializer;
2453
+ /**
2454
+ * Detect and deserialize a change from raw data.
2455
+ * Automatically detects the format and uses appropriate serializer.
2456
+ *
2457
+ * @param data - Raw serialized data
2458
+ * @returns Deserialized change or error
2459
+ */
2460
+ declare function autoDeserialize<T = unknown>(data: SerializedChange): DeserializeOutcome<T>;
2461
+ /**
2462
+ * Serialize a change using the appropriate serializer for its protocol version.
2463
+ *
2464
+ * @param change - Change to serialize
2465
+ * @returns Serialized data
2466
+ */
2467
+ declare function autoSerialize<T>(change: Change<T>): SerializedChange;
2468
+ /**
2469
+ * Create a serializer registry with custom serializers.
2470
+ *
2471
+ * @param defaultVersion - Default protocol version to use
2472
+ * @param serializers - Optional additional serializers to register
2473
+ * @returns New serializer registry
2474
+ */
2475
+ declare function createSerializerRegistry(defaultVersion?: number, serializers?: ChangeSerializer[]): SerializerRegistry;
2476
+
2477
+ /**
2478
+ * Change Handler Registry - Version-specific handlers for processing changes.
2479
+ *
2480
+ * This registry allows registering handlers by change type and version range,
2481
+ * enabling backward-compatible processing of older change formats.
2482
+ *
2483
+ * @example
2484
+ * ```typescript
2485
+ * const registry = new ChangeHandlerRegistry()
2486
+ *
2487
+ * registry.register({
2488
+ * type: 'yjs-update',
2489
+ * minVersion: 1,
2490
+ * maxVersion: Infinity,
2491
+ * canHandle: (change) => change.type === 'yjs-update',
2492
+ * process: async (change, ctx) => { ... },
2493
+ * validate: (change) => ({ valid: true, errors: [] })
2494
+ * })
2495
+ *
2496
+ * await registry.process(incomingChange, context)
2497
+ * ```
2498
+ */
2499
+
2500
+ /**
2501
+ * Result of validating a change.
2502
+ */
2503
+ interface ValidationResult {
2504
+ /** Whether the change is valid */
2505
+ valid: boolean;
2506
+ /** Error messages if validation failed */
2507
+ errors: string[];
2508
+ }
2509
+ /**
2510
+ * Context provided to change handlers during processing.
2511
+ */
2512
+ interface HandlerContext {
2513
+ /** Store an unknown change for later processing */
2514
+ storeUnknown: (change: Change<unknown>) => Promise<void>;
2515
+ /** Emit an event */
2516
+ emit: (event: string, data: Record<string, unknown>) => void;
2517
+ /** The author DID of the current user */
2518
+ authorDID?: string;
2519
+ }
2520
+ /**
2521
+ * Handler for processing a specific type and version range of changes.
2522
+ */
2523
+ interface ChangeHandler<T = unknown> {
2524
+ /** Change type this handler processes (e.g., 'yjs-update', 'record-create') */
2525
+ type: string;
2526
+ /** Minimum protocol version this handler supports (inclusive) */
2527
+ minVersion: number;
2528
+ /** Maximum protocol version this handler supports (inclusive, use Infinity for latest) */
2529
+ maxVersion: number;
2530
+ /**
2531
+ * Check if this handler can process the given change.
2532
+ * Called after type and version matching for additional checks.
2533
+ */
2534
+ canHandle(change: Change<unknown>): boolean;
2535
+ /**
2536
+ * Process the change and apply it to the store/state.
2537
+ */
2538
+ process(change: Change<T>, context: HandlerContext): Promise<void>;
2539
+ /**
2540
+ * Validate the change structure and content.
2541
+ * Should check that all required fields are present and valid.
2542
+ */
2543
+ validate(change: Change<T>): ValidationResult;
2544
+ }
2545
+ /**
2546
+ * Registry for version-specific change handlers.
2547
+ *
2548
+ * Handlers are matched by:
2549
+ * 1. Change type (exact match)
2550
+ * 2. Protocol version (within handler's min/max range)
2551
+ * 3. Optional canHandle() for additional checks
2552
+ *
2553
+ * If no exact match is found, falls back to the newest handler that
2554
+ * can process older versions (for backward compatibility).
2555
+ */
2556
+ declare class ChangeHandlerRegistry {
2557
+ private handlers;
2558
+ private unknownTypeListeners;
2559
+ private invalidChangeListeners;
2560
+ /**
2561
+ * Register a handler for a change type and version range.
2562
+ */
2563
+ register<T>(handler: ChangeHandler<T>): void;
2564
+ /**
2565
+ * Unregister all handlers for a type.
2566
+ */
2567
+ unregister(type: string): boolean;
2568
+ /**
2569
+ * Get the appropriate handler for a change.
2570
+ * Returns null if no handler can process this change.
2571
+ */
2572
+ getHandler(change: Change<unknown>): ChangeHandler<unknown> | null;
2573
+ /**
2574
+ * Check if any handler can process this change.
2575
+ */
2576
+ canProcess(change: Change<unknown>): boolean;
2577
+ /**
2578
+ * Process a change using the appropriate handler.
2579
+ */
2580
+ process(change: Change<unknown>, context: HandlerContext): Promise<void>;
2581
+ /**
2582
+ * Get all registered handler types.
2583
+ */
2584
+ getTypes(): string[];
2585
+ /**
2586
+ * Get handlers for a specific type.
2587
+ */
2588
+ getHandlersForType(type: string): ChangeHandler<unknown>[];
2589
+ /**
2590
+ * Subscribe to unknown change type events.
2591
+ */
2592
+ onUnknownType(listener: (change: Change<unknown>) => void): () => void;
2593
+ /**
2594
+ * Subscribe to invalid change events.
2595
+ */
2596
+ onInvalidChange(listener: (change: Change<unknown>, errors: string[]) => void): () => void;
2597
+ /**
2598
+ * Clear all registered handlers.
2599
+ */
2600
+ clear(): void;
2601
+ private notifyUnknownType;
2602
+ private notifyInvalidChange;
2603
+ }
2604
+ /**
2605
+ * Default global change handler registry instance.
2606
+ */
2607
+ declare const changeHandlerRegistry: ChangeHandlerRegistry;
2608
+ /**
2609
+ * Create a simple handler that accepts all versions.
2610
+ */
2611
+ declare function createHandler<T>(type: string, process: (change: Change<T>, context: HandlerContext) => Promise<void>, validate?: (change: Change<T>) => ValidationResult): ChangeHandler<T>;
2612
+ /**
2613
+ * Create a handler for a specific version range.
2614
+ */
2615
+ declare function createVersionedHandler<T>(type: string, minVersion: number, maxVersion: number, process: (change: Change<T>, context: HandlerContext) => Promise<void>, validate?: (change: Change<T>) => ValidationResult): ChangeHandler<T>;
2616
+ /**
2617
+ * Create a mock context for testing handlers.
2618
+ */
2619
+ declare function createTestContext(overrides?: Partial<HandlerContext>): HandlerContext;
2620
+ /**
2621
+ * A validation error with optional context.
2622
+ */
2623
+ interface ValidationError {
2624
+ /** Error code */
2625
+ code: string;
2626
+ /** Human-readable message */
2627
+ message: string;
2628
+ /** Property path if applicable */
2629
+ path?: string;
2630
+ }
2631
+ /**
2632
+ * A validation warning (non-fatal issue).
2633
+ */
2634
+ interface ValidationWarning {
2635
+ /** Warning code */
2636
+ code: string;
2637
+ /** Human-readable message */
2638
+ message: string;
2639
+ /** Property path if applicable */
2640
+ path?: string;
2641
+ }
2642
+ /**
2643
+ * Events emitted during change processing.
2644
+ */
2645
+ type HandlerEvent = {
2646
+ type: 'unknownChangeType';
2647
+ change: Change<unknown>;
2648
+ } | {
2649
+ type: 'invalidChange';
2650
+ change: Change<unknown>;
2651
+ errors: string[];
2652
+ } | {
2653
+ type: 'processed';
2654
+ change: Change<unknown>;
2655
+ duration: number;
2656
+ };
2657
+ /**
2658
+ * Result of processing a change.
2659
+ */
2660
+ interface ProcessResult {
2661
+ /** Whether processing succeeded */
2662
+ success: boolean;
2663
+ /** Handler that processed the change (if any) */
2664
+ handlerType?: string;
2665
+ /** Processing duration in ms */
2666
+ duration: number;
2667
+ /** Errors if processing failed */
2668
+ errors?: string[];
2669
+ }
2670
+ /**
2671
+ * Statistics about the registry.
2672
+ */
2673
+ interface RegistryStats {
2674
+ /** Number of registered handler types */
2675
+ typeCount: number;
2676
+ /** Total number of handlers */
2677
+ handlerCount: number;
2678
+ /** Types with multiple version-specific handlers */
2679
+ versionedTypes: string[];
2680
+ }
2681
+
2682
+ /**
2683
+ * Data Integrity Verification
2684
+ *
2685
+ * Utilities for verifying the integrity of change data, including:
2686
+ * - Hash verification
2687
+ * - Signature verification
2688
+ * - Chain integrity checks
2689
+ * - Detection of corruption and repair suggestions
2690
+ *
2691
+ * @example
2692
+ * ```typescript
2693
+ * const report = await verifyIntegrity(changes)
2694
+ * if (report.issues.length > 0) {
2695
+ * console.log('Issues found:', report.issues)
2696
+ * if (report.repairable) {
2697
+ * console.log('All issues can be repaired')
2698
+ * }
2699
+ * }
2700
+ * ```
2701
+ */
2702
+
2703
+ /**
2704
+ * Types of integrity issues that can be detected.
2705
+ */
2706
+ type IntegrityIssueType = 'hash-mismatch' | 'signature-invalid' | 'chain-broken' | 'missing-parent' | 'duplicate-id' | 'invalid-lamport' | 'future-timestamp';
2707
+ /**
2708
+ * Possible repair actions for integrity issues.
2709
+ */
2710
+ type RepairActionType = 'recompute-hash' | 'request-from-peers' | 'remove-duplicate' | 'mark-orphan' | 'none';
2711
+ /**
2712
+ * A repair action that can be taken to fix an integrity issue.
2713
+ */
2714
+ interface RepairAction {
2715
+ type: RepairActionType;
2716
+ description: string;
2717
+ /** Whether this repair can be done automatically */
2718
+ automatic: boolean;
2719
+ }
2720
+ /**
2721
+ * An integrity issue found during verification.
2722
+ */
2723
+ interface IntegrityIssue {
2724
+ /** The change ID that has the issue */
2725
+ changeId: string;
2726
+ /** Type of issue */
2727
+ type: IntegrityIssueType;
2728
+ /** Human-readable description */
2729
+ details: string;
2730
+ /** Severity: error = data may be corrupted, warning = anomaly detected */
2731
+ severity: 'error' | 'warning';
2732
+ /** Suggested repair action (if any) */
2733
+ repairAction?: RepairAction;
2734
+ }
2735
+ /**
2736
+ * Result of an integrity verification.
2737
+ */
2738
+ interface IntegrityReport {
2739
+ /** Number of changes checked */
2740
+ checked: number;
2741
+ /** Number of changes that passed all checks */
2742
+ valid: number;
2743
+ /** List of issues found */
2744
+ issues: IntegrityIssue[];
2745
+ /** Whether all issues can be repaired */
2746
+ repairable: boolean;
2747
+ /** Summary statistics */
2748
+ summary: {
2749
+ errors: number;
2750
+ warnings: number;
2751
+ byType: Record<IntegrityIssueType, number>;
2752
+ };
2753
+ /** Time taken for verification (ms) */
2754
+ durationMs: number;
2755
+ }
2756
+ /**
2757
+ * Options for integrity verification.
2758
+ */
2759
+ interface VerifyOptions {
2760
+ /** Skip signature verification (faster but less thorough) */
2761
+ skipSignatures?: boolean;
2762
+ /** Skip hash verification */
2763
+ skipHashes?: boolean;
2764
+ /** Skip chain verification */
2765
+ skipChain?: boolean;
2766
+ /** Maximum future timestamp allowed (ms from now) */
2767
+ maxFutureTimestamp?: number;
2768
+ /** Progress callback */
2769
+ onProgress?: (checked: number, total: number) => void;
2770
+ }
2771
+ /**
2772
+ * Verify the integrity of a set of changes.
2773
+ *
2774
+ * Checks performed:
2775
+ * 1. Hash verification - computed hash matches stored hash
2776
+ * 2. Signature verification - Ed25519 signature is valid
2777
+ * 3. Chain integrity - parent hashes exist and form valid chains
2778
+ * 4. Duplicate detection - no duplicate change IDs
2779
+ * 5. Timestamp validation - no future timestamps
2780
+ */
2781
+ declare function verifyIntegrity(changes: Change<unknown>[], options?: VerifyOptions): Promise<IntegrityReport>;
2782
+ /**
2783
+ * Quick integrity check - only verifies hashes, not signatures.
2784
+ * Much faster but less thorough.
2785
+ */
2786
+ declare function quickIntegrityCheck(changes: Change<unknown>[]): Promise<IntegrityReport>;
2787
+ /**
2788
+ * Verify a single change.
2789
+ */
2790
+ declare function verifySingleChange(change: Change<unknown>, options?: VerifyOptions): Promise<{
2791
+ valid: boolean;
2792
+ issues: IntegrityIssue[];
2793
+ }>;
2794
+ /**
2795
+ * Find orphaned changes (changes whose parents are missing).
2796
+ */
2797
+ declare function findOrphans(changes: Change<unknown>[]): Change<unknown>[];
2798
+ /**
2799
+ * Find root changes (changes with no parent).
2800
+ */
2801
+ declare function findRoots(changes: Change<unknown>[]): Change<unknown>[];
2802
+ /**
2803
+ * Find head changes (changes that are not parents of any other change).
2804
+ */
2805
+ declare function findHeads(changes: Change<unknown>[]): Change<unknown>[];
2806
+ /**
2807
+ * Get the chain depth (longest path from any root to any head).
2808
+ */
2809
+ declare function getChainDepth(changes: Change<unknown>[]): number;
2810
+ /**
2811
+ * Attempt to repair issues that can be fixed automatically.
2812
+ * Returns the repaired changes and any issues that couldn't be fixed.
2813
+ */
2814
+ declare function attemptRepair(changes: Change<unknown>[], issues: IntegrityIssue[]): Promise<{
2815
+ repaired: Change<unknown>[];
2816
+ remainingIssues: IntegrityIssue[];
2817
+ repairCount: number;
2818
+ }>;
2819
+ /**
2820
+ * Generate a human-readable integrity report.
2821
+ */
2822
+ declare function formatIntegrityReport(report: IntegrityReport): string;
2823
+
2824
+ /**
2825
+ * Deprecation System
2826
+ *
2827
+ * Tracks deprecated protocols, schemas, and features to help developers
2828
+ * migrate away from outdated functionality before it's removed.
2829
+ *
2830
+ * @example
2831
+ * ```typescript
2832
+ * // Check for deprecation warnings
2833
+ * const warnings = checkDeprecations({
2834
+ * protocolVersion: 0,
2835
+ * schemas: ['xnet://xnet.fyi/Task@1.0.0'],
2836
+ * features: ['legacy-auth']
2837
+ * })
2838
+ *
2839
+ * for (const warning of warnings) {
2840
+ * console.warn(warning.message)
2841
+ * }
2842
+ * ```
2843
+ */
2844
+
2845
+ /**
2846
+ * Schema IRI type (mirrors @xnetjs/data to avoid circular dependency).
2847
+ * Format: xnet://namespace/Name@version
2848
+ */
2849
+ type SchemaIRI = `xnet://${string}/${string}` | `xnet://${string}/${string}@${string}`;
2850
+ /**
2851
+ * Type of deprecated item.
2852
+ */
2853
+ type DeprecationType = 'protocol' | 'schema' | 'feature' | 'api';
2854
+ /**
2855
+ * A deprecation notice for a protocol, schema, feature, or API.
2856
+ */
2857
+ interface DeprecationNotice {
2858
+ /** Type of deprecated item */
2859
+ type: DeprecationType;
2860
+ /** Identifier of the deprecated item */
2861
+ subject: string;
2862
+ /** Human-readable description */
2863
+ description: string;
2864
+ /** Version where this was deprecated */
2865
+ deprecatedIn: string;
2866
+ /** Version where this will be/was removed (if known) */
2867
+ removedIn?: string;
2868
+ /** What to use instead */
2869
+ alternative?: string;
2870
+ /** URL to migration documentation */
2871
+ migrationGuide?: string;
2872
+ /** Date when deprecated */
2873
+ deprecatedDate?: string;
2874
+ /** Date when removed/sunset (if known) */
2875
+ sunsetDate?: string;
2876
+ }
2877
+ /**
2878
+ * Context for checking deprecations.
2879
+ */
2880
+ interface DeprecationContext {
2881
+ /** Current protocol version in use */
2882
+ protocolVersion?: number;
2883
+ /** Schema IRIs currently in use */
2884
+ schemas?: (SchemaIRI | string)[];
2885
+ /** Features currently enabled */
2886
+ features?: (FeatureFlag | string)[];
2887
+ /** Package version */
2888
+ packageVersion?: string;
2889
+ }
2890
+ /**
2891
+ * A deprecation warning generated from context.
2892
+ */
2893
+ interface DeprecationWarning {
2894
+ /** The deprecation notice that triggered this warning */
2895
+ notice: DeprecationNotice;
2896
+ /** Human-readable warning message */
2897
+ message: string;
2898
+ /** Recommended action to take */
2899
+ action: string;
2900
+ /** Severity: 'warning' for deprecated, 'error' for removed */
2901
+ severity: 'warning' | 'error';
2902
+ /** Days until removal (if sunsetDate is known) */
2903
+ daysUntilRemoval?: number;
2904
+ }
2905
+ /**
2906
+ * Callback for deprecation events.
2907
+ */
2908
+ type DeprecationCallback = (warning: DeprecationWarning) => void;
2909
+ /**
2910
+ * Registry of all known deprecations.
2911
+ *
2912
+ * Add new entries here when deprecating functionality.
2913
+ * This serves as both documentation and runtime checking.
2914
+ */
2915
+ declare const DEPRECATIONS: DeprecationNotice[];
2916
+ /**
2917
+ * Check for deprecation warnings based on current context.
2918
+ *
2919
+ * @param context - The current usage context to check
2920
+ * @returns Array of deprecation warnings
2921
+ *
2922
+ * @example
2923
+ * ```typescript
2924
+ * const warnings = checkDeprecations({
2925
+ * protocolVersion: 0,
2926
+ * schemas: ['xnet://xnet.fyi/Task@1.0.0']
2927
+ * })
2928
+ *
2929
+ * if (warnings.length > 0) {
2930
+ * console.warn('Deprecation warnings:', warnings)
2931
+ * }
2932
+ * ```
2933
+ */
2934
+ declare function checkDeprecations(context: DeprecationContext): DeprecationWarning[];
2935
+ /**
2936
+ * Default deprecation policy.
2937
+ *
2938
+ * - Minimum deprecation period: 6 months
2939
+ * - Breaking changes require major version bump
2940
+ * - Warnings logged to console in development
2941
+ * - Errors thrown in strict mode for removed functionality
2942
+ */
2943
+ declare const DEPRECATION_POLICY: {
2944
+ /** Minimum time between deprecation and removal */
2945
+ minimumDeprecationPeriodDays: number;
2946
+ /** Whether to log warnings to console */
2947
+ logWarnings: boolean;
2948
+ /** Whether to throw errors for removed functionality */
2949
+ strictMode: boolean;
2950
+ /** Console logger for warnings */
2951
+ logger: (message: string) => void;
2952
+ };
2953
+ /**
2954
+ * Configure the deprecation policy.
2955
+ */
2956
+ declare function configureDeprecationPolicy(options: Partial<typeof DEPRECATION_POLICY>): void;
2957
+ /**
2958
+ * Log a deprecation warning (once per session).
2959
+ *
2960
+ * @param warning - The deprecation warning to log
2961
+ */
2962
+ declare function logDeprecation(warning: DeprecationWarning): void;
2963
+ /**
2964
+ * Check and log deprecation warnings for a context.
2965
+ *
2966
+ * @param context - The current usage context
2967
+ * @returns Array of warnings (also logs them)
2968
+ */
2969
+ declare function checkAndLogDeprecations(context: DeprecationContext): DeprecationWarning[];
2970
+ /**
2971
+ * Clear the logged deprecations set (for testing).
2972
+ */
2973
+ declare function clearLoggedDeprecations(): void;
2974
+ /**
2975
+ * Get all deprecation notices of a specific type.
2976
+ */
2977
+ declare function getDeprecationsByType(type: DeprecationType): DeprecationNotice[];
2978
+ /**
2979
+ * Get a specific deprecation notice by subject.
2980
+ */
2981
+ declare function getDeprecation(subject: string): DeprecationNotice | undefined;
2982
+ /**
2983
+ * Check if a specific item is deprecated.
2984
+ */
2985
+ declare function isDeprecated(subject: string): boolean;
2986
+ /**
2987
+ * Check if a specific item has been removed.
2988
+ */
2989
+ declare function isRemoved(subject: string): boolean;
2990
+ /**
2991
+ * Register a new deprecation notice.
2992
+ * Useful for applications to add their own deprecations.
2993
+ */
2994
+ declare function registerDeprecation(notice: DeprecationNotice): void;
2995
+ /**
2996
+ * Error thrown when using removed functionality in strict mode.
2997
+ */
2998
+ declare class DeprecationError extends Error {
2999
+ readonly warning: DeprecationWarning;
3000
+ constructor(warning: DeprecationWarning);
3001
+ }
3002
+ /**
3003
+ * Format deprecation warnings as a human-readable report.
3004
+ */
3005
+ declare function formatDeprecationReport(warnings: DeprecationWarning[]): string;
3006
+
3007
+ /**
3008
+ * Periodic Integrity Monitor
3009
+ *
3010
+ * A background service that periodically checks data integrity and
3011
+ * emits events when issues are detected.
3012
+ *
3013
+ * @example
3014
+ * ```typescript
3015
+ * const monitor = createIntegrityMonitor({
3016
+ * getChanges: () => storage.getAllChanges(),
3017
+ * intervalMs: 60000, // Check every minute
3018
+ * onIssues: (report) => {
3019
+ * console.warn('Integrity issues:', report.issues)
3020
+ * }
3021
+ * })
3022
+ *
3023
+ * monitor.start()
3024
+ * // ... later
3025
+ * monitor.stop()
3026
+ * ```
3027
+ */
3028
+
3029
+ /**
3030
+ * Configuration for the integrity monitor.
3031
+ */
3032
+ interface IntegrityMonitorConfig {
3033
+ /** Function to retrieve all changes for verification */
3034
+ getChanges: () => Promise<Change<unknown>[]> | Change<unknown>[];
3035
+ /** Interval between checks in milliseconds (default: 5 minutes) */
3036
+ intervalMs?: number;
3037
+ /** Use quick check (skip signatures) for periodic checks */
3038
+ quickCheck?: boolean;
3039
+ /** Additional verification options */
3040
+ verifyOptions?: VerifyOptions;
3041
+ /** Called when issues are detected */
3042
+ onIssues?: (report: IntegrityReport) => void;
3043
+ /** Called after each check (including clean ones) */
3044
+ onCheck?: (report: IntegrityReport) => void;
3045
+ /** Called when an error occurs during check */
3046
+ onError?: (error: Error) => void;
3047
+ /** Whether to run a check immediately on start */
3048
+ checkOnStart?: boolean;
3049
+ /** Minimum number of changes before running checks */
3050
+ minChangesForCheck?: number;
3051
+ /** Enable debug logging */
3052
+ debug?: boolean;
3053
+ }
3054
+ /**
3055
+ * Statistics from the integrity monitor.
3056
+ */
3057
+ interface IntegrityMonitorStats {
3058
+ /** Number of checks performed */
3059
+ checksPerformed: number;
3060
+ /** Number of issues found across all checks */
3061
+ totalIssuesFound: number;
3062
+ /** Time of last check */
3063
+ lastCheckAt: Date | null;
3064
+ /** Duration of last check in ms */
3065
+ lastCheckDurationMs: number;
3066
+ /** Result of last check */
3067
+ lastReport: IntegrityReport | null;
3068
+ /** Whether the monitor is currently running */
3069
+ isRunning: boolean;
3070
+ /** Whether a check is currently in progress */
3071
+ isChecking: boolean;
3072
+ }
3073
+ /**
3074
+ * The integrity monitor instance.
3075
+ */
3076
+ interface IntegrityMonitor {
3077
+ /** Start periodic monitoring */
3078
+ start(): void;
3079
+ /** Stop periodic monitoring */
3080
+ stop(): void;
3081
+ /** Run a check immediately (independent of the periodic schedule) */
3082
+ checkNow(): Promise<IntegrityReport>;
3083
+ /** Get current statistics */
3084
+ getStats(): IntegrityMonitorStats;
3085
+ /** Check if the monitor is running */
3086
+ isRunning(): boolean;
3087
+ /** Update configuration */
3088
+ configure(config: Partial<IntegrityMonitorConfig>): void;
3089
+ }
3090
+ /**
3091
+ * Create an integrity monitor instance.
3092
+ */
3093
+ declare function createIntegrityMonitor(config: IntegrityMonitorConfig): IntegrityMonitor;
3094
+ /**
3095
+ * Options for creating a React-friendly integrity monitor.
3096
+ */
3097
+ interface ReactIntegrityMonitorOptions extends IntegrityMonitorConfig {
3098
+ /** Emit state changes for React to observe */
3099
+ onStateChange?: (stats: IntegrityMonitorStats) => void;
3100
+ }
3101
+ /**
3102
+ * Create an integrity monitor with React-friendly state updates.
3103
+ * This wraps the monitor to emit state changes that can be observed
3104
+ * by React hooks.
3105
+ */
3106
+ declare function createReactIntegrityMonitor(options: ReactIntegrityMonitorOptions): IntegrityMonitor;
3107
+
3108
+ /**
3109
+ * Security policy for operation-based security level selection.
3110
+ *
3111
+ * Different operations may warrant different security levels:
3112
+ * - High-frequency, low-value ops (cursors) → Level 0 (fast)
3113
+ * - Regular data ops → Level 1 (hybrid)
3114
+ * - Critical ops (key rotation) → Level 2 (maximum)
3115
+ */
3116
+
3117
+ /**
3118
+ * Security policy configuration.
3119
+ */
3120
+ interface SecurityPolicy {
3121
+ /** Default level for operations not explicitly configured */
3122
+ default: SecurityLevel;
3123
+ /** Per-operation type overrides */
3124
+ overrides: Record<string, SecurityLevel>;
3125
+ }
3126
+ /**
3127
+ * Common operation types for security level selection.
3128
+ */
3129
+ type OperationType = 'cursor-update' | 'presence-update' | 'typing-indicator' | 'viewport-update' | 'awareness-update' | 'node-create' | 'node-update' | 'node-delete' | 'yjs-update' | 'comment-add' | 'key-rotation' | 'permission-grant' | 'permission-revoke' | 'identity-recovery' | 'share-create';
3130
+ /**
3131
+ * Default security policy.
3132
+ *
3133
+ * Uses Level 0 (Ed25519-only) by default for performance.
3134
+ * High-frequency ephemeral operations use Level 0.
3135
+ * Regular operations inherit the default.
3136
+ * Critical operations can be configured to use Level 1 or 2.
3137
+ */
3138
+ declare const DEFAULT_SECURITY_POLICY: SecurityPolicy;
3139
+ /**
3140
+ * Hybrid security policy for when PQ protection is desired.
3141
+ *
3142
+ * Uses Level 1 (hybrid) for regular operations.
3143
+ * Still uses Level 0 for high-frequency ephemeral ops.
3144
+ * Critical ops use Level 2 (PQ-only).
3145
+ */
3146
+ declare const HYBRID_SECURITY_POLICY: SecurityPolicy;
3147
+ /**
3148
+ * Maximum security policy for high-security environments.
3149
+ *
3150
+ * Uses Level 2 (PQ-only) for everything except ephemeral ops.
3151
+ */
3152
+ declare const MAX_SECURITY_POLICY: SecurityPolicy;
3153
+ /**
3154
+ * Get the security level for an operation type.
3155
+ *
3156
+ * @param operationType - The type of operation
3157
+ * @param policy - Security policy to use (default: DEFAULT_SECURITY_POLICY)
3158
+ * @returns The appropriate security level
3159
+ *
3160
+ * @example
3161
+ * ```typescript
3162
+ * // Get level for a cursor update (fast)
3163
+ * const level = getSecurityLevel('cursor-update') // 0
3164
+ *
3165
+ * // Get level for a node creation
3166
+ * const level = getSecurityLevel('node-create') // uses policy default
3167
+ *
3168
+ * // Use hybrid policy for PQ protection
3169
+ * const level = getSecurityLevel('node-create', HYBRID_SECURITY_POLICY) // 1
3170
+ * ```
3171
+ */
3172
+ declare function getSecurityLevel(operationType: string, policy?: SecurityPolicy): SecurityLevel;
3173
+ /**
3174
+ * Check if an operation type is ephemeral (high-frequency, low-value).
3175
+ *
3176
+ * Ephemeral operations typically use Level 0 for performance.
3177
+ */
3178
+ declare function isEphemeralOperation(operationType: string): boolean;
3179
+ /**
3180
+ * Check if an operation type is critical (requires high security).
3181
+ */
3182
+ declare function isCriticalOperation(operationType: string): boolean;
3183
+ /**
3184
+ * Create a custom security policy.
3185
+ *
3186
+ * @example
3187
+ * ```typescript
3188
+ * const myPolicy = createSecurityPolicy({
3189
+ * default: 1,
3190
+ * overrides: {
3191
+ * 'cursor-update': 0,
3192
+ * 'key-rotation': 2
3193
+ * }
3194
+ * })
3195
+ * ```
3196
+ */
3197
+ declare function createSecurityPolicy(options?: Partial<SecurityPolicy>): SecurityPolicy;
3198
+ /**
3199
+ * Merge policies, with later policies taking precedence.
3200
+ */
3201
+ declare function mergeSecurityPolicies(...policies: Partial<SecurityPolicy>[]): SecurityPolicy;
3202
+
3203
+ export { ALL_FEATURES, type AttestationVerificationResult, type AttestationVerifyResult, type AuthorizedDoc, type AuthorizedRoom, type AuthorizedStateAdapter, AuthorizedSyncManager, type AuthorizedSyncManagerOptions, AuthorizedYjsError, AuthorizedYjsSyncProvider, type AuthorizedYjsSyncProviderOptions, BaseSyncProvider, type BatchFlushCallback, COMPACTION_TIME_THRESHOLD, COMPACTION_UPDATE_THRESHOLD, CURRENT_PROTOCOL_VERSION, type ChainValidationResult, type Change, type ChangeHandler, ChangeHandlerRegistry, type ChangeSerializer, type ClientIdAttestation, type ClientIdAttestationV1, type ClientIdAttestationV2, type ClientIdAttestationWire, type ClientIdMap, ClientIdMapImpl, type ContentKeyProvider, type CreateAttestationOptions, type CreateChangeOptions, type CreateEnvelopeOptions, type CreateYjsChangeOptions, DEFAULT_BATCHER_CONFIG, DEFAULT_RATE_LIMITER_CONFIG, DEFAULT_SECURITY_POLICY, DEFAULT_YJS_SCORING_CONFIG, DEPRECATIONS, DEPRECATION_POLICY, type DeprecationCallback, type DeprecationContext, DeprecationError, type DeprecationNotice, type DeprecationType, type DeprecationWarning, type DeserializeError, type DeserializeOutcome, type DeserializeResult, type EncryptedYjsState, type EnvelopeVerificationResult, type EnvelopeVerifyResult, FEATURES, type FeatureConfig, type FeatureFlag, type FeatureValidationError, type FeatureValidationResult, type FeatureValidationWarning, type Fork, type GrantEventStore, HYBRID_SECURITY_POLICY, type HandlerContext, type HandlerEvent, type IntegrityIssue, type IntegrityIssueType, type IntegrityMonitor, type IntegrityMonitorConfig, type IntegrityMonitorStats, type IntegrityReport, type LamportClock, type LamportTimestamp, MAX_SECURITY_POLICY, MAX_YJS_DOC_SIZE, MAX_YJS_UPDATES_PER_MINUTE, MAX_YJS_UPDATES_PER_SECOND, MAX_YJS_UPDATE_SIZE, type MergeUpdatesFn, type NegotiatedSession, type NegotiationFailure, type NegotiationResult, type NegotiationWarning, type OperationType, type PeerAction, type PeerCapabilities, type PeerInfo, type PersistedDocState, type ProcessResult, type RateLimiterConfig, type ReactIntegrityMonitorOptions, type RecipientKeyResolver, type RegistryStats, type RepairAction, type RepairActionType, type SecurityPolicy, type SerializeOptions, type SerializedChange, type SerializerRegistry, type SignedYjsEnvelope, type SignedYjsEnvelopeV1, type SignedYjsEnvelopeV2, type SignedYjsEnvelopeWire, type SyncEventListener, type SyncProvider, type SyncProviderEvents, type SyncProviderOptions, type SyncStatus, type UnsignedChange, type UnsignedYjsChange, V1Serializer, V2Serializer, type ValidationError, type ValidationResult, type ValidationWarning, type VerifyAttestationOptions, type VerifyEnvelopeOptions, type VerifyOptions, VersionNegotiator, type YDocCodec, type YDocLike, YJS_CHANGE_TYPE, YJS_RATE_BURST_ALLOWANCE, YJS_SYNC_CHUNK_SIZE, type YjsAuthDecision, YjsAuthGate, type YjsAuthGateOptions, YjsBatcher, type YjsBatcherConfig, type YjsChange, YjsCheckpointer, type YjsCheckpointerOptions, YjsIntegrityError, type YjsPeerMetrics, YjsPeerScorer, YjsRateLimiter, type YjsScoringConfig, YjsStateIntegrityError, type YjsUpdatePayload, type YjsViolationType, addDependencies, attemptRepair, autoDeserialize, autoSerialize, calculateChunkCount, changeHandlerRegistry, checkAndLogDeprecations, checkDeprecations, chunkUpdate, clearLoggedDeprecations, compareLamportTimestamps, computeChangeHash, configureDeprecationPolicy, createBatchId, createChangeId, createClientIdAttestation, createClientIdAttestationV1, createClientIdAttestationV2, createHandler, createIntegrityMonitor, createLamportClock, createLocalCapabilities, createPersistedDocState, createReactIntegrityMonitor, createSecurityPolicy, createSerializerRegistry, createTestContext, createUnsignedChange, createUnsignedYjsChange, createVersionedHandler, createYjsChange, decryptYjsState, defaultNegotiator, deserializeClientIdAttestation, deserializeEncryptedYjsState, deserializeYjsEnvelope, detectFork, diffFeatures, encryptYjsState, envelopeSize, findCommonAncestor, findHeads, findOrphans, findRoots, formatDeprecationReport, formatIntegrityReport, getAllDependencies, getAncestry, getChainDepth, getChainHeads, getChainRoots, getChangeNodeId, getDefaultSerializer, getDeprecation, getDeprecationsByType, getEnabledFeatures, getFeatureConflicts, getFeatureDependencies, getFeatureVersion, getForks, getOptionalFeatures, getRequiredFeatures, getSecurityLevel, getSerializer, hasSignedEnvelope, hashYjsState, intersectFeatures, isAfter, isBefore, isCriticalOperation, isDeprecated, isDocumentTooLarge, isEphemeralOperation, isFeatureAvailable, isFeatureEnabled, isLegacyUpdate, isNodeChange, isRemoved, isUpdateTooLarge, isV1Attestation, isV1Envelope, isV2Attestation, isV2Envelope, isYjsChange, loadVerifiedState, logDeprecation, maxTime, mergeSecurityPolicies, parseCapabilities, parseTimestamp, quickIntegrityCheck, reassembleChunks, receive, registerDeprecation, serializeClientIdAttestation, serializeEncryptedYjsState, serializeTimestamp, serializeYjsEnvelope, serializerRegistry, shouldCompact, signChange, signYjsUpdate, signYjsUpdateBatch, signYjsUpdateV1, signYjsUpdateV2, tick, toEncryptedData, topologicalSort, v1Serializer, v2Serializer, validateChain, validateClientIdOwnership, validateFeatureSet, verifyChange, verifyChangeHash, verifyClientIdAttestation, verifyClientIdAttestationV1, verifyClientIdAttestationV2, verifyIntegrity, verifyPersistedDocState, verifySingleChange, verifyYjsEnvelope, verifyYjsEnvelopeQuick, verifyYjsEnvelopeV1, verifyYjsEnvelopeV2, verifyYjsStateIntegrity };