@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,12 +1,21 @@
1
1
  import { type Identity, type OwnIdentity } from '@kokuin/token';
2
2
  import type { StoreProvider } from '@kubun/db';
3
3
  import { HLC } from '@kubun/hlc';
4
+ import type { Logger } from '@kubun/logger';
4
5
  import { type GroupPermission, type Invite, type KeyPackageBundle } from '@kumiai/mls';
6
+ import type { PendingCommit } from '@kumiai/rpc';
7
+ import type { Runtime } from '@sozai/runtime';
8
+ import type { P2PEventEmitter } from './events.js';
5
9
  import type { GroupHandleRegistry } from './group-handle-registry.js';
6
10
  import type { SuggestedHub } from './invite-payload.js';
7
11
  export type GroupManagerParams = {
8
12
  identity: Identity;
9
- getRandomID: () => string;
13
+ /**
14
+ * Host runtime, propagated rather than constructed here: this package runs on
15
+ * React Native and in the browser as well as on node, so the environment's
16
+ * own primitives (id generation, randomness) must come from the caller.
17
+ */
18
+ runtime: Runtime;
10
19
  /**
11
20
  * Single canonical access point for the device's MLS `GroupHandle` instances.
12
21
  * Required: all member ops + createGroup/joinGroup route MLS state through
@@ -21,6 +30,21 @@ export type GroupManagerParams = {
21
30
  * timestamps (the second would silently lose under last-writer-wins).
22
31
  */
23
32
  hlc: HLC;
33
+ /**
34
+ * Device-wide store coordinator for work that runs DECOUPLED from any request
35
+ * transaction — a commit's `onAccepted` fires on the lane, long after the
36
+ * resolver that started it committed, so it must observe durable state and
37
+ * open its own short transaction. Optional: without it a device's own
38
+ * committed entries are not folded locally (the projections still heal from a
39
+ * reproject or catch-up).
40
+ */
41
+ stores?: StoreProvider;
42
+ /**
43
+ * Emitter for the domain events a device's own committed control entries
44
+ * drive — the same events a broadcast-delivered entry fires.
45
+ */
46
+ emitter?: P2PEventEmitter;
47
+ logger?: Logger;
24
48
  };
