<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Developer guide on Eventboat</title><link>https://eventboat.dev/docs/developer/</link><description>Recent content in Developer guide on Eventboat</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Mon, 01 Jan 0001 00:00:00 +0000</lastBuildDate><atom:link href="https://eventboat.dev/docs/developer/index.xml" rel="self" type="application/rss+xml"/><item><title>Architecture &amp; package map</title><link>https://eventboat.dev/docs/developer/01-architecture/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://eventboat.dev/docs/developer/01-architecture/</guid><description>&lt;p&gt;Eventboat is a single Go binary that routes events: messages arrive from&#10;sources (Kafka, HTTP, cron, file, SQL), flow through an explicit DAG of&#10;transforms and filtered edges, and land at sinks — at-least-once, verifiable&#10;before anything runs, and replayable after a crash. Pipelines are declared in&#10;YAML, statically verified against plugin JSON Schemas and topology rules, and&#10;then executed by an engine whose entire reliability model is pinned by eight&#10;dedicated invariant tests. Predicates are CEL, transforms are Starlark (or&#10;WASM), so there is no custom language to learn.&lt;/p&gt;</description></item><item><title>Engine internals</title><link>https://eventboat.dev/docs/developer/02-engine/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://eventboat.dev/docs/developer/02-engine/</guid><description>&lt;p&gt;The engine (&lt;code&gt;internal/engine&lt;/code&gt;) executes one compiled pipeline against one&#10;store: source admission through a durable spool, in-memory DAG execution with&#10;commit tracking, per-edge delivery retries, dead lettering, checkpointing and&#10;backpressure. The package is five files plus tests: &lt;code&gt;engine.go&lt;/code&gt; (lifecycle,&#10;persistence), &lt;code&gt;admission.go&lt;/code&gt; (the inbound path), &lt;code&gt;nodes.go&lt;/code&gt; (transform/sink&#10;workers, dead letters), &lt;code&gt;commit.go&lt;/code&gt; (the commit frontier), plus&#10;&lt;code&gt;sourcecommit.go&lt;/code&gt; (the per-source watermark committers). Every reliability&#10;property is phrased as one of the eight invariants (see&#10;&lt;a href="../01-architecture/"&gt;Architecture&lt;/a&gt;), each with a dedicated test in&#10;&lt;code&gt;internal/engine/invariants_test.go&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Plugin system &amp; registry</title><link>https://eventboat.dev/docs/developer/03-plugins/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://eventboat.dev/docs/developer/03-plugins/</guid><description>&lt;p&gt;Every source, sink, codec and transform in eventboat is a plugin registered&#10;in a &lt;code&gt;registry.Registry&lt;/code&gt; (&lt;code&gt;internal/registry/registry.go&lt;/code&gt;). Registration&#10;carries a JSON Schema; configuration blocks are validated strictly against it&#10;(unknown fields are errors), which is the mechanism that keeps agents from&#10;inventing plugins or fields that do not exist (redesign-v3.md §5.6). The&#10;registry is a leaf package: it imports nothing internal, so any Go program —&#10;including your own build of eventboat — can register plugins into it.&lt;/p&gt;</description></item><item><title>Configuration &amp; diagnostics</title><link>https://eventboat.dev/docs/developer/04-config-pipeline/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://eventboat.dev/docs/developer/04-config-pipeline/</guid><description>&lt;p&gt;A pipeline is one YAML file loaded by &lt;code&gt;internal/config&lt;/code&gt; and compiled by&#10;&lt;code&gt;internal/ir&lt;/code&gt;. Loading is deliberately strict: unknown keys, unknown plugins&#10;and un-compilable expressions are errors, not warnings — the config is a&#10;contract with agents as much as with humans. Every finding surfaces as a&#10;&lt;code&gt;config.Diagnostic&lt;/code&gt; with a stable &lt;code&gt;code&lt;/code&gt;, severity, file/line, message and&#10;hint (&lt;code&gt;internal/config/config.go&lt;/code&gt;); &lt;code&gt;--json&lt;/code&gt; output, the LSP, the MCP verify&#10;tool and the admin UI all render the same structs.&lt;/p&gt;</description></item><item><title>Expressions &amp; scripting sandbox</title><link>https://eventboat.dev/docs/developer/05-scripting/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://eventboat.dev/docs/developer/05-scripting/</guid><description>&lt;p&gt;Eventboat hosts three languages, each with one job: &lt;strong&gt;CEL&lt;/strong&gt; is the default&#10;edge predicate dialect, &lt;strong&gt;CESQL&lt;/strong&gt; is an opt-in CloudEvents-flavored&#10;alternative for edges, and &lt;strong&gt;Starlark&lt;/strong&gt; is the scripting language of the&#10;built-in &lt;code&gt;script&lt;/code&gt; transform. All three are sandboxed, deterministic and&#10;side-effect free — a property &lt;code&gt;explain&lt;/code&gt; relies on to dry-run pipelines, and&#10;the reason replay produces the same answers as production. The WASM tier&#10;covers compute-heavy transforms that outgrow Starlark.&lt;/p&gt;</description></item><item><title>Observability &amp; operations</title><link>https://eventboat.dev/docs/developer/06-observability/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://eventboat.dev/docs/developer/06-observability/</guid><description>&lt;p&gt;Eventboat exposes three operations surfaces that share one implementation:&#10;the MCP tools, the Admin REST endpoints (plus SSE and an embedded read-only&#10;UI), and the &lt;code&gt;--json&lt;/code&gt; CLI outputs are all thin shells over&#10;&lt;code&gt;internal/ops.Service&lt;/code&gt; — REST, MCP and CLI emit the same Go structs and JSON&#10;shapes. Telemetry is OpenTelemetry with dual export (Prometheus pull +&#10;OTLP/HTTP push); the metric set is fixed and enumerated in&#10;&lt;code&gt;internal/obs/obs.go&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h2 id="metrics"&gt;Metrics&#10;&lt;/h2&gt;&#10;&lt;p&gt;One MeterProvider, two readers (&lt;code&gt;internal/obs/obs.go&lt;/code&gt;): Prometheus&#10;exposition at &lt;code&gt;/metrics&lt;/code&gt; and, when &lt;code&gt;telemetry.otlp_endpoint&lt;/code&gt; is set, an&#10;OTLP/HTTP push reader (10s interval). Fully disabled telemetry costs nothing&#10;(noop providers; every &lt;code&gt;Obs&lt;/code&gt; helper is nil-receiver safe). Every instrument&#10;carries the &lt;code&gt;eventboat_&lt;/code&gt; prefix (the static set below, plus the per-source&#10;counters created lazily on first use):&lt;/p&gt;</description></item><item><title>Testing guide</title><link>https://eventboat.dev/docs/developer/07-testing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://eventboat.dev/docs/developer/07-testing/</guid><description>&lt;p&gt;Eventboat&amp;rsquo;s testing philosophy: every reliability property is a named,&#10;retrievable test; every externally visible surface (help screens, plugin&#10;schemas) is pinned by a golden file; and anything that claims to work&#10;against the real world (Kafka, long runs) is proven against the real thing&#10;in CI. The full suite is &lt;code&gt;go test ./...&lt;/code&gt;; CI runs it with &lt;code&gt;-race&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h2 id="the-test-taxonomy"&gt;The test taxonomy&#10;&lt;/h2&gt;&#10;&lt;div class="td-table-scroll td-table-scroll--static"&gt;&#10;&lt;table&gt;&#10; &lt;thead&gt;&#10; &lt;tr&gt;&#10; &lt;th scope="col"&gt;Layer&lt;/th&gt;&#10; &lt;th scope="col"&gt;Where&lt;/th&gt;&#10; &lt;th scope="col"&gt;What it proves&lt;/th&gt;&#10; &lt;/tr&gt;&#10; &lt;/thead&gt;&#10; &lt;tbody&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;Unit tests&lt;/td&gt;&#10; &lt;td&gt;beside the code (&lt;code&gt;*_test.go&lt;/code&gt; per package)&lt;/td&gt;&#10; &lt;td&gt;package-level behavior: loader rules, schema generation, host evaluation, store semantics&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;Engine invariants&lt;/td&gt;&#10; &lt;td&gt;&lt;code&gt;internal/engine/invariants_test.go&lt;/code&gt;&lt;/td&gt;&#10; &lt;td&gt;the eight §6.2 reliability invariants, one test each&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;Engine behavior&lt;/td&gt;&#10; &lt;td&gt;&lt;code&gt;internal/engine/*_test.go&lt;/code&gt;&lt;/td&gt;&#10; &lt;td&gt;delivery, fan-out, split, transform plugins, wasm, CESQL, admission pooling&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;Persistence/recovery&lt;/td&gt;&#10; &lt;td&gt;&lt;code&gt;internal/engine/commit_persist_test.go&lt;/code&gt;, &lt;code&gt;retention_test.go&lt;/code&gt;, &lt;code&gt;internal/store&lt;/code&gt;&lt;/td&gt;&#10; &lt;td&gt;flush ordering, retention bounds, crash replay&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;Golden tests&lt;/td&gt;&#10; &lt;td&gt;&lt;code&gt;internal/cli/testdata/help/&lt;/code&gt;, &lt;code&gt;internal/registry/builtin/testdata/schemas/&lt;/code&gt;&lt;/td&gt;&#10; &lt;td&gt;CLI help screens; generated plugin JSON Schemas&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;Contract tests&lt;/td&gt;&#10; &lt;td&gt;&lt;code&gt;internal/testrun&lt;/code&gt; + &lt;code&gt;examples/*/tests/&lt;/code&gt;&lt;/td&gt;&#10; &lt;td&gt;pipeline behavior through the real engine via suite YAML&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;Integration&lt;/td&gt;&#10; &lt;td&gt;&lt;code&gt;internal/inttests/kafka&lt;/code&gt;, &lt;code&gt;internal/inttests/soak&lt;/code&gt;&lt;/td&gt;&#10; &lt;td&gt;real broker roundtrips; long-run stability (env-gated, need Docker)&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;Benchmarks&lt;/td&gt;&#10; &lt;td&gt;&lt;code&gt;internal/engine&lt;/code&gt;, &lt;code&gt;internal/lang/*&lt;/code&gt;, &lt;code&gt;internal/wasmhost&lt;/code&gt;&lt;/td&gt;&#10; &lt;td&gt;performance gates and the Starlark-vs-WASM comparison&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;Acceptance&lt;/td&gt;&#10; &lt;td&gt;&lt;code&gt;internal/cli/examples_test.go&lt;/code&gt;, &lt;code&gt;acceptance_test.go&lt;/code&gt; (root), &lt;code&gt;internal/rpcplugin/acceptance_test.go&lt;/code&gt;, &lt;code&gt;internal/cli/agent_loop_test.go&lt;/code&gt;&lt;/td&gt;&#10; &lt;td&gt;every example verifies and passes its suites; the custom-build binary links &lt;code&gt;pkg/plugin&lt;/code&gt; plugins and runs them; the third-party gRPC plugin builds and runs; the full agent loop over MCP stdio&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&#10;&lt;h2 id="the-eight-engine-invariants"&gt;The eight engine invariants&#10;&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;internal/engine/invariants_test.go&lt;/code&gt; pins redesign-v3.md §6.2 — these names&#10;are load-bearing; do not rename them:&lt;/p&gt;</description></item><item><title>Building &amp; release</title><link>https://eventboat.dev/docs/developer/08-building/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://eventboat.dev/docs/developer/08-building/</guid><description>&lt;p&gt;Eventboat builds with the standard Go toolchain and no code generation step&#10;in the normal path: &lt;code&gt;go build ./...&lt;/code&gt; is the whole story for contributors.&#10;This page covers the local build, the container image, the CI workflows,&#10;and how releases are cut.&lt;/p&gt;&#10;&lt;h2 id="prerequisites"&gt;Prerequisites&#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Go 1.25&lt;/strong&gt; (matches &lt;code&gt;go.mod&lt;/code&gt;; the CI jobs pin the same version).&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Docker&lt;/strong&gt; — only for the env-gated integration suites (the Kafka&#10;testcontainers test) and for building/running the container image.&lt;/li&gt;&#10;&lt;li&gt;Nothing else: all drivers are pure Go (SQLite via &lt;code&gt;modernc.org/sqlite&lt;/code&gt;,&#10;Postgres via pgx, Kafka via kafka-go), so CGO is never required.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="local-build-and-run"&gt;Local build and run&#10;&lt;/h2&gt;&#10;&lt;div class="td-code td-code--untitled" id="td-code-79a1c2a4-fence-0" data-td-code data-td-code-auto-id&#10; data-td-language="bash" data-td-line-count="6"&gt;&#10; &lt;div class="td-code__viewport" id="td-code-79a1c2a4-fence-0-viewport" data-td-code-viewport&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go build -o eventboat ./cmd/eventboat &lt;span class="c1"&gt;# the single binary&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./eventboat &lt;span class="c1"&gt;# bare invocation prints help, exit 0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./eventboat verify --config examples/linear/pipeline.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./eventboat &lt;span class="nb"&gt;test&lt;/span&gt; examples&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./eventboat run --config examples/linear/pipeline.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./eventboat run --config my.yaml --ephemeral &lt;span class="c1"&gt;# in-memory store, no ./data&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;The example job pipeline&amp;rsquo;s sqlite source database regenerates with&#10;&lt;code&gt;go run ./examples/job-sync/seed&lt;/code&gt;. The WASM test guest is committed as a&#10;built artifact (&lt;code&gt;internal/wasmhost/testdata/aggregate.wasm&lt;/code&gt;), so plain&#10;&lt;code&gt;go test ./...&lt;/code&gt; needs no wasm toolchain; rebuild it with&#10;&lt;code&gt;GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o ../aggregate.wasm .&lt;/code&gt;&#10;from &lt;code&gt;internal/wasmhost/testdata/guest&lt;/code&gt; (CI does exactly this).&lt;/p&gt;</description></item><item><title>Contributing</title><link>https://eventboat.dev/docs/developer/09-contributing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://eventboat.dev/docs/developer/09-contributing/</guid><description>&lt;p&gt;This repo has a strong house style — it is what keeps a codebase with this&#10;many guarantees reviewable. Read this page before your first PR; most of it&#10;is enforceable by CI, but the commit message convention and the comment&#10;voice are reviewed by humans.&lt;/p&gt;&#10;&lt;h2 id="dev-workflow"&gt;Dev workflow&#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Branch from &lt;code&gt;main&lt;/code&gt;; keep &lt;strong&gt;one logical change per commit&lt;/strong&gt; — the git log&#10;reads as a series of self-contained rationales, and bisectability depends&#10;on it.&lt;/li&gt;&#10;&lt;li&gt;Run the fast loop before pushing: &lt;code&gt;go build ./...&lt;/code&gt;, &lt;code&gt;go test -race ./...&lt;/code&gt;&#10;(CI runs the race build on every push), and &lt;code&gt;bash scripts/bench-gate.sh&lt;/code&gt;&#10;if you touched a hot path.&lt;/li&gt;&#10;&lt;li&gt;User-visible changes get a &lt;code&gt;CHANGELOG.md&lt;/code&gt; &lt;strong&gt;Unreleased&lt;/strong&gt; entry at commit&#10;time, not at release time. Entries carry a &lt;code&gt;Changed&lt;/code&gt;/&lt;code&gt;Added&lt;/code&gt;/&lt;code&gt;Fixed&lt;/code&gt;/&#10;&lt;code&gt;Removed&lt;/code&gt; heading and the same paragraph voice as the commit — the&#10;Settled→Commit rename entry, for example, includes the complete old→new&#10;mapping because external consumers needed it to migrate.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="the-commit-message-convention"&gt;The commit message convention&#10;&lt;/h2&gt;&#10;&lt;p&gt;This repo&amp;rsquo;s commits are &lt;strong&gt;single, detailed, paragraph-led messages written in&#10;the imperative&lt;/strong&gt; that explain WHAT and WHY, with concrete file and identifier&#10;references. No one-line subjects, no bullet laundry lists (a short list is&#10;acceptable &lt;em&gt;inside&lt;/em&gt; the paragraph when enumerating parallel changes). Read a&#10;few from &lt;code&gt;git log&lt;/code&gt; — e.g. the &lt;code&gt;Rename Settled → Commit everywhere&lt;/code&gt; commit or&#10;the &lt;code&gt;Promote transforms to registered plugins&lt;/code&gt; commit — they record the&#10;motivation, the ruling that decided contested points, the migration/compat&#10;story, and the verification done (which tests were run). A good test: could&#10;a reviewer reconstruct the change from the message alone in a year?&lt;/p&gt;</description></item></channel></rss>