fossil-rfc

Documentation
Login

Documentation





Network Working Group                                    D. R. Hipp, Ed.
Internet-Draft                                                    Fossil
Intended status: Standards Track                                 S. Beal
Expires: 30 January 2027                                       libfossil
                                                              D. Shearer
                                                            29 July 2026


  The Fossil Distributed Version Control System: Repository State and
                          Check-in Resolution
                       draft-fossil-repo-state-00

Abstract

   Fossil records history as an append-only set of immutable, content-
   addressed artifacts.  Branches, tags, leaves, closed markers, and the
   "current trunk" check-in are derived views over that set.  A
   repository computes them by reading manifest parent cards,
   crosslinking tag cards into tag state, propagating tags, enumerating
   leaves, and resolving symbolic names to check-ins.  The current-trunk
   view also depends on the repository-local main-branch setting.  The
   Fossil Artifact Format [FossilArtifact] defines the card syntax; the
   Fossil Sync Protocol [FossilSync] defines how peers exchange the
   artifacts.

Status of This Memo

   This Internet-Draft is submitted in full conformance with the
   provisions of BCP 78 and BCP 79.

   Internet-Drafts are working documents of the Internet Engineering
   Task Force (IETF).  Note that other groups may also distribute
   working documents as Internet-Drafts.  The list of current Internet-
   Drafts is at https://datatracker.ietf.org/drafts/current/.

   Internet-Drafts are draft documents valid for a maximum of six months
   and may be updated, replaced, or obsoleted by other documents at any
   time.  It is inappropriate to use Internet-Drafts as reference
   material or to cite them other than as "work in progress."

   This Internet-Draft will expire on 30 January 2027.

Copyright Notice

   Copyright (c) 2026 IETF Trust and the persons identified as the
   document authors.  All rights reserved.





Hipp, et al.             Expires 30 January 2027                [Page 1]

Internet-Draft           Fossil Repository State               July 2026


   This document is subject to BCP 78 and the IETF Trust's Legal
   Provisions Relating to IETF Documents (https://trustee.ietf.org/
   license-info) in effect on the date of publication of this document.
   Please review these documents carefully, as they describe your rights
   and restrictions with respect to this document.  Code Components
   extracted from this document must include Revised BSD License text as
   described in Section 4.e of the Trust Legal Provisions and are
   provided without warranty as described in the Revised BSD License.

Table of Contents

   1.  Introduction  . . . . . . . . . . . . . . . . . . . . . . . .   3
     1.1.  Scope . . . . . . . . . . . . . . . . . . . . . . . . . .   4
   2.  Terminology . . . . . . . . . . . . . . . . . . . . . . . . .   4
   3.  Check-in DAG Construction . . . . . . . . . . . . . . . . . .   7
     3.1.  Linkage record shape  . . . . . . . . . . . . . . . . . .   7
     3.2.  Primary parent vs. merge parents  . . . . . . . . . . . .   8
     3.3.  Ancestry direction and traversal  . . . . . . . . . . . .   8
   4.  Crosslink Processing  . . . . . . . . . . . . . . . . . . . .   9
     4.1.  T-card syntax validation  . . . . . . . . . . . . . . . .   9
     4.2.  Crosslink procedure . . . . . . . . . . . . . . . . . . .   9
     4.3.  Rejection conditions  . . . . . . . . . . . . . . . . . .  11
     4.4.  Unclassifiable artifacts  . . . . . . . . . . . . . . . .  11
     4.5.  Comment rendering of crosslink effects  . . . . . . . . .  12
     4.6.  Serialisation of tag artifacts  . . . . . . . . . . . . .  12
   5.  Tag Types and Propagation . . . . . . . . . . . . . . . . . .  12
     5.1.  Tag Type Values . . . . . . . . . . . . . . . . . . . . .  12
     5.2.  Storage Model . . . . . . . . . . . . . . . . . . . . . .  13
     5.3.  Direct Application Procedure  . . . . . . . . . . . . . .  13
     5.4.  Propagation Algorithm . . . . . . . . . . . . . . . . . .  14
     5.5.  Blocking Conditions . . . . . . . . . . . . . . . . . . .  15
   6.  Branch Membership and Naming  . . . . . . . . . . . . . . . .  16
     6.1.  Branch of a check-in  . . . . . . . . . . . . . . . . . .  16
     6.2.  Branch creation . . . . . . . . . . . . . . . . . . . . .  17
     6.3.  Enumerating branches  . . . . . . . . . . . . . . . . . .  17
     6.4.  A note on branch current  . . . . . . . . . . . . . . . .  18
   7.  Leaves  . . . . . . . . . . . . . . . . . . . . . . . . . . .  19
     7.1.  Definition  . . . . . . . . . . . . . . . . . . . . . . .  19
     7.2.  Normative leaf test . . . . . . . . . . . . . . . . . . .  19
     7.3.  Maintenance algorithm . . . . . . . . . . . . . . . . . .  19
     7.4.  Open leaves and closed-marker semantics . . . . . . . . .  20
     7.5.  Leaf-set enumeration and ambiguity  . . . . . . . . . . .  21
     7.6.  Triggers for re-evaluation  . . . . . . . . . . . . . . .  21
     7.7.  Relationship to name resolution . . . . . . . . . . . . .  22
   8.  Symbolic Name Resolution  . . . . . . . . . . . . . . . . . .  22
     8.1.  Scope . . . . . . . . . . . . . . . . . . . . . . . . . .  22
     8.2.  Rule  . . . . . . . . . . . . . . . . . . . . . . . . . .  22
     8.3.  Procedure . . . . . . . . . . . . . . . . . . . . . . . .  23



Hipp, et al.             Expires 30 January 2027                [Page 2]

Internet-Draft           Fossil Repository State               July 2026


     8.4.  Branch-walk rule  . . . . . . . . . . . . . . . . . . . .  25
     8.5.  Distinguishing name resolution from open-leaf
           enumeration . . . . . . . . . . . . . . . . . . . . . . .  26
   9.  Current Trunk and Forks . . . . . . . . . . . . . . . . . . .  26
   10. Worked Example: Fork with a Closed Leaf . . . . . . . . . . .  28
     10.1.  Fork at trunk, one leaf closed . . . . . . . . . . . . .  28
     10.2.  Edge case: a closed leaf newer than the open leaf  . . .  28
     10.3.  Generalization . . . . . . . . . . . . . . . . . . . . .  29
   11. Security Considerations . . . . . . . . . . . . . . . . . . .  29
   12. IANA Considerations . . . . . . . . . . . . . . . . . . . . .  30
   13. Normative References  . . . . . . . . . . . . . . . . . . . .  30
   Authors' Addresses  . . . . . . . . . . . . . . . . . . . . . . .  30

1.  Introduction

   Fossil is a distributed version-control system.  Many people can
   track the full history of a project and exchange that history with
   peers.  Fossil stores the files, change history, wiki, tickets, and
   forum in a single repository file.

   Fossil records history by never changing anything it has already
   written.  Every check-in, tag, and edit is stored as an immutable,
   content-addressed _artifact_: a run of bytes named by its own hash.
   A project's history is therefore an append-only set of artifacts.

   That design gives strong integrity, but the artifacts do not answer
   everyday questions on their own.  Which check-in is the latest on
   trunk?  Which branch does a check-in belong to?  Which leaves are
   still open?  What does a tag point to now?  No artifact records these
   facts directly.  Each is implied by the whole set of artifacts and
   must be computed from it, by reading the parent links and tag records
   spread across the artifacts and resolving them into an ordered
   picture of what the repository currently contains.

   The stored artifacts are defined byte-for-byte by the companion
   Fossil Artifact Format [FossilArtifact].  From those artifacts, a
   repository derives its _state_: the check-in ancestry graph, branch
   and tag membership, open leaves, closed markers, and the check-in
   selected by a symbolic name such as a branch name or trunk.  None of
   these views is stored.  An implementation recomputes them from the
   artifacts so that two independent implementations reading the same
   artifacts reach the same answers.  The current-trunk check-in also
   uses the repository-local main-branch setting (Section 9), so two
   repositories with the same artifacts can resolve different current-
   trunk check-ins when that setting differs.






