@kubun/plugin-p2p 0.10.1 → 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 (201) hide show
  1. package/lib/context/delegation.js +130 -1
  2. package/lib/context/group.js +1646 -1
  3. package/lib/context/hub.js +151 -1
  4. package/lib/context/join.js +115 -1
  5. package/lib/context/peer.d.ts +92 -0
  6. package/lib/context/peer.js +1216 -0
  7. package/lib/context/require-admin.d.ts +57 -0
  8. package/lib/context/require-admin.js +57 -0
  9. package/lib/context/sync.d.ts +12 -0
  10. package/lib/context/sync.js +252 -1
  11. package/lib/context/types.d.ts +58 -22
  12. package/lib/context/types.js +48 -1
  13. package/lib/groups/access-default-token.d.ts +44 -0
  14. package/lib/groups/access-default-token.js +103 -0
  15. package/lib/groups/admin-roster.d.ts +55 -0
  16. package/lib/groups/admin-roster.js +69 -0
  17. package/lib/groups/anchor-store.d.ts +15 -0
  18. package/lib/groups/anchor-store.js +37 -0
  19. package/lib/groups/app-cursor-store.d.ts +17 -0
  20. package/lib/groups/app-cursor-store.js +34 -0
  21. package/lib/groups/broadcast-codec.js +6 -1
  22. package/lib/groups/broadcast.d.ts +238 -106
  23. package/lib/groups/broadcast.js +702 -1
  24. package/lib/groups/catalog-token.d.ts +50 -0
  25. package/lib/groups/catalog-token.js +96 -0
  26. package/lib/groups/circle-projection.d.ts +90 -0
  27. package/lib/groups/circle-projection.js +202 -0
  28. package/lib/groups/circle-reducers.d.ts +139 -0
  29. package/lib/groups/circle-reducers.js +128 -0
  30. package/lib/groups/commit-adoption.d.ts +77 -0
  31. package/lib/groups/commit-adoption.js +108 -0
  32. package/lib/groups/commit-journal.d.ts +14 -0
  33. package/lib/groups/commit-journal.js +65 -0
  34. package/lib/groups/control-request.d.ts +66 -0
  35. package/lib/groups/control-request.js +101 -0
  36. package/lib/groups/events.d.ts +19 -2
  37. package/lib/groups/events.js +10 -1
  38. package/lib/groups/group-anchor.d.ts +24 -0
  39. package/lib/groups/group-anchor.js +22 -0
  40. package/lib/groups/group-crypto.d.ts +42 -0
  41. package/lib/groups/group-crypto.js +159 -0
  42. package/lib/groups/group-handle-registry.d.ts +138 -9
  43. package/lib/groups/group-handle-registry.js +533 -1
  44. package/lib/groups/group-handlers.d.ts +30 -0
  45. package/lib/groups/group-handlers.js +218 -0
  46. package/lib/groups/group-health-monitor.d.ts +11 -0
  47. package/lib/groups/group-health-monitor.js +139 -1
  48. package/lib/groups/group-mls.d.ts +66 -0
  49. package/lib/groups/group-mls.js +696 -0
  50. package/lib/groups/group-peer-manager.d.ts +301 -0
  51. package/lib/groups/group-peer-manager.js +1336 -0
  52. package/lib/groups/group-protocols.d.ts +754 -0
  53. package/lib/groups/group-protocols.js +448 -0
  54. package/lib/groups/invite-payload.d.ts +49 -1
  55. package/lib/groups/invite-payload.js +26 -1
  56. package/lib/groups/join-request-identity.d.ts +21 -0
  57. package/lib/groups/join-request-identity.js +43 -0
  58. package/lib/groups/join-utils.d.ts +61 -0
  59. package/lib/groups/join-utils.js +366 -1
  60. package/lib/groups/ledger-affected-events.d.ts +20 -0
  61. package/lib/groups/ledger-affected-events.js +85 -0
  62. package/lib/groups/ledger-commit-fold.d.ts +107 -0
  63. package/lib/groups/ledger-commit-fold.js +167 -0
  64. package/lib/groups/ledger-ingest.d.ts +124 -0
  65. package/lib/groups/ledger-ingest.js +212 -0
  66. package/lib/groups/ledger.d.ts +44 -0
  67. package/lib/groups/ledger.js +30 -0
  68. package/lib/groups/manager.d.ts +217 -85
  69. package/lib/groups/manager.js +798 -1
  70. package/lib/groups/mls-codec.d.ts +28 -7
  71. package/lib/groups/mls-codec.js +33 -1
  72. package/lib/groups/mls-encryptor.d.ts +2 -2
  73. package/lib/groups/mls-encryptor.js +33 -1
  74. package/lib/groups/mls-group-handle.d.ts +37 -2
  75. package/lib/groups/mls-group-handle.js +18 -1
  76. package/lib/groups/mls-json.js +17 -1
  77. package/lib/groups/mls-receive-errors.d.ts +27 -0
  78. package/lib/groups/mls-receive-errors.js +39 -0
  79. package/lib/groups/mls-state.d.ts +6 -4
  80. package/lib/groups/mls-state.js +79 -1
  81. package/lib/groups/peer-presence.d.ts +90 -0
  82. package/lib/groups/peer-presence.js +295 -0
  83. package/lib/groups/peer-selection.d.ts +37 -0
  84. package/lib/groups/peer-selection.js +72 -0
  85. package/lib/groups/roster-projection.d.ts +53 -0
  86. package/lib/groups/roster-projection.js +65 -0
  87. package/lib/groups/store-received-grant.d.ts +7 -3
  88. package/lib/groups/store-received-grant.js +161 -1
  89. package/lib/groups/store-received-revocation.d.ts +19 -16
  90. package/lib/groups/store-received-revocation.js +83 -1
  91. package/lib/hub/ack-backoff.d.ts +22 -0
  92. package/lib/hub/ack-backoff.js +88 -0
  93. package/lib/hub/http-client.d.ts +18 -10
  94. package/lib/hub/http-client.js +15 -1
  95. package/lib/hub/hub-like.d.ts +75 -0
  96. package/lib/hub/hub-like.js +1022 -0
  97. package/lib/hub/loopback-log-hub.d.ts +20 -0
  98. package/lib/hub/loopback-log-hub.js +178 -0
  99. package/lib/hub/manager.d.ts +25 -40
  100. package/lib/hub/manager.js +155 -1
  101. package/lib/hub/peer-scoped-hub-view.d.ts +35 -0
  102. package/lib/hub/peer-scoped-hub-view.js +92 -0
  103. package/lib/hub/sender-scoped-hub-view.d.ts +41 -0
  104. package/lib/hub/sender-scoped-hub-view.js +59 -0
  105. package/lib/hub/server-did.d.ts +67 -0
  106. package/lib/hub/server-did.js +131 -0
  107. package/lib/hub/wiring.d.ts +77 -51
  108. package/lib/hub/wiring.js +189 -1
  109. package/lib/index.d.ts +58 -22
  110. package/lib/index.js +563 -1
  111. package/lib/peer/connection-registry.d.ts +45 -0
  112. package/lib/peer/connection-registry.js +52 -0
  113. package/lib/peer/handlers.d.ts +69 -0
  114. package/lib/peer/handlers.js +110 -0
  115. package/lib/protocol.d.ts +214 -1
  116. package/lib/protocol.js +519 -1
  117. package/lib/schema.d.ts +3 -1
  118. package/lib/schema.js +1150 -39
  119. package/lib/sync/access-default-sender.d.ts +23 -0
  120. package/lib/sync/access-default-sender.js +80 -0
  121. package/lib/sync/authorize.d.ts +58 -4
  122. package/lib/sync/authorize.js +114 -1
  123. package/lib/sync/broadcast-queue.d.ts +3 -4
  124. package/lib/sync/broadcast-queue.js +182 -1
  125. package/lib/sync/broadcast-sender.js +179 -1
  126. package/lib/sync/catalog-scope.d.ts +8 -7
  127. package/lib/sync/catalog-scope.js +84 -1
  128. package/lib/sync/errors.d.ts +12 -2
  129. package/lib/sync/errors.js +29 -1
  130. package/lib/sync/forwarder.d.ts +13 -19
  131. package/lib/sync/forwarder.js +271 -1
  132. package/lib/sync/handlers.js +200 -1
  133. package/lib/sync/hub-tunnel-sync-listener.d.ts +19 -5
  134. package/lib/sync/hub-tunnel-sync-listener.js +261 -1
  135. package/lib/sync/hub-tunnel-sync-provider.d.ts +34 -14
  136. package/lib/sync/hub-tunnel-sync-provider.js +113 -1
  137. package/lib/sync/merkle-apply.d.ts +5 -0
  138. package/lib/sync/merkle-apply.js +194 -1
  139. package/lib/sync/merkle-channel.d.ts +11 -3
  140. package/lib/sync/merkle-channel.js +0 -0
  141. package/lib/sync/merkle-tree.js +120 -1
  142. package/lib/sync/peer-registry.d.ts +15 -11
  143. package/lib/sync/peer-registry.js +82 -1
  144. package/lib/sync/receive-access-gate.js +24 -1
  145. package/lib/sync/scope-resolver.js +41 -1
  146. package/lib/sync/sync-client.d.ts +14 -4
  147. package/lib/sync/sync-client.js +254 -1
  148. package/lib/sync/sync-manager.d.ts +24 -6
  149. package/lib/sync/sync-manager.js +301 -1
  150. package/lib/sync/tunnel-listeners.d.ts +52 -0
  151. package/lib/sync/tunnel-listeners.js +175 -0
  152. package/lib/sync/tunnel-observability.d.ts +25 -0
  153. package/lib/sync/tunnel-observability.js +63 -0
  154. package/lib/sync/tunnel-topics.d.ts +20 -0
  155. package/lib/sync/tunnel-topics.js +28 -0
  156. package/lib/types.d.ts +704 -22
  157. package/lib/types.js +1 -1
  158. package/lib/util/handler-error.d.ts +16 -0
  159. package/lib/util/handler-error.js +64 -0
  160. package/lib/util/mutex.d.ts +14 -1
  161. package/lib/util/mutex.js +44 -1
  162. package/lib/utils.js +21 -1
  163. package/package.json +58 -47
  164. package/lib/groups/broadcast-service.d.ts +0 -263
  165. package/lib/groups/broadcast-service.js +0 -1
  166. package/lib/groups/rejoin-codec.d.ts +0 -14
  167. package/lib/groups/rejoin-codec.js +0 -1
  168. package/lib/groups/wire-frame.d.ts +0 -34
  169. package/lib/groups/wire-frame.js +0 -1
  170. package/lib/hub/circle-catchup-requester.d.ts +0 -58
  171. package/lib/hub/circle-catchup-requester.js +0 -1
  172. package/lib/hub/circle-catchup-responder.d.ts +0 -50
  173. package/lib/hub/circle-catchup-responder.js +0 -1
  174. package/lib/hub/connection-pool.d.ts +0 -43
  175. package/lib/hub/connection-pool.js +0 -1
  176. package/lib/hub/did-observing-transport.d.ts +0 -64
  177. package/lib/hub/did-observing-transport.js +0 -1
  178. package/lib/hub/epoch-stale-detector.d.ts +0 -18
  179. package/lib/hub/epoch-stale-detector.js +0 -1
  180. package/lib/hub/errors.d.ts +0 -30
  181. package/lib/hub/errors.js +0 -1
  182. package/lib/hub/forward-remote-broadcast.d.ts +0 -15
  183. package/lib/hub/forward-remote-broadcast.js +0 -1
  184. package/lib/hub/group-channel.d.ts +0 -89
  185. package/lib/hub/group-channel.js +0 -1
  186. package/lib/hub/hub-connection.d.ts +0 -96
  187. package/lib/hub/hub-connection.js +0 -1
  188. package/lib/hub/receive-handler.d.ts +0 -51
  189. package/lib/hub/receive-handler.js +0 -1
  190. package/lib/hub/rejoin-manager.d.ts +0 -78
  191. package/lib/hub/rejoin-manager.js +0 -1
  192. package/lib/hub/rejoin-responder.d.ts +0 -32
  193. package/lib/hub/rejoin-responder.js +0 -1
  194. package/lib/hub/relay-manager.d.ts +0 -142
  195. package/lib/hub/relay-manager.js +0 -1
  196. package/lib/hub/send-handler.d.ts +0 -40
  197. package/lib/hub/send-handler.js +0 -1
  198. package/lib/hub/tunnel-inbox.d.ts +0 -20
  199. package/lib/hub/tunnel-inbox.js +0 -1
  200. package/lib/hub/wait-for-gate.d.ts +0 -14
  201. package/lib/hub/wait-for-gate.js +0 -1
