@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.
Files changed (249) hide show
  1. package/{readme.md → README.md} +688 -668
  2. package/dist/src/cluster/block-transfer-service.d.ts +133 -10
  3. package/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
  4. package/dist/src/cluster/block-transfer-service.js +155 -9
  5. package/dist/src/cluster/block-transfer-service.js.map +1 -1
  6. package/dist/src/cluster/block-transfer.d.ts +67 -7
  7. package/dist/src/cluster/block-transfer.d.ts.map +1 -1
  8. package/dist/src/cluster/block-transfer.js +127 -24
  9. package/dist/src/cluster/block-transfer.js.map +1 -1
  10. package/dist/src/cluster/certified-claims.d.ts +170 -0
  11. package/dist/src/cluster/certified-claims.d.ts.map +1 -0
  12. package/dist/src/cluster/certified-claims.js +200 -0
  13. package/dist/src/cluster/certified-claims.js.map +1 -0
  14. package/dist/src/cluster/client-signature-verifier.d.ts +23 -0
  15. package/dist/src/cluster/client-signature-verifier.d.ts.map +1 -0
  16. package/dist/src/cluster/client-signature-verifier.js +33 -0
  17. package/dist/src/cluster/client-signature-verifier.js.map +1 -0
  18. package/dist/src/cluster/cluster-policy.d.ts +9 -3
  19. package/dist/src/cluster/cluster-policy.d.ts.map +1 -1
  20. package/dist/src/cluster/cluster-policy.js +111 -27
  21. package/dist/src/cluster/cluster-policy.js.map +1 -1
  22. package/dist/src/cluster/cluster-repo.d.ts +140 -11
  23. package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
  24. package/dist/src/cluster/cluster-repo.js +485 -29
  25. package/dist/src/cluster/cluster-repo.js.map +1 -1
  26. package/dist/src/cluster/commit-proof.d.ts +134 -0
  27. package/dist/src/cluster/commit-proof.d.ts.map +1 -0
  28. package/dist/src/cluster/commit-proof.js +263 -0
  29. package/dist/src/cluster/commit-proof.js.map +1 -0
  30. package/dist/src/cluster/quorum-restore.d.ts +95 -8
  31. package/dist/src/cluster/quorum-restore.d.ts.map +1 -1
  32. package/dist/src/cluster/quorum-restore.js +113 -14
  33. package/dist/src/cluster/quorum-restore.js.map +1 -1
  34. package/dist/src/cluster/rebalance-monitor.d.ts +84 -0
  35. package/dist/src/cluster/rebalance-monitor.d.ts.map +1 -1
  36. package/dist/src/cluster/rebalance-monitor.js +207 -5
  37. package/dist/src/cluster/rebalance-monitor.js.map +1 -1
  38. package/dist/src/cluster/reconcile-block.d.ts +27 -7
  39. package/dist/src/cluster/reconcile-block.d.ts.map +1 -1
  40. package/dist/src/cluster/reconcile-block.js +179 -38
  41. package/dist/src/cluster/reconcile-block.js.map +1 -1
  42. package/dist/src/cluster/service.d.ts +13 -4
  43. package/dist/src/cluster/service.d.ts.map +1 -1
  44. package/dist/src/cluster/service.js +17 -18
  45. package/dist/src/cluster/service.js.map +1 -1
  46. package/dist/src/cluster/spread-on-churn.d.ts +8 -2
  47. package/dist/src/cluster/spread-on-churn.d.ts.map +1 -1
  48. package/dist/src/cluster/spread-on-churn.js +7 -7
  49. package/dist/src/cluster/spread-on-churn.js.map +1 -1
  50. package/dist/src/cohort-topic/host.d.ts.map +1 -1
  51. package/dist/src/cohort-topic/host.js +8 -7
  52. package/dist/src/cohort-topic/host.js.map +1 -1
  53. package/dist/src/cohort-topic/stream-util.d.ts +7 -6
  54. package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
  55. package/dist/src/cohort-topic/stream-util.js +14 -41
  56. package/dist/src/cohort-topic/stream-util.js.map +1 -1
  57. package/dist/src/dispute/cascade.d.ts +7 -8
  58. package/dist/src/dispute/cascade.d.ts.map +1 -1
  59. package/dist/src/dispute/cascade.js +24 -7
  60. package/dist/src/dispute/cascade.js.map +1 -1
  61. package/dist/src/dispute/dispute-service.d.ts +5 -0
  62. package/dist/src/dispute/dispute-service.d.ts.map +1 -1
  63. package/dist/src/dispute/dispute-service.js +13 -3
  64. package/dist/src/dispute/dispute-service.js.map +1 -1
  65. package/dist/src/dispute/invalidation.d.ts +33 -15
  66. package/dist/src/dispute/invalidation.d.ts.map +1 -1
  67. package/dist/src/dispute/invalidation.js +127 -34
  68. package/dist/src/dispute/invalidation.js.map +1 -1
  69. package/dist/src/dispute/service.d.ts.map +1 -1
  70. package/dist/src/dispute/service.js +2 -1
  71. package/dist/src/dispute/service.js.map +1 -1
  72. package/dist/src/index.d.ts +7 -0
  73. package/dist/src/index.d.ts.map +1 -1
  74. package/dist/src/index.js +7 -0
  75. package/dist/src/index.js.map +1 -1
  76. package/dist/src/libp2p-key-network.d.ts +14 -10
  77. package/dist/src/libp2p-key-network.d.ts.map +1 -1
  78. package/dist/src/libp2p-key-network.js +28 -58
  79. package/dist/src/libp2p-key-network.js.map +1 -1
  80. package/dist/src/libp2p-node-base.d.ts +38 -7
  81. package/dist/src/libp2p-node-base.d.ts.map +1 -1
  82. package/dist/src/libp2p-node-base.js +148 -63
  83. package/dist/src/libp2p-node-base.js.map +1 -1
  84. package/dist/src/network/open-protocol-stream.d.ts +56 -0
  85. package/dist/src/network/open-protocol-stream.d.ts.map +1 -0
  86. package/dist/src/network/open-protocol-stream.js +60 -0
  87. package/dist/src/network/open-protocol-stream.js.map +1 -0
  88. package/dist/src/network/register-protocol-handler.d.ts +66 -0
  89. package/dist/src/network/register-protocol-handler.d.ts.map +1 -0
  90. package/dist/src/network/register-protocol-handler.js +51 -0
  91. package/dist/src/network/register-protocol-handler.js.map +1 -0
  92. package/dist/src/peer-address-book.d.ts +60 -2
  93. package/dist/src/peer-address-book.d.ts.map +1 -1
  94. package/dist/src/peer-address-book.js +89 -1
  95. package/dist/src/peer-address-book.js.map +1 -1
  96. package/dist/src/pend-validation.d.ts +65 -0
  97. package/dist/src/pend-validation.d.ts.map +1 -0
  98. package/dist/src/pend-validation.js +69 -0
  99. package/dist/src/pend-validation.js.map +1 -0
  100. package/dist/src/reactivity/notify-transport.d.ts.map +1 -1
  101. package/dist/src/reactivity/notify-transport.js +2 -1
  102. package/dist/src/reactivity/notify-transport.js.map +1 -1
  103. package/dist/src/reactivity/push-state-gossip.d.ts.map +1 -1
  104. package/dist/src/reactivity/push-state-gossip.js +2 -1
  105. package/dist/src/reactivity/push-state-gossip.js.map +1 -1
  106. package/dist/src/repo/cluster-coordinator.d.ts +32 -6
  107. package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
  108. package/dist/src/repo/cluster-coordinator.js +44 -10
  109. package/dist/src/repo/cluster-coordinator.js.map +1 -1
  110. package/dist/src/repo/coordinator-repo.d.ts +181 -25
  111. package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
  112. package/dist/src/repo/coordinator-repo.js +650 -62
  113. package/dist/src/repo/coordinator-repo.js.map +1 -1
  114. package/dist/src/repo/served-repo-proxy.d.ts +30 -0
  115. package/dist/src/repo/served-repo-proxy.d.ts.map +1 -0
  116. package/dist/src/repo/served-repo-proxy.js +50 -0
  117. package/dist/src/repo/served-repo-proxy.js.map +1 -0
  118. package/dist/src/repo/service.d.ts +16 -1
  119. package/dist/src/repo/service.d.ts.map +1 -1
  120. package/dist/src/repo/service.js +18 -16
  121. package/dist/src/repo/service.js.map +1 -1
  122. package/dist/src/reputation/types.d.ts +3 -1
  123. package/dist/src/reputation/types.d.ts.map +1 -1
  124. package/dist/src/reputation/types.js +3 -1
  125. package/dist/src/reputation/types.js.map +1 -1
  126. package/dist/src/rn.d.ts +24 -2
  127. package/dist/src/rn.d.ts.map +1 -1
  128. package/dist/src/rn.js +33 -4
  129. package/dist/src/rn.js.map +1 -1
  130. package/dist/src/storage/block-archive.d.ts +153 -0
  131. package/dist/src/storage/block-archive.d.ts.map +1 -0
  132. package/dist/src/storage/block-archive.js +194 -0
  133. package/dist/src/storage/block-archive.js.map +1 -0
  134. package/dist/src/storage/block-latch.d.ts +92 -0
  135. package/dist/src/storage/block-latch.d.ts.map +1 -0
  136. package/dist/src/storage/block-latch.js +132 -0
  137. package/dist/src/storage/block-latch.js.map +1 -0
  138. package/dist/src/storage/block-storage.d.ts +169 -12
  139. package/dist/src/storage/block-storage.d.ts.map +1 -1
  140. package/dist/src/storage/block-storage.js +415 -152
  141. package/dist/src/storage/block-storage.js.map +1 -1
  142. package/dist/src/storage/cached-raw-storage.d.ts +5 -1
  143. package/dist/src/storage/cached-raw-storage.d.ts.map +1 -1
  144. package/dist/src/storage/cached-raw-storage.js +16 -1
  145. package/dist/src/storage/cached-raw-storage.js.map +1 -1
  146. package/dist/src/storage/cached-store-driver.d.ts +14 -4
  147. package/dist/src/storage/cached-store-driver.d.ts.map +1 -1
  148. package/dist/src/storage/cached-store-driver.js +39 -5
  149. package/dist/src/storage/cached-store-driver.js.map +1 -1
  150. package/dist/src/storage/i-block-storage.d.ts +70 -19
  151. package/dist/src/storage/i-block-storage.d.ts.map +1 -1
  152. package/dist/src/storage/i-block-storage.js +21 -1
  153. package/dist/src/storage/i-block-storage.js.map +1 -1
  154. package/dist/src/storage/i-raw-storage.d.ts +38 -0
  155. package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
  156. package/dist/src/storage/kv-raw-storage.d.ts +14 -8
  157. package/dist/src/storage/kv-raw-storage.d.ts.map +1 -1
  158. package/dist/src/storage/kv-raw-storage.js +29 -9
  159. package/dist/src/storage/kv-raw-storage.js.map +1 -1
  160. package/dist/src/storage/memory-store-driver.d.ts +4 -1
  161. package/dist/src/storage/memory-store-driver.d.ts.map +1 -1
  162. package/dist/src/storage/memory-store-driver.js +22 -2
  163. package/dist/src/storage/memory-store-driver.js.map +1 -1
  164. package/dist/src/storage/raw-store-codec.js +2 -2
  165. package/dist/src/storage/raw-store-driver.d.ts +44 -3
  166. package/dist/src/storage/raw-store-driver.d.ts.map +1 -1
  167. package/dist/src/storage/shared-cache-pool.d.ts +60 -3
  168. package/dist/src/storage/shared-cache-pool.d.ts.map +1 -1
  169. package/dist/src/storage/shared-cache-pool.js +68 -4
  170. package/dist/src/storage/shared-cache-pool.js.map +1 -1
  171. package/dist/src/storage/storage-repo.d.ts +204 -26
  172. package/dist/src/storage/storage-repo.d.ts.map +1 -1
  173. package/dist/src/storage/storage-repo.js +422 -119
  174. package/dist/src/storage/storage-repo.js.map +1 -1
  175. package/dist/src/storage/store-identity.d.ts +21 -0
  176. package/dist/src/storage/store-identity.d.ts.map +1 -0
  177. package/dist/src/storage/store-identity.js +33 -0
  178. package/dist/src/storage/store-identity.js.map +1 -0
  179. package/dist/src/storage/struct.d.ts +15 -0
  180. package/dist/src/storage/struct.d.ts.map +1 -1
  181. package/dist/src/storage/with-read-cache.d.ts +92 -0
  182. package/dist/src/storage/with-read-cache.d.ts.map +1 -0
  183. package/dist/src/storage/with-read-cache.js +132 -0
  184. package/dist/src/storage/with-read-cache.js.map +1 -0
  185. package/dist/src/sync/service.d.ts +13 -3
  186. package/dist/src/sync/service.d.ts.map +1 -1
  187. package/dist/src/sync/service.js +7 -30
  188. package/dist/src/sync/service.js.map +1 -1
  189. package/dist/src/testing/mesh-harness.d.ts +75 -2
  190. package/dist/src/testing/mesh-harness.d.ts.map +1 -1
  191. package/dist/src/testing/mesh-harness.js +193 -81
  192. package/dist/src/testing/mesh-harness.js.map +1 -1
  193. package/dist/src/testing/raw-storage-conformance.d.ts +8 -0
  194. package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
  195. package/dist/src/testing/raw-storage-conformance.js +97 -2
  196. package/dist/src/testing/raw-storage-conformance.js.map +1 -1
  197. package/package.json +5 -6
  198. package/src/cluster/block-transfer-service.ts +528 -312
  199. package/src/cluster/block-transfer.ts +547 -424
  200. package/src/cluster/certified-claims.ts +319 -0
  201. package/src/cluster/client-signature-verifier.ts +34 -0
  202. package/src/cluster/cluster-policy.ts +294 -203
  203. package/src/cluster/cluster-repo.ts +2501 -2034
  204. package/src/cluster/commit-proof.ts +344 -0
  205. package/src/cluster/quorum-restore.ts +348 -223
  206. package/src/cluster/rebalance-monitor.ts +539 -254
  207. package/src/cluster/reconcile-block.ts +404 -203
  208. package/src/cluster/service.ts +23 -20
  209. package/src/cluster/spread-on-churn.ts +352 -346
  210. package/src/cohort-topic/host.ts +8 -7
  211. package/src/cohort-topic/stream-util.ts +14 -43
  212. package/src/dispute/cascade.ts +31 -15
  213. package/src/dispute/dispute-service.ts +706 -696
  214. package/src/dispute/invalidation.ts +162 -52
  215. package/src/dispute/service.ts +126 -125
  216. package/src/index.ts +7 -0
  217. package/src/libp2p-key-network.ts +1204 -1235
  218. package/src/libp2p-node-base.ts +185 -70
  219. package/src/network/open-protocol-stream.ts +87 -0
  220. package/src/network/register-protocol-handler.ts +85 -0
  221. package/src/peer-address-book.ts +96 -2
  222. package/src/pend-validation.ts +89 -0
  223. package/src/reactivity/notify-transport.ts +2 -1
  224. package/src/reactivity/push-state-gossip.ts +2 -1
  225. package/src/repo/cluster-coordinator.ts +65 -11
  226. package/src/repo/coordinator-repo.ts +729 -71
  227. package/src/repo/served-repo-proxy.ts +55 -0
  228. package/src/repo/service.ts +25 -16
  229. package/src/reputation/types.ts +3 -1
  230. package/src/rn.ts +34 -4
  231. package/src/storage/block-archive.ts +242 -0
  232. package/src/storage/block-latch.ts +144 -0
  233. package/src/storage/block-storage.ts +787 -498
  234. package/src/storage/cached-raw-storage.ts +21 -1
  235. package/src/storage/cached-store-driver.ts +898 -859
  236. package/src/storage/i-block-storage.ts +172 -116
  237. package/src/storage/i-raw-storage.ts +40 -0
  238. package/src/storage/kv-raw-storage.ts +34 -9
  239. package/src/storage/memory-store-driver.ts +26 -2
  240. package/src/storage/raw-store-codec.ts +42 -42
  241. package/src/storage/raw-store-driver.ts +45 -3
  242. package/src/storage/shared-cache-pool.ts +516 -452
  243. package/src/storage/storage-repo.ts +1421 -1014
  244. package/src/storage/store-identity.ts +41 -0
  245. package/src/storage/struct.ts +52 -34
  246. package/src/storage/with-read-cache.ts +182 -0
  247. package/src/sync/service.ts +178 -198
  248. package/src/testing/mesh-harness.ts +524 -344
  249. package/src/testing/raw-storage-conformance.ts +114 -2
