Skip to content
zit Erratum — draft-fossil-repo-state-00 §4.2 step 4
Login

Erratum — draft-fossil-repo-state-00 §4.2 step 4

Document draft-fossil-repo-state-00
Section 4.2 — Crosslink procedure, step 4 (propagation)
Type Technical (the text diverges from Fossil's implemented behavior, and from §5.3 step 4 of the same draft)
Status Proposed — operator decision pending
Reported 2026-10-11
Submitter Dan Mestas
Found via Cross-check of the draft against Fossil 2.28 src/tag.c; confirmed by running Fossil 2.28

1. Summary

§4.2 step 4 says a singleton (+) tag "affects only its target and triggers no walk". Fossil starts a walk for every tag it applies, singletons included: it downgrades the singleton to a cancel and walks from the target. The walk deletes the same-named tag that the target's descendants had inherited from an ancestor, so a singleton placed in the middle of a propagated run stops the inheritance below it.

§5.3 step 4 of the same draft already describes this ("When the tagged artifact is used as a propagation source, the cancel it becomes blocks a same-named tag from propagating through it"), so the two sections contradict each other. §5.3 matches Fossil; §4.2 does not.

2. Location

§4.2 step 4, last two sentences. Current text:

In addition, whenever a T-card in steps 1-3 applies a propagating (*) or cancel (-) tag to a resolved target, the propagation walk of Section 5.4 MUST be invoked rooted at that target, so that the applied or cancelled tag reaches the target's descendants. A singleton (+) tag affects only its target and triggers no walk.

3. The divergence

Verified against Fossil 2.28 by reading the source and by running it.

3.1 Source

tag_insert() (src/tag.c:157) is the single entry point for every T-card the crosslink applies (src/manifest.c:2467) and for the implicit cluster tag (src/manifest.c:2433). After writing the target's own row it ends with:

src/tag.c:239    if( tagtype==1 ) tagtype = 0;
src/tag.c:240    tag_propagate(rid, tagid, tagtype, rid, zValue, mtime);

There is no type test in front of the call: every applied tag starts a walk rooted at its target. A singleton enters the walk as a cancel. In tag_propagate() (src/tag.c:34), a cancel deletes a child's row when that row was inherited (srcid=0) and is older than the singleton (src/tag.c:57, the doit column), and the walk continues below every child it changed (src/tag.c:91).

3.2 Run

A five-check-in chain on trunk (initial, c1, c2, c3, c4), using fossil tag add --raw so the tag names are not prefixed. tagxref rows for foo, as (tagtype, srcid, value):

Check-in after *foo=v1 on c2 after +foo=v2 on c3 (1 s later)
c1 — —
c2 2, direct, v1 2, direct, v1
c3 2, 0, v1 1, direct, v2
c4 2, 0, v1 —

c4's inherited row is gone. fossil rebuild produces the same table, so it is the crosslink result, not an artifact of the tag command. Under the text of §4.2, c4 would keep foo=v1.

4. Impact

Observable whenever a singleton names a tag that the target's descendants inherit. The usual case is a singleton bgcolor or sym-* placed on a check-in inside a propagated run: in Fossil it truncates the run, so the descendants lose the inherited tag (and, for bgcolor, their event colour). An implementation that follows §4.2 keeps the inherited rows, and its tag state, tag find output and timeline colours differ from Fossil's for the same artifacts.

5. Correction

Make §4.2 say what §5.3 step 4 and Fossil do:

-      In addition, whenever a T-card in steps 1-3 applies a propagating
-      (*) or cancel (-) tag to a resolved target, the propagation walk
-      of Section 5.4 MUST be invoked rooted at that target, so that the
-      applied or cancelled tag reaches the target's descendants.  A
-      singleton (+) tag affects only its target and triggers no walk.
+      In addition, whenever a T-card in steps 1-3 applies a tag of any
+      type to a resolved target, the propagation walk of Section 5.4
+      MUST be invoked rooted at that target, so that the applied or
+      cancelled tag reaches the target's descendants.  A singleton (+)
+      tag is first downgraded to cancel (Section 5.3, step 4): its value
+      stays on the target, and the walk removes the same-named tag that
+      the target's descendants had inherited.

6. Rationale

7. Verification


Prepared against draft-fossil-repo-state-00. The RFC remains the oracle; the reference implementation is used only to confirm the observable behavior.