@optimystic/db-p2p 0.24.2 → 0.25.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 → README.md} +688 -668
- package/dist/src/cluster/block-transfer-service.d.ts +133 -10
- package/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer-service.js +155 -9
- package/dist/src/cluster/block-transfer-service.js.map +1 -1
- package/dist/src/cluster/block-transfer.d.ts +67 -7
- package/dist/src/cluster/block-transfer.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer.js +127 -24
- package/dist/src/cluster/block-transfer.js.map +1 -1
- package/dist/src/cluster/certified-claims.d.ts +170 -0
- package/dist/src/cluster/certified-claims.d.ts.map +1 -0
- package/dist/src/cluster/certified-claims.js +200 -0
- package/dist/src/cluster/certified-claims.js.map +1 -0
- package/dist/src/cluster/client-signature-verifier.d.ts +23 -0
- package/dist/src/cluster/client-signature-verifier.d.ts.map +1 -0
- package/dist/src/cluster/client-signature-verifier.js +33 -0
- package/dist/src/cluster/client-signature-verifier.js.map +1 -0
- package/dist/src/cluster/cluster-policy.d.ts +9 -3
- package/dist/src/cluster/cluster-policy.d.ts.map +1 -1
- package/dist/src/cluster/cluster-policy.js +111 -27
- package/dist/src/cluster/cluster-policy.js.map +1 -1
- package/dist/src/cluster/cluster-repo.d.ts +140 -11
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +485 -29
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/commit-proof.d.ts +134 -0
- package/dist/src/cluster/commit-proof.d.ts.map +1 -0
- package/dist/src/cluster/commit-proof.js +263 -0
- package/dist/src/cluster/commit-proof.js.map +1 -0
- package/dist/src/cluster/quorum-restore.d.ts +95 -8
- package/dist/src/cluster/quorum-restore.d.ts.map +1 -1
- package/dist/src/cluster/quorum-restore.js +113 -14
- package/dist/src/cluster/quorum-restore.js.map +1 -1
- package/dist/src/cluster/rebalance-monitor.d.ts +84 -0
- package/dist/src/cluster/rebalance-monitor.d.ts.map +1 -1
- package/dist/src/cluster/rebalance-monitor.js +207 -5
- package/dist/src/cluster/rebalance-monitor.js.map +1 -1
- package/dist/src/cluster/reconcile-block.d.ts +27 -7
- package/dist/src/cluster/reconcile-block.d.ts.map +1 -1
- package/dist/src/cluster/reconcile-block.js +179 -38
- package/dist/src/cluster/reconcile-block.js.map +1 -1
- package/dist/src/cluster/service.d.ts +13 -4
- package/dist/src/cluster/service.d.ts.map +1 -1
- package/dist/src/cluster/service.js +17 -18
- package/dist/src/cluster/service.js.map +1 -1
- package/dist/src/cluster/spread-on-churn.d.ts +8 -2
- package/dist/src/cluster/spread-on-churn.d.ts.map +1 -1
- package/dist/src/cluster/spread-on-churn.js +7 -7
- package/dist/src/cluster/spread-on-churn.js.map +1 -1
- package/dist/src/cohort-topic/host.d.ts.map +1 -1
- package/dist/src/cohort-topic/host.js +8 -7
- package/dist/src/cohort-topic/host.js.map +1 -1
- package/dist/src/cohort-topic/stream-util.d.ts +7 -6
- package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
- package/dist/src/cohort-topic/stream-util.js +14 -41
- package/dist/src/cohort-topic/stream-util.js.map +1 -1
- package/dist/src/dispute/cascade.d.ts +7 -8
- package/dist/src/dispute/cascade.d.ts.map +1 -1
- package/dist/src/dispute/cascade.js +24 -7
- package/dist/src/dispute/cascade.js.map +1 -1
- package/dist/src/dispute/dispute-service.d.ts +5 -0
- package/dist/src/dispute/dispute-service.d.ts.map +1 -1
- package/dist/src/dispute/dispute-service.js +13 -3
- package/dist/src/dispute/dispute-service.js.map +1 -1
- package/dist/src/dispute/invalidation.d.ts +33 -15
- package/dist/src/dispute/invalidation.d.ts.map +1 -1
- package/dist/src/dispute/invalidation.js +127 -34
- package/dist/src/dispute/invalidation.js.map +1 -1
- package/dist/src/dispute/service.d.ts.map +1 -1
- package/dist/src/dispute/service.js +2 -1
- package/dist/src/dispute/service.js.map +1 -1
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/libp2p-key-network.d.ts +14 -10
- package/dist/src/libp2p-key-network.d.ts.map +1 -1
- package/dist/src/libp2p-key-network.js +28 -58
- package/dist/src/libp2p-key-network.js.map +1 -1
- package/dist/src/libp2p-node-base.d.ts +38 -7
- package/dist/src/libp2p-node-base.d.ts.map +1 -1
- package/dist/src/libp2p-node-base.js +148 -63
- package/dist/src/libp2p-node-base.js.map +1 -1
- package/dist/src/network/open-protocol-stream.d.ts +56 -0
- package/dist/src/network/open-protocol-stream.d.ts.map +1 -0
- package/dist/src/network/open-protocol-stream.js +60 -0
- package/dist/src/network/open-protocol-stream.js.map +1 -0
- package/dist/src/network/register-protocol-handler.d.ts +66 -0
- package/dist/src/network/register-protocol-handler.d.ts.map +1 -0
- package/dist/src/network/register-protocol-handler.js +51 -0
- package/dist/src/network/register-protocol-handler.js.map +1 -0
- package/dist/src/peer-address-book.d.ts +60 -2
- package/dist/src/peer-address-book.d.ts.map +1 -1
- package/dist/src/peer-address-book.js +89 -1
- package/dist/src/peer-address-book.js.map +1 -1
- package/dist/src/pend-validation.d.ts +65 -0
- package/dist/src/pend-validation.d.ts.map +1 -0
- package/dist/src/pend-validation.js +69 -0
- package/dist/src/pend-validation.js.map +1 -0
- package/dist/src/reactivity/notify-transport.d.ts.map +1 -1
- package/dist/src/reactivity/notify-transport.js +2 -1
- package/dist/src/reactivity/notify-transport.js.map +1 -1
- package/dist/src/reactivity/push-state-gossip.d.ts.map +1 -1
- package/dist/src/reactivity/push-state-gossip.js +2 -1
- package/dist/src/reactivity/push-state-gossip.js.map +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts +32 -6
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +44 -10
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +181 -25
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +650 -62
- package/dist/src/repo/coordinator-repo.js.map +1 -1
- package/dist/src/repo/served-repo-proxy.d.ts +30 -0
- package/dist/src/repo/served-repo-proxy.d.ts.map +1 -0
- package/dist/src/repo/served-repo-proxy.js +50 -0
- package/dist/src/repo/served-repo-proxy.js.map +1 -0
- package/dist/src/repo/service.d.ts +16 -1
- package/dist/src/repo/service.d.ts.map +1 -1
- package/dist/src/repo/service.js +18 -16
- package/dist/src/repo/service.js.map +1 -1
- package/dist/src/reputation/types.d.ts +3 -1
- package/dist/src/reputation/types.d.ts.map +1 -1
- package/dist/src/reputation/types.js +3 -1
- package/dist/src/reputation/types.js.map +1 -1
- package/dist/src/rn.d.ts +24 -2
- package/dist/src/rn.d.ts.map +1 -1
- package/dist/src/rn.js +33 -4
- package/dist/src/rn.js.map +1 -1
- package/dist/src/storage/block-archive.d.ts +153 -0
- package/dist/src/storage/block-archive.d.ts.map +1 -0
- package/dist/src/storage/block-archive.js +194 -0
- package/dist/src/storage/block-archive.js.map +1 -0
- package/dist/src/storage/block-latch.d.ts +92 -0
- package/dist/src/storage/block-latch.d.ts.map +1 -0
- package/dist/src/storage/block-latch.js +132 -0
- package/dist/src/storage/block-latch.js.map +1 -0
- package/dist/src/storage/block-storage.d.ts +169 -12
- package/dist/src/storage/block-storage.d.ts.map +1 -1
- package/dist/src/storage/block-storage.js +415 -152
- package/dist/src/storage/block-storage.js.map +1 -1
- package/dist/src/storage/cached-raw-storage.d.ts +5 -1
- package/dist/src/storage/cached-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/cached-raw-storage.js +16 -1
- package/dist/src/storage/cached-raw-storage.js.map +1 -1
- package/dist/src/storage/cached-store-driver.d.ts +14 -4
- package/dist/src/storage/cached-store-driver.d.ts.map +1 -1
- package/dist/src/storage/cached-store-driver.js +39 -5
- package/dist/src/storage/cached-store-driver.js.map +1 -1
- package/dist/src/storage/i-block-storage.d.ts +70 -19
- package/dist/src/storage/i-block-storage.d.ts.map +1 -1
- package/dist/src/storage/i-block-storage.js +21 -1
- package/dist/src/storage/i-block-storage.js.map +1 -1
- package/dist/src/storage/i-raw-storage.d.ts +38 -0
- package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/kv-raw-storage.d.ts +14 -8
- package/dist/src/storage/kv-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/kv-raw-storage.js +29 -9
- package/dist/src/storage/kv-raw-storage.js.map +1 -1
- package/dist/src/storage/memory-store-driver.d.ts +4 -1
- package/dist/src/storage/memory-store-driver.d.ts.map +1 -1
- package/dist/src/storage/memory-store-driver.js +22 -2
- package/dist/src/storage/memory-store-driver.js.map +1 -1
- package/dist/src/storage/raw-store-codec.js +2 -2
- package/dist/src/storage/raw-store-driver.d.ts +44 -3
- package/dist/src/storage/raw-store-driver.d.ts.map +1 -1
- package/dist/src/storage/shared-cache-pool.d.ts +60 -3
- package/dist/src/storage/shared-cache-pool.d.ts.map +1 -1
- package/dist/src/storage/shared-cache-pool.js +68 -4
- package/dist/src/storage/shared-cache-pool.js.map +1 -1
- package/dist/src/storage/storage-repo.d.ts +204 -26
- package/dist/src/storage/storage-repo.d.ts.map +1 -1
- package/dist/src/storage/storage-repo.js +422 -119
- package/dist/src/storage/storage-repo.js.map +1 -1
- package/dist/src/storage/store-identity.d.ts +21 -0
- package/dist/src/storage/store-identity.d.ts.map +1 -0
- package/dist/src/storage/store-identity.js +33 -0
- package/dist/src/storage/store-identity.js.map +1 -0
- package/dist/src/storage/struct.d.ts +15 -0
- package/dist/src/storage/struct.d.ts.map +1 -1
- package/dist/src/storage/with-read-cache.d.ts +92 -0
- package/dist/src/storage/with-read-cache.d.ts.map +1 -0
- package/dist/src/storage/with-read-cache.js +132 -0
- package/dist/src/storage/with-read-cache.js.map +1 -0
- package/dist/src/sync/service.d.ts +13 -3
- package/dist/src/sync/service.d.ts.map +1 -1
- package/dist/src/sync/service.js +7 -30
- package/dist/src/sync/service.js.map +1 -1
- package/dist/src/testing/mesh-harness.d.ts +75 -2
- package/dist/src/testing/mesh-harness.d.ts.map +1 -1
- package/dist/src/testing/mesh-harness.js +193 -81
- package/dist/src/testing/mesh-harness.js.map +1 -1
- package/dist/src/testing/raw-storage-conformance.d.ts +8 -0
- package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
- package/dist/src/testing/raw-storage-conformance.js +97 -2
- package/dist/src/testing/raw-storage-conformance.js.map +1 -1
- package/package.json +5 -6
- package/src/cluster/block-transfer-service.ts +528 -312
- package/src/cluster/block-transfer.ts +547 -424
- package/src/cluster/certified-claims.ts +319 -0
- package/src/cluster/client-signature-verifier.ts +34 -0
- package/src/cluster/cluster-policy.ts +294 -203
- package/src/cluster/cluster-repo.ts +2501 -2034
- package/src/cluster/commit-proof.ts +344 -0
- package/src/cluster/quorum-restore.ts +348 -223
- package/src/cluster/rebalance-monitor.ts +539 -254
- package/src/cluster/reconcile-block.ts +404 -203
- package/src/cluster/service.ts +23 -20
- package/src/cluster/spread-on-churn.ts +352 -346
- package/src/cohort-topic/host.ts +8 -7
- package/src/cohort-topic/stream-util.ts +14 -43
- package/src/dispute/cascade.ts +31 -15
- package/src/dispute/dispute-service.ts +706 -696
- package/src/dispute/invalidation.ts +162 -52
- package/src/dispute/service.ts +126 -125
- package/src/index.ts +7 -0
- package/src/libp2p-key-network.ts +1204 -1235
- package/src/libp2p-node-base.ts +185 -70
- package/src/network/open-protocol-stream.ts +87 -0
- package/src/network/register-protocol-handler.ts +85 -0
- package/src/peer-address-book.ts +96 -2
- package/src/pend-validation.ts +89 -0
- package/src/reactivity/notify-transport.ts +2 -1
- package/src/reactivity/push-state-gossip.ts +2 -1
- package/src/repo/cluster-coordinator.ts +65 -11
- package/src/repo/coordinator-repo.ts +729 -71
- package/src/repo/served-repo-proxy.ts +55 -0
- package/src/repo/service.ts +25 -16
- package/src/reputation/types.ts +3 -1
- package/src/rn.ts +34 -4
- package/src/storage/block-archive.ts +242 -0
- package/src/storage/block-latch.ts +144 -0
- package/src/storage/block-storage.ts +787 -498
- package/src/storage/cached-raw-storage.ts +21 -1
- package/src/storage/cached-store-driver.ts +898 -859
- package/src/storage/i-block-storage.ts +172 -116
- package/src/storage/i-raw-storage.ts +40 -0
- package/src/storage/kv-raw-storage.ts +34 -9
- package/src/storage/memory-store-driver.ts +26 -2
- package/src/storage/raw-store-codec.ts +42 -42
- package/src/storage/raw-store-driver.ts +45 -3
- package/src/storage/shared-cache-pool.ts +516 -452
- package/src/storage/storage-repo.ts +1421 -1014
- package/src/storage/store-identity.ts +41 -0
- package/src/storage/struct.ts +52 -34
- package/src/storage/with-read-cache.ts +182 -0
- package/src/sync/service.ts +178 -198
- package/src/testing/mesh-harness.ts +524 -344
- package/src/testing/raw-storage-conformance.ts +114 -2
|
@@ -1,203 +1,294 @@
|
|
|
1
|
-
import { DEFAULT_SUPER_MAJORITY_THRESHOLD, type ClusterConsensusConfig } from "@optimystic/db-core";
|
|
2
|
-
import { createLogger } from "../logger.js";
|
|
3
|
-
import { CORROBORATION_FLOOR } from "./quorum-restore.js";
|
|
4
|
-
|
|
5
|
-
const log = createLogger('cluster-policy');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Resolves the operator-facing cluster knobs (`clusterSize`, `clusterPolicy.*`) into the concrete
|
|
9
|
-
* numbers the consensus and block-restoration paths run on.
|
|
10
|
-
*
|
|
11
|
-
* Extracted from `createLibp2pNodeBase` rather than left inline so the composition root's defaults
|
|
12
|
-
* are assertable without booting a libp2p node — the layer a real deployment actually uses, and
|
|
13
|
-
* therefore the layer where a default that relaxed the repair corroboration floor to a single voter
|
|
14
|
-
* survived unnoticed (see `test/cluster-policy.spec.ts`).
|
|
15
|
-
*
|
|
16
|
-
* ## Why two size yardsticks, not one
|
|
17
|
-
*
|
|
18
|
-
* One operator field — `clusterPolicy.assumedClusterSize`, "the smallest cohort this deployment can
|
|
19
|
-
* genuinely field" — feeds two consumers whose failure modes point in opposite directions, so its
|
|
20
|
-
* *default* cannot serve both:
|
|
21
|
-
*
|
|
22
|
-
* - **Membership admission gate** (`cluster/cluster-repo.ts`, `admitMembership`) reads it only on its
|
|
23
|
-
* fallback path, when this node has no confident network-size estimate. Too small: a
|
|
24
|
-
* partition-induced downsize slips past while the node is unconfident. Too large: the node refuses
|
|
25
|
-
* legitimate writes — unavailability. It wants a *permissive* default, because an unconfigured
|
|
26
|
-
* two-node mesh must still be able to transact. It gets {@link minAbsoluteClusterSize} (2).
|
|
27
|
-
* - **Repair corroboration floor** (`corroboratorCapacity` in `cluster/quorum-restore.ts`, called by
|
|
28
|
-
* `CoordinatorRepo.queryClusterForLatest` and `createReconcileBlock`) reads it on *every* repair,
|
|
29
|
-
* unconditionally. Too small: a shrunken — and always unauthenticated — cohort view buys a lone
|
|
30
|
-
* peer full trust. Too large: a block stays unrepaired, degraded rather than dead. It wants a
|
|
31
|
-
* *strict* default. It gets {@link ResolvedClusterPolicy.repairCorroborationClusterSize}, which
|
|
32
|
-
* falls back to `clusterSize` (the configured replication factor).
|
|
33
|
-
*
|
|
34
|
-
* A single explicit `clusterPolicy.assumedClusterSize` still sets BOTH — an operator declaring their
|
|
35
|
-
* real cohort size means it for both consumers. Only the unconfigured case diverges.
|
|
36
|
-
*
|
|
37
|
-
* So a genuine two-node mesh needs exactly one setting to self-repair: either
|
|
38
|
-
* `clusterPolicy.assumedClusterSize: 2` (which does not lower the replication factor) or an honest
|
|
39
|
-
* `clusterSize: 2`. Writes and voting still work with zero configuration.
|
|
40
|
-
*
|
|
41
|
-
* ## Future
|
|
42
|
-
*
|
|
43
|
-
* Deriving the yardstick from observation (the largest peer group this node has ever seen for the
|
|
44
|
-
* key) would remove the trade entirely and subsume both values. Filed as backlog
|
|
45
|
-
* `feat-admission-floor-from-observed-cohort-high-water-mark`; do not build it here.
|
|
46
|
-
*/
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Absolute floor below which no cohort is safe, whatever the size references say. Named rather than
|
|
50
|
-
* inlined because the admission gate's `assumedClusterSize` defaults to exactly this value — the two
|
|
51
|
-
* must not drift.
|
|
52
|
-
*/
|
|
53
|
-
export const minAbsoluteClusterSize = 2;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Default replication factor / target cohort breadth when the operator declares no `clusterSize`.
|
|
57
|
-
*
|
|
58
|
-
* Exported (and re-exported from the package root) rather than left inline because a caller that
|
|
59
|
-
* must construct a `Libp2pKeyPeerNetwork` for a node it did not build has to state a cluster size —
|
|
60
|
-
* the constructor no longer supplies one — and the only defensible answer is "whatever a node built
|
|
61
|
-
* here would have resolved to". Repeating the literal is how the two drifted last time.
|
|
62
|
-
*/
|
|
63
|
-
export const DEFAULT_CLUSTER_SIZE = 10;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* The operator-facing cluster knobs. `NodeOptions` (`libp2p-node-base.ts`) intersects this rather
|
|
67
|
-
* than restating it, so a knob added here is one `resolveClusterPolicy` is guaranteed to see — a
|
|
68
|
-
* second declaration would compile fine and be silently dropped.
|
|
69
|
-
*/
|
|
70
|
-
export interface ClusterPolicyOptions {
|
|
71
|
-
/**
|
|
72
|
-
* Desired cluster size per key (default 10) — the replication factor / target cohort breadth
|
|
73
|
-
* the coordinator aims for. NOT a statement about how many peers actually exist, so the
|
|
74
|
-
* membership admission gate is never measured against it (see `cluster/cluster-repo.ts`).
|
|
75
|
-
*
|
|
76
|
-
* The read-repair/reconcile corroboration floor DOES fall back to it when
|
|
77
|
-
* `clusterPolicy.assumedClusterSize` is absent — the strict direction, so an unconfigured node
|
|
78
|
-
* cannot have its floor talked down by a shrunken cohort view. A deployment that genuinely runs
|
|
79
|
-
* fewer peers than this should declare `clusterPolicy.assumedClusterSize`.
|
|
80
|
-
*/
|
|
81
|
-
clusterSize?: number;
|
|
82
|
-
clusterPolicy?: {
|
|
83
|
-
allowDownsize?: boolean;
|
|
84
|
-
/** Acceptable relative difference (e.g. 0.5 = +/-50%). */
|
|
85
|
-
sizeTolerance?: number;
|
|
86
|
-
/** Fraction of peers needed for super-majority (default {@link DEFAULT_SUPER_MAJORITY_THRESHOLD}). */
|
|
87
|
-
superMajorityThreshold?: number;
|
|
88
|
-
/**
|
|
89
|
-
* Opt in to transacting below the safe cluster-size floor when FRET has no confident
|
|
90
|
-
* network-size estimate — the membership-admission and coordinator small-cluster gates both
|
|
91
|
-
* fail closed without it. Default false. Turn on only for single-node / local dev meshes that
|
|
92
|
-
* knowingly run undersized.
|
|
93
|
-
*/
|
|
94
|
-
allowUnvalidatedSmallCluster?: boolean;
|
|
95
|
-
/**
|
|
96
|
-
* The smallest cohort this deployment can genuinely field — normally the number of nodes you
|
|
97
|
-
* actually run, capped at `clusterSize`. Two consumers read it: the membership admission gate,
|
|
98
|
-
* on its fallback path when the node has no confident network-size estimate; and the
|
|
99
|
-
* read-repair/reconcile corroboration floor (`corroboratorCapacity`), unconditionally.
|
|
100
|
-
*
|
|
101
|
-
* Declaring it sets BOTH. Leaving it unset does NOT — see the module doc for why the two
|
|
102
|
-
* defaults point in opposite directions. A large deployment should still set this to its real
|
|
103
|
-
* cohort size, otherwise the admission gate cannot police a partition-induced downsize while
|
|
104
|
-
* its size estimate is unconfident; a genuine two-node mesh needs it (or an honest
|
|
105
|
-
* `clusterSize: 2`) to self-repair.
|
|
106
|
-
*/
|
|
107
|
-
assumedClusterSize?: number;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
1
|
+
import { DEFAULT_SUPER_MAJORITY_THRESHOLD, type ClusterConsensusConfig, type UnvalidatablePendPolicy } from "@optimystic/db-core";
|
|
2
|
+
import { createLogger } from "../logger.js";
|
|
3
|
+
import { CORROBORATION_FLOOR } from "./quorum-restore.js";
|
|
4
|
+
|
|
5
|
+
const log = createLogger('cluster-policy');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolves the operator-facing cluster knobs (`clusterSize`, `clusterPolicy.*`) into the concrete
|
|
9
|
+
* numbers the consensus and block-restoration paths run on.
|
|
10
|
+
*
|
|
11
|
+
* Extracted from `createLibp2pNodeBase` rather than left inline so the composition root's defaults
|
|
12
|
+
* are assertable without booting a libp2p node — the layer a real deployment actually uses, and
|
|
13
|
+
* therefore the layer where a default that relaxed the repair corroboration floor to a single voter
|
|
14
|
+
* survived unnoticed (see `test/cluster-policy.spec.ts`).
|
|
15
|
+
*
|
|
16
|
+
* ## Why two size yardsticks, not one
|
|
17
|
+
*
|
|
18
|
+
* One operator field — `clusterPolicy.assumedClusterSize`, "the smallest cohort this deployment can
|
|
19
|
+
* genuinely field" — feeds two consumers whose failure modes point in opposite directions, so its
|
|
20
|
+
* *default* cannot serve both:
|
|
21
|
+
*
|
|
22
|
+
* - **Membership admission gate** (`cluster/cluster-repo.ts`, `admitMembership`) reads it only on its
|
|
23
|
+
* fallback path, when this node has no confident network-size estimate. Too small: a
|
|
24
|
+
* partition-induced downsize slips past while the node is unconfident. Too large: the node refuses
|
|
25
|
+
* legitimate writes — unavailability. It wants a *permissive* default, because an unconfigured
|
|
26
|
+
* two-node mesh must still be able to transact. It gets {@link minAbsoluteClusterSize} (2).
|
|
27
|
+
* - **Repair corroboration floor** (`corroboratorCapacity` in `cluster/quorum-restore.ts`, called by
|
|
28
|
+
* `CoordinatorRepo.queryClusterForLatest` and `createReconcileBlock`) reads it on *every* repair,
|
|
29
|
+
* unconditionally. Too small: a shrunken — and always unauthenticated — cohort view buys a lone
|
|
30
|
+
* peer full trust. Too large: a block stays unrepaired, degraded rather than dead. It wants a
|
|
31
|
+
* *strict* default. It gets {@link ResolvedClusterPolicy.repairCorroborationClusterSize}, which
|
|
32
|
+
* falls back to `clusterSize` (the configured replication factor).
|
|
33
|
+
*
|
|
34
|
+
* A single explicit `clusterPolicy.assumedClusterSize` still sets BOTH — an operator declaring their
|
|
35
|
+
* real cohort size means it for both consumers. Only the unconfigured case diverges.
|
|
36
|
+
*
|
|
37
|
+
* So a genuine two-node mesh needs exactly one setting to self-repair: either
|
|
38
|
+
* `clusterPolicy.assumedClusterSize: 2` (which does not lower the replication factor) or an honest
|
|
39
|
+
* `clusterSize: 2`. Writes and voting still work with zero configuration.
|
|
40
|
+
*
|
|
41
|
+
* ## Future
|
|
42
|
+
*
|
|
43
|
+
* Deriving the yardstick from observation (the largest peer group this node has ever seen for the
|
|
44
|
+
* key) would remove the trade entirely and subsume both values. Filed as backlog
|
|
45
|
+
* `feat-admission-floor-from-observed-cohort-high-water-mark`; do not build it here.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Absolute floor below which no cohort is safe, whatever the size references say. Named rather than
|
|
50
|
+
* inlined because the admission gate's `assumedClusterSize` defaults to exactly this value — the two
|
|
51
|
+
* must not drift.
|
|
52
|
+
*/
|
|
53
|
+
export const minAbsoluteClusterSize = 2;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Default replication factor / target cohort breadth when the operator declares no `clusterSize`.
|
|
57
|
+
*
|
|
58
|
+
* Exported (and re-exported from the package root) rather than left inline because a caller that
|
|
59
|
+
* must construct a `Libp2pKeyPeerNetwork` for a node it did not build has to state a cluster size —
|
|
60
|
+
* the constructor no longer supplies one — and the only defensible answer is "whatever a node built
|
|
61
|
+
* here would have resolved to". Repeating the literal is how the two drifted last time.
|
|
62
|
+
*/
|
|
63
|
+
export const DEFAULT_CLUSTER_SIZE = 10;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The operator-facing cluster knobs. `NodeOptions` (`libp2p-node-base.ts`) intersects this rather
|
|
67
|
+
* than restating it, so a knob added here is one `resolveClusterPolicy` is guaranteed to see — a
|
|
68
|
+
* second declaration would compile fine and be silently dropped.
|
|
69
|
+
*/
|
|
70
|
+
export interface ClusterPolicyOptions {
|
|
71
|
+
/**
|
|
72
|
+
* Desired cluster size per key (default 10) — the replication factor / target cohort breadth
|
|
73
|
+
* the coordinator aims for. NOT a statement about how many peers actually exist, so the
|
|
74
|
+
* membership admission gate is never measured against it (see `cluster/cluster-repo.ts`).
|
|
75
|
+
*
|
|
76
|
+
* The read-repair/reconcile corroboration floor DOES fall back to it when
|
|
77
|
+
* `clusterPolicy.assumedClusterSize` is absent — the strict direction, so an unconfigured node
|
|
78
|
+
* cannot have its floor talked down by a shrunken cohort view. A deployment that genuinely runs
|
|
79
|
+
* fewer peers than this should declare `clusterPolicy.assumedClusterSize`.
|
|
80
|
+
*/
|
|
81
|
+
clusterSize?: number;
|
|
82
|
+
clusterPolicy?: {
|
|
83
|
+
allowDownsize?: boolean;
|
|
84
|
+
/** Acceptable relative difference (e.g. 0.5 = +/-50%). */
|
|
85
|
+
sizeTolerance?: number;
|
|
86
|
+
/** Fraction of peers needed for super-majority (default {@link DEFAULT_SUPER_MAJORITY_THRESHOLD}). */
|
|
87
|
+
superMajorityThreshold?: number;
|
|
88
|
+
/**
|
|
89
|
+
* Opt in to transacting below the safe cluster-size floor when FRET has no confident
|
|
90
|
+
* network-size estimate — the membership-admission and coordinator small-cluster gates both
|
|
91
|
+
* fail closed without it. Default false. Turn on only for single-node / local dev meshes that
|
|
92
|
+
* knowingly run undersized.
|
|
93
|
+
*/
|
|
94
|
+
allowUnvalidatedSmallCluster?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* The smallest cohort this deployment can genuinely field — normally the number of nodes you
|
|
97
|
+
* actually run, capped at `clusterSize`. Two consumers read it: the membership admission gate,
|
|
98
|
+
* on its fallback path when the node has no confident network-size estimate; and the
|
|
99
|
+
* read-repair/reconcile corroboration floor (`corroboratorCapacity`), unconditionally.
|
|
100
|
+
*
|
|
101
|
+
* Declaring it sets BOTH. Leaving it unset does NOT — see the module doc for why the two
|
|
102
|
+
* defaults point in opposite directions. A large deployment should still set this to its real
|
|
103
|
+
* cohort size, otherwise the admission gate cannot police a partition-induced downsize while
|
|
104
|
+
* its size estimate is unconfident; a genuine two-node mesh needs it (or an honest
|
|
105
|
+
* `clusterSize: 2`) to self-repair.
|
|
106
|
+
*/
|
|
107
|
+
assumedClusterSize?: number;
|
|
108
|
+
/**
|
|
109
|
+
* What a validator-configured member does with a pend that carries no `validation` payload
|
|
110
|
+
* (nothing to re-execute — the single-collection `Collection.sync` shape). Default 'accept';
|
|
111
|
+
* see {@link UnvalidatablePendPolicy}.
|
|
112
|
+
*/
|
|
113
|
+
unvalidatablePendPolicy?: UnvalidatablePendPolicy;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Everything a node's consensus + restoration paths need, with every default already applied. */
|
|
118
|
+
export type ResolvedClusterPolicy = ClusterConsensusConfig & {
|
|
119
|
+
/** Replication factor / target cohort breadth. Always concrete after resolution. */
|
|
120
|
+
clusterSize: number;
|
|
121
|
+
/**
|
|
122
|
+
* Yardstick the repair corroboration floor measures a (possibly shrunken, always unauthenticated)
|
|
123
|
+
* cohort view against — see `corroboratorCapacity` in `cluster/quorum-restore.ts`.
|
|
124
|
+
*
|
|
125
|
+
* Deliberately distinct from {@link ClusterConsensusConfig.assumedClusterSize}, which the
|
|
126
|
+
* membership admission gate reads: the two share an operator field but not a default, because
|
|
127
|
+
* over- and under-stating them cost opposite things. See the module doc.
|
|
128
|
+
*/
|
|
129
|
+
repairCorroborationClusterSize: number;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Apply every cluster-policy default a node needs. Same options in, same numbers out, so the
|
|
134
|
+
* composition root's behavior is unit-testable (`test/cluster-policy.spec.ts`). Its one side effect
|
|
135
|
+
* is the `repair-fault-tolerance` advisory below, which lives here because this is the only place
|
|
136
|
+
* that knows the resolution produced a combination with no repair margin — or none at all.
|
|
137
|
+
*/
|
|
138
|
+
export function resolveClusterPolicy(options: ClusterPolicyOptions): ResolvedClusterPolicy {
|
|
139
|
+
// undefined here means "the operator said nothing", which is the only case where the two
|
|
140
|
+
// yardsticks below diverge.
|
|
141
|
+
//
|
|
142
|
+
// NOTE: a declared value is passed through unvalidated. The admission gate floors a degenerate one
|
|
143
|
+
// (0, negative, NaN, Infinity) itself — see `cluster-repo.admissionFloor` and its specs — but
|
|
144
|
+
// `corroboratorCapacity` does not: NaN there makes every quorum comparison false, so repair
|
|
145
|
+
// silently declines forever. Fail-safe, and unreachable through the reference-peer CLI (which
|
|
146
|
+
// rejects non-positive integers). If another composition root starts accepting unvalidated config,
|
|
147
|
+
// clamp here rather than in each consumer.
|
|
148
|
+
const declaredCohortSize = options.clusterPolicy?.assumedClusterSize;
|
|
149
|
+
const clusterSize = options.clusterSize ?? DEFAULT_CLUSTER_SIZE;
|
|
150
|
+
const repairCorroborationClusterSize = declaredCohortSize ?? clusterSize;
|
|
151
|
+
|
|
152
|
+
// ## What the advisory actually claims, and why the trigger is what it is
|
|
153
|
+
//
|
|
154
|
+
// The rule it states is measurable, not rhetorical: sweep `corroboratorCapacity` and `quorumSize`
|
|
155
|
+
// over real cohort sizes and every configuration lands on the same requirement — **2 cohort peers
|
|
156
|
+
// BESIDES the reader must answer that reader and agree on the same (rev, actionId)**, relaxed to 1
|
|
157
|
+
// only for a cohort DECLARED smaller than three. Two consequences follow, and both are worth
|
|
158
|
+
// saying out loud because both surprised people in the field:
|
|
159
|
+
//
|
|
160
|
+
// - Fewer than three machines, undeclared, can never repair at all: the floor of two never
|
|
161
|
+
// relaxes, because `repairCorroborationClusterSize` falls back to `clusterSize` (default 10).
|
|
162
|
+
// - Exactly three machines is the MINIMUM that can ever repair, not a size at which repair is
|
|
163
|
+
// safe. The reader has exactly two peers and needs both, so a single peer unreachable FROM THAT
|
|
164
|
+
// READER — perfectly healthy and reachable from everybody else — leaves that reader's copy
|
|
165
|
+
// permanently unrepairable. Four machines is the first size with any margin.
|
|
166
|
+
//
|
|
167
|
+
// Both of those are claims about MACHINES, and the advisory used to stop there — which made it
|
|
168
|
+
// wrong in the operator's favour, because a machine count is only half the requirement. The peers
|
|
169
|
+
// that answer must also HOLD the block, and two of them must. A block only ONE cohort peer holds
|
|
170
|
+
// cannot be repaired at any deployment size whatsoever: the sole holder cannot second itself, and
|
|
171
|
+
// the two mechanisms that would give a second peer a copy (read-repair and reconcile) both consume
|
|
172
|
+
// this same decision. So an operator at four-plus machines reading "the first size with any margin"
|
|
173
|
+
// could believe they were covered while a block written when the deployment was smaller stayed
|
|
174
|
+
// stranded at one copy forever. The advisory now says so; the per-block half is
|
|
175
|
+
// `cluster-fetch:repair-deadlock` with `reason: 'sole-holder'` (`CoordinatorRepo`), and the
|
|
176
|
+
// behavioural fix — replicating owned blocks once the cohort grows — is separate work.
|
|
177
|
+
//
|
|
178
|
+
// So the trigger is a union of two conditions, not one:
|
|
179
|
+
//
|
|
180
|
+
// - **undeclared** (the original case): the operator has asserted nothing, so a deployment
|
|
181
|
+
// smaller than three would be silently unrepairable. Conditional wording ("if you run fewer
|
|
182
|
+
// than N machines"), never a fault — this fires off configuration, not an observed cohort, so a
|
|
183
|
+
// correctly-provisioned large deployment sees it too.
|
|
184
|
+
// - **resolved cohort <= CORROBORATION_FLOOR + 1**, whether declared or not. Declaring
|
|
185
|
+
// `assumedClusterSize: 3` does not conjure a third peer; it has exactly the same zero tolerance
|
|
186
|
+
// as an undeclared three. The earlier "a declaration is an explicit assertion we cannot
|
|
187
|
+
// contradict" reasoning holds for whether the NUMBER is honest — it does not hold for the
|
|
188
|
+
// fragility implied by the number itself, which is arithmetic.
|
|
189
|
+
//
|
|
190
|
+
// Still one line per node construction (`resolveClusterPolicy` runs once), never per repair: a
|
|
191
|
+
// per-attempt warn on a busy node is noise that gets filtered, which defeats the point. The
|
|
192
|
+
// per-repair half of this — naming a decline that is provably permanent — lives at the repair site
|
|
193
|
+
// instead (`CoordinatorRepo.reportRepairDeadlock`, `cluster-fetch:repair-deadlock`), where the
|
|
194
|
+
// actual cohort is known.
|
|
195
|
+
//
|
|
196
|
+
// NOTE: an operator who declares an assumedClusterSize LARGER than the cohort they actually run is
|
|
197
|
+
// equally unable to repair and still gets no fault — this function has no observed cohort to
|
|
198
|
+
// contradict the declaration with, and the undeclared arm's conditional wording is the closest it
|
|
199
|
+
// can honestly get. If `feat-admission-floor-from-observed-cohort-high-water-mark` ever lands
|
|
200
|
+
// (deriving the yardstick from observation), that check becomes cheap and belongs here.
|
|
201
|
+
const minimumSelfHealingDeployment = CORROBORATION_FLOOR + 1;
|
|
202
|
+
const cohortUndeclared = declaredCohortSize === undefined && clusterSize > minAbsoluteClusterSize;
|
|
203
|
+
const noRepairMargin = repairCorroborationClusterSize <= minimumSelfHealingDeployment;
|
|
204
|
+
if (cohortUndeclared || noRepairMargin) {
|
|
205
|
+
// How many peers besides the reader must answer and agree, at the resolved size. Two once the
|
|
206
|
+
// cohort is three or larger; one for a cohort declared at two, which is the only size whose
|
|
207
|
+
// floor relaxes. Never below one — a claim nobody made is never accepted.
|
|
208
|
+
const requiredAnsweringPeers = Math.max(1, Math.min(CORROBORATION_FLOOR, repairCorroborationClusterSize - 1));
|
|
209
|
+
const availablePeers = Math.max(0, repairCorroborationClusterSize - 1);
|
|
210
|
+
const rule =
|
|
211
|
+
`Block repair (read-repair and reconcile) converges only when ${CORROBORATION_FLOOR} cohort peers ` +
|
|
212
|
+
`BESIDES the reader answer that reader and agree on the same (rev, actionId); that requirement drops ` +
|
|
213
|
+
`to 1 only for a cohort that DECLARES it is smaller than ${minimumSelfHealingDeployment}. ` +
|
|
214
|
+
`${minimumSelfHealingDeployment} machines is therefore the MINIMUM that can repair at all, not a safe ` +
|
|
215
|
+
`size — at exactly ${minimumSelfHealingDeployment} the reader has two peers and needs both, so one ` +
|
|
216
|
+
`peer unreachable from that reader (healthy and reachable from everyone else) leaves that reader's ` +
|
|
217
|
+
`copy permanently unrepairable. ${minimumSelfHealingDeployment + 1} machines is the first size with ` +
|
|
218
|
+
`any margin.`;
|
|
219
|
+
// Every number above counts MACHINES. Saying only that overstates the guarantee, because repair
|
|
220
|
+
// also needs the answering peers to actually HOLD the block — which is a property of the block,
|
|
221
|
+
// not of the deployment, and which no machine count can supply.
|
|
222
|
+
//
|
|
223
|
+
// NOTE: accepted tradeoff — the caveat rides the EXISTING trigger (cohort size undeclared, or
|
|
224
|
+
// repairCorroborationClusterSize <= 3) rather than firing for every deployment. A correctly-
|
|
225
|
+
// declared large deployment is arguably the operator most likely to believe a machine count
|
|
226
|
+
// covers them, and never sees this paragraph at startup; they learn it from the per-block
|
|
227
|
+
// `cluster-fetch:repair-deadlock` line with reason=sole-holder instead. Weighed and kept: a
|
|
228
|
+
// startup advisory that fires on every correctly-configured node forever is one operators
|
|
229
|
+
// filter, which costs more than it buys. Revisit if the per-block line proves too late to be
|
|
230
|
+
// useful — i.e. if field reports show operators hitting stranded founding data without ever
|
|
231
|
+
// having read a repair-deadlock line.
|
|
232
|
+
const holdersCaveat =
|
|
233
|
+
` All of that counts MACHINES, and machines are only half the requirement: the peers that answer ` +
|
|
234
|
+
`must also HOLD the block, and ${CORROBORATION_FLOOR} of them must. A block that only ONE cohort ` +
|
|
235
|
+
`peer holds can never be repaired at ANY deployment size — the sole holder cannot second itself — ` +
|
|
236
|
+
`so every size claim above is about a block at least ${CORROBORATION_FLOOR} peers already hold. ` +
|
|
237
|
+
`The usual way to fall outside that: data written while the deployment (or that block's cohort) ` +
|
|
238
|
+
`was smaller keeps the number of copies it was written with, and GROWING THE DEPLOYMENT DOES NOT ` +
|
|
239
|
+
`COPY IT — so founding data can stay stranded however many machines you later run. That case is ` +
|
|
240
|
+
`reported once per affected block as cluster-fetch:repair-deadlock with reason=sole-holder, and its ` +
|
|
241
|
+
`remedy is another cohort peer holding it (commit a new revision of the block), never more machines.`;
|
|
242
|
+
const undeclaredAdvice = cohortUndeclared
|
|
243
|
+
? ` No clusterPolicy.assumedClusterSize declared, so the floor is measured against ` +
|
|
244
|
+
`repairCorroborationClusterSize=${repairCorroborationClusterSize} and never relaxes: if you actually ` +
|
|
245
|
+
`run fewer than ${minimumSelfHealingDeployment} machines, every repair declines, permanently. Set ` +
|
|
246
|
+
`clusterPolicy.assumedClusterSize to your real cohort size; it does not lower ` +
|
|
247
|
+
`clusterSize=${clusterSize} (the replication factor). Larger deployments can ignore this.`
|
|
248
|
+
: '';
|
|
249
|
+
const noMarginAdvice = noRepairMargin
|
|
250
|
+
? ` This node resolved repairCorroborationClusterSize=${repairCorroborationClusterSize}, which leaves ` +
|
|
251
|
+
`repair with NO fault tolerance: the reader has ${availablePeers} cohort peer(s) and needs ` +
|
|
252
|
+
`${requiredAnsweringPeers} of them to answer, so losing one is not survivable. Run at least ` +
|
|
253
|
+
`${minimumSelfHealingDeployment + 1} machines if repair must survive an unreachable peer.`
|
|
254
|
+
: '';
|
|
255
|
+
log('repair-fault-tolerance', {
|
|
256
|
+
clusterSize,
|
|
257
|
+
repairCorroborationClusterSize,
|
|
258
|
+
corroborationFloor: CORROBORATION_FLOOR,
|
|
259
|
+
declaredCohortSize,
|
|
260
|
+
cohortUndeclared,
|
|
261
|
+
noRepairMargin,
|
|
262
|
+
requiredAnsweringPeers,
|
|
263
|
+
minimumSelfHealingDeployment,
|
|
264
|
+
message: rule + undeclaredAdvice + noMarginAdvice + holdersCaveat
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
superMajorityThreshold: options.clusterPolicy?.superMajorityThreshold ?? DEFAULT_SUPER_MAJORITY_THRESHOLD,
|
|
270
|
+
simpleMajorityThreshold: 0.51,
|
|
271
|
+
minAbsoluteClusterSize,
|
|
272
|
+
allowClusterDownsize: options.clusterPolicy?.allowDownsize ?? true,
|
|
273
|
+
clusterSizeTolerance: options.clusterPolicy?.sizeTolerance ?? 0.5,
|
|
274
|
+
// Fail closed by default (an undersized cluster with no confident network-size estimate is
|
|
275
|
+
// rejected); embedders running knowingly-small meshes opt in through clusterPolicy.
|
|
276
|
+
allowUnvalidatedSmallCluster: options.clusterPolicy?.allowUnvalidatedSmallCluster ?? false,
|
|
277
|
+
// Pass through undefined (ClusterMember defaults it to 'accept') so an operator who said
|
|
278
|
+
// nothing gets the historical behaviour.
|
|
279
|
+
unvalidatablePendPolicy: options.clusterPolicy?.unvalidatablePendPolicy,
|
|
280
|
+
partitionDetectionWindow: 60000,
|
|
281
|
+
// Replication factor / target cohort breadth — what the coordinator aims for when selecting a
|
|
282
|
+
// cohort. Deliberately NOT the membership admission gate's yardstick: it says nothing about how
|
|
283
|
+
// many peers actually exist, so an unconfigured small mesh would refuse every write.
|
|
284
|
+
clusterSize,
|
|
285
|
+
// Membership admission gate, fallback path only (no confident network-size estimate). Defaults
|
|
286
|
+
// permissive so a two- or three-node mesh transacts unconfigured; the cost of that default is
|
|
287
|
+
// bounded to the gate, since the repair floor no longer reads this field.
|
|
288
|
+
assumedClusterSize: declaredCohortSize ?? minAbsoluteClusterSize,
|
|
289
|
+
// Repair corroboration floor, every repair. Defaults strict — to the replication factor — so an
|
|
290
|
+
// unconfigured node cannot have its floor talked down to a single voter by a shrunken cohort
|
|
291
|
+
// view. A genuinely small mesh declares its size (either field) to regain self-repair.
|
|
292
|
+
repairCorroborationClusterSize
|
|
293
|
+
};
|
|
294
|
+
}
|