@kubun/plugin-p2p 0.10.1 → 0.11.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 (145) hide show
  1. package/lib/context/delegation.js +130 -1
  2. package/lib/context/group.js +1319 -1
  3. package/lib/context/hub.js +150 -1
  4. package/lib/context/join.js +80 -1
  5. package/lib/context/sync.js +19 -1
  6. package/lib/context/types.d.ts +3 -3
  7. package/lib/context/types.js +28 -1
  8. package/lib/groups/admin-roster.d.ts +130 -0
  9. package/lib/groups/admin-roster.js +184 -0
  10. package/lib/groups/broadcast-codec.js +6 -1
  11. package/lib/groups/broadcast.d.ts +105 -91
  12. package/lib/groups/broadcast.js +627 -1
  13. package/lib/groups/circle-projection.d.ts +122 -0
  14. package/lib/groups/circle-projection.js +288 -0
  15. package/lib/groups/circle-reducers.d.ts +178 -0
  16. package/lib/groups/circle-reducers.js +198 -0
  17. package/lib/groups/events.d.ts +5 -1
  18. package/lib/groups/events.js +10 -1
  19. package/lib/groups/group-anchor.d.ts +63 -0
  20. package/lib/groups/group-anchor.js +107 -0
  21. package/lib/groups/group-crypto.d.ts +19 -0
  22. package/lib/groups/group-crypto.js +85 -0
  23. package/lib/groups/group-handle-registry.d.ts +61 -2
  24. package/lib/groups/group-handle-registry.js +243 -1
  25. package/lib/groups/group-handlers.d.ts +29 -0
  26. package/lib/groups/group-handlers.js +92 -0
  27. package/lib/groups/group-health-monitor.d.ts +11 -0
  28. package/lib/groups/group-health-monitor.js +139 -1
  29. package/lib/groups/group-mls.d.ts +35 -0
  30. package/lib/groups/group-mls.js +164 -0
  31. package/lib/groups/group-peer-manager.d.ts +108 -0
  32. package/lib/groups/group-peer-manager.js +344 -0
  33. package/lib/groups/group-protocols.d.ts +297 -0
  34. package/lib/groups/group-protocols.js +174 -0
  35. package/lib/groups/invite-payload.d.ts +1 -1
  36. package/lib/groups/invite-payload.js +26 -1
  37. package/lib/groups/join-utils.d.ts +1 -0
  38. package/lib/groups/join-utils.js +104 -1
  39. package/lib/groups/ledger-affected-events.d.ts +15 -0
  40. package/lib/groups/ledger-affected-events.js +96 -0
  41. package/lib/groups/ledger-entry.d.ts +37 -0
  42. package/lib/groups/ledger-entry.js +56 -0
  43. package/lib/groups/ledger-fold.d.ts +56 -0
  44. package/lib/groups/ledger-fold.js +53 -0
  45. package/lib/groups/manager.d.ts +20 -71
  46. package/lib/groups/manager.js +281 -1
  47. package/lib/groups/mls-codec.d.ts +1 -1
  48. package/lib/groups/mls-codec.js +18 -1
  49. package/lib/groups/mls-encryptor.d.ts +2 -2
  50. package/lib/groups/mls-encryptor.js +30 -1
  51. package/lib/groups/mls-group-handle.d.ts +1 -1
  52. package/lib/groups/mls-group-handle.js +10 -1
  53. package/lib/groups/mls-json.js +17 -1
  54. package/lib/groups/mls-receive-errors.d.ts +29 -0
  55. package/lib/groups/mls-receive-errors.js +42 -0
  56. package/lib/groups/mls-state.d.ts +1 -1
  57. package/lib/groups/mls-state.js +56 -1
  58. package/lib/groups/store-received-grant.d.ts +4 -1
  59. package/lib/groups/store-received-grant.js +116 -1
  60. package/lib/groups/store-received-revocation.d.ts +3 -3
  61. package/lib/groups/store-received-revocation.js +82 -1
  62. package/lib/hub/ack-backoff.d.ts +22 -0
  63. package/lib/hub/ack-backoff.js +88 -0
  64. package/lib/hub/did-observing-transport.d.ts +13 -2
  65. package/lib/hub/did-observing-transport.js +126 -1
  66. package/lib/hub/errors.d.ts +0 -6
  67. package/lib/hub/errors.js +33 -1
  68. package/lib/hub/http-client.d.ts +28 -3
  69. package/lib/hub/http-client.js +32 -1
  70. package/lib/hub/hub-like.d.ts +24 -0
  71. package/lib/hub/hub-like.js +535 -0
  72. package/lib/hub/manager.js +198 -1
  73. package/lib/hub/wiring.d.ts +17 -48
  74. package/lib/hub/wiring.js +132 -1
  75. package/lib/index.d.ts +17 -16
  76. package/lib/index.js +392 -1
  77. package/lib/protocol.d.ts +1 -1
  78. package/lib/protocol.js +251 -1
  79. package/lib/schema.d.ts +2 -1
  80. package/lib/schema.js +439 -2
  81. package/lib/sync/authorize.d.ts +1 -1
  82. package/lib/sync/authorize.js +73 -1
  83. package/lib/sync/broadcast-queue.js +183 -1
  84. package/lib/sync/broadcast-sender.js +179 -1
  85. package/lib/sync/catalog-scope.js +68 -1
  86. package/lib/sync/errors.d.ts +3 -2
  87. package/lib/sync/errors.js +19 -1
  88. package/lib/sync/forwarder.js +277 -1
  89. package/lib/sync/handlers.js +160 -1
  90. package/lib/sync/hub-tunnel-sync-listener.d.ts +8 -4
  91. package/lib/sync/hub-tunnel-sync-listener.js +172 -1
  92. package/lib/sync/hub-tunnel-sync-provider.d.ts +14 -14
  93. package/lib/sync/hub-tunnel-sync-provider.js +64 -1
  94. package/lib/sync/merkle-apply.d.ts +5 -0
  95. package/lib/sync/merkle-apply.js +194 -1
  96. package/lib/sync/merkle-channel.d.ts +2 -2
  97. package/lib/sync/merkle-channel.js +182 -1
  98. package/lib/sync/merkle-tree.js +120 -1
  99. package/lib/sync/peer-registry.d.ts +1 -1
  100. package/lib/sync/peer-registry.js +107 -1
  101. package/lib/sync/receive-access-gate.js +24 -1
  102. package/lib/sync/scope-resolver.js +41 -1
  103. package/lib/sync/sync-client.d.ts +3 -3
  104. package/lib/sync/sync-client.js +192 -1
  105. package/lib/sync/sync-manager.d.ts +2 -2
  106. package/lib/sync/sync-manager.js +272 -1
  107. package/lib/types.d.ts +11 -0
  108. package/lib/types.js +2 -1
  109. package/lib/util/mutex.js +10 -1
  110. package/lib/utils.js +21 -1
  111. package/package.json +47 -40
  112. package/lib/groups/broadcast-service.d.ts +0 -263
  113. package/lib/groups/broadcast-service.js +0 -1
  114. package/lib/groups/rejoin-codec.d.ts +0 -14
  115. package/lib/groups/rejoin-codec.js +0 -1
  116. package/lib/groups/wire-frame.d.ts +0 -34
  117. package/lib/groups/wire-frame.js +0 -1
  118. package/lib/hub/circle-catchup-requester.d.ts +0 -58
  119. package/lib/hub/circle-catchup-requester.js +0 -1
  120. package/lib/hub/circle-catchup-responder.d.ts +0 -50
  121. package/lib/hub/circle-catchup-responder.js +0 -1
  122. package/lib/hub/connection-pool.d.ts +0 -43
  123. package/lib/hub/connection-pool.js +0 -1
  124. package/lib/hub/epoch-stale-detector.d.ts +0 -18
  125. package/lib/hub/epoch-stale-detector.js +0 -1
  126. package/lib/hub/forward-remote-broadcast.d.ts +0 -15
  127. package/lib/hub/forward-remote-broadcast.js +0 -1
  128. package/lib/hub/group-channel.d.ts +0 -89
  129. package/lib/hub/group-channel.js +0 -1
  130. package/lib/hub/hub-connection.d.ts +0 -96
  131. package/lib/hub/hub-connection.js +0 -1
  132. package/lib/hub/receive-handler.d.ts +0 -51
  133. package/lib/hub/receive-handler.js +0 -1
  134. package/lib/hub/rejoin-manager.d.ts +0 -78
  135. package/lib/hub/rejoin-manager.js +0 -1
  136. package/lib/hub/rejoin-responder.d.ts +0 -32
  137. package/lib/hub/rejoin-responder.js +0 -1
  138. package/lib/hub/relay-manager.d.ts +0 -142
  139. package/lib/hub/relay-manager.js +0 -1
  140. package/lib/hub/send-handler.d.ts +0 -40
  141. package/lib/hub/send-handler.js +0 -1
  142. package/lib/hub/tunnel-inbox.d.ts +0 -20
  143. package/lib/hub/tunnel-inbox.js +0 -1
  144. package/lib/hub/wait-for-gate.d.ts +0 -14
  145. package/lib/hub/wait-for-gate.js +0 -1
