Skip to content

Commit b3b8048

Browse files
authored
[1/N] feat(storage): add request read-model stores (#340)
## Summary Add storage contracts, MySQL implementations, generated mocks, and integration coverage for request summaries, queue projections, and change URI mappings. ## Test Plan ✅ `make fmt && make build && make test && make e2e-test` ## Issues ## Stack 1. @ #340 1. #341 1. #342 1. #343 1. #344 1. #345 1. #346
1 parent b7ffb44 commit b3b8048

23 files changed

Lines changed: 1083 additions & 59 deletions

doc/rfc/submitqueue/history-api.md

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1-
# Gateway History APIs
1+
# Gateway Request History APIs
22

3-
Design notes for gateway history APIs that return retained lifecycle events selected by SubmitQueue request ID or change ID.
3+
Design notes for gateway history APIs that return retained lifecycle events selected by SubmitQueue request ID or change URI.
44

55
This document captures **design decisions and rationale only**.
66

77
## Problem
88

9-
Users need to inspect how a request progressed through SubmitQueue, not only its current reconciled state. They may start with the `sqid` returned by `Land` or with a provider-specific change ID represented by a change URI supplied to `Land`. The existing `Status` API collapses the append-only request log into one current status, which is appropriate for polling but hides the sequence of events needed for debugging and lifecycle displays.
9+
Users need to inspect how a request progressed through SubmitQueue, not only its current reconciled state. They may start with the `sqid` returned by `Land` or with a provider-specific change URI supplied to `Land`. The request-summary API collapses the append-only request log into one current status, which is appropriate for polling but hides the sequence of events needed for debugging and lifecycle displays.
1010

1111
The gateway owns the request log and is the only service that reads it. The history APIs preserve that ownership boundary by serving gateway-owned `RequestLog` records for the request or requests selected by the caller.
1212

1313
## API Shape
1414

15-
The gateway exposes two read-only RPCs because an `sqid` selects one event list while a change ID may select multiple requests:
15+
The gateway exposes two read-only RPCs because an `sqid` selects one event list while a change URI may select multiple requests:
1616

1717
```proto
18-
message HistoryBySQIDRequest {
18+
message GetRequestHistoryByIDRequest {
1919
// Globally unique identifier for a request, as returned by Land.
2020
string sqid = 1;
2121
}
@@ -31,14 +31,14 @@ message HistoryEvent {
3131
map<string, string> metadata = 4;
3232
}
3333
34-
message HistoryBySQIDResponse {
34+
message GetRequestHistoryByIDResponse {
3535
// Retained request-log events ordered by timestamp_ms ascending with a stable tie-breaker.
3636
repeated HistoryEvent events = 1;
3737
}
3838
39-
message HistoryByChangeIDRequest {
40-
// Provider-specific change identifier represented by a change URI supplied to Land.
41-
string change_id = 1;
39+
message GetRequestHistoryByChangeURIRequest {
40+
// Exact change URI supplied to Land.
41+
string change_uri = 1;
4242
}
4343
4444
message RequestHistory {
@@ -48,22 +48,22 @@ message RequestHistory {
4848
repeated HistoryEvent events = 2;
4949
}
5050
51-
message HistoryByChangeIDResponse {
51+
message GetRequestHistoryByChangeURIResponse {
5252
// Request histories ordered by the numeric sqid counter ascending.
5353
repeated RequestHistory histories = 1;
5454
}
5555
5656
service SubmitQueueGateway {
57-
rpc HistoryBySQID(HistoryBySQIDRequest) returns (HistoryBySQIDResponse) {}
58-
rpc HistoryByChangeID(HistoryByChangeIDRequest) returns (HistoryByChangeIDResponse) {}
57+
rpc GetRequestHistoryByID(GetRequestHistoryByIDRequest) returns (GetRequestHistoryByIDResponse) {}
58+
rpc GetRequestHistoryByChangeURI(GetRequestHistoryByChangeURIRequest) returns (GetRequestHistoryByChangeURIResponse) {}
5959
}
6060
```
6161

62-
`HistoryBySQID` returns one list of events for exactly one request. `HistoryByChangeID` returns a list of request histories because the same change can be submitted more than once. Each history includes its `sqid` so callers can distinguish submissions and use the identifier with other gateway APIs.
62+
`GetRequestHistoryByID` returns one list of events for exactly one request. `GetRequestHistoryByChangeURI` returns a list of request histories because the same change can be submitted more than once. Each history includes its `sqid` so callers can distinguish submissions and use the identifier with other gateway APIs.
6363

6464
## Status Contract
6565

66-
`HistoryEvent.status` is a string, not a protobuf enum. Its value is populated from `entity.RequestStatus`, the same customer-facing status type stored in `RequestLog` and returned by `Status`.
66+
`HistoryEvent.status` is a string, not a protobuf enum. Its value is populated from `entity.RequestStatus`, the same customer-facing status type stored in `RequestLog` and returned by `GetRequestSummaryByID` and `GetRequestSummaryByChangeURI`.
6767

6868
Keeping the wire field as a string allows SubmitQueue to add request statuses without requiring clients to adopt a new generated enum before they can read the response. Clients must tolerate status strings they do not recognize.
6969

@@ -80,7 +80,7 @@ The history APIs do not introduce a projection table or persist a second history
8080

8181
`RequestVersion` is intentionally excluded. It is an internal reconciliation signal used to determine the current state and is not part of the public lifecycle event contract.
8282

83-
`RequestID` is excluded from each event because the request is identified by the `HistoryBySQID` request or by `RequestHistory.sqid`.
83+
`RequestID` is excluded from each event because the request is identified by the `GetRequestHistoryByID` request or by `RequestHistory.sqid`.
8484

8585
## Ordering
8686

@@ -90,7 +90,7 @@ Request-log timestamps are generated by callers, not by the storage backend. The
9090

9191
Multiple events may have the same timestamp. Events with equal timestamps are ordered by a stable, implementation-defined tie-breaker so repeated reads of the same retained rows return the same sequence. The tie-breaker is not exposed in the API because it has no lifecycle meaning. For example, the MySQL implementation uses the persisted `salt` column as its secondary sort key.
9292

93-
`HistoryByChangeIDResponse.histories` is ordered by the numeric SQID counter ascending. Implementations must parse the counter rather than compare SQIDs lexicographically, so `main/2` precedes `main/10`.
93+
`GetRequestHistoryByChangeURIResponse.histories` is ordered by the numeric SQID counter ascending. Implementations must parse the counter rather than compare SQIDs lexicographically, so `main/2` precedes `main/10`.
9494

9595
## Preserve Every Stored Event
9696

@@ -110,24 +110,24 @@ A SubmitQueue request has a bounded lifecycle under normal operation, and a chan
110110

111111
The history APIs are eventually consistent. Direct request-log writes become visible after storage persistence, while events sent through the log topic become visible after the gateway consumes and persists them. A successful response may therefore briefly omit recently emitted events.
112112

113-
Unlike `Status`, the history APIs do not reconcile competing log entries. They expose retained event sequences directly.
113+
Unlike request-summary retrieval, the history APIs do not reconcile competing log entries. They expose retained event sequences directly.
114114

115115
## Errors
116116

117-
Error behavior follows the conventions established by `Status`:
117+
Error behavior follows the conventions established by request-summary retrieval:
118118

119-
- An empty `sqid` passed to `HistoryBySQID` is an invalid request.
120-
- An empty `change_id` passed to `HistoryByChangeID` is an invalid request.
121-
- If no request-log records exist for an `sqid`, `HistoryBySQID` returns the existing `RequestNotFoundError`.
122-
- If no retained request histories match a `change_id`, `HistoryByChangeID` returns a change-ID-specific not-found user error.
119+
- An empty `sqid` passed to `GetRequestHistoryByID` is an invalid request.
120+
- An empty `change_uri` passed to `GetRequestHistoryByChangeURI` is an invalid request.
121+
- If no request-log records exist for an `sqid`, `GetRequestHistoryByID` returns the existing `RequestNotFoundError`.
122+
- If no retained request histories match a `change_uri`, `GetRequestHistoryByChangeURI` returns a change-URI-specific not-found user error.
123123
- A request-log storage failure is returned as an infrastructure error.
124124

125125
Using the existing request not-found error for `sqid` lookups keeps point lookups consistent across the gateway API.
126126

127127
## Flow
128128

129129
```text
130-
HistoryBySQIDRequest(sqid)
130+
GetRequestHistoryByIDRequest(sqid)
131131
|
132132
v
133133
validate sqid
@@ -139,12 +139,12 @@ RequestLogStore.List(sqid)
139139
project each RequestLog to one HistoryEvent
140140
|
141141
v
142-
HistoryBySQIDResponse(events)
142+
GetRequestHistoryByIDResponse(events)
143143
144-
HistoryByChangeIDRequest(change_id)
144+
GetRequestHistoryByChangeURIRequest(change_uri)
145145
|
146146
v
147-
validate change_id
147+
validate change_uri
148148
|
149149
v
150150
resolve matching sqids
@@ -156,7 +156,7 @@ RequestLogStore.List(sqid) for each match
156156
project each RequestLog to one HistoryEvent
157157
|
158158
v
159-
HistoryByChangeIDResponse(histories)
159+
GetRequestHistoryByChangeURIResponse(histories)
160160
```
161161

162-
The API contract does not prescribe how a change ID is mapped to matching requests. That lookup is an implementation concern and must preserve the gateway's ownership of the history read path.
162+
The API contract does not prescribe how a change URI is mapped to matching requests. That lookup is an implementation concern and must preserve the gateway's ownership of the history read path.

0 commit comments

Comments
 (0)