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
Add the speculation domain model in submitqueue/entity/speculation.go: SpeculationPath (keyed by a content hash), DependencyBet/DependencyBetType, SpeculationPathStatus, SpeculationPathEntry, and SpeculationPathSet, plus the run vocabulary PathAction/Speculation/CandidatePath. Enums are string-valued with empty-string sentinels; the path's ID is a SHA-256 over the head and its ordered bets, computed on each call. Covered by table tests.
Also aligns the speculation RFC with the implemented design: field names match the entities (Head, Batch, string enums), the Generator opens over the batches alone and enumerates the whole path space, terminal-path suppression moves to the Allocator (the piece that reconciles candidates against the path sets by ID), and the stale depth-bound language is removed — lazy generation removed the 2^n cost the bound existed to cap.
No storage, no wiring, and no serialization helpers — queue payloads get their own contract types when a message needs one. These are the entities the speculation extension and controller build on.
Copy file name to clipboardExpand all lines: doc/rfc/submitqueue/speculation.md
+26-25Lines changed: 26 additions & 25 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -62,7 +62,7 @@ Verdicts are controller-owned facts: the Speculator can neither compute nor veto
62
62
63
63
Conflict analysis is conservative — it flags any *possible* conflict — so heads carry dependencies that rarely matter and over-serialize. Relaxation lets the Speculator **drop** the weakest: the path tags that dependency *dropped* and ignores it — it neither gates the merge nor refutes the path if it lands. Which to drop is a per-run Speculator policy.
64
64
65
-
The drop lives on the path, so the path stays self-describing: finalization needs no external relaxed set, and dropped dependencies don't count toward the depth bound (relaxing is what shrinks a head's depth).
65
+
The drop lives on the path, so the path stays self-describing: finalization needs no external relaxed set(relaxing is what shrinks the space of guesses a head's paths range over).
66
66
67
67
Example: `H` conflicts with `B1` and weak `B2`. Drop `B2`, and `H` merges once `B1` lands and its build passes — even if `B2` later lands. Without it, `H` waits on both.
68
68
@@ -95,14 +95,14 @@ The one extension. It decides *which paths to build and which running ones to ca
95
95
-`batches`: every in-flight batch plus finalized batches still referenced as dependencies by an in-flight batch; each carries its dependency list and state.
96
96
-`pathSets`: every materialized path for those batches, whether pending, in flight, or terminal, including recently finished paths retained to prevent duplicate work.
97
97
-**Out:** a list of build/cancel actions whose heads are in `BatchStateSpeculating`; batches in every other state provide facts but are never action targets.
98
-
- Budget, depth bound, and clock are injected at construction. An impl may read extra data (also injected); the controller checks the output, so extra data never affects correctness.
98
+
- Budget and clock are injected at construction. An impl may read extra data (also injected); the controller checks the output, so extra data never affects correctness.
99
99
100
100
### The default Speculator
101
101
102
-
The default Speculator is composed from two swappable interfaces — a **Generator** and an **Allocator** — so scoring and preemption policy can vary independently. They are composition points inside the default implementation, not controller-facing extensions: the controller depends only on the Speculator contract, and an alternate Speculator need not use or expose this split. The default opens the Generator's candidate stream over the batches and their path sets, then hands that stream and the path sets to the Allocator.
102
+
The default Speculator is composed from two swappable interfaces — a **Generator** and an **Allocator** — so scoring and preemption policy can vary independently. They are composition points inside the default implementation, not controller-facing extensions: the controller depends only on the Speculator contract, and an alternate Speculator need not use or expose this split. The default opens the Generator's candidate stream over the batches, then hands that stream and the path sets to the Allocator.
103
103
104
-
-**Generator** — yields the queue's candidate paths as one iterator, best-first across heads in `BatchStateSpeculating`. *Contract:* every candidate has a Speculating head, is coherent, and is within the depth bound (the count of unresolved dependencies a head's paths range over); none repeats or contradicts a resolved fact; a head past the bound is skipped until its dependencies resolve. Ranking is implementation-owned: the Generator may compute it directly, call an injected scorer extension, or use other injected data. It carries the resulting ranking score only within the run and skips paths already terminal in the path sets.
105
-
- **Allocator** — spends the build budget (the queue's cap on concurrent builds) over the iterator. *Contract:* it pulls in order until the budget fills and matches candidates to existing paths by ID, so a pending or building path remains funded rather than starting a new attempt; pending dispatches are replayed by the controller as described above. Pending, building, and cancelling paths charge the budget (a cancelling build holds CI until terminal), while terminal ones charge none. Cancellation is best-effort, so the Allocator does not spend capacity it merely expects a cancel to release and risk exceeding the hard CI cap. *Default:* the sticky policy fills only free slots and leaves in-flight builds running; a preempting policy cancels in-flight paths below the funded set. Budget is the only rationing lever — there is no ranking-score floor. A build cancelled to make room still charges budget until its cancel reaches terminal and publishes dirty, so the next run funds the released slot — the queue converges over successive ticks rather than oversubscribing in a single pass.
104
+
-**Generator** — yields the queue's candidate paths as one iterator, best-first across heads in `BatchStateSpeculating`. *Contract:* every candidate has a Speculating headand is coherent; none repeats or contradicts a resolved fact. Ranking is implementation-owned: the Generator may compute it directly, call an injected scorer extension, or use other injected data. It carries the resulting ranking score only within the run.
105
+
- **Allocator** — spends the build budget (the queue's cap on concurrent builds) over the iterator. *Contract:* it pulls in order until the budget fills and matches candidates to existing paths by ID, so a pending or building path remains funded rather than starting a new attempt, and a candidate whose path is already terminal in the path sets is skipped rather than rebuilt; pending dispatches are replayed by the controller as described above. Pending, building, and cancelling paths charge the budget (a cancelling build holds CI until terminal), while terminal ones charge none. Cancellation is best-effort, so the Allocator does not spend capacity it merely expects a cancel to release and risk exceeding the hard CI cap. *Default:* the sticky policy fills only free slots and leaves in-flight builds running; a preempting policy cancels in-flight paths below the funded set. Budget is the only rationing lever — there is no ranking-score floor. A build cancelled to make room still charges budget until its cancel reaches terminal and publishes dirty, so the next run funds the released slot — the queue converges over successive ticks rather than oversubscribing in a single pass.
106
106
107
107
### Extension APIs
108
108
@@ -123,35 +123,35 @@ type SpeculationPathEntry struct {
123
123
// SpeculationPath identifies a head batch and its ordered dependency bets; every
124
124
// dependency appears exactly once, so the logical path is self-describing.
125
125
typeSpeculationPathstruct {
126
-
HeadBatchIDstring// ID of the batch being built
127
-
Bets[]DependencyBet// one bet per dependency of HeadBatchID, in queue order
126
+
Headstring// ID of the batch being built
127
+
Bets []DependencyBet// one bet per dependency of Head, in queue order
128
128
}
129
129
130
130
// DependencyBet is the path's bet on one dependency.
131
131
typeDependencyBetstruct {
132
-
BatchIDstring// dependency batch ID
133
-
BetDependencyBetType// included | excluded | dropped
132
+
Batchstring// dependency batch ID
133
+
BetDependencyBetType// included | excluded | dropped
134
134
}
135
135
136
136
// DependencyBetType is how a path treats one dependency.
137
-
typeDependencyBetTypeint
137
+
typeDependencyBetTypestring
138
138
139
139
const (
140
-
BetUnknownDependencyBetType = 0// zero-value sentinel; never valid
141
-
BetIncludedDependencyBetType = 1// bet it lands; head built on top of it, refuted if it fails
142
-
BetExcludedDependencyBetType = 2// bet it does not land; refuted if it lands
143
-
BetDroppedDependencyBetType = 3// dropped by relaxation; landing or failing never affects the path
140
+
BetUnknownDependencyBetType = ""// zero-value sentinel; never valid
141
+
BetIncludedDependencyBetType = "included"// bet it lands; head built on top of it, refuted if it fails
142
+
BetExcludedDependencyBetType = "excluded"// bet it does not land; refuted if it lands
143
+
BetDroppedDependencyBetType = "dropped"// dropped by relaxation; landing or failing never affects the path
144
144
)
145
145
146
146
// SpeculationPathSet is one head's chosen paths — live and recently finished —
147
147
// under a single version. Finished entries linger briefly so a re-run cannot
148
148
// collide with an old build; live heads are listed via the batch store's
149
149
// by-state query. Every logical path is self-describing, but a store may encode
150
150
// the common head and ordered dependency IDs once per set and store each path's
151
-
// bets positionally — two bits per dependency, or a base-3 code the depth bound
152
-
//keeps to a small integer.
151
+
// bets positionally — two bits per dependency, or a base-3 code that stays a
152
+
// small integer.
153
153
typeSpeculationPathSetstruct {
154
-
BatchIDstring// primary key; the head batch
154
+
Headstring// primary key; ID of the head batch
155
155
Paths []SpeculationPathEntry// the head's chosen paths
156
156
Versionint// for compare-and-swap writes
157
157
}
@@ -170,17 +170,17 @@ type Speculator interface {
170
170
// Speculation is one proposed action on one path; a kept path has no entry.
171
171
typeSpeculationstruct {
172
172
Path entity.SpeculationPath// the path acted on; its ID hashes head batch ID + its bets
173
-
ActionPathAction//Build | Cancel
173
+
ActionPathAction//build | cancel
174
174
}
175
175
176
176
// The only two actions the Speculator may propose. No Merge or Fail —
177
177
// verdicts are the controller's.
178
-
typePathActionint
178
+
typePathActionstring
179
179
180
180
const (
181
-
PathActionUnknownPathAction = 0// zero-value sentinel; never valid, rejected by the controller's check step
182
-
BuildPathAction = 1// start (or resurrect) a build for the path
183
-
CancelPathAction = 2// preempt this in-flight path (refutation cancels are the controller's)
181
+
PathActionUnknownPathAction = ""// zero-value sentinel; never valid, rejected by the controller's check step
182
+
PathActionBuildPathAction = "build"// start (or resurrect) a build for the path
183
+
PathActionCancelPathAction = "cancel"// preempt this in-flight path (refutation cancels are the controller's)
184
184
)
185
185
```
186
186
@@ -189,12 +189,12 @@ const (
189
189
// lazily; the producer computes only what is pulled.
190
190
typeGeneratorinterface {
191
191
// Open starts the queue's candidate stream, best-first across Speculating heads.
0 commit comments