@kubun/plugin-p2p 0.11.0 → 0.12.0

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 +56 -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,14 +1,18 @@
1
+ import type { OwnIdentity } from '@kokuin/token';
1
2
  import type { DefaultAccessLevel, GraphInternals } from '@kubun/engine';
2
3
  import { HLC } from '@kubun/hlc';
3
4
  import type { Logger } from '@kubun/logger';
4
5
  import type { CatalogRecord } from '@kubun/protocol';
5
6
  import type { DelegationStoreAPI } from '@kubun/store-delegation';
6
7
  import type { GraphStoreAPI } from '@kubun/store-graph';
7
- import type { Circle, CircleMember, Group, P2PStoreAPI } from '@kubun/store-p2p';
8
+ import type { P2PStoreAPI } from '@kubun/store-p2p';
9
+ import type { GroupAnchor, LedgerLogEntry, RosterState } from '@kumiai/mls';
8
10
  import { type ForwardingConfig } from '../sync/forwarder.js';
9
11
  import type { StoreUnreadableMode } from '../types.js';
12
+ import { type AccessDefaultRule } from './access-default-token.js';
10
13
  import type { P2PEventEmitter } from './events.js';
11
- import type { GroupAnchor } from './group-anchor.js';
14
+ import type { PeerAvailability, PeerCapability } from './group-protocols.js';
15
+ import { type AffectedRow, type LedgerIngestParams } from './ledger-ingest.js';
12
16
  export type MutationApplyEntry = {
13
17
  mutationJWT: string;
14
18
  docID: string;
@@ -43,6 +47,39 @@ export type GroupBroadcastMessage = {
43
47
  catalogID: string;
44
48
  hlc: string;
45
49
  auth?: ControlAuth;
50
+ } | {
51
+ /**
52
+ * A member states its own model access-default so co-members can evaluate
53
+ * the owner's documents. `auth` is a signed token whose issuer is the
54
+ * owner; the receiver derives every authoritative field from that verified
55
+ * token and applies the rule ONLY when `ownerDID` equals the issuer (a
56
+ * member states its own policy, never another's). LWW by `hlc`.
57
+ */
58
+ type: 'access-default:set';
59
+ modelID: string;
60
+ permissionType: 'read' | 'write';
61
+ rule: {
62
+ level: string;
63
+ allowedDIDs: Array<string> | null;
64
+ allowedCircles: Array<string> | null;
65
+ allowedGroups: Array<string> | null;
66
+ };
67
+ ownerDID: string;
68
+ hlc: string;
69
+ auth: ControlAuth;
70
+ } | {
71
+ /**
72
+ * A member removes its own model access-default(s). Same sender-bound and
73
+ * LWW rules as `access-default:set`: applied only when `ownerDID` equals
74
+ * the verified `auth` issuer and the removal `hlc` is newer than the
75
+ * stored rule's.
76
+ */
77
+ type: 'access-default:remove';
78
+ modelID: string;
79
+ permissionTypes: Array<'read' | 'write'>;
80
+ ownerDID: string;
81
+ hlc: string;
82
+ auth: ControlAuth;
46
83
  } | {
47
84
  /**
48
85
  * Distributes a minted `document/write` capability token to group
@@ -74,10 +111,6 @@ export type GroupBroadcastMessage = {
74
111
  groupID: string;
75
112
  /** HLC stamped by the revoker; mirrors the revoker-side stored row. */
76
113
  hlc: string;
77
- } | {
78
- type: 'ledger:entry';
79
- groupID: string;
80
- token: string;
81
114
  } | {
82
115
  /**
83
116
  * A member announces a voluntary leave. MLS forbids self-removal, so this
@@ -93,64 +126,35 @@ export type GroupBroadcastMessage = {
93
126
  } | {
94
127
  type: 'mutation:apply';
95
128
  entries: Array<MutationApplyEntry>;
96
- /** DID of the peer that authored this broadcast — used for loopback drop. */
97
- senderPeerDID: string;
98
- } | {
99
129
  /**
100
- * A rejoining peer asks current members for the group's full control
101
- * ledger so it can backfill entries it missed while absent. `requestID`
102
- * correlates the eventual batched reply.
130
+ * DID of the peer this broadcast arrived FROM used for loopback drop.
131
+ * For a forwarded mutation that is the forwarding peer, not the author, so
132
+ * it is never an authorization input: each entry carries its own signed
133
+ * `mutationJWT`, verified independently.
134
+ *
135
+ * `undefined` when the frame opened at a leaf that could not be named. The
136
+ * entries still apply — see the loopback guard.
103
137
  */
104
- type: 'ledger-catchup:request';
105
- requestID: string;
138
+ senderPeerDID: string | undefined;
106
139
  } | {
107
140
  /**
108
- * A member's reply to a `ledger-catchup:request`: the group's control
109
- * ledger as an array of signed entry tokens. The responder is UNTRUSTED —
110
- * each token self-authenticates, so the requester re-verifies every one
111
- * independently and a forged or unsigned token is dropped without
112
- * poisoning the rest. Idempotent + order-independent on the token digest.
141
+ * A device advertises itself to its co-members. Everything here except
142
+ * `announcerDID` came off the wire; `announcerDID` did NOT it is the
143
+ * MLS-authenticated sender the handler read from `message.payload.iss`,
144
+ * because the announce body carries no DID field at all.
145
+ *
146
+ * `undefined` when the frame opened at a leaf that could not be named, and
147
+ * the apply then writes nothing: a capability claim attributed to nobody is
148
+ * worse than no row.
113
149
  */
114
- type: 'ledger-catchup:reply';
115
- requestID: string;
116
- entries: Array<string>;
117
- };
118
- /** Narrow {@link GroupBroadcastMessage} to the variant(s) carrying the given `type`. */
119
- export type BroadcastOf<TType extends GroupBroadcastMessage['type']> = Extract<GroupBroadcastMessage, {
120
- type: TType;
121
- }>;
122
- /**
123
- * The local-event descriptor for an applied `ledger:entry`. The ingest arm
124
- * captures it after reprojecting, while the store still holds the post-state,
125
- * so {@link forwardRemoteBroadcast} can emit the matching local event for a
126
- * remote-driven change without re-reading or re-verifying. Each variant carries
127
- * the projected row plus the create-vs-change / add-vs-remove discriminators the
128
- * ingest derived by comparing the pre-state it read before reprojecting.
129
- */
130
- export type LedgerAffected = {
131
- entry: 'circle.def';
132
- row: Circle;
133
- /** True when an active circle row existed before this entry (changed, not created). */
134
- preExisted: boolean;
135
- /** True when the entry's catalog set differs from the pre-state's (catalogs changed). */
136
- catalogsChanged: boolean;
137
- } | {
138
- entry: 'circle.def.removed';
139
- row: Circle;
140
- } | {
141
- entry: 'circle.member';
142
- row: CircleMember;
143
- } | {
144
- entry: 'circle.member.removed';
145
- row: CircleMember;
146
- } | {
147
- entry: 'group.settings';
148
- row: Group;
149
- };
150
- export type AffectedRow = {
151
- kind: 'ledger';
152
- ledger: LedgerAffected;
150
+ type: 'peer:announce';
151
+ label: string;
152
+ availability: PeerAvailability;
153
+ capabilities: Array<PeerCapability>;
154
+ hlc: string;
155
+ announcerDID: string | undefined;
153
156
  };
157
+ export type { AffectedRow, LedgerAffected } from './ledger-ingest.js';
154
158
  export type ProcessBroadcastResult = {
155
159
  applied: boolean;
156
160
  /**
@@ -187,6 +191,8 @@ export type ProcessBroadcastResult = {
187
191
  };
188
192
  export type ProcessBroadcastParams = {
189
193
  p2pStore: P2PStoreAPI;
194
+ /** See {@link ApplyLedgerEntriesParams.withProjectionTransaction}. */
195
+ withProjectionTransaction?: ApplyLedgerEntriesParams['withProjectionTransaction'];
190
196
  /**
191
197
  * Delegation store for received-grant / received-revocation handling. The
192
198
  * delegation token and revocation rows live here (group-agnostic); the p2p
@@ -203,6 +209,12 @@ export type ProcessBroadcastParams = {
203
209
  graph: GraphInternals;
204
210
  /** Self DID for `mutation:apply` loopback detection. */
205
211
  selfDID: string;
212
+ /**
213
+ * This device's signing identity, used to restate its own access-defaults on
214
+ * a `control/policyCatchup`. Optional: a device without one serves nothing
215
+ * rather than an unsigned rule, which a requester would drop anyway.
216
+ */
217
+ identity?: OwnIdentity;
206
218
  /**
207
219
  * Receiver storage mode for incoming `mutation:apply` entries.
208
220
  * - `'persist'` (default) — every entry the engine accepts is persisted;
@@ -239,7 +251,7 @@ export type ProcessBroadcastParams = {
239
251
  */
240
252
  emitter?: P2PEventEmitter;
241
253
  /**
242
- * Device-wide monotonic clock. On a newly-appended `ledger:entry` the
254
+ * Device-wide monotonic clock. On a newly-appended control-ledger entry the
243
255
  * receiver merges the entry's HLC into this clock so its own later entries
244
256
  * sort strictly after observed ones. Optional: callers that never receive
245
257
  * ledger entries (and existing tests) omit it and the merge is skipped.
@@ -247,12 +259,28 @@ export type ProcessBroadcastParams = {
247
259
  hlc?: HLC;
248
260
  /**
249
261
  * Resolve a group's genesis anchor, or `null` when none is available yet. A
250
- * `ledger:entry` is always stored durably, but the admin-role overlay can
251
- * only be projected once the authenticated epoch-0 root is known; a `null`
262
+ * control-ledger entry is always stored durably, but the admin-role overlay
263
+ * can only be projected once the authenticated epoch-0 root is known; a `null`
252
264
  * anchor defers projection without dropping the entry. Optional so callers
253
265
  * that never project (and existing tests) can omit it.
254
266
  */
255
267
  getGroupAnchor?: (groupID: string) => Promise<GroupAnchor | null>;
268
+ /** Read the group's control ledger — see `LedgerIngestParams.getGroupLedger`. */
269
+ getGroupLedger?: (groupID: string) => Promise<ReadonlyArray<LedgerLogEntry>>;
270
+ /**
271
+ * The epoch this device's handle is at, stamped onto a `peer:announce` row.
272
+ * Synchronous because it reads the registry's published scalar rather than a
273
+ * handle.
274
+ *
275
+ * It is also the epoch the announcement was SEALED at, not merely the one it
276
+ * was applied at: the app lane only ever hands a handler a frame whose seal
277
+ * epoch matches the current one — everything else is held as `ahead` or
278
+ * discarded as dead — so the two cannot disagree for a delivered frame.
279
+ *
280
+ * Optional; a caller that omits it (or a group the registry has not observed)
281
+ * stamps 0, which reads as "epoch unknown" rather than "epoch zero".
282
+ */
283
+ getGroupEpoch?: (groupID: string) => number | undefined;
256
284
  /** Optional logger — warns on per-entry apply failures. */
257
285
  logger?: Logger;
258
286
  };
@@ -275,31 +303,121 @@ export type ProcessBroadcastParams = {
275
303
  */
276
304
  export declare function processBroadcast(params: ProcessBroadcastParams, message: GroupBroadcastMessage, groupID: string): Promise<ProcessBroadcastResult>;
277
305
  /**
278
- * Summary of a batched ledger catch-up apply.
306
+ * Sender-bound, LWW apply of a signed `access-default:set` frame. Shared by the
307
+ * live broadcast path and the invite-seeding path so both authenticate through
308
+ * the same token verification. The authoritative fields come from the verified
309
+ * token; the plaintext `ownerDID` is used only for the sender-bound equality
310
+ * check. Returns whether a row was written.
279
311
  */
280
- export type ApplyLedgerCatchupReplyResult = {
281
- /** Count of entries whose verified token was newly appended to the ledger. */
312
+ export type AccessDefaultSetFrame = {
313
+ ownerDID: string;
314
+ modelID: string;
315
+ permissionType: 'read' | 'write';
316
+ rule: AccessDefaultRule;
317
+ hlc: string;
318
+ auth: string;
319
+ };
320
+ export type ApplyAccessDefaultSetFrameParams = {
321
+ graphStore: GraphStoreAPI;
322
+ frame: AccessDefaultSetFrame;
323
+ logger?: Logger;
324
+ /** Scopes the log lines to the group the frame arrived on, when it arrived on one. */
325
+ groupID?: string;
326
+ };
327
+ export declare function applyAccessDefaultSetFrame(params: ApplyAccessDefaultSetFrameParams): Promise<boolean>;
328
+ /**
329
+ * Apply one signed access-default set token, with no surrounding frame.
330
+ *
331
+ * What a catch-up reply carries: a token and nothing else, so there is no
332
+ * plaintext mirror to cross-check and none is needed — the frame's mirror
333
+ * fields exist for a receiver rebuilding what was signed, and the authoritative
334
+ * values were always the claim's. Every other rule is the live path's: the owner
335
+ * is the authenticated issuer, and the write happens only if strictly newer than
336
+ * what is stored.
337
+ */
338
+ export declare function applyAccessDefaultSetToken(params: {
339
+ graphStore: GraphStoreAPI;
340
+ token: string;
341
+ logger?: Logger;
342
+ groupID: string;
343
+ }): Promise<boolean>;
344
+ /** Summary of a whole-ledger adoption. */
345
+ type ApplyLedgerCatchupReplyResult = {
346
+ /** Size of the ledger that was adopted. */
282
347
  applied: number;
283
- /** Count of entries dropped because their token failed verification. */
348
+ /**
349
+ * Always `0` on this path, and structurally so: the install verifies the whole
350
+ * ledger against the authenticated head before folding a single entry, so a
351
+ * bad token rejects the batch rather than dropping an entry out of it. Carried
352
+ * because the catch-up summary reports it beside counts from paths that CAN
353
+ * drop per entry.
354
+ */
284
355
  rejected: number;
285
356
  };
357
+ /** Inputs the ledger-fold helpers need — a strict subset of the receive params. */
358
+ export type ApplyLedgerEntriesParams = LedgerIngestParams & {
359
+ /**
360
+ * Run the projection rebuild atomically, given the store to write it through.
361
+ *
362
+ * A rebuild materializes several tables from one fold — circle rows, every
363
+ * circle's `open` flag, the group row, the commit-hub designation — and a crash
364
+ * between them leaves them disagreeing. A device with no flagged commit hub
365
+ * cannot commit at all, which reads as a hang rather than an error.
366
+ *
367
+ * A seam rather than a `StoreProvider` because the surrounding operations must
368
+ * NOT be in the same transaction: an install or a gather waits on the network,
369
+ * and a transaction spanning one holds kubun's single SQLite connection across
370
+ * it. So the caller decides what the transaction covers. Omitted, the rebuild
371
+ * runs on {@link LedgerIngestParams.p2pStore} unwrapped — correct when the
372
+ * caller already holds a transaction, and self-healing on the bootstrap paths,
373
+ * where the next reproject rebuilds from the same ledger anyway.
374
+ *
375
+ * Declared here rather than on {@link LedgerIngestParams} because only the
376
+ * batch folds below read it: `ingestVerifiedLedgerEntry` reprojects through
377
+ * `p2pStore` directly, so a caller passing this to the single-entry path would
378
+ * be promised an atomicity it does not get.
379
+ */
380
+ withProjectionTransaction?: (run: (store: P2PStoreAPI) => Promise<void>) => Promise<void>;
381
+ };
382
+ /**
383
+ * Adopt the control state a handle already holds: merge the HLCs its entries
384
+ * carry into this device's clock, then rebuild every projection from them.
385
+ *
386
+ * For a peer whose handle was filled by something other than a landed commit —
387
+ * a joiner, whose Welcome folded the group's whole ledger and checked it against
388
+ * the authenticated head before this device had any say. Those entries never
389
+ * passed through the receive path, so nothing else has observed their clocks or
390
+ * projected them.
391
+ *
392
+ * The merge keeps this device's later writes sorting after what the group did
393
+ * before it arrived. It decides no authority — that reads the enacted position —
394
+ * but the LWW comparisons that are not ledger folds (removal timestamps,
395
+ * access-default rules) still depend on a clock that has seen the group's.
396
+ */
397
+ export declare function adoptHandleLedgerState(params: ApplyLedgerEntriesParams, groupID: string): Promise<void>;
398
+ /** Inputs {@link bootstrapGroupLedger} needs on top of the ingest's. */
399
+ export type BootstrapGroupLedgerParams = ApplyLedgerEntriesParams & {
400
+ /**
401
+ * Install a gathered WHOLE ledger into the group's handle, verified against
402
+ * the authenticated head before a single entry is folded, and answer with the
403
+ * roster the install left the handle holding. Throws when the list does not
404
+ * reproduce that head.
405
+ *
406
+ * The roster is returned rather than read back afterwards so it is the one the
407
+ * install itself produced, taken while the handle is still held: a second read
408
+ * could observe a handle another operation has since advanced.
409
+ */
410
+ installGroupLedger: (groupID: string, tokens: Array<string>) => Promise<RosterState>;
411
+ };
286
412
  /**
287
- * Apply a batched ledger catch-up reply from an UNTRUSTED responder. Each entry
288
- * carries its own signed token, so the responder's identity is irrelevant: every
289
- * token is verified independently and a forged or unsigned one is dropped without
290
- * aborting the batch. Surviving entries are appended idempotently on the token
291
- * digest, so a replayed or duplicated reply is a harmless no-op.
413
+ * Adopt a whole ledger gathered from one untrusted responder.
292
414
  *
293
- * A catch-up reply delivers the ledger's history out of order by construction, so
294
- * after any new entry lands the projection is rebuilt authoritatively from the
295
- * whole ledger (a full reproject) rather than applied incrementally. All four
296
- * control projections admin roster, circle defs, circle members, group
297
- * settings rebuild here, so a circle or settings entry delivered via catch-up
298
- * materializes immediately rather than waiting for a later live entry. Projection
299
- * is deferred when no genesis anchor is available yet — the entries are still
300
- * stored durably.
415
+ * Signatures do NOT cover omission or reordering a responder can hand back
416
+ * genuine tokens with a demotion missing — so the list is installed against the
417
+ * authenticated head FIRST and rejected whole on mismatch, before anything is
418
+ * projected. Roles are reconciled against the roster the install folded, not the
419
+ * ledger, because nothing later revisits a stale `admin` row.
301
420
  *
302
- * Never throws out of the per-token loop: one bad token must not abort the batch.
421
+ * Throws whatever the install throws a rejected ledger changes nothing.
303
422
  */
304
- export declare function applyLedgerCatchupReply(params: ProcessBroadcastParams, reply: BroadcastOf<'ledger-catchup:reply'>, groupID: string): Promise<ApplyLedgerCatchupReplyResult>;
305
- export {};
423
+ export declare function bootstrapGroupLedger(params: BootstrapGroupLedgerParams, tokens: Array<string>, groupID: string): Promise<ApplyLedgerCatchupReplyResult>;