Hipp, et al.             Expires 30 January 2027                [Page 3]

Internet-Draft           Fossil Repository State               July 2026


   [FossilArtifact] defines the _syntax_ of the artifacts and cards read
   here; where a rule below names a hash, a card field, or a content
   type without defining it, the definition is there.  The Fossil Sync
   Protocol [FossilSync] defines how artifacts move between peers.  The
   rules here define the _meaning_ a repository computes over a given
   set of artifacts, independent of how they arrived.

1.1.  Scope

   The scope is the repository-state meaning computed from parent and
   tag (P/T) cards: ancestry, branch membership, named references,
   symbolic references, leaves, closed markers, and current-trunk
   resolution.

   [FossilArtifact] supplies the byte-level grammar of the P and T
   cards, the artifact-hash format and permitted lengths, artifact
   content-type determination, hash-to-check-in resolution, and delta-
   manifest reconstruction.  [FossilDelta] supplies only the byte-level
   content-delta encoding beneath that.  Ordering uses the stored
   numeric event.mtime and tagxref.mtime values derived from artifact
   timestamps.  Reproducible repository-state results require a stored
   D-card timestamp denoting a deterministic instant.  The
   compatibility-only relative literals now, subsec, and subsecond
   described in [FossilArtifact] fall outside the reproducible semantics
   specified here.  Where a rule below refers to a hash, a card field,
   or a content type without defining it, that definition is in
   [FossilArtifact].

   [FossilArtifact] defines reconstruction of the file tree belonging to
   a check-in, including delta-manifest walks that materialise file
   contents.  [FossilSync] defines repository-state exchange between
   peers.  The rules here use both as inputs.

   No normative claims are established in this section.  The terminology
   and structural rules that follow are normative and use BCP14 keywords
   where applicable.

2.  Terminology

   The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
   "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
   "OPTIONAL" in this document are to be interpreted as described in BCP
   14 [RFC2119] [RFC8174] when, and only when, they appear in all
   capitals, as shown here.







Hipp, et al.             Expires 30 January 2027                [Page 4]

Internet-Draft           Fossil Repository State               July 2026


   These terms are used throughout this specification and its companion
   specifications [FossilArtifact], [FossilSync], and [FossilDelta].
   The on-artifact syntax read here -- the notion of a _card_, a
   _manifest_, and the individual card letters -- is defined in
   [FossilArtifact] and is used here as given.

   Row id (rid):  The local integer identifier assigned to a stored
      artifact within one repository.  The derived views in this
      document are computed over rid values: a hash names an artifact
      globally, while its rid names the same artifact locally.  An rid
      is local to a repository, is not part of the artifact model, and
      is not synchronised between peers.

   Check-in DAG:  The directed acyclic graph formed by check-in
      artifacts connected through parent links.  Each check-in other
      than an initial commit designates one or more parents,
      establishing ancestry.

   Primary parent:  The first parent listed on a check-in.  The primary
      parent determines the check-in's place in the check-in DAG for
      purposes of branch inheritance and ancestry walks.

   Merge parent:  Any parent listed on a check-in beyond the primary
      parent.  A check-in with one or more merge parents records the
      joining of otherwise independent lines of development into a
      single check-in.

   Tag:  A named annotation associated with a check-in (rid) that either
      asserts a property of that check-in and its descendants, or
      revokes a previously asserted property.  Tags are recorded in a
      tag table indexed by a fixed tagid; tagid 8 denotes the branch tag
      ("branch") and tagid 9 denotes the closed tag ("closed").

   Tag type:  Every tag application is recorded with a tagtype value
      that determines its effect: a tagtype of 0 ("-") is a cancel tag,
      a tagtype of 1 ("+") is a singleton tag, and a tagtype of 2 ("*")
      is a propagating tag.  A tagtype greater than zero indicates the
      tag is active, or in effect, for the rid to which it is attached.

   Singleton tag:  A tag (tagtype 1) whose effect applies only to the
      single check-in it is attached to and does not propagate to
      descendant check-ins.

   Propagating tag:  A tag (tagtype 2) whose effect applies to the
      check-in it is attached to and, absent an intervening cancel, to
      its descendant check-ins along the primary-parent/primary-child
      chain -- not across merge edges (Section 5).  The branch tag is a
      propagating tag.



Hipp, et al.             Expires 30 January 2027                [Page 5]

Internet-Draft           Fossil Repository State               July 2026


   Cancel tag:  A tag (tagtype 0) that revokes a previously propagating
      or singleton tag of the same name at or below the check-in it is
      attached to.  A cancel tag is inactive by definition and stops the
      propagation of the tag it cancels.

   Branch:  The propagating tag of tagid 8 ("branch") and, by extension,
      the set of check-ins over which that tag is in effect.  A check-
      in's branch is determined by the innermost active branch tag that
      reaches it along the primary-parent/primary-child chain -- not
      across merge edges (Section 5).

   Leaf:  A check-in with no child on the same branch.  A check-in MAY
      have children on other branches and still be a leaf of its own
      branch; the canonical, normative definition is given in the Leaves
      section.

   Open leaf:  A leaf that does not have an active closed marker in
      effect.  Open leaves represent lines of development still eligible
      to receive new descendant check-ins.

   Closed marker:  The tag of tagid 9 ("closed").  When in effect
      (tagtype greater than zero) on a leaf, the closed marker indicates
      that the leaf is not expected to receive further descendants.

   Symbolic name:  A user-facing name for a branch, tag, or other
      referenceable point in the check-in DAG.  Symbolic names supplied
      to check-in targeting operations (for example, tag addition or
      cancellation) are stored internally with a "sym-" prefix; commands
      that accept a symbolic name MUST prepend "sym-" to the supplied
      name unless an explicit raw-name option is given, in which case
      the name is used unprefixed.

   Reserved tag-name prefix:  The prefixes "sym-", "wiki-", "tkt-", and
      "event-" are reserved and carry implementation-defined meaning
      distinct from ordinary user tags.  Of these, only "sym-" MAY be
      produced through ordinary user tagging operations; tags beginning
      with "wiki-", "tkt-", or "event-" MUST NOT be created directly by
      users, as they are reserved for internal use.

   Current-trunk:  The check-in obtained by resolving the repository's











Hipp, et al.             Expires 30 January 2027                [Page 6]

Internet-Draft           Fossil Repository State               July 2026


      default branch name -- the main-branch configuration setting,
      whose default value is trunk -- through symbolic-name resolution
      as specified in this document: the check-in carrying that branch's
      active tag with the greatest modification time (the sym-trunk tag
      when the default branch is trunk, its usual value).  It is used as
      the default target for operations that do not otherwise specify a
      branch or check-in, and is not necessarily an open leaf -- when a
      closed or non-leaf check-in carries the trunk tag with a later
      modification time, that check-in is the current-trunk.

   The tag, tag-type, and reserved-prefix rules defined above govern the
   T-card and P-card grammar for tag artifacts and parent references
   specified in [FossilArtifact].

3.  Check-in DAG Construction

   Each check-in manifest declares its ancestry through its P-card
   parent references, as defined in [FossilArtifact].  Following that
   grammar, a manifest MAY carry zero parent references (an initial
   check-in, whose P card is empty) or several.  A reader reconstructs
   the check-in DAG by materializing each manifest's parent references
   as parent/child linkage records.  It traverses those records rather
   than re-parsing manifest content on every query.

3.1.  Linkage record shape

   Each parent/child relationship is recorded as one row keyed on the
   pair (parent-id, child-id), together with:

   *  an indicator of whether the parent is the child's primary parent,

   *  the check-in's modification time, and

   *  a baseline id used for delta-manifest reconstruction (see
      [FossilArtifact]).

   The pair (parent-id, child-id) is unique in the linkage table: a
   given parent contributes at most one linkage row to a given child.  A
   manifest MAY name the same parent more than once, because
   [FossilArtifact] does not require P-card parent references to be
   sorted or deduplicated.  The linkage table records such repeats as
   the single row for that pair.









