Skip to content
zit Artifact [ac469df2ca]
Login

Artifact [ac469df2ca]

Artifact ac469df2caea108878774258dadb41eaf8625d3c86756ef630da63bd6878c869:


# 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.*