@kubun/plugin-p2p 0.10.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/lib/context/delegation.js +130 -1
  2. package/lib/context/group.js +1646 -1
  3. package/lib/context/hub.js +151 -1
  4. package/lib/context/join.js +115 -1
  5. package/lib/context/peer.d.ts +92 -0
  6. package/lib/context/peer.js +1216 -0
  7. package/lib/context/require-admin.d.ts +57 -0
  8. package/lib/context/require-admin.js +57 -0
  9. package/lib/context/sync.d.ts +12 -0
  10. package/lib/context/sync.js +252 -1
  11. package/lib/context/types.d.ts +58 -22
  12. package/lib/context/types.js +48 -1
  13. package/lib/groups/access-default-token.d.ts +44 -0
  14. package/lib/groups/access-default-token.js +103 -0
  15. package/lib/groups/admin-roster.d.ts +55 -0
  16. package/lib/groups/admin-roster.js +69 -0
  17. package/lib/groups/anchor-store.d.ts +15 -0
  18. package/lib/groups/anchor-store.js +37 -0
  19. package/lib/groups/app-cursor-store.d.ts +17 -0
  20. package/lib/groups/app-cursor-store.js +34 -0
  21. package/lib/groups/broadcast-codec.js +6 -1
  22. package/lib/groups/broadcast.d.ts +238 -106
  23. package/lib/groups/broadcast.js +702 -1
  24. package/lib/groups/catalog-token.d.ts +50 -0
  25. package/lib/groups/catalog-token.js +96 -0
  26. package/lib/groups/circle-projection.d.ts +90 -0
  27. package/lib/groups/circle-projection.js +202 -0
  28. package/lib/groups/circle-reducers.d.ts +139 -0
  29. package/lib/groups/circle-reducers.js +128 -0
  30. package/lib/groups/commit-adoption.d.ts +77 -0
  31. package/lib/groups/commit-adoption.js +108 -0
  32. package/lib/groups/commit-journal.d.ts +14 -0
  33. package/lib/groups/commit-journal.js +65 -0
  34. package/lib/groups/control-request.d.ts +66 -0
  35. package/lib/groups/control-request.js +101 -0
  36. package/lib/groups/events.d.ts +19 -2
  37. package/lib/groups/events.js +10 -1
  38. package/lib/groups/group-anchor.d.ts +24 -0
  39. package/lib/groups/group-anchor.js +22 -0
  40. package/lib/groups/group-crypto.d.ts +42 -0
  41. package/lib/groups/group-crypto.js +159 -0
  42. package/lib/groups/group-handle-registry.d.ts +138 -9
  43. package/lib/groups/group-handle-registry.js +533 -1
  44. package/lib/groups/group-handlers.d.ts +30 -0
  45. package/lib/groups/group-handlers.js +218 -0
  46. package/lib/groups/group-health-monitor.d.ts +11 -0
  47. package/lib/groups/group-health-monitor.js +139 -1
  48. package/lib/groups/group-mls.d.ts +66 -0
  49. package/lib/groups/group-mls.js +696 -0
  50. package/lib/groups/group-peer-manager.d.ts +301 -0
  51. package/lib/groups/group-peer-manager.js +1336 -0
  52. package/lib/groups/group-protocols.d.ts +754 -0
  53. package/lib/groups/group-protocols.js +448 -0
  54. package/lib/groups/invite-payload.d.ts +49 -1
  55. package/lib/groups/invite-payload.js +26 -1
  56. package/lib/groups/join-request-identity.d.ts +21 -0
  57. package/lib/groups/join-request-identity.js +43 -0
  58. package/lib/groups/join-utils.d.ts +61 -0
  59. package/lib/groups/join-utils.js +366 -1
  60. package/lib/groups/ledger-affected-events.d.ts +20 -0
  61. package/lib/groups/ledger-affected-events.js +85 -0
  62. package/lib/groups/ledger-commit-fold.d.ts +107 -0
  63. package/lib/groups/ledger-commit-fold.js +167 -0
  64. package/lib/groups/ledger-ingest.d.ts +124 -0
  65. package/lib/groups/ledger-ingest.js +212 -0
  66. package/lib/groups/ledger.d.ts +44 -0
  67. package/lib/groups/ledger.js +30 -0
  68. package/lib/groups/manager.d.ts +217 -85
  69. package/lib/groups/manager.js +798 -1
  70. package/lib/groups/mls-codec.d.ts +28 -7
  71. package/lib/groups/mls-codec.js +33 -1
  72. package/lib/groups/mls-encryptor.d.ts +2 -2
  73. package/lib/groups/mls-encryptor.js +33 -1
  74. package/lib/groups/mls-group-handle.d.ts +37 -2
  75. package/lib/groups/mls-group-handle.js +18 -1
  76. package/lib/groups/mls-json.js +17 -1
  77. package/lib/groups/mls-receive-errors.d.ts +27 -0
  78. package/lib/groups/mls-receive-errors.js +39 -0
  79. package/lib/groups/mls-state.d.ts +6 -4
  80. package/lib/groups/mls-state.js +79 -1
  81. package/lib/groups/peer-presence.d.ts +90 -0
  82. package/lib/groups/peer-presence.js +295 -0
  83. package/lib/groups/peer-selection.d.ts +37 -0
  84. package/lib/groups/peer-selection.js +72 -0
  85. package/lib/groups/roster-projection.d.ts +53 -0
  86. package/lib/groups/roster-projection.js +65 -0
  87. package/lib/groups/store-received-grant.d.ts +7 -3
  88. package/lib/groups/store-received-grant.js +161 -1
  89. package/lib/groups/store-received-revocation.d.ts +19 -16
  90. package/lib/groups/store-received-revocation.js +83 -1
  91. package/lib/hub/ack-backoff.d.ts +22 -0
  92. package/lib/hub/ack-backoff.js +88 -0
  93. package/lib/hub/http-client.d.ts +18 -10
  94. package/lib/hub/http-client.js +15 -1
  95. package/lib/hub/hub-like.d.ts +75 -0
  96. package/lib/hub/hub-like.js +1022 -0
  97. package/lib/hub/loopback-log-hub.d.ts +20 -0
  98. package/lib/hub/loopback-log-hub.js +178 -0
  99. package/lib/hub/manager.d.ts +25 -40
  100. package/lib/hub/manager.js +155 -1
  101. package/lib/hub/peer-scoped-hub-view.d.ts +35 -0
  102. package/lib/hub/peer-scoped-hub-view.js +92 -0
  103. package/lib/hub/sender-scoped-hub-view.d.ts +41 -0
  104. package/lib/hub/sender-scoped-hub-view.js +59 -0
  105. package/lib/hub/server-did.d.ts +67 -0
  106. package/lib/hub/server-did.js +131 -0
  107. package/lib/hub/wiring.d.ts +77 -51
  108. package/lib/hub/wiring.js +189 -1
  109. package/lib/index.d.ts +58 -22
  110. package/lib/index.js +563 -1
  111. package/lib/peer/connection-registry.d.ts +45 -0
  112. package/lib/peer/connection-registry.js +52 -0
  113. package/lib/peer/handlers.d.ts +69 -0
  114. package/lib/peer/handlers.js +110 -0
  115. package/lib/protocol.d.ts +214 -1
  116. package/lib/protocol.js +519 -1
  117. package/lib/schema.d.ts +3 -1
  118. package/lib/schema.js +1150 -39
  119. package/lib/sync/access-default-sender.d.ts +23 -0
  120. package/lib/sync/access-default-sender.js +80 -0
  121. package/lib/sync/authorize.d.ts +58 -4
  122. package/lib/sync/authorize.js +114 -1
  123. package/lib/sync/broadcast-queue.d.ts +3 -4
  124. package/lib/sync/broadcast-queue.js +182 -1
  125. package/lib/sync/broadcast-sender.js +179 -1
  126. package/lib/sync/catalog-scope.d.ts +8 -7
  127. package/lib/sync/catalog-scope.js +84 -1
  128. package/lib/sync/errors.d.ts +12 -2
  129. package/lib/sync/errors.js +29 -1
  130. package/lib/sync/forwarder.d.ts +13 -19
  131. package/lib/sync/forwarder.js +271 -1
  132. package/lib/sync/handlers.js +200 -1
  133. package/lib/sync/hub-tunnel-sync-listener.d.ts +19 -5
  134. package/lib/sync/hub-tunnel-sync-listener.js +261 -1
  135. package/lib/sync/hub-tunnel-sync-provider.d.ts +34 -14
  136. package/lib/sync/hub-tunnel-sync-provider.js +113 -1
  137. package/lib/sync/merkle-apply.d.ts +5 -0
  138. package/lib/sync/merkle-apply.js +194 -1
  139. package/lib/sync/merkle-channel.d.ts +11 -3
  140. package/lib/sync/merkle-channel.js +0 -0
  141. package/lib/sync/merkle-tree.js +120 -1
  142. package/lib/sync/peer-registry.d.ts +15 -11
  143. package/lib/sync/peer-registry.js +82 -1
  144. package/lib/sync/receive-access-gate.js +24 -1
  145. package/lib/sync/scope-resolver.js +41 -1
  146. package/lib/sync/sync-client.d.ts +14 -4
  147. package/lib/sync/sync-client.js +254 -1
  148. package/lib/sync/sync-manager.d.ts +24 -6
  149. package/lib/sync/sync-manager.js +301 -1
  150. package/lib/sync/tunnel-listeners.d.ts +52 -0
  151. package/lib/sync/tunnel-listeners.js +175 -0
  152. package/lib/sync/tunnel-observability.d.ts +25 -0
  153. package/lib/sync/tunnel-observability.js +63 -0
  154. package/lib/sync/tunnel-topics.d.ts +20 -0
  155. package/lib/sync/tunnel-topics.js +28 -0
  156. package/lib/types.d.ts +704 -22
  157. package/lib/types.js +1 -1
  158. package/lib/util/handler-error.d.ts +16 -0
  159. package/lib/util/handler-error.js +64 -0
  160. package/lib/util/mutex.d.ts +14 -1
  161. package/lib/util/mutex.js +44 -1
  162. package/lib/utils.js +21 -1
  163. package/package.json +58 -47
  164. package/lib/groups/broadcast-service.d.ts +0 -263
  165. package/lib/groups/broadcast-service.js +0 -1
  166. package/lib/groups/rejoin-codec.d.ts +0 -14
  167. package/lib/groups/rejoin-codec.js +0 -1
  168. package/lib/groups/wire-frame.d.ts +0 -34
  169. package/lib/groups/wire-frame.js +0 -1
  170. package/lib/hub/circle-catchup-requester.d.ts +0 -58
  171. package/lib/hub/circle-catchup-requester.js +0 -1
  172. package/lib/hub/circle-catchup-responder.d.ts +0 -50
  173. package/lib/hub/circle-catchup-responder.js +0 -1
  174. package/lib/hub/connection-pool.d.ts +0 -43
  175. package/lib/hub/connection-pool.js +0 -1
  176. package/lib/hub/did-observing-transport.d.ts +0 -64
  177. package/lib/hub/did-observing-transport.js +0 -1
  178. package/lib/hub/epoch-stale-detector.d.ts +0 -18
  179. package/lib/hub/epoch-stale-detector.js +0 -1
  180. package/lib/hub/errors.d.ts +0 -30
  181. package/lib/hub/errors.js +0 -1
  182. package/lib/hub/forward-remote-broadcast.d.ts +0 -15
  183. package/lib/hub/forward-remote-broadcast.js +0 -1
  184. package/lib/hub/group-channel.d.ts +0 -89
  185. package/lib/hub/group-channel.js +0 -1
  186. package/lib/hub/hub-connection.d.ts +0 -96
  187. package/lib/hub/hub-connection.js +0 -1
  188. package/lib/hub/receive-handler.d.ts +0 -51
  189. package/lib/hub/receive-handler.js +0 -1
  190. package/lib/hub/rejoin-manager.d.ts +0 -78
  191. package/lib/hub/rejoin-manager.js +0 -1
  192. package/lib/hub/rejoin-responder.d.ts +0 -32
  193. package/lib/hub/rejoin-responder.js +0 -1
  194. package/lib/hub/relay-manager.d.ts +0 -142
  195. package/lib/hub/relay-manager.js +0 -1
  196. package/lib/hub/send-handler.d.ts +0 -40
  197. package/lib/hub/send-handler.js +0 -1
  198. package/lib/hub/tunnel-inbox.d.ts +0 -20
  199. package/lib/hub/tunnel-inbox.js +0 -1
  200. package/lib/hub/wait-for-gate.d.ts +0 -14
  201. package/lib/hub/wait-for-gate.js +0 -1