Hipp, et al.             Expires 30 January 2027                [Page 7]

Internet-Draft           Fossil Repository State               July 2026


3.2.  Primary parent vs. merge parents

   A check-in manifest's P-card list may name more than one parent hash.
   When more than one parent is present, exactly one linkage row per
   child carries the primary-parent indicator set.  This is the row
   corresponding to the first parent hash listed on the manifest's
   P-card.  All other parent hashes listed on the same P-card produce
   linkage rows with the indicator unset, marking them as merge parents.
   If a check-in names exactly one parent, that sole parent row carries
   the primary-parent indicator and is the primary parent.

   If a P-card repeats a parent hash, no second linkage row is created.
   The row retains the primary-parent indicator selected by that hash's
   first occurrence: a repeated first parent remains primary, and a
   repeated later parent remains a merge parent.

   A reader constructing the DAG MUST treat the single row per child
   with the primary-parent indicator set as that child's mainline
   ancestor for linear-history operations (e.g., "first parent"
   traversal, timeline mainline display).  Rows with the indicator unset
   identify additional ancestors contributed by a merge.  They MUST be
   included when the reader needs full ancestry (e.g., merge-base
   computation, ancestor-set membership tests), but they MUST NOT be
   treated as the mainline predecessor.

3.3.  Ancestry direction and traversal

   The linkage records form directed edges from parent to child.
   Because manifests reference their parents by hash and are immutable
   once stored, edge direction is fixed at write time and does not
   change under later operations.

   Two access patterns are supported by construction:

   *  given a parent id, find all children it links to (forward
      traversal, in commit order); and

   *  given a child id, find all its parents -- both primary and merge
      -- using the linkage index keyed on (child-id, parent-id).

   A reader walking ancestry from a given check-in toward its origins
   performs child-to-parent lookups using the (child-id, parent-id)
   index.  A reader walking descent from a given check-in toward its
   descendants performs parent-to-child lookups using the (parent-id,
   child-id) key.  Both directions read from the same set of linkage
   records -- there is no separate descendant-only or ancestor-only
   store.




Hipp, et al.             Expires 30 January 2027                [Page 8]

Internet-Draft           Fossil Repository State               July 2026


4.  Crosslink Processing

   Stored structural artifacts carry tag cards (T-cards) that are
   validated and routed into repository tag and branch state.  T-card
   syntax is defined normatively in [FossilArtifact].  The rules below
   cover parse-time validation and the crosslinking procedure that
   converts a validated card into applied tag/branch effects.

4.1.  T-card syntax validation

   Each T-card carries a tag name, an artifact UUID (or the literal
   self-tag marker), and an optional value.  The following constraints
   are enforced at parse time, before any crosslinking effect is
   applied:

   *  The tag name MUST begin with one of the prefix characters -, +, or
      *, denoting cancellation, singleton, and propagating tags
      respectively.  A tag name lacking one of these prefixes is a
      syntax error and parsing of the manifest fails.

   *  A tag name, after the prefix character is removed, MUST NOT
      consist entirely of hexadecimal digits at any length -- the tag-
      name validity rule specified by the T-card grammar in
      [FossilArtifact].  This keeps a tag name from being mistaken for
      an artifact hash reference; a card violating it is rejected at
      parse time.

   *  T-cards within a single manifest MUST appear in strictly ascending
      order, sorted first by tag name and then by UUID.  A T-card that
      is out of order relative to its predecessor is a syntax error.

   *  A T-card's UUID field may be the literal *, which designates a
      self-tag: the tag applies to the manifest currently being parsed
      rather than to some other artifact named by UUID.

   A manifest that fails any of the above checks MUST be rejected in its
   entirety; no partial tag state is derived from a syntactically
   invalid card set.

4.2.  Crosslink procedure

   Once a manifest's T-cards have been validated, the crosslink step
   (manifest_crosslink) converts them into tag/branch state:

   1.  *Target resolution.* For each T-card, the tag's target artifact
       (rid) is determined as follows.  If the card is a self-tag (the
       UUID field present and holding the literal *), the target is the
       artifact being crosslinked; otherwise the target is obtained by



Hipp, et al.             Expires 30 January 2027                [Page 9]

Internet-Draft           Fossil Repository State               July 2026


       resolving the card's UUID to an rid.  If that UUID names an
       artifact not yet stored, it resolves to a phantom placeholder --
       an rid that carries no content yet.  The tag effect is recorded
       against that placeholder and becomes visible in the derived views
       once the referenced artifact arrives and is crosslinked.
       Unresolved parent references in the check-in DAG (Section 3) are
       handled the same way: an absent parent hash yields a phantom
       placeholder rather than a fatal error.

   2.  *Applicability.* The T-card loop that applies tags (one
       tag_insert per card) runs only when the manifest's content type
       is control, manifest, or event.  For artifacts of other types the
       loop is skipped entirely and no T-card-derived tags are applied
       from that artifact.

   3.  *Cluster artifacts.* Artifacts of type cluster are handled
       outside the T-card loop: an implicit singleton cluster tag is
       applied directly via tag_insert, independent of any T-cards the
       artifact may carry.

   4.  *Propagation.* After the T-card loop completes, if the check-in
       being crosslinked has a resolved primary parent, the parent's tag
       state MUST be re-propagated to its descendants by invoking the
       propagation walk of Section 5.4 rooted at the parent.  This step
       keeps propagating tags consistent across the check-in graph
       whenever a new crosslink is applied beneath an existing parent.
       In addition, whenever a T-card in steps 1-3 applies a propagating
       (*), cancel (-), or singleton (+) tag to a resolved target, the
       propagation walk of Section 5.4 MUST be invoked rooted at that
       target.  Before propagation, a singleton is downgraded to a
       cancel/block as described in Section 5.3, so it affects its
       target directly and blocks same-named propagation through that
       target rather than propagating its own value to children.

   5.  *Idempotency.* Crosslinking MUST be idempotent in persisted
       linkage and event state: a repeat MUST NOT create an additional
       linkage or event record.  Fossil normally uses the presence of an
       mlink row for the manifest as its linkage-derivation guard.  A
       parentless manifest with no file-level mlink rows does not
       satisfy that guard on a repeat, so Fossil may execute the
       linkage/event derivation again; its REPLACE INTO event operation
       nevertheless leaves one event row for the check-in.  An
       implementation MAY use a different completion marker, but MUST
       preserve the same idempotent persisted result.  Tag application
       and propagation in steps 1-4 still execute.






Hipp, et al.             Expires 30 January 2027               [Page 10]

Internet-Draft           Fossil Repository State               July 2026


   6.  *Batching.* A sequence of crosslinks MAY be bracketed as a single
       batch, delimited by matching begin/end operations against
       temporary tables for the duration of one transaction.  On
       completion of the batch, any singleton reparent operations
       recorded during the batch are re-applied, ensuring that a parent
       changed mid-batch is reflected in the final reparented state
       rather than an intermediate one.  See [FossilSync] for how
       batches of crosslinks arise from a synchronisation exchange.

4.3.  Rejection conditions

   A T-card, and by extension the manifest containing it, MUST be
   rejected at parse time, before any crosslink effect, when:

   *  the tag name lacks a -, +, or * prefix;

   *  the tag name, after prefix removal, consists entirely of
      hexadecimal digits at any length (the tag-name validity rule of
      [FossilArtifact]); or

   *  the T-card sequence within the manifest is not strictly ascending
      by (name, uuid).

   No crosslink effect (tag_insert, propagation, or linkage derivation)
   is ever applied on behalf of a manifest that fails these checks.

4.4.  Unclassifiable artifacts

   A repository conforming to [FossilArtifact] may contain stored
   artifacts that a conformant parser declines to classify; the
   required-card rule of [FossilArtifact] Section 5.2 is one such case,
   and instances of it exist in the reference repository (see
   [FossilArtifact] Appendix C, _Wiki user card_).

   An artifact whose octets hash to the name under which it is stored
   has been stored and transmitted correctly, whether or not the reader
   classifies it.  State assembly MUST NOT treat a classification
   failure of such an artifact as a failure of the assembly.  The
   artifact is opaque to repository state: it contributes no check-in
   linkage, no tag effect, and no branch membership, and assembly
   proceeds as though it were an artifact of a non-participating type
   under Section 4.2 step 2.

   An artifact whose octets do *not* hash to the name under which it is
   stored is a distinct integrity failure.  This section does not apply
   to it, and an implementation MUST reject it.





