Skip to content
zit Artifact [1c24d8f1f7]
Login

Artifact [1c24d8f1f7]

Artifact 1c24d8f1f754e81779a5cf2de081bac8b40a3b9ec63787f46cd59770dfd1d2a4:


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sub-Manifests: An Illustrated Guide to draft-fossil-submanifests-00</title>
<meta name="description" content="A technical, diagram-driven walkthrough of the Fossil sub-manifests specification: the S card, the partition function, tree reconstruction, sync semantics, and deployment gates.">
<link rel="stylesheet" href="assets/katex.min.css">
<link rel="stylesheet" href="style.css">
</head>
<body>

<a class="skip-link" href="#ch1">Skip to the guide</a>

<header class="hero">
  <p class="eyebrow">An illustrated companion to draft-fossil-submanifests-00</p>
  <h1>Sub-Manifests</h1>
  <p class="subtitle">A check-in manifest that carries its file set as a tree of shared, content-addressed slices instead of one flat list. What the partition function computes, what the tree walk reconstructs, and where deployment breaks.</p>
</header>

<aside class="status-note" aria-label="Document status">
  <p><strong>This page is an explanation, not the specification.</strong> When exact wording matters, follow <a href="https://fossil.craftdesign.group/zit/doc/trunk/docs/rfcs/draft-fossil-submanifests-00.txt">draft-fossil-submanifests-00</a>. The draft is experimental and provisional. It develops the idea in Fossil's 2010 <a href="https://fossil-scm.org/home/technote/be8f2f3447ef2ea3344f8058b6733aa08c08336f">“Scaling Fossil Up”</a> note into a precise format, construction, reader, and deployment contract.</p>
</aside>

<aside class="vocabulary" aria-labelledby="vocabulary-title">
  <h2 id="vocabulary-title">Working vocabulary</h2>
  <dl>
    <div><dt>Artifact</dt><dd>An immutable byte sequence named by its content hash.</dd></div>
    <div><dt>Manifest / check-in</dt><dd>A <code>MANIFEST</code> artifact records one project snapshot; the check-in is that snapshot.</dd></div>
    <div><dt><code>F</code> / <code>S</code></dt><dd><code>F</code> records one file entry. <code>S</code> names a sub-manifest holding a sorted slice of entries.</dd></div>
    <div><dt><code>B</code> / <code>R</code> / <code>Z</code></dt><dd><code>B</code> names a delta-manifest baseline. <code>R</code> checks the complete file set. <code>Z</code> checks one artifact's stripped card stream.</dd></div>
    <div><dt>Crosslink / rebuild</dt><dd>Crosslink derives repository state from artifacts. Rebuild discards and recomputes that derived state.</dd></div>
    <div><dt>Canonical order</dt><dd>The required card order and decoded-filename order that every conforming writer must reproduce.</dd></div>
  </dl>
</aside>

<nav class="toc">
  <h2>Contents</h2>
  <ol>
    <li><a href="#ch1"><span class="toc-title">Why flat manifests stop scaling</span><span class="toc-desc">Linear cardinality, the 2010 note, and what delta manifests do not fix</span></a></li>
    <li><a href="#ch2"><span class="toc-title">The S card and the SUBMANIFEST type</span><span class="toc-desc">Grammar, arity, occurrence class, ordering, and type reassignment</span></a></li>
    <li><a href="#ch3"><span class="toc-title">The partition function</span><span class="toc-desc">zbits, k, MIN/MAX, and the canonical construction</span></a></li>
    <li><a href="#ch4"><span class="toc-title">Tree reconstruction</span><span class="toc-desc">The walk, single-name lookup, delta composition, and memoization</span></a></li>
    <li><a href="#ch5"><span class="toc-title">Sync, phantoms, and the flag day</span><span class="toc-desc">Version gates, deferred processing, and the three deployment layers</span></a></li>
    <li><a href="#ch6"><span class="toc-title">Implementation traps</span><span class="toc-desc">The four structural failure modes this spec exists to prevent</span></a></li>
  </ol>
</nav>

<!-- ================= CHAPTER 1 ================= -->
<section class="chapter" id="ch1">
  <div class="chapter-num">1 — Motivation</div>
  <h2>Why flat manifests stop scaling</h2>

  <p>A Fossil baseline manifest carries one <code>F</code> card per file in the check-in — not per <em>changed</em> file. Manifest size is therefore linear in tree width. Any operation that reconstructs a check-in's complete file set performs work proportional to its file count; checkout, diff, crosslink, rebuild, annotate, and commit all need that enumeration.</p>

  <p>The 2010 design note measured this on NetBSD's pkgsrc history: more than <strong>60,000 files</strong>, manifests around <strong>5 MB</strong>, and an eight-hour rebuild dominated by parsing roughly 190,000 manifests. The draft records a later measurement at roughly <strong>103,000 files</strong> and <strong>10.2 MB per manifest</strong>. The precise numbers changed; the shape did not. Work grew with the number of files in every check-in rather than with the handful of files changed.</p>

  <p>The note proposed the remedy the draft makes precise: a tree of sub-manifests linked by a new <code>S</code> card. It supplied the direction; the draft fixes the card grammar, deterministic partition, tree walk, sync behavior, and deployment gates needed for independent implementations to agree. In a one-level tree a typical change rewrites the check-in manifest and one leaf; in a deeper tree it also rewrites the interior nodes on the path between them.</p>

  <p>Figures 1–3 include compact card-order context. <code>D</code> and the final <code>Z</code> are required on these check-in manifests. <code>R</code> and <code>U</code> are shown to orient the file-set run within a representative manifest, not to imply that either card is universally required.</p>

  <p>The complete card-stack diagrams use one convention throughout: a blue-gray header names the artifact type and, when useful, its abbreviated hash; every bordered card row represents one LF-terminated card; and rust marks a changed or exceptional row rather than a different card class. Compact algorithm diagrams may summarize a run, but never place two individual cards on the same text line.</p>

  <figure class="diagram">
<div class="diagram-body">
<svg role="img" aria-labelledby="fig-1-caption" xmlns='http://www.w3.org/2000/svg' class="pikchr" width="439" height="426" viewBox="0 0 536.625 520.013">
<path d="M28,113L251,113A2 2 0 0 0 253 110L253,46A2 2 0 0 0 251 44L28,44A2 2 0 0 0 26 46L26,110A2 2 0 0 0 28 113Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="140" y="67" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">BASELINE MANIFEST</text>
<text x="140" y="89" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">91af...c302</text>
<path d="M285,113L507,113A2 2 0 0 0 510 110L510,46A2 2 0 0 0 507 44L285,44A2 2 0 0 0 282 46L282,110A2 2 0 0 0 285 113Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="396" y="67" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">BASELINE MANIFEST</text>
<text x="396" y="89" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">b74e...19d0</text>
<text x="140" y="12" text-anchor="middle" fill="rgb(83,97,106)" font-size="108%" dominant-baseline="central">CHECK-IN 1</text>
<text x="396" y="12" text-anchor="middle" fill="rgb(83,97,106)" font-size="108%" dominant-baseline="central">CHECK-IN 2</text>
<path d="M28,153L251,153A2 2 0 0 0 253 150L253,115A2 2 0 0 0 251 113L28,113A2 2 0 0 0 26 115L26,150A2 2 0 0 0 28 153Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="140" y="133" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">D  2026-09-13...</text>
<path d="M285,153L507,153A2 2 0 0 0 510 150L510,115A2 2 0 0 0 507 113L285,113A2 2 0 0 0 282 115L282,150A2 2 0 0 0 285 153Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="396" y="133" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">D  2026-09-14...</text>
<path d="M28,193L251,193A2 2 0 0 0 253 191L253,156A2 2 0 0 0 251 153L28,153A2 2 0 0 0 26 156L26,191A2 2 0 0 0 28 193Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="140" y="173" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">F README  a1...</text>
<path d="M28,234L251,234A2 2 0 0 0 253 231L253,196A2 2 0 0 0 251 193L28,193A2 2 0 0 0 26 196L26,231A2 2 0 0 0 28 234Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="140" y="213" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">F guide   b2...</text>
<path d="M28,274L251,274A2 2 0 0 0 253 271L253,236A2 2 0 0 0 251 234L28,234A2 2 0 0 0 26 236L26,271A2 2 0 0 0 28 274Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="140" y="254" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">F main    c3...</text>
<path d="M28,314L251,314A2 2 0 0 0 253 312L253,276A2 2 0 0 0 251 274L28,274A2 2 0 0 0 26 276L26,312A2 2 0 0 0 28 314Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="140" y="294" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">F tests   d4...</text>
<path d="M285,193L507,193A2 2 0 0 0 510 191L510,156A2 2 0 0 0 507 153L285,153A2 2 0 0 0 282 156L282,191A2 2 0 0 0 285 193Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="396" y="173" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">F README  a1...</text>
<path d="M285,234L507,234A2 2 0 0 0 510 231L510,196A2 2 0 0 0 507 193L285,193A2 2 0 0 0 282 196L282,231A2 2 0 0 0 285 234Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="396" y="213" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">F guide   b2...</text>
<path d="M285,274L507,274A2 2 0 0 0 510 271L510,236A2 2 0 0 0 507 234L285,234A2 2 0 0 0 282 236L282,271A2 2 0 0 0 285 274Z"  style="fill:rgb(244,232,226);stroke-width:1.584;stroke:rgb(139,74,50);" />
<text x="396" y="254" text-anchor="middle" fill="rgb(139,74,50)" font-size="108%" dominant-baseline="central">F main    e9...</text>
<path d="M285,314L507,314A2 2 0 0 0 510 312L510,276A2 2 0 0 0 507 274L285,274A2 2 0 0 0 282 276L282,312A2 2 0 0 0 285 314Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="396" y="294" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">F tests   d4...</text>
<path d="M28,352L251,352A2 2 0 0 0 253 349L253,317A2 2 0 0 0 251 314L28,314A2 2 0 0 0 26 317L26,349A2 2 0 0 0 28 352Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="140" y="333" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">R  ...</text>
<path d="M28,389L251,389A2 2 0 0 0 253 387L253,354A2 2 0 0 0 251 352L28,352A2 2 0 0 0 26 354L26,387A2 2 0 0 0 28 389Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="140" y="370" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">U  alice</text>
<path d="M28,427L251,427A2 2 0 0 0 253 424L253,392A2 2 0 0 0 251 389L28,389A2 2 0 0 0 26 392L26,424A2 2 0 0 0 28 427Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="140" y="408" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">Z  ...</text>
<path d="M285,352L507,352A2 2 0 0 0 510 349L510,317A2 2 0 0 0 507 314L285,314A2 2 0 0 0 282 317L282,349A2 2 0 0 0 285 352Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="396" y="333" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">R  ...</text>
<path d="M285,389L507,389A2 2 0 0 0 510 387L510,354A2 2 0 0 0 507 352L285,352A2 2 0 0 0 282 354L282,387A2 2 0 0 0 285 389Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="396" y="370" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">U  alice</text>
<path d="M285,427L507,427A2 2 0 0 0 510 424L510,392A2 2 0 0 0 507 389L285,389A2 2 0 0 0 282 392L282,424A2 2 0 0 0 285 427Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="396" y="408" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">Z  ...</text>
<path d="M253,173L282,173"  style="fill:none;stroke-width:0.864;stroke:rgb(139,148,154);stroke-dasharray:7.2,7.2;" />
<path d="M253,213L282,213"  style="fill:none;stroke-width:0.864;stroke:rgb(139,148,154);stroke-dasharray:7.2,7.2;" />
<polygon points="282,254 270,258 270,249" style="fill:rgb(139,74,50)"/>
<path d="M253,254L276,254"  style="fill:none;stroke-width:1.584;stroke:rgb(139,74,50);" />
<path d="M253,294L282,294"  style="fill:none;stroke-width:0.864;stroke:rgb(139,148,154);stroke-dasharray:7.2,7.2;" />
<text x="268" y="461" text-anchor="middle" fill="rgb(83,97,106)" font-size="108%" dominant-baseline="central">D -&gt; complete F run -&gt; R -&gt; U -&gt; Z (final)</text>
<text x="268" y="507" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">main changes; every F card is stored + parsed again: O(N)</text>
</svg>
</div>
<figcaption id="fig-1-caption">Figure 1. A flat baseline check-in places its complete F run after D and before representative R and U context; Z remains final, and every F card repeats at the next check-in.</figcaption>
<details class="pikchr-source"><summary>Pikchr source</summary><pre><code>scale = 0.82
fill = 0xffffff
color = 0x29333a
thickness = 0.006
fontscale = 1.08
boxrad = 0.018

# Two complete baseline check-in manifests. D precedes the F run; Z is final.
H1: box "BASELINE MANIFEST" "91af...c302" wid 1.58 ht 0.48 fill 0xe7eef2 at 0,0
H2: box "BASELINE MANIFEST" "b74e...19d0" wid 1.58 ht 0.48 fill 0xe7eef2 at H1.x + 1.78,H1.y
text "CHECK-IN 1" at H1.x,H1.n.y + 0.22 color 0x53616a
text "CHECK-IN 2" at H2.x,H2.n.y + 0.22 color 0x53616a
D1: box "D  2026-09-13..." wid 1.58 ht 0.28 fill 0xf3f4f3 at H1.x,H1.s.y - 0.14
D2: box "D  2026-09-14..." same at H2.x,H2.s.y - 0.14

A1: box "F README  a1..." wid 1.58 ht 0.28 fill 0xf7f8f7 at H1.x,D1.s.y - 0.14
A2: box "F guide   b2..." same at H1.x,A1.s.y - 0.14
A3: box "F main    c3..." same at H1.x,A2.s.y - 0.14
A4: box "F tests   d4..." same at H1.x,A3.s.y - 0.14
B1: box "F README  a1..." wid 1.58 ht 0.28 fill 0xf7f8f7 at H2.x,D2.s.y - 0.14
B2: box "F guide   b2..." same at H2.x,B1.s.y - 0.14
B3: box "F main    e9..." same fill 0xf4e8e2 color 0x8b4a32 thickness 0.011 at H2.x,B2.s.y - 0.14
B4: box "F tests   d4..." same fill 0xf7f8f7 color 0x29333a thickness 0.006 at H2.x,B3.s.y - 0.14

R1: box "R  ..." wid 1.58 ht 0.26 fill 0xf3f4f3 at H1.x,A4.s.y - 0.13
U1: box "U  alice" same at H1.x,R1.s.y - 0.13
Z1: box "Z  ..." same fill 0xe7eef2 at H1.x,U1.s.y - 0.13
R2: box "R  ..." wid 1.58 ht 0.26 fill 0xf3f4f3 at H2.x,B4.s.y - 0.13
U2: box "U  alice" same at H2.x,R2.s.y - 0.13
Z2: box "Z  ..." same fill 0xe7eef2 at H2.x,U2.s.y - 0.13

line dashed from A1.e to B1.w color 0x8b949a
line dashed from A2.e to B2.w color 0x8b949a
arrow from A3.e to B3.w color 0x8b4a32 thickness 0.011
line dashed from A4.e to B4.w color 0x8b949a
text "D -&gt; complete F run -&gt; R -&gt; U -&gt; Z (final)" at (H1.x + H2.x)/2,Z1.s.y - 0.24 color 0x53616a
text "main changes; every F card is stored + parsed again: O(N)" at (H1.x + H2.x)/2,Z1.s.y - 0.56 color 0x29333a</code></pre></details>
</figure>

  <h3>What classic delta manifests already do</h3>

  <p>Older Fossil readers already understand two check-in encodings. A <em>baseline manifest</em> carries the complete sorted <code>F</code> run. A <em>delta manifest</em> carries a <code>B</code> card naming a baseline plus a sorted <code>F</code> run containing only additions, changes, and hashless deletions. The established merge produces the same effective complete file set.</p>

  <figure class="diagram">
<div class="diagram-body">
<svg role="img" aria-labelledby="fig-2-caption" xmlns='http://www.w3.org/2000/svg' class="pikchr" width="441" height="666" viewBox="0 0 538.56 813.744">
<text x="118" y="12" text-anchor="middle" fill="rgb(83,97,106)" font-size="110%" dominant-baseline="central">CHECK-IN 1</text>
<path d="M4,113L232,113A2 2 0 0 0 234 110L234,46A2 2 0 0 0 232 44L4,44A2 2 0 0 0 1 46L1,110A2 2 0 0 0 4 113Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="118" y="67" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">BASELINE MANIFEST</text>
<text x="118" y="89" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">91af...c302</text>
<path d="M11,152L224,152A2 2 0 0 0 227 149L227,115A2 2 0 0 0 224 113L11,113A2 2 0 0 0 8 115L8,149A2 2 0 0 0 11 152Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="118" y="132" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">D  2026-09-13...</text>
<path d="M11,191L224,191A2 2 0 0 0 227 188L227,154A2 2 0 0 0 224 152L11,152A2 2 0 0 0 8 154L8,188A2 2 0 0 0 11 191Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="118" y="171" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">F alpha    a1...</text>
<path d="M11,229L224,229A2 2 0 0 0 227 227L227,193A2 2 0 0 0 224 191L11,191A2 2 0 0 0 8 193L8,227A2 2 0 0 0 11 229Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="118" y="210" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">F beta     b2...</text>
<path d="M11,268L224,268A2 2 0 0 0 227 266L227,232A2 2 0 0 0 224 229L11,229A2 2 0 0 0 8 232L8,266A2 2 0 0 0 11 268Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="118" y="249" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">F delta    d4...</text>
<path d="M11,304L224,304A2 2 0 0 0 227 302L227,271A2 2 0 0 0 224 268L11,268A2 2 0 0 0 8 271L8,302A2 2 0 0 0 11 304Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="118" y="286" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">R  ...</text>
<path d="M11,340L224,340A2 2 0 0 0 227 338L227,307A2 2 0 0 0 224 304L11,304A2 2 0 0 0 8 307L8,338A2 2 0 0 0 11 340Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="118" y="322" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">U  alice</text>
<path d="M11,376L224,376A2 2 0 0 0 227 374L227,343A2 2 0 0 0 224 340L11,340A2 2 0 0 0 8 343L8,374A2 2 0 0 0 11 376Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="118" y="358" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">Z  ...</text>
<text x="420" y="12" text-anchor="middle" fill="rgb(83,97,106)" font-size="110%" dominant-baseline="central">CHECK-IN 2</text>
<path d="M306,113L534,113A2 2 0 0 0 537 110L537,46A2 2 0 0 0 534 44L306,44A2 2 0 0 0 303 46L303,110A2 2 0 0 0 306 113Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="420" y="67" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">DELTA MANIFEST</text>
<text x="420" y="89" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">b74e...19d0</text>
<path d="M306,152L534,152A2 2 0 0 0 537 149L537,115A2 2 0 0 0 534 113L306,113A2 2 0 0 0 303 115L303,149A2 2 0 0 0 306 152Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="420" y="132" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">B  91af...c302</text>
<path d="M306,191L534,191A2 2 0 0 0 537 188L537,154A2 2 0 0 0 534 152L306,152A2 2 0 0 0 303 154L303,188A2 2 0 0 0 306 191Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="420" y="171" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">D  2026-09-14...</text>
<path d="M306,229L534,229A2 2 0 0 0 537 227L537,193A2 2 0 0 0 534 191L306,191A2 2 0 0 0 303 193L303,227A2 2 0 0 0 306 229Z"  style="fill:rgb(244,232,226);stroke-width:1.584;stroke:rgb(139,74,50);" />
<text x="420" y="210" text-anchor="middle" fill="rgb(139,74,50)" font-size="110%" dominant-baseline="central">F beta   b9...  changed</text>
<path d="M306,268L534,268A2 2 0 0 0 537 266L537,232A2 2 0 0 0 534 229L306,229A2 2 0 0 0 303 232L303,266A2 2 0 0 0 306 268Z"  style="fill:rgb(244,232,226);stroke-width:0.864;stroke:rgb(139,74,50);" />
<text x="420" y="249" text-anchor="middle" fill="rgb(139,74,50)" font-size="110%" dominant-baseline="central">F charlie c3...  added</text>
<path d="M306,307L534,307A2 2 0 0 0 537 305L537,271A2 2 0 0 0 534 268L306,268A2 2 0 0 0 303 271L303,305A2 2 0 0 0 306 307Z"  style="fill:rgb(244,232,226);stroke-width:0.864;stroke:rgb(139,74,50);" />
<text x="420" y="288" text-anchor="middle" fill="rgb(139,74,50)" font-size="110%" dominant-baseline="central">F delta       deleted</text>
<path d="M306,343L534,343A2 2 0 0 0 537 341L537,310A2 2 0 0 0 534 307L306,307A2 2 0 0 0 303 310L303,341A2 2 0 0 0 306 343Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="420" y="325" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">R  ...</text>
<path d="M306,379L534,379A2 2 0 0 0 537 377L537,346A2 2 0 0 0 534 343L306,343A2 2 0 0 0 303 346L303,377A2 2 0 0 0 306 379Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="420" y="361" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">U  alice</text>
<path d="M306,415L534,415A2 2 0 0 0 537 413L537,382A2 2 0 0 0 534 379L306,379A2 2 0 0 0 303 382L303,413A2 2 0 0 0 306 415Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="420" y="397" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">Z  ...</text>
<path d="M109,523L428,523A2 2 0 0 0 431 521L431,468A2 2 0 0 0 428 466L109,466A2 2 0 0 0 107 468L107,521A2 2 0 0 0 109 523Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="269" y="494" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">existing delta-manifest merge</text>
<polygon points="108,466 106,459 111,460" style="fill:rgb(83,97,106)"/>
<path d="M118,376L108,463"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<polygon points="430,466 426,460 431,459" style="fill:rgb(83,97,106)"/>
<path d="M420,415L429,463"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<path d="M109,611L428,611A2 2 0 0 0 431 608L431,559A2 2 0 0 0 428 556L109,556A2 2 0 0 0 107 559L107,608A2 2 0 0 0 109 611Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="269" y="584" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">effective complete sorted F run</text>
<polygon points="269,556 266,549 271,549" style="fill:rgb(83,97,106)"/>
<path d="M269,523L269,553"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<path d="M41,651L497,651A2 2 0 0 0 499 649L499,614A2 2 0 0 0 497 611L41,611A2 2 0 0 0 38 614L38,649A2 2 0 0 0 41 651Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="269" y="631" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">F alpha    a1...</text>
<path d="M41,692L497,692A2 2 0 0 0 499 689L499,654A2 2 0 0 0 497 651L41,651A2 2 0 0 0 38 654L38,689A2 2 0 0 0 41 692Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="269" y="672" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">F beta     b9...</text>
<path d="M41,732L497,732A2 2 0 0 0 499 729L499,694A2 2 0 0 0 497 692L41,692A2 2 0 0 0 38 694L38,729A2 2 0 0 0 41 732Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="269" y="712" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">F charlie  c3...</text>
<text x="269" y="779" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">artifact-level B/F merge</text>
<text x="269" y="801" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">not blob compression; no S cards</text>
</svg>
</div>
<figcaption id="fig-2-caption">Figure 2. A classic delta orders B before D, then its delta F run, representative R and U, and final Z; the existing merge with the complete baseline recovers the effective sorted F run.</figcaption>
<details class="pikchr-source"><summary>Pikchr source</summary><pre><code>scale = 0.82
fill = 0xffffff
color = 0x29333a
thickness = 0.006
fontscale = 1.10
boxrad = 0.018

