Changes touching this path

  • what a peer declares in a negotiation is a type now, so the head list that strands a pull cannot be passed (#799) #40's fault-injection harness found a peer holding 3 of 6 files whose head had advanced to the sender's tip, with the negotiation reporting up to date. The ticket read that as "an interrupted pull cannot resume." It does resume: `pull_via_interrupted_fetch_resumes` has been proving that since #217, and `pull_via` declares `negotiation_have()` at all three of its call sites. The harness resumed by declaring `bob.repo.heads()` instead. That is the defect — not the sync path, but the fact that `have` accepted any `Vec<Oid>` and the obvious thing to reach for was the wrong one. `Repo::heads`' own doc said "what a peer passes as have", so the trait was teaching it. `loot clone` was doing it too, correct only because a freshly inited repo has no heads. What a peer may declare is now `Have`, and `negotiation_have()` is the only thing that makes one — the #217 filter is the check the type certifies. `Have::nothing()` is the one other constructor, for transports probing a relay and clients with no repo to ask yet; declaring less can only over-send, and over-declaring is the strandable direction. Threaded through the receiver-declares half only: SyncTransport, loot_net::{offer,fetch,pull} and the forge's signed twins. The sender side keeps `&[Oid]` deliberately. `have` means one thing in both directions — what the recipient of the bundle holds — so there is no second concept to name; what differs is provenance, and a type cannot carry a guarantee across a network. Wrapping it would advertise a check that does not happen. That reasoning lives on `Have`'s doc, which is the only place it is written. Candidate 1 from the ticket — refuse to advance the head over an incomplete closure — is not built, because ADR 0024 already priced and rejected it under "cross-batch atomicity is deliberately not provided". A confirmation note says so there, so the next reader does not re-derive it. The new `tests/sync_resume.rs` asserts the head advance rather than treating it as the failure, and covers the fault the suite genuinely missed: a bundle that is short but valid, where every batch succeeds, the pull returns Ok, and the operator is told it worked while holding half the files. The next pull heals it, which is what is pinned. Acceptance criterion 3 ("am I up to date" must answer no while any object is absent) is deliberately not built: `closure_complete` is unsatisfiable on the forge path, where a reader legitimately never receives ciphertext it holds no grant for, so the warning would fire forever on every forge repo with restricted content. That is #803. `an_interrupted_transfer_resumes_to_convergence` runs with its ignore deleted. 271dd5cf · dbf3dbe6…

Renames are not followed. loot's tree maps a path to an address, so a rename is a delete and an add. This list is the history of the name, not of the bytes.