@serfab/cadre-core 1.16.0 → 1.18.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 (268) hide show
  1. package/README.md +4 -3
  2. package/dist/bootstrap-peer-store-file.js +1 -1
  3. package/dist/bootstrap-peer-store.d.ts +1 -1
  4. package/dist/bootstrap-peer-store.js +4 -4
  5. package/dist/cadre-invite-protocol.d.ts +81 -142
  6. package/dist/cadre-invite-protocol.js +168 -458
  7. package/dist/cadre-invite-protocol.js.map +1 -1
  8. package/dist/cadre-join-request.d.ts +15 -2
  9. package/dist/cadre-join-request.js +22 -3
  10. package/dist/cadre-join-request.js.map +1 -1
  11. package/dist/cadre-node.d.ts +1089 -475
  12. package/dist/cadre-node.js +2170 -1037
  13. package/dist/cadre-node.js.map +1 -1
  14. package/dist/control-authorization.d.ts +36 -14
  15. package/dist/control-authorization.js +20 -3
  16. package/dist/control-authorization.js.map +1 -1
  17. package/dist/control-cohort.d.ts +88 -32
  18. package/dist/control-cohort.js +91 -33
  19. package/dist/control-cohort.js.map +1 -1
  20. package/dist/control-database.d.ts +856 -318
  21. package/dist/control-database.js +1422 -701
  22. package/dist/control-database.js.map +1 -1
  23. package/dist/control-formation-recorder.d.ts +46 -43
  24. package/dist/control-formation-recorder.js +77 -78
  25. package/dist/control-formation-recorder.js.map +1 -1
  26. package/dist/control-protocol-guard.d.ts +11 -4
  27. package/dist/control-protocol-guard.js +20 -5
  28. package/dist/control-protocol-guard.js.map +1 -1
  29. package/dist/control-schema-version.d.ts +97 -0
  30. package/dist/control-schema-version.js +225 -0
  31. package/dist/control-schema-version.js.map +1 -0
  32. package/dist/control-schema.d.ts +45 -5
  33. package/dist/control-schema.js +262 -78
  34. package/dist/control-schema.js.map +1 -1
  35. package/dist/control-write-retry.d.ts +45 -10
  36. package/dist/control-write-retry.js +92 -22
  37. package/dist/control-write-retry.js.map +1 -1
  38. package/dist/delegate-admission.d.ts +13 -0
  39. package/dist/delegate-admission.js +27 -0
  40. package/dist/delegate-admission.js.map +1 -1
  41. package/dist/ed25519-key.d.ts +10 -7
  42. package/dist/ed25519-key.js +23 -20
  43. package/dist/ed25519-key.js.map +1 -1
  44. package/dist/edge-node/config.d.ts +122 -0
  45. package/dist/edge-node/config.js +199 -0
  46. package/dist/edge-node/config.js.map +1 -0
  47. package/dist/edge-node/dissolve.d.ts +95 -0
  48. package/dist/edge-node/dissolve.js +184 -0
  49. package/dist/edge-node/dissolve.js.map +1 -0
  50. package/dist/edge-node/edge-join.d.ts +44 -0
  51. package/dist/edge-node/edge-join.js +227 -0
  52. package/dist/edge-node/edge-join.js.map +1 -0
  53. package/dist/edge-node/edge-node.d.ts +295 -0
  54. package/dist/edge-node/edge-node.js +704 -0
  55. package/dist/edge-node/edge-node.js.map +1 -0
  56. package/dist/edge-node/index.d.ts +18 -0
  57. package/dist/edge-node/index.js +18 -0
  58. package/dist/edge-node/index.js.map +1 -0
  59. package/dist/edge-node/join-credential.d.ts +11 -0
  60. package/dist/edge-node/join-credential.js +11 -0
  61. package/dist/edge-node/join-credential.js.map +1 -0
  62. package/dist/edge-node/lazy-raw-storage.d.ts +40 -0
  63. package/dist/edge-node/lazy-raw-storage.js +134 -0
  64. package/dist/edge-node/lazy-raw-storage.js.map +1 -0
  65. package/dist/edge-node/node-local.d.ts +60 -0
  66. package/dist/edge-node/node-local.js +58 -0
  67. package/dist/edge-node/node-local.js.map +1 -0
  68. package/dist/edge-node/options.d.ts +83 -0
  69. package/dist/edge-node/options.js +110 -0
  70. package/dist/edge-node/options.js.map +1 -0
  71. package/dist/edge-node/owner-grant.d.ts +41 -0
  72. package/dist/edge-node/owner-grant.js +38 -0
  73. package/dist/edge-node/owner-grant.js.map +1 -0
  74. package/dist/edge-node/platform.d.ts +140 -0
  75. package/dist/edge-node/platform.js +12 -0
  76. package/dist/edge-node/platform.js.map +1 -0
  77. package/dist/edge-node/strand-move.d.ts +115 -0
  78. package/dist/edge-node/strand-move.js +223 -0
  79. package/dist/edge-node/strand-move.js.map +1 -0
  80. package/dist/edge-node/strands.d.ts +28 -0
  81. package/dist/edge-node/strands.js +60 -0
  82. package/dist/edge-node/strands.js.map +1 -0
  83. package/dist/enrolled-machine-store.d.ts +3 -3
  84. package/dist/enrolled-machine-store.js +3 -3
  85. package/dist/formation-token.d.ts +48 -0
  86. package/dist/formation-token.js +26 -0
  87. package/dist/formation-token.js.map +1 -0
  88. package/dist/index.d.ts +27 -17
  89. package/dist/index.js +39 -17
  90. package/dist/index.js.map +1 -1
  91. package/dist/joined-strand-store.d.ts +29 -12
  92. package/dist/joined-strand-store.js +66 -27
  93. package/dist/joined-strand-store.js.map +1 -1
  94. package/dist/key-store.d.ts +9 -4
  95. package/dist/key-store.js +9 -4
  96. package/dist/key-store.js.map +1 -1
  97. package/dist/link-budget.d.ts +9 -7
  98. package/dist/link-budget.js +9 -7
  99. package/dist/link-budget.js.map +1 -1
  100. package/dist/machine-removal.d.ts +19 -39
  101. package/dist/machine-removal.js +24 -98
  102. package/dist/machine-removal.js.map +1 -1
  103. package/dist/member-redemption.d.ts +256 -0
  104. package/dist/member-redemption.js +490 -0
  105. package/dist/member-redemption.js.map +1 -0
  106. package/dist/membership-view.d.ts +91 -0
  107. package/dist/membership-view.js +110 -0
  108. package/dist/membership-view.js.map +1 -0
  109. package/dist/owner-anchor-sync.d.ts +4 -4
  110. package/dist/owner-anchor-sync.js +16 -15
  111. package/dist/owner-anchor-sync.js.map +1 -1
  112. package/dist/owner-grant-protocol.d.ts +251 -0
  113. package/dist/owner-grant-protocol.js +344 -0
  114. package/dist/owner-grant-protocol.js.map +1 -0
  115. package/dist/owner-key-export.d.ts +70 -0
  116. package/dist/owner-key-export.js +279 -0
  117. package/dist/owner-key-export.js.map +1 -0
  118. package/dist/owner-key-flows.d.ts +55 -0
  119. package/dist/owner-key-flows.js +60 -0
  120. package/dist/owner-key-flows.js.map +1 -0
  121. package/dist/owner-key-store.d.ts +31 -0
  122. package/dist/owner-key-store.js +81 -0
  123. package/dist/owner-key-store.js.map +1 -0
  124. package/dist/owner-keys.d.ts +78 -0
  125. package/dist/owner-keys.js +133 -0
  126. package/dist/owner-keys.js.map +1 -0
  127. package/dist/owner-signer.d.ts +54 -0
  128. package/dist/owner-signer.js +51 -0
  129. package/dist/owner-signer.js.map +1 -0
  130. package/dist/peer-addr-book.d.ts +10 -1
  131. package/dist/peer-addr-book.js +4 -3
  132. package/dist/peer-addr-book.js.map +1 -1
  133. package/dist/peer-authorization.d.ts +83 -18
  134. package/dist/peer-authorization.js +123 -53
  135. package/dist/peer-authorization.js.map +1 -1
  136. package/dist/peer-join-backfill.d.ts +89 -20
  137. package/dist/peer-join-backfill.js +265 -108
  138. package/dist/peer-join-backfill.js.map +1 -1
  139. package/dist/pending-join-runner.d.ts +48 -18
  140. package/dist/pending-join-runner.js +74 -56
  141. package/dist/pending-join-runner.js.map +1 -1
  142. package/dist/provider/rent-node.js +1 -1
  143. package/dist/provider/rent-node.js.map +1 -1
  144. package/dist/seed-bootstrap.d.ts +162 -133
  145. package/dist/seed-bootstrap.js +300 -283
  146. package/dist/seed-bootstrap.js.map +1 -1
  147. package/dist/seed-trust-policy.js +4 -4
  148. package/dist/strand-addr-protocol.d.ts +4 -1
  149. package/dist/strand-addr-protocol.js +8 -2
  150. package/dist/strand-addr-protocol.js.map +1 -1
  151. package/dist/strand-cohort.d.ts +3 -0
  152. package/dist/strand-cohort.js.map +1 -1
  153. package/dist/strand-database.d.ts +24 -2
  154. package/dist/strand-database.js +29 -6
  155. package/dist/strand-database.js.map +1 -1
  156. package/dist/strand-first-sync-gate.d.ts +176 -88
  157. package/dist/strand-first-sync-gate.js +288 -121
  158. package/dist/strand-first-sync-gate.js.map +1 -1
  159. package/dist/strand-formation-deadlines.d.ts +9 -9
  160. package/dist/strand-formation-deadlines.js +9 -9
  161. package/dist/strand-formation-manager.d.ts +23 -17
  162. package/dist/strand-formation-manager.js +46 -44
  163. package/dist/strand-formation-manager.js.map +1 -1
  164. package/dist/strand-formation-protocol.d.ts +5 -8
  165. package/dist/strand-formation-protocol.js +101 -16
  166. package/dist/strand-formation-protocol.js.map +1 -1
  167. package/dist/strand-instance-manager.d.ts +27 -18
  168. package/dist/strand-instance-manager.js +119 -50
  169. package/dist/strand-instance-manager.js.map +1 -1
  170. package/dist/strand-membership-reconciler.d.ts +16 -13
  171. package/dist/strand-membership-reconciler.js +30 -22
  172. package/dist/strand-membership-reconciler.js.map +1 -1
  173. package/dist/strand-membership-writer.d.ts +45 -22
  174. package/dist/strand-membership-writer.js +226 -131
  175. package/dist/strand-membership-writer.js.map +1 -1
  176. package/dist/strand-move.d.ts +6 -5
  177. package/dist/strand-move.js +36 -32
  178. package/dist/strand-move.js.map +1 -1
  179. package/dist/strand-peer-redial.js +2 -3
  180. package/dist/strand-peer-redial.js.map +1 -1
  181. package/dist/strand-revocation-enforcer.d.ts +134 -60
  182. package/dist/strand-revocation-enforcer.js +144 -71
  183. package/dist/strand-revocation-enforcer.js.map +1 -1
  184. package/dist/strand-solicitation.d.ts +50 -39
  185. package/dist/strand-solicitation.js +10 -3
  186. package/dist/strand-solicitation.js.map +1 -1
  187. package/dist/strand-watcher.d.ts +80 -13
  188. package/dist/strand-watcher.js +152 -48
  189. package/dist/strand-watcher.js.map +1 -1
  190. package/dist/table-change-watch.d.ts +63 -0
  191. package/dist/table-change-watch.js +209 -0
  192. package/dist/table-change-watch.js.map +1 -0
  193. package/dist/timeout-scheduler.d.ts +4 -3
  194. package/dist/timeout-scheduler.js +4 -3
  195. package/dist/timeout-scheduler.js.map +1 -1
  196. package/dist/types.d.ts +374 -150
  197. package/dist/types.js +4 -4
  198. package/dist/types.js.map +1 -1
  199. package/package.json +17 -8
  200. package/src/bootstrap-peer-store-file.ts +44 -44
  201. package/src/bootstrap-peer-store.ts +214 -214
  202. package/src/cadre-invite-protocol.ts +740 -1052
  203. package/src/cadre-join-request.ts +24 -3
  204. package/src/cadre-node.ts +2402 -1079
  205. package/src/control-authorization.ts +47 -14
  206. package/src/control-cohort.ts +204 -128
  207. package/src/control-database.ts +1749 -820
  208. package/src/control-formation-recorder.ts +359 -353
  209. package/src/control-protocol-guard.ts +23 -6
  210. package/src/control-schema-version.ts +301 -0
  211. package/src/control-schema.ts +273 -78
  212. package/src/control-write-retry.ts +641 -567
  213. package/src/delegate-admission.ts +351 -314
  214. package/src/ed25519-key.ts +174 -171
  215. package/src/edge-node/config.ts +277 -0
  216. package/src/edge-node/dissolve.ts +251 -0
  217. package/src/edge-node/edge-join.ts +319 -0
  218. package/src/edge-node/edge-node.ts +1045 -0
  219. package/src/edge-node/index.ts +59 -0
  220. package/src/edge-node/join-credential.ts +16 -0
  221. package/src/edge-node/lazy-raw-storage.ts +178 -0
  222. package/src/edge-node/node-local.ts +84 -0
  223. package/src/edge-node/options.ts +179 -0
  224. package/src/edge-node/owner-grant.ts +63 -0
  225. package/src/edge-node/platform.ts +151 -0
  226. package/src/edge-node/strand-move.ts +270 -0
  227. package/src/edge-node/strands.ts +72 -0
  228. package/src/enrolled-machine-store.ts +3 -3
  229. package/src/formation-token.ts +63 -0
  230. package/src/index.ts +899 -789
  231. package/src/joined-strand-store.ts +73 -28
  232. package/src/key-store.ts +10 -4
  233. package/src/link-budget.ts +9 -7
  234. package/src/machine-removal.ts +26 -111
  235. package/src/member-redemption.ts +649 -0
  236. package/src/membership-view.ts +144 -0
  237. package/src/owner-anchor-sync.ts +19 -18
  238. package/src/owner-grant-protocol.ts +547 -0
  239. package/src/owner-key-export.ts +348 -0
  240. package/src/owner-key-flows.ts +109 -0
  241. package/src/owner-key-store.ts +85 -0
  242. package/src/owner-keys.ts +178 -0
  243. package/src/owner-signer.ts +94 -0
  244. package/src/peer-addr-book.ts +4 -3
  245. package/src/peer-authorization.ts +558 -488
  246. package/src/peer-join-backfill.ts +971 -761
  247. package/src/pending-join-runner.ts +76 -53
  248. package/src/provider/rent-node.ts +1 -1
  249. package/src/seed-bootstrap.ts +343 -349
  250. package/src/seed-trust-policy.ts +4 -4
  251. package/src/strand-addr-protocol.ts +547 -538
  252. package/src/strand-cohort.ts +4 -0
  253. package/src/strand-database.ts +352 -319
  254. package/src/strand-first-sync-gate.ts +334 -133
  255. package/src/strand-formation-deadlines.ts +227 -227
  256. package/src/strand-formation-manager.ts +731 -725
  257. package/src/strand-formation-protocol.ts +122 -24
  258. package/src/strand-instance-manager.ts +2092 -2026
  259. package/src/strand-membership-reconciler.ts +33 -25
  260. package/src/strand-membership-writer.ts +2093 -1974
  261. package/src/strand-move.ts +50 -38
  262. package/src/strand-peer-redial.ts +2 -3
  263. package/src/strand-revocation-enforcer.ts +740 -654
  264. package/src/strand-solicitation.ts +61 -34
  265. package/src/strand-watcher.ts +175 -56
  266. package/src/table-change-watch.ts +285 -0
  267. package/src/timeout-scheduler.ts +29 -28
  268. package/src/types.ts +391 -152
