@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,96 @@
|
|
|
1
|
+
import { toISO } from '../context/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Emit the local `P2PEventEmitter` events for a remote-driven control-ledger
|
|
4
|
+
* entry that a receiving peer just applied, so GraphQL subscribers see
|
|
5
|
+
* remote circle/group changes live — the same events a local producer fires.
|
|
6
|
+
*
|
|
7
|
+
* Only `ledger:entry` projections surface as `affected`; every other applied
|
|
8
|
+
* message (delegation grant/revoke, advisory leave) already emits its own event
|
|
9
|
+
* from inside `processBroadcast`, so this covers exactly the circle/group
|
|
10
|
+
* projection rows the apply core returns rather than emits. The payloads are
|
|
11
|
+
* built from `affected.row`, which `processBroadcast` snapshots at the right
|
|
12
|
+
* moment (after-state for create/change, before-state for delete/remove).
|
|
13
|
+
*/ export async function emitLedgerAffectedEvents(emitter, result) {
|
|
14
|
+
if (!result.applied || result.affected == null || result.affected.kind !== 'ledger') {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const { ledger } = result.affected;
|
|
18
|
+
switch(ledger.entry){
|
|
19
|
+
case 'circle.def':
|
|
20
|
+
{
|
|
21
|
+
const { row } = ledger;
|
|
22
|
+
// No active pre-state means a create; otherwise a change. Match the
|
|
23
|
+
// producer: a catalogs-only change emits `circleCatalogsChanged`, any other
|
|
24
|
+
// change emits `circleDataChanged`. The `*Changed` payloads carry `circleID`.
|
|
25
|
+
if (!ledger.preExisted) {
|
|
26
|
+
await emitter.emit('circleCreated', {
|
|
27
|
+
id: row.id,
|
|
28
|
+
groupID: row.group_id,
|
|
29
|
+
name: row.name,
|
|
30
|
+
description: row.description,
|
|
31
|
+
createdAt: toISO(row.created_at)
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const eventName = ledger.catalogsChanged ? 'circleCatalogsChanged' : 'circleDataChanged';
|
|
36
|
+
await emitter.emit(eventName, {
|
|
37
|
+
id: row.id,
|
|
38
|
+
groupID: row.group_id,
|
|
39
|
+
name: row.name,
|
|
40
|
+
description: row.description,
|
|
41
|
+
createdAt: toISO(row.created_at),
|
|
42
|
+
circleID: row.id
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
case 'circle.def.removed':
|
|
47
|
+
{
|
|
48
|
+
const { row } = ledger;
|
|
49
|
+
await emitter.emit('circleDeleted', {
|
|
50
|
+
id: row.id,
|
|
51
|
+
groupID: row.group_id,
|
|
52
|
+
name: row.name,
|
|
53
|
+
description: row.description,
|
|
54
|
+
createdAt: toISO(row.created_at)
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
case 'circle.member':
|
|
59
|
+
{
|
|
60
|
+
const { row } = ledger;
|
|
61
|
+
// Both a new membership and a role change map here: there is no distinct
|
|
62
|
+
// "role changed" circle-member event, mirroring the producer.
|
|
63
|
+
await emitter.emit('circleMemberAdded', {
|
|
64
|
+
circleID: row.circle_id,
|
|
65
|
+
memberDID: row.member_did,
|
|
66
|
+
role: row.role,
|
|
67
|
+
createdAt: toISO(row.created_at)
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
case 'circle.member.removed':
|
|
72
|
+
{
|
|
73
|
+
const { row } = ledger;
|
|
74
|
+
await emitter.emit('circleMemberRemoved', {
|
|
75
|
+
circleID: row.circle_id,
|
|
76
|
+
memberDID: row.member_did,
|
|
77
|
+
role: row.role,
|
|
78
|
+
createdAt: toISO(row.created_at)
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
case 'group.settings':
|
|
83
|
+
{
|
|
84
|
+
const { row } = ledger;
|
|
85
|
+
await emitter.emit('groupDataChanged', {
|
|
86
|
+
id: row.id,
|
|
87
|
+
name: row.name,
|
|
88
|
+
description: row.description,
|
|
89
|
+
createdBy: row.created_by,
|
|
90
|
+
createdAt: toISO(row.created_at),
|
|
91
|
+
groupID: row.id
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type OwnIdentity } from '@kokuin/token';
|
|
2
|
+
/**
|
|
3
|
+
* A control-ledger claim. The payload of a signed ledger token: `type` selects
|
|
4
|
+
* the reducer, `subject` is the entity the claim is about, `value` carries the
|
|
5
|
+
* claim data, and `hlc` orders it. The authenticated author is never part of
|
|
6
|
+
* this shape — it comes from the verified token's `iss`.
|
|
7
|
+
*/
|
|
8
|
+
export type LedgerEntry<TValue = unknown> = {
|
|
9
|
+
type: string;
|
|
10
|
+
subject: string;
|
|
11
|
+
value: TValue;
|
|
12
|
+
hlc: string;
|
|
13
|
+
};
|
|
14
|
+
/** A ledger entry whose token signature has been cryptographically verified. */
|
|
15
|
+
export type VerifiedLedgerEntry<TValue = unknown> = {
|
|
16
|
+
/** Authenticated author DID (the verified token issuer), normalized. */
|
|
17
|
+
issuer: string;
|
|
18
|
+
entry: LedgerEntry<TValue>;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Sign a ledger entry. The signer fills `iss` with its DID, so the signature
|
|
22
|
+
* covers every claim field and binds the author. Returns the stringified token.
|
|
23
|
+
*/
|
|
24
|
+
export declare function signLedgerEntry(identity: OwnIdentity, entry: LedgerEntry): Promise<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Verify a signed ledger token and extract its claim. Returns `null` (never
|
|
27
|
+
* throws) when the token is unparseable, unsigned (`alg: 'none'` — `verifyToken`
|
|
28
|
+
* returns those without checking a signature, so an attacker could forge an
|
|
29
|
+
* arbitrary `iss`), or structurally malformed. The claim fields come from the
|
|
30
|
+
* verified payload only; the issuer is the normalized verified `iss`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function verifyLedgerEntry<TValue = unknown>(token: string): Promise<VerifiedLedgerEntry<TValue> | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Content-addressed digest of a signed ledger token, used as the append-only
|
|
35
|
+
* store's dedup key. Multibase-encoded SHA-256 multihash over the token bytes.
|
|
36
|
+
*/
|
|
37
|
+
export declare function ledgerEntryDigest(signedToken: string): string;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { encodeMultibase, isVerifiedToken, multihashSHA256, normalizeDID, stringifyToken, verifyToken } from '@kokuin/token';
|
|
2
|
+
const textEncoder = new TextEncoder();
|
|
3
|
+
/**
|
|
4
|
+
* Sign a ledger entry. The signer fills `iss` with its DID, so the signature
|
|
5
|
+
* covers every claim field and binds the author. Returns the stringified token.
|
|
6
|
+
*/ export async function signLedgerEntry(identity, entry) {
|
|
7
|
+
const signed = await identity.signToken({
|
|
8
|
+
type: entry.type,
|
|
9
|
+
subject: entry.subject,
|
|
10
|
+
value: entry.value,
|
|
11
|
+
hlc: entry.hlc
|
|
12
|
+
}, // Embed the long-form DID so each entry is self-verifying offline: the
|
|
13
|
+
// ledger is the source of truth and receivers fold it long after first
|
|
14
|
+
// contact, when the author's DID document may no longer be cached. No-op
|
|
15
|
+
// for did:key (long form === id).
|
|
16
|
+
{
|
|
17
|
+
embedLongForm: true
|
|
18
|
+
});
|
|
19
|
+
return stringifyToken(signed);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Verify a signed ledger token and extract its claim. Returns `null` (never
|
|
23
|
+
* throws) when the token is unparseable, unsigned (`alg: 'none'` — `verifyToken`
|
|
24
|
+
* returns those without checking a signature, so an attacker could forge an
|
|
25
|
+
* arbitrary `iss`), or structurally malformed. The claim fields come from the
|
|
26
|
+
* verified payload only; the issuer is the normalized verified `iss`.
|
|
27
|
+
*/ export async function verifyLedgerEntry(token) {
|
|
28
|
+
let verified;
|
|
29
|
+
try {
|
|
30
|
+
verified = await verifyToken(token);
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
if (!isVerifiedToken(verified)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const { iss, type, subject, value, hlc } = verified.payload;
|
|
38
|
+
if (typeof type !== 'string' || typeof subject !== 'string' || typeof hlc !== 'string') {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
issuer: normalizeDID(iss),
|
|
43
|
+
entry: {
|
|
44
|
+
type,
|
|
45
|
+
subject,
|
|
46
|
+
value,
|
|
47
|
+
hlc
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Content-addressed digest of a signed ledger token, used as the append-only
|
|
53
|
+
* store's dedup key. Multibase-encoded SHA-256 multihash over the token bytes.
|
|
54
|
+
*/ export function ledgerEntryDigest(signedToken) {
|
|
55
|
+
return encodeMultibase(multihashSHA256(textEncoder.encode(signedToken)));
|
|
56
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { GroupAnchor } from './group-anchor.js';
|
|
2
|
+
import type { VerifiedLedgerEntry } from './ledger-entry.js';
|
|
3
|
+
/**
|
|
4
|
+
* A per-type projection over the control ledger. The fold stays free of any
|
|
5
|
+
* admin/circle semantics: a reducer owns its own initial state (derived from
|
|
6
|
+
* the genesis anchor), its authority rule, and its fold step, so a new ledger
|
|
7
|
+
* type plugs in as a parameter with zero edits to {@link foldLedger}.
|
|
8
|
+
*/
|
|
9
|
+
export type LedgerReducer<TValue, TState> = {
|
|
10
|
+
/** Ledger entry `type` this reducer projects; entries of any other type are dropped. */
|
|
11
|
+
type: string;
|
|
12
|
+
/**
|
|
13
|
+
* Initial fold state, derived from the genesis anchor. The anchor is the
|
|
14
|
+
* authenticated epoch-0 root (e.g. the creator DID is the first admin), so
|
|
15
|
+
* seeding from it is what makes the very first claims evaluable.
|
|
16
|
+
*/
|
|
17
|
+
seed(anchor: GroupAnchor): TState;
|
|
18
|
+
/** Is the verified issuer allowed to make this claim, given the state so far? */
|
|
19
|
+
verifyAuthority(verified: VerifiedLedgerEntry<TValue>, stateSoFar: TState): boolean;
|
|
20
|
+
/** Fold step: return the next state after applying an authorized claim. */
|
|
21
|
+
apply(verified: VerifiedLedgerEntry<TValue>, stateSoFar: TState): TState;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* A verified entry paired with its content-addressed id. The id is the
|
|
25
|
+
* total-order tie-breaker when two entries share an `hlc`, and identifies the
|
|
26
|
+
* entry in drop notices.
|
|
27
|
+
*/
|
|
28
|
+
export type FoldInput<TValue = unknown> = {
|
|
29
|
+
verified: VerifiedLedgerEntry<TValue>;
|
|
30
|
+
entryID: string;
|
|
31
|
+
};
|
|
32
|
+
/** Why an entry was skipped during the fold, for an optional observer. */
|
|
33
|
+
export type FoldDrop = {
|
|
34
|
+
entryID: string;
|
|
35
|
+
type: string;
|
|
36
|
+
reason: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Replay the ledger into a single reducer's projection.
|
|
40
|
+
*
|
|
41
|
+
* Signatures are verified at ingest, not here — this fold evaluates AUTHORITY:
|
|
42
|
+
* whether each issuer was allowed to make its claim given the state accumulated
|
|
43
|
+
* from strictly-earlier entries. Evaluating against state-so-far (never the
|
|
44
|
+
* final state) is what makes rotation sound: a key authorized at HLC `t` can be
|
|
45
|
+
* revoked at `t+1` without retroactively invalidating its earlier claims.
|
|
46
|
+
*
|
|
47
|
+
* Deterministic on any arrival order: it seeds from the genesis anchor, sorts a
|
|
48
|
+
* copy of the entries by the total order `(hlc, entryID)`, then folds. Pure — no
|
|
49
|
+
* clock, no randomness, no I/O, and the input array is never mutated. A claim of
|
|
50
|
+
* an unrelated type or one whose authority fails is dropped (never thrown) so a
|
|
51
|
+
* single bad entry can never abort the projection. Drops are silent unless an
|
|
52
|
+
* `onDrop` observer is supplied — the fold runs on every authority check, where
|
|
53
|
+
* authority-failed drops are expected, so the caller decides whether to surface
|
|
54
|
+
* them rather than the fold emitting to the console.
|
|
55
|
+
*/
|
|
56
|
+
export declare function foldLedger<TValue, TState>(entries: Array<FoldInput<TValue>>, anchor: GroupAnchor, reducer: LedgerReducer<TValue, TState>, onDrop?: (drop: FoldDrop) => void): TState;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** Total order over the ledger: HLC first, then content-addressed id to break ties. */ function compareFoldInputs(a, b) {
|
|
2
|
+
if (a.verified.entry.hlc !== b.verified.entry.hlc) {
|
|
3
|
+
return a.verified.entry.hlc < b.verified.entry.hlc ? -1 : 1;
|
|
4
|
+
}
|
|
5
|
+
if (a.entryID === b.entryID) {
|
|
6
|
+
return 0;
|
|
7
|
+
}
|
|
8
|
+
return a.entryID < b.entryID ? -1 : 1;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Replay the ledger into a single reducer's projection.
|
|
12
|
+
*
|
|
13
|
+
* Signatures are verified at ingest, not here — this fold evaluates AUTHORITY:
|
|
14
|
+
* whether each issuer was allowed to make its claim given the state accumulated
|
|
15
|
+
* from strictly-earlier entries. Evaluating against state-so-far (never the
|
|
16
|
+
* final state) is what makes rotation sound: a key authorized at HLC `t` can be
|
|
17
|
+
* revoked at `t+1` without retroactively invalidating its earlier claims.
|
|
18
|
+
*
|
|
19
|
+
* Deterministic on any arrival order: it seeds from the genesis anchor, sorts a
|
|
20
|
+
* copy of the entries by the total order `(hlc, entryID)`, then folds. Pure — no
|
|
21
|
+
* clock, no randomness, no I/O, and the input array is never mutated. A claim of
|
|
22
|
+
* an unrelated type or one whose authority fails is dropped (never thrown) so a
|
|
23
|
+
* single bad entry can never abort the projection. Drops are silent unless an
|
|
24
|
+
* `onDrop` observer is supplied — the fold runs on every authority check, where
|
|
25
|
+
* authority-failed drops are expected, so the caller decides whether to surface
|
|
26
|
+
* them rather than the fold emitting to the console.
|
|
27
|
+
*/ export function foldLedger(entries, anchor, reducer, onDrop) {
|
|
28
|
+
const ordered = [
|
|
29
|
+
...entries
|
|
30
|
+
].sort(compareFoldInputs);
|
|
31
|
+
let state = reducer.seed(anchor);
|
|
32
|
+
for (const { verified, entryID } of ordered){
|
|
33
|
+
const { entry } = verified;
|
|
34
|
+
if (entry.type !== reducer.type) {
|
|
35
|
+
onDrop?.({
|
|
36
|
+
entryID,
|
|
37
|
+
type: entry.type,
|
|
38
|
+
reason: `unknown type for reducer '${reducer.type}'`
|
|
39
|
+
});
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (!reducer.verifyAuthority(verified, state)) {
|
|
43
|
+
onDrop?.({
|
|
44
|
+
entryID,
|
|
45
|
+
type: entry.type,
|
|
46
|
+
reason: `issuer '${verified.issuer}' not authorized`
|
|
47
|
+
});
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
state = reducer.apply(verified, state);
|
|
51
|
+
}
|
|
52
|
+
return state;
|
|
53
|
+
}
|
package/lib/groups/manager.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import type { Identity, OwnIdentity } from '@enkaku/token';
|
|
1
|
+
import { type Identity, type OwnIdentity } from '@kokuin/token';
|
|
3
2
|
import type { StoreProvider } from '@kubun/db';
|
|
4
|
-
import
|
|
3
|
+
import { HLC } from '@kubun/hlc';
|
|
4
|
+
import { type GroupPermission, type Invite, type KeyPackageBundle } from '@kumiai/mls';
|
|
5
5
|
import type { GroupHandleRegistry } from './group-handle-registry.js';
|
|
6
6
|
import type { SuggestedHub } from './invite-payload.js';
|
|
7
7
|
export type GroupManagerParams = {
|
|
@@ -13,6 +13,14 @@ export type GroupManagerParams = {
|
|
|
13
13
|
* the registry's per-group mutex with write-through persist.
|
|
14
14
|
*/
|
|
15
15
|
registry: GroupHandleRegistry;
|
|
16
|
+
/**
|
|
17
|
+
* Device-wide monotonic clock for stamping group/circle/member metadata.
|
|
18
|
+
* Required: production passes the engine's single instance so every write
|
|
19
|
+
* from this device — graph mutations and group metadata alike — advances one
|
|
20
|
+
* shared clock, preventing two same-millisecond writes from minting identical
|
|
21
|
+
* timestamps (the second would silently lose under last-writer-wins).
|
|
22
|
+
*/
|
|
23
|
+
hlc: HLC;
|
|
16
24
|
};
|
|
17
25
|
export type CreateGroupParams = {
|
|
18
26
|
stores: StoreProvider;
|
|
@@ -25,17 +33,6 @@ export type CreateGroupParams = {
|
|
|
25
33
|
export type CreateGroupResult = {
|
|
26
34
|
groupID: string;
|
|
27
35
|
};
|
|
28
|
-
export type UpdateGroupParams = {
|
|
29
|
-
stores: StoreProvider;
|
|
30
|
-
groupID: string;
|
|
31
|
-
update: {
|
|
32
|
-
name?: string;
|
|
33
|
-
description?: string;
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
|
-
export type UpdateGroupResult = {
|
|
37
|
-
broadcast: GroupBroadcastMessage;
|
|
38
|
-
};
|
|
39
36
|
export type InviteToGroupParams = {
|
|
40
37
|
stores: StoreProvider;
|
|
41
38
|
groupID: string;
|
|
@@ -46,7 +43,7 @@ export type InviteToGroupParams = {
|
|
|
46
43
|
};
|
|
47
44
|
export type InviteToGroupResult = {
|
|
48
45
|
invite: Invite;
|
|
49
|
-
/** Framed MLSMessage(Welcome) bytes for the invitee (`@
|
|
46
|
+
/** Framed MLSMessage(Welcome) bytes for the invitee (`@kumiai/mls`). */
|
|
50
47
|
welcomeMessage: Uint8Array;
|
|
51
48
|
/** Framed MLSMessage(Commit) bytes to fan out to existing members. */
|
|
52
49
|
commitMessage: Uint8Array;
|
|
@@ -54,7 +51,7 @@ export type InviteToGroupResult = {
|
|
|
54
51
|
* Ratchet tree from the post-commit handle. Required by `joinGroup`
|
|
55
52
|
* callers that build an invite payload — they no longer hold a
|
|
56
53
|
* `GroupHandle` reference, so the manager surfaces the tree directly.
|
|
57
|
-
* Typed `unknown` to match `@
|
|
54
|
+
* Typed `unknown` to match `@kumiai/mls`'s `ProcessWelcomeParams.ratchetTree`,
|
|
58
55
|
* which is opaque on that side too.
|
|
59
56
|
*/
|
|
60
57
|
ratchetTree: unknown;
|
|
@@ -84,6 +81,14 @@ export type RemoveGroupMemberParams = {
|
|
|
84
81
|
stores: StoreProvider;
|
|
85
82
|
groupID: string;
|
|
86
83
|
memberDID: string;
|
|
84
|
+
/**
|
|
85
|
+
* The HLC stamped by the caller for the roster tombstone. Lifted out of this
|
|
86
|
+
* method so the producer can stamp ONE HLC that orders both the MLS-roster
|
|
87
|
+
* tombstone here and the admin-role revocation it appends in the same
|
|
88
|
+
* transaction — the two must share a timestamp for the fold to drop exactly
|
|
89
|
+
* the ex-admin's later control entries.
|
|
90
|
+
*/
|
|
91
|
+
hlc: string;
|
|
87
92
|
};
|
|
88
93
|
export type RemoveGroupMemberResult = {
|
|
89
94
|
commitMessage: Uint8Array;
|
|
@@ -92,62 +97,20 @@ export type LeaveGroupParams = {
|
|
|
92
97
|
stores: StoreProvider;
|
|
93
98
|
groupID: string;
|
|
94
99
|
identity: OwnIdentity;
|
|
100
|
+
/**
|
|
101
|
+
* The HLC stamped by the caller for the self-removal tombstone. Lifted out of
|
|
102
|
+
* this method so the `leave` producer can stamp ONE HLC that orders both this
|
|
103
|
+
* self-tombstone and the admin-role self-revocation it appends in the same
|
|
104
|
+
* transaction — the two must share a timestamp for a receiver's fold to drop
|
|
105
|
+
* exactly the leaver's control entries signed at or after the departure.
|
|
106
|
+
*/
|
|
107
|
+
hlc: string;
|
|
95
108
|
};
|
|
96
109
|
export type LeaveGroupResult = undefined;
|
|
97
|
-
export type CreateCircleParams = {
|
|
98
|
-
stores: StoreProvider;
|
|
99
|
-
groupID: string;
|
|
100
|
-
name: string;
|
|
101
|
-
description?: string;
|
|
102
|
-
catalogIDs?: Array<string>;
|
|
103
|
-
};
|
|
104
|
-
export type CreateCircleResult = {
|
|
105
|
-
circleID: string;
|
|
106
|
-
broadcast: GroupBroadcastMessage;
|
|
107
|
-
};
|
|
108
|
-
export type UpdateCircleParams = {
|
|
109
|
-
stores: StoreProvider;
|
|
110
|
-
circleID: string;
|
|
111
|
-
update: {
|
|
112
|
-
name?: string;
|
|
113
|
-
description?: string;
|
|
114
|
-
catalogIDs?: Array<string>;
|
|
115
|
-
};
|
|
116
|
-
};
|
|
117
|
-
export type UpdateCircleResult = {
|
|
118
|
-
broadcast: GroupBroadcastMessage;
|
|
119
|
-
broadcasts: Array<GroupBroadcastMessage>;
|
|
120
|
-
};
|
|
121
|
-
export type DeleteCircleParams = {
|
|
122
|
-
stores: StoreProvider;
|
|
123
|
-
circleID: string;
|
|
124
|
-
};
|
|
125
|
-
export type DeleteCircleResult = {
|
|
126
|
-
broadcast: GroupBroadcastMessage;
|
|
127
|
-
};
|
|
128
|
-
export type AddCircleMemberParams = {
|
|
129
|
-
stores: StoreProvider;
|
|
130
|
-
groupID: string;
|
|
131
|
-
circleID: string;
|
|
132
|
-
memberDID: string;
|
|
133
|
-
role: 'admin' | 'member';
|
|
134
|
-
};
|
|
135
|
-
export type AddCircleMemberResult = {
|
|
136
|
-
broadcast: GroupBroadcastMessage;
|
|
137
|
-
};
|
|
138
|
-
export type RemoveCircleMemberParams = {
|
|
139
|
-
stores: StoreProvider;
|
|
140
|
-
circleID: string;
|
|
141
|
-
memberDID: string;
|
|
142
|
-
};
|
|
143
|
-
export type RemoveCircleMemberResult = {
|
|
144
|
-
broadcast: GroupBroadcastMessage;
|
|
145
|
-
};
|
|
146
110
|
export declare class GroupManager {
|
|
147
111
|
#private;
|
|
148
112
|
constructor(params: GroupManagerParams);
|
|
149
113
|
createGroup(params: CreateGroupParams): Promise<CreateGroupResult>;
|
|
150
|
-
updateGroup(params: UpdateGroupParams): Promise<UpdateGroupResult>;
|
|
151
114
|
inviteToGroup(params: InviteToGroupParams): Promise<InviteToGroupResult>;
|
|
152
115
|
joinGroup(params: JoinGroupParams): Promise<JoinGroupResult>;
|
|
153
116
|
removeMember(params: RemoveMemberParams): Promise<RemoveMemberResult>;
|
|
@@ -157,9 +120,4 @@ export declare class GroupManager {
|
|
|
157
120
|
epoch: number;
|
|
158
121
|
credential: string;
|
|
159
122
|
} | undefined>;
|
|
160
|
-
createCircle(params: CreateCircleParams): Promise<CreateCircleResult>;
|
|
161
|
-
updateCircle(params: UpdateCircleParams): Promise<UpdateCircleResult>;
|
|
162
|
-
deleteCircle(params: DeleteCircleParams): Promise<DeleteCircleResult>;
|
|
163
|
-
addCircleMember(params: AddCircleMemberParams): Promise<AddCircleMemberResult>;
|
|
164
|
-
removeCircleMember(params: RemoveCircleMemberParams): Promise<RemoveCircleMemberResult>;
|
|
165
123
|
}
|