Hipp, et al.             Expires 30 January 2027               [Page 11]

Internet-Draft           Fossil Repository State               July 2026


   An implementation SHOULD distinguish these two conditions in any
   diagnostic it reports.  Reporting a classification failure as
   repository damage misdirects the operator toward a defect that the
   verified hash has already excluded.

4.5.  Comment rendering of crosslink effects

   Human-readable comment generation over a crosslink recognises certain
   T-card shapes as compound edits rather than independent tag
   operations:

   *  A T-card whose name is the literal *branch is rendered as a "Move
      to branch" comment; the paired cancellation/addition of the
      corresponding sym- tag is suppressed from the rendered comment
      rather than shown as a separate entry.

   *  T-cards named -sym- and *sym- are rendered individually as "Cancel
      tag" and "Add propagating tag" respectively, except when paired
      with a *branch card, in which case they are folded silently into
      the same "Move to branch" edit rather than appearing twice.

   This rendering behaviour reflects presentation of crosslink effects
   and does not alter the underlying tag state produced by the procedure
   above.

4.6.  Serialisation of tag artifacts

   A tag or antitag originates as a control artifact containing exactly
   one T-card of the form T <c><prefix><name> <uuid> [value], where the
   leading character c is selected according to the tag's type.
   Producing such an artifact consists of writing this control artifact
   via content storage and then invoking the crosslink procedure defined
   above against it.  The newly stored artifact's T-card is then
   validated and applied using the same rules as any other manifest's
   T-cards.  See [FossilArtifact] for the control artifact's card
   grammar.  The byte-level delta encoding used when any artifact,
   including a tag artifact, is stored or transmitted as a content delta
   is defined in [FossilDelta].

5.  Tag Types and Propagation

5.1.  Tag Type Values

   Every tag reference is classified into exactly one of three types,
   encoded as a small integer and persisted per (artifact, tag) pair:

   *  *cancel* (value 0, surface syntax -): removes or blocks a tag's
      effect.



Hipp, et al.             Expires 30 January 2027               [Page 12]

Internet-Draft           Fossil Repository State               July 2026


   *  *singleton* (value 1, surface syntax +): applies the tag to the
      target artifact only.

   *  *propagating* (value 2, surface syntax *): applies the tag to the
      target artifact and carries it forward along the artifact's
      primary-child chain.

   The type is selected from the leading character of a T-card at
   manifest-parse time: - selects cancel (0), + selects singleton (1),
   and * selects propagating (2).  Any other leading character on a
   T-card MUST be treated as a fatal crosslink error and MUST NOT be
   accepted as a valid tag type.

5.2.  Storage Model

   Per-artifact tag state is recorded in a tagxref relation, keyed
   uniquely per (artifact, tag).  Each row carries:

   *  the resolved tagtype (0, 1, or 2);

   *  a source-artifact identifier (srcid), which is the artifact whose
      T-card directly applied the tag, or zero if the row instead
      reflects a value propagated in from an ancestor;

   *  an origin-artifact identifier (origid), naming the artifact that
      originally holds the propagating tag;

   *  the tag's value payload;

   *  a modification time (mtime); and

   *  the artifact the row describes (rid).  The UNIQUE constraint on
      (rid, tag) means a given artifact holds at most one live entry per
      tag at any time -- later writes supersede earlier ones for that
      pair.

5.3.  Direct Application Procedure

   When a tag is applied directly to an artifact:












Hipp, et al.             Expires 30 January 2027               [Page 13]

Internet-Draft           Fossil Repository State               July 2026


   1.  If an existing tagxref row for that (tag, artifact) pair already
       carries an mtime greater than or equal to the incoming tag's
       mtime, the write MUST be skipped.  Resolution between competing
       tag applications on the same artifact is by timestamp: a strictly
       later mtime supersedes an earlier one.  If two competing
       applications for the same (tag, artifact) pair carry exactly
       equal mtime, the >= skip rule makes the winner depend on write
       order, so that equal-mtime case is implementation-defined rather
       than crosslink-order-independent.

   2.  Otherwise the row is replaced with the raw tag type (0, 1, or 2)
       supplied by the T-card, srcid set to the artifact carrying the
       T-card, and both origid and rid set to the target artifact.

   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.

   4.  Before propagation is attempted, a singleton type (1) is
       downgraded to cancel (0).  A singleton (+) tag never propagates
       its own value to children and contributes no inherited value to a
       descendant.  When the tagged artifact is used as a propagation
       source, the cancel it becomes blocks a same-named tag from
       propagating through it to its descendants (Section 5.5).  A
       propagating type (2) passes through unchanged.

5.4.  Propagation Algorithm

   Propagation extends a tag's effect from an artifact to its
   descendants along primary-parent/primary-child links only.  It MUST
   NOT traverse merge (non-primary) parent/child links.  A propagating
   tag introduced anywhere else in the DAG topology (e.g. reached only
   via a merge edge) has no effect beyond the artifact it names.

   The propagation entry point accepts only two tag types as input:
   propagate (2) or cancel/block (0), and MUST reject any other value.
   This restriction is enforced by construction.  When propagation is
   driven from a parent artifact's full tag set, every singleton (1) tag
   on that parent is first downgraded to cancel (0), so that a singleton
   tag on a parent blocks propagation through that artifact rather than
   propagating past it.

   Given a starting artifact and a tag type of 0 or 2, propagation
   proceeds as follows:

   1.  Initialize a priority queue of candidate artifacts ordered by
       mtime, seeded with the starting artifact.




Hipp, et al.             Expires 30 January 2027               [Page 14]

Internet-Draft           Fossil Repository State               July 2026


   2.  For each artifact dequeued, enumerate its primary children
       (children reached via a primary, non-merge link).

   3.  For each primary child, determine whether it qualifies to receive
       the propagated value ("doit").  Propagation overwrites the
       child's existing same-tag entry only when that entry has srcid
       zero AND a modification time strictly earlier than the
       propagating tag's modification time.  Equivalently, the child's
       existing entry blocks the propagation whenever EITHER its srcid
       is nonzero OR its modification time is greater than or equal to
       the propagating tag's modification time.  A nonzero srcid marks
       the entry as an origin override -- a tag applied directly at that
       artifact (or at a nearer ancestor) rather than merely inherited
       -- and is never overwritten by propagation.  When the two
       modification times are exactly equal, the existing entry is
       retained: propagation does not overwrite it.  A child that
       currently has no same-tag entry qualifies for a propagating
       tagtype 2, but not for a cancel/block tagtype 0; cancel
       propagation therefore stops when there is no same-tag row to
       delete.

   4.  For a qualifying child, apply the effect according to tag type:

       *  If the type is propagate (2), replace the child's tagxref row
          with tagtype 2, srcid 0 (marking the row as propagated rather
          than directly applied), origid set to the origin artifact of
          the propagating tag, value set to the propagating tag's value,
          and mtime set to the propagating tag's mtime.  A propagated
          row therefore carries the origin tag's value and modification
          time, not the child's own.

       *  If the type is cancel (0), delete the child's tagxref row
          entirely and clear its value.

   5.  Each child that receives an update as a result of step 4 is
       enqueued for further propagation, so that the walk continues
       transitively down the primary-child chain.

   6.  The walk terminates when the queue is empty -- that is, when no
       further primary child qualifies to receive the tag under the rule
       in step 3.

5.5.  Blocking Conditions

   Propagation of a * tag along a primary-child chain is halted at any
   artifact where either of the following holds:





Hipp, et al.             Expires 30 January 2027               [Page 15]