@@ -11,16 +11,16 @@
11
11
  * founder from a joiner; sereus can, because the launch knows whether it is founding.
12
12
  *
13
13
  * The invariant this module enforces: **a machine that has never held this strand's
14
- * `Strand.Header` row, and has not read every `App` table once, must not commit to it.**
15
- * The founder writes the Header in its bootstrap; every other machine can only receive it
16
- * from a peer, so "no local Header" means "never synced". The gate therefore holds the
14
+ * `Strand.Header` row, and has not read every `Strand` and `App` table once, must not commit
15
+ * to it.** The founder writes the Header in its bootstrap; every other machine can only receive
16
+ * it from a peer, so "no local Header" means "never synced". The gate therefore holds the
17
17
  * freshly initialized `StrandDatabase` back from the app — `StrandInstance.database` stays
18
- * unset and the instance reports `'syncing'` — and probes on a cadence until the Header is
19
- * readable AND a read of each app table settles, at which point the database is published
20
- * and the instance goes `'active'`. A machine whose store already holds the Header and every
21
- * app table (a founder, and normally a restart or a hibernation resume) passes the probe on
22
- * the first try and is never gated, so offline-first writes on a machine that has synced
23
- * before keep working.
18
+ * unset and the instance reports `'syncing'` — and probes, whenever data lands in the strand's
19
+ * store and on a backstop timer, until the Header is readable AND a read of each other table
20
+ * settles, at which point the database is published and the instance goes `'active'`. A
21
+ * machine whose store already holds the Header and every other table (a founder, and normally
22
+ * a restart or a hibernation resume) passes the probe on the first try and is never gated, so
23
+ * offline-first writes on a machine that has synced before keep working.
24
24
  *
