@kubun/plugin-p2p 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/lib/context/group.js +1073 -746
  2. package/lib/context/hub.js +2 -1
  3. package/lib/context/join.js +50 -15
  4. package/lib/context/peer.d.ts +92 -0
  5. package/lib/context/peer.js +1216 -0
  6. package/lib/context/require-admin.d.ts +57 -0
  7. package/lib/context/require-admin.js +57 -0
  8. package/lib/context/sync.d.ts +12 -0
  9. package/lib/context/sync.js +234 -1
  10. package/lib/context/types.d.ts +56 -20
  11. package/lib/context/types.js +23 -3
  12. package/lib/groups/access-default-token.d.ts +44 -0
  13. package/lib/groups/access-default-token.js +103 -0
  14. package/lib/groups/admin-roster.d.ts +42 -117
  15. package/lib/groups/admin-roster.js +53 -168
  16. package/lib/groups/anchor-store.d.ts +15 -0
  17. package/lib/groups/anchor-store.js +37 -0
  18. package/lib/groups/app-cursor-store.d.ts +17 -0
  19. package/lib/groups/app-cursor-store.js +34 -0
  20. package/lib/groups/broadcast.d.ts +199 -81
  21. package/lib/groups/broadcast.js +443 -368
  22. package/lib/groups/catalog-token.d.ts +50 -0
  23. package/lib/groups/catalog-token.js +96 -0
  24. package/lib/groups/circle-projection.d.ts +32 -64
  25. package/lib/groups/circle-projection.js +95 -181
  26. package/lib/groups/circle-reducers.d.ts +71 -110
  27. package/lib/groups/circle-reducers.js +57 -127
  28. package/lib/groups/commit-adoption.d.ts +77 -0
  29. package/lib/groups/commit-adoption.js +108 -0
  30. package/lib/groups/commit-journal.d.ts +14 -0
  31. package/lib/groups/commit-journal.js +65 -0
  32. package/lib/groups/control-request.d.ts +66 -0
  33. package/lib/groups/control-request.js +101 -0
  34. package/lib/groups/events.d.ts +14 -1
  35. package/lib/groups/group-anchor.d.ts +18 -57
  36. package/lib/groups/group-anchor.js +15 -100
  37. package/lib/groups/group-crypto.d.ts +36 -13
  38. package/lib/groups/group-crypto.js +124 -50
  39. package/lib/groups/group-handle-registry.d.ts +106 -36
  40. package/lib/groups/group-handle-registry.js +351 -61
  41. package/lib/groups/group-handlers.d.ts +5 -4
  42. package/lib/groups/group-handlers.js +155 -29
  43. package/lib/groups/group-health-monitor.d.ts +1 -1
  44. package/lib/groups/group-health-monitor.js +1 -1
  45. package/lib/groups/group-mls.d.ts +35 -4
  46. package/lib/groups/group-mls.js +594 -62
  47. package/lib/groups/group-peer-manager.d.ts +212 -19
  48. package/lib/groups/group-peer-manager.js +1061 -69
  49. package/lib/groups/group-protocols.d.ts +485 -28
  50. package/lib/groups/group-protocols.js +289 -15
  51. package/lib/groups/invite-payload.d.ts +48 -0
  52. package/lib/groups/join-request-identity.d.ts +21 -0
  53. package/lib/groups/join-request-identity.js +43 -0
  54. package/lib/groups/join-utils.d.ts +60 -0
  55. package/lib/groups/join-utils.js +262 -0
  56. package/lib/groups/ledger-affected-events.d.ts +11 -6
  57. package/lib/groups/ledger-affected-events.js +15 -26
  58. package/lib/groups/ledger-commit-fold.d.ts +107 -0
  59. package/lib/groups/ledger-commit-fold.js +167 -0
  60. package/lib/groups/ledger-ingest.d.ts +124 -0
  61. package/lib/groups/ledger-ingest.js +212 -0
  62. package/lib/groups/ledger.d.ts +44 -0
  63. package/lib/groups/ledger.js +30 -0
  64. package/lib/groups/manager.d.ts +202 -19
  65. package/lib/groups/manager.js +623 -106
  66. package/lib/groups/mls-codec.d.ts +27 -6
  67. package/lib/groups/mls-codec.js +30 -15
  68. package/lib/groups/mls-encryptor.js +9 -6
  69. package/lib/groups/mls-group-handle.d.ts +37 -2
  70. package/lib/groups/mls-group-handle.js +12 -4
  71. package/lib/groups/mls-receive-errors.d.ts +0 -2
  72. package/lib/groups/mls-receive-errors.js +3 -6
  73. package/lib/groups/mls-state.d.ts +5 -3
  74. package/lib/groups/mls-state.js +27 -4
  75. package/lib/groups/peer-presence.d.ts +90 -0
  76. package/lib/groups/peer-presence.js +295 -0
  77. package/lib/groups/peer-selection.d.ts +37 -0
  78. package/lib/groups/peer-selection.js +72 -0
  79. package/lib/groups/roster-projection.d.ts +53 -0
  80. package/lib/groups/roster-projection.js +65 -0
  81. package/lib/groups/store-received-grant.d.ts +4 -3
  82. package/lib/groups/store-received-grant.js +80 -35
  83. package/lib/groups/store-received-revocation.d.ts +16 -13
  84. package/lib/groups/store-received-revocation.js +23 -22
  85. package/lib/hub/http-client.d.ts +10 -27
  86. package/lib/hub/http-client.js +2 -19
  87. package/lib/hub/hub-like.d.ts +57 -6
  88. package/lib/hub/hub-like.js +539 -52
  89. package/lib/hub/loopback-log-hub.d.ts +20 -0
  90. package/lib/hub/loopback-log-hub.js +178 -0
  91. package/lib/hub/manager.d.ts +25 -40
  92. package/lib/hub/manager.js +51 -94
  93. package/lib/hub/peer-scoped-hub-view.d.ts +35 -0
  94. package/lib/hub/peer-scoped-hub-view.js +92 -0
  95. package/lib/hub/sender-scoped-hub-view.d.ts +41 -0
  96. package/lib/hub/sender-scoped-hub-view.js +59 -0
  97. package/lib/hub/server-did.d.ts +67 -0
  98. package/lib/hub/server-did.js +131 -0
  99. package/lib/hub/wiring.d.ts +71 -14
  100. package/lib/hub/wiring.js +80 -23
  101. package/lib/index.d.ts +48 -13
  102. package/lib/index.js +257 -86
  103. package/lib/peer/connection-registry.d.ts +45 -0
  104. package/lib/peer/connection-registry.js +52 -0
  105. package/lib/peer/handlers.d.ts +69 -0
  106. package/lib/peer/handlers.js +110 -0
  107. package/lib/protocol.d.ts +213 -0
  108. package/lib/protocol.js +268 -0
  109. package/lib/schema.d.ts +2 -1
  110. package/lib/schema.js +805 -131
  111. package/lib/sync/access-default-sender.d.ts +23 -0
  112. package/lib/sync/access-default-sender.js +80 -0
  113. package/lib/sync/authorize.d.ts +57 -3
  114. package/lib/sync/authorize.js +45 -4
  115. package/lib/sync/broadcast-queue.d.ts +3 -4
  116. package/lib/sync/broadcast-queue.js +2 -3
  117. package/lib/sync/catalog-scope.d.ts +8 -7
  118. package/lib/sync/catalog-scope.js +55 -39
  119. package/lib/sync/errors.d.ts +9 -0
  120. package/lib/sync/errors.js +10 -0
  121. package/lib/sync/forwarder.d.ts +13 -19
  122. package/lib/sync/forwarder.js +13 -19
  123. package/lib/sync/handlers.js +54 -14
  124. package/lib/sync/hub-tunnel-sync-listener.d.ts +16 -6
  125. package/lib/sync/hub-tunnel-sync-listener.js +126 -37
  126. package/lib/sync/hub-tunnel-sync-provider.d.ts +31 -11
  127. package/lib/sync/hub-tunnel-sync-provider.js +68 -19
  128. package/lib/sync/merkle-channel.d.ts +9 -1
  129. package/lib/sync/merkle-channel.js +0 -0
  130. package/lib/sync/peer-registry.d.ts +14 -10
  131. package/lib/sync/peer-registry.js +0 -25
  132. package/lib/sync/sync-client.d.ts +11 -1
  133. package/lib/sync/sync-client.js +72 -10
  134. package/lib/sync/sync-manager.d.ts +22 -4
  135. package/lib/sync/sync-manager.js +39 -10
  136. package/lib/sync/tunnel-listeners.d.ts +52 -0
  137. package/lib/sync/tunnel-listeners.js +175 -0
  138. package/lib/sync/tunnel-observability.d.ts +25 -0
  139. package/lib/sync/tunnel-observability.js +63 -0
  140. package/lib/sync/tunnel-topics.d.ts +20 -0
  141. package/lib/sync/tunnel-topics.js +28 -0
  142. package/lib/types.d.ts +699 -28
  143. package/lib/types.js +0 -1
  144. package/lib/util/handler-error.d.ts +16 -0
  145. package/lib/util/handler-error.js +64 -0
  146. package/lib/util/mutex.d.ts +14 -1
  147. package/lib/util/mutex.js +37 -3
  148. package/package.json +59 -52
  149. package/lib/groups/ledger-entry.d.ts +0 -37
  150. package/lib/groups/ledger-entry.js +0 -56
  151. package/lib/groups/ledger-fold.d.ts +0 -56
  152. package/lib/groups/ledger-fold.js +0 -53
  153. package/lib/hub/did-observing-transport.d.ts +0 -75
  154. package/lib/hub/did-observing-transport.js +0 -126
  155. package/lib/hub/errors.d.ts +0 -24
  156. package/lib/hub/errors.js +0 -33
