.
D 2026-08-25T21:26:53.229
L rfc-vector-ledger
N text/x-markdown
U dmestas
W 3616
# 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 in `fossil timeline` as 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 to `zit`. 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
<verbatim>
before after
blobs 3232 3209
phantoms 2 0
drh timeline 3 0
</verbatim>
`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.
Z c81a21755889787ada892bcf7e52df4e