@@ -1,11 +1,18 @@
1
+ import type { OwnIdentity } from '@kokuin/token';
1
2
  import type { DefaultAccessLevel, GraphInternals } from '@kubun/engine';
3
+ import { HLC } from '@kubun/hlc';
2
4
  import type { Logger } from '@kubun/logger';
3
- import type { CatalogRecord, CircleMemberRecord, CircleRecord } from '@kubun/protocol';
4
- import type { Catalog, GraphStoreAPI } from '@kubun/store-graph';
5
- import type { Circle, CircleMember, Group, P2PStoreAPI } from '@kubun/store-p2p';
5
+ import type { CatalogRecord } from '@kubun/protocol';
6
+ import type { DelegationStoreAPI } from '@kubun/store-delegation';
7
+ import type { GraphStoreAPI } from '@kubun/store-graph';
8
+ import type { P2PStoreAPI } from '@kubun/store-p2p';
9
+ import type { GroupAnchor, LedgerLogEntry, RosterState } from '@kumiai/mls';
6
10
  import { type ForwardingConfig } from '../sync/forwarder.js';
7
11
  import type { StoreUnreadableMode } from '../types.js';
12
+ import { type AccessDefaultRule } from './access-default-token.js';
8
13
  import type { P2PEventEmitter } from './events.js';