@@ -1,17 +1,35 @@
1
+ import type { ProcedureHandlers } from '@enkaku/server';
2
+ import type { OwnIdentity } from '@kokuin/token';
1
3
  import type { StoreProvider } from '@kubun/db';
2
4
  import type { DefaultAccessLevel, GraphInternals } from '@kubun/engine';
3
5
  import type { HLC } from '@kubun/hlc';
4
6
  import type { Logger } from '@kubun/logger';
5
7
  import type { GraphStoreAPI } from '@kubun/store-graph';
6
- import type { HubLike } from '@kumiai/hub-tunnel';
7
- import { type GroupPeer } from '@kumiai/rpc';
8
- import type { OnServerDIDObserved } from '../hub/did-observing-transport.js';
8
+ import { type GroupPeer, type LaneResult, type PendingCommit } from '@kumiai/rpc';
9
+ import type { Runtime } from '@sozai/runtime';
9
10
  import type { CreateHubClient } from '../hub/http-client.js';
10
- import type { RejoinResult, StoreUnreadableMode } from '../types.js';
11
+ import { type DeviceHub, type ReconnectingDeviceHub } from '../hub/hub-like.js';
12
+ import type { SyncProtocol } from '../protocol.js';
13
+ import type { ForwardingConfig } from '../sync/forwarder.js';
14
+ import type { LedgerCatchupOptions, LedgerCatchupSummary, RejoinResult, StoreUnreadableMode } from '../types.js';
11
15
  import type { GroupBroadcastMessage } from './broadcast.js';