Internet-Draft           Fossil Repository State               July 2026


   *  The artifact carries a directly-applied (srcid nonzero) same-tag
      entry -- an explicit re-tag at that point in the chain overrides
      the inbound propagation regardless of that entry's mtime.  (A
      nonzero srcid blocks propagation unconditionally; the mtime
      comparison in Section 5.4 applies only to inherited entries, whose
      srcid is zero.)

   *  The artifact carries a cancel (-) tag for the same tag name, which
      deletes any propagated tagxref row at that artifact and prevents
      the value from reaching that artifact's own primary children.

   A singleton (+) tag never itself propagates, but per the downgrade
   rule above it acts as a local cancel when present on an artifact
   being used as a propagation source, which has the same blocking
   effect as an explicit - tag at that point.

   This algorithm is the complete definition of the "propagation until
   blocked" behaviour referenced, but not specified, elsewhere in this
   document set.  [FossilArtifact] treats tag semantics at the manifest
   level and defers to this section for the traversal and blocking rules
   given here.  Interaction with synchronisation transport is described
   in [FossilSync].

6.  Branch Membership and Naming

6.1.  Branch of a check-in

   Every check-in belongs to exactly one branch.  A check-in's branch is
   the value of its active sym-<name> designation as recorded through
   the branch tag mechanism.  Implementations MUST determine branch
   membership by locating a tagxref row for TAG_BRANCH associated with
   the check-in whose value equals the branch name and whose tagtype
   indicates the tag is active (tagtype > 0).  A check-in with no such
   active tag row is attributed to the repository's default branch.  In
   a conformant repository the first check-in of every branch --
   including the initial check-in, which starts the default branch --
   carries that branch's propagating branch tag and the matching
   propagating sym-<branch> symbolic tag.  Every later check-in on the
   branch therefore inherits both an active branch tag and the
   resolvable symbolic name.  Creating a branch (including the default
   branch at repository initialization) therefore MUST create the
   sym-<branch> marker, without which the branch name would not resolve
   (Section 8).  The default-branch fallback therefore governs only the
   branch attributed to a check-in that carries no branch tag.  It does
   not by itself make such a check-in the resolvable tip (Section 8) or
   the open leaf (Section 7) of that branch, both of which operate on an
   active branch or symbolic tag.




Hipp, et al.             Expires 30 January 2027               [Page 16]

Internet-Draft           Fossil Repository State               July 2026


   The default branch name is the value of the main-branch configuration
   setting, which itself defaults to trunk when the setting is absent.
   A conforming implementation MUST read the configured value, and MUST
   fall back to trunk only when main-branch is unset.  It MUST NOT
   assume a fixed string in the presence of an explicit setting.  See
   [FossilArtifact] for the manifest card grammar that carries branch
   and symbolic-name tags.

6.2.  Branch creation

   Creating a new branch is performed by writing two propagating tag
   cards onto the branch-root manifest: a branch tag whose value is the
   new branch name, and a marker sym-<name> tag of the same name.  Both
   tags propagate forward to descendant check-ins per the propagating-
   tag rules in Section 5 of this document, which is what causes
   subsequent check-ins on that line of descent to inherit the branch's
   identity until superseded.

   Branch creation MUST cancel inherited propagating sym-* tags on the
   branch-root check-in by emitting corresponding - (cancel) cards,
   because symbolic tags are otherwise inherited from a check-in's
   parent.  This ensures a newly created branch does not carry forward
   the parent branch's symbolic names alongside its own.  Fossil's
   standalone branch new command performs a broader cleanup by
   cancelling every active sym-* tag visible on the branch artifact; the
   ordinary check-in path for branch creation cancels the inherited
   propagating symbolic tags.

   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.

   Fossil's branch-name reuse guard is narrower than the open-leaf
   predicate in Section 9: it looks for a propagating branch tag for the
   candidate name and excludes only a singleton closed marker.  The
   semantic open-branch report in Section 9 remains based on open leaves
   and active closed markers.

6.3.  Enumerating branches

   The set of known branches is derived, not stored directly.  It is
   computed by grouping tagxref rows where tag.tagname = 'branch' and
   tagtype > 0 by their value, yielding one branch entry per distinct
   value together with the maximum event.mtime and the count of check-
   ins observed for that value.  The representative check-in of a branch
   entry -- the one whose short identifier and open/closed status a
   listing reports -- is the group member with the greatest event.mtime,



Hipp, et al.             Expires 30 January 2027               [Page 17]

Internet-Draft           Fossil Repository State               July 2026


   with ties resolved as in symbolic-name resolution (Section 8).  This
   grouped view is the basis for branch listings; see [FossilSync] for
   how such derived views interact with synchronisation state.

   A branch's closed/open status in this derived listing is determined
   by checking for an active closed tag on a single representative row
   (rid) chosen for the group, not by inspecting every check-in
   belonging to the branch.  Implementations reproducing branch-list
   semantics should be aware that this representative-row check is a
   property of the grouping procedure and is not equivalent to asserting
   that all check-ins on the branch carry the closed tag.  This listing
   determination is deliberately distinct from the semantic branch-open
   predicate of Section 9, which reports a branch as open exactly when
   it has at least one open leaf (Section 7).  The two can disagree for
   a forked branch: when the representative (greatest-mtime) check-in
   carries an active closed tag while an older leaf on the same branch
   is still open, the listing reports the branch closed even though an
   open leaf exists.  Neither result is in error -- they answer
   different questions.  The listing column summarizes the state of the
   branch's most recent check-in, whereas the Section 9 predicate
   reports whether any open work remains.  This is the same recency-
   over-open/closed divergence documented for name resolution in
   Section 8.

   Branch listings support filtering by open/closed state, by private-
   branch status, by a name glob, and by merged/unmerged state.  The
   open/closed filter selects on the representative-row listing status
   defined above, not on the Section 9 open-leaf predicate: a branch is
   included in the closed set exactly when its representative row
   carries an active closed tag.  Because that representative row is the
   group's greatest-mtime member, with exact-mtime ties resolved as in
   Section 8, the listing open/closed status of a branch whose top
   members share an exact mtime is itself implementation-defined.  They
   also support ordering by name or by modification time, optionally
   reversed and optionally limited to the most-recently-modified
   branches.

6.4.  A note on branch current

   Some code paths that report the "current" branch of a checkout look
   up the tagxref.value for TAG_BRANCH on the checkout's rid without
   filtering on tagtype > 0, in contrast to the branch-of-check-in
   procedure defined above, which requires that filter.  This is a
   textual inconsistency between the two code paths rather than an
   intended alternate rule.  Implementations conforming to this
   specification MUST apply the active-tag (tagtype > 0) filter
   uniformly when determining branch membership, including when
   reporting the current branch of a checkout, so that branch identity



Hipp, et al.             Expires 30 January 2027               [Page 18]

Internet-Draft           Fossil Repository State               July 2026


   is computed consistently regardless of entry point.

7.  Leaves

7.1.  Definition

   A leaf is a check-in that has no child check-in on the same branch.
   This set is, in principle, computable at query time by joining the
   "plink" (parent-link) and "tagxref" (tag-cross-reference) relations,
   but implementations precompute it into a dedicated leaf table for
   performance.  The leaf table stores exactly one column, an integer
   row identifier, and carries no open/closed metadata of its own.
   Closed status is derived separately, as described below.

7.2.  Normative leaf test

   Implementations MUST determine that a check-in is a leaf if and only
   if no parent-link row naming it as parent has a corresponding child
   on the same branch.  This test MUST NOT be filtered by the primary/
   non-primary (merge) distinction.  A same-branch child reached only
   via a merge edge (a non-primary parent-link) still disqualifies the
   check-in as a leaf.  This is the canonical, normative leaf
   definition.

   Implementations MAY also expose a narrower, primary-only child count
   (counting only primary same-branch children) for other purposes.
   Where an implementation's documentation for such a helper describes
   it as testing whether a check-in "has zero children of any kind,"
   that description MUST NOT be read as restating the canonical leaf
   test above.  The canonical test in this section governs, and the
   narrower helper's own filtered query governs its own semantics.

7.3.  Maintenance algorithm

   The leaf table MUST be kept consistent with the canonical leaf test
   through the following procedures:















