Artifact 54295509ecbfd06211a2c94f0e3218f1156774e23a4829db2e0310bb08ff917d:
- File docs/rfcs/erratum/repo-state-00-s5.3-cancel-value.md — part of check-in [844bfbd08c] at 2026-10-14 12:59:01 on branch trunk — repo-state §5.3: a cancel's value stays on the stored row (user: dmestas, size: 3505) [annotate] [blame] [check-ins using]
Erratum: draft-fossil-repo-state-00 §5.3 step 3
| Document | draft-fossil-repo-state-00 |
| Section | 5.3, Direct Application Procedure, step 3 |
| Type | Technical (the text says the stored row loses a cancel's value; Fossil keeps it) |
| Status | Applied in draft-fossil-repo-state-01 (2026-10-14) |
| Reported | 2026-10-14 |
| Submitter | Dan Mestas |
| Found via | libfossil's conformance suite (repostate.cancel-propagation), then Fossil's tag_insert(); confirmed with Fossil 2.28 |
1. Summary
Step 3 says that when a cancel is applied, the tag's value is cleared to null "as well as the stored row". Fossil does not clear the stored row. It writes the T card's value into the target's tagxref row, and only then clears its local copy of the value, so the null reaches the derived state (the event table) and propagation, not the row itself. A cancel may carry a value: the artifact format allows one on every T card, and some tools use it. Under the current text a conforming reader drops that value and diverges from Fossil.
2. The text in error
Page 15 (§5.3 step 3):
If the resolved type is cancel (0), the tag's value is cleared to null before any further processing, erasing the value from cached state as well as the stored row.
3. What Fossil implements
tag_insert() in src/tag.c (trunk, 2026-10-13):
tag.c:188 REPLACE INTO tagxref(tagid,tagtype,srcId,origid,value,mtime,rid)
... zValue ... /* the row gets the value */
tag.c:196 if( tagtype==0 ){
tag.c:197 zValue = 0; /* cleared after the write */
}
tag.c:222 event updates (bgcolor, comment, user, date) use zValue
tag.c:240 tag_propagate(rid, tagid, tagtype, rid, zValue, mtime);
manifest_crosslink() passes a T card's value to tag_insert() unchanged,
so a cancel card's value is what the REPLACE stores.
Checked with Fossil 2.28 [52445a27f1]. Chain c1 <- c2 <- c3; a propagating
bgcolor tag (#aabbcc) at c1; then a control artifact carrying
T -bgcolor <c2> ignored, followed by fossil rebuild:
| Check-in | tagxref (tagtype, value) | event.bgcolor |
|---|---|---|
| c1 | 2, '#aabbcc' |
'#aabbcc' |
| c2 | 0, 'ignored' |
NULL |
| c3 | no row (inherited row deleted) | NULL |
The canceled row keeps 'ignored'. The event table and the descendant
show the cancel's effect with no value, as the rest of §5.3 and §5.4
expect.
4. Corrected text
- If the resolved type is cancel (0), the row written in step 2 keeps the T-card's value, if one was supplied. For all further processing (cached state such as the event table, and the propagation walk of Section 5.4) the value is treated as null.
5. Observable consequence of the uncorrected text
An implementation that follows the text stores NULL where Fossil stores the cancel card's value. Any view reading a canceled tag's value from tagxref (a tag listing, a tool that records why a tag was canceled) then differs from Fossil for the same artifacts, and a repository rebuilt by one implementation differs from the same repository rebuilt by the other. Nothing else changes: the cancel still blocks and removes the tag exactly as §5.4 describes.
6. Implementations
libfossil already keeps the value, as Fossil does. Its conformance suite asserted the text in error and so reported libfossil as failing; the suite is being corrected to expect the value kept.