Artifact 46bf42de0914e3ed343509a521db97f0d0e60c6bbe08c70e873a16e5edf0394c:
- File docs/rfcs/erratum/sync-protocol-00-s6.6-push-refusal-pull-only.md — part of check-in [275e884be5] at 2026-10-11 21:47:48 on branch trunk — errata: sync-protocol behaviours where Fossil departs from the draft (user: dmestas, size: 6841) [annotate] [blame] [check-ins using]
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:
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:
+ 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:
- 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
pullhaving 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 betweenpullandpush, 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 REPOwith anapplication/x-fossil-debugPOST to/xfer, the three request shapes in the table above,nobodycapabilitieso. - Source references are to
src/xfer.cat tagversion-2.28. - go-libfossil's client cases
sync.10.2.pull-onlyandsync.10.2.pull-only-gimmeexercise 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.