@@ -1,7 +1,28 @@
1
- import type { GroupHandle } from '@enkaku/group';
2
- export type MLSEncryptFramedResult = {
3
- framed: Uint8Array;
4
- consumed: Array<Uint8Array>;
5
- };
6
- export declare function mlsEncryptFramed(handle: GroupHandle, plaintext: Uint8Array): Promise<MLSEncryptFramedResult>;
7
- export declare function mlsDecryptFramed(handle: GroupHandle, framed: Uint8Array): Promise<Uint8Array>;
1
+ import type { GroupHandle } from '@kumiai/mls';
2
+ /**
3
+ * Encrypt an application message for the group. `@kumiai/mls` returns framed MLS
4
+ * wire bytes, so kubun puts those on the wire directly — no JSON envelope of its
5
+ * own, and no retired secret material crossing the boundary.
6
+ */
7
+ export declare function mlsEncryptFramed(handle: GroupHandle, plaintext: Uint8Array): Promise<Uint8Array>;
8
+ /**
9
+ * Decrypt a received application message and recover WHO SENT IT.
10
+ *
11
+ * `handle.decrypt` rather than `processMessage`, which returns an application
12
+ * message's plaintext with no sender. The sender is AUTHENTICATED, not claimed:
13
+ * the leaf index rides sender-data encrypted under the epoch's sender-data
14
+ * secret, and the body opens only under the ratchet key derived at that leaf, so
15
+ * a frame naming a leaf it was not sealed at does not open at all.
16
+ *
17
+ * A missing sender is an ERROR, never a filled-in blank. Upstream types
18
+ * `senderDID` optional because the authenticated leaf may hold no parsable
19
+ * credential — "I cannot name the author", never "there is no author". Any
20
+ * placeholder (empty string, a transport-claimed DID) would hand callers an
21
+ * unauthenticated value wearing an authenticated one's type, so the open fails
22
+ * instead. Callers already treat a throw here as ordinary control flow:
23
+ * `decrypt` throws for every epoch but its own.
24
+ */
25
+ export declare function mlsDecryptFramed(handle: GroupHandle, framed: Uint8Array): Promise<{
26
+ payload: Uint8Array;
27
+ senderDID: string;
28
+ }>;
@@ -1 +1,33 @@
1
- import{replacer as e,reviver as r}from"./mls-json.js";let t=new TextEncoder,n=new TextDecoder;export async function mlsEncryptFramed(r,n){let{message:c,consumed:o}=await r.encrypt(n);return{framed:t.encode(JSON.stringify(c,e)),consumed:o}}export async function mlsDecryptFramed(e,t){let c=JSON.parse(n.decode(t),r);return await e.decrypt(c)}
1
+ /**
2
+ * Encrypt an application message for the group. `@kumiai/mls` returns framed MLS
3
+ * wire bytes, so kubun puts those on the wire directly — no JSON envelope of its
4
+ * own, and no retired secret material crossing the boundary.
5
+ */ export async function mlsEncryptFramed(handle, plaintext) {
6
+ return await handle.encrypt(plaintext);
7
+ }
8
+ /**
9
+ * Decrypt a received application message and recover WHO SENT IT.
10
+ *
11
+ * `handle.decrypt` rather than `processMessage`, which returns an application
12
+ * message's plaintext with no sender. The sender is AUTHENTICATED, not claimed:
13
+ * the leaf index rides sender-data encrypted under the epoch's sender-data
14
+ * secret, and the body opens only under the ratchet key derived at that leaf, so
15
+ * a frame naming a leaf it was not sealed at does not open at all.
16
+ *
17
+ * A missing sender is an ERROR, never a filled-in blank. Upstream types
18
+ * `senderDID` optional because the authenticated leaf may hold no parsable
19
+ * credential — "I cannot name the author", never "there is no author". Any
20
+ * placeholder (empty string, a transport-claimed DID) would hand callers an
21
+ * unauthenticated value wearing an authenticated one's type, so the open fails
22
+ * instead. Callers already treat a throw here as ordinary control flow:
23
+ * `decrypt` throws for every epoch but its own.
24
+ */ export async function mlsDecryptFramed(handle, framed) {
25
+ const { payload, senderDID } = await handle.decrypt(framed);
26
+ if (senderDID == null) {
27
+ throw new Error('MLS application message opened at a leaf with no nameable sender');
28
+ }
29
+ return {
30
+ payload,
31
+ senderDID
32
+ };
33
+ }
@@ -1,11 +1,11 @@
1
- import type { Encryptor } from '@enkaku/hub-tunnel';
1
+ import type { Encryptor } from '@kumiai/hub-tunnel';
2
2
  import type { GroupHandleRegistry } from './group-handle-registry.js';
