Introduction
Firewood is an embedded key-value store optimized for storing recent Merkleized blockchain state with minimal overhead. It is built for the Avalanche C-Chain and EVM-compatible blockchains that keep state in Merkle tries.
Unlike most state-management approaches, Firewood uses the trie structure directly as its on-disk index rather than emulating a trie on top of a generic key-value store such as LevelDB or RocksDB. It is compaction-less: trie nodes are written directly to a single database file, addressed by their offset within that file.
Firewood’s documentation is organized into the following sections. Greyed-out sidebar entries are not yet written.
- Getting Started walks through setting up a development environment.
- Concepts & Architecture explains the trie storage model and revisions.
- Design Documents records the designs behind Firewood’s subsystems and how new designs are proposed and promoted.
- Integration covers the Go FFI and how AvalancheGo consumes Firewood.
- Operations & Benchmarking covers the
fwdctlCLI and performance dashboards. - Reference links to the generated Rust and Go API documentation and the benchmark dashboards.
- Meta documents the repository’s processes and how these docs are built.
Warning
Firewood is beta-level software. Its API is still evolving and stability is not yet guaranteed.
Design Documents
Firewood records its designs as living documentation in a single flat directory.
Every design is YYYY-MM-DD-slug.md: the date the design was proposed, followed by a
short slug. The date is assigned once, when the file is created, and never changes, so
a design’s path stays a stable reference in review threads and cross-links. Freshness
is a separate question, answered by git history: just design-age lists designs by
last commit date, oldest first.
A design’s lifecycle state lives in its status frontmatter field, not in its
location:
draft: a work in progress, not yet ready for review.proposed: under review, not yet built.active: describes what the code does today; kept up to date as the code evolves.superseded: replaced by a newer design; kept for history.rejected: considered and declined; kept for the record.
Note
This is a lightweight convention, not a mandatory gate. A
proposeddesign is encouraged for significant or non-obvious work, where writing it down sharpens the discussion; small or low-risk changes do not need one, and nothing blocks a pull request for lacking one. Anactivedesign may also be written after the fact to document something already built.
Designs
| Design | Proposed | Status |
|---|---|---|
| Firewood mdBook Documentation Site | 2026-06-17 | active |
Subsystems not yet documented as designs are tracked in ava-labs/firewood#2139.
Frontmatter
Every design begins with a YAML frontmatter block, stripped from the rendered site by
the in-repo frontmatter-strip preprocessor:
| Field | Required | Values |
|---|---|---|
title | yes | Human-readable design title. |
status | yes | draft, proposed, active, superseded, or rejected. |
category | yes | The Firewood subsystem(s) the design touches: storage, ffi, revision-management, hashing, proposals, docs, or tooling. A design spanning several lists them, e.g. [storage, hashing]. Extend the list as new subsystems gain designs. |
authors | yes | List of GitHub handles. |
tracking-issue | no | An owner/repo#N reference to the issue or PR tracking the work. |
There is no date field because the proposal date is in the filename.
Propose a design
- Run
just new-design <slug>to scaffoldYYYY-MM-DD-<slug>.mdfromtemplate.md. The slug is lowercase kebab-case: letters, digits, and single hyphens. - Fill in the template.
- Add a row to the Designs table and a sidebar entry in
docs/src/SUMMARY.md. - Open a pull request.
Promote a design
When a proposed design is implemented, promote it in place:
- Flip the frontmatter
status: proposedtostatus: active. - Drop the Unresolved questions section, folding any settled answers into the design. Keep Drawbacks: it records what was accepted and why.
- Rewrite future-tense prose as present tense.
- Add cross-links to the implementing pull request(s) and commits.
- Update this index’s status marker and the sidebar entry in
docs/src/SUMMARY.md.
Firewood mdBook Documentation Site
Summary
Stand up an mdBook documentation site for Firewood, deployed to GitHub Pages, and make it the front door of the published site. The generated rustdoc, Go docs, and performance benchmark dashboards become resources navigable from the book rather than the site root.
The book ships with an Introduction, a fully-authored Getting Started guide for
initializing development environments, and a Designs subsystem that establishes a
peer-reviewable, RFC-style workflow for proposing designs and promoting them to
living documentation once implemented; promotion is a status frontmatter flip,
not a file move. Additional sections (Concepts, AvalancheGo/EVM Integration,
Operations & Benchmarking, Reference, Meta) each ship an authored landing page;
not-yet-written sub-pages are omitted from the sidebar until their content lands,
rather than shipped as empty stub files.
Note
Bootstrapping note. The layout, naming convention, frontmatter schema, template, and tooling this design specifies did not exist when it was written, so it could not originally follow them. When the foundation landed it was retrofitted to its own schema and its
statusflipped toactive. Its filename carries the date it was first written, not the date of the retrofit, as for every other design.Rollout. The site lands one pull request per page. This document describes the complete design; the Acceptance criteria record which parts have landed, and a section whose landing page is still pending appears in the sidebar as a greyed-out draft chapter.
Goals
- Deploy an mdBook site to GitHub Pages with the book served at the site root.
- Demote generated rustdoc to
/rustdoc/, navigable from the book. - Keep Go docs (
/ffi/) and benchmark dashboards (/bench/,/dev/bench/) reachable from the book without relocating them. - Establish a design-doc workflow: propose → peer review → land → implement → promote to living documentation.
- Author a complete multi-environment developer setup guide.
- Make the book self-documenting: a
meta/section documents the documentation system itself and scaffolds repository-process docs (release, contributing, code review). - Scaffold remaining sections so they look intentional and are easy to expand.
Non-Goals
- Backfilling every existing design. This MVP writes exactly one fully-realized
active design (on-disk format & addressing) and tracks the rest in the backfill
issue linked from
designs/README.md. - Authoring full content for the scaffolded sections beyond an authored landing page;
their deeper sub-pages are omitted from
SUMMARY.mduntil written. - Changing how benchmark data is collected or stored (
track-performance.ymland thebenchmark-databranch are unchanged). - Custom mdBook theming beyond the preprocessor-provided assets and standard
book.tomloptions.
Background: published-site layout before this design
The previous gh-pages.yaml workflow assembled a single GitHub Pages deployment from
multiple sources:
cargo doc --document-private-items --no-depsemits one top-level directory per documented target plus sharedstatic.files/. There is no cargo-generated rootindex.html. rustdoc names each directory after the crate’s target, meaning the library name for lib crates or the[[bin]]name for bin-only crates, with hyphens converted to underscores; it does not use the workspace-member path. The workspace has nine members. The seven library crates producefirewood,firewood_storage(thestoragecrate),firewood_ffi(ffi),firewood_macros,firewood_metrics(metrics),firewood_triehash(triehash), andfirewood_replay(replayhas alib.rs, so its directory isfirewood_replay, notreplay). The two bin-only crates (fwdctl,benchmark) produce directories named after their[[bin]]targets:fwdctlandbenchmark. Runningcargo doc --document-private-items --no-depsconfirms the full set: all nine target directories above plus rustdoc’s sharedstatic.files/andsrc/(source-view) directories, and no rootindex.html. Notablyfirewood_ffi/is emitted with its ownindex.htmleven thoughfirewood-ffiiscrate-type = ["staticlib"]; rustdoc documents its[lib]regardless.- A hand-written step writes
target/doc/index.htmlas a<meta refresh>redirect to/firewood/. doc2goemits Go docs to/ffi/.- Benchmark history is fetched from the
benchmark-databranch and merged into the output at/bench/(official,main) and/dev/bench/{branch}/(experimental). - The
buildjob hardcodedref: mainon checkout; thedeployjob was gated to the canonical repo and non-pull_requestevents.
Because both rustdoc and mdBook want to own the site root (each emits index.html
and asset directories), making the book the root requires relocating cargo-doc
output into a subdirectory.
Design decisions
- Site layout: Book at site root; relocate cargo-doc output to
/rustdoc/; leave/ffi/and/bench/unchanged. - Book source location:
docs/is the mdBook root (docs/book.toml,docs/src/). Reuses the existingdocs/src/assets/architecture.svgalready referenced byREADME.md. - CI strategy: Extend the existing
gh-pages.yamlrather than add a competing workflow (a separate workflow would race over the single-source Pages artifact). - Design lifecycle: A single flat
designs/directory; lifecycle state lives in thestatusfrontmatter field, not in the directory layout. Promotion is a status flip plus a prose pass, never a file move, so a design’sYYYY-MM-DD-slug.mdpath is stable for its whole life. See Design-doc subsystem. - Backfill scope: One template + one fully-written seed (
on-disk format & addressing) + a backfill index (a GitHub issue linked fromdesigns/README.md) for the rest. - Extra sections: Concepts/Architecture, AvalancheGo/EVM Integration, Operations
& Benchmarking, Reference (generated artifacts), and Meta (self-documenting docs +
repository-process docs: release, contributing, code review). Sections with real
seed content are authored at landing-page depth. Every section ships an authored
landing page once its content lands; until then it is an mdBook draft chapter,
a link-less
SUMMARY.mdentry that renders greyed-out. Not-yet-written sub-pages are omitted fromSUMMARY.mdrather than drafted or shipped as empty stub pages. See “Scaffolding via draft chapters” below. - Preprocessors and callouts: Two preprocessors:
mdbook-mermaid(diagrams) and an in-repo frontmatter stripper (scripts/mdbook-frontmatter-strip.sh, a small jq script that removes each chapter’s YAML frontmatter so the schema metadata does not render as a stray horizontal rule and heading). mermaid’s generated JS assets are not vendored; insteadmdbook-mermaid install docsruns as part of every build (CI and the localjustfilerecipes), so there is no checked-in JS to drift. The frontmatter stripper is owned in-repo rather than pulled from a published crate (see decision 10) and needs onlyjqonPATH. Callouts use mdBook’s built-in alert syntax (> [!NOTE],> [!WARNING], …) rather than a preprocessor.Note
mdbook-admonishwas the original choice for callouts, but it requires mdBook< 0.5.0, which is incompatible with themdbook 0.5.xthatmdbook-linkcheck2requires. mdBook 0.5’s native alert syntax provides the same callouts with no preprocessor, no checked-in CSS, and no asset-drift surface, so admonish is dropped entirely. - Install commands in docs: The authored developer guide may include concrete
install commands (
rustup,cargo install,brew install, VS Code extensions). - Frontmatter with no dates: Design docs carry a small YAML frontmatter schema
(
title,status,category,authors, optionaltracking-issue), stripped from the rendered output by the in-repo frontmatter preprocessor. It has no date field: the proposal date is the filename prefix, and freshness comes from git history viajust design-age. A hand-maintained date inside a document would only drift. - In-repo frontmatter stripper (not a published crate): The stripper is a small
in-repo jq script (
scripts/mdbook-frontmatter-strip.sh). The published mdBook frontmatter preprocessors are single-maintainer crates with no prebuilt binary, so they would compile from source on every docs/Go CI run and, worse, a yank or an unmaintained crate would block every docs and Go PR with no fallback. The transform is trivial (strip a leading--- … ---block), so owning it in-repo removes that supply-chain dependency and the per-run source compile, at the cost of only ajqdependency (already on CI runners and standard developer machines).
Architecture
On-disk layout (book root = docs/)
docs/
├── book.toml # mdBook config + mermaid preprocessor + linkcheck backend
├── mermaid.min.js # generated at build time; git-ignored
├── mermaid-init.js # generated at build time; git-ignored
└── src/
├── SUMMARY.md # table of contents / sidebar
├── assets/ # architecture.svg, relocated from docs/assets/ (see note below)
├── introduction.md
├── getting-started/
│ ├── README.md # section landing
│ └── dev-environment.md # FULLY AUTHORED: macOS / Docker / remote SSH
├── concepts/
│ └── README.md # authored landing: promoted README terminology + architecture prose
├── designs/ # FLAT: every design is YYYY-MM-DD-slug.md; status lives in frontmatter
│ ├── README.md # the model, frontmatter schema, how to propose, promotion checklist, backfill issue link
│ ├── template.md # single RFC-style template carrying the frontmatter schema
│ ├── 2024-08-13-on-disk-format-and-addressing.md # the one FULLY WRITTEN seed (status: active)
│ └── 2026-06-17-mdbook-documentation-site.md # this design (status: active)
├── integration/
│ └── README.md # authored: Go API (Database/Proposal/Revision) + firewood-go-ethhash publish relationship
├── operations/
│ └── README.md # authored landing: fwdctl, benchmarks, dashboards; links benchmark/docs/* + /bench/ (sub-pages omitted until written)
├── reference/
│ └── README.md # authored landing: link-out hub for GENERATED artifacts: rustdoc, godoc, benchmarks
└── meta/
├── README.md # authored landing: about this repo & these docs
├── documentation.md # authored: how the docs work (mdbook, preprocessors, build/serve, authoring)
└── release.md # thin: links to RELEASE.md; + contributing/code-review pointers
Note
Label vocabulary in this tree: authored landing = a real
README.md/page with seed content, linked inSUMMARY.md; thin = a short real page that mostly links out; draft chapter = aSUMMARY.mdentry written with empty link parentheses (- [Title]()), which has no file and renders greyed-out (see “Scaffolding via draft chapters”). A section is a draft chapter until its landing page lands; the landing pages arrive one pull request at a time. Not-yet-written sub-pages are omitted fromSUMMARY.mduntil written.
Architecture diagram asset (relocation)
mdBook only copies non-Markdown files that live under src/ into the rendered
output. The existing docs/assets/architecture.svg therefore moves to
docs/src/assets/architecture.svg so book pages can reference it with a relative
path (./assets/architecture.svg from introduction.md/concepts/). The repo-root
README.md reference is updated from ./docs/assets/architecture.svg to
./docs/src/assets/architecture.svg. The gh-pages.yaml “Copy static assets” step
(cp -rv docs/assets target/doc/) is removed because mdBook copies the asset into
the book output itself.
Deployed site URL map
/ → mdBook landing (introduction)
/getting-started/ … → book pages
/concepts/ … → book pages
/designs/ … → book pages
/integration/ … → book pages
/operations/ … → book pages
/reference/ … → book pages
/meta/ … → book pages (about the repo & these docs)
/rustdoc/index.html → redirect to firewood
/rustdoc/firewood/ → cargo doc (relocated from /firewood/)
/rustdoc/<other-target>/ → one dir per documented target (see Background for the exact set)
/ffi/ → Go docs (doc2go, unchanged)
/bench/ , /dev/bench/… → benchmark history (unchanged)
The /rustdoc/index.html redirect targets firewood only; the other crate
directories are reachable directly and via cross-links within rustdoc. Per-crate
redirect stubs at the old /firewood/<crate>/ paths are out of scope for this
work; old external deep links 404, accepted in exchange for the cleaner root UX
(see Drawbacks).
How the book links to generated docs
The generated docs are linked from one page, reference/README.md, whose entire
content is the three link-outs; the Introduction’s section list points readers there.
They are not sidebar entries: mdBook resolves every SUMMARY.md link as a path under
src/, with no special case for URLs, so a raw-URL entry there fails the build rather
than rendering as an external sidebar item. Reaching the generated docs therefore costs
one click through Reference. The alternatives (thin link-out chapters per artifact, or
custom theme JavaScript that injects sidebar links) were both rejected as more
machinery than one click is worth.
The link-outs are fully-qualified https://ava-labs.github.io/firewood/… URLs rather
than site-absolute /firewood/… paths. Absolute URLs resolve from anywhere, including
local mdbook serve and a fork’s Pages deployment, where a site-absolute path would
404. They are also web links, so follow-web-links = false already skips them during
link checking and no per-path exclude is needed.
Design-doc subsystem
Model
One flat designs/ directory. Every design is YYYY-MM-DD-slug.md: the date the
design was proposed, followed by a short slug. The date is assigned once, when the file
is created, and never changes, so a design’s path is a permanent identifier and a
stable reference in review threads. Designs written on the same day are distinguished
by their slugs.
A date prefix is preferred over a monotonic sequence number for two reasons. It needs no coordination: two authors proposing designs concurrently pick their filenames independently, whereas both would compute the same next sequence number and collide on merge. And it is self-describing: a reader scanning the directory sees a design’s vintage without opening it or consulting git, which matters most for the oldest designs, where age is the first thing worth knowing.
Lifecycle state lives entirely in the status frontmatter field:
draft: a work in progress, not yet ready for review.proposed: under peer review, not yet built. A proposal typically lands before development so reviewers comment on the document through normal PR review, but this ordering is a recommendation, not a requirement.active: describes what the code does today (living documentation).superseded: replaced by a newer design; kept for history.rejected: considered and declined; kept for the record.
Note
This is a lightweight convention, not a mandatory gate. The intent is to make design discussion and documentation easy and welcome, not to impose RFC rigor on every change. A
proposeddesign is encouraged for significant or non-obvious work, where writing it down sharpens the discussion; small, obvious, or low-risk changes do not need one, and nothing blocks a PR for lacking one. Authors may also write anactivedesign after the fact to document something already built. The template exists to lower the cost of writing a design down, not as a checklist to satisfy. Optimize for “more designs discussed and recorded,” not “process followed.”
Frontmatter schema
Every design begins with a YAML frontmatter block, stripped from the rendered HTML by
the in-repo frontmatter preprocessor (scripts/mdbook-frontmatter-strip.sh):
---
title: On-Disk Format & Addressing
status: active
category: storage
authors: [demosdemon]
tracking-issue: ava-labs/firewood#1234 # optional
---
| Field | Required | Values |
|---|---|---|
title | yes | Human-readable design title. |
status | yes | draft, proposed, active, superseded, or rejected. |
category | yes | The Firewood subsystem(s) the design touches: storage, ffi, revision-management, hashing, proposals, docs, or tooling. A design spanning several lists them, e.g. [storage, hashing]. Extend the enum as new subsystems gain designs. |
authors | yes | List of GitHub handles. |
tracking-issue | no | An owner/repo#N reference to the issue or PR tracking the work. |
The schema carries no date field, and neither does a design’s body (see design decision 9).
Lifecycle
flowchart LR
A[Draft PR adds<br/>YYYY-MM-DD-x.md<br/>status: proposed] --> B[Peer review<br/>on the PR]
B --> C[Merge: proposal<br/>accepted, not built]
C --> D[Implementation PRs<br/>reference the proposal]
D --> E[Promotion: flip<br/>status to active]
E --> F[Living doc; updated<br/>as code evolves]
Template
A single template.md (RFC-style) carrying the frontmatter schema above and the
sections: Summary; Motivation; Guide-level explanation (optional); Detailed design;
Evaluation (optional); Drawbacks; Rationale and alternatives; Prior art (optional);
Unresolved questions; Future possibilities. Evaluation holds the evidence behind a
design: measurements, prototypes, and parameter choices, including results that rule
out an alternative. Placeholder values in the template are written in angle brackets
(<Title>). Only Unresolved questions is proposal-only: settled answers fold into the
design on promotion. Drawbacks stays, because the record of what was accepted and why
is as valuable after implementation as before it.
Promotion checklist (documented in designs/README.md)
Promotion is a status flip plus a prose pass, with no file move:
- Flip frontmatter
status: proposed→active. - Drop the Unresolved questions section, folding settled answers into the design. Keep Drawbacks.
- Rewrite future-tense prose into present tense.
- Add cross-links to the implementing PR(s)/commits.
- Update the design’s status in the
designs/README.mdindex andSUMMARY.md.
Seed design: on-disk format & addressing
Filed flat as 2024-08-13-on-disk-format-and-addressing.md; the date prefix is the
day that design was originally written, well before this documentation site existed.
Fully written (status: active) from README.md prose
plus the storage/ and firewood/src/ sources. Covers: disk-offset addressing (root address = disk offset;
branch nodes point to disk offsets), node allocation from end-of-file vs. free lists,
free-list size-class management, the future-delete log (FDL), and recoverability
guarantees (no references to new nodes before flush; careful free-list management
across revision creation/expiration).
designs/README.md backfill list
The subsystems still to be documented are tracked in
ava-labs/firewood#2139, linked
from designs/README.md rather than duplicated inline where the list would drift.
Free lists & the FDL are already covered by the on-disk-format design and are
deliberately not on the list.
CI/CD changes (gh-pages.yaml)
Output assembly moves from cargo-owned target/doc/ into a staging directory
site/ so mdBook owns root and rustdoc moves under /rustdoc/:
site/ ← uploaded as the Pages artifact
├── index.html + book pages ← copied from docs/book/html/ (see step 4)
├── rustdoc/
│ ├── index.html ← redirect → firewood
│ ├── firewood/ … ← moved from cargo doc output
│ └── <other-target>/ …
├── ffi/ ← doc2go (repointed to site/)
└── bench/ , dev/bench/… ← merged from benchmark-data branch
build job steps
-
Checkout ref. The
setup-rustaction checks out a PR-head-aware ref instead of the hardcodedref: mainthe previous workflow had:checkout-ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || 'main' }}For
push,workflow_dispatch, andworkflow_runevents this evaluates tomain(docs always deploy frommain); forpull_requestit checks out the PR head so the newdocs/**filter validates the PR’s actual doc changes. This is deliberate: aworkflow_dispatchtriggered against a non-mainbranch still builds and deploysmain’s docs, never the dispatched branch; the published site only ever reflectsmain. -
mdBook toolchain.
mdbookandmdbook-mermaidinstall through thesetup-rustaction’stoolsinput, which passes them totaiki-e/install-action(pinned by commit SHA), each pinned to an explicit version.mdbook-linkcheck2is not in thetaiki-e/install-actionmanifest, so it installs withcargo binstall --no-confirm mdbook-linkcheck2@<version>(cargo-binstallitself comes fromtaiki-e/install-action).cargo binstalldownloads a prebuilt binary when one is published for the runner target and automatically falls back tocargo installwhen no prebuilt artifact is available or the download fails (e.g. GitHub rate limiting), so no manual artifact-availability check or fallback step is required. Frontmatter stripping needs no tool install at all: it is an in-repo jq preprocessor (scripts/mdbook-frontmatter-strip.sh), andjqis preinstalled on the runners. All three installed mdBook-toolchain versions (mdbook,mdbook-mermaid,mdbook-linkcheck2) are pinned and recorded. Because the mermaid assets are installed fresh from the pinned binary at build time (step 3) rather than vendored, there is no asset/binary version coupling to track by hand. -
Preprocessor assets (not committed).
mdbook-mermaid install docsruns before the build. The JS is generated fresh from the pinned binary on every build and the output paths are git-ignored, so there is nothing to drift; CI fails if the install errors. -
Book build and staging.
mdbook build docsruns. Becausebook.tomlenables two renderers (html+linkcheck), mdBook writes HTML todocs/book/html/(notdocs/book/). The step stages it withmkdir -p site && cp -r docs/book/html/. site/, then asserts the site root exists before continuing:test -s site/index.html(failing the build otherwise). Since the hand-written root-redirect step is gone (step 9) anddeploydoes not run on PRs, this is the only automated guard that the deployed site root resolves. -
Reserved top-level paths. The step fails the build if the book output contains
site/rustdoc,site/ffi,site/bench, orsite/dev. Those names belong to the generated artifacts merged in by the following steps, whosemv/tarwould otherwise silently overwrite a book section rendered there. -
Rustdoc relocation: after
cargo doc,mkdir -p site/rustdoc && mv target/doc/* site/rustdoc/, thensite/rustdoc/index.htmlis written as a redirect →firewood. -
Go docs:
doc2go -C ffi -home github.com/ava-labs/firewood/ffi -out $(pwd)/site/ffi ./.... Thedoc2goinstall is pinned to an explicit version, for consistency with the pinned mdBook tools:go install go.abhg.dev/doc2go@v0.12.2(tagv0.12.2, commitcb3c122f7e08194070fd4a8fce4466b2a7d74159;github.com/abhinav/doc2go). -
Benchmark merge (scoped):
git fetch origin benchmark-data, then extract each ofbenchanddevonly if it exists inFETCH_HEAD:for path in bench dev; do if git ls-tree -d --name-only FETCH_HEAD -- "$path" | grep -q .; then git archive FETCH_HEAD "$path" | tar -x -C site/ fi donetrack-performance.ymlpopulates thebenchmark-databranch viagithub-action-benchmark(auto-push: true,gh-pages-branch: benchmark-data), writing officialmainhistory tobench/and experimental feature-branch history todev/bench/{branch}/. Both are directories at the branch root, sogit ls-tree -dis the correct existence test. Scoping the archive to those two paths, rather than the whole branch root as the previous workflow did withgit archive FETCH_HEAD | tar -x, prevents the extract from clobbering the mdBookindex.htmlor other book output now that the book, not rustdoc, owns the site root. The per-path existence check is required becausegit archive FETCH_HEAD bench deverrors (pathspec 'dev' did not match) wheneverdev/is absent, which is the branch’s state until the first experimental feature-branch benchmark runs, sincetrack-performance.ymlcurrently publishes onlymainhistory (bench/). The outer “branch may not exist on first run” guard wraps the whole block. -
Removed steps. The previous workflow’s hand-written root-redirect step and “Copy static assets” step are gone: mdBook emits the root
index.htmland copiessrc/assets/itself. -
Upload artifact
path: site(previouslytarget/doc).
Triggers
The pull_request.paths filter includes docs/** and ffi/**/*.go alongside the
workflow file itself. The deploy job’s existing github.event_name != 'pull_request'
guard keeps PR runs build-only.
docs/** validates the book against documentation changes. ffi/**/*.go is included
because gh-pages.yaml is the only workflow that runs doc2go, so a change to the Go
sources must rebuild the Pages artifact to catch broken godoc output. Rust source
changes are deliberately excluded: ci.yaml already runs cargo doc with
-D warnings on every PR, so gating this workflow on **/*.rs would only duplicate an
existing, stricter check.
A matching PR therefore runs the entire site assembly (cargo doc, doc2go, benchmark
merge, mdBook build), not just mdbook build. This is intentional: validating the
full Pages artifact against the PR’s changes is the point: it catches cross-component
breakage (e.g. a relocated rustdoc path, a broken reference/ link-out, or an asset
collision in site/) before merge. The extra CI cost is an accepted trade for that
end-to-end coverage; the build is not split into a book-only fast path.
Link checking
mdbook-linkcheck2 runs as a backend during mdbook build, configured under
[output.linkcheck2] with follow-web-links = false. It validates internal book links
(broken SUMMARY.md entries, bad cross-references) and skips web links, including the
https://ava-labs.github.io/firewood/… link-outs to generated artifacts that exist only
post-deploy. Broken internal links fail the PR build. Enabling this second renderer is
what moves HTML output to docs/book/html/ (see build step 4).
mdbook-linkcheck2 is a maintained fork of the original mdbook-linkcheck. The
original (last released 2022) rejects a book.toml whose [rust] table sets
edition = "2024", the workspace default, failing with unknown variant '2024';
the fork accepts it. The fork preserves the same link-checking behavior and also reads
the legacy [output.linkcheck] table name for backward compatibility.
Post-deploy smoke check (automated)
Because linkcheck runs with follow-web-links = false, the /rustdoc/, /ffi/, and
/bench/ link-outs from the reference/ hub, the things most likely to rot after a
rustdoc relocation, are never validated by the build. A third job, smoke, in
gh-pages.yaml covers them:
needs: [build, deploy]so it runs only after a successful deploy and can read thebuildjob’shas_benchoutput (below).if:gated to the same condition asdeploy(canonical repo, non-pull_requestevents) so PR runs and forks are unaffected.- A step that runs
curl --failagainst the Pages base URL for/,/rustdoc/(the redirect),/rustdoc/firewood/, and/ffi/, failing the workflow if any returns a non-success status./bench/is checked only when thebuildjob reportshas_bench == true; a fresh deploy with nobenchmark-datahistory yet has no/bench/to curl, so that path is skipped rather than failing the job.
This is the automated backstop for the consciously-skipped external link validation and
surfaces a stale crate-directory name (e.g. firewood_replay) introduced by a future
relocation.
book.toml essentials
[build]: leavebuild-dirat the default (book); with a second renderer enabled, mdBook writes each renderer to its own subdirectory, so HTML lands indocs/book/html/and link-check output indocs/book/linkcheck2/(onlyhtml/is copied intosite/).[output.html]:site-url = "/firewood/"(used only for the generated 404 page’s absolute links; normal pages use relativepath_to_rootlinks and render correctly both under localmdbook serveand at the/firewood/Pages base, so omitting or misconfiguringsite-urlbreaks only the 404 page, not navigation),git-repository-url,edit-url-template(edit-on-GitHub links),default-theme,additional-js = ["mermaid.min.js", "mermaid-init.js"](wires the mermaid preprocessor’s generated JS into every page), search enabled (default).[preprocessor.mermaid]. Its JS is produced bymdbook-mermaid install docsat build time (CI build step 3 and thejustfilerecipes) and is git-ignored, not committed. Callouts need no preprocessor; they use mdBook’s native alert syntax (> [!NOTE]).[preprocessor.frontmatter-strip]withcommand = "../scripts/mdbook-frontmatter-strip.sh". Strips each chapter’s YAML frontmatter before rendering so the schema metadata does not surface as a stray<hr>+ heading. It is an in-repo jq script (needsjqonPATH; no binary to install). mdBook runs preprocessor commands from the book root, so thecommandpath is relative todocs/, not the repo root.[output.linkcheck2]withfollow-web-links = false, which checks internal links only and skips thehttps://link-outs to generated artifacts.
Local tooling
justfile recipes
book-assets→mdbook-mermaid install docs(regenerates the git-ignored mermaid assets; a prerequisite of the build recipes so a fresh checkout builds without a manual step).book-serve→book-assetsthenmdbook serve docs --open(live reload).book-build→book-assetsthenmdbook build docs(mirrors the book-build and link-check portion of CI: it validates book content and internal links, but not the rustdoc relocation, doc2go output, or benchmark merge that the full CI site assembly also performs).mdbook-linkcheck2is a renderer backend, not a standalone binary, so it runs automatically as part ofmdbook buildonce[output.linkcheck2]is configured inbook.toml; there is no separate command to invoke. Themdbook-mermaidbinary must be onPATH, and the in-repo frontmatter stripper needsjqonPATH; mdBook runs both automatically per theirbook.tomltables. Neither needs an install step inbook-assets: mermaid’s assets do, its binary does not, and the stripper is already in the repo.new-design slug→ scaffoldsdocs/src/designs/YYYY-MM-DD-slug.mdfromtemplate.md, dated today. The slug must be lowercase kebab-case and is passed to the recipe’s shell as a quoted word, so it can neither escape thedesigns/directory nor inject commands. The recipe refuses to overwrite an existing file, which catches the one possible collision: a second design with the same slug on the same day.design-age→ runsscripts/design-doc-age.sh: lists everydocs/src/designs/YYYY-MM-DD-*.mdby last git-commit date, oldest first, with uncommitted designs last. Use it to spotactivedesigns that have drifted from the code.
Recipes call mdbook directly; the dev-environment guide names the required tools
and links to upstream install docs (and may include concrete install commands).
Getting Started: dev-environment.md (fully authored)
- Common prerequisites:
rustup+ pinned toolchain (MSRV 1.94.0, edition 2024),just,jq(used by the in-repo frontmatter preprocessor), Go (FFI), Nix (FFI flake), and the mdBook toolchain (mdbook,mdbook-mermaid,mdbook-linkcheck2). - macOS local: rustup install; components (
rustfmt,clippy,rust-analyzer); VS Code +rust-analyzerextension settings;justworkflows; build/test (cargo nextest run --workspace --features logger). - Docker / devcontainer: use the existing
.devcontainer/; open in VS Code Dev Containers; what is preinstalled vs. what to run. - Remote Linux over SSH: VS Code Remote-SSH; install rustup/Go/Nix on the host;
rust-analyzer running remotely; port-forwarding
mdbook servefor live preview. - Verifying your setup: the canonical build/test/clippy/doc commands from
CONTRIBUTING.md.
Scaffolding via draft chapters
mdBook supports draft chapters, SUMMARY.md entries written with empty link
parentheses (- [Title]()), which render as greyed-out/disabled items in the sidebar.
Per the mdBook guide their purpose is “to signal future chapters still to be written.”
This is the idiomatic mdBook mechanism for signalling future chapters instead of
creating empty stub .md files (hollow pages would be search-indexed and present as
real-but-empty content). The rule:
- A section or sub-page with real seed content gets an authored landing page and a
linked
SUMMARY.mdentry. - A section or sub-page with nothing to say yet is a draft chapter (link-less entry): it shows in the outline as “coming soon” without creating a hollow page, and is promoted to a linked entry when its first real content lands.
Applying that rule to the extra sections: each is a draft chapter until its authored
landing page lands, and no sub-page draft chapters ship; unwritten sub-pages are
omitted from SUMMARY.md until their content lands.
concepts/: authored, seeded by promoting the README terminology + architecture- diagram prose.integration/: authored (this section has real material; see below). Documents the Go API surface and the AvalancheGo consumption story via FFI.operations/: authored landing page (operations/README.md) coveringfwdctlusage, the C-Chain reexecution benchmark, and reading/bench/dashboards at a paragraph-each depth. The existingbenchmark/docs/cchain-reexecution.mdandbenchmark/docs/synthetic-workloads.mdare linked to (relative links to the GitHub-rendered files), not copied or{{#include}}-ed, for the MVP; this avoids duplicating content and avoids those files’ site descriptions going stale against the new layout. Migrating their content into the book is a follow-up. The landing page links to/bench/for the live dashboards. MVP scope: exactly one file,operations/README.md; deeper sub-pages (e.g.fwdctl.md,benchmarks.md) are omitted fromSUMMARY.mduntil their content is written.reference/: authored landing page (reference/README.md), a link-out hub for generated artifacts only (rustdoc, godoc, benchmarks). It is authored (the three link-outs are its content), not a draft chapter. Repository-process docs (CONTRIBUTING / RELEASE / STYLE_GUIDE) live in themeta/section, not here.
Integration & the Go API (integration/, authored)
This section has substantial, non-trivial material and is authored at MVP, not drafted. Two things it must get right:
1. The Go API surface. The in-repo ffi/ crate ships a Go wrapper. Its public
types map onto the Rust concepts as follows; note the Go names deliberately differ
from the Rust ones:
Go type (ffi/*.go) | Rust concept | Notes |
|---|---|---|
Database (firewood.go) | Db | the database handle; there is no Go type named Db |
Proposal (proposal.go) | Proposal | uncommitted batch atop a base root, awaiting commit |
Revision (revision.go) | DbView | read-only view of a historical revision; DbView is the Rust-side name |
Reconstructed (reconstructed.go) | reconstructed/archival view | state rebuilt incrementally from range proofs during state sync; released with Drop |
Iterator (iterator.go) | view iterator | streams key/value pairs from a view; Next copies into Go memory, NextBorrowed lends Rust-owned memory |
BatchOp (batch_op.go) | BatchOp | a single Put/Delete/PrefixDelete; the unit of a write batch |
RangeProof, ChangeProof, NextKeyRange (proofs.go) | proof types | range/change proofs and key-range cursors used for state sync |
Construction and tuning options (New, the With* Options) and the metrics/logging
entry points (Gatherer, LogConfig, StartMetrics, StartLogs) are documented
alongside the types they configure.
2. The publish relationship (the part most likely to be missed). AvalancheGo and
its grafts do not import the in-repo ffi/ directory. They depend on the
separately published Go module github.com/ava-labs/firewood-go-ethhash/ffi, pinned
in avalanchego/go.mod and in the graft/evm, graft/coreth, and graft/subnet-evm
go.mods. That published module tracks the firewood-ffi crate version: when
firewood-ffi is released, CI builds the static libraries, copies the in-repo ffi/
directory into the ava-labs/firewood-go-ethhash repository, and tags it (see
RELEASE.md and
.github/workflows/attach-static-libs.yaml).
The section documents how the in-repo ffi/ crate is built (the cargo build →
go tool cgo flow described in ffi/README.md), packaged,
and published, and how a downstream consumer pins and upgrades it. It links to /ffi/
(godoc) for the generated API reference and to meta/release.md for the publish/version
cadence. Without this, the integration story is incomplete: a reader following the
in-repo ffi/ path would never find what AvalancheGo actually consumes.
Meta section (self-documenting)
The meta/ section makes the book document its own machinery and the repository’s
processes:
meta/documentation.md(authored). Seeded with the mdBook setup: the toolchain and preprocessors, thedocs/layout, how to build/serve locally (just book-serve/book-build), how to add or edit a page and updateSUMMARY.md, and a pointer to the design-doc workflow. This is the self-documenting core: a new contributor learns how the docs work from the docs themselves. It links togetting-started/dev-environment.mdfor tool installation rather than repeating it.- Repository-function pages (thin, link-out).
meta/release.mdis a thin authored page documenting the release process. For the MVP it links to the canonicalRELEASE.md(migrating the content into the book is a follow-up) alongside link-out pointers toCONTRIBUTING.mdandSTYLE_GUIDE.md. The section is structured so additional repository functions are easy to add as further thin pages or draft chapters.
This section resolves the bootstrapping note from the Summary: once implemented,
meta/documentation.md is the living record of how the documentation system works,
and this spec is its originating design artifact.
Best practices applied (from the mdBook catalog review)
Distilled from the mdbooks.yaml catalog
(~120 mdBooks):
book.toml+src/SUMMARY.mdconvention (universal).mdbook-mermaidfor diagrams (Embedded Rust Book, Polkadot SDK Best Practices).- RFC-style design docs (Rust RFC lineage).
- Edit-on-GitHub links + repo URL (Cargo book, mdBook guide).
- Search enabled by default.
- Link checking in CI (mdBook guide).
- A
reference/link-out hub rather than burying generated API docs (common in tooling books).
Testing & verification
- CI build validation: a PR touching
docs/**orffi/**/*.gotriggers thebuildjob, which runsmdbook build docswith the linkcheck backend; broken internal links fail. - Local:
just book-buildreproduces the book-build + link-check portion of CI (not the full site assembly). - Deploy smoke check (automated): the
smokejob (CI/CD changes above) runs afterdeployon non-PR events andcurl --fails/,/rustdoc/,/rustdoc/firewood/, and/ffi/, plus/bench/only when thebuildjob reports benchmark history (has_bench), failing the workflow on any non-success status. The build job additionally assertssite/index.htmlexists before upload. new-designrecipe: running it producesdocs/src/designs/YYYY-MM-DD-slug.mdfromtemplate.md, dated today, and fails rather than overwriting an existing file.- Frontmatter stripping: the in-repo
frontmatter-strippreprocessor removes each chapter’s YAML block; spot-check that this design’s own frontmatter does not appear in the rendereddocs/book/html/output (no stray<hr>or metadata heading). design-agerecipe:just design-agelists everydocs/src/designs/YYYY-MM-DD-*.mdby last git-commit date, oldest first, with uncommitted docs last.- Existing checks unaffected:
cargo doc --no-deps,cargo fmt,cargo clippy, andcargo nextestare unchanged; the repository’s markdownlint check passes on the new Markdown. A book-scopeddocs/.markdownlint.jsondisablesMD025(multiple H1) andMD042(empty links) for mdBook’sSUMMARY.mdformat; it lives outsidesrc/so mdBook does not copy it into the rendered output.
Acceptance criteria
-
docs/book.toml+docs/src/SUMMARY.mdexist;mdbook build docssucceeds locally with mermaid, in-repofrontmatter-stripstripping, and linkcheck (internal links only); callouts use mdBook’s native alert syntax (no admonish preprocessor). -
gh-pages.yamlcopies the book HTML (docs/book/html/) intosite/, relocates rustdoc tosite/rustdoc/with a redirect index, repoints go docs tosite/ffi, merges benchmark data with a per-pathgit archive(extractingbench/devonly if each exists inFETCH_HEAD), removes the old root-redirect and copy-static-assets steps, and uploadssite/.mdbook,mdbook-mermaid,mdbook-linkcheck2, anddoc2goare pinned to explicit versions; frontmatter stripping is an in-repo jq preprocessor (no install); CI runsmdbook-mermaid install docsbefore building. The build job assertssite/index.htmlexists before upload. - The hardcoded
ref: mainis replaced with a PR-head-aware checkout, anddocs/**andffi/**/*.goare added to thepull_request.pathsfilter (Rust doc coverage stays inci.yaml); deploy stays gated to non-PR events on the canonical repo. Asmokejob (needs: [build, deploy]) runs afterdeploy(non-PR events) andcurl --fails/,/rustdoc/,/rustdoc/firewood/, and/ffi/, plus/bench/only when the build reports benchmark history. - Introduction and a fully-authored
getting-started/dev-environment.md(macOS, Docker, remote SSH) are written. Structural completeness (reviewer-checkable): every section contains the concrete install/build/verify commands from the outline. - Author sign-off (recorded in the PR description, not a CI gate): the author has run the macOS and devcontainer command sequences end-to-end on a clean environment before merge; the remote-SSH section reuses the same commands and is reviewed for accuracy.
-
designs/is a flat directory: every design isYYYY-MM-DD-slug.md, dated when the design was proposed, with lifecycle state in astatusfrontmatter field (noproposed//active/folders). It contains a single RFC-styletemplate.mdand aREADME.mddocumenting the model, the promotion checklist, and a link to the backfill tracking issue. - The fully-written
2024-08-13-on-disk-format-and-addressing.mdseed is in place. - Design docs carry the YAML frontmatter schema (
title,status,category,authors, optionaltracking-issue) with no date fields; the in-repo[preprocessor.frontmatter-strip]jq script strips it so it does not render. This design itself uses the schema (its oldStatus:/Date:/Author:bullets are gone). -
scripts/design-doc-age.sh(run viajust design-age) lists each design by last git-commit date, oldest first. -
reference/has an authored landing page linking only to generated artifacts (rustdoc/godoc/benchmarks). - Sections with seed content (
concepts/,integration/,operations/) have authored landing pages and linkedSUMMARY.mdentries. Not-yet-written sub-pages are omitted fromSUMMARY.md(added when their content lands) rather than shipped as empty.mdfiles; no sub-page draft chapters ship.integration/documents the Go wrapper types (Database/Proposal/Revision, mapping to the RustDb/Proposal/DbView) and thefirewood-go-ethhashpublish relationship that AvalancheGo actually consumes. - A
meta/section exists with an authoreddocumentation.md(how the docs work: tooling, layout, build/serve, authoring, design workflow) and thin repository-function pages (release.mdplus link-out pointers to CONTRIBUTING / STYLE_GUIDE); the process-doc link-outs are inmeta/, notreference/. -
justfilegainsbook-assets,book-serve, andbook-build. -
justfilegains anew-designrecipe scaffoldingdocs/src/designs/YYYY-MM-DD-slug.mdfromtemplate.md. - Mermaid assets are installed at build time (not committed): CI and the
book-assetsrecipe runmdbook-mermaid install docs, the generated asset paths are git-ignored, and a fresh checkout builds without a manual install step. -
architecture.svgis moved todocs/src/assets/and the rootREADME.mdreference is updated accordingly; the book renders the diagram. -
markdownlint-cli2 .passes.
Drawbacks
- Broken external deep links to
/firewood/<crate>. Relocating rustdoc to/rustdoc/breaks existing bookmarks/SEO to…/firewood/firewood/…. Mitigated by the/rustdoc/redirect index; per-crate redirect stubs at the old paths are out of scope (accepted 404s for the cleaner root UX) and remain a documented future option if the breakage proves painful.
<Title>
Summary
One paragraph describing the change.
Motivation
Why this is worth doing; the problem it solves.
Guide-level explanation
Optional. The design explained as if teaching it to another contributor.
Detailed design
The technical detail: data structures, algorithms, on-disk/runtime behavior, edge cases.
Evaluation
Optional. Evidence for the design: measurements, experiments, prototypes, and the reasoning behind parameter choices. Results that rule out an alternative belong here, referenced from Rationale and alternatives.
Drawbacks
Reasons this design might not be worth doing. Keep this section after promotion: it records what was accepted and why.
Rationale and alternatives
Why this design over others; what alternatives were considered.
Prior art
Optional. Comparable designs in other systems.
Unresolved questions
Open questions to settle during review. Drop this section when promoting to active,
folding settled answers into the design.
Future possibilities
Natural extensions, explicitly out of scope here.
Reference
Generated API documentation and dashboards, published alongside this book:
- Rust API documentation (rustdoc):
the
firewood,firewood-storage, andfirewood-fficrates. - Go API documentation (godoc): the Go wrapper around the FFI layer.
- Benchmark dashboards: performance
history for
main.