Artifact dc53e482e166e18ea4aeb1e8de55812f46be969f2f8a44de195009ab851ae941:
- File
conformance/rfcs/erratum/artifact-format-01-s4.4-z-card-last-by-ordering.md
— part of check-in
[f61c8174a8]
at
2026-10-17 20:27:53
on branch main
— test(conformance): re-pin the -01 drafts with the 2026-10-14/16 errata (#436)
zit check-in 3f86ff4261 applies six more errata to the -01 drafts. Copy the three drafts and the five new -01 errata files, re-pin their SHA-256 in the requirements tables, and record the check-in in the README.
The draft text changes add six requirements and reword two:
- AF-4.7.3-2: a named delta's delete markers are filtered out of a delta-of-delta's file set and lookups. - AF-4.7.8-1: an absent and a present empty J value decode alike. - AF-6.2-7: the Z verification state is read from the bytes at n-35 and n-34, not from card lines. - RS-5.3-3: a cancel keeps its card's value on the row it writes. - RS-5.3-6: the event table and propagation treat that value as null. - RS-5.3-7: an artifact's tag cards apply in card order. - RS-6.2-4: a branch root carrying *sym-NAME and -sym-NAME keeps the tag. - SP-6.6-4: a client recognizes a refusal by its leading text.
go-libfossil already behaves this way. Each row has a case, checked against fossil 2.28 where the harness does: 421 requirements, 420 covered, 1 partial (SP-9.5-7, as before).
The repo-state §5.3 step 3 change is filed in zit as a -00 erratum and is not pinned, like the other -00 errata; the -01 draft carries its text. (user: [email protected], size: 5647) [annotate] [blame] [check-ins using]
# Erratum: draft-fossil-artifact-format-01 §4.4, §4.5.1 and §6.2 | | | |---|---| | **Document** | `draft-fossil-artifact-format-01` | | **Section** | 4.4, Card Ordering ("Nothing follows Z"); 4.5.1, Occurrence Classes; 6.2, Z Card: Artifact Self-Checksum | | **Type** | Editorial (the reason given for the Z card being last is wrong; the verification states are described by card rather than by byte position) | | **Status** | Applied in `draft-fossil-artifact-format-01` (2026-10-16) | | **Reported** | 2026-10-16 | | **Submitter** | Dan Mestas | | **Found via** | A reader working from the draft text alone; confirmed by running Fossil 2.28 and reading its source | ## 1. Summary §4.4 and §4.5.1 say the `Z` card's must-be-last property follows from the checksum framing, which "fixes the Z card as occupying exactly the final 35 bytes". The framing fixes nothing: it reads the final 35 bytes by position and treats them as a `Z` card when the first two are `Z` and a space, whether or not a card line begins there. The `Z` card is last for two other reasons: the ordering rule admits only another `Z` after a `Z`, and every artifact type requires a `Z` card (Table 4). Its 32-digit token then puts it in the final 35 bytes. §6.2's state table names the absent and mismatch states by whether a `Z` card is present. Fossil decides them by the bytes at offsets n-35 and n-34, so an artifact with no `Z` card can reach the mismatch state. ## 2. The text in error Page 14 (§4.4): > Z is the lexicographically last defined letter, the ordering rule admits > no letter after it (the N-after-P exception does not involve Z), and the > checksum-verification framing fixes the Z card as occupying exactly the > final 35 bytes of the stripped content: Z, space, 32 hexadecimal digits, > LF. The Z card's must-be-last property is enforced structurally — by > ordering plus byte-position framing — rather than by an explicit > last-card check in the Z card grammar, which validates only token syntax. Page 15 (§4.5.1): > No separate rule requires the Z card to be exactly one and last; that > property follows from the framing. Page 33 (§6.2, the state table): > absent: no Z card present, or artifact too short to hold one > mismatch: Z card present but token differs ## 3. What Fossil implements `verify_z_card()` in `src/manifest.c`: ``` if( n<35 ) return 0; if( z[n-35]!='Z' || z[n-34]!=' ' ) return 0; md5sum_init(); md5sum_step_text(z, n-35); zHash = md5sum_finish(0); if( memcmp(&z[n-33], zHash, 32)==0 ){ return 1; }else{ ... return 2; } ``` It looks at two bytes, not at line boundaries. `manifestCardTypes` lists `Z` among the required cards of all eight types, and the ordering check in `manifest_parse()` rejects any letter that sorts below the one before it. Checked with Fossil 2.28 [52445a27f1], `fossil test-parse-manifest`, on a control artifact with no `Z` card whose last line is a `U` card ending in `Z` and a 32-character token: | Last line | Result | |---|---| | `U aliceZ 0123456789abcdef0123456789abcdef` | `incorrect Z-card cksum` | | `U aliceZ` followed by the MD5 of the bytes before that `Z` | `line 3: extra characters at end of card` | | `U alice` | `Z-card missing in tag` | The first artifact has no `Z` card and still fails the checksum. All three are rejected; only the reason differs. ## 4. Corrected text §4.4, replace the bullet with: > Nothing follows Z. Z is the lexicographically last defined letter and > the ordering rule admits no letter after it (the N-after-P exception > does not involve Z), so only another Z card can follow a Z card. Every > artifact type requires a Z card (Section 5.2), so the last line of an > accepted artifact is a Z card, and because its token is exactly 32 > hexadecimal digits it occupies exactly the final 35 bytes of the > stripped content: Z, space, 32 hexadecimal digits, LF. The must-be-last > property follows from ordering and the required-card rule, not from an > explicit last-card check in the Z card grammar, which validates only > token syntax. Checksum verification (Section 6.2) does not establish it > either: it reads the final 35 bytes by position, whatever card they > belong to. §4.5.1, replace the sentence quoted in Section 2 with: > No separate rule requires the Z card to be exactly one, and that it is > last follows from the ordering and required-card rules (Section 4.4). §6.2, replace the absent and mismatch conditions with: > absent: offsets n-35 and n-34 do not hold Z and a space, or artifact too > short to hold one > mismatch: offsets n-35 and n-34 hold Z and a space, but the token differs and add after the paragraph that follows the table: > The state is decided by the bytes at those offsets, not by where card > lines begin: an artifact with no Z card whose last line ends in Z, a > space and 32 further bytes before its LF takes the match or mismatch > state rather than absent. Such an artifact is rejected either way, by > the mismatch here or by the card rules, so only the reported reason > differs. ## 5. Observable consequence of the uncorrected text A reader that trusts the framing to keep the `Z` card last can skip the ordering check after a `Z` card, or the required-card check, and accept an artifact Fossil rejects. A reader that decides the verification state by looking for a `Z` card line reports a different reason than Fossil for an artifact whose last line merely ends in `Z` and 32 characters. ## 6. Implementations zit checks the same two bytes at the same offsets, applies the ordering rule after a `Z` card, and requires a `Z` card for every type. No change is needed.