# zit 0.3.23

zit 0.3.23 answers an HTTP Basic challenge. A forge that gates a read
answers a clone's first request with 401; the credential can now reach the
wire on that first request, which is the one a private repository refuses.

No artifact, tag namespace, sync message, or on-disk repository format
changed. One HTTP request header is new, sent only when a peer asks for it.

## The round that could not carry the card

0.3.22 taught `clone` and `pull` to send a login card, and a private
repository on a read-gating forge was still unclonable. The two halves
were each correct alone. A clone cannot sign its first request, because the
card's shared secret is derived from the project code and the project code
is what the first reply teaches; fossil's client sends no card on that
round either. And the forge refuses the first request with an HTTP 401,
which the transport treated as fatal, so the round that would have carried
the card never came. The server's log showed one request per clone and no
credential offered, whether or not one was given.

Fossil's own server never gates a read at the HTTP layer, so the retry
0.3.22 added, which fires on a protocol-level refusal that teaches the
project code, was the right answer to fossil and no answer to a forge.
Fossil's client reaches a forge with `--httpauth`, an HTTP Basic header on
every request, and that is the mechanism a first request can use.

## The transport answers the challenge

When a verb holds a plaintext password, from `--password`, `ZIT_PASSWORD`,
or the prompt, the transport keeps a Basic credential ready and sends
nothing until asked. A 401 whose `WWW-Authenticate` names the Basic scheme
is answered by sending the same round again with the `Authorization`
header, and every later round carries it, as fossil's does. After the first
reply teaches the project code the login card rides the later rounds as
before, so a peer that admits either is served either way.

A 401 is no longer a bare status. With a challenge and no password to
answer it, such as a pull on a stored login, the message says the peer
wants a login and names the flags. With a credential sent and still
refused, it says the peer refused the login. A 401 without a challenge says
so.

A read-only account on a forge whose card secrets exist only for writers
can now read with Basic, which is the case the card never covered.

## Verification

- Transport unit tests: a Basic challenge is recognized case-insensitively
  and a Bearer or absent challenge is not; the header value is the one
  fossil's `--httpauth` sends.
- An end-to-end test stands an authenticating gate in front of `zit serve`:
  every request without `Authorization` is answered 401 with a Basic
  challenge, every request with it is relayed. An anonymous clone is
  refused with the message naming the flags; a clone with `--user` and
  `--password` lands, and the gate's log shows its first request bare and
  every request after it carrying the header.

## Compatibility

- No on-disk repository or wire format changed; Fossil RFC revision 00
  remains the target.
- The header is sent only after a peer challenges for it, and only when a
  plaintext password was given to the run. A stored login never produces
  one. A peer that never challenges sees exactly the requests it saw before.

## Qualification

`zig fmt --check`, `zig build test`, `e2e`, `smoke`, `muscle`,
`git-import-e2e`, `git-export-e2e`, `dst`, and `cross`, plus the
three-runner CI battery.
