| Document | draft-fossil-artifact-format-00 |
| Section | 4.6.3 — Reserved Filenames; 5.3 — Type Determination |
| Type | Technical (divergence from the reference implementation) |
| Status | Proposed — operator decision pending |
| Reported | 2026-10-11 |
| Submitter | Dan Mestas |
| Found via | Checking an illustrated reading of the draft against Fossil 2.28 |
1. Summary
§5.3 says the F card sets the artifact type to MANIFEST unconditionally, and that the untyped fallback is therefore reachable only through the C or P card. In Fossil an F card whose current filename is reserved (§4.6.3) is dropped before it sets the type. Such a card leaves the type unset, so the artifact can reach the fallback, become a CONTROL artifact and fail the allowed-card check because it contains an F card. An earlier copy of the draft said this correctly; the current text removed it.
2. Location
§5.3, Table 5 and the paragraph introducing it:
Thirteen card letters are type-setting; each unconditionally assigns the artifact type when processed …
B, F, Q, R MANIFEST
§5.3, fallback paragraph:
In practice this fallback is reachable only via the C card or P card (the F card already sets MANIFEST directly), because the C and P card grammars never assign a type.
§4.6.3 says nothing about where the drop falls relative to type assignment.
3. The divergence
Verified against Fossil 2.28 (52445a27f1) by source reading and by running
its parser.
3.1 Source
In manifest_parse()'s F-card case (src/manifest.c:678), the reserved-name
test (:710) decrements nFile and breaks out of the case (:714-715).
The type assignment p->type = CFTYPE_MANIFEST comes after it (:721), so a
dropped card never reaches it. The card's letter is still recorded in
seenCard (:564), because that happens before the switch.
After the loop, the fallback (:1076-1082) tests p->zComment, p->nFile
and p->nParent. A dropped card does not count towards nFile, so an artifact
whose only F cards were reserved, and which has no C or P card, becomes
CONTROL. The allowed-card check (:1086) then rejects it, because F is not in
CONTROL's allowed set DTUZ (:134).
3.2 Experiment
The SHA1 below is a valid hash token. Each file ends with a correct Z card.
| Input cards | fossil test-parse-manifest |
|---|---|
D, F foo.txt <h>, Z |
ok (MANIFEST via F) |
D, F _FOSSIL_ <h>, Z |
ERROR: line 3: F-card in tag |
D, F _FOSSIL_ <h>, T +sym-x *, U u, Z |
ERROR: line 5: F-card in tag |
C x, D, F _FOSSIL_ <h>, Z |
ok (MANIFEST via fallback on C) |
The second and third rows reach the fallback with no C or P card, which §5.3 says cannot happen. The third row also shows that a dropped F card does not fit cleanly into a control artifact either: F is still a seen card.
4. Impact
A parser built to §5.3 sets MANIFEST when it reads a reserved-name F card. It accepts the second-row artifact as an empty manifest where Fossil rejects it, so the two disagree on which artifacts are valid. That is an interoperability failure in the direction that matters: the reimplementation accepts and cross-links content that every Fossil peer refuses.
The case is narrow (reserved names reach manifests only through old writers), but the draft states the opposite of the observable behavior as a fact about reachability.
5. Correction
§4.6.3, after "Dropping removes the card from the effective file set.":
An F card whose decoded current filename is reserved MUST be silently
dropped, not rejected. Dropping removes the card from the effective
- file set. Such names leaked into historical manifests, and existing
+ file set. The drop occurs before the F card would set the artifact
+ type to MANIFEST (Section 5.3): a dropped card assigns no type and
+ records no file entry, though its letter still counts as present
+ for the allowed-card check of Section 5.2. Such names leaked into
+ historical manifests, and existing
documentation contains no mention of this behavior (Appendix C); the
§5.3, introduction to Table 5:
- cards. Thirteen card letters are type-setting; each unconditionally
- assigns the artifact type when processed, with no guard against a
- previously assigned value:
+ cards. Thirteen card letters are type-setting; each assigns the
+ artifact type when processed, with no guard against a previously
+ assigned value. The one exception is an F card dropped for a
+ reserved filename (Section 4.6.3), which assigns nothing:
§5.3, fallback paragraph:
- In practice this fallback is reachable only via the C card or P card
- (the F card already sets MANIFEST directly), because the C and P card
- grammars never assign a type. The allowed-card and required-card
+ In practice this fallback is reachable via the C card or P card,
+ whose grammars never assign a type, and also when every F card was
+ dropped for a reserved filename. In the last case, with no C card
+ and no P card, the artifact resolves to CONTROL and is then rejected
+ because F is not an allowed CONTROL card. The allowed-card and
+ required-card
6. Rationale
- The ordering is observable. It decides whether an artifact is accepted, so it belongs in the normative text, not only in an implementation note.
- This restores earlier wording. The July draft of §4.6.3 and §5.3 said this, and Fossil 2.28 matches it. The later "reachable only via C or P" sentence is the regression.
- Seen-card subtlety stated once. Without the "still counts as present" clause a reader could drop the card entirely before the legality checks and accept the third-row artifact as CONTROL.
7. Verification
- Source:
src/manifest.c:564(seenCard),:710-715(drop and break),:721(type assignment),:1076-1082(fallback),:1086(allowed-card check),:134(CONTROL allowed set), Fossil 2.28. - Experiment: the four inputs in §3.2, built by hand with an MD5 Z card and
run through
fossil test-parse-manifeston Fossil 2.28 (52445a27f1).
Prepared against draft-fossil-artifact-format-00. The RFC remains the oracle;
the reference implementation is used only to confirm the observable behavior.