@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
@@ -43,7 +43,7 @@
43
43
  * CHECK the `'reissue'`-tagged digest `ControlDatabase.reissueRevocations`
44
44
  * signs — and `countRows` counts it.
45
45
  */
46
- export declare const CONTROL_TABLES: readonly ["OwnerKey", "ValidationKey", "Strand", "StrandPartyKey", "JoinedStrand", "JoinRequest", "JoinSuccess", "JoinFailure", "CadrePeer", "DeviceToken", "FormationInvite", "FormationUsage", "CadreInvite", "CadreInviteUsage", "Revocation"];
46
+ export declare const CONTROL_TABLES: readonly ["OwnerKey", "ValidationKey", "Strand", "StrandPartyKey", "JoinedStrand", "JoinRequest", "JoinSuccess", "JoinFailure", "CadrePeer", "DeviceToken", "FormationInvite", "FormationUsage", "CadreInvite", "CadreInviteUsage", "OwnerGrant", "OwnerGrantUsage", "Revocation"];
47
47
  /**
48
48
  * Name of a CadreControl table. Doubles as the table half of a
49
49
  * {@link ControlDomain} tag.
@@ -57,17 +57,17 @@ export type ControlTable = typeof CONTROL_TABLES[number];
57
57
  * {@link ControlTable} rather than a fresh literal list, so a renamed table is a
58
58
  * compile error here instead of a silently dead branch.
59
59
  *
60
- * `CadreInvite` is the one member whose rows are never deleted: its tombstone is a
61
- * withdrawal over a row that stays (`NotRevoked` refuses re-seating the withdrawn
62
- * row; there is no `RevocationRecorded`, since nothing is ever removed), so it is
63
- * revocable without being removable — `control-database.ts`'s `RemovableTable`
64
- * excludes it.
60
+ * `CadreInvite` and `OwnerGrant` are the two members whose rows are never deleted: a
61
+ * tombstone there is a withdrawal over a row that stays (`NotRevoked` refuses re-seating
62
+ * the withdrawn row; there is no `RevocationRecorded`, since nothing is ever removed), so
63
+ * they are revocable without being removable — `control-database.ts`'s `RemovableTable`
64
+ * excludes them.
65
65
  *
66
66
  * Lives in this import-free module rather than beside its main consumer
67
67
  * (`control-database.ts`) so the lightweight signers — `peer-authorization.ts`'s
68
68
  * `revocationDigest` — can type against it without pulling in the runtime.
69
69
  */
70
- export type RevocableTable = Extract<ControlTable, 'OwnerKey' | 'CadrePeer' | 'ValidationKey' | 'Strand' | 'StrandPartyKey' | 'JoinedStrand' | 'JoinRequest' | 'DeviceToken' | 'CadreInvite'>;
70
+ export type RevocableTable = Extract<ControlTable, 'OwnerKey' | 'CadrePeer' | 'ValidationKey' | 'Strand' | 'StrandPartyKey' | 'JoinedStrand' | 'JoinRequest' | 'DeviceToken' | 'CadreInvite' | 'OwnerGrant'>;
71
71
  /**
72
72
  * What a signature authorizes, scoped to one table rule — or, for
73
73
  * `'Cadre.Enrollment'`, the offline peer vouch that `cadre enroll register`
@@ -99,10 +99,11 @@ export type BoundControlDomain = Exclude<ControlDomain, UnboundControlDomain>;
99
99
  * owner key.
100
100
  * - `'consent'` — a peer self-signs its OWN `FormationUsage` or
101
101
  * `CadreInviteUsage` redemption (the joiner proving it agreed to join), with
102
- * its own key. Distinct from the approver's `'vouch'` over the same table so
103
- * the two stored signatures are never interchangeable.
104
- * - `'redeem'` — the holder of a `CadreInvite`'s private key proves possession
105
- * on a `CadreInviteUsage` row. Distinct from the device's `'consent'` over the
102
+ * its own key, or the key an `OwnerGrantUsage` seats signs that it is held.
103
+ * Distinct from the approver's `'vouch'` over the same table so the two
104
+ * stored signatures are never interchangeable.
105
+ * - `'redeem'` — the holder of a `CadreInvite`'s or `OwnerGrant`'s private key
106
+ * proves possession on the usage row. Distinct from the `'consent'` over the
106
107
  * same fields: the two signatures are by different keys and neither may stand
107
108
  * in for the other.
108
109
  * - `'reissue'` — an owner re-writes an existing `Revocation` tombstone,
@@ -121,7 +122,6 @@ export type ControlAction = 'add' | 'remove' | 'vouch' | 'publish' | 'consent' |
121
122
  export interface CadreInviteSignedFields {
122
123
  key: string;
123
124
  peerId: string | null;
124
- grantsOwner: boolean;
125
125
  expiresAt: string | null;
126
126
  totalUses: number | null;
127
127
  stampId: string;
@@ -131,10 +131,32 @@ export interface CadreInviteSignedFields {
131
131
  * (`control-database.ts`'s `cadreInviteAddMessage`, raw bytes) and the verifier
132
132
  * (`peer-authorization.ts`'s `cadreInviteAddDigest`, base64url) so the two cannot
133
133
  * drift. SQL mirror, in `CadreInvite.AuthorizedInsert`:
134
- * `new.Key, coalesce(new.PeerId, ''), cast(new.GrantsOwner as text),
135
- * coalesce(cast(new.ExpiresAt as text), ''), coalesce(cast(new.TotalUses as text), ''), new.StampId`.
134
+ * `new.Key, coalesce(new.PeerId, ''), coalesce(cast(new.ExpiresAt as text), ''),
135
+ * coalesce(cast(new.TotalUses as text), ''), new.StampId`.
136
136
  */
