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
## Summary
Intent:
- Provide one direct, idempotent materialization path for durable
request state logs.
- Keep the initial rollout limited to state entries while preserving a
boundary for future request summaries and other projections.
Changes:
- Add `Materializer.PersistLog` over the queue-scoped storage aggregate.
- Compose readable stable occurrence IDs with `publish.IntentID`.
- Deduplicate retained occurrences because Stovepipe writes directly;
SubmitQueue instead deduplicates the message carrying a log to its
materializer.
- Preserve caller-supplied timestamps, assign missing timestamps, and
reconcile duplicate writes by retained semantic content.
- Emit bounded metrics with context-derived tags.
- Document how future projections can be added within the materializer
without changing callers.
## Test Plan
- `./tool/bazel test //stovepipe/core/requestlog:go_default_test
--cache_test_results=no`
- `make check-gazelle`
- `make lint`
## Revert Plan
- Revert this PR. No production call site depends on the materializer
until the next PR in the stack lands.
## Issues
## Stack
1. @ #657
1. #658
Copy file name to clipboardExpand all lines: doc/rfc/stovepipe/request-history-api.md
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -107,7 +107,9 @@ Event rows remain in SubmitQueue history but never participate in current-status
107
107
108
108
Stovepipe has no equivalent ownership gap. The same service owns the queue-scoped `Request`, `ValidationFact`, request-URI mapping, and request-log store. Operational reads use their owning entities, while request history reads retained log records directly.
109
109
110
-
Stovepipe therefore does not add `RequestSummary`, replay history to determine current state, or materialize another history table. The controller performs only an in-memory wire projection from stored log records to protobuf messages. This avoids a second winner-selection algorithm competing with Request CAS state.
110
+
Stovepipe calls `requestlog.Materializer.PersistLog` directly, without an intermediate topic. Its initial materializer appends only the request log: it does not add `RequestSummary`, replay history to determine current state, or materialize another history table. The controller performs only an in-memory wire projection from stored log records to protobuf messages. This avoids a second winner-selection algorithm competing with Request CAS state.
111
+
112
+
`PersistLog` receives the whole queue-scoped storage aggregate so a future current-status or queue-list API can add SubmitQueue-style summary and index projections behind the same write boundary without changing request-log producers. Such projections remain deferred until a concrete read path needs them; `Request` remains authoritative in the initial implementation.
Copy file name to clipboardExpand all lines: doc/rfc/stovepipe/request-log.md
+12-10Lines changed: 12 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
## Summary
4
4
5
-
Stovepipe retains an append-only request log for each validation request. Its internal `RequestLog` is the counterpart of SubmitQueue's `RequestLog`: both retain request status changes and explanatory lifecycle events, while Stovepipe persists records directly instead of sending them through a cross-service log topic and materializer. The public API presents these records as request history. The log records every durable `Request.State` transition plus three asynchronous milestones needed to explain those transitions and the public verdict:
5
+
Stovepipe retains an append-only request log for each validation request. Its internal `RequestLog` is the counterpart of SubmitQueue's `RequestLog`: both retain request status changes and explanatory lifecycle events, while Stovepipe calls its materializer directly instead of sending records through a cross-service log topic. The public API presents these records as request history. The log records every durable `Request.State` transition plus three asynchronous milestones needed to explain those transitions and the public verdict:
6
6
7
7
-`build_triggered`;
8
8
-`build_finished`;
@@ -73,13 +73,13 @@ The retained unit is `entity.RequestLog`:
73
73
74
74
```go
75
75
typeRequestLogstruct {
76
-
// ID is the stable identity of one logical occurrence within the request. It is opaque, stable across redelivery, and never derived from time or randomness.
76
+
// ID is the stable identity of one logical occurrence within the request. It is stable across redelivery and never derived from time or randomness.
77
77
IDstring
78
78
// Queue is the logical queue containing the request and scopes RequestID.
79
79
Queuestring
80
80
// RequestID identifies the request whose log contains this record.
81
81
RequestIDstring
82
-
// TimestampMs is the durable occurrence time in Unix milliseconds.
82
+
// TimestampMs is when the occurrence was first retained, in Unix milliseconds.
83
83
TimestampMsint64
84
84
// State is the durable request state recorded by a state entry. It is unset on an event entry.
85
85
StateRequestState
@@ -145,7 +145,9 @@ Terminal entries retain domain reasons rather than transport mechanisms. Initial
145
145
146
146
## Stable IDs and Idempotency
147
147
148
-
`stovepipe/core/requestlog.Recorder` constructs opaque IDs from durable identities:
148
+
`stovepipe/core/requestlog` composes readable IDs from durable identities with the same `publish.IntentID` convention used by SubmitQueue before passing each record to `Materializer.PersistLog`. The surrounding `(queue, request_id)` storage key scopes the ID to one request, so a request state entry uses `state/<request-version>` without repeating the request identity:
149
+
150
+
SubmitQueue applies that identity to the message carrying a log to its materializer, while its log store remains append-only and may retain a duplicate after a later materialization step fails. Stovepipe has no intermediate log topic, so it applies the identity to the retained row itself: retrying the direct call reloads the existing occurrence and succeeds only when its semantic content is compatible.
149
151
150
152
| Entry | Stable identity inputs |
151
153
|---|---|
@@ -154,7 +156,7 @@ Terminal entries retain domain reasons rather than transport mechanisms. Initial
154
156
| Build finished | Request ID, event kind, and build ID |
155
157
| Validation fact recorded | Request ID, event kind, and whole-repository fact identity |
156
158
157
-
The recorder calls `RequestLogStore.Create`. If the ID already exists, it loads the stored record and compares every semantic field. Identical content is idempotent success; conflicting content is an internal consistency error, and the stored record is never overwritten.
159
+
The controller passes the materializer the same queue-scoped storage aggregate used for the source write. The materializer preserves a supplied occurrence time or assigns the current time immediately before the first insertion attempt, then calls `RequestLogStore.Create`. If the ID already exists, it loads the stored record and compares the explicitly designated stable semantic fields. The first successfully retained timestamp is authoritative and is not compared with a later retry's newly sampled time. Metadata keys emitted by both records must agree, while a key present on only one record remains compatible so an additive metadata rollout does not turn retries of older occurrences into conflicts. Compatible content is idempotent success; conflicting content is an internal consistency error, and the stored record is never overwritten or enriched.
158
160
159
161
## Storage Contract
160
162
@@ -188,11 +190,11 @@ Request-log durability is part of completing a pipeline transition. The source w
188
190
189
191
For a Request transition, the controller:
190
192
191
-
1. builds an immutable updated copy with transition context and `StateChangedAtMs`;
193
+
1. builds an immutable updated copy for the state transition;
4. assigns the in-memory version only after the store succeeds;
195
-
5.asks the recorder to create the log record from durable Request data;
197
+
5.constructs the log record from the durable Request and bounded context still owned by that stage, then calls `Materializer.PersistLog`;
196
198
6. publishes the downstream handoff.
197
199
198
200
Request creation, Build changes, and fact creation use the same source-write, log-write, dependent-publish ordering. A request-log outage can leave a source update visible, but it cannot allow dependent processing to move past an unrecorded transition.
@@ -224,7 +226,7 @@ This is rollout work, not deferred cleanup: a mandatory request log without a du
224
226
225
227
Rollout therefore:
226
228
227
-
1. deploys source timestamp and provenance fields, request-log storage, recorder, and readers;
229
+
1. deploys request-log storage, the materializer, and readers;
228
230
2. enables writers and verifies every repair path stage by stage;
229
231
3. enables the public API after every writer and repair path is active.
230
232
@@ -246,11 +248,11 @@ Identifiers, outcome reasons, and reasonable per-request build counts have expli
Writer tests cover source success followed by log failure, redelivery with the log record absent or present, CAS loss, conflicting terminal writers, downstream publish failure, stable timestamps, and controller-owned version arithmetic. End-to-end tests cover successful, failed, cancelled, superseded, and fail-closed paths plus idempotent redelivery.
251
+
Writer tests cover source success followed by log failure, redelivery with the log record absent or present, CAS loss, conflicting terminal writers, downstream publish failure, first-insert timestamp reuse, and controller-owned version arithmetic. End-to-end tests cover successful, failed, cancelled, superseded, and fail-closed paths plus idempotent redelivery.
250
252
251
253
Tests reconstruct the latest Request state from state entries by request version and compare it with `RequestStore.Get`. They separately verify that only a durable fact produces green or broken.
252
254
253
-
The recorder reports create, identical-existing, conflict, validation failure, and storage failure counters tagged only by bounded state or event. IDs, URIs, and errors remain structured log fields rather than metric tags. Alerts cover sustained repair gaps and content conflicts.
255
+
The materializer reports create, identical-existing, conflict, validation failure, and storage failure counters tagged only by bounded state or event plus tags carried by the context. IDs, URIs, and errors remain structured log fields rather than metric tags. Alerts cover sustained repair gaps and content conflicts.
0 commit comments