Skip to content

Checkpoint admission applies copy bounds, so each seq row keeps one writer - #3649

Merged
kriszyp merged 9 commits into
mainfrom
feat/checkpoint-copy-bound-admission
Oct 28, 2026
Merged

kriszyp merged 9 commits into
mainfrom
feat/checkpoint-copy-bound-admission

Conversation

@kriszyp

@kriszyp kriszyp commented Oct 27, 2026 •

Copy link
Copy Markdown
Member
Kris rejects a stale ticket at a canyon workshop belt

⊙ Problem

A base copy writes rows without log entries, so a route that certified their origin from below those rows is no longer complete. Admission reads only [seq, peer] rows, so replication rewrote other routes' rows to void such starts. Core's sequence updater commits from an earlier read and silently overwrote those voids (harper-pro#1092).

🤔 Should the reader apply the copy bound, rather than a writer voiding route rows?

Yes. A copy's effect is database-wide, and the route it voids may be disconnected, with no updater of its own. Reading the bound at admission keeps one writer per [seq, peer] row and needs no lock on the replicated apply path.

💡 Solution

  • Admission and capture read every [copyBound, peer] row and ignore a covered certifiedFrom, this node's own origin included. A malformed row fails with a 410.
  • A capture taken before the epoch bump that follows a bound commit is not used. The bound writer is replication's, in harper-pro#1092.
  • A revocation naming no generation clears a level-1 floor.
  • A route mark can restore a level-2 route's fullCoverage.
  • A failed sequence-row write now reaches the end_txn's onFailure (with afterCommit), so a source that settled route state at commit can restore it.

⚖️ Alternatives

  • Lock the seq row from read to commit, for every writer: puts a cross-thread lock on every replicated sequence update, and keeps the third-party writes.
  • Record the bound in the copy's own transaction: not crash-atomic. That transaction is WAL-off, __dbis__ is WAL-on, and column families flush independently. Tracked in harper-pro#1134.

🔧 Changes

✅ Verification

  • New cases in subscribeCheckpoint.test.js (admission refuses a covered route, capture issues nothing under the bound, a route that starts above the bound is admitted again, an own-origin bound, six malformed row shapes as 410s) and replicationSeqCursor.test.js (level-1 clear then re-floor; coverage restore only for a matching level-2 row). Both fail on base core and pass with this change.
  • npm run test:unit:resources: 5070 passing. The 4 failures are the four full-text native suites, which fail identically on base core with the locally installed @harperfast/fulltext package.
  • End to end through harper-pro#1092: a real core updater for a disconnected route, with a copy landing between its read and its write, and admission refusing (red on that PR's previous head).
Read cost (bench host, turbo off, 3 interleaved runs)

unitTests/resources/subscriptionCheckpoint.bench.js: captureCertifiedFloors over 16 peer rows of 16 level-2 origins each.

copy bound rows base this change
0 124.9 µs/op 130.3 µs/op
16 (16 origins each) 122.2 µs/op 201.6 µs/op

The read runs once per 5 s capture tick and once per checkpoint resume.

🤖 Generated by Claude Opus 5.5; posted via @kriszyp.

Release target: main — ships with harper-pro#1092, which is the only producer of copy bounds and level-2 route marks

Related PRs: #3469 overlaps, #3494 overlaps, #3504 overlaps (audit-less copy apply and commit-before-sequence ordering), #3605 overlaps (both register a bench in package.json), 15 others independent

Deferred, each filed

Deferred, each filed. Admission items go to the stacked admission-hardening work rather than widening this PR:

Deferred findings, each filed

Deferred findings

  • 6ed82865c0362e2e minor data-integrity — resources/subscriptionCheckpoint.ts — Stale-capture drop and replay recheck depend on harper-pro writer committing bound before copied row and bumping epoch — filed Enforce the copy-bound ordering contract in core, where admission reads it #3647
  • 1f5d09ccc6af379f minor reliability — resources/Table.ts — Level-1 seq row latched floorRevoked:true by a pre-upgrade build is never cleared; the route cannot certify that origin again — filed A level-1 seq row revoked by an earlier build never takes a floor again #3648
  • d77bb44f8670cac9 minor correctness — resources/subscriptionCheckpoint.ts — Capture qualifies certifiedFrom against raw floor; folded boundary S makes admission 410 the node's own fresh token — NOT FILED
  • 56b9969961f1f0cb nit data-integrity — resources/Table.ts — Replay recheck and stale-capture drop depend on the harper-pro writer committing the bound before the copied row and bumping the epoch — filed Enforce the copy-bound ordering contract in core, where admission reads it #3647
  • 792157686c563d60 nit performance — resources/subscriptionCheckpoint.ts — Per-call Map/Buffer/closure allocations in readCopyBounds/readOriginStates — NOT FILED
  • b2e21a27610c15cd nit reliability — resources/Table.ts — lastSequenceId advanced before the data commit stays set when the commit or onCommit fails and the source moves past — NOT FILED
  • 67486c147c73ef9e nit reliability — resources/Table.ts — Companion onFrameFailure logs and meters a committed transaction as failed when only its cursor write failed — NOT FILED
  • cb91c0012ad49d49 nit reliability — resources/Table.ts — Sources without onFailure are not told of a cursor-write failure — NOT FILED

Late Grok findings

6 surviving (highest minor) — Grok reviewed 7e92ec1, ruled against a9ae1b6
  • minor — resources/Table.ts:2343 — A level-1 [seq, P] row that a pre-upgrade build already marked floorRevoked: true stays floorless permanently. Main's revocation loop (seen in the standalone dev/harper checkout) set the flag with no generation check. After upgrade, :2311 skips every later certificate for that row. :2345 s
  • minor — resources/subscriptionCheckpoint.ts:259 — Two checks depend on the harper-pro copy-bound writer doing two things in order: committing [copyBound, peer] before the copied row, then calling bumpOriginStateEpoch. The checks are the stale-capture drop (certifiedFloors at resources/transactionBroadcast.ts:208) and the admitted-replay rec
  • nit — unitTests/resources/subscribeCheckpoint.test.js:525 — The test writes the bound row itself and bumps the epoch by hand. Nothing in core proves the real writer-to-reader ordering end to end.
  • nit — resources/subscriptionCheckpoint.ts:108 — readCopyBounds is exported but only used inside its own module. The companion may duplicate the reader with skip-on-malformed semantics instead of this fail-closed one.
  • nit — resources/subscriptionCheckpoint.ts:110 — Each scan allocates range objects and the routeCertifies closure. Runs once per 5 s capture and once per resume, so off the hot path; subscriptionCheckpoint.bench.js now covers this scan.
  • nit — resources/Table.ts:2402 — Added comments that narrate the adjacent code: resources/Table.ts:2402, unitTests/resources/replicationSeqCursor.test.js:365, unitTests/resources/subscribeCheckpoint.test.js:536, unitTests/resources/subscriptionCheckpoint.bench.js:1-2.

Not standing: 6 resolved, 2 merged. Grok ran 19m (19m from launch to report); the fold adjudication (cursor-grok@7e92ec115b33-745ecacf, claude) took 2m.

Review-Coverage: authored=claude; ran=cursor-composer,gemini,codex,cursor-grok; late=cursor-grok:adjudicated; adjudicated=domain; declined=muse; rounds=6; full=2 @ 43ebf79

Review-Attention: study ~20m (critical: Table.ts, transactionBroadcast.ts; decisions: copy-bound-read-side, malformed-bound-fails-db-wide, level1-revocation-clears, coverage-restore-via-route-mark, do-less-alternative) @ 43ebf79

kriszyp and others added 5 commits October 27, 2026 06:57
A base copy writes an origin's rows without log entries, so no route
certified from at or below the highest copied version holds that origin
complete. readOriginStates now reads every peer's [copyBound, peerId]
row and treats a covered certifiedFrom as absent (a malformed row fails
closed). Replication no longer has to rewrite other routes' seq rows,
which raced this updater's snapshot commit.

A capture carries the origin-state epoch it read first, and
certifiedFloors drops one the epoch has passed. A revocation naming no
generation clears a level-1 floor instead of revoking it for good, and
a route mark can restore a level-2 route's fullCoverage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xoBA9XmjPA2mM5joANzNm
Dispatch-Task: harper-pro-1092-row-ownership
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xoBA9XmjPA2mM5joANzNm
Dispatch-Task: harper-pro-1092-row-ownership
A copy can restore this node's own lost rows, which its own log then
lacks: an own boundary at or below that bound, or a whole-log replay,
is refused. A copy bound row must be a plain record of canonical
non-negative origin ids and non-negative versions; anything else is a
410, so the client resubscribes rather than retrying an internal error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xoBA9XmjPA2mM5joANzNm
Dispatch-Task: harper-pro-1092-row-ownership
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xoBA9XmjPA2mM5joANzNm
Dispatch-Task: harper-pro-1092-row-ownership
It used to win on its higher floor and then disqualify the origin,
although another route of the same generation qualified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xoBA9XmjPA2mM5joANzNm
Dispatch-Task: harper-pro-1092-row-ownership

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces copy bounds handling for audit-less base copies to ensure subscription checkpoints are complete and correct, updating the design documentation, table resource handling, and checkpoint admission logic, alongside adding corresponding tests and benchmarks. The review feedback highlights a potential upgrade compatibility issue where pre-upgrade level-1 floors marked as revoked could remain permanently floorless, suggests refactoring routeCertifies to safely handle undefined copy bounds lookup, and recommends checking nodeState.fullCoverage !== true instead of === false to robustly restore full coverage when the initial state is undefined.

Comment thread resources/Table.ts
Comment on lines +2347 to +2355
if (generation === undefined && typeof nodeState.originGeneration !== 'string') {
// a level-1 floor has no generation to stay revoked within: clear it, and a later certificate floors again
if (nodeState.closedFloor !== undefined || nodeState.relayable) {
nodeState.closedFloor = undefined;
nodeState.relayable = false;
originCursorsChanged = true;
}
continue;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There is a potential upgrade compatibility issue here. If a pre-upgrade build already marked a level-1 [seq, P] row as floorRevoked: true, it will stay floorless permanently. This is because the certificate block at line 2311 skips any later certificate if nodeState.floorRevoked === true, and since level-1 floors do not have a generation, generationChanged is never triggered to clear floorRevoked. Consider adding a mechanism to clear floorRevoked for level-1 floors when a new level-1 certificate is received.

Comment on lines +152 to +161
const routeCertifies = (node: any) =>
isValidFloor(node.closedFloor) &&
typeof node.originGeneration === 'string' &&
node.fullCoverage === true &&
node.partialHistory !== true &&
node.hole !== true &&
node.floorRevoked !== true &&
typeof node.certifiedFrom === 'number' &&
!(node.certifiedFrom > node.closedFloor) &&
!(node.certifiedFrom <= copyBounds.get(node.id)!);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The helper function routeCertifies can be simplified and made safer by extracting the copyBounds.get(node.id) lookup into a local variable. This avoids the non-null assertion operator ! (which is a type lie since the bound can be undefined) and avoids implicit loose comparisons with undefined (e.g., node.certifiedFrom <= undefined which implicitly evaluates to false). Using == null is also the preferred idiom for nullish checks in this repository.

const routeCertifies = (node: any) => {
	const bound = copyBounds.get(node.id);
	return (
		isValidFloor(node.closedFloor) &&
		typeof node.originGeneration === 'string' &&
		node.fullCoverage === true &&
		node.partialHistory !== true &&
		node.hole !== true &&
		node.floorRevoked !== true &&
		typeof node.certifiedFrom === 'number' &&
		!(node.certifiedFrom > node.closedFloor) &&
		(bound == null || node.certifiedFrom > bound)
	);
};
References
  1. Loose equality == null / != null is the intentional idiom for null-or-undefined checks in this repository. (link)
  2. Ensure helper functions are self-safeguarding by incorporating defensive null/undefined checks (e.g., using explicit == null checks) rather than assuming callers have already validated the inputs.

Comment thread resources/Table.ts
Comment on lines +2402 to +2406
if (
fullCoverage === true &&
typeof nodeState.originGeneration === 'string' &&
nodeState.fullCoverage === false
) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If nodeState.fullCoverage is undefined (which is its initial state or when cleared), the condition nodeState.fullCoverage === false will evaluate to false, preventing it from being updated to true. Changing the check to nodeState.fullCoverage !== true makes the restoration of full coverage more robust.

if (
	fullCoverage === true &&
	typeof nodeState.originGeneration === 'string' &&
	nodeState.fullCoverage !== true
) {

kriszyp and others added 4 commits October 27, 2026 11:55
The transaction committed but its position and route state did not, and
the loop only logged it, so a source that settles what it attached at
commit counted a hole or revocation as recorded. The end_txn's onFailure
now runs (and may hold the stream) before the error is rethrown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xoBA9XmjPA2mM5joANzNm
Dispatch-Task: harper-pro-1092-row-ownership
A source that releases in onFailure what onCommit would have (an
outstanding-commit count, a backlog pause) released it twice. The third
argument, true here, says onCommit already ran and only what the end_txn
attached for recording was lost.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xoBA9XmjPA2mM5joANzNm
Dispatch-Task: harper-pro-1092-row-ownership
…t was

Otherwise a repeat of the same position reads as not advancing and its
write is skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xoBA9XmjPA2mM5joANzNm
Dispatch-Task: harper-pro-1092-row-ownership
… a test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xoBA9XmjPA2mM5joANzNm
Dispatch-Task: harper-pro-1092-row-ownership
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant