Artifact 70a18e4e49d1f247613d16629878cf0fbbe02363debe5d4539ec14e07066d85e:
- File
docs/site/content/docs/architecture.md
— part of check-in
[e2a2f28861]
at
2026-04-26 07:52:11
on branch trunk
— docs site + landing + Cloudflare auto-deploy (#6)
* design: docs site + landing page + Cloudflare auto-deploy
Mirror dagnats's docs/landing infrastructure for libfossil: a single-page landing at site/index.html (forked CSS/HTML, content rewritten), a Hugo + Hextra docs site at docs/site/, gomarkdoc-generated SDK reference under docs/site/content/docs/reference/sdk/, llms.txt artifacts, and a wrangler.jsonc that lets Cloudflare's git integration auto-deploy on push to main. Existing docs/*.md files move under docs/site/content/docs/.
* design: correct landing-page path to Hugo layout override
dagnats's working landing page lives at docs/site/layouts/index.html (a Hugo home-page layout override), not at site/index.html. The top-level site/index.html in dagnats is an older draft that the build pipeline ignores. Match the working setup.
* design: align visual system + section list with live dagnats landing
The earlier description came from dagnats's stale top-level site/index.html (JetBrains Mono + Instrument Serif italic + ember/teal accents, 8 sections). The live Hugo layout that actually serves at / uses Geist Mono only, prefers-color-scheme dual-mode (warm cream / warm dark), a single ember accent, and 7 sections (Install, What it is, Why you might want it, How it fits together, Compared to neighbors, What it is not, Get going). Update the spec to match what we are actually mirroring.
* design: drop SDK regen from CF build to match dagnats exactly
Earlier draft had CF install Go + gomarkdoc and re-run docs-gen-sdk on every deploy. dagnats does not do that; it relies on the pre-commit hook to keep committed SDK pages in sync, and ships only Hugo + llms-gen + hugo --minify in the CF build. Match that: smaller, faster CF build, no Go-runtime assumption about Workers Builds, and a true 1:1 mirror.
* plan: docs site + landing + CF auto-deploy implementation steps
11 tasks covering Hugo+Hextra bootstrap, content moves, gomarkdoc SDK generation with pre-commit drift check, llms.txt artifacts, Makefile targets, landing page fork from dagnats's live Hugo home layout, wrangler.jsonc for CF Workers Static Assets, README update, and PR creation.
* docs(site): bootstrap Hugo + Hextra skeleton
* docs(site): move existing docs into Hugo content tree
* docs(site): fix links + H1s broken by docs-tree move
- Drop .md extensions on Hugo cross-doc links - Switch source-code references to absolute github.com/danmestas/libfossil URLs (relative ../ paths broke when files moved 3 dirs deeper) - Align H1 of architecture.md and migration-from-fossil.md with their frontmatter titles
* docs(site): add quickstart, examples, and SDK section stubs
* docs(site): tighten quickstart snippets + reference index
- Make the Sync example self-contained (open + sync, full main, real signatures) - Bridge from Clone to Open with a one-line prose link - Add a sentence to reference/_index.md so it does not render blank - Use the project's canonical "pure-Go, CGo-free" phrasing
* docs(site): add docs-gen-sdk Makefile target + commit generated SDK pages
* docs(site): pin gomarkdoc version + replace empty driver stubs with prose
- Add GOMARKDOC_VERSION Makefile variable so contributors don't drift on output format when a new gomarkdoc ships - Drop the two driver packages (modernc, ncruces) from gomarkdoc and hand-write _index.md pages explaining the register-on-import contract; the stubs were byte-stable but rendered as visibly broken documentation
* docs(site): add gen-llms-txt.sh + docs-gen-llms target
* docs(site): switch llms-full frontmatter strip from sed to awk
The sed range-negation pattern '/^---$/,/^---$/!p' silently re-opens a suppression range on any bare '---' in body content, eating Markdown horizontal rules and embedded YAML in fenced code blocks. The awk pattern strips the frontmatter only when it begins on line 1, leaving body content intact regardless of how many '---' lines it contains.
* hooks: pre-commit fails when SDK pages drift from code
* hooks: pre-commit re-installs gomarkdoc if it's the wrong version
The drift check's existing gate just looks for gomarkdoc on PATH. A contributor with an older gomarkdoc installed locally would have the gate pass and then run regeneration with their stale binary, producing either a silent false-negative or a confusing OUT-OF-DATE error with no version hint. Compare the installed version against GOMARKDOC_VERSION from Makefile and re-install on mismatch. Best-effort; offline contributors can still commit.
* docs(site): add docs-serve, docs-build, docs aggregate targets
* docs(site): make docs aggregate parallel-safe via explicit deps
The docs aggregate previously listed gen-sdk, gen-llms, and build as side-by-side prereqs. Make doesn't guarantee left-to-right ordering under -j, and gen-llms reads what gen-sdk writes, while build reads both. Encode the chain explicitly so 'make -j docs' is correct. Side benefit: 'make docs-build' standalone now produces a complete deployable site, matching contributor expectation.
* site(landing): fork dagnats home layout; rebrand chrome
* site(landing): rewrite §01–§07 content for libfossil
* site(landing): use .ascii class for diagram + fix §03 phrasing
* deploy: add wrangler.jsonc for CF Workers Static Assets
* docs: point README at the new docs site (user: [email protected], size: 8041) [annotate] [blame] [check-ins using]
--- title: Architecture weight: 20 --- # Architecture ## Overview libfossil is a pure-Go library and CLI that reads and writes Fossil SCM's `.fossil` SQLite repositories. The public surface (package `libfossil`) exposes `Repo`, `Create`, `Open`, `Clone`, checkout, and check-in operations; internals live under `internal/`. SQLite access is mediated by a pluggable driver layer in `db/`, with two shippable drivers (`modernc`, `ncruces`) selected by blank import. Instrumentation is pulled in by implementing the `SyncObserver` / `CheckoutObserver` interfaces; an optional OTel adapter lives in its own submodule. A deterministic simulator under `dst/` plus a `BUGGIFY` fault-injection harness in `simio/` exercise concurrent repo operations under a seed. ## Module layout This is a multi-module workspace (`go.work`) with four `go.mod` files: the root, two driver modules, and the OTel observer. Layout: - **Root package `libfossil`** — thin facade over `internal/repo`, `internal/sync`, and `internal/checkout`. Files: `fossil.go` (Create, Open, Clone), `repo.go` (Repo handle + DB accessor), `checkout.go`, `repo_checkout.go`, `repo_merge.go`, `repo_sync.go`, `repo_history.go`, `repo_admin.go`, `repo_extras.go`, `transport.go`, `observer.go`, `types.go`, `errors.go`, `julian.go`. - **`cli/`** — Kong command definitions. `repo.go` aggregates 35+ subcommands (`new`, `clone`, `ci`, `co`, `timeline`, `diff`, `merge`, `tag`, `branch`, `uv`, `stash`, `bisect`, `annotate`, `user`, etc.). Intended to be embedded by any `main` that wants a fossil-shaped CLI. - **`cmd/libfossil/`** — the shipped binary (`main.go`, 25 lines). Wires Kong, blank-imports `db/driver/modernc`, and dispatches to `cli`. - **`internal/`** — private implementation, grouped by concern: - Storage layer: `blob`, `content`, `manifest`, `deck`, `delta`, `hash`, `fsltype`, `repo` (schema + DB boot), `tag`. - Working-tree operations: `checkout` (checkin, extract, update, revert, fork, rename, status, vfile), `merge` (three-way, ancestor, detect, resolve, strategies), `diff`, `annotate`, `undo`, `stash`, `bisect`, `branch`, `path`. - Sync / transport: `sync` (client, handler, clone, session, ckin-lock, serve-http, table-sync, UV), `xfer` (card codec). - Housekeeping: `auth`, `shun`, `verify`, `search`, `uv`, `testdriver`. - **`db/`** — SQLite abstraction (see below). - **`db/driver/modernc`**, **`db/driver/ncruces`** — separate modules, each registers exactly one `database/sql` driver. - **`observer/otel/`** — separate module implementing the observer interfaces against OpenTelemetry. - **`dst/`** — deterministic simulator (`simulator.go`, `node.go`, `network.go`, `peer_network.go`, `mock_fossil.go`, `invariants.go`, `event.go`) with scenario-style tests. - **`simio/`** — deterministic IO primitives: `SimClock`, seeded `Rand`, in-memory storage, and the global `Buggify` switch. - **`testutil/`** — shared test helpers for the rest of the tree. ## Storage model A `.fossil` file is a SQLite database. All repository state — blobs, manifests, tags, branches, unversioned files, tickets, wiki, config, users — lives in that file. libfossil does not maintain any sidecar state for the repo itself (a working tree has its own SQLite DB). Content is stored **content-addressed** in the `blob` table, keyed by either SHA1 or SHA3-256 UUIDs (hash mode is per-project). Every blob is zlib-compressed with a 4-byte big-endian uncompressed-size prefix; `internal/blob.Compress` and `Decompress` handle that framing. Blobs may be stored as full content or as deltas against a parent; reconstruction walks the chain and applies deltas via `internal/content.Expand`, with cycle detection. `internal/content.Verify` rehashes expanded content against the stored UUID. **Manifests** are themselves blobs whose text describes a commit, check- out state, tag change, branch operation, wiki edit, ticket change, or cluster. `internal/manifest` and `internal/deck` parse and assemble manifests; "crosslinking" (`internal/manifest/crosslink.go`) is the step that projects a manifest's effects into derived tables (`mlink`, `plink`, `event`, `tagxref`, etc.) so queries like timeline and checkout work without re-parsing blobs. ## Driver layer `db/` is a thin wrapper over `database/sql`. See [`db/doc.go`](https://github.com/danmestas/libfossil/blob/main/db/doc.go). A driver registers at `init` time via `db.Register(DriverConfig{Name, BuildDSN})`; exactly one registration is allowed (a second call panics). `db.Open` / `db.OpenWith` look up the registered driver, run `BuildDSN(path, pragmas)` to produce the DSN, and then call `sql.Open` with that driver name. Both shipped drivers construct `file:<path>?_pragma=k(v)&...` DSNs — differing only in the `database/sql` driver name they register under (`"sqlite"` for modernc, `"sqlite3"` for ncruces). Shipped drivers: - **`db/driver/modernc`** — default. Blank-imports `modernc.org/sqlite`, a pure-Go, CGo-free translation of SQLite with full filesystem access. This is what `cmd/libfossil/main.go` imports. - **`db/driver/ncruces`** — blank-imports `github.com/ncruces/go-sqlite3`, which runs SQLite as WebAssembly. Has a `ncruces_js.go` variant for `GOOS=js` / browser targets. Used for `GOOS=wasip1` builds and for the `test_ncruces` build tag in the Makefile's driver matrix. ## Sync protocol libfossil implements Fossil's sync-over-HTTP protocol as a client / server pair in `internal/sync`. Wire framing (xfer "cards") lives in `internal/xfer`. Public entry points are re-exported from `libfossil` as `Clone` (`fossil.go`) and from `repo_sync.go`. - **Client** (`internal/sync/client.go`, `clone.go`, `session.go`): drives rounds until `igot`/`gimme` reconcile. Supports clone, pull, push, sync (both directions), plus unversioned-content (`client_uv*.go`) and config table (`client_tablesync.go`) sync. - **Server** (`internal/sync/handler.go`, `serve_http.go`, `handler_uv.go`, `handler_tablesync.go`): stateless per round; `XferHandler` plugs into `http.ServeMux`. - **Check-in lock** (`ckin_lock.go`): a `ci-lock-<parent>` row in the `config` table serialises concurrent check-ins against the same parent. Stale entries expire after `DefaultCkinLockTimeout` (60s). - **Shun propagation** (`xdelete.go`, `internal/shun`): deleted blobs are tombstoned and the tombstone propagates during sync. ## Observer model The core keeps instrumentation as a non-goal; hooks are plain Go interfaces on the public API. `SyncObserver` and `CheckoutObserver` in [`observer.go`](https://github.com/danmestas/libfossil/blob/main/observer.go) define lifecycle events (session start/end, per-round stats, extract/scan/commit phases). `NopSyncObserver` / `NopCheckoutObserver` are zero-cost defaults; `StdoutSyncObserver` / `StdoutCheckoutObserver` log to stderr for quick debugging. For OpenTelemetry, [`observer/otel`](https://github.com/danmestas/libfossil/tree/main/observer/otel/) is a separate Go module so the core doesn't pull in the OTel SDK. Users import it explicitly when they want traces, metrics, or structured logs. ## DST / simulation `dst/` runs many Fossil nodes deterministically under a seed. A `Simulator` (`dst/simulator.go`) owns a priority-queue event loop, a `SimNetwork`, a `simio.SimClock`, and a set of `Node` actors. Scenarios schedule clones, check-ins, merges, UV pushes, and partitions, and tests assert invariants (`dst/invariants.go`, `crosslink_completeness_test.go`). Fault injection uses `simio.Buggify(p)` — a global probability gate that returns `false` in production but flips `true` with probability `p` during simulation. Sites like `clone.emitCloneBatch.truncate` in the sync code wrap calls behind `Buggify` so the simulator can exercise failure paths. `SeededBuggify` in `dst` adapts this to the `BuggifyChecker` interface used by the sync client. See [testing.md](./testing) for running the DST matrix, seed sweeps, and the driver test matrix.