Hipp, et al.             Expires 30 January 2027               [Page 19]

Internet-Draft           Fossil Repository State               July 2026


   *  Full rebuild: implementations MUST support recomputing the entire
      leaf table by selecting every check-in and excluding those that
      have a same-branch child, with no primary/non-primary filter
      applied.  As an interoperability rule, 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.  Current Fossil's leaf_rebuild() query uses
      child ids from the parent-link table as its candidate set, so this
      requirement is a deliberate normalisation of the leaf definition
      rather than a literal transcription of that query.  This is a
      whole-table recomputation, so its cost scales with repository
      size.  Implementations SHOULD reserve it for explicit rebuild
      operations rather than routine updates.

   *  Incremental check: implementations MUST support recomputing leaf
      membership for a single check-in: if that check-in has a same-
      branch child (again, with no primary/non-primary filter), it MUST
      be removed from the leaf table; otherwise it MUST be inserted,
      tolerating the case where it is already present.

   *  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.  Establishing a new child can retroactively remove
      a parent's leaf status.

   *  Batch application: scheduled rechecks MUST be applied -- each
      queued check-in run through the incremental check -- and the
      schedule cleared, before the leaf table is considered consistent
      for subsequent queries.  This batch-apply step is the point at
      which a commit's leaf-table effects become visible.

7.4.  Open leaves and closed-marker semantics

   A leaf is open if and only if it is a member of the leaf table and
   does not carry an active "closed" tag.  Determining closed status
   MUST be done by testing for the existence of an active closed-tag
   cross-reference row (a tag-cross-reference entry whose tag type
   indicates an assignment, not a cancellation) on the check-in in
   question.  Implementations MUST expose both a form suitable for
   embedding as an existence-test expression, so that callers can negate
   it to select the open subset directly, and a form that evaluates the
   same test for a single, specific check-in.








Hipp, et al.             Expires 30 January 2027               [Page 20]

Internet-Draft           Fossil Repository State               July 2026


7.5.  Leaf-set enumeration and ambiguity

   Given the open/closed distinction above, implementations MUST
   enumerate the open leaves of a branch as follows: a check-in is an
   open leaf of its branch if it is a member of the leaf table, is not
   closed by the test above, and shares its branch with the check-in
   under consideration.  When determining whether a specific check-in is
   an "ambiguous" leaf -- i.e., whether propagating from it is
   underdetermined -- implementations MUST first confirm the check-in is
   itself a leaf, returning a negative result immediately if not.  They
   MUST then test for the existence of at least one _other_ open leaf
   sharing the same branch.  This ambiguity test necessarily queries the
   precomputed leaf table rather than recomputing from parent-links
   directly, so its correctness is contingent on the leaf table having
   been kept in sync via the incremental-check and rebuild procedures
   above.  A leaf table that has drifted out of sync will produce
   incorrect ambiguity results even though the underlying parent-link
   data is unchanged.

   Where an implementation reports leaf ambiguity to a user, it SHOULD
   enumerate every open leaf on the affected branch, ordered by
   modification time descending, presenting each leaf's identifying
   hash, timestamp, and internal row identifier.  It SHOULD also mark
   whichever leaf corresponds to the current checkout, so the user can
   distinguish the leaf they are already on from the other open leaves
   competing for the branch tip.

7.6.  Triggers for re-evaluation

   Leaf status MUST be re-evaluated whenever branch topology changes in
   a way that can add or remove a same-branch child relationship:

   *  Directly applying a branch tag to a check-in MUST trigger a re-
      evaluation of that check-in's own leaf status, since branch
      reassignment can change which children are "same-branch."

   *  When a branch tag propagates to descendants, each descendant that
      gains or loses the propagating tag MUST have its leaf status re-
      evaluated, since the same-branch relationship between it and its
      parent may change as a result.

   Both cases route through the deferred-scheduling and batch-
   application procedure described above rather than performing an
   immediate synchronous recomputation.







Hipp, et al.             Expires 30 January 2027               [Page 21]

Internet-Draft           Fossil Repository State               July 2026


7.7.  Relationship to name resolution

   Leaf enumeration, as defined and computed in this section, is a
   distinct operation from name resolution as specified in this
   document.  Name resolution answers "what check-in does this name
   refer to"; leaf enumeration answers "which check-ins on a branch
   currently have no same-branch descendant, and are any of them still
   open."  A name resolver MAY consult the leaf table as one candidate
   source when resolving a symbolic branch-tip reference.  However, the
   leaf table's consistency guarantees, its rebuild and incremental-
   check procedures, and its ambiguity semantics are defined
   independently of, and MUST NOT be conflated with, whatever precedence
   or fallback rules a name resolver applies among competing candidate
   check-ins.  See [FossilSync] for how leaf changes interact with
   synchronisation.

8.  Symbolic Name Resolution

8.1.  Scope

   This section specifies the rule and procedure by which a symbolic
   name (a branch name, tag name, or one of the reserved keywords below)
   is resolved to a single check-in.  Symbolic name resolution is a
   distinct operation from open-leaf enumeration: the two answer
   different questions and MUST NOT be conflated.  Name resolution asks
   "which check-in does this name currently refer to?" and answers with
   the check-in carrying the active instance of the corresponding tag
   that has the greatest event.mtime, as the Rule below specifies.
   Open-leaf enumeration asks "which check-ins are open and have no
   children on this branch?" and is an independent, closed/leaf-filtered
   query.  A name resolves to exactly one check-in even when that check-
   in is closed, is not a leaf, or both.

8.2.  Rule

   For a plain symbolic name N (a bare identifier that is not a hash, a
   reserved keyword, or a recognised prefixed form), resolution MUST
   proceed by finding the check-in carrying an active tag "sym-N"
   (tagtype greater than zero) with the maximum value of event.mtime
   among all check-ins carrying that active tag.  This is the terminal
   fallback of name resolution.  If none of the special forms described
   below match, this rule applies and MUST succeed or fail resolution
   outright.

   Two conditions explicitly MUST NOT hold for this selection:






Hipp, et al.             Expires 30 January 2027               [Page 22]

Internet-Draft           Fossil Repository State               July 2026


   *  Closed-ness of the candidate check-in MUST NOT filter or otherwise
      affect the selection.  A check-in tagged "closed" is exactly as
      eligible as one that is not.

   *  Leaf-ness of the candidate check-in MUST NOT filter or otherwise
      affect the selection.  A check-in that has children (i.e. is not a
      leaf) is exactly as eligible as one that is.

   The selection query joins only tag, tag-cross-reference, and event
   data; it carries no join against, or predicate over, leaf status, and
   no predicate over the "closed" tag.  An implementation MAY apply a
   fast path that considers only the thirty most recently modified
   candidates before a full scan over all candidates.  The fast path and
   the full scan MUST apply identical tagtype-and-maximum-mtime
   semantics and MUST yield the same result.  The fast path is an
   optimization only; it has no observable effect on which check-in is
   selected.

   If two or more check-ins carrying the same active sym-tag share an
   exactly equal, maximal event.mtime, the tie is broken by underlying
   row ordering of the query engine and is implementation-defined.  No
   normative tie-break rule is specified.

8.3.  Procedure

   Name resolution attempts the following forms, in order, before
   falling through to the plain-name rule above:

   1.  *rid:*DIGITS -- an undocumented form interpreted directly as a
       numeric RID, optionally validated against the artifact's recorded
       type.

   2.  *tag:*NAME -- forces literal tag resolution via the same maximum-
       active-tag-mtime rule described above, bypassing any attempt at
       hash interpretation.

   3.  Reserved keywords, matched by exact string:

       *  *tip* resolves to the check-in (type "ci") having the global
          maximum event.mtime, independent of any tag, whenever the
          requested type is unconstrained or is itself a check-in type.
          An exact-time tie is resolved by the underlying query-engine
          row order and is implementation-defined.

       *  *current* resolves to the RID of the open local checkout, and
          is available only when a local checkout is open.





Hipp, et al.             Expires 30 January 2027               [Page 23]

