# Erratum — draft-fossil-sync-protocol-00 §5.2 / §10.1

| | |
|---|---|
| **Document** | `draft-fossil-sync-protocol-00` |
| **Section** | 5.2 — Card Syntax (file, cfile and uvfile handler failures); 10.1 — Error Card |
| **Type** | Technical (compatibility note — the reference server URL-encodes one family of error texts; the draft's encoding 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; confirmed by reading Fossil's source and by sending it a malformed card |

## 1. Summary

§5.2 says a validation failure in the `file`, `cfile` or `uvfile` handler
"emits the handler's fossilized error", and §10.1 says an error card's TEXT is
fossilized. Fossil does not fossilize these three: it URL-encodes them, so
spaces arrive as `+` and other unsafe bytes as `%XX`. Every other error card
Fossil sends is fossilized.

Both encodings yield a single token, so a receiver never misreads the card's
framing. The difference is in the text a person sees: a Fossil client
defossilizes what it receives, and shows a Fossil server's
`malformed+file+line` with the plus signs in it.

## 2. Location

§5.2, the paragraph beginning "After dispatch enters the file, cfile, or uvfile
handler" (~line 1013):

> …a validation failure reported by that handler instead resets accumulated
> response output, emits the handler's fossilized error, increments the request
> error count, and stops request-card parsing.

## 3. The divergence

Verified against Fossil 2.28 by source reading and by running it.

`page_xfer()` (`src/xfer.c`) sends the handler's accumulated error with `%T`
in all three places, at lines 1370 (`file`), 1391 (`cfile`) and 1405
(`uvfile`):

```c
@ error %T(blob_str(&xfer.err))
```

In Fossil's printf, `%T` is `etURLIZE` (`src/printf.c:162`, "Make text safe for
HTTP"), while `%F` is `etFOSSILIZE`. The other error cards in the same function
use `%F` or are written pre-escaped, for example
`@ error bad\scommand:\s%F(...)` at line 1893.

A raw request through `fossil http`, as `nobody` with push permission, gives:

| Request card | Fossil 2.28 reply card |
|---|---|
| `file notahash 5` | `error malformed+file+line` |
| `uvfile x/y 0 0 0` | `error malformed+uvfile+line` |

The fossilized forms the draft describes would be `error malformed\sfile\sline`
and `error malformed\suvfile\sline`.

## 4. Impact

Cosmetic on the wire, visible to people.

- **Framing.** Unaffected: URL-encoding leaves no space in the text, so the
  card stays at two tokens and every receiver recognizes it as an error.
- **Displayed text.** A receiver following §10.1 defossilizes the text, which
  turns `\s` into a space but leaves `+` and `%XX` as they are. Fossil-to-Fossil,
  the person pushing reads "malformed+file+line".
- **zit.** zit's server follows the draft and fossilizes these errors
  (`src/sync_test.zig` pins `error malformed\sfile\sline`), so a stock Fossil
  client reads zit's refusal with real spaces. zit's client receiving Fossil's
  form shows the plus signs, as Fossil's own client does.
- **Conformance testing.** A test comparing a server's bytes against Fossil's
  will fail on these three cards whichever encoding the draft names, unless the
  divergence is recorded.

## 5. Correction

Keep the draft's rule. Fossilized TEXT is what §10.1 requires of every error
card, it is what Fossil does everywhere else, and it is what a receiver
decodes. Record Fossil's divergence where the handler path is described.

In §5.2, after "emits the handler's fossilized error,":

```diff
    accumulated response output, emits the handler's fossilized error,
    increments the request error count, and stops request-card parsing.
+   (Fossil instead URL-encodes the text of these three handler errors,
+   so spaces arrive as "+"; the card remains a single TEXT token, and a
+   receiver that defossilizes it, as Section 10.1 requires, shows the
+   "+" characters.  Senders SHOULD fossilize, as for every other error
+   card.)
```

**Alternative, which needs the operator's decision:** propose that Fossil change
`%T` to `%F` at the three lines. That is a three-character patch to
`src/xfer.c`, and it would let the parenthetical be dropped. Filing it is a
separate decision from this erratum.

## 6. Rationale

- **One encoding for every error card.** §10.1 states a single rule. Making
  three cards an exception would give a receiver two decodings to choose
  between with nothing on the wire to tell them apart.
- **The draft's rule is the readable one.** Fossilized text defossilizes to the
  message as written; URL-encoded text does not, under the decoding §10.1 names.
- **Recording it is enough.** The divergence never breaks framing, so a note
  that tells implementers and conformance tests what to expect from Fossil
  covers it.

## 7. Verification

- Source: Fossil 2.28 `src/xfer.c:1370`, `:1391`, `:1405` (`%T`), `:1893`
  (`%F`, for contrast); `src/printf.c:161-164` (the conversion table).
- Run: a `fossil new` repository with `nobody` given `gioz`, sent
  `push SERVERCODE PROJECTCODE` followed by `file notahash 5` (and separately
  `uvfile x/y 0 0 0`) as `application/x-fossil-debug` through `fossil http`.
  The replies are `error malformed+file+line` and `error malformed+uvfile+line`.

---

*Prepared against `draft-fossil-sync-protocol-00`. The RFC remains the oracle;
the reference implementation is used only to confirm the observable behavior.*