137
137
  export declare function cadreInviteRowFields(row: CadreInviteSignedFields): string[];
138
+ /**
139
+ * The `OwnerGrant` columns its `'add'` signature binds, in the schema's order and with
140
+ * the row's stored forms (`expiresAt` canonical, as {@link CadreInviteSignedFields}).
141
+ * `label` is the label the seated `OwnerKey` row must carry: the issuer's choice, bound
142
+ * here so the holder cannot seat the key under another name. Structurally satisfied by
143
+ * an `OwnerGrantRow` (`types.ts`), which adds the issuer pair the signature does not cover.
144
+ */
145
+ export interface OwnerGrantSignedFields {
146
+ key: string;
147
+ label: string | null;
148
+ expiresAt: string | null;
149
+ totalUses: number | null;
150
+ stampId: string;
151
+ }
152
+ /**
153
+ * The row-field half of an `OwnerGrant` `'add'` digest, shared by the signer
154
+ * (`control-database.ts`'s `ownerGrantAddMessage`) and the verifier
155
+ * (`peer-authorization.ts`'s `ownerGrantAddDigest`), as {@link cadreInviteRowFields} is.
156
+ * SQL mirror, in `OwnerGrant.AuthorizedInsert`: `new.Key, coalesce(new.Label, ''),
157
+ * coalesce(cast(new.ExpiresAt as text), ''), coalesce(cast(new.TotalUses as text), ''), new.StampId`.
158
+ */
159
+ export declare function ownerGrantRowFields(row: OwnerGrantSignedFields): string[];
138
160
  /**
139
161
  * The full ordered field vector a party-bound control-plane signature covers. Digest
140
162
  * this with the crypto plugin's injective multi-field encoding (every field TEXT);
@@ -58,6 +58,8 @@ export const CONTROL_TABLES = [
58
58
  'FormationUsage',
59
59
  'CadreInvite',
60
60
  'CadreInviteUsage',
61
+ 'OwnerGrant',
62
+ 'OwnerGrantUsage',
61
63
  'Revocation',
62
64
  ];
63
65
  /**
@@ -65,14 +67,29 @@ export const CONTROL_TABLES = [
65
67
  * (`control-database.ts`'s `cadreInviteAddMessage`, raw bytes) and the verifier
66
68
  * (`peer-authorization.ts`'s `cadreInviteAddDigest`, base64url) so the two cannot
67
69
  * drift. SQL mirror, in `CadreInvite.AuthorizedInsert`:
68
- * `new.Key, coalesce(new.PeerId, ''), cast(new.GrantsOwner as text),
69
- * coalesce(cast(new.ExpiresAt as text), ''), coalesce(cast(new.TotalUses as text), ''), new.StampId`.
70
+ * `new.Key, coalesce(new.PeerId, ''), coalesce(cast(new.ExpiresAt as text), ''),
71
+ * coalesce(cast(new.TotalUses as text), ''), new.StampId`.
70
72
  */
