Let the target decide when a bootstrap checkpoint cannot be split further

Commit

nodo4d ago

Rebased onto main after #117 (Soph's batched-cutover marker fix) and squashed: the five original commits were an iterative refinement of the same code, and resolving their conflicts one at a time against the moved main produced worse results than resolving the net change once. The review narrative lives in the PR discussion.

Once subdivision bottoms out — checkpoints split BETWEEN commits, so at one commit per gap there is nothing left to split — a single commit whose pack exceeded git-sync's own budget failed the run. That budget is self-imposed and far below what the target accepts: TargetMaxPack defaults to 512 MiB while the target announces 10 GiB, and autoTargetMaxPackBytes derives 5 GiB from that announcement and then discards it for being larger than the default. So the run gave up against a number git-sync chose, having never asked the server. gh/nicschick/vc3r dies exactly here.

The batching budget stays small on purpose — it bounds the waste of a doomed push and makes the temp ref advance often, both of which require a smaller pack to be possible. On a one-commit gap neither is, so the ceiling for that push is the target's announced limit instead, chosen before the push rather than after a doomed attempt. This costs nothing: a ceiling is an abort threshold, not an upload size, so a pack that fits sends identical bytes either way — and it avoids fetching an indivisible multi-GiB commit twice.

That gives the failure a verdict worth classifying. An abort against our own budget stays retryable: a larger budget or a raised server limit could still mirror the repo. A checkpoint that is indivisible AND refused by the target — a parsed body-limit rejection, or an attempt at its announced limit that still overshot — returns ErrCheckpointExceedsTargetLimit, aliased into the root package so the mirror worker can match it with errors.Is and stop redelivering an identical pack ten times. A deadline (408/504) is availability, not size, so it stays retryable; classifying it permanent would let one target rolling restart permafail every large bootstrap in flight.

Also stops discarding the bootstrap Result when Execute fails. The route facts (RelayMode, RelayReason, batch counts, temp refs, Plans) are set before anything can fail and describe the route rather than the outcome; zeroing them is why a failed sync could report its strategy only when it succeeded — the gap that made ENT-2054 a source read instead of a log query. Batching is recorded before checkpoint planning, whose commit-graph fetch is the likeliest failure for exactly the repos that batch, and unstable.Client.Bootstrap no longer throws the result away either.

budgetFromObservation guards against escalating past a cutoff MEASURED from bytes actually sent (a middlebox that cuts without announcing) rather than one the target stated. It lives beside selfImposedBudget, which spans branches: provenance has to travel with the value, or a later branch escalates past a limit an earlier one demonstrated. It has no test coverage — a fixture I built did not bite when the guard was removed, and I deleted it rather than keep a test passing for reasons I could not explain.

Verified on the rebased tree: full suite and golangci-lint green, Soph's cutover tests pass alongside these, and removing the escalation or treating a deadline as a size verdict each still break the new tests.

Entire-Checkpoint: 01M1C4BS40PK95XMCB8MF5YH3Q

Checkpoints

Review git-sync PR #118 Size Failures

Claude CodeOpus 5.[1m]
View session
Checkpoint 1

Fix Interrupted Batched Bootstrap Resume

Claude CodeOpus 5
View session
Checkpoint 1