|
| 1 | +# AGENTS.md |
| 2 | + |
| 3 | +Guidance for AI coding agents working in this repository. |
| 4 | + |
| 5 | +## What Commit-Boost is |
| 6 | + |
| 7 | +Commit-Boost is an Ethereum validator sidecar that standardizes how proposers make commitments |
| 8 | +to third-party protocols, MEV-boost-style block building being the primary one. It is a Rust |
| 9 | +workspace whose release artifact is the `commit-boost` binary, one subcommand per service, run |
| 10 | +either as generated Docker services or as native processes. |
| 11 | + |
| 12 | +The services cooperate, wired together by one TOML config file: |
| 13 | + |
| 14 | +- **PBS service**: implements the Builder API toward the consensus client (header retrieval, |
| 15 | + blinded-block submission, validator registration, status) and fans requests out to the |
| 16 | + configured relays, applying selection, validation, and timing logic. Custom PBS builds can |
| 17 | + replace the request/response logic while reusing the service scaffolding. |
| 18 | +- **Signer service**: holds validator (consensus) keys and module-requested proxy keys, |
| 19 | + generated behind the signer boundary. It exposes an authenticated HTTP API for signing |
| 20 | + commitment data and never releases private keys; modules receive signatures and signed |
| 21 | + delegation objects only. |
| 22 | +- **Commit modules**: separate processes, first- or third-party, that implement commitment |
| 23 | + protocols. They consume the SDK (the workspace's prelude library) to load config, talk to |
| 24 | + the signer, and register metrics. Modules authenticate to the signer with per-module |
| 25 | + pre-shared HMAC secrets from which short-lived JWTs are minted; separate admin credentials |
| 26 | + protect the signer's administrative endpoints. |
| 27 | + |
| 28 | +`commit-boost init` reads the TOML config and generates a Docker Compose setup (services, |
| 29 | +networks, an env file carrying generated secrets); each service also runs directly via its own |
| 30 | +subcommand, with configuration passed through `CB_*` environment variables. Every service |
| 31 | +exposes Prometheus metrics. |
| 32 | + |
| 33 | +## Repository map |
| 34 | + |
| 35 | +The authoritative workspace layout is `[workspace].members` in the root `Cargo.toml`. Roles: |
| 36 | + |
| 37 | +- `bin/`: the `commit-boost` binary and the prelude library that modules import. |
| 38 | +- `crates/`: the library crates. Shared config, types, signing, and wire formats live in the |
| 39 | + crate every other crate depends on (`common`); service logic lives in per-service crates; |
| 40 | + compose generation lives in the CLI crate. |
| 41 | +- `tests/`: the workspace integration suite (mock relays, validators, signer service). |
| 42 | + Service-behavior regression tests go here; `just test` runs them. |
| 43 | +- `benches/`: microbenchmarks and a Docker-based PBS load-benchmark harness (see the |
| 44 | + justfile bench recipes). |
| 45 | +- `examples/`: runnable module examples and config presets; docs snippets mirror these, so |
| 46 | + changing an example implies checking the docs that quote it. |
| 47 | +- `provisioning/`: Dockerfiles, the build container, the Helm chart, Grafana dashboards. |
| 48 | +- `docs/`: the Docusaurus site. |
| 49 | +- `api/`: the signer OpenAPI spec. The docs' API page renders it with SwaggerUI fetching the |
| 50 | + main-branch copy from GitHub raw at page load, so spec changes are user-visible on merge and |
| 51 | + the docs build does not validate the spec; unreleased behavior needs its marker inside the |
| 52 | + spec's own description text. |
| 53 | +- `.releases/`: one YAML per release naming the released commit; the anchor for every |
| 54 | + version-line question (see Ground truth). |
| 55 | +- `justfile`: the canonical developer commands; prefer its recipes over hand-rolled |
| 56 | + invocations. |
| 57 | + |
| 58 | +## Working on the code |
| 59 | + |
| 60 | +- Format with `just fmt`, lint with `just clippy`, run the suite with `just test` (the |
| 61 | + justfile pins its own toolchain and mirrors CI's lint invocation; the recipes are the source |
| 62 | + of truth). Building requires `protoc`; `just install-protoc` provides it. `just build-bin |
| 63 | + <version>` builds through the Docker build container, not the local toolchain; a plain local |
| 64 | + build is `cargo build --release`. |
| 65 | +- Match the existing code style: comments are minimal and state constraints, not narration; |
| 66 | + error-binding names differ per crate, so follow the surrounding crate; struct field names |
| 67 | + match wire names. |
| 68 | +- Config keys are user-facing API. Only relay entries reject unknown keys (`[[relays]]`, |
| 69 | + including relay entries inside `[[mux]]`); every other config section silently ignores |
| 70 | + them, so a typo or an unreleased key outside a |
| 71 | + relay entry does nothing rather than failing. Adding, renaming, or defaulting a key is a |
| 72 | + compatibility decision, and `config.example.toml` plus the owning docs page change in the |
| 73 | + same commit. |
| 74 | +- Wire-visible strings (error bodies, log lines users are told to grep for, metric names and |
| 75 | + labels) are documented surface: changing one means updating the docs pages and OpenAPI specs |
| 76 | + that quote it. |
| 77 | + |
| 78 | +## Ground truth |
| 79 | + |
| 80 | +- The code is the source of truth. A documentation claim is wrong until the code proves it; |
| 81 | + when reviewing or writing docs, ground every behavioral claim in a specific code location. |
| 82 | +- Two lines always exist: the **released line** and **main**. The released commit is the |
| 83 | + `commit:` field of the semver-newest file in `.releases/` (file names are exact tags, |
| 84 | + including `-rcN` pre-releases; order by semantic version, never lexically or by mtime, since |
| 85 | + `v0.9.*` sorts after `v0.10.*` as a string; naming rules in `.releases/README.md`). The docs |
| 86 | + voice tracks the newest stable (non-rc) release. Release pins live on release-branch |
| 87 | + lineages and are generally **not ancestors of main**, so never use `git merge-base |
| 88 | + --is-ancestor` to decide whether a feature is released. Probe content instead: |
| 89 | + `git show <release-commit>:<path>`. |
| 90 | +- While the docs site is unversioned (no `versioned_docs/` under `docs/`), one tree serves |
| 91 | + users of the released binaries, so docs speak in the **released version's voice**. Behavior |
| 92 | + that exists only on main is marked with a `:::info Unreleased` admonition (for sections) or |
| 93 | + an inline marker naming the first version that will ship it, `(unreleased, from vX.Y)`, |
| 94 | + stated once and tersely. When that release ships, deleting its markers is the whole docs |
| 95 | + update. If the site gains versioned docs, per-version trees supersede this policy. |
| 96 | +- `config.example.toml` is copied verbatim by users of released binaries. Unreleased keys |
| 97 | + appear there **commented out**, with the unreleased marker in the comment: an uncommented |
| 98 | + unreleased key either breaks released binaries at startup (inside relay entries) or is |
| 99 | + silently ignored (everywhere else), and both mislead. |
| 100 | +- Example and log values must be reproducible by a real binary: version strings exactly as |
| 101 | + binaries self-report them, commit hashes equal to the release commit (annotated tag objects |
| 102 | + are never printable by any build), timestamps/slots/block numbers arithmetically consistent |
| 103 | + with each other, placeholder hosts from RFC 2606 (`example.com`), `https://` schemes, and |
| 104 | + secrets shown truncated and obviously non-functional. |
| 105 | + |
| 106 | +## Writing style (docs and prose) |
| 107 | + |
| 108 | +- Plain operator prose, American English. No em dashes. No "Note that" lead-ins. No marketing |
| 109 | + or filler vocabulary (leverage, seamlessly, robust, powerful, comprehensive, utilize). No |
| 110 | + sentences that announce what the reader is about to read. |
| 111 | +- Don't explain absences. When removing content, splice the neighbors and move on; never add |
| 112 | + text justifying why something is no longer there. |
| 113 | +- Terminology: "PBS service", "Signer service"; "commit module(s)" in running prose ("Commit |
| 114 | + Modules" only in headings; never hyphenated). Heading case follows the page's dominant style. |
| 115 | +- **One home per fact.** Every contract or nuance gets one full statement on the page that |
| 116 | + owns its topic; every other mention is a one-line pointer to that home. Before adding a |
| 117 | + fact, find where existing mentions of the topic already point and follow them. As of |
| 118 | + writing: `configuration.md` owns config semantics (including hot reload, TLS, rate limiting, |
| 119 | + keystore layouts); `mux-key-loaders.md` owns mux mechanics; `running/binary.md` owns |
| 120 | + environment variables; `metrics-catalog.md` owns per-metric reference rows; |
| 121 | + `troubleshooting.md` owns log walkthroughs and symptom tables; `developing/*` own SDK and |
| 122 | + module-API contracts. `config.example.toml` comments stay one line per key plus a docs link: |
| 123 | + it is the annotated reference for values, not a second prose home. |
| 124 | +- Long runs of bold-label bullets are hard to scan; prefer prose or a real table. |
| 125 | + |
| 126 | +## Verifying changes |
| 127 | + |
| 128 | +- Docs build gate: run the docs build the way CI does (`.github/workflows/`; today |
| 129 | + `npm install && npm run build` in `docs/`). Broken links fail the build; broken |
| 130 | + anchors only warn, so also grep the build output case-insensitively for "broken anchor". |
| 131 | +- Parse every fenced `toml`/`yaml`/`json` block in changed pages, and `config.example.toml` |
| 132 | + itself (python `tomllib` / `yaml.safe_load`). |
| 133 | +- Spellcheck prose with code spans and fences stripped. |
| 134 | +- Examples must run. Prefer executing a documented flow (generate the compose setup from the |
| 135 | + documented config, perform the documented auth round-trip) over reading it; executed |
| 136 | + walkthroughs find breaks that reading does not. Check Rust snippets against the real SDK |
| 137 | + signatures in the workspace or the `examples/` crates they mirror. |
| 138 | + |
| 139 | +## Trap classes |
| 140 | + |
| 141 | +Recurring bug shapes in this repository. Check for the class, not just the past instance. |
| 142 | + |
| 143 | +- **Released-vs-main drift**: a behavior claim may hold on only one line. Any claim about wire |
| 144 | + formats, error bodies, config keys, or metrics needs checking against both the released |
| 145 | + commit and main; cite the code location that supports it. |
| 146 | +- **Secret vs token**: several environment variables hold HMAC *secrets* from which |
| 147 | + short-lived tokens are minted. Calling them tokens produces curl examples that can never |
| 148 | + authenticate. Verify what the receiving middleware actually validates before documenting an |
| 149 | + auth flow. |
| 150 | +- **Startup-frozen environment**: environment variables are read at process start; reload |
| 151 | + endpoints cannot observe new values. Verify the actual data flow before documenting any |
| 152 | + hot-rotation or reload pattern, and state what a reload can and cannot pick up. |
| 153 | +- **Silently ignored config**: outside relay entries, unknown config keys are accepted and |
| 154 | + dropped, so a misplaced or misspelled key looks configured while doing nothing. Verify a key |
| 155 | + is consumed at the nesting level where the docs place it. |
| 156 | +- **Dead knobs**: config fields, chart values, or flags consumed by no code path (Helm |
| 157 | + templates are especially prone). Verify each documented option is actually read before |
| 158 | + listing it. |
| 159 | +- **Documented intent, not behavior**: comments and older docs sometimes describe features |
| 160 | + that were planned but never implemented. Trust only the code path you can cite. |
| 161 | + |
| 162 | +## Maintenance of this file |
| 163 | + |
| 164 | +This file holds only orientation, conventions, methods, and trap classes, so it stays valid as |
| 165 | +features are added. If an edit records a fact about a specific version or feature, that fact |
| 166 | +belongs in the docs themselves, a code comment, or the issue tracker instead. |
0 commit comments