Internet-Draft           Fossil Repository State               July 2026


       *  *prev* / *previous* resolves to the open checkout's primary
          parent (the parent link marked primary).

       *  *next* resolves to a child of the open checkout, ordering
          candidates by primary-parent marking first and plink.mtime
          second, both descending.  If candidates remain tied, the
          underlying query-engine row order selects one and the result
          is implementation-defined.

       *  *ckout* (only when the requested type is "ci" or a checkout-
          inclusive type) resolves to the sentinel checkout RID rather
          than an ordinary artifact RID, and marks that a local checkout
          is in use.

   4.  *root:*BR -- resolves BR via this same procedure, then rewrites
       the result to the parent-branch check-in from which BR originally
       diverged (see the branch-walk rule below, requesting the "first
       ancestor off branch" variant).

   5.  *start:*BR -- resolves BR via this same procedure, then rewrites
       the result to the earliest check-in belonging to BR's branch (the
       branch-walk rule's "earliest still on branch" variant).

   6.  *merge-in:*BR -- resolves BR via this same procedure, then
       rewrites the result to the youngest ancestor on the branch from
       which BR diverged, i.e. the most recent merge-in point.

   7.  Date/time forms: an explicit *date:*TIMESTAMP prefix, a bare
       date-time literal, the deprecated *utc:* prefix, and a
       NAME*:*TIMESTAMP compound resolve to the most recent event whose
       mtime is less than or equal to an upper-bound timestamp derived
       as follows.  A date-only input is treated as YYYY-MM-DD
       23:59:59.999; a minute-precision input as YYYY-MM-DD
       HH:MM:59.999; and a whole-second input as YYYY-MM-DD
       HH:MM:SS.999.  A fractional-second input with one to three digits
       is copied, padded to three digits with trailing 9 digits, and,
       unless the resulting millisecond field is already .999, advanced
       by one millisecond before the mtime <= comparison (for example,
       .1 becomes .200, .12 becomes .130, .123 becomes .124, and .999
       stays .999).  The deprecated *local:*TIMESTAMP form uses the same
       mtime <= comparison but passes the timestamp text directly to the
       underlying julianday() parser, without that upper-bound
       expansion.  For a NAME*:*TIMESTAMP compound, candidates are
       limited to events whose check-ins carry an active sym-NAME tag
       (tagtype > 0); the greatest eligible event.mtime is selected,
       with an exact-time tie resolved by the underlying query-engine
       row order and therefore implementation-defined.




Hipp, et al.             Expires 30 January 2027               [Page 24]

Internet-Draft           Fossil Repository State               July 2026


   For portable resolution, a timestamp MUST use the absolute form YYYY-
   MM-DDTHH:MM:SS[.fraction]Z.  Fossil also admits compatibility forms:
   eight, twelve, or fourteen decimal digits, each optionally suffixed
   by Z, which expand respectively to a date, minute, or second upper
   bound; and hyphenated inputs beginning YYYY-MM-DD, whose remaining
   syntax is accepted only when the underlying SQLite date/time parser
   accepts it.  The date: form passes such an input through that parser
   after compact-form expansion; a bare date-time requires the
   hyphenated prefix; and NAME*:*TIMESTAMP accepts either form.  An
   omitted Z is interpreted through Fossil's configured display-time
   mode: as UTC when UTC display is selected, otherwise as local time
   converted to UTC.  Implementations that support broader SQLite-
   specific syntax MUST NOT treat it as portable across conforming
   implementations.

   After date/time resolution, hexadecimal strings and plain names are
   tried.  A hexadecimal string of length four up to the maximum
   artifact-hash length is tried as an artifact-hash prefix, matched
   against artifact content hashes by pattern.  If more than one
   artifact hash matches the prefix, resolution MUST reject the name as
   ambiguous, signaled by returning an RID of -1; it MUST NOT silently
   pick one of the matches.  If no hexadecimal-prefix case applies, the
   plain-name fallback described in the Rule above is used.

   If none of the preceding forms match and the name ends in exactly one
   trailing punctuation character (for example, punctuation incidentally
   captured when a name is lifted from surrounding prose or a URL),
   resolution MUST retry recursively with that trailing character
   stripped.

   Independently of the above dispatch, when the caller's requested type
   begins with "b" (a branch-type request), the effective type is
   rewritten to "ci".  After the underlying name resolves by the
   ordinary procedure, the result RID is post-processed through the
   branch-walk rule's "earliest still on branch" variant, so that a
   branch-type request yields the branch's first check-in rather than
   the check-in the tag most recently pointed to.

8.4.  Branch-walk rule

   The root:/start:/merge-in: forms, and the branch-type post-processing
   step, all depend on a single walk: starting from a resolved check-in,
   follow the primary-parent link (the parent link marked primary)
   recursively toward the repository root, tracking at each step whether
   the ancestor still carries the same active branch tag as the starting
   check-in.  Depending on which variant is requested, the walk returns
   either the earliest ancestor that still carries that same active
   branch tag ("earliest still on branch"), or the first ancestor



Hipp, et al.             Expires 30 January 2027               [Page 25]

Internet-Draft           Fossil Repository State               July 2026


   encountered that no longer carries it ("first ancestor off branch").
   This walk is independent of the closed/leaf-neutral selection rule
   above: it is a parentage traversal, not a repeat of tag-and-mtime
   selection, though its starting point is ordinarily produced by that
   selection.

8.5.  Distinguishing name resolution from open-leaf enumeration

   Because closed-ness and leaf-ness are excluded from the selection
   predicate, name resolution for a branch or tag name can, and in
   general will, return a check-in that open-leaf enumeration would
   never surface.  Consider a branch BR with an open, childless check-in
   C1 recorded at mtime T1, and a later check-in C2 recorded at mtime T2
   greater than T1, where C2 is closed (tagged "closed") and also
   carries the active sym-BR tag.  The active sym-BR tag was reassigned
   onto C2, for instance, by a subsequent commit or retag operation.
   Resolving the name "BR" MUST return C2: it is the check-in carrying
   the active sym-BR tag with the maximum event.mtime, and its closed
   state and non-leaf status (if it has children) are immaterial to
   selection.  An open-leaf enumeration over the same branch, by
   contrast, would exclude C2 on the basis of its closed tag (or its
   non-leaf status) and would instead surface C1 (or some other open,
   childless check-in) as the branch's leaf.  The two operations are
   answering different questions and MUST be treated as such by callers.
   Resolving a name to "the check-in it refers to" is not equivalent to,
   and MUST NOT be substituted for, asking "what are the open leaves of
   this branch."  Tag artifacts and the tag data model referenced
   throughout this section are defined in [FossilArtifact].

9.  Current Trunk and Forks

   The current trunk is determined by resolving the repository's
   configured default branch name through the symbolic-name resolution
   procedure defined in this document.  That procedure applies uniformly
   to any branch name, including the default branch, and does not treat
   the default branch as a special case.

   Current-trunk therefore has a repository-local input that the other
   views do not.  The branch name to resolve comes from the main-branch
   configuration setting (Section 6), which is a local value: it is not
   an artifact and is not synchronised between peers.  Given that name,
   resolution to a specific check-in is entirely artifact-derived.  Two
   repositories that hold the identical set of artifacts but are
   configured with different main-branch settings can therefore resolve
   different current-trunk check-ins.  Current-trunk is thus not a pure
   function of the artifact set alone; it is a function of the artifact
   set together with the local main-branch setting.




Hipp, et al.             Expires 30 January 2027               [Page 26]

Internet-Draft           Fossil Repository State               July 2026


   A branch MAY have more than one leaf check-in.  A branch with more
   than one leaf is, for the purposes of this specification, forked.
   Symbolic-name resolution against a forked branch does not refuse and
   does not raise an ambiguity error.  The resolution procedure selects
   exactly one check-in among the candidates by maximum mtime
   (modification time), and that check-in is returned as the resolution
   of the branch name.

   Independent of symbolic-name resolution and of the branch-listing
   closed column (Section 6.3), an implementation determines whether a
   branch is currently open by locating its open leaf.  A check-in
   qualifies as a branch's open leaf, and implementations MUST treat it
   as such, only when all of the following hold:

   *  it carries an active branch tag (tag type greater than zero) whose
      value equals the branch name;

   *  it is present among the repository's current leaves; and

   *  it does not carry an active closed tag (tag type greater than
      zero).  A branch lacking any check-in meeting these three
      conditions has no open leaf.  Branch-status reporting reflects
      this determination directly.  A report of a branch as open,
      including its open-as-of date and a short check-in identifier, is
      produced exactly when a branch has an open leaf by the above test;
      otherwise the branch is reported as not open.  When a branch has
      more than one open leaf, any report that must name a single open
      leaf -- supplying one open-as-of date and one short check-in
      identifier -- MUST select the open leaf with the greatest mtime.
      An exact-mtime tie among open leaves is resolved as in symbolic-
      name resolution (Section 8) and is therefore implementation-
      defined.

   A branch's merge target is likewise a single-valued property rather
   than an aggregate over the branch's leaves.  It is obtained by
   following, from the branch's representative check-in, at most one
   parent-child (plink) edge to a child check-in, and reading that
   child's active branch-tag value.  A branch therefore has at most one
   merge target under this procedure, never a set of targets, regardless
   of how many leaves the branch itself has.  If the representative
   check-in has more than one child, this document does not define which
   child's branch-tag value is read.  If an implementation's underlying
   query can return those children in more than one order, the selected
   child's branch-tag value is implementation-defined.  This
   specification therefore records the multi-child case as
   implementation-defined rather than mandating a cross-implementation
   rule.




Hipp, et al.             Expires 30 January 2027               [Page 27]

Internet-Draft           Fossil Repository State               July 2026


   When two or more candidate check-ins for a branch (or for trunk
   specifically) share the identical maximum mtime, this specification
   does not define which one is selected.  If an implementation's
   underlying storage query can return tied rows in more than one order,
   the selected check-in is implementation-defined.  A conforming
   implementation MAY return any of the tied candidates, callers MUST
   NOT rely on which candidate is returned, and two conforming
   implementations MAY differ.  This specification deliberately does not
   mandate a cross-implementation tie-break rule (such as selecting by
   artifact hash or check-in identifier).  An exact tie is possible only
   when two check-ins carry identical modification times to full
   precision.

10.  Worked Example: Fork with a Closed Leaf

   These examples show two repository states in which open-leaf
   enumeration and symbolic-name resolution diverge over the same graph.
   In both states, closed filters the default leaves view but does not
   change which check-in whatis and update resolve for trunk.

10.1.  Fork at trunk, one leaf closed

   In the first repository state, trunk forks after c4 trunk, producing
   two trunk leaves and one feature-branch leaf.  Here leaves lists the
   open trunk leaf [4167aef42f] and the open feature leaf [210d9276e4],
   while omitting the closed trunk sibling [7aa867c048]. leaves --all
   then restores [7aa867c048] alongside the same two open leaves.  The
   closed trunk check-in therefore remains a leaf of the DAG and remains
   on trunk; it is omitted only from the default open-leaf view.

   The same repository state yields one scalar trunk resolution. whatis
   trunk reports artifact
   4167aef42fd2eb399b05ae64293a058e86435036f26c7a9262c26454156cda2e, and
   update trunk checks out that same check-in.  In this state, closed-
   ness changes leaf enumeration but does not affect branch-name
   resolution or update targeting.

10.2.  Edge case: a closed leaf newer than the open leaf

   In the second repository state, trunk again forks, but now the
   surviving open leaf is older than its closed sibling.  Here leaves
   lists only the older open leaf [b05cbea82b]. leaves --all lists both
   trunk leaves and places the closed newer leaf [ee3a7eed71] ahead of
   the older open leaf [b05cbea82b].

   whatis trunk reports artifact
   ee3a7eed71fb15bd4f468bfa71b8b79a47ad97678a2a226dadaaa23904dafc21,
   even though the default open-leaf view omitted that check-in. update



Hipp, et al.             Expires 30 January 2027               [Page 28]

Internet-Draft           Fossil Repository State               July 2026


   trunk likewise moves the checkout from
   b05cbea82bf9e747754eae8cc0eb7c070f87a1f1069c429a990f6ecc92e24580 to
   ee3a7eed71fb15bd4f468bfa71b8b79a47ad97678a2a226dadaaa23904dafc21.  In
   this state, leaves identifies the only open leaf, but it does not
   predict the target that trunk name resolution selects.

10.3.  Generalization

   Taken together, these states illustrate the distinction specified in
   Section 8.3: open-leaf enumeration and symbolic-name resolution
   answer different questions.  Tooling that needs the target of update
   or whatis MUST resolve the branch or tag name directly instead of
   inferring it from leaves output.

11.  Security Considerations

   The crosslink state described in this document is a derived view.  It
   is computed locally from received artifacts and is never itself
   transmitted, negotiated, or authenticated as an independent object.
   Tag and parent claims embedded in an artifact are asserted by the
   peer that produced the artifact.  A recipient's crosslink state is
   therefore only as trustworthy as the set of artifacts it has
   admitted.

   Implementations MUST treat crosslink state as derived data with no
   independent trust basis.  A recipient MUST NOT grant an artifact's
   tag or parent claims any weight beyond what the admission procedure
   defined in [FossilArtifact] and [FossilSync] has already established
   for that artifact.  In particular, a crosslink computed from an
   artifact MUST be rejected or discarded whenever the artifact itself
   would be rejected under those admission rules (for example, malformed
   structure, failed integrity checks, or provenance the recipient does
   not accept).

   Because the trust boundary sits entirely at artifact admission, this
   document defines no additional authentication, integrity, or
   authorization mechanism for crosslink state itself.  Any weakness in
   the admission checks specified in [FossilArtifact], [FossilSync], or
   [FossilDelta] propagates directly into the derived crosslink view.
   Conversely, correct enforcement of those checks is sufficient to
   bound the trust an implementation extends to the views described
   here.  Implementations SHOULD recompute crosslink state from the
   currently admitted artifact set rather than caching it across changes
   to that set.  This way, a later rejection or retraction of an
   artifact is reflected in the derived view without requiring separate
   revocation logic.





Hipp, et al.             Expires 30 January 2027               [Page 29]

Internet-Draft           Fossil Repository State               July 2026


12.  IANA Considerations

   This document has no IANA actions.

13.  Normative References

   [FossilArtifact]
              Hipp, D. R., Ed., Beal, S., and D. Shearer, "The Fossil
              Distributed Version Control System: Artifact Format", Work
              in Progress, Internet-Draft, draft-fossil-artifact-format-
              00, 2026, <https://datatracker.ietf.org/doc/html/draft-
              fossil-artifact-format-00>.

   [FossilDelta]
              Hipp, D. R., Ed., Beal, S., and D. Shearer, "The Fossil
              Delta Format", Work in Progress, Internet-Draft, draft-
              fossil-delta-format-00, 2026,
              <https://datatracker.ietf.org/doc/html/draft-fossil-delta-
              format-00>.

   [FossilSync]
              Hipp, D. R., Ed., Beal, S., and D. Shearer, "The Fossil
              Distributed Version Control System: Sync Protocol", Work
              in Progress, Internet-Draft, draft-fossil-sync-protocol-
              00, 2026, <https://datatracker.ietf.org/doc/html/draft-
              fossil-sync-protocol-00>.

   [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
              Requirement Levels", BCP 14, RFC 2119,
              DOI 10.17487/RFC2119, March 1997,
              <https://www.rfc-editor.org/rfc/rfc2119>.

   [RFC8174]  Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
              2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
              May 2017, <https://www.rfc-editor.org/rfc/rfc8174>.

Authors' Addresses

   D. Richard Hipp (editor)
   Fossil


   Stephan Beal
   libfossil


   Dan Shearer
   Email: dan@shearer.org



Hipp, et al.             Expires 30 January 2027               [Page 30]

Internet-Draft           Fossil Repository State               July 2026


   URI:   https://shearer.org


















































Hipp, et al.             Expires 30 January 2027               [Page 31]