@serfab/cadre-core 1.17.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 (144) hide show
  1. package/README.md +3 -2
  2. package/dist/cadre-invite-protocol.d.ts +35 -13
  3. package/dist/cadre-invite-protocol.js +90 -25
  4. package/dist/cadre-invite-protocol.js.map +1 -1
  5. package/dist/cadre-node.d.ts +586 -255
  6. package/dist/cadre-node.js +1176 -564
  7. package/dist/cadre-node.js.map +1 -1
  8. package/dist/control-cohort.d.ts +66 -9
  9. package/dist/control-cohort.js +67 -9
  10. package/dist/control-cohort.js.map +1 -1
  11. package/dist/control-database.d.ts +451 -193
  12. package/dist/control-database.js +701 -359
  13. package/dist/control-database.js.map +1 -1
  14. package/dist/control-formation-recorder.d.ts +46 -43
  15. package/dist/control-formation-recorder.js +77 -78
  16. package/dist/control-formation-recorder.js.map +1 -1
  17. package/dist/control-protocol-guard.d.ts +6 -0
  18. package/dist/control-protocol-guard.js +14 -2
  19. package/dist/control-protocol-guard.js.map +1 -1
  20. package/dist/control-schema-version.d.ts +48 -31
  21. package/dist/control-schema-version.js +67 -45
  22. package/dist/control-schema-version.js.map +1 -1
  23. package/dist/control-schema.d.ts +8 -8
  24. package/dist/control-schema.js +16 -16
  25. package/dist/control-schema.js.map +1 -1
  26. package/dist/control-write-retry.d.ts +42 -7
  27. package/dist/control-write-retry.js +87 -19
  28. package/dist/control-write-retry.js.map +1 -1
  29. package/dist/enrolled-machine-store.d.ts +3 -3
  30. package/dist/enrolled-machine-store.js +3 -3
  31. package/dist/formation-token.d.ts +48 -0
  32. package/dist/formation-token.js +26 -0
  33. package/dist/formation-token.js.map +1 -0
  34. package/dist/index.d.ts +14 -13
  35. package/dist/index.js +16 -13
  36. package/dist/index.js.map +1 -1
  37. package/dist/joined-strand-store.d.ts +26 -9
  38. package/dist/joined-strand-store.js +63 -24
  39. package/dist/joined-strand-store.js.map +1 -1
  40. package/dist/link-budget.d.ts +9 -7
  41. package/dist/link-budget.js +9 -7
  42. package/dist/link-budget.js.map +1 -1
  43. package/dist/member-redemption.d.ts +15 -9
  44. package/dist/member-redemption.js +13 -8
  45. package/dist/member-redemption.js.map +1 -1
  46. package/dist/membership-view.d.ts +91 -0
  47. package/dist/membership-view.js +110 -0
  48. package/dist/membership-view.js.map +1 -0
  49. package/dist/owner-grant-protocol.d.ts +11 -7
  50. package/dist/owner-grant-protocol.js +31 -17
  51. package/dist/owner-grant-protocol.js.map +1 -1
  52. package/dist/peer-addr-book.d.ts +10 -1
  53. package/dist/peer-addr-book.js +4 -3
  54. package/dist/peer-addr-book.js.map +1 -1
  55. package/dist/peer-join-backfill.d.ts +89 -20
  56. package/dist/peer-join-backfill.js +265 -108
  57. package/dist/peer-join-backfill.js.map +1 -1
  58. package/dist/pending-join-runner.d.ts +48 -18
  59. package/dist/pending-join-runner.js +74 -56
  60. package/dist/pending-join-runner.js.map +1 -1
  61. package/dist/seed-bootstrap.d.ts +47 -40
  62. package/dist/seed-bootstrap.js +66 -61
  63. package/dist/seed-bootstrap.js.map +1 -1
  64. package/dist/strand-database.d.ts +23 -2
  65. package/dist/strand-database.js +26 -6
  66. package/dist/strand-database.js.map +1 -1
  67. package/dist/strand-first-sync-gate.d.ts +176 -89
  68. package/dist/strand-first-sync-gate.js +288 -122
  69. package/dist/strand-first-sync-gate.js.map +1 -1
  70. package/dist/strand-formation-deadlines.d.ts +9 -9
  71. package/dist/strand-formation-deadlines.js +9 -9
  72. package/dist/strand-formation-manager.d.ts +23 -17
  73. package/dist/strand-formation-manager.js +46 -44
  74. package/dist/strand-formation-manager.js.map +1 -1
  75. package/dist/strand-formation-protocol.d.ts +5 -8
  76. package/dist/strand-formation-protocol.js +101 -16
  77. package/dist/strand-formation-protocol.js.map +1 -1
  78. package/dist/strand-instance-manager.d.ts +26 -17
  79. package/dist/strand-instance-manager.js +119 -50
  80. package/dist/strand-instance-manager.js.map +1 -1
  81. package/dist/strand-membership-reconciler.d.ts +16 -13
  82. package/dist/strand-membership-reconciler.js +30 -22
  83. package/dist/strand-membership-reconciler.js.map +1 -1
  84. package/dist/strand-membership-writer.d.ts +40 -28
  85. package/dist/strand-membership-writer.js +212 -153
  86. package/dist/strand-membership-writer.js.map +1 -1
  87. package/dist/strand-move.js +5 -2
  88. package/dist/strand-move.js.map +1 -1
  89. package/dist/strand-revocation-enforcer.d.ts +134 -60
  90. package/dist/strand-revocation-enforcer.js +144 -71
  91. package/dist/strand-revocation-enforcer.js.map +1 -1
  92. package/dist/strand-solicitation.d.ts +50 -39
  93. package/dist/strand-solicitation.js +10 -3
  94. package/dist/strand-solicitation.js.map +1 -1
  95. package/dist/strand-watcher.d.ts +80 -13
  96. package/dist/strand-watcher.js +152 -48
  97. package/dist/strand-watcher.js.map +1 -1
  98. package/dist/table-change-watch.d.ts +63 -0
  99. package/dist/table-change-watch.js +209 -0
  100. package/dist/table-change-watch.js.map +1 -0
  101. package/dist/timeout-scheduler.d.ts +4 -3
  102. package/dist/timeout-scheduler.js +4 -3
  103. package/dist/timeout-scheduler.js.map +1 -1
  104. package/dist/types.d.ts +90 -49
  105. package/dist/types.js +4 -4
  106. package/dist/types.js.map +1 -1
  107. package/package.json +8 -8
  108. package/src/cadre-invite-protocol.ts +119 -31
  109. package/src/cadre-node.ts +1282 -584
  110. package/src/control-cohort.ts +86 -9
  111. package/src/control-database.ts +848 -403
  112. package/src/control-formation-recorder.ts +359 -353
  113. package/src/control-protocol-guard.ts +16 -2
  114. package/src/control-schema-version.ts +101 -51
  115. package/src/control-schema.ts +16 -16
  116. package/src/control-write-retry.ts +91 -19
  117. package/src/delegate-admission.ts +351 -351
  118. package/src/enrolled-machine-store.ts +3 -3
  119. package/src/formation-token.ts +63 -0
  120. package/src/index.ts +30 -17
  121. package/src/joined-strand-store.ts +69 -24
  122. package/src/link-budget.ts +9 -7
  123. package/src/member-redemption.ts +20 -13
  124. package/src/membership-view.ts +144 -0
  125. package/src/owner-grant-protocol.ts +33 -18
  126. package/src/peer-addr-book.ts +4 -3
  127. package/src/peer-join-backfill.ts +971 -761
  128. package/src/pending-join-runner.ts +76 -53
  129. package/src/seed-bootstrap.ts +72 -67
  130. package/src/strand-database.ts +352 -322
  131. package/src/strand-first-sync-gate.ts +334 -134
  132. package/src/strand-formation-deadlines.ts +227 -227
  133. package/src/strand-formation-manager.ts +731 -725
  134. package/src/strand-formation-protocol.ts +122 -24
  135. package/src/strand-instance-manager.ts +2092 -2026
  136. package/src/strand-membership-reconciler.ts +33 -25
  137. package/src/strand-membership-writer.ts +2093 -2019
  138. package/src/strand-move.ts +5 -2
  139. package/src/strand-revocation-enforcer.ts +740 -654
  140. package/src/strand-solicitation.ts +61 -34
  141. package/src/strand-watcher.ts +175 -56
  142. package/src/table-change-watch.ts +285 -0
  143. package/src/timeout-scheduler.ts +29 -28
  144. package/src/types.ts +89 -51
