crates/dwarves-embed/proto/explorer.proto
// explorer.proto — the forge's 🧭 EXPLORER (2026-10-16).
//
// The Dwarves project (`<warehouse>/projects/<name>.dwarves.toml`) as a tree —
// project → Mímir → Workspaces → Build Things → Pipelines — and, for the node a
// person selects, what the forge knows about it: a workspace's repos and their
// state and its recent builds; a Build Thing's variants and the artifacts Builder 2
// landed in Mímir; a pipeline's stages and its recent runs.
//
// Every answer is AT ONCE. A read is folded on the forge's jera queue (the kept
// answer, or `queued` / `running` and the job); every action (build a workspace,
// build a Build Thing, run a pipeline) is a jera job too: the call answers with the
// job id, and `Job` answers how that job is doing. The UI asks again, each a short
// call. It never dials nornir.
//
// The SAME file is compiled by the forge (`crates/dwarves`, server + client) and by
// the UI's thin client (`crates/dwarves-embed`, client): a test keeps the copies
// byte-identical.
syntax = "proto3";
package dwarf.v1;
service Explorer {
// The project tree + the detail of `select` (a node id of that tree).
rpc Ask (ExplorerAsk) returns (ExplorerAnswer);
// Queue the super build of a workspace (`nornir workspace build <ws>`, the road a
// Runa `BuildWorkspace` step runs). `only = "affected"` builds what moved and what
// depends on it (`--affected`); any other scope is refused by name.
rpc BuildWorkspace (ExplorerBuildWorkspace) returns (ExplorerAnswer);
// Queue a build of one of the project's Build Things (Builder 2).
rpc BuildThing (ExplorerBuildThing) returns (ExplorerAnswer);
// Queue a run of one of the project's Runa pipelines.
rpc RunPipeline (ExplorerRunPipeline) returns (ExplorerAnswer);
// How a queued action is doing.
rpc Job (ExplorerJob) returns (ExplorerAnswer);
}
message ExplorerAsk {
// The registered project; empty = the forge's first.
string project = 1;
// The selected node id (`workspace:nordisk`, `build_thing:nornir`,
// `pipeline:dwarves-forge`, …); empty = the project itself.
string select = 2;
// Fold it again even when a fresh answer is kept.
bool fresh = 3;
}
message ExplorerBuildWorkspace {
string project = 1;
string workspace = 2;
// `all` (or empty) · `affected` (what moved and what depends on it).
string only = 3;
}
message ExplorerBuildThing {
string project = 1;
// The Build Thing's name in the project.
string name = 2;
// One variant; empty = the Build Thing's default.
string variant = 3;
// The operator's cargo feature choice for its cargo variant(s): `--features`
// (one entry per feature), `--no-default-features`, `--all-features`.
repeated string features = 4;
bool no_default_features = 5;
bool all_features = 6;
}
message ExplorerRunPipeline {
string project = 1;
// The pipeline's name in the project.
string name = 2;
}
message ExplorerJob {
string job_id = 1;
}
message ExplorerAnswer {
// Ask: `done` (json is the answer) · `queued` / `running` (job_id; json may carry
// the last kept answer) · `failed` · `refused` (note says why).
// An action: `queued` (job_id) · `refused` (note says why).
// Job: `queued` · `running` · `done` · `failed` · `unknown`.
string state = 1;
string job_id = 2;
string note = 3;
string json = 4;
// How long the job has waited / run, in ms.
uint64 waited_ms = 5;
// How old the answer in json is, in ms.
uint64 age_ms = 6;
}