text "CHECK-IN 1" at -1.05,0 color 0x53616a
BH: box "BASELINE MANIFEST" "91af...c302" wid 1.62 ht 0.48 fill 0xe7eef2 at -1.05,-0.46
BD: box "D  2026-09-13..." wid 1.52 ht 0.27 fill 0xf3f4f3 at BH.x,BH.s.y - 0.135
BF1: box "F alpha    a1..." wid 1.52 ht 0.27 fill 0xf7f8f7 at BH.x,BD.s.y - 0.135
BF2: box "F beta     b2..." same at BH.x,BF1.s.y - 0.135
BF3: box "F delta    d4..." same at BH.x,BF2.s.y - 0.135
BR: box "R  ..." wid 1.52 ht 0.25 fill 0xf3f4f3 at BH.x,BF3.s.y - 0.125
BU: box "U  alice" same at BH.x,BR.s.y - 0.125
BZ: box "Z  ..." same fill 0xe7eef2 at BH.x,BU.s.y - 0.125

text "CHECK-IN 2" at 1.05,0 color 0x53616a
DH: box "DELTA MANIFEST" "b74e...19d0" wid 1.62 ht 0.48 fill 0xe7eef2 at 1.05,-0.46
DB: box "B  91af...c302" wid 1.62 ht 0.27 fill 0xf3f4f3 at DH.x,DH.s.y - 0.135
DD: box "D  2026-09-14..." same at DH.x,DB.s.y - 0.135
DF1: box "F beta   b9...  changed" wid 1.62 ht 0.27 fill 0xf4e8e2 color 0x8b4a32 thickness 0.011 at DH.x,DD.s.y - 0.135
DF2: box "F charlie c3...  added" wid 1.62 ht 0.27 fill 0xf4e8e2 color 0x8b4a32 thickness 0.006 at DH.x,DF1.s.y - 0.135
DF3: box "F delta       deleted" wid 1.62 ht 0.27 fill 0xf4e8e2 color 0x8b4a32 thickness 0.006 at DH.x,DF2.s.y - 0.135
DR: box "R  ..." wid 1.62 ht 0.25 fill 0xf3f4f3 at DH.x,DF3.s.y - 0.125
DU: box "U  alice" same at DH.x,DR.s.y - 0.125
DZ: box "Z  ..." same fill 0xe7eef2 at DH.x,DU.s.y - 0.125

M: box "existing delta-manifest merge" wid 2.25 ht 0.40 fill 0xe7eef2 at 0,DZ.s.y - 0.55
arrow from BZ.s to M.nw color 0x53616a
arrow from DZ.s to M.ne color 0x53616a
EH: box "effective complete sorted F run" wid 2.25 ht 0.38 fill 0xe7eef2 at M.x,M.s.y - 0.42
arrow from M.s to EH.n color 0x53616a
EF1: box "F alpha    a1..." wid 3.20 ht 0.28 fill 0xf7f8f7 at 0,EH.s.y - 0.14
EF2: box "F beta     b9..." same at 0,EF1.s.y - 0.14
EF3: box "F charlie  c3..." same at 0,EF2.s.y - 0.14

text "artifact-level B/F merge" "not blob compression; no S cards" at 0,EF3.s.y - 0.40 color 0x29333a</code></pre></details>
</figure>

  <p>This is an <em>artifact-level</em> delta manifest, not the repository's lower-level blob delta compression. It also contains no <code>S</code> cards: <code>B</code> means “merge against this baseline,” whereas <code>S</code> means “this check-in's file set is stored in referenced tree nodes.”</p>

  <p>Delta manifests can reduce the literal <code>F</code>-card text in the manifest artifact, but Fossil normally stores ordinary manifest artifacts using delta compression already, so the additional repository-storage saving is often negligible. More importantly, the consumer must still expand the baseline and walk the delta-manifest merge to recover the complete file set. The read and parse work therefore remains linear in tree width.</p>

  <p>That linear reconstruction has a second consequence: flat and delta manifests expose no independently addressed slices of the file list. A change therefore cannot reuse an unchanged portion as a structural artifact. Existing file-content blobs may still be shared, and Fossil may delta-compress one stored artifact against another; neither avoids reconstructing the complete file set.</p>

  <h3>What a tree changes</h3>

  <p>Sub-manifests address that reconstruction and sharing boundary. A sub-manifest carries a contiguous slice of a check-in's <em>sorted</em> file set, either as <code>F</code> cards (a leaf) or as <code>S</code> cards referencing lower sub-manifests (interior). Because the slices are content-addressed, an unchanged slice is literally the same artifact across check-ins and is stored once.</p>

  <figure class="diagram">
<div class="diagram-body">
<svg role="img" aria-labelledby="fig-3-caption" xmlns='http://www.w3.org/2000/svg' class="pikchr" width="433" height="994" viewBox="0 0 529.92 1213.06">
<text x="264" y="12" text-anchor="middle" fill="rgb(83,97,106)" font-size="110%" dominant-baseline="central">CHECK-IN 1</text>
<path d="M58,113L471,113A2 2 0 0 0 473 110L473,46A2 2 0 0 0 471 44L58,44A2 2 0 0 0 56 46L56,110A2 2 0 0 0 58 113Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="264" y="67" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">TREE CHECK-IN MANIFEST</text>
<text x="264" y="89" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">r41...</text>
<path d="M58,150L471,150A2 2 0 0 0 473 148L473,115A2 2 0 0 0 471 113L58,113A2 2 0 0 0 56 115L56,148A2 2 0 0 0 58 150Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="264" y="132" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">D  2026-09-13...</text>
<path d="M58,188L471,188A2 2 0 0 0 473 185L473,153A2 2 0 0 0 471 150L58,150A2 2 0 0 0 56 153L56,185A2 2 0 0 0 58 188Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="264" y="169" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">R  ...</text>
<path d="M58,225L471,225A2 2 0 0 0 473 223L473,190A2 2 0 0 0 471 188L58,188A2 2 0 0 0 56 190L56,223A2 2 0 0 0 58 225Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="264" y="206" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">S  A  a17...</text>
<path d="M58,263L471,263A2 2 0 0 0 473 260L473,228A2 2 0 0 0 471 225L58,225A2 2 0 0 0 56 228L56,260A2 2 0 0 0 58 263Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="264" y="244" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">S  B  b28...</text>
<path d="M58,300L471,300A2 2 0 0 0 473 297L473,265A2 2 0 0 0 471 263L58,263A2 2 0 0 0 56 265L56,297A2 2 0 0 0 58 300Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="264" y="281" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">S  C  c36...</text>
<path d="M58,337L471,337A2 2 0 0 0 473 335L473,303A2 2 0 0 0 471 300L58,300A2 2 0 0 0 56 303L56,335A2 2 0 0 0 58 337Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="264" y="319" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">U  alice</text>
<path d="M58,375L471,375A2 2 0 0 0 473 372L473,340A2 2 0 0 0 471 337L58,337A2 2 0 0 0 56 340L56,372A2 2 0 0 0 58 375Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="264" y="356" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">Z  ...   (final card)</text>
<path d="M4,561L168,561A2 2 0 0 0 171 558L171,437A2 2 0 0 0 168 434L4,434A2 2 0 0 0 1 437L1,558A2 2 0 0 0 4 561Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="86" y="453" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">LEAF</text>
<text x="86" y="475" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">SUBMANIFEST</text>
<text x="86" y="497" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">A  ·  a17...</text>
<text x="86" y="519" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">F run</text>
<text x="86" y="542" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">Z final</text>
<path d="M182,561L347,561A2 2 0 0 0 349 558L349,437A2 2 0 0 0 347 434L182,434A2 2 0 0 0 180 437L180,558A2 2 0 0 0 182 561Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="264" y="453" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">LEAF</text>
<text x="264" y="475" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">SUBMANIFEST</text>
<text x="264" y="497" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">B  ·  b28...</text>
<text x="264" y="519" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">F run</text>
<text x="264" y="542" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">Z final</text>
<path d="M361,561L525,561A2 2 0 0 0 528 558L528,437A2 2 0 0 0 525 434L361,434A2 2 0 0 0 358 437L358,558A2 2 0 0 0 361 561Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="443" y="453" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">LEAF</text>
<text x="443" y="475" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">SUBMANIFEST</text>
<text x="443" y="497" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">C  ·  c36...</text>
<text x="443" y="519" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">F run</text>
<text x="443" y="542" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">Z final</text>
<polygon points="86,434 83,427 88,427" style="fill:rgb(83,97,106)"/>
<path d="M86,375L86,430"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<polygon points="264,434 262,427 267,427" style="fill:rgb(83,97,106)"/>
<path d="M264,375L264,430"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<polygon points="443,434 440,427 446,427" style="fill:rgb(83,97,106)"/>
<path d="M443,375L443,430"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<text x="264" y="615" text-anchor="middle" fill="rgb(83,97,106)" font-size="110%" dominant-baseline="central">CHECK-IN 2</text>
<path d="M58,718L471,718A2 2 0 0 0 473 715L473,651A2 2 0 0 0 471 649L58,649A2 2 0 0 0 56 651L56,715A2 2 0 0 0 58 718Z"  style="fill:rgb(244,232,226);stroke-width:1.584;stroke:rgb(139,74,50);" />
<text x="264" y="672" text-anchor="middle" fill="rgb(139,74,50)" font-size="110%" dominant-baseline="central">TREE CHECK-IN MANIFEST</text>
<text x="264" y="694" text-anchor="middle" fill="rgb(139,74,50)" font-size="110%" dominant-baseline="central">r92...  changed</text>
<path d="M58,755L471,755A2 2 0 0 0 473 752L473,720A2 2 0 0 0 471 718L58,718A2 2 0 0 0 56 720L56,752A2 2 0 0 0 58 755Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="264" y="736" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">D  2026-09-14...</text>
<path d="M58,793L471,793A2 2 0 0 0 473 790L473,758A2 2 0 0 0 471 755L58,755A2 2 0 0 0 56 758L56,790A2 2 0 0 0 58 793Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="264" y="774" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">R  ...</text>
<path d="M58,830L471,830A2 2 0 0 0 473 827L473,795A2 2 0 0 0 471 793L58,793A2 2 0 0 0 56 795L56,827A2 2 0 0 0 58 830Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="264" y="811" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">S  A   a17...</text>
<path d="M58,867L471,867A2 2 0 0 0 473 865L473,833A2 2 0 0 0 471 830L58,830A2 2 0 0 0 56 833L56,865A2 2 0 0 0 58 867Z"  style="fill:rgb(244,232,226);stroke-width:1.584;stroke:rgb(139,74,50);" />
<text x="264" y="849" text-anchor="middle" fill="rgb(139,74,50)" font-size="110%" dominant-baseline="central">S  B'  e90...   changed</text>
<path d="M58,905L471,905A2 2 0 0 0 473 902L473,870A2 2 0 0 0 471 867L58,867A2 2 0 0 0 56 870L56,902A2 2 0 0 0 58 905Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="264" y="886" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">S  C   c36...</text>
<path d="M58,942L471,942A2 2 0 0 0 473 940L473,907A2 2 0 0 0 471 905L58,905A2 2 0 0 0 56 907L56,940A2 2 0 0 0 58 942Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="264" y="924" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">U  alice</text>
<path d="M58,980L471,980A2 2 0 0 0 473 977L473,945A2 2 0 0 0 471 942L58,942A2 2 0 0 0 56 945L56,977A2 2 0 0 0 58 980Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="264" y="961" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">Z  ...   (final card)</text>
<path d="M4,1165L168,1165A2 2 0 0 0 171 1163L171,1041A2 2 0 0 0 168 1039L4,1039A2 2 0 0 0 1 1041L1,1163A2 2 0 0 0 4 1165Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="86" y="1058" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">LEAF</text>
<text x="86" y="1080" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">SUBMANIFEST</text>
<text x="86" y="1102" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">A  ·  a17...</text>
<text x="86" y="1124" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">F run</text>
<text x="86" y="1146" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">Z final</text>
<path d="M182,1165L347,1165A2 2 0 0 0 349 1163L349,1041A2 2 0 0 0 347 1039L182,1039A2 2 0 0 0 180 1041L180,1163A2 2 0 0 0 182 1165Z"  style="fill:rgb(244,232,226);stroke-width:1.584;stroke:rgb(139,74,50);" />
<text x="264" y="1058" text-anchor="middle" fill="rgb(139,74,50)" font-size="110%" dominant-baseline="central">LEAF</text>
<text x="264" y="1080" text-anchor="middle" fill="rgb(139,74,50)" font-size="110%" dominant-baseline="central">SUBMANIFEST</text>
<text x="264" y="1102" text-anchor="middle" fill="rgb(139,74,50)" font-size="110%" dominant-baseline="central">B' ·  e90...</text>
<text x="264" y="1124" text-anchor="middle" fill="rgb(139,74,50)" font-size="110%" dominant-baseline="central">F run</text>
<text x="264" y="1146" text-anchor="middle" fill="rgb(139,74,50)" font-size="110%" dominant-baseline="central">Z final</text>
<path d="M361,1165L525,1165A2 2 0 0 0 528 1163L528,1041A2 2 0 0 0 525 1039L361,1039A2 2 0 0 0 358 1041L358,1163A2 2 0 0 0 361 1165Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="443" y="1058" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">LEAF</text>
<text x="443" y="1080" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">SUBMANIFEST</text>
<text x="443" y="1102" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">C  ·  c36...</text>
<text x="443" y="1124" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">F run</text>
<text x="443" y="1146" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">Z final</text>
<polygon points="86,1039 83,1032 88,1032" style="fill:rgb(83,97,106)"/>
<path d="M86,980L86,1035"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<polygon points="264,1039 260,1026 269,1026" style="fill:rgb(139,74,50)"/>
<path d="M264,980L264,1032"  style="fill:none;stroke-width:1.584;stroke:rgb(139,74,50);" />
<polygon points="443,1039 440,1032 446,1032" style="fill:rgb(83,97,106)"/>
<path d="M443,980L443,1035"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<text x="86" y="1200" text-anchor="middle" fill="rgb(83,97,106)" font-size="110%" dominant-baseline="central">same A hash</text>
<text x="264" y="1200" text-anchor="middle" fill="rgb(139,74,50)" font-size="110%" dominant-baseline="central">B changed</text>
<text x="443" y="1200" text-anchor="middle" fill="rgb(83,97,106)" font-size="110%" dominant-baseline="central">same C hash</text>
</svg>
</div>
<figcaption id="fig-3-caption">Figure 3. A tree check-in keeps canonical card order: D, representative R, the S run, representative U, and final Z. Referenced leaf SUBMANIFESTs contain only their F run and final Z; A and C are shared while B changes.</figcaption>
<details class="pikchr-source"><summary>Pikchr source</summary><pre><code>scale = 0.82
fill = 0xffffff
color = 0x29333a
thickness = 0.006
fontscale = 1.10
boxrad = 0.018

# Tree check-in card order plus leaf sub-manifest legality. Each snapshot is
# top-to-bottom with exactly one card per row; R/U are representative context.
text "CHECK-IN 1" at 0,0 color 0x53616a
OH: box "TREE CHECK-IN MANIFEST" "r41..." wid 2.90 ht 0.48 fill 0xe7eef2 at 0,-0.46
OD: box "D  2026-09-13..." wid 2.90 ht 0.26 fill 0xf3f4f3 at 0,OH.s.y - 0.13
OR: box "R  ..." wid 2.90 ht 0.26 fill 0xf3f4f3 at 0,OD.s.y - 0.13
OS1: box "S  A  a17..." wid 2.90 ht 0.26 fill 0xe7eef2 at 0,OR.s.y - 0.13
OS2: box "S  B  b28..." same at 0,OS1.s.y - 0.13
OS3: box "S  C  c36..." same at 0,OS2.s.y - 0.13
OU: box "U  alice" wid 2.90 ht 0.26 fill 0xf3f4f3 at 0,OS3.s.y - 0.13
OZ: box "Z  ...   (final card)" wid 2.90 ht 0.26 fill 0xe7eef2 at 0,OU.s.y - 0.13
OA: box "LEAF" "SUBMANIFEST" "A  ·  a17..." "F run" "Z final" wid 1.18 ht 0.88 fill 0xe7eef2 at -1.24,OZ.s.y - 0.85
OB: box "LEAF" "SUBMANIFEST" "B  ·  b28..." "F run" "Z final" same fill 0xf3f4f3 at 0,OA.y
OC: box "LEAF" "SUBMANIFEST" "C  ·  c36..." "F run" "Z final" same fill 0xe7eef2 at 1.24,OA.y
arrow from (OA.x,OZ.s.y) to OA.n color 0x53616a
arrow from (OB.x,OZ.s.y) to OB.n color 0x53616a
arrow from (OC.x,OZ.s.y) to OC.n color 0x53616a

text "CHECK-IN 2" at 0,OA.s.y - 0.38 color 0x53616a
NH: box "TREE CHECK-IN MANIFEST" "r92...  changed" wid 2.90 ht 0.48 fill 0xf4e8e2 color 0x8b4a32 thickness 0.011 at 0,OA.s.y - 0.85
ND: box "D  2026-09-14..." wid 2.90 ht 0.26 fill 0xf3f4f3 at 0,NH.s.y - 0.13
NR: box "R  ..." wid 2.90 ht 0.26 fill 0xf3f4f3 at 0,ND.s.y - 0.13
NS1: box "S  A   a17..." wid 2.90 ht 0.26 fill 0xe7eef2 at 0,NR.s.y - 0.13
NS2: box "S  B'  e90...   changed" same fill 0xf4e8e2 color 0x8b4a32 thickness 0.011 at 0,NS1.s.y - 0.13
NS3: box "S  C   c36..." same fill 0xe7eef2 color 0x29333a thickness 0.006 at 0,NS2.s.y - 0.13
NU: box "U  alice" wid 2.90 ht 0.26 fill 0xf3f4f3 color 0x29333a thickness 0.006 at 0,NS3.s.y - 0.13
NZ: box "Z  ...   (final card)" wid 2.90 ht 0.26 fill 0xe7eef2 at 0,NU.s.y - 0.13
NA: box "LEAF" "SUBMANIFEST" "A  ·  a17..." "F run" "Z final" wid 1.18 ht 0.88 fill 0xe7eef2 at -1.24,NZ.s.y - 0.85
NB: box "LEAF" "SUBMANIFEST" "B' ·  e90..." "F run" "Z final" same fill 0xf4e8e2 color 0x8b4a32 thickness 0.011 at 0,NA.y
NC: box "LEAF" "SUBMANIFEST" "C  ·  c36..." "F run" "Z final" same fill 0xe7eef2 color 0x29333a thickness 0.006 at 1.24,NA.y
arrow from (NA.x,NZ.s.y) to NA.n color 0x53616a
arrow from (NB.x,NZ.s.y) to NB.n color 0x8b4a32 thickness 0.011
arrow from (NC.x,NZ.s.y) to NC.n color 0x53616a

