@real-life/trust-protocol 0.3.2 → 0.5.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 (277) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +209 -78
  3. package/dist/ack.d.ts +84 -0
  4. package/dist/ack.d.ts.map +1 -0
  5. package/dist/ack.js +114 -0
  6. package/dist/ack.js.map +1 -0
  7. package/dist/adapters/vti-mediator.d.ts +169 -0
  8. package/dist/adapters/vti-mediator.d.ts.map +1 -0
  9. package/dist/adapters/vti-mediator.js +659 -0
  10. package/dist/adapters/vti-mediator.js.map +1 -0
  11. package/dist/anchor.d.ts +226 -0
  12. package/dist/anchor.d.ts.map +1 -0
  13. package/dist/anchor.js +347 -0
  14. package/dist/anchor.js.map +1 -0
  15. package/dist/{holder.d.ts → carrier-holder.d.ts} +1 -1
  16. package/dist/carrier-holder.d.ts.map +1 -0
  17. package/dist/{holder.js → carrier-holder.js} +5 -4
  18. package/dist/carrier-holder.js.map +1 -0
  19. package/dist/carrier-identity.js +4 -4
  20. package/dist/carrier-identity.js.map +1 -1
  21. package/dist/carrier.d.ts.map +1 -1
  22. package/dist/carrier.js +4 -3
  23. package/dist/carrier.js.map +1 -1
  24. package/dist/ceremony.d.ts +3 -2
  25. package/dist/ceremony.d.ts.map +1 -1
  26. package/dist/ceremony.js +16 -26
  27. package/dist/ceremony.js.map +1 -1
  28. package/dist/codec.d.ts +54 -0
  29. package/dist/codec.d.ts.map +1 -0
  30. package/dist/codec.js +494 -0
  31. package/dist/codec.js.map +1 -0
  32. package/dist/continuity.d.ts +67 -0
  33. package/dist/continuity.d.ts.map +1 -0
  34. package/dist/continuity.js +110 -0
  35. package/dist/continuity.js.map +1 -0
  36. package/dist/core.d.ts +4 -0
  37. package/dist/core.d.ts.map +1 -1
  38. package/dist/core.js.map +1 -1
  39. package/dist/crypto.d.ts +23 -7
  40. package/dist/crypto.d.ts.map +1 -1
  41. package/dist/crypto.js +54 -20
  42. package/dist/crypto.js.map +1 -1
  43. package/dist/delivery.d.ts +31 -3
  44. package/dist/delivery.d.ts.map +1 -1
  45. package/dist/delivery.js +75 -28
  46. package/dist/delivery.js.map +1 -1
  47. package/dist/encounter/ceremony.d.ts +37 -0
  48. package/dist/encounter/ceremony.d.ts.map +1 -0
  49. package/dist/encounter/ceremony.js +662 -0
  50. package/dist/encounter/ceremony.js.map +1 -0
  51. package/dist/encounter.d.ts +91 -1
  52. package/dist/encounter.d.ts.map +1 -1
  53. package/dist/encounter.js +191 -12
  54. package/dist/encounter.js.map +1 -1
  55. package/dist/engine.d.ts +303 -0
  56. package/dist/engine.d.ts.map +1 -0
  57. package/dist/engine.js +1509 -0
  58. package/dist/engine.js.map +1 -0
  59. package/dist/entropy.d.ts +28 -0
  60. package/dist/entropy.d.ts.map +1 -0
  61. package/dist/entropy.js +96 -0
  62. package/dist/entropy.js.map +1 -0
  63. package/dist/events.d.ts +198 -0
  64. package/dist/events.d.ts.map +1 -0
  65. package/dist/events.js +2 -0
  66. package/dist/events.js.map +1 -0
  67. package/dist/fixtures.d.ts +30 -0
  68. package/dist/fixtures.d.ts.map +1 -0
  69. package/dist/fixtures.js +155 -0
  70. package/dist/fixtures.js.map +1 -0
  71. package/dist/forge.d.ts +47 -0
  72. package/dist/forge.d.ts.map +1 -0
  73. package/dist/forge.js +20 -0
  74. package/dist/forge.js.map +1 -0
  75. package/dist/group-star.d.ts +251 -0
  76. package/dist/group-star.d.ts.map +1 -0
  77. package/dist/group-star.js +348 -0
  78. package/dist/group-star.js.map +1 -0
  79. package/dist/holder-api.d.ts +165 -0
  80. package/dist/holder-api.d.ts.map +1 -0
  81. package/dist/holder-api.js +142 -0
  82. package/dist/holder-api.js.map +1 -0
  83. package/dist/holder-state-v1.d.ts +801 -0
  84. package/dist/holder-state-v1.d.ts.map +1 -0
  85. package/dist/holder-state-v1.js +2 -0
  86. package/dist/holder-state-v1.js.map +1 -0
  87. package/dist/holder-state-v2.d.ts +858 -0
  88. package/dist/holder-state-v2.d.ts.map +1 -0
  89. package/dist/holder-state-v2.js +2 -0
  90. package/dist/holder-state-v2.js.map +1 -0
  91. package/dist/identity-state.d.ts +43 -0
  92. package/dist/identity-state.d.ts.map +1 -0
  93. package/dist/identity-state.js +345 -0
  94. package/dist/identity-state.js.map +1 -0
  95. package/dist/identity.d.ts +24 -2
  96. package/dist/identity.d.ts.map +1 -1
  97. package/dist/identity.js +70 -11
  98. package/dist/identity.js.map +1 -1
  99. package/dist/index.d.ts +27 -1
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +81 -29
  102. package/dist/index.js.map +1 -1
  103. package/dist/internal.d.ts +37 -0
  104. package/dist/internal.d.ts.map +1 -0
  105. package/dist/internal.js +22 -0
  106. package/dist/internal.js.map +1 -0
  107. package/dist/introduction/holder.d.ts +18 -0
  108. package/dist/introduction/holder.d.ts.map +1 -0
  109. package/dist/introduction/holder.js +764 -0
  110. package/dist/introduction/holder.js.map +1 -0
  111. package/dist/introduction.d.ts +101 -0
  112. package/dist/introduction.d.ts.map +1 -0
  113. package/dist/introduction.js +132 -0
  114. package/dist/introduction.js.map +1 -0
  115. package/dist/invariants.d.ts +43 -0
  116. package/dist/invariants.d.ts.map +1 -0
  117. package/dist/invariants.js +953 -0
  118. package/dist/invariants.js.map +1 -0
  119. package/dist/keys-seed.d.ts +7 -0
  120. package/dist/keys-seed.d.ts.map +1 -0
  121. package/dist/keys-seed.js +133 -0
  122. package/dist/keys-seed.js.map +1 -0
  123. package/dist/log-memory.d.ts +12 -0
  124. package/dist/log-memory.d.ts.map +1 -0
  125. package/dist/log-memory.js +101 -0
  126. package/dist/log-memory.js.map +1 -0
  127. package/dist/membership/holder.d.ts +33 -0
  128. package/dist/membership/holder.d.ts.map +1 -0
  129. package/dist/membership/holder.js +929 -0
  130. package/dist/membership/holder.js.map +1 -0
  131. package/dist/membership.d.ts +123 -0
  132. package/dist/membership.d.ts.map +1 -0
  133. package/dist/membership.js +180 -0
  134. package/dist/membership.js.map +1 -0
  135. package/dist/message/holder.d.ts +49 -0
  136. package/dist/message/holder.d.ts.map +1 -0
  137. package/dist/message/holder.js +290 -0
  138. package/dist/message/holder.js.map +1 -0
  139. package/dist/message.d.ts +43 -0
  140. package/dist/message.d.ts.map +1 -0
  141. package/dist/message.js +51 -0
  142. package/dist/message.js.map +1 -0
  143. package/dist/ports.d.ts +237 -0
  144. package/dist/ports.d.ts.map +1 -0
  145. package/dist/ports.js +2 -0
  146. package/dist/ports.js.map +1 -0
  147. package/dist/profile.d.ts +56 -0
  148. package/dist/profile.d.ts.map +1 -0
  149. package/dist/profile.js +193 -0
  150. package/dist/profile.js.map +1 -0
  151. package/dist/prompts.d.ts +14 -0
  152. package/dist/prompts.d.ts.map +1 -0
  153. package/dist/prompts.js +69 -0
  154. package/dist/prompts.js.map +1 -0
  155. package/dist/receive.d.ts +134 -0
  156. package/dist/receive.d.ts.map +1 -0
  157. package/dist/receive.js +317 -0
  158. package/dist/receive.js.map +1 -0
  159. package/dist/results.d.ts +173 -0
  160. package/dist/results.d.ts.map +1 -0
  161. package/dist/results.js +21 -0
  162. package/dist/results.js.map +1 -0
  163. package/dist/schemas.d.ts.map +1 -1
  164. package/dist/schemas.js +45 -38
  165. package/dist/schemas.js.map +1 -1
  166. package/dist/sha256.d.ts +3 -0
  167. package/dist/sha256.d.ts.map +1 -0
  168. package/dist/sha256.js +69 -0
  169. package/dist/sha256.js.map +1 -0
  170. package/dist/star.d.ts +31 -0
  171. package/dist/star.d.ts.map +1 -0
  172. package/dist/star.js +56 -0
  173. package/dist/star.js.map +1 -0
  174. package/dist/state-builder.d.ts +42 -0
  175. package/dist/state-builder.d.ts.map +1 -0
  176. package/dist/state-builder.js +100 -0
  177. package/dist/state-builder.js.map +1 -0
  178. package/dist/state.d.ts +914 -0
  179. package/dist/state.d.ts.map +1 -0
  180. package/dist/state.js +37 -0
  181. package/dist/state.js.map +1 -0
  182. package/dist/store-contract.d.ts +11 -0
  183. package/dist/store-contract.d.ts.map +1 -0
  184. package/dist/store-contract.js +189 -0
  185. package/dist/store-contract.js.map +1 -0
  186. package/dist/store-memory.d.ts +8 -0
  187. package/dist/store-memory.d.ts.map +1 -0
  188. package/dist/store-memory.js +17 -0
  189. package/dist/store-memory.js.map +1 -0
  190. package/dist/testing.d.ts +22 -0
  191. package/dist/testing.d.ts.map +1 -0
  192. package/dist/testing.js +21 -0
  193. package/dist/testing.js.map +1 -0
  194. package/dist/trust/continuity.d.ts +71 -0
  195. package/dist/trust/continuity.d.ts.map +1 -0
  196. package/dist/trust/continuity.js +1222 -0
  197. package/dist/trust/continuity.js.map +1 -0
  198. package/dist/trust/duties.d.ts +67 -0
  199. package/dist/trust/duties.d.ts.map +1 -0
  200. package/dist/trust/duties.js +179 -0
  201. package/dist/trust/duties.js.map +1 -0
  202. package/dist/trust/entries.d.ts +139 -0
  203. package/dist/trust/entries.d.ts.map +1 -0
  204. package/dist/trust/entries.js +795 -0
  205. package/dist/trust/entries.js.map +1 -0
  206. package/dist/trust/group-star.d.ts +43 -0
  207. package/dist/trust/group-star.d.ts.map +1 -0
  208. package/dist/trust/group-star.js +188 -0
  209. package/dist/trust/group-star.js.map +1 -0
  210. package/dist/trust/mapping.d.ts +56 -0
  211. package/dist/trust/mapping.d.ts.map +1 -0
  212. package/dist/trust/mapping.js +584 -0
  213. package/dist/trust/mapping.js.map +1 -0
  214. package/dist/trust/profile.d.ts +25 -0
  215. package/dist/trust/profile.d.ts.map +1 -0
  216. package/dist/trust/profile.js +249 -0
  217. package/dist/trust/profile.js.map +1 -0
  218. package/dist/trust/star.d.ts +208 -0
  219. package/dist/trust/star.d.ts.map +1 -0
  220. package/dist/trust/star.js +588 -0
  221. package/dist/trust/star.js.map +1 -0
  222. package/dist/trust/state.d.ts +83 -0
  223. package/dist/trust/state.d.ts.map +1 -0
  224. package/dist/trust/state.js +154 -0
  225. package/dist/trust/state.js.map +1 -0
  226. package/dist/util-freeze.d.ts +3 -0
  227. package/dist/util-freeze.d.ts.map +1 -0
  228. package/dist/util-freeze.js +11 -0
  229. package/dist/util-freeze.js.map +1 -0
  230. package/dist/views.d.ts +179 -0
  231. package/dist/views.d.ts.map +1 -0
  232. package/dist/views.js +286 -0
  233. package/dist/views.js.map +1 -0
  234. package/dist/visibility.d.ts +5 -5
  235. package/dist/visibility.d.ts.map +1 -1
  236. package/dist/visibility.js +22 -28
  237. package/dist/visibility.js.map +1 -1
  238. package/dist/wire.d.ts +218 -83
  239. package/dist/wire.d.ts.map +1 -1
  240. package/dist/wire.js.map +1 -1
  241. package/package.json +60 -10
  242. package/schemas/holder-state-v1.schema.json +3880 -0
  243. package/schemas/holder-state-v2.schema.json +4123 -0
  244. package/dist/holder.d.ts.map +0 -1
  245. package/dist/holder.js.map +0 -1
  246. package/dist/probe/acks.d.ts +0 -27
  247. package/dist/probe/acks.d.ts.map +0 -1
  248. package/dist/probe/acks.js +0 -152
  249. package/dist/probe/acks.js.map +0 -1
  250. package/dist/probe/continuity.d.ts +0 -20
  251. package/dist/probe/continuity.d.ts.map +0 -1
  252. package/dist/probe/continuity.js +0 -805
  253. package/dist/probe/continuity.js.map +0 -1
  254. package/dist/probe/deps.d.ts +0 -61
  255. package/dist/probe/deps.d.ts.map +0 -1
  256. package/dist/probe/deps.js +0 -54
  257. package/dist/probe/deps.js.map +0 -1
  258. package/dist/probe/encounter.d.ts +0 -26
  259. package/dist/probe/encounter.d.ts.map +0 -1
  260. package/dist/probe/encounter.js +0 -938
  261. package/dist/probe/encounter.js.map +0 -1
  262. package/dist/probe/introduce.d.ts +0 -62
  263. package/dist/probe/introduce.d.ts.map +0 -1
  264. package/dist/probe/introduce.js +0 -306
  265. package/dist/probe/introduce.js.map +0 -1
  266. package/dist/probe/membership.d.ts +0 -96
  267. package/dist/probe/membership.d.ts.map +0 -1
  268. package/dist/probe/membership.js +0 -467
  269. package/dist/probe/membership.js.map +0 -1
  270. package/dist/probe/trust.d.ts +0 -156
  271. package/dist/probe/trust.d.ts.map +0 -1
  272. package/dist/probe/trust.js +0 -1233
  273. package/dist/probe/trust.js.map +0 -1
  274. package/dist/probe.d.ts +0 -7
  275. package/dist/probe.d.ts.map +0 -1
  276. package/dist/probe.js +0 -42
  277. package/dist/probe.js.map +0 -1
