# 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 temporary `unk` table (peer-lacks) at the top
of each cycle (line 2269) and runs `request_phantoms()` for the outgoing
request (line 2287) before the exchange.
- A reply `gimme HASH` calls `remote_unk()` (line 2564), inserting into `unk`.
- At the end of the cycle `unk` is dropped (line 3011). The next cycle
recreates it empty before its `request_phantoms()`, whose
`NOT 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 `gimme` cards fill `unk`
(line 1420) before `request_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 `gimme` per 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 `gimme` does 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:
```diff
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 pull` against a scripted peer whose first reply is
`pragma server-version 22800 20260311 113146`, `igot U`, `gimme U`; the
logged second request contains `gimme U`.
---
*Prepared against `draft-fossil-sync-protocol-00`. The RFC remains the oracle;
the reference implementation is used only to confirm the observable behavior.*