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
Copy file name to clipboardExpand all lines: doc/rfc/stovepipe/steps/process.md
+21-4Lines changed: 21 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -21,7 +21,7 @@ For a delivery carrying request id `R`:
21
21
2. If R.State is terminal (superseded / succeeded / failed / cancelled):
22
22
- ack and return (idempotent no-op).
23
23
3. If R.State is processing (strategy already recorded):
24
-
- re-publish R to build (the prior publish may have failed), ack, return.
24
+
- re-announce validation start and re-publish R to build (either prior publish may have failed), ack, return.
25
25
4. R.State is accepted. Load the Queue row Q.
26
26
5. Coalesce: if CompareRequestID(R.Queue, R.ID, Q.latest_request_id) < 0:
27
27
- a newer head exists -> mark R superseded, ack, return. (No slot consumed.)
@@ -31,8 +31,9 @@ For a delivery carrying request id `R`:
31
31
a. Derive build strategy + baseline (see "Build-strategy decision").
32
32
b. CAS the Queue row: in_flight_count += 1.
33
33
c. CAS the Request: accepted -> processing, persist build_strategy + base_uri.
34
-
d. Publish R to build.
35
-
e. ack.
34
+
d. Announce validation start on the hook topic (see "Hooks").
35
+
e. Publish R to build.
36
+
f. ack.
36
37
```
37
38
38
39
Step 5 runs regardless of the gate: an intermediate head is superseded on sight (even mid-validation), because superseding consumes no slot.
@@ -138,12 +139,28 @@ A, D, F each get a full cycle; B, C, E end `superseded`. No intermediate is vali
138
139
- A newer head does **not** preempt an in-flight validation.
139
140
- Deferred messages are **not** failed or dead-lettered — they wait for the gate (see [Waiting for a slot](#waiting-for-a-slot)).
140
141
142
+
## Hooks
143
+
144
+
Admitting a request is when the rest of the company can learn "validation of this commit has begun". `process` publishes that as a `HookEvent` on Stovepipe's durable `hook` topic — the same seam `record` uses to announce the outcome. The mechanics (envelope, delivery promise, per-domain dispatcher stage, `hook_dlq`) are settled in [hook-framework.md](../../hook-framework.md); this section covers only what admitting has to decide.
145
+
146
+
The event type is `validation.repository.started`. Its payload names the Queue and the Request and nothing else, exactly as the terminal events in [record.md](record.md#hooks) do: a hook resolves the commit, the chosen strategy, and the baseline from the request store rather than reading a snapshot off the wire.
147
+
148
+
Published after the admit CAS and before the publish to `build`:
149
+
150
+
```
151
+
CAS accepted -> processing → publish HookEvent → publish to build → ack
152
+
```
153
+
154
+
After the CAS because the payload names the Request rather than snapshotting it, and the two facts a start event exists to carry — the scope it chose and the baseline it builds on — are written by that very CAS. A hook that reloads the Request must not find it still `accepted` with neither set. Before the build publish because the announce is the cheaper of the two to retry: a failed announce leaves nothing downstream to undo, whereas announcing after the build publish would make a failed announce force the redelivery to re-publish a build that was already accepted.
155
+
156
+
Only an admit announces. A Request that coalescing supersedes never reaches step 7, so it produces no start event — and a start event is not a promise that a verdict follows, since an admitted Request can still be cancelled or driven to a fail-closed outcome. Consumers pairing a start with an end must tolerate a start that never gets one.
157
+
141
158
## Idempotency and at-least-once delivery
142
159
143
160
Every branch is safe under redelivery:
144
161
145
162
-**accepted, no strategy** → full admit path. On a crash after incrementing `in_flight_count` but before persisting `processing`, redelivery re-reads `accepted` and re-runs; the increment re-applies only if the count CAS hasn't already moved (see integrity below).
146
-
-**processing** → re-publish to `build` and ack. The `build` consumer is keyed on the request id and idempotent, so a duplicate publish is harmless.
163
+
-**processing** → re-announce the start event, re-publish to `build`, ack. The `build` consumer is keyed on the request id and idempotent, so a duplicate publish is harmless, and the start event's id is derived from the transition rather than the clock, so a re-announce carries the id the first attempt would have and consumers dedupe on it. Re-announcing here is what makes the event at-least-once rather than at-most-once: this is the only branch a redelivery takes once `processing` is durable, so an admit that failed after the state write would otherwise lose the event for good.
Copy file name to clipboardExpand all lines: doc/rfc/stovepipe/workflow.md
+7-7Lines changed: 7 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -55,10 +55,10 @@ The ref is a *cache* of the last-green URI, not a second record of greenness. It
55
55
|---|---|
56
56
|**SourceControl**| Resolve a Queue name to its current head URI; answer ancestry/comparison questions between two URIs (is the new head a fast-forward descendant of the last green, or was history rewritten?); enumerate commits in a range; advance the Queue's **promotion ref** to a commit. The sole owner of URI semantics, including which refs a Queue name resolves to. |
57
57
|**build-runner**| Build a scope at a URI (optionally relative to a baseline URI), returning pass/fail and the target graph. See [build-runner.md](../submitqueue/build-runner.md). |
58
-
|**Hooks**| Deliver Stovepipe's greenness events to downstream systems — "this URI / this project is now green (or not green)". Fire-and-forget notification, decoupled so Stovepipe does not know or care who consumes the event. The shared cross-domain hook seam rather than a Stovepipe-specific extension. See [hook-framework.md](../hook-framework.md). |
58
+
|**Hooks**| Deliver Stovepipe's validation events to downstream systems — "validation of this URI has begun", "this URI / this project is now green (or not green)". Fire-and-forget notification, decoupled so Stovepipe does not know or care who consumes the event. The shared cross-domain hook seam rather than a Stovepipe-specific extension. See [hook-framework.md](../hook-framework.md). |
59
59
|**Storage**| Persist Queues (incl. last-green URI), Requests, build records, and per-URI / per-project greenness. Key/value-shaped per the extension-design rules in [AGENTS.md](../../../AGENTS.md). |
60
60
61
-
Hooks are the notification boundary. When a validation fact is recorded — whole-repo green/not-green, or later a project green/not-green — the event reaches deployment systems, dashboards, and developer tooling without any of them polling Stovepipe's store, and each environment can route it to its own downstream (a deploy gate, a Slack notifier, an event bus) without changing the pipeline. The mechanism is the cross-domain hook framework rather than a call out of the recording stage: `record`publishes a `HookEvent` to Stovepipe's `hook` topic, and a dispatcher stage consumes it and invokes the wired hooks, so a slow or failing downstream cannot add latency to the pipeline. Both halves exist; what a deployment supplies is the hooks themselves, since the example server resolves every event to `noop`. See [record.md](steps/record.md#hooks) for the fact-to-event mapping.
61
+
Hooks are the notification boundary. When validation of a commit begins, and when a validation fact is recorded — whole-repo green/not-green, or later a project green/not-green — the event reaches deployment systems, dashboards, and developer tooling without any of them polling Stovepipe's store, and each environment can route it to its own downstream (a deploy gate, a Slack notifier, an event bus) without changing the pipeline. The mechanism is the cross-domain hook framework rather than a call out of the pipeline stages: `process` and `record`publish a `HookEvent` to Stovepipe's `hook` topic, and a dispatcher stage consumes it and invokes the wired hooks, so a slow or failing downstream cannot add latency to the pipeline. Both halves exist; what a deployment supplies is the hooks themselves, since the example server resolves every event to `noop`. See[process.md](steps/process.md#hooks) for the start event and[record.md](steps/record.md#hooks) for the fact-to-event mapping.
62
62
63
63
## Workflow
64
64
@@ -74,9 +74,9 @@ The pipeline runs in two phases against the same Request. **Phase 1** establishe
74
74
└───────────────┬──────────────┘
75
75
│ RequestID
76
76
▼
77
-
┌──────────────────────────────┐
78
-
│ process │
79
-
│ Ask SourceControl: is head a │
77
+
┌──────────────────────────────┐ Hooks
78
+
│ process │┄┄┄┄┄► "validation
79
+
│ Ask SourceControl: is head a │ started"
80
80
│ descendant of last-green? │
81
81
│ → incremental since green │
82
82
│ else (history rewrite) │
@@ -132,7 +132,7 @@ The pipeline runs in two phases against the same Request. **Phase 1** establishe
132
132
### Phase 1 — whole-repo greenness
133
133
134
134
1.**ingest** — invoked by the external poller with a **Queue name**. It asks `SourceControl` for that Queue's current head URI, mints a Request namespaced by the Queue, persists it with no recorded greenness yet, and dedups on `(Queue, head URI)` so a re-reported head is processed once. It publishes the RequestID onward.
135
-
2.**process** — decides build strategy (incremental since last-green vs full monorepo), gates concurrent work per Queue, coalesces backlog to the latest head, and publishes to `build`. See [process.md](steps/process.md).
135
+
2.**process** — decides build strategy (incremental since last-green vs full monorepo), gates concurrent work per Queue, coalesces backlog to the latest head, publishes a **hook event** announcing that validation of the commit has begun, and publishes to `build`. See [process.md](steps/process.md).
136
136
3.**build** — runs the build-runner for the chosen scope. A flag derived from `process` decides whether to build relative to the last-green **baseline URI** (incremental) or from scratch (full). It records a build and publishes the BuildID.
137
137
4.**buildsignal** — records the build's status and target graph when the build completes, then releases the Queue's `in_flight_count` slot, projects the terminal status onto the Request (`succeeded` / `failed` / `cancelled`), and publishes the RequestID to `record`.
138
138
5.**record** — writes the whole-repo greenness for the head URI (`0` green / `1` broken to start), derived from the Request's build outcome. On green it advances the Queue's **last-green URI** so the next `process` can build incrementally from here, and asks `SourceControl` to advance the Queue's **promotion ref** to the same commit (see [Promotion ref](#promotion-ref--the-last-green-commit-by-name)). It publishes a **hook event** for the green/not-green transition, then fans out into Phase 2. The Queue's `in_flight_count` was already released by `buildsignal` when the build went terminal.
@@ -150,7 +150,7 @@ The pipeline runs in two phases against the same Request. **Phase 1** establishe
150
150
| Controller | In | Out | One-line role |
151
151
|---|---|---|---|
152
152
|**ingest**| Queue name (from poller) | process | Resolve head URI via SourceControl, mint Request, persist (no greenness), dedup on `(Queue, head URI)`|
|**build**| RequestID | buildsignal | Run the build-runner for the chosen scope; baseline = last-green URI iff incremental |
155
155
|**buildsignal**| BuildID | record (P1), record (P2) | Record build status + target graph; release `in_flight_count`; project the outcome onto the Request; signal completion |
156
156
|**record**| RequestID | analyze (P1→P2), hook topic | Write greenness; on whole-repo green advance last-green URI and the promotion ref; publish the hook event |
0 commit comments