# Erratum — draft-fossil-repo-state-00 §6.2

| | |
|---|---|
| **Document** | `draft-fossil-repo-state-00` |
| **Section** | 6.2 — Branch creation (symbolic-tag cancels; name-reuse check) |
| **Type** | Compatibility note (the draft is correct; Fossil's two creation commands differ from each other and, in corner cases, from the draft) |
| **Status** | Proposed — operator decision pending |
| **Reported** | 2026-10-11 |
| **Submitter** | Dan Mestas |
| **Found via** | Cross-check of the illustrated reading and an older draft copy against Fossil 2.28 `src/branch.c` and `src/checkin.c`; confirmed by running Fossil 2.28 |

## 1. Summary

§6.2 is right as written. Two things Fossil does around it are worth recording,
because an implementer comparing manifests or behavior against Fossil will
meet them:

1. **The cancel cards differ by command.** `fossil commit --branch` cancels
   only the propagating `sym-*` tags of the parent, which are exactly the
   ones the new root would inherit. `fossil branch new` cancels every active
   `sym-*` tag of the parent, singletons included, so its root manifest also
   carries cancel cards for tags the root would never have inherited. The
   active tags of the root are the same either way.
2. **Fossil's reuse check is not §9's open-leaf test.** It requires a
   propagating branch tag and treats only a singleton `closed` tag as
   closing. A branch whose leaf carries a propagated `closed` tag is listed as
   closed by `fossil branch list`, yet `fossil branch new` refuses to reuse its
   name as open. `fossil commit --branch --force` skips the check entirely.

## 2. Location

§6.2. Current text:

> Branch creation MUST also cancel every pre-existing active sym-* tag on
> the branch-root check-in by emitting corresponding - (cancel) cards,
> because symbolic tags are otherwise inherited from a check-in's parent.

> Branch creation MUST NOT succeed if the requested branch name already
> names an open branch.  An implementation MUST check openness of the
> candidate name before emitting the branch-root manifest and MUST reject
> the operation if a branch of that name is already open.

## 3. The divergence

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

### 3.1 Cancel cards

Both commands read the tags of the parent check-in, since the root does not
exist yet.

- `fossil branch new` (`src/branch.c:172-182`) selects
  `tagtype>0 AND tagname GLOB 'sym-*'`: singleton and propagating.
- `fossil commit --branch` (`src/checkin.c:2099-2112`) selects
  `tagtype==2 AND tagname GLOB 'sym-*' AND tagname!='sym-'||<new name>`:
  propagating only, and never the new branch's own name.

Run: trunk check-in c4 carries singleton `sym-rel`, `sym-release`, `sym-relx`
and inherits propagating `sym-prop` and `sym-trunk`.

```
$ fossil branch new b1 c4          $ fossil commit --branch b2   (from c4)
T *branch * b1                     T *branch * b2
T *sym-b1 *                        T *sym-b2 *
T -sym-prop *                      T -sym-prop *
T -sym-rel *                       T -sym-trunk *
T -sym-release *
T -sym-relx *
T -sym-trunk *
```

The three extra cards in `b1` cancel nothing the root had; they leave
cancel-type rows on the root and nothing else. §6.2's "every pre-existing
active sym-* tag on the branch-root check-in" is the inherited, propagating
set, which is what `commit --branch` emits. Both commands satisfy it.

### 3.2 Reuse check

`branch_is_open()` (`src/branch.c:388-401`), used by both commands
(`src/branch.c:116`, `src/checkin.c:2735-2741`):

```sql
SELECT rid FROM tagxref AS ox
 WHERE tagid=<branch> AND tagtype=2 AND value=<name>
   AND rid IN leaf
   AND NOT EXISTS(SELECT 1 FROM tagxref AS ix
                   WHERE tagid=<closed> AND tagtype=1 AND ox.rid=ix.rid)
```

§9's open-leaf test, and Fossil's own branch listing
(`src/branch.c:249-250`, `src/leaf.c:148-153`), use `tagtype>0` for both
tags. The two tests disagree in two corner cases:

| Leaf of branch `X` carries | §9 / `fossil branch list` | `branch_is_open` |
|---|---|---|
| propagated `*closed` | closed | **open**: reuse refused |
| singleton `+branch X` | open | **not open**: reuse allowed |

Run (first row): `fossil tag add --raw --propagate closed b1`; `fossil branch
list -c` lists `b1`; `fossil branch new b1 trunk` fails with `an open branch
named "b1" already exists`. The control, a singleton `closed` on `b2`, lets
`fossil branch new b2 trunk` succeed. The second row is from the source only;
Fossil never writes a singleton branch tag itself.

In addition, `commit --branch` skips the check under `--force` and for the
name `private` (`src/checkin.c:2735-2737`), so Fossil can create a second open
branch of an existing name, which §6.2 forbids.

## 4. Impact

Small. The cancel-card difference is visible only in the root manifest's bytes
and in cancel rows on the root; resolution, branch membership and leaves are
unchanged. The reuse-check difference needs a propagated `closed` tag or a
singleton branch tag, neither of which Fossil's own branch commands write, or
the undocumented `--force`. Fossil's check disagreeing with its own branch
listing is Fossil's to fix; the draft should not copy it.

## 5. Correction

No normative change. Add an informative paragraph at the end of §6.2:

```
   This paragraph is informative.  Fossil computes the cancel cards from
   the parent check-in.  Its commit --branch command emits them only for
   the parent's propagating sym-* tags other than the new branch's own,
   which are the tags the root would inherit.  Its branch new command
   emits them for every active sym-* tag of the parent, singletons
   included; the extra cards cancel nothing on the root.  Fossil's
   name-reuse check treats a branch as open only when a leaf carries a
   propagating branch tag and no singleton closed tag, which differs
   from the open-leaf test of Section 9 when a leaf carries a propagated
   closed tag or a singleton branch tag, and commit --branch skips the
   check when forced.
```

## 6. Rationale

- **The draft states the useful rule.** Cancelling the inherited set is what
  keeps the parent's names off the new branch; the extra singleton cancels of
  `branch new` have no effect on derived state, so requiring them would add
  bytes without meaning.
- **One openness test.** §6.2 relies on §9 for "open". Writing Fossil's
  narrower check into §6.2 would give the draft two definitions of an open
  branch, which is the inconsistency Fossil has between `branch new` and
  `branch list`.
- **Recording it saves a false alarm.** An implementation that compares its
  branch-root manifests with Fossil's will see the extra cancel cards from
  `branch new` and should know they are not a conformance failure.

## 7. Verification

- Source: Fossil 2.28 `src/branch.c:79-200` (`branch_new`), `:249-250` (the
  listing's closed test), `:388-401` (`branch_is_open`);
  `src/checkin.c:2099-2112` (cancel cards for `commit --branch`),
  `:2735-2741` (its reuse check); `src/leaf.c:148-165` (closed tests).
- Run: `fossil new`; four commits; singleton tags `rel`, `release`, `relx` on
  c4; `fossil tag add --propagate prop c2`; `fossil branch new b1 c4` and,
  from c4, `fossil commit --branch b2`; compare `T` cards with `fossil
  artifact`. Then `fossil tag add --raw --propagate closed b1`, `fossil branch
  list -c`, `fossil branch new b1 trunk` (refused); `fossil tag add --raw
  closed b2`, `fossil branch new b2 trunk` (succeeds).

---

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