3
3
  export type MLSEncryptorParams = {
4
4
  registry: GroupHandleRegistry;
5
5
  groupID: string;
6
6
  };
7
7
  /**
8
- * Adapts the `GroupHandleRegistry` to the `@enkaku/hub-tunnel` `Encryptor`
8
+ * Adapts the `GroupHandleRegistry` to the `@kumiai/hub-tunnel` `Encryptor`
9
9
  * interface. The registry's per-group mutex serializes encrypt + decrypt
10
10
  * across all encryptor instances bound to the same `(registry, groupID)`.
11
11
  *
@@ -1 +1,33 @@
1
- import{mlsDecryptFramed as r,mlsEncryptFramed as t}from"./mls-codec.js";export class MLSEncryptor{#r;#t;constructor(r){this.#r=r.registry,this.#t=r.groupID}encrypt(r){return this.#r.withHandle(this.#t,async s=>{let{framed:e}=await t(s,r);return e})}decrypt(t){return this.#r.withHandle(this.#t,s=>r(s,t))}}
1
+ import { mlsDecryptFramed, mlsEncryptFramed } from './mls-codec.js';
2
+ /**
3
+ * Adapts the `GroupHandleRegistry` to the `@kumiai/hub-tunnel` `Encryptor`
4
+ * interface. The registry's per-group mutex serializes encrypt + decrypt
5
+ * across all encryptor instances bound to the same `(registry, groupID)`.
6
+ *
7
+ * Wedge-protection note: encrypt persists the advanced ratchet immediately
8
+ * on callback success — BEFORE the wire send happens inside the hub-tunnel
9
+ * transport. If the wire send fails, the persisted state is already at
10
+ * generation N+1; the next encrypt advances to N+2 and generation N is
11
+ * "burned" without wire egress. This is the documented forward-secrecy
12
+ * trade-off for tunnel sessions; the broadcast path (`BroadcastService.
13
+ * prepareSend`) inverts the order by pulling send into the registry callback.
14
+ */ export class MLSEncryptor {
15
+ #registry;
16
+ #groupID;
17
+ constructor(params){
18
+ this.#registry = params.registry;
19
+ this.#groupID = params.groupID;
20
+ }
21
+ encrypt(plaintext) {
22
+ return this.#registry.withHandle(this.#groupID, (handle)=>mlsEncryptFramed(handle, plaintext));
23
+ }
24
+ // The `Encryptor` port is bytes-in/bytes-out, so the authenticated sender the
25
+ // open also recovers is discarded here. The sync-transport path this serves
26
+ // carries its own per-entry signed tokens and authorizes on those; it has no
27
+ // seam to receive a frame sender through. Dropping it is deliberate — not an
28
+ // oversight to be routed around by widening the port.
29
+ async decrypt(ciphertext) {
30
+ const { payload } = await this.#registry.withHandle(this.#groupID, (handle)=>mlsDecryptFramed(handle, ciphertext));
31
+ return payload;
32
+ }
33
+ }
@@ -1,4 +1,39 @@
1
- import { type GroupHandle } from '@enkaku/group';
1
+ import { type GroupHandle, type VerifiedLedgerEntry } from '@kumiai/mls';
2
2
  import { type SerializedMLSGroupState } from './mls-state.js';
