Skip to content

Commit 61db11f

Browse files
authored
feat(requestlog): report speculation and build progress to requests (#568)
## Summary ### Why? A request read `batched` for the whole of its active life. It kept that status while its batch was admitted to speculation, while CI built it, and while it was being pushed, so a customer could not tell a working request from a wedged one — and diagnosing a stall meant reading the batch table directly. `entity.RequestStatus` defined eighteen values and only nine were ever published. Two things stood in the way of simply adding publish calls. `PublishLog` derived the queue message id as `{requestID}/{status}`, and `queue_messages` is uniquely indexed on `(topic, partition_key, id)` with `ON DUPLICATE KEY UPDATE`. A second `building` for a request was therefore dropped at publish time, silently. Since a batch is rebuilt every time speculation re-plans, a one-shot `building` would have left a request stuck in a rebuild loop reading identically to one that built cleanly first try — the exact distinction this is meant to restore. More fundamentally, every log entry competed to become the request's current status, and build progress cannot be one. A head funds several speculation paths and each is built separately, so one build succeeding while its siblings still run does not mean the request is finished — and because nothing publishes again until the batch resolves, a summary moved there would stay there. That is the "looks done but is wedged" reading the issue exists to kill. ### What? The request log gains two tiers. **Statuses** are coarse, one per pipeline position, and move the summary: `validating` on entry to validation, `speculating` on admission, `speculated` once a path has passed and the head is only waiting on its dependencies, and `landing` when the merge request is dispatched. `speculated` sits at the passed-path observation rather than at the merge decision, because the decision is one queue hop from `landing` while the dependency wait can run for minutes. **Events** are per (path, build), appear in history, and never move the summary: `building` and `built`, carrying the batch, path, attempt, and the runner's CI URL. `entity.IsRequestStatusEvent` names them and `logWins` skips them — no schema change, since `request_log` already stores every entry and history already returns all of them. The message id gains an occurrence discriminator, `{requestID}/{status}[/{occurrence}]`: the build id for build events, the path id for `speculated`, the batch id for `speculating` and `landing`. A redelivery of one occurrence still dedupes; a genuine repeat gets through. Existing call sites pass `""` and keep today's one-shot behaviour, which is what a terminal status wants. Every new publish goes out before the state write it reports. Each of these branches runs once — `admit` only from `BatchStateCreated`, `buildsignal` only on an observed transition — so an entry published after the write would be lost for good when it failed, since the replay reads the updated record and takes neither branch. Publishing first means a failure nacks with nothing changed, and a crash in between re-publishes under the same occurrence. `landing` is published by the orchestrator rather than runway, correcting the issue's "Where": runway is a separate service that consumes `MergeRequest` protos and holds no submitqueue storage, no log topic, and no request ids. `building` moves to `buildsignal` rather than the build controller, because `Trigger` returns only an id and the CI URL comes from `Status` — which `buildsignal` already called and discarded. The build controller is unchanged and keeps its "this stage only starts builds" invariant. `waitingpath` is dropped: the window it named is now the interval between `speculated` and `landing`. `batching` and `processing` stay in the enum unpublished. The batch controller batches immediately with no waiting gate, and `RequestStateProcessing` is never written anywhere in the submitqueue domain, so both would be synthetic moments invented to fill the enum. ## Test Plan ✅ `bazel test //submitqueue/... //service/... //platform/...` — 70 pass New coverage for the parts that are easy to get wrong: - `materializer_test.go` — the tier guarantee: a `building` or `built` entry is inserted into the log but leaves `RequestSummary.Status` untouched, while the position after it still wins. - `log_test.go` — the occurrence appears in the message id, `""` reproduces today's id exactly, and one fan-out shares its occurrence across members. - `speculate` — `speculated` fires for a passed path with unsettled assumptions; a dependency that resolves against that path puts the members back to `speculating`; `livePassedPath` recognises the waiting window that `mergeablePath` rejects. - `buildsignal` — one `building` per build carrying `build_url`, `built` only on success, nothing on an unchanged poll, and nothing recorded when the report fails. - `merge` — the `landing` fan-out is published before the runway dispatch, and a failed report stops the run before runway hears about the merge. The e2e happy path now asserts the full trail as an ordered subsequence and checks that the summary never reported an event status. ## Issue Closes CODEM-426 ## Issues
1 parent 581118c commit 61db11f

41 files changed

Lines changed: 1327 additions & 216 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

api/submitqueue/gateway/proto/gateway.proto

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,18 @@ message GetRequestHistoryByIDRequest {
170170
message HistoryEvent {
171171
// Time the request-log entry was created, in Unix milliseconds.
172172
int64 timestamp_ms = 1;
173-
// Customer-friendly request status recorded by the event.
173+
// Customer-friendly request status recorded by the event. Set only when type is "status".
174174
string status = 2;
175175
// Error message associated with the event. Empty when absent.
176176
string last_error = 3;
177177
// Display and debugging metadata associated with this event. Each lifecycle event carries its own values.
178178
map<string, string> metadata = 4;
179+
// What this entry records: "status" when the request reached a position in the pipeline,
180+
// "event" when something happened while it sat at one. Exactly one of status and event is set.
181+
string type = 5;
182+
// Occurrence recorded by the entry, e.g. a build starting or finishing. Set only when type is "event".
183+
// A request records many of these — one per build — and they never change its current status.
184+
string event = 6;
179185
}
180186

181187
// GetRequestHistoryByIDResponse contains all retained events for one request.

api/submitqueue/gateway/protopb/gateway.pb.go

Lines changed: 26 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/submitqueue/gateway/protopb/gateway.pb.yarpc.go

Lines changed: 67 additions & 66 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

service/submitqueue/gateway/server/mapper/request_history.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,18 @@ func ProtoToGetRequestHistoryByChangeURIRequest(req *pb.GetRequestHistoryByChang
3030
}
3131

3232
// HistoryEventsToProto maps retained request-log events to wire history events.
33+
//
34+
// Status and Event are mutually exclusive, mirroring the entry itself: a client
35+
// reading the timeline can render a position and an occurrence differently
36+
// without having to know which values belong to which vocabulary.
3337
func HistoryEventsToProto(logs []entity.RequestLog) []*pb.HistoryEvent {
3438
events := make([]*pb.HistoryEvent, len(logs))
3539
for i, log := range logs {
3640
events[i] = &pb.HistoryEvent{
3741
TimestampMs: log.TimestampMs,
42+
Type: string(log.Type),
3843
Status: string(log.Status),
44+
Event: string(log.Event),
3945
LastError: log.LastError,
4046
Metadata: cloneStringMap(log.Metadata),
4147
}

0 commit comments

Comments
 (0)