text "same A hash" at NA.x,NA.s.y - 0.24 color 0x53616a
text "B changed" at NB.x,NB.s.y - 0.24 color 0x8b4a32
text "same C hash" at NC.x,NC.s.y - 0.24 color 0x53616a</code></pre></details>
</figure>

  <p>The sharing is a property of content addressing, not of any explicit copy-on-write machinery. If two check-ins produce byte-identical sub-manifest bytes, they name the same artifact. No history is rewritten and no reference counting is needed. The check-in manifest remains the root artifact. Its <code>D</code> and <code>Z</code> cards remain required; ordinary context cards such as <code>C</code>, <code>P</code>, <code>R</code>, <code>T</code>, and <code>U</code> remain conditional. Only the file-set representation changes from an <code>F</code> run to an <code>S</code> run.</p>

  <div class="callout"><strong>The compatibility boundary.</strong> Old readers understand both classic forms — flat baseline manifests and <code>B</code>-based delta manifests — but reject the new <code>S</code> card and cannot interpret a tree check-in. New readers continue to understand both old forms. This is not a soft degradation — see <a href="#ch5">chapter 5</a> for why the closed card alphabet leaves no fallback path.</div>
</section>

<!-- ================= CHAPTER 2 ================= -->
<section class="chapter" id="ch2">
  <div class="chapter-num">2 — Syntax and typing</div>
  <h2>The S card and the SUBMANIFEST type</h2>

  <p>The <code>S</code> card changes several existing artifact-format rules. This chapter shows each affected rule and the resulting artifact types.</p>

  <h3>Grammar and arity</h3>

  <div class="grammar-panel" aria-label="S card grammar">
    <div class="grammar-row">
      <span class="grammar-label">ABNF production</span>
      <code><strong>s-card</strong> = <span class="grammar-literal">%x53</span> SP filename SP hash LF</code>
    </div>
    <div class="grammar-row grammar-wire">
      <span class="grammar-label">Wire form</span>
      <code><span class="grammar-literal">S</span> <span class="grammar-separator">␠</span> filename <span class="grammar-separator">␠</span> hash <span class="grammar-separator">↵</span></code>
    </div>
    <p class="grammar-note"><code>%x53</code> is the hexadecimal byte for uppercase <code>S</code>. Here <span aria-hidden="true">␠</span><span class="visually-hidden">the open-box symbol</span> means one space and <span aria-hidden="true">↵</span><span class="visually-hidden">the return-arrow symbol</span> means line feed.</p>
  </div>

  <p>Exactly two arguments. A third token on <code>S</code> is rejected, because the exact-arity enumeration now includes this card. The filename is escape-decoded and must pass the pathname predicate; the hash is <em>raw</em> — never decoded — and must be exactly 40 or 64 case-insensitive hexadecimal characters, matching the artifact-hash validation already applied to other hash-bearing positions.</p>

  <figure class="diagram">
<div class="diagram-body">
<svg role="img" aria-labelledby="fig-4-caption" xmlns='http://www.w3.org/2000/svg' class="pikchr" width="387" height="201" viewBox="0 0 473.472 246.24">
<path d="M45,59L95,59A2 2 0 0 0 97 56L97,4A2 2 0 0 0 95 1L45,1A2 2 0 0 0 42 4L42,56A2 2 0 0 0 45 59Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="70" y="30" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">S</text>
<path d="M100,59L308,59A2 2 0 0 0 310 56L310,4A2 2 0 0 0 308 1L100,1A2 2 0 0 0 97 4L97,56A2 2 0 0 0 100 59Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="204" y="30" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">alpha/lib/parse.c</text>
<path d="M313,59L469,59A2 2 0 0 0 472 56L472,4A2 2 0 0 0 469 1L313,1A2 2 0 0 0 310 4L310,56A2 2 0 0 0 313 59Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="391" y="30" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">7f3a...bb02</text>
<path d="M70,59L70,102"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<path d="M204,59L204,102"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<path d="M391,59L391,102"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<text x="70" y="128" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">card letter</text>
<text x="204" y="128" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">decoded filename</text>
<text x="391" y="128" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">raw hash token</text>
<text x="204" y="167" text-anchor="middle" fill="rgb(83,97,106)" dominant-baseline="central">hash abbreviated here</text>
<text x="204" y="187" text-anchor="middle" fill="rgb(83,97,106)" dominant-baseline="central">wire token: exactly 40 or 64 hex</text>
<text x="204" y="234" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">filename = least member of the referenced slice</text>
</svg>
</div>
<figcaption id="fig-4-caption">Figure 4. The S card's decoded filename names the slice's least member; its raw hash token identifies that sub-manifest. The hash is abbreviated only in this figure.</figcaption>
<details class="pikchr-source"><summary>Pikchr source</summary><pre><code>scale = 0.82
fill = 0xffffff
color = 0x29333a
thickness = 0.006
fontscale = 1.0
boxrad = 0.018

# The displayed hash is deliberately abbreviated to keep the anatomy legible.
# On the wire the final token is exactly 40 or 64 hexadecimal characters.
C1: box "S" wid 0.38 ht 0.40 fill 0xe7eef2
C2: box "alpha/lib/parse.c" wid 1.48 ht 0.40 fill 0xf7f8f7 with .w at C1.e
C3: box "7f3a...bb02" wid 1.12 ht 0.40 fill 0xf7f8f7 with .w at C2.e

line from C1.s down 0.30 color 0x53616a
line from C2.s down 0.30 color 0x53616a
line from C3.s down 0.30 color 0x53616a
text "card letter" at C1.x, C1.s.y - 0.48 color 0x29333a
text "decoded filename" at C2.x, C2.s.y - 0.48 color 0x29333a
text "raw hash token" at C3.x, C3.s.y - 0.48 color 0x29333a
text "hash abbreviated here" "wire token: exactly 40 or 64 hex" at C2.x, C2.s.y - 0.82 color 0x53616a
text "filename = least member of the referenced slice" at C2.x, C2.s.y - 1.22 color 0x29333a</code></pre></details>
</figure>

  <h3>Occurrence class and ordering</h3>

  <p><code>S</code> joins the repeatable occurrence class, which becomes <code>F, J, M, T, P, Q, S</code>. The intra-run ordering rule requires <code>S</code> cards to be <strong>strictly ascending by decoded filename</strong>: equal or descending filenames are rejected at parse time, under the canonical comparator that already governs the <code>F</code> run.</p>

  <p>Inter-type ordering is unchanged, which places <code>S</code> between <code>R</code> and <code>T</code>. So a manifest's <code>S</code> run precedes its <code>T</code> cards. This matters because it means the ordering amendment is local: no existing card's position in a manifest changes.</p>

  <h3>What an S card asserts</h3>

  <p>Each card announces that the referenced artifact is a sub-manifest carrying the contiguous slice of <em>this</em> artifact's file set whose least member is exactly the decoded filename. The slice extends to, and excludes, the next <code>S</code> card's decoded filename. The final card's slice is unbounded above.</p>

  <p>Two properties are <em>not</em> checked at parse time, deliberately:</p>

  <ul>
    <li>The hash must resolve to an artifact, and that artifact must be of type <code>SUBMANIFEST</code>.</li>
    <li>The referenced slice must actually begin at the filename the card names.</li>
  </ul>

  <p>Both are enforced by the tree walk. Parse-time acceptance validates only token shapes and intra-run ordering. A reference to any type other than <code>SUBMANIFEST</code> must cause the tree walk to fail.</p>

  <h3>Type determination</h3>

  <p><code>SUBMANIFEST</code> is type code 9. It has no card letter that assigns it directly. Instead, one reassignment rule runs after the fallback type determination finalizes a type and before the card-legality checks:</p>

  <blockquote class="rfc-quote">
    <p>An artifact whose finalized type is <code>MANIFEST</code> and which contains no <code>D</code> card <strong>MUST</strong> be reclassified as <code>SUBMANIFEST</code> (code 9).</p>
    <footer>— <cite><a href="https://fossil.craftdesign.group/zit/doc/trunk/docs/rfcs/draft-fossil-submanifests-00.txt">draft-fossil-submanifests-00, Section 4.3</a></cite></footer>
  </blockquote>

  <p>The rule is deliberately narrow. After <code>B</code>, <code>F</code>, <code>Q</code>, <code>R</code>, or <code>S</code> provisionally selects <code>MANIFEST</code>, absence of <code>D</code> reclassifies the artifact as <code>SUBMANIFEST</code>. Other artifact types are unaffected. The legality table then rejects a <code>D</code>-less artifact carrying any card outside <code>F</code>, <code>S</code>, and <code>Z</code>.</p>

  <p>The legality table gains a row and revises one:</p>

  <div class="artifact-table-wrap">
    <table class="artifact-table">
      <caption>Artifact card legality</caption>
      <colgroup>
        <col class="artifact-code">
        <col class="artifact-type">
        <col class="artifact-allowed">
        <col class="artifact-required">
      </colgroup>
      <thead>
        <tr>
          <th scope="col">Code</th>
          <th scope="col">Type</th>
          <th scope="col">Allowed cards</th>
          <th scope="col">Required cards</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td><code>1</code></td>
          <th scope="row"><code>MANIFEST</code></th>
          <td><code>B C D F N P Q R S T U Z</code></td>
          <td><code>D Z</code></td>
        </tr>
        <tr>
          <td><code>9</code></td>
          <th scope="row"><code>SUBMANIFEST</code></th>
          <td><code>F S Z</code></td>
          <td><code>Z</code></td>
        </tr>
      </tbody>
    </table>
  </div>

  <div class="callout"><strong>Tree check-in versus tree node.</strong> The top-level check-in remains a <code>MANIFEST</code>: <code>D</code> and <code>Z</code> are required, while <code>C</code>, <code>N</code>, <code>P</code>, <code>Q</code>, <code>R</code>, <code>T</code>, and <code>U</code> remain conditional. Its <code>S</code> run replaces the <code>F</code> run. Only the referenced nodes are <code>SUBMANIFEST</code>s restricted to <code>F</code>, <code>S</code>, and <code>Z</code>.</div>

  <p>A signature is not an artifact card and therefore does not appear in this table. PGP or SSH signature armor wraps the complete stored artifact and is removed before its card stream is classified. A tree check-in can therefore be signed exactly like a flat check-in: the signed wrapper surrounds the top-level <code>MANIFEST</code>, while its <code>S</code> references continue to name the child artifacts. The wrapper participates in the manifest's artifact name; the inner <code>Z</code> checksum is still computed over the stripped card stream. A generic reader can unwrap a signed structural artifact, but canonical construction leaves generated <code>SUBMANIFEST</code> nodes unsigned so that independent writers produce the required byte-identical node artifacts.</p>

  <p>For <code>SUBMANIFEST</code> only, the content rule requires at least one <code>F</code> or at least one <code>S</code>, never both. It is a semantic check, not expressible in the table's required-set column. Separate <code>MANIFEST</code> rules forbid both <code>B</code> and <code>S</code>, and forbid both <code>F</code> and <code>S</code>.</p>

  <p>A leaf sub-manifest is not a delta. Every <code>F</code> card in it must carry a content hash; the hashless deletion form is forbidden. Permission and old-name tokens remain legal exactly as in a baseline manifest, and the tree walk must deliver those tokens to consumers unchanged. Moving the card into a leaf changes where it is stored, not what the file entry means.</p>

  <div class="callout"><strong>Why classification is a post-pass.</strong> The parser must first finish the card scan and provisional type assignment. Only a provisional <code>MANIFEST</code> without <code>D</code> becomes <code>SUBMANIFEST</code>. This ordering also guarantees that no artifact valid under the earlier format changes classification.</div>

  <h3>The pre-filter exception</h3>

  <p>The well-formedness pre-filter may reject input whose first card letter falls in <code>N</code> through <code>Z</code> before full parsing. <code>S</code> must be excluded from that fast rejection, because an interior sub-manifest's smallest allowed card letter <em>is</em> <code>S</code>. A pre-filter that rejects on <code>N</code>–<code>Z</code> without the exception rejects every interior node.</p>
</section>

<!-- ================= CHAPTER 3 ================= -->
<section class="chapter" id="ch3">
  <div class="chapter-num">3 — Canonical construction</div>
  <h2>The partition function</h2>

  <p><strong>The partition is part of the check-in identity.</strong> Give two conforming writers the same ordered file entries — including every token — the same repository hash policy, and the same pinned <span class="math" data-tex="k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span>. They must build byte-identical sub-manifests and the same manifest <code>S</code> run. Any difference propagates through the referenced artifact hashes and produces a different check-in hash.</p>

  <h3>The predicate</h3>

  <p>Define <span class="math" data-tex="\operatorname{PH}(\mathit{name})"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="normal">PH</mi><mo>⁡</mo><mo stretchy="false">(</mo><mrow><mi>n</mi><mi>a</mi><mi>m</mi><mi>e</mi></mrow><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\operatorname{PH}(\mathit{name})</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop"><span class="mord mathrm">PH</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathit">name</span></span><span class="mclose">)</span></span></span></span><!--/math--></span> as the SHA3-256 digest of the <em>decoded</em> filename — the byte string ordered by the canonical comparator, with no terminator and no transformation. Then:</p>

  <div class="math-block"><span class="math display" data-tex="\operatorname{zbits}(\mathit{name}) = \text{number of leading zero bits of }\operatorname{PH}(\mathit{name})"><!--math--><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="normal">zbits</mi><mo>⁡</mo><mo stretchy="false">(</mo><mrow><mi>n</mi><mi>a</mi><mi>m</mi><mi>e</mi></mrow><mo stretchy="false">)</mo><mo>=</mo><mtext>number of leading zero bits of </mtext><mi mathvariant="normal">PH</mi><mo>⁡</mo><mo stretchy="false">(</mo><mrow><mi>n</mi><mi>a</mi><mi>m</mi><mi>e</mi></mrow><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\operatorname{zbits}(\mathit{name}) = \text{number of leading zero bits of }\operatorname{PH}(\mathit{name})</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop"><span class="mord mathrm">zbits</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathit">name</span></span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord text"><span class="mord">number of leading zero bits of </span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop"><span class="mord mathrm">PH</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathit">name</span></span><span class="mclose">)</span></span></span></span></span><!--/math--></span></div>

  <h3>Why the hashes balance the tree</h3>

  <p><strong>The partition is deterministic, but its distribution is random-looking.</strong> A particular decoded filename always produces the same SHA3-256 digest and therefore the same <code>zbits</code> value. Nothing is sampled again when the repository is rebuilt. Across a varied set of filenames, however, SHA3 output is expected to behave like uniformly distributed bits: the chance that the first bit is zero is one half, the chance that the first two bits are both zero is one quarter, and in general:</p>

  <div class="callout"><strong>Natural-boundary probability.</strong> <span class="math" data-tex="\Pr[\operatorname{zbits}(\mathit{name}) \ge k] = 2^{-k}"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>Pr</mi><mo>⁡</mo><mo stretchy="false">[</mo><mi mathvariant="normal">zbits</mi><mo>⁡</mo><mo stretchy="false">(</mo><mrow><mi>n</mi><mi>a</mi><mi>m</mi><mi>e</mi></mrow><mo stretchy="false">)</mo><mo>≥</mo><mi>k</mi><mo stretchy="false">]</mo><mo>=</mo><msup><mn>2</mn><mrow><mo>−</mo><mi>k</mi></mrow></msup></mrow><annotation encoding="application/x-tex">\Pr[\operatorname{zbits}(\mathit{name}) \ge k] = 2^{-k}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop">Pr</span><span class="mopen">[</span><span class="mop"><span class="mord mathrm">zbits</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathit">name</span></span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≥</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mclose">]</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8491em;"></span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8491em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span></span></span></span></span></span></span></span></span><!--/math--></span>. Ignoring the <code>MIN</code> and <code>MAX</code> corrections for a moment, that means one natural leaf boundary about every <span class="math" data-tex="2^k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mn>2</mn><mi>k</mi></msup></mrow><annotation encoding="application/x-tex">2^k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8491em;"></span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8491em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span></span></span></span></span></span></span></span><!--/math--></span> filenames.</div>

  <div class="artifact-table-wrap">
    <table class="artifact-table probability-table">
      <caption>Expected natural-boundary frequency</caption>
      <thead>
        <tr>
          <th scope="col"><span class="math" data-tex="k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span></th>
          <th scope="col">Boundary probability</th>
          <th scope="col">Expected spacing</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th scope="row"><span class="math" data-tex="1"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1</mn></mrow><annotation encoding="application/x-tex">1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1</span></span></span></span><!--/math--></span></th>
          <td><span class="math" data-tex="\frac{1}{2}"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mn>1</mn><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">\frac{1}{2}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.1901em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8451em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span><!--/math--></span></td>
          <td>about 2 names</td>
        </tr>
        <tr>
          <th scope="row"><span class="math" data-tex="3"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>3</mn></mrow><annotation encoding="application/x-tex">3</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">3</span></span></span></span><!--/math--></span></th>
          <td><span class="math" data-tex="\frac{1}{8}"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mn>1</mn><mn>8</mn></mfrac></mrow><annotation encoding="application/x-tex">\frac{1}{8}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.1901em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8451em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">8</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span><!--/math--></span></td>
          <td>about 8 names</td>
        </tr>
        <tr>
          <th scope="row"><span class="math" data-tex="5"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>5</mn></mrow><annotation encoding="application/x-tex">5</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">5</span></span></span></span><!--/math--></span></th>
          <td><span class="math" data-tex="\frac{1}{32}"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mn>1</mn><mn>32</mn></mfrac></mrow><annotation encoding="application/x-tex">\frac{1}{32}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.1901em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8451em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">32</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span><!--/math--></span></td>
          <td>about 32 names</td>
        </tr>
      </tbody>
    </table>
  </div>

  <p>This is the source of the tree's statistical balance. A smaller <span class="math" data-tex="k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span> makes qualifying hashes common, producing more, smaller leaves and more <code>S</code> references. A larger <span class="math" data-tex="k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span> makes them rarer, producing fewer, larger leaves and usually fewer levels. At each higher level the threshold gains another <span class="math" data-tex="k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span> zero bits, so natural boundaries thin out by roughly another factor of <span class="math" data-tex="2^k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mn>2</mn><mi>k</mi></msup></mrow><annotation encoding="application/x-tex">2^k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8491em;"></span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8491em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span></span></span></span></span></span></span></span><!--/math--></span>. The shape is balanced <em>in expectation</em>, not perfectly balanced for every filename set.</p>

  <p>The group limits make that probabilistic scheme safe. <code>MIN</code> suppresses a natural boundary that arrives too soon, while <code>MAX</code> forces a boundary when the hashes produce an unusually long run. Thus SHA3 supplies stable, content-derived randomness; <span class="math" data-tex="k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span> sets its target scale; and <code>MIN</code>/<code>MAX</code> constrain the outliers.</p>

  <p>Every boundary test compares <code>zbits</code> against a multiple of a single positive integer <span class="math" data-tex="k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span>, the partition parameter. A leaf boundary requires <span class="math" data-tex="\operatorname{zbits}(\mathit{name}) \ge k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="normal">zbits</mi><mo>⁡</mo><mo stretchy="false">(</mo><mrow><mi>n</mi><mi>a</mi><mi>m</mi><mi>e</mi></mrow><mo stretchy="false">)</mo><mo>≥</mo><mi>k</mi></mrow><annotation encoding="application/x-tex">\operatorname{zbits}(\mathit{name}) \ge k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop"><span class="mord mathrm">zbits</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathit">name</span></span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≥</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span>. A boundary between height-<span class="math" data-tex="h"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>h</mi></mrow><annotation encoding="application/x-tex">h</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">h</span></span></span></span><!--/math--></span> nodes requires <span class="math" data-tex="\operatorname{zbits}(\mathit{key}) \ge (h+1)k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="normal">zbits</mi><mo>⁡</mo><mo stretchy="false">(</mo><mrow><mi>k</mi><mi>e</mi><mi>y</mi></mrow><mo stretchy="false">)</mo><mo>≥</mo><mo stretchy="false">(</mo><mi>h</mi><mo>+</mo><mn>1</mn><mo stretchy="false">)</mo><mi>k</mi></mrow><annotation encoding="application/x-tex">\operatorname{zbits}(\mathit{key}) \ge (h+1)k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop"><span class="mord mathrm">zbits</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathit">key</span></span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≥</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal">h</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mclose">)</span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span>.</p>

  <p>For <span class="math" data-tex="k \le 2"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi><mo>≤</mo><mn>2</mn></mrow><annotation encoding="application/x-tex">k \le 2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8304em;vertical-align:-0.136em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">2</span></span></span></span><!--/math--></span>, <code>MIN</code> may be 0 or 1, so <code>MIN</code> alone cannot prevent single-child spines; the non-reducing-scan stop and the height cap provide the remaining bounds.</p>

  <p>The digest algorithm, the bit granularity, and the input bytes are fixed and must not be configurable or negotiable per artifact. The predicate reads the filename only: content hash, permission, and old-name never influence the partition. Thus editing a file's content changes one leaf's contents without moving partition boundaries; the hashes of that leaf's ancestors and the check-in manifest change in consequence.</p>

  <div class="callout"><strong><span class="math" data-tex="k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span> never appears in artifact bytes.</strong> It is a writer-side, per-repository setting. A reader must not consult it. This is why <span class="math" data-tex="k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span> can be revised without a format change — and why readers accept any tree satisfying the structural invariants rather than only the canonical one.</div>

  <h3>Group limits</h3>

  <p>Two limits count members — file entries at height 0, child nodes above — never bytes:</p>

  <div class="math-block"><span class="math display" data-tex="\mathrm{MIN}=\left\lfloor\frac{2^k}{4}\right\rfloor,\qquad \mathrm{MAX}=4\cdot 2^k"><!--math--><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mi mathvariant="normal">M</mi><mi mathvariant="normal">I</mi><mi mathvariant="normal">N</mi></mrow><mo>=</mo><mrow><mo fence="true">⌊</mo><mfrac><msup><mn>2</mn><mi>k</mi></msup><mn>4</mn></mfrac><mo fence="true">⌋</mo></mrow><mo separator="true">,</mo><mspace width="2em"/><mrow><mi mathvariant="normal">M</mi><mi mathvariant="normal">A</mi><mi mathvariant="normal">X</mi></mrow><mo>=</mo><mn>4</mn><mo>⋅</mo><msup><mn>2</mn><mi>k</mi></msup></mrow><annotation encoding="application/x-tex">\mathrm{MIN}=\left\lfloor\frac{2^k}{4}\right\rfloor,\qquad \mathrm{MAX}=4\cdot 2^k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord"><span class="mord mathrm">MIN</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4761em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">⌊</span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.5261em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">4</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8491em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">⌋</span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mpunct">,</span><span class="mspace" style="margin-right:2em;"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathrm">MAX</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">4</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8991em;"></span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8991em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span></span></span></span></span></span></span></span></span><!--/math--></span></div>

  <p><code>MAX</code> forces a split when a group would otherwise grow without bound; this is the content-defined-chunking corrective inherited from FastCDC, and it bounds the pathological case where no filename's digest qualifies. <code>MIN</code> suppresses tiny groups and usually prevents one-child spines. For <code>k</code> values 1 and 2, however, <code>MIN</code> is 0 or 1 and cannot suppress a singleton; the non-reducing-scan stop and height cap provide the bound instead.</p>

  <h3>The scan</h3>

  <p>Construction begins from the check-in's file entries <span class="math" data-tex="e_1 \lt \cdots \lt e_N"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>e</mi><mn>1</mn></msub><mo>&lt;</mo><mo>⋯</mo><mo>&lt;</mo><msub><mi>e</mi><mi>N</mi></msub></mrow><annotation encoding="application/x-tex">e_1 \lt \cdots \lt e_N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6891em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&lt;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.5782em;vertical-align:-0.0391em;"></span><span class="minner">⋯</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&lt;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.5806em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><!--/math--></span> in canonical filename order — the same entries a flat baseline manifest would emit as <code>F</code> cards after the reserved-name exclusions.</p>

  <p>Step 1 scans <span class="math" data-tex="e_1,\ldots,e_N"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>e</mi><mn>1</mn></msub><mo separator="true">,</mo><mo>…</mo><mo separator="true">,</mo><msub><mi>e</mi><mi>N</mi></msub></mrow><annotation encoding="application/x-tex">e_1,\ldots,e_N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner">…</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><!--/math--></span> once, left to right, into height-0 groups. <span class="math" data-tex="e_1"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>e</mi><mn>1</mn></msub></mrow><annotation encoding="application/x-tex">e_1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.5806em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><!--/math--></span> starts the first group. Each subsequent entry <span class="math" data-tex="e_i"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>e</mi><mi>i</mi></msub></mrow><annotation encoding="application/x-tex">e_i</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.5806em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><!--/math--></span> starts a new group if and only if the open group holds <code>MAX</code> members, <em>or</em> <span class="math" data-tex="\operatorname{zbits}(\mathit{name}(e_i)) \ge k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="normal">zbits</mi><mo>⁡</mo><mo stretchy="false">(</mo><mrow><mi>n</mi><mi>a</mi><mi>m</mi><mi>e</mi></mrow><mo stretchy="false">(</mo><msub><mi>e</mi><mi>i</mi></msub><mo stretchy="false">)</mo><mo stretchy="false">)</mo><mo>≥</mo><mi>k</mi></mrow><annotation encoding="application/x-tex">\operatorname{zbits}(\mathit{name}(e_i)) \ge k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop"><span class="mord mathrm">zbits</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathit">name</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mclose">))</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≥</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span> and the open group holds at least <code>MIN</code> members.</p>

  <figure class="diagram">