71
73
  export function cadreInviteRowFields(row) {
72
74
  return [
73
75
  row.key,
74
76
  row.peerId ?? '',
75
- row.grantsOwner ? '1' : '0',
77
+ row.expiresAt ?? '',
78
+ row.totalUses === null ? '' : String(row.totalUses),
79
+ row.stampId,
80
+ ];
81
+ }
82
+ /**
83
+ * The row-field half of an `OwnerGrant` `'add'` digest, shared by the signer
84
+ * (`control-database.ts`'s `ownerGrantAddMessage`) and the verifier
85
+ * (`peer-authorization.ts`'s `ownerGrantAddDigest`), as {@link cadreInviteRowFields} is.
86
+ * SQL mirror, in `OwnerGrant.AuthorizedInsert`: `new.Key, coalesce(new.Label, ''),
87
+ * coalesce(cast(new.ExpiresAt as text), ''), coalesce(cast(new.TotalUses as text), ''), new.StampId`.
88
+ */
89
+ export function ownerGrantRowFields(row) {
90
+ return [
91
+ row.key,
92
+ row.label ?? '',
76
93
  row.expiresAt ?? '',
77
94
  row.totalUses === null ? '' : String(row.totalUses),
78
95
  row.stampId,
@@ -1 +1 @@
1
- {"version":3,"file":"control-authorization.js","sourceRoot":"","sources":["../src/control-authorization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,UAAU;IACV,eAAe;IACf,QAAQ;IACR,gBAAgB;IAChB,cAAc;IACd,aAAa;IACb,aAAa;IACb,aAAa;IACb,WAAW;IACX,aAAa;IACb,iBAAiB;IACjB,gBAAgB;IAChB,aAAa;IACb,kBAAkB;IAClB,YAAY;CACJ,CAAC;AA2FX;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAA4B;IAC/D,OAAO;QACL,GAAG,CAAC,GAAG;QACP,GAAG,CAAC,MAAM,IAAI,EAAE;QAChB,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;QAC3B,GAAG,CAAC,SAAS,IAAI,EAAE;QACnB,GAAG,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;QACnD,GAAG,CAAC,OAAO;KACZ,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,0BAA0B,CACxC,MAA0B,EAC1B,MAAqB,EACrB,OAAe,EACf,SAAmB;IAEnB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,SAAS,CAAC,CAAC;AACjD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CACxC,MAA4B,EAC5B,MAAqB,EACrB,SAAmB;IAEnB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;AACxC,CAAC"}
1
+ {"version":3,"file":"control-authorization.js","sourceRoot":"","sources":["../src/control-authorization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,UAAU;IACV,eAAe;IACf,QAAQ;IACR,gBAAgB;IAChB,cAAc;IACd,aAAa;IACb,aAAa;IACb,aAAa;IACb,WAAW;IACX,aAAa;IACb,iBAAiB;IACjB,gBAAgB;IAChB,aAAa;IACb,kBAAkB;IAClB,YAAY;IACZ,iBAAiB;IACjB,YAAY;CACJ,CAAC;AA2FX;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAA4B;IAC/D,OAAO;QACL,GAAG,CAAC,GAAG;QACP,GAAG,CAAC,MAAM,IAAI,EAAE;QAChB,GAAG,CAAC,SAAS,IAAI,EAAE;QACnB,GAAG,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;QACnD,GAAG,CAAC,OAAO;KACZ,CAAC;AACJ,CAAC;AAiBD;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAA2B;IAC7D,OAAO;QACL,GAAG,CAAC,GAAG;QACP,GAAG,CAAC,KAAK,IAAI,EAAE;QACf,GAAG,CAAC,SAAS,IAAI,EAAE;QACnB,GAAG,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;QACnD,GAAG,CAAC,OAAO;KACZ,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,0BAA0B,CACxC,MAA0B,EAC1B,MAAqB,EACrB,OAAe,EACf,SAAmB;IAEnB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,SAAS,CAAC,CAAC;AACjD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CACxC,MAA4B,EAC5B,MAAqB,EACrB,SAAmB;IAEnB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;AACxC,CAAC"}
@@ -5,16 +5,16 @@
5
5
  * connected (and thus replicating), without devolving into an N² mesh.
6
6
  *
7
7
  * FRET only needs a *connected* graph with the block clusters reachable, not a
8
- * full mesh. Storage/owner nodes are the stable, publicly-dialable backbone
9
- * that hold the control blocks, so the policy is **backbone-preferential with a
10
- * bounded out-degree**:
8
+ * full mesh. Always-on (`storage` profile) machines are the stable, dialable
9
+ * backbone that hold the control blocks, so the policy is **backbone-preferential
10
+ * with a bounded out-degree**:
11
11
  *
12
- * - **Always dial every backbone (owner) member** — the owner set is
13
- * small by design, and routing cohort formation through publicly-dialable
14
- * owner nodes keeps the cohort ≥2 where owner-signed writes originate.
15
- * - **Fill the remainder** up to `targetDegree` with non-owner members,
16
- * ordered deterministically (peerId sort) so every node makes the same stable
17
- * choice across passes and load spreads predictably.
12
+ * - **Always dial every backbone (always-on) member** — a party runs few of them,
13
+ * and they are the machines that stay reachable while phones sleep or roam.
14
+ * - **Fill the remainder** up to `targetDegree` with the other members (phones,
15
+ * and machines that have not yet published a profile), ordered
16
+ * deterministically (peerId sort) so every node makes the same stable choice
17
+ * across passes and load spreads predictably.
18
18
  *
19
19
  * For a small party (members ≤ degree) this degenerates to a full mesh, which is
20
20
  * correct, cheap, and matches what the convergence tests do by hand; for a large
@@ -22,34 +22,61 @@
22
22
  */
23
23
  import type { CohortPeerRow } from './strand-cohort.js';
24
24
  /**
25
- * Default recurring cadence for {@link CadreNode.reconcileControlCohort} (ms).
26
- * In the same spirit as the 5s strand-watch poll but lighter, since a control
27
- * cohort changes far less often than strand activity.
25
+ * Default backstop for {@link CadreNode.reconcileControlCohort} (ms): a pass runs this long
26
+ * after the last one settled whether or not anything woke it. Passes normally run on a
27
+ * change — a membership table changing, a sibling connecting or disconnecting, an address
28
+ * change — and on the retries a pass arms for itself ({@link nextReconcileWakeMs}); the
29
+ * backstop bounds how stale this node can be after a change it hears nothing of: one that
30
+ * reaches it with no storage event, which happens to a machine outside the block's cohort
31
+ * (a cadre larger than `CONTROL_REPLICATION_BREADTH`) and to one in the cohort that the
32
+ * writer could not reach when it committed. The membership rows view's maximum age
33
+ * (`MEMBERSHIP_VIEW_MAX_AGE_MS`, 15 s) is shorter than this, so the backstop pass reads
34
+ * storage and starts the cohort consult that brings such a change in.
28
35
  *
29
- * An interval between pass starts, not a deadline on a pass. Passes are
30
- * single-flight, so a pass that runs longer than this is joined rather than
31
- * stacked, and the effective cadence becomes the pass length: one unreachable
32
- * sibling's dial alone can take the whole per-peer dial budget (86 s at the
33
- * default declared link).
36
+ * `network.controlCohort.reconcileMs` overrides it. An interval between a pass settling and
37
+ * the next start, not a deadline on a pass: passes are single-flight, and one unreachable
38
+ * sibling's dial alone can take the whole per-peer dial budget (86 s at the default
39
+ * declared link).
40
+ *
41
+ * NOTE: a machine whose storage takes no event for a change — one outside the block's cohort
42
+ * in a cadre larger than `CONTROL_REPLICATION_BREADTH` (16 machines), or one the writer could
43
+ * not reach when it committed — sees it only at this backstop, where the change-driven path
44
+ * serves a machine the commit reached within the 50 ms coalescing window. If a party ever
45
+ * needs a tighter bound, lower the backstop for its nodes or give the plugin's change bridge
46
+ * a path for outside-the-cohort reads.
47
+ */
48
+ export declare const DEFAULT_CONTROL_COHORT_RECONCILE_MS = 120000;
49
+ /**
50
+ * First redial delay after a pass's dial of a selected sibling, or a cold-start bootstrap peer,
51
+ * fails (ms). Doubles per consecutive such pass and is capped at the backstop, so a
52
+ * sibling that is offline costs one dial per backstop once the ladder saturates, and a
53
+ * stranded cold-start node keeps trying forever (15 s, 30 s, 60 s, then every backstop).
34
54
  */
35
- export declare const DEFAULT_CONTROL_COHORT_RECONCILE_MS = 15000;
55
+ export declare const CONTROL_COHORT_REDIAL_MS = 15000;
36
56
  /**
37
- * Default cap on the number of **non-owner** members a single reconcile pass
38
- * proactively dials. Backbone (owner) members are always dialed and do NOT
57
+ * How often an owner retries a node it has just claimed while that node restarts into the
58
+ * owner's party (ms; `CadreNode.claimNode`). A poll step, not a deadline: the retries run for
59
+ * {@link CONTROL_COHORT_REDIAL_MS} after the node accepts the claim, the delay of the reconcile
60
+ * pass's first redial, which would otherwise be the owner's next dial of the node.
61
+ */
62
+ export declare const CLAIM_RESTART_REDIAL_STEP_MS = 1000;
63
+ /**
64
+ * Default cap on the number of **non-backbone** members a single reconcile pass
65
+ * proactively dials. Backbone (always-on) members are always dialed and do NOT
39
66
  * count against this cap.
40
67
  */
41
68
  export declare const DEFAULT_CONTROL_COHORT_TARGET_DEGREE = 6;
42
69
  /** Outcome of {@link selectControlCohortDials}: who to dial + what the cap dropped. */
43
70
  export interface ControlCohortSelection {
44
71
  /**
45
- * Siblings to dial this pass, backbone first then the bounded non-owner
72
+ * Siblings to dial this pass, backbone first then the bounded non-backbone
46
73
  * fill. Already self-excluded (the caller must pass self-excluded `siblings`).
47
74
  * Still includes peers that may turn out to be already-connected — the caller
48
75
  * diffs against live connections separately.
49
76
  */
50
77
  dials: CohortPeerRow[];
51
- /** Non-owner members dropped by the `targetDegree` cap (0 when none). */
52
- cappedNonOwner: number;
78
+ /** Non-backbone members dropped by the `targetDegree` cap (0 when none). */
79
+ cappedNonBackbone: number;
53
80
  }
54
81
  /** What one {@link CadreNode.reconcileControlCohort} pass did. */
55
82
  export interface ControlCohortReconcileResult {
@@ -68,15 +95,44 @@ export interface ControlCohortReconcileResult {
68
95
  * Backbone-preferential, bounded-out-degree selection of control-cohort siblings
69
96
  * to dial.
70
97
  *
71
- * A member is **backbone** iff the ed25519 key derived from its peerId
72
- * (see {@link ed25519PublicKeyB64FromPeerId}) is in `ownerKeys`. All backbone
73
- * members are selected; non-owner members fill up to `targetDegree` in
74
- * deterministic peerId order.
98
+ * A member is **backbone** iff its `CadrePeer` row declares the `storage` profile
99
+ * (an always-on machine). All backbone members are selected; the rest fill up to
100
+ * `targetDegree` in deterministic peerId order. A member whose profile is still
101
+ * unknown (it has not self-published) is not backbone until it publishes one.
102
+ * A profile is a dial preference only: it is self-declared, and a machine
103
+ * misdeclaring one costs extra dials, never access.
75
104
  *
76
105
  * @param siblings - cadre members EXCLUDING self (the caller filters self out).
77
- * @param ownerKeys - the converged `OwnerKey` set; may be empty/partial
78
- * before owner convergence, in which case no member classifies as backbone
79
- * yet and the bounded fill still makes progress (preference sharpens later).
80
- * @param targetDegree - cap on non-owner dials (negative is treated as 0).
106
+ * @param targetDegree - cap on non-backbone dials (negative is treated as 0).
107
+ */
108
+ export declare function selectControlCohortDials(siblings: CohortPeerRow[], targetDegree: number): ControlCohortSelection;
109
+ /** What {@link nextReconcileWakeMs} decides from, all times epoch milliseconds. */
110
+ export interface ReconcileWakeInput {
111
+ /**
112
+ * Consecutive passes, this one included, whose dial of a selected sibling or a cold-start
113
+ * bootstrap peer failed; 0 when none of this pass's dials did.
114
+ */
115
+ redialStreak: number;
116
+ /** When the pass that just ended began. */
117
+ passStartedAt: number;
118
+ /** Every (sibling, strand) strand-address due time this node holds. */
119
+ strandAddrDueAt: Iterable<number>;
120
+ now: number;
121
+ /** The scheduler's backstop (ms); no wake is later than it. */
122
+ backstopMs: number;
123
+ }
124
+ /**
125
+ * How long after a pass the next one should run for the pass's own reasons, or `undefined`
126
+ * when the backstop covers everything:
127
+ *
128
+ * - **Redial.** A streak of unconnected passes wakes after
129
+ * `min(CONTROL_COHORT_REDIAL_MS × 2^(streak − 1), backstop)`.
130
+ * - **Strand addresses.** The earliest strand-address due time LATER THAN the pass's start,
131
+ * when it is sooner than the backstop, wakes when it falls due (now at the latest). A due
132
+ * time at or before the start belongs to a (sibling, strand) the pass skipped for another
133
+ * reason — the strand stopped, the sibling disconnected — and waking for it would spin; the
134
+ * strand watcher's launch retry keeps the same rule.
135
+ *
136
+ * The smaller wins.
81
137
  */
82
- export declare function selectControlCohortDials(siblings: CohortPeerRow[], ownerKeys: ReadonlySet<string>, targetDegree: number): ControlCohortSelection;
138
+ export declare function nextReconcileWakeMs(input: ReconcileWakeInput): number | undefined;
@@ -5,37 +5,63 @@
5
5
  * connected (and thus replicating), without devolving into an N² mesh.
6
6
  *
7
7
  * FRET only needs a *connected* graph with the block clusters reachable, not a
8
- * full mesh. Storage/owner nodes are the stable, publicly-dialable backbone
9
- * that hold the control blocks, so the policy is **backbone-preferential with a
10
- * bounded out-degree**:
8
+ * full mesh. Always-on (`storage` profile) machines are the stable, dialable
9
+ * backbone that hold the control blocks, so the policy is **backbone-preferential
10
+ * with a bounded out-degree**:
11
11
  *
12
- * - **Always dial every backbone (owner) member** — the owner set is
13
- * small by design, and routing cohort formation through publicly-dialable
14
- * owner nodes keeps the cohort ≥2 where owner-signed writes originate.
15
- * - **Fill the remainder** up to `targetDegree` with non-owner members,
16
- * ordered deterministically (peerId sort) so every node makes the same stable
17
- * choice across passes and load spreads predictably.
12
+ * - **Always dial every backbone (always-on) member** — a party runs few of them,
13
+ * and they are the machines that stay reachable while phones sleep or roam.
14
+ * - **Fill the remainder** up to `targetDegree` with the other members (phones,
15
+ * and machines that have not yet published a profile), ordered
16
+ * deterministically (peerId sort) so every node makes the same stable choice
17
+ * across passes and load spreads predictably.
18
18
  *
19
19
  * For a small party (members ≤ degree) this degenerates to a full mesh, which is
20
20
  * correct, cheap, and matches what the convergence tests do by hand; for a large
21
21
  * party it bounds out-degree to `backbone + targetDegree`.
22
22
  */
23
- import { ed25519PublicKeyB64FromPeerId } from './ed25519-key.js';
24
23
  /**
25
- * Default recurring cadence for {@link CadreNode.reconcileControlCohort} (ms).
26
- * In the same spirit as the 5s strand-watch poll but lighter, since a control
27
- * cohort changes far less often than strand activity.
24
+ * Default backstop for {@link CadreNode.reconcileControlCohort} (ms): a pass runs this long
25
+ * after the last one settled whether or not anything woke it. Passes normally run on a
26
+ * change — a membership table changing, a sibling connecting or disconnecting, an address
27
+ * change — and on the retries a pass arms for itself ({@link nextReconcileWakeMs}); the
28
+ * backstop bounds how stale this node can be after a change it hears nothing of: one that
29
+ * reaches it with no storage event, which happens to a machine outside the block's cohort
30
+ * (a cadre larger than `CONTROL_REPLICATION_BREADTH`) and to one in the cohort that the
31
+ * writer could not reach when it committed. The membership rows view's maximum age
32
+ * (`MEMBERSHIP_VIEW_MAX_AGE_MS`, 15 s) is shorter than this, so the backstop pass reads
33
+ * storage and starts the cohort consult that brings such a change in.
28
34
  *
29
- * An interval between pass starts, not a deadline on a pass. Passes are
30
- * single-flight, so a pass that runs longer than this is joined rather than
31
- * stacked, and the effective cadence becomes the pass length: one unreachable
32
- * sibling's dial alone can take the whole per-peer dial budget (86 s at the
33
- * default declared link).
35
+ * `network.controlCohort.reconcileMs` overrides it. An interval between a pass settling and
36
+ * the next start, not a deadline on a pass: passes are single-flight, and one unreachable
37
+ * sibling's dial alone can take the whole per-peer dial budget (86 s at the default
38
+ * declared link).
39
+ *
40
+ * NOTE: a machine whose storage takes no event for a change — one outside the block's cohort
41
+ * in a cadre larger than `CONTROL_REPLICATION_BREADTH` (16 machines), or one the writer could
42
+ * not reach when it committed — sees it only at this backstop, where the change-driven path
43
+ * serves a machine the commit reached within the 50 ms coalescing window. If a party ever
44
+ * needs a tighter bound, lower the backstop for its nodes or give the plugin's change bridge
45
+ * a path for outside-the-cohort reads.
46
+ */
47
+ export const DEFAULT_CONTROL_COHORT_RECONCILE_MS = 120000;
48
+ /**
49
+ * First redial delay after a pass's dial of a selected sibling, or a cold-start bootstrap peer,
50
+ * fails (ms). Doubles per consecutive such pass and is capped at the backstop, so a
51
+ * sibling that is offline costs one dial per backstop once the ladder saturates, and a
52
+ * stranded cold-start node keeps trying forever (15 s, 30 s, 60 s, then every backstop).
34
53
  */
35
- export const DEFAULT_CONTROL_COHORT_RECONCILE_MS = 15000;
54
+ export const CONTROL_COHORT_REDIAL_MS = 15000;
36
55
  /**
37
- * Default cap on the number of **non-owner** members a single reconcile pass
38
- * proactively dials. Backbone (owner) members are always dialed and do NOT
56
+ * How often an owner retries a node it has just claimed while that node restarts into the
57
+ * owner's party (ms; `CadreNode.claimNode`). A poll step, not a deadline: the retries run for
58
+ * {@link CONTROL_COHORT_REDIAL_MS} after the node accepts the claim, the delay of the reconcile
59
+ * pass's first redial, which would otherwise be the owner's next dial of the node.
60
+ */
61
+ export const CLAIM_RESTART_REDIAL_STEP_MS = 1000;
62
+ /**
63
+ * Default cap on the number of **non-backbone** members a single reconcile pass
64
+ * proactively dials. Backbone (always-on) members are always dialed and do NOT
39
65
  * count against this cap.
40
66
  */
41
67
  export const DEFAULT_CONTROL_COHORT_TARGET_DEGREE = 6;
@@ -51,23 +77,24 @@ function byPeerId(a, b) {
51
77
  * Backbone-preferential, bounded-out-degree selection of control-cohort siblings
52
78
  * to dial.
53
79
  *
54
- * A member is **backbone** iff the ed25519 key derived from its peerId
55
- * (see {@link ed25519PublicKeyB64FromPeerId}) is in `ownerKeys`. All backbone
56
- * members are selected; non-owner members fill up to `targetDegree` in
57
- * deterministic peerId order.
80
+ * A member is **backbone** iff its `CadrePeer` row declares the `storage` profile
81
+ * (an always-on machine). All backbone members are selected; the rest fill up to
82
+ * `targetDegree` in deterministic peerId order. A member whose profile is still
83
+ * unknown (it has not self-published) is not backbone until it publishes one.
84
+ * A profile is a dial preference only: it is self-declared, and a machine
85
+ * misdeclaring one costs extra dials, never access.
58
86
  *
59
87
  * @param siblings - cadre members EXCLUDING self (the caller filters self out).
60
- * @param ownerKeys - the converged `OwnerKey` set; may be empty/partial
61
- * before owner convergence, in which case no member classifies as backbone
62
- * yet and the bounded fill still makes progress (preference sharpens later).
63
- * @param targetDegree - cap on non-owner dials (negative is treated as 0).
88
+ * @param targetDegree - cap on non-backbone dials (negative is treated as 0).
64
89
  */
65
- export function selectControlCohortDials(siblings, ownerKeys, targetDegree) {
90
+ export function selectControlCohortDials(siblings, targetDegree) {
66
91
  const backbone = [];
67
92
  const rest = [];
93
+ // NOTE: the backbone is uncapped, so out-degree grows with the number of always-on
94
+ // machines (and with any sibling that declares `storage` without being one); if a party
95
+ // ever runs more than a handful, cap the backbone too.
68
96
  for (const peer of siblings) {
69
- const pubKey = ed25519PublicKeyB64FromPeerId(peer.peerId);
70
- if (pubKey !== null && ownerKeys.has(pubKey)) {
97
+ if (peer.profile === 'storage') {
71
98
  backbone.push(peer);
72
99
  }
73
100
  else {
@@ -80,7 +107,38 @@ export function selectControlCohortDials(siblings, ownerKeys, targetDegree) {
80
107
  const fill = rest.slice(0, cap);
81
108
  return {
82
109
  dials: [...backbone, ...fill],
83
- cappedNonOwner: rest.length - fill.length,
110
+ cappedNonBackbone: rest.length - fill.length,
84
111
  };
85
112
  }
113
+ /**
114
+ * How long after a pass the next one should run for the pass's own reasons, or `undefined`
115
+ * when the backstop covers everything:
116
+ *
117
+ * - **Redial.** A streak of unconnected passes wakes after
118
+ * `min(CONTROL_COHORT_REDIAL_MS × 2^(streak − 1), backstop)`.
119
+ * - **Strand addresses.** The earliest strand-address due time LATER THAN the pass's start,
120
+ * when it is sooner than the backstop, wakes when it falls due (now at the latest). A due
121
+ * time at or before the start belongs to a (sibling, strand) the pass skipped for another
122
+ * reason — the strand stopped, the sibling disconnected — and waking for it would spin; the
123
+ * strand watcher's launch retry keeps the same rule.
124
+ *
125
+ * The smaller wins.
126
+ */
127
+ export function nextReconcileWakeMs(input) {
128
+ const { redialStreak, passStartedAt, strandAddrDueAt, now, backstopMs } = input;
129
+ let wake;
130
+ if (redialStreak > 0) {
131
+ wake = Math.min(CONTROL_COHORT_REDIAL_MS * 2 ** (redialStreak - 1), backstopMs);
132
+ }
133
+ for (const dueAt of strandAddrDueAt) {
134
+ if (dueAt <= passStartedAt) {
135
+ continue;
136
+ }
137
+ const delay = Math.max(0, dueAt - now);
138
+ if (delay < backstopMs && (wake === undefined || delay < wake)) {
139
+ wake = delay;
140
+ }
141
+ }
142
+ return wake;
143
+ }
86
144
  //# sourceMappingURL=control-cohort.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"control-cohort.js","sourceRoot":"","sources":["../src/control-cohort.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAC;AAGjE;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,mCAAmC,GAAG,KAAM,CAAC;AAE1D;;;;GAIG;AACH,MAAM,CAAC,MAAM,oCAAoC,GAAG,CAAC,CAAC;AAkCtD,mFAAmF;AACnF,SAAS,QAAQ,CAAC,CAAgB,EAAE,CAAgB;IAClD,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC,CAAC;IACnC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC;IAClC,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,wBAAwB,CACtC,QAAyB,EACzB,SAA8B,EAC9B,YAAoB;IAEpB,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,MAAM,IAAI,GAAoB,EAAE,CAAC;IAEjC,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,6BAA6B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1D,IAAI,MAAM,KAAK,IAAI,IAAI,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7C,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAEpB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAEhC,OAAO;QACL,KAAK,EAAE,CAAC,GAAG,QAAQ,EAAE,GAAG,IAAI,CAAC;QAC7B,cAAc,EAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM;KAC1C,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"control-cohort.js","sourceRoot":"","sources":["../src/control-cohort.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,mCAAmC,GAAG,MAAO,CAAC;AAE3D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,KAAM,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,IAAK,CAAC;AAElD;;;;GAIG;AACH,MAAM,CAAC,MAAM,oCAAoC,GAAG,CAAC,CAAC;AAkCtD,mFAAmF;AACnF,SAAS,QAAQ,CAAC,CAAgB,EAAE,CAAgB;IAClD,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC,CAAC;IACnC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC;IAClC,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,wBAAwB,CACtC,QAAyB,EACzB,YAAoB;IAEpB,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,MAAM,IAAI,GAAoB,EAAE,CAAC;IAEjC,mFAAmF;IACnF,wFAAwF;IACxF,uDAAuD;IACvD,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAC/B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAEpB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAEhC,OAAO;QACL,KAAK,EAAE,CAAC,GAAG,QAAQ,EAAE,GAAG,IAAI,CAAC;QAC7B,iBAAiB,EAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM;KAC7C,CAAC;AACJ,CAAC;AAkBD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAyB;IAC3D,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC;IAChF,IAAI,IAAwB,CAAC;IAC7B,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;QACrB,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,wBAAwB,GAAG,CAAC,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IAClF,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,eAAe,EAAE,CAAC;QACpC,IAAI,KAAK,IAAI,aAAa,EAAE,CAAC;YAC3B,SAAS;QACX,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,GAAG,CAAC,CAAC;QACvC,IAAI,KAAK,GAAG,UAAU,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC;YAC/D,IAAI,GAAG,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}