@serfab/cadre-core 0.13.0 → 1.0.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 +20 -6
- package/dist/bootstrap-peer-store-file.js +2 -2
- package/dist/bootstrap-peer-store.d.ts +32 -12
- package/dist/bootstrap-peer-store.js +41 -15
- package/dist/bootstrap-peer-store.js.map +1 -1
- package/dist/cadre-node.d.ts +558 -80
- package/dist/cadre-node.js +1060 -170
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-authorization.d.ts +2 -2
- package/dist/control-authorization.js +1 -0
- package/dist/control-authorization.js.map +1 -1
- package/dist/control-cohort.d.ts +13 -26
- package/dist/control-cohort.js +0 -26
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +299 -44
- package/dist/control-database.js +469 -69
- package/dist/control-database.js.map +1 -1
- package/dist/control-read-retry.d.ts +40 -11
- package/dist/control-read-retry.js +48 -15
- package/dist/control-read-retry.js.map +1 -1
- package/dist/control-retry.d.ts +58 -3
- package/dist/control-retry.js +58 -3
- package/dist/control-retry.js.map +1 -1
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +103 -19
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +80 -11
- package/dist/control-write-retry.js +168 -29
- package/dist/control-write-retry.js.map +1 -1
- package/dist/enrolled-machine-store.js +2 -2
- package/dist/index.d.ts +14 -7
- package/dist/index.js +33 -6
- package/dist/index.js.map +1 -1
- package/dist/node-local-snapshot.d.ts +15 -7
- package/dist/node-local-snapshot.js +23 -8
- package/dist/node-local-snapshot.js.map +1 -1
- package/dist/peer-dial.d.ts +143 -0
- package/dist/peer-dial.js +176 -0
- package/dist/peer-dial.js.map +1 -0
- package/dist/relay-addrs.d.ts +90 -52
- package/dist/relay-addrs.js +91 -59
- package/dist/relay-addrs.js.map +1 -1
- package/dist/relay-reservation.d.ts +71 -3
- package/dist/relay-reservation.js +116 -8
- package/dist/relay-reservation.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +30 -1
- package/dist/seed-bootstrap.js +62 -17
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/storage-scope.d.ts +62 -0
- package/dist/storage-scope.js +75 -0
- package/dist/storage-scope.js.map +1 -0
- package/dist/strand-database.d.ts +34 -16
- package/dist/strand-database.js +38 -24
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +152 -0
- package/dist/strand-first-sync-gate.js +208 -0
- package/dist/strand-first-sync-gate.js.map +1 -0
- package/dist/strand-formation-manager.d.ts +87 -5
- package/dist/strand-formation-manager.js +109 -5
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +21 -1
- package/dist/strand-formation-protocol.js +30 -0
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +243 -6
- package/dist/strand-instance-manager.js +470 -37
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-member-key.d.ts +20 -12
- package/dist/strand-member-key.js +20 -12
- package/dist/strand-member-key.js.map +1 -1
- package/dist/strand-member-registry.js +3 -0
- package/dist/strand-member-registry.js.map +1 -1
- package/dist/strand-membership-reconciler.d.ts +361 -0
- package/dist/strand-membership-reconciler.js +548 -0
- package/dist/strand-membership-reconciler.js.map +1 -0
- package/dist/strand-membership-writer.d.ts +131 -11
- package/dist/strand-membership-writer.js +396 -134
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-network-config.d.ts +46 -14
- package/dist/strand-network-config.js +79 -23
- package/dist/strand-network-config.js.map +1 -1
- package/dist/strand-solicitation.d.ts +10 -1
- package/dist/strand-solicitation.js +5 -0
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-wake-protocol.d.ts +11 -7
- package/dist/strand-wake-protocol.js +20 -52
- package/dist/strand-wake-protocol.js.map +1 -1
- package/dist/strand-watcher.js +6 -0
- package/dist/strand-watcher.js.map +1 -1
- package/dist/timed-step.d.ts +12 -0
- package/dist/timed-step.js +28 -0
- package/dist/timed-step.js.map +1 -0
- package/dist/timeout-scheduler.d.ts +19 -0
- package/dist/timeout-scheduler.js +22 -0
- package/dist/timeout-scheduler.js.map +1 -0
- package/dist/types.d.ts +262 -30
- package/dist/types.js.map +1 -1
- package/package.json +8 -8
- package/src/bootstrap-peer-store-file.ts +2 -2
- package/src/bootstrap-peer-store.ts +69 -27
- package/src/cadre-node.ts +7301 -6353
- package/src/control-authorization.ts +2 -1
- package/src/control-cohort.ts +122 -130
- package/src/control-database.ts +537 -69
- package/src/control-read-retry.ts +202 -167
- package/src/control-retry.ts +297 -190
- package/src/control-schema.ts +103 -19
- package/src/control-write-retry.ts +525 -383
- package/src/enrolled-machine-store.ts +2 -2
- package/src/index.ts +83 -6
- package/src/node-local-snapshot.ts +25 -8
- package/src/peer-dial.ts +215 -0
- package/src/relay-addrs.ts +458 -436
- package/src/relay-reservation.ts +951 -828
- package/src/seed-bootstrap.ts +76 -18
- package/src/storage-scope.ts +78 -0
- package/src/strand-database.ts +50 -27
- package/src/strand-first-sync-gate.ts +258 -0
- package/src/strand-formation-manager.ts +140 -5
- package/src/strand-formation-protocol.ts +43 -1
- package/src/strand-instance-manager.ts +577 -37
- package/src/strand-member-key.ts +20 -12
- package/src/strand-member-registry.ts +3 -0
- package/src/strand-membership-reconciler.ts +680 -0
- package/src/strand-membership-writer.ts +527 -189
- package/src/strand-network-config.ts +102 -24
- package/src/strand-solicitation.ts +16 -1
- package/src/strand-wake-protocol.ts +21 -66
- package/src/strand-watcher.ts +6 -0
- package/src/timed-step.ts +28 -0
- package/src/timeout-scheduler.ts +28 -0
- package/src/types.ts +1789 -1554
|
@@ -14,8 +14,19 @@ import type { ControlRetryOptions } from './control-retry.js';
|
|
|
14
14
|
* a moment later, which the degraded-cohort-member scenario measured as safe (a failed
|
|
15
15
|
* write rolls back, nothing half-commits) and effective (the next write commits in ~1 s).
|
|
16
16
|
*
|
|
17
|
-
* A write somebody actually REJECTED is never retried
|
|
18
|
-
*
|
|
17
|
+
* A write somebody actually REJECTED is never retried on the strength of the rejection itself —
|
|
18
|
+
* no matcher here claims a rejection message, at any phase, because re-presenting one would
|
|
19
|
+
* re-present a spent signature against a cohort that already said no. Rejections are re-presented
|
|
20
|
+
* anyway when they arrive inside the transactor's promise-phase `[block:` aggregate, which
|
|
21
|
+
* {@link isUncommittedTransactorAggregate} claims on the WRAPPER alone, whatever the cause inside
|
|
22
|
+
* says. Safe because nothing has committed at that phase, and deliberately kept — one of the
|
|
23
|
+
* remaining promise-phase rejections is exactly the kind a re-presentation fixes. See the
|
|
24
|
+
* accepted-tradeoff `NOTE:` there for which, and why no blanket rejection veto is wanted.
|
|
25
|
+
*
|
|
26
|
+
* A failure that says some of the write may be STORED is a different matter, and never
|
|
27
|
+
* re-presented whatever text it carries: {@link reportsPossiblyStoredWrite} vetoes it by type
|
|
28
|
+
* before any matcher runs. The one half-landed write that IS re-presented is a torn write the
|
|
29
|
+
* library marks final — not saved and unable to land ({@link isFinalTornWrite}).
|
|
19
30
|
*
|
|
20
31
|
* TWO policies ship from here, and the split is deliberate: the default
|
|
21
32
|
* ({@link isRetriableControlWriteFailure}, {@link CONTROL_WRITE_ATTEMPTS}) sits under every
|
|
@@ -63,6 +74,17 @@ export declare const SCHEMA_INIT_ATTEMPTS = 5;
|
|
|
63
74
|
* attempts, measured in `control-write-degraded-cohort-member.integration.ts`), which
|
|
64
75
|
* already exceeds this budget when attempt 1 returns — so that case is surfaced immediately
|
|
65
76
|
* and retry adds ZERO latency to the case where it cannot help.
|
|
77
|
+
*
|
|
78
|
+
* NOTE: a failed commit attempt now also pays a cancel discharge before its error returns
|
|
79
|
+
* (optimystic `TransactorSource.transact`'s catch, added by upstream
|
|
80
|
+
* `1-a-failed-attempt-must-discharge-its-own-pend`) — bounded by six rounds and
|
|
81
|
+
* `abortOrCancelTimeoutMs`, which every collection this repo opens sets to 5 s
|
|
82
|
+
* (`../optimystic/packages/quereus-plugin-optimystic/src/optimystic-adapter/collection-factory.ts`).
|
|
83
|
+
* Two failed attempts whose cancels each run their full budget would consume this whole 10 s
|
|
84
|
+
* ceiling and cut the three-attempt policy to two. Not observed — every measured round of the
|
|
85
|
+
* transient-reset case committed on attempt 3 of 3 — so this is a condition to watch, not work
|
|
86
|
+
* to do: if that case ever starts failing with `failed after 2/3 attempt(s)`, the cancel
|
|
87
|
+
* discharge is where the time went.
|
|
66
88
|
*/
|
|
67
89
|
export declare const CONTROL_WRITE_RETRY_BUDGET_MS = 10000;
|
|
68
90
|
/**
|
|
@@ -101,6 +123,42 @@ export declare const CONTROL_WRITE_RETRY_BUDGET_MS = 10000;
|
|
|
101
123
|
* token on the live aggregate; if it reddens there, an upstream reformat is the first thing to
|
|
102
124
|
* check.
|
|
103
125
|
*
|
|
126
|
+
* NOTE: the discriminator is the prefix AND the token within ONE message — never relax it to
|
|
127
|
+
* "contains `[block:`". Beyond the `get` and `pend` sites above, Optimystic renders `[block:` in
|
|
128
|
+
* one further place — a third formatter, outside the aggregate's three sites — `dischargeCancel`
|
|
129
|
+
* (`Cancel of action <id> did not discharge <n> block(s): …`), which does not raise the
|
|
130
|
+
* `Some peers did not complete:` prefix; only the conjunction keeps a cancel fault from
|
|
131
|
+
* classifying as a retriable get/pend (checked against optimystic `c56c2bd4`, 2026-09-16).
|
|
132
|
+
*
|
|
133
|
+
* NOTE: accepted tradeoff — a REJECTION raised while promises are still being collected reaches
|
|
134
|
+
* this repo wrapped in this same aggregate, and this matcher claims it on the wrapper alone,
|
|
135
|
+
* whatever the cause inside says. That re-presents such a rejection up to two more times inside
|
|
136
|
+
* the 10 s budget. Kept deliberately, and re-decided on 2026-09-17 against the current upstream
|
|
137
|
+
* behaviour rather than inherited:
|
|
138
|
+
*
|
|
139
|
+
* - Contention is no longer a rejection at all. "Another write holds this block right now"
|
|
140
|
+
* (`pending conflict`) is now a `held` verdict that counts toward neither approvals nor
|
|
141
|
+
* rejections (`validatePendOperations`,
|
|
142
|
+
* `../optimystic/packages/db-p2p/src/cluster/cluster-repo.ts`), so the rejection this tradeoff
|
|
143
|
+
* was originally written about does not arrive any more. It reaches this funnel as
|
|
144
|
+
* `SyncRetryExhaustedError` instead, which {@link reportsPossiblyStoredWrite} declines by type
|
|
145
|
+
* — upstream documents resubmitting after it as unsafe (the attempt that spent the budget may
|
|
146
|
+
* have left its log entry standing), and its collection sync has already spent ten retries on
|
|
147
|
+
* the race. The torn-write rule beside it: a `TornActionError` is re-presented only when
|
|
148
|
+
* `final` is true; any other torn write is declined.
|
|
149
|
+
* - What remains rejectable at the promise phase is stale revision, block-unavailable,
|
|
150
|
+
* membership-not-admitted, and a configured validator's refusal. Only the first is worth a
|
|
151
|
+
* retry — and it is worth one: this loop re-runs the WHOLE write body, reads included
|
|
152
|
+
* (`ControlDatabase.withWriteLock`'s contract), so attempt 2 presents a pend against the
|
|
153
|
+
* revision that made attempt 1 stale. A blanket "decline any chain reporting a validator
|
|
154
|
+
* rejection" rule would remove that retry to save two wasted attempts on the other three.
|
|
155
|
+
*
|
|
156
|
+
* Revisit if a re-presentation ever becomes expensive relative to what it buys — a rejection class
|
|
157
|
+
* that costs a full cohort round-trip to re-present, or a write body whose reads are no longer
|
|
158
|
+
* cheap — or when upstream offers a typed refusal surface to classify on instead of this text
|
|
159
|
+
* (`../optimystic/tickets/backlog/debt-a-downstream-repo-classifies-retries-by-parsing-our-error-text`).
|
|
160
|
+
* Contention is no longer the reason to revisit.
|
|
161
|
+
*
|
|
104
162
|
* An aggregate whose details came out EMPTY (possible when `formatBatchStatuses` has no
|
|
105
163
|
* batches to format) carries neither token, matches nothing here, and is not retried — an
|
|
106
164
|
* unattributable failure is not a proven non-commit.
|
|
@@ -114,16 +172,18 @@ export declare function isUncommittedTransactorAggregate(message: string): boole
|
|
|
114
172
|
* Did this control write fail because the cluster cohort did not ANSWER — i.e. is
|
|
115
173
|
* re-presenting the SAME signed write a moment later the right response?
|
|
116
174
|
*
|
|
117
|
-
* Classifies by MESSAGE, walking the `cause` chain with the shared
|
|
118
|
-
* (Quereus' own `unwrapError` underneath) —
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
175
|
+
* Classifies the transient cases by MESSAGE, walking the `cause` chain with the shared
|
|
176
|
+
* {@link chainMessages} (Quereus' own `unwrapError` underneath) — the transactor and the
|
|
177
|
+
* coordinator both throw bare `Error`s, not recognisable by type. By the time one reaches
|
|
178
|
+
* `ControlDatabase` it is wrapped in a `QuereusError` (the real chain is `QuereusError` → `Error`
|
|
179
|
+
* → `Error`), so the match must work at any depth. Anything that is not an `Error` is never
|
|
180
|
+
* retried.
|
|
123
181
|
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
182
|
+
* Two vetoes run over the whole chain before any matcher: {@link reportsPossiblyStoredWrite}
|
|
183
|
+
* (by type — a spent sync budget, a non-final torn write, a partial commit) and
|
|
184
|
+
* {@link reportsIndeterminateCommit} (by text — a commit-phase batch). A chain either veto flags,
|
|
185
|
+
* at any level, is never retried on the strength of some other level looking transient. The one
|
|
186
|
+
* typed failure claimed as retriable is a final torn write ({@link isFinalTornWrite}).
|
|
127
187
|
*
|
|
128
188
|
* NOTE: this depends on engine/transactor error TEXT. The messages producible without a
|
|
129
189
|
* network are driven from the REAL engine in `control-formation-seat-budget.spec.ts`, so
|
|
@@ -181,5 +241,14 @@ export declare const SCHEMA_INIT_RETRY_POLICY: Readonly<ControlWriteRetryOptions
|
|
|
181
241
|
* On exhaustion (attempts or budget) the LAST error is rethrown unchanged — never wrapped,
|
|
182
242
|
* so the exact messages downstream code and the degraded-cohort-member scenario assert on
|
|
183
243
|
* survive. A non-retriable failure propagates from the attempt that raised it.
|
|
244
|
+
*
|
|
245
|
+
* Either way — declined or exhausted — an `options.onAbandon` observer is notified exactly
|
|
246
|
+
* once before the rethrow (a `ControlRetryAbandonment`). Only WRITES pass one: an abandoned
|
|
247
|
+
* read throws to a caller that is awaiting it, so nothing is lost silently there
|
|
248
|
+
* (`control-read-retry.ts` says so at its own seam), while an abandoned write may have no
|
|
249
|
+
* caller at all — a background self-record republish is `void`-ed with a `debug` catch, so
|
|
250
|
+
* its failure reaches nobody unless something is listening here.
|
|
251
|
+
* `ControlDatabase.setControlWriteAbandonedListener` is the only thing that passes one in
|
|
252
|
+
* production; `retryControlWrite` itself neither supplies nor requires an observer.
|
|
184
253
|
*/
|
|
185
254
|
export declare function retryControlWrite<T>(attempt: () => Promise<T>, options?: ControlWriteRetryOptions): Promise<T>;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CoordinatorPartialCommitError, SyncRetryExhaustedError, TornActionError } from '@optimystic/db-core';
|
|
2
|
+
import { PartialCommitError } from '@optimystic/quereus-plugin-optimystic';
|
|
3
|
+
import { causeChain, chainMessages, retryControlOperation } from './control-retry.js';
|
|
2
4
|
/**
|
|
3
5
|
* Bounded retry for TRANSIENT control-write failures — the classifier and loop behind
|
|
4
6
|
* `ControlDatabase.lockedWithRetry`, the single funnel every local control write passes
|
|
@@ -14,8 +16,19 @@ import { chainMessages, retryControlOperation } from './control-retry.js';
|
|
|
14
16
|
* a moment later, which the degraded-cohort-member scenario measured as safe (a failed
|
|
15
17
|
* write rolls back, nothing half-commits) and effective (the next write commits in ~1 s).
|
|
16
18
|
*
|
|
17
|
-
* A write somebody actually REJECTED is never retried
|
|
18
|
-
*
|
|
19
|
+
* A write somebody actually REJECTED is never retried on the strength of the rejection itself —
|
|
20
|
+
* no matcher here claims a rejection message, at any phase, because re-presenting one would
|
|
21
|
+
* re-present a spent signature against a cohort that already said no. Rejections are re-presented
|
|
22
|
+
* anyway when they arrive inside the transactor's promise-phase `[block:` aggregate, which
|
|
23
|
+
* {@link isUncommittedTransactorAggregate} claims on the WRAPPER alone, whatever the cause inside
|
|
24
|
+
* says. Safe because nothing has committed at that phase, and deliberately kept — one of the
|
|
25
|
+
* remaining promise-phase rejections is exactly the kind a re-presentation fixes. See the
|
|
26
|
+
* accepted-tradeoff `NOTE:` there for which, and why no blanket rejection veto is wanted.
|
|
27
|
+
*
|
|
28
|
+
* A failure that says some of the write may be STORED is a different matter, and never
|
|
29
|
+
* re-presented whatever text it carries: {@link reportsPossiblyStoredWrite} vetoes it by type
|
|
30
|
+
* before any matcher runs. The one half-landed write that IS re-presented is a torn write the
|
|
31
|
+
* library marks final — not saved and unable to land ({@link isFinalTornWrite}).
|
|
19
32
|
*
|
|
20
33
|
* TWO policies ship from here, and the split is deliberate: the default
|
|
21
34
|
* ({@link isRetriableControlWriteFailure}, {@link CONTROL_WRITE_ATTEMPTS}) sits under every
|
|
@@ -63,6 +76,17 @@ export const SCHEMA_INIT_ATTEMPTS = 5;
|
|
|
63
76
|
* attempts, measured in `control-write-degraded-cohort-member.integration.ts`), which
|
|
64
77
|
* already exceeds this budget when attempt 1 returns — so that case is surfaced immediately
|
|
65
78
|
* and retry adds ZERO latency to the case where it cannot help.
|
|
79
|
+
*
|
|
80
|
+
* NOTE: a failed commit attempt now also pays a cancel discharge before its error returns
|
|
81
|
+
* (optimystic `TransactorSource.transact`'s catch, added by upstream
|
|
82
|
+
* `1-a-failed-attempt-must-discharge-its-own-pend`) — bounded by six rounds and
|
|
83
|
+
* `abortOrCancelTimeoutMs`, which every collection this repo opens sets to 5 s
|
|
84
|
+
* (`../optimystic/packages/quereus-plugin-optimystic/src/optimystic-adapter/collection-factory.ts`).
|
|
85
|
+
* Two failed attempts whose cancels each run their full budget would consume this whole 10 s
|
|
86
|
+
* ceiling and cut the three-attempt policy to two. Not observed — every measured round of the
|
|
87
|
+
* transient-reset case committed on attempt 3 of 3 — so this is a condition to watch, not work
|
|
88
|
+
* to do: if that case ever starts failing with `failed after 2/3 attempt(s)`, the cancel
|
|
89
|
+
* discharge is where the time went.
|
|
66
90
|
*/
|
|
67
91
|
export const CONTROL_WRITE_RETRY_BUDGET_MS = 10000;
|
|
68
92
|
/**
|
|
@@ -110,9 +134,14 @@ const COMMIT_BATCH_TOKEN = '[blocks:';
|
|
|
110
134
|
* matched ONLY with a ZERO rejection count. The same message with a non-zero count means a
|
|
111
135
|
* member actually voted no (that branch carries `membership-not-admitted` rejections), and
|
|
112
136
|
* retrying it would re-present a spent signature to a cohort that already refused it. This one
|
|
113
|
-
* is raised while collecting PROMISES, before any commit, so re-presenting it is safe.
|
|
114
|
-
*
|
|
115
|
-
*
|
|
137
|
+
* is raised while collecting PROMISES, before any commit, so re-presenting it is safe.
|
|
138
|
+
*
|
|
139
|
+
* This regex itself never matches a decisive rejection (`ValidatorRejectionError`, `Transaction
|
|
140
|
+
* rejected by validators`) — that text carries no `Failed to get super-majority:` prefix. But the
|
|
141
|
+
* composite classifier is not this matcher alone: a rejection raised while promises are still
|
|
142
|
+
* being collected reaches this repo wrapped in the transactor's `Some peers did not complete:
|
|
143
|
+
* …[block:…]` aggregate, and {@link isUncommittedTransactorAggregate} matches that wrapper on its
|
|
144
|
+
* own, whatever the cause inside it says — see the accepted-tradeoff `NOTE:` there.
|
|
116
145
|
*/
|
|
117
146
|
const SUPER_MAJORITY_SHORTFALL_UNANSWERED = /Failed to get super-majority: \d+\/\d+ approvals \(needed \d+, 0 rejections\)/;
|
|
118
147
|
/**
|
|
@@ -151,6 +180,42 @@ const SUPER_MAJORITY_SHORTFALL_UNANSWERED = /Failed to get super-majority: \d+\/
|
|
|
151
180
|
* token on the live aggregate; if it reddens there, an upstream reformat is the first thing to
|
|
152
181
|
* check.
|
|
153
182
|
*
|
|
183
|
+
* NOTE: the discriminator is the prefix AND the token within ONE message — never relax it to
|
|
184
|
+
* "contains `[block:`". Beyond the `get` and `pend` sites above, Optimystic renders `[block:` in
|
|
185
|
+
* one further place — a third formatter, outside the aggregate's three sites — `dischargeCancel`
|
|
186
|
+
* (`Cancel of action <id> did not discharge <n> block(s): …`), which does not raise the
|
|
187
|
+
* `Some peers did not complete:` prefix; only the conjunction keeps a cancel fault from
|
|
188
|
+
* classifying as a retriable get/pend (checked against optimystic `c56c2bd4`, 2026-09-16).
|
|
189
|
+
*
|
|
190
|
+
* NOTE: accepted tradeoff — a REJECTION raised while promises are still being collected reaches
|
|
191
|
+
* this repo wrapped in this same aggregate, and this matcher claims it on the wrapper alone,
|
|
192
|
+
* whatever the cause inside says. That re-presents such a rejection up to two more times inside
|
|
193
|
+
* the 10 s budget. Kept deliberately, and re-decided on 2026-09-17 against the current upstream
|
|
194
|
+
* behaviour rather than inherited:
|
|
195
|
+
*
|
|
196
|
+
* - Contention is no longer a rejection at all. "Another write holds this block right now"
|
|
197
|
+
* (`pending conflict`) is now a `held` verdict that counts toward neither approvals nor
|
|
198
|
+
* rejections (`validatePendOperations`,
|
|
199
|
+
* `../optimystic/packages/db-p2p/src/cluster/cluster-repo.ts`), so the rejection this tradeoff
|
|
200
|
+
* was originally written about does not arrive any more. It reaches this funnel as
|
|
201
|
+
* `SyncRetryExhaustedError` instead, which {@link reportsPossiblyStoredWrite} declines by type
|
|
202
|
+
* — upstream documents resubmitting after it as unsafe (the attempt that spent the budget may
|
|
203
|
+
* have left its log entry standing), and its collection sync has already spent ten retries on
|
|
204
|
+
* the race. The torn-write rule beside it: a `TornActionError` is re-presented only when
|
|
205
|
+
* `final` is true; any other torn write is declined.
|
|
206
|
+
* - What remains rejectable at the promise phase is stale revision, block-unavailable,
|
|
207
|
+
* membership-not-admitted, and a configured validator's refusal. Only the first is worth a
|
|
208
|
+
* retry — and it is worth one: this loop re-runs the WHOLE write body, reads included
|
|
209
|
+
* (`ControlDatabase.withWriteLock`'s contract), so attempt 2 presents a pend against the
|
|
210
|
+
* revision that made attempt 1 stale. A blanket "decline any chain reporting a validator
|
|
211
|
+
* rejection" rule would remove that retry to save two wasted attempts on the other three.
|
|
212
|
+
*
|
|
213
|
+
* Revisit if a re-presentation ever becomes expensive relative to what it buys — a rejection class
|
|
214
|
+
* that costs a full cohort round-trip to re-present, or a write body whose reads are no longer
|
|
215
|
+
* cheap — or when upstream offers a typed refusal surface to classify on instead of this text
|
|
216
|
+
* (`../optimystic/tickets/backlog/debt-a-downstream-repo-classifies-retries-by-parsing-our-error-text`).
|
|
217
|
+
* Contention is no longer the reason to revisit.
|
|
218
|
+
*
|
|
154
219
|
* An aggregate whose details came out EMPTY (possible when `formatBatchStatuses` has no
|
|
155
220
|
* batches to format) carries neither token, matches nothing here, and is not retried — an
|
|
156
221
|
* unattributable failure is not a proven non-commit.
|
|
@@ -177,6 +242,59 @@ export function isUncommittedTransactorAggregate(message) {
|
|
|
177
242
|
function reportsIndeterminateCommit(messages) {
|
|
178
243
|
return messages.some(message => message.includes(COMMIT_BATCH_TOKEN));
|
|
179
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Does any link of the `cause` chain say some of this write may be stored, or may still be?
|
|
247
|
+
* Such a failure is never re-presented — a re-run could store the write twice.
|
|
248
|
+
*
|
|
249
|
+
* - `SyncRetryExhaustedError` (and its `SyncRevisionStalledError` subclass): upstream documents
|
|
250
|
+
* resubmitting as unsafe, because the attempt that spent the budget may have left its log
|
|
251
|
+
* entry standing.
|
|
252
|
+
* - `TornActionError` without `final: true`: the write's log entry is stored and nobody could
|
|
253
|
+
* establish that the rest can never land — it may be saved already, or land later.
|
|
254
|
+
* - `CoordinatorPartialCommitError` / `PartialCommitError`: another collection or tree of the
|
|
255
|
+
* same transaction is already durable, and re-running the whole body would apply it twice.
|
|
256
|
+
*
|
|
257
|
+
* Every one of these embeds text from elsewhere in its message (a responder's refusal, the
|
|
258
|
+
* underlying failure), which can carry the pend-phase aggregate's prefix and `[block:` token —
|
|
259
|
+
* so this veto runs before any text matcher.
|
|
260
|
+
*
|
|
261
|
+
* Matched by TYPE: each survives the bridge's and Quereus' rewraps on `cause`. An error built by a
|
|
262
|
+
* second loaded copy of `@optimystic/db-core` (or of the plugin, for `PartialCommitError`) fails
|
|
263
|
+
* `instanceof`, and then this veto does not fire — falling back to the text classifiers, which is
|
|
264
|
+
* how these failures were classified before the veto existed. The asymmetry with
|
|
265
|
+
* {@link isFinalTornWrite} is deliberate: there, a missed `instanceof` means no retry, which is the
|
|
266
|
+
* safe side. No text fallback parses `TornActionError`'s closing sentence, since upstream says its
|
|
267
|
+
* wording is for log lines only.
|
|
268
|
+
*
|
|
269
|
+
* NOTE: the plugin is registered through its `/plugin` entry (`control-database.ts`) but
|
|
270
|
+
* `PartialCommitError` is imported from its root entry; the two are one class only because the
|
|
271
|
+
* plugin's build emits both entries over one shared chunk. If upstream ever bundles the entries
|
|
272
|
+
* separately, this `instanceof` stops matching with every spec still green — import the class from
|
|
273
|
+
* the entry the plugin is registered through.
|
|
274
|
+
*/
|
|
275
|
+
function reportsPossiblyStoredWrite(links) {
|
|
276
|
+
return links.some(link => link instanceof SyncRetryExhaustedError
|
|
277
|
+
|| (link instanceof TornActionError && link.final !== true)
|
|
278
|
+
|| link instanceof CoordinatorPartialCommitError
|
|
279
|
+
|| link instanceof PartialCommitError);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* A torn write the library marks FINAL: not saved, unable to land, its pending records confirmed
|
|
283
|
+
* cancelled — so submitting it again stores it once.
|
|
284
|
+
*
|
|
285
|
+
* Worth re-presenting, not merely safe to: upstream raises it mostly when a rival holds the
|
|
286
|
+
* revision this write's log entry claimed, which is contention, and the loop re-runs the write
|
|
287
|
+
* body's reads, so the next attempt builds on the rival's revision — the same argument that keeps
|
|
288
|
+
* stale-revision rejections retried (the accepted-tradeoff `NOTE:` on
|
|
289
|
+
* {@link isUncommittedTransactorAggregate}).
|
|
290
|
+
*
|
|
291
|
+
* NOTE: `CoordinatorStaleLossError` (db-core; "nothing durably committed, safe to re-drive") is not
|
|
292
|
+
* claimed, so it falls to the text matchers; it escapes only after the coordinator's own retry budget
|
|
293
|
+
* ran out. If control writes are seen abandoned on it, claim it here by type beside this one.
|
|
294
|
+
*/
|
|
295
|
+
function isFinalTornWrite(link) {
|
|
296
|
+
return link instanceof TornActionError && link.final === true;
|
|
297
|
+
}
|
|
180
298
|
/** The coordinator's shortfall, raised pre-commit, with nobody having voted no. */
|
|
181
299
|
function isUnansweredSuperMajorityShortfall(message) {
|
|
182
300
|
return SUPER_MAJORITY_SHORTFALL_UNANSWERED.test(message);
|
|
@@ -193,13 +311,14 @@ function isUnansweredSuperMajorityShortfall(message) {
|
|
|
193
311
|
* one connection again, which a cold-starting node dialling its bootstrap peers is actively
|
|
194
312
|
* working on.
|
|
195
313
|
*
|
|
196
|
-
* NOTE: matched by TEXT, not by type
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
* `
|
|
200
|
-
* (`quereus-plugin-optimystic/src/optimystic-module.ts`)
|
|
201
|
-
*
|
|
202
|
-
*
|
|
314
|
+
* NOTE: matched by TEXT, not by type. `FindCoordinatorError` (`code: 'SELF_COORDINATION_BLOCKED'`)
|
|
315
|
+
* is exported from `@optimystic/db-p2p`, and when this matcher was written the error OBJECT did not
|
|
316
|
+
* survive the trip: `OptimysticVirtualTable.initialize` rethrew as `new Error(message)` with no
|
|
317
|
+
* `cause`. It now rethrows through `rewrapAsQueryError`, which keeps `cause`
|
|
318
|
+
* (`quereus-plugin-optimystic/src/optimystic-module.ts`); whether every Quereus wrapper above it on
|
|
319
|
+
* the schema-init path keeps it too has not been checked, and the text still arrives either way.
|
|
320
|
+
* Like every matcher here this fails CLOSED — a rewording upstream stops the retry engaging, it
|
|
321
|
+
* never makes it unsafe.
|
|
203
322
|
*/
|
|
204
323
|
const SELF_COORDINATION_GRACE_REFUSAL = /Self-coordination blocked: grace-period-not-elapsed\. No coordinator available for key\./;
|
|
205
324
|
/** Self-coordination refused because the node's last connection dropped moments ago. */
|
|
@@ -246,16 +365,18 @@ const RETRIABLE_SCHEMA_INIT_MATCHERS = [
|
|
|
246
365
|
* Did this control write fail because the cluster cohort did not ANSWER — i.e. is
|
|
247
366
|
* re-presenting the SAME signed write a moment later the right response?
|
|
248
367
|
*
|
|
249
|
-
* Classifies by MESSAGE, walking the `cause` chain with the shared
|
|
250
|
-
* (Quereus' own `unwrapError` underneath) —
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
368
|
+
* Classifies the transient cases by MESSAGE, walking the `cause` chain with the shared
|
|
369
|
+
* {@link chainMessages} (Quereus' own `unwrapError` underneath) — the transactor and the
|
|
370
|
+
* coordinator both throw bare `Error`s, not recognisable by type. By the time one reaches
|
|
371
|
+
* `ControlDatabase` it is wrapped in a `QuereusError` (the real chain is `QuereusError` → `Error`
|
|
372
|
+
* → `Error`), so the match must work at any depth. Anything that is not an `Error` is never
|
|
373
|
+
* retried.
|
|
255
374
|
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
375
|
+
* Two vetoes run over the whole chain before any matcher: {@link reportsPossiblyStoredWrite}
|
|
376
|
+
* (by type — a spent sync budget, a non-final torn write, a partial commit) and
|
|
377
|
+
* {@link reportsIndeterminateCommit} (by text — a commit-phase batch). A chain either veto flags,
|
|
378
|
+
* at any level, is never retried on the strength of some other level looking transient. The one
|
|
379
|
+
* typed failure claimed as retriable is a final torn write ({@link isFinalTornWrite}).
|
|
259
380
|
*
|
|
260
381
|
* NOTE: this depends on engine/transactor error TEXT. The messages producible without a
|
|
261
382
|
* network are driven from the REAL engine in `control-formation-seat-budget.spec.ts`, so
|
|
@@ -266,7 +387,7 @@ const RETRIABLE_SCHEMA_INIT_MATCHERS = [
|
|
|
266
387
|
* aggregate from a real stream reset, which the retry there absorbs end to end.
|
|
267
388
|
*/
|
|
268
389
|
export function isRetriableControlWriteFailure(error) {
|
|
269
|
-
return
|
|
390
|
+
return matchesRetriableFailure(error, RETRIABLE_CONTROL_WRITE_MATCHERS);
|
|
270
391
|
}
|
|
271
392
|
/**
|
|
272
393
|
* {@link isRetriableControlWriteFailure} widened by exactly one class — the self-coordination
|
|
@@ -276,22 +397,31 @@ export function isRetriableControlWriteFailure(error) {
|
|
|
276
397
|
* default classifier untouched.
|
|
277
398
|
*/
|
|
278
399
|
export function isRetriableSchemaInitFailure(error) {
|
|
279
|
-
return
|
|
400
|
+
return matchesRetriableFailure(error, RETRIABLE_SCHEMA_INIT_MATCHERS);
|
|
280
401
|
}
|
|
281
402
|
/**
|
|
282
|
-
* Shared body of both classifiers: walk the `cause` chain, let
|
|
283
|
-
* {@link reportsIndeterminateCommit} veto it, then
|
|
284
|
-
*
|
|
285
|
-
*
|
|
403
|
+
* Shared body of both classifiers: walk the `cause` chain, let {@link reportsPossiblyStoredWrite}
|
|
404
|
+
* and then {@link reportsIndeterminateCommit} veto it, then claim a final torn write
|
|
405
|
+
* ({@link isFinalTornWrite}), then ask `matchers`. The vetoes run BEFORE any matcher for every
|
|
406
|
+
* policy — a chain reporting a possibly-stored write or a commit-phase batch is never retried,
|
|
407
|
+
* however transient some other level looks, and a final torn write carried by a partial commit is
|
|
408
|
+
* still declined because a sibling collection landed.
|
|
286
409
|
*/
|
|
287
|
-
function
|
|
410
|
+
function matchesRetriableFailure(error, matchers) {
|
|
288
411
|
if (!(error instanceof Error)) {
|
|
289
412
|
return false;
|
|
290
413
|
}
|
|
414
|
+
const links = causeChain(error);
|
|
415
|
+
if (reportsPossiblyStoredWrite(links)) {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
291
418
|
const messages = chainMessages(error);
|
|
292
419
|
if (reportsIndeterminateCommit(messages)) {
|
|
293
420
|
return false;
|
|
294
421
|
}
|
|
422
|
+
if (links.some(isFinalTornWrite)) {
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
295
425
|
return messages.some(message => matchers.some(matches => matches(message)));
|
|
296
426
|
}
|
|
297
427
|
/**
|
|
@@ -326,6 +456,15 @@ export const SCHEMA_INIT_RETRY_POLICY = {
|
|
|
326
456
|
* On exhaustion (attempts or budget) the LAST error is rethrown unchanged — never wrapped,
|
|
327
457
|
* so the exact messages downstream code and the degraded-cohort-member scenario assert on
|
|
328
458
|
* survive. A non-retriable failure propagates from the attempt that raised it.
|
|
459
|
+
*
|
|
460
|
+
* Either way — declined or exhausted — an `options.onAbandon` observer is notified exactly
|
|
461
|
+
* once before the rethrow (a `ControlRetryAbandonment`). Only WRITES pass one: an abandoned
|
|
462
|
+
* read throws to a caller that is awaiting it, so nothing is lost silently there
|
|
463
|
+
* (`control-read-retry.ts` says so at its own seam), while an abandoned write may have no
|
|
464
|
+
* caller at all — a background self-record republish is `void`-ed with a `debug` catch, so
|
|
465
|
+
* its failure reaches nobody unless something is listening here.
|
|
466
|
+
* `ControlDatabase.setControlWriteAbandonedListener` is the only thing that passes one in
|
|
467
|
+
* production; `retryControlWrite` itself neither supplies nor requires an observer.
|
|
329
468
|
*/
|
|
330
469
|
export function retryControlWrite(attempt, options = {}) {
|
|
331
470
|
return retryControlOperation(attempt, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control-write-retry.js","sourceRoot":"","sources":["../src/control-write-retry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"control-write-retry.js","sourceRoot":"","sources":["../src/control-write-retry.ts"],"names":[],"mappings":"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"}
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
* `Memory*` / `Persistent*` / `File*` split, same party scoping. It does NOT share
|
|
42
42
|
* their `NodeLocalSnapshot` machinery, for two reasons:
|
|
43
43
|
*
|
|
44
|
-
* - **Shape.** `NodeLocalSnapshot` is a `key -> entry` map
|
|
45
|
-
*
|
|
44
|
+
* - **Shape.** `NodeLocalSnapshot` is a `key -> entry` map. This record is one
|
|
45
|
+
* integer that must be able to go DOWN after a machine is removed.
|
|
46
46
|
* - **Load policy — the important one.** `NodeLocalSnapshot.open` THROWS when the
|
|
47
47
|
* slot is present but unreadable. That is right for the anchor (a record that
|
|
48
48
|
* cannot be read in full is not a trust anchor) and right for the peer store (a
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
2
|
export { canonicalJson } from './canonical-json.js';
|
|
3
3
|
export { CadreNode } from './cadre-node.js';
|
|
4
|
-
export { ControlDatabase, MissingHostStrandError, buildAuthorizationMessage, formationVouchMessage, formationConsentMessage, REAPABLE_TABLES, type ReapableTable, type ControlDatabaseConfig, type ControlTable, type RevocableTable, type FormationUsageResult, type MembershipChangeListener, type GuardedDeleteListener, type RevokedRowRef } from './control-database.js';
|
|
4
|
+
export { ControlDatabase, MissingHostStrandError, buildAuthorizationMessage, formationVouchMessage, formationConsentMessage, REAPABLE_TABLES, type ReapableTable, type ControlDatabaseConfig, type ControlTable, type RevocableTable, type FormationUsageResult, type MembershipChangeListener, type GuardedDeleteListener, type ControlWriteAbandonedListener, type RevokedRowRef } from './control-database.js';
|
|
5
5
|
export { CONTROL_WRITE_ATTEMPTS, CONTROL_WRITE_RETRY_BUDGET_MS, SCHEMA_INIT_ATTEMPTS, SCHEMA_INIT_RETRY_POLICY, isRetriableControlWriteFailure, isRetriableSchemaInitFailure, retryControlWrite, type ControlWriteRetryOptions } from './control-write-retry.js';
|
|
6
6
|
export { CONTROL_READ_ATTEMPTS, CONTROL_READ_RETRY_DELAYS_MS, CONTROL_READ_RETRY_BUDGET_MS, isRetriableControlReadFailure, retryControlRead, type ControlReadRetryOptions } from './control-read-retry.js';
|
|
7
|
+
export type { ControlRetryAbandonment, ControlRetryAbandonReason, ControlRetryAbandonListener } from './control-retry.js';
|
|
7
8
|
export { controlAuthorizationFields, type ControlDomain, type ControlAction } from './control-authorization.js';
|
|
8
9
|
export { ed25519KeyPairFromLibp2p, ed25519PublicKeyFromPrivate, requireEd25519PublicKeyB64, type Ed25519KeyPair } from './ed25519-key.js';
|
|
9
10
|
export { InMemoryKeyStore, KeyStoreAccessError, DEFAULT_IDENTITY_KEY_ID, type KeyStore, type KeyId } from './key-store.js';
|
|
@@ -12,11 +13,15 @@ export { MemoryTrustedOwnerStore, PersistentTrustedOwnerStore, type TrustedOwner
|
|
|
12
13
|
export type { DurableSlot } from './node-local-snapshot.js';
|
|
13
14
|
export { MemoryBootstrapPeerStore, PersistentBootstrapPeerStore, type BootstrapPeerStore, type BootstrapPeerEntry } from './bootstrap-peer-store.js';
|
|
14
15
|
export { MemoryEnrolledMachineStore, PersistentEnrolledMachineStore, type EnrolledMachineStore } from './enrolled-machine-store.js';
|
|
16
|
+
export { controlStorageScope, isControlStorageScope } from './storage-scope.js';
|
|
15
17
|
export { StrandDatabase, type StrandDatabaseConfig } from './strand-database.js';
|
|
16
18
|
export { StrandWatcher, type StrandWatcherCallbacks, type StrandQueryable, type SAppIdLookup, type StrandRowWithApp } from './strand-watcher.js';
|
|
17
|
-
export { StrandInstanceManager, type StartStrandConfig, type ResumeStrandOverrides, getStrandStoragePath } from './strand-instance-manager.js';
|
|
19
|
+
export { StrandInstanceManager, type StartStrandConfig, type ResumeStrandOverrides, getStrandStoragePath, isAwaitingFirstSync, liveStrandStatus } from './strand-instance-manager.js';
|
|
20
|
+
export { StrandAwaitingFirstSyncError, DEFAULT_STRAND_FIRST_SYNC_TIMEOUT_MS, DEFAULT_STRAND_FIRST_SYNC_POLL_MS, strandHeaderHeld, appTablesReadable, strandFirstSyncComplete, type StrandFirstSyncConfig } from './strand-first-sync-gate.js';
|
|
18
21
|
export { PeerJoinBackfill, DEFAULT_PEER_JOIN_BACKFILL, MAX_BLOCK_MESSAGE_BYTES, type PeerJoinBackfillConfig, type PeerJoinBackfillDeps, type PeerJoinBackfillResult, type PeerJoinBackfillPushClient } from './peer-join-backfill.js';
|
|
19
22
|
export { StrandRevocationEnforcer, createRevocationConnectionGater, readStrandRevocationRows, DEFAULT_REVOCATION_POLL_INTERVAL_MS, type StrandRevocationEnforcementConfig, type StrandRevocationEnforcerDeps, type StrandRevocationRows, type StrandMemberPeerBinding, type StrandRevocationJudge, type StrandRevocationNetwork, type RevocationRefreshScheduler } from './strand-revocation-enforcer.js';
|
|
23
|
+
export { StrandMembershipReconciler, IDLE_PASSES_BEFORE_ESCALATION, INITIAL_JOIN_RETRY_INTERVAL_MS, type StrandMembershipReconcilerDeps, type StrandMembershipReconciliationConfig, type PendingMembershipInviteSource } from './strand-membership-reconciler.js';
|
|
24
|
+
export { defaultTimeoutScheduler, type TimeoutScheduler } from './timeout-scheduler.js';
|
|
20
25
|
export { HibernationManager, type HibernationCallbacks } from './hibernation-manager.js';
|
|
21
26
|
export { ArachnodeStub, createArachnodeStub, type RingConfig } from './arachnode-stub.js';
|
|
22
27
|
export { EnrollmentService, type MemberVerifier, type MemberRegistry } from './enrollment.js';
|
|
@@ -24,7 +29,7 @@ export { StrandSolicitationService, createDefaultFormationResponseValidator, typ
|
|
|
24
29
|
export { createHttpFormationApprover, signFormationApproval, verifyFormationApproval, FormationApprovalError, type FormationApprovalRequest, type FormationVouchFields, type FormationApproval, type FormationApprover, type FormationApprovalFailure } from './formation-approval.js';
|
|
25
30
|
export { ControlFormationUsageRecorder } from './control-formation-recorder.js';
|
|
26
31
|
export { generateStrandMemberKey, strandMemberKeyPair } from './strand-member-key.js';
|
|
27
|
-
export { signStrandPayload, verifyStrandPayload, signStrandApproval, generateStrandStampId, bootstrapFounderMembership, issueInvite, consumeInvite, cancelInvite, listOutstandingInvites, addMemberByManager, revokeMember, leaveStrand, registerMemberPeer, listMemberPeers, removeMemberPeer, addManager, admitManager, removeManager, sealStrand, isStrandSealed, STRAND_ENGINE, STRAND_ENGINE_VERSION, type FounderBootstrapParams, type IssueInviteParams, type IssuedInvite, type ConsumeInviteParams, type CancelInviteParams, type OutstandingInvite, type AddMemberByManagerParams, type RevokeMemberParams, type LeaveStrandParams, type RegisterMemberPeerParams, type RemoveOwnPeerParams, type RemoveMemberPeerByManagerParams, type RemoveMemberPeerParams, type AddManagerParams, type AdmitManagerParams, type RemoveManagerParams, type SealStrandParams } from './strand-membership-writer.js';
|
|
32
|
+
export { signStrandPayload, verifyStrandPayload, signStrandApproval, generateStrandStampId, bootstrapFounderMembership, PreSplitStrandIdentityError, issueInvite, consumeInvite, burnInvite, isStrandMember, cancelInvite, listOutstandingInvites, addMemberByManager, revokeMember, leaveStrand, registerMemberPeer, listMemberPeers, removeMemberPeer, addManager, admitManager, removeManager, sealStrand, isStrandSealed, StrandTransactionBusyError, STRAND_ENGINE, STRAND_ENGINE_VERSION, type FounderBootstrapParams, type IssueInviteParams, type IssuedInvite, type ConsumeInviteParams, type CancelInviteParams, type OutstandingInvite, type AddMemberByManagerParams, type RevokeMemberParams, type LeaveStrandParams, type RegisterMemberPeerParams, type RemoveOwnPeerParams, type RemoveMemberPeerByManagerParams, type RemoveMemberPeerParams, type AddManagerParams, type AdmitManagerParams, type RemoveManagerParams, type SealStrandParams, type StrandWriteOptions } from './strand-membership-writer.js';
|
|
28
33
|
export { canonicalDatetime } from './canonical-datetime.js';
|
|
29
34
|
export { StrandMemberRegistry, StrandMemberVerifier, memberRegistrationPayload, type StrandAdmission } from './strand-member-registry.js';
|
|
30
35
|
export { SeedBootstrapService, SEED_PROTOCOL, ed25519PublicKeyB64FromPeerId, type SeedBootstrapConfig, type SeedEventCallbacks } from './seed-bootstrap.js';
|
|
@@ -37,12 +42,14 @@ export { STRAND_WAKE_TYPE, type StrandWakePayload } from './strand-wake-payload.
|
|
|
37
42
|
export type { PushNotifier, PushMessage, PushSendResult } from './push-notifier.js';
|
|
38
43
|
export { validatePushCredentials, redactPushCredentials, REDACTED } from './push-credentials.js';
|
|
39
44
|
export { PushFanoutService, DEFAULT_PUSH_COOLDOWN_MS, DEFAULT_PUSH_DEBOUNCE_MS, type PushFanoutOptions, type FanoutMember } from './push-fanout.js';
|
|
40
|
-
export { selectControlCohortDials,
|
|
45
|
+
export { selectControlCohortDials, DEFAULT_CONTROL_COHORT_RECONCILE_MS, DEFAULT_CONTROL_COHORT_TARGET_DEGREE, type ControlCohortSelection, type ControlCohortReconcileResult } from './control-cohort.js';
|
|
46
|
+
export { dialPeerAddrs, tryAddrsInTurn, directBeforeRelayed, DEFAULT_CONTROL_COHORT_DIAL_TIMEOUT_MS, DEFAULT_CONTROL_COHORT_PER_ADDRESS_DIAL_TIMEOUT_MS, DEFAULT_PEER_DIAL_BUDGET, type PeerDialBudget, type AddrDialer } from './peer-dial.js';
|
|
41
47
|
export { anchoredTrustPolicy, pinnedKeyTrustPolicy, tofuTrustPolicy, type SeedTrustPolicy, type SeedTrustContext, type SeedTrustDecision } from './seed-trust-policy.js';
|
|
42
48
|
export { signSchema, verifySchema, assertSchemaSignature, SchemaVerificationError } from './schema-verification.js';
|
|
43
|
-
export { FormationListener, dialFormation, isValidResponderCreatesResult, sanitizeStrandAddrs, FORMATION_PROTOCOL, type FormationParty, type FormationContactMessage, type FormationResultMessage, type FormationProvisionResult, type FormationStrandInfo, type FormationDbConnectionInfo, type FormationListenerOptions, type FormationDialOptions, type FormationDialResult, type ResponderProvisionOutcome } from './strand-formation-protocol.js';
|
|
44
|
-
export { StrandFormationManager, createStrandFormationManager, type StrandFormationManagerConfig, type StrandFormationManagerOptions } from './strand-formation-manager.js';
|
|
49
|
+
export { FormationListener, dialFormation, isValidResponderCreatesResult, isWellFormedMembershipInvite, sanitizeStrandAddrs, FORMATION_PROTOCOL, type FormationParty, type FormationContactMessage, type FormationResultMessage, type FormationProvisionResult, type FormationStrandInfo, type FormationDbConnectionInfo, type FormationListenerOptions, type FormationDialOptions, type FormationDialResult, type ResponderProvisionOutcome } from './strand-formation-protocol.js';
|
|
50
|
+
export { StrandFormationManager, createStrandFormationManager, MEMBERSHIP_INVITE_TTL_MS, MEMBERSHIP_INVITE_UNAVAILABLE_REASON, HOST_STRAND_MUST_BE_RECREATED_REASON, type StrandFormationManagerConfig, type StrandFormationManagerOptions } from './strand-formation-manager.js';
|
|
45
51
|
export { createMembershipConnectionGater, STRANGER_OPEN_PROTOCOLS, DEFAULT_ENROLLMENT_WINDOW_MS, ADMISSION_DECISION_TIMEOUT_MS, RELAY_ADMISSION_RESERVE_DEADLINE_MS, MAX_UNAUTHORIZED_RELAY_RESERVATIONS, UNAUTHORIZED_RESERVATION_TTL_MS, UnauthorizedReservationBudget, type InboundAdmissionPolicy, type InboundConnectionVerdict } from './membership-connection-gater.js';
|
|
46
|
-
export { RelayReservationFailedError, UnbindableListenAddressError, RELAY_SEARCH_LISTEN_ADDR } from './relay-addrs.js';
|
|
52
|
+
export { RelayReservationFailedError, UnbindableListenAddressError, RELAY_SEARCH_LISTEN_ADDR, resolveListenAddrs } from './relay-addrs.js';
|
|
53
|
+
export { strandNodeAddrs, type StrandNodeAddrs } from './strand-network-config.js';
|
|
47
54
|
export { circuitMultiaddrs, superviseRelayReservation, DEFAULT_RELAY_RESERVE_TIMEOUT_MS, DEFAULT_RELAY_RESERVE_POLL_MS, DEFAULT_RELAY_CHECK_MS, DEFAULT_RELAY_MIN_BACKOFF_MS, DEFAULT_RELAY_MAX_BACKOFF_MS, type RelayReservationStatus, type RelayReservationState, type RelayReserveOptions, type RelayReservationSupervisor, type RelayReservationSupervisorOptions } from './relay-reservation.js';
|
|
48
55
|
export { classifyTransport, classifyConnectionPath, summarizeConnectionPaths, emptyConnectionPathSummary, DEFAULT_SETTLE_WINDOW_MS, type ConnectionPathKind, type ConnectionTransport, type ConnectionPath, type ConnectionPathSummary, type ConnectionLike } from './diagnostics/connection-path.js';
|
package/dist/index.js
CHANGED
|
@@ -49,11 +49,19 @@ export { MemoryBootstrapPeerStore, PersistentBootstrapPeerStore } from './bootst
|
|
|
49
49
|
// DIVERGES from theirs (an unreadable slot cold-starts rather than throwing) —
|
|
50
50
|
// see the module comment before unifying the three.
|
|
51
51
|
export { MemoryEnrolledMachineStore, PersistentEnrolledMachineStore } from './enrolled-machine-store.js';
|
|
52
|
+
// Storage scope keys — what `CadreNodeConfig.storage.provider` is called with. A
|
|
53
|
+
// strand's key is its strand id; the control database's key carries the party id,
|
|
54
|
+
// so two parties on one device never share a control store.
|
|
55
|
+
export { controlStorageScope, isControlStorageScope } from './storage-scope.js';
|
|
52
56
|
// Strand database
|
|
53
57
|
export { StrandDatabase } from './strand-database.js';
|
|
54
58
|
// Strand management
|
|
55
59
|
export { StrandWatcher } from './strand-watcher.js';
|
|
56
|
-
export { StrandInstanceManager, getStrandStoragePath } from './strand-instance-manager.js';
|
|
60
|
+
export { StrandInstanceManager, getStrandStoragePath, isAwaitingFirstSync, liveStrandStatus } from './strand-instance-manager.js';
|
|
61
|
+
// The joining machine's first-sync write gate: a non-founder launch withholds its
|
|
62
|
+
// database (status 'syncing') until the strand's Header has arrived from a peer, so a
|
|
63
|
+
// write before the first sync can never fork the strand's tables.
|
|
64
|
+
export { StrandAwaitingFirstSyncError, DEFAULT_STRAND_FIRST_SYNC_TIMEOUT_MS, DEFAULT_STRAND_FIRST_SYNC_POLL_MS, strandHeaderHeld, appTablesReadable, strandFirstSyncComplete } from './strand-first-sync-gate.js';
|
|
57
65
|
// Peer-join block catch-up (push this network's own blocks to each newly
|
|
58
66
|
// connected peer, so a late joiner physically holds pre-join blocks). Shared
|
|
59
67
|
// by the strand networks (ungated) and the control network (membership-gated).
|
|
@@ -63,6 +71,14 @@ export { PeerJoinBackfill, DEFAULT_PEER_JOIN_BACKFILL, MAX_BLOCK_MESSAGE_BYTES }
|
|
|
63
71
|
// already hold — see the module doc for the settled deny-list design, the
|
|
64
72
|
// teardown seam, and the fail directions).
|
|
65
73
|
export { StrandRevocationEnforcer, createRevocationConnectionGater, readStrandRevocationRows, DEFAULT_REVOCATION_POLL_INTERVAL_MS } from './strand-revocation-enforcer.js';
|
|
74
|
+
// Closed-strand membership reconciliation: the bring-up loop that finishes a
|
|
75
|
+
// party's join on every machine — redeem the staged formation invitation, then
|
|
76
|
+
// write the durable machine→party MemberPeer binding — without ever blocking
|
|
77
|
+
// bring-up (see the module doc for the pass ladder and terminal states).
|
|
78
|
+
export { StrandMembershipReconciler, IDLE_PASSES_BEFORE_ESCALATION, INITIAL_JOIN_RETRY_INTERVAL_MS } from './strand-membership-reconciler.js';
|
|
79
|
+
// The timer seam the self-rescheduling strand bring-up loops share (first-sync gate
|
|
80
|
+
// probe loop, membership retry ladder) — injected by tests as a hand-cranked clock.
|
|
81
|
+
export { defaultTimeoutScheduler } from './timeout-scheduler.js';
|
|
66
82
|
// Hibernation
|
|
67
83
|
export { HibernationManager } from './hibernation-manager.js';
|
|
68
84
|
// Arachnode (stub)
|
|
@@ -83,7 +99,7 @@ export { generateStrandMemberKey, strandMemberKeyPair } from './strand-member-ke
|
|
|
83
99
|
// manager-admit, member-peer registration, manager rotation, sealing — the sole
|
|
84
100
|
// manager permanently freezing admission — + shared signing primitives reused
|
|
85
101
|
// across the flows).
|
|
86
|
-
export { signStrandPayload, verifyStrandPayload, signStrandApproval, generateStrandStampId, bootstrapFounderMembership, issueInvite, consumeInvite, cancelInvite, listOutstandingInvites, addMemberByManager, revokeMember, leaveStrand, registerMemberPeer, listMemberPeers, removeMemberPeer, addManager, admitManager, removeManager, sealStrand, isStrandSealed, STRAND_ENGINE, STRAND_ENGINE_VERSION } from './strand-membership-writer.js';
|
|
102
|
+
export { signStrandPayload, verifyStrandPayload, signStrandApproval, generateStrandStampId, bootstrapFounderMembership, PreSplitStrandIdentityError, issueInvite, consumeInvite, burnInvite, isStrandMember, cancelInvite, listOutstandingInvites, addMemberByManager, revokeMember, leaveStrand, registerMemberPeer, listMemberPeers, removeMemberPeer, addManager, admitManager, removeManager, sealStrand, isStrandSealed, StrandTransactionBusyError, STRAND_ENGINE, STRAND_ENGINE_VERSION } from './strand-membership-writer.js';
|
|
87
103
|
// Engine-canonical datetime helper (shared by control + strand signed-write flows
|
|
88
104
|
// so a signed timestamp byte-matches the datetime-coerced column the CHECK sees).
|
|
89
105
|
export { canonicalDatetime } from './canonical-datetime.js';
|
|
@@ -115,15 +131,18 @@ export { validatePushCredentials, redactPushCredentials, REDACTED } from './push
|
|
|
115
131
|
export { PushFanoutService, DEFAULT_PUSH_COOLDOWN_MS, DEFAULT_PUSH_DEBOUNCE_MS } from './push-fanout.js';
|
|
116
132
|
// Control-cohort dial selection (backbone-preferential, bounded out-degree) +
|
|
117
133
|
// the cadence/degree defaults the proactive reconcile routine reads.
|
|
118
|
-
export { selectControlCohortDials,
|
|
134
|
+
export { selectControlCohortDials, DEFAULT_CONTROL_COHORT_RECONCILE_MS, DEFAULT_CONTROL_COHORT_TARGET_DEGREE } from './control-cohort.js';
|
|
135
|
+
// Dialing one peer from several candidate addresses, each under its own time
|
|
136
|
+
// limit, so an address that never answers cannot starve the rest.
|
|
137
|
+
export { dialPeerAddrs, tryAddrsInTurn, directBeforeRelayed, DEFAULT_CONTROL_COHORT_DIAL_TIMEOUT_MS, DEFAULT_CONTROL_COHORT_PER_ADDRESS_DIAL_TIMEOUT_MS, DEFAULT_PEER_DIAL_BUDGET } from './peer-dial.js';
|
|
119
138
|
// Seed trust policy (trust anchor for incoming seeds)
|
|
120
139
|
export { anchoredTrustPolicy, pinnedKeyTrustPolicy, tofuTrustPolicy } from './seed-trust-policy.js';
|
|
121
140
|
// Schema Verification
|
|
122
141
|
export { signSchema, verifySchema, assertSchemaSignature, SchemaVerificationError } from './schema-verification.js';
|
|
123
142
|
// Strand Formation transport (native cadre-core protocol)
|
|
124
|
-
export { FormationListener, dialFormation, isValidResponderCreatesResult, sanitizeStrandAddrs, FORMATION_PROTOCOL } from './strand-formation-protocol.js';
|
|
143
|
+
export { FormationListener, dialFormation, isValidResponderCreatesResult, isWellFormedMembershipInvite, sanitizeStrandAddrs, FORMATION_PROTOCOL } from './strand-formation-protocol.js';
|
|
125
144
|
// Strand Formation manager (drives the native transport)
|
|
126
|
-
export { StrandFormationManager, createStrandFormationManager } from './strand-formation-manager.js';
|
|
145
|
+
export { StrandFormationManager, createStrandFormationManager, MEMBERSHIP_INVITE_TTL_MS, MEMBERSHIP_INVITE_UNAVAILABLE_REASON, HOST_STRAND_MUST_BE_RECREATED_REASON } from './strand-formation-manager.js';
|
|
127
146
|
// Control-network inbound connection gate (membership defense-in-depth)
|
|
128
147
|
export { createMembershipConnectionGater, STRANGER_OPEN_PROTOCOLS, DEFAULT_ENROLLMENT_WINDOW_MS, ADMISSION_DECISION_TIMEOUT_MS, RELAY_ADMISSION_RESERVE_DEADLINE_MS, MAX_UNAUTHORIZED_RELAY_RESERVATIONS, UNAUTHORIZED_RESERVATION_TTL_MS, UnauthorizedReservationBudget } from './membership-connection-gater.js';
|
|
129
148
|
// Thrown out of `CadreNode.start()` when a `network.relayAddrs` reservation does
|
|
@@ -132,7 +151,15 @@ export { createMembershipConnectionGater, STRANGER_OPEN_PROTOCOLS, DEFAULT_ENROL
|
|
|
132
151
|
// `UnbindableListenAddressError` is the same posture for the OTHER half of the listen
|
|
133
152
|
// config: a `network.listenAddrs` entry whose transport the node will not have refuses
|
|
134
153
|
// start instead of being silently dropped by libp2p's transport manager.
|
|
135
|
-
|
|
154
|
+
//
|
|
155
|
+
// `resolveListenAddrs` and `strandNodeAddrs` are the derivations themselves — what a
|
|
156
|
+
// `NetworkConfig` actually turns into for the control node and for each strand node.
|
|
157
|
+
// Exported so an embedder that hand-writes a `network` block (the React Native and web
|
|
158
|
+
// reference apps) can assert on the resulting SHAPE rather than on its own field names,
|
|
159
|
+
// which is the difference between a test that proves reachability and one that proves a
|
|
160
|
+
// spelling.
|
|
161
|
+
export { RelayReservationFailedError, UnbindableListenAddressError, RELAY_SEARCH_LISTEN_ADDR, resolveListenAddrs } from './relay-addrs.js';
|
|
162
|
+
export { strandNodeAddrs } from './strand-network-config.js';
|
|
136
163
|
// Relay reservation via the bare `/p2p-circuit` search listener — the one route
|
|
137
164
|
// every control node takes; `network.relayAddrs` is its fail-fast posture
|
|
138
165
|
// (see relay-reservation.ts)
|