# Erratum — draft-fossil-sync-protocol-00 §6.6
| | |
|---|---|
| **Document** | `draft-fossil-sync-protocol-00` |
| **Section** | 6.6 — Capability Model (server refusal of `push` without Write); cross-references §8.5, §10.1 and §10.2 |
| **Type** | Technical (the server's refusal form changes: a `message` card, not an `error` card, when a pull is present) |
| **Status** | Proposed — operator decision pending |
| **Reported** | 2026-10-11 |
| **Submitter** | Dan Mestas |
| **Found via** | Reading §10.2 against the server side; reproduced on fossil 2.28 [52445a27f1] with `fossil http` and an `application/x-fossil-debug` request |
## 1. Summary
§10.2 tells a client what to do when it receives `message pull only *`: turn
push off for the session and hide the message. Nothing in the draft tells a
server to send it. §6.6 says instead that "the server checks each card's
required capability and emits error when lacking", and §10.1 lists
"authorization" among the reset sites. A server built from the draft
answers a `push` from a principal without Write with an `error` card.
Fossil 2.28 does that only when no `pull` card preceded the `push` in the
request. When one did, it emits the nonfatal
`message pull\sonly\s-\snot\sauthorized\sto\spush`, does not reset the
response, and serves the pull. Fossil's own client sends `pull` before
`push` in every round, so a stock `fossil sync` by a read-only user always
gets the message, never the error. In neither form does the push refusal
end card processing.
## 2. Location
- §6.6 (~lines 1189-1193): "The server checks each card's required
capability and emits error when lacking, except where an owning section
specifies silent behavior."
- §8.5 (~lines 1630-1634): the push/pull paragraph, which states no
capability rule.
- §10.1 (~lines 2093-2097): "At wrong-project, authorization, duplicate-login,
unknown-card, and other fatal reset sites, server discards partial output
before error".
- §10.2 (~lines 2133-2138): the client's `pull only *` rule, unchanged.
## 3. The divergence
Fossil 2.28, `src/xfer.c:1503-1524`, the `push`/`pull` handler after the
project-code check:
```c
if( blob_eq(&xfer.aToken[0], "pull") ){
if( !g.perm.Read ){ cgi_reset_content(); @ error not\sauthorized\sto\sread...
nErr++; break; }
isPull = 1;
}else{
if( !g.perm.Write ){
if( !isPull ){
cgi_reset_content();
@ error not\sauthorized\sto\swrite%s(whyNotAuth())
nErr++;
}else{
@ message pull\sonly\s-\snot\sauthorized\sto\spush%s(whyNotAuth())
}
}else{
isPush = 1;
}
}
```
Neither push branch executes `break`: card processing continues, and
`isPush` stays clear, so later `file`/`cfile` cards are refused
individually (`xfer.c:1361-1363`, `1382-1384`). The client emits `pull`
before `push` (`xfer.c:2475-2480`). `whyNotAuth()` appends a suffix only in
specific configurations; it was empty in these tests.
Observed with `fossil http`, `nobody` holding `o` only:
| Request operation cards | Reply |
|---|---|
| `pull`, `push` | `message pull\sonly\s-\snot\sauthorized\sto\spush`, the `igot` inventory, `# timestamp ... errors 0` |
| `push`, `pull` | `error not\sauthorized\sto\swrite`, the `igot` inventory, `# timestamp ... errors 1` |
| `push` | `error not\sauthorized\sto\swrite`, `# timestamp ... errors 1` |
The second row shows that the error form also does not end card
processing: the `pull` that follows it still produces the inventory.
## 4. Impact
Wire-visible. A server that answers a read-only user's `sync` with an
`error` card makes a stock Fossil client stop its reply loop on every round
(§10.1) and count an error, where against Fossil the same client
quietly switches to pull-only and completes. (The client-side effect is
taken from the draft's §10.1 and §10.2; only the server replies were
reproduced.) Post-commit autopush hides
part of this through the `%not authorized to write%` exception (§7.6), but
`fossil sync` and `fossil update` with autosync do not. A server that
treats the error as ending card processing also drops the pull's inventory
when `push` precedes `pull`.
## 5. Correction
In §6.6, after the sentence ending "specifies silent behavior.", add:
```diff
+ A push card from a principal without Write is refused in one of two
+ forms, and neither ends card processing. If a pull card was already
+ accepted earlier in the same request, the server MUST NOT reset
+ accumulated output and MUST emit the nonfatal message
+ "pull only - not authorized to push" (fossilized:
+ message pull\sonly\s-\snot\sauthorized\sto\spush), which a client
+ handles as Section 10.2 states. Otherwise the server resets
+ accumulated output and emits error not authorized to write. In both
+ forms push is not enabled for the request, so later file and cfile
+ cards are refused individually, and later cards, including a pull,
+ are still processed. A client that sends both cards SHOULD send
+ pull before push, as the reference client does, so that a refused
+ push yields the message form.
```
In §10.1, amend the reset-site sentence:
```diff
- At wrong-project, authorization, duplicate-login, unknown-card, and
- other fatal reset sites, server discards partial output before error;
+ At wrong-project, authorization, duplicate-login, unknown-card, and
+ other reset sites, server discards partial output before error (the
+ push refusal of Section 6.6 has a non-reset message form, and it and
+ the unknown-card error continue card processing);
```
## 6. Rationale
- **Gives §10.2 its producer.** The draft specifies the client half of the
pull-only exchange; this specifies the server half, so the pair
interoperates with Fossil in both directions.
- **States the order dependency.** The message form depends on a `pull`
having been seen first. Stating it lets a cleanroom server match Fossil
for the request order real clients use, and for the reverse order too.
§3.2.1 fixes no order between `pull` and `push`, so the SHOULD names
the order the reference client uses (`xfer.c:2475-2480`).
- **Keeps the error text.** The error form and its text are the ones the
autopush exception in §7.6 already matches, so nothing else changes.
## 7. Verification
- Fossil 2.28 [52445a27f1], macOS arm64, `fossil http REPO` with an
`application/x-fossil-debug` POST to `/xfer`, the three request shapes
in the table above, `nobody` capabilities `o`.
- Source references are to `src/xfer.c` at tag `version-2.28`.
- go-libfossil's client cases `sync.10.2.pull-only` and
`sync.10.2.pull-only-gimme` exercise the client half only.
---
*Prepared against `draft-fossil-sync-protocol-00`. The RFC remains the oracle; the reference implementation is used only to confirm the observable behavior.*