14
+ import type { PeerAvailability, PeerCapability } from './group-protocols.js';
15
+ import { type AffectedRow, type LedgerIngestParams } from './ledger-ingest.js';
9
16
  export type MutationApplyEntry = {
10
17
  mutationJWT: string;
11
18
  docID: string;
@@ -14,49 +21,17 @@ export type MutationApplyEntry = {
14
21
  modelID: string;
15
22
  };
16
23
  /**
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.
24
+ * Optional signed token over a control operation. Its issuer (`iss`) is the
25
+ * authenticated author; receivers verify it and authorize that author as a
26
+ * group admin, dropping the operation when the token is absent or invalid. The
27
+ * signed payload is the whole message minus this field, so the signature covers
28
+ * every operation field, not just the author.
20
29
  */
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
- };
30
+ type ControlAuth = string;
33
31
  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
32
  type: 'catalog:create';
59
33
  catalog: CatalogRecord;
34
+ auth?: ControlAuth;
60
35
  } | {
61
36
  type: 'catalog:update';
62
37
  catalogID: string;
@@ -66,10 +41,45 @@ export type GroupBroadcastMessage = {
66
41
  filterCriteria?: CatalogRecord['filterCriteria'];
67
42
  hlc: string;
68
43
  };
44
+ auth?: ControlAuth;
69
45
  } | {
70
46
  type: 'catalog:delete';
71
47
  catalogID: string;
72
48
  hlc: string;
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;
73
83
  } | {
74
84
  /**
75
85
  * Distributes a minted `document/write` capability token to group
@@ -101,14 +111,6 @@ export type GroupBroadcastMessage = {
101
111
  groupID: string;
102
112
  /** HLC stamped by the revoker; mirrors the revoker-side stored row. */
103
113
  hlc: string;
104
- } | {
105
- type: 'group:update';
106
- groupID: string;
107
- update: {
108
- name?: string;
109
- description?: string;
110
- hlc: string;
111
- };
112
114
  } | {
113
115
  /**
114
116
  * A member announces a voluntary leave. MLS forbids self-removal, so this
@@ -124,42 +126,35 @@ export type GroupBroadcastMessage = {
124
126
  } | {
125
127
  type: 'mutation:apply';
126
128
  entries: Array<MutationApplyEntry>;
127
- /** DID of the peer that authored this broadcast — used for loopback drop. */
128
- senderPeerDID: string;
129
- } | {
130
129
  /**
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.
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.
134
137
  */
135
- type: 'circle-catchup:request';
136
- requestID: string;
138
+ senderPeerDID: string | undefined;
137
139
  } | {
138
140
  /**
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.
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.
144
149
  */
145
- type: 'circle-catchup:reply';
146
- requestID: string;
147
- circles: Array<CircleSnapshot>;
148
- circleMembers: Array<CircleMemberSnapshot>;
149
- };
150
- export type AffectedRow = {
151
- kind: 'circle';
152
- row: Circle;
153
- } | {
154
- kind: 'member';
155
- row: CircleMember;
156
- } | {
157
- kind: 'catalog';
158
- row: Catalog;
159
- } | {
160
- kind: 'group';
161
- row: Group;
150
+ type: 'peer:announce';
151
+ label: string;
152
+ availability: PeerAvailability;
153
+ capabilities: Array<PeerCapability>;
154
+ hlc: string;
155
+ announcerDID: string | undefined;
162
156
  };
