Artifact ac469df2caea108878774258dadb41eaf8625d3c86756ef630da63bd6878c869:
- File docs/rfcs/erratum/sync-protocol-00-s3.3-client-peer-lacks-dropped.md — part of check-in [275e884be5] at 2026-10-11 21:47:48 on branch trunk — errata: sync-protocol behaviours where Fossil departs from the draft (user: dmestas, size: 4752) [annotate] [blame] [check-ins using]
Erratum — draft-fossil-sync-protocol-00 §3.3
| Document | draft-fossil-sync-protocol-00 |
| Section | 3.3 — Phantom Record Lifecycle (peer-lacks filter on the client); 8.8 — Gimme Eligibility Filters |
| Type | Compatibility note (Fossil's client discards peer-lacks before it is used; the draft's rule is kept) |
| Status | Proposed — operator decision pending |
| Reported | 2026-10-11 |
| Submitter | Dan Mestas |
| Found via | go-libfossil's conformance suite against Fossil 2.28 (scope note SP-3.3-3); confirmed by reading Fossil's source and by running a Fossil client against a scripted peer |
1. Summary
§3.3 says the phantom scan excludes peer-lacks hashes, the hashes the peer
just asked for with gimme. Fossil's server applies that filter within a
request. Fossil's client records peer-lacks while reading a reply and then
drops the table before its next phantom scan, so its next request asks the
peer again for a hash the peer has just said it lacks.
2. Location
§3.3 (~lines 516-530):
Each round, peer-has records hashes announced or successfully sent by the peer; peer-lacks records gimme requests. … The ordinary phantom scan excludes peer-lacks hashes, …
and the reply-processing table (~line 412): gimme: add peer-lacks; ….
3. The divergence
Source, Fossil 2.28 (src/xfer.c):
client_sync()creates the temporaryunktable (peer-lacks) at the top of each cycle (line 2269) and runsrequest_phantoms()for the outgoing request (line 2287) before the exchange.- A reply
gimme HASHcallsremote_unk()(line 2564), inserting intounk. - At the end of the cycle
unkis dropped (line 3011). The next cycle recreates it empty before itsrequest_phantoms(), whoseNOT EXISTS(SELECT 1 FROM unk …)filter (lines 766-781) therefore never sees what the last reply asked for. - On the server the order is reversed: request
gimmecards fillunk(line 1420) beforerequest_phantoms()runs at the end of the request (line 1906), so the filter works there.
The client's remote_unk() call has no other reader, so its intent is the
draft's rule; the table's lifetime defeats it.
Run: a scripted HTTP peer answered a fossil pull from an empty repository
with igot U and gimme U in one reply (U = 01256859…, absent). Fossil
made U a phantom, went a second round, and its second request contained
gimme 01256859….
4. Impact
Harmless, but visible.
- Wire. One redundant
gimmeper round for each such hash. The peer lacks the content, so §3.3's "If absent, silently skip" applies and nothing else happens. - Termination. No effect: a re-sent
gimmedoes not by itself satisfy any §3.7 continuation limb. - Conformance. A test of the client-side filter fails against Fossil; go-libfossil leaves that part out.
5. Correction
Keep the rule. Note Fossil's client after the filter sentence in §3.3:
The ordinary phantom scan excludes peer-lacks hashes, names in the
repository shun table, and private hashes unless private sync is
enabled; shun-sha1 does not itself suppress an otherwise eligible
gimme.
+ (Fossil's client discards the peer-lacks it records from a reply
+ before building the next request, so it may repeat gimme for a hash
+ the peer has just requested. Receivers already skip a gimme for
+ absent content silently; no further handling is needed.)
Alternative, which needs the operator's decision: propose that Fossil keep
unk across the cycle boundary in client_sync() (create it once before the
loop and drop it after). Filing that is a separate decision from this erratum.
6. Rationale
- The draft's rule is the intended one. Fossil applies it on the server and records the data for it on the client; only the table's lifetime is wrong.
- Changing the draft would add nothing. Making the client filter optional would describe a defect as a choice, while the redundant card is already handled by the existing "silently skip" rule.
- A note is enough. It tells implementers and test authors what Fossil sends, which is all they need.
7. Verification
- Source: Fossil 2.28
src/xfer.c:766-781(request_phantoms),:1322,:1420,:1906(server order),:2269,:2287,:2564,:3011(client order). - Run: Fossil 2.28
fossil pullagainst a scripted peer whose first reply ispragma server-version 22800 20260311 113146,igot U,gimme U; the logged second request containsgimme U.
Prepared against draft-fossil-sync-protocol-00. The RFC remains the oracle;
the reference implementation is used only to confirm the observable behavior.