@derec-alliance/react-native 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/types.ts CHANGED
@@ -28,6 +28,136 @@ export interface SecretStore {
28
28
  remove(secretId: string, channelId: string, kind: 0 | 1 | 2): Promise<void>;
29
29
  }
30
30
 
31
+
32
+ /**
33
+ * A channel's lifecycle status, as the Rust variant name the core emits.
34
+ */
35
+ export type ChannelStatusName = "Pending" | "Paired" | "Unpairing";
36
+
37
+ /**
38
+ * A peer's role on a helper channel, as the Rust variant name.
39
+ */
40
+ export type SenderKindName =
41
+ | "Owner"
42
+ | "Helper"
43
+ | "ReplicaSource"
44
+ | "ReplicaDestination";
45
+
46
+ /**
47
+ * A member's role within a replica group, as the Rust variant name.
48
+ */
49
+ export type ReplicaRoleName = "Source" | "Destination";
50
+
51
+ /**
52
+ * Narrows a listing from {@link ChannelStore}.
53
+ *
54
+ * Every field is a restriction, and every field's empty value means "do not
55
+ * restrict on this" — a filter of all-empties selects everything.
56
+ * Restrictions combine with AND, and `exclude` is applied last, overriding
57
+ * `ids`.
58
+ *
59
+ * **Returning everything under the `secretId` and ignoring the filter is
60
+ * correct**, and the implementation to write unless there is a measured reason
61
+ * not to. The library re-applies the filter to whatever you return and drops
62
+ * what it excludes, so a superset is trimmed before anything acts on it.
63
+ *
64
+ * Pushing the filter into your query — a `WHERE` clause, a key-condition
65
+ * expression — is an optimization you opt into. It saves transferring rows the
66
+ * caller discards, which costs bandwidth everywhere and real money on a metered
67
+ * backing that bills by bytes read. Verify one against
68
+ * `library/tests/fixtures/channel_filter.json`.
69
+ *
70
+ * The asymmetry is what makes pushdown worth verifying: the re-check can drop
71
+ * rows but cannot recover one that was never returned, so selecting too *few*
72
+ * is undetectable at runtime — no exception, no event, just a share that was
73
+ * never published. That matters most here, because TypeScript accepts a
74
+ * function of fewer parameters where more are declared: a store written before
75
+ * this parameter existed still satisfies the interface and compiles clean under
76
+ * `--strict`, so the type system cannot see the gap either.
77
+ *
78
+ * Ids are decimal strings, like every other `u64` on this bridge.
79
+ */
80
+ export interface ChannelFilter<Role> {
81
+ /** Restrict to these ids. Empty selects every record. */
82
+ ids: string[];
83
+ /** Restrict to these statuses. Empty selects any status. */
84
+ status: ChannelStatusName[];
85
+ /** Restrict to this role. `null` selects any role. */
86
+ role: Role | null;
87
+ /** Omit these ids, applied after `ids`. Empty omits nothing. */
88
+ exclude: string[];
89
+ }
90
+
91
+ /**
92
+ * Whether a channel or member with these attributes survives `filter`.
93
+ *
94
+ * Every empty field means "do not restrict", `exclude` is applied after `ids`,
95
+ * and the restrictions combine with AND — the same contract the core states on
96
+ * `ChannelFilter`. A store whose backing cannot express the filter as a query
97
+ * can list and call this; that is correct but transfers the rows the filter
98
+ * exists to leave behind.
99
+ *
100
+ * `id` is a decimal string, as ids are everywhere on this bridge. `role` is the
101
+ * peer's `SenderKind` name for `listHelpers` and the member's `ReplicaRole`
102
+ * name for `listReplicas`.
103
+ */
104
+ export function channelFilterMatches(
105
+ filter: HelperFilter | ReplicaFilter | null | undefined,
106
+ id: string,
107
+ status: ChannelStatusName,
108
+ role: SenderKindName | ReplicaRoleName,
109
+ ): boolean {
110
+ if (!filter) return true;
111
+ const ids = filter.ids ?? [];
112
+ const statuses = filter.status ?? [];
113
+ const exclude = filter.exclude ?? [];
114
+ if (ids.length > 0 && !ids.includes(id)) return false;
115
+ if (statuses.length > 0 && !statuses.includes(status)) return false;
116
+ if (filter.role != null && filter.role !== role) return false;
117
+ return !exclude.includes(id);
118
+ }
119
+
120
+ /**
121
+ * The endpoints a peer-supplied message advertises, in the peer's own order.
122
+ *
123
+ * Yields `supported_transports` when it is non-empty, and otherwise the
124
+ * singular `transport_protocol` — which is how every implementation predating
125
+ * the offer list advertises, and the reason this is a function rather than a
126
+ * field read. Reading `transport_protocol` directly is a bug: its meaning
127
+ * narrowed to "one entry of a list, and possibly absent", so a peer that has
128
+ * moved past it looks unreachable to a reader that was correct before 0.0.3.
129
+ *
130
+ * Reports what was advertised, not what is acceptable — nothing here is
131
+ * validated, and the protocol still applies its own transport policy to
132
+ * whatever it records.
133
+ */
134
+ export function advertisedEndpoints(
135
+ message:
136
+ | {
137
+ transport_protocol?: TransportProtocol;
138
+ supported_transports?: TransportProtocol[];
139
+ }
140
+ | null
141
+ | undefined,
142
+ ): TransportProtocol[] {
143
+ if (!message) return [];
144
+ const offers = message.supported_transports ?? [];
145
+ if (offers.length > 0) return offers;
146
+ return message.transport_protocol ? [message.transport_protocol] : [];
147
+ }
148
+
149
+ /**
150
+ * Narrows `listHelpers`. Ids are the channel's `channel_id` and the role is
151
+ * the **peer's** `peer_role`.
152
+ */
153
+ export type HelperFilter = ChannelFilter<SenderKindName>;
154
+
155
+ /**
156
+ * Narrows `listReplicas`. Ids are the member's `replica_id` and the role is
157
+ * the member's `role`.
158
+ */
159
+ export type ReplicaFilter = ChannelFilter<ReplicaRoleName>;
160
+
31
161
  /**
32
162
  * Channel-record persistence.
33
163
  *
@@ -48,7 +178,7 @@ export interface SecretStore {
48
178
  *
49
179
  * `listHelpers` and `listReplicas` are **not** arrays of that union — they
50
180
  * return a JSON array of the **inner** records with the tag stripped:
51
- * `[{ channel_id, transport, ... }, ...]`, `HelperChannel` for the first and
181
+ * `[{ schema_version, channel_id, transports, ... }, ...]`, `HelperChannel` for the first and
52
182
  * `ReplicaMember` for the second. Wrapping each element back in
53
183
  * `{ "Helper": ... }` will not decode.
54
184
  *
@@ -77,8 +207,19 @@ export interface ChannelStore {
77
207
  bytes: Uint8Array,
78
208
  ): Promise<void>;
79
209
  remove(secretId: string, channelId: string, replicaId: string): Promise<boolean>;
80
- /** JSON array of the helper channels stored under `secretId`. */
81
- listHelpers(secretId: string): Promise<Uint8Array | null | undefined>;
210
+ /**
211
+ * JSON array of the helper channels stored under `secretId` that `filter`
212
+ * selects.
213
+ *
214
+ * Apply the filter in your query rather than listing everything and
215
+ * discarding rows; see {@link ChannelFilter}. The library re-applies it to
216
+ * whatever you return, so ignoring it is slow rather than wrong — but
217
+ * returning fewer rows than it selects is wrong, and undetectable.
218
+ */
219
+ listHelpers(
220
+ secretId: string,
221
+ filter: HelperFilter,
222
+ ): Promise<Uint8Array | null | undefined>;
82
223
  /**
83
224
  * JSON array of the replica-group members stored under `secretId`,
84
225
  * including this device's own row.
@@ -97,7 +238,10 @@ export interface ChannelStore {
97
238
  * insertion order after arbitrary edits are both effectively arbitrary.
98
239
  * Order explicitly to make succession predictable.
99
240
  */
