You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 8f30300
Browse filesBrowse the repository at this point in the historyBrowse files
Summary:
Builds on #799 to serve List from immutable request_receipt keys and authoritative request_summary rows, instead of the duplicated queue-summary projection.
### Changes
- Scan receipt keys, then point-read summaries for visible results; keep bounds, string-ID ordering, and page-token format unchanged.
- Use the existing RequestSummary response type and wire mapper.
- Fail the page on missing, mismatched, or hidden summaries using the existing InternalConsistencyError.
- Add controller and gateway integration coverage; update the RFC. Keep legacy projection writes for rollback.
### Rollout
Deploy only after #799 reaches all writers. No backfill or legacy-read fallback: requests without receipt keys are absent from List. Duplicate-write and old-table removal remain follow-up work.
## Issue Links
None — no tracking issue was supplied.
Test Plan:
After CD rollout, confirm new Land requests appear in List with the same summary fields as ID lookup, check continuation pages, and monitor page latency.
Revert Plan:
Revert the read cutover to restore the old List path. Legacy projection writes remain active; this PR makes no schema changes.
API Changes:
No RPC/proto changes. The Go ListResult now contains RequestSummary values.
Copy file name to clipboardExpand all lines: doc/rfc/submitqueue/status-list-api.md
+16-18Lines changed: 16 additions & 18 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -87,11 +87,11 @@ The immutable context is queue, change URIs, and receipt time. The mutable respo
87
87
88
88
The key supports authoritative lookup and conditional status updates for one request without a secondary index, and leads with the queue so the table is shardable by queue.
89
89
90
-
### Request Summaries by Queue
90
+
### Requests by Receipt Time
91
91
92
-
The queue projection is logically keyed by `(queue, received_at_ms, sqid)`and must support a bounded descending scan over `(received_at_ms, sqid)`. A backend may satisfy that contract with a reverse range scan or by descending-encoding the ordered key components; cursors always carry the original receipt time and sqid values.
92
+
The immutable `request_receipt` lookup contains only `(queue, received_at_ms, request_id)`as its primary key and supports a bounded descending scan over `(received_at_ms, request_id)`. Request IDs break timestamp ties in string order, not numeric order. A backend may use a reverse range scan or descending-encode the ordered key components; cursors always carry the original receipt time and request ID.
93
93
94
-
The current `request_summary_by_queue` row duplicates the complete `List` response so a page is served by one bounded range scan. Status updates still propagate to this projection during the receipt-lookup transition below; it is not the target storage model.
94
+
List resolves these keys through `request_summary` rather than storing response fields in the lookup. The old `request_summary_by_queue`projection still receives writes for rollback, but List no longer reads it.
95
95
96
96
The logical key covers the `List` queue predicate, receipt-time range, newest-first ordering, and complete keyset cursor in one bounded scan.
97
97
@@ -111,23 +111,23 @@ The URI is stored in the canonical form received from the validated Land request
111
111
112
112
After synchronous validation, Land generates the sqid and one receipt timestamp. The gateway persists the authoritative summary in the internal `accepting` state, then publishes the request to the orchestrator. It does not create the URI or queue projections while the request remains `accepting`.
113
113
114
-
After publication succeeds, Land appends the initial `accepted` request log. Materializing `accepted`, `started`, or any later event promotes the authoritative summary out of `accepting` and creates the URI and queue projections. This handles the race where the orchestrator emits `started` before Land finishes persisting `accepted`.
114
+
After publication succeeds, Land appends the initial `accepted` request log. Materializing `accepted`, `started`, or any later lifecycle state promotes the authoritative summary out of `accepting`, creates the URI and legacy queue projections, and ensures the receipt lookup key exists. This handles the race where the orchestrator emits `started` before Land finishes persisting `accepted`.
115
115
116
116
An `accepted` event is the lowest public lifecycle state. A late `accepted` event is retained in request history but must not replace `started` or any later materialized status.
117
117
118
118
Pipeline publication is the Land success boundary. If publication fails, Land returns an error and leaves the hidden `accepting` receipt for operational cleanup. If publication succeeds but appending or materializing `accepted` fails, Land still returns the sqid because retrying the RPC would submit a duplicate request that is already in the pipeline. A later pipeline event activates and repairs the public projections.
119
119
120
120
### Request-Log Materialization
121
121
122
-
Every gateway request-log persistence path uses the same materialization component. It appends the audit log, compares the incoming entry with the authoritative summary, conditionally advances the winner, and propagates the authoritative value to the queue projection.
122
+
Every gateway request-log persistence path uses the same materialization component. It appends the audit log, compares the incoming entry with the authoritative summary, and conditionally advances the winner. For public summaries, it completes the URI and legacy queue projection writes before ensuring the immutable receipt key, including on unchanged or stale-log retries. Hidden `accepting` summaries remain excluded; a failed receipt write is returned through the existing projection-failure path.
123
123
124
124
The winner comparison preserves the existing current-status reconciliation behavior:
125
125
126
126
1. A terminal request-state entry with a positive request version beats every non-terminal or unversioned winner.
127
127
2. Between versioned terminal entries, the greater request version wins.
128
128
3. Equal terminal versions use the greater log timestamp as a tie-breaker.
129
129
4. When no versioned terminal winner exists, the greater log timestamp wins.
130
-
5.Any retained lifecycle event promotes an `accepting` summary into the public projections.
130
+
5.A retained lifecycle status entry promotes an `accepting` summary into the public projections; audit-only events do not.
131
131
6.`accepted` cannot replace `started` or any later status, even when the accepted log arrives later.
132
132
133
133
Materialization uses optimistic concurrency so stale or out-of-order consumers cannot replace a newer winner. Version arithmetic and reconciliation decisions belong to the materialization component; stores perform only mechanical creates, reads, conditional updates, and bounded page queries.
@@ -144,15 +144,17 @@ The gateway performs a bounded newest-first scan of the URI reverse mapping and
144
144
145
145
### List by Queue and Receipt Time
146
146
147
-
The gateway performs one bounded range scan of the queue projection using queue, receipt-time bounds, and an optional keyset cursor. Queue projections are created only when the request reaches `accepted` or a later state, so `accepting` receipts are absent by construction. The ordering key is immutable, so later status updates cannot move an item across an issued cursor.
147
+
The gateway scans `request_receipt` using the queue, receipt-time bounds, and optional keyset cursor, fetching one extra key to determine whether another page exists. It point-reads `request_summary` for each visible result, preserves receipt-key order, and forms the next token from the last returned receipt. The extra key does not require a summary read. Later status updates cannot move an item across the immutable cursor.
148
+
149
+
Receipt keys are created only for public summaries. An invalid key, missing summary, identity or receipt-time mismatch, or `accepting` summary fails the entire page as an internal consistency error rather than returning a user-facing not-found or silently omitting the item. Other backend failures retain their error chains.
148
150
149
151
## Consistency
150
152
151
-
The request log remains the append-only audit record. The authoritative summary and queue projection are eventually consistent views of its winning current state.
153
+
The request log remains the append-only audit record. The authoritative summary is an eventually consistent view of its winning current state.
152
154
153
-
Because the authoritative summary and queue projection are separate writes, a short interval can exist where request-summary retrieval and `List` show different statuses. Retried materialization repairs the queue projection from the authoritative sqid summary until both converge. Neither API reconciles logs during reads to hide this interval.
155
+
List and request-summary retrieval read the same authoritative projection, so List no longer waits for mutable state to propagate into a second table. Membership may lag until the receipt key is written and retries repair partial writes. Separate calls and per-item reads are not a shared snapshot and may observe different lifecycle versions. Neither API reconciles logs during reads.
154
156
155
-
Request context has a stronger guarantee than status convergence: the authoritative receipt is persisted before the request is published to the orchestrator. Public URI and queue projections are activated only by `accepted` or a later event. Once a queue projection is visible, its queue, sqid, change URIs, and receipt time are complete.
157
+
Request context has a stronger guarantee than status convergence: the authoritative receipt is persisted before the request is published to the orchestrator. Public lookup keys are activated only by `accepted` or a later lifecycle state. Once a receipt key is visible, its authoritative summary has complete queue, sqid, change URIs, and receipt time.
156
158
157
159
## Compatibility and Rollout
158
160
@@ -164,17 +166,13 @@ The request-summary RPCs replace the former polymorphic Status shape with explic
164
166
165
167
### Receipt-Lookup Transition
166
168
167
-
The target model keeps the full projection only in `request_summary`. An immutable `request_receipt` lookup contains only `(queue, received_at_ms, request_id)` as its primary key. List will scan these keys and point-read the corresponding authoritative summaries, preserving the existing response, required time window, string-ID tie ordering, and page tokens. Receipt time and all response fields are already captured; no additional data collection is needed.
168
-
169
-
The first phase adds the lookup and its writes, without changing List reads or removing the old projection. After materializing a public summary and completing the existing URI/queue projection writes, the materializer ensures the receipt key exists, including on unchanged or stale-log retries. Hidden `accepting` receipts are excluded. A failed lookup write is returned through the existing projection-failure path.
169
+
The receipt lookup and writer path are implemented, and List now scans receipt keys and point-reads authoritative summaries. The response, required time window, string-ID tie ordering, and page-token format are unchanged. Receipt time and all response fields are already captured; no additional data collection is needed.
170
170
171
-
Deploy the additive table before enabling these writes. To finish the transition:
171
+
CD must roll out the additive table and receipt writers to all instances before deploying the read cutover. The cutover performs one scan plus one point read per visible result; check page latency after rollout.
172
172
173
-
1. Upgrade all writers, backfill existing List membership from the old table's keys, validate each key against its public authoritative summary, and verify lookup coverage.
174
-
2. Switch List to the receipt lookup plus authoritative summary reads, keeping old projection writes during the rollback window. This trades one page scan for one scan plus a point read per visible result; verify page latency before cutover.
175
-
3. Stop duplicate projection writes, remove `RequestQueueSummary` and its store/mapper, and retire `request_summary_by_queue` after the rollback window. Only the small immutable lookup key remains duplicated.
173
+
There is no historical backfill or fallback to the legacy queue projection. Requests without receipt keys are absent from List, even if their old queue-summary rows remain. Later log activity can create missing receipt keys.
176
174
177
-
This backfill preserves existing List history; it does not reconstruct requests predating the original read-model rollout. Backfill tooling, read cutover, and removal are follow-up work.
175
+
Keep legacy projection writes during the rollback window; reverting the read cutover restores the old List path. Once stable, a follow-up stops duplicate projection writes and removes `RequestQueueSummary` and its store. Retire `request_summary_by_queue` after the rollback window. The final model keeps one full projection plus immutable lookup keys.
0 commit comments