<div class="diagram-body">
<svg role="img" aria-labelledby="fig-5-caption" xmlns='http://www.w3.org/2000/svg' class="pikchr" width="366" height="429" viewBox="0 0 447.12 524.232">
<text x="93" y="12" text-anchor="middle" fill="rgb(41,51,58)" font-size="105%" dominant-baseline="central">natural boundary</text>
<path d="M4,127L183,127A2 2 0 0 0 185 124L185,49A2 2 0 0 0 183 46L4,46A2 2 0 0 0 1 49L1,124A2 2 0 0 0 4 127Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="93" y="76" text-anchor="middle" fill="rgb(41,51,58)" font-size="105%" dominant-baseline="central">open group</text>
<text x="93" y="97" text-anchor="middle" fill="rgb(41,51,58)" font-size="105%" dominant-baseline="central">size &gt;= MIN</text>
<path d="M239,127L443,127A2 2 0 0 0 445 124L445,49A2 2 0 0 0 443 46L239,46A2 2 0 0 0 236 49L236,124A2 2 0 0 0 239 127Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="341" y="76" text-anchor="middle" fill="rgb(41,51,58)" font-size="105%" dominant-baseline="central">next name</text>
<text x="341" y="97" text-anchor="middle" fill="rgb(41,51,58)" font-size="105%" dominant-baseline="central">zbits(name) &gt;= k</text>
<polygon points="236,86 229,89 229,84" style="fill:rgb(83,97,106)"/>
<path d="M185,86L233,86"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<path d="M257,230L425,230A2 2 0 0 0 427 228L427,175A2 2 0 0 0 425 173L257,173A2 2 0 0 0 254 175L254,228A2 2 0 0 0 257 230Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="341" y="202" text-anchor="middle" fill="rgb(41,51,58)" font-size="105%" dominant-baseline="central">start new group</text>
<polygon points="341,173 338,166 343,166" style="fill:rgb(83,97,106)"/>
<path d="M341,127L341,169"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<text x="93" y="253" text-anchor="middle" fill="rgb(41,51,58)" font-size="105%" dominant-baseline="central">forced boundary</text>
<path d="M4,354L183,354A2 2 0 0 0 185 352L185,276A2 2 0 0 0 183 274L4,274A2 2 0 0 0 1 276L1,352A2 2 0 0 0 4 354Z"  style="fill:rgb(244,232,226);stroke-width:0.864;stroke:rgb(139,74,50);" />
<text x="93" y="303" text-anchor="middle" fill="rgb(139,74,50)" font-size="105%" dominant-baseline="central">open group</text>
<text x="93" y="325" text-anchor="middle" fill="rgb(139,74,50)" font-size="105%" dominant-baseline="central">size = MAX</text>
<path d="M239,354L443,354A2 2 0 0 0 445 352L445,276A2 2 0 0 0 443 274L239,274A2 2 0 0 0 236 276L236,352A2 2 0 0 0 239 354Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="341" y="303" text-anchor="middle" fill="rgb(41,51,58)" font-size="105%" dominant-baseline="central">next name</text>
<text x="341" y="325" text-anchor="middle" fill="rgb(41,51,58)" font-size="105%" dominant-baseline="central">predicate may fail</text>
<polygon points="236,314 229,317 229,311" style="fill:rgb(139,74,50)"/>
<path d="M185,314L233,314"  style="fill:none;stroke-width:0.864;stroke:rgb(139,74,50);" />
<path d="M257,458L425,458A2 2 0 0 0 427 455L427,403A2 2 0 0 0 425 400L257,400A2 2 0 0 0 254 403L254,455A2 2 0 0 0 257 458Z"  style="fill:rgb(244,232,226);stroke-width:0.864;stroke:rgb(139,74,50);" />
<text x="341" y="429" text-anchor="middle" fill="rgb(139,74,50)" font-size="105%" dominant-baseline="central">start new group</text>
<polygon points="341,400 338,393 343,393" style="fill:rgb(139,74,50)"/>
<path d="M341,354L341,397"  style="fill:none;stroke-width:0.864;stroke:rgb(139,74,50);" />
<text x="217" y="491" text-anchor="middle" fill="rgb(83,97,106)" font-size="105%" dominant-baseline="central">MIN and MAX come from k</text>
<text x="217" y="512" text-anchor="middle" fill="rgb(83,97,106)" font-size="105%" dominant-baseline="central">the split causes are distinct</text>
</svg>
</div>
<figcaption id="fig-5-caption">Figure 5. Two separate schematic scans show the distinct causes: a natural boundary needs both the hash predicate and MIN, while MAX forces a split regardless of the predicate.</figcaption>
<details class="pikchr-source"><summary>Pikchr source</summary><pre><code>scale = 0.82
fill = 0xffffff
color = 0x29333a
thickness = 0.006
fontscale = 1.05
boxrad = 0.018

# Separate schematic scans show the two alternatives without fake limits.
text "natural boundary" at 0,0 color 0x29333a
G1: box "open group" "size &gt;= MIN" wid 1.28 ht 0.56 fill 0xe7eef2 at 0,-0.52
N1: box "next name" "zbits(name) &gt;= k" wid 1.45 ht 0.56 fill 0xf7f8f7 at G1.x + 1.72, G1.y
arrow from G1.e to N1.w color 0x53616a
O1: box "start new group" wid 1.20 ht 0.40 fill 0xe7eef2 at N1.x, N1.s.y - 0.52
arrow from N1.s to O1.n color 0x53616a

text "forced boundary" at 0,O1.s.y - 0.16 color 0x29333a
G2: box "open group" "size = MAX" wid 1.28 ht 0.56 fill 0xf4e8e2 color 0x8b4a32 at 0,O1.s.y - 0.58
N2: box "next name" "predicate may fail" wid 1.45 ht 0.56 fill 0xf7f8f7 at G2.x + 1.72, G2.y
arrow from G2.e to N2.w color 0x8b4a32
O2: box "start new group" wid 1.20 ht 0.40 fill 0xf4e8e2 color 0x8b4a32 at N2.x, N2.s.y - 0.52
arrow from N2.s to O2.n color 0x8b4a32

text "MIN and MAX come from k" "the split causes are distinct" at (G2.x + N2.x)/2, O2.s.y - 0.30 color 0x53616a</code></pre></details>
</figure>

  <p>When the scan completes, one merge rule applies: if the final group holds fewer than <code>MIN</code> members, a preceding group exists, and the two together hold at most <code>MAX</code> members, the final group is merged into the group preceding it. Without this, a trailing fragment would become its own undersized leaf.</p>

  <p>Step 2 checks for degenerate outcomes. If the scan yields fewer than two groups, the writer must emit a flat manifest and no sub-manifests. The case <code>N = 0</code> — an empty check-in, which Fossil permits — yields zero groups and a flat manifest carrying no <code>F</code> cards and no <code>S</code> cards.</p>

  <p>If the height-0 scan yields at least two groups, each group becomes a leaf sub-manifest carrying exactly that group's ordered <code>F</code> cards and a final <code>Z</code> card. Subject only to the emission gate, the <code>avoid-submanifests</code> pragma, and the out-of-range-pin fallback, an enabled writer must emit this canonical tree. Choosing a flat manifest instead is nonconforming.</p>

  <p>The <strong>key</strong> of a node is the decoded filename of the first file entry in its subtree.</p>

  <h3>Recursion and termination</h3>

  <p>For <span class="math" data-tex="h=1,2,\ldots"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>h</mi><mo>=</mo><mn>1</mn><mo separator="true">,</mo><mn>2</mn><mo separator="true">,</mo><mo>…</mo></mrow><annotation encoding="application/x-tex">h=1,2,\ldots</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">h</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8389em;vertical-align:-0.1944em;"></span><span class="mord">1</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">2</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner">…</span></span></span></span><!--/math--></span>, let <span class="math" data-tex="n_1,\ldots,n_m"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>n</mi><mn>1</mn></msub><mo separator="true">,</mo><mo>…</mo><mo separator="true">,</mo><msub><mi>n</mi><mi>m</mi></msub></mrow><annotation encoding="application/x-tex">n_1,\ldots,n_m</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">n</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner">…</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathnormal">n</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">m</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><!--/math--></span> be the height-<span class="math" data-tex="h-1"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>h</mi><mo>−</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">h-1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7778em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">h</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1</span></span></span></span><!--/math--></span> nodes in key order. Partition them by the <em>same</em> scan, including the final-group merge, with the boundary test <span class="math" data-tex="\operatorname{zbits}(\mathit{key}(n_i)) \ge (h+1)k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="normal">zbits</mi><mo>⁡</mo><mo stretchy="false">(</mo><mrow><mi>k</mi><mi>e</mi><mi>y</mi></mrow><mo stretchy="false">(</mo><msub><mi>n</mi><mi>i</mi></msub><mo stretchy="false">)</mo><mo stretchy="false">)</mo><mo>≥</mo><mo stretchy="false">(</mo><mi>h</mi><mo>+</mo><mn>1</mn><mo stretchy="false">)</mo><mi>k</mi></mrow><annotation encoding="application/x-tex">\operatorname{zbits}(\mathit{key}(n_i)) \ge (h+1)k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop"><span class="mord mathrm">zbits</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathit">key</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal">n</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mclose">))</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≥</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal">h</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mclose">)</span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span> and with <code>MIN</code> and <code>MAX</code> counting nodes.</p>

  <p>The RFC's four-file Appendix A vector makes that rule concrete. With <span class="math" data-tex="k=3"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi><mo>=</mo><mn>3</mn></mrow><annotation encoding="application/x-tex">k=3</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">3</span></span></span></span><!--/math--></span>, <span class="math" data-tex="\mathrm{MIN}=2"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi mathvariant="normal">M</mi><mi mathvariant="normal">I</mi><mi mathvariant="normal">N</mi></mrow><mo>=</mo><mn>2</mn></mrow><annotation encoding="application/x-tex">\mathrm{MIN}=2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord"><span class="mord mathrm">MIN</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">2</span></span></span></span><!--/math--></span>, and <span class="math" data-tex="\mathrm{MAX}=32"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi mathvariant="normal">M</mi><mi mathvariant="normal">A</mi><mi mathvariant="normal">X</mi></mrow><mo>=</mo><mn>32</mn></mrow><annotation encoding="application/x-tex">\mathrm{MAX}=32</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord"><span class="mord mathrm">MAX</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">32</span></span></span></span><!--/math--></span>, only <code>src/node11.c</code> qualifies at height 0, after two entries are already open. The result is exactly two leaves.</p>

  <figure class="diagram">
<div class="diagram-body">
<svg role="img" aria-labelledby="fig-6-caption" xmlns='http://www.w3.org/2000/svg' class="pikchr" width="438" height="710" viewBox="0 0 535.68 867.686">
<text x="267" y="12" text-anchor="middle" fill="rgb(83,97,106)" font-size="108%" dominant-baseline="central">APPENDIX A VECTOR   k=3   MIN=2   MAX=32</text>
<text x="267" y="44" text-anchor="middle" fill="rgb(83,97,106)" font-size="108%" dominant-baseline="central">hashes abbreviated only in this figure</text>
<path d="M67,154L468,154A2 2 0 0 0 470 152L470,62A2 2 0 0 0 468 59L67,59A2 2 0 0 0 64 62L64,152A2 2 0 0 0 67 154Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="267" y="85" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">TREE CHECK-IN MANIFEST</text>
<text x="267" y="107" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">95fb...c509</text>
<text x="267" y="129" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">S-RUN EXCERPT; OTHER CARDS OMITTED</text>
<path d="M67,203L468,203A2 2 0 0 0 470 201L470,157A2 2 0 0 0 468 154L67,154A2 2 0 0 0 64 157L64,201A2 2 0 0 0 67 203Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="267" y="179" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">S README.md -&gt; aa9d...0acf</text>
<path d="M67,252L468,252A2 2 0 0 0 470 250L470,206A2 2 0 0 0 468 203L67,203A2 2 0 0 0 64 206L64,250A2 2 0 0 0 67 252Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="267" y="228" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">S src/node11.c -&gt; a50a...f52ea</text>
<path d="M4,424L255,424A2 2 0 0 0 257 421L257,337A2 2 0 0 0 255 334L4,334A2 2 0 0 0 1 337L1,421A2 2 0 0 0 4 424Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="129" y="357" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">LEAF SUBMANIFEST</text>
<text x="129" y="379" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">aa9d...0acf</text>
<text x="129" y="401" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">key README.md</text>
<path d="M4,473L255,473A2 2 0 0 0 257 470L257,426A2 2 0 0 0 255 424L4,424A2 2 0 0 0 1 426L1,470A2 2 0 0 0 4 473Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="129" y="448" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">F README.md   zbits 1</text>
<path d="M4,522L255,522A2 2 0 0 0 257 519L257,475A2 2 0 0 0 255 473L4,473A2 2 0 0 0 1 475L1,519A2 2 0 0 0 4 522Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="129" y="497" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">F src/main.c  zbits 0</text>
<path d="M4,571L255,571A2 2 0 0 0 257 568L257,524A2 2 0 0 0 255 522L4,522A2 2 0 0 0 1 524L1,568A2 2 0 0 0 4 571Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="129" y="546" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">Z  bdbb...53f1</text>
<path d="M280,424L531,424A2 2 0 0 0 534 421L534,337A2 2 0 0 0 531 334L280,334A2 2 0 0 0 277 337L277,421A2 2 0 0 0 280 424Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="406" y="357" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">LEAF SUBMANIFEST</text>
<text x="406" y="379" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">a50a...f52ea</text>
<text x="406" y="401" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">key src/node11.c</text>
<path d="M280,473L531,473A2 2 0 0 0 534 470L534,426A2 2 0 0 0 531 424L280,424A2 2 0 0 0 277 426L277,470A2 2 0 0 0 280 473Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="406" y="448" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">F src/node11.c  zbits 8</text>
<path d="M280,522L531,522A2 2 0 0 0 534 519L534,475A2 2 0 0 0 531 473L280,473A2 2 0 0 0 277 475L277,519A2 2 0 0 0 280 522Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="406" y="497" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">F src/util.c    zbits 0</text>
<path d="M280,571L531,571A2 2 0 0 0 534 568L534,524A2 2 0 0 0 531 522L280,522A2 2 0 0 0 277 524L277,568A2 2 0 0 0 280 571Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="406" y="546" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">Z  87c5...4a6e</text>
<polygon points="129,334 124,329 128,327" style="fill:rgb(83,97,106)"/>
<path d="M65,203L128,331"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<polygon points="406,334 408,327 412,331" style="fill:rgb(83,97,106)"/>
<path d="M470,252L408,332"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<path d="M48,720L487,720A2 2 0 0 0 489 718L489,619A2 2 0 0 0 487 617L48,617A2 2 0 0 0 46 619L46,718A2 2 0 0 0 48 720Z"  style="fill:rgb(244,232,226);stroke-width:0.864;stroke:rgb(139,74,50);" />
<text x="267" y="647" text-anchor="middle" fill="rgb(139,74,50)" font-size="108%" dominant-baseline="central">height 1: src/node11.c has zbits 8</text>
<text x="267" y="668" text-anchor="middle" fill="rgb(139,74,50)" font-size="108%" dominant-baseline="central">8 &gt;= 2k = 6</text>
<text x="267" y="690" text-anchor="middle" fill="rgb(139,74,50)" font-size="108%" dominant-baseline="central">but open group has 1 node &lt; MIN = 2</text>
<path d="M129,571L46,617"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<path d="M406,571L488,617"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<path d="M48,866L487,866A2 2 0 0 0 489 863L489,765A2 2 0 0 0 487 762L48,762A2 2 0 0 0 46 765L46,863A2 2 0 0 0 48 866Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="267" y="792" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">split suppressed -&gt; one group</text>
<text x="267" y="814" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">manifest names both leaves directly</text>
<text x="267" y="836" text-anchor="middle" fill="rgb(41,51,58)" font-size="108%" dominant-baseline="central">no interior node</text>
<polygon points="267,762 265,755 270,755" style="fill:rgb(139,74,50)"/>
<path d="M267,720L267,759"  style="fill:none;stroke-width:0.864;stroke:rgb(139,74,50);" />
</svg>
</div>
<figcaption id="fig-6-caption">Figure 6. The completed Appendix A vector produces two leaves. At height 1 the <span class="math" data-tex="2k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>2</mn><mi>k</mi></mrow><annotation encoding="application/x-tex">2k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span> predicate qualifies, but MIN suppresses the split, so the manifest names both leaves directly and no interior node is needed.</figcaption>
<details class="pikchr-source"><summary>Pikchr source</summary><pre><code>scale = 0.82
fill = 0xffffff
color = 0x29333a
thickness = 0.006
fontscale = 1.08
boxrad = 0.018

