@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.
- package/lib/context/delegation.js +130 -1
- package/lib/context/group.js +1646 -1
- package/lib/context/hub.js +151 -1
- package/lib/context/join.js +115 -1
- package/lib/context/peer.d.ts +92 -0
- package/lib/context/peer.js +1216 -0
- package/lib/context/require-admin.d.ts +57 -0
- package/lib/context/require-admin.js +57 -0
- package/lib/context/sync.d.ts +12 -0
- package/lib/context/sync.js +252 -1
- package/lib/context/types.d.ts +58 -22
- package/lib/context/types.js +48 -1
- package/lib/groups/access-default-token.d.ts +44 -0
- package/lib/groups/access-default-token.js +103 -0
- package/lib/groups/admin-roster.d.ts +55 -0
- package/lib/groups/admin-roster.js +69 -0
- package/lib/groups/anchor-store.d.ts +15 -0
- package/lib/groups/anchor-store.js +37 -0
- package/lib/groups/app-cursor-store.d.ts +17 -0
- package/lib/groups/app-cursor-store.js +34 -0
- package/lib/groups/broadcast-codec.js +6 -1
- package/lib/groups/broadcast.d.ts +238 -106
- package/lib/groups/broadcast.js +702 -1
- package/lib/groups/catalog-token.d.ts +50 -0
- package/lib/groups/catalog-token.js +96 -0
- package/lib/groups/circle-projection.d.ts +90 -0
- package/lib/groups/circle-projection.js +202 -0
- package/lib/groups/circle-reducers.d.ts +139 -0
- package/lib/groups/circle-reducers.js +128 -0
- package/lib/groups/commit-adoption.d.ts +77 -0
- package/lib/groups/commit-adoption.js +108 -0
- package/lib/groups/commit-journal.d.ts +14 -0
- package/lib/groups/commit-journal.js +65 -0
- package/lib/groups/control-request.d.ts +66 -0
- package/lib/groups/control-request.js +101 -0
- package/lib/groups/events.d.ts +19 -2
- package/lib/groups/events.js +10 -1
- package/lib/groups/group-anchor.d.ts +24 -0
- package/lib/groups/group-anchor.js +22 -0
- package/lib/groups/group-crypto.d.ts +42 -0
- package/lib/groups/group-crypto.js +159 -0
- package/lib/groups/group-handle-registry.d.ts +138 -9
- package/lib/groups/group-handle-registry.js +533 -1
- package/lib/groups/group-handlers.d.ts +30 -0
- package/lib/groups/group-handlers.js +218 -0
- package/lib/groups/group-health-monitor.d.ts +11 -0
- package/lib/groups/group-health-monitor.js +139 -1
- package/lib/groups/group-mls.d.ts +66 -0
- package/lib/groups/group-mls.js +696 -0
- package/lib/groups/group-peer-manager.d.ts +301 -0
- package/lib/groups/group-peer-manager.js +1336 -0
- package/lib/groups/group-protocols.d.ts +754 -0
- package/lib/groups/group-protocols.js +448 -0
- package/lib/groups/invite-payload.d.ts +49 -1
- package/lib/groups/invite-payload.js +26 -1
- package/lib/groups/join-request-identity.d.ts +21 -0
- package/lib/groups/join-request-identity.js +43 -0
- package/lib/groups/join-utils.d.ts +61 -0
- package/lib/groups/join-utils.js +366 -1
- package/lib/groups/ledger-affected-events.d.ts +20 -0
- package/lib/groups/ledger-affected-events.js +85 -0
- package/lib/groups/ledger-commit-fold.d.ts +107 -0
- package/lib/groups/ledger-commit-fold.js +167 -0
- package/lib/groups/ledger-ingest.d.ts +124 -0
- package/lib/groups/ledger-ingest.js +212 -0
- package/lib/groups/ledger.d.ts +44 -0
- package/lib/groups/ledger.js +30 -0
- package/lib/groups/manager.d.ts +217 -85
- package/lib/groups/manager.js +798 -1
- package/lib/groups/mls-codec.d.ts +28 -7
- package/lib/groups/mls-codec.js +33 -1
- package/lib/groups/mls-encryptor.d.ts +2 -2
- package/lib/groups/mls-encryptor.js +33 -1
- package/lib/groups/mls-group-handle.d.ts +37 -2
- package/lib/groups/mls-group-handle.js +18 -1
- package/lib/groups/mls-json.js +17 -1
- package/lib/groups/mls-receive-errors.d.ts +27 -0
- package/lib/groups/mls-receive-errors.js +39 -0
- package/lib/groups/mls-state.d.ts +6 -4
- package/lib/groups/mls-state.js +79 -1
- package/lib/groups/peer-presence.d.ts +90 -0
- package/lib/groups/peer-presence.js +295 -0
- package/lib/groups/peer-selection.d.ts +37 -0
- package/lib/groups/peer-selection.js +72 -0
- package/lib/groups/roster-projection.d.ts +53 -0
- package/lib/groups/roster-projection.js +65 -0
- package/lib/groups/store-received-grant.d.ts +7 -3
- package/lib/groups/store-received-grant.js +161 -1
- package/lib/groups/store-received-revocation.d.ts +19 -16
- package/lib/groups/store-received-revocation.js +83 -1
- package/lib/hub/ack-backoff.d.ts +22 -0
- package/lib/hub/ack-backoff.js +88 -0
- package/lib/hub/http-client.d.ts +18 -10
- package/lib/hub/http-client.js +15 -1
- package/lib/hub/hub-like.d.ts +75 -0
- package/lib/hub/hub-like.js +1022 -0
- package/lib/hub/loopback-log-hub.d.ts +20 -0
- package/lib/hub/loopback-log-hub.js +178 -0
- package/lib/hub/manager.d.ts +25 -40
- package/lib/hub/manager.js +155 -1
- package/lib/hub/peer-scoped-hub-view.d.ts +35 -0
- package/lib/hub/peer-scoped-hub-view.js +92 -0
- package/lib/hub/sender-scoped-hub-view.d.ts +41 -0
- package/lib/hub/sender-scoped-hub-view.js +59 -0
- package/lib/hub/server-did.d.ts +67 -0
- package/lib/hub/server-did.js +131 -0
- package/lib/hub/wiring.d.ts +77 -51
- package/lib/hub/wiring.js +189 -1
- package/lib/index.d.ts +58 -22
- package/lib/index.js +563 -1
- package/lib/peer/connection-registry.d.ts +45 -0
- package/lib/peer/connection-registry.js +52 -0
- package/lib/peer/handlers.d.ts +69 -0
- package/lib/peer/handlers.js +110 -0
- package/lib/protocol.d.ts +214 -1
- package/lib/protocol.js +519 -1
- package/lib/schema.d.ts +3 -1
- package/lib/schema.js +1150 -39
- package/lib/sync/access-default-sender.d.ts +23 -0
- package/lib/sync/access-default-sender.js +80 -0
- package/lib/sync/authorize.d.ts +58 -4
- package/lib/sync/authorize.js +114 -1
- package/lib/sync/broadcast-queue.d.ts +3 -4
- package/lib/sync/broadcast-queue.js +182 -1
- package/lib/sync/broadcast-sender.js +179 -1
- package/lib/sync/catalog-scope.d.ts +8 -7
- package/lib/sync/catalog-scope.js +84 -1
- package/lib/sync/errors.d.ts +12 -2
- package/lib/sync/errors.js +29 -1
- package/lib/sync/forwarder.d.ts +13 -19
- package/lib/sync/forwarder.js +271 -1
- package/lib/sync/handlers.js +200 -1
- package/lib/sync/hub-tunnel-sync-listener.d.ts +19 -5
- package/lib/sync/hub-tunnel-sync-listener.js +261 -1
- package/lib/sync/hub-tunnel-sync-provider.d.ts +34 -14
- package/lib/sync/hub-tunnel-sync-provider.js +113 -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 +11 -3
- package/lib/sync/merkle-channel.js +0 -0
- package/lib/sync/merkle-tree.js +120 -1
- package/lib/sync/peer-registry.d.ts +15 -11
- package/lib/sync/peer-registry.js +82 -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 +14 -4
- package/lib/sync/sync-client.js +254 -1
- package/lib/sync/sync-manager.d.ts +24 -6
- package/lib/sync/sync-manager.js +301 -1
- package/lib/sync/tunnel-listeners.d.ts +52 -0
- package/lib/sync/tunnel-listeners.js +175 -0
- package/lib/sync/tunnel-observability.d.ts +25 -0
- package/lib/sync/tunnel-observability.js +63 -0
- package/lib/sync/tunnel-topics.d.ts +20 -0
- package/lib/sync/tunnel-topics.js +28 -0
- package/lib/types.d.ts +704 -22
- package/lib/types.js +1 -1
- package/lib/util/handler-error.d.ts +16 -0
- package/lib/util/handler-error.js +64 -0
- package/lib/util/mutex.d.ts +14 -1
- package/lib/util/mutex.js +44 -1
- package/lib/utils.js +21 -1
- package/package.json +58 -47
- package/lib/groups/broadcast-service.d.ts +0 -263
- package/lib/groups/broadcast-service.js +0 -1
- package/lib/groups/rejoin-codec.d.ts +0 -14
- package/lib/groups/rejoin-codec.js +0 -1
- package/lib/groups/wire-frame.d.ts +0 -34
- package/lib/groups/wire-frame.js +0 -1
- package/lib/hub/circle-catchup-requester.d.ts +0 -58
- package/lib/hub/circle-catchup-requester.js +0 -1
- package/lib/hub/circle-catchup-responder.d.ts +0 -50
- package/lib/hub/circle-catchup-responder.js +0 -1
- package/lib/hub/connection-pool.d.ts +0 -43
- package/lib/hub/connection-pool.js +0 -1
- package/lib/hub/did-observing-transport.d.ts +0 -64
- package/lib/hub/did-observing-transport.js +0 -1
- package/lib/hub/epoch-stale-detector.d.ts +0 -18
- package/lib/hub/epoch-stale-detector.js +0 -1
- package/lib/hub/errors.d.ts +0 -30
- package/lib/hub/errors.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 -89
- 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 -51
- package/lib/hub/receive-handler.js +0 -1
- package/lib/hub/rejoin-manager.d.ts +0 -78
- package/lib/hub/rejoin-manager.js +0 -1
- package/lib/hub/rejoin-responder.d.ts +0 -32
- package/lib/hub/rejoin-responder.js +0 -1
- package/lib/hub/relay-manager.d.ts +0 -142
- 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
|
@@ -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
|
|
4
|
-
import type {
|
|
5
|
-
import type {
|
|
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
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
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
|
-
|
|
136
|
-
requestID: string;
|
|
138
|
+
senderPeerDID: string | undefined;
|
|
137
139
|
} | {
|
|
138
140
|
/**
|
|
139
|
-
* A
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
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: '
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
*
|
|
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
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
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
|
-
*
|
|
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
|
|
290
|
-
type: 'circle-catchup:reply';
|
|
291
|
-
}>, groupID: string): Promise<ApplyCatchupReplyResult>;
|
|
423
|
+
export declare function bootstrapGroupLedger(params: BootstrapGroupLedgerParams, tokens: Array<string>, groupID: string): Promise<ApplyLedgerCatchupReplyResult>;
|