@kubun/plugin-p2p 0.10.0 → 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.
- package/lib/context/delegation.js +130 -1
- package/lib/context/group.js +1319 -1
- package/lib/context/hub.js +150 -1
- package/lib/context/join.js +80 -1
- package/lib/context/sync.js +19 -1
- package/lib/context/types.d.ts +12 -3
- package/lib/context/types.js +28 -1
- package/lib/groups/admin-roster.d.ts +130 -0
- package/lib/groups/admin-roster.js +184 -0
- package/lib/groups/broadcast-codec.js +6 -1
- package/lib/groups/broadcast.d.ts +122 -38
- package/lib/groups/broadcast.js +627 -1
- package/lib/groups/circle-projection.d.ts +122 -0
- package/lib/groups/circle-projection.js +288 -0
- package/lib/groups/circle-reducers.d.ts +178 -0
- package/lib/groups/circle-reducers.js +198 -0
- package/lib/groups/events.d.ts +7 -2
- package/lib/groups/events.js +10 -1
- package/lib/groups/group-anchor.d.ts +63 -0
- package/lib/groups/group-anchor.js +107 -0
- package/lib/groups/group-crypto.d.ts +19 -0
- package/lib/groups/group-crypto.js +85 -0
- package/lib/groups/group-handle-registry.d.ts +69 -2
- package/lib/groups/group-handle-registry.js +243 -1
- package/lib/groups/group-handlers.d.ts +29 -0
- package/lib/groups/group-handlers.js +92 -0
- package/lib/groups/group-health-monitor.d.ts +56 -0
- package/lib/groups/group-health-monitor.js +139 -0
- package/lib/groups/group-mls.d.ts +35 -0
- package/lib/groups/group-mls.js +164 -0
- package/lib/groups/group-peer-manager.d.ts +108 -0
- package/lib/groups/group-peer-manager.js +344 -0
- package/lib/groups/group-protocols.d.ts +297 -0
- package/lib/groups/group-protocols.js +174 -0
- package/lib/groups/invite-payload.d.ts +1 -1
- package/lib/groups/invite-payload.js +26 -1
- package/lib/groups/join-utils.d.ts +1 -0
- package/lib/groups/join-utils.js +104 -1
- package/lib/groups/ledger-affected-events.d.ts +15 -0
- package/lib/groups/ledger-affected-events.js +96 -0
- package/lib/groups/ledger-entry.d.ts +37 -0
- package/lib/groups/ledger-entry.js +56 -0
- package/lib/groups/ledger-fold.d.ts +56 -0
- package/lib/groups/ledger-fold.js +53 -0
- package/lib/groups/manager.d.ts +29 -71
- package/lib/groups/manager.js +281 -1
- package/lib/groups/mls-codec.d.ts +1 -1
- package/lib/groups/mls-codec.js +18 -1
- package/lib/groups/mls-encryptor.d.ts +2 -2
- package/lib/groups/mls-encryptor.js +30 -1
- package/lib/groups/mls-group-handle.d.ts +1 -1
- package/lib/groups/mls-group-handle.js +10 -1
- package/lib/groups/mls-json.js +17 -1
- package/lib/groups/mls-receive-errors.d.ts +29 -0
- package/lib/groups/mls-receive-errors.js +42 -0
- package/lib/groups/mls-state.d.ts +1 -1
- package/lib/groups/mls-state.js +56 -1
- package/lib/groups/store-received-grant.d.ts +4 -1
- package/lib/groups/store-received-grant.js +116 -1
- package/lib/groups/store-received-revocation.d.ts +3 -3
- package/lib/groups/store-received-revocation.js +82 -1
- package/lib/hub/ack-backoff.d.ts +22 -0
- package/lib/hub/ack-backoff.js +88 -0
- package/lib/hub/did-observing-transport.d.ts +13 -2
- package/lib/hub/did-observing-transport.js +126 -1
- package/lib/hub/errors.d.ts +4 -8
- package/lib/hub/errors.js +33 -1
- package/lib/hub/http-client.d.ts +28 -3
- package/lib/hub/http-client.js +32 -1
- package/lib/hub/hub-like.d.ts +24 -0
- package/lib/hub/hub-like.js +535 -0
- package/lib/hub/manager.d.ts +5 -4
- package/lib/hub/manager.js +198 -1
- package/lib/hub/wiring.d.ts +28 -41
- package/lib/hub/wiring.js +132 -1
- package/lib/index.d.ts +29 -13
- package/lib/index.js +392 -1
- package/lib/protocol.d.ts +8 -1
- package/lib/protocol.js +251 -1
- package/lib/schema.d.ts +2 -1
- package/lib/schema.js +471 -2
- package/lib/sync/authorize.d.ts +25 -0
- package/lib/sync/authorize.js +73 -0
- package/lib/sync/broadcast-queue.js +183 -1
- package/lib/sync/broadcast-sender.js +179 -1
- package/lib/sync/catalog-scope.js +68 -1
- package/lib/sync/errors.d.ts +12 -0
- package/lib/sync/errors.js +19 -0
- package/lib/sync/forwarder.js +277 -1
- package/lib/sync/handlers.d.ts +3 -22
- package/lib/sync/handlers.js +160 -1
- package/lib/sync/hub-tunnel-sync-listener.d.ts +9 -3
- package/lib/sync/hub-tunnel-sync-listener.js +172 -1
- package/lib/sync/hub-tunnel-sync-provider.d.ts +14 -14
- package/lib/sync/hub-tunnel-sync-provider.js +64 -1
- package/lib/sync/merkle-apply.d.ts +5 -0
- package/lib/sync/merkle-apply.js +194 -1
- package/lib/sync/merkle-channel.d.ts +19 -0
- package/lib/sync/merkle-channel.js +182 -1
- package/lib/sync/merkle-tree.js +120 -1
- package/lib/sync/peer-registry.d.ts +8 -1
- package/lib/sync/peer-registry.js +107 -1
- package/lib/sync/receive-access-gate.js +24 -1
- package/lib/sync/scope-resolver.js +41 -1
- package/lib/sync/sync-client.d.ts +5 -3
- package/lib/sync/sync-client.js +192 -1
- package/lib/sync/sync-manager.d.ts +2 -2
- package/lib/sync/sync-manager.js +272 -1
- package/lib/types.d.ts +41 -0
- package/lib/types.js +2 -1
- package/lib/util/mutex.js +10 -1
- package/lib/utils.d.ts +5 -0
- package/lib/utils.js +21 -0
- package/package.json +48 -41
- package/lib/groups/broadcast-service.d.ts +0 -189
- package/lib/groups/broadcast-service.js +0 -1
- package/lib/groups/wire-frame.d.ts +0 -32
- package/lib/groups/wire-frame.js +0 -1
- package/lib/hub/connection-pool.d.ts +0 -43
- package/lib/hub/connection-pool.js +0 -1
- package/lib/hub/forward-remote-broadcast.d.ts +0 -15
- package/lib/hub/forward-remote-broadcast.js +0 -1
- package/lib/hub/group-channel.d.ts +0 -67
- package/lib/hub/group-channel.js +0 -1
- package/lib/hub/hub-connection.d.ts +0 -96
- package/lib/hub/hub-connection.js +0 -1
- package/lib/hub/receive-handler.d.ts +0 -33
- package/lib/hub/receive-handler.js +0 -1
- package/lib/hub/relay-manager.d.ts +0 -127
- package/lib/hub/relay-manager.js +0 -1
- package/lib/hub/send-handler.d.ts +0 -40
- package/lib/hub/send-handler.js +0 -1
- package/lib/hub/tunnel-inbox.d.ts +0 -20
- package/lib/hub/tunnel-inbox.js +0 -1
- package/lib/hub/wait-for-gate.d.ts +0 -14
- package/lib/hub/wait-for-gate.js +0 -1
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import type { FromSchema } from '@sozai/schema';
|
|
2
|
+
/**
|
|
3
|
+
* App-lane group-rpc protocols carried over a group's rotating broadcast topics.
|
|
4
|
+
* Each peer is group-scoped (its topics encode the group), so payloads omit
|
|
5
|
+
* `groupID` — the handler already knows which group it serves. Loopback drop is
|
|
6
|
+
* automatic: the hub fans a publish to subscribers minus the sender, so a peer
|
|
7
|
+
* never receives its own broadcast.
|
|
8
|
+
*
|
|
9
|
+
* Procedure kinds map to the broadcast surface: `event` is fire-and-forget
|
|
10
|
+
* (`dispatch`); `request` is correlated (`request` for the first reply,
|
|
11
|
+
* `gather` for every reply within a window).
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Control plane: membership ledger, delegation, and advisory leave. All are
|
|
15
|
+
* self-authenticating at the payload level (signed tokens / HLC-stamped
|
|
16
|
+
* records), so the lane provides ordering + delivery, not authorization.
|
|
17
|
+
*/
|
|
18
|
+
export declare const controlProtocol: {
|
|
19
|
+
readonly 'control/ledgerEntry': {
|
|
20
|
+
readonly type: "event";
|
|
21
|
+
readonly description: "A self-signed control-ledger entry appended by its author.";
|
|
22
|
+
readonly data: {
|
|
23
|
+
readonly type: "object";
|
|
24
|
+
readonly properties: {
|
|
25
|
+
readonly token: {
|
|
26
|
+
readonly type: "string";
|
|
27
|
+
readonly description: "Signed control-ledger entry token.";
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
readonly required: readonly ["token"];
|
|
31
|
+
readonly additionalProperties: false;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
readonly 'control/ledgerCatchup': {
|
|
35
|
+
readonly type: "request";
|
|
36
|
+
readonly description: "Ask current members for the group control ledger to backfill missed entries. Gathered: each member replies with its entries; the requester re-verifies every token.";
|
|
37
|
+
readonly param: {
|
|
38
|
+
readonly type: "object";
|
|
39
|
+
readonly properties: {};
|
|
40
|
+
readonly additionalProperties: false;
|
|
41
|
+
};
|
|
42
|
+
readonly result: {
|
|
43
|
+
readonly type: "object";
|
|
44
|
+
readonly properties: {
|
|
45
|
+
readonly entries: {
|
|
46
|
+
readonly type: "array";
|
|
47
|
+
readonly items: {
|
|
48
|
+
readonly type: "string";
|
|
49
|
+
};
|
|
50
|
+
readonly description: "Signed control-ledger entry tokens.";
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
readonly required: readonly ["entries"];
|
|
54
|
+
readonly additionalProperties: false;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
readonly 'control/delegationShare': {
|
|
58
|
+
readonly type: "event";
|
|
59
|
+
readonly description: "Distribute a minted document/write capability token to group co-members.";
|
|
60
|
+
readonly data: {
|
|
61
|
+
readonly type: "object";
|
|
62
|
+
readonly properties: {
|
|
63
|
+
readonly token: {
|
|
64
|
+
readonly type: "string";
|
|
65
|
+
readonly description: "Stringified capability JWT.";
|
|
66
|
+
};
|
|
67
|
+
readonly hlc: {
|
|
68
|
+
readonly type: "string";
|
|
69
|
+
readonly description: "Grantor-stamped HLC, mirroring the issued row.";
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
readonly required: readonly ["token", "hlc"];
|
|
73
|
+
readonly additionalProperties: false;
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
readonly 'control/delegationRevoke': {
|
|
77
|
+
readonly type: "event";
|
|
78
|
+
readonly description: "Distribute a signed revocation record for a previously minted capability.";
|
|
79
|
+
readonly data: {
|
|
80
|
+
readonly type: "object";
|
|
81
|
+
readonly properties: {
|
|
82
|
+
readonly token: {
|
|
83
|
+
readonly type: "string";
|
|
84
|
+
readonly description: "Stringified signed revocation JWT.";
|
|
85
|
+
};
|
|
86
|
+
readonly hlc: {
|
|
87
|
+
readonly type: "string";
|
|
88
|
+
readonly description: "Revoker-stamped HLC, mirroring the stored row.";
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
readonly required: readonly ["token", "hlc"];
|
|
92
|
+
readonly additionalProperties: false;
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
readonly 'control/groupLeaveRequest': {
|
|
96
|
+
readonly type: "event";
|
|
97
|
+
readonly description: "Advisory voluntary-leave notice. Not authoritative — an admin must issue a real removeMember; receivers surface it as an event and never tombstone on it.";
|
|
98
|
+
readonly data: {
|
|
99
|
+
readonly type: "object";
|
|
100
|
+
readonly properties: {
|
|
101
|
+
readonly memberDID: {
|
|
102
|
+
readonly type: "string";
|
|
103
|
+
readonly description: "DID of the member announcing departure.";
|
|
104
|
+
};
|
|
105
|
+
readonly hlc: {
|
|
106
|
+
readonly type: "string";
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
readonly required: readonly ["memberDID", "hlc"];
|
|
110
|
+
readonly additionalProperties: false;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Sync plane: document mutation broadcasts. Each entry carries a signed mutation
|
|
116
|
+
* JWT the receiver verifies before applying, so the lane is transport only.
|
|
117
|
+
*/
|
|
118
|
+
export declare const syncProtocol: {
|
|
119
|
+
readonly 'sync/mutationApply': {
|
|
120
|
+
readonly type: "event";
|
|
121
|
+
readonly description: "Broadcast a batch of signed document mutations to group members.";
|
|
122
|
+
readonly data: {
|
|
123
|
+
readonly type: "object";
|
|
124
|
+
readonly properties: {
|
|
125
|
+
readonly entries: {
|
|
126
|
+
readonly type: "array";
|
|
127
|
+
readonly items: {
|
|
128
|
+
readonly type: "object";
|
|
129
|
+
readonly properties: {
|
|
130
|
+
readonly mutationJWT: {
|
|
131
|
+
readonly type: "string";
|
|
132
|
+
};
|
|
133
|
+
readonly docID: {
|
|
134
|
+
readonly type: "string";
|
|
135
|
+
};
|
|
136
|
+
readonly version: {
|
|
137
|
+
readonly type: "string";
|
|
138
|
+
readonly description: "HLC string identifying this version.";
|
|
139
|
+
};
|
|
140
|
+
readonly modelID: {
|
|
141
|
+
readonly type: "string";
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
readonly required: readonly ["mutationJWT", "docID", "version", "modelID"];
|
|
145
|
+
readonly additionalProperties: false;
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
readonly required: readonly ["entries"];
|
|
150
|
+
readonly additionalProperties: false;
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
export type ControlProtocol = typeof controlProtocol;
|
|
155
|
+
export type SyncProtocol = typeof syncProtocol;
|
|
156
|
+
export type LedgerEntryData = FromSchema<(typeof controlProtocol)['control/ledgerEntry']['data']>;
|
|
157
|
+
export type LedgerCatchupResult = FromSchema<(typeof controlProtocol)['control/ledgerCatchup']['result']>;
|
|
158
|
+
export type DelegationShareData = FromSchema<(typeof controlProtocol)['control/delegationShare']['data']>;
|
|
159
|
+
export type DelegationRevokeData = FromSchema<(typeof controlProtocol)['control/delegationRevoke']['data']>;
|
|
160
|
+
export type GroupLeaveRequestData = FromSchema<(typeof controlProtocol)['control/groupLeaveRequest']['data']>;
|
|
161
|
+
export type MutationApplyData = FromSchema<(typeof syncProtocol)['sync/mutationApply']['data']>;
|
|
162
|
+
/** The two app-lane protocols, keyed for `createGroupPeer({ protocols })`. */
|
|
163
|
+
export declare const groupProtocols: {
|
|
164
|
+
control: {
|
|
165
|
+
readonly 'control/ledgerEntry': {
|
|
166
|
+
readonly type: "event";
|
|
167
|
+
readonly description: "A self-signed control-ledger entry appended by its author.";
|
|
168
|
+
readonly data: {
|
|
169
|
+
readonly type: "object";
|
|
170
|
+
readonly properties: {
|
|
171
|
+
readonly token: {
|
|
172
|
+
readonly type: "string";
|
|
173
|
+
readonly description: "Signed control-ledger entry token.";
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
readonly required: readonly ["token"];
|
|
177
|
+
readonly additionalProperties: false;
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
readonly 'control/ledgerCatchup': {
|
|
181
|
+
readonly type: "request";
|
|
182
|
+
readonly description: "Ask current members for the group control ledger to backfill missed entries. Gathered: each member replies with its entries; the requester re-verifies every token.";
|
|
183
|
+
readonly param: {
|
|
184
|
+
readonly type: "object";
|
|
185
|
+
readonly properties: {};
|
|
186
|
+
readonly additionalProperties: false;
|
|
187
|
+
};
|
|
188
|
+
readonly result: {
|
|
189
|
+
readonly type: "object";
|
|
190
|
+
readonly properties: {
|
|
191
|
+
readonly entries: {
|
|
192
|
+
readonly type: "array";
|
|
193
|
+
readonly items: {
|
|
194
|
+
readonly type: "string";
|
|
195
|
+
};
|
|
196
|
+
readonly description: "Signed control-ledger entry tokens.";
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
readonly required: readonly ["entries"];
|
|
200
|
+
readonly additionalProperties: false;
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
readonly 'control/delegationShare': {
|
|
204
|
+
readonly type: "event";
|
|
205
|
+
readonly description: "Distribute a minted document/write capability token to group co-members.";
|
|
206
|
+
readonly data: {
|
|
207
|
+
readonly type: "object";
|
|
208
|
+
readonly properties: {
|
|
209
|
+
readonly token: {
|
|
210
|
+
readonly type: "string";
|
|
211
|
+
readonly description: "Stringified capability JWT.";
|
|
212
|
+
};
|
|
213
|
+
readonly hlc: {
|
|
214
|
+
readonly type: "string";
|
|
215
|
+
readonly description: "Grantor-stamped HLC, mirroring the issued row.";
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
readonly required: readonly ["token", "hlc"];
|
|
219
|
+
readonly additionalProperties: false;
|
|
220
|
+
};
|
|
221
|
+
};
|
|
222
|
+
readonly 'control/delegationRevoke': {
|
|
223
|
+
readonly type: "event";
|
|
224
|
+
readonly description: "Distribute a signed revocation record for a previously minted capability.";
|
|
225
|
+
readonly data: {
|
|
226
|
+
readonly type: "object";
|
|
227
|
+
readonly properties: {
|
|
228
|
+
readonly token: {
|
|
229
|
+
readonly type: "string";
|
|
230
|
+
readonly description: "Stringified signed revocation JWT.";
|
|
231
|
+
};
|
|
232
|
+
readonly hlc: {
|
|
233
|
+
readonly type: "string";
|
|
234
|
+
readonly description: "Revoker-stamped HLC, mirroring the stored row.";
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
readonly required: readonly ["token", "hlc"];
|
|
238
|
+
readonly additionalProperties: false;
|
|
239
|
+
};
|
|
240
|
+
};
|
|
241
|
+
readonly 'control/groupLeaveRequest': {
|
|
242
|
+
readonly type: "event";
|
|
243
|
+
readonly description: "Advisory voluntary-leave notice. Not authoritative — an admin must issue a real removeMember; receivers surface it as an event and never tombstone on it.";
|
|
244
|
+
readonly data: {
|
|
245
|
+
readonly type: "object";
|
|
246
|
+
readonly properties: {
|
|
247
|
+
readonly memberDID: {
|
|
248
|
+
readonly type: "string";
|
|
249
|
+
readonly description: "DID of the member announcing departure.";
|
|
250
|
+
};
|
|
251
|
+
readonly hlc: {
|
|
252
|
+
readonly type: "string";
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
readonly required: readonly ["memberDID", "hlc"];
|
|
256
|
+
readonly additionalProperties: false;
|
|
257
|
+
};
|
|
258
|
+
};
|
|
259
|
+
};
|
|
260
|
+
sync: {
|
|
261
|
+
readonly 'sync/mutationApply': {
|
|
262
|
+
readonly type: "event";
|
|
263
|
+
readonly description: "Broadcast a batch of signed document mutations to group members.";
|
|
264
|
+
readonly data: {
|
|
265
|
+
readonly type: "object";
|
|
266
|
+
readonly properties: {
|
|
267
|
+
readonly entries: {
|
|
268
|
+
readonly type: "array";
|
|
269
|
+
readonly items: {
|
|
270
|
+
readonly type: "object";
|
|
271
|
+
readonly properties: {
|
|
272
|
+
readonly mutationJWT: {
|
|
273
|
+
readonly type: "string";
|
|
274
|
+
};
|
|
275
|
+
readonly docID: {
|
|
276
|
+
readonly type: "string";
|
|
277
|
+
};
|
|
278
|
+
readonly version: {
|
|
279
|
+
readonly type: "string";
|
|
280
|
+
readonly description: "HLC string identifying this version.";
|
|
281
|
+
};
|
|
282
|
+
readonly modelID: {
|
|
283
|
+
readonly type: "string";
|
|
284
|
+
};
|
|
285
|
+
};
|
|
286
|
+
readonly required: readonly ["mutationJWT", "docID", "version", "modelID"];
|
|
287
|
+
readonly additionalProperties: false;
|
|
288
|
+
};
|
|
289
|
+
};
|
|
290
|
+
};
|
|
291
|
+
readonly required: readonly ["entries"];
|
|
292
|
+
readonly additionalProperties: false;
|
|
293
|
+
};
|
|
294
|
+
};
|
|
295
|
+
};
|
|
296
|
+
};
|
|
297
|
+
export type GroupProtocols = typeof groupProtocols;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { defineGroupProtocol } from '@kumiai/rpc';
|
|
2
|
+
/**
|
|
3
|
+
* App-lane group-rpc protocols carried over a group's rotating broadcast topics.
|
|
4
|
+
* Each peer is group-scoped (its topics encode the group), so payloads omit
|
|
5
|
+
* `groupID` — the handler already knows which group it serves. Loopback drop is
|
|
6
|
+
* automatic: the hub fans a publish to subscribers minus the sender, so a peer
|
|
7
|
+
* never receives its own broadcast.
|
|
8
|
+
*
|
|
9
|
+
* Procedure kinds map to the broadcast surface: `event` is fire-and-forget
|
|
10
|
+
* (`dispatch`); `request` is correlated (`request` for the first reply,
|
|
11
|
+
* `gather` for every reply within a window).
|
|
12
|
+
*/ /**
|
|
13
|
+
* Control plane: membership ledger, delegation, and advisory leave. All are
|
|
14
|
+
* self-authenticating at the payload level (signed tokens / HLC-stamped
|
|
15
|
+
* records), so the lane provides ordering + delivery, not authorization.
|
|
16
|
+
*/ export const controlProtocol = defineGroupProtocol({
|
|
17
|
+
'control/ledgerEntry': {
|
|
18
|
+
type: 'event',
|
|
19
|
+
description: 'A self-signed control-ledger entry appended by its author.',
|
|
20
|
+
data: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
token: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'Signed control-ledger entry token.'
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
required: [
|
|
29
|
+
'token'
|
|
30
|
+
],
|
|
31
|
+
additionalProperties: false
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
'control/ledgerCatchup': {
|
|
35
|
+
type: 'request',
|
|
36
|
+
description: 'Ask current members for the group control ledger to backfill missed entries. Gathered: each member replies with its entries; the requester re-verifies every token.',
|
|
37
|
+
param: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {},
|
|
40
|
+
additionalProperties: false
|
|
41
|
+
},
|
|
42
|
+
result: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
entries: {
|
|
46
|
+
type: 'array',
|
|
47
|
+
items: {
|
|
48
|
+
type: 'string'
|
|
49
|
+
},
|
|
50
|
+
description: 'Signed control-ledger entry tokens.'
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
required: [
|
|
54
|
+
'entries'
|
|
55
|
+
],
|
|
56
|
+
additionalProperties: false
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
'control/delegationShare': {
|
|
60
|
+
type: 'event',
|
|
61
|
+
description: 'Distribute a minted document/write capability token to group co-members.',
|
|
62
|
+
data: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
token: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
description: 'Stringified capability JWT.'
|
|
68
|
+
},
|
|
69
|
+
hlc: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
description: 'Grantor-stamped HLC, mirroring the issued row.'
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
required: [
|
|
75
|
+
'token',
|
|
76
|
+
'hlc'
|
|
77
|
+
],
|
|
78
|
+
additionalProperties: false
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
'control/delegationRevoke': {
|
|
82
|
+
type: 'event',
|
|
83
|
+
description: 'Distribute a signed revocation record for a previously minted capability.',
|
|
84
|
+
data: {
|
|
85
|
+
type: 'object',
|
|
86
|
+
properties: {
|
|
87
|
+
token: {
|
|
88
|
+
type: 'string',
|
|
89
|
+
description: 'Stringified signed revocation JWT.'
|
|
90
|
+
},
|
|
91
|
+
hlc: {
|
|
92
|
+
type: 'string',
|
|
93
|
+
description: 'Revoker-stamped HLC, mirroring the stored row.'
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
required: [
|
|
97
|
+
'token',
|
|
98
|
+
'hlc'
|
|
99
|
+
],
|
|
100
|
+
additionalProperties: false
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
'control/groupLeaveRequest': {
|
|
104
|
+
type: 'event',
|
|
105
|
+
description: 'Advisory voluntary-leave notice. Not authoritative — an admin must issue a real removeMember; receivers surface it as an event and never tombstone on it.',
|
|
106
|
+
data: {
|
|
107
|
+
type: 'object',
|
|
108
|
+
properties: {
|
|
109
|
+
memberDID: {
|
|
110
|
+
type: 'string',
|
|
111
|
+
description: 'DID of the member announcing departure.'
|
|
112
|
+
},
|
|
113
|
+
hlc: {
|
|
114
|
+
type: 'string'
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
required: [
|
|
118
|
+
'memberDID',
|
|
119
|
+
'hlc'
|
|
120
|
+
],
|
|
121
|
+
additionalProperties: false
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
/**
|
|
126
|
+
* Sync plane: document mutation broadcasts. Each entry carries a signed mutation
|
|
127
|
+
* JWT the receiver verifies before applying, so the lane is transport only.
|
|
128
|
+
*/ export const syncProtocol = defineGroupProtocol({
|
|
129
|
+
'sync/mutationApply': {
|
|
130
|
+
type: 'event',
|
|
131
|
+
description: 'Broadcast a batch of signed document mutations to group members.',
|
|
132
|
+
data: {
|
|
133
|
+
type: 'object',
|
|
134
|
+
properties: {
|
|
135
|
+
entries: {
|
|
136
|
+
type: 'array',
|
|
137
|
+
items: {
|
|
138
|
+
type: 'object',
|
|
139
|
+
properties: {
|
|
140
|
+
mutationJWT: {
|
|
141
|
+
type: 'string'
|
|
142
|
+
},
|
|
143
|
+
docID: {
|
|
144
|
+
type: 'string'
|
|
145
|
+
},
|
|
146
|
+
version: {
|
|
147
|
+
type: 'string',
|
|
148
|
+
description: 'HLC string identifying this version.'
|
|
149
|
+
},
|
|
150
|
+
modelID: {
|
|
151
|
+
type: 'string'
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
required: [
|
|
155
|
+
'mutationJWT',
|
|
156
|
+
'docID',
|
|
157
|
+
'version',
|
|
158
|
+
'modelID'
|
|
159
|
+
],
|
|
160
|
+
additionalProperties: false
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
required: [
|
|
165
|
+
'entries'
|
|
166
|
+
],
|
|
167
|
+
additionalProperties: false
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
/** The two app-lane protocols, keyed for `createGroupPeer({ protocols })`. */ export const groupProtocols = {
|
|
172
|
+
control: controlProtocol,
|
|
173
|
+
sync: syncProtocol
|
|
174
|
+
};
|
|
@@ -1 +1,26 @@
|
|
|
1
|
-
import{fromB64
|
|
1
|
+
import { fromB64, toB64 } from '@sozai/codec';
|
|
2
|
+
import { replacer, reviver } from './mls-json.js';
|
|
3
|
+
export function encodeJoinRequest(payload) {
|
|
4
|
+
const json = JSON.stringify(payload, replacer);
|
|
5
|
+
return toB64(new TextEncoder().encode(json));
|
|
6
|
+
}
|
|
7
|
+
export function decodeJoinRequest(encoded) {
|
|
8
|
+
const json = new TextDecoder().decode(fromB64(encoded));
|
|
9
|
+
return JSON.parse(json, reviver);
|
|
10
|
+
}
|
|
11
|
+
export function encodeInvitePayload(payload) {
|
|
12
|
+
const json = JSON.stringify(payload, replacer);
|
|
13
|
+
return toB64(new TextEncoder().encode(json));
|
|
14
|
+
}
|
|
15
|
+
export function decodeInvitePayload(encoded) {
|
|
16
|
+
const json = new TextDecoder().decode(fromB64(encoded));
|
|
17
|
+
return JSON.parse(json, reviver);
|
|
18
|
+
}
|
|
19
|
+
export function encodeFullJoinRequest(payload) {
|
|
20
|
+
const json = JSON.stringify(payload, replacer);
|
|
21
|
+
return toB64(new TextEncoder().encode(json));
|
|
22
|
+
}
|
|
23
|
+
export function decodeFullJoinRequest(encoded) {
|
|
24
|
+
const json = new TextDecoder().decode(fromB64(encoded));
|
|
25
|
+
return JSON.parse(json, reviver);
|
|
26
|
+
}
|
package/lib/groups/join-utils.js
CHANGED
|
@@ -1 +1,104 @@
|
|
|
1
|
-
import{HLC as
|
|
1
|
+
import { HLC as HLCClass } from '@kubun/hlc';
|
|
2
|
+
import { getDelegationStore } from '@kubun/store-delegation';
|
|
3
|
+
import { getP2PStore } from '@kubun/store-p2p';
|
|
4
|
+
import { toISO } from '../context/types.js';
|
|
5
|
+
import { storeReceivedGrant } from './store-received-grant.js';
|
|
6
|
+
import { storeReceivedRevocation } from './store-received-revocation.js';
|
|
7
|
+
export async function finalizeJoinedGroup(params) {
|
|
8
|
+
const { stores, emitter, invite, logger } = params;
|
|
9
|
+
const store = await getP2PStore(stores);
|
|
10
|
+
const groupRow = await store.getGroup(invite.groupID);
|
|
11
|
+
const joinedGroupData = groupRow != null ? {
|
|
12
|
+
id: groupRow.id,
|
|
13
|
+
name: groupRow.name,
|
|
14
|
+
description: groupRow.description,
|
|
15
|
+
createdBy: groupRow.created_by,
|
|
16
|
+
createdAt: toISO(groupRow.created_at)
|
|
17
|
+
} : null;
|
|
18
|
+
if (joinedGroupData != null) {
|
|
19
|
+
// Defer past the outermost commit (`stores` is that tx): the `groupJoined`
|
|
20
|
+
// listener reads the P2P store on the main connection (deadlocks single-conn
|
|
21
|
+
// SQLite / uncommitted on PG).
|
|
22
|
+
stores.onCommit(()=>{
|
|
23
|
+
emitter.emit('groupJoined', joinedGroupData).catch((error)=>{
|
|
24
|
+
logger?.error('groupJoined emit failed', {
|
|
25
|
+
groupID: invite.groupID,
|
|
26
|
+
error
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
group: joinedGroupData
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Apply invite-bootstrap grants after a join completes. Each token is verified
|
|
37
|
+
* and, when addressed to the joining device, stored as a held row so the
|
|
38
|
+
* engine's auto-attach can use it on the very first mutation — before any group
|
|
39
|
+
* broadcast arrives.
|
|
40
|
+
*
|
|
41
|
+
* No grantor HLC travels in the invite (it carries plain tokens, not the
|
|
42
|
+
* grantor's issued rows), so the joiner stamps a fresh HLC of its own at apply
|
|
43
|
+
* time. This is consistent with how every other join-time row the joiner
|
|
44
|
+
* materializes is locally stamped.
|
|
45
|
+
*
|
|
46
|
+
* Returns the number of grants that resulted in a stored held row.
|
|
47
|
+
*/ export async function applyInviteGrants(params) {
|
|
48
|
+
const { stores, selfDID, groupID, grants, hlc, emitter, logger } = params;
|
|
49
|
+
if (grants == null || grants.length === 0) {
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
const [store, delegationStore] = await Promise.all([
|
|
53
|
+
getP2PStore(stores),
|
|
54
|
+
getDelegationStore(stores)
|
|
55
|
+
]);
|
|
56
|
+
const results = await Promise.all(grants.map((token)=>storeReceivedGrant({
|
|
57
|
+
delegationStore,
|
|
58
|
+
p2pStore: store,
|
|
59
|
+
token,
|
|
60
|
+
groupID,
|
|
61
|
+
hlc: HLCClass.serialize(hlc.now()),
|
|
62
|
+
selfDID,
|
|
63
|
+
...emitter != null ? {
|
|
64
|
+
emitter
|
|
65
|
+
} : {},
|
|
66
|
+
...logger != null ? {
|
|
67
|
+
logger
|
|
68
|
+
} : {}
|
|
69
|
+
})));
|
|
70
|
+
return results.reduce((count, applied)=>count + (applied ? 1 : 0), 0);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Apply invite-bootstrap revocations after a join completes. Each token is
|
|
74
|
+
* verified and stored — cap-known locally with matching `iss` yields a
|
|
75
|
+
* verified row, cap-unknown yields a pending row that the joiner will
|
|
76
|
+
* cross-check when the cap later arrives.
|
|
77
|
+
*
|
|
78
|
+
* The inviter's caller is responsible for filtering to verified revocations
|
|
79
|
+
* only; pending revocations do not travel via the invite envelope. The joiner
|
|
80
|
+
* stamps a fresh HLC since the inviter's HLC for these rows is not in the
|
|
81
|
+
* payload.
|
|
82
|
+
*
|
|
83
|
+
* Returns the number of revocations that resulted in a stored row.
|
|
84
|
+
*/ export async function applyInviteRevocations(params) {
|
|
85
|
+
const { stores, groupID, revocations, hlc, selfDID, emitter, logger } = params;
|
|
86
|
+
if (revocations == null || revocations.length === 0) {
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
const delegationStore = await getDelegationStore(stores);
|
|
90
|
+
const results = await Promise.all(revocations.map((token)=>storeReceivedRevocation({
|
|
91
|
+
delegationStore,
|
|
92
|
+
token,
|
|
93
|
+
groupID,
|
|
94
|
+
hlc: HLCClass.serialize(hlc.now()),
|
|
95
|
+
selfDID,
|
|
96
|
+
...emitter != null ? {
|
|
97
|
+
emitter
|
|
98
|
+
} : {},
|
|
99
|
+
...logger != null ? {
|
|
100
|
+
logger
|
|
101
|
+
} : {}
|
|
102
|
+
})));
|
|
103
|
+
return results.reduce((count, applied)=>count + (applied ? 1 : 0), 0);
|
|
104
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ProcessBroadcastResult } from './broadcast.js';
|
|
2
|
+
import type { P2PEventEmitter } from './events.js';
|
|
3
|
+
/**
|
|
4
|
+
* Emit the local `P2PEventEmitter` events for a remote-driven control-ledger
|
|
5
|
+
* entry that a receiving peer just applied, so GraphQL subscribers see
|
|
6
|
+
* remote circle/group changes live — the same events a local producer fires.
|
|
7
|
+
*
|
|
8
|
+
* Only `ledger:entry` projections surface as `affected`; every other applied
|
|
9
|
+
* message (delegation grant/revoke, advisory leave) already emits its own event
|
|
10
|
+
* from inside `processBroadcast`, so this covers exactly the circle/group
|
|
11
|
+
* projection rows the apply core returns rather than emits. The payloads are
|
|
12
|
+
* built from `affected.row`, which `processBroadcast` snapshots at the right
|
|
13
|
+
* moment (after-state for create/change, before-state for delete/remove).
|
|
14
|
+
*/
|
|
15
|
+
export declare function emitLedgerAffectedEvents(emitter: P2PEventEmitter, result: ProcessBroadcastResult): Promise<void>;
|