@derec-alliance/react-native 0.0.5 → 0.0.7

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
@@ -1,6 +1,8 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (c) 2026 DeRec Alliance. All rights reserved.
3
3
 
4
+ import { getNative } from './native';
5
+
4
6
  export interface SecretStore {
5
7
  load(
6
8
  secretId: string,
@@ -9,15 +11,15 @@ export interface SecretStore {
9
11
  ): Promise<Uint8Array | null | undefined>;
10
12
  /**
11
13
  * Load secrets of the same `kind` for several channels in one call,
12
- * scoped to `secretId`. Must return an array with one entry per input
13
- * id, in the same order, using `null` (or `undefined`) for channels
14
- * with no stored secret of `kind`.
14
+ * scoped to `secretId`. Must return an array with exactly one entry per
15
+ * input id, in the same order, using `null` (or `undefined`) for channels
16
+ * with no stored secret of `kind`. Whether a missing entry is an error is
17
+ * decided by the library.
15
18
  */
16
19
  loadMany(
17
20
  secretId: string,
18
21
  channelIds: string[],
19
22
  kind: 0 | 1 | 2,
20
- missingPolicy: "skip" | "fail",
21
23
  ): Promise<Array<Uint8Array | null | undefined>>;
22
24
  save(
23
25
  secretId: string,
@@ -120,30 +122,15 @@ export function channelFilterMatches(
120
122
  /**
121
123
  * The endpoints a peer-supplied message advertises, in the peer's own order.
122
124
  *
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
125
  * Reports what was advertised, not what is acceptable — nothing here is
131
126
  * validated, and the protocol still applies its own transport policy to
132
127
  * whatever it records.
133
128
  */
134
129
  export function advertisedEndpoints(
135
- message:
136
- | {
137
- transport_protocol?: TransportProtocol;
138
- supported_transports?: TransportProtocol[];
139
- }
140
- | null
141
- | undefined,
130
+ message: { supported_transports?: TransportProtocol[] } | null | undefined,
142
131
  ): TransportProtocol[] {
143
132
  if (!message) return [];
144
- const offers = message.supported_transports ?? [];
145
- if (offers.length > 0) return offers;
146
- return message.transport_protocol ? [message.transport_protocol] : [];
133
+ return message.supported_transports ?? [];
147
134
  }
148
135
 
149
136
  /**
@@ -242,11 +229,7 @@ export interface ChannelStore {
242
229
  secretId: string,
243
230
  filter: ReplicaFilter,
244
231
  ): Promise<Uint8Array | null | undefined>;
245
- linkChannel(
246
- secretId: string,
247
- channelId: string,
248
- linkedChannelId: string,
249
- ): Promise<void>;
232
+ linkChannel(secretId: string, a: string, b: string): Promise<void>;
250
233
  linkedChannels(secretId: string, channelId: string): Promise<string[]>;
251
234
  }
252
235
 
@@ -278,6 +261,42 @@ export interface ShareStore {
278
261
  save(secretId: string, channelId: string, share: Share): Promise<void>;
279
262
  latestVersion(secretId: string): Promise<number | null>;
280
263
  removeChannel(secretId: string, channelId: string): Promise<void>;
264
+ /**
265
+ * Drop the shares stored under `(secretId, channelId)` at each of
266
+ * `versions`. Idempotent: a version that is not stored is skipped, and
267
+ * an empty array is a no-op.
268
+ *
269
+ * A helper calls this to apply `StoreShareRequestMessage.keepList`, the
270
+ * complete set of versions the owner wants retained: every stored
271
+ * version outside it is removed once the incoming share is persisted.
272
+ * Shares under other channels or partitions must be left untouched.
273
+ */
274
+ removeVersions(
275
+ secretId: string,
276
+ channelId: string,
277
+ versions: number[],
278
+ ): Promise<void>;
279
+ /**
280
+ * Owner only: the versions every helper keeps after the owner distributes
281
+ * `version`. Asked once per sharing round, before anything is sent,
282
+ * including the rounds the library starts itself; the answer becomes
283
+ * `keepList` for every helper.
284
+ *
285
+ * Return `null` or `undefined` to send no `keepList` (it goes out empty):
286
+ * helpers then keep every version they hold. An app that wants to cap how
287
+ * many versions helpers retain returns that cap here. A returned list is
288
+ * used as is, plus `version`, which the library always adds. Helpers
289
+ * delete every version that is not listed, so list every version that
290
+ * could still become the latest: those that committed (for example, whose
291
+ * `SharingComplete` reported `threshold_met`) and those whose round is
292
+ * still open. Leave out only versions whose round failed or that the user
293
+ * rolled back; a list that leaves out too much can make the secret
294
+ * unrecoverable.
295
+ */
296
+ keepList(
297
+ secretId: string,
298
+ version: number,
299
+ ): Promise<number[] | null | undefined>;
281
300
  }
282
301
 
283
302
  export interface UserSecretEntry {
@@ -290,6 +309,10 @@ export interface UserSecrets {
290
309
  version: number;
291
310
  secrets: UserSecretEntry[];
292
311
  description?: string;
312
+ /** Decimal `replica_id` of the member that published `version`. Absent
313
+ * when none is recorded. Store and return it unchanged: replica members
314
+ * compare it to detect a conflicting copy of the same version. */
315
+ author_replica_id?: string;
293
316
  }
294
317
 
295
318
  /**
@@ -333,6 +356,18 @@ export interface StateStore {
333
356
  loadAll(secretId: string, kind: 0 | 1 | 2 | 3 | 4): Promise<Uint8Array[]>;
334
357
  }
335
358
 
359
+ /** A transport protocol, by name. */
360
+ export type TransportProtocolName = "https" | "grpc";
361
+
362
+ /**
363
+ * One endpoint a node serves or a peer advertised, as every app-facing call
364
+ * and event carries it.
365
+ */
366
+ export interface Endpoint {
367
+ uri: string;
368
+ protocol: TransportProtocolName;
369
+ }
370
+
336
371
  /**
337
372
  * Outbound message delivery.
338
373
  *
@@ -378,7 +413,7 @@ export interface Transport {
378
413
  * {@link singleEndpointTransport} rather than by indexing.
379
414
  */
380
415
  send(
381
- endpoints: ReadonlyArray<{ protocol: string; uri: string }>,
416
+ endpoints: ReadonlyArray<Endpoint>,
382
417
  message: Uint8Array,
383
418
  ): Promise<void>;
384
419
  }
@@ -398,10 +433,28 @@ export interface Transport {
398
433
  * a round trip; skipping one that would have worked costs the delivery.
399
434
  */
400
435
  export type SendOne = (
401
- endpoint: { protocol: string; uri: string },
436
+ endpoint: Endpoint,
402
437
  message: Uint8Array,
403
438
  ) => Promise<void>;
404
439
 
440
+ /**
441
+ * The DeRec protocol version this build speaks — the `protocolVersionMajor` /
442
+ * `protocolVersionMinor` it writes into every envelope it produces. Not the
443
+ * package version.
444
+ */
445
+ export function protocol_version(): { major: number; minor: number } {
446
+ return getNative().version();
447
+ }
448
+
449
+ /**
450
+ * A fresh, random replica id — never `0`. Generate it once per device,
451
+ * persist it, and pass the same value to
452
+ * `DeRecProtocolBuilder.withReplicaId` on every init.
453
+ */
454
+ export function generate_replica_id(): bigint {
455
+ return getNative().generate_replica_id();
456
+ }
457
+
405
458
  /**
406
459
  * Builds a {@link Transport} that tries each endpoint in the order the peer
407
460
  * offered it and stops at the first success.
@@ -473,7 +526,7 @@ export enum SenderKind {
473
526
  * the scanner must fetch the actual keys over the wire via the `PrePair`
474
527
  * round-trip and verify them against the commitment before pairing.
475
528
  * - `NoKeys`: no key material and no commitment. The contact carries only
476
- * `channel_id`, `nonce`, and `transport_protocol` — small enough to be
529
+ * `channel_id`, `nonce`, and `supported_transports` — small enough to be
477
530
  * hand-typed or dictated. Keys are generated on the fly by the contact
478
531
  * creator when the `PrePairRequest` arrives; the scanner accepts them
479
532
  * without cryptographic verification. Trust rests entirely on the OOB
@@ -515,20 +568,33 @@ export enum FlowKind {
515
568
  UnpairReplica = 8,
516
569
  }
517
570
 
571
+ /** Result status carried in every protocol response (`result.proto`). Passed
572
+ * to `reject()` to say why an inbound request was refused. */
573
+ export enum StatusEnum {
574
+ Ok = 0,
575
+ Partial = 1,
576
+ Fail = 2,
577
+ SizeLimitExceeded = 3,
578
+ TooFrequent = 4,
579
+ UnknownSecretId = 5,
580
+ UnknownShareVersion = 6,
581
+ DecryptionFailed = 7,
582
+ VerificationFailed = 8,
583
+ FormatError = 9,
584
+ Rejected = 10,
585
+ IncompatibleParameterRange = 11,
586
+ UnsupportedTransportProtocol = 12,
587
+ VersionConflict = 13,
588
+ ReplicaIdConflict = 14,
589
+ RequestToClose = 99,
590
+ }
591
+
518
592
  export type UnpairAck = "required" | "not_required";
519
593
 
520
594
  export interface ContactMessage {
521
595
  channel_id: bigint;
522
596
  /** `ContactMode` numeric value (0 = INLINE_KEYS, 1 = HASHED_KEYS, 2 = NO_KEYS). */
523
597
  contact_mode: number;
524
- /**
525
- * @deprecated Reading this field directly is incorrect: its meaning narrowed
526
- * to "one entry of a list, and possibly absent", so a peer advertising only
527
- * `supported_transports` looks unreachable to a reader that was correct
528
- * before 0.0.3. Call {@link advertisedEndpoints}, which resolves both
529
- * spellings. Removed at 0.0.5.
530
- */
531
- transport_protocol?: TransportProtocol;
532
598
  nonce: bigint;
533
599
  /** Present only when `contact_mode === ContactMode.InlineKeys`. */
534
600
  mlkem_encapsulation_key?: Uint8Array;
@@ -538,8 +604,7 @@ export interface ContactMessage {
538
604
  contact_binding_hash?: Uint8Array;
539
605
  timestamp?: Timestamp;
540
606
  /** Every transport endpoint the creator of this contact can be reached
541
- * on, in its own preference order. Empty means "only
542
- * `transport_protocol` is offered". */
607
+ * on, in its own preference order. */
543
608
  supported_transports: TransportProtocol[];
544
609
  }
545
610
 
@@ -587,20 +652,11 @@ export interface UpdateChannelInfoParams {
587
652
  * map untouched; pass an empty object to clear it. */
588
653
  communication_info?: Record<string, string>;
589
654
 
590
- /**
591
- * New transport endpoint. Absent leaves it untouched.
592
- *
593
- * @deprecated Use `own_transports`, which carries every endpoint this node
594
- * now serves; its first entry also fills this field for peers predating the
595
- * list. Removed at 0.0.5.
596
- */
597
- transport_protocol?: { uri: string; protocol: number };
598
655
  /**
599
656
  * Every endpoint this node now serves, in its own preference order.
600
- * Omitted leaves the target(s)' stored set untouched. Takes precedence
601
- * over `transport_protocol`, whose first entry it also fills.
657
+ * Omitted leaves the target(s)' stored set untouched.
602
658
  */
603
- own_transports?: TransportProtocol[];
659
+ own_transports?: Endpoint[];
604
660
  }
605
661
 
606
662
  /**
@@ -633,6 +689,15 @@ export interface Timeouts {
633
689
  * are both read from the stores. The argument may be omitted entirely. */
634
690
  export type ReplicaDiscoveryParams = Record<string, never>;
635
691
 
692
+ /** Any member may remove any member, the source included: a lost or stolen
693
+ * source must be removable by the devices that remain, and the library
694
+ * checks no role. Ask the user before starting this flow, above all when it
695
+ * names the source. Removing the source promotes the first remaining member
696
+ * in the order the channel store's `listReplicas` returns. The removed
697
+ * member is not asked and gets no event when told to leave: when a roster
698
+ * excluding it arrives it drops its whole `secret_id` partition and emits
699
+ * `SelfRemovedFromGroup`. The secret survives on the remaining members and
700
+ * the helpers. */
636
701
  export interface UnpairReplicaParams {
637
702
  /** The member to remove, as a **decimal** `u64` string — the same form
638
703
  * `ReplicaPaired.peer_replica_id` hands back. A value naming no current
@@ -659,18 +724,34 @@ export type DeRecEvent =
659
724
  action: Uint8Array;
660
725
 
661
726
  action_kind: PendingActionKind;
727
+ /** Correlation token of the inbound request, decimal-encoded. */
728
+ trace_id: string;
729
+ /** Pairing only. */
662
730
  peer_communication_info?: Record<string, string>;
663
-
731
+ /** `sender_kind` of the inbound pair request (Pairing only). */
664
732
  sender_kind?: SenderKind;
665
-
733
+ /** Share version (StoreShare / VerifyShare / GetShare). */
666
734
  version?: number;
735
+ /** Description of the secret version (StoreShare only). */
667
736
  share_description?: string;
668
-
737
+ /** Secret identifier, decimal-encoded (StoreShare / VerifyShare /
738
+ * GetShare). On GetShare, with `version`, names the requested share. */
669
739
  share_secret_id?: string;
740
+ /** Length in bytes of the share the helper would store (StoreShare
741
+ * only) — what a size or quota decision is made on. */
742
+ share_size?: number;
743
+ /** The peer's memo (Unpair only). */
744
+ unpair_memo?: string;
745
+ /** Communication info the peer replaces its stored map with
746
+ * (UpdateChannelInfo only). Absent: unchanged. Empty: cleared. */
747
+ updated_communication_info?: Record<string, string>;
748
+ /** Endpoints the peer is moving to (UpdateChannelInfo only). Absent:
749
+ * unchanged. */
750
+ updated_transports?: Endpoint[];
670
751
  }
671
752
  | { type: "ShareStored"; channel_id: string; version: number }
672
753
  | { type: "ShareConfirmed"; channel_id: string; version: number }
673
- | { type: "ShareRejected"; channel_id: string; version: number; status: number; memo: string }
754
+ | { type: "ShareRejected"; channel_id: string; version: number; status: StatusEnum; memo: string }
674
755
  /** A publishing round finished — every targeted helper confirmed,
675
756
  * rejected, or timed out.
676
757
  *
@@ -688,14 +769,17 @@ export type DeRecEvent =
688
769
  | { type: "SharingComplete"; version: number; confirmed_count: number; failed_count: number; threshold_met: boolean }
689
770
  /** A group member refused a secret sync. Keyed by `replica_id`, not
690
771
  * `channel_id`: every member answers on the one group channel. A
691
- * `VERSION_CONFLICT` status means the round must be resolved and
692
- * republished at a new version. */
772
+ * `VERSION_CONFLICT` status means another member holds a different copy
773
+ * of this version: do not publish from this device again until the
774
+ * conflict is resolved. Run `start(FlowKind.ReplicaDiscovery)` to receive
775
+ * the group's copy as `ReplicaVersionConflict`, merge, and publish the
776
+ * result once with `start(FlowKind.ProtectSecret)`. */
693
777
  | {
694
778
  type: "ReplicaSyncRejected";
695
779
  replica_id: string;
696
780
  secret_id: string;
697
781
  version: number;
698
- status: number;
782
+ status: StatusEnum;
699
783
  memo: string;
700
784
  }
701
785
  /** A secret sync could not be delivered to a member at all — distinct from
@@ -712,7 +796,8 @@ export type DeRecEvent =
712
796
  /** This device left the group and dropped its whole `secret_id` partition —
713
797
  * group channel, helper channels, shares, secrets and the snapshot. Fires
714
798
  * only once it was told to leave *and* has since seen a roster excluding
715
- * it; absence alone never destroys a copy of the secret. */
799
+ * it; absence alone never destroys a copy of the secret. The teardown is
800
+ * automatic: this device is not asked first and gets no earlier event. */
716
801
  | { type: "SelfRemovedFromGroup"; version: number }
717
802
  /** A replica catch-up finished. `fetched_from` is absent when this device
718
803
  * was already current, in which case no hydration event follows. */
@@ -728,6 +813,10 @@ export type DeRecEvent =
728
813
  * library keeps no durable per-member sync state. */
729
814
  | { type: "ReplicaSyncComplete"; version: number; synced: string[]; behind: string[] }
730
815
  | { type: "ShareVerified"; channel_id: string; version: number }
816
+ /** A helper refused a verification challenge: its response carried a
817
+ * non-OK `status` instead of a proof. The challenge is spent; a new
818
+ * `VerifyShares` round challenges the helper again. */
819
+ | { type: "ShareVerifyRejected"; channel_id: string; version: number; status: StatusEnum; memo: string }
731
820
  | {
732
821
  type: "SecretsDiscovered";
733
822
  channel_id: string;
@@ -736,6 +825,22 @@ export type DeRecEvent =
736
825
  }
737
826
  | { type: "RecoveryShareReceived"; channel_id: string; shares_received: number }
738
827
  | { type: "RecoveryShareError"; channel_id: string; shares_received: number; error: string }
828
+ /** A helper refused a recovery share request: its response carried a
829
+ * non-OK `status` (e.g. `UNKNOWN_SHARE_VERSION`) instead of a share. The
830
+ * refusal is not collected — it does not count towards `shares_received`
831
+ * and the recovery stays open for the other helpers' shares — but it does
832
+ * answer that helper's `RecoverSecretStarted`. */
833
+ | { type: "RecoveryShareRefused"; channel_id: string; version: number; status: StatusEnum; memo: string }
834
+ /** A helper answered with a share that cannot be part of the secret;
835
+ * `reason` says how it failed. `Malformed` and `InvalidProof` are judged
836
+ * on arrival; `Inconsistent` (valid on its own but disagreeing with the
837
+ * shares the secret was rebuilt from) is reported alongside
838
+ * `SecretRecovered`, once per helper. The share is set aside — it does
839
+ * not count towards `shares_received` and never blocks the recovery. An
840
+ * honest helper never sends one, so the app may treat it as a sign of a
841
+ * damaged or compromised helper, e.g. offer to unpair it. It also
842
+ * answers that helper's `RecoverSecretStarted`. */
843
+ | { type: "RecoveryShareCorrupted"; channel_id: string; version: number; reason: CorruptionReason }
739
844
  /** Recovery completed — the typed `Secret` snapshot the owner
740
845
  * originally protected. Mirrors `ReplicaSecretReceived.secret`:
741
846
  * `secrets` is the user-facing `Vec<UserSecret>` the application
@@ -748,9 +853,9 @@ export type DeRecEvent =
748
853
  helpers: Array<{
749
854
  channel_id: string;
750
855
  /** Every endpoint this peer advertised, in the order it offered them. */
751
- transports: Array<{ uri: string; protocol: number }>;
856
+ transports: Endpoint[];
752
857
  shared_key: Uint8Array;
753
- communication_info: Record<string, string>;
858
+ communication_info?: Record<string, string>;
754
859
  }>;
755
860
  secrets: Array<{
756
861
  id: Uint8Array;
@@ -770,9 +875,9 @@ export type DeRecEvent =
770
875
  members: Array<{
771
876
  replica_id: string;
772
877
  /** Every endpoint this peer advertised, in the order it offered them. */
773
- transports: Array<{ uri: string; protocol: number }>;
878
+ transports: Endpoint[];
774
879
  role: "Source" | "Destination";
775
- communication_info: Record<string, string>;
880
+ communication_info?: Record<string, string>;
776
881
  }>;
777
882
  shared_key: Uint8Array;
778
883
  };
@@ -781,12 +886,12 @@ export type DeRecEvent =
781
886
 
782
887
  | { type: "Unpaired"; channel_id: string }
783
888
 
784
- | { type: "UnpairRejected"; channel_id: string; status: number; memo: string }
889
+ | { type: "UnpairRejected"; channel_id: string; status: StatusEnum; memo: string }
785
890
 
786
891
  /** Contact creator answered the scanner's `PrePairRequest` with a
787
892
  * non-Ok status (HashedKeys flow). Distinct from a cryptographic
788
893
  * hash mismatch, which surfaces as a thrown error from `process()`. */
789
- | { type: "PrePairRejected"; channel_id: string; status: number; memo: string }
894
+ | { type: "PrePairRejected"; channel_id: string; status: StatusEnum; memo: string }
790
895
 
791
896
  /** Fires alongside `PairingCompleted` on replica-mode pair handshakes.
792
897
  * `peer_replica_id` is the peer's `u64` as a **decimal** string,
@@ -804,22 +909,28 @@ export type DeRecEvent =
804
909
  /** A `ReplicaSource` peer pushed a secret sync on a
805
910
  * `ReplicaDestination` channel. The library decoded the
806
911
  * `ReplicaSecretPayload`; the app installs `secret.secrets` and
807
- * optionally uses `shares` for recovery. `from_replica_id` and the
808
- * `replica_id` fields inside `secret` are `u64` as **decimal**
809
- * strings. */
912
+ * optionally uses `shares` for recovery. `from_replica_id`,
913
+ * `author_replica_id` and the `replica_id` fields inside `secret` are
914
+ * `u64` as **decimal** strings.
915
+ *
916
+ * `from_replica_id` is the member this copy came from: the publisher on
917
+ * a push, the serving member on a catch-up. `author_replica_id` is the
918
+ * member that published `version`, or `null` when the serving member's
919
+ * snapshot records none. */
810
920
  | {
811
921
  type: "ReplicaSecretReceived";
812
922
  channel_id: string;
813
923
  from_replica_id: string;
924
+ author_replica_id: string | null;
814
925
  secret_id: string;
815
926
  version: number;
816
927
  secret: {
817
928
  helpers: Array<{
818
929
  channel_id: string;
819
930
  /** Every endpoint this peer advertised, in the order it offered them. */
820
- transports: Array<{ uri: string; protocol: number }>;
931
+ transports: Endpoint[];
821
932
  shared_key: Uint8Array;
822
- communication_info: Record<string, string>;
933
+ communication_info?: Record<string, string>;
823
934
  }>;
824
935
  secrets: Array<{
825
936
  id: Uint8Array;
@@ -837,9 +948,9 @@ export type DeRecEvent =
837
948
  members: Array<{
838
949
  replica_id: string;
839
950
  /** Every endpoint this peer advertised, in the order it offered them. */
840
- transports: Array<{ uri: string; protocol: number }>;
951
+ transports: Endpoint[];
841
952
  role: "Source" | "Destination";
842
- communication_info: Record<string, string>;
953
+ communication_info?: Record<string, string>;
843
954
  }>;
844
955
  shared_key: Uint8Array;
845
956
  };
@@ -862,15 +973,16 @@ export type DeRecEvent =
862
973
  type: "ReplicaSecretInstalled";
863
974
  channel_id: string;
864
975
  from_replica_id: string;
976
+ author_replica_id: string | null;
865
977
  secret_id: string;
866
978
  version: number;
867
979
  secret: {
868
980
  helpers: Array<{
869
981
  channel_id: string;
870
982
  /** Every endpoint this peer advertised, in the order it offered them. */
871
- transports: Array<{ uri: string; protocol: number }>;
983
+ transports: Endpoint[];
872
984
  shared_key: Uint8Array;
873
- communication_info: Record<string, string>;
985
+ communication_info?: Record<string, string>;
874
986
  }>;
875
987
  secrets: Array<{
876
988
  id: Uint8Array;
@@ -888,9 +1000,9 @@ export type DeRecEvent =
888
1000
  members: Array<{
889
1001
  replica_id: string;
890
1002
  /** Every endpoint this peer advertised, in the order it offered them. */
891
- transports: Array<{ uri: string; protocol: number }>;
1003
+ transports: Endpoint[];
892
1004
  role: "Source" | "Destination";
893
- communication_info: Record<string, string>;
1005
+ communication_info?: Record<string, string>;
894
1006
  }>;
895
1007
  shared_key: Uint8Array;
896
1008
  };
@@ -900,6 +1012,59 @@ export type DeRecEvent =
900
1012
  committed_share: Uint8Array;
901
1013
  }>;
902
1014
  }
1015
+ /** A member offered a different copy of the version this device holds.
1016
+ * Nothing was written: this device keeps its own copy and refuses the
1017
+ * incoming one with `VERSION_CONFLICT`, so the publisher sees
1018
+ * `ReplicaSyncRejected`. Both copies are complete states — the held one
1019
+ * is in the local stores, the incoming one is `secret`. Resolve by
1020
+ * publishing the chosen state with `start(FlowKind.ProtectSecret)`; the
1021
+ * next version supersedes both on every member and helper. Until then,
1022
+ * do not publish from this device: any further `ProtectSecret` is a
1023
+ * higher version that every other member applies over its own copy,
1024
+ * losing the change it never merged.
1025
+ *
1026
+ * `held_author_replica_id` / `incoming_author_replica_id` are the
1027
+ * decimal `replica_id` of each copy's publisher, or `null` when that
1028
+ * copy records none. */
1029
+ | {
1030
+ type: "ReplicaVersionConflict";
1031
+ channel_id: string;
1032
+ from_replica_id: string;
1033
+ secret_id: string;
1034
+ version: number;
1035
+ held_author_replica_id: string | null;
1036
+ incoming_author_replica_id: string | null;
1037
+ secret: {
1038
+ helpers: Array<{
1039
+ channel_id: string;
1040
+ /** Every endpoint this peer advertised, in the order it offered them. */
1041
+ transports: Endpoint[];
1042
+ shared_key: Uint8Array;
1043
+ communication_info?: Record<string, string>;
1044
+ }>;
1045
+ secrets: Array<{
1046
+ id: Uint8Array;
1047
+ name: string;
1048
+ data: Uint8Array;
1049
+ }>;
1050
+ /** The same shape as `SecretRecovered.secret.replicas`. */
1051
+ replicas?: {
1052
+ /** The one channel every member is addressed on. */
1053
+ channel_id: string;
1054
+ /** Every member of the group, including the writer. Exactly one
1055
+ * carries `role: "Source"` — that member is where the secret
1056
+ * originated, which is why no separate owner field is needed. */
1057
+ members: Array<{
1058
+ replica_id: string;
1059
+ /** Every endpoint this peer advertised, in the order it offered them. */
1060
+ transports: Endpoint[];
1061
+ role: "Source" | "Destination";
1062
+ communication_info?: Record<string, string>;
1063
+ }>;
1064
+ shared_key: Uint8Array;
1065
+ };
1066
+ };
1067
+ }
903
1068
  /** Peer's ack of a secret sync we sent. `status` is the `StatusEnum`
904
1069
  * integer (0 = Ok), `memo` is the peer's explanation. */
905
1070
  | {
@@ -908,7 +1073,7 @@ export type DeRecEvent =
908
1073
  from_replica_id: string;
909
1074
  secret_id: string;
910
1075
  version: number;
911
- status: number;
1076
+ status: StatusEnum;
912
1077
  memo: string;
913
1078
  }
914
1079
  /** A peer announced an updated `communication_info` map and/or
@@ -925,7 +1090,7 @@ export type DeRecEvent =
925
1090
  | {
926
1091
  type: "ChannelInfoUpdateRejected";
927
1092
  channel_id: string;
928
- status: number;
1093
+ status: StatusEnum;
929
1094
  memo: string;
930
1095
  }
931
1096
  /** Emitted by `process()` in place of `ActionRequired` when the
@@ -937,6 +1102,38 @@ export type DeRecEvent =
937
1102
  * (`"Pairing"`, `"StoreShare"`, …). */
938
1103
  | { type: "AutoAccepted"; channel_id: string; action_kind: PendingActionKind }
939
1104
  | { type: "NoOp" }
1105
+ /** An inbound message was dropped untouched: no store was written and
1106
+ * nothing was sent back. `reason` says why.
1107
+ *
1108
+ * `"PendingVerification"` means the peer sent something before this
1109
+ * device confirmed the channel's fingerprint — typically a replica source
1110
+ * pushing its first copy while this destination still shows the code.
1111
+ * Confirming does not replay it: after `verifyFingerprint` succeeds, a
1112
+ * replica destination calls `start(FlowKind.ReplicaDiscovery)` to pull
1113
+ * the copy itself. `"Expired"` means the message was older than the
1114
+ * inbound timeout. `trace_id` matches the peer's `*Started` event for
1115
+ * the same round (`"0"` when the sender set none). */
1116
+ | {
1117
+ type: "MessageIgnored";
1118
+ channel_id: string;
1119
+ reason: IgnoreReason;
1120
+ trace_id: string;
1121
+ }
1122
+ /** Returned by `restore`: a roster entry got no channel, so this device
1123
+ * cannot reach that peer. Every other entry and the user-secret snapshot
1124
+ * were restored; `reason` says why this one was not. `"NoTransports"`
1125
+ * means the recovered roster names no endpoint for it.
1126
+ *
1127
+ * For a helper, `channel_id` is its channel and `replica_id` is absent.
1128
+ * For a replica group member, `channel_id` is the group's channel and
1129
+ * `replica_id` names the member. The peer itself is untouched — a helper
1130
+ * still holds its share — and pairing with it again makes it reachable. */
1131
+ | {
1132
+ type: "PeerNotRestored";
1133
+ channel_id: string;
1134
+ replica_id?: string;
1135
+ reason: NotRestoredReason;
1136
+ }
940
1137
  /** A pairing handshake was dispatched successfully. `kind` is the
941
1138
  * local party's role — same value the subsequent `PairingCompleted`
942
1139
  * will carry. Emitted by `start(Pairing)`. */
@@ -1005,8 +1202,8 @@ export type DeRecEvent =
1005
1202
  * inbound shares. The protocol enforces no size, quota or rate limit
1006
1203
  * of its own, and `maxShareSize` is checked for range overlap at
1007
1204
  * pairing time only, never against an actual share. While this is
1008
- * `false`, `ActionRequired` carries the decoded request, so the
1009
- * application can inspect the share and call `reject()` with
1205
+ * `false`, `ActionRequired` carries `share_size`, so the application
1206
+ * can compare it to its quota and call `reject()` with
1010
1207
  * `StatusEnum.SizeLimitExceeded`. Setting it `true` removes that
1011
1208
  * opportunity entirely: every share from every paired Owner is stored
1012
1209
  * unconditionally, at whatever size it arrives. Keep off in any
@@ -1099,6 +1296,22 @@ export interface CommunicationInfo {
1099
1296
  // `ContactMessage` is defined once above and covers both `INLINE_KEYS` and
1100
1297
  // `HASHED_KEYS` modes.
1101
1298
 
1299
+ /** Why a `MessageIgnored` event dropped a message. Matches the Rust
1300
+ * `IgnoreReason` discriminants one-for-one. */
1301
+ export type IgnoreReason = "PendingVerification" | "Expired";
1302
+
1303
+ /** Why a `PeerNotRestored` event left a roster entry without a channel.
1304
+ * Matches the Rust `NotRestoredReason` discriminants one-for-one. */
1305
+ export type NotRestoredReason = "NoTransports";
1306
+
1307
+ /** Why a `RecoveryShareCorrupted` event set a helper's share aside.
1308
+ * Matches the Rust `CorruptionReason` discriminants one-for-one:
1309
+ * `Malformed` — no decodable share for the requested secret and version;
1310
+ * `InvalidProof` — the share fails its own Merkle proof;
1311
+ * `Inconsistent` — valid on its own, but its commitment root or ciphertext
1312
+ * disagrees with the shares the secret was rebuilt from. */
1313
+ export type CorruptionReason = "Malformed" | "InvalidProof" | "Inconsistent";
1314
+
1102
1315
  /**
1103
1316
  * The label vocabulary for `ActionRequired.action_kind` and
1104
1317
  * `AutoAccepted.action_kind` — one value per pending-action kind the
@@ -1135,17 +1348,9 @@ export interface PairRequestMessage {
1135
1348
  nonce: bigint;
1136
1349
  communication_info?: CommunicationInfo;
1137
1350
  parameter_range?: ParameterRange;
1138
- /**
1139
- * @deprecated Reading this field directly is incorrect: its meaning narrowed
1140
- * to "one entry of a list, and possibly absent", so a peer advertising only
1141
- * `supported_transports` looks unreachable to a reader that was correct
1142
- * before 0.0.3. Call {@link advertisedEndpoints}, which resolves both
1143
- * spellings. Removed at 0.0.5.
1144
- */
1145
- transport_protocol?: TransportProtocol;
1146
1351
  timestamp?: Timestamp;
1147
1352
  /** Every transport endpoint the initiator can be reached on, in its own
1148
- * preference order. Empty means "only `transport_protocol` is offered". */
1353
+ * preference order. */
1149
1354
  supported_transports: TransportProtocol[];
1150
1355
  }
1151
1356
 
@@ -1167,21 +1372,12 @@ export interface PairResponseMessage {
1167
1372
 
1168
1373
  export interface PrePairRequestMessage {
1169
1374
  nonce: bigint;
1170
- /**
1171
- * @deprecated Reading this field directly is incorrect: its meaning narrowed
1172
- * to "one entry of a list, and possibly absent", so a peer advertising only
1173
- * `supported_transports` looks unreachable to a reader that was correct
1174
- * before 0.0.3. Call {@link advertisedEndpoints}, which resolves both
1175
- * spellings. Removed at 0.0.5.
1176
- */
1177
- transport_protocol?: TransportProtocol;
1178
1375
  timestamp?: Timestamp;
1179
1376
  /**
1180
1377
  * Every endpoint the sender can be reached on for the PrePair reply, in
1181
- * its own preference order. At least one of this and `transport_protocol`
1182
- * must be present.
1378
+ * its own preference order.
1183
1379
  */
1184
- supported_transports?: TransportProtocol[];
1380
+ supported_transports: TransportProtocol[];
1185
1381
  }
1186
1382
 
1187
1383
  export interface PrePairResponseMessage {
@@ -1367,6 +1563,15 @@ export interface ProducePrePairResult {
1367
1563
  envelope: Uint8Array;
1368
1564
  }
1369
1565
 
1566
+ export interface ProducePrePairNoKeysResult {
1567
+
1568
+ envelope: Uint8Array;
1569
+
1570
+ /** Secret key material generated for this pairing. The caller MUST persist
1571
+ * it: the `PairRequest` that follows is encrypted to it. */
1572
+ secret_key_material: Uint8Array;
1573
+ }
1574
+
1370
1575
  export interface PrePairRequestExtractResult {
1371
1576
 
1372
1577
  request: PrePairRequestMessage;