@@ -1,4 +1,16 @@
1
+ import type { TableChangeWatch, TableChangeWatchOptions } from './table-change-watch.js';
1
2
  import type { StrandFilter, StrandRow } from './types.js';
3
+ /**
4
+ * Default backstop: the longest the watcher goes without re-reading when no change to
5
+ * `Strand` or `JoinedStrand` is heard (`docs/architecture.md` → "Table change subscriptions"
6
+ * says what the change subscription misses).
7
+ *
8
+ * NOTE: on a cadre larger than `CONTROL_REPLICATION_BREADTH` (16), a machine outside the
9
+ * `Strand` block's cohort hears no change event, so this backstop is its only path and a
10
+ * strand founded elsewhere reaches it up to 5 minutes later. If that matters, shorten the
11
+ * backstop for such machines rather than for everyone.
12
+ */
13
+ export declare const DEFAULT_STRAND_WATCH_BACKSTOP_MS: number;
2
14
  /**
3
15
  * Extended strand row that includes the sAppId for filtering purposes.
4
16
  * The sAppId is provided by the hosting application, not from the control network.
@@ -15,10 +27,14 @@ export interface StrandWatcherCallbacks {
15
27
  onStrandRemoved: (strandId: string) => Promise<void>;
16
28
  }
17
29
  /**
18
- * Interface for querying strands from control network
30
+ * What the watcher reads, and how it hears that what it reads changed.
19
31
  */