100
- listReplicas(secretId: string): Promise<Uint8Array | null | undefined>;
241
+ listReplicas(
242
+ secretId: string,
243
+ filter: ReplicaFilter,
244
+ ): Promise<Uint8Array | null | undefined>;
101
245
  linkChannel(
102
246
  secretId: string,
103
247
  channelId: string,
@@ -155,7 +299,7 @@ export interface UserSecretStore {
155
299
  * returns the exact blob it received, `remove` drops the row, and
156
300
  * `loadAll` returns every blob whose `kind` matches the requested
157
301
  * category (`0` = PendingVerification, `1` = PendingRecovery,
158
- * `2` = PendingUnpair, `3` = SharingRound, `4` = PendingSyncCheck).
302
+ * `2` = PendingUnpair, `3` = SharingRound, `4` = PendingReplicaDiscovery).
159
303
  *
160
304
  * Rows are keyed by `(secretId, StateKey)` — the `keyJson` buffer is
161
305
  * a JSON object `{ kind, channel_id?, version? }` matching the `kind`
@@ -197,7 +341,109 @@ export interface StateStore {
197
341
  * DeRec over request/response transports" in the Rust SDK README.
198
342
  */
199
343
  export interface Transport {
200
- send(endpoint: { protocol: string; uri: string }, message: Uint8Array): Promise<void>;
344
+ /**
345
+ * Delivers `message` to a peer reachable at any of `endpoints`.
346
+ *
347
+ * `endpoints` are the addresses that peer advertised, in the order it
348
+ * offered them, already filtered to those the library will record. The
349
+ * library does not rank them: which to dial, and whether to fall back when
350
+ * one is unreachable, is this implementation's choice. Never empty.
351
+ *
352
+ * Delivery to any one endpoint is success. Reject only when the message
353
+ * reached none of them.
354
+ *
355
+ * **Deliver once.** Every entry addresses the same peer, so sending to all
356
+ * of them delivers one authenticated message several times. Stop at the
357
+ * first success. The protocol's handlers are idempotent, so a duplicate
358
+ * does not corrupt state, but it is still a duplicate to anything counting
359
+ * messages, and a peer entitled to treat re-delivery as a replay will.
360
+ *
361
+ * **Prefer an adapter to writing this by hand.** Choosing which endpoint to
362
+ * dial is yours and stays here; the bookkeeping around it is the same
363
+ * everywhere and is already written and tested. Write a {@link SendOne} and
364
+ * wrap it in {@link sequentialFailover}. Taking `endpoints[0]` type-checks,
365
+ * passes every test, and silently gives up the failover the list exists to
366
+ * provide — if that is genuinely wanted, say so with
367
+ * {@link singleEndpointTransport} rather than by indexing.
368
+ */
369
+ send(
370
+ endpoints: ReadonlyArray<{ protocol: string; uri: string }>,
371
+ message: Uint8Array,
372
+ ): Promise<void>;
373
+ }
374
+
375
+ /**
376
+ * Delivers one message to one endpoint.
377
+ *
378
+ * The narrow half of a transport: everything genuinely about dialing, and
379
+ * nothing about which endpoint to dial. Pass one of these to
380
+ * {@link sequentialFailover} or {@link singleEndpointTransport} to get a
381
+ * {@link Transport}.
382
+ *
383
+ * Rejecting means the endpoint did not receive the message. The rejection
384
+ * reason need not distinguish "unreachable" from "rejected":
385
+ * {@link sequentialFailover} treats both as a reason to try the next endpoint,
386
+ * which is the safe reading. Trying an endpoint that would have refused costs
387
+ * a round trip; skipping one that would have worked costs the delivery.
388
+ */
389
+ export type SendOne = (
390
+ endpoint: { protocol: string; uri: string },
391
+ message: Uint8Array,
392
+ ) => Promise<void>;
393
+
394
+ /**
395
+ * Builds a {@link Transport} that tries each endpoint in the order the peer
396
+ * offered it and stops at the first success.
397
+ *
398
+ * An error is thrown only when every endpoint failed. The message is delivered
399
+ * at most once.
400
+ *
401
+ * This is the right default. A peer advertising several endpoints is saying it
402
+ * can be reached at any of them, and the reason 0.0.3 records the whole list is
403
+ * so one being down does not end the conversation.
404
+ */
405
+ export function sequentialFailover(dialer: SendOne): Transport {
406
+ return {
407
+ async send(endpoints, message) {
408
+ let last: unknown;
409
+ for (const endpoint of endpoints) {
410
+ try {
411
+ await dialer(endpoint, message);
412
+ return;
413
+ } catch (e) {
414
+ last = e;
415
+ }
416
+ }
417
+ // `endpoints` is never empty — the library refuses to record a peer
418
+ // whose endpoints were all filtered away — so reaching here means at
419
+ // least one attempt was made and `last` is populated.
420
+ throw last ?? new Error('send was called with no endpoints');
421
+ },
422
+ };
423
+ }
424
+
425
+ /**
426
+ * Builds a {@link Transport} that uses the first endpoint only.
427
+ *
428
+ * Reproduces the pre-0.0.3 behaviour exactly, for an application that genuinely
429
+ * serves one endpoint or has a reason not to fail over.
430
+ *
431
+ * It exists so that choosing it is visible. `endpoints[0]` written inline looks
432
+ * like an implementation detail and reads as finished; naming this records that
433
+ * failover was considered and declined, which is a claim a reviewer can
434
+ * disagree with. If the peers this application talks to advertise more than one
435
+ * endpoint, prefer {@link sequentialFailover} — every endpoint after the first
436
+ * is reachability being thrown away.
437
+ */
438
+ export function singleEndpointTransport(dialer: SendOne): Transport {
439
+ return {
440
+ async send(endpoints, message) {
441
+ if (endpoints.length === 0) {
442
+ throw new Error('send was called with no endpoints');
443
+ }
444
+ await dialer(endpoints[0]!, message);
445
+ },
446
+ };
201
447
  }
202
448
 
203
449
  export enum SenderKind {
@@ -250,12 +496,12 @@ export enum FlowKind {
250
496
  /** Ask the replica group whether this device is behind, and catch up if it
251
497
  * is. Replica-only, and takes no parameters — the group and this device's
252
498
  * own version both come from the stores. */
253
- SyncCheck = 7,
499
+ ReplicaDiscovery = 7,
254
500
  /** Remove a member from the replica group. Replica-only. Naming this device
255
501
  * is a voluntary departure; naming another is an eviction. Params:
256
502
  * `{ replica_id: string; memo?: string }` — `replica_id` is a decimal
257
503
  * string so ids above 2^53 survive JS number handling. */
258
- RemoveReplica = 8,
504
+ UnpairReplica = 8,
259
505
  }
260
506
 
261
507
  export type UnpairAck = "required" | "not_required";
@@ -264,6 +510,13 @@ export interface ContactMessage {
264
510
  channel_id: bigint;
265
511
  /** `ContactMode` numeric value (0 = INLINE_KEYS, 1 = HASHED_KEYS, 2 = NO_KEYS). */
266
512
  contact_mode: number;
513
+ /**
514
+ * @deprecated Reading this field directly is incorrect: its meaning narrowed
515
+ * to "one entry of a list, and possibly absent", so a peer advertising only
516
+ * `supported_transports` looks unreachable to a reader that was correct
517
+ * before 0.0.3. Call {@link advertisedEndpoints}, which resolves both
518
+ * spellings. Removed at 0.0.5.
519
+ */
267
520
  transport_protocol?: TransportProtocol;
268
521
  nonce: bigint;
269
522
  /** Present only when `contact_mode === ContactMode.InlineKeys`. */
@@ -273,6 +526,10 @@ export interface ContactMessage {
273
526
  /** Present only when `contact_mode === ContactMode.HashedKeys`. SHA-384 digest (48 bytes). */
274
527
  contact_binding_hash?: Uint8Array;
275
528
  timestamp?: Timestamp;
529
+ /** Every transport endpoint the creator of this contact can be reached
530
+ * on, in its own preference order. Empty means "only
531
+ * `transport_protocol` is offered". */
532
+ supported_transports: TransportProtocol[];
276
533
  }
277
534
 
278
535
  export interface UserSecret {
@@ -319,8 +576,20 @@ export interface UpdateChannelInfoParams {
319
576
  * map untouched; pass an empty object to clear it. */
320
577
  communication_info?: Record<string, string>;
321
578
 
322
- /** New transport endpoint. Absent leaves it untouched. */
579
+ /**
580
+ * New transport endpoint. Absent leaves it untouched.
581
+ *
582
+ * @deprecated Use `own_transports`, which carries every endpoint this node
583
+ * now serves; its first entry also fills this field for peers predating the
584
+ * list. Removed at 0.0.5.
585
+ */
323
586
  transport_protocol?: { uri: string; protocol: number };
587
+ /**
588
+ * Every endpoint this node now serves, in its own preference order.
589
+ * Omitted leaves the target(s)' stored set untouched. Takes precedence
590
+ * over `transport_protocol`, whose first entry it also fills.
591
+ */
592
+ own_transports?: TransportProtocol[];
324
593
  }
325
594
 
326
595
  /**
@@ -349,11 +618,11 @@ export interface Timeouts {
349
618
  expired_channels?: { enabled: boolean; timeout_in_secs: number };
350
619
  }
351
620
 
352
- /** `SyncCheck` takes no parameters: the group and this device's own version
621
+ /** `ReplicaDiscovery` takes no parameters: the group and this device's own version
353
622
  * are both read from the stores. The argument may be omitted entirely. */
354
- export type SyncCheckParams = Record<string, never>;
623
+ export type ReplicaDiscoveryParams = Record<string, never>;
355
624
 
356
- export interface RemoveReplicaParams {
625
+ export interface UnpairReplicaParams {
357
626
  /** The member to remove, as a **decimal** `u64` string — the same form
358
627
  * `ReplicaPaired.peer_replica_id` hands back. A value naming no current
359
628
  * member is rejected; it is not silently ignored. */
@@ -378,7 +647,7 @@ export type DeRecEvent =
378
647
 
379
648
  action: Uint8Array;
380
649
 
381
- action_kind: string;
650
+ action_kind: PendingActionKind;
382
651
  peer_communication_info?: Record<string, string>;
383
652
 
384
653
  sender_kind?: SenderKind;
@@ -437,7 +706,7 @@ export type DeRecEvent =
437
706
  /** A replica catch-up finished. `fetched_from` is absent when this device
438
707
  * was already current, in which case no hydration event follows. */
439
708
  | {
440
- type: "SyncCheckComplete";
709
+ type: "ReplicaDiscoveryComplete";
441
710
  local_version: number;
442
711
  group_version: number;
443
712
  fetched_from?: string;
@@ -467,7 +736,8 @@ export type DeRecEvent =
467
736
  secret: {
468
737
  helpers: Array<{
469
738
  channel_id: string;
470
- transport_uri: string;
739
+ /** Every endpoint this peer advertised, in the order it offered them. */
740
+ transports: Array<{ uri: string; protocol: number }>;
471
741
  shared_key: Uint8Array;
472
742
  communication_info: Record<string, string>;
473
743
  }>;
@@ -488,7 +758,8 @@ export type DeRecEvent =
488
758
  * originated, which is why no separate owner field is needed. */
489
759
  members: Array<{
490
760
  replica_id: string;
491
- transport_uri: string;
761
+ /** Every endpoint this peer advertised, in the order it offered them. */
762
+ transports: Array<{ uri: string; protocol: number }>;
492
763
  role: "Source" | "Destination";
493
764
  communication_info: Record<string, string>;
494
765
  }>;
@@ -509,7 +780,7 @@ export type DeRecEvent =
509
780
  /** Fires alongside `PairingCompleted` on replica-mode pair handshakes.
510
781
  * `peer_replica_id` is the peer's `u64` as a **decimal** string,
511
782
  * matching the wire `derec.replica_id` representation and every other
512
- * id across this boundary. Pass it back verbatim — `RemoveReplica`
783
+ * id across this boundary. Pass it back verbatim — `UnpairReplica`
513
784
  * expects the same decimal form. The local side's role
514
785
  * (`ReplicaSource` vs `ReplicaDestination`) is on the persisted
515
786
  * channel record — replica pairings are unidirectional, so there is
@@ -534,7 +805,8 @@ export type DeRecEvent =
534
805
  secret: {
535
806
  helpers: Array<{
536
807
  channel_id: string;
537
- transport_uri: string;
808
+ /** Every endpoint this peer advertised, in the order it offered them. */
809
+ transports: Array<{ uri: string; protocol: number }>;
538
810
  shared_key: Uint8Array;
539
811
  communication_info: Record<string, string>;
540
812
  }>;
@@ -553,7 +825,8 @@ export type DeRecEvent =
553
825
  * originated, which is why no separate owner field is needed. */
554
826
  members: Array<{
555
827
  replica_id: string;
556
- transport_uri: string;
828
+ /** Every endpoint this peer advertised, in the order it offered them. */
829
+ transports: Array<{ uri: string; protocol: number }>;
557
830
  role: "Source" | "Destination";
558
831
  communication_info: Record<string, string>;
559
832
  }>;
@@ -583,7 +856,8 @@ export type DeRecEvent =
583
856
  secret: {
584
857
  helpers: Array<{
585
858
  channel_id: string;
586
- transport_uri: string;
859
+ /** Every endpoint this peer advertised, in the order it offered them. */
860
+ transports: Array<{ uri: string; protocol: number }>;
587
861
  shared_key: Uint8Array;
588
862
  communication_info: Record<string, string>;
589
863
  }>;
@@ -602,7 +876,8 @@ export type DeRecEvent =
602
876
  * originated, which is why no separate owner field is needed. */
603
877
  members: Array<{
604
878
  replica_id: string;
605
- transport_uri: string;
879
+ /** Every endpoint this peer advertised, in the order it offered them. */
880
+ transports: Array<{ uri: string; protocol: number }>;
606
881
  role: "Source" | "Destination";
607
882
  communication_info: Record<string, string>;
608
883
  }>;
@@ -649,21 +924,21 @@ export type DeRecEvent =
649
924
  * for observability — no further action is required. `action_kind`
650
925
  * is the same label vocabulary as `ActionRequired.action_kind`
651
926
  * (`"Pairing"`, `"StoreShare"`, …). */
652
- | { type: "AutoAccepted"; channel_id: string; action_kind: string }
927
+ | { type: "AutoAccepted"; channel_id: string; action_kind: PendingActionKind }
653
928
  | { type: "NoOp" }
654
929
  /** A pairing handshake was dispatched successfully. `kind` is the
655
930
  * local party's role — same value the subsequent `PairingCompleted`
656
931
  * will carry. Emitted by `start(Pairing)`. */
657
- | { type: "PairingStarted"; channel_id: string; kind: SenderKind }
932
+ | { type: "PairingStarted"; channel_id: string; kind: SenderKind; trace_id: string }
658
933
  /** A discovery request was dispatched to `channel_id`. Emitted per
659
934
  * targeted helper by `start(Discovery)`. */
660
- | { type: "DiscoveryStarted"; channel_id: string }
935
+ | { type: "DiscoveryStarted"; channel_id: string; trace_id: string }
661
936
  /** A discovery request could not be dispatched to `channel_id`. Other
662
937
  * targeted channels are unaffected. */
663
938
  | { type: "DiscoveryFailed"; channel_id: string; error: string }
664
939
  /** A share-storage request was dispatched to `channel_id`. Emitted per
665
940
  * targeted peer by `start(ProtectSecret)`. */
666
- | { type: "ProtectSecretStarted"; channel_id: string; version: number }
941
+ | { type: "ProtectSecretStarted"; channel_id: string; version: number; trace_id: string }
667
942
  /** A share-storage request could not be dispatched to `channel_id`. */
668
943
  | {
669
944
  type: "ProtectSecretFailed";
@@ -672,7 +947,7 @@ export type DeRecEvent =
672
947
  error: string;
673
948
  }
674
949
  /** A verify-share challenge was dispatched to `channel_id`. */
675
- | { type: "VerifySharesStarted"; channel_id: string; version: number }
950
+ | { type: "VerifySharesStarted"; channel_id: string; version: number; trace_id: string }
676
951
  /** A verify-share challenge could not be dispatched to `channel_id`. */
677
952
  | {
678
953
  type: "VerifySharesFailed";
@@ -681,7 +956,7 @@ export type DeRecEvent =
681
956
  error: string;
682
957
  }
683
958
  /** A recovery share request was dispatched to `channel_id`. */
684
- | { type: "RecoverSecretStarted"; channel_id: string; version: number }
959
+ | { type: "RecoverSecretStarted"; channel_id: string; version: number; trace_id: string }
685
960
  /** A recovery share request could not be dispatched to `channel_id`. */
686
961
  | {
687
962
  type: "RecoverSecretFailed";
@@ -693,9 +968,9 @@ export type DeRecEvent =
693
968
  * `Unpaired` event once the peer acknowledges (or in the same event
694
969
  * vec, under `UnpairAck.NotRequired`). */
695
970
  | { type: "UnpairFailed"; channel_id: string; error: string }
696
- | { type: "UnpairStarted"; channel_id: string }
971
+ | { type: "UnpairStarted"; channel_id: string; trace_id: string }
697
972
  /** An update-channel-info request was dispatched to `channel_id`. */
698
- | { type: "UpdateChannelInfoStarted"; channel_id: string }
973
+ | { type: "UpdateChannelInfoStarted"; channel_id: string; trace_id: string }
699
974
  /** An update-channel-info request could not be dispatched to
700
975
  * `channel_id`. */
701
976
  | { type: "UpdateChannelInfoFailed"; channel_id: string; error: string };
@@ -756,7 +1031,12 @@ export interface GetSecretIdsVersionsRequestMessage {
756
1031
  timestamp?: Timestamp;
757
1032
  /** Ephemeral endpoint where the requester wants the response routed.
758
1033
  * Absent means "use the channel's stored peer endpoint". */
759
- reply_to?: TransportProtocol;
1034
+ /**
1035
+ * Every endpoint the requester can be answered on for this exchange, in
1036
+ * its own preference order. Omitted means route to the endpoints already
1037
+ * recorded for the channel.
1038
+ */
1039
+ reply_to?: TransportProtocol[];
760
1040
  /** Replica-group member that sent this; see `replicaId` semantics. */
761
1041
  replica_id?: bigint;
762
1042
  }
@@ -808,6 +1088,22 @@ export interface CommunicationInfo {
808
1088
  // `ContactMessage` is defined once above and covers both `INLINE_KEYS` and
809
1089
  // `HASHED_KEYS` modes.
810
1090
 
1091
+ /**
1092
+ * The label vocabulary for `ActionRequired.action_kind` and
1093
+ * `AutoAccepted.action_kind` — one value per pending-action kind the
1094
+ * protocol can raise. Matches the Rust `PendingActionKind` discriminants
1095
+ * one-for-one.
1096
+ */
1097
+ export type PendingActionKind =
1098
+ | "Pairing"
1099
+ | "PrePair"
1100
+ | "StoreShare"
1101
+ | "VerifyShare"
1102
+ | "Discovery"
1103
+ | "GetShare"
1104
+ | "Unpair"
1105
+ | "UpdateChannelInfo";
1106
+
811
1107
  export interface ParameterRange {
812
1108
  min_share_size: bigint;
813
1109
  max_share_size: bigint;
@@ -828,8 +1124,18 @@ export interface PairRequestMessage {
828
1124
  nonce: bigint;
829
1125
  communication_info?: CommunicationInfo;
830
1126
  parameter_range?: ParameterRange;
1127
+ /**
1128
+ * @deprecated Reading this field directly is incorrect: its meaning narrowed
1129
+ * to "one entry of a list, and possibly absent", so a peer advertising only
1130
+ * `supported_transports` looks unreachable to a reader that was correct
1131
+ * before 0.0.3. Call {@link advertisedEndpoints}, which resolves both
1132
+ * spellings. Removed at 0.0.5.
1133
+ */
831
1134
  transport_protocol?: TransportProtocol;
832
1135
  timestamp?: Timestamp;
1136
+ /** Every transport endpoint the initiator can be reached on, in its own
1137
+ * preference order. Empty means "only `transport_protocol` is offered". */
1138
+ supported_transports: TransportProtocol[];
833
1139
  }
834
1140
 
835
1141
  export interface PairResponseMessage {
@@ -850,8 +1156,21 @@ export interface PairResponseMessage {
850
1156
 
851
1157
  export interface PrePairRequestMessage {
852
1158
  nonce: bigint;
1159
+ /**
1160
+ * @deprecated Reading this field directly is incorrect: its meaning narrowed
1161
+ * to "one entry of a list, and possibly absent", so a peer advertising only
1162
+ * `supported_transports` looks unreachable to a reader that was correct
1163
+ * before 0.0.3. Call {@link advertisedEndpoints}, which resolves both
1164
+ * spellings. Removed at 0.0.5.
1165
+ */
853
1166
  transport_protocol?: TransportProtocol;
854
1167
  timestamp?: Timestamp;
1168
+ /**
1169
+ * Every endpoint the sender can be reached on for the PrePair reply, in
1170
+ * its own preference order. At least one of this and `transport_protocol`
1171
+ * must be present.
1172
+ */
1173
+ supported_transports?: TransportProtocol[];
855
1174
  }
856
1175
 
857
1176
  export interface PrePairResponseMessage {
@@ -869,7 +1188,12 @@ export interface GetShareRequestMessage {
869
1188
  version: number;
870
1189
  timestamp?: Timestamp;
871
1190
  /** Ephemeral response endpoint; see `replyTo` semantics. */
872
- reply_to?: TransportProtocol;
1191
+ /**
1192
+ * Every endpoint the requester can be answered on for this exchange, in
1193
+ * its own preference order. Omitted means route to the endpoints already
1194
+ * recorded for the channel.
1195
+ */
1196
+ reply_to?: TransportProtocol[];
873
1197
  /** Replica-group member that sent this; see `replicaId` semantics. */
874
1198
  replica_id?: bigint;
875
1199
  }
@@ -911,7 +1235,12 @@ export interface StoreShareRequestMessage {
911
1235
  timestamp?: Timestamp;
912
1236
  secret_id: bigint;
913
1237
  /** Ephemeral response endpoint; see `replyTo` semantics. */
914
- reply_to?: TransportProtocol;
1238
+ /**
1239
+ * Every endpoint the requester can be answered on for this exchange, in
1240
+ * its own preference order. Omitted means route to the endpoints already
1241
+ * recorded for the channel.
1242
+ */
1243
+ reply_to?: TransportProtocol[];
915
1244
  /** Replica-group member that sent this; see `replicaId` semantics. */
916
1245
  replica_id?: bigint;
917
1246
  }
@@ -929,7 +1258,12 @@ export interface UnpairRequestMessage {
929
1258
  memo: string;
930
1259
  timestamp?: Timestamp;
931
1260
  /** Ephemeral response endpoint; see `replyTo` semantics. */
932
- reply_to?: TransportProtocol;
1261
+ /**
1262
+ * Every endpoint the requester can be answered on for this exchange, in
1263
+ * its own preference order. Omitted means route to the endpoints already
1264
+ * recorded for the channel.
1265
+ */
1266
+ reply_to?: TransportProtocol[];
933
1267
  /** Replica-group member that sent this; see `replicaId` semantics. */
934
1268
  replica_id?: bigint;
935
1269
  }
@@ -945,7 +1279,12 @@ export interface VerifyShareRequestMessage {
945
1279
  nonce: bigint;
946
1280
  timestamp?: Timestamp;
947
1281
  /** Ephemeral response endpoint; see `replyTo` semantics. */
948
- reply_to?: TransportProtocol;
1282
+ /**
1283
+ * Every endpoint the requester can be answered on for this exchange, in
1284
+ * its own preference order. Omitted means route to the endpoints already
1285
+ * recorded for the channel.
1286
+ */
1287
+ reply_to?: TransportProtocol[];
949
1288
  }
950
1289
 
951
1290
  export interface VerifyShareResponseMessage {
@@ -981,7 +1320,12 @@ export interface PairingRequestProduceResult extends ProduceResult {
981
1320
  }
982
1321
 
983
1322
  export interface PairingResponseProduceResult extends ProduceResult {
984
- peer_transport_protocol: TransportProtocol;
1323
+ /**
1324
+ * Every endpoint the requester advertised, in the order it offered them,
1325
+ * filtered to those the library will record. Never empty. Choosing which
1326
+ * to dial, and failing over when one is unreachable, is the application's.
1327
+ */
1328
+ peer_transports: TransportProtocol[];
985
1329
 
986
1330
  shared_key: Uint8Array;
987
1331