@@ -0,0 +1,588 @@
1
+ // trust/star — the star on the holder (lib Stage 2, PR 4b): star@2 as the
2
+ // registered task star/0.2 (5.1–5.2a), reconciled per recipient (5.4),
3
+ // and the machinery the group star (trust/group-star.ts) shares with it:
4
+ // the sender's salt scope, baseline automaton and in-flight deliveries,
5
+ // and the receiver's assembly per (tuple, salt), on HolderState.
6
+ //
7
+ // Rule numbers: bare four-digit numbers are RLTP-TRU (trust-layer.md).
8
+ //
9
+ // What it registers (through trustModule, trust/mapping.ts):
10
+ // sweep trust.star resume's reconciliation: per contact and live
11
+ // relationship, the view against the baseline;
12
+ // where they differ, the class is on and the scope
13
+ // can issue, one production under the next salt
14
+ // task star/0.2 (proof absent) reception: assembly, completion,
15
+ // the accepted snapshot
16
+ // ack star/0.2 completion of a salt by the verified acks of
17
+ // every chunk (5660)
18
+ //
19
+ // The sender (5.4). The view (5610/5620) is the set of (member, anchor
20
+ // head) over the admitted entries, member = the entry id, sorted by
21
+ // member; every recipient gets the same view. A production takes the
22
+ // snapshot, assigns lastSalt + 1, builds the chunks blinded under the
23
+ // tuple's pair secret (KeyProvider.agree on the tuple's rkid), stamps one
24
+ // fresh threadId on every chunk of the salt (entropy thread.id), and
25
+ // commits `lastSalt`, `inflight[salt] = {view, viewDigest, digests, acked: []}` and
26
+ // the outbox items in one transition (5090, 5110); the outbound leaves
27
+ // only after that commit. Chunk retries are the outbox's, byte-identical
28
+ // down to the envelope and the threadId (Delivery 4.2). Completion: the
29
+ // verified ack of a chunk marks it acked in its inflight entry; the last
30
+ // one runs the baseline automaton (5670): s > H sets H = s and S = the
31
+ // view sent under s, s ≤ H changes nothing; the salt and every in-flight
32
+ // salt at or below the new H leave `inflight`. No rollback (5680).
33
+ //
34
+ // Choices this file makes where note and spec leave them open:
35
+ // - an available delivery contact (5690) is a resume. A divergence is
36
+ // delivered once per view: no new salt while an in-flight salt above
37
+ // H carries exactly the current view and none of its chunks has
38
+ // failed (the outbox retries it byte-identical). A view that changes
39
+ // again, or a delivery the outbox gives up (failed, its horizon or the
40
+ // adapter's verdict) leaves the divergence standing, and the next
41
+ // resume ships the current view under a fresh, higher salt (5680)
42
+ // - completion counts verified delivery-acks only, never an adapter's
43
+ // `delivered` report (ports.ts DeliveryReport, 5660)
44
+ // - superseded salts are not withdrawn from the outbox: their retries
45
+ // run to the horizon like any item (no trias state says "superseded"),
46
+ // and the receiver refuses them (5150) or answers a cached duplicate
47
+ // - needing delivery and completing are kept apart: a delivery with a
48
+ // failed chunk no longer counts as in flight (the next resume issues a
49
+ // fresh salt), but its inflight entry (view, digests, acked) stays as
50
+ // long as any of its outbox items exists, so a late ack still completes
51
+ // it (5660; the automaton then advances on s > H only, 5670). It leaves
52
+ // when the last of its items has left the outbox (tick's key-retention):
53
+ // at the tuple's next production and by tick (retainInflight)
54
+ // - the star goes on every live relationship of the contact (2820), the
55
+ // active-head tuple of each, lagging ones included (6595); a contact
56
+ // with no entry yet is a recipient like any other (2760: every contact)
57
+ // - the scope cannot issue once `exhausted` (set by the commit that
58
+ // issues 10¹⁸−1, 2600) and nothing is produced; state loss (5100) is
59
+ // not modelled: a restored state carries its lastSalt
60
+ // - a production whose key agreement is unavailable (the counterpart key
61
+ // invalid or the own key not released) is skipped silently and stays
62
+ // pending
63
+ //
64
+ // The receiver (5.2a). One open assembly per tuple and artifact
65
+ // (Tuple.starIn.assembly); its chunks are kept as {digest, threadId,
66
+ // body}. The checks, in order: the arrival tuple is a live active head
67
+ // (5360); salt above the completed high-water mark (5150) and above the
68
+ // lower bound (5366); not below the open assembly (5340); the MAC under
69
+ // the salt's key (5080); then against the assembly: a higher salt
70
+ // displaces it (5330); seq above the resource limit discards (5365);
71
+ // another threadId (5280), a repeated seq of other bytes (5260), a second
72
+ // last (5270), a seq beyond last (5290) reject the delivery; the same
73
+ // digest again is a repeat, acked, no change (5250). On completion the
74
+ // union must be strictly ascending (5300, 5310). A rejection (the chunk is
75
+ // authenticated: its MAC verified) and a resource-limit discard (5365)
76
+ // both commit as receive.ts TaskDiscarded, disposition `discarded` with
77
+ // reason `rejected` or `resource-limit`: the assembly is dropped and its
78
+ // salt becomes the lower bound, so no later chunk of it is acked and the
79
+ // delivery never completes (5366); the completed hwm stays. Checks before
80
+ // the MAC (salt rules) and a failed MAC are plain refusals that touch
81
+ // nothing, so an unauthenticated chunk can discard no assembly. Every other chunk before completion is acked on its
82
+ // own (Delivery: every chunk is acked); only the completing chunk
83
+ // changes the accepted snapshot, advances hwm (5140, 5350) and announces
84
+ // star.accepted, all in its ack's transaction.
85
+ //
86
+ // Choices on the receiver side:
87
+ // - the resource limit is a bound on seq: an open assembly holds at most
88
+ // ASSEMBLY_LIMIT.star = 1024 chunks (≤ 1 048 576 entries); a chunk
89
+ // with a higher seq discards the assembly (5365, 5366)
90
+ // - `accepted` keeps the whole assembled union with its salt and the
91
+ // matches (6160): the entry ids of the holder's other contacts one of
92
+ // whose held anchors (the entry's anchor head, every relationship's
93
+ // held head and both ends of every held lineage link, 6390) blinds to
94
+ // a value in it. When the holder comes to hold new anchors for a
95
+ // contact (the anchor mapping's acceptance: entry commit, advanced
96
+ // head, new links), the mapping transition re-matches every accepted
97
+ // star against them (starBlinds in its preparation, rematchStars in
98
+ // its apply). Matches only grow: held anchors are never dropped
99
+ import { iso, jcs } from '../core.js';
100
+ import { digestDoc, xRawOfMk } from '../crypto.js';
101
+ import { seal } from '../delivery.js';
102
+ import { PLAINTEXT_MAX, blindAnchor, buildStarChunks, starChunkMacOk, starChunkOk, starKeyOf, strictlyAscending } from '../star.js';
103
+ import { tupleByRkid } from '../receive.js';
104
+ import { sha256Hex } from '../sha256.js';
105
+ import { triasOf } from '../state.js';
106
+ import { ADMISSION_BOUND, activeTuple, classOnToward, entryOf, entryRelationships, findContact } from './state.js';
107
+ /** The registered task type that carries star@2 (Delivery §4.4, 5640). */
108
+ export const STAR_TYPE = 'https://real-life.org/trust-tasks/star/0.2';
109
+ /** The receiver's resource limit (5365): the highest seq an open assembly
110
+ * takes, per artifact. */
111
+ export const ASSEMBLY_LIMIT = Object.freeze({ star: 1024, groupStar: 64, probe: 64 });
112
+ const te = new TextEncoder();
113
+ const inc = (d) => (BigInt(d) + 1n).toString();
114
+ const cmp = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
115
+ const refuse = (reason, ref) => ({ ok: false, reason, ...(ref ? { ref } : {}) });
116
+ const bodyOf = (chunk) => chunk.body;
117
+ /** Every contact's live relationships, by contact id, then relationship
118
+ * order; a relationship behind two entries under the one its
119
+ * verifications go to (2146), once. */
120
+ export function recipients(state) {
121
+ const out = [];
122
+ for (const contact of Object.values(state.contacts).sort((a, b) => cmp(a.id, b.id))) {
123
+ for (const rel of entryRelationships(state, contact)) {
124
+ if (rel.chainedInto !== null || entryOf(state, rel)?.id !== contact.id)
125
+ continue;
126
+ const tuple = activeTuple(rel);
127
+ if (tuple && !tuple.deactivated)
128
+ out.push({ contact, rel, tuple });
129
+ }
130
+ }
131
+ return out;
132
+ }
133
+ /** 2600: the scope has issued its maximum, or would exceed the wire domain. */
134
+ const canIssue = (o) => !o.exhausted && BigInt(o.lastSalt) < ADMISSION_BOUND;
135
+ /** An in-flight salt above H carrying exactly this view, none of whose
136
+ * chunks has failed: the divergence is being delivered. */
137
+ const inFlight = (state, o, viewDigest) => Object.entries(o.inflight).some(([salt, f]) => BigInt(salt) > BigInt(o.hwm) && f.viewDigest === viewDigest && canComplete(state, f));
138
+ /** A uniform view, once per contacts record (the record is replaced on
139
+ * every change of a contact, so the cache cannot go stale). */
140
+ const uniformViews = new WeakMap();
141
+ /** The digest of a view: SHA-256 over its JCS form. Views are compared by
142
+ * it (the baseline's snapshotDigest, an inflight entry's viewDigest),
143
+ * never re-serialized per comparison. */
144
+ export const viewDigest = (view) => sha256Hex(jcs(view));
145
+ /** The view toward a recipient and its digest. */
146
+ function viewAt(art, state, r) {
147
+ if (!art.uniform) {
148
+ const view = art.view(state, r.contact, r.rel);
149
+ return { view, digest: viewDigest(view) };
150
+ }
151
+ let byKind = uniformViews.get(state.contacts);
152
+ if (!byKind)
153
+ uniformViews.set(state.contacts, (byKind = new Map()));
154
+ let v = byKind.get(art.kind);
155
+ if (!v) {
156
+ const view = art.view(state, r.contact, r.rel);
157
+ byKind.set(art.kind, (v = { view, digest: viewDigest(view) }));
158
+ }
159
+ return v;
160
+ }
161
+ /** Whether an in-flight delivery can still complete: every chunk acked,
162
+ * or still in the outbox and not failed (an ack refers to an outbox item). */
163
+ export const canComplete = (state, f) => f.digests.every((d) => f.acked.includes(d) || (Object.hasOwn(state.outbox, d) && triasOf(state.outbox[d]) !== 'failed'));
164
+ /** The in-flight deliveries whose completion data is still needed: any
165
+ * of their outbox items exists, failed ones included — an ack can still
166
+ * arrive late for it (Delivery §6.1 failed → delivered) and complete the
167
+ * salt (5660). Whether one still needs delivering is canComplete's. */
168
+ function retainedInflight(state, o) {
169
+ const out = {};
170
+ for (const [s, f] of Object.entries(o.inflight))
171
+ if (f.digests.some((d) => Object.hasOwn(state.outbox, d)))
172
+ out[s] = f;
173
+ return out;
174
+ }
175
+ /** Every tuple's in-flight deliveries of both artifacts pruned to those
176
+ * whose outbox items are not all gone; tick's retention (the same state
177
+ * object where nothing is pruned). */
178
+ export function retainInflight(state) {
179
+ let relationships = null;
180
+ for (const rel of Object.values(state.relationships)) {
181
+ let changed = false;
182
+ const tuples = rel.tuples.map((t) => {
183
+ const so = retainedInflight(state, t.starOut), go = retainedInflight(state, t.groupStarOut);
184
+ if (Object.keys(so).length === Object.keys(t.starOut.inflight).length && Object.keys(go).length === Object.keys(t.groupStarOut.inflight).length)
185
+ return t;
186
+ changed = true;
187
+ return { ...t, starOut: { ...t.starOut, inflight: so }, groupStarOut: { ...t.groupStarOut, inflight: go } };
188
+ });
189
+ if (changed)
190
+ (relationships ??= { ...state.relationships })[rel.id] = { ...rel, tuples };
191
+ }
192
+ return relationships ? { ...state, relationships } : state;
193
+ }
194
+ /** Whether the artifact is owed now on this recipient (5690–5720, 5830):
195
+ * the class on, the scope able to issue, the view diverging from the
196
+ * baseline (absent: by definition) and not already in flight. */
197
+ export function owed(art, state, r, digest) {
198
+ const o = art.out(r.tuple);
199
+ if (!classOnToward(r.contact, art.cls) || !canIssue(o))
200
+ return false;
201
+ if (o.snapshotDigest !== null && o.snapshotDigest === digest)
202
+ return false;
203
+ return !inFlight(state, o, digest);
204
+ }
205
+ /** The sweep's pending keys: one per owed recipient and view. */
206
+ export function pendingOf(art, state) {
207
+ const keys = [];
208
+ for (const r of recipients(state)) {
209
+ if (!classOnToward(r.contact, art.cls))
210
+ continue;
211
+ const { digest } = viewAt(art, state, r);
212
+ if (owed(art, state, r, digest))
213
+ keys.push(`${art.kind}|${r.rel.id}|${r.tuple.ownPairLabel}|${digest}`);
214
+ }
215
+ return keys;
216
+ }
217
+ /** A key agreement on the tuple's own rkid with the counterpart key, or null. */
218
+ export async function pairShared(ctx, t, peerKeyAgreement = t.counterpartKeyAgreement) {
219
+ const x = xRawOfMk(peerKeyAgreement);
220
+ if (!x)
221
+ return null;
222
+ const a = await ctx.keys.agree(t.rkid, x, ctx.release);
223
+ return a.status === 'available' ? a.shared : null;
224
+ }
225
+ /** Seal one chunk as its own document of the salt's thread (Delivery §4.4). */
226
+ export async function chunkDocument(ctx, type, issuer, t, contactId, threadId, chunk) {
227
+ const S = ctx.crypto.subtle;
228
+ const doc = { id: ctx.entropy.id('doc.id'), type, issuer, recipient: t.counterpartAnchor, threadId, issuedAt: iso(ctx.when), payload: chunk };
229
+ // 5190: within the Delivery Contract's plaintext limit, by construction
230
+ if (te.encode(jcs(doc)).length > PLAINTEXT_MAX)
231
+ throw new Error(`trust: a ${type} chunk exceeds ${PLAINTEXT_MAX} bytes`);
232
+ const envelope = await seal(doc, t.counterpartKeyAgreement, { ephSeed: ctx.entropy.bytes('seal.eph', 32), nonce: ctx.entropy.bytes('seal.nonce', 12) }, S);
233
+ return { digest: await digestDoc(doc, S), envelope, type, threadId, recipient: { contactId, rkid: t.rkid, keyAgreement: t.counterpartKeyAgreement } };
234
+ }
235
+ /** The reconciliation sweep of one artifact (5.4, 5730). */
236
+ export function sweepOf(art) {
237
+ return {
238
+ pending: (state) => pendingOf(art, state),
239
+ // contacts (views, settings), relationships (scopes), groups (the group
240
+ // star's view) and the outbox (whether a delivery can still complete)
241
+ reads: ['contacts', 'relationships', 'groups', 'outbox'],
242
+ async prepare(ctx) {
243
+ const st = ctx.snapshot;
244
+ const out = [];
245
+ for (const r of recipients(st)) {
246
+ if (!classOnToward(r.contact, art.cls))
247
+ continue;
248
+ const { view, digest } = viewAt(art, st, r);
249
+ if (!owed(art, st, r, digest))
250
+ continue;
251
+ const shared = await pairShared(ctx, r.tuple);
252
+ if (!shared)
253
+ continue;
254
+ const own = (await ctx.keys.anchorOf(r.tuple.ownPairLabel)).anchor;
255
+ const o = art.out(r.tuple);
256
+ const salt = inc(o.lastSalt);
257
+ const chunks = await art.build(ctx, { shared, own, tuple: r.tuple, salt, view });
258
+ if (!chunks)
259
+ continue;
260
+ // one salt = one thread: a fresh threadId on every chunk of it (Delivery §4.4)
261
+ const threadId = ctx.entropy.id('thread.id');
262
+ const outbound = [];
263
+ for (const chunk of chunks)
264
+ outbound.push(await chunkDocument(ctx, art.type, own, r.tuple, r.contact.id, threadId, chunk));
265
+ out.push({ relId: r.rel.id, tuple: r.tuple.ownPairLabel, contactId: r.contact.id, baseSalt: o.lastSalt, salt, view, viewDigest: digest, outbound });
266
+ }
267
+ return out;
268
+ },
269
+ apply(state, prepared) {
270
+ let cur = state;
271
+ const outbound = [];
272
+ let deferred = false;
273
+ for (const p of prepared.value) {
274
+ const rel = Object.hasOwn(cur.relationships, p.relId) ? cur.relationships[p.relId] : undefined;
275
+ const contact = findContact(cur, p.contactId);
276
+ const tuple = rel && activeTuple(rel);
277
+ if (!rel || !contact || !tuple || tuple.ownPairLabel !== p.tuple || rel.chainedInto !== null || tuple.deactivated || !contact.relIds.includes(rel.id) || entryOf(cur, rel)?.id !== contact.id)
278
+ continue;
279
+ const r = { contact, rel, tuple };
280
+ const { digest } = viewAt(art, cur, r);
281
+ // 5110: the snapshot, the salt and the commit are one step — a
282
+ // production prepared on another view or salt is not committed
283
+ if (art.out(tuple).lastSalt !== p.baseSalt || digest !== p.viewDigest || !owed(art, cur, r, digest)) {
284
+ if (owed(art, cur, r, digest))
285
+ deferred = true;
286
+ continue;
287
+ }
288
+ const o = art.out(tuple);
289
+ const next = {
290
+ ...o, lastSalt: p.salt, exhausted: BigInt(p.salt) >= ADMISSION_BOUND,
291
+ // what no ack can reach any more leaves with the next production
292
+ inflight: { ...retainedInflight(cur, o), [p.salt]: { view: p.view, viewDigest: p.viewDigest, digests: p.outbound.map((d) => d.digest), acked: [] } },
293
+ };
294
+ const tuples = rel.tuples.map((t) => (t.ownPairLabel === p.tuple ? art.withOut(t, next) : t));
295
+ cur = { ...cur, relationships: { ...cur.relationships, [rel.id]: { ...rel, tuples } } };
296
+ outbound.push(...p.outbound);
297
+ }
298
+ return { state: cur, outbound, deferred };
299
+ },
300
+ };
301
+ }
302
+ /** Completion of a salt by the verified acks of all its chunks (5660),
303
+ * then the baseline automaton (5670). */
304
+ export function ackOf(art) {
305
+ return (state, ref) => {
306
+ const item = state.outbox[ref];
307
+ const at = tupleByRkid(state, item.recipient.rkid);
308
+ if (!at)
309
+ return { state: state };
310
+ const o = art.out(at.tuple);
311
+ const hit = Object.entries(o.inflight).find(([, f]) => f.digests.includes(ref));
312
+ if (!hit)
313
+ return { state: state };
314
+ const [salt, f] = hit;
315
+ const acked = f.acked.includes(ref) ? f.acked : [...f.acked, ref];
316
+ let next;
317
+ if (f.digests.every((d) => acked.includes(d))) {
318
+ const advance = BigInt(salt) > BigInt(o.hwm);
319
+ const hwm = advance ? salt : o.hwm;
320
+ const inflight = {};
321
+ for (const [s, x] of Object.entries(o.inflight))
322
+ if (s !== salt && BigInt(s) > BigInt(hwm))
323
+ inflight[s] = x;
324
+ next = { ...o, hwm, snapshot: advance ? f.view : o.snapshot, snapshotDigest: advance ? f.viewDigest : o.snapshotDigest, inflight };
325
+ }
326
+ else
327
+ next = { ...o, inflight: { ...o.inflight, [salt]: { ...f, acked } } };
328
+ const tuples = at.rel.tuples.map((t) => (t.ownPairLabel === at.tuple.ownPairLabel ? art.withOut(t, next) : t));
329
+ return { state: { ...state, relationships: { ...state.relationships, [at.rel.id]: { ...at.rel, tuples } } } };
330
+ };
331
+ }
332
+ /**
333
+ * The assembly rules after the salt checks and the MAC (5250–5290, 5330,
334
+ * 5365): a higher salt displaces the open assembly; a seq above `limit`
335
+ * is the resource limit; the same digest again a repeat; another thread,
336
+ * a repeated seq of other bytes, a second last or a seq beyond last
337
+ * reject the delivery; otherwise the chunk is held, or completes the
338
+ * assembly with every seq up to last present.
339
+ */
340
+ export function assemblyStep(open, salt, body, threadId, digest, limit) {
341
+ const base = open && open.salt === salt ? open : { salt, chunks: {} };
342
+ const seq = BigInt(body.seq);
343
+ if (seq > BigInt(limit))
344
+ return { kind: 'limit' };
345
+ const held = Object.values(base.chunks);
346
+ const same = Object.hasOwn(base.chunks, body.seq) ? base.chunks[body.seq] : undefined;
347
+ if (same && same.digest === digest)
348
+ return { kind: 'repeat' }; // 5250
349
+ if (held.some((h) => h.threadId !== threadId))
350
+ return { kind: 'reject', ref: 'RLTP-TRU-5280' };
351
+ if (same)
352
+ return { kind: 'reject', ref: 'RLTP-TRU-5260' };
353
+ const heldLast = held.find((h) => h.body.last);
354
+ if (body.last && heldLast)
355
+ return { kind: 'reject', ref: 'RLTP-TRU-5270' };
356
+ const last = body.last ? seq : heldLast ? BigInt(heldLast.body.seq) : null;
357
+ if (last !== null && (seq > last || held.some((h) => BigInt(h.body.seq) > last)))
358
+ return { kind: 'reject', ref: 'RLTP-TRU-5290' };
359
+ const chunks = { ...base.chunks, [body.seq]: { digest, threadId, body } };
360
+ if (last === null)
361
+ return { kind: 'hold', assembly: { salt, chunks } };
362
+ for (let i = 1n; i <= last; i++)
363
+ if (!Object.hasOwn(chunks, String(i)))
364
+ return { kind: 'hold', assembly: { salt, chunks } };
365
+ return { kind: 'complete', chunks, last };
366
+ }
367
+ /** The held bodies of a complete assembly, in seq order. */
368
+ export const assembledBodies = (chunks, last) => {
369
+ const out = [];
370
+ for (let i = 1n; i <= last; i++)
371
+ out.push(chunks[String(i)].body);
372
+ return out;
373
+ };
374
+ /** The arrival tuple, if it is the active head of a live relationship (6.4, 5360). */
375
+ function arrivalTuple(state, rkid) {
376
+ const at = tupleByRkid(state, rkid);
377
+ if (!at || at.tuple.deactivated || at.rel.chainedInto !== null || at.rel.activeHead !== at.tuple.ownPairLabel)
378
+ return null;
379
+ return at;
380
+ }
381
+ const maxDec = (a, b) => (BigInt(a) >= BigInt(b) ? a : b);
382
+ /** The task handler of one artifact (5.2a). */
383
+ export function taskOf(art, limit) {
384
+ return {
385
+ proof: 'absent',
386
+ // stage 7: the closed chunk schema (5070, 5370)
387
+ decode(payload) {
388
+ return art.chunkOk(payload) ? payload : refuse('malformed', art.malformedRef);
389
+ },
390
+ // stage 8: the salt rules, the MAC, and the assembly decision
391
+ async prepare(ctx, doc, arrival) {
392
+ if (doc.ceremony?.enactment !== undefined)
393
+ return refuse('validation-failed');
394
+ const at = arrivalTuple(ctx.snapshot, arrival.rkid);
395
+ if (!at)
396
+ return refuse('validation-failed', 'RLTP-TRU-5360');
397
+ const { rel, tuple: t } = at;
398
+ const inn = art.inn(t);
399
+ const body = bodyOf(doc.payload);
400
+ const salt = BigInt(body.salt);
401
+ if (salt <= BigInt(inn.hwm))
402
+ return refuse('validation-failed', 'RLTP-TRU-5150');
403
+ if (salt <= BigInt(inn.lowerBound))
404
+ return refuse('validation-failed', 'RLTP-TRU-5366');
405
+ if (inn.assembly && salt < BigInt(inn.assembly.salt))
406
+ return refuse('validation-failed', 'RLTP-TRU-5340');
407
+ const shared = await pairShared(ctx, t);
408
+ if (!shared)
409
+ return refuse('validation-failed', 'RLTP-TRU-5080');
410
+ const own = (await ctx.keys.anchorOf(t.ownPairLabel)).anchor;
411
+ if (!(await art.macOk(ctx, { shared, from: t.counterpartAnchor, own, salt: body.salt, chunk: doc.payload })))
412
+ return refuse('validation-failed', 'RLTP-TRU-5080');
413
+ const ids = { relId: rel.id, tuple: t.ownPairLabel };
414
+ // the chunk is authenticated: a violation rejects the whole delivery (5260–5310)
415
+ const reject = (ref) => ({ kind: 'discard', ...ids, salt: body.salt, reason: 'rejected', ref });
416
+ const step = assemblyStep(inn.assembly, body.salt, body, doc.threadId, arrival.digest, limit);
417
+ if (step.kind === 'limit')
418
+ return { kind: 'discard', ...ids, salt: body.salt, reason: 'resource-limit', ref: 'RLTP-TRU-5365' };
419
+ if (step.kind === 'repeat')
420
+ return { kind: 'repeat' };
421
+ if (step.kind === 'reject')
422
+ return reject(step.ref);
423
+ if (step.kind === 'hold')
424
+ return { kind: 'hold', ...ids, assembly: step.assembly };
425
+ // the completing chunk: the union in seq order, validated, evaluated
426
+ const union = assembledBodies(step.chunks, step.last).flatMap((b) => art.entries(b));
427
+ const done = await art.complete(ctx, { shared, from: t.counterpartAnchor, own, salt: body.salt, union, rel, tuple: t });
428
+ if (!done.ok)
429
+ return reject(done.ref);
430
+ const contactId = entryOf(ctx.snapshot, rel)?.id ?? rel.contactId;
431
+ return { kind: 'complete', ...ids, salt: body.salt, accepted: done.accepted, ...(done.memory ? { memory: done.memory } : {}), contactId };
432
+ },
433
+ // stage 9: the decision lands on the same snapshot
434
+ apply(state, prepared) {
435
+ const p = prepared.value;
436
+ if (p.kind === 'repeat')
437
+ return { state: state };
438
+ const rel = state.relationships[p.relId];
439
+ const t = rel.tuples.find((x) => x.ownPairLabel === p.tuple);
440
+ const inn = art.inn(t);
441
+ const put = (i, memory) => ({
442
+ ...state,
443
+ relationships: {
444
+ ...state.relationships,
445
+ [rel.id]: { ...rel, ...(memory ? { memory } : {}), tuples: rel.tuples.map((x) => (x.ownPairLabel === p.tuple ? art.withIn(x, i) : x)) },
446
+ },
447
+ });
448
+ switch (p.kind) {
449
+ case 'hold': return { state: put({ ...inn, assembly: p.assembly }) };
450
+ case 'discard':
451
+ // the resource limit or a rejected delivery: the assembly dropped,
452
+ // its salt the lower bound (5365, 5366); the completed hwm stays
453
+ return { discarded: true, reason: p.reason, ref: p.ref, state: put({ ...inn, assembly: null, lowerBound: maxDec(inn.lowerBound, p.salt) }) };
454
+ case 'complete': {
455
+ const events = [{ type: art.kind === 'star' ? 'star.accepted' : 'groupStar.accepted', contactId: p.contactId, relationshipId: rel.id, salt: p.salt }];
456
+ return { state: put({ ...inn, hwm: p.salt, assembly: null, accepted: p.accepted }, p.memory ? { ...rel.memory, ...p.memory } : undefined), events };
457
+ }
458
+ }
459
+ },
460
+ };
461
+ }
462
+ /** A tuple with its open assemblies dropped, as its deactivation leaves
463
+ * it (5360): for chaining and departure (PR 4c). */
464
+ export const discardAssemblies = (t) => ({
465
+ ...t,
466
+ starIn: { ...t.starIn, assembly: null },
467
+ groupStarIn: { ...t.groupStarIn, assembly: null },
468
+ probeIn: { ...t.probeIn, assembly: null },
469
+ });
470
+ // ── the star ───────────────────────────────────────────────────────────
471
+ /** The reconciliation view of 5610/5620: (member, anchor head) of every
472
+ * admitted entry, member = the entry id, sorted by member. */
473
+ export function starView(state) {
474
+ return Object.values(state.contacts)
475
+ .filter((c) => c.status === 'admitted' && c.anchorHead !== null)
476
+ .map((c) => ({ member: c.id, head: c.anchorHead }))
477
+ .sort((a, b) => cmp(a.member, b.member));
478
+ }
479
+ /** The entry a tuple's documents come from (2145): the star's sender. */
480
+ const senderOf = (state, rel, t) => findContact(state, t.entryId)?.id ?? findContact(state, rel.contactId)?.id ?? rel.contactId;
481
+ export async function starBlinds(ctx, anchors) {
482
+ const out = {};
483
+ if (!anchors.length)
484
+ return out;
485
+ const S = ctx.crypto.subtle;
486
+ for (const rel of Object.values(ctx.snapshot.relationships)) {
487
+ for (const t of rel.tuples) {
488
+ const acc = t.starIn.accepted;
489
+ if (acc === null)
490
+ continue;
491
+ const shared = await pairShared(ctx, t);
492
+ if (!shared)
493
+ continue;
494
+ const own = (await ctx.keys.anchorOf(t.ownPairLabel)).anchor;
495
+ const k = await starKeyOf(shared, t.counterpartAnchor, own, acc.salt, S);
496
+ out[`${rel.id}|${t.ownPairLabel}`] = { salt: acc.salt, blinds: await Promise.all(anchors.map((a) => blindAnchor(k, a, S))) };
497
+ }
498
+ }
499
+ return out;
500
+ }
501
+ /**
502
+ * The sync half (6160, 6390): `contactId` now holds the anchors `blinds`
503
+ * were prepared for; every accepted star (other than the contact's own)
504
+ * whose stored union carries one of them lists the contact among its
505
+ * matches. Run in the transition that makes the holder hold them (the
506
+ * anchor mapping's acceptance: a new entry, an advanced head, a new link).
507
+ */
508
+ export function rematchStars(state, contactId, blinds) {
509
+ let relationships = null;
510
+ for (const [key, b] of Object.entries(blinds)) {
511
+ const [relId, label] = [key.slice(0, key.indexOf('|')), key.slice(key.indexOf('|') + 1)];
512
+ const rel = (relationships ?? state.relationships)[relId];
513
+ const t = rel?.tuples.find((x) => x.ownPairLabel === label);
514
+ const acc = t?.starIn.accepted;
515
+ if (!rel || !t || !acc || acc.salt !== b.salt || acc.matches.includes(contactId) || senderOf(state, rel, t) === contactId)
516
+ continue;
517
+ const union = new Set(acc.blinded);
518
+ if (!b.blinds.some((x) => union.has(x)))
519
+ continue;
520
+ const matches = [...acc.matches, contactId].sort(cmp);
521
+ relationships ??= { ...state.relationships };
522
+ relationships[relId] = { ...rel, tuples: rel.tuples.map((x) => (x === t ? { ...x, starIn: { ...x.starIn, accepted: { ...acc, matches } } } : x)) };
523
+ }
524
+ return relationships ? { ...state, relationships } : state;
525
+ }
526
+ /** Every anchor the holder holds for a contact (6160, 6390): the entry's
527
+ * head, each relationship's held head, both ends of every held link. */
528
+ export function heldAnchors(state, c) {
529
+ const out = new Set();
530
+ if (c.anchorHead !== null)
531
+ out.add(c.anchorHead);
532
+ for (const r of entryRelationships(state, c)) {
533
+ if (r.heldCounterpartHead !== null)
534
+ out.add(r.heldCounterpartHead);
535
+ for (const l of r.links) {
536
+ out.add(l.prev);
537
+ out.add(l.next);
538
+ }
539
+ }
540
+ return [...out].sort();
541
+ }
542
+ /** The star as an artifact (5.1–5.4). */
543
+ export const STAR = {
544
+ kind: 'star',
545
+ type: STAR_TYPE,
546
+ cls: 'star',
547
+ malformedRef: 'RLTP-TRU-5070',
548
+ out: (t) => t.starOut,
549
+ withOut: (t, o) => ({ ...t, starOut: o }),
550
+ inn: (t) => t.starIn,
551
+ withIn: (t, i) => ({ ...t, starIn: i }),
552
+ view: (state) => starView(state),
553
+ uniform: true,
554
+ async build(ctx, p) {
555
+ const S = ctx.crypto.subtle;
556
+ const k = await starKeyOf(p.shared, p.own, p.tuple.counterpartAnchor, p.salt, S);
557
+ return (await buildStarChunks(k, p.salt, p.view.map((v) => v.head), S));
558
+ },
559
+ chunkOk: starChunkOk,
560
+ async macOk(ctx, p) {
561
+ const S = ctx.crypto.subtle;
562
+ return starChunkMacOk(await starKeyOf(p.shared, p.from, p.own, p.salt, S), p.chunk, S);
563
+ },
564
+ entries: (body) => body.blinded,
565
+ async complete(ctx, p) {
566
+ const union = p.union;
567
+ if (!strictlyAscending(union))
568
+ return { ok: false, ref: 'RLTP-TRU-5300' }; // 5300 order, 5310 duplicates
569
+ const S = ctx.crypto.subtle;
570
+ const k = await starKeyOf(p.shared, p.from, p.own, p.salt, S);
571
+ const inStar = new Set(union);
572
+ const sender = senderOf(ctx.snapshot, p.rel, p.tuple);
573
+ const matches = [];
574
+ for (const c of Object.values(ctx.snapshot.contacts).sort((a, b) => cmp(a.id, b.id))) {
575
+ if (c.id === sender)
576
+ continue;
577
+ for (const a of heldAnchors(ctx.snapshot, c)) {
578
+ if (inStar.has(await blindAnchor(k, a, S))) {
579
+ matches.push(c.id);
580
+ break;
581
+ }
582
+ }
583
+ }
584
+ // the whole union is kept: anchors learned later are tested against it (rematchStars)
585
+ return { ok: true, accepted: { salt: p.salt, blinded: union, matches } };
586
+ },
587
+ };
588
+ //# sourceMappingURL=star.js.map