krabka-protocol
September 14, 2026 · View on GitHub
The Apache Kafka wire layer that krabka is built on: request and response codecs for every Kafka API, the KRaft metadata record types, SASL/TLS authentication, and the small domain crates the rest of the stack shares.
Everything here is below the client and the broker in the dependency graph. Nothing in this repository depends on either.
Crates
| Crate | What it is |
|---|---|
krabka-protocol | Kafka request/response codecs, generated from the upstream JSON schemas. |
krabka-protocol-codegen | The generator that emits those codecs. Driven by tools/regenerate.sh. |
krabka-metadata | KRaft metadata records and the in-memory metadata image. |
krabka-security | SASL (SCRAM, OAUTHBEARER, GSSAPI, PLAIN) and TLS configuration. |
krabka-compression | gzip, snappy, lz4 and zstd record-batch codecs. |
krabka-voters | KRaft voter-set model. |
krabka-ids | Topic, partition, broker and producer identifiers. |
krabka-units | Typed quantities (bytes, durations, rates) used across the stack. |
krabka-trace-context | W3C trace-context and sqlcommenter propagation. |
krabka-kafka-tap | Test-only TCP tap that records Kafka frames for corpus capture. |
Build
Bazel is the build and test path. Cargo stays the dependency source of truth:
rules_rs reads the same
Cargo.toml and Cargo.lock that Cargo does, so there is no second dependency
set to keep in sync.
bazel test //...
cargo still works for everything Bazel does not cover:
cargo nextest run --workspace
Everything CI does, locally
The Aspect CLI narrows each task to what a change actually touched. Every one has a plain-Bazel equivalent, so the CLI is a convenience rather than a requirement:
| Aspect CLI | Plain Bazel | |
|---|---|---|
| Build | aspect build //... | bazel build //... |
| Test | aspect test //... | bazel test //... |
| Lint | aspect lint | bazel build --config=lint //... |
| Format | aspect format | bazel run //tools/format |
| Coverage | aspect test --coverage | bazel coverage //crates/... |
| Docs | — | bazel build //crates/protocol:protocol_doc |
Formatting and linting are Bazel targets rather than a separate cargo fmt /
cargo clippy pass, so they see exactly the files and crates the build sees. A
file in no target cannot drift unnoticed, and clippy resolves the same features
the build resolves.
Two details worth knowing:
- rustfmt runs on a pinned nightly.
rustfmt.tomlusesformat_code_in_doc_comments,group_importsandimports_granularity, all still nightly-gated; stable rustfmt warns and silently skips them. The nightly is pinned inMODULE.bazel, so formatting is reproducible rather than a function of whichever nightly is installed. rustfmt.tomlstates its edition.cargo fmtpasses--editionfromCargo.toml; rustfmt invoked directly defaults to 2015 and sortsuselists differently. Stating it makes formatting a property of the repository rather than of how rustfmt was launched.
Mutation testing
Mutation sweeps run through
rules_rs_mutants, which lets
cargo-mutants enumerate mutants and lets Bazel build and test them. Each crate
has a <crate>_mutants target:
bazel test //crates/metadata:metadata_mutants
They are tagged manual, so bazel test //... skips them and a nightly job runs
the full sweep. Two things to know about the results:
- Only
#[cfg(test)]unit tests take part. Mutants that thetests/*.rssuites would kill are reported as survivors here, so scores read lower than the monorepo'scargo mutantsnumbers for the same code. - The
cargo-mutantsversion is pinned bytools/mutants/Cargo.lock, not by whatever is onPATH.
What does not run under Bazel
One recording tool, and the tests of krabka-protocol-codegen. Everything else
is the same set under bazel test //... and cargo test --workspace.
capture_corpus is tagged manual: it records new fixtures through the JVM
oracle rather than reading them, so it is a tool rather than a test.
The codegen tests, apart from differential_table_emit, are tagged manual as
well. Each one reads the Kafka schemas in //crates/protocol, or the snapshots
in its own package, through env!("CARGO_MANIFEST_DIR"). Cargo resolves that
to the crate directory. Bazel gives it the absolute path of a sandbox, and
rules_rs refuses to build an output that retains one, so the unit target does
not even compile. The codegen drift CI job runs the whole crate under Cargo.
Suites needing Docker, the JVM oracle or an MIT KDC are #[ignore]d
individually, so they build and skip under both build systems, the same way.
The jvm differential CI job builds tools/oracle
and runs the JVM oracle suites under Cargo with --ignored.
Getting there took two things worth knowing about:
datatest-stableis patched under Bazel. Its fixture walk keeps only entries whosefile_type().is_file(), andwalkdirdoes not follow symlinks by default — so for the symlinks Bazel stages in a runfiles tree the predicate is false, the walk finds nothing, and the harness reports an empty run rather than a failure. That silently skipped 620 fixtures.bazel/patches/datatest-stable-follow-links.patchaddsfollow_links(true), applied throughcrate.annotation; Cargo keeps using the released crate.- Fixture paths resolve against the working directory. Cargo runs an
integration test from the crate directory; Bazel runs it from an execution
root and stages data under
$TEST_SRCDIR/$TEST_WORKSPACE. The suites that read fixtures take aFIXTURE_ROOTprefix, set in theircrate_testscall, and fall back to the bare relative path when it is unset.
Neither of these is Bazel-specific pedantry: a test that resolves
CARGO_MANIFEST_DIR at run time, or reads a path relative to the working
directory, only works when it is launched the way Cargo happens to launch it.
Contributing
docs/CONTRIBUTING.md covers the style guides, the
regeneration of the wire codecs, and the procedure for bumping the upstream
Kafka version.
Publishing
These crates are published to crates.io from
robot-head/crabka, which is still the
release home for the krabka-* names. This repository has no release
automation; consumers pin it by git revision.