@@ -1,11 +1,14 @@
1
1
  import type { DefaultAccessLevel, GraphInternals } from '@kubun/engine';
2
+ import { HLC } from '@kubun/hlc';
2
3
  import type { Logger } from '@kubun/logger';
3
- import type { CatalogRecord, CircleMemberRecord, CircleRecord } from '@kubun/protocol';
4
- import type { Catalog, GraphStoreAPI } from '@kubun/store-graph';
4
+ import type { CatalogRecord } from '@kubun/protocol';
5
+ import type { DelegationStoreAPI } from '@kubun/store-delegation';
6
+ import type { GraphStoreAPI } from '@kubun/store-graph';
5
7
  import type { Circle, CircleMember, Group, P2PStoreAPI } from '@kubun/store-p2p';
6
8
  import { type ForwardingConfig } from '../sync/forwarder.js';
7
9
  import type { StoreUnreadableMode } from '../types.js';
8
10
  import type { P2PEventEmitter } from './events.js';
11
+ import type { GroupAnchor } from './group-anchor.js';
9
12
  export type MutationApplyEntry = {
10
13
  mutationJWT: string;
11
14
  docID: string;
@@ -14,49 +17,17 @@ export type MutationApplyEntry = {
14
17
  modelID: string;
15
18
  };
16
19
  /**
17
- * A circle row in a catch-up snapshot. Mirrors the wire form of `circle:create`
18
- * (matching {@link CircleRecord} field names) plus `removedAtHLC` so tombstoned
19
- * circles carry their removal HLC. `null` when the circle is active.
20
+ * Optional signed token over a control operation. Its issuer (`iss`) is the
21
+ * authenticated author; receivers verify it and authorize that author as a
22
+ * group admin, dropping the operation when the token is absent or invalid. The
23
+ * signed payload is the whole message minus this field, so the signature covers
24
+ * every operation field, not just the author.
20
25
  */
21
- export type CircleSnapshot = CircleRecord & {
22
- removedAtHLC: string | null;
23
- };
24
- /**
25
- * A circle-member row in a catch-up snapshot. Mirrors the wire form of
26
- * `member:add` (matching {@link CircleMemberRecord} field names) plus
27
- * `removedAtHLC` so tombstoned memberships carry their removal HLC. `null` when
28
- * the membership is active.
29
- */
30
- export type CircleMemberSnapshot = CircleMemberRecord & {
31
- removedAtHLC: string | null;
32
- };
26
+ type ControlAuth = string;
33
27
  export type GroupBroadcastMessage = {
34
- type: 'circle:create';
35
- circle: CircleRecord;
36
- } | {
37
- type: 'circle:update';
38
- circleID: string;
39
- update: {
40
- name?: string;
41
- description?: string;
42
- catalogIDs?: Array<string>;
43
- hlc: string;
44
- };
45
- } | {
46
- type: 'circle:delete';
47
- circleID: string;
48
- hlc: string;
49
- } | {
50
- type: 'member:add';
51
- member: CircleMemberRecord;
52
- } | {
53
- type: 'member:remove';
54
- circleID: string;
55
- memberDID: string;
56
- hlc: string;
57
- } | {
58
28
  type: 'catalog:create';
59
29
  catalog: CatalogRecord;
30
+ auth?: ControlAuth;
60
31
  } | {
61
32
  type: 'catalog:update';
62
33
  catalogID: string;
@@ -66,10 +37,12 @@ export type GroupBroadcastMessage = {
66
37
  filterCriteria?: CatalogRecord['filterCriteria'];
67
38
  hlc: string;
68
39
  };
40
+ auth?: ControlAuth;
69
41
  } | {
70
42
  type: 'catalog:delete';
71
43
  catalogID: string;
72
44
  hlc: string;
45
+ auth?: ControlAuth;
73
46
  } | {
74
47
  /**
75
48
  * Distributes a minted `document/write` capability token to group
@@ -102,13 +75,9 @@ export type GroupBroadcastMessage = {
102
75
  /** HLC stamped by the revoker; mirrors the revoker-side stored row. */
103
76
  hlc: string;
104
77
  } | {
105
- type: 'group:update';
78
+ type: 'ledger:entry';
106
79
  groupID: string;
107
- update: {
108
- name?: string;
109
- description?: string;
110
- hlc: string;
111
- };
80
+ token: string;
112
81
  } | {
113
82
  /**
114
83
  * A member announces a voluntary leave. MLS forbids self-removal, so this
@@ -128,38 +97,60 @@ export type GroupBroadcastMessage = {
128
97
  senderPeerDID: string;
129
98
  } | {
130
99
  /**
131
- * A rejoining peer asks current members for a snapshot of the group's
132
- * circle + circle-member rows so it can recover state it missed while
133
- * absent. `requestID` correlates the eventual batched reply.
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.
134
103
  */
135
- type: 'circle-catchup:request';
104
+ type: 'ledger-catchup:request';
136
105
  requestID: string;
137
106
  } | {
138
107
  /**
139
- * A current member's batched response to a `circle-catchup:request`. One
140
- * frame carrying the group's current circle + circle-member rows,
141
- * INCLUDING tombstoned entries (each row's `removedAtHLC` distinguishes
142
- * active from removed). The requester correlates it via `requestID` and
143
- * applies each row through the existing circle/member broadcast handlers.
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.
144
113
  */
145
- type: 'circle-catchup:reply';
114
+ type: 'ledger-catchup:reply';
146
115
  requestID: string;
147
- circles: Array<CircleSnapshot>;
148
- circleMembers: Array<CircleMemberSnapshot>;
116
+ entries: Array<string>;
149
117
  };
150
- export type AffectedRow = {
151
- kind: 'circle';
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';
152
139
  row: Circle;
153
140
  } | {
154
- kind: 'member';
141
+ entry: 'circle.member';
155
142
  row: CircleMember;
156
143
  } | {
157
- kind: 'catalog';
158
- row: Catalog;
144
+ entry: 'circle.member.removed';
145
+ row: CircleMember;
159
146
  } | {
160
- kind: 'group';
147
+ entry: 'group.settings';
161
148
  row: Group;
162
149
  };
150
+ export type AffectedRow = {
151
+ kind: 'ledger';
152
+ ledger: LedgerAffected;
153
+ };
163
154
  export type ProcessBroadcastResult = {
164
155
  applied: boolean;
165
156
  /**
@@ -187,9 +178,21 @@ export type ProcessBroadcastResult = {
187
178
  * (possibly `0`) on `mutation:apply`.
188
179
  */
189
180
  duplicates?: number;
181
+ /**
182
+ * Set to `'unknown-type'` when the message carried a `type` outside the
183
+ * known discriminated union (version skew). The frame is not applied and is
184
+ * logged; other branches never set this.
185
+ */
186
+ reason?: 'unknown-type';
190
187
  };
191
188
  export type ProcessBroadcastParams = {
192
189
  p2pStore: P2PStoreAPI;
190
+ /**
191
+ * Delegation store for received-grant / received-revocation handling. The
192
+ * delegation token and revocation rows live here (group-agnostic); the p2p
193
+ * store keeps only the `jti` → group edge.
194
+ */
195
+ delegationStore: DelegationStoreAPI;
193
196
  graphStore: GraphStoreAPI;
194
197
  /**
195
198
  * Engine graph internals — required to apply `mutation:apply` entries via
@@ -235,6 +238,21 @@ export type ProcessBroadcastParams = {
235
238
  * Absent in tests that don't exercise the subscription path.
236
239
  */
237
240
  emitter?: P2PEventEmitter;
241
+ /**
242
+ * Device-wide monotonic clock. On a newly-appended `ledger:entry` the
243
+ * receiver merges the entry's HLC into this clock so its own later entries
244
+ * sort strictly after observed ones. Optional: callers that never receive
245
+ * ledger entries (and existing tests) omit it and the merge is skipped.
246
+ */
247
+ hlc?: HLC;
248
+ /**
249
+ * 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`
252
+ * anchor defers projection without dropping the entry. Optional so callers
253
+ * that never project (and existing tests) can omit it.
254
+ */
255
+ getGroupAnchor?: (groupID: string) => Promise<GroupAnchor | null>;
238
256
  /** Optional logger — warns on per-entry apply failures. */
239
257
  logger?: Logger;
240
258
  };
@@ -257,35 +275,31 @@ export type ProcessBroadcastParams = {
257
275
  */
258
276
  export declare function processBroadcast(params: ProcessBroadcastParams, message: GroupBroadcastMessage, groupID: string): Promise<ProcessBroadcastResult>;
259
277
  /**
260
- * Summary of a batched catch-up apply: how many rows mutated local state.
278
+ * Summary of a batched ledger catch-up apply.
261
279
  */
262
- export type ApplyCatchupReplyResult = {
263
- /** Count of circle rows whose synthesized message mutated local state. */
264
- circlesApplied: number;
265
- /** Count of member rows whose synthesized message mutated local state. */
266
- membersApplied: number;
280
+ export type ApplyLedgerCatchupReplyResult = {
281
+ /** Count of entries whose verified token was newly appended to the ledger. */
282
+ applied: number;
283
+ /** Count of entries dropped because their token failed verification. */
284
+ rejected: number;
267
285
  };
268
286
  /**
269
- * Apply a batched catch-up reply row-by-row through the existing per-row
270
- * broadcast handlers. Each snapshot row is turned into the single equivalent
271
- * {@link GroupBroadcastMessage} and fed through {@link processBroadcast}, so all
272
- * conflict resolution (HLC last-write-wins for circle create/update, the
273
- * earliest-wins tombstone guards for circle/member removal) is the exact same
274
- * code the live broadcast path runs. No additional merge logic lives here.
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.
275
292
  *
276
- * A tombstoned circle replays a `circle:create` (so a rejoiner that never saw
277
- * the circle materializes the row) immediately followed by a `circle:delete`
278
- * stamped with the row's `removedAtHLC`. The delete's earliest-wins guard
279
- * converges the tombstone; the create is a no-op when a newer local row already
280
- * exists. An active circle replays a single `circle:create` (the apply path
281
- * routes create -> update for an existing id). Member rows map symmetrically to
282
- * `member:add` / `member:remove` `member:add` materializes the row so the
283
- * subsequent `member:remove` has a row to tombstone.
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.
284
301
  *
285
- * Idempotent and order-independent: re-applying the same batch, or applying its
286
- * rows in any order, lands the same final state because every synthesized
287
- * message runs the same LWW / earliest-wins guards.
302
+ * Never throws out of the per-token loop: one bad token must not abort the batch.
288
303
  */
289
- export declare function applyCatchupReply(params: ProcessBroadcastParams, reply: Extract<GroupBroadcastMessage, {
290
- type: 'circle-catchup:reply';
291
- }>, groupID: string): Promise<ApplyCatchupReplyResult>;
304
+ export declare function applyLedgerCatchupReply(params: ProcessBroadcastParams, reply: BroadcastOf<'ledger-catchup:reply'>, groupID: string): Promise<ApplyLedgerCatchupReplyResult>;
305
+ export {};