@serfab/cadre-core 1.14.0 → 1.16.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 +293 -274
- package/dist/cadre-invite-protocol.d.ts +64 -29
- package/dist/cadre-invite-protocol.js +207 -38
- package/dist/cadre-invite-protocol.js.map +1 -1
- package/dist/cadre-join-request.d.ts +42 -0
- package/dist/cadre-join-request.js +144 -0
- package/dist/cadre-join-request.js.map +1 -0
- package/dist/cadre-node.d.ts +375 -79
- package/dist/cadre-node.js +758 -186
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-database.d.ts +92 -12
- package/dist/control-database.js +323 -142
- package/dist/control-database.js.map +1 -1
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +19 -3
- package/dist/control-schema.js.map +1 -1
- package/dist/ed25519-key.d.ts +7 -9
- package/dist/ed25519-key.js +20 -10
- package/dist/ed25519-key.js.map +1 -1
- package/dist/fetch-impl.d.ts +12 -0
- package/dist/fetch-impl.js +23 -0
- package/dist/fetch-impl.js.map +1 -0
- package/dist/file-durable-slot.js +2 -1
- package/dist/file-durable-slot.js.map +1 -1
- package/dist/file-safe-name.d.ts +25 -0
- package/dist/file-safe-name.js +29 -0
- package/dist/file-safe-name.js.map +1 -0
- package/dist/formation-approval.js +5 -11
- package/dist/formation-approval.js.map +1 -1
- package/dist/fs-atomic.d.ts +0 -19
- package/dist/fs-atomic.js +3 -24
- package/dist/fs-atomic.js.map +1 -1
- package/dist/index.d.ts +12 -5
- package/dist/index.js +31 -6
- package/dist/index.js.map +1 -1
- package/dist/invitation-bootstrap.d.ts +50 -12
- package/dist/invitation-bootstrap.js +76 -13
- package/dist/invitation-bootstrap.js.map +1 -1
- package/dist/joined-strand-store.d.ts +7 -0
- package/dist/joined-strand-store.js +9 -0
- package/dist/joined-strand-store.js.map +1 -1
- package/dist/key-store-file.js +2 -1
- package/dist/key-store-file.js.map +1 -1
- package/dist/link-budget.d.ts +9 -4
- package/dist/link-budget.js +9 -4
- package/dist/link-budget.js.map +1 -1
- package/dist/machine-removal.d.ts +78 -0
- package/dist/machine-removal.js +166 -0
- package/dist/machine-removal.js.map +1 -0
- package/dist/membership-connection-gater.d.ts +6 -5
- package/dist/membership-connection-gater.js +6 -5
- package/dist/membership-connection-gater.js.map +1 -1
- package/dist/node-claim-payload.d.ts +55 -0
- package/dist/node-claim-payload.js +108 -4
- package/dist/node-claim-payload.js.map +1 -1
- package/dist/node-default-transports.d.ts +27 -0
- package/dist/node-default-transports.js +35 -0
- package/dist/node-default-transports.js.map +1 -0
- package/dist/node-transports.d.ts +30 -0
- package/dist/node-transports.js +40 -0
- package/dist/node-transports.js.map +1 -0
- package/dist/peer-dial.d.ts +124 -27
- package/dist/peer-dial.js +254 -30
- package/dist/peer-dial.js.map +1 -1
- package/dist/peer-join-backfill.js +6 -0
- package/dist/peer-join-backfill.js.map +1 -1
- package/dist/peer-record.d.ts +6 -45
- package/dist/peer-record.js +8 -50
- package/dist/peer-record.js.map +1 -1
- package/dist/primary-lan-address.d.ts +8 -0
- package/dist/primary-lan-address.js +46 -0
- package/dist/primary-lan-address.js.map +1 -0
- package/dist/provider/client.d.ts +131 -0
- package/dist/provider/client.js +226 -0
- package/dist/provider/client.js.map +1 -0
- package/dist/provider/descriptor.d.ts +41 -0
- package/dist/provider/descriptor.js +103 -0
- package/dist/provider/descriptor.js.map +1 -0
- package/dist/provider/index.d.ts +9 -0
- package/dist/provider/index.js +10 -0
- package/dist/provider/index.js.map +1 -0
- package/dist/provider/oauth.d.ts +125 -0
- package/dist/provider/oauth.js +199 -0
- package/dist/provider/oauth.js.map +1 -0
- package/dist/provider/rent-node.d.ts +101 -0
- package/dist/provider/rent-node.js +220 -0
- package/dist/provider/rent-node.js.map +1 -0
- package/dist/relay-addrs.d.ts +9 -4
- package/dist/relay-addrs.js +9 -4
- package/dist/relay-addrs.js.map +1 -1
- package/dist/relay-reservation.d.ts +51 -16
- package/dist/relay-reservation.js +107 -24
- package/dist/relay-reservation.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +22 -5
- package/dist/seed-bootstrap.js +48 -25
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/strand-commit-durability.d.ts +54 -0
- package/dist/strand-commit-durability.js +74 -0
- package/dist/strand-commit-durability.js.map +1 -0
- package/dist/strand-database.d.ts +23 -8
- package/dist/strand-database.js +41 -13
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +7 -1
- package/dist/strand-first-sync-gate.js +7 -1
- package/dist/strand-first-sync-gate.js.map +1 -1
- package/dist/strand-formation-protocol.js +4 -1
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-fret-addrs.d.ts +19 -1
- package/dist/strand-fret-addrs.js +27 -9
- package/dist/strand-fret-addrs.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +81 -19
- package/dist/strand-instance-manager.js +216 -105
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-move.d.ts +124 -0
- package/dist/strand-move.js +438 -0
- package/dist/strand-move.js.map +1 -0
- package/dist/strand-network-config.js +7 -6
- package/dist/strand-network-config.js.map +1 -1
- package/dist/strand-peer-redial.d.ts +113 -0
- package/dist/strand-peer-redial.js +236 -0
- package/dist/strand-peer-redial.js.map +1 -0
- package/dist/strand-wake-protocol.js +10 -7
- package/dist/strand-wake-protocol.js.map +1 -1
- package/dist/types.d.ts +212 -4
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -1
- package/package.json +18 -10
- package/src/cadre-invite-protocol.ts +1052 -864
- package/src/cadre-join-request.ts +167 -0
- package/src/cadre-node.ts +10003 -9381
- package/src/control-database.ts +4663 -4451
- package/src/control-schema.ts +19 -3
- package/src/ed25519-key.ts +20 -10
- package/src/fetch-impl.ts +23 -0
- package/src/file-durable-slot.ts +64 -63
- package/src/file-safe-name.ts +32 -0
- package/src/formation-approval.ts +604 -610
- package/src/fs-atomic.ts +92 -117
- package/src/index.ts +71 -4
- package/src/invitation-bootstrap.ts +91 -13
- package/src/joined-strand-store.ts +508 -498
- package/src/key-store-file.ts +142 -141
- package/src/link-budget.ts +9 -4
- package/src/machine-removal.ts +216 -0
- package/src/membership-connection-gater.ts +638 -637
- package/src/node-claim-payload.ts +138 -4
- package/src/node-default-transports.ts +38 -0
- package/src/node-transports.ts +49 -0
- package/src/peer-dial.ts +604 -336
- package/src/peer-join-backfill.ts +6 -0
- package/src/peer-record.ts +159 -202
- package/src/primary-lan-address.ts +47 -0
- package/src/provider/client.ts +326 -0
- package/src/provider/descriptor.ts +129 -0
- package/src/provider/index.ts +59 -0
- package/src/provider/oauth.ts +285 -0
- package/src/provider/rent-node.ts +275 -0
- package/src/relay-addrs.ts +9 -4
- package/src/relay-reservation.ts +1211 -1121
- package/src/seed-bootstrap.ts +1502 -1471
- package/src/strand-commit-durability.ts +116 -0
- package/src/strand-database.ts +46 -14
- package/src/strand-first-sync-gate.ts +7 -1
- package/src/strand-formation-protocol.ts +1039 -1036
- package/src/strand-fret-addrs.ts +179 -145
- package/src/strand-instance-manager.ts +2026 -1883
- package/src/strand-move.ts +585 -0
- package/src/strand-network-config.ts +7 -6
- package/src/strand-peer-redial.ts +307 -0
- package/src/strand-wake-protocol.ts +10 -7
- package/src/types.ts +220 -4
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
import { durabilityRank } from '@optimystic/db-core';
|
|
3
|
+
import type { WriteDurability } from '@optimystic/db-core';
|
|
4
|
+
import type { CommitFullyHeld, SqlCommitDurability } from '@serfab/quereus-plugin-sereus';
|
|
5
|
+
|
|
6
|
+
const log = debug('sereus:cadre:strand-commit-durability');
|
|
7
|
+
|
|
8
|
+
/** What one commit on a strand wrote, and whether it survives this machine stopping for good. */
|
|
9
|
+
export interface StrandCommitDurability {
|
|
10
|
+
readonly strandId: string;
|
|
11
|
+
/** `StampId()` inside the committed transaction. */
|
|
12
|
+
readonly transactionId: string;
|
|
13
|
+
/**
|
|
14
|
+
* Another member holds every block the commit wrote: the overall answer is `majority` or `full`
|
|
15
|
+
* and no block was abandoned (`torn`). False for `local`, `unrouted`, an unknown overall, or a
|
|
16
|
+
* torn commit. Becomes true once `strand:commit-held` fires for the commit.
|
|
17
|
+
*/
|
|
18
|
+
readonly heldElsewhere: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* When {@link heldElsewhere} is false: `strand:commit-held` will fire for this commit if it
|
|
21
|
+
* reaches the others while this database stays open. False means nothing will say so.
|
|
22
|
+
*/
|
|
23
|
+
readonly followed: boolean;
|
|
24
|
+
/** The plugin's report, for per-table detail. */
|
|
25
|
+
readonly report: SqlCommitDurability;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A commit that was held only below full replication is now held by every member of its cohort. */
|
|
29
|
+
export interface StrandCommitHeld {
|
|
30
|
+
readonly strandId: string;
|
|
31
|
+
readonly transactionId: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Where a strand database forwards each commit's durability and each held commit. */
|
|
35
|
+
export interface StrandCommitListeners {
|
|
36
|
+
onCommitDurability?: (commit: StrandCommitDurability) => void;
|
|
37
|
+
onCommitHeld?: (event: StrandCommitHeld) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// NOTE: if an app reads a commit's durability later than 256 commits after it, raise this or let
|
|
41
|
+
// the app pass the id up front.
|
|
42
|
+
const RECENT_COMMIT_LIMIT = 256;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* `majority` counts: a strict majority of a cohort of at least two is at least two machines, so
|
|
46
|
+
* the write survives this one stopping. A torn commit does not, though it can succeed as `majority`
|
|
47
|
+
* (Optimystic clamps a torn `full` to it): its abandoned blocks are held by nobody. The one place
|
|
48
|
+
* this rule lives; apps read the field.
|
|
49
|
+
*/
|
|
50
|
+
function isHeldElsewhere(overall: WriteDurability | undefined): boolean {
|
|
51
|
+
return overall !== undefined
|
|
52
|
+
&& durabilityRank(overall.quorum) >= durabilityRank('majority')
|
|
53
|
+
&& (overall.torn?.length ?? 0) === 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The durability of the most recent commits on one strand database, by transaction id. The
|
|
58
|
+
* plugin reports a commit — and a `covered` commit is held — before the statement that made it
|
|
59
|
+
* resolves, so an app that learns its id from `returning StampId()` reads the answer from here
|
|
60
|
+
* afterwards. Each entry is frozen and replaced when the commit becomes held, so a value read
|
|
61
|
+
* from {@link get} never changes under its reader.
|
|
62
|
+
*/
|
|
63
|
+
export class StrandCommitRecord {
|
|
64
|
+
private readonly commits = new Map<string, StrandCommitDurability>();
|
|
65
|
+
|
|
66
|
+
constructor(
|
|
67
|
+
private readonly strandId: string,
|
|
68
|
+
private readonly listeners: StrandCommitListeners
|
|
69
|
+
) {}
|
|
70
|
+
|
|
71
|
+
get(transactionId: string): StrandCommitDurability | undefined {
|
|
72
|
+
return this.commits.get(transactionId);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** A plugin `onCommitDurability` report: record it, then forward it. */
|
|
76
|
+
reportCommit(report: SqlCommitDurability): void {
|
|
77
|
+
const commit = Object.freeze({
|
|
78
|
+
strandId: this.strandId,
|
|
79
|
+
transactionId: report.transactionId,
|
|
80
|
+
heldElsewhere: isHeldElsewhere(report.durability.overall),
|
|
81
|
+
followed: report.followed,
|
|
82
|
+
report
|
|
83
|
+
});
|
|
84
|
+
this.remember(commit);
|
|
85
|
+
this.forward('durability', report.transactionId, () => this.listeners.onCommitDurability?.(commit));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A plugin `onCommitFullyHeld` event: mark the commit held, then forward it — also for one no longer recorded. */
|
|
89
|
+
reportHeld({ transactionId }: CommitFullyHeld): void {
|
|
90
|
+
const recorded = this.commits.get(transactionId);
|
|
91
|
+
if (recorded) {
|
|
92
|
+
this.commits.set(transactionId, Object.freeze({ ...recorded, heldElsewhere: true }));
|
|
93
|
+
}
|
|
94
|
+
this.forward('held', transactionId, () => this.listeners.onCommitHeld?.({ strandId: this.strandId, transactionId }));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private remember(commit: StrandCommitDurability): void {
|
|
98
|
+
this.commits.set(commit.transactionId, commit);
|
|
99
|
+
if (this.commits.size > RECENT_COMMIT_LIMIT) {
|
|
100
|
+
const oldest = this.commits.keys().next().value!;
|
|
101
|
+
this.commits.delete(oldest);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Run an app's handler isolated: it runs inside the commit, and a throw reaching the plugin
|
|
107
|
+
* would be logged there as the plugin's own listener failing.
|
|
108
|
+
*/
|
|
109
|
+
private forward(kind: string, transactionId: string, deliver: () => void): void {
|
|
110
|
+
try {
|
|
111
|
+
deliver();
|
|
112
|
+
} catch (error) {
|
|
113
|
+
log('strand %s: %s handler threw for transaction %s: %O', this.strandId, kind, transactionId, error);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
package/src/strand-database.ts
CHANGED
|
@@ -7,11 +7,13 @@ import type { IRepo } from '@optimystic/db-core';
|
|
|
7
7
|
import type { SAppConfig } from './types.js';
|
|
8
8
|
import { bootstrapFounderMembership } from './strand-membership-writer.js';
|
|
9
9
|
import { strandMemberKeyPair } from './strand-member-key.js';
|
|
10
|
+
import { StrandCommitRecord } from './strand-commit-durability.js';
|
|
11
|
+
import type { StrandCommitDurability, StrandCommitListeners } from './strand-commit-durability.js';
|
|
10
12
|
|
|
11
13
|
const log = debug('sereus:cadre:strand-db');
|
|
12
14
|
const timing = debug('sereus:cadre:timing');
|
|
13
15
|
|
|
14
|
-
export interface StrandDatabaseConfig {
|
|
16
|
+
export interface StrandDatabaseConfig extends StrandCommitListeners {
|
|
15
17
|
/** The strand ID */
|
|
16
18
|
strandId: string;
|
|
17
19
|
/**
|
|
@@ -63,10 +65,12 @@ export interface StrandDatabaseConfig {
|
|
|
63
65
|
* but delegates the actual SQL-surface composition — plugin registration, node
|
|
64
66
|
* wiring, catalog hydration, schema apply — to `connectToStrand` from
|
|
65
67
|
* `@serfab/quereus-plugin-sereus`, the single shared composition. The libp2p
|
|
66
|
-
* node is injected here, so `connectToStrand`
|
|
67
|
-
* `StrandInstanceManager` owns the node lifecycle
|
|
68
|
-
*
|
|
69
|
-
*
|
|
68
|
+
* node is injected here, so `connectToStrand` neither creates nor stops it:
|
|
69
|
+
* `StrandInstanceManager` owns the node lifecycle, and a failed or closed
|
|
70
|
+
* database leaves the node running.
|
|
71
|
+
*
|
|
72
|
+
* One instance is one attempt: a failed {@link initialize} is closed and a new
|
|
73
|
+
* instance built for the next attempt.
|
|
70
74
|
*/
|
|
71
75
|
export class StrandDatabase {
|
|
72
76
|
private db: Database | null = null;
|
|
@@ -74,9 +78,12 @@ export class StrandDatabase {
|
|
|
74
78
|
private resolvedTransactor: SereusPluginResult['transactor'] | null = null;
|
|
75
79
|
private readonly config: StrandDatabaseConfig;
|
|
76
80
|
private initialized = false;
|
|
81
|
+
private readonly commits: StrandCommitRecord;
|
|
82
|
+
private unsubscribeCommits: (() => void) | null = null;
|
|
77
83
|
|
|
78
84
|
constructor(config: StrandDatabaseConfig) {
|
|
79
85
|
this.config = config;
|
|
86
|
+
this.commits = new StrandCommitRecord(config.strandId, config);
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
/**
|
|
@@ -103,10 +110,8 @@ export class StrandDatabase {
|
|
|
103
110
|
this.db = new Database();
|
|
104
111
|
|
|
105
112
|
// Delegate to the shared composition. The node is injected, so:
|
|
106
|
-
// - `connectToStrand` never creates a node
|
|
107
|
-
//
|
|
108
|
-
// still stop the injected node via the collection factory, so the
|
|
109
|
-
// manager's later `node.stop()` is an idempotent second stop;
|
|
113
|
+
// - `connectToStrand` never creates or stops a node; `StrandInstanceManager`
|
|
114
|
+
// owns the node lifecycle;
|
|
110
115
|
// - no `storage` is passed: the plugin consumes it only to build a local
|
|
111
116
|
// transactor's raw-storage factory or to create a node it was not given,
|
|
112
117
|
// and cadre-core always injects the node and runs the network transactor.
|
|
@@ -126,14 +131,16 @@ export class StrandDatabase {
|
|
|
126
131
|
});
|
|
127
132
|
this.shutdownStrand = result.shutdown;
|
|
128
133
|
this.resolvedTransactor = result.transactor;
|
|
134
|
+
// Before the founder bootstrap, so its commits are reported like any other.
|
|
135
|
+
this.subscribeToCommits(result);
|
|
129
136
|
timing('[strandDb:%s] connectToStrand: %dms (hydrated tables=%d, indexes=%d)',
|
|
130
137
|
sid, Math.round(performance.now() - t0),
|
|
131
138
|
result.hydrated?.tables ?? 0, result.hydrated?.indexes ?? 0);
|
|
132
139
|
|
|
133
140
|
// Founder-only: write the one-time membership bootstrap now that the schema is
|
|
134
141
|
// applied (connectToStrand has returned). A throw here propagates out of
|
|
135
|
-
// initialize()
|
|
136
|
-
//
|
|
142
|
+
// initialize(), and the caller closes this half-built database (close() runs the
|
|
143
|
+
// composition's shutdown). Joiners skip this — their rows arrive via sync.
|
|
137
144
|
if (this.config.founder === true) {
|
|
138
145
|
await this.bootstrapFounder();
|
|
139
146
|
}
|
|
@@ -142,6 +149,15 @@ export class StrandDatabase {
|
|
|
142
149
|
log('StrandDatabase for strand %s initialized successfully', sid);
|
|
143
150
|
}
|
|
144
151
|
|
|
152
|
+
private subscribeToCommits(result: SereusPluginResult): void {
|
|
153
|
+
const unsubscribeDurability = result.onCommitDurability(report => this.commits.reportCommit(report));
|
|
154
|
+
const unsubscribeHeld = result.onCommitFullyHeld(event => this.commits.reportHeld(event));
|
|
155
|
+
this.unsubscribeCommits = () => {
|
|
156
|
+
unsubscribeDurability();
|
|
157
|
+
unsubscribeHeld();
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
145
161
|
/**
|
|
146
162
|
* Run the founder membership bootstrap against the freshly-composed strand DB.
|
|
147
163
|
*
|
|
@@ -247,6 +263,19 @@ export class StrandDatabase {
|
|
|
247
263
|
return this.db!;
|
|
248
264
|
}
|
|
249
265
|
|
|
266
|
+
/**
|
|
267
|
+
* The durability of a recent commit on this database, by its `StampId()` — the id a single
|
|
268
|
+
* `insert … returning StampId() as Tx` statement returns. Read it after that statement
|
|
269
|
+
* resolves: the plugin reports the commit before the statement returns, and a commit already
|
|
270
|
+
* held everywhere is marked held by then too. `undefined` for a commit that wrote nothing, one
|
|
271
|
+
* made before this database was built, or one more than 256 commits back. Each value is a
|
|
272
|
+
* snapshot: read again after `strand:commit-held` to see the change.
|
|
273
|
+
*/
|
|
274
|
+
commitDurability(transactionId: string): StrandCommitDurability | undefined {
|
|
275
|
+
this.ensureInitialized();
|
|
276
|
+
return this.commits.get(transactionId);
|
|
277
|
+
}
|
|
278
|
+
|
|
250
279
|
/**
|
|
251
280
|
* The Optimystic transactor this strand's connection resolved to — always
|
|
252
281
|
* `'network'` here, since cadre-core passes no `transactor` option and takes
|
|
@@ -261,11 +290,14 @@ export class StrandDatabase {
|
|
|
261
290
|
|
|
262
291
|
/**
|
|
263
292
|
* Close the database and cleanup resources. Runs the strand-connection
|
|
264
|
-
* shutdown (collection-factory teardown
|
|
265
|
-
* then closes the `Database`.
|
|
266
|
-
*
|
|
293
|
+
* shutdown (collection-factory teardown; the injected node keeps running),
|
|
294
|
+
* then closes the `Database`. Safe on an instance whose {@link initialize}
|
|
295
|
+
* failed part-way.
|
|
267
296
|
*/
|
|
268
297
|
async close(): Promise<void> {
|
|
298
|
+
// First, so an event from the closing connection reaches no app handler.
|
|
299
|
+
this.unsubscribeCommits?.();
|
|
300
|
+
this.unsubscribeCommits = null;
|
|
269
301
|
if (this.shutdownStrand) {
|
|
270
302
|
await this.shutdownStrand();
|
|
271
303
|
this.shutdownStrand = null;
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
* seed writes the catalog collection (`optimystic/schema` plus two hash-named blocks,
|
|
56
56
|
* measured 2026-09-16) alone, during `connectToStrand`'s schema apply, before any peer
|
|
57
57
|
* contact. (One with a seed cannot until a seed peer answers — Optimystic refuses the
|
|
58
|
-
* absence ruling — and
|
|
58
|
+
* absence ruling — and retries its database build on a timer, `StrandInstanceManager.deferUnreachableJoin`.) That is the same "two histories under one id" shape this gate
|
|
59
59
|
* exists to prevent, and it is only safe because both sides derive byte-identical content
|
|
60
60
|
* from the same schema text. If the catalog ever carries per-machine or ordering-dependent
|
|
61
61
|
* content (optimystic's own `0.5-same-named-tables-in-two-schemas-share-storage` re-keys
|
|
@@ -294,6 +294,12 @@ export interface StrandFirstSyncGateDeps {
|
|
|
294
294
|
* Holds one launch's `StrandDatabase` until {@link strandFirstSyncComplete}. Probes are
|
|
295
295
|
* sequential (the next is scheduled only after the previous read settles), so a slow
|
|
296
296
|
* network read never stacks probes.
|
|
297
|
+
*
|
|
298
|
+
* NOTE: a probe's reads dial the founder, through the relay when that is the only route, even
|
|
299
|
+
* while the founder is unreachable: one run of `joiner-first-commit-over-slow-relay` logged 17
|
|
300
|
+
* `hop connect denied` at the relay in about 10 s, each a few frames on the joiner's relay
|
|
301
|
+
* connection. If that connection ever shows saturated while no strand member is connected, hold
|
|
302
|
+
* the probe while the strand member redial (`strand-peer-redial.ts`) reports none connected.
|
|
297
303
|
*/
|
|
298
304
|
export class StrandFirstSyncGate {
|
|
299
305
|
private readonly scheduler: TimeoutScheduler;
|