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 | |
|---|---|
message | Echoed 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 | |
|---|---|
message | The message that was sent. |
daemon_version | The node's version, for a pod that wants to report what served it. |
pod | The 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 | |
|---|---|
applied | How 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 | |
|---|---|
id | Caller-chosen node id, unique within this pod. Ids are the caller's own namespace — the node maps them onto the build's tree. |
parent | The parent node's id. Absent → the node hangs directly beneath the pod's own node in the build's progress tree. |
label | Human-readable description, e.g. a test name or a source file. |
state | The node's lifecycle state. |
error | Why the node failed. Meaningful with state = NODE_STATE_FAILED. |
progress | Quantitative progress of a running node. |
labels | Free-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 | |
total | Absent 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_QUEUED | Declared, not started — the build knows the node is coming. |
NODE_STATE_RUNNING | |
NODE_STATE_COMPLETED | Terminal. |
NODE_STATE_FAILED | Terminal; NodeEdit.error says why. |
NodeProgressUnit
enum NodeProgressUnit {
NODE_PROGRESS_UNIT_UNSPECIFIED = 0;
NODE_PROGRESS_UNIT_ITEMS = 1;
NODE_PROGRESS_UNIT_BYTES = 2;
}