25
49
  export type CreateGroupParams = {
26
50
  stores: StoreProvider;
@@ -43,6 +67,13 @@ export type InviteToGroupParams = {
43
67
  };
44
68
  export type InviteToGroupResult = {
45
69
  invite: Invite;
70
+ /**
71
+ * The signed ledger tokens this invite ADDS to the group's ledger (the
72
+ * invitee's role entry). Already persisted locally, and the bodies the
73
+ * add-commit's envelope names by content id alone: they ride that commit's own
74
+ * frame, which is where every other member resolves them.
75
+ */
76
+ addedTokens: Array<string>;
46
77
  /** Framed MLSMessage(Welcome) bytes for the invitee (`@kumiai/mls`). */
47
78
  welcomeMessage: Uint8Array;
48
79
  /** Framed MLSMessage(Commit) bytes to fan out to existing members. */
@@ -68,30 +99,103 @@ export type JoinGroupParams = {
68
99
  ratchetTree: unknown;
69
100
  };
70
101
  export type JoinGroupResult = undefined;
71
- export type RemoveMemberParams = {
72
- stores: StoreProvider;
102
+ export type BuildEnactLedgerCommitParams = {
73
103
  groupID: string;
74
- leafIndex: number;
75
- memberDID: string;
104
+ /** Signed control-ledger tokens this commit enacts, in enactment order. */
105
+ tokens: Array<string>;
106
+ /**
107
+ * The queued control request this commit enacts, settled `committed` the
108
+ * moment the commit lands. Optional: a commit driven with no request behind
109
+ * it (a test, or an internal re-enact) settles nothing.
110
+ */
111
+ requestID?: string;
76
112
  };
77
- export type RemoveMemberResult = {
78
- commitMessage: Uint8Array;
113
+ /**
114
+ * The raw MLS output of ONE invite attempt, handed to the caller's encoder.
115
+ *
116
+ * Everything a joiner needs beyond this — the group's name, its suggested hubs,
117
+ * the control-state seeds — lives in the context layer, which is why the encoder
118
+ * is injected rather than implemented here.
119
+ */
120
+ export type InviteMaterial = {
121
+ invite: Invite;
122
+ /** Framed MLSMessage(Welcome) bytes for the invitee (`@kumiai/mls`). */
123
+ welcomeMessage: Uint8Array;
124
+ /**
125
+ * Ratchet tree from the post-commit handle. Typed `unknown` to match
126
+ * `@kumiai/mls`'s `ProcessWelcomeParams.ratchetTree`, which is opaque on that
127
+ * side too.
128
+ */
129
+ ratchetTree: unknown;
79
130
  };
80
- export type RemoveGroupMemberParams = {
81
- stores: StoreProvider;
131
+ /**
132
+ * The invite artifact of the attempt the group ACCEPTED, and only that one.
133
+ *
134
+ * A build closure runs once per rebase attempt and every losing attempt is
135
+ * discarded whole, so each attempt mints its own invite, Welcome and ratchet
136
+ * tree against the epoch it framed at — and therefore its own encoded payload.
137
+ * Handing the payload back through a callback fired inside `onAccepted` is what
138
+ * keeps a discarded attempt's from reaching a caller: `onAccepted` runs for the
139
+ * accepted attempt and no other, so a losing attempt's payload never surfaces.
140
+ * Firing the same callback from the build closure would surface every attempt's.
141
+ */
142
+ export type AcceptedInvite = {
143
+ /** The encoded invite payload the caller's encoder built for this attempt. */
144
+ invitePayload: string;
145
+ /**
146
+ * The signed ledger tokens this invite ADDS to the group's ledger (the
147
+ * invitee's role entry). Already persisted locally, and the bodies the
148
+ * add-commit's envelope names by content id alone: they ride that commit's own
149
+ * frame, which is where every other member resolves them.
150
+ */
151
+ addedTokens: Array<string>;
152
+ };
153
+ export type BuildInviteCommitParams = {
154
+ groupID: string;
155
+ identity: OwnIdentity;
156
+ recipientDID: string;
157
+ recipientKeyPackage: KeyPackageBundle['publicPackage'];
158
+ permission: GroupPermission;
159
+ /**
160
+ * The queued control request this commit enacts, settled `committed` the
161
+ * moment the commit lands. Optional: a commit driven with no request behind it
162
+ * settles nothing.
163
+ */
164
+ requestID?: string;
165
+ /**
166
+ * Encode one attempt's material into the self-contained payload a joiner acts
167
+ * on. Injected because the fields it has to fold in — the group's name, its
168
+ * hubs, the control-state seeds — belong to the context layer, not here.
169
+ *
170
+ * Runs INSIDE the build closure, once per attempt, so the resulting bytes can
171
+ * ride the journal blob before the commit is published. That is what makes a
172
+ * crashed-and-replayed commit able to hand the payload back at all; a payload
173
+ * built after the drive would not exist on the replay path.
174
+ */
175
+ encodeInvitePayload: (material: InviteMaterial) => Promise<string>;
176
+ /**
177
+ * Receives the accepted attempt's payload once the commit has landed and this
178
+ * device's own rows are written. Never called for a losing attempt.
179
+ */
180
+ onInvited?: (accepted: AcceptedInvite) => Promise<void>;
181
+ };
182
+ export type BuildRemoveCommitParams = {
82
183
  groupID: string;
83
184
  memberDID: string;
84
185
  /**
85
- * The HLC stamped by the caller for the roster tombstone. Lifted out of this
86
- * method so the producer can stamp ONE HLC that orders both the MLS-roster
87
- * tombstone here and the admin-role revocation it appends in the same
88
- * transaction — the two must share a timestamp for the fold to drop exactly
89
- * the ex-admin's later control entries.
186
+ * Signed control-ledger tokens this removal enacts. Removing an admin means
187
+ * riding their demotion entry on the same commit: a receiver refuses a Remove
188
+ * whose target is still an admin in the roster the commit folds to.
189
+ */
190
+ ledgerEntries?: Array<string>;
191
+ /**
192
+ * The HLC stamped by the caller for the roster tombstone, so one timestamp
193
+ * orders both the MLS-roster tombstone and the admin-role revocation riding
194
+ * the same commit — the fold drops exactly the ex-admin's later entries only
195
+ * when the two share a position.
90
196
  */
91
197
  hlc: string;
92
- };
93
- export type RemoveGroupMemberResult = {
94
- commitMessage: Uint8Array;
198
+ requestID?: string;
95
199
  };
96
200
  export type LeaveGroupParams = {
97
201
  stores: StoreProvider;
@@ -111,10 +215,89 @@ export declare class GroupManager {
111
215
  #private;
112
216
  constructor(params: GroupManagerParams);
113
217
  createGroup(params: CreateGroupParams): Promise<CreateGroupResult>;
218
+ /**
219
+ * Add a member by committing directly against the local handle. TEST-ONLY.
220
+ *
221
+ * Production invites ride the commit lane: the Add is published to the commit
222
+ * hub's log under the head CAS, and the local epoch advances only once the hub
223
+ * accepts. This does neither — it advances and persists the epoch outright —
224
+ * so calling it on a hub-backed group forks that group, and the fork presents
225
+ * as a hang rather than an error. `git log -S "groupManager.inviteToGroup"`
226
+ * shows the last production caller removed by `2e901689`, when the invite
227
+ * moved onto the lane; `addGroupMember` was deleted rather than renamed for
228
+ * the same reason. Kept for the ~40 suites that drive a hubless group
229
+ * directly, and unreachable from outside this package: {@link GroupManager} is
230
+ * not exported from the plugin's entry point.
231
+ */
114
232
  inviteToGroup(params: InviteToGroupParams): Promise<InviteToGroupResult>;
115
233
  joinGroup(params: JoinGroupParams): Promise<JoinGroupResult>;
116
- removeMember(params: RemoveMemberParams): Promise<RemoveMemberResult>;
117
- removeGroupMember(params: RemoveGroupMemberParams): Promise<RemoveGroupMemberResult>;
234
+ /**
235
+ * The admin write path for the control ledger: build a commit carrying no
236
+ * membership change, only the entries it enacts and the head move covering them.
237
+ * A role entry that never rides a commit is invisible to every other peer's
238
+ * roster and to the ledger head.
239
+ *
240
+ * BUILD IS SPLIT FROM DRIVE, and every `build*Commit` here shares the contract:
241
+ * the returned closure is re-run per lane attempt, reads the group's CURRENT
242
+ * handle and derives a fresh commit against it without advancing the live one,
243
+ * so a rebase after a lost compare-and-set frames at the up-to-date epoch.
244
+ * Nothing is adopted, written or handed out before `onAccepted` — adopting
245
+ * earlier seals the bodies under an epoch no receiver holds — so a losing
246
+ * attempt is discarded whole.
247
+ */
248
+ buildEnactLedgerCommit(params: BuildEnactLedgerCommitParams): () => Promise<PendingCommit>;
249
+ /**
250
+ * The admin membership-add path for the commit lane: build an Add commit
251
+ * carrying the invitee's key package plus the role entry the invite mints, and
252
+ * the head move covering it. Same build-vs-drive contract as
253
+ * {@link buildEnactLedgerCommit}.
254
+ *
255
+ * `kind` is `'invite'` because a host cannot re-issue this commit after a crash:
256
+ * the intent lives in the Add proposal and the KeyPackage, neither of which
257
+ * survives the process.
258
+ */
259
+ buildInviteCommit(params: BuildInviteCommitParams): () => Promise<PendingCommit>;
260
+ /**
261
+ * The admin membership-removal path for the commit lane: build a Remove commit
262
+ * evicting a member, carrying any demotion entry the removal must enact and the
263
+ * head move covering it. Same build-vs-drive contract as
264
+ * {@link buildEnactLedgerCommit}; the leaf index is resolved per attempt so a
265
+ * rebase cannot evict the leaf a superseded roster held.
266
+ *
267
+ * An admin's demotion must ride this very commit — that is what `ledgerEntries`
268
+ * is for, enforced by {@link assertRemovalDemotes} so a caller that forgets it
269
+ * fails here rather than shipping a commit the group silently drops.
270
+ *
271
+ * `kind` is `'remove'` because a host cannot re-issue this commit after a crash:
272
+ * the intent lives in the MLS Remove proposal, which does not survive the
273
+ * process.
274
+ */
275
+ buildRemoveCommit(params: BuildRemoveCommitParams): () => Promise<PendingCommit>;
276
+ /**
277
+ * Record locally that this device has left, without touching MLS.
278
+ *
279
+ * Split from the teardown because the two want opposite timing. What the
280
+ * device says about ITSELF has to be true the moment `leaveGroup` returns — a
281
+ * screen that navigates away must not still be told it belongs to the group —
282
+ * while the departure announcement must reach co-members BEFORE the key it is
283
+ * encrypted under is deleted, and publishing cannot be awaited inside the
284
+ * caller's write transaction.
285
+ *
286
+ * The group row is kept and marked instead of deleted: its circles, circle
287
+ * members, peer announcements and hub bindings hang off it, and a device
288
+ * invited back would otherwise have to rebuild state it still holds.
289
+ */
290
+ markGroupLeft(params: LeaveGroupParams): Promise<void>;
291
+ /**
292
+ * Drop this device's MLS state for a group, and nothing else.
293
+ *
294
+ * The other half of {@link GroupManager.markGroupLeft}: it must run AFTER the
295
+ * departure announcement, because that announcement is encrypted under the key
296
+ * this deletes. Separate from the row writes so the caller can commit those
297
+ * with its own transaction and leave only this for afterwards — repeating the
298
+ * writes out here would have them block on a connection the caller still holds.
299
+ */
300
+ tearDownGroupMLS(stores: StoreProvider, groupID: string): Promise<void>;
118
301
  leaveGroup(params: LeaveGroupParams): Promise<LeaveGroupResult>;
119
302
  loadGroup(stores: StoreProvider, groupID: string): Promise<{
120
303
  epoch: number;