# RFC Appendix A.1-A.3, rendered top-to-bottom. Hashes are explicitly
# abbreviated only for this illustration.
text "APPENDIX A VECTOR   k=3   MIN=2   MAX=32" at 0,0 color 0x53616a
text "hashes abbreviated only in this figure" at 0,-0.22 color 0x53616a
MH: box "TREE CHECK-IN MANIFEST" "95fb...c509" "S-RUN EXCERPT; OTHER CARDS OMITTED" wid 2.82 ht 0.66 fill 0xe7eef2 at 0,-0.66
MS1: box "S README.md -&gt; aa9d...0acf" wid 2.82 ht 0.34 fill 0xe7eef2 at MH.x,MH.s.y - 0.17
MS2: box "S src/node11.c -&gt; a50a...f52ea" same at MH.x,MS1.s.y - 0.17

L1H: box "LEAF SUBMANIFEST" "aa9d...0acf" "key README.md" wid 1.78 ht 0.62 fill 0xf3f4f3 at -0.96,MS2.s.y - 0.88
L1A: box "F README.md   zbits 1" wid 1.78 ht 0.34 fill 0xf7f8f7 at L1H.x,L1H.s.y - 0.17
L1B: box "F src/main.c  zbits 0" same at L1H.x,L1A.s.y - 0.17
L1Z: box "Z  bdbb...53f1" same fill 0xe7eef2 at L1H.x,L1B.s.y - 0.17
L2H: box "LEAF SUBMANIFEST" "a50a...f52ea" "key src/node11.c" wid 1.78 ht 0.62 fill 0xf3f4f3 at 0.96,L1H.y
L2A: box "F src/node11.c  zbits 8" wid 1.78 ht 0.34 fill 0xf7f8f7 at L2H.x,L2H.s.y - 0.17
L2B: box "F src/util.c    zbits 0" same at L2H.x,L2A.s.y - 0.17
L2Z: box "Z  87c5...4a6e" same fill 0xe7eef2 at L2H.x,L2B.s.y - 0.17
arrow from MS1.sw to L1H.n color 0x53616a
arrow from MS2.se to L2H.n color 0x53616a

T: box "height 1: src/node11.c has zbits 8" "8 &gt;= 2k = 6" "but open group has 1 node &lt; MIN = 2" wid 3.08 ht 0.72 fill 0xf4e8e2 color 0x8b4a32 at 0,L1Z.s.y - 0.68
line from L1Z.s to T.nw color 0x53616a
line from L2Z.s to T.ne color 0x53616a
R: box "split suppressed -&gt; one group" "manifest names both leaves directly" "no interior node" wid 3.08 ht 0.72 fill 0xe7eef2 at 0,T.s.y - 0.65
arrow from T.s to R.n color 0x8b4a32</code></pre></details>
</figure>

  <p>This small vector stops with the manifest's two <code>S</code> cards because the height-1 scan yields one group. Larger file sets can yield multiple groups at that level; those groups become interior, <code>S</code>-only sub-manifests, and the same scan repeats upward.</p>

  <h3>What changing k tends to change</h3>

  <p>The predicate qualifies a filename with probability about <span class="math" data-tex="2^{-k}"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mn>2</mn><mrow><mo>−</mo><mi>k</mi></mrow></msup></mrow><annotation encoding="application/x-tex">2^{-k}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8491em;"></span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8491em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span></span></span></span></span></span></span></span></span><!--/math--></span>, so the unconstrained expected group size is about <span class="math" data-tex="2^k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mn>2</mn><mi>k</mi></msup></mrow><annotation encoding="application/x-tex">2^k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8491em;"></span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8491em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span></span></span></span></span></span></span></span><!--/math--></span> members. <code>MIN</code> and <code>MAX</code> remain corrective limits, not a promise that every group falls above a universal lower bound: final merging, degenerate scans, and the actual filename hashes still determine the concrete tree.</p>

  <figure class="diagram">
<div class="diagram-body">
<svg role="img" aria-labelledby="fig-7-caption" xmlns='http://www.w3.org/2000/svg' class="pikchr" width="478" height="578" viewBox="0 0 583.35 705.168">
<text x="258" y="12" text-anchor="middle" fill="rgb(83,97,106)" font-size="110%" dominant-baseline="central">EXPECTED SCALE AS k CHANGES</text>
<path d="M10,143L166,143A2 2 0 0 0 168 140L168,51A2 2 0 0 0 166 48L10,48A2 2 0 0 0 7 51L7,140A2 2 0 0 0 10 143Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="88" y="73" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">k=1</text>
<text x="88" y="96" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">expected ~2</text>
<text x="88" y="118" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">MIN=0  MAX=8</text>
<path d="M188,97L275,97A2 2 0 0 0 278 94L278,51A2 2 0 0 0 275 48L188,48A2 2 0 0 0 186 51L186,94A2 2 0 0 0 188 97Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="232" y="73" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">leaf group</text>
<path d="M289,97L376,97A2 2 0 0 0 379 94L379,51A2 2 0 0 0 376 48L289,48A2 2 0 0 0 286 51L286,94A2 2 0 0 0 289 97Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="332" y="73" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">leaf group</text>
<path d="M390,97L477,97A2 2 0 0 0 479 94L479,51A2 2 0 0 0 477 48L390,48A2 2 0 0 0 387 51L387,94A2 2 0 0 0 390 97Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="433" y="73" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">leaf group</text>
<path d="M491,97L578,97A2 2 0 0 0 580 94L580,51A2 2 0 0 0 578 48L491,48A2 2 0 0 0 488 51L488,94A2 2 0 0 0 491 97Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="534" y="73" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">leaf group</text>
<text x="364" y="120" text-anchor="middle" fill="rgb(83,97,106)" font-size="110%" dominant-baseline="central">more small leaves / references</text>
<text x="364" y="143" text-anchor="middle" fill="rgb(83,97,106)" font-size="110%" dominant-baseline="central">often more levels</text>
<path d="M10,287L166,287A2 2 0 0 0 168 284L168,195A2 2 0 0 0 166 192L10,192A2 2 0 0 0 7 195L7,284A2 2 0 0 0 10 287Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="88" y="217" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">k=3</text>
<text x="88" y="240" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">expected ~8</text>
<text x="88" y="262" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">MIN=2  MAX=32</text>
<path d="M207,241L314,241A2 2 0 0 0 317 238L317,195A2 2 0 0 0 314 192L207,192A2 2 0 0 0 204 195L204,238A2 2 0 0 0 207 241Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="260" y="217" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">leaf group</text>
<path d="M328,241L435,241A2 2 0 0 0 438 238L438,195A2 2 0 0 0 435 192L328,192A2 2 0 0 0 325 195L325,238A2 2 0 0 0 328 241Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="381" y="217" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">leaf group</text>
<path d="M449,241L556,241A2 2 0 0 0 559 238L559,195A2 2 0 0 0 556 192L449,192A2 2 0 0 0 446 195L446,238A2 2 0 0 0 449 241Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="502" y="217" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">leaf group</text>
<text x="373" y="276" text-anchor="middle" fill="rgb(83,97,106)" font-size="110%" dominant-baseline="central">middle scale</text>
<path d="M10,431L166,431A2 2 0 0 0 168 428L168,339A2 2 0 0 0 166 336L10,336A2 2 0 0 0 7 339L7,428A2 2 0 0 0 10 431Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="88" y="361" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">k=5</text>
<text x="88" y="384" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">expected ~32</text>
<text x="88" y="406" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">MIN=8  MAX=128</text>
<path d="M229,385L517,385A2 2 0 0 0 520 382L520,339A2 2 0 0 0 517 336L229,336A2 2 0 0 0 226 339L226,382A2 2 0 0 0 229 385Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="373" y="361" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">larger expected leaf group</text>
<text x="373" y="408" text-anchor="middle" fill="rgb(83,97,106)" font-size="110%" dominant-baseline="central">fewer larger leaves / references</text>
<text x="373" y="431" text-anchor="middle" fill="rgb(83,97,106)" font-size="110%" dominant-baseline="central">often fewer levels</text>
<path d="M31,582L484,582A2 2 0 0 0 487 580L487,478A2 2 0 0 0 484 476L31,476A2 2 0 0 0 29 478L29,580A2 2 0 0 0 31 582Z"  style="fill:rgb(244,232,226);stroke-width:0.864;stroke:rgb(139,74,50);" />
<text x="258" y="507" text-anchor="middle" fill="rgb(139,74,50)" font-size="110%" dominant-baseline="central">EXPECTATION, NOT A GUARANTEE</text>
<text x="258" y="529" text-anchor="middle" fill="rgb(139,74,50)" font-size="110%" dominant-baseline="central">for a fixed filename set, results may be</text>
<text x="258" y="551" text-anchor="middle" fill="rgb(139,74,50)" font-size="110%" dominant-baseline="central">non-monotonic as k changes</text>
<path d="M31,703L484,703A2 2 0 0 0 487 701L487,622A2 2 0 0 0 484 620L31,620A2 2 0 0 0 29 622L29,701A2 2 0 0 0 31 703Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="258" y="650" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">lookup tradeoff</text>
<text x="258" y="673" text-anchor="middle" fill="rgb(41,51,58)" font-size="110%" dominant-baseline="central">fewer levels  &lt;-&gt;  more bytes in the chosen leaf</text>
</svg>
</div>
<figcaption id="fig-7-caption">Figure 7. Increasing k raises expected group size and usually reduces leaf count, references, and depth, while making each selected leaf larger; these are expectations, not monotonic guarantees for a fixed filename set.</figcaption>
<details class="pikchr-source"><summary>Pikchr source</summary><pre><code>scale = 0.82
fill = 0xffffff
color = 0x29333a
thickness = 0.006
fontscale = 1.10
boxrad = 0.018

text "EXPECTED SCALE AS k CHANGES" at 0,0 color 0x53616a

K1: box "k=1" "expected ~2" "MIN=0  MAX=8" wid 1.12 ht 0.66 fill 0xf7f8f7 at -1.18,-0.58
A1: box "leaf group" wid 0.64 ht 0.34 fill 0xe7eef2 at -0.18,K1.y + 0.16
A2: box "leaf group" same at A1.x + 0.70,A1.y
A3: box "leaf group" same at A2.x + 0.70,A2.y
A4: box "leaf group" same at A3.x + 0.70,A3.y
text "more small leaves / references" "often more levels" at 0.74,K1.y - 0.25 color 0x53616a

K3: box "k=3" "expected ~8" "MIN=2  MAX=32" wid 1.12 ht 0.66 fill 0xf7f8f7 at K1.x,K1.y - 1.00
B1: box "leaf group" wid 0.78 ht 0.34 fill 0xe7eef2 at 0.02,K3.y + 0.16
B2: box "leaf group" same at B1.x + 0.84,B1.y
B3: box "leaf group" same at B2.x + 0.84,B2.y
text "middle scale" at 0.80,K3.y - 0.25 color 0x53616a

K5: box "k=5" "expected ~32" "MIN=8  MAX=128" wid 1.12 ht 0.66 fill 0xf7f8f7 at K1.x,K3.y - 1.00
C1: box "larger expected leaf group" wid 2.04 ht 0.34 fill 0xe7eef2 at 0.80,K5.y + 0.16
text "fewer larger leaves / references" "often fewer levels" at 0.80,K5.y - 0.25 color 0x53616a

N: box "EXPECTATION, NOT A GUARANTEE" "for a fixed filename set, results may be" "non-monotonic as k changes" wid 3.18 ht 0.74 fill 0xf4e8e2 color 0x8b4a32 at 0,K5.s.y - 0.68
T: box "lookup tradeoff" "fewer levels  &lt;-&gt;  more bytes in the chosen leaf" wid 3.18 ht 0.58 fill 0xe7eef2 at 0,N.s.y - 0.55</code></pre></details>
</figure>

  <p>That is the lookup tradeoff: a higher <span class="math" data-tex="k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span> often means fewer levels to traverse but more bytes in the final leaf, while a lower <span class="math" data-tex="k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span> often means smaller leaves reached through more references. This tendency motivates parameter selection; it does not alter reader correctness.</p>

  <p>Two stop conditions prevent degenerate upper levels:</p>

  <ul>
    <li>If the scan, after the merge, yields a <em>single</em> group, the writer disregards it and emits one <code>S</code> card per height-<code>(h-1)</code> node in the manifest itself. Construction terminates.</li>
    <li>If the scan yields <em>as many groups as it was given nodes</em> — a non-reducing scan that would make every node a one-child parent — the same thing happens.</li>
  </ul>

  <p>Otherwise each group becomes an interior sub-manifest carrying one <code>S</code> card per member node, in key order, and a <code>Z</code> card, and the process repeats at height <span class="math" data-tex="h+1"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>h</mi><mo>+</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">h+1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7778em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">h</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1</span></span></span></span><!--/math--></span>.</p>

  <p>A third stop condition is a hard cap: construction must not create a node of height 16. If the scan at <span class="math" data-tex="h=16"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>h</mi><mo>=</mo><mn>16</mn></mrow><annotation encoding="application/x-tex">h=16</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">h</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">16</span></span></span></span><!--/math--></span> yields more than one group after the merge, the writer emits one <code>S</code> card per height-15 node in the manifest. This keeps every canonical tree within the reader-side depth bound.</p>

  <h3>Canonical emission</h3>

  <p><strong>Rule.</strong> Each <code>S</code> filename is the canonical encoding of the child's key. Its hash is the child's artifact name under the repository hash policy.</p>

  <p><strong>Example.</strong> Canonical encoding uses minimal escaping: <code>SP</code> becomes <code>\s</code>, <code>LF</code> becomes <code>\n</code>, and backslash becomes <code>\\</code>. TAB, CR, VT, FF, NUL, and every other octet are emitted raw; escaping any of them is non-canonical.</p>

  <p><strong>Consequence.</strong> The same encoding rule applies to <code>S</code>-card filenames and to the filename and old-name tokens of leaf <code>F</code> cards. Decoding restores the original decoded name exactly.</p>

  <p>Sub-manifest artifacts are stored before the manifest that references them. Content-addressed storage deduplicates any node identical to one already stored. A writer may reuse a subtree of a parent check-in's tree provided the resulting artifacts are byte-identical to those of the full construction — reuse is position-dependent under the group limits.</p>

  <h3>Parameter selection</h3>

  <p>Selection is advisory, not canonical. It begins from the repository's current check-in — the parent of the next commit — resolved once. If there is no current check-in, automatic selection has no result. For each <span class="math" data-tex="k \in \{1,\ldots,12\}"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi><mo>∈</mo><mo stretchy="false">{</mo><mn>1</mn><mo separator="true">,</mo><mo>…</mo><mo separator="true">,</mo><mn>12</mn><mo stretchy="false">}</mo></mrow><annotation encoding="application/x-tex">k \in \{1,\ldots,12\}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7335em;vertical-align:-0.0391em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">∈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">{</span><span class="mord">1</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner">…</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">12</span><span class="mclose">}</span></span></span></span><!--/math--></span>, the implementation performs the full construction under the SHA3 hash policy. A value is a candidate only when its height-0 scan yields at least two groups; in other words, only when it actually produces a tree.</p>

  <p>For each candidate, let <span class="math" data-tex="L_i"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>L</mi><mi>i</mi></msub></mrow><annotation encoding="application/x-tex">L_i</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">L</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><!--/math--></span> be the complete serialized byte length of leaf artifact <span class="math" data-tex="i"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>i</mi></mrow><annotation encoding="application/x-tex">i</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6595em;"></span><span class="mord mathnormal">i</span></span></span></span><!--/math--></span>, including every card and terminating LF. Let <span class="math" data-tex="S"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>S</mi></mrow><annotation encoding="application/x-tex">S</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span></span></span></span><!--/math--></span> be the sum of complete serialized byte lengths, including LF, of every <code>S</code> card that names a leaf. Then:</p>

  <div class="math-block"><span class="math display" data-tex="C(k)=S+\frac{\sum_i L_i^2}{\sum_i L_i}"><!--math--><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>C</mi><mo stretchy="false">(</mo><mi>k</mi><mo stretchy="false">)</mo><mo>=</mo><mi>S</mi><mo>+</mo><mfrac><mrow><munder><mo>∑</mo><mi>i</mi></munder><msubsup><mi>L</mi><mi>i</mi><mn>2</mn></msubsup></mrow><mrow><munder><mo>∑</mo><mi>i</mi></munder><msub><mi>L</mi><mi>i</mi></msub></mrow></mfrac></mrow><annotation encoding="application/x-tex">C(k)=S+\frac{\sum_i L_i^2}{\sum_i L_i}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:2.4895em;vertical-align:-0.9857em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.5038em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop"><span class="mop op-symbol small-op" style="position:relative;top:0em;">∑</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.162em;"><span style="top:-2.4003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2997em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathnormal">L</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.6897em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop"><span class="mop op-symbol small-op" style="position:relative;top:0em;">∑</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.162em;"><span style="top:-2.4003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2997em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathnormal">L</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-2.4413em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2587em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.9857em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span><!--/math--></span></div>

  <p>An implementation should choose the lowest-cost candidate and must choose the lowest <span class="math" data-tex="k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span> among candidates with equal minimum cost. If no value is a candidate, automatic selection has no result, and an implementation must not silently substitute a value. An operator may supply any value from 1 to 12 inclusive; any pinned value yields conforming artifacts.</p>

  <p>The analyzed optimum tracks roughly half the base-2 logarithm of the file count. The cost function's second term is a size-weighted mean leaf size, so oversized leaves weigh more heavily in the choice. It is an optimization objective, not a safety bound: the hard <code>MAX</code> group limit and the depth cap are what bound tree shape and lookup work.</p>

  <h3>Re-tuning</h3>

  <p>The pinned parameter may be changed at any time; a re-tune is <em>forward-only</em>. The first commit after the change is constructed under the new value. No history is rewritten, and a reader must not behave differently across the change.</p>

  <p>Sharing across a re-tune survives exactly for nodes whose full recursive construction remains byte-identical. Repositories pinned to different values exchange artifacts correctly — differing values can reduce sharing but do not prevent it. So propagation of <span class="math" data-tex="k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span> need be reliable only for cost, not for correctness.</p>

  <p>Because a re-tune can reduce sharing and increase the artifact bytes introduced by the next commit, it must be manual and operator-initiated. Automatic re-tuning requires the <code>submanifest-auto-retune</code> project-scope setting — default false, and it must not be inferred from repository state. When enabled, the writer retains the current value unless the candidate is distinct and:</p>

  <div class="math-block"><span class="math display" data-tex="C_{\mathrm{candidate}} \lt (1-m)C_{\mathrm{current}},\qquad 0 \lt m \lt 1"><!--math--><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>C</mi><mrow><mi mathvariant="normal">c</mi><mi mathvariant="normal">a</mi><mi mathvariant="normal">n</mi><mi mathvariant="normal">d</mi><mi mathvariant="normal">i</mi><mi mathvariant="normal">d</mi><mi mathvariant="normal">a</mi><mi mathvariant="normal">t</mi><mi mathvariant="normal">e</mi></mrow></msub><mo>&lt;</mo><mo stretchy="false">(</mo><mn>1</mn><mo>−</mo><mi>m</mi><mo stretchy="false">)</mo><msub><mi>C</mi><mrow><mi mathvariant="normal">c</mi><mi mathvariant="normal">u</mi><mi mathvariant="normal">r</mi><mi mathvariant="normal">r</mi><mi mathvariant="normal">e</mi><mi mathvariant="normal">n</mi><mi mathvariant="normal">t</mi></mrow></msub><mo separator="true">,</mo><mspace width="2em"/><mn>0</mn><mo>&lt;</mo><mi>m</mi><mo>&lt;</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">C_{\mathrm{candidate}} \lt (1-m)C_{\mathrm{current}},\qquad 0 \lt m \lt 1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathrm mtight">candidate</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&lt;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord">1</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">m</span><span class="mclose">)</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathrm mtight">current</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:2em;"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">0</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&lt;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.5782em;vertical-align:-0.0391em;"></span><span class="mord mathnormal">m</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&lt;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1</span></span></span></span></span><!--/math--></span><p>for an implementation-defined margin <span class="math" data-tex="m"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>m</mi></mrow><annotation encoding="application/x-tex">m</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">m</span></span></span></span><!--/math--></span></p></div>

  <h3>Consequences</h3>

  <p>Between the limits, group sizes follow the geometric distribution of the boundary test with mean <span class="math" data-tex="2^k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mn>2</mn><mi>k</mi></msup></mrow><annotation encoding="application/x-tex">2^k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8491em;"></span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8491em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span></span></span></span></span></span></span></span><!--/math--></span> members. Tree depth grows logarithmically, base <span class="math" data-tex="2^k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mn>2</mn><mi>k</mi></msup></mrow><annotation encoding="application/x-tex">2^k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8491em;"></span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8491em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span></span></span></span></span></span></span></span><!--/math--></span>.</p>

  <p>An insertion or deletion can cascade — a boundary decision depends on the group preceding it — but the next natural boundary preceded by at least <code>MIN</code> members re-synchronizes the scan. Editing a file's <em>content</em> changes one leaf's contents without changing the partition; that leaf's ancestor hashes and the check-in manifest necessarily change as the new hash propagates to the root.</p>

  <p>Measured on eight Fossil-hosted repositories (28 to 2,215 files, analyzed <span class="math" data-tex="2 \le k \le 5"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>2</mn><mo>≤</mo><mi>k</mi><mo>≤</mo><mn>5</mn></mrow><annotation encoding="application/x-tex">2 \le k \le 5</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7804em;vertical-align:-0.136em;"></span><span class="mord">2</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8304em;vertical-align:-0.136em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">5</span></span></span></span><!--/math--></span>), expected bytes read to resolve one file dropped by a factor of 2.2 to 19.9 versus flat <code>F</code> card bytes. Exhaustive insertion and deletion sweeps changed 0.96 to 1.38 leaves on average, worst observed 3. There is no rebalance operation: every boundary is decided by the one-pass scan at construction. The construction is that of Merkle Search Trees with the standard content-defined-chunking group-limit corrective.</p>
