# Erratum — draft-fossil-repo-state-00 §7.3
| | |
|---|---|
| **Document** | `draft-fossil-repo-state-00` |
| **Section** | 7.3 — Maintenance algorithm (full rebuild; deferred scheduling) |
| **Type** | Technical (two divergences from the reference implementation; the draft's leaf definition is unaffected) |
| **Status** | Applied in `draft-fossil-repo-state-01` (2026-10-12) |
| **Reported** | 2026-10-11 |
| **Submitter** | Dan Mestas |
| **Found via** | go-libfossil's conformance suite against Fossil 2.28; confirmed by reading Fossil's source and running it |
## 1. Summary
§7.2's leaf test and §7.3's full rebuild agree: a check-in with no same-branch
child is a leaf, and §7.3 says outright that a check-in with no parent "is a
leaf when it has no same-branch child, and MUST be included." Fossil keeps its
leaf table by a narrower rule, in two places:
1. **Its full recompute excludes every check-in with no parent.** The query
draws its candidates from the parent-link table's child column, so a root
is never a candidate. On a new repository, `fossil leaves --recompute`
leaves no leaves at all.
2. **Its incremental maintenance runs only on branch-tag events.** A leaf
recheck is scheduled when a branch tag is set on a check-in or propagated
to it, never when a parent-link is created. A check-in with no parent and
no branch tag receives no such event, so it never becomes a leaf.
Every check-in Fossil itself writes carries a branch tag (its own, or one
propagated from its parent), so for Fossil-authored history the two rules
agree, except under `--recompute`. They differ for a parentless check-in with
no branch tag, such as the §A.1.1 example of the artifact-format draft: under
the draft it is a leaf, and in Fossil it is not.
## 2. Location
§7.3, two bullets. Current text:
> *Full rebuild:* … The candidate set is every check-in, not only those that
> appear as a child of some parent-link. A check-in with no parent at all (for
> example the sole check-in of a new repository, or any root check-in) is a
> leaf when it has no same-branch child, and MUST be included.
> *Deferred scheduling:* implementations MUST schedule an incremental leaf
> recheck for the affected check-in and for each of its direct parents when a
> parent-link is created or a branch tag is applied or propagated.
## 3. The defects
Verified against Fossil 2.28 by reading the source and by running it.
### 3.1 The full recompute has no roots
`leaf_rebuild()` (`src/leaf.c:85`):
```sql
DELETE FROM leaf;
INSERT OR IGNORE INTO leaf
SELECT cid FROM plink
EXCEPT
SELECT pid FROM plink WHERE <parent and child on the same branch>
```
Its only caller is `fossil leaves --recompute` (`src/descendants.c`). Plain
`fossil rebuild` does not call it. On a repository holding only the initial
check-in of `fossil new`:
```
$ fossil leaves # (1) [eeb2389b69] initial empty check-in
$ fossil leaves --recompute # nothing; the leaf table is empty
```
The draft's rule is the coherent one: the incremental path keeps that same
check-in as a leaf, so Fossil's two paths disagree with each other. This half
is a Fossil defect, and the draft needs no change for it beyond recording it.
### 3.2 Scheduling is driven by branch tags alone
`leaf_eventually_check()` (`src/leaf.c:172`), which queues a check-in and its
parents for a recheck, has exactly two callers, both in `src/tag.c`: setting a
branch tag on a check-in (`:195`) and propagating one to a child (`:101`).
Crosslinking a check-in and its parent-links schedules nothing. A parentless
check-in with no branch tag therefore never reaches `leaf_check()`:
| Check-in in one repository (Fossil 2.28, after `fossil rebuild`) | parents | branch tag | leaf |
|---|---|---|---|
| `eeb2389b69`, the initial check-in of `fossil new` | none | `trunk` | yes |
| `a28c83647d`, the §A.1.1 artifact, stored with `test-content-put` | none | none | **no** |
## 4. Impact
Observable, rare. `zit leaves` follows the draft (`src/repostate.zig`: the leaf
candidates are every check-in) and lists a parentless check-in with no branch
tag. `fossil leaves` on the same repository does not. Fossil never writes such
a check-in, so it reaches a repository only by import or by synthesis.
`fossil leaves --recompute` is more serious for Fossil users, since it drops the
sole leaf of a new repository and every root of a multi-root one, but that is
Fossil's to fix.
## 5. Correction
The leaf definition (§7.1, §7.2) and the full-rebuild bullet stay as they are.
Two options for the rest, which are an operator decision.
**Option A (recommended): keep the draft's rule, and record Fossil's divergence.**
The draft has no compatibility appendix, so add an informative paragraph at the
end of §7.3:
```
This paragraph is informative. Fossil's full leaf recompute (fossil
leaves --recompute) draws its candidates from the child column of the
parent-link table, so it omits every check-in with no parent,
check-ins this section requires to be included. Fossil's incremental maintenance schedules a
recheck only when a branch tag is set on a check-in or propagated to
it, so a check-in with no parent and no branch tag never enters its
leaf table. Every check-in Fossil writes carries a branch tag, so the
two agree on Fossil-authored history.
```
and make the scheduling bullet say what the leaf test needs, so that every
check-in is rechecked at least once:
```diff
- parent-link is created or a branch tag is applied or propagated.
+ check-in is crosslinked, when a parent-link is created, or when a
+ branch tag is applied or propagated.
```
**Option B: describe Fossil.** Narrow §7.3 so that a check-in is a leaf
candidate only when it is the child of a parent-link or carries a branch tag,
and drop "MUST be included" for parentless check-ins. This matches Fossil's
incremental path, but writes its `--recompute` defect into the draft, and
contradicts §7.2's test for an untagged root.
## 6. Rationale
- **§7.2 is the normative test.** A parentless check-in has no parent-link
naming it as parent, so §7.2 makes it a leaf. Option B makes §7.3 disagree
with §7.2; option A keeps them in step.
- **Fossil disagrees with itself.** Its two paths give different answers for a
tagged root, so there is no single Fossil behavior to describe; the draft
would have to choose one of them anyway.
- **The divergence costs Fossil users nothing in practice.** Fossil never
writes a parentless check-in without a branch tag, so option A changes
nothing a Fossil-only user sees.
## 7. Verification
- Source: Fossil 2.28 `src/leaf.c` (`leaf_rebuild`, `leaf_check`,
`leaf_eventually_check`), `src/tag.c:101`, `:195`, and
`src/descendants.c` (the one `leaf_rebuild` caller).
- Run: `fossil new` (one leaf); `fossil leaves --recompute` (no leaves); store
the §A.1.1 artifact with `fossil test-content-put` and `fossil rebuild`
(it is not a leaf, while the initial check-in is).
---
*Prepared against `draft-fossil-repo-state-00`. The RFC remains the oracle; the
reference implementation is used only to confirm the observable behavior.*