25
25
  * "Has synced before" is decided by what the local store actually holds, not by whether this
26
26
  * machine once read the strand: a machine that left soon after its first sync can hold less
@@ -29,22 +29,28 @@
29
29
  * it kept, without the `Strand.Header` collection it had read before it left. Gating it is
30
30
  * right: writing into a collection this machine does not hold would fork it.
31
31
  *
32
- * Why the app tables too: the Header is ONE collection. It reaches a joiner ahead of the
33
- * founder's app-table collections (peer-join backfill and pull-on-read deliver those
34
- * separately), and a write to a table whose collection this machine has not yet fetched
35
- * invents one exactly as a lone joiner did. Measured on the reference chat schema
36
- * (2026-09-16, direct connections, Header-only probe): the joiner's participant + message
37
- * written the instant `addStrand` resolved diverged in 3 of 4 runs. Reading each table once
38
- * pulls its collection while the founder is reachable, so the write that follows appends.
32
+ * Why the other tables too: the Header is ONE collection. It reaches a joiner ahead of the
33
+ * founder's other collections (peer-join backfill and pull-on-read deliver those separately),
34
+ * and a write to a table whose collection this machine has not yet fetched invents one exactly
35
+ * as a lone joiner did. Measured on the reference chat schema (2026-09-16, direct connections,
36
+ * Header-only probe): the joiner's participant + message written the instant `addStrand`
37
+ * resolved diverged in 3 of 4 runs. The same holds for the `Strand` tables, which reach a joiner
38
+ * by the same backfill: the membership loop writes `ConsumedInvite`, `Member` and `MemberPeer`
39
+ * about a second after publication, and a manager writes `Invite`, `CancelledInvite`, `Manager`
40
+ * and `Revocation`. Reading each table once pulls its collection while the founder is
41
+ * reachable, so the write that follows appends.
39
42
  *
