[bldr]docs
gRPC API

Build progress signalling

What a build pod reports about its own progress, over the socket its node binds for it.

Pod → daemon build notifications: the API facade a build pod uses to report its own progress into the build that started it.

Everything else a pod tells the build is inferred from the outside — an exit code, a log stream, a captured tree. That is enough to know whether a step worked and not enough to know what it did: a test runner knows which case it is on, a compiler knows which file failed to typecheck, and today both can only spell that out in a log line somebody has to read. This service is where they say it structurally instead.

Reached over a Unix socket the node binds per pod and bind-mounts into it (/run/bldr-notify.sock), served by a server instance dedicated to that one pod. The socket therefore is the credential: there is no pod id, build id or token in any request here, because a caller cannot reach a socket that belongs to another pod. Opt-in per pod (pod.dag's run.notify), so a pod that was not given the facade has nothing to call.

The client shipped for pods is bldr-notify (statically linked, mounted at /bin/bldr-notify), so the common cases need no gRPC code in the pod.

Service bldr.notify.v1.BuildNotifyService, 2 rpcs.

Ping(PingRequest) -> PingResponse

Round-trip check: confirms the socket is reachable and names the pod the facade is bound to. Changes no build state.

Request: PingRequest

message PingRequest {
  optional string message = 1;
}
Field
messageEchoed back verbatim, so a caller can tell its own round-trip apart from a stale reply.

Response: PingResponse

message PingResponse {
  optional string message = 1;
  optional string daemon_version = 2;
  optional string pod = 3;
}
Field
messageThe message that was sent.
daemon_versionThe node's version, for a pod that wants to report what served it.
podThe pod this facade belongs to, as the build names it — the pod.dag op id, or the pod's explicit name.

Emit(EmitRequest) -> EmitResponse

Apply a batch of edits to this pod's progress subtree.

Deliberately unary rather than streaming: an edit that reached the daemon is an edit the build has, so a pod that dies mid-run keeps every node it already reported. A stream would leave the last window of edits in a broken pipe, which is exactly the window a crashing test runner cares about. Batching is how a caller trades round-trips for that granularity — one call carries as many edits as it likes, and they apply in order.

Request: EmitRequest

A batch of progress-tree edits, applied in order.

message EmitRequest {
  repeated NodeEdit edits = 1;
}

Response: EmitResponse

message EmitResponse {
  optional uint32 applied = 1;
}
Field
appliedHow many edits were applied.

Types used above

NodeEdit

One edit of one node in the pod's progress subtree.

An edit is an upsert keyed on id: the first edit naming an id creates the node, later ones update it. Every unset field leaves that property of the node as it is, so a caller that only moves a node to COMPLETED sends only id and state.

message NodeEdit {
  optional string id = 1;
  optional map<string, string> labels = 7;
  oneof _parent {
    string parent = 2;
  }
  oneof _label {
    string label = 3;
  }
  oneof _state {
    NodeState state = 4;
  }
  oneof _error {
    string error = 5;
  }
  oneof _progress {
    NodeProgress progress = 6;
  }
}
Field
idCaller-chosen node id, unique within this pod. Ids are the caller's own namespace — the node maps them onto the build's tree.
parentThe parent node's id. Absent → the node hangs directly beneath the pod's own node in the build's progress tree.
labelHuman-readable description, e.g. a test name or a source file.
stateThe node's lifecycle state.
errorWhy the node failed. Meaningful with state = NODE_STATE_FAILED.
progressQuantitative progress of a running node.
labelsFree-form node metadata. language names a syntax for rendering label (e.g. "sh"), matching the build API's own task labels.

NodeProgress

message NodeProgress {
  optional uint64 current = 1;
  optional NodeProgressUnit unit = 3;
  oneof _total {
    uint64 total = 2;
  }
}
Field
current 
totalAbsent for an open-ended count (bytes read with no known total).
unit 

NodeState

enum NodeState {
  NODE_STATE_UNSPECIFIED = 0;
  NODE_STATE_QUEUED = 1;
  NODE_STATE_RUNNING = 2;
  NODE_STATE_COMPLETED = 3;
  NODE_STATE_FAILED = 4;
}
Value
NODE_STATE_UNSPECIFIED 
NODE_STATE_QUEUEDDeclared, not started — the build knows the node is coming.
NODE_STATE_RUNNING 
NODE_STATE_COMPLETEDTerminal.
NODE_STATE_FAILEDTerminal; NodeEdit.error says why.

NodeProgressUnit

enum NodeProgressUnit {
  NODE_PROGRESS_UNIT_UNSPECIFIED = 0;
  NODE_PROGRESS_UNIT_ITEMS = 1;
  NODE_PROGRESS_UNIT_BYTES = 2;
}

On this page