</section>

<!-- ================= CHAPTER 4 ================= -->
<section class="chapter" id="ch4">
  <div class="chapter-num">4 — Reading</div>
  <h2>Tree reconstruction</h2>

  <p>A top-level <code>MANIFEST</code> is a tree manifest if and only if it contains an <code>S</code> card. Every referenced node must be resolvable; each is parsed when the walk encounters it. Consumers that need the complete file set must use that walk, which also enforces canonical ordering across every <code>S</code> and <code>F</code> run.</p>

  <h3>The walk</h3>

  <p>Enumeration is a depth-first, in-order walk over the <code>S</code> references, maintaining a stack of frames — one per artifact being read, each with a cursor over that artifact's <code>S</code> run — and a running value <code>LAST</code>, the decoded filename most recently emitted.</p>

  <figure class="diagram">
<div class="diagram-body">
<svg role="img" aria-labelledby="fig-8-caption" xmlns='http://www.w3.org/2000/svg' class="pikchr" width="473" height="643" viewBox="0 0 577.401 785.203">
<text x="281" y="11" text-anchor="middle" fill="rgb(83,97,106)" font-size="102%" dominant-baseline="central">DEPTH-FIRST WALK   one S card at a time</text>
<text x="79" y="33" text-anchor="middle" fill="rgb(83,97,106)" font-size="102%" dominant-baseline="central">TREE CHECK-IN</text>
<text x="79" y="53" text-anchor="middle" fill="rgb(83,97,106)" font-size="102%" dominant-baseline="central">MANIFEST S card</text>
<text x="281" y="50" text-anchor="middle" fill="rgb(83,97,106)" font-size="102%" dominant-baseline="central">resolved artifact</text>
<text x="483" y="50" text-anchor="middle" fill="rgb(83,97,106)" font-size="102%" dominant-baseline="central">validation state</text>
<path d="M4,181L155,181A2 2 0 0 0 157 179L157,103A2 2 0 0 0 155 101L4,101A2 2 0 0 0 2 103L2,179A2 2 0 0 0 4 181Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="79" y="131" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">1   S aardvark</text>
<text x="79" y="151" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">a17...</text>
<path d="M187,214L375,214A2 2 0 0 0 378 212L378,70A2 2 0 0 0 375 67L187,67A2 2 0 0 0 185 70L185,212A2 2 0 0 0 187 214Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="281" y="100" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">LEAF SUBMANIFEST</text>
<text x="281" y="120" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">F aardvark</text>
<text x="281" y="141" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">F bee</text>
<text x="281" y="161" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">F cat</text>
<text x="281" y="182" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">Z final</text>
<path d="M405,201L561,201A2 2 0 0 0 563 199L563,83A2 2 0 0 0 561 80L405,80A2 2 0 0 0 402 83L402,199A2 2 0 0 0 405 201Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="483" y="110" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">type valid</text>
<text x="483" y="131" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">F run sorted</text>
<text x="483" y="151" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">first F = S key</text>
<text x="483" y="172" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">emit 3; LAST = cat</text>
<polygon points="185,141 178,143 178,138" style="fill:rgb(83,97,106)"/>
<path d="M157,141L181,141"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<polygon points="402,141 395,143 395,138" style="fill:rgb(83,97,106)"/>
<path d="M378,141L399,141"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<text x="281" y="246" text-anchor="middle" fill="rgb(63,117,80)" font-size="102%" dominant-baseline="central">cross-leaf check:  cat &lt; dog  (next S key)</text>
<path d="M4,380L155,380A2 2 0 0 0 157 377L157,302A2 2 0 0 0 155 299L4,299A2 2 0 0 0 2 302L2,377A2 2 0 0 0 4 380Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="79" y="329" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">2   S dog</text>
<text x="79" y="350" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">b28...</text>
<path d="M187,413L375,413A2 2 0 0 0 378 410L378,269A2 2 0 0 0 375 266L187,266A2 2 0 0 0 185 269L185,410A2 2 0 0 0 187 413Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="281" y="298" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">LEAF SUBMANIFEST</text>
<text x="281" y="319" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">F dog</text>
<text x="281" y="340" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">F elephant</text>
<text x="281" y="360" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">F elk</text>
<text x="281" y="381" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">Z final</text>
<path d="M405,400L561,400A2 2 0 0 0 563 397L563,282A2 2 0 0 0 561 279L405,279A2 2 0 0 0 402 282L402,397A2 2 0 0 0 405 400Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="483" y="309" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">type valid</text>
<text x="483" y="329" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">F run sorted</text>
<text x="483" y="350" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">first F = S key</text>
<text x="483" y="370" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">emit 3; LAST = elk</text>
<polygon points="185,340 178,342 178,337" style="fill:rgb(83,97,106)"/>
<path d="M157,340L181,340"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<polygon points="402,340 395,342 395,337" style="fill:rgb(83,97,106)"/>
<path d="M378,340L399,340"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<text x="281" y="445" text-anchor="middle" fill="rgb(63,117,80)" font-size="102%" dominant-baseline="central">cross-leaf check:  elk &lt; goose  (next S key)</text>
<path d="M4,579L155,579A2 2 0 0 0 157 576L157,501A2 2 0 0 0 155 498L4,498A2 2 0 0 0 2 501L2,576A2 2 0 0 0 4 579Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="79" y="528" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">3   S goose</text>
<text x="79" y="549" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">c36...</text>
<path d="M187,612L375,612A2 2 0 0 0 378 609L378,467A2 2 0 0 0 375 465L187,465A2 2 0 0 0 185 467L185,609A2 2 0 0 0 187 612Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="281" y="518" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">LEAF SUBMANIFEST</text>
<text x="281" y="538" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">F goose</text>
<text x="281" y="559" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">Z final</text>
<path d="M405,599L561,599A2 2 0 0 0 563 596L563,480A2 2 0 0 0 561 478L405,478A2 2 0 0 0 402 480L402,596A2 2 0 0 0 405 599Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="483" y="507" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">type valid</text>
<text x="483" y="528" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">F run sorted</text>
<text x="483" y="549" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">first F = S key</text>
<text x="483" y="569" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">emit 1; LAST = goose</text>
<polygon points="185,538 178,541 178,536" style="fill:rgb(83,97,106)"/>
<path d="M157,538L181,538"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<polygon points="402,538 395,541 395,536" style="fill:rgb(83,97,106)"/>
<path d="M378,538L399,538"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<path d="M19,737L543,737A2 2 0 0 0 546 734L546,647A2 2 0 0 0 543 645L19,645A2 2 0 0 0 16 647L16,734A2 2 0 0 0 19 737Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="281" y="681" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">CONCATENATED F-CARD ENUMERATION</text>
<text x="281" y="701" text-anchor="middle" fill="rgb(41,51,58)" font-size="102%" dominant-baseline="central">aardvark &lt; bee &lt; cat &lt; dog &lt; elephant &lt; elk &lt; goose</text>
<polygon points="545,646 538,644 541,639" style="fill:rgb(83,97,106)"/>
<path d="M483,599L543,644"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<text x="281" y="773" text-anchor="middle" fill="rgb(83,97,106)" font-size="102%" dominant-baseline="central">LAST after each leaf:  cat  -&gt;  elk  -&gt;  goose</text>
</svg>
</div>
<figcaption id="fig-8-caption">Figure 8. Visit the sorted S run sequentially. Each reference must resolve to a SUBMANIFEST leaf whose first F filename equals the S key; each local F run is sorted, the first filename of each later leaf exceeds LAST, and the runs concatenate into the check-in's file enumeration. Artifact hashes are abbreviated.</figcaption>
<details class="pikchr-source"><summary>Pikchr source</summary><pre><code>scale = 0.82
fill = 0xffffff
color = 0x29333a
thickness = 0.006
fontscale = 1.02
boxrad = 0.018

# A sequential trace of the depth-first walk. Each row resolves one S card,
# validates the leaf, emits its sorted F run, and advances LAST before the
# next S card is visited. Artifact hashes are deliberately abbreviated.
text "DEPTH-FIRST WALK   one S card at a time" at 0,0.90 color 0x53616a
text "TREE CHECK-IN" "MANIFEST S card" at -1.40,0.68 color 0x53616a
text "resolved artifact" at 0,0.63 color 0x53616a
text "validation state" at 1.40,0.63 color 0x53616a

S1: box "1   S aardvark" "a17..." wid 1.08 ht 0.56 fill 0xe7eef2 at -1.40,0
L1: box "LEAF SUBMANIFEST" "F aardvark" "F bee" "F cat" "Z final" wid 1.34 ht 1.02 fill 0xf7f8f7 at 0,S1.y
V1: box "type valid" "F run sorted" "first F = S key" "emit 3; LAST = cat" wid 1.12 ht 0.84 fill 0xe7eef2 at 1.40,S1.y
arrow from S1.e to L1.w color 0x53616a
arrow from L1.e to V1.w color 0x53616a

text "cross-leaf check:  cat &lt; dog  (next S key)" at 0,L1.s.y - 0.22 color 0x3f7550

S2: box "2   S dog" "b28..." wid 1.08 ht 0.56 fill 0xe7eef2 at S1.x,S1.y - 1.38
L2: box "LEAF SUBMANIFEST" "F dog" "F elephant" "F elk" "Z final" wid 1.34 ht 1.02 fill 0xf7f8f7 at L1.x,S2.y
V2: box "type valid" "F run sorted" "first F = S key" "emit 3; LAST = elk" wid 1.12 ht 0.84 fill 0xe7eef2 at V1.x,S2.y
arrow from S2.e to L2.w color 0x53616a
arrow from L2.e to V2.w color 0x53616a

text "cross-leaf check:  elk &lt; goose  (next S key)" at 0,L2.s.y - 0.22 color 0x3f7550

S3: box "3   S goose" "c36..." wid 1.08 ht 0.56 fill 0xe7eef2 at S2.x,S2.y - 1.38
L3: box "LEAF SUBMANIFEST" "F goose" "Z final" wid 1.34 ht 1.02 fill 0xf7f8f7 at L2.x,S3.y
V3: box "type valid" "F run sorted" "first F = S key" "emit 1; LAST = goose" wid 1.12 ht 0.84 fill 0xe7eef2 at V2.x,S3.y
arrow from S3.e to L3.w color 0x53616a
arrow from L3.e to V3.w color 0x53616a

O: box "CONCATENATED F-CARD ENUMERATION" "aardvark &lt; bee &lt; cat &lt; dog &lt; elephant &lt; elk &lt; goose" wid 3.68 ht 0.64 fill 0xe7eef2 at 0,L3.s.y - 0.55
arrow from V3.s to O.ne color 0x53616a
text "LAST after each leaf:  cat  -&gt;  elk  -&gt;  goose" at 0,O.s.y - 0.25 color 0x53616a</code></pre></details>
</figure>

  <p>Step by step:</p>

  <ol>
    <li>Initialize the stack with one frame over the manifest's <code>S</code> run, and <code>LAST</code> as the absent value, which sorts before any present value.</li>
    <li>If the top frame's cursor is exhausted, pop the frame. When the popped frame is the manifest's, enumeration terminates.</li>
    <li>Otherwise read the cursor's <code>S</code> card. Its decoded filename must compare strictly greater than <code>LAST</code>. Resolve its hash.</li>
    <li>If the resolved sub-manifest is a <strong>leaf</strong>, emit its <code>F</code> cards in order. The first emitted decoded filename must be byte-identical to the <code>S</code> card's decoded filename, and every emitted filename must compare strictly greater than <code>LAST</code>, updating <code>LAST</code> as it is emitted. Then advance the referencing frame's cursor.</li>
    <li>If the resolved sub-manifest is <strong>interior</strong>, its first <code>S</code> card's decoded filename must be byte-identical to the referencing card's decoded filename. Advance the referencing frame's cursor, push a frame over the resolved sub-manifest's <code>S</code> run.</li>
  </ol>

  <p>Four violations at step 3 <em>must</em> cause the enumeration to fail:</p>

  <ul>
    <li>The referenced artifact is absent.</li>
    <li>The referenced artifact is not of type <code>SUBMANIFEST</code>.</li>
    <li>The referenced artifact is the artifact of any frame currently on the stack.</li>
    <li>The referenced artifact's depth exceeds 16 — counting sub-manifest artifacts on the reference path from the manifest down to and including the resolved artifact, leaves included.</li>
  </ul>

  <p>That third check is the cycle rejection, and together with the depth bound it is the entire defense against a reference cycle in attacker-suppliable artifacts. Downstream consumers — the <code>R</code> card computation, file-difference walks, checkout — must not distinguish a walked enumeration from a flat or delta one.</p>

  <h3>Resolution discipline</h3>

  <p><code>S</code> card hashes are resolved lazily, on first need, under the same two disciplines as the <code>B</code> card baseline:</p>

  <ul>
    <li><strong>Hard failure.</strong> The enumeration entry points — beginning the tree walk, and single-name lookup — must treat an unresolvable sub-manifest as a fatal error.</li>
    <li><strong>Soft failure.</strong> Consumers that can defer, such as post-parse relationship processing, may record the gap keyed by the dependent artifact and the missing hash, inserted idempotently, report failure to the caller, and retry when the sub-manifest arrives.</li>
  </ul>

  <p>An implementation must not present a partially resolved tree as a complete file set. There is no degraded partial enumeration analogous to the delta-only degradation the artifact specification cautions against.</p>

  <h3>Single-name lookup</h3>

  <p>Resolving one filename against a tree manifest never requires the full walk. Binary-search the <code>S</code> run for the last card whose decoded filename compares less than or equal to the target; if no such card exists the name is absent. Otherwise resolve that card's sub-manifest and repeat — the <code>S</code> run of an interior node, the <code>F</code> run of a leaf — until a leaf either contains the name or proves it absent.</p>

  <figure class="diagram">
<div class="diagram-body">
<svg role="img" aria-labelledby="fig-9-caption" xmlns='http://www.w3.org/2000/svg' class="pikchr" width="489" height="461" viewBox="0 0 597.658 563.04">
<text x="252" y="28" text-anchor="middle" fill="rgb(83,97,106)" dominant-baseline="central">SINGLE-NAME LOOKUP</text>
<path d="M364,56L592,56A2 2 0 0 0 595 53L595,4A2 2 0 0 0 592 1L364,1A2 2 0 0 0 362 4L362,53A2 2 0 0 0 364 56Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="478" y="28" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">target filename  ·  elephant</text>
<text x="82" y="89" text-anchor="middle" fill="rgb(83,97,106)" dominant-baseline="central">TREE CHECK-IN</text>
<text x="82" y="109" text-anchor="middle" fill="rgb(83,97,106)" dominant-baseline="central">MANIFEST S cards</text>
<path d="M189,178L316,178A2 2 0 0 0 318 175L318,126A2 2 0 0 0 316 123L189,123A2 2 0 0 0 186 126L186,175A2 2 0 0 0 189 178Z"  style="fill:rgb(244,232,226);stroke-width:1.584;stroke:rgb(139,74,50);" />
<text x="252" y="151" text-anchor="middle" fill="rgb(139,74,50)" dominant-baseline="central">S  dog  b28...</text>
<path d="M19,178L183,178A2 2 0 0 0 186 175L186,126A2 2 0 0 0 183 123L19,123A2 2 0 0 0 16 126L16,175A2 2 0 0 0 19 178Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="101" y="151" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">S  aardvark  a17...</text>
<path d="M321,178L471,178A2 2 0 0 0 474 175L474,126A2 2 0 0 0 471 123L321,123A2 2 0 0 0 318 126L318,175A2 2 0 0 0 321 178Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="396" y="151" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">S  goose  c36...</text>
<polygon points="252,123 263,115 266,124" style="fill:rgb(139,74,50)"/>
<path d="M478,56L258,122"  style="fill:none;stroke-width:1.584;stroke:rgb(139,74,50);" />
<path d="M138,306L366,306A2 2 0 0 0 369 304L369,231A2 2 0 0 0 366 228L138,228A2 2 0 0 0 136 231L136,304A2 2 0 0 0 138 306Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="252" y="257" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">LEAF SUBMANIFEST</text>
<text x="252" y="277" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">b28...</text>
<polygon points="252,228 247,216 257,216" style="fill:rgb(139,74,50)"/>
<path d="M252,178L252,222"  style="fill:none;stroke-width:1.584;stroke:rgb(139,74,50);" />
<text x="428" y="203" text-anchor="middle" fill="rgb(139,74,50)" dominant-baseline="central">last S key &lt;= target</text>
<path d="M138,349L366,349A2 2 0 0 0 369 347L369,309A2 2 0 0 0 366 306L138,306A2 2 0 0 0 136 309L136,347A2 2 0 0 0 138 349Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="252" y="328" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">F  dog</text>
<path d="M138,393L366,393A2 2 0 0 0 369 390L369,352A2 2 0 0 0 366 349L138,349A2 2 0 0 0 136 352L136,390A2 2 0 0 0 138 393Z"  style="fill:rgb(244,232,226);stroke-width:1.584;stroke:rgb(139,74,50);" />
<text x="252" y="371" text-anchor="middle" fill="rgb(139,74,50)" dominant-baseline="central">F  elephant</text>
<path d="M138,436L366,436A2 2 0 0 0 369 433L369,395A2 2 0 0 0 366 393L138,393A2 2 0 0 0 136 395L136,433A2 2 0 0 0 138 436Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="252" y="414" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">F  elk</text>
<path d="M138,479L366,479A2 2 0 0 0 369 476L369,438A2 2 0 0 0 366 436L138,436A2 2 0 0 0 136 438L136,476A2 2 0 0 0 138 479Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="252" y="457" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">Z  ...  (final card)</text>
<text x="252" y="514" text-anchor="middle" fill="rgb(63,117,80)" dominant-baseline="central">FOUND  ·  one SUBMANIFEST read at this level</text>
<text x="252" y="551" text-anchor="middle" fill="rgb(83,97,106)" dominant-baseline="central">one referenced artifact per level  ·  hard depth limit 16</text>
</svg>
</div>
<figcaption id="fig-9-caption">Figure 9. For “elephant,” binary search selects the S card keyed by “dog,” the last key not greater than the target, resolves its referenced leaf SUBMANIFEST, and searches that artifact's sorted F-card rows.</figcaption>
<details class="pikchr-source"><summary>Pikchr source</summary><pre><code>scale = 0.82
fill = 0xffffff
color = 0x29333a
thickness = 0.006
fontscale = 1.0
boxrad = 0.018