16
+ import { type SettleLostControlRequestDeps } from './commit-adoption.js';
12
17
  import type { P2PEventEmitter } from './events.js';
13
18
  import type { GroupHandleRegistry } from './group-handle-registry.js';
14
19
  import { type GroupProtocols } from './group-protocols.js';
20
+ import { type PeerPresence } from './peer-presence.js';
21
+ /**
22
+ * Rebuild a fresh `ledger` commit carrying the given signed tokens, to re-drive
23
+ * a lost one under its original request id.
24
+ *
25
+ * A narrow capability, NOT the whole group manager: the lane may only ask for
26
+ * exactly this — a build closure over surviving ledger tokens — so it stays a
27
+ * consumer of the ledger write path, never its owner. Wired from
28
+ * `GroupManager.buildEnactLedgerCommit`. `requestID` is absent when the lost
29
+ * commit journalled none: the tokens are still the work and are re-driven, but
30
+ * the rebuilt commit settles nothing rather than settling a fabricated id.
31
+ */
32
+ export type BuildLedgerRedrive = (groupID: string, tokens: Array<string>, requestID?: string) => () => Promise<PendingCommit>;
15
33
  export type GroupPeerManagerParams = {
16
34
  /**
17
35
  * Spawns a fresh `Client<HubProtocol>` for a hub URL. The manager wraps each
@@ -19,11 +37,17 @@ export type GroupPeerManagerParams = {
19
37
  * receive drain), lazily on first peer creation for that hub.
20
38
  */
21
39
  createHubClient: CreateHubClient;
40
+ /**
41
+ * Rebuilds a lost `ledger` commit's surviving tokens into a fresh commit under
42
+ * the original request id, for the seam to re-drive once. Narrow by design —
43
+ * see {@link BuildLedgerRedrive}.
44
+ */
45
+ buildLedgerRedrive: BuildLedgerRedrive;
22
46
  /** Single canonical access point for the device's MLS `GroupHandle` instances. */
23
47
  registry: GroupHandleRegistry;
24
48
  /**
25
49
  * Store coordinator. Resolves the device's p2p store for the apply path,
26
- * group↔hub binding reads, and server-DID pinning; also passed to
50
+ * group↔hub binding reads, and the hub-DID cache; also passed to
27
51
  * `createGroupMLS` for the receive-side commit transaction.
28
52
  */
29
53
  stores: StoreProvider;
@@ -38,20 +62,54 @@ export type GroupPeerManagerParams = {
38
62
  hlc: HLC;
39
63
  /** Authenticated DID of this device (normalized). */
40
64
  localDID: string;
65
+ /**
66
+ * The device's own signing identity, forwarded to each peer's `GroupMLS` for
67
+ * the recovery members alone — the only lane work that must sign without a
68
+ * request behind it to take a key from.
69
+ */
70
+ identity: OwnIdentity;
41
71
  emitter: P2PEventEmitter;
42
- /** Fresh correlation ids for group-rpc; forwarded to each peer when set. */
43
- getRandomID?: () => string;
72
+ /**
73
+ * Host runtime; forwarded to each peer when set, which sources its fresh
74
+ * group-rpc correlation ids from it.
75
+ */
76
+ runtime?: Runtime;
44
77
  logger?: Logger;
45
78
  /** Receive-side storage mode. Forwarded to the apply path. Defaults to `'persist'`. */
46
79
  storeUnreadable?: StoreUnreadableMode;
47
80
  /** Server default access level — required when `storeUnreadable === 'drop'`. */
48
81
  defaultAccessLevel?: DefaultAccessLevel;
49
82
  /**
50
- * Fired once per freshly-spawned hub client when its first signed response
51
- * arrives, with the hub URL bound. Lets callers route capture into
52
- * `captureServerDID` for TOFU pinning; omitting it skips capture.
83
+ * Per-peer N+1 re-broadcast config for the apply path. Omitted or `false`
84
+ * skips the pipeline.
85
+ */
86
+ forwarding?: ForwardingConfig;
87
+ /**
88
+ * Hub hook a forward re-broadcasts through. The apply path gates on this AND
89
+ * `forwarding`, so supplying one alone leaves forwarding off.
90
+ */
91
+ scheduleBroadcast?: (groupID: string, message: GroupBroadcastMessage) => void | Promise<void>;
92
+ /**
93
+ * Reconnect-backoff overrides for every hub adapter this manager spawns. Only
94
+ * a test harness sets this — production runs on the adapter's defaults. Each
95
+ * field is forwarded verbatim to {@link createHubLike}.
53
96
  */
54
- onServerDIDObserved?: OnServerDIDObserved;
97
+ hubReconnectBackoff?: {
98
+ baseMs?: number;
99
+ maxMs?: number;
100
+ jitter?: number;
101
+ };
102
+ /**
103
+ * Sync procedures served to a co-member that dials in over a hub tunnel. The
104
+ * same handlers the direct and HTTP transports serve — a tunnel is a route,
105
+ * so it must not reach a different implementation.
106
+ *
107
+ * Omitted, the device answers no tunnel session: a caller that has not built
108
+ * handlers has no sync surface to expose.
109
+ */
110
+ syncHandlers?: ProcedureHandlers<SyncProtocol>;
111
+ /** @see TunnelListenersParams.idleTimeoutMs */
112
+ tunnelIdleTimeoutMs?: number;
55
113
  };
56
114
  /**
57
115
  * Coordinates the per-(group, hub) `GroupPeer`s for one device — full multi-hub.
@@ -75,12 +133,64 @@ export type GroupPeerManager = {
75
133
  addBinding: (groupID: string, hubURL: string) => Promise<void>;
76
134
  /** Dispose the (group, hub) peer if present, GC-ing the hub if now idle. */
77
135
  removeBinding: (groupID: string, hubURL: string) => Promise<void>;
136
+ /**
137
+ * Re-derive the group's tunnel listeners from its current roster.
138
+ *
139
+ * Driven by the membership projection rather than only by the epoch, because
140
+ * the epoch advances INSIDE the transaction that writes the new member: a
141
+ * reconcile scheduled there can read the roster as it was and stand up no
142
+ * listener for the device just admitted.
143
+ */
144
+ reconcileTunnelListeners: (groupID: string) => Promise<void>;
78
145
  /** Fan a broadcast message out across every hub-peer of a group. */
79
146
  broadcast: (groupID: string, message: GroupBroadcastMessage) => Promise<void>;
80
- /** Fan a locally-produced MLS Commit out across every hub-peer of a group. */
81
- sendCommit: (groupID: string, commit: Uint8Array) => Promise<void>;
82
- /** Deep recovery is deferred always reports `recovery-failed` without throwing. */
147
+ /**
148
+ * This device's presence on the peer lane: what it advertises, and who it asks.
149
+ * The manager owns it because every internal trigger fires from here — a hub
150
+ * connect, an epoch rotation, a pruned app window — and because a gather needs
151
+ * the group's live peers.
152
+ */
153
+ presence: PeerPresence;
154
+ /**
155
+ * The peer for the group's canonical commit hub — the single hub a group
156
+ * commits through. Resolves the designated commit hub; falls back to the live
157
+ * peer whose hub URL sorts first for a legacy group with no designation (and
158
+ * warns), then to the group's loopback peer when it has no hub-backed peer at
159
+ * all. `undefined` only when the group has no lane whatsoever.
160
+ */
161
+ selectCommitPeer: (groupID: string) => Promise<GroupPeer<GroupProtocols> | undefined>;
162
+ /**
163
+ * Drive a commit through the group's canonical commit hub: build against the
164
+ * live handle, publish to that hub's commit log, adopt only on acceptance.
165
+ * Throws when the group has no live commit peer.
166
+ */
167
+ commit: (groupID: string, build: () => Promise<PendingCommit>) => Promise<LaneResult>;
168
+ /**
169
+ * Deep recovery: rejoin a peer the group left behind, via `GroupPeer.recover()`
170
+ * on the group's canonical commit hub. The peer requests a member's sealed
171
+ * GroupInfo over the non-rotating rendezvous topic, publishes the external
172
+ * commit under a head compare-and-set, and bootstraps the ledger — reissuing
173
+ * any tokens the authenticated ledger lacked. Reports `recovered` once the
174
+ * rejoin landed, or `recovery-failed` (without throwing) when no live responder
175
+ * answers or the group has no lane.
176
+ */
83
177
  rejoinGroup: (groupID: string) => Promise<RejoinResult>;
178
+ /**
179
+ * Gather the group's control ledger from its current members and adopt it
180
+ * locally. Sends a `control/ledgerCatchup` request on every live hub-peer of
181
+ * the group; each responder returns its WHOLE ledger in enactment order, and
182
+ * the first list that reproduces this device's authenticated head is installed
183
+ * — a responder that withholds or reorders entries is dropped, not merged. A
184
+ * drifted joiner thus backfills what its invite snapshot missed, or gets
185
+ * nothing. Returns what landed; a no-op summary when the group has no live
186
+ * peer or every responder was refused.
187
+ */
188
+ requestLedgerCatchup: (groupID: string, options?: LedgerCatchupOptions) => Promise<LedgerCatchupSummary>;
189
+ /**
190
+ * Ask co-members to restate their own model access-defaults, and apply what is
191
+ * newer than what this device holds. Returns how many rules landed.
192
+ */
193
+ requestPolicyCatchup: (groupID: string) => Promise<number>;
84
194
  /** The peer for one (group, hub), or `undefined` (used by tests). */
85
195
  peer: (groupID: string, hubURL: string) => GroupPeer<GroupProtocols> | undefined;
86
196
  /**
@@ -88,9 +198,22 @@ export type GroupPeerManager = {
88
198
  * Lets a caller obtain the device-wide drain to pass into a directed sync
89
199
  * transport (provider/listener) sharing the same hub connection.
90
200
  */
91
- getHubLike: (hubURL: string) => HubLike & {
92
- dispose: () => Promise<void>;
93
- };
201
+ getHubLike: (hubURL: string) => ReconnectingDeviceHub;
202
+ /**
203
+ * The device-wide drain a directed session to this group should ride, or
204
+ * `undefined` when the group has no live hub binding — which is what a caller
205
+ * building a tunnel needs to know before it builds one.
206
+ *
207
+ * The group's first binding. A second hub is a second route to the same
208
+ * mailbox, not a different peer, so choosing between them is a preference
209
+ * nothing here holds.
210
+ */
211
+ tunnelHub: (groupID: string) => DeviceHub | undefined;
212
+ /**
213
+ * Try every hub this device holds now, rather than when each one's backoff
214
+ * says so, and answer with whether any of them is connected afterwards.
215
+ */
216
+ retryHubs: () => Promise<boolean>;
94
217
  /** Dispose every peer and hub, then clear all maps. */
95
218
  stop: () => Promise<void>;
96
219
  /** Alias for {@link stop}. */
@@ -101,8 +224,78 @@ export type GroupPeerManager = {
101
224
  *
102
225
  * Translates a {@link GroupBroadcastMessage} into the matching group-rpc
103
226
  * `dispatch` on a peer's control/sync protocol surface. Variants with no carried
104
- * procedure (catalog control) and the legacy catch-up variants (now a `gather`,
105
- * not a dispatch) are logged and no-op'd, matching the receive side.
227
+ * procedure (catalog control) are logged and no-op'd, matching the receive side.
106
228
  */
107
229
  export declare function dispatchMessage(peer: GroupPeer<GroupProtocols>, message: GroupBroadcastMessage, logger?: Logger): Promise<void>;
230
+ /**
231
+ * Run one lane operation, retrying only while the peer's runtimes are mid-rebuild.
232
+ *
233
+ * Safe to retry because the throw happens BEFORE anything is sealed or published:
234
+ * `surfaceFor` is what fails, so a retry cannot duplicate a frame. Every other
235
+ * failure — a refused publish, an unrouted message type — propagates on the first
236
+ * attempt, which is what keeps this from papering over a real fault.
237
+ */
238
+ export declare function throughRotation<T>(op: () => Promise<T>, label: string, logger?: Logger): Promise<T>;
239
+ /**
240
+ * Everything the seam needs to act on a lost commit: the settle deps it shares
241
+ * with {@link settleLostControlRequest}, plus what a lost `ledger` re-drive
242
+ * takes — the group whose ledger is read for the never-re-append filter, the
243
+ * registry that read runs through, and the narrow builder that rebuilds a
244
+ * surviving-token commit.
245
+ */
246
+ export type CommitThroughPeerDeps = SettleLostControlRequestDeps & {
247
+ groupID: string;
248
+ /** Reads the group's current ledger tokens for the never-re-append filter. */
249
+ registry: Pick<GroupHandleRegistry, 'readHandle'>;
250
+ buildLedgerRedrive: BuildLedgerRedrive;
251
+ };
252
+ /**
253
+ * Publish a commit through the peer and act on what the lane hands back.
254
+ *
255
+ * A `LaneResult` can carry both a `lost` and a `reenact`; handle each
256
+ * independently. A lost `ledger` is re-issuable from its tokens, an `invite` or
257
+ * `remove` is not, so those settle `lost` here.
258
+ *
259
+ * The housekeeping is CONTAINED — never let it throw. By the time it runs THIS
260
+ * commit has landed and its request is settled, while `lost` and `reenact` name
261
+ * other work; an escaping throw would have the producer's per-request catch
262
+ * settle the terminal request `rejected`, writing a failure over a success and
263
+ * nulling the artifact for a member who really was added. That covers
264
+ * `RecoveryRequiredError` too: reaching {@link commitWithRecovery} would re-run
265
+ * the ORIGINAL `build` after its commit landed, publishing a second copy outside
266
+ * the never-re-append filter.
267
+ */
268
+ export declare function commitThroughPeer(peer: Pick<GroupPeer<GroupProtocols>, 'commit'>, build: () => Promise<PendingCommit>, deps: CommitThroughPeerDeps): Promise<LaneResult>;
269
+ export type CommitWithRecoveryParams = {
270
+ peer: Pick<GroupPeer<GroupProtocols>, 'commit' | 'recover'>;
271
+ build: () => Promise<PendingCommit>;
272
+ deps: CommitThroughPeerDeps;
273
+ /** Repair an incomplete ledger, the cheaper of the two remedies. */
274
+ gatherLedger: () => Promise<unknown>;
275
+ };
276
+ /**
277
+ * Publish a commit and, on the lane's stranded/incomplete-ledger gate, self-heal.
278
+ *
279
+ * The gate conflates an incomplete ledger — repaired by a plain ledger GATHER —
280
+ * with a true strand, repaired only by a full rejoin via `recover()`, so the
281
+ * cheaper remedy runs first: gather + retry once, then `recover()` + retry once,
282
+ * then propagate. Each remedy runs AT MOST ONCE (≤3 commit attempts): upstream
283
+ * forbids a tight retry loop, and `recover()` already bounds itself with its own
284
+ * deadline loop. When `recover()` cannot advance, rethrow the ORIGINAL gate error
285
+ * so the caller settles against the real cause. The retry carries the original
286
+ * `requestID`, so a healed commit settles `committed` through its own
287
+ * `onAccepted` rather than surfacing as a failure.
288
+ */
289
+ export declare function commitWithRecovery(params: CommitWithRecoveryParams): Promise<LaneResult>;
290
+ /**
291
+ * Rejoin the group through `GroupPeer.recover()` and map its outcome to a
292
+ * {@link RejoinResult}. `recover()` publishes the external commit under a head
293
+ * compare-and-set and bootstraps the ledger; it bounds itself, so it is never
294
+ * wrapped in a loop. Any `reenact` tokens it hands back — held here but absent
295
+ * from the authenticated ledger after the rejoin — are re-issued through the
296
+ * same {@link reissueLedgerTokens} filter every lane outcome uses. `advanced`
297
+ * maps to `recovered`; a peer that could not advance (no live responder) maps to
298
+ * `recovery-failed` without throwing.
299
+ */
300
+ export declare function recoverThroughPeer(peer: Pick<GroupPeer<GroupProtocols>, 'commit' | 'recover'>, deps: CommitThroughPeerDeps): Promise<RejoinResult>;
108
301
  export declare function createGroupPeerManager(params: GroupPeerManagerParams): GroupPeerManager;