40
- * Every probe is a read (`Strand.Header`, then `select count(1)` on each `App` table). Reads
41
- * never invent a collection (Optimystic's `Collection.open` resolves undefined on an
42
- * authoritatively absent header, and throws on an unreachable one), so probing is safe to
43
- * repeat and a throw is just "not yet". A table nobody has written yet reads as absent on
44
- * every machine; the first write to it still creates its collection — see `docs/strands.md`
45
- * ("Joining") for that residual.
43
+ * Every probe read is `select 1 from <table> limit 1` (`Strand.Header`, then each other `Strand`
44
+ * table, then each `App` table, skipping those already seen settled): opening the table fetches
45
+ * its collection header, and one row walks the root to the first leaf. Reads never commit a
46
+ * collection (one ruled authoritatively absent is staged empty in memory only, and an
47
+ * unreachable one throws), so probing is safe to repeat and a throw is just "not yet". Every
48
+ * table declared at founding has a collection from the start (the founder bootstrap commits the
49
+ * rest empty before its founding transaction writes the Header and the membership rows), so a
50
+ * probe read fetches it; a table a later schema version adds reads as absent until its first
51
+ * write creates the collection — see `docs/strands.md` ("Joining") for that residual.
46
52
  *
47
- * Owned by `StrandInstanceManager`: created in `buildStrandRuntime` for every launch, armed
53
+ * Owned by `StrandInstanceManager`: created in `buildStrandDatabase` for every database build, armed
48
54
  * only for a non-founder launch whose first probe fails, stopped and dropped in
49
55
  * `releaseRuntime` (so a quiesce → resume rebuild re-probes over the same store), and opened
50
56
  * by `publishDatabase` whenever the database is published without its loop — a launch that
@@ -63,10 +69,23 @@
63
69
  * Header too, or have optimystic open the catalog read-only when a peer already holds it.
64
70
  */
65
71
  import debug from 'debug';
72
+ import { scheduleRuns, TABLE_CHANGE_COALESCE_MS } from './table-change-watch.js';
66
73
  import { defaultTimeoutScheduler } from './timeout-scheduler.js';
67
74
  const log = debug('sereus:cadre:strand-first-sync');
