@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.
- package/README.md +4 -3
- package/dist/bootstrap-peer-store-file.js +1 -1
- package/dist/bootstrap-peer-store.d.ts +1 -1
- package/dist/bootstrap-peer-store.js +4 -4
- package/dist/cadre-invite-protocol.d.ts +81 -142
- package/dist/cadre-invite-protocol.js +168 -458
- package/dist/cadre-invite-protocol.js.map +1 -1
- package/dist/cadre-join-request.d.ts +15 -2
- package/dist/cadre-join-request.js +22 -3
- package/dist/cadre-join-request.js.map +1 -1
- package/dist/cadre-node.d.ts +1089 -475
- package/dist/cadre-node.js +2170 -1037
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-authorization.d.ts +36 -14
- package/dist/control-authorization.js +20 -3
- package/dist/control-authorization.js.map +1 -1
- package/dist/control-cohort.d.ts +88 -32
- package/dist/control-cohort.js +91 -33
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +856 -318
- package/dist/control-database.js +1422 -701
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +46 -43
- package/dist/control-formation-recorder.js +77 -78
- package/dist/control-formation-recorder.js.map +1 -1
- package/dist/control-protocol-guard.d.ts +11 -4
- package/dist/control-protocol-guard.js +20 -5
- package/dist/control-protocol-guard.js.map +1 -1
- package/dist/control-schema-version.d.ts +97 -0
- package/dist/control-schema-version.js +225 -0
- package/dist/control-schema-version.js.map +1 -0
- package/dist/control-schema.d.ts +45 -5
- package/dist/control-schema.js +262 -78
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +45 -10
- package/dist/control-write-retry.js +92 -22
- package/dist/control-write-retry.js.map +1 -1
- package/dist/delegate-admission.d.ts +13 -0
- package/dist/delegate-admission.js +27 -0
- package/dist/delegate-admission.js.map +1 -1
- package/dist/ed25519-key.d.ts +10 -7
- package/dist/ed25519-key.js +23 -20
- package/dist/ed25519-key.js.map +1 -1
- package/dist/edge-node/config.d.ts +122 -0
- package/dist/edge-node/config.js +199 -0
- package/dist/edge-node/config.js.map +1 -0
- package/dist/edge-node/dissolve.d.ts +95 -0
- package/dist/edge-node/dissolve.js +184 -0
- package/dist/edge-node/dissolve.js.map +1 -0
- package/dist/edge-node/edge-join.d.ts +44 -0
- package/dist/edge-node/edge-join.js +227 -0
- package/dist/edge-node/edge-join.js.map +1 -0
- package/dist/edge-node/edge-node.d.ts +295 -0
- package/dist/edge-node/edge-node.js +704 -0
- package/dist/edge-node/edge-node.js.map +1 -0
- package/dist/edge-node/index.d.ts +18 -0
- package/dist/edge-node/index.js +18 -0
- package/dist/edge-node/index.js.map +1 -0
- package/dist/edge-node/join-credential.d.ts +11 -0
- package/dist/edge-node/join-credential.js +11 -0
- package/dist/edge-node/join-credential.js.map +1 -0
- package/dist/edge-node/lazy-raw-storage.d.ts +40 -0
- package/dist/edge-node/lazy-raw-storage.js +134 -0
- package/dist/edge-node/lazy-raw-storage.js.map +1 -0
- package/dist/edge-node/node-local.d.ts +60 -0
- package/dist/edge-node/node-local.js +58 -0
- package/dist/edge-node/node-local.js.map +1 -0
- package/dist/edge-node/options.d.ts +83 -0
- package/dist/edge-node/options.js +110 -0
- package/dist/edge-node/options.js.map +1 -0
- package/dist/edge-node/owner-grant.d.ts +41 -0
- package/dist/edge-node/owner-grant.js +38 -0
- package/dist/edge-node/owner-grant.js.map +1 -0
- package/dist/edge-node/platform.d.ts +140 -0
- package/dist/edge-node/platform.js +12 -0
- package/dist/edge-node/platform.js.map +1 -0
- package/dist/edge-node/strand-move.d.ts +115 -0
- package/dist/edge-node/strand-move.js +223 -0
- package/dist/edge-node/strand-move.js.map +1 -0
- package/dist/edge-node/strands.d.ts +28 -0
- package/dist/edge-node/strands.js +60 -0
- package/dist/edge-node/strands.js.map +1 -0
- package/dist/enrolled-machine-store.d.ts +3 -3
- package/dist/enrolled-machine-store.js +3 -3
- package/dist/formation-token.d.ts +48 -0
- package/dist/formation-token.js +26 -0
- package/dist/formation-token.js.map +1 -0
- package/dist/index.d.ts +27 -17
- package/dist/index.js +39 -17
- package/dist/index.js.map +1 -1
- package/dist/joined-strand-store.d.ts +29 -12
- package/dist/joined-strand-store.js +66 -27
- package/dist/joined-strand-store.js.map +1 -1
- package/dist/key-store.d.ts +9 -4
- package/dist/key-store.js +9 -4
- package/dist/key-store.js.map +1 -1
- package/dist/link-budget.d.ts +9 -7
- package/dist/link-budget.js +9 -7
- package/dist/link-budget.js.map +1 -1
- package/dist/machine-removal.d.ts +19 -39
- package/dist/machine-removal.js +24 -98
- package/dist/machine-removal.js.map +1 -1
- package/dist/member-redemption.d.ts +256 -0
- package/dist/member-redemption.js +490 -0
- package/dist/member-redemption.js.map +1 -0
- package/dist/membership-view.d.ts +91 -0
- package/dist/membership-view.js +110 -0
- package/dist/membership-view.js.map +1 -0
- package/dist/owner-anchor-sync.d.ts +4 -4
- package/dist/owner-anchor-sync.js +16 -15
- package/dist/owner-anchor-sync.js.map +1 -1
- package/dist/owner-grant-protocol.d.ts +251 -0
- package/dist/owner-grant-protocol.js +344 -0
- package/dist/owner-grant-protocol.js.map +1 -0
- package/dist/owner-key-export.d.ts +70 -0
- package/dist/owner-key-export.js +279 -0
- package/dist/owner-key-export.js.map +1 -0
- package/dist/owner-key-flows.d.ts +55 -0
- package/dist/owner-key-flows.js +60 -0
- package/dist/owner-key-flows.js.map +1 -0
- package/dist/owner-key-store.d.ts +31 -0
- package/dist/owner-key-store.js +81 -0
- package/dist/owner-key-store.js.map +1 -0
- package/dist/owner-keys.d.ts +78 -0
- package/dist/owner-keys.js +133 -0
- package/dist/owner-keys.js.map +1 -0
- package/dist/owner-signer.d.ts +54 -0
- package/dist/owner-signer.js +51 -0
- package/dist/owner-signer.js.map +1 -0
- package/dist/peer-addr-book.d.ts +10 -1
- package/dist/peer-addr-book.js +4 -3
- package/dist/peer-addr-book.js.map +1 -1
- package/dist/peer-authorization.d.ts +83 -18
- package/dist/peer-authorization.js +123 -53
- package/dist/peer-authorization.js.map +1 -1
- package/dist/peer-join-backfill.d.ts +89 -20
- package/dist/peer-join-backfill.js +265 -108
- package/dist/peer-join-backfill.js.map +1 -1
- package/dist/pending-join-runner.d.ts +48 -18
- package/dist/pending-join-runner.js +74 -56
- package/dist/pending-join-runner.js.map +1 -1
- package/dist/provider/rent-node.js +1 -1
- package/dist/provider/rent-node.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +162 -133
- package/dist/seed-bootstrap.js +300 -283
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/seed-trust-policy.js +4 -4
- package/dist/strand-addr-protocol.d.ts +4 -1
- package/dist/strand-addr-protocol.js +8 -2
- package/dist/strand-addr-protocol.js.map +1 -1
- package/dist/strand-cohort.d.ts +3 -0
- package/dist/strand-cohort.js.map +1 -1
- package/dist/strand-database.d.ts +24 -2
- package/dist/strand-database.js +29 -6
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +176 -88
- package/dist/strand-first-sync-gate.js +288 -121
- package/dist/strand-first-sync-gate.js.map +1 -1
- package/dist/strand-formation-deadlines.d.ts +9 -9
- package/dist/strand-formation-deadlines.js +9 -9
- package/dist/strand-formation-manager.d.ts +23 -17
- package/dist/strand-formation-manager.js +46 -44
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +5 -8
- package/dist/strand-formation-protocol.js +101 -16
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +27 -18
- package/dist/strand-instance-manager.js +119 -50
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-membership-reconciler.d.ts +16 -13
- package/dist/strand-membership-reconciler.js +30 -22
- package/dist/strand-membership-reconciler.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +45 -22
- package/dist/strand-membership-writer.js +226 -131
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-move.d.ts +6 -5
- package/dist/strand-move.js +36 -32
- package/dist/strand-move.js.map +1 -1
- package/dist/strand-peer-redial.js +2 -3
- package/dist/strand-peer-redial.js.map +1 -1
- package/dist/strand-revocation-enforcer.d.ts +134 -60
- package/dist/strand-revocation-enforcer.js +144 -71
- package/dist/strand-revocation-enforcer.js.map +1 -1
- package/dist/strand-solicitation.d.ts +50 -39
- package/dist/strand-solicitation.js +10 -3
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-watcher.d.ts +80 -13
- package/dist/strand-watcher.js +152 -48
- package/dist/strand-watcher.js.map +1 -1
- package/dist/table-change-watch.d.ts +63 -0
- package/dist/table-change-watch.js +209 -0
- package/dist/table-change-watch.js.map +1 -0
- package/dist/timeout-scheduler.d.ts +4 -3
- package/dist/timeout-scheduler.js +4 -3
- package/dist/timeout-scheduler.js.map +1 -1
- package/dist/types.d.ts +374 -150
- package/dist/types.js +4 -4
- package/dist/types.js.map +1 -1
- package/package.json +17 -8
- package/src/bootstrap-peer-store-file.ts +44 -44
- package/src/bootstrap-peer-store.ts +214 -214
- package/src/cadre-invite-protocol.ts +740 -1052
- package/src/cadre-join-request.ts +24 -3
- package/src/cadre-node.ts +2402 -1079
- package/src/control-authorization.ts +47 -14
- package/src/control-cohort.ts +204 -128
- package/src/control-database.ts +1749 -820
- package/src/control-formation-recorder.ts +359 -353
- package/src/control-protocol-guard.ts +23 -6
- package/src/control-schema-version.ts +301 -0
- package/src/control-schema.ts +273 -78
- package/src/control-write-retry.ts +641 -567
- package/src/delegate-admission.ts +351 -314
- package/src/ed25519-key.ts +174 -171
- package/src/edge-node/config.ts +277 -0
- package/src/edge-node/dissolve.ts +251 -0
- package/src/edge-node/edge-join.ts +319 -0
- package/src/edge-node/edge-node.ts +1045 -0
- package/src/edge-node/index.ts +59 -0
- package/src/edge-node/join-credential.ts +16 -0
- package/src/edge-node/lazy-raw-storage.ts +178 -0
- package/src/edge-node/node-local.ts +84 -0
- package/src/edge-node/options.ts +179 -0
- package/src/edge-node/owner-grant.ts +63 -0
- package/src/edge-node/platform.ts +151 -0
- package/src/edge-node/strand-move.ts +270 -0
- package/src/edge-node/strands.ts +72 -0
- package/src/enrolled-machine-store.ts +3 -3
- package/src/formation-token.ts +63 -0
- package/src/index.ts +899 -789
- package/src/joined-strand-store.ts +73 -28
- package/src/key-store.ts +10 -4
- package/src/link-budget.ts +9 -7
- package/src/machine-removal.ts +26 -111
- package/src/member-redemption.ts +649 -0
- package/src/membership-view.ts +144 -0
- package/src/owner-anchor-sync.ts +19 -18
- package/src/owner-grant-protocol.ts +547 -0
- package/src/owner-key-export.ts +348 -0
- package/src/owner-key-flows.ts +109 -0
- package/src/owner-key-store.ts +85 -0
- package/src/owner-keys.ts +178 -0
- package/src/owner-signer.ts +94 -0
- package/src/peer-addr-book.ts +4 -3
- package/src/peer-authorization.ts +558 -488
- package/src/peer-join-backfill.ts +971 -761
- package/src/pending-join-runner.ts +76 -53
- package/src/provider/rent-node.ts +1 -1
- package/src/seed-bootstrap.ts +343 -349
- package/src/seed-trust-policy.ts +4 -4
- package/src/strand-addr-protocol.ts +547 -538
- package/src/strand-cohort.ts +4 -0
- package/src/strand-database.ts +352 -319
- package/src/strand-first-sync-gate.ts +334 -133
- package/src/strand-formation-deadlines.ts +227 -227
- package/src/strand-formation-manager.ts +731 -725
- package/src/strand-formation-protocol.ts +122 -24
- package/src/strand-instance-manager.ts +2092 -2026
- package/src/strand-membership-reconciler.ts +33 -25
- package/src/strand-membership-writer.ts +2093 -1974
- package/src/strand-move.ts +50 -38
- package/src/strand-peer-redial.ts +2 -3
- package/src/strand-revocation-enforcer.ts +740 -654
- package/src/strand-solicitation.ts +61 -34
- package/src/strand-watcher.ts +175 -56
- package/src/table-change-watch.ts +285 -0
- package/src/timeout-scheduler.ts +29 -28
- 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
|
|
15
|
-
* The founder writes the Header in its bootstrap; every other machine can only receive
|
|
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
|
|
19
|
-
*
|
|
20
|
-
* and the instance goes `'active'`. A
|
|
21
|
-
*
|
|
22
|
-
* the first try and is never gated, so
|
|
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
|
|
33
|
-
* founder's
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
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
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* (
|
|
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 `
|
|
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
|
|
@@ -62,11 +68,22 @@
|
|
|
62
68
|
* it), a joiner's catalog will fork against the founder's — gate the schema apply on the
|
|
63
69
|
* Header too, or have optimystic open the catalog read-only when a peer already holds it.
|
|
64
70
|
*/
|
|
65
|
-
import type { Database } from '@quereus/quereus';
|
|
66
71
|
import type { StrandDatabase } from './strand-database.js';
|
|
67
72
|
import { type TimeoutScheduler } from './timeout-scheduler.js';
|
|
68
|
-
/**
|
|
69
|
-
|
|
73
|
+
/**
|
|
74
|
+
* How long a gated launch goes without probing when no data lands in the strand's store: the
|
|
75
|
+
* gate probes when data arrives, and this long after its last probe settled when none has. It
|
|
76
|
+
* covers a probe that failed for a reason no storage event follows — a consult that missed its
|
|
77
|
+
* deadline while the founder held the block, or a push that never comes.
|
|
78
|
+
*
|
|
79
|
+
* Why 2 s: arrivals wake the gate, so this timer is the fallback rather than the main path. At
|
|
80
|
+
* the supported 3 s link round trip one consult takes about 6 s (two round trips, per the 1.8.0
|
|
81
|
+
* measurements on {@link DEFAULT_STRAND_FIRST_SYNC_TIMEOUT_MS}), so a 2 s idle gap after a failed
|
|
82
|
+
* probe adds at most a third of one probe round, and it is under 1% of that 360 s budget. Shorter
|
|
83
|
+
* brings back the cost of polling (the gate re-probed every 500 ms before it woke on arrivals);
|
|
84
|
+
* much longer (5 s) delays the case with no storage event by most of a probe round for nothing.
|
|
85
|
+
*/
|
|
86
|
+
export declare const DEFAULT_STRAND_FIRST_SYNC_BACKSTOP_MS = 2000;
|
|
70
87
|
/**
|
|
71
88
|
* How long `CadreNode.addStrand` waits for a machine's first sync before rejecting with
|
|
72
89
|
* {@link StrandAwaitingFirstSyncError}. It has to cover the slowest attach sereus carries,
|
|
@@ -97,6 +114,37 @@ export declare const DEFAULT_STRAND_FIRST_SYNC_POLL_MS = 500;
|
|
|
97
114
|
* several of those — which is why both bands are recorded here, and why a change to either
|
|
98
115
|
* number has to be weighed against the other.
|
|
99
116
|
*
|
|
117
|
+
* AT THE SUPPORTED LINK ON `@optimystic/*` 1.17.0 (2026-10-21), 1 500 ms one-way, the same
|
|
118
|
+
* scenario, one run per arm, with the probe reading every `Strand` table as well as the `App`
|
|
119
|
+
* one (`debt-first-sync-gate-reads-every-strand-table`); the kept-store arm was not run:
|
|
120
|
+
* - Fresh join: writable at 28.7 s and NOT gated, as on 1.15.0.
|
|
121
|
+
* - Re-attach over an EMPTY store: writable at 105.8 and 105.9 s over two runs, the row A wrote
|
|
122
|
+
* while B was away readable at the same moment. B's first database build fails at 7.0 s (no
|
|
123
|
+
* seed peer has answered; `StrandInstanceManager.deferUnreachableJoin` retries it), B's strand
|
|
124
|
+
* node connects to A's at 15.1 s, the retried build's `connectToStrand` takes 21.2 s, and the
|
|
125
|
+
* launch's first probe then takes 72.6 s and passes with no read failing, so the gate never
|
|
126
|
+
* arms.
|
|
127
|
+
* - The same arm with the probe held to the Header and the `App` table (one run, otherwise the
|
|
128
|
+
* same build): that probe took 12.1 s, writable at 45.4 s, the row at 63.5 s. So the seven
|
|
129
|
+
* `Strand` reads cost about 60 s here, about 8.6 s each, against about 6 s for each of the
|
|
130
|
+
* baseline's two reads — more than the 42 s estimated from one 6 s consult per read.
|
|
131
|
+
*
|
|
132
|
+
* AT THE SUPPORTED LINK ON `@optimystic/*` 1.15.0 (2026-10-20), 1 500 ms one-way, the same
|
|
133
|
+
* scenario, one fresh-join run, with the one-row probes woken by data landing in the store
|
|
134
|
+
* (`first-sync-gate-probes-cheaply`; the full-count probes polled every 500 ms gave the same
|
|
135
|
+
* figures):
|
|
136
|
+
* - Fresh join: writable at 28.8 s, and NOT gated. The launch waits 27.3 s in
|
|
137
|
+
* `connectToStrand` for its seed peer (it hydrates the founder's catalog, 9 tables), and by
|
|
138
|
+
* then the store holds the strand: the first probe passed in 24 ms.
|
|
139
|
+
* - The strand case of `message-cost-accounting` (two relay-only parties on loopback) agrees:
|
|
140
|
+
* B's `addStrand` took 471 ms, with 13 `sync` streams to A, 4 block reads from the Header
|
|
141
|
+
* probe and 8 from the table probes. Its two tables are empty, so a full count would fetch
|
|
142
|
+
* the same blocks as a one-row read.
|
|
143
|
+
* - Neither arms the gate, so neither times the woken path. That was checked on
|
|
144
|
+
* `harness-topology`'s cross-party share (a joiner launched with no seed, dialed afterwards)
|
|
145
|
+
* with the backstop raised to 10 s: the founder's join backfill push opened the gate 2.1 s
|
|
146
|
+
* after it armed.
|
|
147
|
+
*
|
|
100
148
|
* AT THE SUPPORTED LINK ON `@optimystic/*` 1.8.0 (2026-09-29), 1 500 ms one-way, the read
|
|
101
149
|
* deadline derived from the declared link at 7 000 ms (`cohortReadDeadlineMs` in
|
|
102
150
|
* `link-budget.ts`; the plugin's `COHORT_READ_DEADLINE_MS` is the same number), and cadre
|
|
@@ -153,19 +201,19 @@ export declare const DEFAULT_STRAND_FIRST_SYNC_POLL_MS = 500;
|
|
|
153
201
|
* injector are unknown to us) opened the gate at about 150 s, after the previous 120 s budget
|
|
154
202
|
* had already rejected; and a real Galaxy S7 joining fresh through relay.sereus.org took 178 s.
|
|
155
203
|
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* about
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* it clears the device join by only 1.35x,
|
|
163
|
-
* original 30 s sat INSIDE the 1000 ms fresh
|
|
164
|
-
*
|
|
165
|
-
* reporter's re-attach.
|
|
204
|
+
* 360 s clears the worst harness sample (105.9 s, a re-attach over an empty store at the
|
|
205
|
+
* supported link on 1.17.0) by about 3.4x, the reporter's re-attach by 2.4x and the device join
|
|
206
|
+
* by about 2x. Set by the rule every re-measure applies: keep it while the worst harness sample
|
|
207
|
+
* is at most 100 s, which holds a margin of at least 3x; otherwise raise it to 3x the worst
|
|
208
|
+
* sample, rounded up to the next whole minute. The 1.17.0 sample crossed 100 s, and 3x 105.9 s
|
|
209
|
+
* 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
|
|
210
|
+
* kept the 300 s before it. 240 s was rejected earlier: it clears the device join by only 1.35x,
|
|
211
|
+
* and that sample was not a re-attach. History: the original 30 s sat INSIDE the 1000 ms fresh
|
|
212
|
+
* band and refused about half of those joins while their sync was progressing normally; 120 s
|
|
213
|
+
* was sized from fresh joins only and refused the reporter's re-attach.
|
|
166
214
|
*
|
|
167
|
-
* NOTE: accepted tradeoff — what
|
|
168
|
-
* before it is told "not yet", so a strand none of whose members is reachable at all takes
|
|
215
|
+
* NOTE: accepted tradeoff — what 360 s costs. This wait is what an app's `addStrand` sits in
|
|
216
|
+
* before it is told "not yet", so a strand none of whose members is reachable at all takes six
|
|
169
217
|
* minutes to report instead of two. The slow report was weighed against refusing attaches that
|
|
170
218
|
* were working, and the refusal is the worse failure. The cost is bounded — the rejection is
|
|
171
219
|
* retryable, the strand stays launched and keeps probing, and `strand:writable` fires the
|
|
@@ -174,7 +222,7 @@ export declare const DEFAULT_STRAND_FIRST_SYNC_POLL_MS = 500;
|
|
|
174
222
|
* "no peer at all" could then be reported at once and this budget would only ever be spent on a
|
|
175
223
|
* sync that is actually in progress — worth more the longer this budget gets.
|
|
176
224
|
*/
|
|
177
|
-
export declare const DEFAULT_STRAND_FIRST_SYNC_TIMEOUT_MS =
|
|
225
|
+
export declare const DEFAULT_STRAND_FIRST_SYNC_TIMEOUT_MS = 360000;
|
|
178
226
|
/** Embedder-facing tuning for the gate, threaded from `CadreNodeConfig.strandFirstSync`. */
|
|
179
227
|
export interface StrandFirstSyncConfig {
|
|
180
228
|
/**
|
|
@@ -183,8 +231,11 @@ export interface StrandFirstSyncConfig {
|
|
|
183
231
|
* {@link DEFAULT_STRAND_FIRST_SYNC_TIMEOUT_MS}.
|
|
184
232
|
*/
|
|
185
233
|
timeoutMs?: number;
|
|
186
|
-
/**
|
|
187
|
-
|
|
234
|
+
/**
|
|
235
|
+
* While gated, probe this long after the last probe settled when no data has landed in the
|
|
236
|
+
* strand's store. Default {@link DEFAULT_STRAND_FIRST_SYNC_BACKSTOP_MS}.
|
|
237
|
+
*/
|
|
238
|
+
backstopMs?: number;
|
|
188
239
|
}
|
|
189
240
|
/**
|
|
190
241
|
* Thrown by `CadreNode.addStrand` / `whenStrandWritable` when a machine has not received the
|
|
@@ -199,82 +250,119 @@ export declare class StrandAwaitingFirstSyncError extends Error {
|
|
|
199
250
|
readonly waitedMs: number;
|
|
200
251
|
constructor(strandId: string, waitedMs: number);
|
|
201
252
|
}
|
|
202
|
-
/**
|
|
203
|
-
* Whether this machine holds the strand's `Strand.Header` row — the "has synced at least
|
|
204
|
-
* once (or founded)" signal. A read, never a write: it cannot invent the collection. A
|
|
205
|
-
* throwing read (an unreachable cohort mid-sync, a store still hydrating) reports `false`
|
|
206
|
-
* with a log, since the caller's only response to either answer is "probe again".
|
|
207
|
-
*/
|
|
208
|
-
export declare function strandHeaderHeld(db: Database, label: string): Promise<boolean>;
|
|
209
|
-
/**
|
|
210
|
-
* Whether a read of every `App` table settles on this machine — the "each app collection
|
|
211
|
-
* has been fetched from the cohort, or is authoritatively absent" signal. Only a throwing
|
|
212
|
-
* read (an unreachable cohort) reports `false`; an empty table is a settled read.
|
|
213
|
-
*/
|
|
214
|
-
export declare function appTablesReadable(db: Database, label: string): Promise<boolean>;
|
|
215
|
-
/**
|
|
216
|
-
* The gate's whole probe: the Header is held AND every app table has been read once. The
|
|
217
|
-
* app tables are read only once the Header is — a joiner with no peer would otherwise pay
|
|
218
|
-
* one failing network read per table per probe for nothing.
|
|
219
|
-
*/
|
|
220
|
-
export declare function strandFirstSyncComplete(db: Database, label: string): Promise<boolean>;
|
|
221
253
|
export interface StrandFirstSyncGateDeps {
|
|
222
254
|
/** Log tag naming which strand this gate holds (the strand id). */
|
|
223
255
|
label: string;
|
|
224
|
-
/** The initialized database being held back from the app until the
|
|
256
|
+
/** The initialized database being held back from the app until the first sync completes. */
|
|
225
257
|
database: StrandDatabase;
|
|
226
258
|
/**
|
|
227
|
-
* Called exactly once,
|
|
228
|
-
*
|
|
229
|
-
*
|
|
259
|
+
* Called exactly once, from the gate's own probe, when the first sync completes. NOT called
|
|
260
|
+
* by {@link StrandFirstSyncGate.open} — a caller that force-opens the gate (a founder
|
|
261
|
+
* bootstrap it just ran) publishes the database itself.
|
|
230
262
|
*/
|
|
231
263
|
onHeaderHeld: () => void;
|
|
232
|
-
/**
|
|
264
|
+
/**
|
|
265
|
+
* Subscribe `onChange` to every block landing in this strand's store — a push such as join
|
|
266
|
+
* backfill, a fetched block, a commit — and return the unsubscribe. `buildStrandDatabase`
|
|
267
|
+
* passes the strand node's `storageRepo.onAnyCollectionChange`. Omitted (test doubles, a node
|
|
268
|
+
* without it), the gate probes on its backstop alone.
|
|
269
|
+
*/
|
|
270
|
+
subscribeStorageChanges?: (onChange: () => void) => () => void;
|
|
271
|
+
/** Timer seam for the probe schedule; omit for real (unref'd) timeouts. */
|
|
233
272
|
scheduler?: TimeoutScheduler;
|
|
234
273
|
}
|
|
235
274
|
/**
|
|
236
|
-
* Holds one launch's `StrandDatabase` until
|
|
237
|
-
*
|
|
238
|
-
*
|
|
275
|
+
* Holds one launch's `StrandDatabase` until the first sync completes: the `Strand.Header` row is
|
|
276
|
+
* held and a read of every `Strand` and `App` table has settled. What a probe established is
|
|
277
|
+
* remembered for the gate's lifetime, so a later probe — the gate's own, after the caller's
|
|
278
|
+
* {@link probeOnce} — reads only what is still missing. A gate lives for one database build, so a
|
|
279
|
+
* hibernation wake or a deferred-join retry starts over, and over a store that holds everything
|
|
280
|
+
* every read answers locally.
|
|
281
|
+
*
|
|
282
|
+
* While armed it probes when data lands in the strand's store
|
|
283
|
+
* ({@link StrandFirstSyncGateDeps.subscribeStorageChanges}, coalesced over
|
|
284
|
+
* `TABLE_CHANGE_COALESCE_MS`), and {@link StrandFirstSyncConfig.backstopMs} after its last probe
|
|
285
|
+
* settled when nothing has. Probes run on `scheduleRuns`: one at a time, and data landing during
|
|
286
|
+
* one produces exactly one more.
|
|
287
|
+
*
|
|
288
|
+
* The wake source is the node's storage-wide change feed rather than a table watch
|
|
289
|
+
* (`StrandDatabase.watchTables`): the Optimystic plugin arms a table's replicated-change bridge
|
|
290
|
+
* only after the table initializes successfully, and the tables the gate waits on are the ones
|
|
291
|
+
* whose collection could not be fetched yet, so a table watch would not hear the push that
|
|
292
|
+
* delivers them. Each strand has its own libp2p node and store, so the feed hears this strand
|
|
293
|
+
* only.
|
|
239
294
|
*
|
|
240
295
|
* NOTE: a probe's reads dial the founder, through the relay when that is the only route, even
|
|
241
296
|
* while the founder is unreachable: one run of `joiner-first-commit-over-slow-relay` logged 17
|
|
242
297
|
* `hop connect denied` at the relay in about 10 s, each a few frames on the joiner's relay
|
|
243
|
-
* connection
|
|
244
|
-
*
|
|
298
|
+
* connection, when the gate re-probed every 500 ms and re-read every table. It now probes on
|
|
299
|
+
* arrivals and a 2 s backstop and skips what it holds, so expect fewer. If that connection ever
|
|
300
|
+
* shows saturated while no strand member is connected, hold the probe while the strand member
|
|
301
|
+
* redial (`strand-peer-redial.ts`) reports none connected.
|
|
245
302
|
*/
|
|
246
303
|
export declare class StrandFirstSyncGate {
|
|
247
304
|
private readonly deps;
|
|
248
305
|
private readonly scheduler;
|
|
249
|
-
private readonly
|
|
250
|
-
private
|
|
306
|
+
private readonly backstopMs;
|
|
307
|
+
private readonly progress;
|
|
308
|
+
private probes;
|
|
309
|
+
private unsubscribeStorage;
|
|
310
|
+
private listening;
|
|
311
|
+
/**
|
|
312
|
+
* Data landed since the last probe started (or, before any probe, since {@link listen}), so one
|
|
313
|
+
* is already due and will read it.
|
|
314
|
+
*/
|
|
315
|
+
private landedSinceProbe;
|
|
251
316
|
private stopped;
|
|
252
317
|
private opened;
|
|
253
|
-
private probing;
|
|
254
318
|
constructor(deps: StrandFirstSyncGateDeps, config?: StrandFirstSyncConfig);
|
|
255
319
|
/** The database this gate is holding back. */
|
|
256
320
|
get database(): StrandDatabase;
|
|
257
|
-
/** True once the
|
|
321
|
+
/** True once the first sync completed (or the gate was force-opened); probing is then done. */
|
|
258
322
|
get isOpen(): boolean;
|
|
259
|
-
/**
|
|
323
|
+
/** Whether a block landed since the last probe started, or since {@link listen} when none has. */
|
|
324
|
+
get heardDataSinceProbe(): boolean;
|
|
325
|
+
/**
|
|
326
|
+
* Subscribe to the storage change feed ahead of {@link start}, so a block landing before the
|
|
327
|
+
* gate is armed is heard: {@link heardDataSinceProbe} reports it, and `start` then probes at
|
|
328
|
+
* once. For a first join, which skips the caller's first probe unless a member's push already
|
|
329
|
+
* landed while the database was initializing.
|
|
330
|
+
*/
|
|
331
|
+
listen(): void;
|
|
332
|
+
/**
|
|
333
|
+
* Arm the probe schedule. It probes when data lands, or one backstop later; and at once when
|
|
334
|
+
* data already landed since the last probe (or since {@link listen}). Without `listen`, data
|
|
335
|
+
* that landed between the caller's probe and this call raised no event the gate heard, so
|
|
336
|
+
* that first backstop is what catches it.
|
|
337
|
+
*/
|
|
260
338
|
start(): void;
|
|
261
339
|
/**
|
|
262
|
-
* Open without
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
* invoked.
|
|
340
|
+
* Open without probing: the caller is publishing the database itself — a launch whose first
|
|
341
|
+
* probe passed (or a founder's), which never armed the schedule, or a founder bootstrap just
|
|
342
|
+
* run against the gated database. Stops probing; `onHeaderHeld` is NOT invoked.
|
|
266
343
|
*/
|
|
267
344
|
open(): void;
|
|
268
|
-
/** Disarm the
|
|
345
|
+
/** Disarm: unsubscribe and drop the schedule; a probe already in flight completes and is then ignored. */
|
|
269
346
|
stop(): void;
|
|
270
|
-
private schedule;
|
|
271
|
-
private probe;
|
|
272
347
|
/**
|
|
273
|
-
* One probe's answer,
|
|
274
|
-
* guards —
|
|
275
|
-
* any failing read
|
|
276
|
-
*
|
|
277
|
-
|
|
348
|
+
* One probe's answer, reading only what this gate has not yet seen settled. A throw from
|
|
349
|
+
* OUTSIDE the probe's own read guards — a schema lookup, or `getDatabase()` itself — is
|
|
350
|
+
* reported as "not yet" like any failing read, so the caller's first probe cannot fail the
|
|
351
|
+
* launch over it and the gate's log says what happened.
|
|
352
|
+
*/
|
|
353
|
+
probeOnce(): Promise<boolean>;
|
|
354
|
+
/**
|
|
355
|
+
* NOTE: a probe's own reads can land blocks (a fetched block, a read-repair), which fires this
|
|
356
|
+
* feed and schedules one follow-up probe. Each follow-up means a block newly arrived, so they
|
|
357
|
+
* stop when nothing new lands; if gated launches ever show probe storms in the logs, ignore
|
|
358
|
+
* events while a probe is in flight.
|
|
278
359
|
*/
|
|
279
|
-
private
|
|
360
|
+
private subscribeStorage;
|
|
361
|
+
/**
|
|
362
|
+
* Called synchronously by the store after each block lands, so it only arms a timer. A push
|
|
363
|
+
* lands its blocks one by one; only the first since the last probe started arms one, since
|
|
364
|
+
* that probe has not started yet and will read the rest.
|
|
365
|
+
*/
|
|
366
|
+
private onDataLanded;
|
|
367
|
+
private probe;
|
|
280
368
|
}
|