The RFC vector ledger pollution
What was wrong
Fossil treats any blob that parses as an artifact as a real one. The RFC
conformance vectors in src/rfc_vectors/ are artifact text — that is the
whole point of them — so committing them as files handed Fossil 21 live
artifacts. Content addressing means the vector file and the artifact were
never two things: they were one blob wearing a filename.
The consequences, all of which were visible in this repository:
- Two check-ins and a technote crosslinked into the timeline, attributed
to
drh, dated 2007 and 2013. They appear infossil timelineas real history that nobody in this project ever committed. - Twelve sub-manifest slices, invisible to
fossil(2.28 cannot parse an S card) but structural tozit. These never showed up in any Fossil table, which is why they went unnoticed for so long. - Six S-bearing tree and delta manifests.
- A pair of phantoms — parents named by two of those artifacts that were never delivered — which every sync chased forever.
The last item mattered beyond tidiness. Under
draft-fossil-submanifests-00 §8.2 a repository holding an S-bearing
artifact is a disposable test fixture, so this repository was formally
un-propagatable. And zit import-fossil --submanifest refused to convert
it: the rebirth path cannot re-mint a source that already contains tree
manifests, and it failed with an exit code and no diagnostic at all.
The fix, in two halves
Guard (check-in 32ef56aaf418, merged as ca7453995e). Every vector
gains a leading # block. # is not in the card alphabet, so the bytes no
longer parse and Fossil carries them as inert content. src/rfc_vectors.zig
strips the block at comptime, so callers still see the exact RFC bytes and
every recorded artifact name still verifies. A guarded array holds each
vector raw so the tests can assert it fails the §4.1 framing gates.
The guard is not what removes the old artifacts. It is what decouples the working files from them: once a vector hashes differently, the contaminated blob is reachable only from historical revisions, and can be removed without taking the file at tip with it. Renaming the files would not have helped — a blob is named by its content, and the filename lives in the F card.
Shun. The 21 artifact-shaped blobs, then the 2 orphaned phantoms, then
fossil rebuild to reclaim. Ordering is not negotiable: shunning before the
guard lands destroys the files at tip, which was confirmed experimentally
before any of this was applied to a real repository.
Four vectors were left alone. sync_a1_round1_reply, sync_a1_round2_file_cards,
sync_a1_round2_reply and sync_a2_reply are wire payloads, not artifacts;
they never parsed and were never contaminating anything.
Result
fossil test-integrity: 3209 non-phantom blobs out of 3209 total, 0 errors.
What it costs
Revisions of those 21 vector files from before the guard commit can no longer be checked out. Their current content is intact and byte-identical to the RFC text once the guard is stripped. Nothing else in the history is affected.
How to not do this again
A file whose bytes are artifact text cannot be committed unguarded. If a new
conformance vector is added, it goes through src/rfc_vectors.zig and gets
the # block, and the test that asserts every entry of guarded fails the
framing gates is what enforces it.