20
32
  export interface StrandQueryable {
21
33
  queryStrands(): Promise<StrandRow[]>;
34
+ /** Subscribe to changes of the tables {@link queryStrands} reads. Absent: runs come from `trigger` and the backstop only. */
35
+ watchChanges?(onChange: () => Promise<void>, options: TableChangeWatchOptions): TableChangeWatch;
36
+ /** True when the last {@link queryStrands} answered part of its result from a stand-in after a failed read. */
37
+ lastReadPartial?(): boolean;
22
38
  }
23
39
  /**
24
40
  * Interface for looking up sAppId for a strand (for filtering)
@@ -31,11 +47,16 @@ export interface SAppIdLookup {
31
47
  * Watches the control network's Strand table for changes and triggers
32
48
  * strand instance start/stop via callbacks.
33
49
  *
34
- * Uses polling until Optimystic supports reactive subscriptions.
50
+ * Every read is one run on a {@link TableChangeWatch}: once at start, on a change to the
51
+ * tables the queryable reads, on the watcher's own retries, and on the backstop.
35
52
  */
36
53
  export declare class StrandWatcher {
37
54
  private readonly filter;
38
- private readonly pollInterval;
55
+ /** Longest gap between runs when no change is heard. */
56
+ private readonly backstopMs;
57
+ /** First step of the launch-retry and read-retry ladders. */
58
+ private readonly retryBaseMs;
59
+ private readonly readRetryCapMs;
39
60
  private readonly callbacks;
40
61
  private readonly queryable;
41
62
  private readonly sAppIdLookup?;
@@ -48,7 +69,7 @@ export declare class StrandWatcher {
48
69
  */
49
70
  private readonly now;
50
71
  private knownStrands;
51
- /** Ids admitted under a `defer` decision; re-evaluated each poll until they resolve. */
72
+ /** Ids admitted under a `defer` decision; re-evaluated each run until they resolve. */
52
73
  private provisional;
53
74
  /** Ids whose last launch attempt threw, with the backoff gating their retry. */
54
75
  private failureStates;
@@ -59,36 +80,69 @@ export declare class StrandWatcher {
59
80
  * strand, the permanence has to be recorded explicitly.
60
81
  */
61
82
  private suppressed;
62
- private pollTimer;
63
- private initialPollTimer;
83
+ /** Consecutive runs whose read failed or was partial; drives {@link scheduleReadRetry}. */
84
+ private readFailures;
85
+ /** Built by {@link start}, disposed by {@link stop}; a restart builds a new one. */
86
+ private watch;
64
87
  private running;
65
- constructor(queryable: StrandQueryable, callbacks: StrandWatcherCallbacks, filter?: StrandFilter, pollInterval?: number, sAppIdLookup?: SAppIdLookup, now?: () => number);
88
+ /**
89
+ * @param backstopMs - `CadreNodeConfig.strandWatchInterval`: the longest the watcher goes
90
+ * without re-reading when no change is heard. Also caps the retry ladders' first step.
91
+ */
92
+ constructor(queryable: StrandQueryable, callbacks: StrandWatcherCallbacks, filter?: StrandFilter, backstopMs?: number, sAppIdLookup?: SAppIdLookup, now?: () => number);
66
93
  /**
67
94
  * Evaluate a strand against the current filter, distinguishing a not-yet-known
68
95
  * sAppId (`defer`) from a known non-match (`reject`). A `defer` admission is
69
- * provisional and re-checked on subsequent polls.
96
+ * provisional and re-checked on subsequent runs (see {@link sAppIdLearned}).
70
97
  */
71
98
  private evaluateFilter;
72
99
  /**
73
100
  * Record a failed launch attempt and schedule when the strand may be retried:
74
- * `pollInterval * 2^(failures-1)`, capped at {@link MAX_RETRY_BACKOFF_MS}.
101
+ * `retryBaseMs * 2^(failures-1)`, capped at {@link MAX_RETRY_BACKOFF_MS}.
75
102
  */
76
103
  private recordFailure;
77
104
  /**
78
- * Poll for strand changes.
105
+ * One run: read, reconcile, then arm the follow-up runs this run needs. A run the watch
106
+ * starts clears every pending wake, so each run re-arms the retries still owed. A
107
+ * {@link forcePoll} run clears none, so what it arms may sit beside an equivalent wake;
108
+ * whichever fires first runs and clears the other.
109
+ */
110
+ private runOnce;
111
+ /**
112
+ * Wake for the earliest launch retry falling due after `since`. A backoff that expired at
113
+ * or before `since` belongs to a strand the run starting then either attempted or skipped
114
+ * for a reason other than backoff (suppressed, filtered out); waking for it would spin.
115
+ * Without this a failed launch would wait for an unrelated change or the backstop.
116
+ */
117
+ private scheduleLaunchRetry;
118
+ /**
119
+ * After a failed or partial read, run again on a ladder from `retryBaseMs`, doubling per
120
+ * consecutive failed run up to `readRetryCapMs`; a complete read resets it. Not left to
121
+ * the backstop because the plugin arms a table's replicated-change bridge on the table's
122
+ * first successful initialization: a machine whose first read failed (cut off from its
123
+ * party, or its sibling unreachable for a never-written `JoinedStrand`) would otherwise
124
+ * hear nothing until then.
125
+ */
126
+ private scheduleReadRetry;
127
+ /**
128
+ * Read and reconcile. Resolves true when the read answered in full: false when it threw,
129
+ * or when the queryable answered part of it from a stand-in.
79
130
  */
80
131
  private poll;
81
132
  /**
82
133
  * Start watching for strand changes
83
134
  */
84
135
  start(): Promise<void>;
136
+ /** The watch every run goes through: the queryable's change subscription, or a timer-only one. */
137
+ private subscribe;
85
138
  /**
86
139
  * Stop watching for strand changes
87
140
  */
88
141
  stop(): Promise<void>;
89
142
  /**
90
- * Forget a strand whose launch failed outside this watcher, so a later poll
91
- * re-offers it — gated by the same backoff a watcher-driven failure gets.
143
+ * Forget a strand whose launch failed outside this watcher, so a later run
144
+ * re-offers it — gated by the same backoff a watcher-driven failure gets, and woken
145
+ * when that backoff expires.
92
146
  *
93
147
  * A failed launch normally leaves nothing running (StrandInstanceManager drops the
94
148
  * record), which is what makes re-offering it correct. One case does leave something
@@ -99,6 +153,16 @@ export declare class StrandWatcher {
99
153
  * not read the line above as "nothing is running".
100
154
  */
101
155
  forgetStrand(strandId: string): void;
156
+ /**
157
+ * Re-read soon: something the queryable merges in beside the watched tables changed
158
+ * (`CadreNode`'s machine-local joined-strand records). A no-op once stopped.
159
+ */
160
+ refresh(): void;
161
+ /**
162
+ * The host just learned `strandId`'s sAppId: if the strand was admitted provisionally
163
+ * under an `sAppId` filter, run so the admission resolves now rather than on the backstop.
164
+ */
165
+ sAppIdLearned(strandId: string): void;
102
166
  /**
103
167
  * Never offer this strand again this session. Two callers, both meaning "the retry
104
168
  * ladder cannot help here": a deliberate local stop, and a launch that can never
@@ -129,7 +193,10 @@ export declare class StrandWatcher {
129
193
  */
130
194
  getKnownStrands(): Map<string, StrandRow>;
131
195
  /**
132
- * Force an immediate poll (useful for testing)
196
+ * Run once now, outside the watch's schedule (tests, and callers that just wrote a row
197
+ * and want this node to converge before returning). May overlap a scheduled run:
198
+ * `knownStrands` is updated before each callback's await, so neither launches nor
199
+ * removes a strand twice.
133
200
  */
134
201
  forcePoll(): Promise<void>;
135
202
  }
@@ -1,22 +1,48 @@
1
1
  import debug from 'debug';
2
+ import { scheduleRuns } from './table-change-watch.js';
2
3
  const log = debug('sereus:cadre:strand-watcher');
3
4
  /**
4
5
  * Ceiling on the retry delay for a strand whose launch keeps failing. Attempts
5
6
  * are never abandoned — the failure this backoff exists for (a transient network
6
7
  * or storage fault) can last a long time — but a permanently-unlaunchable strand
7
- * must not re-attempt on every poll and storm the host app with `strand:error`.
8
+ * must not re-attempt on every run and storm the host app with `strand:error`.
8
9
  */
9
10
  const MAX_RETRY_BACKOFF_MS = 5 * 60 * 1000;
11
+ /**
12
+ * Default backstop: the longest the watcher goes without re-reading when no change to
13
+ * `Strand` or `JoinedStrand` is heard (`docs/architecture.md` → "Table change subscriptions"
14
+ * says what the change subscription misses).
15
+ *
16
+ * NOTE: on a cadre larger than `CONTROL_REPLICATION_BREADTH` (16), a machine outside the
17
+ * `Strand` block's cohort hears no change event, so this backstop is its only path and a
18
+ * strand founded elsewhere reaches it up to 5 minutes later. If that matters, shorten the
19
+ * backstop for such machines rather than for everyone.
20
+ */
21
+ export const DEFAULT_STRAND_WATCH_BACKSTOP_MS = 5 * 60 * 1000;
22
+ /**
23
+ * First step of the launch-retry and read-retry ladders, unless the backstop is shorter: a
24
+ * test or scenario that passes a short interval keeps the short base it always had.
25
+ */
26
+ const LAUNCH_RETRY_BASE_MS = 5000;
27
+ /** Ceiling on the read-retry ladder (see `StrandWatcher.scheduleReadRetry`), unless the backstop is shorter. */
28
+ const READ_RETRY_CAP_MS = 60000;
29
+ /** Delay before the first run, so `start()` never reads synchronously. */
30
+ const INITIAL_RUN_DELAY_MS = 100;
10
31
  /**
11
32
  * Watches the control network's Strand table for changes and triggers
12
33
  * strand instance start/stop via callbacks.
13
34
  *
14
- * Uses polling until Optimystic supports reactive subscriptions.
35
+ * Every read is one run on a {@link TableChangeWatch}: once at start, on a change to the
36
+ * tables the queryable reads, on the watcher's own retries, and on the backstop.
15
37
  */
16
38
  export class StrandWatcher {
17
- constructor(queryable, callbacks, filter = { mode: 'all' }, pollInterval = 5000, sAppIdLookup, now = Date.now) {
39
+ /**
40
+ * @param backstopMs - `CadreNodeConfig.strandWatchInterval`: the longest the watcher goes
41
+ * without re-reading when no change is heard. Also caps the retry ladders' first step.
42
+ */
43
+ constructor(queryable, callbacks, filter = { mode: 'all' }, backstopMs = DEFAULT_STRAND_WATCH_BACKSTOP_MS, sAppIdLookup, now = Date.now) {
18
44
  this.knownStrands = new Map();
19
- /** Ids admitted under a `defer` decision; re-evaluated each poll until they resolve. */
45
+ /** Ids admitted under a `defer` decision; re-evaluated each run until they resolve. */
20
46
  this.provisional = new Set();
21
47
  /** Ids whose last launch attempt threw, with the backoff gating their retry. */
22
48
  this.failureStates = new Map();
@@ -27,21 +53,25 @@ export class StrandWatcher {
27
53
  * strand, the permanence has to be recorded explicitly.
28
54
  */
29
55
  this.suppressed = new Set();
30
- this.pollTimer = null;
31
- this.initialPollTimer = null;
56
+ /** Consecutive runs whose read failed or was partial; drives {@link scheduleReadRetry}. */
57
+ this.readFailures = 0;
58
+ /** Built by {@link start}, disposed by {@link stop}; a restart builds a new one. */
59
+ this.watch = null;
32
60
  this.running = false;
33
61
  this.queryable = queryable;
34
62
  this.callbacks = callbacks;
35
63
  this.filter = filter;
36
- this.pollInterval = pollInterval;
64
+ this.backstopMs = backstopMs;
65
+ this.retryBaseMs = Math.min(backstopMs, LAUNCH_RETRY_BASE_MS);
66
+ this.readRetryCapMs = Math.min(backstopMs, READ_RETRY_CAP_MS);
37
67
  this.sAppIdLookup = sAppIdLookup;
38
68
  this.now = now;
39
- log('StrandWatcher created with filter: %o, interval: %dms', filter, pollInterval);
69
+ log('StrandWatcher created with filter: %o, backstop: %dms', filter, backstopMs);
40
70
  }
41
71
  /**
42
72
  * Evaluate a strand against the current filter, distinguishing a not-yet-known
43
73
  * sAppId (`defer`) from a known non-match (`reject`). A `defer` admission is
44
- * provisional and re-checked on subsequent polls.
74
+ * provisional and re-checked on subsequent runs (see {@link sAppIdLearned}).
45
75
  */
46
76
  evaluateFilter(strand) {
47
77
  switch (this.filter.mode) {
@@ -58,7 +88,7 @@ export class StrandWatcher {
58
88
  }
59
89
  const sAppId = this.sAppIdLookup.getSAppId(strand.Id);
60
90
  if (sAppId === undefined) {
61
- // sAppId not yet known - admit provisionally and re-evaluate next poll.
91
+ // sAppId not yet known - admit provisionally and re-evaluate once it is learned.
62
92
  log('sAppId unknown for strand %s - deferring filter decision', strand.Id);
63
93
  return 'defer';
64
94
  }
@@ -72,20 +102,70 @@ export class StrandWatcher {
72
102
  }
73
103
  /**
74
104
  * Record a failed launch attempt and schedule when the strand may be retried:
75
- * `pollInterval * 2^(failures-1)`, capped at {@link MAX_RETRY_BACKOFF_MS}.
105
+ * `retryBaseMs * 2^(failures-1)`, capped at {@link MAX_RETRY_BACKOFF_MS}.
76
106
  */
77
107
  recordFailure(strandId) {
78
108
  const failures = (this.failureStates.get(strandId)?.failures ?? 0) + 1;
79
- const delay = Math.min(this.pollInterval * 2 ** (failures - 1), MAX_RETRY_BACKOFF_MS);
109
+ const delay = Math.min(this.retryBaseMs * 2 ** (failures - 1), MAX_RETRY_BACKOFF_MS);
80
110
  this.failureStates.set(strandId, { failures, nextAttemptAt: this.now() + delay });
81
111
  log('Strand %s launch failed (%d consecutive) - next attempt in %dms', strandId, failures, delay);
82
112
  }
83
113
  /**
84
- * Poll for strand changes.
114
+ * One run: read, reconcile, then arm the follow-up runs this run needs. A run the watch
115
+ * starts clears every pending wake, so each run re-arms the retries still owed. A
116
+ * {@link forcePoll} run clears none, so what it arms may sit beside an equivalent wake;
117
+ * whichever fires first runs and clears the other.
118
+ */
119
+ async runOnce() {
120
+ const startedAt = this.now();
121
+ const readComplete = await this.poll();
122
+ if (this.running) {
123
+ this.scheduleLaunchRetry(startedAt);
124
+ this.scheduleReadRetry(readComplete);
125
+ }
126
+ }
127
+ /**
128
+ * Wake for the earliest launch retry falling due after `since`. A backoff that expired at
129
+ * or before `since` belongs to a strand the run starting then either attempted or skipped
130
+ * for a reason other than backoff (suppressed, filtered out); waking for it would spin.
131
+ * Without this a failed launch would wait for an unrelated change or the backstop.
132
+ */
133
+ scheduleLaunchRetry(since) {
134
+ let earliest = Infinity;
135
+ for (const { nextAttemptAt } of this.failureStates.values()) {
136
+ if (nextAttemptAt > since) {
137
+ earliest = Math.min(earliest, nextAttemptAt);
138
+ }
139
+ }
140
+ if (earliest !== Infinity) {
141
+ this.watch?.trigger(Math.max(0, earliest - this.now()));
142
+ }
143
+ }
144
+ /**
145
+ * After a failed or partial read, run again on a ladder from `retryBaseMs`, doubling per
146
+ * consecutive failed run up to `readRetryCapMs`; a complete read resets it. Not left to
147
+ * the backstop because the plugin arms a table's replicated-change bridge on the table's
148
+ * first successful initialization: a machine whose first read failed (cut off from its
149
+ * party, or its sibling unreachable for a never-written `JoinedStrand`) would otherwise
150
+ * hear nothing until then.
151
+ */
152
+ scheduleReadRetry(readComplete) {
153
+ if (readComplete) {
154
+ this.readFailures = 0;
155
+ return;
156
+ }
157
+ this.readFailures++;
158
+ const delay = Math.min(this.retryBaseMs * 2 ** (this.readFailures - 1), this.readRetryCapMs);
159
+ log('Strand read incomplete (%d consecutive) - re-reading in %dms', this.readFailures, delay);
160
+ this.watch?.trigger(delay);
161
+ }
162
+ /**
163
+ * Read and reconcile. Resolves true when the read answered in full: false when it threw,
164
+ * or when the queryable answered part of it from a stand-in.
85
165
  */
86
166
  async poll() {
87
167
  if (!this.running)
88
- return;
168
+ return true;
89
169
  try {
90
170
  const currentStrands = await this.queryable.queryStrands();
91
171
  const currentMap = new Map(currentStrands.map(s => [s.Id, s]));
@@ -96,7 +176,7 @@ export class StrandWatcher {
96
176
  if (this.knownStrands.has(strand.Id))
97
177
  continue;
98
178
  const failure = this.failureStates.get(strand.Id);
99
- // Clock read per candidate, not once per poll: an earlier strand's launch
179
+ // Clock read per candidate, not once per run: an earlier strand's launch
100
180
  // may have taken a long time, and this one's gate must reflect that.
101
181
  if (failure && this.now() < failure.nextAttemptAt)
102
182
  continue; // backing off after a failed launch
@@ -104,11 +184,11 @@ export class StrandWatcher {
104
184
  if (decision === 'reject')
105
185
  continue;
106
186
  log('Strand added: %s', strand.Id);
107
- // Set BEFORE the await: this doubles as the in-flight guard that stops the
108
- // interval timer from starting a second concurrent launch for the same strand.
187
+ // Set BEFORE the await: this doubles as the in-flight guard that stops a
188
+ // concurrent run (`forcePoll` beside a scheduled one) from launching the strand twice.
109
189
  this.knownStrands.set(strand.Id, strand);
110
190
  if (decision === 'defer') {
111
- // Provisional admission: sAppId unknown, re-check on later polls.
191
+ // Provisional admission: sAppId unknown, re-check on later runs.
112
192
  this.provisional.add(strand.Id);
113
193
  }
114
194
  try {
@@ -143,7 +223,7 @@ export class StrandWatcher {
143
223
  log('Error handling strand remove for %s: %o', strandId, error);
144
224
  }
145
225
  }
146
- // decision === 'defer': still unknown, leave provisional for next poll.
226
+ // decision === 'defer': still unknown, leave provisional until sAppIdLearned wakes a run.
147
227
  }
148
228
  // Find removed strands. A strand whose launch failed is no longer in
149
229
  // knownStrands, so it correctly never fires onStrandRemoved — nothing was
@@ -169,8 +249,8 @@ export class StrandWatcher {
169
249
  this.failureStates.delete(strandId);
170
250
  }
171
251
  }
172
- // NOTE: the clear needs a poll that actually SEES the row absent, so a sibling that
173
- // unpublishes and re-publishes a locally-stopped id inside one poll interval leaves
252
+ // NOTE: the clear needs a run that actually SEES the row absent, so a sibling that
253
+ // unpublishes and re-publishes a locally-stopped id between two runs leaves
174
254
  // it suppressed for the session. Contrived today — re-seating a removed id is
175
255
  // owner-gated and manual — and there is no row version to distinguish the new row
176
256
  // from the old. If re-publishing a removed id ever becomes routine, give the row a
@@ -180,15 +260,18 @@ export class StrandWatcher {
180
260
  this.suppressed.delete(strandId);
181
261
  }
182
262
  }
263
+ return !(this.queryable.lastReadPartial?.() ?? false);
183
264
  }
184
265
  catch (error) {
185
266
  // NOTE: a machine cut off from its party before it ever received the `Strand` block
186
- // fails this read `cohort-unreachable` on every poll and logs here (observed in
187
- // `control-cohort-edge-carries-data`). Harmless: nothing was ever launched from a block
188
- // this machine never held, and this catch keeps the known set. Do not answer it as
189
- // empty the way `ControlDatabase.queryRevokedStamps` does for `Revocation` — an empty
190
- // answer here runs the removed-strand loop above. Revisit if the log volume matters.
267
+ // fails this read `cohort-unreachable` on every step of the read-retry ladder and logs
268
+ // here (observed in `control-cohort-edge-carries-data`). Harmless: nothing was ever
269
+ // launched from a block this machine never held, and this catch keeps the known set. Do
270
+ // not answer it as empty the way `ControlDatabase.queryRevokedStamps` does for
271
+ // `Revocation` — an empty answer here runs the removed-strand loop above. Revisit if
272
+ // the log volume matters.
191
273
  log('Error polling strands: %o', error);
274
+ return false;
192
275
  }
193
276
  }
194
277
  /**
@@ -200,19 +283,21 @@ export class StrandWatcher {
200
283
  return;
201
284
  }
202
285
  log('Starting StrandWatcher');
286
+ // Subscribed before the first read, so a commit landing between them is either seen by
287
+ // that read or wakes a follow-up run.
288
+ this.watch = this.subscribe();
203
289
  this.running = true;
204
- // Set up periodic polling
205
- this.pollTimer = setInterval(() => {
206
- void this.poll();
207
- }, this.pollInterval);
208
- // Defer the first poll so start() doesn't block the caller.
209
- // Strands added via addStrand() typically arrive after start() completes.
210
- this.initialPollTimer = setTimeout(() => {
211
- this.initialPollTimer = null;
212
- void this.poll();
213
- }, 100);
290
+ // Deferred so start() doesn't block the caller; strands added via addStrand()
291
+ // typically arrive after start() completes.
292
+ this.watch.trigger(INITIAL_RUN_DELAY_MS);
214
293
  log('StrandWatcher started');
215
294
  }
295
+ /** The watch every run goes through: the queryable's change subscription, or a timer-only one. */
296
+ subscribe() {
297
+ const onChange = () => this.runOnce();
298
+ const options = { label: 'strand-watcher', backstopMs: this.backstopMs };
299
+ return this.queryable.watchChanges?.(onChange, options) ?? scheduleRuns(onChange, options);
300
+ }
216
301
  /**
217
302
  * Stop watching for strand changes
218
303
  */
@@ -222,23 +307,20 @@ export class StrandWatcher {
222
307
  }
223
308
  log('Stopping StrandWatcher');
224
309
  this.running = false;
225
- if (this.initialPollTimer) {
226
- clearTimeout(this.initialPollTimer);
227
- this.initialPollTimer = null;
228
- }
229
- if (this.pollTimer) {
230
- clearInterval(this.pollTimer);
231
- this.pollTimer = null;
232
- }
310
+ // A run already in flight finishes; `running` keeps it from arming anything further.
311
+ this.watch?.dispose();
312
+ this.watch = null;
233
313
  this.knownStrands.clear();
234
314
  this.provisional.clear();
235
315
  this.failureStates.clear();
236
316
  this.suppressed.clear();
317
+ this.readFailures = 0;
237
318
  log('StrandWatcher stopped');
238
319
  }
239
320
  /**
240
- * Forget a strand whose launch failed outside this watcher, so a later poll
241
- * re-offers it — gated by the same backoff a watcher-driven failure gets.
321
+ * Forget a strand whose launch failed outside this watcher, so a later run
322
+ * re-offers it — gated by the same backoff a watcher-driven failure gets, and woken
323
+ * when that backoff expires.
242
324
  *
243
325
  * A failed launch normally leaves nothing running (StrandInstanceManager drops the
244
326
  * record), which is what makes re-offering it correct. One case does leave something
@@ -252,6 +334,25 @@ export class StrandWatcher {
252
334
  this.knownStrands.delete(strandId);
253
335
  this.provisional.delete(strandId);
254
336
  this.recordFailure(strandId);
337
+ if (this.running) {
338
+ this.scheduleLaunchRetry(this.now());
339
+ }
340
+ }
341
+ /**
342
+ * Re-read soon: something the queryable merges in beside the watched tables changed
343
+ * (`CadreNode`'s machine-local joined-strand records). A no-op once stopped.
344
+ */
345
+ refresh() {
346
+ this.watch?.trigger();
347
+ }
348
+ /**
349
+ * The host just learned `strandId`'s sAppId: if the strand was admitted provisionally
350
+ * under an `sAppId` filter, run so the admission resolves now rather than on the backstop.
351
+ */
352
+ sAppIdLearned(strandId) {
353
+ if (this.provisional.has(strandId)) {
354
+ this.watch?.trigger();
355
+ }
255
356
  }
256
357
  /**
257
358
  * Never offer this strand again this session. Two callers, both meaning "the retry
@@ -292,10 +393,13 @@ export class StrandWatcher {
292
393
  return new Map(this.knownStrands);
293
394
  }
294
395
  /**
295
- * Force an immediate poll (useful for testing)
396
+ * Run once now, outside the watch's schedule (tests, and callers that just wrote a row
397
+ * and want this node to converge before returning). May overlap a scheduled run:
398
+ * `knownStrands` is updated before each callback's await, so neither launches nor
399
+ * removes a strand twice.
296
400
  */
297
401
  async forcePoll() {
298
- await this.poll();
402
+ await this.runOnce();
299
403
  }
300
404
  }
301
405
  //# sourceMappingURL=strand-watcher.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"strand-watcher.js","sourceRoot":"","sources":["../src/strand-watcher.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"strand-watcher.js","sourceRoot":"","sources":["../src/strand-watcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import type { Database } from '@quereus/quereus';
2
+ import type { TimeoutScheduler } from './timeout-scheduler.js';
3
+ /**
4
+ * Default coalescing window. One local commit fires the watch twice (Quereus's own
5
+ * post-commit dispatch, then the plugin's storage event); this folds both into one run.
6
+ */
7
+ export declare const TABLE_CHANGE_COALESCE_MS = 50;
8
+ export interface TableChangeWatchOptions {
9
+ /** Short label for log lines (e.g. 'strand-watcher'). */
10
+ label: string;
11
+ /** Coalescing window after the first change; default {@link TABLE_CHANGE_COALESCE_MS}. */
12
+ coalesceMs?: number;
13
+ /**
14
+ * Run anyway this long after the last run settled, change or not. Omit for none.
15
+ * The path for a change that arrives without a storage event (see the module doc).
16
+ */
17
+ backstopMs?: number;
18
+ /**
19
+ * Called inline, synchronously, from the watch handler — inside the committing call,
20
+ * possibly with the control write lock held — once per changed table per event, with the
21
+ * name as the consumer passed it. Only for setting a flag (cache invalidation). Must not
22
+ * read, write or await.
23
+ */
24
+ onInvalidate?: (qualifiedTable: string) => void;
25
+ /** Timer seam; default real, unref'd timers. */
26
+ scheduler?: TimeoutScheduler;
27
+ }
28
+ export interface TableChangeWatch {
29
+ /** Schedule a run after `delayMs` (default 0) through the same single-flight rule; an earlier pending run wins. */
30
+ trigger(delayMs?: number): void;
31
+ /** Stop: unsubscribe, clear timers; no run starts afterwards. Idempotent. Does not wait for a run in flight. */
32
+ dispose(): void;
33
+ }
34
+ /**
35
+ * Subscribe `onChange` to changes in `tables` (qualified `'Schema.Table'` names, e.g.
36
+ * `'CadreControl.Strand'`). Throws synchronously on a name that is not qualified, or a table
37
+ * that does not exist.
38
+ *
39
+ * NOTE: the plugin arms its storage bridge for a table on that table's first full
40
+ * initialization on this `Database`, so a table not yet touched hears local commits only,
41
+ * not replicated ones. Consumers' start runs touch their tables; the seam does not, because
42
+ * touching a never-written collection consults the network.
43
+ *
44
+ * NOTE: Quereus silently disposes a subscription whose table is dropped or altered. Control
45
+ * and strand schemas are fixed for a database's life (an app-schema attach adds `App` tables
46
+ * and alters no `Strand` one), so nothing re-subscribes; revisit if that stops holding.
47
+ */
48
+ export declare function watchTables(db: Database, tables: readonly string[], onChange: () => Promise<void>, options: TableChangeWatchOptions): TableChangeWatch;
49
+ /**
50
+ * {@link watchTables}' schedule — single-flight runs, `trigger`, the backstop — with no table
51
+ * subscription, for a consumer with no database to watch (tests, fakes).
52
+ */
53
+ export declare function scheduleRuns(onChange: () => Promise<void>, options: TableChangeWatchOptions): TableChangeWatch;
54
+ /**
55
+ * The subscriptions one database owner has handed out, so its `close()` can dispose those
56
+ * still live — synchronously, ahead of its own awaits, so nothing a closing commit does
57
+ * schedules a run. (`Database.close()` disposes them too, but only after its own awaits.)
58
+ */
59
+ export declare class TableWatchRegistry {
60
+ private readonly live;
61
+ watch(db: Database, tables: readonly string[], onChange: () => Promise<void>, options: TableChangeWatchOptions): TableChangeWatch;
62
+ disposeAll(): void;
63
+ }