Artifact ae301b23caa725efd241de1f5c3863d894c0f52c8e4316fd6221e059575d2e32:
- File
conformance/rfcs/erratum/artifact-format-00-s4.1.2-sterilizer-position.md
— part of check-in
[57a558fcbf]
at
2026-10-10 23:52:34
on branch trunk
— test(conformance): harness for an RFC conformance suite (#262) (#264)
Adds the conformance package that the per-draft suites build on. Nothing in the repository has yet checked go-libfossil against the Fossil drafts requirement by requirement.
- A Case names its requirement. A Gap (an issue) or Deviation (a reason) marks a known difference. A marked case still runs: while it fails it is skipped, and once it conforms it fails until the mark is removed, so the list of known failures cannot go stale. - Requirements tables pin the SHA-256 of the drafts and errata they were written against. Those are copied unmodified into conformance/rfcs by scripts/refresh-conformance-rfcs.sh. A changed draft fails the coverage test until its table has been re-read and its pin moved. - Coverage is derived from the cases rather than recorded: - COVERED: every case conforms. - PARTIAL: the table's row says what its cases leave out. - DEVIATION: a case is marked as a deliberate difference. - FAILING: a case is marked with an open gap. - GAP: no case yet.
CONFORMANCE_REPORT=<path> writes the requirement-by-requirement report as Markdown. - fossil(1) is run on every case's input, through test-parse-manifest, so a case's expectation is checked against fossil each time the suite runs. withZ appends the Z card, so a case that edits one card never also breaks the checksum.
The harness's own rules are tested in harness_self_test.go. No suites are registered yet; the artifact-format suite is next (#263). (user: [email protected], size: 8411) [annotate] [blame] [check-ins using]
# Erratum — draft-fossil-artifact-format-00 §4.1.2 | | | |---|---| | **Document** | `draft-fossil-artifact-format-00` | | **Section** | 4.1.2 — Sterilization | | **Type** | Technical (three factual divergences from the reference implementation) | | **Status** | Draft — not yet applied | | **Reported** | 2026-08-08 | | **Submitter** | Dan Mestas | | **Found via** | zit `import-git` mirror classifier, against a 36,383-commit SQLite Git mirror | ## 1. Summary §4.1.2 describes Fossil's sterilization marker — the comment line a writer adds to make content deliberately unparseable as a structural artifact. Three of its statements are wrong against the reference implementation: the marker's **position**, the **gate that rejects** the sterilized bytes, and the marker's **literal text**. The section is additionally self-contradictory: it says the marker is the first line, then says the signed form places it before the trailing signature block, which is after the cards. The direction of the error is the dangerous one. An implementer following §4.1.2 as written builds a sterilizer that does not match Fossil's, and the mismatch is invisible until a sterilized artifact reaches another repository — where it surfaces as a foreign check-in rather than as a parse failure. ## 2. Location §4.1.2 (Sterilization), the paragraph beginning *"This subsection is informative."* Current text: > To render content deliberately unparseable as a structural artifact, a writer > inserts a comment line beginning with # (of the form `# Remove this line to > create a well-formed Fossil artifact.`) **as the first line**, which causes > **the line-1 gate of Section 4.1 to fail**. For PGP- or SSH-signed content, > the marker line is instead spliced in immediately before the signature block… ## 3. The defect Verified against Fossil 2.28 (`52445a27f1`) by source reading and by running the code. ### 3.1 Position — the marker is appended, never inserted first `sterilize_manifest()` (`src/manifest.c:2238`) builds its output with `blob_appendf`. The marker lands **after the final card**, not before the first. Observed on a real checkout: ``` U tester Z 633267631377a908d4e09def80ef9fd5 # Remove this line to create a well-formed Fossil manifest. ``` ### 3.2 Failure mechanism — residue, not the line-1 gate Because the marker is trailing, it never reaches the line-1 gate. It is rejected by §4.1's rule that any bytes left unconsumed after the card loop MUST cause rejection — equivalently §4.2.1's residual-bytes clause. A reader that implemented only the stated line-1 mechanism would **accept** a sterilized artifact, which is the precise outcome sterilization exists to prevent. ### 3.3 Literal text — the quoted string is never emitted The format string (`src/manifest.c:2242`) is: ```c "# Remove this line to create a well-formed Fossil %s.\n" ``` `%s` is `manifest` for `CFTYPE_MANIFEST` and `control artifact` for every other type. The draft's `…well-formed Fossil artifact.` is emitted by no code path. ### 3.4 Internal contradiction "as the first line" and "spliced in immediately before the signature block" cannot both hold: the signature block is trailing, so a marker before it is after the cards. The signed description is the accurate one, and it generalizes — the unsigned case is the same insertion point with an empty signature. ## 4. Impact Two mechanisms, in opposite directions. **A reader built to §4.1.2 under-rejects.** It looks for a line-1 marker, finds none, and parses a sterilized artifact as a structural artifact. **A writer built to §4.1.2 diverges byte-for-byte from Fossil**, so its output is not the artifact any Fossil-derived name refers to. This is not theoretical, and the consumer-side cost is measurable. zit's Git import proves a mirror's `manifest`/`manifest.uuid` pair by stripping the sterilizer and checking the result against the name the mirror recorded. Written to the trailing-only reading of the implementation — which is correct for the unsigned case — it failed for every PGP-clearsigned check-in, because there the marker sits mid-blob, immediately before `-----BEGIN PGP SIGNATURE-----`. On a 36,383-commit SQLite mirror: | Proof outcome | Count | |---|---| | Proven by trailing strip | 35,307 | | Proven **only** once the marker is spliced from before the signature block | 1,076 | | Unproven | 0 | All 1,076 date from 2009–2011, when SQLite's `clearsign` setting was on. Each had its mirror metadata retained permanently — the observable symptom of a correct implementation built against an incorrect description of where the marker goes. ## 5. Correction applied Replace the first paragraph of §4.1.2 with text that states the position once, in the general (signature-aware) form, names the correct rejection mechanism, and quotes the two literals the implementation actually emits: ```diff - This subsection is informative. To render content deliberately - unparseable as a structural artifact, a writer inserts a comment line - beginning with # (of the form # Remove this line to create a well- - formed Fossil artifact.) as the first line, which causes the line-1 - gate of Section 4.1 to fail. For PGP- or SSH-signed content, the - marker line is instead spliced in immediately before the signature - block, with the boundary recomputed by the same wrapper-detection - logic of Section 4.1.1, so the signature remains trailing. + This subsection is informative. To render content deliberately + unparseable as a structural artifact, a writer appends a comment line + beginning with # after the final card, and before the signature block + when the content is PGP- or SSH-signed, so that a signature remains + trailing; the boundary is located by the same wrapper-detection logic + of Section 4.1.1. The marker reads # Remove this line to create a + well-formed Fossil manifest. for a check-in and # Remove this line to + create a well-formed Fossil control artifact. for any other type. + Because the marker follows the cards, it is Section 4.1's rule that + unconsumed bytes after the card loop MUST cause rejection — + equivalently the residual-bytes clause of Section 4.2.1 — that rejects + the artifact, not the line-1 gate. ``` ## 6. Rationale - **One statement of position.** The contradiction exists because position is asserted twice. Stating it once, in the form that covers both cases, removes it. - **The mechanism is load-bearing.** Naming the wrong gate produces a reader that fails open on exactly the input sterilization targets. This is the most consequential of the three defects. - **The literals are quoted because they are compared.** Consumers match the marker to strip it; a consumer matching the draft's string matches nothing Fossil writes. - **Informative, not normative.** §4.1.2 does not impose requirements — the correction changes only what the section describes, not what any reader or writer is required to do. ### Note on the producer side Fossil's own use of `sterilize_manifest()` narrowed in `722c248d53` (shipped 2.18, 2022-02-23), which removed it from `tar.c` and `zip.c`, so archive exports are no longer sterilized while checkouts and Git export still are. That is a producer-policy change and needs no draft text; it is recorded here because it makes §4.1.2's accuracy matter more, not less — consumers can no longer assume content arriving from a Fossil-derived tarball has been sterilized at all. --- *Prepared against the frozen final `draft-fossil-artifact-format-00`. The zit reference implementation already emits and strips the marker at the position described in §5; this erratum records the divergence so the draft can settle it.* ## 7. Resolution verification - All three defects verified twice: by reading Fossil 2.28 source (`src/manifest.c:2238`, `:2242`) and by observing a live checkout's `manifest` file and a published SQLite mirror's blobs. - The 1,076/35,307/0 split is reproducible: export `sqlite.fossil` with `fossil git export`, then for each commit compare `manifest` stripped both ways against `manifest.uuid`. - Clean-room effect: an implementer no longer has to discover, from a foreign check-in appearing in an unrelated repository, that the marker is trailing rather than leading and that the text varies by artifact type.