Skip to content
edgesync libfossil Core Library
Login

libfossil Core Library

Pure-Go reimplementation of Fossil's core operations. Standalone repo at github.com/danmestas/libfossil (public, v0.1.x). No CGo in the default build. Every operation must produce .fossil files that fossil rebuild --verify accepts.

Repository & Module Structure

Extracted from EdgeSync monorepo (April 2026) via git filter-repo with history. Three sub-modules, lockstep versioned:

Module Purpose
github.com/danmestas/libfossil Root — opaque handle API, CLI, tests
github.com/danmestas/libfossil/db/driver/modernc Default SQLite driver
github.com/danmestas/libfossil/db/driver/ncruces WASM-compatible SQLite driver

Optional sub-module: observer/otel/ — OTel observer implementation (separate go.mod, zero OTel deps in root).

Versioning: lockstep v0.x tags across all sub-modules. Tags: v0.1.0, db/driver/modernc/v0.1.0, db/driver/ncruces/v0.1.0.

Encapsulation & Public API

All implementation packages live under internal/ (blob, content, manifest, sync, xfer, etc.). The public API is an opaque Repo handle with methods — no direct DB/SQL access for consumers.

Constructors: Open(path), Create(path, user, rand), Clone(ctx, url, path, opts)

Repo handle methods: Sync(), HandleSync(), HandleSyncWithOpts(), XferHandler(), Commit(), Timeline(), ListFiles(), Tag(), UVWrite/Read/List(), CreateUser(), Config()

Checkout handle: CreateCheckout(), OpenCheckout() — working directory management

Extension points:

CLI embedding: cli/ package provides 38 kong command structs (cli.RepoCmd). EdgeSync embeds this and adds only NATS/bridge/iroh commands on top.

Standalone binary: cmd/fossil/ — Fossil-compatible CLI built from cli.RepoCmd.

Package Architecture

All packages below are under internal/ (not importable by consumers). Public packages: db/, db/driver/*, simio/, testutil/, cli/.

Key dependency chain:

hash, delta (no deps)
blob -> db, hash, delta
content -> db, blob, delta
deck (stdlib only, no DB)
xfer -> deck
manifest -> deck, db, blob, content, hash
repo -> db, blob, content, hash
sync -> repo, manifest, xfer, blob, content

No circular imports. deck and xfer are intentionally database-free. internal/fsltype breaks import cycles (root types re-exported).

Context Decomposition

C libfossil uses a monolithic fsl_cx context. Go decomposes it:

C concept Go equivalent
fsl_cx (monolithic) repo.Repo (top-level handle, owns DB)
fsl_cx.dbMem db.DB (connection + statement cache)
fsl_cx error state Standard Go error returns
fsl_cx transactions repo.Repo.WithTx(func(tx) error)

Key Types

Type Definition Notes
FslID int64 Widened from C's int32 -- SQLite returns int64 natively
FslSize int64 Signed, not unsigned -- -1 sentinel for phantom blobs (PhantomSize)
db.Querier interface (Exec, QueryRow, Query) Satisfied by both *db.DB and *db.Tx
deck.Deck struct with fields A-Z All 20 manifest card types; ArtifactType enum discriminates
xfer.Card interface (Type() CardType) 19 card types + UnknownCard for forward compat
xfer.Message struct { Cards []Card } Full xfer request/response with zlib encode/decode

Blob Format

Fossil stores blobs with a 4-byte big-endian uncompressed size prefix followed by zlib-compressed data:

[4-byte BE uint32: uncompressed size][zlib data]

blob.Compress() and blob.Decompress() handle this format. blob.Store auto-marks new blobs as unclustered (matching C's content_put_ex). unsent is a caller concern.

Verify-before-commit: Store() and StoreDelta() re-read, decompress (and for deltas, re-apply), and re-hash after INSERT — unconditionally, matching Fossil's content_put_pk(). Catches compression or delta bugs before they persist. BUGGIFY sites in Decompress (2% truncation) and Expand (1% byte-flip) exercise this in DST.

Xfer Wire Format

HTTP POST to /xfer, Content-Type application/x-fossil, body is zlib-compressed (RFC 1950). Newline-separated cards.

Manifest Format

Cards emitted in strict ASCII order: A B C D E F G H I J K L M N P Q R T U W Z. Z-card (MD5 of everything before it) always last. Manifest UUID = SHA1/SHA3 of complete bytes including Z-card.

SQLite Driver Selection

Two drivers, selected via import (registration API pattern):

Driver Module CGo WASM Import
modernc (default) db/driver/modernc/ No No _ ".../db/driver/modernc"
ncruces db/driver/ncruces/ No Yes _ ".../db/driver/ncruces"

Each driver is a separate Go sub-module with its own go.mod. The db package has zero driver dependencies — drivers self-register via db.Register(DriverConfig) in init(). Consumers import exactly one driver; no build tags needed in production code. Test files use internal/testdriver which selects the driver via build tags (test_ncruces).

Default pragmas applied to every connection: journal_mode=WAL, busy_timeout=5000, foreign_keys=ON. DSN syntax varies per driver; each driver's BuildDSN handles it.

GOWORK=off go test ./...                              # modernc (default)
GOWORK=off go test -tags test_ncruces ./...           # ncruces

TigerStyle Conventions

Applied across all 20 libfossil packages. Zero new features -- hardening only.

Assertions

Function Length

Hard limit: 70 lines. 10 functions were split into same-file private helpers (largest: stash.Save 160 -> 3 functions, deck.Marshal 143 -> 2 functions).

Error Handling

Naming

Diff Engine

diff/ package — Myers O(nd) algorithm, line-based, pure []byte in / string out. No repo dependencies.

File History (finfo)

manifest.FileHistory() — walks the mlink table to return a file's change history across checkins, ordered by date descending.

Content Cache

content.Cache — concurrency-safe LRU cache for content.Expand results, keyed by FslID. Eliminates redundant delta-chain walks (SQLite queries + zlib decompress + delta apply per chain link).

Design Decisions

Integration Points

Layer Where How
content/cache.go Core type NewCache(maxBytes), Expand, Invalidate, Clear, Stats
sync/session.go SyncOpts.ContentCache Client sync uses cache for loadFileCard (push) and resolveFileContent (delta base)
sync/handler.go HandleOpts.ContentCache Server handler uses cache for gimme responses and clone batch sending
leaf/agent/ Config.ContentCacheSize Agent creates 32 MiB cache by default (negative disables). Shared across sync rounds for agent lifetime.

Performance

Cache hits: ~550ns / 1 alloc (copy only). Uncached 5-deep delta chain: ~65µs / 224 allocs. ~120x speedup on hits.

Constraints