agent-comms 8.6.2 → 8.8.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 (240) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +34 -3
  4. package/dist/account-cli.d.ts +34 -0
  5. package/dist/account-cli.d.ts.map +1 -0
  6. package/dist/account-cli.js +140 -0
  7. package/dist/account-cli.js.map +1 -0
  8. package/dist/account-join-cli.d.ts +17 -0
  9. package/dist/account-join-cli.d.ts.map +1 -0
  10. package/dist/account-join-cli.js +144 -0
  11. package/dist/account-join-cli.js.map +1 -0
  12. package/dist/bridges/mcp/server.d.ts +1 -4
  13. package/dist/bridges/mcp/server.d.ts.map +1 -1
  14. package/dist/bridges/mcp/server.js +1 -1
  15. package/dist/bridges/mcp/server.js.map +1 -1
  16. package/dist/bridges/user/web/dist/mesh-worker.js +1 -1
  17. package/dist/bridges/user/web/dist/sw.js +1 -1
  18. package/dist/cli.d.ts +4 -0
  19. package/dist/cli.d.ts.map +1 -1
  20. package/dist/cli.js +24 -1
  21. package/dist/cli.js.map +1 -1
  22. package/dist/core/account-bundle.d.ts +27 -0
  23. package/dist/core/account-bundle.d.ts.map +1 -0
  24. package/dist/core/account-bundle.js +173 -0
  25. package/dist/core/account-bundle.js.map +1 -0
  26. package/dist/core/account-join.d.ts +70 -0
  27. package/dist/core/account-join.d.ts.map +1 -0
  28. package/dist/core/account-join.js +183 -0
  29. package/dist/core/account-join.js.map +1 -0
  30. package/dist/core/account-ledger-crypto.d.ts +28 -0
  31. package/dist/core/account-ledger-crypto.d.ts.map +1 -0
  32. package/dist/core/account-ledger-crypto.js +101 -0
  33. package/dist/core/account-ledger-crypto.js.map +1 -0
  34. package/dist/core/account-ledger-store.d.ts +21 -0
  35. package/dist/core/account-ledger-store.d.ts.map +1 -0
  36. package/dist/core/account-ledger-store.js +34 -0
  37. package/dist/core/account-ledger-store.js.map +1 -0
  38. package/dist/core/account-ledger.d.ts +80 -0
  39. package/dist/core/account-ledger.d.ts.map +1 -0
  40. package/dist/core/account-ledger.js +247 -0
  41. package/dist/core/account-ledger.js.map +1 -0
  42. package/dist/core/agent-registry.d.ts.map +1 -1
  43. package/dist/core/agent-registry.js +2 -0
  44. package/dist/core/agent-registry.js.map +1 -1
  45. package/dist/core/bridge-mesh.d.ts.map +1 -1
  46. package/dist/core/bridge-mesh.js +12 -1
  47. package/dist/core/bridge-mesh.js.map +1 -1
  48. package/dist/core/bridge.d.ts +1 -0
  49. package/dist/core/bridge.d.ts.map +1 -1
  50. package/dist/core/bridge.js +6 -0
  51. package/dist/core/bridge.js.map +1 -1
  52. package/dist/core/data-frame-routing.d.ts +50 -3
  53. package/dist/core/data-frame-routing.d.ts.map +1 -1
  54. package/dist/core/data-frame-routing.js +112 -5
  55. package/dist/core/data-frame-routing.js.map +1 -1
  56. package/dist/core/device-membership.d.ts +8 -8
  57. package/dist/core/device-membership.d.ts.map +1 -1
  58. package/dist/core/device-membership.js +17 -16
  59. package/dist/core/device-membership.js.map +1 -1
  60. package/dist/core/dm-send-delegation.d.ts +4 -4
  61. package/dist/core/dm-send-delegation.d.ts.map +1 -1
  62. package/dist/core/dm-send-delegation.js +2 -3
  63. package/dist/core/dm-send-delegation.js.map +1 -1
  64. package/dist/core/gossip-directory.d.ts +4 -2
  65. package/dist/core/gossip-directory.d.ts.map +1 -1
  66. package/dist/core/gossip-directory.js +4 -5
  67. package/dist/core/gossip-directory.js.map +1 -1
  68. package/dist/core/gossip-extensions.d.ts +2 -0
  69. package/dist/core/gossip-extensions.d.ts.map +1 -1
  70. package/dist/core/gossip-extensions.js +3 -0
  71. package/dist/core/gossip-extensions.js.map +1 -1
  72. package/dist/core/group-proofs.d.ts +15 -6
  73. package/dist/core/group-proofs.d.ts.map +1 -1
  74. package/dist/core/group-proofs.js +73 -18
  75. package/dist/core/group-proofs.js.map +1 -1
  76. package/dist/core/identity-store.d.ts.map +1 -1
  77. package/dist/core/identity-store.js +2 -62
  78. package/dist/core/identity-store.js.map +1 -1
  79. package/dist/core/issuer-grouping.d.ts +52 -0
  80. package/dist/core/issuer-grouping.d.ts.map +1 -0
  81. package/dist/core/{machine-grouping.js → issuer-grouping.js} +33 -33
  82. package/dist/core/issuer-grouping.js.map +1 -0
  83. package/dist/core/issuer-identity-file.d.ts +4 -0
  84. package/dist/core/issuer-identity-file.d.ts.map +1 -1
  85. package/dist/core/issuer-identity-file.js +7 -5
  86. package/dist/core/issuer-identity-file.js.map +1 -1
  87. package/dist/core/ledger-lock.d.ts +12 -0
  88. package/dist/core/ledger-lock.d.ts.map +1 -0
  89. package/dist/core/ledger-lock.js +62 -0
  90. package/dist/core/ledger-lock.js.map +1 -0
  91. package/dist/core/membership-proofs.d.ts +7 -0
  92. package/dist/core/membership-proofs.d.ts.map +1 -1
  93. package/dist/core/membership-proofs.js +26 -1
  94. package/dist/core/membership-proofs.js.map +1 -1
  95. package/dist/core/mesh-store-shared.d.ts +4 -1
  96. package/dist/core/mesh-store-shared.d.ts.map +1 -1
  97. package/dist/core/mesh-store-shared.js.map +1 -1
  98. package/dist/core/mesh-store.d.ts +17 -8
  99. package/dist/core/mesh-store.d.ts.map +1 -1
  100. package/dist/core/mesh-store.js +44 -42
  101. package/dist/core/mesh-store.js.map +1 -1
  102. package/dist/core/naming-actions.d.ts +5 -1
  103. package/dist/core/naming-actions.d.ts.map +1 -1
  104. package/dist/core/naming-actions.js +13 -1
  105. package/dist/core/naming-actions.js.map +1 -1
  106. package/dist/core/naming.d.ts +9 -5
  107. package/dist/core/naming.d.ts.map +1 -1
  108. package/dist/core/naming.js +14 -4
  109. package/dist/core/naming.js.map +1 -1
  110. package/dist/core/peer-versions.d.ts +11 -0
  111. package/dist/core/peer-versions.d.ts.map +1 -1
  112. package/dist/core/peer-versions.js +9 -0
  113. package/dist/core/peer-versions.js.map +1 -1
  114. package/dist/core/pid-lock-file.d.ts +20 -0
  115. package/dist/core/pid-lock-file.d.ts.map +1 -0
  116. package/dist/core/pid-lock-file.js +115 -0
  117. package/dist/core/pid-lock-file.js.map +1 -0
  118. package/dist/core/room-lifecycle.d.ts +2 -2
  119. package/dist/core/room-lifecycle.d.ts.map +1 -1
  120. package/dist/core/room-lifecycle.js +18 -19
  121. package/dist/core/room-lifecycle.js.map +1 -1
  122. package/dist/core/self-name-claim.d.ts +27 -0
  123. package/dist/core/self-name-claim.d.ts.map +1 -0
  124. package/dist/core/{machine-name-claim.js → self-name-claim.js} +7 -8
  125. package/dist/core/self-name-claim.js.map +1 -0
  126. package/dist/core/tool.d.ts.map +1 -1
  127. package/dist/core/tool.js +11 -7
  128. package/dist/core/tool.js.map +1 -1
  129. package/dist/core/transport-queries.d.ts +11 -0
  130. package/dist/core/transport-queries.d.ts.map +1 -0
  131. package/dist/core/transport-queries.js +30 -0
  132. package/dist/core/transport-queries.js.map +1 -0
  133. package/dist/core/types.d.ts +6 -0
  134. package/dist/core/types.d.ts.map +1 -1
  135. package/dist/core/types.js +5 -0
  136. package/dist/core/types.js.map +1 -1
  137. package/dist/core/user-identity.d.ts +28 -18
  138. package/dist/core/user-identity.d.ts.map +1 -1
  139. package/dist/core/user-identity.js +126 -59
  140. package/dist/core/user-identity.js.map +1 -1
  141. package/dist/core/wire-mesh-transport-options.d.ts +11 -2
  142. package/dist/core/wire-mesh-transport-options.d.ts.map +1 -1
  143. package/dist/core/wire-mesh-transport-options.js.map +1 -1
  144. package/dist/core/wire-mesh-transport.d.ts +4 -6
  145. package/dist/core/wire-mesh-transport.d.ts.map +1 -1
  146. package/dist/core/wire-mesh-transport.js +16 -20
  147. package/dist/core/wire-mesh-transport.js.map +1 -1
  148. package/dist/test/account-join.integration.test.d.ts +5 -0
  149. package/dist/test/account-join.integration.test.d.ts.map +1 -0
  150. package/dist/test/account-join.integration.test.js +280 -0
  151. package/dist/test/account-join.integration.test.js.map +1 -0
  152. package/dist/test/account-ledger-lock-worker.d.ts +9 -0
  153. package/dist/test/account-ledger-lock-worker.d.ts.map +1 -0
  154. package/dist/test/account-ledger-lock-worker.js +34 -0
  155. package/dist/test/account-ledger-lock-worker.js.map +1 -0
  156. package/dist/test/account-ledger-lock.integration.test.d.ts +5 -0
  157. package/dist/test/account-ledger-lock.integration.test.d.ts.map +1 -0
  158. package/dist/test/account-ledger-lock.integration.test.js +71 -0
  159. package/dist/test/account-ledger-lock.integration.test.js.map +1 -0
  160. package/dist/test/account-ledger-replication.integration.test.d.ts +5 -0
  161. package/dist/test/account-ledger-replication.integration.test.d.ts.map +1 -0
  162. package/dist/test/account-ledger-replication.integration.test.js +145 -0
  163. package/dist/test/account-ledger-replication.integration.test.js.map +1 -0
  164. package/dist/test/account-ledger.unit.test.d.ts +5 -0
  165. package/dist/test/account-ledger.unit.test.d.ts.map +1 -0
  166. package/dist/test/account-ledger.unit.test.js +317 -0
  167. package/dist/test/account-ledger.unit.test.js.map +1 -0
  168. package/dist/test/account-transfer.unit.test.d.ts +5 -0
  169. package/dist/test/account-transfer.unit.test.d.ts.map +1 -0
  170. package/dist/test/account-transfer.unit.test.js +232 -0
  171. package/dist/test/account-transfer.unit.test.js.map +1 -0
  172. package/dist/test/delivery-engine-delivery.unit.test.js +1 -0
  173. package/dist/test/delivery-engine-delivery.unit.test.js.map +1 -1
  174. package/dist/test/delivery-engine-directed-notify.unit.test.js +1 -0
  175. package/dist/test/delivery-engine-directed-notify.unit.test.js.map +1 -1
  176. package/dist/test/delivery-engine-own-agent.unit.test.js +1 -0
  177. package/dist/test/delivery-engine-own-agent.unit.test.js.map +1 -1
  178. package/dist/test/delivery-engine.unit.test.js +1 -0
  179. package/dist/test/delivery-engine.unit.test.js.map +1 -1
  180. package/dist/test/device-membership.unit.test.js +81 -30
  181. package/dist/test/device-membership.unit.test.js.map +1 -1
  182. package/dist/test/dm-send-delegation.unit.test.js +25 -14
  183. package/dist/test/dm-send-delegation.unit.test.js.map +1 -1
  184. package/dist/test/dm-send-principal-delegation.integration.test.js +9 -2
  185. package/dist/test/dm-send-principal-delegation.integration.test.js.map +1 -1
  186. package/dist/test/hidden-line.unit.test.d.ts +5 -0
  187. package/dist/test/hidden-line.unit.test.d.ts.map +1 -0
  188. package/dist/test/hidden-line.unit.test.js +40 -0
  189. package/dist/test/hidden-line.unit.test.js.map +1 -0
  190. package/dist/test/identity-restart.integration.test.js +8 -1
  191. package/dist/test/identity-restart.integration.test.js.map +1 -1
  192. package/dist/test/issuer-grouping.unit.test.d.ts +2 -0
  193. package/dist/test/issuer-grouping.unit.test.d.ts.map +1 -0
  194. package/dist/test/issuer-grouping.unit.test.js +137 -0
  195. package/dist/test/issuer-grouping.unit.test.js.map +1 -0
  196. package/dist/test/mcp-stdout-channel.integration.test.js +2 -2
  197. package/dist/test/mcp-stdout-channel.integration.test.js.map +1 -1
  198. package/dist/test/mesh-smoke.runner.js +2 -1
  199. package/dist/test/mesh-smoke.runner.js.map +1 -1
  200. package/dist/test/naming.integration.test.d.ts +1 -1
  201. package/dist/test/naming.integration.test.js +45 -1
  202. package/dist/test/naming.integration.test.js.map +1 -1
  203. package/dist/test/room-join-admission.integration.test.js +8 -1
  204. package/dist/test/room-join-admission.integration.test.js.map +1 -1
  205. package/dist/test/room-lifecycle-bare-name.unit.test.js +1 -0
  206. package/dist/test/room-lifecycle-bare-name.unit.test.js.map +1 -1
  207. package/dist/test/room-lifecycle-membership.unit.test.js +1 -0
  208. package/dist/test/room-lifecycle-membership.unit.test.js.map +1 -1
  209. package/dist/test/room-lifecycle-remote.unit.test.js +1 -0
  210. package/dist/test/room-lifecycle-remote.unit.test.js.map +1 -1
  211. package/dist/test/room-lifecycle.unit.test.js +1 -0
  212. package/dist/test/room-lifecycle.unit.test.js.map +1 -1
  213. package/dist/test/room-messaging-durable-send.unit.test.js +1 -0
  214. package/dist/test/room-messaging-durable-send.unit.test.js.map +1 -1
  215. package/dist/test/room-messaging.unit.test.js +1 -0
  216. package/dist/test/room-messaging.unit.test.js.map +1 -1
  217. package/dist/test/room-protocol-notify.unit.test.js +1 -0
  218. package/dist/test/room-protocol-notify.unit.test.js.map +1 -1
  219. package/dist/test/room-protocol.helper.d.ts.map +1 -1
  220. package/dist/test/room-protocol.helper.js +1 -0
  221. package/dist/test/room-protocol.helper.js.map +1 -1
  222. package/dist/test/room-send-retry.integration.test.js +8 -1
  223. package/dist/test/room-send-retry.integration.test.js.map +1 -1
  224. package/dist/test/test-transport.d.ts +9 -0
  225. package/dist/test/test-transport.d.ts.map +1 -1
  226. package/dist/test/test-transport.js +14 -5
  227. package/dist/test/test-transport.js.map +1 -1
  228. package/dist/test/user-identity.unit.test.js +92 -99
  229. package/dist/test/user-identity.unit.test.js.map +1 -1
  230. package/package.json +1 -1
  231. package/dist/core/machine-grouping.d.ts +0 -47
  232. package/dist/core/machine-grouping.d.ts.map +0 -1
  233. package/dist/core/machine-grouping.js.map +0 -1
  234. package/dist/core/machine-name-claim.d.ts +0 -22
  235. package/dist/core/machine-name-claim.d.ts.map +0 -1
  236. package/dist/core/machine-name-claim.js.map +0 -1
  237. package/dist/test/machine-grouping.unit.test.d.ts +0 -2
  238. package/dist/test/machine-grouping.unit.test.d.ts.map +0 -1
  239. package/dist/test/machine-grouping.unit.test.js +0 -68
  240. package/dist/test/machine-grouping.unit.test.js.map +0 -1
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Joining a machine to an account (agent-comms#344): one deliberate act per new machine. A machine holding the account issues an account invite, an ordinary connection code (connection-code.ts) it also remembers as an invite; the person carries it to the new machine, which redeems it. Redeeming trusts the issuing device exactly as any connection code does, then asks that device, over the mesh's end-to-end authenticated request path (directly or relayed by the hub), for the account key, which comes back sealed under a key only the invite's holder can derive. The new machine then holds the account: its devices mint membership proofs as the account, so every machine already trusting the account's principal covers them with no per-pair step, and it replicates and can revoke from the account's grant ledger.
3
+ *
4
+ * The issuing device enforces the invite for real, not only the redeemer: it answers each invite once and never after its expiry, so a copied invite is worth nothing once used or stale, and it refuses to issue one that would stay valid for longer than an ordinary connection code (DEFAULT_CONNECTION_CODE_TTL_MS), which is already sized for a person relaying it by hand, since an invite is the account key to whoever redeems it first. Invites live only in the issuing store's memory, so a restart of that store before the invite is used cancels it.
5
+ *
6
+ * A join can carry a label for the joining machine. The issuing device validates it and keeps it as its own petname for the joiner's machine (naming.ts), so the machine the person just admitted is listed by the name they typed without a second step. The label and the machine id it names are the joiner's own assertion, which is acceptable only because whoever redeems an invite already receives the whole account; the petname is nothing but the issuing person's own view and is never gossiped.
7
+ *
8
+ * Neither half is an agent_comms tool action: an invite hands over the account key and a join replaces the machine's account, so both are `agent-comms account invite` and `agent-comms account join`, run by the person at the terminal (account-cli.ts), where nothing an agent is told can trigger them and the invite is never written into an agent's transcript.
9
+ */
10
+ import type { IncomingManageRequest } from "wire-mesh-core/domain/mesh-session";
11
+ import type { ManageOutcome } from "wire-mesh-core/domain/mesh-session";
12
+ import { type ConnectionCodeLedger, type GenerateConnectionCodeOptions, type RedeemConnectionCodeOptions } from "./connection-code.js";
13
+ import type { GatewayTrust } from "./gateway-trust.js";
14
+ import type { MeshStoreIdentity } from "./mesh-store-shared.js";
15
+ import type { MeshTransport } from "./transport.js";
16
+ import type { ConnectionCode } from "./types.js";
17
+ /** The request verb an invite's redeemer sends its issuer (params.verb), and the capability verb the manage-command carries only to satisfy manage-command.verb's grammar: the handler checks the invite itself, never a token. */
18
+ export declare const ACCOUNT_JOIN_VERB = "account.join";
19
+ /** What became of an invite: answered with the account key, or expired unanswered. */
20
+ export type AccountInviteOutcome = "redeemed" | "expired";
21
+ export interface AccountInvite {
22
+ code: ConnectionCode;
23
+ /** Settles once the invite has been answered or has expired, whichever comes first. */
24
+ settled: Promise<AccountInviteOutcome>;
25
+ }
26
+ export interface AccountJoinDeps {
27
+ requireIdentity: () => MeshStoreIdentity;
28
+ requireTransport: () => MeshTransport;
29
+ getPeerId: () => string;
30
+ connectionCodes: ConnectionCodeLedger;
31
+ gatewayTrust: GatewayTrust;
32
+ /** Re-evaluates whether the store should hold a hub session, after trust changes. */
33
+ reconsiderHub: () => void;
34
+ /** Redeems a connection code the ordinary way (freshness, single use, optional signature) and trusts the device it vouches for. */
35
+ redeemConnectionCode: (candidate: Readonly<ConnectionCode>, options: Readonly<RedeemConnectionCodeOptions>) => Promise<unknown>;
36
+ /** Labels deviceHex as name in the viewer's own petnames (Naming.setPetname). */
37
+ setPetname: (deviceHex: string, name: string) => string;
38
+ /** Re-attaches the store to the account it now holds, so its own proofs and ledger switch at once. */
39
+ setIdentity: (identity: MeshStoreIdentity) => void;
40
+ }
41
+ export interface AccountJoinOptions extends RedeemConnectionCodeOptions {
42
+ /** What the issuing person calls this machine: kept by the issuing device as its petname for it. Must satisfy the display-name rules. */
43
+ label?: string;
44
+ }
45
+ export interface AccountJoinResult {
46
+ /** The account's principal id (hex) this machine now holds. */
47
+ principal: string;
48
+ /** Where the account this machine held before was set aside, when it held a different one. */
49
+ replacedFile?: string;
50
+ }
51
+ export declare class AccountJoin {
52
+ private readonly deps;
53
+ /** Invites this store issued that are neither answered nor known to have expired, keyed by their nonce. */
54
+ private readonly invites;
55
+ constructor(deps: Readonly<AccountJoinDeps>);
56
+ /** Issues an account invite vouching for this store's own device, valid for options.ttlMs (at most DEFAULT_CONNECTION_CODE_TTL_MS, the default). Throws INVITE_TTL_TOO_LONG for a longer one. Also trusts the account's own principal, so this machine holds a hub session the joining machine can reach it through, and admits the joined machine's devices once their proofs name the account. */
57
+ invite(options?: Readonly<Omit<GenerateConnectionCodeOptions, "now">>): Promise<AccountInvite>;
58
+ /** Answers an account.join request: the account key and name sealed for the named invite, once, and only before the invite expires. Every other request, including a second use of the same invite, is refused without saying whether the invite ever existed. */
59
+ readonly handleJoinRequest: (request: Readonly<IncomingManageRequest>) => Promise<ManageOutcome>;
60
+ /** Drops every invite whose expiry has passed by this store's clock, settling each as expired. */
61
+ private pruneExpired;
62
+ /** Redeems an account invite and makes this machine hold the account it names. Throws INVALID_NAME for an unusable options.label before anything is redeemed, INVITE_REFUSED when the issuing device will not answer it (used, expired, cancelled by a restart, unreachable, or unwilling to take the label), and whatever redeeming the connection code itself throws before that. Redeeming trusts the issuing device so the key request can reach it; when the join then fails, that trust is withdrawn again unless the device was already trusted before, so a refused or unanswered invite leaves this machine trusting no one new. */
63
+ join(candidate: Readonly<ConnectionCode>, options?: Readonly<AccountJoinOptions>): Promise<AccountJoinResult>;
64
+ /** The half of join after the invite's issuer is trusted: fetch the account key from it and switch this machine to that account. */
65
+ private takeAccount;
66
+ /** Asks the invite's issuing device for the account key. Redeeming the invite has only just trusted that device, so the route to it (a hub session this store opens because it now trusts someone) may still be coming up: a request that found no route yet is retried until JOIN_ROUTE_WAIT_MS has passed, and any answer from the device itself is returned as it is. */
67
+ private requestAccountKey;
68
+ private trustOwnAccount;
69
+ }
70
+ //# sourceMappingURL=account-join.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-join.d.ts","sourceRoot":"","sources":["../../src/core/account-join.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAChF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAOxE,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,6BAA6B,EAClC,KAAK,2BAA2B,EACjC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAIhE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAIjD,mOAAmO;AACnO,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAehD,sFAAsF;AACtF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG,SAAS,CAAC;AAE1D,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,cAAc,CAAC;IACrB,uFAAuF;IACvF,OAAO,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;CACxC;AAOD,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,iBAAiB,CAAC;IACzC,gBAAgB,EAAE,MAAM,aAAa,CAAC;IACtC,SAAS,EAAE,MAAM,MAAM,CAAC;IACxB,eAAe,EAAE,oBAAoB,CAAC;IACtC,YAAY,EAAE,YAAY,CAAC;IAC3B,qFAAqF;IACrF,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,mIAAmI;IACnI,oBAAoB,EAAE,CACpB,SAAS,EAAE,QAAQ,CAAC,cAAc,CAAC,EACnC,OAAO,EAAE,QAAQ,CAAC,2BAA2B,CAAC,KAC3C,OAAO,CAAC,OAAO,CAAC,CAAC;IACtB,iFAAiF;IACjF,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACxD,sGAAsG;IACtG,WAAW,EAAE,CAAC,QAAQ,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACpD;AAED,MAAM,WAAW,kBAAmB,SAAQ,2BAA2B;IACrE,yIAAyI;IACzI,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;IAClB,8FAA8F;IAC9F,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAoBD,qBAAa,WAAW;IAIV,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHjC,2GAA2G;IAC3G,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;gBAE/B,IAAI,EAAE,QAAQ,CAAC,eAAe,CAAC;IAE5D,oYAAoY;IAC9X,MAAM,CACV,OAAO,GAAE,QAAQ,CAAC,IAAI,CAAC,6BAA6B,EAAE,KAAK,CAAC,CAAM,GACjE,OAAO,CAAC,aAAa,CAAC;IAgCzB,kQAAkQ;IAClQ,QAAQ,CAAC,iBAAiB,GACxB,SAAS,QAAQ,CAAC,qBAAqB,CAAC,KACvC,OAAO,CAAC,aAAa,CAAC,CAyBvB;IAEF,kGAAkG;IAClG,OAAO,CAAC,YAAY;IASpB,6mBAA6mB;IACvmB,IAAI,CACR,SAAS,EAAE,QAAQ,CAAC,cAAc,CAAC,EACnC,OAAO,GAAE,QAAQ,CAAC,kBAAkB,CAAM,GACzC,OAAO,CAAC,iBAAiB,CAAC;IAmB7B,oIAAoI;YACtH,WAAW;IAmCzB,4WAA4W;YAC9V,iBAAiB;IA4B/B,OAAO,CAAC,eAAe;CAKxB"}
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Joining a machine to an account (agent-comms#344): one deliberate act per new machine. A machine holding the account issues an account invite, an ordinary connection code (connection-code.ts) it also remembers as an invite; the person carries it to the new machine, which redeems it. Redeeming trusts the issuing device exactly as any connection code does, then asks that device, over the mesh's end-to-end authenticated request path (directly or relayed by the hub), for the account key, which comes back sealed under a key only the invite's holder can derive. The new machine then holds the account: its devices mint membership proofs as the account, so every machine already trusting the account's principal covers them with no per-pair step, and it replicates and can revoke from the account's grant ledger.
3
+ *
4
+ * The issuing device enforces the invite for real, not only the redeemer: it answers each invite once and never after its expiry, so a copied invite is worth nothing once used or stale, and it refuses to issue one that would stay valid for longer than an ordinary connection code (DEFAULT_CONNECTION_CODE_TTL_MS), which is already sized for a person relaying it by hand, since an invite is the account key to whoever redeems it first. Invites live only in the issuing store's memory, so a restart of that store before the invite is used cancels it.
5
+ *
6
+ * A join can carry a label for the joining machine. The issuing device validates it and keeps it as its own petname for the joiner's machine (naming.ts), so the machine the person just admitted is listed by the name they typed without a second step. The label and the machine id it names are the joiner's own assertion, which is acceptable only because whoever redeems an invite already receives the whole account; the petname is nothing but the issuing person's own view and is never gossiped.
7
+ *
8
+ * Neither half is an agent_comms tool action: an invite hands over the account key and a join replaces the machine's account, so both are `agent-comms account invite` and `agent-comms account join`, run by the person at the terminal (account-cli.ts), where nothing an agent is told can trigger them and the invite is never written into an agent's transcript.
9
+ */
10
+ import { deviceIdToHex } from "wire-mesh-core/domain/device-id";
11
+ import { openAccountKeyFromInvite, sealAccountKeyForInvite, } from "./account-bundle.js";
12
+ import { openAccountLedger } from "./account-ledger-store.js";
13
+ import { DEFAULT_CONNECTION_CODE_TTL_MS, } from "./connection-code.js";
14
+ import { parseDisplayName } from "./display-name.js";
15
+ import { isDeviceIdHex } from "./room-path.js";
16
+ import { CommsError, requireDisplayName } from "./store.js";
17
+ import { importAccountKey, readAccountContents } from "./user-identity.js";
18
+ import { toIdentityPort } from "./wire-mesh-identity.js";
19
+ /** The request verb an invite's redeemer sends its issuer (params.verb), and the capability verb the manage-command carries only to satisfy manage-command.verb's grammar: the handler checks the invite itself, never a token. */
20
+ export const ACCOUNT_JOIN_VERB = "account.join";
21
+ const ACCOUNT_JOIN_CAPABILITY_VERB = "account:join";
22
+ /** The outcomes sendRoomRequest gives when this side has no route to the device yet, as opposed to an answer from the device. */
23
+ const NO_ROUTE_CODES = new Set([
24
+ "no_route",
25
+ "not_connected",
26
+ ]);
27
+ const MS_PER_SECOND = 1000;
28
+ /** How long a join waits for a route to the issuing device to come up: a hub dial and its first directory exchange take seconds, so this is generous without leaving a person waiting on an unreachable issuer for long. */
29
+ const JOIN_ROUTE_WAIT_SECONDS = 30;
30
+ const JOIN_ROUTE_WAIT_MS = JOIN_ROUTE_WAIT_SECONDS * MS_PER_SECOND;
31
+ /** Between attempts to reach the issuing device: short against the wait, long enough not to spin. */
32
+ const JOIN_ROUTE_RETRY_MS = 250;
33
+ /** The label a join request asks the issuer to keep for the joiner's machine: undefined when it asks for none, "invalid" when it asks for one that is not a usable label for a full machine id (a label with no machine id included). */
34
+ function joinerPetname(params) {
35
+ const label = "label" in params ? params.label : undefined;
36
+ const machine = "machine" in params ? params.machine : undefined;
37
+ if (label === undefined && machine === undefined)
38
+ return undefined;
39
+ if (typeof label !== "string" ||
40
+ typeof machine !== "string" ||
41
+ !isDeviceIdHex(machine.toLowerCase()) ||
42
+ parseDisplayName(label) !== label) {
43
+ return "invalid";
44
+ }
45
+ return { machine, label };
46
+ }
47
+ export class AccountJoin {
48
+ deps;
49
+ /** Invites this store issued that are neither answered nor known to have expired, keyed by their nonce. */
50
+ invites = new Map();
51
+ constructor(deps) {
52
+ this.deps = deps;
53
+ }
54
+ /** Issues an account invite vouching for this store's own device, valid for options.ttlMs (at most DEFAULT_CONNECTION_CODE_TTL_MS, the default). Throws INVITE_TTL_TOO_LONG for a longer one. Also trusts the account's own principal, so this machine holds a hub session the joining machine can reach it through, and admits the joined machine's devices once their proofs name the account. */
55
+ async invite(options = {}) {
56
+ const ttlMs = options.ttlMs ?? DEFAULT_CONNECTION_CODE_TTL_MS;
57
+ if (ttlMs > DEFAULT_CONNECTION_CODE_TTL_MS) {
58
+ throw new CommsError(`An account invite can stay valid for at most ${String(DEFAULT_CONNECTION_CODE_TTL_MS)} ms, since whoever redeems it first takes the account key`, "INVITE_TTL_TOO_LONG");
59
+ }
60
+ const { clock } = this.deps.requireIdentity();
61
+ this.pruneExpired();
62
+ const code = await this.deps.connectionCodes.generate(this.deps.getPeerId(), { ...options, ttlMs, now: clock.now() });
63
+ const settled = new Promise((resolve) => {
64
+ this.invites.set(code.code, {
65
+ secret: {
66
+ code: code.code,
67
+ expiresAt: code.expiresAt,
68
+ deviceId: code.deviceId,
69
+ },
70
+ settle: resolve,
71
+ });
72
+ });
73
+ // Settles `settled` on expiry even when nothing else calls in to prune; unref'd so a pending invite never keeps a process alive by itself.
74
+ setTimeout(() => {
75
+ this.pruneExpired();
76
+ }, ttlMs).unref();
77
+ this.trustOwnAccount();
78
+ return { code, settled };
79
+ }
80
+ /** Answers an account.join request: the account key and name sealed for the named invite, once, and only before the invite expires. Every other request, including a second use of the same invite, is refused without saying whether the invite ever existed. */
81
+ handleJoinRequest = async (request) => {
82
+ this.pruneExpired();
83
+ const { params } = request.command;
84
+ const code = "code" in params ? params.code : undefined;
85
+ const invite = typeof code === "string" ? this.invites.get(code) : undefined;
86
+ if (invite === undefined) {
87
+ return Promise.resolve({ result: "error", code: "invalid_invite" });
88
+ }
89
+ const petname = joinerPetname(params);
90
+ if (petname === "invalid") {
91
+ return Promise.resolve({ result: "error", code: "invalid_label" });
92
+ }
93
+ // Before the invite is spent, so a petname file that cannot be written fails the request with the invite still usable rather than losing the invite and the label together.
94
+ if (petname !== undefined) {
95
+ this.deps.setPetname(petname.machine, petname.label);
96
+ }
97
+ this.invites.delete(invite.secret.code);
98
+ const { userIdentityOptions } = this.deps.requireIdentity();
99
+ const sealed = sealAccountKeyForInvite(readAccountContents(userIdentityOptions), invite.secret);
100
+ invite.settle("redeemed");
101
+ return Promise.resolve({ result: "ok", sealed });
102
+ };
103
+ /** Drops every invite whose expiry has passed by this store's clock, settling each as expired. */
104
+ pruneExpired() {
105
+ const now = this.deps.requireIdentity().clock.now();
106
+ for (const [nonce, invite] of this.invites) {
107
+ if (Date.parse(invite.secret.expiresAt) > now)
108
+ continue;
109
+ this.invites.delete(nonce);
110
+ invite.settle("expired");
111
+ }
112
+ }
113
+ /** Redeems an account invite and makes this machine hold the account it names. Throws INVALID_NAME for an unusable options.label before anything is redeemed, INVITE_REFUSED when the issuing device will not answer it (used, expired, cancelled by a restart, unreachable, or unwilling to take the label), and whatever redeeming the connection code itself throws before that. Redeeming trusts the issuing device so the key request can reach it; when the join then fails, that trust is withdrawn again unless the device was already trusted before, so a refused or unanswered invite leaves this machine trusting no one new. */
114
+ async join(candidate, options = {}) {
115
+ const { label, ...redeemOptions } = options;
116
+ const named = label === undefined
117
+ ? undefined
118
+ : requireDisplayName(label, "A machine label");
119
+ const trustedBefore = this.deps.gatewayTrust.isTrusted(candidate.deviceId);
120
+ await this.deps.redeemConnectionCode(candidate, redeemOptions);
121
+ try {
122
+ return await this.takeAccount(candidate, named);
123
+ }
124
+ catch (error) {
125
+ if (!trustedBefore) {
126
+ this.deps.gatewayTrust.remove(candidate.deviceId);
127
+ this.deps.reconsiderHub();
128
+ }
129
+ throw error;
130
+ }
131
+ }
132
+ /** The half of join after the invite's issuer is trusted: fetch the account key from it and switch this machine to that account. */
133
+ async takeAccount(candidate, label) {
134
+ const outcome = await this.requestAccountKey(candidate, label);
135
+ const sealed = outcome.result === "ok" ? outcome.sealed : undefined;
136
+ if (typeof sealed !== "string") {
137
+ throw new CommsError(`The issuing device refused the account invite (${outcome.result === "error" ? (outcome.message ?? outcome.code) : "no key in its answer"})`, "INVITE_REFUSED");
138
+ }
139
+ const contents = openAccountKeyFromInvite(sealed, candidate);
140
+ const current = this.deps.requireIdentity();
141
+ const { identity, replacedFile } = importAccountKey(current.userIdentityOptions, contents);
142
+ this.deps.setIdentity({
143
+ ...current,
144
+ userIdentity: await toIdentityPort(identity),
145
+ accountLedger: await openAccountLedger({
146
+ userIdentityOptions: current.userIdentityOptions,
147
+ userIdentity: identity,
148
+ clock: current.clock,
149
+ }),
150
+ });
151
+ this.trustOwnAccount();
152
+ const principal = deviceIdToHex(Uint8Array.from(identity.deviceId));
153
+ return replacedFile === undefined
154
+ ? { principal }
155
+ : { principal, replacedFile };
156
+ }
157
+ /** Asks the invite's issuing device for the account key. Redeeming the invite has only just trusted that device, so the route to it (a hub session this store opens because it now trusts someone) may still be coming up: a request that found no route yet is retried until JOIN_ROUTE_WAIT_MS has passed, and any answer from the device itself is returned as it is. */
158
+ async requestAccountKey(invite, label) {
159
+ const { clock, machineIdentity } = this.deps.requireIdentity();
160
+ const labelling = label === undefined
161
+ ? {}
162
+ : { label, machine: deviceIdToHex(machineIdentity.deviceId) };
163
+ const deadline = clock.now() + JOIN_ROUTE_WAIT_MS;
164
+ for (;;) {
165
+ const outcome = await this.deps.requireTransport().sendRoomRequest(invite.deviceId, {
166
+ verb: ACCOUNT_JOIN_CAPABILITY_VERB,
167
+ params: { verb: ACCOUNT_JOIN_VERB, code: invite.code, ...labelling },
168
+ }, { kind: "node" });
169
+ const noRouteYet = outcome.result === "error" && NO_ROUTE_CODES.has(outcome.code);
170
+ if (!noRouteYet || clock.now() >= deadline)
171
+ return outcome;
172
+ await new Promise((resolve) => {
173
+ setTimeout(resolve, JOIN_ROUTE_RETRY_MS);
174
+ });
175
+ }
176
+ }
177
+ trustOwnAccount() {
178
+ const { userIdentity } = this.deps.requireIdentity();
179
+ this.deps.gatewayTrust.addPrincipal(deviceIdToHex(userIdentity.deviceId));
180
+ this.deps.reconsiderHub();
181
+ }
182
+ }
183
+ //# sourceMappingURL=account-join.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-join.js","sourceRoot":"","sources":["../../src/core/account-join.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The confidentiality layer of the account's replicated grant ledger (agent-comms#344). A `core/data` entry is opaque bytes to the transport and is replicated to whoever asks for it, so every ledger entry is sealed with a key only a holder of the account's private key can derive: AES-256-GCM under an HKDF-SHA256 key from the account key, with the log it belongs to and its sequence number bound in as associated data so a relayer can neither read an entry nor move one to another log or position. The same derivation gives each machine's writer log an id that any account holder recognises and nobody else can distinguish from a random device-id.
3
+ */
4
+ import type { DeviceId } from "wire-mesh-core/generated/protocol";
5
+ /** A data-domain log id is a device-id, which is a SHA-256 digest. */
6
+ export declare const WRITER_LOG_ID_BYTES = 32;
7
+ /** The random half of a writer log id; the other half authenticates it. */
8
+ export declare const WRITER_NONCE_BYTES: number;
9
+ /** The two keys every ledger operation needs, derived once from the account's private key. */
10
+ export interface AccountLedgerKeys {
11
+ /** Seals and opens entries. */
12
+ entryKey: Buffer;
13
+ /** Derives and recognises writer log ids. */
14
+ writerKey: Buffer;
15
+ }
16
+ /** Derives the ledger's keys from the account's PEM private key. Every copy of the same account key derives the same keys, which is what lets every machine holding it read what any of them wrote. */
17
+ export declare function deriveAccountLedgerKeys(accountPrivateKeyPem: string): AccountLedgerKeys;
18
+ /** A fresh random writer nonce, generated once per machine. */
19
+ export declare function generateWriterNonce(): Uint8Array<ArrayBuffer>;
20
+ /** The log id a machine with this writer nonce appends the account's ledger entries under: the nonce followed by a truncated HMAC of it under the account's writer key. */
21
+ export declare function writerLogId(keys: Readonly<AccountLedgerKeys>, nonce: Uint8Array): DeviceId;
22
+ /** Whether a log id is one of this account's writer logs. Only a holder of the account key can tell; to anyone else it is indistinguishable from a device-id. */
23
+ export declare function isWriterLogId(keys: Readonly<AccountLedgerKeys>, logId: Uint8Array): boolean;
24
+ /** Seals one ledger entry's plaintext for position `seq` of log `logId`: version, IV, tag, then ciphertext. */
25
+ export declare function sealLedgerEntry(keys: Readonly<AccountLedgerKeys>, logId: Uint8Array, seq: number, plaintext: Uint8Array): Uint8Array<ArrayBuffer>;
26
+ /** Opens a sealed entry, or returns undefined if it was not sealed by a holder of this account key for exactly this log and position (tampered, moved, truncated, or someone else's). */
27
+ export declare function openLedgerEntry(keys: Readonly<AccountLedgerKeys>, logId: Uint8Array, seq: number, sealed: Uint8Array): Uint8Array<ArrayBuffer> | undefined;
28
+ //# sourceMappingURL=account-ledger-crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-ledger-crypto.d.ts","sourceRoot":"","sources":["../../src/core/account-ledger-crypto.ts"],"names":[],"mappings":"AAAA;;GAEG;AAWH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAWlE,sEAAsE;AACtE,eAAO,MAAM,mBAAmB,KAAK,CAAC;AACtC,2EAA2E;AAC3E,eAAO,MAAM,kBAAkB,QAA0B,CAAC;AAQ1D,8FAA8F;AAC9F,MAAM,WAAW,iBAAiB;IAChC,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,uMAAuM;AACvM,wBAAgB,uBAAuB,CACrC,oBAAoB,EAAE,MAAM,GAC3B,iBAAiB,CAWnB;AASD,+DAA+D;AAC/D,wBAAgB,mBAAmB,IAAI,UAAU,CAAC,WAAW,CAAC,CAE7D;AAED,2KAA2K;AAC3K,wBAAgB,WAAW,CACzB,IAAI,EAAE,QAAQ,CAAC,iBAAiB,CAAC,EACjC,KAAK,EAAE,UAAU,GAChB,QAAQ,CAUV;AAED,iKAAiK;AACjK,wBAAgB,aAAa,CAC3B,IAAI,EAAE,QAAQ,CAAC,iBAAiB,CAAC,EACjC,KAAK,EAAE,UAAU,GAChB,OAAO,CAOT;AAQD,+GAA+G;AAC/G,wBAAgB,eAAe,CAC7B,IAAI,EAAE,QAAQ,CAAC,iBAAiB,CAAC,EACjC,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,UAAU,GACpB,UAAU,CAAC,WAAW,CAAC,CAazB;AAED,yLAAyL;AACzL,wBAAgB,eAAe,CAC7B,IAAI,EAAE,QAAQ,CAAC,iBAAiB,CAAC,EACjC,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,UAAU,GACjB,UAAU,CAAC,WAAW,CAAC,GAAG,SAAS,CAoBrC"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * The confidentiality layer of the account's replicated grant ledger (agent-comms#344). A `core/data` entry is opaque bytes to the transport and is replicated to whoever asks for it, so every ledger entry is sealed with a key only a holder of the account's private key can derive: AES-256-GCM under an HKDF-SHA256 key from the account key, with the log it belongs to and its sequence number bound in as associated data so a relayer can neither read an entry nor move one to another log or position. The same derivation gives each machine's writer log an id that any account holder recognises and nobody else can distinguish from a random device-id.
3
+ */
4
+ import { createCipheriv, createDecipheriv, createHmac, createPrivateKey, hkdfSync, randomBytes, timingSafeEqual, } from "node:crypto";
5
+ /** AES-256 takes a 256-bit key. */
6
+ const KEY_BYTES = 32;
7
+ /** The 96-bit IV GCM is specified for; any other length is hashed into one, which weakens its guarantees. */
8
+ const IV_BYTES = 12;
9
+ /** GCM's full-length authentication tag. */
10
+ const TAG_BYTES = 16;
11
+ /** The first byte of every sealed entry, so a later format can be told apart from this one. */
12
+ const SEALED_ENTRY_VERSION = 1;
13
+ const VERSION_BYTES = 1;
14
+ /** A data-domain log id is a device-id, which is a SHA-256 digest. */
15
+ export const WRITER_LOG_ID_BYTES = 32;
16
+ /** The random half of a writer log id; the other half authenticates it. */
17
+ export const WRITER_NONCE_BYTES = WRITER_LOG_ID_BYTES / 2;
18
+ /** A sequence number is bound into the associated data as a 64-bit big-endian integer. */
19
+ const SEQ_BYTES = 8;
20
+ const ENTRY_KEY_INFO = "agent-comms/account-ledger/entry/v1";
21
+ const WRITER_KEY_INFO = "agent-comms/account-ledger/writer/v1";
22
+ const ASSOCIATED_DATA_LABEL = "agent-comms/account-ledger/v1";
23
+ /** Derives the ledger's keys from the account's PEM private key. Every copy of the same account key derives the same keys, which is what lets every machine holding it read what any of them wrote. */
24
+ export function deriveAccountLedgerKeys(accountPrivateKeyPem) {
25
+ const ikm = createPrivateKey(accountPrivateKeyPem).export({
26
+ format: "der",
27
+ type: "pkcs8",
28
+ });
29
+ const derive = (info) => Buffer.from(hkdfSync("sha256", ikm, Buffer.alloc(0), info, KEY_BYTES));
30
+ return {
31
+ entryKey: derive(ENTRY_KEY_INFO),
32
+ writerKey: derive(WRITER_KEY_INFO),
33
+ };
34
+ }
35
+ function writerTag(writerKey, nonce) {
36
+ return createHmac("sha256", writerKey)
37
+ .update(nonce)
38
+ .digest()
39
+ .subarray(0, WRITER_LOG_ID_BYTES - WRITER_NONCE_BYTES);
40
+ }
41
+ /** A fresh random writer nonce, generated once per machine. */
42
+ export function generateWriterNonce() {
43
+ return Uint8Array.from(randomBytes(WRITER_NONCE_BYTES));
44
+ }
45
+ /** The log id a machine with this writer nonce appends the account's ledger entries under: the nonce followed by a truncated HMAC of it under the account's writer key. */
46
+ export function writerLogId(keys, nonce) {
47
+ if (nonce.length !== WRITER_NONCE_BYTES) {
48
+ throw new Error(`a writer nonce is ${String(WRITER_NONCE_BYTES)} bytes, got ${String(nonce.length)}`);
49
+ }
50
+ const id = new Uint8Array(WRITER_LOG_ID_BYTES);
51
+ id.set(nonce, 0);
52
+ id.set(writerTag(keys.writerKey, nonce), WRITER_NONCE_BYTES);
53
+ return id;
54
+ }
55
+ /** Whether a log id is one of this account's writer logs. Only a holder of the account key can tell; to anyone else it is indistinguishable from a device-id. */
56
+ export function isWriterLogId(keys, logId) {
57
+ if (logId.length !== WRITER_LOG_ID_BYTES)
58
+ return false;
59
+ const expected = writerTag(keys.writerKey, logId.subarray(0, WRITER_NONCE_BYTES));
60
+ return timingSafeEqual(expected, logId.subarray(WRITER_NONCE_BYTES));
61
+ }
62
+ function associatedData(logId, seq) {
63
+ const seqBytes = Buffer.alloc(SEQ_BYTES);
64
+ seqBytes.writeBigUInt64BE(BigInt(seq));
65
+ return Buffer.concat([Buffer.from(ASSOCIATED_DATA_LABEL), logId, seqBytes]);
66
+ }
67
+ /** Seals one ledger entry's plaintext for position `seq` of log `logId`: version, IV, tag, then ciphertext. */
68
+ export function sealLedgerEntry(keys, logId, seq, plaintext) {
69
+ const iv = randomBytes(IV_BYTES);
70
+ const cipher = createCipheriv("aes-256-gcm", keys.entryKey, iv);
71
+ cipher.setAAD(associatedData(logId, seq));
72
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
73
+ return Uint8Array.from(Buffer.concat([
74
+ Buffer.from([SEALED_ENTRY_VERSION]),
75
+ iv,
76
+ cipher.getAuthTag(),
77
+ ciphertext,
78
+ ]));
79
+ }
80
+ /** Opens a sealed entry, or returns undefined if it was not sealed by a holder of this account key for exactly this log and position (tampered, moved, truncated, or someone else's). */
81
+ export function openLedgerEntry(keys, logId, seq, sealed) {
82
+ const headerBytes = VERSION_BYTES + IV_BYTES + TAG_BYTES;
83
+ if (sealed.length < headerBytes || sealed[0] !== SEALED_ENTRY_VERSION) {
84
+ return undefined;
85
+ }
86
+ const iv = sealed.subarray(VERSION_BYTES, VERSION_BYTES + IV_BYTES);
87
+ const tag = sealed.subarray(VERSION_BYTES + IV_BYTES, headerBytes);
88
+ const decipher = createDecipheriv("aes-256-gcm", keys.entryKey, iv);
89
+ decipher.setAAD(associatedData(logId, seq));
90
+ decipher.setAuthTag(tag);
91
+ try {
92
+ return Uint8Array.from(Buffer.concat([
93
+ decipher.update(sealed.subarray(headerBytes)),
94
+ decipher.final(),
95
+ ]));
96
+ }
97
+ catch {
98
+ return undefined;
99
+ }
100
+ }
101
+ //# sourceMappingURL=account-ledger-crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-ledger-crypto.js","sourceRoot":"","sources":["../../src/core/account-ledger-crypto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Opens this machine's copy of the account's replicated grant ledger (account-ledger.ts): a filesystem store beside user-identity.json, shared by every bridge on the machine, and the one-time migration of a pre-ledger user-identity.json's own issued-grant maps into it.
3
+ */
4
+ import type { Clock } from "wire-mesh-core/ports/clock";
5
+ import { AccountLedger } from "./account-ledger.js";
6
+ import type { PeerIdentity } from "./identity.js";
7
+ import { type UserIdentityOptions } from "./user-identity.js";
8
+ export interface OpenAccountLedgerOptions {
9
+ /** Where user-identity.json lives; the ledger is kept beside it. */
10
+ userIdentityOptions: Readonly<UserIdentityOptions>;
11
+ /** The account's identity as loadOrCreateUserIdentity returned it. */
12
+ userIdentity: Readonly<PeerIdentity>;
13
+ clock: Clock;
14
+ }
15
+ /** The directory an account's ledger is stored in on this machine. Named by the account's device-id, so a machine that imports a different account starts that account's ledger afresh instead of mixing two accounts' logs. */
16
+ export declare function accountLedgerDir(userIdentityOptions: Readonly<UserIdentityOptions>, userIdentity: Readonly<PeerIdentity>): string;
17
+ /**
18
+ * Opens the account ledger for this machine and migrates any grants a pre-ledger build recorded in user-identity.json into it, so an existing user-identity.json needs no manual step: every grant it knew about stays revocable, now from any machine holding the key. The legacy maps are removed only after their grants are in the ledger, so a crash between the two re-migrates them on the next start, which is harmless because the ledger folds a grant recorded twice into one.
19
+ */
20
+ export declare function openAccountLedger(options: Readonly<OpenAccountLedgerOptions>): Promise<AccountLedger>;
21
+ //# sourceMappingURL=account-ledger-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-ledger-store.d.ts","sourceRoot":"","sources":["../../src/core/account-ledger-store.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAC;AAExD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,EAKL,KAAK,mBAAmB,EACzB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,wBAAwB;IACvC,oEAAoE;IACpE,mBAAmB,EAAE,QAAQ,CAAC,mBAAmB,CAAC,CAAC;IACnD,sEAAsE;IACtE,YAAY,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;IACrC,KAAK,EAAE,KAAK,CAAC;CACd;AAED,gOAAgO;AAChO,wBAAgB,gBAAgB,CAC9B,mBAAmB,EAAE,QAAQ,CAAC,mBAAmB,CAAC,EAClD,YAAY,EAAE,QAAQ,CAAC,YAAY,CAAC,GACnC,MAAM,CAKR;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,QAAQ,CAAC,wBAAwB,CAAC,GAC1C,OAAO,CAAC,aAAa,CAAC,CAqBxB"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Opens this machine's copy of the account's replicated grant ledger (account-ledger.ts): a filesystem store beside user-identity.json, shared by every bridge on the machine, and the one-time migration of a pre-ledger user-identity.json's own issued-grant maps into it.
3
+ */
4
+ import * as path from "node:path";
5
+ import { createNodeFsStorage } from "wire-mesh-core/adapters/node-fs-storage";
6
+ import { deviceIdToHex } from "wire-mesh-core/domain/device-id";
7
+ import { generateWriterNonce } from "./account-ledger-crypto.js";
8
+ import { AccountLedger } from "./account-ledger.js";
9
+ import { createFileLedgerLock } from "./ledger-lock.js";
10
+ import { clearLegacyIssuedGrants, loadOrCreateLedgerWriterNonce, readLegacyIssuedGrants, userIdentityDir, } from "./user-identity.js";
11
+ /** The directory an account's ledger is stored in on this machine. Named by the account's device-id, so a machine that imports a different account starts that account's ledger afresh instead of mixing two accounts' logs. */
12
+ export function accountLedgerDir(userIdentityOptions, userIdentity) {
13
+ return path.join(userIdentityDir(userIdentityOptions), `account-ledger-${deviceIdToHex(Uint8Array.from(userIdentity.deviceId))}`);
14
+ }
15
+ /**
16
+ * Opens the account ledger for this machine and migrates any grants a pre-ledger build recorded in user-identity.json into it, so an existing user-identity.json needs no manual step: every grant it knew about stays revocable, now from any machine holding the key. The legacy maps are removed only after their grants are in the ledger, so a crash between the two re-migrates them on the next start, which is harmless because the ledger folds a grant recorded twice into one.
17
+ */
18
+ export async function openAccountLedger(options) {
19
+ const dir = accountLedgerDir(options.userIdentityOptions, options.userIdentity);
20
+ const ledger = new AccountLedger({
21
+ accountPrivateKey: options.userIdentity.privateKey,
22
+ writerNonce: loadOrCreateLedgerWriterNonce(options.userIdentityOptions, generateWriterNonce),
23
+ storage: createNodeFsStorage({ dir }),
24
+ lock: createFileLedgerLock(`${dir}.lock`),
25
+ clock: options.clock,
26
+ });
27
+ const legacy = readLegacyIssuedGrants(options.userIdentityOptions);
28
+ if (legacy.length > 0) {
29
+ await ledger.recordGrants(legacy);
30
+ clearLegacyIssuedGrants(options.userIdentityOptions, legacy);
31
+ }
32
+ return ledger;
33
+ }
34
+ //# sourceMappingURL=account-ledger-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-ledger-store.js","sourceRoot":"","sources":["../../src/core/account-ledger-store.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAEhE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EACL,uBAAuB,EACvB,6BAA6B,EAC7B,sBAAsB,EACtB,eAAe,GAEhB,MAAM,oBAAoB,CAAC;AAU5B,gOAAgO;AAChO,MAAM,UAAU,gBAAgB,CAC9B,mBAAkD,EAClD,YAAoC;IAEpC,OAAO,IAAI,CAAC,IAAI,CACd,eAAe,CAAC,mBAAmB,CAAC,EACpC,kBAAkB,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,EAAE,CAC1E,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAA2C;IAE3C,MAAM,GAAG,GAAG,gBAAgB,CAC1B,OAAO,CAAC,mBAAmB,EAC3B,OAAO,CAAC,YAAY,CACrB,CAAC;IACF,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC;QAC/B,iBAAiB,EAAE,OAAO,CAAC,YAAY,CAAC,UAAU;QAClD,WAAW,EAAE,6BAA6B,CACxC,OAAO,CAAC,mBAAmB,EAC3B,mBAAmB,CACpB;QACD,OAAO,EAAE,mBAAmB,CAAC,EAAE,GAAG,EAAE,CAAC;QACrC,IAAI,EAAE,oBAAoB,CAAC,GAAG,GAAG,OAAO,CAAC;QACzC,KAAK,EAAE,OAAO,CAAC,KAAK;KACrB,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,sBAAsB,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACnE,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAClC,uBAAuB,CAAC,OAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The account's issued-grant ledger as a replicated object (agent-comms#344), following wire-mesh's replicated issuer grant ledger pattern (spec/PATTERNS.md). A token-id never appears on the wire except inside the grant it names, so the only record that a grant exists is what its issuer kept; with the ledger in one machine's user-identity.json, only that machine could revoke what it minted. Here every grant the account's user principal mints, and every revocation of one, is an entry in a `core/data` log, and the logs replicate by data-domain fan-out to every machine holding the account key, so any of them sees every admission and can revoke it.
3
+ *
4
+ * Each machine appends to a writer log of its own rather than all of them sharing one: a data-domain log is single-writer, and two machines appending to one log while apart would both write the same sequence number and fork it, silently losing one side's grants. A writer log's id is self-authenticating (account-ledger-crypto.ts), so any account holder recognises every writer log it is offered without a roster, and the ledger is the union of all of them.
5
+ *
6
+ * Holding a replica grants no authority: an entry is sealed under a key derived from the account key, so a replica held by anyone else is ciphertext they can neither read nor forge, and minting or revoking still needs the account's private key.
7
+ */
8
+ import { type DataEntriesFrame, type DataHaveFrame, type DataRequestFrame, type DeviceId, type RevocationEntry } from "wire-mesh-core/generated/protocol";
9
+ import type { RevocationView } from "wire-mesh-core/domain/revocation-view";
10
+ import type { Clock } from "wire-mesh-core/ports/clock";
11
+ import type { IdentityPort } from "wire-mesh-core/ports/identity";
12
+ import type { KeyValueStorage } from "wire-mesh-core/ports/storage";
13
+ import type { LedgerLock } from "./ledger-lock.js";
14
+ /** Which of the principal's grant kinds an entry is about: a device's own group:member admission (device-membership.ts), or a dm:send grant (room-lifecycle.ts, dm-send-delegation.ts). Each kind is keyed by the grant's bearer, so the two never share a namespace. */
15
+ export type IssuedGrantKind = "device" | "dm";
16
+ /** One grant the ledger records as issued and not revoked. */
17
+ export interface OutstandingGrant {
18
+ tokenId: Uint8Array<ArrayBuffer>;
19
+ /** When it was recorded (epoch ms, the recording machine's clock). */
20
+ at: number;
21
+ }
22
+ export interface AccountLedgerDeps {
23
+ /** The account's PEM private key: every ledger key is derived from it. */
24
+ accountPrivateKey: string;
25
+ /** This machine's writer nonce, persisted once per machine (user-identity.ts). */
26
+ writerNonce: Uint8Array;
27
+ /** Holds every account writer log this machine has, its own included. Shared by every bridge on the machine. */
28
+ storage: KeyValueStorage;
29
+ /** Serialises every read and write of `storage` across every bridge sharing it: appends to this machine's writer log, replicated entries stored for another machine's, and every read of either. The store's writes are not atomic (a file is truncated, then written), so a read racing another bridge's write could see an empty head and treat a log as empty. */
30
+ lock: LedgerLock;
31
+ clock: Clock;
32
+ }
33
+ /** Answers and stores the data-domain frames for the account's writer logs; what a transport needs of the ledger to replicate it. */
34
+ export interface AccountLedgerReplica {
35
+ /** Whether `peer` names one of this account's writer logs. */
36
+ ownsLog: (peer: DeviceId) => boolean;
37
+ /** Answers a have or request for an account log, or stores entries for one after checking every entry was sealed for exactly that log and position. Returns the frame to send back, or null for none. */
38
+ handleDataFrame: (frame: Readonly<DataHaveFrame | DataRequestFrame | DataEntriesFrame>) => Promise<DataHaveFrame | DataRequestFrame | DataEntriesFrame | null>;
39
+ /** A data-have for every account log this machine holds, to offer peers. */
40
+ announcements: () => Promise<DataHaveFrame[]>;
41
+ }
42
+ export declare class AccountLedger implements AccountLedgerReplica {
43
+ private readonly deps;
44
+ /** This machine's own writer log. */
45
+ readonly writerLog: DeviceId;
46
+ private readonly keys;
47
+ private readonly revocationListeners;
48
+ constructor(deps: Readonly<AccountLedgerDeps>);
49
+ /** Records that the principal minted `tokenId` for `subjectHex`. */
50
+ recordGrant(kind: IssuedGrantKind, subjectHex: string, tokenId: Uint8Array<ArrayBuffer>): Promise<void>;
51
+ /** Records grants migrated from a pre-ledger user-identity.json in one append, so a restart part-way through leaves nothing half-recorded worth caring about: a grant recorded twice folds to one. */
52
+ recordGrants(grants: readonly Readonly<{
53
+ kind: IssuedGrantKind;
54
+ subject: string;
55
+ tokenId: Uint8Array<ArrayBuffer>;
56
+ }>[]): Promise<void>;
57
+ /** Records the revocation of `tokenId`, carrying the signed revocation entry itself so every replica can feed it into its own RevocationView. */
58
+ recordRevocation(kind: IssuedGrantKind, subjectHex: string, tokenId: Uint8Array<ArrayBuffer>, revocation: RevocationEntry): Promise<void>;
59
+ /** Every grant of `kind` for `subjectHex` that some machine of the account recorded and none has revoked, oldest first. */
60
+ outstandingGrants(kind: IssuedGrantKind, subjectHex: string): Promise<OutstandingGrant[]>;
61
+ /** Every revocation entry the ledger holds, from every machine, for a RevocationView to learn at start-up. */
62
+ revocations(): Promise<RevocationEntry[]>;
63
+ ownsLog(peer: DeviceId): boolean;
64
+ /** Calls `listener` with each revocation entry another machine's replicated log brings in, so a RevocationView learns it even if the gossiped revocation-announce never reached this node. Returns the unsubscribe. */
65
+ onReplicatedRevocation(listener: (entry: RevocationEntry) => void): () => void;
66
+ announcements(): Promise<DataHaveFrame[]>;
67
+ handleDataFrame(frame: Readonly<DataHaveFrame | DataRequestFrame | DataEntriesFrame>): Promise<DataHaveFrame | DataRequestFrame | DataEntriesFrame | null>;
68
+ private append;
69
+ /** Every account writer log in storage, this machine's own included once it has written anything. Called only under the lock. */
70
+ private heldLogs;
71
+ /** Every record in every held writer log. Throws on an entry that does not open: handleDataFrame and append never store one, so it means the storage was damaged outside the ledger, and carrying on would hide grants that can then never be revoked. */
72
+ private records;
73
+ }
74
+ /** Feeds every revocation the account ledger holds, and each one another machine's log brings in later, into a store's RevocationView, so a grant any machine of the account revoked is refused there even if the gossiped revocation-announce never reached it. A revocation that fails to verify, or a ledger that cannot be read, is reported rather than recorded. Returns the unsubscribe, for a store that replaces its ledger on joining another account. */
75
+ export declare function followLedgerRevocations(identity: Readonly<{
76
+ accountLedger: AccountLedger;
77
+ revocation: RevocationView;
78
+ identity: IdentityPort;
79
+ }>, onError: (error: Error) => void): () => void;
80
+ //# sourceMappingURL=account-ledger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-ledger.d.ts","sourceRoot":"","sources":["../../src/core/account-ledger.ts"],"names":[],"mappings":"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"}