@@ -0,0 +1,200 @@
1
+ import { verifyBlockCommitProofClaim, verifyBlockCommitProofContent } from "./commit-proof.js";
2
+ import { createLogger } from "../logger.js";
3
+ const log = createLogger('certified-claims');
4
+ /**
5
+ * Hard bound on `proof.peerIds.length`, enforced BEFORE any hashing or signature work. Nothing on
6
+ * the repair wire bounds a proof's cohort count — only byte caps apply (1 MiB control / 8 MiB sync
7
+ * response), which would still admit ~80k signature verifications — so this module enforces the
8
+ * bound `commit-proof.ts` declares as its caller obligation #2. 256 is far above any plausible
9
+ * cohort (deployments run ~10) while keeping the worst-case verification cost trivial.
10
+ *
11
+ * NOTE: this cap bounds the SIGNATURE count (at most `MAX_PROOF_SIGNERS` Ed25519 verifies per
12
+ * round, since `countApprovals` skips signers outside `peerIds` before any crypto). It does NOT
13
+ * bound the proof's serialized SIZE: `computeClusterMessageHash` / `...PromiseHash` /
14
+ * `...CommitHash` canonically serialize `message`, `promises` and `commits`, whose entry counts a
15
+ * peer chooses freely inside the transport byte cap. That cost is linear and today bounded by the
16
+ * 1 MiB control-message cap; if repair verification ever shows up in a profile, or a deployment
17
+ * raises those transport caps, add a serialized-size bound here alongside the signer count.
18
+ */
19
+ export const MAX_PROOF_SIGNERS = 256;
20
+ /**
21
+ * Every {@link CertifyFailure}, classified: may it be held against the peer that SERVED the proof?
22
+ *
23
+ * Exhaustive by type — `Record<CertifyFailure, boolean>` means adding a {@link ProofFailure}
24
+ * variant in `commit-proof.ts` fails the build here until it is classified, rather than silently
25
+ * defaulting to `true` (penalize), which is the wrong direction for an unknown reason.
26
+ *
27
+ * `false` — never a penalty, because the identity behind the artifact was not proven (mirrors
28
+ * `ClusterMember.verifySignature`'s outcome discipline, restated in `commit-proof.ts`) or because
29
+ * the outcome is not misbehavior at all.
30
+ *
31
+ * `true` — the peer's own artifact provably lies, or provably does not cover what it was served
32
+ * for.
33
+ */
34
+ const ATTRIBUTABLE_PROOF_FAILURES = {
35
+ // Identity not proven — an unparseable or unbound signer, or structural garbage that could have
36
+ // been authored by anyone in the chain.
37
+ 'unknown-signer': false,
38
+ 'non-ed25519-signer': false,
39
+ 'malformed-signature': false,
40
+ 'malformed-proof': false,
41
+ // A v1 / unversioned record is history, not misbehavior: it binds no peer set and never could.
42
+ 'legacy-record': false,
43
+ // A genuine mega-cohort is conceivable and the cap declines it unexamined — no evidence either way.
44
+ 'oversized-cohort': false,
45
+ // Not a failure of the proof at all: the cohort declared no digest for this block, so there was
46
+ // nothing to compare the served bytes against. A verdict ("content uncertified, rev certified"),
47
+ // never misbehavior.
48
+ 'no-digest-declared': false,
49
+ // The artifact contradicts itself or the claim it was served for. `buildBlockCommitProof` derives
50
+ // `peerIds` from `Object.keys(record.peers)`, so honest construction cannot produce a duplicate —
51
+ // serving one implies authorship, as does a digest or hash that does not recompute.
52
+ 'membership-mismatch': true,
53
+ 'message-hash-mismatch': true,
54
+ 'duplicate-signer': true,
55
+ 'promise-threshold': true,
56
+ 'commit-threshold': true,
57
+ // The replay case: a genuine proof presented for a claim it does not cover.
58
+ 'claim-not-in-message': true,
59
+ // The peer served bytes that provably are not the committed content.
60
+ 'digest-mismatch': true
61
+ };
62
+ /**
63
+ * Failure reasons that must NEVER become a reputation penalty for the serving peer — the `false`
64
+ * half of {@link ATTRIBUTABLE_PROOF_FAILURES}, derived so the set and
65
+ * {@link isAttributableProofFailure} cannot drift apart. Prefer the predicate; this set is exported
66
+ * for callers that want to name the whole population (logs, tests).
67
+ */
68
+ export const NON_ATTRIBUTABLE_PROOF_FAILURES = new Set(Object.keys(ATTRIBUTABLE_PROOF_FAILURES)
69
+ .filter(failure => !ATTRIBUTABLE_PROOF_FAILURES[failure]));
70
+ /**
71
+ * May this failure be held against the peer that served the proof? One predicate so the two repair
72
+ * paths cannot drift on the classification — see {@link ATTRIBUTABLE_PROOF_FAILURES} for the
73
+ * per-reason rationale.
74
+ */
75
+ export function isAttributableProofFailure(failure) {
76
+ return ATTRIBUTABLE_PROOF_FAILURES[failure] === true;
77
+ }
78
+ /**
79
+ * The {@link ProofThresholds} both repair paths verify against, from the cohort's configured
80
+ * `superMajorityThreshold`.
81
+ *
82
+ * `simpleMajorityThreshold` is hardcoded 0.5 — NOT the deployment's configured value (0.51 by
83
+ * default): cohort members enforce `count > total / 2` (`ClusterMember.hasMajority`), and
84
+ * `ProofThresholds` requires a verifier to mirror what the members actually enforced. Verifying
85
+ * against the configured value would reject proofs real cohorts produce.
86
+ *
87
+ * One function so the read path (`CoordinatorRepo.queryClusterForLatest`) and the commit-path
88
+ * reconcile (`cluster/reconcile-block.ts`) cannot drift on the one number that has to match
89
+ * `hasMajority`.
90
+ */
91
+ export function proofThresholds(superMajorityThreshold) {
92
+ return { superMajorityThreshold, simpleMajorityThreshold: 0.5 };
93
+ }
94
+ /**
95
+ * Certify a repair claim by its attached cohort commit proof: cap the cohort, verify offline
96
+ * ({@link verifyBlockCommitProofClaim}), then run the anchoring layer on success. Total on hostile
97
+ * input like the verifier it wraps — never throws.
98
+ */
99
+ export async function certifyClaim(proof, claim, thresholds, anchoring = {}) {
100
+ if (exceedsSignerCap(proof)) {
101
+ return { certified: false, failure: 'oversized-cohort' };
102
+ }
103
+ const verdict = await verifyBlockCommitProofClaim(proof, claim, thresholds);
104
+ if (!verdict.ok) {
105
+ return { certified: false, failure: verdict.reason };
106
+ }
107
+ await anchorAcceptedProof(proof, claim, anchoring);
108
+ return { certified: true };
109
+ }
110
+ /**
111
+ * Certify a claim AND the served block bytes ({@link verifyBlockCommitProofContent}). The two
112
+ * halves are reported separately because the content checks (`digest-mismatch` /
113
+ * `no-digest-declared`) only run after the claim half has already PASSED — a proof that certifies
114
+ * the revision while the bytes disagree still certifies the revision; the caller drops (and, for
115
+ * `digest-mismatch`, penalizes) the bytes. Anchoring runs whenever the claim half passed: that is
116
+ * the moment a proof was accepted as evidence.
117
+ */
118
+ export async function certifyContent(proof, claim, block, thresholds, anchoring = {}) {
119
+ if (exceedsSignerCap(proof)) {
120
+ return { revCertified: false, contentCertified: false, failure: 'oversized-cohort' };
121
+ }
122
+ const verdict = await verifyBlockCommitProofContent(proof, claim, block, thresholds);
123
+ if (verdict.ok) {
124
+ await anchorAcceptedProof(proof, claim, anchoring);
125
+ return { revCertified: true, contentCertified: true };
126
+ }
127
+ if (verdict.reason === 'digest-mismatch' || verdict.reason === 'no-digest-declared') {
128
+ await anchorAcceptedProof(proof, claim, anchoring);
129
+ return { revCertified: true, contentCertified: false, failure: verdict.reason };
130
+ }
131
+ return { revCertified: false, contentCertified: false, failure: verdict.reason };
132
+ }
133
+ /**
134
+ * The cap check, structurally safe on hostile shapes: a `peerIds` that is not an array — or a
135
+ * property access that throws, which is why the whole read is guarded — falls through to the
136
+ * verifier, which reports `malformed-proof` without doing signature work either. This runs before
137
+ * {@link certifyClaim}'s and {@link certifyContent}'s only other call, so it is the one place their
138
+ * "never throws" contract could be broken.
139
+ */
140
+ function exceedsSignerCap(proof) {
141
+ try {
142
+ return proof !== null && typeof proof === 'object'
143
+ && Array.isArray(proof.peerIds) && proof.peerIds.length > MAX_PROOF_SIGNERS;
144
+ }
145
+ catch {
146
+ return false;
147
+ }
148
+ }
149
+ /**
150
+ * Layer 2 + degradation for a proof the offline layer accepted. Purely observational — nothing
151
+ * here can revoke the certification: overlap is logged (never gated — historic cohort rotation
152
+ * makes zero overlap legitimate for old data, and gating would re-create the lone-holder-unreadable
153
+ * defect), infeasibility degrades to accept-and-surface, and a throwing capability is logged and
154
+ * treated as infeasible.
155
+ */
156
+ async function anchorAcceptedProof(proof, claim, anchoring) {
157
+ const signerCount = proof.peerIds.length;
158
+ if (!anchoring.recomputeBlockCohort) {
159
+ acceptUnanchored(claim, signerCount, 'no-recompute-capability', anchoring);
160
+ return;
161
+ }
162
+ let verdict;
163
+ try {
164
+ verdict = await anchoring.recomputeBlockCohort(claim.blockId);
165
+ }
166
+ catch (err) {
167
+ log('anchor-recompute-error block=%s error=%o', claim.blockId, err);
168
+ acceptUnanchored(claim, signerCount, 'recompute-infeasible', anchoring);
169
+ return;
170
+ }
171
+ // A verdict that is null or otherwise off-contract reads as infeasible rather than throwing —
172
+ // `recomputeBlockCohort` is caller-supplied, so its return shape is as untrusted as its behavior.
173
+ if (!verdict?.feasible) {
174
+ acceptUnanchored(claim, signerCount, 'recompute-infeasible', anchoring);
175
+ return;
176
+ }
177
+ try {
178
+ const cohort = new Set(verdict.cohortPeerIds);
179
+ const overlap = proof.peerIds.filter(id => cohort.has(id)).length;
180
+ log('anchor-overlap block=%s rev=%d action=%s overlap=%d signers=%d cohort=%d', claim.blockId, claim.rev, claim.actionId, overlap, signerCount, cohort.size);
181
+ }
182
+ catch (err) {
183
+ // A non-iterable `cohortPeerIds`. The comparison is observational, so losing it costs a log
184
+ // line, never the certification.
185
+ log('anchor-overlap-error block=%s error=%o', claim.blockId, err);
186
+ }
187
+ }
188
+ /** Accept-and-surface a proof certified on layer 1 alone — never silent, never throwing. */
189
+ function acceptUnanchored(claim, signerCount, reason, anchoring) {
190
+ log('accept-unanchored block=%s rev=%d action=%s signers=%d reason=%s', claim.blockId, claim.rev, claim.actionId, signerCount, reason);
191
+ try {
192
+ anchoring.onUnanchored?.({
193
+ blockId: claim.blockId, rev: claim.rev, actionId: claim.actionId, signerCount, reason
194
+ });
195
+ }
196
+ catch (err) {
197
+ log('accept-unanchored-callback-error block=%s error=%o', claim.blockId, err);
198
+ }
199
+ }
200
+ //# sourceMappingURL=certified-claims.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"certified-claims.js","sourceRoot":"","sources":["../../../src/cluster/certified-claims.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The p2p-backed {@link ClientSignatureVerifier}: the closure a cluster member's
3
+ * {@link TransactionValidator} uses to decide whether a client transaction was really signed by the
4
+ * identity its stamp claims.
5
+ *
6
+ * Lives here — one exported factory rather than a closure inlined at each wiring site — because two
7
+ * consumers must agree byte-for-byte or the tests stop proving anything about production: the
8
+ * Quereus plugin's `createQuereusValidator` (the real deployment path once a composition root
9
+ * supplies `NodeOptions.validator`) and `db-p2p`'s own signature specs, whose whole claim is that
10
+ * they drive "the exact closure production binds".
11
+ */
12
+ import type { ClientSignatureVerifier } from '@optimystic/db-core';
13
+ /**
14
+ * Derive the signer's Ed25519 public key from the peer-id string embedded in `stamp.peerId` and
15
+ * verify the base64url signature over the canonical payload.
16
+ *
17
+ * TOTAL on adversarial input, which the {@link ClientSignatureVerifier} port requires: a throw here
18
+ * escapes `ClusterMember.validatePendOperations` instead of becoming a signed reject vote.
19
+ * `verifyPeerSig` already returns `false` rather than throwing on a non-Ed25519 or malformed
20
+ * peer-id; the try/catch adds the same guarantee for the base64url decode.
21
+ */
22
+ export declare function createPeerClientSignatureVerifier(): ClientSignatureVerifier;
23
+ //# sourceMappingURL=client-signature-verifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-signature-verifier.d.ts","sourceRoot":"","sources":["../../../src/cluster/client-signature-verifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAInE;;;;;;;;GAQG;AACH,wBAAgB,iCAAiC,IAAI,uBAAuB,CAQ3E"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The p2p-backed {@link ClientSignatureVerifier}: the closure a cluster member's
3
+ * {@link TransactionValidator} uses to decide whether a client transaction was really signed by the
4
+ * identity its stamp claims.
5
+ *
6
+ * Lives here — one exported factory rather than a closure inlined at each wiring site — because two
7
+ * consumers must agree byte-for-byte or the tests stop proving anything about production: the
8
+ * Quereus plugin's `createQuereusValidator` (the real deployment path once a composition root
9
+ * supplies `NodeOptions.validator`) and `db-p2p`'s own signature specs, whose whole claim is that
10
+ * they drive "the exact closure production binds".
11
+ */
12
+ import { b64urlToBytes } from '@optimystic/db-core';
13
+ import { verifyPeerSig } from '../cohort-topic/peer-sig.js';
14
+ /**
15
+ * Derive the signer's Ed25519 public key from the peer-id string embedded in `stamp.peerId` and
16
+ * verify the base64url signature over the canonical payload.
17
+ *
18
+ * TOTAL on adversarial input, which the {@link ClientSignatureVerifier} port requires: a throw here
19
+ * escapes `ClusterMember.validatePendOperations` instead of becoming a signed reject vote.
20
+ * `verifyPeerSig` already returns `false` rather than throwing on a non-Ed25519 or malformed
21
+ * peer-id; the try/catch adds the same guarantee for the base64url decode.
22
+ */
23
+ export function createPeerClientSignatureVerifier() {
24
+ return (peerId, payload, signature) => {
25
+ try {
26
+ return verifyPeerSig(peerId, payload, b64urlToBytes(signature));
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ };
32
+ }
33
+ //# sourceMappingURL=client-signature-verifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-signature-verifier.js","sourceRoot":"","sources":["../../../src/cluster/client-signature-verifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAE5D;;;;;;;;GAQG;AACH,MAAM,UAAU,iCAAiC;IAChD,OAAO,CAAC,MAAc,EAAE,OAAmB,EAAE,SAAiB,EAAW,EAAE;QAC1E,IAAI,CAAC;YACJ,OAAO,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC;QACjE,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,KAAK,CAAC;QACd,CAAC;IACF,CAAC,CAAC;AACH,CAAC"}
@@ -1,4 +1,4 @@
1
- import { type ClusterConsensusConfig } from "@optimystic/db-core";
1
+ import { type ClusterConsensusConfig, type UnvalidatablePendPolicy } from "@optimystic/db-core";
2
2
  /**
3
3
  * Resolves the operator-facing cluster knobs (`clusterSize`, `clusterPolicy.*`) into the concrete
4
4
  * numbers the consensus and block-restoration paths run on.
@@ -97,6 +97,12 @@ export interface ClusterPolicyOptions {
97
97
  * `clusterSize: 2`) to self-repair.
98
98
  */
99
99
  assumedClusterSize?: number;
100
+ /**
101
+ * What a validator-configured member does with a pend that carries no `validation` payload
102
+ * (nothing to re-execute — the single-collection `Collection.sync` shape). Default 'accept';
103
+ * see {@link UnvalidatablePendPolicy}.
104
+ */
105
+ unvalidatablePendPolicy?: UnvalidatablePendPolicy;
100
106
  };
