zit 0.3.24 closes a door in branch protection: a push may no longer close, or reopen, a protected branch it is not allowed to commit to. It also fixes a crash in the container decoder that any peer could trigger with a damaged sync body, and makes that decoder verify the checksum it read.
No artifact, tag namespace, sync message, or on-disk repository format changed. One more shape of pushed tag is refused by a server that protects branches; every other push is admitted exactly as before.
Closing a protected branch is a protected move
A server given protected-branch globs refuses a pushed check-in that would
join one of those branches, and a pushed tag that would move a held
check-in into or out of one: sym-B under any sign, and branch=B by
value. Both name the branch in the tag itself, so the gate judged tags by
what they named.
A closed tag names a check-in, not a branch. The gate let it through, and
its test said so on purpose: closing a leaf is not a membership change.
That reasoning was right and the rule was still half a rule. An account
with check-in rights but no ownership of trunk's protection could not
push onto trunk, and could close its tip. The owners' next commit was then
refused against a closed leaf, with a message that named neither the cause
nor the actor, and the recovery was a raw tag cancel that appears in no
interface. The cheapest way past a protected branch was not to push to it.
The gate now treats lifecycle as it treats membership. A closed tag aimed
at a check-in this server holds resolves that check-in's branch through the
resolver the gate already has, and is refused when the branch is protected,
under any sign: +closed and *closed would close it, -closed would
reopen it, and the refusal says which.
The line is drawn narrowly. A release name on a protected leaf is admitted.
Closing a leaf off the protected branch is admitted. A closed aimed at a
check-in this server does not hold is admitted, as an unknown parent is: it
cannot be judged yet and its target is judged on arrival. A check-in that
closes itself has no target and is judged by where it joins. A resolver
that cannot answer refuses, as it always did; a protection that admits
under a read fault is not one.
A damaged container is an error, not a crash and not wrong bytes
The first full nightly soak, added in the previous release, found this on its first night. The fuzz target that feeds the container codec damaged containers took a valid container, flipped one bit, and brought the process down: an integer underflow inside the standard library's inflate, in its end-of-input bit peek, that a truncated stream can arrange. Every sync request and reply passes through that decoder, so any peer could send those bytes to a server or a client.
The decoder is now the tree's own copy of the standard one, with the comparison rearranged so the stream ends instead of underflowing, and a note on where it came from and why; it goes back to the standard library when the fix is upstream. With the crash gone, the same bytes decoded to fifty-eight bytes reported as good: the flip had made a block final, the stream ended cleanly early, and the zlib checksum in the footer was read but never checked. The codec now requires a finished stream and verifies the footer's Adler-32 against the output. A container that lies about either is refused. The exact bytes are a fixed test.
Compatibility
- No on-disk repository or wire format changed; Fossil RFC revision 00 remains the target.
- A server with no protected globs is unchanged. On a server with them, the
only new refusal is a
closedtag, under any sign, whose held target is on a protected branch. The refusal is a protocol error card the pusher reads, naming the tag, the target, and the branch.
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.