Guidance for AI coding agents working in this repository.
Commit-Boost is an Ethereum validator sidecar that standardizes how proposers make commitments
to third-party protocols, MEV-boost-style block building being the primary one. It is a Rust
workspace whose release artifact is the commit-boost binary, one subcommand per service, run
either as generated Docker services or as native processes.
The services cooperate, wired together by one TOML config file:
- PBS service: implements the Builder API toward the consensus client (header retrieval, blinded-block submission, validator registration, status) and fans requests out to the configured relays, applying selection, validation, and timing logic. Custom PBS builds can replace the request/response logic while reusing the service scaffolding.
- Signer service: holds validator (consensus) keys and module-requested proxy keys, generated behind the signer boundary. It exposes an authenticated HTTP API for signing commitment data and never releases private keys; modules receive signatures and signed delegation objects only.
- Commit modules: separate processes, first- or third-party, that implement commitment protocols. They consume the SDK (the workspace's prelude library) to load config, talk to the signer, and register metrics. Modules authenticate to the signer with per-module pre-shared HMAC secrets from which short-lived JWTs are minted; separate admin credentials protect the signer's administrative endpoints.
commit-boost init reads the TOML config and generates a Docker Compose setup (services,
networks, an env file carrying generated secrets); each service also runs directly via its own
subcommand, with configuration passed through CB_* environment variables. Every service
exposes Prometheus metrics.
The authoritative workspace layout is [workspace].members in the root Cargo.toml. Roles:
bin/: thecommit-boostbinary and the prelude library that modules import.crates/: the library crates. Shared config, types, signing, and wire formats live in the crate every other crate depends on (common); service logic lives in per-service crates; compose generation lives in the CLI crate.tests/: the workspace integration suite (mock relays, validators, signer service). Service-behavior regression tests go here;just testruns them.benches/: microbenchmarks and a Docker-based PBS load-benchmark harness (see the justfile bench recipes).examples/: runnable module examples and config presets; docs snippets mirror these, so changing an example implies checking the docs that quote it.provisioning/: Dockerfiles, the build container, the Helm chart, Grafana dashboards.docs/: the Docusaurus site.api/: the signer OpenAPI spec. The docs' API page renders it with SwaggerUI fetching the main-branch copy from GitHub raw at page load, so spec changes are user-visible on merge and the docs build does not validate the spec; unreleased behavior needs its marker inside the spec's own description text..releases/: one YAML per release naming the released commit; the anchor for every version-line question (see Ground truth).justfile: the canonical developer commands; prefer its recipes over hand-rolled invocations.
- Format with
just fmt, lint withjust clippy, run the suite withjust test(the justfile pins its own toolchain and mirrors CI's lint invocation; the recipes are the source of truth). Building requiresprotoc;just install-protocprovides it.just build-bin <version>builds through the Docker build container, not the local toolchain; a plain local build iscargo build --release. - Match the existing code style: comments are minimal and state constraints, not narration; error-binding names differ per crate, so follow the surrounding crate; struct field names match wire names.
- Config keys are user-facing API. Only relay entries reject unknown keys (
[[relays]], including relay entries inside[[mux]]); every other config section silently ignores them, so a typo or an unreleased key outside a relay entry does nothing rather than failing. Adding, renaming, or defaulting a key is a compatibility decision, andconfig.example.tomlplus the owning docs page change in the same commit. - Wire-visible strings (error bodies, log lines users are told to grep for, metric names and labels) are documented surface: changing one means updating the docs pages and OpenAPI specs that quote it.
- The code is the source of truth. A documentation claim is wrong until the code proves it; when reviewing or writing docs, ground every behavioral claim in a specific code location.
- Two lines always exist: the released line and main. The released commit is the
commit:field of the semver-newest file in.releases/(file names are exact tags, including-rcNpre-releases; order by semantic version, never lexically or by mtime, sincev0.9.*sorts afterv0.10.*as a string; naming rules in.releases/README.md). The docs voice tracks the newest stable (non-rc) release. Release pins live on release-branch lineages and are generally not ancestors of main, so never usegit merge-base --is-ancestorto decide whether a feature is released. Probe content instead:git show <release-commit>:<path>. - While the docs site is unversioned (no
versioned_docs/underdocs/), one tree serves users of the released binaries, so docs speak in the released version's voice. Behavior that exists only on main is marked with a:::info Unreleasedadmonition (for sections) or an inline marker naming the first version that will ship it,(unreleased, from vX.Y), stated once and tersely. When that release ships, deleting its markers is the whole docs update. If the site gains versioned docs, per-version trees supersede this policy. config.example.tomlis copied verbatim by users of released binaries. Unreleased keys appear there commented out, with the unreleased marker in the comment: an uncommented unreleased key either breaks released binaries at startup (inside relay entries) or is silently ignored (everywhere else), and both mislead.- Example and log values must be reproducible by a real binary: version strings exactly as
binaries self-report them, commit hashes equal to the release commit (annotated tag objects
are never printable by any build), timestamps/slots/block numbers arithmetically consistent
with each other, placeholder hosts from RFC 2606 (
example.com),https://schemes, and secrets shown truncated and obviously non-functional.
- Plain operator prose, American English. No em dashes. No "Note that" lead-ins. No marketing or filler vocabulary (leverage, seamlessly, robust, powerful, comprehensive, utilize). No sentences that announce what the reader is about to read.
- Don't explain absences. When removing content, splice the neighbors and move on; never add text justifying why something is no longer there.
- Terminology: "PBS service", "Signer service"; "commit module(s)" in running prose ("Commit Modules" only in headings; never hyphenated). Heading case follows the page's dominant style.
- One home per fact. Every contract or nuance gets one full statement on the page that
owns its topic; every other mention is a one-line pointer to that home. Before adding a
fact, find where existing mentions of the topic already point and follow them. As of
writing:
configuration.mdowns config semantics (including hot reload, TLS, rate limiting, keystore layouts);mux-key-loaders.mdowns mux mechanics;running/binary.mdowns environment variables;metrics-catalog.mdowns per-metric reference rows;troubleshooting.mdowns log walkthroughs and symptom tables;developing/*own SDK and module-API contracts.config.example.tomlcomments stay one line per key plus a docs link: it is the annotated reference for values, not a second prose home. - Long runs of bold-label bullets are hard to scan; prefer prose or a real table.
- Docs build gate: run the docs build the way CI does (
.github/workflows/; todaynpm install && npm run buildindocs/). Broken links fail the build; broken anchors only warn, so also grep the build output case-insensitively for "broken anchor". - Parse every fenced
toml/yaml/jsonblock in changed pages, andconfig.example.tomlitself (pythontomllib/yaml.safe_load). - Spellcheck prose with code spans and fences stripped.
- Examples must run. Prefer executing a documented flow (generate the compose setup from the
documented config, perform the documented auth round-trip) over reading it; executed
walkthroughs find breaks that reading does not. Check Rust snippets against the real SDK
signatures in the workspace or the
examples/crates they mirror.
Recurring bug shapes in this repository. Check for the class, not just the past instance.
- Released-vs-main drift: a behavior claim may hold on only one line. Any claim about wire formats, error bodies, config keys, or metrics needs checking against both the released commit and main; cite the code location that supports it.
- Secret vs token: several environment variables hold HMAC secrets from which short-lived tokens are minted. Calling them tokens produces curl examples that can never authenticate. Verify what the receiving middleware actually validates before documenting an auth flow.
- Startup-frozen environment: environment variables are read at process start; reload endpoints cannot observe new values. Verify the actual data flow before documenting any hot-rotation or reload pattern, and state what a reload can and cannot pick up.
- Silently ignored config: outside relay entries, unknown config keys are accepted and dropped, so a misplaced or misspelled key looks configured while doing nothing. Verify a key is consumed at the nesting level where the docs place it.
- Dead knobs: config fields, chart values, or flags consumed by no code path (Helm templates are especially prone). Verify each documented option is actually read before listing it.
- Documented intent, not behavior: comments and older docs sometimes describe features that were planned but never implemented. Trust only the code path you can cite.
This file holds only orientation, conventions, methods, and trap classes, so it stays valid as features are added. If an edit records a fact about a specific version or feature, that fact belongs in the docs themselves, a code comment, or the issue tracker instead.