crates/nornir-warehouse-embed/proto/bench.proto
// A TRIMMED copy of nornir-server's `Bench` service — `Submit` + `History`, and
// the messages they need. Package `nornir.v1` so the generated method paths
// (`/nornir.v1.Bench/Submit`, `/nornir.v1.Bench/History`) match the live
// `nornir-server` — dwarves speaks it as a THIN client WITHOUT linking the
// `nornir` crate (a `dwarves → nornir` edge would be a dependency cycle; the
// same pattern telemetry.proto follows).
//
// WHY dwarves needs it. Running a build-and-measure over a product is FORGE work
// (`dwarves bench run`); persisting the numbers is WAREHOUSE work. nornir holds
// the redb catalog lock, so the forge cannot append to `bench_runs` itself — it
// ships the finished `BenchRun` here and the server appends it. That is exactly
// what the upstream comment on `rpc Submit` already described ("the CLI runs
// benches locally … and ships the result over"); this makes the forge one of
// those callers.
//
// `History` was deliberately not copied at first — reading bench history was a
// warehouse query and stayed a nornir verb (`nornir bench history-show`). The
// doc-book MASHUP renderer moving into dwarves changed that: it composes the
// recorded numbers into the docs, so dwarves needs its OWN read-back leg. This
// is still the thin-client shape, not the twin: the QUERY stays nornir's (the
// server answers it out of its warehouse); dwarves only carries the call.
//
// Keep the field numbers identical to `nornir.proto` (edda's `nornir-wire` leaf)
// so the wire is byte-compatible — `tests/proto_drift.rs` re-derives that
// contract and fails on drift.
syntax = "proto3";
package nornir.v1;
service Bench {
// Read a repo's recorded runs back out of the warehouse — the leg the
// doc-book mashup renderer composes its numbers from.
rpc History (RepoOnly) returns (BenchHistoryResponse);
// Append a freshly-computed BenchRun. The CLI runs benches locally
// (the only place that can — they need the dev's hardware) and ships
// the result over for the server to persist into iceberg.
rpc Submit (SubmitBenchRequest) returns (SubmitBenchResponse);
}
message SubmitBenchRequest {
string repo = 1;
BenchRun run = 2;
}
message SubmitBenchResponse {
string run_id = 1;
}
message RepoOnly {
string repo = 1;
}
message BenchHistoryResponse {
string repo = 1;
repeated BenchRun runs = 2;
}
// Byte-identical field numbering to nornir-wire's `KVF`. Declared here because
// the trimmed telemetry/workspaces/warehouse stubs never needed it.
message KVF {
string key = 1;
double value = 2;
}
message BenchResult {
string name = 1;
repeated KVF metrics = 2;
}
message BenchRun {
string date = 1;
string timestamp = 2; // RFC3339, may be empty
string version = 3;
string machine = 4;
uint32 cores = 5;
repeated BenchResult results = 6;
repeated TestOutcome tests = 7;
// The run's identity, minted by the PRODUCER (uuid v7) when the run was
// measured — NOT by the server that stores it. Empty on a receipt written
// before 2026-09-20, which is what tells the store "this one cannot be
// deduplicated". Carrying it over the wire is what makes `Bench.Submit`
// idempotent; without it the remote path would still double-write.
string id = 8;
// `nornir_bench::machine::Machine` as JSON — what the BOX is (hostname, cpu
// model, physical cores, logical threads, RAM, GPU, kernel, OS). A JSON
// string rather than a nested message so the block can gain a field without
// a wire change on both sides; empty when the producer did not look.
string machine_facts_json = 9;
}
message TestOutcome {
string name = 1;
bool passed = 2;
double duration_ms = 3; // 0 when absent
bool has_duration = 4; // distinguishes "0 ms" from "not recorded"
string message = 5; // empty when absent
}