@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
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { isVerifiedToken, normalizeDID, stringifyToken, verifyToken } from '@kokuin/token';
|
|
2
|
+
export async function signAccessDefaultSet(identity, claim) {
|
|
3
|
+
const signed = await identity.signToken({
|
|
4
|
+
op: 'set',
|
|
5
|
+
...claim
|
|
6
|
+
}, {
|
|
7
|
+
embedLongForm: true
|
|
8
|
+
});
|
|
9
|
+
return stringifyToken(signed);
|
|
10
|
+
}
|
|
11
|
+
export async function signAccessDefaultRemove(identity, claim) {
|
|
12
|
+
const signed = await identity.signToken({
|
|
13
|
+
op: 'remove',
|
|
14
|
+
...claim
|
|
15
|
+
}, {
|
|
16
|
+
embedLongForm: true
|
|
17
|
+
});
|
|
18
|
+
return stringifyToken(signed);
|
|
19
|
+
}
|
|
20
|
+
function isPermissionType(value) {
|
|
21
|
+
return value === 'read' || value === 'write';
|
|
22
|
+
}
|
|
23
|
+
/** The levels the store's read predicate is written for, and no others. */ const ACCESS_LEVELS = new Set([
|
|
24
|
+
'only_owner',
|
|
25
|
+
'anyone',
|
|
26
|
+
'restricted'
|
|
27
|
+
]);
|
|
28
|
+
function isDIDList(value) {
|
|
29
|
+
return value == null || Array.isArray(value) && value.every((entry)=>typeof entry === 'string');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Every field, not just `level`.
|
|
33
|
+
*
|
|
34
|
+
* This is the only gate between the wire and `setUserModelAccessDefault`: the
|
|
35
|
+
* applier writes the rule verbatim. Checking `level` alone let a member land an
|
|
36
|
+
* arbitrary string as an access level, and non-array values in the allow-lists,
|
|
37
|
+
* in its own rows on every peer — shapes the receiving device's SQL read
|
|
38
|
+
* predicate was never written for. The blast radius is the sender's own rows
|
|
39
|
+
* (the applier is sender-bound), which is a reason it is not urgent, not a
|
|
40
|
+
* reason to accept the shape.
|
|
41
|
+
*/ function isRule(value) {
|
|
42
|
+
if (value == null || typeof value !== 'object') {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const rule = value;
|
|
46
|
+
return ACCESS_LEVELS.has(rule.level) && isDIDList(rule.allowedDIDs) && isDIDList(rule.allowedCircles) && // Accepted from the wire but never minted locally: `setModelAccessDefaults`
|
|
47
|
+
// exposes circles only. Refusing it here would drop a frame a peer on an
|
|
48
|
+
// older build can still legitimately send, so it is validated and kept.
|
|
49
|
+
isDIDList(rule.allowedGroups);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Verify a signed access-default token and extract its claim. Returns `null`
|
|
53
|
+
* (never throws) when the token is unparseable, unsigned (`alg: 'none'`), or
|
|
54
|
+
* structurally malformed — an attacker cannot forge an `iss` this way. The
|
|
55
|
+
* issuer is the normalized verified `iss`.
|
|
56
|
+
*/ export async function verifyAccessDefault(token) {
|
|
57
|
+
let verified;
|
|
58
|
+
try {
|
|
59
|
+
verified = await verifyToken(token);
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if (!isVerifiedToken(verified)) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const { iss, op, modelID, hlc } = verified.payload;
|
|
67
|
+
if (typeof modelID !== 'string' || typeof hlc !== 'string') {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const issuer = normalizeDID(iss);
|
|
71
|
+
if (op === 'set') {
|
|
72
|
+
const { permissionType, rule } = verified.payload;
|
|
73
|
+
if (!isPermissionType(permissionType) || !isRule(rule)) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
issuer,
|
|
78
|
+
claim: {
|
|
79
|
+
op: 'set',
|
|
80
|
+
modelID,
|
|
81
|
+
permissionType,
|
|
82
|
+
rule,
|
|
83
|
+
hlc
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (op === 'remove') {
|
|
88
|
+
const { permissionTypes } = verified.payload;
|
|
89
|
+
if (!Array.isArray(permissionTypes) || !permissionTypes.every(isPermissionType)) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
issuer,
|
|
94
|
+
claim: {
|
|
95
|
+
op: 'remove',
|
|
96
|
+
modelID,
|
|
97
|
+
permissionTypes,
|
|
98
|
+
hlc
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type GroupAnchor, type LedgerLogEntry, type RoleValue } from '@kumiai/mls';
|
|
2
|
+
import type { PositionedFoldInput } from './ledger.js';
|
|
3
|
+
/**
|
|
4
|
+
* Ledger entry `type` for a role grant or demotion. This is `@kumiai/mls`'s own
|
|
5
|
+
* interpreted type: the same signed entry the library folds into the roster its
|
|
6
|
+
* commit policy enforces, so kubun's projection and the MLS authority that
|
|
7
|
+
* accepts or refuses a commit can never disagree about who is an admin.
|
|
8
|
+
*/
|
|
9
|
+
export declare const ADMIN_ROLE_ENTRY_TYPE = "kumiai.role";
|
|
10
|
+
/**
|
|
11
|
+
* The claim carried by a role entry: the permission the subject holds. Demotion
|
|
12
|
+
* is `member`, not a distinct "revoked" value — the library's roster is a
|
|
13
|
+
* DID→permission map, and an absent admin is simply a member.
|
|
14
|
+
*/
|
|
15
|
+
export type AdminRoleValue = RoleValue;
|
|
16
|
+
/** Folded admin set: the normalized DIDs currently holding the admin role. */
|
|
17
|
+
export type AdminRosterState = {
|
|
18
|
+
admins: Set<string>;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Fold the role ledger into the admin set.
|
|
22
|
+
*
|
|
23
|
+
* With `throughPosition`, only entries enacted at or before that ledger slot are
|
|
24
|
+
* folded — the roster as it stood at that point in the group's history. That is
|
|
25
|
+
* the was-admin-then answer the current-only `role` column cannot express, and
|
|
26
|
+
* unlike the HLC it used to be keyed on, a position is assigned by the commit
|
|
27
|
+
* chain rather than asserted by the signer, so it cannot be backdated.
|
|
28
|
+
*
|
|
29
|
+
* The entries are folded in the order given, which is the order the handle's log
|
|
30
|
+
* holds them. Authority is rooted at the genesis anchor (the creator is the
|
|
31
|
+
* epoch-0 admin) and grows only through admins-so-far — `foldRoster` also drops
|
|
32
|
+
* an entry signed for another group and one that would leave the roster with
|
|
33
|
+
* zero admins.
|
|
34
|
+
*/
|
|
35
|
+
export declare function foldAdminRoster(entries: Array<PositionedFoldInput<RoleValue>>, anchor: GroupAnchor, groupID: string, throughPosition?: number): AdminRosterState;
|
|
36
|
+
/**
|
|
37
|
+
* Select the role entries out of a group's control ledger, as fold inputs
|
|
38
|
+
* carrying the ledger slot each one occupies.
|
|
39
|
+
*
|
|
40
|
+
* Every entry is already verified: the log is the handle's own, and each of its
|
|
41
|
+
* doors — `GroupHandle.applyLedgerEntries`, `bootstrapLedger`, an accepted
|
|
42
|
+
* commit — re-verifies what it admits. Nothing left to check, nothing to
|
|
43
|
+
* memoize; this replaced a loader that re-verified every stored `signed_token`
|
|
44
|
+
* per authority check, needed only because the SQL row could not vouch for
|
|
45
|
+
* itself.
|
|
46
|
+
*
|
|
47
|
+
* The position indexes the WHOLE ledger, not the selected subset, so a role
|
|
48
|
+
* entry's slot is comparable with a circle entry's — which is what makes "was
|
|
49
|
+
* this issuer an admin when that entry was enacted" answerable at all.
|
|
50
|
+
*
|
|
51
|
+
* The cast is unavoidable: the log is typed over an unknown claim value, and the
|
|
52
|
+
* entry `type` is what says the value is a role, so selecting by that type is
|
|
53
|
+
* what makes the narrowing true.
|
|
54
|
+
*/
|
|
55
|
+
export declare function selectRoleEntries(ledger: ReadonlyArray<LedgerLogEntry>): Array<PositionedFoldInput<RoleValue>>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { foldRoster, ROLE_ENTRY_TYPE } from '@kumiai/mls';
|
|
2
|
+
/**
|
|
3
|
+
* Ledger entry `type` for a role grant or demotion. This is `@kumiai/mls`'s own
|
|
4
|
+
* interpreted type: the same signed entry the library folds into the roster its
|
|
5
|
+
* commit policy enforces, so kubun's projection and the MLS authority that
|
|
6
|
+
* accepts or refuses a commit can never disagree about who is an admin.
|
|
7
|
+
*/ export const ADMIN_ROLE_ENTRY_TYPE = ROLE_ENTRY_TYPE;
|
|
8
|
+
function adminsFromRoles(roles) {
|
|
9
|
+
const admins = new Set();
|
|
10
|
+
for (const [did, permission] of roles){
|
|
11
|
+
if (permission === 'admin') {
|
|
12
|
+
admins.add(did);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return admins;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Fold the role ledger into the admin set.
|
|
19
|
+
*
|
|
20
|
+
* With `throughPosition`, only entries enacted at or before that ledger slot are
|
|
21
|
+
* folded — the roster as it stood at that point in the group's history. That is
|
|
22
|
+
* the was-admin-then answer the current-only `role` column cannot express, and
|
|
23
|
+
* unlike the HLC it used to be keyed on, a position is assigned by the commit
|
|
24
|
+
* chain rather than asserted by the signer, so it cannot be backdated.
|
|
25
|
+
*
|
|
26
|
+
* The entries are folded in the order given, which is the order the handle's log
|
|
27
|
+
* holds them. Authority is rooted at the genesis anchor (the creator is the
|
|
28
|
+
* epoch-0 admin) and grows only through admins-so-far — `foldRoster` also drops
|
|
29
|
+
* an entry signed for another group and one that would leave the roster with
|
|
30
|
+
* zero admins.
|
|
31
|
+
*/ export function foldAdminRoster(entries, anchor, groupID, throughPosition) {
|
|
32
|
+
const scoped = throughPosition == null ? entries : entries.filter(({ position })=>position <= throughPosition);
|
|
33
|
+
const roster = foldRoster(scoped, anchor, groupID);
|
|
34
|
+
return {
|
|
35
|
+
admins: adminsFromRoles(roster.roles)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Select the role entries out of a group's control ledger, as fold inputs
|
|
40
|
+
* carrying the ledger slot each one occupies.
|
|
41
|
+
*
|
|
42
|
+
* Every entry is already verified: the log is the handle's own, and each of its
|
|
43
|
+
* doors — `GroupHandle.applyLedgerEntries`, `bootstrapLedger`, an accepted
|
|
44
|
+
* commit — re-verifies what it admits. Nothing left to check, nothing to
|
|
45
|
+
* memoize; this replaced a loader that re-verified every stored `signed_token`
|
|
46
|
+
* per authority check, needed only because the SQL row could not vouch for
|
|
47
|
+
* itself.
|
|
48
|
+
*
|
|
49
|
+
* The position indexes the WHOLE ledger, not the selected subset, so a role
|
|
50
|
+
* entry's slot is comparable with a circle entry's — which is what makes "was
|
|
51
|
+
* this issuer an admin when that entry was enacted" answerable at all.
|
|
52
|
+
*
|
|
53
|
+
* The cast is unavoidable: the log is typed over an unknown claim value, and the
|
|
54
|
+
* entry `type` is what says the value is a role, so selecting by that type is
|
|
55
|
+
* what makes the narrowing true.
|
|
56
|
+
*/ export function selectRoleEntries(ledger) {
|
|
57
|
+
const entries = [];
|
|
58
|
+
for (const [position, held] of ledger.entries()){
|
|
59
|
+
if (held.verified.entry.type !== ADMIN_ROLE_ENTRY_TYPE) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
entries.push({
|
|
63
|
+
verified: held.verified,
|
|
64
|
+
entryID: held.entryID,
|
|
65
|
+
position
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return entries;
|
|
69
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { StoreProvider } from '@kubun/db';
|
|
2
|
+
import type { AnchorStore } from '@kumiai/rpc';
|
|
3
|
+
/**
|
|
4
|
+
* The `@kumiai/rpc` `AnchorStore` port for one group, backed by the device's
|
|
5
|
+
* single-slot anchor store. The port carries no group scope — the group a peer
|
|
6
|
+
* belongs to is fixed for its lifetime — so the adapter closes over `groupID`
|
|
7
|
+
* and threads it into every store call.
|
|
8
|
+
*
|
|
9
|
+
* `null` from `load` MEANS FIRST BOOT AND ONLY FIRST BOOT — the peer answers it by
|
|
10
|
+
* seeding the anchor from its live handle, and MLS ratchets forward so no earlier
|
|
11
|
+
* epoch's secret can be re-exported. A read error must therefore PROPAGATE, never
|
|
12
|
+
* be coerced to `null`: a `null` for a group that HAS an anchor silently derives
|
|
13
|
+
* different topic IDs and partitions this device with nothing reported.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createAnchorStore(stores: StoreProvider, groupID: string): AnchorStore;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getP2PStore } from '@kubun/store-p2p';
|
|
2
|
+
/**
|
|
3
|
+
* The `@kumiai/rpc` `AnchorStore` port for one group, backed by the device's
|
|
4
|
+
* single-slot anchor store. The port carries no group scope — the group a peer
|
|
5
|
+
* belongs to is fixed for its lifetime — so the adapter closes over `groupID`
|
|
6
|
+
* and threads it into every store call.
|
|
7
|
+
*
|
|
8
|
+
* `null` from `load` MEANS FIRST BOOT AND ONLY FIRST BOOT — the peer answers it by
|
|
9
|
+
* seeding the anchor from its live handle, and MLS ratchets forward so no earlier
|
|
10
|
+
* epoch's secret can be re-exported. A read error must therefore PROPAGATE, never
|
|
11
|
+
* be coerced to `null`: a `null` for a group that HAS an anchor silently derives
|
|
12
|
+
* different topic IDs and partitions this device with nothing reported.
|
|
13
|
+
*/ export function createAnchorStore(stores, groupID) {
|
|
14
|
+
return {
|
|
15
|
+
async load () {
|
|
16
|
+
const store = await getP2PStore(stores);
|
|
17
|
+
const row = await store.getGroupAnchor(groupID);
|
|
18
|
+
if (row == null) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
// Copied into a fresh `Uint8Array` the port owns, so nothing downstream
|
|
22
|
+
// holds a view onto the adapter's decoded row.
|
|
23
|
+
return {
|
|
24
|
+
secret: new Uint8Array(row.secret),
|
|
25
|
+
epoch: row.epoch
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
async save (anchor) {
|
|
29
|
+
const store = await getP2PStore(stores);
|
|
30
|
+
await store.putGroupAnchor({
|
|
31
|
+
groupID,
|
|
32
|
+
secret: anchor.secret,
|
|
33
|
+
epoch: anchor.epoch
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { StoreProvider } from '@kubun/db';
|
|
2
|
+
import type { AppCursorStore } from '@kumiai/rpc';
|
|
3
|
+
/**
|
|
4
|
+
* The `@kumiai/rpc` `AppCursorStore` port for one group, backed by the device's
|
|
5
|
+
* per-topic cursor store. The port keys positions by topic ID alone — a topic
|
|
6
|
+
* ID is already globally unique — so the adapter closes over `groupID` purely
|
|
7
|
+
* to give each row an owner: a deleted group's cursors go with it, rather than
|
|
8
|
+
* staying behind as unreachable rows nothing accounts for.
|
|
9
|
+
*
|
|
10
|
+
* `null` here means this device has never processed a frame on the topic, and
|
|
11
|
+
* the drain answers it by reading from the hub's oldest retained frame. Unlike
|
|
12
|
+
* the anchor, a wrong `null` costs a re-read, not a partition.
|
|
13
|
+
*
|
|
14
|
+
* Positions are opaque log positions: stored and returned verbatim, never
|
|
15
|
+
* compared or parsed here.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createAppCursorStore(stores: StoreProvider, groupID: string): AppCursorStore;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { getP2PStore } from '@kubun/store-p2p';
|
|
2
|
+
/**
|
|
3
|
+
* The `@kumiai/rpc` `AppCursorStore` port for one group, backed by the device's
|
|
4
|
+
* per-topic cursor store. The port keys positions by topic ID alone — a topic
|
|
5
|
+
* ID is already globally unique — so the adapter closes over `groupID` purely
|
|
6
|
+
* to give each row an owner: a deleted group's cursors go with it, rather than
|
|
7
|
+
* staying behind as unreachable rows nothing accounts for.
|
|
8
|
+
*
|
|
9
|
+
* `null` here means this device has never processed a frame on the topic, and
|
|
10
|
+
* the drain answers it by reading from the hub's oldest retained frame. Unlike
|
|
11
|
+
* the anchor, a wrong `null` costs a re-read, not a partition.
|
|
12
|
+
*
|
|
13
|
+
* Positions are opaque log positions: stored and returned verbatim, never
|
|
14
|
+
* compared or parsed here.
|
|
15
|
+
*/ export function createAppCursorStore(stores, groupID) {
|
|
16
|
+
return {
|
|
17
|
+
async load (topicID) {
|
|
18
|
+
const store = await getP2PStore(stores);
|
|
19
|
+
const row = await store.getAppCursor({
|
|
20
|
+
groupID,
|
|
21
|
+
topicID
|
|
22
|
+
});
|
|
23
|
+
return row?.position ?? null;
|
|
24
|
+
},
|
|
25
|
+
async save (topicID, position) {
|
|
26
|
+
const store = await getP2PStore(stores);
|
|
27
|
+
await store.putAppCursor({
|
|
28
|
+
groupID,
|
|
29
|
+
topicID,
|
|
30
|
+
position
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -1 +1,6 @@
|
|
|
1
|
-
export function serializeBroadcast(
|
|
1
|
+
export function serializeBroadcast(message) {
|
|
2
|
+
return new TextEncoder().encode(JSON.stringify(message));
|
|
3
|
+
}
|
|
4
|
+
export function deserializeBroadcast(bytes) {
|
|
5
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
6
|
+
}
|