Repository navigation
Conversation
replicatedConfirmation: N (and confirm=N) resolves once any N peers ack the commit (knownNodes.ts createConfirmationWaiter: a Set keyed by peer name, settled at size === confirmationCount) — not N specific peers, so it cannot guarantee a particular node has the write. Point readers at replicateTo with an explicit node list for that instead. Applied to all three Replication Control subsections (REST, Operations API, programmatic) in both reference/replication/sharding.md and the v4 versioned copy. Dispatch-Task: documentation-replicated-confirmation-semantics Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YRqjFRTLq5wic3yFKhYajq
The first pass said confirm=N guarantees "N replicas exist somewhere", and that replicateTo's explicit node list fixes targeting for confirm. Both are false: a peer outside the record's residency still advances the confirmation watermark via an invalidation entry (partial record, no full copy), and the confirmation waiter counts acks from whichever peers this node replicates to regardless of the request's replicateTo list (knownNodes.ts getReplicationRecipients / createConfirmationWaiter). Also drops the implication that a count and a node list combine in one X-Replicate-To value — REST.ts treats "2,node1" as two hostnames, not a count plus a list. Restates the three notes to separate what replicateTo controls (where the record is stored) from what confirm/replicatedConfirmation counts (any N acks, residency-independent), without claiming the combination proves a named node has the write. Dispatch-Task: documentation-replicated-confirmation-semantics Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YRqjFRTLq5wic3yFKhYajq
…excluded peers A non-resident peer doesn't always get an invalidation entry: with setResidencyById, or on a put/patch to a record the peer was already excluded from in the previous residency, Harper sends it nothing for that record at all (replicationConnection.ts skip branch, same in v4). Its replication position — and so its confirmation count — still advances via a later sequence update or record, independent of what it actually holds. Dispatch-Task: documentation-replicated-confirmation-semantics Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YRqjFRTLq5wic3yFKhYajq
"replicateTo controls where the record is stored" was unqualified: a table's setResidency/setResidencyById function takes precedence over replicateTo entirely (Table.ts: setResidencyById short-circuits before context.replicateTo is read; setResidency overwrites the static getResidency method outright). Added the caveat with a pointer to Custom Sharding, in all three sections of both docs. Also softened the two still-unqualified lines directly above/after the any-N explanation (the confirm=1 bullet, and "confirm can be combined with explicit node lists") so a reader skimming past the explanatory paragraph doesn't land on a sentence that reads as a per-node guarantee on its own. Dispatch-Task: documentation-replicated-confirmation-semantics Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YRqjFRTLq5wic3yFKhYajq
v4's confirmation waiter (knownNodes.ts) is a plain counter with no per-peer identity tracking, unlike v5 Pro's Set-based dedup (knownNodes.ts:894-901) — a peer whose replicated position regresses and re-crosses the write's txnTime can fire onConfirm twice. Dropped "distinct" from the v4 copy only; v5 keeps it since it does dedupe. Also widened the REST section's residency-precedence caveat: it only mentioned "an explicit node list", but setResidencyById short-circuits before context.replicateTo is ever read regardless of whether the request used a count or a node list, so the numeric X-Replicate-To form needed the same caveat (the Operations API/programmatic sections already stated it generically). Dispatch-Task: documentation-replicated-confirmation-semantics Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YRqjFRTLq5wic3yFKhYajq
…re modes Three review rounds flagged the same root cause: repeating the full any-N/placement caveat in the REST, Operations API, and programmatic sections let their wording drift (v5's "distinct" word was missing from two of the three; v4's non-dedup caveat wasn't propagated to the other two after fixing REST). Restructured both docs to state the semantics once, in the REST section, and have Operations API and programmatic point to it — removing the drift risk instead of re-patching each copy. Also documents the confirmation wait's other two exits, which kept surfacing as a gap across rounds: v5 rejects an excessive count with 400 and times out with 504 after 900s; v4 has no timeout and only checks the count against total node count, not actual recipients. Dispatch-Task: documentation-replicated-confirmation-semantics Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YRqjFRTLq5wic3yFKhYajq
…by-one
Four more verified corrections from review round 6:
- Peer-identity dedup landed in harper-pro commit 61a1531b, between
v5.2.0 and v5.2.1 (verified via `git merge-base --is-ancestor` across
tags) — v5.0.0/v5.1.0/v5.2.0 used a bare counter like v4 still does.
Added <VersionBadge type="changed" version="v5.2.1" /> and a sentence
on the earlier behavior.
- "Rejected outright (400)" / "fails with a 504" read as if the write
itself failed. Both throw from inside the commit's success path
(DatabaseTransaction.ts, both versions) — the local write has already
landed either way, only the confirmation wait fails. Reworded, and
fixed "three other ways" to "two" to match what's actually listed.
- v4's rejection check is `confirmationCount > server.nodes.length`,
and server.nodes excludes the local node (knownNodes.ts never pushes
getThisNodeName()) — "total number of nodes in the cluster" overcounts
by one; reworded to "other nodes it knows about".
- v4's :67 ("acknowledgements from any N peers") read as N distinct
peers, contradicting :54's "not necessarily N distinct ones" two
lines up. Reworded to drop the N-peers framing for v4 specifically.
Dispatch-Task: documentation-replicated-confirmation-semantics
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YRqjFRTLq5wic3yFKhYajq
Verified via git history that the whole confirmation rewrite — peer
dedup (61a1531b), the 900s timeout (26b7b3b1), and bounding the reject
threshold to actual recipients (f41142aa) — landed together, first in
v5.2.1 (same commit timestamp, earliest containing tag). Badged both
the dedup claim and the failure-mode paragraph instead of just one,
since both were previously stated as unconditional v5 behavior.
Also:
- "As of <VersionBadge .../>" rendered literally as "As of Changed in:
v5.2.1" since the component already renders its own label — dropped
"As of" and used it as a leading fragment instead, matching the
convention at clustering.md:144.
- "(see below)" pointed at an explanation that only exists on the
separate v4 page, not this one — replaced with an inline comparison
("the way v4 still does").
- "the record exists either way" is wrong for a confirmed delete (it
commits a tombstone, not a record) — reworded to "the write (or
delete)" in both docs.
Dispatch-Task: documentation-replicated-confirmation-semantics
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YRqjFRTLq5wic3yFKhYajq
… term drift Four more verified items from review round 8: - N must be a positive integer in both versions: the waiter compares with strict equality (confirmed.size === confirmationCount in harper-pro, ++count === confirmationCount in v4/harperdb), so a string or fractional count never matches and the wait never resolves on its own. - Confirmation waiting is Harper Pro only: the OSS core submodule reads `confirmReplication` (DatabaseTransaction.ts/LMDBTransaction.ts) but never calls `replicationConfirmation(...)` to register a handler — only harper-pro's top-level replication/knownNodes.ts does. Without Pro, confirm/replicatedConfirmation is silently a no-op. - v4's wait can also hang on an otherwise-healthy peer: its waiter seeds no already-crossed state when registered (no analog to harper-pro's alreadyConfirmedPeers seed, knownNodes.ts:792-798), so an ack that lands between commit and registration is lost. Broadened "if a peer never catches up" accordingly. - Replaced "watermark crossings" with "replication-position crossings" — the only use of "watermark" on the page, inconsistent with "replication position" used everywhere else. Dispatch-Task: documentation-replicated-confirmation-semantics Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YRqjFRTLq5wic3yFKhYajq
My own prior claim ("never matches and the wait never resolves") was
wrong: the REST parser drops a non-numeric confirm value outright
(NaN fails the >= 0 check), the already-confirmed-peers seed can
resolve a bogus count immediately if enough peers already crossed
before the wait registered, and from v5.2.1 the wait ends in a 504
regardless. Rather than chase the exact behavior of unvalidated input
across versions and races, just state the requirement (N must be a
positive integer) and say the value isn't validated and an invalid
one's behavior is unspecified.
Dispatch-Task: documentation-replicated-confirmation-semantics
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YRqjFRTLq5wic3yFKhYajq
There was a problem hiding this comment.
Code Review
This pull request updates the replication and sharding documentation for both the current version and the v4 versioned docs, clarifying the semantics of replication confirmation (confirm=N), deduplication, timeouts, and residency. The review feedback suggests avoiding the use of the component mid-sentence in running prose, recommending plain text alternatives like (as of v5.2.1) to maintain a consistent reading flow.
| - `confirm=1` — wait for confirmation from one additional node before responding | ||
| - `confirm=1` — wait for confirmation from one additional peer before responding (see below for exactly what this confirms) | ||
|
|
||
| `confirm=N` (and the operation-body/programmatic `replicatedConfirmation: N` used below) waits until any N peers acknowledge the commit — not N specific peers. N must be a positive integer; it isn't validated, and a non-integer value's behavior is unspecified. <VersionBadge type="changed" version="v5.2.1" /> — acknowledgements are deduplicated by peer identity, so the same peer can't satisfy more than one of the N; earlier releases, like v4 still does, counted repeated replication-position crossings from one peer as satisfying more than one of the N on their own. A peer outside the record's residency still counts, once its replication position passes the write — even though it may hold only an invalidation entry, or nothing at all for that record. So confirmation cannot guarantee that N full copies exist, or that any particular node — even one named in `X-Replicate-To`/`replicateTo` — has the write. Confirmation waiting requires Harper Pro; without it, `confirm`/`replicatedConfirmation` is accepted but has no effect — the write succeeds immediately with no wait and no error. |
There was a problem hiding this comment.
Avoid using the <VersionBadge> component mid-sentence in running prose. Instead, use plain text such as (as of v5.2.1) to maintain a consistent reading flow.
| `confirm=N` (and the operation-body/programmatic `replicatedConfirmation: N` used below) waits until any N peers acknowledge the commit — not N specific peers. N must be a positive integer; it isn't validated, and a non-integer value's behavior is unspecified. <VersionBadge type="changed" version="v5.2.1" /> — acknowledgements are deduplicated by peer identity, so the same peer can't satisfy more than one of the N; earlier releases, like v4 still does, counted repeated replication-position crossings from one peer as satisfying more than one of the N on their own. A peer outside the record's residency still counts, once its replication position passes the write — even though it may hold only an invalidation entry, or nothing at all for that record. So confirmation cannot guarantee that N full copies exist, or that any particular node — even one named in `X-Replicate-To`/`replicateTo` — has the write. Confirmation waiting requires Harper Pro; without it, `confirm`/`replicatedConfirmation` is accepted but has no effect — the write succeeds immediately with no wait and no error. | |
| `confirm=N` (and the operation-body/programmatic `replicatedConfirmation: N` used below) waits until any N peers acknowledge the commit — not N specific peers. N must be a positive integer; it isn't validated, and a non-integer value's behavior is unspecified. Acknowledgements are deduplicated by peer identity (as of v5.2.1), so the same peer can't satisfy more than one of the N; earlier releases, like v4 still does, counted repeated replication-position crossings from one peer as satisfying more than one of the N on their own. A peer outside the record's residency still counts, once its replication position passes the write — even though it may hold only an invalidation entry, or nothing at all for that record. So confirmation cannot guarantee that N full copies exist, or that any particular node — even one named in `X-Replicate-To`/`replicateTo` — has the write. Confirmation waiting requires Harper Pro; without it, `confirm`/`replicatedConfirmation` is accepted but has no effect — the write succeeds immediately with no wait and no error. |
References
- Avoid using the component mid-sentence in prose as it is awkward; use plain text (e.g., (vX.Y.Z)) instead.
|
|
||
| `X-Replicate-To`/`replicateTo` controls where the record is stored — whether given as a count or an explicit node list — but only under the default residency. A table's `setResidency` or `setResidencyById` function (see Custom Sharding below) takes precedence over it, and placement is independent of what the confirmation count counts either way. | ||
|
|
||
| Confirmation can also end two other ways, both after the write (or delete) has already taken effect locally — only the wait itself fails: a count larger than the peers this node actually replicates the database to is rejected with a 400; otherwise, the request fails with a 504 if confirmation doesn't complete within the server's timeout (900 seconds by default, raised to match a longer configured blob-transfer timeout). <VersionBadge type="changed" version="v5.2.1" /> — earlier releases had no timeout and checked the count against every known node rather than actual recipients, the way v4 still does. |
There was a problem hiding this comment.
Avoid using the <VersionBadge> component mid-sentence in running prose. Use plain text such as (as of v5.2.1) instead to keep the text clean and readable.
| Confirmation can also end two other ways, both after the write (or delete) has already taken effect locally — only the wait itself fails: a count larger than the peers this node actually replicates the database to is rejected with a 400; otherwise, the request fails with a 504 if confirmation doesn't complete within the server's timeout (900 seconds by default, raised to match a longer configured blob-transfer timeout). <VersionBadge type="changed" version="v5.2.1" /> — earlier releases had no timeout and checked the count against every known node rather than actual recipients, the way v4 still does. | |
| Confirmation can also end two other ways, both after the write (or delete) has already taken effect locally — only the wait itself fails: a count larger than the peers this node actually replicates the database to is rejected with a 400; otherwise, the request fails with a 504 if confirmation doesn't complete within the server's timeout (900 seconds by default, raised to match a longer configured blob-transfer timeout) (as of v5.2.1). Earlier releases had no timeout and checked the count against every known node rather than actual recipients, the way v4 still does. |
References
- Avoid using the component mid-sentence in prose as it is awkward; use plain text (e.g., (vX.Y.Z)) instead.
🚀 Preview DeploymentYour preview deployment is ready! 🔗 Preview URL: https://preview.harper-documentation.harperfabric.com/pr-723 This preview will update automatically when you push new commits. |
⊙ Problem
replicatedConfirmation: N/confirm=Nwas documented, but readers (per the QA report this task is based on) assumedconfirm=1meant a specific peer acknowledged the write — it doesn't. Confirmation counts any N peer acknowledgements, and a peer outside the record's residency counts too even though it may hold only an invalidation entry or nothing at all for that record.💡 Solution
Added one clarification, stated once per doc and cross-referenced from the other two Replication Control subsections (REST, Operations API, programmatic) instead of being duplicated three times — duplication is what let earlier drafts of this same note drift internally during review (see commit history).
confirm=N/replicatedConfirmation: Nwaits until any N peers acknowledge — not N specific peers, and not necessarily N peers holding the full record (a non-resident peer can count via an invalidation entry, or nothing at all). Same note, v4 semantics.replicateTo/X-Replicate-Tocontrols where the record is stored (under the default residency —setResidency/setResidencyByIdtake precedence); it does not change which peers' acknowledgements are counted.confirm/replicatedConfirmationdeduplicates by peer identity as of v5.2.1; earlier v5 releases counted repeated crossings from one peer, the way v4 still does.confirm/replicatedConfirmationis silently a no-op.🔧 Changes
reference/replication/sharding.md— current (v5) docs, all three Replication Control subsections under Dynamic Sharding.reference_versioned_docs/version-v4/replication/sharding.md— same clarification with v4-specific semantics (no peer dedup, no timeout,server.nodesexcludes the local node).✅ Verification
Semantics verified against
harper-prosource (not just read, traced and in several cases executed against extracted waiter code across tagged releases) andharperdb(v4) source, per# Context's instruction to verify before writing:harper-pro/replication/knownNodes.tscreateConfirmationWaiterresolves onconfirmed.size === confirmationCount, aSetkeyed by peer name — any N peers, independent of residency (countAlreadyConfirmedPeers/getReplicationRecipients).git merge-base --is-ancestor 61a1531b <tag>across local tags shows the dedup commit (and the paired timeout/recipient-bound commits, same timestamp) first lands inv5.2.1;v5.0.0/v5.1.x/v5.2.0use a bare counter like v4 still does today.harperdb/server/replication/knownNodes.tsincrements a plain counter with no per-peer tracking, and its confirmationPromisehas no reject/timeout path at all..then(DatabaseTransaction.ts/LMDBTransaction.ts, both versions) — the write is durable either way.coreonly readsconfirmReplication(resources/DatabaseTransaction.ts,resources/LMDBTransaction.ts); onlyharper-pro/replication/knownNodes.tscallsreplicationConfirmation(...)to register a handler.npx prettier --checkon both changed files — clean.Findings deliberately left open (pre-existing or outside this diff)
Per the dispatch Findings section — not fixed here, since none are introduced by this diff:
reference/replication/sharding.md:33-38(+ v4) — the top-levelreplication.replicateTo: NYAML example may never be read by core (onlyreplication.databases[].replicateTois) — needs verification against a live cluster.reference/replication/sharding.md:40(+ v4) — "stored on three nodes total" is unconditional; doesn't mentionsetResidency/setResidencyByIdoverride, unlike the per-request paths this PR fixed.reference/replication/sharding.md:44(+ v4) — non-super_user callers get a 403 for any replication parameter; undocumented anywhere on this page.DatabaseTransaction.ts;replicatedConfirmationpassed as a string is never coerced and the strict-equality waiter comparison never matches it.Related PRs: none found
Dispatch: task
documentation-replicated-confirmation-semantics· queued by unknown · ran by claude/sonnet/high · worker kzyp-xps-1Review-Coverage: authored=claude; ran=gemini,codex; adjudicated=domain; declined=cursor-grok,cursor-composer,cursor-kimi,cursor-muse; rounds=10; full=6 @ 6d513c8
Review-Attention: skim ~2m (decisions: unspecified-vs-product-validation, pro-requirement-placement, publish-timeout-constant, document-v4-defect-as-contract) @ 6d513c8