Skip to content
zit Artifact [71f82076a3]
Login

Artifact [71f82076a3]

Artifact 71f82076a33402ba367b6130730c705601f0caca97c8eb5c2090bfd8215d8a44:


# 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:

```c
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:

```diff
-      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

- **The draft already has the rule.** §5.3 step 4 and the opening of §5.4
  describe the downgrade and its blocking effect. Only §4.2 says otherwise;
  the correction removes the contradiction in favour of the sections that
  match Fossil.
- **Fossil applies it on every path.** The downgrade appears twice in
  `src/tag.c`, at `:239` for a newly applied tag and at `:131` for the
  re-propagation of a parent's tags (`tag_propagate_all`), so a singleton
  blocks inheritance whether it is crosslinked before or after its
  descendants. Without the walk, the outcome would depend on crosslink order.

## 7. Verification

- Source: Fossil 2.28 `src/tag.c:34-110` (`tag_propagate`), `:118-135`
  (`tag_propagate_all`), `:157-242` (`tag_insert`); `src/manifest.c:2433`,
  `:2467`, `:2472` (the crosslink callers).
- Run: `fossil new`; four commits; `fossil tag add --raw --propagate foo c2
  v1`; `fossil tag add --raw foo c3 v2`; query `tagxref` for `foo` on every
  check-in; `fossil rebuild`; query again. c4 has no `foo` row after the
  singleton, before and after the rebuild.

---

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