@joe-broadhead/messaging 0.0.0-stage → 0.7.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 (257) hide show
  1. package/CHANGELOG.md +175 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE +12 -0
  4. package/README.md +36 -2
  5. package/dist/bin/messaging-mcp.js +25887 -0
  6. package/dist/bin/messaging-relay.js +19014 -0
  7. package/dist/bin/messaging.js +14732 -0
  8. package/dist/bin/messagingd.js +28682 -0
  9. package/dist/lib/core-testing.js +1 -0
  10. package/dist/lib/core.js +1 -0
  11. package/dist/lib/ipc-testing.js +1 -0
  12. package/dist/lib/ipc.js +1 -0
  13. package/dist/lib/modules/core/address.js +42 -0
  14. package/dist/lib/modules/core/agent/operations.js +595 -0
  15. package/dist/lib/modules/core/agent/present.js +244 -0
  16. package/dist/lib/modules/core/agent/text.js +79 -0
  17. package/dist/lib/modules/core/agent-tools.js +458 -0
  18. package/dist/lib/modules/core/b64u.js +20 -0
  19. package/dist/lib/modules/core/cards.js +55 -0
  20. package/dist/lib/modules/core/check.js +20 -0
  21. package/dist/lib/modules/core/consent.js +199 -0
  22. package/dist/lib/modules/core/contact-card.js +74 -0
  23. package/dist/lib/modules/core/delegation.js +174 -0
  24. package/dist/lib/modules/core/directory/device-log.js +258 -0
  25. package/dist/lib/modules/core/directory/epoch.js +103 -0
  26. package/dist/lib/modules/core/directory/handle.js +29 -0
  27. package/dist/lib/modules/core/directory/jelly-device.js +204 -0
  28. package/dist/lib/modules/core/directory/jelly-issuer.js +12 -0
  29. package/dist/lib/modules/core/directory/jelly-token.js +305 -0
  30. package/dist/lib/modules/core/directory/keys-statement.js +43 -0
  31. package/dist/lib/modules/core/directory/link.js +81 -0
  32. package/dist/lib/modules/core/directory/merkle-log.js +250 -0
  33. package/dist/lib/modules/core/directory/person-view.js +197 -0
  34. package/dist/lib/modules/core/directory/person.js +40 -0
  35. package/dist/lib/modules/core/directory/realm.js +59 -0
  36. package/dist/lib/modules/core/directory/smt.js +266 -0
  37. package/dist/lib/modules/core/envelope.js +320 -0
  38. package/dist/lib/modules/core/hash.js +5 -0
  39. package/dist/lib/modules/core/head.js +27 -0
  40. package/dist/lib/modules/core/identity.js +71 -0
  41. package/dist/lib/modules/core/index.js +44 -0
  42. package/dist/lib/modules/core/jcs.js +96 -0
  43. package/dist/lib/modules/core/jelly-host-double.js +35 -0
  44. package/dist/lib/modules/core/jelly-host.js +211 -0
  45. package/dist/lib/modules/core/keys.js +79 -0
  46. package/dist/lib/modules/core/peer-text.js +563 -0
  47. package/dist/lib/modules/core/realm.js +1 -0
  48. package/dist/lib/modules/core/roster.js +454 -0
  49. package/dist/lib/modules/core/schema/a2a.js +85 -0
  50. package/dist/lib/modules/core/schema/common.js +254 -0
  51. package/dist/lib/modules/core/schema/consent.js +120 -0
  52. package/dist/lib/modules/core/schema/delegation.js +58 -0
  53. package/dist/lib/modules/core/schema/directory-frames.js +270 -0
  54. package/dist/lib/modules/core/schema/directory.js +169 -0
  55. package/dist/lib/modules/core/schema/envelope.js +211 -0
  56. package/dist/lib/modules/core/schema/errors.js +65 -0
  57. package/dist/lib/modules/core/schema/index.js +15 -0
  58. package/dist/lib/modules/core/schema/ipc/consent.js +181 -0
  59. package/dist/lib/modules/core/schema/ipc/device.js +276 -0
  60. package/dist/lib/modules/core/schema/ipc/jelly.js +189 -0
  61. package/dist/lib/modules/core/schema/ipc/shapes.js +185 -0
  62. package/dist/lib/modules/core/schema/ipc/surfaces.js +223 -0
  63. package/dist/lib/modules/core/schema/ipc.js +451 -0
  64. package/dist/lib/modules/core/schema/jelly.js +122 -0
  65. package/dist/lib/modules/core/schema/realm.js +27 -0
  66. package/dist/lib/modules/core/schema/relay.js +112 -0
  67. package/dist/lib/modules/core/schema/roster.js +126 -0
  68. package/dist/lib/modules/core/sign.js +43 -0
  69. package/dist/lib/modules/core/task.js +37 -0
  70. package/dist/lib/modules/core/testing.js +20 -0
  71. package/dist/lib/modules/core/ulid.js +36 -0
  72. package/dist/lib/modules/core/version.js +6 -0
  73. package/dist/lib/modules/core/wire.js +255 -0
  74. package/dist/lib/modules/ipc/client.js +289 -0
  75. package/dist/lib/modules/ipc/framing.js +33 -0
  76. package/dist/lib/modules/ipc/index.js +6 -0
  77. package/dist/lib/modules/ipc/paths.js +102 -0
  78. package/dist/lib/modules/ipc/pipes.js +65 -0
  79. package/dist/lib/modules/ipc/rpc.js +8 -0
  80. package/dist/lib/modules/ipc/server.js +317 -0
  81. package/dist/lib/modules/ipc/testing.js +145 -0
  82. package/dist/lib/modules/ipc/trust.js +166 -0
  83. package/dist/lib/modules/ipc/windows-acl.js +275 -0
  84. package/dist/lib/modules/vendor/zod/index.js +7 -0
  85. package/dist/lib/modules/vendor/zod/v4/classic/checks.js +1 -0
  86. package/dist/lib/modules/vendor/zod/v4/classic/coerce.js +17 -0
  87. package/dist/lib/modules/vendor/zod/v4/classic/compat.js +31 -0
  88. package/dist/lib/modules/vendor/zod/v4/classic/deep-partial.js +13 -0
  89. package/dist/lib/modules/vendor/zod/v4/classic/errors.js +57 -0
  90. package/dist/lib/modules/vendor/zod/v4/classic/external.js +17 -0
  91. package/dist/lib/modules/vendor/zod/v4/classic/from-json-schema.js +881 -0
  92. package/dist/lib/modules/vendor/zod/v4/classic/in-out.js +36 -0
  93. package/dist/lib/modules/vendor/zod/v4/classic/iso.js +15 -0
  94. package/dist/lib/modules/vendor/zod/v4/classic/parse.js +16 -0
  95. package/dist/lib/modules/vendor/zod/v4/classic/schemas.js +1535 -0
  96. package/dist/lib/modules/vendor/zod/v4/core/api.js +1107 -0
  97. package/dist/lib/modules/vendor/zod/v4/core/checks.js +516 -0
  98. package/dist/lib/modules/vendor/zod/v4/core/compile.js +1866 -0
  99. package/dist/lib/modules/vendor/zod/v4/core/core.js +141 -0
  100. package/dist/lib/modules/vendor/zod/v4/core/doc.js +38 -0
  101. package/dist/lib/modules/vendor/zod/v4/core/errors.js +267 -0
  102. package/dist/lib/modules/vendor/zod/v4/core/index.js +19 -0
  103. package/dist/lib/modules/vendor/zod/v4/core/json-schema-generator.js +99 -0
  104. package/dist/lib/modules/vendor/zod/v4/core/json-schema-processors.js +843 -0
  105. package/dist/lib/modules/vendor/zod/v4/core/json-schema.js +1 -0
  106. package/dist/lib/modules/vendor/zod/v4/core/memoizer.js +311 -0
  107. package/dist/lib/modules/vendor/zod/v4/core/parse.js +175 -0
  108. package/dist/lib/modules/vendor/zod/v4/core/regexes.js +156 -0
  109. package/dist/lib/modules/vendor/zod/v4/core/registries.js +51 -0
  110. package/dist/lib/modules/vendor/zod/v4/core/schemas.js +2636 -0
  111. package/dist/lib/modules/vendor/zod/v4/core/to-json-schema.js +651 -0
  112. package/dist/lib/modules/vendor/zod/v4/core/util.js +873 -0
  113. package/dist/lib/modules/vendor/zod/v4/core/versions.js +5 -0
  114. package/dist/lib/modules/vendor/zod/v4/core/visit.js +169 -0
  115. package/dist/lib/modules/vendor/zod/v4/locales/ar.js +111 -0
  116. package/dist/lib/modules/vendor/zod/v4/locales/az.js +110 -0
  117. package/dist/lib/modules/vendor/zod/v4/locales/be.js +168 -0
  118. package/dist/lib/modules/vendor/zod/v4/locales/bg.js +125 -0
  119. package/dist/lib/modules/vendor/zod/v4/locales/bn.js +113 -0
  120. package/dist/lib/modules/vendor/zod/v4/locales/ca.js +112 -0
  121. package/dist/lib/modules/vendor/zod/v4/locales/ckb.js +133 -0
  122. package/dist/lib/modules/vendor/zod/v4/locales/cs.js +116 -0
  123. package/dist/lib/modules/vendor/zod/v4/locales/da.js +120 -0
  124. package/dist/lib/modules/vendor/zod/v4/locales/de.js +113 -0
  125. package/dist/lib/modules/vendor/zod/v4/locales/el.js +112 -0
  126. package/dist/lib/modules/vendor/zod/v4/locales/en.js +125 -0
  127. package/dist/lib/modules/vendor/zod/v4/locales/eo.js +114 -0
  128. package/dist/lib/modules/vendor/zod/v4/locales/es.js +136 -0
  129. package/dist/lib/modules/vendor/zod/v4/locales/fa.js +119 -0
  130. package/dist/lib/modules/vendor/zod/v4/locales/fi.js +117 -0
  131. package/dist/lib/modules/vendor/zod/v4/locales/fr-CA.js +112 -0
  132. package/dist/lib/modules/vendor/zod/v4/locales/fr.js +129 -0
  133. package/dist/lib/modules/vendor/zod/v4/locales/gu.js +113 -0
  134. package/dist/lib/modules/vendor/zod/v4/locales/he.js +221 -0
  135. package/dist/lib/modules/vendor/zod/v4/locales/hi.js +111 -0
  136. package/dist/lib/modules/vendor/zod/v4/locales/hr.js +126 -0
  137. package/dist/lib/modules/vendor/zod/v4/locales/hu.js +113 -0
  138. package/dist/lib/modules/vendor/zod/v4/locales/hy.js +158 -0
  139. package/dist/lib/modules/vendor/zod/v4/locales/id.js +111 -0
  140. package/dist/lib/modules/vendor/zod/v4/locales/index.js +63 -0
  141. package/dist/lib/modules/vendor/zod/v4/locales/is.js +114 -0
  142. package/dist/lib/modules/vendor/zod/v4/locales/it.js +113 -0
  143. package/dist/lib/modules/vendor/zod/v4/locales/ja.js +112 -0
  144. package/dist/lib/modules/vendor/zod/v4/locales/ka.js +117 -0
  145. package/dist/lib/modules/vendor/zod/v4/locales/kh.js +5 -0
  146. package/dist/lib/modules/vendor/zod/v4/locales/km.js +115 -0
  147. package/dist/lib/modules/vendor/zod/v4/locales/kn.js +116 -0
  148. package/dist/lib/modules/vendor/zod/v4/locales/ko.js +116 -0
  149. package/dist/lib/modules/vendor/zod/v4/locales/lt.js +207 -0
  150. package/dist/lib/modules/vendor/zod/v4/locales/mk.js +114 -0
  151. package/dist/lib/modules/vendor/zod/v4/locales/ms.js +112 -0
  152. package/dist/lib/modules/vendor/zod/v4/locales/ne.js +111 -0
  153. package/dist/lib/modules/vendor/zod/v4/locales/nl.js +115 -0
  154. package/dist/lib/modules/vendor/zod/v4/locales/nn.js +113 -0
  155. package/dist/lib/modules/vendor/zod/v4/locales/no.js +113 -0
  156. package/dist/lib/modules/vendor/zod/v4/locales/ota.js +114 -0
  157. package/dist/lib/modules/vendor/zod/v4/locales/pl.js +114 -0
  158. package/dist/lib/modules/vendor/zod/v4/locales/ps.js +119 -0
  159. package/dist/lib/modules/vendor/zod/v4/locales/pt-BR.js +143 -0
  160. package/dist/lib/modules/vendor/zod/v4/locales/pt.js +142 -0
  161. package/dist/lib/modules/vendor/zod/v4/locales/ro.js +122 -0
  162. package/dist/lib/modules/vendor/zod/v4/locales/ru.js +168 -0
  163. package/dist/lib/modules/vendor/zod/v4/locales/sk.js +116 -0
  164. package/dist/lib/modules/vendor/zod/v4/locales/sl.js +114 -0
  165. package/dist/lib/modules/vendor/zod/v4/locales/sv.js +115 -0
  166. package/dist/lib/modules/vendor/zod/v4/locales/ta.js +115 -0
  167. package/dist/lib/modules/vendor/zod/v4/locales/tg.js +117 -0
  168. package/dist/lib/modules/vendor/zod/v4/locales/th.js +115 -0
  169. package/dist/lib/modules/vendor/zod/v4/locales/tk.js +107 -0
  170. package/dist/lib/modules/vendor/zod/v4/locales/tr.js +110 -0
  171. package/dist/lib/modules/vendor/zod/v4/locales/ua.js +5 -0
  172. package/dist/lib/modules/vendor/zod/v4/locales/uk.js +113 -0
  173. package/dist/lib/modules/vendor/zod/v4/locales/ur.js +115 -0
  174. package/dist/lib/modules/vendor/zod/v4/locales/uz.js +113 -0
  175. package/dist/lib/modules/vendor/zod/v4/locales/vi.js +113 -0
  176. package/dist/lib/modules/vendor/zod/v4/locales/yo.js +112 -0
  177. package/dist/lib/modules/vendor/zod/v4/locales/zh-CN.js +114 -0
  178. package/dist/lib/modules/vendor/zod/v4/locales/zh-TW.js +112 -0
  179. package/dist/lib/testing-jelly-id.js +1219 -0
  180. package/dist/lib/zod.js +1 -0
  181. package/dist/spec/messaging-v1.md +3426 -0
  182. package/dist/third-party-notices.md +239 -0
  183. package/dist/third-party.json +88 -0
  184. package/dist/types/core/address.d.ts +16 -0
  185. package/dist/types/core/agent/operations.d.ts +129 -0
  186. package/dist/types/core/agent/present.d.ts +57 -0
  187. package/dist/types/core/agent/text.d.ts +62 -0
  188. package/dist/types/core/agent-tools.d.ts +948 -0
  189. package/dist/types/core/b64u.d.ts +6 -0
  190. package/dist/types/core/cards.d.ts +21 -0
  191. package/dist/types/core/check.d.ts +10 -0
  192. package/dist/types/core/consent.d.ts +132 -0
  193. package/dist/types/core/contact-card.d.ts +34 -0
  194. package/dist/types/core/delegation.d.ts +71 -0
  195. package/dist/types/core/directory/device-log.d.ts +119 -0
  196. package/dist/types/core/directory/epoch.d.ts +53 -0
  197. package/dist/types/core/directory/handle.d.ts +11 -0
  198. package/dist/types/core/directory/jelly-device.d.ts +88 -0
  199. package/dist/types/core/directory/jelly-issuer.d.ts +8 -0
  200. package/dist/types/core/directory/jelly-token.d.ts +116 -0
  201. package/dist/types/core/directory/keys-statement.d.ts +22 -0
  202. package/dist/types/core/directory/link.d.ts +36 -0
  203. package/dist/types/core/directory/merkle-log.d.ts +40 -0
  204. package/dist/types/core/directory/person-view.d.ts +90 -0
  205. package/dist/types/core/directory/person.d.ts +8 -0
  206. package/dist/types/core/directory/realm.d.ts +23 -0
  207. package/dist/types/core/directory/smt.d.ts +52 -0
  208. package/dist/types/core/envelope.d.ts +68 -0
  209. package/dist/types/core/hash.d.ts +2 -0
  210. package/dist/types/core/head.d.ts +34 -0
  211. package/dist/types/core/identity.d.ts +35 -0
  212. package/dist/types/core/index.d.ts +38 -0
  213. package/dist/types/core/jcs.d.ts +12 -0
  214. package/dist/types/core/jelly-host-double.d.ts +21 -0
  215. package/dist/types/core/jelly-host.d.ts +86 -0
  216. package/dist/types/core/keys.d.ts +16 -0
  217. package/dist/types/core/peer-text.d.ts +87 -0
  218. package/dist/types/core/realm.d.ts +36 -0
  219. package/dist/types/core/roster.d.ts +146 -0
  220. package/dist/types/core/schema/a2a.d.ts +251 -0
  221. package/dist/types/core/schema/common.d.ts +272 -0
  222. package/dist/types/core/schema/consent.d.ts +140 -0
  223. package/dist/types/core/schema/delegation.d.ts +58 -0
  224. package/dist/types/core/schema/directory-frames.d.ts +1301 -0
  225. package/dist/types/core/schema/directory.d.ts +375 -0
  226. package/dist/types/core/schema/envelope.d.ts +750 -0
  227. package/dist/types/core/schema/errors.d.ts +104 -0
  228. package/dist/types/core/schema/index.d.ts +15 -0
  229. package/dist/types/core/schema/ipc/consent.d.ts +555 -0
  230. package/dist/types/core/schema/ipc/device.d.ts +729 -0
  231. package/dist/types/core/schema/ipc/jelly.d.ts +501 -0
  232. package/dist/types/core/schema/ipc/shapes.d.ts +411 -0
  233. package/dist/types/core/schema/ipc/surfaces.d.ts +674 -0
  234. package/dist/types/core/schema/ipc.d.ts +3131 -0
  235. package/dist/types/core/schema/jelly.d.ts +160 -0
  236. package/dist/types/core/schema/realm.d.ts +38 -0
  237. package/dist/types/core/schema/relay.d.ts +1122 -0
  238. package/dist/types/core/schema/roster.d.ts +347 -0
  239. package/dist/types/core/sign.d.ts +13 -0
  240. package/dist/types/core/task.d.ts +6 -0
  241. package/dist/types/core/testing.d.ts +18 -0
  242. package/dist/types/core/ulid.d.ts +5 -0
  243. package/dist/types/core/version.d.ts +6 -0
  244. package/dist/types/core/wire.d.ts +14 -0
  245. package/dist/types/ipc/client.d.ts +85 -0
  246. package/dist/types/ipc/framing.d.ts +12 -0
  247. package/dist/types/ipc/index.d.ts +6 -0
  248. package/dist/types/ipc/paths.d.ts +39 -0
  249. package/dist/types/ipc/pipes.d.ts +16 -0
  250. package/dist/types/ipc/rpc.d.ts +13 -0
  251. package/dist/types/ipc/server.d.ts +94 -0
  252. package/dist/types/ipc/testing.d.ts +89 -0
  253. package/dist/types/ipc/trust.d.ts +49 -0
  254. package/dist/types/ipc/windows-acl.d.ts +56 -0
  255. package/dist/types/testing/jelly-id.d.ts +518 -0
  256. package/dist/types/zod.d.ts +1 -0
  257. package/package.json +77 -4
