Changes touching this path
- a fault-injection harness for the relay sync path, and the interrupted pull it found on its first run (#40)
1f62dbbc · dbf3dbe6… - 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…diff - the relay's grant mailbox authorizes before it parses, and a read addresses only the caller's own (#621)
4b349b72 · dbf3dbe6…diff - the change graph gets one read view, and the two forwarding bands over it retire (#868)
826c3339 · dbf3dbe6…diff - the reader names its own holes: the completeness filter and Have retire, and pull reports what never arrived (#803)
f15d576e · dbf3dbe6…diff - info names the build, not only the wire format: a deploy becomes verifiable from outside (#922)
ffe01788 · dbf3dbe6…diff
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.