text "SINGLE-NAME LOOKUP" at 0,0.48 color 0x53616a
Q: box "target filename  ·  elephant" wid 1.62 ht 0.38 fill 0xf7f8f7

text "TREE CHECK-IN" "MANIFEST S cards" at -1.18,Q.s.y - 0.30 color 0x53616a
S2: box "S  dog  b28..." wid 0.92 ht 0.38 fill 0xf4e8e2 color 0x8b4a32 thickness 0.011 at 0,Q.s.y - 0.66
S1: box "S  aardvark  a17..." wid 1.18 ht 0.38 fill 0xf7f8f7 with .e at S2.w
S3: box "S  goose  c36..." wid 1.08 ht 0.38 fill 0xf7f8f7 color 0x29333a thickness 0.006 with .w at S2.e
arrow from Q.s to S2.n color 0x8b4a32 thickness 0.011

LH: box "LEAF SUBMANIFEST" "b28..." wid 1.62 ht 0.54 fill 0xe7eef2 at 0,S2.s.y - 0.62
arrow from S2.s to LH.n color 0x8b4a32 thickness 0.011
text "last S key &lt;= target" at 1.22,(S2.s.y + LH.n.y)/2 color 0x8b4a32

F1: box "F  dog" wid 1.62 ht 0.30 fill 0xf7f8f7 at 0,LH.s.y - 0.15
F2: box "F  elephant" same fill 0xf4e8e2 color 0x8b4a32 thickness 0.011 at 0,F1.s.y - 0.15
F3: box "F  elk" same fill 0xf7f8f7 color 0x29333a thickness 0.006 at 0,F2.s.y - 0.15
FZ: box "Z  ...  (final card)" same fill 0xe7eef2 at 0,F3.s.y - 0.15

text "FOUND  ·  one SUBMANIFEST read at this level" at 0,FZ.s.y - 0.24 color 0x3f7550
text "one referenced artifact per level  ·  hard depth limit 16" at 0,FZ.s.y - 0.50 color 0x53616a</code></pre></details>
</figure>

  <p>The depth bound must be enforced here too: a lookup that would resolve a sub-manifest at depth greater than 16 must fail hard. In particular, without this check a crafted deep spine turns an unauthenticated lookup endpoint into unbounded sequential artifact reads. At most one sub-manifest per tree level, hence at most 16 per lookup, is read.</p>

  <h3>Composition with delta manifests</h3>

  <p>A tree manifest carries no <code>B</code> card, so it is a baseline manifest under the existing predicate. Therefore a delta manifest <em>may</em> name a tree manifest in its <code>B</code> card. Full enumeration composes the two walks: the baseline cursor of the merge-walk becomes the enumeration above, and the merge-walk itself is unchanged.</p>

  <p>One writer-side precaution remains even after tree emission is turned off: a writer <strong>should not create a new delta manifest whose baseline is a tree manifest</strong>. Existing immutable deltas remain readable through the composed walk; the recommendation prevents disabled writers from introducing new dependencies on tree baselines.</p>

  <figure class="diagram">
<div class="diagram-body">
<svg role="img" aria-labelledby="fig-10-caption" xmlns='http://www.w3.org/2000/svg' class="pikchr" width="411" height="343" viewBox="0 0 502.099 419.04">
<path d="M9,76L237,76A2 2 0 0 0 240 73L240,4A2 2 0 0 0 237 1L9,1A2 2 0 0 0 6 4L6,73A2 2 0 0 0 9 76Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="123" y="28" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">TREE CHECK-IN MANIFEST</text>
<text x="123" y="48" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">baseline file set</text>
<path d="M282,67L447,67A2 2 0 0 0 450 65L450,12A2 2 0 0 0 447 10L282,10A2 2 0 0 0 280 12L280,65A2 2 0 0 0 282 67Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="365" y="38" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">in-order cursor</text>
<polygon points="280,38 273,41 273,36" style="fill:rgb(83,97,106)"/>
<path d="M240,38L276,38"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<path d="M9,223L237,223A2 2 0 0 0 240 220L240,150A2 2 0 0 0 237 148L9,148A2 2 0 0 0 6 150L6,220A2 2 0 0 0 9 223Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="123" y="175" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">DELTA MANIFEST</text>
<text x="123" y="195" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">delta F run</text>
<path d="M275,226L454,226A2 2 0 0 0 457 223L457,148A2 2 0 0 0 454 145L275,145A2 2 0 0 0 273 148L273,223A2 2 0 0 0 275 226Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="365" y="175" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">EXISTING DELTA</text>
<text x="365" y="195" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">merge walk</text>
<polygon points="365,145 362,138 367,138" style="fill:rgb(83,97,106)"/>
<path d="M365,67L365,141"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<text x="365" y="106" text-anchor="middle" fill="rgb(83,97,106)" dominant-baseline="central">baseline cursor</text>
<polygon points="273,185 266,188 266,183" style="fill:rgb(83,97,106)"/>
<path d="M240,185L269,185"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<text x="123" y="249" text-anchor="middle" fill="rgb(83,97,106)" dominant-baseline="central">delta entries</text>
<path d="M232,345L498,345A2 2 0 0 0 500 343L500,287A2 2 0 0 0 498 285L232,285A2 2 0 0 0 229 287L229,343A2 2 0 0 0 232 345Z"  style="fill:rgb(243,244,243);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="365" y="315" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">effective sorted file set</text>
<polygon points="365,285 362,278 367,278" style="fill:rgb(83,97,106)"/>
<path d="M365,226L365,281"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<text x="244" y="387" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">tree enumeration supplies the cursor</text>
<text x="244" y="407" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">delta precedence is unchanged</text>
</svg>
</div>
<figcaption id="fig-10-caption">Figure 10. The tree walk supplies the sorted baseline cursor consumed by the existing delta-manifest merge; its precedence rules do not change.</figcaption>
<details class="pikchr-source"><summary>Pikchr source</summary><pre><code>scale = 0.82
fill = 0xffffff
color = 0x29333a
thickness = 0.006
fontscale = 1.0
boxrad = 0.018

T: box "TREE CHECK-IN MANIFEST" "baseline file set" wid 1.62 ht 0.52 fill 0xe7eef2
C: box "in-order cursor" wid 1.18 ht 0.40 fill 0xf7f8f7 at T.x + 1.68, T.y
arrow from T.e to C.w color 0x53616a

D: box "DELTA MANIFEST" "delta F run" wid 1.62 ht 0.52 fill 0xf7f8f7 at T.x, T.s.y - 0.76
M: box "EXISTING DELTA" "merge walk" wid 1.28 ht 0.56 fill 0xe7eef2 at C.x, D.y
arrow from C.s to M.n color 0x53616a "baseline cursor"
arrow from D.e to M.w color 0x53616a
text "delta entries" at D.x, D.s.y - 0.18 color 0x53616a

E: box "effective sorted file set" wid 1.88 ht 0.42 fill 0xf3f4f3 at M.x, M.s.y - 0.62
arrow from M.s to E.n color 0x53616a
text "tree enumeration supplies the cursor" "delta precedence is unchanged" at (T.x + C.x)/2, E.s.y - 0.36 color 0x29333a</code></pre></details>
</figure>

  <p>Single-name lookup follows the artifact specification's delta lookup procedure with the section 5.3 lookup as the baseline-search step. One writer-side constraint applies: while <code>enable-submanifests</code> is in force, a writer must not create a delta manifest at all. The setting overrides the seen-delta-manifest latch.</p>

  <h3>Checksums still cover the same file set</h3>

  <p>Indirection does not weaken the check-in checksum. A tree manifest's <code>R</code> card is computed over the complete walk enumeration — tracked files in canonical filename order — and then uses Fossil's existing MD5 aggregate algorithm byte for byte. A delta manifest whose baseline is a tree obtains the baseline side of that calculation from the same walk.</p>

  <p>There are two useful verification views. Given a checkout, a verifier can recompute <code>R</code> from the checked-out files and compare it with the root manifest without traversing the sub-manifest tree. A repository verifier can instead walk the tree and check each file hash. That walk is simpler than delta expansion: leaves are contiguous sorted slices, so their <code>F</code> runs concatenate in <code>S</code>-card order; there is no two-cursor merge and no deletion-marker precedence.</p>

  <p>Every sub-manifest's <code>Z</code> card detects accidental corruption in that node's stripped card stream. The artifact's content hash — not <code>Z</code> — cryptographically names and binds its complete stored bytes.</p>

  <h3>Memoization</h3>

  <p>Implementations <em>should</em> cache per-sub-manifest enumeration results keyed by artifact name. Because unchanged subtrees are shared by hash across check-ins, a memoizing rebuild parses each distinct sub-manifest once — converting rebuild cost from the sum of full file-set sizes to the sum of distinct-artifact sizes.</p>

  <div class="callout"><strong>This is the point.</strong> A naive implementation obtains the byte reduction of this format but not the parse-count reduction that motivated it. On the 103k-file case, the bytes were never the whole problem — the hours of rebuild parsing were.</div>
</section>

<!-- ================= CHAPTER 5 ================= -->
<section class="chapter" id="ch5">
  <div class="chapter-num">5 — Deployment</div>
  <h2>Sync, phantoms, and the flag day</h2>

  <p>Sub-manifests are ordinary artifacts for every discovery and transfer purpose: announced by <code>igot</code>, requested by <code>gimme</code>, carried by <code>file</code> and <code>cfile</code>, and included in cluster artifacts through <code>M</code> cards. No wire-format change exists. The requirements are about state and sequencing.</p>

  <h3>Phantoms and convergence</h3>

  <p>Storing a structural artifact carrying <code>S</code> cards whose referenced sub-manifests are absent must create a phantom for each missing reference, with the referencing artifact's privacy classification — exactly as an absent delta source creates a source phantom.</p>

  <p>The subtlety is timing. An interior sub-manifest's own <code>S</code> references become known <em>only when it arrives</em>. So a tree of depth <span class="math" data-tex="d"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi></mrow><annotation encoding="application/x-tex">d</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">d</span></span></span></span><!--/math--></span> converges in at most <span class="math" data-tex="d+1"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi><mo>+</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">d+1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7778em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">d</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1</span></span></span></span><!--/math--></span> pull rounds, not one, because each round reveals one more level of the frontier.</p>

  <figure class="diagram">
<div class="diagram-body">
<svg role="img" aria-labelledby="fig-11-caption" xmlns='http://www.w3.org/2000/svg' class="pikchr" width="497" height="621" viewBox="0 0 607.104 758.88">
<text x="300" y="11" text-anchor="middle" fill="rgb(83,97,106)" dominant-baseline="central">PHANTOM FRONTIER EXPANSION   depth-2 example</text>
<text x="81" y="51" text-anchor="middle" fill="rgb(83,97,106)" dominant-baseline="central">pull round</text>
<text x="300" y="51" text-anchor="middle" fill="rgb(83,97,106)" dominant-baseline="central">store, dephantomize, then parse</text>
<text x="519" y="51" text-anchor="middle" fill="rgb(83,97,106)" dominant-baseline="central">new S frontier</text>
<path d="M10,185L152,185A2 2 0 0 0 154 183L154,99A2 2 0 0 0 152 96L10,96A2 2 0 0 0 8 99L8,183A2 2 0 0 0 10 185Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="81" y="131" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">ROUND 1</text>
<text x="81" y="151" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">receive root</text>
<path d="M183,207L417,207A2 2 0 0 0 419 204L419,77A2 2 0 0 0 417 74L183,74A2 2 0 0 0 180 77L180,204A2 2 0 0 0 183 207Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="300" y="110" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">TREE CHECK-IN MANIFEST</text>
<text x="300" y="131" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">a0...</text>
<text x="300" y="151" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">S aardvark  b1...</text>
<text x="300" y="171" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">S goose     c2...</text>
<path d="M435,195L603,195A2 2 0 0 0 605 193L605,88A2 2 0 0 0 603 86L435,86A2 2 0 0 0 432 88L432,193A2 2 0 0 0 435 195Z"  style="fill:rgb(255,255,255);stroke-width:0.864;stroke:rgb(139,74,50);stroke-dasharray:7.2,7.2;" />
<text x="519" y="120" text-anchor="middle" fill="rgb(139,74,50)" dominant-baseline="central">create phantoms</text>
<text x="519" y="141" text-anchor="middle" fill="rgb(139,74,50)" dominant-baseline="central">{ b1..., c2... }</text>
<text x="519" y="161" text-anchor="middle" fill="rgb(139,74,50)" dominant-baseline="central">privacy copied</text>
<polygon points="180,141 173,143 173,138" style="fill:rgb(83,97,106)"/>
<path d="M154,141L177,141"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<polygon points="432,141 425,143 425,138" style="fill:rgb(139,74,50)"/>
<path d="M419,141L429,141"  style="fill:none;stroke-width:0.864;stroke:rgb(139,74,50);" />
<text x="300" y="241" text-anchor="middle" fill="rgb(83,97,106)" dominant-baseline="central">next pull: gimme b1... and c2... together</text>
<path d="M10,372L152,372A2 2 0 0 0 154 370L154,286A2 2 0 0 0 152 283L10,283A2 2 0 0 0 8 286L8,370A2 2 0 0 0 10 372Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="81" y="318" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">ROUND 2</text>
<text x="81" y="338" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">receive frontier</text>
<path d="M183,388L417,388A2 2 0 0 0 419 386L419,270A2 2 0 0 0 417 267L183,267A2 2 0 0 0 180 270L180,386A2 2 0 0 0 183 388Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="300" y="308" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">INTERIOR SUBMANIFESTS</text>
<text x="300" y="328" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">b1...  c2...</text>
<text x="300" y="348" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">parse both S runs</text>
<path d="M435,383L603,383A2 2 0 0 0 605 380L605,276A2 2 0 0 0 603 273L435,273A2 2 0 0 0 432 276L432,380A2 2 0 0 0 435 383Z"  style="fill:rgb(255,255,255);stroke-width:0.864;stroke:rgb(139,74,50);stroke-dasharray:7.2,7.2;" />
<text x="519" y="308" text-anchor="middle" fill="rgb(139,74,50)" dominant-baseline="central">create phantoms</text>
<text x="519" y="328" text-anchor="middle" fill="rgb(139,74,50)" dominant-baseline="central">{ d3..., e4..., f5... }</text>
<text x="519" y="348" text-anchor="middle" fill="rgb(139,74,50)" dominant-baseline="central">privacy copied</text>
<polygon points="180,328 173,330 173,325" style="fill:rgb(83,97,106)"/>
<path d="M154,328L177,328"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<polygon points="432,328 425,330 425,325" style="fill:rgb(139,74,50)"/>
<path d="M419,328L429,328"  style="fill:none;stroke-width:0.864;stroke:rgb(139,74,50);" />
<text x="300" y="423" text-anchor="middle" fill="rgb(83,97,106)" dominant-baseline="central">next pull: gimme the whole leaf frontier</text>
<path d="M10,560L152,560A2 2 0 0 0 154 557L154,473A2 2 0 0 0 152 470L10,470A2 2 0 0 0 8 473L8,557A2 2 0 0 0 10 560Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="81" y="505" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">ROUND 3</text>
<text x="81" y="525" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">= d + 1 here</text>
<path d="M183,576L417,576A2 2 0 0 0 419 573L419,457A2 2 0 0 0 417 455L183,455A2 2 0 0 0 180 457L180,573A2 2 0 0 0 183 576Z"  style="fill:rgb(247,248,247);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="300" y="495" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">LEAF SUBMANIFESTS</text>
<text x="300" y="515" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">d3...  e4...  f5...</text>
<text x="300" y="535" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">F runs; no S cards</text>
<path d="M435,570L603,570A2 2 0 0 0 605 567L605,463A2 2 0 0 0 603 460L435,460A2 2 0 0 0 432 463L432,567A2 2 0 0 0 435 570Z"  style="fill:rgb(232,241,234);stroke-width:0.864;stroke:rgb(63,117,80);" />
<text x="519" y="495" text-anchor="middle" fill="rgb(63,117,80)" dominant-baseline="central">S frontier empty</text>
<text x="519" y="515" text-anchor="middle" fill="rgb(63,117,80)" dominant-baseline="central">tree closure present</text>
<text x="519" y="535" text-anchor="middle" fill="rgb(63,117,80)" dominant-baseline="central">retry dependents</text>
<polygon points="180,515 173,518 173,512" style="fill:rgb(83,97,106)"/>
<path d="M154,515L177,515"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<polygon points="432,515 425,518 425,512" style="fill:rgb(63,117,80)"/>
<path d="M419,515L429,515"  style="fill:none;stroke-width:0.864;stroke:rgb(63,117,80);" />
<path d="M27,711L572,711A2 2 0 0 0 575 708L575,616A2 2 0 0 0 572 613L27,613A2 2 0 0 0 25 616L25,708A2 2 0 0 0 27 711Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="300" y="652" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">GENERAL BOUND</text>
<text x="300" y="672" text-anchor="middle" fill="rgb(41,51,58)" dominant-baseline="central">manifest round + d sub-manifest levels = at most d + 1 rounds</text>
<polygon points="574,614 567,611 570,607" style="fill:rgb(83,97,106)"/>
<path d="M519,570L571,612"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<text x="300" y="747" text-anchor="middle" fill="rgb(83,97,106)" dominant-baseline="central">already-present artifacts can make it fewer; width does not add rounds</text>
</svg>
</div>
<figcaption id="fig-11-caption">Figure 11. In this depth-2 example, parsing the root creates the interior-node frontier; parsing that entire frontier creates the leaf frontier; and parsing all leaves closes the sub-manifest tree in round 3. In general, a depth-<span class="math" data-tex="d"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi></mrow><annotation encoding="application/x-tex">d</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">d</span></span></span></span><!--/math--></span> tree needs at most <span class="math" data-tex="d+1"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi><mo>+</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">d+1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7778em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">d</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1</span></span></span></span><!--/math--></span> pull rounds. Artifact hashes are abbreviated.</figcaption>
<details class="pikchr-source"><summary>Pikchr source</summary><pre><code>scale = 0.82
fill = 0xffffff
color = 0x29333a
thickness = 0.006
fontscale = 1.00
boxrad = 0.018

# A concrete depth-2 frontier expansion. All hashes are abbreviated.
# Dashed rust boxes are newly created phantoms; the green box is closure.
text "PHANTOM FRONTIER EXPANSION   depth-2 example" at 0,0.90 color 0x53616a
text "pull round" at -1.52,0.62 color 0x53616a
text "store, dephantomize, then parse" at 0,0.62 color 0x53616a
text "new S frontier" at 1.52,0.62 color 0x53616a

R1: box "ROUND 1" "receive root" wid 1.02 ht 0.62 fill 0xe7eef2 at -1.52,0
A1: box "TREE CHECK-IN MANIFEST" "a0..." "S aardvark  b1..." "S goose     c2..." wid 1.66 ht 0.92 fill 0xf7f8f7 at 0,R1.y
F1: box "create phantoms" "{ b1..., c2... }" "privacy copied" wid 1.20 ht 0.76 fill 0xffffff color 0x8b4a32 dashed at 1.52,R1.y
arrow from R1.e to A1.w color 0x53616a
arrow from A1.e to F1.w color 0x8b4a32

text "next pull: gimme b1... and c2... together" at 0,A1.s.y - 0.24 color 0x53616a

R2: box "ROUND 2" "receive frontier" wid 1.02 ht 0.62 fill 0xe7eef2 at R1.x,R1.y - 1.30
A2: box "INTERIOR SUBMANIFESTS" "b1...  c2..." "parse both S runs" wid 1.66 ht 0.84 fill 0xf7f8f7 at A1.x,R2.y
F2: box "create phantoms" "{ d3..., e4..., f5... }" "privacy copied" wid 1.20 ht 0.76 fill 0xffffff color 0x8b4a32 dashed at F1.x,R2.y
arrow from R2.e to A2.w color 0x53616a
arrow from A2.e to F2.w color 0x8b4a32

text "next pull: gimme the whole leaf frontier" at 0,A2.s.y - 0.24 color 0x53616a

R3: box "ROUND 3" "= d + 1 here" wid 1.02 ht 0.62 fill 0xe7eef2 at R2.x,R2.y - 1.30
A3: box "LEAF SUBMANIFESTS" "d3...  e4...  f5..." "F runs; no S cards" wid 1.66 ht 0.84 fill 0xf7f8f7 at A2.x,R3.y
F3: box "S frontier empty" "tree closure present" "retry dependents" wid 1.20 ht 0.76 fill 0xe8f1ea color 0x3f7550 at F2.x,R3.y
arrow from R3.e to A3.w color 0x53616a
arrow from A3.e to F3.w color 0x3f7550

