core/scripts/cre/reconciler/README.md
A CLI tool that reconciles Chainlink node configurations for nodes deployed to Kubernetes via Griddle.
This tool configures already-deployed CRE nodes (contracts, capabilities, jobs, TOML); it sits on top of Griddle rather than being part of it. Griddle itself (
griddle.yaml, chart deployment) is unaffected by this rename.
devenvironment only. This tool exists to quickly stand up a CRE DON indevwithout going through the full GitOps flow. Do not use it against higher environments (stage,prod, etc.) — those must continue to go through GitOps.
After Griddle deploys Chainlink nodes and databases to Kubernetes, the nodes are running but unconfigured — no contracts deployed, no capabilities registered, no jobs created. This tool fills that gap:
In the future it will be reconciliation-based: run it repeatedly, and it only applies the delta between desired and actual state. Run it twice with no changes, and the second run is a no-op. Currently it requires you to manually rewing the phase in the state file to apply what comes after it.
# From the chainlink repo root:
cd core/scripts/cre/reconciler
# Build
go build -o bin/reconciler ./cmd
# Create a desired-state TOML (see example below)
cat > cre/desired.toml << 'EOF'
[infra]
type = "griddle"
chart_values = "deploy/config/my-repo"
namespace = "my-repo-nodeset"
[jd]
grpc = "grpc-job-distributor.main.stage.cldev.sh:443"
domain = "cre"
environment = "dev"
[[chains]]
chain_id = 1337
ws_url = "wss://anvil-1337.my-repo-nodeset.svc.cluster.local"
http_url = "https://anvil-1337.my-repo-nodeset.svc.cluster.local"
registry = true
[[dons]]
name = "workflow"
don_types = ["workflow"]
capabilities = ["cron", "evm-1337"]
[capability_configs.cron]
binary_name = "cron"
[capability_configs.evm]
binary_name = "evm"
EOF
# Run reconciliation
./bin/reconciler apply --desired cre/desired.toml --state cre/state.toml
# If the tool writes a TOML patch, apply it via Griddle (.deploy dev),
# wait for the nodes to re-roll, then re-run:
./bin/reconciler apply --desired cre/desired.toml --state cre/state.toml
The tool includes a built-in web UI for visual DON composition and status monitoring. This is the recommended
way to create and edit your desired.toml — build the DON layout visually, then use the TOML Preview / Save
features to write it out, rather than hand-writing the TOML from scratch.
./bin/reconciler serve --desired cre/desired.toml --state cre/state.toml --chart-dir deploy/config/my-repo
Then open http://localhost:8089 in your browser.
The web UI is self-contained in the binary (embedded files, no external frontend build step). It uses SortableJS for drag & drop and Tailwind CSS via CDN.
The desired state is declared in a TOML file in your consumer repo. The easiest way to produce this file is via the web UI above — use its DON Builder and TOML Preview / Save features rather than hand-writing it. The format below is documented for reference and for making manual edits once the file exists:
[infra]
type = "griddle"
chart_values = "deploy/config/my-repo" # path to Griddle chart values dir
namespace = "my-repo-nodeset" # K8s namespace
kubeconfig = "~/.kube/config" # optional, defaults to KUBECONFIG env
[jd]
grpc = "grpc-job-distributor.main.stage.cldev.sh:443"
domain = "cre"
environment = "dev"
# Chains — every EVM chain a capability or the on-chain registry needs, with
# exactly one entry marked as the registry chain (where CapabilitiesRegistry /
# WorkflowRegistry are deployed and where nodes register). This is the only
# source of chain data the reconciler uses — nothing is inferred from the
# chart's anvil.instances, and the tool never writes [[EVM]] node config: the
# chart must already declare the network (and thus generate a node key) for
# any chain a capability references, or `apply` fails fast with an actionable
# error instead of silently skipping the affected work.
[[chains]]
chain_id = 1337
ws_url = "wss://anvil-1337.my-repo-nodeset.svc.cluster.local"
http_url = "https://anvil-1337.my-repo-nodeset.svc.cluster.local"
registry = true
[[chains]]
chain_id = 11155111
ws_url = "wss://sepolia.infura.io/ws/v3/..."
http_url = "https://sepolia.infura.io/v3/..."
# DONs — each [[dons]] entry's name must equal a Griddle chart node-set's
# don-name label (chainlink-node.registerNodes.labels.don-name); membership
# is always that node-set's nodes, resolved from the chart at run time.
[[dons]]
name = "workflow"
don_types = ["workflow"]
capabilities = ["cron", "http-action", "http-trigger", "consensus", "evm-1337"]
# bootstrap_node defaults to the node with nodeType: boot in the chart
[[dons]]
name = "capabilities"
don_types = ["capabilities"]
exposes_remote_capabilities = true
capabilities = ["vault", "evm-11155111"]
# Optional: assign gateway nodes to specific DONs
# [[gateway_nodes]]
# node = "node-gw-0"
# don = "workflow"
# Capability configs — same format as local CRE
[capability_configs.evm]
binary_name = "evm"
[capability_configs.evm.values]
LogTriggerPollInterval = 1500000000
ReceiverGasMinimum = 500
[capability_configs.cron]
binary_name = "cron"
Export GRIDDLE_JD_ACCESS_TOKEN before running apply:
export GRIDDLE_JD_ACCESS_TOKEN="..."
./bin/reconciler apply --desired cre/desired.toml --state cre/state.toml
The token is a live bearer credential and is never stored in desired.toml, state.toml, or the web UI — it is read
only from this environment variable.
apply checks that GRIDDLE_JD_ACCESS_TOKEN is set (whenever jd.grpc is configured) before doing any other
work — including Kubernetes discovery — and fails immediately with a clear error if it's missing, rather than
failing later with a confusing "JD client is required" error after partial progress. serve does not require the
token to start, so you can start it first and export the token afterward — but the web UI's "Check JD Connectivity"
button reads the token from the server's environment at click-time and reports immediately if it's missing, instead
of attempting (and failing) a real connection to JD.
Node roles are read from the Griddle chart values (nodeType field per instance):
standard (default) — oracle/plugin nodes that form DONs and run capabilities.boot — bootstrap nodes, one per DON, used as P2P bootstrappers.gateway — gateway nodes that route capability calls (vault, http-action, http-trigger). Not registered on-chain as signatory nodes.If nodeType is not set in the chart, bootstrap is inferred by naming convention (*-bt-* or *bootstrap*).
DON membership is derived from the chart's chainlink-node.registerNodes.labels.don-name label. A [[dons]] entry's name must equal a chart node-set's don-name; its member nodes are exactly that node-set. A nodes key is no longer accepted — desired.toml never lists individual node names.
Gateway nodes are detected from nodeType: gateway in the chart — they are discovered automatically, not listed under any DON. The reconciler:
[Capabilities.GatewayConnector] with Gateways[] pointing at the gateway node's WebSocket endpoint."gateway") via JD with service handler configs and sharded DON topology.All TOML changes (gateway + worker) are in the single breakpoint patch.
DISCOVER Read node info from JD (P2P IDs, OCR keys)
Read node endpoints from K8s (API URLs, credentials)
Read chain info + node roles from chart values
P1 DEPLOY Deploy Keystone contracts
P4 CONFIG Register worker nodes + capabilities + DONs on-chain
P5 RESOLVE Read actual DON IDs from the contract
P6 CONFIG Configure Workflow Registry
P9 VAULT Update vault capability config (if gateway DON)
T1 INJECT [BREAKPOINT] Write all node TOML into chart values → EXIT
User applies via Griddle (.deploy dev), re-runs tool.
R1 VERIFY Confirm nodes re-rolled with new TOML
J1 JOBS Propose + approve all JD jobs (capability + gateway)
V1 DONE Verify convergence
The breakpoint at T1 is the only place the tool stops. It writes a 30-cre TOML layer into your chart values YAML, then exits with code 42. After applying the patch via Griddle and re-running, the tool resumes from where it left off.
The state file (cre/state.toml) is committed to the consumer repo. It records:
This will enablee idempotent runs — the tool checks actual state before each phase and skips if already done.
In the future per-changeset artifacts will written in CLD-compatible format to cre/artifacts/durable_pipelines/. The directory layout and JSON schema match what the cld runtime produces.
See TODO.md for the groomed backlog of planned work (reconciliation model, feature/UX gaps, open decisions) beyond what's described here.
infra-griddle-app, griddle-stacks): deploys nodes (and optionally Anvil chains) + databases to K8s. Does not know about contracts, capabilities, or jobs. The reconciler never reads Griddle's anvil.instances — chains are declared explicitly in desired.toml (see [[chains]] above).core/scripts/cre/environment): same concept but for Docker-based environments. This tool reuses the local CRE's changeset library but not its orchestration code.fund-nodes: a separate script (not part of this tool) that tops up node EVM accounts. The reconciler assumes nodes are funded and only warns if it detects a zero balance.cd core/scripts/cre/reconciler
go build -o bin/reconciler ./cmd
go test ./cre/reconciler/... -v