68
- /** How often a gated launch re-probes (`Strand.Header`, then every `App` table) while waiting for its first sync. */
69
- export const DEFAULT_STRAND_FIRST_SYNC_POLL_MS = 500;
75
+ /**
76
+ * How long a gated launch goes without probing when no data lands in the strand's store: the
77
+ * gate probes when data arrives, and this long after its last probe settled when none has. It
78
+ * covers a probe that failed for a reason no storage event follows — a consult that missed its
79
+ * deadline while the founder held the block, or a push that never comes.
80
+ *
81
+ * Why 2 s: arrivals wake the gate, so this timer is the fallback rather than the main path. At
82
+ * the supported 3 s link round trip one consult takes about 6 s (two round trips, per the 1.8.0
83
+ * measurements on {@link DEFAULT_STRAND_FIRST_SYNC_TIMEOUT_MS}), so a 2 s idle gap after a failed
84
+ * probe adds at most a third of one probe round, and it is under 1% of that 360 s budget. Shorter
85
+ * brings back the cost of polling (the gate re-probed every 500 ms before it woke on arrivals);
86
+ * much longer (5 s) delays the case with no storage event by most of a probe round for nothing.
87
+ */
88
+ export const DEFAULT_STRAND_FIRST_SYNC_BACKSTOP_MS = 2000;
70
89
  /**
71
90
  * How long `CadreNode.addStrand` waits for a machine's first sync before rejecting with
72
91
  * {@link StrandAwaitingFirstSyncError}. It has to cover the slowest attach sereus carries,
@@ -97,6 +116,37 @@ export const DEFAULT_STRAND_FIRST_SYNC_POLL_MS = 500;
97
116
  * several of those — which is why both bands are recorded here, and why a change to either
98
117
  * number has to be weighed against the other.
99
118
  *
119
+ * AT THE SUPPORTED LINK ON `@optimystic/*` 1.17.0 (2026-10-21), 1 500 ms one-way, the same
120
+ * scenario, one run per arm, with the probe reading every `Strand` table as well as the `App`
121
+ * one (`debt-first-sync-gate-reads-every-strand-table`); the kept-store arm was not run:
122
+ * - Fresh join: writable at 28.7 s and NOT gated, as on 1.15.0.
123
+ * - Re-attach over an EMPTY store: writable at 105.8 and 105.9 s over two runs, the row A wrote
124
+ * while B was away readable at the same moment. B's first database build fails at 7.0 s (no
125
+ * seed peer has answered; `StrandInstanceManager.deferUnreachableJoin` retries it), B's strand
126
+ * node connects to A's at 15.1 s, the retried build's `connectToStrand` takes 21.2 s, and the
127
+ * launch's first probe then takes 72.6 s and passes with no read failing, so the gate never
128
+ * arms.
129
+ * - The same arm with the probe held to the Header and the `App` table (one run, otherwise the
130
+ * same build): that probe took 12.1 s, writable at 45.4 s, the row at 63.5 s. So the seven
131
+ * `Strand` reads cost about 60 s here, about 8.6 s each, against about 6 s for each of the
132
+ * baseline's two reads — more than the 42 s estimated from one 6 s consult per read.
133
+ *
134
+ * AT THE SUPPORTED LINK ON `@optimystic/*` 1.15.0 (2026-10-20), 1 500 ms one-way, the same
135
+ * scenario, one fresh-join run, with the one-row probes woken by data landing in the store
136
+ * (`first-sync-gate-probes-cheaply`; the full-count probes polled every 500 ms gave the same
137
+ * figures):
138
+ * - Fresh join: writable at 28.8 s, and NOT gated. The launch waits 27.3 s in
139
+ * `connectToStrand` for its seed peer (it hydrates the founder's catalog, 9 tables), and by
140
+ * then the store holds the strand: the first probe passed in 24 ms.
141
+ * - The strand case of `message-cost-accounting` (two relay-only parties on loopback) agrees:
142
+ * B's `addStrand` took 471 ms, with 13 `sync` streams to A, 4 block reads from the Header
143
+ * probe and 8 from the table probes. Its two tables are empty, so a full count would fetch
144
+ * the same blocks as a one-row read.
145
+ * - Neither arms the gate, so neither times the woken path. That was checked on
146
+ * `harness-topology`'s cross-party share (a joiner launched with no seed, dialed afterwards)
147
+ * with the backstop raised to 10 s: the founder's join backfill push opened the gate 2.1 s
148
+ * after it armed.
149
+ *
100
150
  * AT THE SUPPORTED LINK ON `@optimystic/*` 1.8.0 (2026-09-29), 1 500 ms one-way, the read
101
151
  * deadline derived from the declared link at 7 000 ms (`cohortReadDeadlineMs` in
102
152
  * `link-budget.ts`; the plugin's `COHORT_READ_DEADLINE_MS` is the same number), and cadre
@@ -153,19 +203,19 @@ export const DEFAULT_STRAND_FIRST_SYNC_POLL_MS = 500;
153
203
  * injector are unknown to us) opened the gate at about 150 s, after the previous 120 s budget
154
204
  * had already rejected; and a real Galaxy S7 joining fresh through relay.sereus.org took 178 s.
155
205
  *
156
- * 300 s clears the worst harness sample (82 s, a fresh join at the supported link on 1.7.0; the
157
- * worst on 1.8.0 is 64 s) by about 3.6x, the reporter's re-attach by 2x and the device join by
158
- * about 1.7x. Kept at 300 s on 2026-09-29, and again after the 1.8.0 re-measure the same day,
159
- * by the rule that re-measure applied and the next one should: keep it while the
160
- * worst harness sample is at most 100 s, which holds a margin of at least 3x; otherwise raise
161
- * it to 3x the worst sample, rounded up to the next whole minute. 240 s was rejected earlier:
162
- * it clears the device join by only 1.35x, and that sample was not a re-attach. History: the
163
- * original 30 s sat INSIDE the 1000 ms fresh band and refused about half of those joins while
164
- * their sync was progressing normally; 120 s was sized from fresh joins only and refused the
165
- * reporter's re-attach.
206
+ * 360 s clears the worst harness sample (105.9 s, a re-attach over an empty store at the
207
+ * supported link on 1.17.0) by about 3.4x, the reporter's re-attach by 2.4x and the device join
208
+ * by about 2x. Set by the rule every re-measure applies: keep it while the worst harness sample
209
+ * is at most 100 s, which holds a margin of at least 3x; otherwise raise it to 3x the worst
210
+ * sample, rounded up to the next whole minute. The 1.17.0 sample crossed 100 s, and 3x 105.9 s
211
+ * is 318 s, so 360 s (2026-10-21); the 1.7.0 and 1.8.0 re-measures (worst 82 s and 64 s) had
212
+ * kept the 300 s before it. 240 s was rejected earlier: it clears the device join by only 1.35x,
213
+ * and that sample was not a re-attach. History: the original 30 s sat INSIDE the 1000 ms fresh
214
+ * band and refused about half of those joins while their sync was progressing normally; 120 s
215
+ * was sized from fresh joins only and refused the reporter's re-attach.
166
216
  *
167
- * NOTE: accepted tradeoff — what 300 s costs. This wait is what an app's `addStrand` sits in
168
- * before it is told "not yet", so a strand none of whose members is reachable at all takes five
217
+ * NOTE: accepted tradeoff — what 360 s costs. This wait is what an app's `addStrand` sits in
218
+ * before it is told "not yet", so a strand none of whose members is reachable at all takes six
169
219
  * minutes to report instead of two. The slow report was weighed against refusing attaches that
170
220
  * were working, and the refusal is the worse failure. The cost is bounded — the rejection is
171
221
  * retryable, the strand stays launched and keeps probing, and `strand:writable` fires the
@@ -175,7 +225,7 @@ export const DEFAULT_STRAND_FIRST_SYNC_POLL_MS = 500;
175
225
  * sync that is actually in progress — worth more the longer this budget gets.
176
226
  */
177
227
  // eslint-disable-next-line no-restricted-syntax -- measured, not derived: the bands above, from integration-tests' strand-reattach-first-sync-measure.integration.ts; a whole first sync has no round-trip count
178
- export const DEFAULT_STRAND_FIRST_SYNC_TIMEOUT_MS = 300000;
228
+ export const DEFAULT_STRAND_FIRST_SYNC_TIMEOUT_MS = 360000;
179
229
  /**
180
230
  * Thrown by `CadreNode.addStrand` / `whenStrandWritable` when a machine has not received the
181
231
  * strand's data from another member within the wait budget — because no other member is
@@ -196,16 +246,24 @@ export class StrandAwaitingFirstSyncError extends Error {
196
246
  this.waitedMs = waitedMs;
197
247
  }
198
248
  }
249
+ const STRAND_HEADER = { schema: 'Strand', name: 'Header' };
250
+ function qualifiedName(table) {
251
+ return `${table.schema}.${table.name}`;
252
+ }
253
+ /** The read `openTables` in `quereus-plugin-sereus/src/empty-collections.ts` opens a collection with. */
254
+ function probeSql(table) {
255
+ return `select 1 from ${table.schema}."${table.name}" limit 1`;
256
+ }
199
257
  /**
200
- * Whether this machine holds the strand's `Strand.Header` row — the "has synced at least
201
- * once (or founded)" signal. A read, never a write: it cannot invent the collection. A
202
- * throwing read (an unreachable cohort mid-sync, a store still hydrating) reports `false`
203
- * with a log, since the caller's only response to either answer is "probe again".
258
+ * Whether this machine holds the strand's `Strand.Header` row — the "has synced at least once
259
+ * (or founded)" signal: held when the read returns a row. A throwing read (an unreachable cohort
260
+ * mid-sync, a store still hydrating) reports `false` with a log, since the caller's only
261
+ * response to either answer is "probe again".
204
262
  */
