This directory holds the config and secrets reference for every CCV app, one
*.documented.toml file per config/secrets structure:
| App | Files |
|---|---|
| executor | executor/config.documented.toml |
| aggregator | aggregator/config.documented.toml, aggregator/secrets.documented.toml |
| committee verifier | verifier/committee/config.documented.toml |
| token verifier | verifier/token/config.documented.toml |
| verifier (shared) | verifier/secrets.documented.toml |
| indexer | indexer/config.documented.toml, indexer/secrets.documented.toml |
| bootstrap | bootstrap/config.documented.toml, bootstrap/secrets.documented.toml |
| monitoring (shared) | common/monitoring.documented.toml |
Each file is a working TOML document: the values are the app's defaults where a default exists, and illustrative examples otherwise, and every field is annotated with its Go doc comment.
Every file carries a # Code generated by tools/configdoc. DO NOT EDIT. header.
The single source of truth is the Go doc comment on each config struct field.
To change what a field's documentation says, edit the doc comment on the struct
(e.g. in executor/config.go, common/monitoring/config.go), then regenerate:
just config-docsTestConfigDocsFresh (in tools/configdoc) runs on every PR as a normal unit
test (it has no Docker/DB dependency — it regenerates each doc in memory and
diffs it against the committed file). CI fails if:
- a committed file is out of date — you changed a struct or a doc comment but
didn't run
just config-docs; or - a documented field is missing a doc comment — the generator refuses to emit a field it can't describe.
In both cases the fix is the same: add the missing comment and/or run
just config-docs, then commit the result.
The generic engine lives in tools/configdoc; this repo's target list lives in
tools/configdoc/registry. To document a new structure:
- Write a
New()builder intools/configdoc/registrythat returns a fully-populated, valid instance of the config/secrets struct. Populate required fields with illustrative values, run the app's real defaulting routine if it has one (so defaults appear), and pin any non-deterministic values (e.g. hostnames) so the output is stable. Allocate pointer sub-structs you want documented — the encoder omitsnil. - Register a
configdoc.Targetin theTargetsslice, giving it aName, an output path underdocs/config/, and itsKind(configdoc.KindConfigorconfigdoc.KindSecrets). - Run
just config-docsand commit the new file.
The generator TOML-encodes the instance and injects each field's Go doc comment, so there is no per-app rendering code to write — only the builder and the doc comments on the struct.
tools/configdoc is a repo-agnostic engine. A repo that already depends on the
chainlink-ccv module (e.g. a chain-specific integration) can import it directly
and document its own config structs — no fork or copy:
g, err := configdoc.NewGenerator(cwd) // walks up to the enclosing go.mod
targets := []configdoc.Target{
{Name: "canton", Out: "canton/config.documented.toml", Kind: configdoc.KindConfig, New: func() any { return newCantonConfig() }},
}
_, err = g.Write(targets, "docs/config") // generate
stale, err := g.Check(targets, "docs/config") // verify (use in a freshness test / CLI -check)NewGenerator auto-detects the module path and root, so nothing is hardcoded.
Doc comments come from source for a type that module declares, and from the
DocComments method a dependency generated for one it does not — generation
writes a doccomments_gen.go beside every documented type for exactly that
reason, and those files are committed. A nested type from another module is
therefore fine, as long as that module generates too; one that has never generated has no
comments to read, and the completeness gate fails on its fields.