# Erratum — draft-fossil-sync-protocol-00 §6.4
| | |
|---|---|
| **Document** | `draft-fossil-sync-protocol-00` |
| **Section** | 6.4 — Signature Computation (verification failure outcome); cross-references §3.2 and §10.1 |
| **Type** | Technical (the reply after a failed login is under-specified: end-of-request output is omitted) |
| **Status** | Applied in `draft-fossil-sync-protocol-01` (2026-10-12) |
| **Reported** | 2026-10-11 |
| **Submitter** | Dan Mestas |
| **Found via** | go-libfossil's sync conformance suite (case `sync.6.4.failure-shape`); reproduced on fossil 2.28 [52445a27f1] with `fossil http` and an `application/x-fossil-debug` request |
## 1. Summary
§6.4 ends with "Either failure keeps HTTP 200, discards staged output, emits
error login failed, records failure, and ends card processing." That is
true of the card loop, but a reader takes "ends card processing" as the end
of the reply. In Fossil 2.28 it is not. The server leaves the card loop and
then runs its end-of-request steps for whatever direction earlier cards
established: phantom `gimme` requests if a `push` was accepted, the `igot`
inventory if a `pull` was accepted, and in every case the closing
`# timestamp ... errors N` comment. The draft says none of this for the
login failure, or for any other site that ends the card loop.
In the canonical order (login card first, §3.2) no direction has been
established when the login fails, so the reply is only the error and the
timestamp comment. The extra output appears only when `push` or `pull`
precedes the login card, which the server accepts.
## 2. Location
- §6.4, last paragraph of the verification order (~line 1152-1153): "Either
failure keeps HTTP 200, discards staged output, emits error login failed,
records failure, and ends card processing."
- §6.4 baseline paragraph (~line 1125-1130): "failed login leaves
baseline untouched but aborts."
- For comparison, unchanged: §3.2 (~line 401-403), "Ordinary completed
replies ... end with the timestamp/error-count comment. Pre-dispatch and
fail-fast server replies need not contain that comment."
## 3. The divergence
Fossil 2.28, `src/xfer.c`:
- `xfer.c:1603-1609`: on a failed `check_tail_hash()` or `check_login()`,
`cgi_reset_content()`, then `@ error login\sfailed`, `nErr++`, `break`.
The `break` leaves the `while( blob_line(...) )` card loop; it does not
return from `page_xfer()`.
- `xfer.c:1898-1906`: after the loop, `if( isPush )` runs the push script
and `request_phantoms(&xfer, 500)`.
- `xfer.c:1910-1925`: `if( isClone && nGimme==0 ) send_all(...)`, else
`if( isPull ) { create_cluster(); send_unclustered(...); ... }`.
- `xfer.c:1968-1969`: `@ # timestamp %s(zNow) errors %d(nErr)`, then
`db_commit_transaction()`. The request transaction commits; nothing is
rolled back.
The same `break` and the same end-of-request steps follow every other site
that ends the card loop (`error wrong\sproject` at `xfer.c:1499`,
`error not\sauthorized\sto\sread` at `xfer.c:1507`, the duplicate-login
error).
Observed with `fossil http` on a repository where `nobody` holds `o`:
```
request: pragma client-version 22800 20260311 113146
pull x PROJECTCODE
gimme HASH
login alice NONCE SIGNATURE (wrong signature)
# rnd 1
reply: error login\sfailed
igot 90c57917...
igot a57d4ad9...
# timestamp 2026-10-11T21:29:18 errors 1
```
The staged `file` for the `gimme` is discarded by the reset, as §6.4 says;
the inventory the `pull` asked for is still sent. With `push` and `pull`
before the failing login and `nobody` holding `oi`, an `igot` of an unknown
hash also produces a phantom on the server and a `gimme` for it after the
error. With the login card first, the reply is only `error login\sfailed`
and the timestamp comment.
## 4. Impact
Wire-visible, server side only. A server that stops the reply at the error
sends less than Fossil does when `push` or `pull` precedes the login card:
no inventory, no phantom requests, and possibly no timestamp comment, which
§3.2 expects of an ordinary completed reply (a login failure is not a
fail-fast path; nothing is rolled back). Clients are not affected: a client
stops its reply-card loop at the first `error` card (§10.1), so the trailing
cards are not acted on. Stock Fossil clients put the login card first and
never see the extra cards.
## 5. Correction
In §6.4, replace the last sentence of the verification order:
```diff
- Either failure keeps HTTP 200, discards staged output, emits error
- login failed, records failure, and ends card processing.
+ Either failure keeps HTTP 200, discards staged output, emits error
+ login failed, records failure, and ends card processing: later
+ request cards are not parsed. The request is not rolled back, and
+ the server still performs the end-of-request steps for any direction
+ an earlier card established (phantom gimme for push, igot inventory
+ for pull) and ends the reply with the timestamp/error-count comment
+ (Section 3.2). When the login card is first, as Section 3.2
+ requires of a client, no direction has been established and the
+ reply is the error and that comment alone.
```
In the baseline paragraph, replace "but aborts" with "but ends card
processing as described below".
## 6. Rationale
- **"Ends card processing" stays true.** The correction only says what the
phrase does not cover, which is the part a server implementer needs.
- **Consistent with §3.2.** A login failure is not a fail-fast path, so the
reply keeps the timestamp comment §3.2 assigns to ordinary replies.
- **No new client obligation.** Clients already stop at the first `error`;
the correction describes server output only.
## 7. Verification
- Fossil 2.28 [52445a27f1], macOS arm64, `fossil http REPO` with an
`application/x-fossil-debug` POST to `/xfer`, three request shapes: pull
then failing login (error, inventory, timestamp); push, pull, unknown
`igot`, failing login (error, `gimme`, inventory, timestamp; the phantom
row is committed); failing login first (error, timestamp).
- Source references are to `src/xfer.c` at tag `version-2.28`.
- go-libfossil conformance case `sync.6.4.failure-shape` asserts the error
and the absence of the staged `file`; its comment records that Fossil
still sends the inventory.
---
*Prepared against `draft-fossil-sync-protocol-00`. The RFC remains the oracle; the reference implementation is used only to confirm the observable behavior.*