3
3
  export type MLSGroupHandle = GroupHandle;
4
- export declare function restoreMLSGroupHandle(serialized: SerializedMLSGroupState): Promise<MLSGroupHandle>;
4
+ /**
5
+ * Fetch control-ledger entry bodies a handle does not hold, by content id.
6
+ *
7
+ * A commit's envelope names the entries it enacts by content id, not by body, and
8
+ * the ONLY source for a body a receiver lacks is the commit's own frame, wired in
9
+ * for the duration of applying it. The answer is untrusted: only a token whose
10
+ * digest matches the requested id and whose signature verifies is kept.
11
+ *
12
+ * A handle built without a resolver resolves nothing, deliberately: kubun's store
13
+ * writes its copy AFTER apply, so reading it would answer every miss with an empty
14
+ * list — indistinguishable from a resolver that ran. Passing none instead lets
15
+ * `MissingLedgerEntriesError` reach the lane, which drops the frame as poison.
16
+ */
17
+ export type LedgerEntryResolver = (ids: Array<string>) => Promise<Array<string>>;
18
+ /**
19
+ * Receive the notarized non-`group.role` entries an accepted commit carried, in
20
+ * envelope order. Synchronous by contract, so a sink can only COLLECT: any store
21
+ * write the entries drive must run after `processMessage` returns.
22
+ */
23
+ export type LedgerEntrySink = (entries: Array<VerifiedLedgerEntry>) => void;
24
+ export type RestoreMLSGroupHandleParams = {
25
+ serialized: SerializedMLSGroupState;
26
+ /**
27
+ * Resolves the entry bodies a received commit's envelope names, from the
28
+ * commit's own frame. Supplied only while the restored handle is applying such
29
+ * a commit.
30
+ */
31
+ resolveLedgerEntries?: LedgerEntryResolver;
32
+ /**
33
+ * Collects the notarized entries an accepted commit surfaces on this handle,
34
+ * so the caller can fold them into the projections once `processMessage` has
35
+ * returned.
36
+ */
37
+ onLedgerEntries?: LedgerEntrySink;
38
+ };
39
+ export declare function restoreMLSGroupHandle(params: RestoreMLSGroupHandleParams): Promise<MLSGroupHandle>;
@@ -1 +1,18 @@
1
- import{restoreGroup as r}from"@enkaku/group";import{deserializeMLSGroupState as o}from"./mls-state.js";export async function restoreMLSGroupHandle(t){let{state:e,credential:n,rootCapability:m}=o(t);return r({state:e,credential:n,rootCapability:m})}
1
+ import { restoreGroup } from '@kumiai/mls';
2
+ import { deserializeMLSGroupState } from './mls-state.js';
3
+ export async function restoreMLSGroupHandle(params) {
4
+ const { state, credential, ledgerEntries } = deserializeMLSGroupState(params.serialized);
5
+ return await restoreGroup({
6
+ state,
7
+ credential,
8
+ ledgerEntries,
9
+ options: {
10
+ ...params.resolveLedgerEntries != null ? {
11
+ resolveLedgerEntries: params.resolveLedgerEntries
12
+ } : {},
13
+ ...params.onLedgerEntries != null ? {
14
+ onLedgerEntries: params.onLedgerEntries
15
+ } : {}
16
+ }
17
+ });
18
+ }
@@ -1 +1,17 @@
1
- import{fromB64 as t,toB64 as e}from"@enkaku/codec";let r="\0bi:",i="\0u8:";export function replacer(t,n){if("bigint"==typeof n)return`${r}${n}`;let o=this[t];return o instanceof Uint8Array?`${i}${e(o)}`:n}export function reviver(e,n){return"string"!=typeof n?n:n.startsWith(r)?BigInt(n.slice(r.length)):n.startsWith(i)?t(n.slice(i.length)):n}
1
+ import { fromB64, toB64 } from '@sozai/codec';
2
+ const BIGINT_PREFIX = '\x00bi:';
3
+ const BYTES_PREFIX = '\x00u8:';
4
+ export function replacer(_key, value) {
5
+ if (typeof value === 'bigint') return `${BIGINT_PREFIX}${value}`;
6
+ const raw = this[_key];
7
+ if (raw instanceof Uint8Array) {
8
+ return `${BYTES_PREFIX}${toB64(raw)}`;
9
+ }
10
+ return value;
11
+ }
12
+ export function reviver(_key, value) {
13
+ if (typeof value !== 'string') return value;
14
+ if (value.startsWith(BIGINT_PREFIX)) return BigInt(value.slice(BIGINT_PREFIX.length));
15
+ if (value.startsWith(BYTES_PREFIX)) return fromB64(value.slice(BYTES_PREFIX.length));
16
+ return value;
17
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Errors raised while applying a received MLS handshake Commit to the local
3
+ * group state. Kept in a standalone, transport-independent module so the
4
+ * `GroupMLS` adapter and any other surviving receive path can import them
5
+ * without depending on a specific transport implementation.
6
+ */
7
+ /**
8
+ * Thrown when a received Commit fails the authenticated MLS check at the
9
+ * matching epoch (a corrupt or forged Commit). Unrecoverable: redelivery will
10
+ * not help, so callers should ack-and-skip rather than wedge the group's
11
+ * ordered receive stream.
12
+ */
13
+ export declare class DecryptError extends Error {
14
+ constructor(message: string, cause: unknown);
15
+ }
16
+ /**
17
+ * Thrown when a received MLS Commit targets a future epoch the local handle has
18
+ * not reached yet (a gap in the in-order Commit stream). Callers should NOT ack
19
+ * — the hub redelivers in order, so the gap-filling Commit arrives first and
20
+ * this one applies on a later delivery.
21
+ */
22
+ export declare class CommitOutOfOrderError extends Error {
23
+ #private;
24
+ constructor(messageEpoch: bigint, handleEpoch: bigint);
25
+ get messageEpoch(): bigint;
26
+ get handleEpoch(): bigint;
27
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Errors raised while applying a received MLS handshake Commit to the local
3
+ * group state. Kept in a standalone, transport-independent module so the
4
+ * `GroupMLS` adapter and any other surviving receive path can import them
5
+ * without depending on a specific transport implementation.
6
+ */ /**
7
+ * Thrown when a received Commit fails the authenticated MLS check at the
8
+ * matching epoch (a corrupt or forged Commit). Unrecoverable: redelivery will
9
+ * not help, so callers should ack-and-skip rather than wedge the group's
10
+ * ordered receive stream.
11
+ */ export class DecryptError extends Error {
12
+ constructor(message, cause){
13
+ super(message, {
14
+ cause
15
+ });
16
+ this.name = 'DecryptError';
17
+ }
18
+ }
19
+ /**
20
+ * Thrown when a received MLS Commit targets a future epoch the local handle has
21
+ * not reached yet (a gap in the in-order Commit stream). Callers should NOT ack
22
+ * — the hub redelivers in order, so the gap-filling Commit arrives first and
23
+ * this one applies on a later delivery.
24
+ */ export class CommitOutOfOrderError extends Error {
25
+ #messageEpoch;
26
+ #handleEpoch;
27
+ constructor(messageEpoch, handleEpoch){
28
+ super(`MLS commit epoch ${messageEpoch} ahead of handle epoch ${handleEpoch}`);
29
+ this.name = 'CommitOutOfOrderError';
30
+ this.#messageEpoch = messageEpoch;
31
+ this.#handleEpoch = handleEpoch;
32
+ }
33
+ get messageEpoch() {
34
+ return this.#messageEpoch;
35
+ }
36
+ get handleEpoch() {
37
+ return this.#handleEpoch;
38
+ }
39
+ }
@@ -1,14 +1,16 @@
1
- import { type ClientState, type GroupHandle, type MemberCredential } from '@enkaku/group';
2
1
  import type { GroupMLSState, InsertGroupMLSState } from '@kubun/store-p2p';
2
+ import { type ClientState, type GroupHandle, type MemberCredential } from '@kumiai/mls';
3
3
  /**
4
4
  * Serialized form of a GroupHandle for DB persistence.
5
- * The MLS ClientState is opaque binary; the credential is JSON.
5
+ * The MLS ClientState is opaque binary; the credential and the control-ledger
6
+ * tokens are JSON.
6
7
  */
7
8
  export type SerializedMLSGroupState = {
8
9
  mlsState: Uint8Array;
9
10
  credential: string;
10
11
  epoch: number;
11
- rootCapability: string;
12
+ /** The handle's signed ledger tokens, in enactment order, JSON-encoded. */
13
+ ledgerEntries: string;
12
14
  };
13
15
  /**
14
16
  * Serialize a GroupHandle's state for persistence.
@@ -19,7 +21,7 @@ export declare function serializeMLSGroupState(group: GroupHandle): SerializedML
19
21
  export type MLSGroupState = {
20
22
  state: ClientState;
21
23
  credential: MemberCredential;
22
- rootCapability: string;
24
+ ledgerEntries: Array<string>;
23
25
  };
24
26
  /**
25
27
  * Deserialize persisted state back into GroupHandle constructor params.
@@ -1 +1,79 @@
1
- import{decodeClientState as t,encodeClientState as e}from"@enkaku/group";import{normalizeCredential as o}from"../context/types.js";export function serializeMLSGroupState(t){return{mlsState:e(t.state),credential:JSON.stringify(t.credential),epoch:Number(t.epoch),rootCapability:t.rootCapability}}export function deserializeMLSGroupState(e){let o=t(e.mlsState);if(null==o)throw Error("Could not decode ClientState");return{state:o,credential:JSON.parse(e.credential),rootCapability:e.rootCapability}}export function fromMLSStateRow(t){return{mlsState:t.mls_state,credential:o(t.credential),epoch:t.epoch,rootCapability:t.root_capability}}export function toMLSStateInsert(t,e,o){return{group_id:e,device_id:o,mls_state:t.mlsState,credential:t.credential,epoch:t.epoch,root_capability:t.rootCapability}}
1
+ import { decodeClientState, encodeClientState } from '@kumiai/mls';
2
+ import { normalizeCredential } from '../context/types.js';
3
+ /**
4
+ * Serialize a GroupHandle's state for persistence.
5
+ * The implementer must verify the serialization approach works
6
+ * with the installed version of ts-mls.
7
+ */ export function serializeMLSGroupState(group) {
8
+ return {
9
+ mlsState: encodeClientState(group.state),
10
+ credential: JSON.stringify(group.credential),
11
+ epoch: Number(group.epoch),
12
+ // The tokens are the ledger's only export form — the verified entries a
13
+ // handle folds cannot be reconstructed from anything else — so persisting
14
+ // them is what lets a restored handle refold the roster it had.
15
+ ledgerEntries: JSON.stringify(group.ledgerTokens)
16
+ };
17
+ }
18
+ /**
19
+ * Deserialize persisted state back into GroupHandle constructor params.
20
+ * Returns the params needed to reconstruct a GroupHandle.
21
+ */ export function deserializeMLSGroupState(serialized) {
22
+ const state = decodeClientState(serialized.mlsState);
23
+ if (state == null) {
24
+ throw new Error('Could not decode ClientState');
25
+ }
26
+ return {
27
+ state,
28
+ credential: JSON.parse(serialized.credential),
29
+ ledgerEntries: parseLedgerEntries(serialized.ledgerEntries)
30
+ };
31
+ }
32
+ /**
33
+ * Ledger tokens are stored as a JSON array of strings. An empty column (a row
34
+ * written before any entry was enacted) parses to an empty ledger, which folds
35
+ * to the anchor-seeded roster — the same state the handle held.
36
+ */ function parseLedgerEntries(value) {
37
+ if (value === '') {
38
+ return [];
39
+ }
40
+ const parsed = JSON.parse(value);
41
+ if (!Array.isArray(parsed)) {
42
+ throw new Error('Could not decode persisted control-ledger tokens');
43
+ }
44
+ return parsed;
45
+ }
46
+ /**
47
+ * Map a persisted `GroupMLSState` DB row to the `SerializedMLSGroupState` shape
48
+ * expected by `BroadcastService.prepareSend` / `processReceived`.
49
+ *
50
+ * Credential is stored as TEXT but Kysely's `ParseJSONResultsPlugin` may have
51
+ * already parsed it into an object on read. `SerializedMLSGroupState.credential`
52
+ * is a JSON string, so re-stringify when needed.
53
+ */ export function fromMLSStateRow(row) {
54
+ return {
55
+ mlsState: row.mls_state,
56
+ credential: normalizeCredential(row.credential),
57
+ epoch: row.epoch,
58
+ ledgerEntries: normalizeLedgerEntriesColumn(row.ledger_entries)
59
+ };
60
+ }
61
+ /**
62
+ * The ledger-tokens column is TEXT, but `ParseJSONResultsPlugin` may hand back
63
+ * an already-parsed array — the same asymmetry `normalizeCredential` handles.
64
+ */ function normalizeLedgerEntriesColumn(value) {
65
+ return typeof value === 'string' ? value : JSON.stringify(value);
66
+ }
67
+ /**
68
+ * Map a `SerializedMLSGroupState` plus scope (group + device) to an
69
+ * `InsertGroupMLSState` row suitable for `P2PStoreAPI.saveMLSState`.
70
+ */ export function toMLSStateInsert(serialized, groupID, deviceID) {
71
+ return {
72
+ group_id: groupID,
73
+ device_id: deviceID,
74
+ mls_state: serialized.mlsState,
75
+ credential: serialized.credential,
76
+ epoch: serialized.epoch,
77
+ ledger_entries: serialized.ledgerEntries
78
+ };
79
+ }
@@ -0,0 +1,90 @@
1
+ import type { StoreProvider } from '@kubun/db';
2
+ import { HLC } from '@kubun/hlc';
3
+ import type { Logger } from '@kubun/logger';
4
+ import type { GatheredPeer, LocalPeerAnnouncement, PeerGatherOptions, PresenceReason } from '../types.js';
5
+ import type { GroupBroadcastMessage } from './broadcast.js';
6
+ import type { PeerQueryParam } from './group-protocols.js';
7
+ /** One `peer/query` reply as the gather surface hands it back. */
8
+ type RawReply = {
9
+ senderDID: string;
10
+ value: unknown;
11
+ };
12
+ export type PeerPresenceParams = {
13
+ stores: StoreProvider;
14
+ /** This device's authenticated DID. */
15
+ localDID: string;
16
+ /** Device-wide clock. Every announcement is stamped from it and never by a caller. */
17
+ hlc: HLC;
18
+ /** The epoch stamped onto this device's own row, as the apply path stamps a received one. */
19
+ getGroupEpoch: (groupID: string) => number | undefined;
20
+ /** Fan one broadcast out across every live peer of the group. */
21
+ publish: (groupID: string, message: GroupBroadcastMessage) => Promise<void>;
22
+ /** Gather `peer/query` replies from the group's live peers. */
23
+ query: (groupID: string, param: PeerQueryParam, options: PeerGatherOptions) => Promise<Array<RawReply>>;
24
+ /**
25
+ * Fold one reply into the projection through the SAME apply a broadcast frame
26
+ * takes, so a gathered announcement and a pushed one cannot diverge.
27
+ */
28
+ applyAnnouncement: (groupID: string, message: GroupBroadcastMessage) => Promise<void>;
29
+ /** Every group this device currently belongs to, for a device-wide announce. */
30
+ joinedGroupIDs: () => Array<string>;
31
+ /**
32
+ * Called after this device declares (or redeclares) its profile — the moment
33
+ * it becomes visible, and so the moment a co-member may start dialling it.
34
+ * Whatever answers those dials is stood up here rather than on a timer.
35
+ */
36
+ onProfileSet?: () => void;
37
+ logger?: Logger;
38
+ };
39
+ /**
40
+ * Everything that makes this device visible to its co-members, and everything
41
+ * that asks who else is.
42
+ *
43
+ * A GATHER IS LIVENESS, NEVER MEMBERSHIP. It reports who answered inside a
44
+ * window; it never deletes a row, and a device that says nothing keeps whatever
45
+ * it last said. Only a roster removal deletes.
46
+ */
47
+ export type PeerPresence = {
48
+ /** Record what this device is, then announce it to every group it belongs to. */
49
+ setProfile: (profile: LocalPeerAnnouncement) => Promise<void>;
50
+ getProfile: () => Promise<LocalPeerAnnouncement | null>;
51
+ /**
52
+ * Announce this device's stored profile to one group. Reports whether anything
53
+ * was said — a device with no profile advertises nothing rather than inventing
54
+ * a label for itself.
55
+ */
56
+ announce: (groupID: string) => Promise<boolean>;
57
+ /** Ask who is live. Annotates the projection; never prunes it. */
58
+ gather: (groupID: string, options?: PeerGatherOptions) => Promise<Array<GatheredPeer>>;
59
+ /**
60
+ * Announce, then gather — what "I have just (re)appeared" means, and what every
61
+ * internal trigger runs.
62
+ *
63
+ * A device with no declared profile does NEITHER: presence is opt-in, and a host
64
+ * that never said what its device is has not joined the peer lane. So the whole
65
+ * invariant is "no profile, no peer-lane traffic" — an explicit
66
+ * {@link PeerPresence.gather} still asks, because there the host did.
67
+ */
68
+ refresh: (groupID: string, options?: PeerGatherOptions) => Promise<Array<GatheredPeer>>;
69
+ /** Fire-and-forget {@link refresh}, off the current turn. For an event handler. */
70
+ scheduleRefresh: (groupID: string, reason: PresenceReason) => void;
71
+ /**
72
+ * Fire-and-forget {@link announce}, off the current turn and COALESCED per
73
+ * group. Only the epoch lane uses this, and only it needs to: a device catching
74
+ * up walks many commits back to back, and one announce per commit is a storm
75
+ * where one announce per burst is the same information.
76
+ */
77
+ scheduleAnnounce: (groupID: string, reason: PresenceReason) => void;
78
+ /**
79
+ * Stop scheduling, and WAIT for whatever is already running.
80
+ *
81
+ * Cancelling the pending timers is only half of it: a scheduled task whose
82
+ * timer has already fired is a live store read, and the caller's next step is
83
+ * closing the database. On Postgres a query overlapping the close wedges it
84
+ * permanently, so abandoning that read hangs the shutdown it was meant to
85
+ * finish.
86
+ */
87
+ dispose: () => Promise<void>;
88
+ };
89
+ export declare function createPeerPresence(params: PeerPresenceParams): PeerPresence;
90
+ export {};