157
+ export type { AffectedRow, LedgerAffected } from './ledger-ingest.js';
163
158
  export type ProcessBroadcastResult = {
164
159
  applied: boolean;
165
160
  /**
@@ -187,9 +182,23 @@ export type ProcessBroadcastResult = {
187
182
  * (possibly `0`) on `mutation:apply`.
188
183
  */
189
184
  duplicates?: number;
185
+ /**
186
+ * Set to `'unknown-type'` when the message carried a `type` outside the
187
+ * known discriminated union (version skew). The frame is not applied and is
188
+ * logged; other branches never set this.
189
+ */
190
+ reason?: 'unknown-type';
190
191
  };
191
192
  export type ProcessBroadcastParams = {
192
193
  p2pStore: P2PStoreAPI;
194
+ /** See {@link ApplyLedgerEntriesParams.withProjectionTransaction}. */
195
+ withProjectionTransaction?: ApplyLedgerEntriesParams['withProjectionTransaction'];
196
+ /**
197
+ * Delegation store for received-grant / received-revocation handling. The
198
+ * delegation token and revocation rows live here (group-agnostic); the p2p
199
+ * store keeps only the `jti` → group edge.
200
+ */
201
+ delegationStore: DelegationStoreAPI;
193
202
  graphStore: GraphStoreAPI;
194
203
  /**
195
204
  * Engine graph internals — required to apply `mutation:apply` entries via
@@ -200,6 +209,12 @@ export type ProcessBroadcastParams = {
200
209
  graph: GraphInternals;
201
210
  /** Self DID for `mutation:apply` loopback detection. */
202
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;
203
218
  /**
204
219
  * Receiver storage mode for incoming `mutation:apply` entries.
205
220
  * - `'persist'` (default) — every entry the engine accepts is persisted;
@@ -235,6 +250,37 @@ export type ProcessBroadcastParams = {
235
250
  * Absent in tests that don't exercise the subscription path.
236
251
  */
237
252
  emitter?: P2PEventEmitter;
253
+ /**
254
+ * Device-wide monotonic clock. On a newly-appended control-ledger entry the
255
+ * receiver merges the entry's HLC into this clock so its own later entries
256
+ * sort strictly after observed ones. Optional: callers that never receive
257
+ * ledger entries (and existing tests) omit it and the merge is skipped.
258
+ */
259
+ hlc?: HLC;
260
+ /**
261
+ * Resolve a group's genesis anchor, or `null` when none is available yet. A
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`
264
+ * anchor defers projection without dropping the entry. Optional so callers
265
+ * that never project (and existing tests) can omit it.
266
+ */
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;
238
284
  /** Optional logger — warns on per-entry apply failures. */
239
285
  logger?: Logger;
240
286
  };
@@ -257,35 +303,121 @@ export type ProcessBroadcastParams = {
257
303
  */
258
304
  export declare function processBroadcast(params: ProcessBroadcastParams, message: GroupBroadcastMessage, groupID: string): Promise<ProcessBroadcastResult>;
259
305
  /**
260
- * Summary of a batched catch-up apply: how many rows mutated local state.
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.
261
311
  */
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;
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. */
347
+ applied: number;
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
+ */
355
+ rejected: number;
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>;
267
411
  };
268
412
  /**
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.
413
+ * Adopt a whole ledger gathered from one untrusted responder.
275
414
  *
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.
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.
284
420
  *
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.
421
+ * Throws whatever the install throws a rejected ledger changes nothing.
288
422
  */
289
- export declare function applyCatchupReply(params: ProcessBroadcastParams, reply: Extract<GroupBroadcastMessage, {
290
- type: 'circle-catchup:reply';
291
- }>, groupID: string): Promise<ApplyCatchupReplyResult>;
423
+ export declare function bootstrapGroupLedger(params: BootstrapGroupLedgerParams, tokens: Array<string>, groupID: string): Promise<ApplyLedgerCatchupReplyResult>;