205
- export async function strandHeaderHeld(db, label) {
263
+ async function headerHeld(db, label) {
206
264
  try {
207
- for await (const row of db.eval('select count(1) as Count from Strand.Header')) {
208
- return (row.Count ?? 0) > 0;
265
+ for await (const _row of db.eval(probeSql(STRAND_HEADER))) {
266
+ return true;
209
267
  }
210
268
  return false;
211
269
  }
@@ -214,78 +272,175 @@ export async function strandHeaderHeld(db, label) {
214
272
  return false;
215
273
  }
216
274
  }
217
- /** The tables the sApp declared in the `App` schema, by name; views excluded. */
218
- function appTableNames(db) {
219
- const app = db.schemaManager.getSchema('App');
220
- return app ? Array.from(app.getAllTables()).filter((table) => !table.isView).map((table) => table.name) : [];
275
+ /**
276
+ * Whether a read of `table` settles on this machine — its collection has been fetched from the
277
+ * cohort, or is authoritatively absent. Only a throwing read (an unreachable cohort, a block not
278
+ * yet arrived) reports `false`; an empty table is a settled read, because the founder commits
279
+ * an empty collection for every declared table.
280
+ */
281
+ async function tableReadable(db, table, label) {
282
+ try {
283
+ for await (const _row of db.eval(probeSql(table))) {
284
+ break;
285
+ }
286
+ return true;
287
+ }
288
+ catch (error) {
289
+ log('[%s] %s probe failed (treated as not yet synced): %s', label, qualifiedName(table), error instanceof Error ? error.message : String(error));
290
+ return false;
291
+ }
292
+ }
293
+ /** The tables `schemaName` declares, views excluded; none when the schema is absent. */
294
+ function schemaTables(db, schemaName) {
295
+ const schema = db.schemaManager.getSchema(schemaName);
296
+ return schema
297
+ ? Array.from(schema.getAllTables()).filter((table) => !table.isView).map((table) => ({ schema: schemaName, name: table.name }))
298
+ : [];
221
299
  }
222
300
  /**
223
- * Whether a read of every `App` table settles on this machine — the "each app collection
224
- * has been fetched from the cohort, or is authoritatively absent" signal. Only a throwing
225
- * read (an unreachable cohort) reports `false`; an empty table is a settled read.
301
+ * Every table this machine may write, in probe order: the membership tables in `Strand` (a fixed
302
+ * set every strand has) and then the sApp's in `App`. The Header is left out, compared
303
+ * case-insensitively as SQL identifiers are, because it is read first and alone, and its read
304
+ * means something else (held = a row came back).
226
305
  */
227
- export async function appTablesReadable(db, label) {
228
- for (const table of appTableNames(db)) {
229
- try {
230
- for await (const _row of db.eval(`select count(1) as Count from App."${table}"`)) {
231
- break;
232
- }
306
+ function writableTables(db) {
307
+ const header = STRAND_HEADER.name.toLowerCase();
308
+ return [
309
+ ...schemaTables(db, STRAND_HEADER.schema).filter((table) => table.name.toLowerCase() !== header),
310
+ ...schemaTables(db, 'App')
311
+ ];
312
+ }
313
+ /**
314
+ * The gate's whole probe: the Header is held AND a read of every other `Strand` table and every
315
+ * `App` table has settled. Reads only what `progress` does not record yet, and records what it
316
+ * establishes.
317
+ *
318
+ * The Header is read first and alone: a joiner with no peer would otherwise pay one failing
319
+ * network read per table for nothing. The tables are then read one after another, stopping at
320
+ * the first that fails — the next probe starts there, and while the cohort is unreachable every
321
+ * read of a block this machine lacks fails the same way, each after a consult.
322
+ *
323
+ * NOTE: the reads are serialized (Quereus's default), not `readConcurrency: 'committed'`, though
324
+ * committed reads would let the table reads overlap. The Optimystic plugin's committed read
325
+ * serves the tree the table already holds and never refreshes it from the network, and schema
326
+ * apply initializes every table when the joiner has no catalog to hydrate, over an empty
327
+ * collection staged in memory when no peer could answer. A committed probe of such a table reads
328
+ * that empty collection as settled and opens the gate onto a fork. A serialized read refreshes
329
+ * the tree first (`Collection.update`), which adopts the founder's log once a peer holds it.
330
+ * Revisit if the plugin's committed read ever refreshes a tree it has never synced, or the gate
331
+ * can ask Optimystic directly whether a collection is held.
332
+ */
333
+ async function strandFirstSyncComplete(db, label, progress) {
334
+ if (!progress.header) {
335
+ if (!await headerHeld(db, label)) {
336
+ return false;
233
337
  }
234
- catch (error) {
235
- log('[%s] App.%s probe failed (treated as not yet synced): %s', label, table, error instanceof Error ? error.message : String(error));
338
+ progress.header = true;
339
+ }
340
+ for (const table of writableTables(db)) {
341
+ const name = qualifiedName(table);
342
+ if (progress.tables.has(name)) {
343
+ continue;
344
+ }
345
+ if (!await tableReadable(db, table, label)) {
236
346
  return false;
237
347
  }
348
+ progress.tables.add(name);
238
349
  }
239
350
  return true;
240
351
  }
241
352
  /**
242
- * The gate's whole probe: the Header is held AND every app table has been read once. The
243
- * app tables are read only once the Header is — a joiner with no peer would otherwise pay
244
- * one failing network read per table per probe for nothing.
245
- */
246
- export async function strandFirstSyncComplete(db, label) {
247
- return await strandHeaderHeld(db, label) && await appTablesReadable(db, label);
248
- }
249
- /**
250
- * Holds one launch's `StrandDatabase` until {@link strandFirstSyncComplete}. Probes are
251
- * sequential (the next is scheduled only after the previous read settles), so a slow
252
- * network read never stacks probes.
353
+ * Holds one launch's `StrandDatabase` until the first sync completes: the `Strand.Header` row is
354
+ * held and a read of every `Strand` and `App` table has settled. What a probe established is
355
+ * remembered for the gate's lifetime, so a later probe — the gate's own, after the caller's
356
+ * {@link probeOnce} — reads only what is still missing. A gate lives for one database build, so a
357
+ * hibernation wake or a deferred-join retry starts over, and over a store that holds everything
358
+ * every read answers locally.
359
+ *
360
+ * While armed it probes when data lands in the strand's store
361
+ * ({@link StrandFirstSyncGateDeps.subscribeStorageChanges}, coalesced over
362
+ * `TABLE_CHANGE_COALESCE_MS`), and {@link StrandFirstSyncConfig.backstopMs} after its last probe
363
+ * settled when nothing has. Probes run on `scheduleRuns`: one at a time, and data landing during
364
+ * one produces exactly one more.
365
+ *
366
+ * The wake source is the node's storage-wide change feed rather than a table watch
367
+ * (`StrandDatabase.watchTables`): the Optimystic plugin arms a table's replicated-change bridge
368
+ * only after the table initializes successfully, and the tables the gate waits on are the ones
369
+ * whose collection could not be fetched yet, so a table watch would not hear the push that
370
+ * delivers them. Each strand has its own libp2p node and store, so the feed hears this strand
371
+ * only.
253
372
  *
254
373
  * NOTE: a probe's reads dial the founder, through the relay when that is the only route, even
255
374
  * while the founder is unreachable: one run of `joiner-first-commit-over-slow-relay` logged 17
256
375
  * `hop connect denied` at the relay in about 10 s, each a few frames on the joiner's relay
257
- * connection. If that connection ever shows saturated while no strand member is connected, hold
258
- * the probe while the strand member redial (`strand-peer-redial.ts`) reports none connected.
376
+ * connection, when the gate re-probed every 500 ms and re-read every table. It now probes on
377
+ * arrivals and a 2 s backstop and skips what it holds, so expect fewer. If that connection ever
378
+ * shows saturated while no strand member is connected, hold the probe while the strand member
379
+ * redial (`strand-peer-redial.ts`) reports none connected.
259
380
  */
260
381
  export class StrandFirstSyncGate {
261
382
  constructor(deps, config) {
262
383
  this.deps = deps;
384
+ this.progress = { header: false, tables: new Set() };
385
+ this.listening = false;
386
+ /**
387
+ * Data landed since the last probe started (or, before any probe, since {@link listen}), so one
388
+ * is already due and will read it.
389
+ */
390
+ this.landedSinceProbe = false;
263
391
  this.stopped = false;
264
392
  this.opened = false;
265
- this.probing = false;
266
393
  this.scheduler = deps.scheduler ?? defaultTimeoutScheduler;
267
- this.pollIntervalMs = config?.pollIntervalMs ?? DEFAULT_STRAND_FIRST_SYNC_POLL_MS;
394
+ this.backstopMs = config?.backstopMs ?? DEFAULT_STRAND_FIRST_SYNC_BACKSTOP_MS;
268
395
  }
269
396
  /** The database this gate is holding back. */
270
397
  get database() {
271
398
  return this.deps.database;
272
399
  }
273
- /** True once the Header was seen (or the gate was force-opened); the loop is then done. */
400
+ /** True once the first sync completed (or the gate was force-opened); probing is then done. */
274
401
  get isOpen() {
275
402
  return this.opened;
276
403
  }
277
- /** Arm the probe loop. The first probe runs after one interval — the caller already probed once. */
404
+ /** Whether a block landed since the last probe started, or since {@link listen} when none has. */
405
+ get heardDataSinceProbe() {
406
+ return this.landedSinceProbe;
407
+ }
408
+ /**
409
+ * Subscribe to the storage change feed ahead of {@link start}, so a block landing before the
410
+ * gate is armed is heard: {@link heardDataSinceProbe} reports it, and `start` then probes at
411
+ * once. For a first join, which skips the caller's first probe unless a member's push already
412
+ * landed while the database was initializing.
413
+ */
414
+ listen() {
415
+ if (this.stopped || this.opened || this.listening)
416
+ return;
417
+ this.listening = true;
418
+ this.unsubscribeStorage = this.subscribeStorage();
419
+ }
420
+ /**
421
+ * Arm the probe schedule. It probes when data lands, or one backstop later; and at once when
422
+ * data already landed since the last probe (or since {@link listen}). Without `listen`, data
423
+ * that landed between the caller's probe and this call raised no event the gate heard, so
424
+ * that first backstop is what catches it.
425
+ */
278
426
  start() {
279
- if (this.stopped || this.opened || this.timer !== undefined)
427
+ if (this.stopped || this.opened || this.probes !== undefined)
280
428
  return;
281
- this.schedule();
282
- log('[%s] first-sync gate armed: no Strand.Header held yet; probing every %dms', this.deps.label, this.pollIntervalMs);
429
+ this.probes = scheduleRuns(() => this.probe(), {
430
+ label: `first-sync:${this.deps.label}`,
431
+ backstopMs: this.backstopMs,
432
+ scheduler: this.scheduler,
433
+ });
434
+ this.listen();
435
+ if (this.landedSinceProbe) {
436
+ this.probes.trigger(TABLE_CHANGE_COALESCE_MS);
437
+ }
438
+ log('[%s] first-sync gate armed: probing when data lands, and %dms after the last probe otherwise', this.deps.label, this.backstopMs);
283
439
  }
284
440
  /**
285
- * Open without the probe loop: the caller is publishing the database itself — a launch
286
- * whose first probe passed (or a founder's), which never armed the loop, or a founder
287
- * bootstrap just run against the gated database. Stops the loop; `onHeaderHeld` is NOT
288
- * invoked.
441
+ * Open without probing: the caller is publishing the database itself — a launch whose first
442
+ * probe passed (or a founder's), which never armed the schedule, or a founder bootstrap just
443
+ * run against the gated database. Stops probing; `onHeaderHeld` is NOT invoked.
289
444
  */
290
445
  open() {
291
446
  if (this.opened)
@@ -294,55 +449,67 @@ export class StrandFirstSyncGate {
294
449
  this.stop();
295
450
  log('[%s] database published by the caller; first-sync gate not needed or force-opened', this.deps.label);
296
451
  }
297
- /** Disarm the loop; a probe already in flight completes and is then ignored. */
452
+ /** Disarm: unsubscribe and drop the schedule; a probe already in flight completes and is then ignored. */
298
453
  stop() {
299
454
  this.stopped = true;
300
- if (this.timer !== undefined) {
301
- this.scheduler.clearTimeout(this.timer);
302
- this.timer = undefined;
303
- }
304
- }
305
- schedule() {
306
- this.timer = this.scheduler.setTimeout(() => {
307
- this.timer = undefined;
308
- void this.probe();
309
- }, this.pollIntervalMs);
310
- }
311
- async probe() {
312
- if (this.stopped || this.opened || this.probing)
313
- return;
314
- this.probing = true;
315
- const held = await this.probeHeld();
316
- // Re-check after the await: a release or a force-open may have landed mid-read.
317
- if (this.stopped || this.opened)
318
- return;
319
- if (!held) {
320
- this.schedule();
321
- return;
322
- }
323
- this.opened = true;
324
- this.stop();
325
- log('[%s] first-sync gate opened: Strand.Header and every App table read from a peer', this.deps.label);
326
- this.deps.onHeaderHeld();
455
+ this.unsubscribeStorage?.();
456
+ this.unsubscribeStorage = undefined;
457
+ this.probes?.dispose();
458
+ this.probes = undefined;
327
459
  }
328
460
  /**
329
- * One probe's answer, with a throw from OUTSIDE {@link strandFirstSyncComplete}'s own read
330
- * guards — the `App` schema lookup, or `getDatabase()` itself — reported as "not yet" like
331
- * any failing read. The loop is scheduled from `void this.probe()`, so an escaping rejection
332
- * would be unhandled AND leave nothing scheduled: the strand would stay gated for the whole
333
- * `timeoutMs` and never recover, which is the one failure this gate must not have.
461
+ * One probe's answer, reading only what this gate has not yet seen settled. A throw from
462
+ * OUTSIDE the probe's own read guards — a schema lookup, or `getDatabase()` itself — is
463
+ * reported as "not yet" like any failing read, so the caller's first probe cannot fail the
464
+ * launch over it and the gate's log says what happened.
334
465
  */
335
- async probeHeld() {
466
+ async probeOnce() {
467
+ this.landedSinceProbe = false;
336
468
  try {
337
- return await strandFirstSyncComplete(this.deps.database.getDatabase(), this.deps.label);
469
+ return await strandFirstSyncComplete(this.deps.database.getDatabase(), this.deps.label, this.progress);
338
470
  }
339
471
  catch (error) {
340
472
  log('[%s] first-sync probe threw (treated as not yet synced): %s', this.deps.label, error instanceof Error ? error.message : String(error));
341
473
  return false;
342
474
  }
343
- finally {
344
- this.probing = false;
475
+ }
476
+ /**
477
+ * NOTE: a probe's own reads can land blocks (a fetched block, a read-repair), which fires this
478
+ * feed and schedules one follow-up probe. Each follow-up means a block newly arrived, so they
479
+ * stop when nothing new lands; if gated launches ever show probe storms in the logs, ignore
480
+ * events while a probe is in flight.
481
+ */
482
+ subscribeStorage() {
483
+ const subscribe = this.deps.subscribeStorageChanges;
484
+ if (!subscribe) {
485
+ log('[%s] no storage change feed on this node; probing every %dms only', this.deps.label, this.backstopMs);
486
+ return undefined;
345
487
  }
488
+ return subscribe(() => this.onDataLanded());
489
+ }
490
+ /**
491
+ * Called synchronously by the store after each block lands, so it only arms a timer. A push
492
+ * lands its blocks one by one; only the first since the last probe started arms one, since
493
+ * that probe has not started yet and will read the rest.
494
+ */
495
+ onDataLanded() {
496
+ if (this.landedSinceProbe)
497
+ return;
498
+ this.landedSinceProbe = true;
499
+ this.probes?.trigger(TABLE_CHANGE_COALESCE_MS);
500
+ }
501
+ async probe() {
502
+ if (this.stopped || this.opened)
503
+ return;
504
+ log('[%s] first-sync probe (%s)', this.deps.label, this.landedSinceProbe ? 'data landed' : 'backstop');
505
+ const held = await this.probeOnce();
506
+ // Re-check after the await: a release or a force-open may have landed mid-read.
507
+ if (this.stopped || this.opened || !held)
508
+ return;
509
+ this.opened = true;
510
+ this.stop();
511
+ log('[%s] first-sync gate opened: Strand.Header, every Strand table and every App table read from a peer', this.deps.label);
512
+ this.deps.onHeaderHeld();
346
513
  }
347
514
  }
348
515
  //# sourceMappingURL=strand-first-sync-gate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"strand-first-sync-gate.js","sourceRoot":"","sources":["../src/strand-first-sync-gate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"strand-first-sync-gate.js","sourceRoot":"","sources":["../src/strand-first-sync-gate.ts"],"names":[],"mappings":"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"}
@@ -64,15 +64,15 @@ export interface FormationDeadlines {
64
64
  * trip, budgeted as a request over an open stream (2) for the same headroom as every other
65
65
  * one-exchange read in `link-budget.ts`.
66
66
  * - **validation** and the reads inside **provisionWork** are budgeted at two consulting
67
- * reads each, not one per read the code issues. What actually runs: validation is three
68
- * control reads (`isTokenValid` reads the invite; `isTokenUsed` reads it again and counts
69
- * its usages) plus whatever the disclosure validator does; the bound provisioning path
70
- * (`StrandFormationManager.provisionAsResponder`) reads the invite and the strand row to
71
- * resolve the host strand, the invite again, the approver's `ValidationKey` and the seat
72
- * count around the outside approval hook (an HTTP call, so its own
73
- * `DEFAULT_APPROVAL_TIMEOUT_MS` is link-independent and added flat), the strand row and its
74
- * `StrandPartyKey` to issue the joiner's membership invite, and the host strand's stamp
75
- * before the `FormationUsage` write; then one strand-database commit issues the invite.
67
+ * reads each, not one per read the code issues. What actually runs: validation is two
68
+ * control reads (`readToken` reads the invite and, for a metered unexpired one, counts its
69
+ * usages) plus whatever the disclosure validator does; the bound provisioning path
70
+ * (`StrandFormationManager.provisionAsResponder`) acts on that read, reads the strand row
71
+ * with its stamp to resolve the host strand, the approver's `ValidationKey` and the seat
72
+ * count around the outside approval hook when the invite names one (an HTTP call, so its
73
+ * own `DEFAULT_APPROVAL_TIMEOUT_MS` is link-independent and added flat), and the host
74
+ * strand's `StrandPartyKey` to issue the joiner's membership invite; then one
75
+ * strand-database commit issues the invite.
76
76
  * Every one of those is a read of this party's own control database, which in steady state
77
77
  * touches held blocks and consults nobody (`cadre-consistency.md` → "Deadlines Over
78
78
  * Optimystic's Reads and Commits"), so `R` is charged as headroom for the ones that do