G: box "GENERAL BOUND" "manifest round + d sub-manifest levels = at most d + 1 rounds" wid 3.82 ht 0.68 fill 0xe7eef2 at 0,A3.s.y - 0.60
arrow from F3.s to G.ne color 0x53616a
text "already-present artifacts can make it fewer; width does not add rounds" at 0,G.s.y - 0.25 color 0x53616a</code></pre></details>
</figure>

  <p>A frontier may contain many artifacts and the next pull may request them together, so the bound is controlled by tree depth, not tree width. “Tree closure present” means every reachable <code>SUBMANIFEST</code> is available; file-content blobs named by leaf <code>F</code> cards have their own ordinary phantom state.</p>

  <p>Dephantomization of a sub-manifest must retry the deferred processing of every recorded dependent. This connects to a specific implementation trap — see <a href="#ch6">chapter 6</a>.</p>

  <h3>Crosslink behavior</h3>

  <p>Crosslinking a tree manifest requires its full enumeration wherever crosslinking a flat manifest requires the <code>F</code> card list. When a referenced sub-manifest is absent, the deferred-processing discipline for a missing delta baseline applies unchanged: record the gap keyed by dependent and missing hash, idempotently, and retry on arrival.</p>

  <p>A <code>SUBMANIFEST</code> artifact has <em>no direct crosslink effect</em>. It carries no state-bearing card and contributes to repository state only through the manifests whose trees contain it. Its arrival triggers retry of recorded dependents and nothing else.</p>

  <h3>The peer-version gate</h3>

  <p>When the remote peer's announced version predates sub-manifest support and a local artifact is of type <code>SUBMANIFEST</code>, or is a manifest carrying an <code>S</code> card, the sender must <strong>refuse to transmit</strong> that artifact — with an explicit error, never by converting, downgrading, or silently omitting it. The rule parallels the SHA3-256 peer-version gate.</p>

  <p>A server whose <code>enable-submanifests</code> setting is false sends the pragma <code>avoid-submanifests</code>, in the same reply positions as <code>avoid-delta-manifests</code>. A client receiving it must not emit <code>S</code> cards in commits created during the session.</p>

  <h3>Why there is no fallback</h3>

  <p>Exactly 22 card letters are defined and the alphabet is <em>closed</em>. A card letter outside <code>A</code>–<code>Z</code> must be rejected; an in-range but undefined letter (<code>O</code>, <code>V</code>, <code>X</code>, <code>Y</code>) must be rejected. There is no forward-compatibility acceptance path for unknown card letters.</p>

  <p>So a sub-manifest or tree manifest is unreadable — rejected as structural, held as opaque content — by every reader conforming to the published artifact specification. Such a reader's check-in graph is <em>visibly truncated, never silently divergent</em>. That property is deliberate: a truncated graph is a loud failure, whereas a silently wrong file set would be a data-integrity event.</p>

  <h3>The settings travel as one decision</h3>

  <p><strong>Rule.</strong> <code>submanifest-k</code>, <code>enable-submanifests</code>, and <code>submanifest-auto-retune</code> travel together in the scalar <code>/project</code> configuration group. A supporting client with Read permission requests that group during pull and, after clone deferral, in the first authorized request. A clone that cannot retrieve the complete authorized group must leave emission disabled.</p>

  <p><strong>Transaction.</strong> A receiver stages all three entries through the end of the request or reply, then validates and commits them atomically, independent of card order. A supporting server recognizes all three names; pushing them requires Admin permission.</p>

  <p><strong>Failure example.</strong> An invalid value, or a final state that enables emission without <span class="math" data-tex="k \in \{1,\ldots,12\}"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi><mo>∈</mo><mo stretchy="false">{</mo><mn>1</mn><mo separator="true">,</mo><mo>…</mo><mo separator="true">,</mo><mn>12</mn><mo stretchy="false">}</mo></mrow><annotation encoding="application/x-tex">k \in \{1,\ldots,12\}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7335em;vertical-align:-0.0391em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">∈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">{</span><span class="mord">1</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner">…</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">12</span><span class="mclose">}</span></span></span></span><!--/math--></span> rejects this three-setting transaction and preserves its prior values. Other valid <code>/project</code> settings may still apply.</p>

  <p><strong>Consequence.</strong> A writer reads <code>k</code> from a successfully stored repository pin and never derives a fresh value per commit. If corrupted or hand-edited local state contains an out-of-range pin, it emits a flat manifest and should warn the operator; it must not substitute another parameter.</p>


  <h3>Three gates</h3>

  <figure class="diagram">
<div class="diagram-body">
<svg role="img" aria-labelledby="fig-12-caption" xmlns='http://www.w3.org/2000/svg' class="pikchr" width="394" height="593" viewBox="0 0 481.681 724.925">
<text x="240" y="12" text-anchor="middle" fill="rgb(83,97,106)" font-size="106%" dominant-baseline="central">1  EMISSION — BEFORE CREATION</text>
<path d="M131,105L349,105A2 2 0 0 0 352 103L352,47A2 2 0 0 0 349 45L131,45A2 2 0 0 0 129 47L129,103A2 2 0 0 0 131 105Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="240" y="75" text-anchor="middle" fill="rgb(41,51,58)" font-size="106%" dominant-baseline="central">enabled and no avoid?</text>
<path d="M25,219L219,219A2 2 0 0 0 222 216L222,161A2 2 0 0 0 219 159L25,159A2 2 0 0 0 23 161L23,216A2 2 0 0 0 25 219Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="122" y="189" text-anchor="middle" fill="rgb(41,51,58)" font-size="106%" dominant-baseline="central">yes: tree permitted</text>
<path d="M249,219L467,219A2 2 0 0 0 470 216L470,161A2 2 0 0 0 467 159L249,159A2 2 0 0 0 247 161L247,216A2 2 0 0 0 249 219Z"  style="fill:rgb(244,232,226);stroke-width:0.864;stroke:rgb(139,74,50);" />
<text x="358" y="189" text-anchor="middle" fill="rgb(139,74,50)" font-size="106%" dominant-baseline="central">no: flat or refuse enable</text>
<polygon points="122,159 121,151 126,152" style="fill:rgb(83,97,106)"/>
<path d="M129,104L123,155"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<polygon points="358,159 355,152 360,151" style="fill:rgb(139,74,50)"/>
<path d="M351,104L358,155"  style="fill:none;stroke-width:0.864;stroke:rgb(139,74,50);" />
<text x="240" y="274" text-anchor="middle" fill="rgb(83,97,106)" font-size="106%" dominant-baseline="central">2  TRANSFER — EXISTING IMMUTABLE S ARTIFACT</text>
<path d="M131,357L349,357A2 2 0 0 0 352 355L352,299A2 2 0 0 0 349 297L131,297A2 2 0 0 0 129 299L129,355A2 2 0 0 0 131 357Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="240" y="327" text-anchor="middle" fill="rgb(41,51,58)" font-size="106%" dominant-baseline="central">peer supports S?</text>
<path d="M25,471L219,471A2 2 0 0 0 222 468L222,413A2 2 0 0 0 219 411L25,411A2 2 0 0 0 23 413L23,468A2 2 0 0 0 25 471Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="122" y="441" text-anchor="middle" fill="rgb(41,51,58)" font-size="106%" dominant-baseline="central">yes: send unchanged</text>
<path d="M249,471L467,471A2 2 0 0 0 470 468L470,413A2 2 0 0 0 467 411L249,411A2 2 0 0 0 247 413L247,468A2 2 0 0 0 249 471Z"  style="fill:rgb(244,232,226);stroke-width:0.864;stroke:rgb(139,74,50);" />
<text x="358" y="441" text-anchor="middle" fill="rgb(139,74,50)" font-size="106%" dominant-baseline="central">no: refuse; never flatten</text>
<polygon points="122,411 121,403 126,404" style="fill:rgb(83,97,106)"/>
<path d="M129,356L123,407"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<polygon points="358,411 355,404 360,403" style="fill:rgb(139,74,50)"/>
<path d="M351,356L358,407"  style="fill:none;stroke-width:0.864;stroke:rgb(139,74,50);" />
<text x="240" y="526" text-anchor="middle" fill="rgb(83,97,106)" font-size="106%" dominant-baseline="central">3  PROVISIONAL REPOSITORY-USE OVERLAY</text>
<path d="M131,609L349,609A2 2 0 0 0 352 607L352,551A2 2 0 0 0 349 549L131,549A2 2 0 0 0 129 551L129,607A2 2 0 0 0 131 609Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="240" y="579" text-anchor="middle" fill="rgb(41,51,58)" font-size="106%" dominant-baseline="central">disposable test fixture?</text>
<path d="M25,723L219,723A2 2 0 0 0 222 720L222,665A2 2 0 0 0 219 663L25,663A2 2 0 0 0 23 665L23,720A2 2 0 0 0 25 723Z"  style="fill:rgb(231,238,242);stroke-width:0.864;stroke:rgb(41,51,58);" />
<text x="122" y="693" text-anchor="middle" fill="rgb(41,51,58)" font-size="106%" dominant-baseline="central">yes: test use only</text>
<path d="M249,723L467,723A2 2 0 0 0 470 720L470,665A2 2 0 0 0 467 663L249,663A2 2 0 0 0 247 665L247,720A2 2 0 0 0 249 723Z"  style="fill:rgb(244,232,226);stroke-width:0.864;stroke:rgb(139,74,50);" />
<text x="358" y="693" text-anchor="middle" fill="rgb(139,74,50)" font-size="106%" dominant-baseline="central">no: do not propagate</text>
<polygon points="122,663 121,655 126,656" style="fill:rgb(83,97,106)"/>
<path d="M129,608L123,659"  style="fill:none;stroke-width:0.864;stroke:rgb(83,97,106);" />
<polygon points="358,663 355,656 360,655" style="fill:rgb(139,74,50)"/>
<path d="M351,608L358,659"  style="fill:none;stroke-width:0.864;stroke:rgb(139,74,50);" />
</svg>
</div>
<figcaption id="fig-12-caption">Figure 12. Three related but independent decisions: emission happens before artifact creation; transfer preserves or refuses an existing immutable artifact; provisional policy limits repository use.</figcaption>
<details class="pikchr-source"><summary>Pikchr source</summary><pre><code>scale = 0.82
fill = 0xffffff
color = 0x29333a
thickness = 0.006
fontscale = 1.06
boxrad = 0.018

# Three independent compact panels. Each decision has its own input and
# outcomes; no artifact is implied to flow through all three.
text "1  EMISSION — BEFORE CREATION" at 0,0 color 0x53616a
G1: box "enabled and no avoid?" wid 1.55 ht 0.42 fill 0xe7eef2 at 0,-0.44
Y1: box "yes: tree permitted" wid 1.38 ht 0.42 fill 0xe7eef2 at G1.x - 0.82, G1.s.y - 0.58
N1: box "no: flat or refuse enable" wid 1.55 ht 0.42 fill 0xf4e8e2 color 0x8b4a32 at G1.x + 0.82, Y1.y
arrow from G1.sw to Y1.n color 0x53616a
arrow from G1.se to N1.n color 0x8b4a32

text "2  TRANSFER — EXISTING IMMUTABLE S ARTIFACT" at 0,Y1.s.y - 0.38 color 0x53616a
G2: box "peer supports S?" wid 1.55 ht 0.42 fill 0xe7eef2 at 0,Y1.s.y - 0.75
Y2: box "yes: send unchanged" wid 1.38 ht 0.42 fill 0xe7eef2 at G2.x - 0.82, G2.s.y - 0.58
N2: box "no: refuse; never flatten" wid 1.55 ht 0.42 fill 0xf4e8e2 color 0x8b4a32 at G2.x + 0.82, Y2.y
arrow from G2.sw to Y2.n color 0x53616a
arrow from G2.se to N2.n color 0x8b4a32

text "3  PROVISIONAL REPOSITORY-USE OVERLAY" at 0,Y2.s.y - 0.38 color 0x53616a
G3: box "disposable test fixture?" wid 1.55 ht 0.42 fill 0xe7eef2 at 0,Y2.s.y - 0.75
Y3: box "yes: test use only" wid 1.38 ht 0.42 fill 0xe7eef2 at G3.x - 0.82, G3.s.y - 0.58
N3: box "no: do not propagate" wid 1.55 ht 0.42 fill 0xf4e8e2 color 0x8b4a32 at G3.x + 0.82, Y3.y
arrow from G3.sw to Y3.n color 0x53616a
arrow from G3.se to N3.n color 0x8b4a32</code></pre></details>
</figure>

  <p><strong>Emission.</strong> A writer must not emit an <code>S</code> card unless <code>enable-submanifests</code> — a project-scope boolean, default false — is true, and must not emit one in a commit against a server from which it received the <code>avoid-submanifests</code> pragma in the current sync session.</p>

  <p><strong>Rule.</strong> For a local request to turn emission on, an existing valid pin <span class="math" data-tex="k \in \{1,\ldots,12\}"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi><mo>∈</mo><mo stretchy="false">{</mo><mn>1</mn><mo separator="true">,</mo><mo>…</mo><mo separator="true">,</mo><mn>12</mn><mo stretchy="false">}</mo></mrow><annotation encoding="application/x-tex">k \in \{1,\ldots,12\}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7335em;vertical-align:-0.0391em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">∈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">{</span><span class="mord">1</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner">…</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">12</span><span class="mclose">}</span></span></span></span><!--/math--></span> wins. If the request also supplies a different explicit value, the writer rejects the request atomically and directs the operator to re-tuning.</p>

  <p><strong>Example.</strong> With no valid pin, an explicit value from 1 through 12 may be stored. An explicit out-of-range value is rejected; it is not treated as omitted and does not trigger automatic selection. If the value is omitted, the writer runs automatic selection.</p>

  <p><strong>Consequence.</strong> If automatic selection has no result, the writer refuses the request atomically: <code>enable-submanifests</code> and <code>submanifest-k</code> retain their prior values, and no <code>S</code> card is emitted. Emission must never be enabled without a valid pin.</p>


  <p>Disabling the setting stops the emission of new tree manifests; it cannot retire existing ones, because artifact names are immutable. When the first <code>S</code>-bearing artifact is stored, an implementation should record a local <code>seen-submanifest</code> indicator so operational surfaces can warn before the setting is enabled or a pre-<code>S</code> client is granted access.</p>

  <p><strong>Provisional phase.</strong> Until this specification folds into a <code>-01</code> revision of the artifact format and conforming readers ship in released versions of the implementations that serve a repository's users, every repository containing an <code>S</code>-bearing artifact is a <em>disposable test fixture</em>:</p>

  <ul>
    <li>It must not be synchronized with, cloned to, or otherwise propagated into any repository that is not itself a disposable test fixture.</li>
    <li>The setting must not be enabled on any repository that any pre-<code>S</code> implementation serves.</li>
    <li>Implementers must assume any element of the specification may still change, and that a change invalidates every existing <code>S</code>-bearing artifact without migration.</li>
  </ul>

  <div class="callout"><strong>Why publication precedes implementation.</strong> The closed card alphabet makes deployment a flag day: every reader must understand sub-manifests before any writer may emit one. So the format must be fixed and covered by conformance vectors before the first writer exists. Publication is deliberately first — and conformance of one implementation establishes nothing about the wider ecosystem.</div>
</section>

<!-- ================= CHAPTER 6 ================= -->
<section class="chapter" id="ch6">
  <div class="chapter-num">6 — Implementation</div>
  <h2>The four structural traps</h2>

  <p>These requirements constrain implementation structure rather than artifact bytes. Each prevents a specific failure mode identified in structural audits of the four extant Fossil implementations.</p>

  <h3>1. Type determination without a positive signal</h3>

  <p><code>SUBMANIFEST</code> must be determined through the reassignment rule — a post-pass keyed on the <em>absence</em> of a <code>D</code> card — and an implementation must not commit to a type while cards are still being read. Required-card logic that assumes every type requires a <code>D</code> card must treat <code>SUBMANIFEST</code>, like <code>CLUSTER</code>, as an explicit exception.</p>

  <p>The failure mode: a parser that decides type incrementally will classify a sub-manifest as a manifest on the strength of its first <code>F</code> or <code>S</code> card, then hold a type that the legality table will later contradict.</p>

  <h3>2. Sweep termination</h3>

  <p>A <code>SUBMANIFEST</code> artifact never produces a derived row of its own. An implementation that discovers crosslink work by <em>negative space</em> — selecting every structural artifact for which no derived row exists — must exclude sub-manifest artifacts from the sweep, or record an explicit completion marker for them.</p>

  <p>The failure mode is a livelock: the sweep re-selects and re-parses the same sub-manifests on every pass, forever, because they never acquire the row the selection predicate is looking for.</p>

  <h3>3. The enumeration seam</h3>

  <p>Every consumer of a check-in's file set — checkout, diff, crosslink, rebuild, annotate, commit — must obtain it through <em>one shared enumeration interface</em> implementing the tree walk and its delta composition. Implementations must route every bypass site through the interface before enabling the reader, and should enforce the seam with an assertion or type distinction.</p>

  <p>A bypass site is any place that iterates a raw file-card array. Such a site either fails on tree manifests or silently diverges from the seam — and the second is worse, because it produces a plausible but incomplete file set.</p>

  <h3>4. Dephantomization of S references</h3>

  <p>Deferred-processing cascades typically know two edge kinds: delta-manifest baselines and content delta sources. The <code>S</code> reference is a <em>third</em>.</p>

  <p>An implementation must record, when a tree manifest's processing is deferred, enough to rediscover that manifest when the missing sub-manifest arrives, and its dephantomization path must traverse that record. A cascade that walks only the two older edge kinds leaves such a check-in permanently unprocessed.</p>

  <h3>Security notes</h3>

  <p>The tree walk recurses through attacker-suppliable artifacts. The depth bound and the repeated-artifact check together reject reference cycles and bound the stack. Implementations should bound total sub-manifests visited per enumeration and <em>must</em> bound it when serving unauthenticated requests — in particular, the depth bound must be enforced in single-name lookup.</p>

  <p>A phantom sub-manifest leaves a tree unresolvable, exactly as a phantom baseline leaves a delta manifest unresolvable. Shunning a sub-manifest renders <em>every</em> check-in whose tree contains it unenumerable — a strictly wider blast radius than shunning one file's content, so operators should shun sub-manifests only with the same caution as baseline manifests.</p>

  <p>The partition predicate is <strong>not</strong> an integrity mechanism. Integrity binds through <code>S</code> and <code>F</code> card hashes; SHA3-256's role in the partition is only to choose boundaries. A committer who controls filenames can shape the tree within the group limits: all-non-qualifying names partition into <code>MAX</code>-member groups once the file count exceeds <code>MAX</code>, all-qualifying names yield <code>MIN</code>-member leaves, and a name whose digest carries <span class="math" data-tex="(h+1)k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>h</mi><mo>+</mo><mn>1</mn><mo stretchy="false">)</mo><mi>k</mi></mrow><annotation encoding="application/x-tex">(h+1)k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal">h</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mclose">)</span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span> leading zero bits — findable by search at small <span class="math" data-tex="k"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span><!--/math--></span> — raises a boundary at height <span class="math" data-tex="h"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>h</mi></mrow><annotation encoding="application/x-tex">h</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">h</span></span></span></span><!--/math--></span>.</p>

  <p>The <code>MIN</code> limit suppresses the single-member spine such names would otherwise chain. At <span class="math" data-tex="k \le 2"><!--math--><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi><mo>≤</mo><mn>2</mn></mrow><annotation encoding="application/x-tex">k \le 2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8304em;vertical-align:-0.136em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">2</span></span></span></span><!--/math--></span>, where <code>MIN</code> is 1 or 0, it cannot, and the non-reducing-scan stop and height cap bound spines instead; partial single-child nodes inside otherwise-reducing levels still occur. A crafted name distorts shape, not validity. Commit privilege is required, and already suffices to write a flat manifest of arbitrary size — but repositories accepting commits from marginally trusted users should monitor artifact-count growth.</p>
</section>

<footer class="footer">
  <p><strong>Primary sources:</strong> <a href="https://fossil.craftdesign.group/zit/doc/trunk/docs/rfcs/draft-fossil-submanifests-00.txt">draft-fossil-submanifests-00</a> (D. R. Hipp, S. Beal, D. Shearer, July 2026), the original <a href="https://fossil-scm.org/home/technote/be8f2f3447ef2ea3344f8058b6733aa08c08336f">Scaling Fossil Up</a> technote, Fossil's <a href="https://fossil-scm.org/home/doc/trunk/www/fileformat.wiki">file-format description</a>, and its explanation of <a href="https://fossil-scm.org/home/doc/trunk/www/delta-manifests.md">delta manifests</a>. Normative terms and section numbers belong to the draft.</p>
  <p>The diagrams are authored in <a href="https://pikchr.org">Pikchr</a>, pre-rendered as inline SVG, and accompanied by their source. The page needs no JavaScript, web font, or network access to render.</p>
</footer>

</body>
</html>