# zit 0.3.20

zit 0.3.20 fixes a server refusal that a stock fossil client could not read,
and a `remote` verb that saved whatever it was handed.

No artifact, tag namespace, or on-disk repository format changed. Two error
cards change their encoding on the wire, to the encoding the protocol already
requires of them.

## A refused push is now refused out loud

When a zit server rejects a request card, it resets its response and answers
with one error card. The protocol writes those answers as prose, `error bad
command: LINE` and `error malformed CARD line`, and zit emitted exactly that
prose: the words separated by real spaces. The auth, sequence-number, sparse
and content-policy refusals went through the encoder and arrived as one
token; these two did not.

A fossil client reads an error card as exactly two tokens, the operator and
one encoded text. Four tokens is not an error card to it: it parses `error
malformed`, reports the trailing words as unknown commands, discards the
lot, and prints its success summary. On a forge that ran for fifteen hours:
every push said `Artifacts sent: 94`, every push sent the same 94 again, and
a fresh clone had none of them. `zit push` against the same server said "the
peer refused" in the same minute, because zit's own client tolerates the
malformed card.

Every server error card is now assembled at one point, with its text
encoded. The wire texts the protocol pins are unchanged; their spaces are
`\s`, as fossil's own server writes them. A unit test drives every refusal
variant through the emitter and fails if any card has a raw space after its
operator, so a new fault cannot reintroduce this.

The sync draft's prose describes these texts unencoded, which is how they
were transcribed here. That is an erratum against the draft's wording, not
its meaning: the card grammar in the same document already says TEXT is
encoded.

## `zit remote` refuses what is not a URL

`zit remote list` saved the word `list` as the remote and reported success.
The saved remote was gone, and the mistake surfaced at the next push, with
an error that no longer looked related. Git trained everyone that `remote`
has subcommands, so this was a natural thing to type.

The verb's help already defined its argument as an http:// or https://
address. The parser now holds it to that: any other positional is refused
with a usage error, the saved remote is untouched, and the message says
plainly that there are no subcommands and which form shows, sets, or clears
the remote. Storing a valid URL verbatim is unchanged.

## Compatibility

- No on-disk repository or wire format changed; Fossil RFC revision 00 remains
  the target. The `bad command` and `malformed` error cards are now encoded
  as §11.1 requires; a zit client accepted both forms before and still does.
- `zit remote` exits 2 for a non-URL argument where it used to exit 0 and
  overwrite the remote. Nothing else about the verb changed.

## Qualification

`zig build test`, `e2e`, `smoke` (Fossil interop against a real `fossil`),
`muscle`, `git-import-e2e`, `git-export-e2e`, `dst`, and `cross`, plus the
three-runner CI battery.