101
107
  }
102
108
  /** Everything a node's consensus + restoration paths need, with every default already applied. */
@@ -116,8 +122,8 @@ export type ResolvedClusterPolicy = ClusterConsensusConfig & {
116
122
  /**
117
123
  * Apply every cluster-policy default a node needs. Same options in, same numbers out, so the
118
124
  * composition root's behavior is unit-testable (`test/cluster-policy.spec.ts`). Its one side effect
119
- * is the `assumed-cluster-size-unset` advisory below, which lives here because this is the only place
120
- * that knows the resolution produced a self-defeating combination.
125
+ * is the `repair-fault-tolerance` advisory below, which lives here because this is the only place
126
+ * that knows the resolution produced a combination with no repair margin — or none at all.
121
127
  */
122
128
  export declare function resolveClusterPolicy(options: ClusterPolicyOptions): ResolvedClusterPolicy;
123
129
  //# sourceMappingURL=cluster-policy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cluster-policy.d.ts","sourceRoot":"","sources":["../../../src/cluster/cluster-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAMpG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACpC;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE;QACf,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,0DAA0D;QAC1D,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,sGAAsG;QACtG,sBAAsB,CAAC,EAAE,MAAM,CAAC;QAChC;;;;;WAKG;QACH,4BAA4B,CAAC,EAAE,OAAO,CAAC;QACvC;;;;;;;;;;;WAWG;QACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;KAC5B,CAAC;CACF;AAED,kGAAkG;AAClG,MAAM,MAAM,qBAAqB,GAAG,sBAAsB,GAAG;IAC5D,oFAAoF;IACpF,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;;OAOG;IACH,8BAA8B,EAAE,MAAM,CAAC;CACvC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,oBAAoB,GAAG,qBAAqB,CAuEzF"}
1
+ {"version":3,"file":"cluster-policy.d.ts","sourceRoot":"","sources":["../../../src/cluster/cluster-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,sBAAsB,EAAE,KAAK,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAMlI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACpC;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE;QACf,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,0DAA0D;QAC1D,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,sGAAsG;QACtG,sBAAsB,CAAC,EAAE,MAAM,CAAC;QAChC;;;;;WAKG;QACH,4BAA4B,CAAC,EAAE,OAAO,CAAC;QACvC;;;;;;;;;;;WAWG;QACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B;;;;WAIG;QACH,uBAAuB,CAAC,EAAE,uBAAuB,CAAC;KAClD,CAAC;CACF;AAED,kGAAkG;AAClG,MAAM,MAAM,qBAAqB,GAAG,sBAAsB,GAAG;IAC5D,oFAAoF;IACpF,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;;OAOG;IACH,8BAA8B,EAAE,MAAM,CAAC;CACvC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,oBAAoB,GAAG,qBAAqB,CA4JzF"}
@@ -60,8 +60,8 @@ export const DEFAULT_CLUSTER_SIZE = 10;
60
60
  /**
61
61
  * Apply every cluster-policy default a node needs. Same options in, same numbers out, so the
62
62
  * composition root's behavior is unit-testable (`test/cluster-policy.spec.ts`). Its one side effect
63
- * is the `assumed-cluster-size-unset` advisory below, which lives here because this is the only place
64
- * that knows the resolution produced a self-defeating combination.
63
+ * is the `repair-fault-tolerance` advisory below, which lives here because this is the only place
64
+ * that knows the resolution produced a combination with no repair margin — or none at all.
65
65
  */
66
66
  export function resolveClusterPolicy(options) {
67
67
  // undefined here means "the operator said nothing", which is the only case where the two
@@ -76,36 +76,117 @@ export function resolveClusterPolicy(options) {
76
76
  const declaredCohortSize = options.clusterPolicy?.assumedClusterSize;
77
77
  const clusterSize = options.clusterSize ?? DEFAULT_CLUSTER_SIZE;
78
78
  const repairCorroborationClusterSize = declaredCohortSize ?? clusterSize;
79
- // Called once per node (resolveClusterPolicy runs once at construction), so this fires once per
80
- // node startup, not per repair — a per-attempt warn on a busy node would be noise that gets
81
- // filtered, defeating the point. Fires purely off configuration (not an observed cohort), so a
82
- // deployment that genuinely runs `clusterSize` machines sees it too; worded as a conditional
83
- // ("if you run fewer than N machines") rather than a fault for exactly that reason.
79
+ // ## What the advisory actually claims, and why the trigger is what it is
84
80
  //
85
- // The machine count in the message is CORROBORATION_FLOOR + 1, NOT repairCorroborationClusterSize:
86
- // `corroboratorCapacity` caps only the FLOOR of two (`quorum-restore.ts`), so a cohort with two
87
- // peers besides the reader meets it whatever the declared size. What an undeclared size costs is
88
- // the relaxation below two, which is only reachable when repairCorroborationClusterSize <= 2.
89
- // NOTE: only the UNDECLARED case warns. An operator who declares an assumedClusterSize larger than
90
- // the cohort they actually run is equally unable to repair and gets no warning — deliberate, since
91
- // a declaration is an explicit assertion and this function has no observed cohort to contradict it
92
- // with. If `feat-admission-floor-from-observed-cohort-high-water-mark` ever lands (deriving the
93
- // yardstick from observation), that warning becomes cheap and worth adding here.
94
- if (declaredCohortSize === undefined && clusterSize > minAbsoluteClusterSize) {
95
- const minimumSelfHealingDeployment = CORROBORATION_FLOOR + 1;
96
- log('assumed-cluster-size-unset', {
81
+ // The rule it states is measurable, not rhetorical: sweep `corroboratorCapacity` and `quorumSize`
82
+ // over real cohort sizes and every configuration lands on the same requirement — **2 cohort peers
83
+ // BESIDES the reader must answer that reader and agree on the same (rev, actionId)**, relaxed to 1
84
+ // only for a cohort DECLARED smaller than three. Two consequences follow, and both are worth
85
+ // saying out loud because both surprised people in the field:
86
+ //
87
+ // - Fewer than three machines, undeclared, can never repair at all: the floor of two never
88
+ // relaxes, because `repairCorroborationClusterSize` falls back to `clusterSize` (default 10).
89
+ // - Exactly three machines is the MINIMUM that can ever repair, not a size at which repair is
90
+ // safe. The reader has exactly two peers and needs both, so a single peer unreachable FROM THAT
91
+ // READER perfectly healthy and reachable from everybody else — leaves that reader's copy
92
+ // permanently unrepairable. Four machines is the first size with any margin.
93
+ //
94
+ // Both of those are claims about MACHINES, and the advisory used to stop there — which made it
95
+ // wrong in the operator's favour, because a machine count is only half the requirement. The peers
96
+ // that answer must also HOLD the block, and two of them must. A block only ONE cohort peer holds
97
+ // cannot be repaired at any deployment size whatsoever: the sole holder cannot second itself, and
98
+ // the two mechanisms that would give a second peer a copy (read-repair and reconcile) both consume
99
+ // this same decision. So an operator at four-plus machines reading "the first size with any margin"
100
+ // could believe they were covered while a block written when the deployment was smaller stayed
101
+ // stranded at one copy forever. The advisory now says so; the per-block half is
102
+ // `cluster-fetch:repair-deadlock` with `reason: 'sole-holder'` (`CoordinatorRepo`), and the
103
+ // behavioural fix — replicating owned blocks once the cohort grows — is separate work.
104
+ //
105
+ // So the trigger is a union of two conditions, not one:
106
+ //
107
+ // - **undeclared** (the original case): the operator has asserted nothing, so a deployment
108
+ // smaller than three would be silently unrepairable. Conditional wording ("if you run fewer
109
+ // than N machines"), never a fault — this fires off configuration, not an observed cohort, so a
110
+ // correctly-provisioned large deployment sees it too.
111
+ // - **resolved cohort <= CORROBORATION_FLOOR + 1**, whether declared or not. Declaring
112
+ // `assumedClusterSize: 3` does not conjure a third peer; it has exactly the same zero tolerance
113
+ // as an undeclared three. The earlier "a declaration is an explicit assertion we cannot
114
+ // contradict" reasoning holds for whether the NUMBER is honest — it does not hold for the
115
+ // fragility implied by the number itself, which is arithmetic.
116
+ //
117
+ // Still one line per node construction (`resolveClusterPolicy` runs once), never per repair: a
118
+ // per-attempt warn on a busy node is noise that gets filtered, which defeats the point. The
119
+ // per-repair half of this — naming a decline that is provably permanent — lives at the repair site
120
+ // instead (`CoordinatorRepo.reportRepairDeadlock`, `cluster-fetch:repair-deadlock`), where the
121
+ // actual cohort is known.
122
+ //
123
+ // NOTE: an operator who declares an assumedClusterSize LARGER than the cohort they actually run is
124
+ // equally unable to repair and still gets no fault — this function has no observed cohort to
125
+ // contradict the declaration with, and the undeclared arm's conditional wording is the closest it
126
+ // can honestly get. If `feat-admission-floor-from-observed-cohort-high-water-mark` ever lands
127
+ // (deriving the yardstick from observation), that check becomes cheap and belongs here.
128
+ const minimumSelfHealingDeployment = CORROBORATION_FLOOR + 1;
129
+ const cohortUndeclared = declaredCohortSize === undefined && clusterSize > minAbsoluteClusterSize;
130
+ const noRepairMargin = repairCorroborationClusterSize <= minimumSelfHealingDeployment;
131
+ if (cohortUndeclared || noRepairMargin) {
132
+ // How many peers besides the reader must answer and agree, at the resolved size. Two once the
133
+ // cohort is three or larger; one for a cohort declared at two, which is the only size whose
134
+ // floor relaxes. Never below one — a claim nobody made is never accepted.
135
+ const requiredAnsweringPeers = Math.max(1, Math.min(CORROBORATION_FLOOR, repairCorroborationClusterSize - 1));
136
+ const availablePeers = Math.max(0, repairCorroborationClusterSize - 1);
137
+ const rule = `Block repair (read-repair and reconcile) converges only when ${CORROBORATION_FLOOR} cohort peers ` +
138
+ `BESIDES the reader answer that reader and agree on the same (rev, actionId); that requirement drops ` +
139
+ `to 1 only for a cohort that DECLARES it is smaller than ${minimumSelfHealingDeployment}. ` +
140
+ `${minimumSelfHealingDeployment} machines is therefore the MINIMUM that can repair at all, not a safe ` +
141
+ `size — at exactly ${minimumSelfHealingDeployment} the reader has two peers and needs both, so one ` +
142
+ `peer unreachable from that reader (healthy and reachable from everyone else) leaves that reader's ` +
143
+ `copy permanently unrepairable. ${minimumSelfHealingDeployment + 1} machines is the first size with ` +
144
+ `any margin.`;
145
+ // Every number above counts MACHINES. Saying only that overstates the guarantee, because repair
146
+ // also needs the answering peers to actually HOLD the block — which is a property of the block,
147
+ // not of the deployment, and which no machine count can supply.
148
+ //
149
+ // NOTE: accepted tradeoff — the caveat rides the EXISTING trigger (cohort size undeclared, or
150
+ // repairCorroborationClusterSize <= 3) rather than firing for every deployment. A correctly-
151
+ // declared large deployment is arguably the operator most likely to believe a machine count
152
+ // covers them, and never sees this paragraph at startup; they learn it from the per-block
153
+ // `cluster-fetch:repair-deadlock` line with reason=sole-holder instead. Weighed and kept: a
154
+ // startup advisory that fires on every correctly-configured node forever is one operators
155
+ // filter, which costs more than it buys. Revisit if the per-block line proves too late to be
156
+ // useful — i.e. if field reports show operators hitting stranded founding data without ever
157
+ // having read a repair-deadlock line.
158
+ const holdersCaveat = ` All of that counts MACHINES, and machines are only half the requirement: the peers that answer ` +
159
+ `must also HOLD the block, and ${CORROBORATION_FLOOR} of them must. A block that only ONE cohort ` +
160
+ `peer holds can never be repaired at ANY deployment size — the sole holder cannot second itself — ` +
161
+ `so every size claim above is about a block at least ${CORROBORATION_FLOOR} peers already hold. ` +
162
+ `The usual way to fall outside that: data written while the deployment (or that block's cohort) ` +
163
+ `was smaller keeps the number of copies it was written with, and GROWING THE DEPLOYMENT DOES NOT ` +
164
+ `COPY IT — so founding data can stay stranded however many machines you later run. That case is ` +
165
+ `reported once per affected block as cluster-fetch:repair-deadlock with reason=sole-holder, and its ` +
166
+ `remedy is another cohort peer holding it (commit a new revision of the block), never more machines.`;
167
+ const undeclaredAdvice = cohortUndeclared
168
+ ? ` No clusterPolicy.assumedClusterSize declared, so the floor is measured against ` +
169
+ `repairCorroborationClusterSize=${repairCorroborationClusterSize} and never relaxes: if you actually ` +
170
+ `run fewer than ${minimumSelfHealingDeployment} machines, every repair declines, permanently. Set ` +
171
+ `clusterPolicy.assumedClusterSize to your real cohort size; it does not lower ` +
172
+ `clusterSize=${clusterSize} (the replication factor). Larger deployments can ignore this.`
173
+ : '';
174
+ const noMarginAdvice = noRepairMargin
175
+ ? ` This node resolved repairCorroborationClusterSize=${repairCorroborationClusterSize}, which leaves ` +
176
+ `repair with NO fault tolerance: the reader has ${availablePeers} cohort peer(s) and needs ` +
177
+ `${requiredAnsweringPeers} of them to answer, so losing one is not survivable. Run at least ` +
178
+ `${minimumSelfHealingDeployment + 1} machines if repair must survive an unreachable peer.`
179
+ : '';
180
+ log('repair-fault-tolerance', {
97
181
  clusterSize,
98
182
  repairCorroborationClusterSize,
99
183
  corroborationFloor: CORROBORATION_FLOOR,
184
+ declaredCohortSize,
185
+ cohortUndeclared,
186
+ noRepairMargin,
187
+ requiredAnsweringPeers,
100
188
  minimumSelfHealingDeployment,
101
- message: `No clusterPolicy.assumedClusterSize declared: block repair (read-repair and reconcile) requires ` +
102
- `${CORROBORATION_FLOOR} distinct corroborating peers other than the reader, and that floor is ` +
103
- `relaxed only for a cohort that DECLARES it is smaller — with ` +
104
- `repairCorroborationClusterSize=${repairCorroborationClusterSize} it never relaxes. So a deployment ` +
105
- `that actually runs fewer than ${minimumSelfHealingDeployment} machines can never supply the floor ` +
106
- `and every repair declines, permanently. If you run fewer than ${minimumSelfHealingDeployment} ` +
107
- `machines, set clusterPolicy.assumedClusterSize to your real cohort size; it does not lower ` +
108
- `clusterSize=${clusterSize} (the replication factor). Larger deployments can ignore this.`
189
+ message: rule + undeclaredAdvice + noMarginAdvice + holdersCaveat
109
190
  });
110
191
  }
111
192
  return {
@@ -117,6 +198,9 @@ export function resolveClusterPolicy(options) {
117
198
  // Fail closed by default (an undersized cluster with no confident network-size estimate is
118
199
  // rejected); embedders running knowingly-small meshes opt in through clusterPolicy.
119
200
  allowUnvalidatedSmallCluster: options.clusterPolicy?.allowUnvalidatedSmallCluster ?? false,
201
+ // Pass through undefined (ClusterMember defaults it to 'accept') so an operator who said
202
+ // nothing gets the historical behaviour.
203
+ unvalidatablePendPolicy: options.clusterPolicy?.unvalidatablePendPolicy,
120
204
  partitionDetectionWindow: 60000,
121
205
  // Replication factor / target cohort breadth — what the coordinator aims for when selecting a
122
206
  // cohort. Deliberately NOT the membership admission gate's yardstick: it says nothing about how
@@ -1 +1 @@
1
- {"version":3,"file":"cluster-policy.js","sourceRoot":"","sources":["../../../src/cluster/cluster-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gCAAgC,EAA+B,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D,MAAM,GAAG,GAAG,YAAY,CAAC,gBAAgB,CAAC,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC;AAExC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AA+DvC;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAA6B;IACjE,yFAAyF;IACzF,4BAA4B;IAC5B,EAAE;IACF,mGAAmG;IACnG,8FAA8F;IAC9F,4FAA4F;IAC5F,8FAA8F;IAC9F,mGAAmG;IACnG,2CAA2C;IAC3C,MAAM,kBAAkB,GAAG,OAAO,CAAC,aAAa,EAAE,kBAAkB,CAAC;IACrE,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,oBAAoB,CAAC;IAChE,MAAM,8BAA8B,GAAG,kBAAkB,IAAI,WAAW,CAAC;IAEzE,gGAAgG;IAChG,4FAA4F;IAC5F,+FAA+F;IAC/F,6FAA6F;IAC7F,oFAAoF;IACpF,EAAE;IACF,mGAAmG;IACnG,gGAAgG;IAChG,iGAAiG;IACjG,8FAA8F;IAC9F,mGAAmG;IACnG,mGAAmG;IACnG,mGAAmG;IACnG,gGAAgG;IAChG,iFAAiF;IACjF,IAAI,kBAAkB,KAAK,SAAS,IAAI,WAAW,GAAG,sBAAsB,EAAE,CAAC;QAC9E,MAAM,4BAA4B,GAAG,mBAAmB,GAAG,CAAC,CAAC;QAC7D,GAAG,CAAC,4BAA4B,EAAE;YACjC,WAAW;YACX,8BAA8B;YAC9B,kBAAkB,EAAE,mBAAmB;YACvC,4BAA4B;YAC5B,OAAO,EACN,kGAAkG;gBAClG,GAAG,mBAAmB,yEAAyE;gBAC/F,+DAA+D;gBAC/D,kCAAkC,8BAA8B,qCAAqC;gBACrG,iCAAiC,4BAA4B,uCAAuC;gBACpG,iEAAiE,4BAA4B,GAAG;gBAChG,6FAA6F;gBAC7F,eAAe,WAAW,gEAAgE;SAC3F,CAAC,CAAC;IACJ,CAAC;IAED,OAAO;QACN,sBAAsB,EAAE,OAAO,CAAC,aAAa,EAAE,sBAAsB,IAAI,gCAAgC;QACzG,uBAAuB,EAAE,IAAI;QAC7B,sBAAsB;QACtB,oBAAoB,EAAE,OAAO,CAAC,aAAa,EAAE,aAAa,IAAI,IAAI;QAClE,oBAAoB,EAAE,OAAO,CAAC,aAAa,EAAE,aAAa,IAAI,GAAG;QACjE,2FAA2F;QAC3F,oFAAoF;QACpF,4BAA4B,EAAE,OAAO,CAAC,aAAa,EAAE,4BAA4B,IAAI,KAAK;QAC1F,wBAAwB,EAAE,KAAK;QAC/B,8FAA8F;QAC9F,gGAAgG;QAChG,qFAAqF;QACrF,WAAW;QACX,+FAA+F;QAC/F,8FAA8F;QAC9F,0EAA0E;QAC1E,kBAAkB,EAAE,kBAAkB,IAAI,sBAAsB;QAChE,gGAAgG;QAChG,6FAA6F;QAC7F,uFAAuF;QACvF,8BAA8B;KAC9B,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"cluster-policy.js","sourceRoot":"","sources":["../../../src/cluster/cluster-policy.ts"],"names":[],"mappings":"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"}