@@ -0,0 +1,65 @@
1
+ import { z } from "../../vendor/zod/index.js";
2
+ /** The closed set of Messaging error codes. Every emitted code must be documented in docs/spec/messaging-v1.md. */
3
+ export const ErrorCode = z.enum([
4
+ "invalid_request", // malformed input or schema violation
5
+ "not_member", // sender or recipient machine is not a current team member
6
+ "bad_signature", // signature or seal failed verification
7
+ "unknown_session", // recipient session does not exist on the target machine
8
+ "ambiguous_address", // address matches more than one session
9
+ "no_recipients", // address resolved to nothing
10
+ "unknown_address", // an agent tool's address names no person, agent or channel this machine knows (client-side; never on the wire)
11
+ "refused", // recipient policy refuses this sender
12
+ "declined", // the recipient's person declined a held request (spec §6.9)
13
+ "request_required", // the recipient's person has not accepted this sender: a message request comes first (spec §6.9)
14
+ "rate_limited", // too many envelopes; retry later
15
+ "loop_guard", // agent-to-agent loop guard tripped
16
+ "too_large", // size limit exceeded
17
+ "secret_refused", // the sending daemon found a credential in outbound text; nothing was stored or sent
18
+ "expired", // envelope TTL passed, or ts outside the skew window
19
+ "duplicate", // envelope id already seen (receivers treat as success)
20
+ "relay_unavailable", // no relay connection
21
+ "not_found", // unknown id
22
+ "invalid_state", // illegal task transition or wrong role for the operation
23
+ "forbidden", // caller lacks authority (e.g. non-admin roster change)
24
+ "guarded_turn", // a guarded peer turn tried to send outside its allowance (client-side; never on the wire)
25
+ "roster_conflict", // roster entry does not extend the current head; resync and retry
26
+ "invite_invalid", // unknown, expired, revoked, used or mismatched invite
27
+ "no_identity", // daemon has no identity/team yet
28
+ "binding_invalid", // a device binding, log entry or its authority failed verification (hosted realms)
29
+ "jelly_unavailable", // Jelly ID's key set or key history could not be fetched; never reported as invalid
30
+ "log_conflict", // a device-log entry does not extend the log's head; resync and retry
31
+ "device_limit", // the person already has LIMITS.devicesPerPerson current devices
32
+ "admission_required", // dir.bind without a Jelly-vouched bind of the connecting device
33
+ "admission_invalid", // dir.bind whose Jelly token is not live, or not for this directory's issuer and resource
34
+ "directory_inconsistent", // the directory showed a split view, a fork or an inconsistent tree head; evidence kept, alarm raised
35
+ "unknown_device", // a directory realm: the sender's device is not a current device of its person
36
+ "directory_unavailable", // the key directory could not be reached to verify a person's devices within the freshness bound
37
+ "login_unsupported", // Jelly ID offers no device sign-in for this realm's resource (no v1.2 device sign-in in its metadata)
38
+ "login_denied", // the person denied the sign-in at Jelly ID, or the account is disabled or being deleted
39
+ "grant_required", // deployment "jelly": a human- or admin-tier call carried no action grant (spec §8.11)
40
+ "grant_invalid", // deployment "jelly": the action grant did not verify, is for another call, expired or was spent
41
+ "ticket_required", // deployment "jelly": session.register carried no session ticket
42
+ "ticket_invalid", // deployment "jelly": the session ticket did not verify, expired, or names another session or agent
43
+ "daemon_unavailable", // client could not reach the local daemon socket
44
+ "daemon_mismatch", // the daemon on the socket is another version or IPC protocol than the client (spec §8.2)
45
+ "internal", // unexpected failure; details are logged locally, never sent
46
+ ]);
47
+ export const ErrorBody = z.object({
48
+ code: ErrorCode,
49
+ message: z.string().max(500),
50
+ });
51
+ /** Error carrying a Messaging code. Messages must never contain bodies, keys or secrets. */
52
+ export class MessagingError extends Error {
53
+ code;
54
+ constructor(code, message, options) {
55
+ super(message, options);
56
+ this.name = "MessagingError";
57
+ this.code = code;
58
+ }
59
+ toBody() {
60
+ return { code: this.code, message: this.message.slice(0, 500) };
61
+ }
62
+ }
63
+ export function isMessagingError(value) {
64
+ return value instanceof MessagingError;
65
+ }
@@ -0,0 +1,15 @@
1
+ export * from "./common.js";
2
+ export * from "./errors.js";
3
+ export * from "./realm.js";
4
+ export * from "./jelly.js";
5
+ export * from "./directory.js";
6
+ export * as A2A from "./a2a.js";
7
+ export * from "./envelope.js";
8
+ export * from "./delegation.js";
9
+ export * from "./consent.js";
10
+ export * from "./roster.js";
11
+ export * from "./relay.js";
12
+ export * from "./directory-frames.js";
13
+ export * from "./ipc.js";
14
+ export * from "./ipc/device.js";
15
+ export * from "./ipc/consent.js";
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Consent methods (spec §8.2, "Consent"; §6.9): grants and held requests. Every one is human tier: none is in `AGENT_METHODS`, so the agent
3
+ * socket refuses them, and agents never set or read a person's grants. Spread into `IPC_METHODS`.
4
+ */
5
+ import { z } from "../../../vendor/zod/index.js";
6
+ import { ChannelName, LIMITS, MachineId, MachineLabel, Millis, PersonId, RoomRef, SessionAddress, SessionName, Ulid } from "../common.js";
7
+ import { AgentMailLevel, AskLevel, CardCan, ChatLevel } from "../consent.js";
8
+ import { Email } from "../directory-frames.js";
9
+ /** A grant's subject as a person names it: a person, or a channel by its name here. */
10
+ export const GrantTarget = z.union([z.strictObject({ person: PersonId }), z.strictObject({ channel: ChannelName })]);
11
+ const Levels = {
12
+ chat: ChatLevel.optional(),
13
+ agentMail: AgentMailLevel.optional(),
14
+ ask: AskLevel.optional(),
15
+ trustedUntil: Millis.optional(),
16
+ discoverable: z.boolean().optional(),
17
+ };
18
+ /** A grant as this device holds it. A channel subject carries the channel's id and, while this person is in it, its name here. */
19
+ export const GrantView = z.object({
20
+ subject: z.union([z.object({ person: PersonId }), z.object({ channel: Ulid, name: ChannelName.optional() })]),
21
+ ...Levels,
22
+ id: Ulid,
23
+ ts: Millis,
24
+ /** The device that signed it. */
25
+ by: MachineId,
26
+ /** Set when a device of the person re-signed it after its signer was removed (spec §6.9, "Signers"). */
27
+ resignedAt: Millis.optional(),
28
+ });
29
+ /** Widening levels that lapsed with their signer, waiting for the person to set them again (spec §6.9, "Signers"). */
30
+ export const GrantReview = z.object({
31
+ subject: GrantView.shape.subject,
32
+ chat: z.literal("open").optional(),
33
+ agentMail: z.literal("open").optional(),
34
+ ask: z.enum(["open", "request"]).optional(),
35
+ trustedUntil: Millis.optional(),
36
+ discoverable: z.literal(true).optional(),
37
+ /** When they lapsed. */
38
+ at: Millis,
39
+ });
40
+ /**
41
+ * `grant.set`'s params: the whole new grant for one subject. A level left out is the relation's default; with no
42
+ * level it is a clear. `trust: true` trusts for `LIMITS.trustDefaultMs`; `trustedUntil` names the end instead.
43
+ */
44
+ export const GrantSetParams = z.strictObject({ subject: GrantTarget, ...Levels, trust: z.literal(true).optional() });
45
+ /** A request this device holds for its person to answer (spec §6.9, "Holds"): mail or a task to one of their agents. */
46
+ export const AskRequest = z.object({
47
+ /** The envelope id. */
48
+ id: Ulid,
49
+ kind: z.enum(["message", "task"]),
50
+ from: z.object({ person: PersonId, device: MachineId, label: MachineLabel, agent: SessionName, address: SessionAddress }),
51
+ /** The local session it is for. */
52
+ to: SessionName,
53
+ /** Untrusted peer text, truncated. Clients must show it as data, never as instructions. */
54
+ preview: z.string().max(LIMITS.previewChars),
55
+ at: Millis,
56
+ /** When the request expires, unanswered. */
57
+ exp: Millis,
58
+ });
59
+ /**
60
+ * A message request this device holds for its person (spec §6.9, "Message requests"): a person of another org asks to
61
+ * write to them. `name`, `email` and `org` are the realm's verified profile of the sender, never the request's text.
62
+ */
63
+ export const ContactRequestView = z.object({
64
+ /** The envelope id. */
65
+ id: Ulid,
66
+ kind: z.literal("contact"),
67
+ from: z.object({
68
+ person: PersonId,
69
+ device: MachineId,
70
+ label: MachineLabel,
71
+ name: z.string().max(200),
72
+ email: z.string().max(320).optional(),
73
+ org: z.enum(["same", "other", "none"]),
74
+ }),
75
+ /** Untrusted peer text: the whole request, at most `LIMITS.requestChars`. Clients show it as data. */
76
+ text: z.string().max(LIMITS.requestChars),
77
+ at: Millis,
78
+ exp: Millis,
79
+ });
80
+ /**
81
+ * A contact one of this person's agents asks them to make (spec §6.9, "Message requests"): `send {to: "me", request:
82
+ * {contact}}`. Only the person sends a message request, with `contact.request`; answering this one `decline`
83
+ * dismisses it.
84
+ */
85
+ export const ContactSuggestion = z.object({
86
+ id: Ulid,
87
+ kind: z.literal("suggestion"),
88
+ /** The local session that asked. */
89
+ agent: SessionName,
90
+ email: Email,
91
+ at: Millis,
92
+ });
93
+ /** What `request.list` lists: asks to the person's agents, message requests, and contacts their agents suggest. */
94
+ export const HeldRequest = z.union([AskRequest, ContactRequestView, ContactSuggestion]);
95
+ /**
96
+ * For an ask: `once` delivers it, guarded; `always` sets the sender's grant to `ask: "open"` (keeping its other
97
+ * levels), then delivers it, guarded. For a message request: `accept` sets the sender's grant to `chat: "open"`
98
+ * (keeping its other levels), files the request as their first line and sends them a card. For both: `decline`
99
+ * refuses it (`declined`); `block` sets the sender's grant to `chat: "blocked"` (keeping its other levels), then
100
+ * declines it. For a suggestion: `decline` dismisses it.
101
+ */
102
+ export const RequestAnswer = z.enum(["once", "always", "accept", "decline", "block"]);
103
+ /** A message request's text: 1 to `LIMITS.requestChars` UTF-16 code units. */
104
+ export const RequestText = z
105
+ .string()
106
+ .min(1)
107
+ .refine((text) => text.length <= LIMITS.requestChars, { message: `at most ${LIMITS.requestChars} UTF-16 code units` });
108
+ /**
109
+ * What another person's card says this person may send them, or, with no card, the defaults of the relation
110
+ * (spec §6.9, "Contact cards"). `at` is when the card was signed.
111
+ */
112
+ /**
113
+ * What a person may be sent (spec §6.9, "Contact cards"), and `devicesChangedAt`: when a device of theirs was last
114
+ * added or removed, while that is under 7 days ago (design §2.8: their devices changed; a rekey or attest is not a change).
115
+ */
116
+ export const PersonReach = z.object({ person: PersonId, can: CardCan, source: z.enum(["card", "default"]), at: Millis.optional(), devicesChangedAt: Millis.optional() });
117
+ /** `request.answer`'s params, which `request.answer.preview` takes too (spec §8.2, "Previews"). */
118
+ export const RequestAnswerParams = z.strictObject({ id: Ulid, answer: RequestAnswer, all: z.boolean().optional() });
119
+ /** `contact.request`'s params; its preview takes them with `text` optional (it reads only `email`). */
120
+ export const ContactRequestParams = z.strictObject({ email: Email, text: RequestText, suggestion: Ulid.optional() });
121
+ export const CONSENT_METHODS = {
122
+ /** This person's grants, newest first, and the review list. With `subject`, only that subject's. */
123
+ "grant.list": {
124
+ params: z.strictObject({ subject: GrantTarget.optional() }),
125
+ result: z.object({ grants: z.array(GrantView), review: z.array(GrantReview) }),
126
+ },
127
+ /**
128
+ * Signs and stores the subject's new grant, sends it to this person's other devices, and drops the subject from
129
+ * the review list. `not_found` for a person the realm does not know or a channel this person is not in.
130
+ */
131
+ "grant.set": { params: GrantSetParams, result: z.object({ grant: GrantView }) },
132
+ /** What `grant.set` with these params would mean, in one fixed sentence; changes nothing. */
133
+ "grant.set.preview": { params: GrantSetParams, result: z.object({ sentence: z.string().max(500) }) },
134
+ /** The requests this device holds for its person, oldest first: asks, then message requests, then suggestions. */
135
+ "request.list": { params: z.strictObject({}), result: z.object({ requests: z.array(HeldRequest).max(LIMITS.heldTotal * 3) }) },
136
+ /**
137
+ * Answers a held request; with `all`, every request of the same kind held from the same sending person. Returns
138
+ * the ids answered. `not_found` for an id this device does not hold; `invalid_request` for an answer its kind does
139
+ * not take.
140
+ */
141
+ "request.answer": {
142
+ params: RequestAnswerParams,
143
+ result: z.object({ answered: z.array(Ulid).max(LIMITS.heldTotal) }),
144
+ },
145
+ /**
146
+ * Read-only: what `request.answer` would do, for its confirmation (spec §6.9, "Holds", "Message requests"). It takes
147
+ * exactly `request.answer`'s input. It returns whom the person answers (`from`: the realm's verified profile of the
148
+ * sender, or of the person for their own agent's suggestion; `name` and `email` are untrusted text), the request's
149
+ * `kind`, the sender's `agent` and the local session it is `to` (an ask), how many requests the answer would settle
150
+ * (`count`, more than 1 only with `all`), and one fixed `sentence`: what this `answer` does. `not_found` and `invalid_request` as `request.answer` would answer. Changes nothing.
151
+ */
152
+ "request.answer.preview": {
153
+ params: RequestAnswerParams,
154
+ result: z.object({
155
+ kind: z.enum(["message", "task", "contact", "suggestion"]),
156
+ from: z.object({ name: z.string().max(200), email: z.string().max(320).optional(), org: z.enum(["same", "other", "none"]) }),
157
+ agent: SessionName.optional(),
158
+ to: SessionName.optional(),
159
+ count: z.number().int().positive(),
160
+ sentence: z.string().max(500),
161
+ }),
162
+ },
163
+ /**
164
+ * A message request (spec §6.9, "Message requests"), hosted realms only: resolves the exact `email` at the
165
+ * directory, then sends `text` to every device of that person as a request, and files it as this person's line in
166
+ * `dm:<person>`. `not_found` when no Messaging user with that email accepts requests (the same for an unknown and an
167
+ * undiscoverable email); `rate_limited` while a request to them waits, or past `LIMITS.requestsPerDay` persons
168
+ * today. `suggestion` dismisses the agent's suggestion it answers.
169
+ */
170
+ "contact.request": {
171
+ params: ContactRequestParams,
172
+ result: z.object({ id: Ulid, conversation: RoomRef, person: PersonId }),
173
+ },
174
+ /** Whether `contact.request` would find a person for `email`; counts against the directory's lookup limits. Sends nothing. */
175
+ "contact.request.preview": { params: ContactRequestParams.partial({ text: true }), result: z.object({ found: z.boolean() }) },
176
+ /**
177
+ * Asks `person` for their card (spec §6.9, "Contact cards"): at most once a day per person; `sent: false` when
178
+ * this device asked them in the last day.
179
+ */
180
+ "card.get": { params: z.strictObject({ person: PersonId }), result: z.object({ sent: z.boolean() }) },
181
+ };
@@ -0,0 +1,276 @@
1
+ /**
2
+ * The `device.*` IPC methods (spec §8.2, "Devices"; design §2.2, §7.5): this device's binding in its realm, and
3
+ * its person's devices. `device.status` and `device.bind` are on the agent tier (the plugin's runtime binds its
4
+ * host's daemon zero-click with a Jelly token, never a tool); `device.list`, `device.remove`, `device.link` and
5
+ * `device.approve` are for people, on the admin socket, and `device.approve.preview` is human tier. Spread into
6
+ * `IPC_METHODS`.
7
+ */
8
+ import { z } from "../../../vendor/zod/index.js";
9
+ import { Hash, MachineId, MachineLabel, Millis, PersonId } from "../common.js";
10
+ import { DeviceKind, UnbindReason } from "../directory.js";
11
+ import { ErrorCode } from "../errors.js";
12
+ import { Jws } from "../jelly.js";
13
+ /**
14
+ * - `none`: this device is not a device of any person yet (a directory realm: not bound; a roster realm: no team).
15
+ * - `bound`: a current device of its person.
16
+ * - `attest-due`: bound, and its last Jelly token is over `LIMITS.attestAfterMs` old: the plugin binds again.
17
+ * - `removed`: its person's log (or the roster) shows it removed; it can never be bound again.
18
+ * - `replace`: `identity.replace` made this device new keys to replace its predecessor (design §13.8); the plugin
19
+ * binds it with a Jelly token for `device`, as for `none`, and the daemon then removes the predecessor.
20
+ */
21
+ export const BindingState = z.enum(["none", "bound", "attest-due", "removed", "replace"]);
22
+ /** An alarm of this device's realm (spec §15.9): `directory` (the directory contradicted itself) or `restored`. */
23
+ export const RealmAlarmSummary = z.object({
24
+ kind: z.enum(["directory", "restored"]),
25
+ reason: z.string().max(500),
26
+ at: Millis,
27
+ });
28
+ export const DeviceStatus = z.object({
29
+ realm: z.enum(["roster", "directory"]),
30
+ /** This device's public key: the `x` of the `cnf` a Jelly token for it names. Absent before the home has keys. */
31
+ device: MachineId.optional(),
32
+ binding: BindingState,
33
+ person: PersonId.optional(),
34
+ label: MachineLabel.optional(),
35
+ /** A directory realm's adopted tree head. */
36
+ sth: z.object({ size: z.number().int().positive(), root: Hash, ts: Millis }).optional(),
37
+ alarm: RealmAlarmSummary.optional(),
38
+ });
39
+ /**
40
+ * One change of a person's devices (design §2.8): `added` (bound; a roster realm: admitted), `vouched` (an `attest`
41
+ * raised a device approved by link to Jelly-vouched), `removed` (unbound, or `member.remove`) or `replaced` (unbound
42
+ * by its successor after a restore, design §13.8). `label`, `kind`, `vouch` and `via` are the device's; `approver`
43
+ * names the device that approved one bound by link, by its label; `at` is the entry's time.
44
+ */
45
+ export const DeviceChangeView = z.object({
46
+ change: z.enum(["added", "vouched", "removed", "replaced"]),
47
+ device: MachineId,
48
+ label: MachineLabel,
49
+ kind: DeviceKind.optional(),
50
+ vouch: z.enum(["jelly", "device", "admin"]),
51
+ via: z.enum(["host", "browser"]).optional(),
52
+ approver: MachineLabel.optional(),
53
+ at: Millis,
54
+ });
55
+ /** One device of this device's person. */
56
+ export const DeviceListEntry = z.object({
57
+ device: MachineId,
58
+ label: MachineLabel,
59
+ /** A directory realm's device kind; a roster realm has none. */
60
+ kind: DeviceKind.optional(),
61
+ /** `jelly`: vouched by Jelly ID; `device`: approved by another device of the person; `admin`: a roster admin admitted it. */
62
+ vouched: z.enum(["jelly", "device", "admin"]),
63
+ /**
64
+ * How a Jelly-vouched device got its latest Jelly token: from a Jelly `host`, or signed in through the `browser`
65
+ * (`src: "device"`, spec §15.9). Absent for other vouches.
66
+ */
67
+ via: z.enum(["host", "browser"]).optional(),
68
+ current: z.boolean(),
69
+ self: z.boolean(),
70
+ since: Millis.optional(),
71
+ /** For a removed device of a directory realm: when, why and by which device. */
72
+ unbound: z.object({ at: Millis, reason: UnbindReason, by: MachineId }).optional(),
73
+ });
74
+ /** A link request's text (`messaging-link.…`, spec §15.3): what a new device shows and its person's device reads. */
75
+ const LinkText = z.string().min(1).max(2048);
76
+ /** The six-digit code both devices show for a link request (`linkSas`). */
77
+ const Sas = z.string().regex(/^[0-9]{6}$/, "six digits");
78
+ /** What a link request asks for, and the code to compare. */
79
+ export const LinkPreview = z.object({ device: MachineId, label: MachineLabel, kind: DeviceKind, sas: Sas });
80
+ /**
81
+ * A key's fingerprint as Jelly ID's consent page shows it (Jelly's contract v1.2 §12.2): the first six bytes of the
82
+ * key's RFC 7638 thumbprint, lower-case hex, in three groups of four.
83
+ */
84
+ export const KeyFingerprint = z.string().regex(/^[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}$/, "key fingerprint");
85
+ /** The code the person types at Jelly ID's page: ten symbols grouped 5-5 (Jelly's contract v1.2 §12.1). */
86
+ export const LoginUserCode = z.string().regex(/^[A-HJ-NP-Z2-9]{5}-[A-HJ-NP-Z2-9]{5}$/, "user code");
87
+ /**
88
+ * What a sign-in is for: binding (or attesting) this device, removing another device of its person, a step of
89
+ * `identity.replace` (binding the new keys, then removing the predecessor), or `directory.erase`.
90
+ */
91
+ export const LoginPurpose = z.enum(["bind", "remove", "replace", "erase"]);
92
+ /** The kinds a device that signs in may give itself: never `jelly-host`. */
93
+ export const LoginKind = z.enum(["laptop", "server", "other"]);
94
+ /**
95
+ * Where the person signs in, the code they type there, and what to check. Never the token or Jelly's device code, and
96
+ * never a link with the code in it: Jelly ID sends none (its contract v1.2 §12.1), so a phished link is not a sign-in.
97
+ */
98
+ const LoginShown = z.object({
99
+ /** Jelly ID's page (`verification_uri`), where the person types `userCode`. */
100
+ verificationUri: z.string().url().max(2048),
101
+ userCode: LoginUserCode,
102
+ /** This device's key as the page shows it. */
103
+ fingerprint: KeyFingerprint,
104
+ /** When the code expires (ms). */
105
+ expires: Millis,
106
+ });
107
+ /**
108
+ * A device's sign-in through the browser (spec §15.9, "Signing in through the browser"), as `device.login.status`
109
+ * reports it and the `login` event announces it.
110
+ *
111
+ * - `idle`: none since the daemon started.
112
+ * - `pending`: waiting for the person at Jelly ID (or binding with the token Jelly ID just gave).
113
+ * - `done`: `bind` bound or attested this device; `remove` removed `device`. `seq` is the log entry's.
114
+ * - `failed`: `code` and `message` in the house style (`expired`, `login_denied`, `login_unsupported`, ...).
115
+ * - `cancelled`: `device.login.cancel`, a newer `device.login`, or the daemon stopping.
116
+ */
117
+ export const LoginState = z.discriminatedUnion("state", [
118
+ z.object({ state: z.literal("idle") }),
119
+ LoginShown.extend({ state: z.literal("pending"), purpose: LoginPurpose }),
120
+ z.object({ state: z.literal("done"), purpose: LoginPurpose, person: PersonId, device: MachineId, seq: z.number().int().nonnegative() }),
121
+ z.object({ state: z.literal("failed"), purpose: LoginPurpose, code: ErrorCode, message: z.string().max(500) }),
122
+ z.object({ state: z.literal("cancelled"), purpose: LoginPurpose }),
123
+ ]);
124
+ /** `device.remove`'s params, which its preview takes too (spec §8.2, "Previews"). */
125
+ export const DeviceRemoveParams = z.strictObject({ device: MachineId, reason: z.enum(["self", "removed", "lost"]).optional(), token: Jws.optional() });
126
+ /** `device.approve`'s params; its preview takes them with `sas` optional. */
127
+ export const DeviceApproveParams = z.strictObject({ link: LinkText, sas: Sas });
128
+ /** `identity.replace`'s params, which its preview takes too (spec §8.2, "Previews"); the preview never reads `token`. */
129
+ export const IdentityReplaceParams = z.strictObject({
130
+ /** A fresh Jelly token whose `cnf` names this device's new key: a Jelly host's. Without it, a sign-in through the browser. */
131
+ token: Jws.optional(),
132
+ /** The new device's label; default the predecessor's. */
133
+ label: MachineLabel.optional(),
134
+ });
135
+ /** A sign-in that a step of `identity.replace` or `directory.erase` waits for: where the person approves it. */
136
+ const SignInStep = LoginShown.extend({ state: z.literal("sign-in"), purpose: LoginPurpose, interval: z.number().int().positive() });
137
+ /** `directory.erase`'s params, which its preview takes too; the preview never reads `token`. */
138
+ export const DirectoryEraseParams = z.strictObject({
139
+ /** A fresh Jelly token bound to this device: a Jelly host's. Without it, a sign-in through the browser. */
140
+ token: Jws.optional(),
141
+ });
142
+ export const DEVICE_METHODS = {
143
+ /** Agent tier. This device's realm and binding; the device id the plugin asks Jelly ID to bind. */
144
+ "device.status": { params: z.strictObject({}), result: DeviceStatus },
145
+ /**
146
+ * Agent tier, for the plugin's runtime only (design §2.2). Binds this device (or attests it again) with a Jelly
147
+ * resource token whose `cnf` names this device's key, for this home's account if it has one. A directory realm only.
148
+ */
149
+ "device.bind": {
150
+ params: z.strictObject({ token: Jws, label: MachineLabel.optional(), kind: DeviceKind.optional() }),
151
+ result: z.object({ person: PersonId, device: MachineId, seq: z.number().int().nonnegative() }),
152
+ },
153
+ /** Admin tier. This device's person's devices, current first. */
154
+ "device.list": { params: z.strictObject({}), result: z.object({ devices: z.array(DeviceListEntry) }) },
155
+ /**
156
+ * Admin tier. Removes a device of this device's person: this device itself, or another with a fresh Jelly token
157
+ * bound to this device (design §2.1). A directory realm only; a roster realm removes machines with `team.remove`.
158
+ */
159
+ "device.remove": {
160
+ params: DeviceRemoveParams,
161
+ result: z.object({ seq: z.number().int().nonnegative() }),
162
+ },
163
+ /**
164
+ * Admin tier, on a new device that Jelly ID does not enrol (design §2.5). Makes this device's link request: its
165
+ * text (shown as a QR code too) and the code to compare, valid until `expires`. The daemon then waits to be
166
+ * approved, and binds itself when its person's log shows the approval. A directory realm only.
167
+ */
168
+ "device.link": {
169
+ params: z.strictObject({ label: MachineLabel.optional(), kind: DeviceKind.optional() }),
170
+ result: z.object({ link: LinkText, sas: Sas, expires: Millis }),
171
+ },
172
+ /**
173
+ * Admin tier, in a directory realm, not under a Jelly host (spec §15.9, "Signing in through the browser"). Starts
174
+ * a sign-in at Jelly ID for this device and returns where the person approves it, the code and this key's
175
+ * fingerprint to check there, and when it expires. The daemon then polls Jelly ID and, once approved, binds this
176
+ * device (or attests it again), or with `remove` removes that other device of its person with the token. A newer
177
+ * call cancels a pending one. Needs Jelly ID's device sign-in (its contract v1.2 §12): `login_unsupported` without.
178
+ */
179
+ "device.login": {
180
+ params: z.strictObject({
181
+ label: MachineLabel.optional(),
182
+ kind: LoginKind.optional(),
183
+ remove: z.strictObject({ device: MachineId, reason: z.enum(["removed", "lost"]).optional() }).optional(),
184
+ }),
185
+ result: LoginShown.extend({ purpose: LoginPurpose, interval: z.number().int().positive() }),
186
+ },
187
+ /** Admin tier. The sign-in's state; with `waitMs`, answers once it changes or the wait ends. */
188
+ "device.login.status": { params: z.strictObject({ waitMs: z.number().int().min(0).max(120_000).optional() }), result: LoginState },
189
+ /** Admin tier. Cancels a pending sign-in; nothing when none is pending. */
190
+ "device.login.cancel": { params: z.strictObject({}), result: z.object({ ok: z.literal(true) }) },
191
+ /** Human tier, read-only (design §13.12): what a link request asks for, and its code. Refuses an expired one. */
192
+ /** `device.approve`'s input (spec §8.2, "Previews"); the code may be left out, and is never checked here. */
193
+ "device.approve.preview": { params: DeviceApproveParams.partial({ sas: true }), result: LinkPreview },
194
+ /**
195
+ * Admin tier, on a device of the person that Jelly ID vouched for: approves the link request, whose code the
196
+ * person compared and gives back as `sas`, by appending the new device's `bind` to the person's log.
197
+ */
198
+ "device.approve": {
199
+ params: DeviceApproveParams,
200
+ result: z.object({ device: MachineId, seq: z.number().int().nonnegative() }),
201
+ },
202
+ /**
203
+ * Admin tier, a directory realm (design §13.8, A8; spec §15.9 "Replacing this device"): this home gets new keys
204
+ * and replaces its predecessor, the device its store belongs to (after a restore: `keys_missing` or `restored`; or
205
+ * on purpose, for a fresh key). Each call moves the replacement on and says where it stands:
206
+ * - `done`: the new device is bound (a `bind` with a Jelly token for its key) and the predecessor unbound with
207
+ * reason `replaced` (signed by the new device, with a Jelly token for it); grants re-signed, conversations kept
208
+ * under the successor rule;
209
+ * - `token`: under a Jelly host, waiting for the host's token for `device` (`device.bind` or this method with it);
210
+ * - `sign-in`: waiting for the person at Jelly ID; call again once it is done. A sign-in's token vouches for one
211
+ * act (spec §15.3), so through the browser the replacement takes two: one binds the new key, one removes the
212
+ * predecessor. A Jelly host's token does both.
213
+ * `no_identity` before this home was bound to an account; `invalid_state` on a removed device.
214
+ */
215
+ "identity.replace": {
216
+ params: IdentityReplaceParams,
217
+ result: z.discriminatedUnion("state", [
218
+ z.object({ state: z.literal("done"), person: PersonId, device: MachineId, predecessor: MachineId, seq: z.number().int().nonnegative() }),
219
+ z.object({ state: z.literal("token"), device: MachineId }),
220
+ SignInStep.extend({ device: MachineId }),
221
+ ]),
222
+ },
223
+ /**
224
+ * Human tier, read-only: what `identity.replace` would do (design §13.8: the predecessor as `device.remove.preview`
225
+ * shows a device), why, how many sign-ins it still needs, and the confirmation's fixed sentence.
226
+ */
227
+ "identity.replace.preview": {
228
+ params: IdentityReplaceParams,
229
+ result: z.object({
230
+ predecessor: z.object({
231
+ device: MachineId,
232
+ label: MachineLabel,
233
+ kind: DeviceKind.optional(),
234
+ vouch: z.enum(["jelly", "device"]),
235
+ via: z.enum(["host", "browser"]).optional(),
236
+ since: Millis.optional(),
237
+ current: z.boolean(),
238
+ }),
239
+ /** Why: the store's key files are gone, it runs from an older copy, or neither (a fresh key on purpose). */
240
+ alarm: z.enum(["keys_missing", "restored"]).nullable(),
241
+ /** The new device's id, once `identity.replace` made it. */
242
+ device: MachineId.optional(),
243
+ /** Sign-ins through the browser still needed without a host's token: 2, 1 (the new key is bound) or 0. */
244
+ signIns: z.number().int().min(0).max(2),
245
+ sentence: z.string().max(600),
246
+ }),
247
+ },
248
+ /**
249
+ * Admin tier, a directory realm (design §3.7; spec §15.10 `dir.erase`): erases this person's account from
250
+ * Messaging, from this device: an `unbind` of every current device, with a fresh Jelly token bound to this one (a
251
+ * Jelly host's `token`, or a sign-in through the browser). The directory deletes the profile, every log body and
252
+ * keys statement, and the queues; the account can never be bound again. `done`: erased, and this device stops as
253
+ * removed. `rate_limited` (with `retryAfterMs`) past the directory's 10 erasures per person a day.
254
+ */
255
+ "directory.erase": {
256
+ params: DirectoryEraseParams,
257
+ result: z.discriminatedUnion("state", [z.object({ state: z.literal("done"), person: PersonId, devices: z.number().int().nonnegative() }), SignInStep]),
258
+ },
259
+ /** Human tier, read-only: exactly what `directory.erase` deletes, and what stays. */
260
+ "directory.erase.preview": {
261
+ params: DirectoryEraseParams,
262
+ result: z.object({
263
+ person: PersonId,
264
+ /** Every current device of the person, each unbound for good. */
265
+ devices: z.array(z.object({ device: MachineId, label: MachineLabel, kind: DeviceKind.optional(), self: z.boolean() })),
266
+ /** Sign-ins through the browser needed without a host's token. */
267
+ signIns: z.number().int().min(0).max(1),
268
+ /** The directory's limit: erasures per person a day. */
269
+ perDay: z.number().int().positive(),
270
+ /** What is lost, in fixed sentences. */
271
+ lost: z.array(z.string().max(300)).max(10),
272
+ /** What stays. */
273
+ kept: z.array(z.string().max(300)).max(10),
274
+ }),
275
+ },
276
+ };