Skip to main content

degenbot_workers/
plan.rs

1//! `plan` — the `FleetPlan` tiered boot authority (FLEETFLOOR FF-T2 / MEBF4V).
2//!
3//! LW-T4 made the budget the sole sizing authority with ONE
4//! floor: below the pinned-role floor the boot refused. FF-T2 generalizes
5//! that one floor into ordered HOST TIERS, one pure function of the
6//! budget:
7//!
8//! - pinned: the pinned-role derivation succeeds (floor(Q) >= H+A+R+M+2)
9//!   — today's topology, byte-stable (the derivation is the ONE
10//!   `FleetBudget::derive`, unchanged);
11//! - serial: 2-5 core hosts (the pinned derivation refuses but the host
12//!   has the 2-core minimum: one core for I/O work, one core for solve
13//!   work) — the arm itself lands with FF-T4; until then the boot
14//!   refuses with the tier's own typed refusal, never a silent narrow
15//!   (the reth `has_enough_parallelism` lesson: lane capability is
16//!   explicit);
17//! - refused: below 2 cores — `BudgetError::BelowHostFloor`, typed.
18//!
19//! Forced bindings (`runtime.fleet_profile = pinned | serial`, env
20//! `DEGENBOT_FLEET_PROFILE`) run on any host with 2 or more cores; a forced
21//! pinned binding below the floor is MARKED `oversubscribed` (latency
22//! contract void, correctness contract intact) — loud, never silent.
23//!
24//! # Composition, never duplication
25//!
26//! The plan picks the BINDING and the per-binding budget projection;
27//! `SlotLayout::of` stays the ONE geometry derivation under each
28//! binding (a serial-plan projection must yield a legal non-empty layout:
29//! 1 solver seat, 1 resolve, >= 1 poolupd, sim per budget). The plan is
30//! boot-frozen like the landed frozen-layout property: no resize
31//! re-derivation is ever triggered. The plan INHERITS budget.rs's
32//! documented derive-vs-doc-table discrepancy (the module note): the rule
33//! column is the authority; a fix, if ever wanted, is its own card.
34//!
35//! # Plan identity
36//!
37//! `PLAN_ID` names and versions the algebra (`fleetplan/1`): the ONE boot
38//! log line names it with the binding and the detected budget, and a
39//! tier edit bumps the version so logs stay unambiguous.
40
41use degenbot_config::FleetProfile;
42
43use crate::budget::{BudgetError, BudgetMode, BudgetOverrides, FleetBudget};
44use crate::dispatcher::BootError;
45
46/// The plan algebra's name + version (named and versioned by contract: the
47/// boot log line names it; a tier edit bumps the version).
48pub const PLAN_ID: &str = "fleetplan/1";
49
50/// The minimum usable host (the epic's goal state): one core for I/O
51/// work, one core for solve work. Below this the plan refuses TYPED.
52/// Canonically owned by budget.rs — re-exported for the plan's
53/// tier gate.
54pub use crate::budget::HOST_FLOOR_CORES;
55
56/// The fleet host binding (the adapter that maps lanes to threads; the
57/// FLEETFLOOR design contract). The census/log label is
58/// [`Binding::label`].
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum Binding {
61    /// The pinned binding: dedicated seats per role, today's topology
62    /// (6 cores or more under default overrides).
63    Pinned,
64    /// The serial binding: one ambient I/O lane plus one cycle lane, one
65    /// solve seat (the arm lands with FF-T4).
66    Serial,
67}
68
69impl Binding {
70    /// The census `binding` label (the field's closed vocabulary:
71    /// pinned / shared / logical). The fleet roles stamp the PINNED
72    /// binding today; the serial binding maps them onto shared threads as
73    /// LOGICAL lanes when it lands (FF-T4) — the label changes with the
74    /// binding, the row does not.
75    #[must_use]
76    pub const fn label(self) -> &'static str {
77        match self {
78            Self::Pinned => "pinned",
79            Self::Serial => "logical",
80        }
81    }
82
83    /// The binding NAME (error-message vocabulary: pinned / serial — the
84    /// plan profile the operator set, distinct from the census label).
85    #[must_use]
86    pub const fn name(self) -> &'static str {
87        match self {
88            Self::Pinned => "pinned",
89            Self::Serial => "serial",
90        }
91    }
92}
93
94impl std::fmt::Display for Binding {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        // The NAME (pinned / serial) — the log/error vocabulary; the
97        // census label is the thread-mapping vocabulary ([`Binding::label`]).
98        f.write_str(self.name())
99    }
100}
101
102/// The resolved boot plan: one pure function of the budget (the tier
103/// decision + the per-binding projection inputs). Boot-frozen by design;
104/// [`Clone`] so the host can carry it without borrowing.
105#[derive(Debug, Clone, PartialEq)]
106pub struct FleetPlan {
107    /// The plan identity ([`PLAN_ID`]) — the boot log line names it.
108    pub id: &'static str,
109    /// The binding the host tier resolves to.
110    pub binding: Binding,
111    /// A forced pinned binding below the pinned-role floor: the latency
112    /// contract is void (the declared shares exceed the quota), the
113    /// correctness contract is intact. Loud, never silent.
114    pub oversubscribed: bool,
115    /// The detected budget the plan decided on (cores, fractional).
116    pub budget_cpus: f64,
117    /// The typed budget refusal the resolved plan overrode or fell from
118    /// (`None` when nothing was refused): under `auto`, the pinned-budget
119    /// refusal that placed this host in the serial tier (the boot re-raises
120    /// it while the serial arm is pending — FF-T4 — so the refusal stays
121    /// the typed, named budget error the pinned derivation produced); under
122    /// a FORCED pinned profile below the pinned-role floor, the overridden
123    /// `QuotaTooSmallForPinnedRoles` the marked plan runs past (the runtime
124    /// status names the floor it fell from — FF-T5 addendum, 452GZC);
125    /// `None` for forced serial and every unrefused boot.
126    pub tier_refusal: Option<BudgetError>,
127}
128
129impl FleetPlan {
130    /// The per-binding budget projection: the [`FleetBudget`] the binding
131    /// boots under. Every binding selects a [`BudgetMode`] and the ONE
132    /// owner ([`FleetBudget::project`]) produces the table — this
133    /// module holds no budget arithmetic. The PINNED eligible path is the
134    /// sum-checked derivation (byte-stable); the marked and serial modes
135    /// are total (the sum invariant is void there by contract —
136    /// oversubscribed / shared threads).
137    ///
138    /// # Errors
139    /// [`BootError`] — the pinned eligible projection's typed refusal. The
140    /// marked/serial projections are total; their layout legality is
141    /// `SlotLayout::of`'s dead-station check, asserted by the
142    /// plan-tier invariant tests.
143    pub fn projected_budget(&self, overrides: &BudgetOverrides) -> Result<FleetBudget, BootError> {
144        let mode = match self.binding {
145            Binding::Pinned if !self.oversubscribed => BudgetMode::Pinned,
146            Binding::Pinned => BudgetMode::PinnedMarked,
147            Binding::Serial => BudgetMode::Serial,
148        };
149        FleetBudget::project(self.budget_cpus, overrides, mode).map_err(BootError::from)
150    }
151
152    /// The typed refusal the BOOT raises while the serial arm is pending
153    /// (FF-T4): under `auto` the tier's own pinned-budget refusal (the
154    /// CI-stable message); under a FORCED serial profile the named
155    /// pending-arm invariant (the operator asked for a binding that does
156    /// not exist yet — refusing is the honest answer, never a silent
157    /// narrow).
158    #[must_use]
159    pub fn pending_serial_refusal(&self) -> BootError {
160        match self.tier_refusal {
161            Some(refusal) => BootError::Budget(refusal),
162            None => BootError::Invariant(
163                "the serial binding (the 2-5-core tier) lands with FF-T4 \
164                 — forced serial refuses rather than run the pinned topology \
165                 silently narrower",
166            ),
167        }
168    }
169}
170
171/// ONE pure function of the budget (the tiered boot authority). Total,
172/// deterministic, no host reads: the caller supplies the detected
173/// quota, the profile, and the terminal overrides. The budget stays the
174/// sole sizing authority (min(cgroup quota, affinity), fractional,
175/// rounded down for tier eligibility).
176///
177/// # Errors
178/// [`BootError::Budget`] — below the 2-core host floor, an override the
179/// resolved binding cannot honor (out-of-bounds, never a silent clamp),
180/// or (forced/oversubscribed configs) the derivation's own refusal.
181pub fn plan(
182    quota_cpus: f64,
183    profile: FleetProfile,
184    overrides: &BudgetOverrides,
185) -> Result<FleetPlan, BootError> {
186    // Tier eligibility floors the fractional quota (budget.rs floors at
187    // 1.0 on detection; the tier check is against HOST_FLOOR_CORES).
188    #[expect(
189        clippy::cast_possible_truncation,
190        reason = "quota floors are small positive values (core counts)"
191    )]
192    #[expect(
193        clippy::cast_sign_loss,
194        reason = "the quota is floored at 1.0 before the cast"
195    )]
196    let quota_floor = quota_cpus.max(1.0).floor() as u64;
197    if quota_floor < HOST_FLOOR_CORES {
198        return Err(BootError::Budget(BudgetError::BelowHostFloor {
199            quota: quota_cpus,
200        }));
201    }
202    match profile {
203        FleetProfile::Auto => match FleetBudget::derive(quota_cpus, overrides) {
204            Ok(_) => {
205                validate_io_workers(Binding::Pinned, overrides)?;
206                Ok(FleetPlan {
207                    id: PLAN_ID,
208                    binding: Binding::Pinned,
209                    oversubscribed: false,
210                    budget_cpus: quota_cpus,
211                    tier_refusal: None,
212                })
213            }
214            // The 2-5-core tier: the plan says Serial, carrying the pinned
215            // refusal that placed the host there. The arm lands with
216            // FF-T4; until then the boot re-raises the tier refusal.
217            Err(refusal @ BudgetError::QuotaTooSmallForPinnedRoles { .. }) => {
218                validate_io_workers(Binding::Serial, overrides)?;
219                Ok(FleetPlan {
220                    id: PLAN_ID,
221                    binding: Binding::Serial,
222                    oversubscribed: false,
223                    budget_cpus: quota_cpus,
224                    tier_refusal: Some(refusal),
225                })
226            }
227            // An oversubscribed / under-minimum pinned config is a
228            // configuration bug on ANY tier: refuse with its own typed
229            // error, never fall through to a narrower plan.
230            Err(other) => Err(BootError::Budget(other)),
231        },
232        FleetProfile::Pinned => {
233            validate_io_workers(Binding::Pinned, overrides)?;
234            // Forced pinned runs on any host with 2 or more cores; below
235            // the pinned-role floor the latency contract is void (marked).
236            // The typed refusal the operator overrode RIDES the plan — like
237            // the auto serial tier carries its placing refusal — so the
238            // runtime status names the pinned floor it fell from (FF-T5
239            // addendum, 452GZC).
240            let (oversubscribed, tier_refusal) = match FleetBudget::derive(quota_cpus, overrides) {
241                Ok(_) => (false, None),
242                Err(refusal) => (true, Some(refusal)),
243            };
244            Ok(FleetPlan {
245                id: PLAN_ID,
246                binding: Binding::Pinned,
247                oversubscribed,
248                budget_cpus: quota_cpus,
249                tier_refusal,
250            })
251        }
252        FleetProfile::Serial => {
253            validate_io_workers(Binding::Serial, overrides)?;
254            Ok(FleetPlan {
255                id: PLAN_ID,
256                binding: Binding::Serial,
257                oversubscribed: false,
258                budget_cpus: quota_cpus,
259                tier_refusal: None,
260            })
261        }
262    }
263}
264
265/// The per-binding `runtime.io_workers` bounds (FF-T2: an out-of-bounds
266/// override raises a typed refusal with a hint, never a silent clamp).
267/// The pinned binding keeps the ambient floor (A >= 1); the serial
268/// binding owns exactly ONE ambient I/O lane (A == 1).
269fn validate_io_workers(binding: Binding, overrides: &BudgetOverrides) -> Result<(), BootError> {
270    let Some(requested) = overrides.ambient_io_workers else {
271        return Ok(());
272    };
273    let legal = match binding {
274        Binding::Pinned => requested >= 1,
275        Binding::Serial => requested == 1,
276    };
277    if legal {
278        return Ok(());
279    }
280    Err(BootError::Budget(BudgetError::IoWorkersOutOfBounds {
281        requested,
282        binding: binding.name(),
283    }))
284}
285
286#[cfg(test)]
287#[expect(clippy::expect_used)]
288mod tests {
289    use super::*;
290    use crate::dispatcher::SlotLayout;
291    use degenbot_config::FleetProfile;
292
293    fn overrides() -> BudgetOverrides {
294        BudgetOverrides::default()
295    }
296
297    /// THE FF-T2 algebra table (the AC, verbatim): one pure function of
298    /// the budget over the ordered host tiers.
299    #[test]
300    fn the_tier_table_is_the_contract() {
301        // plan(1.5) -> Err: below the 2-core host floor (one core for
302        // I/O work, one core for solve work), typed.
303        let err = plan(1.5, FleetProfile::Auto, &overrides()).expect_err("sub-2-core host");
304        assert!(
305            matches!(err, BootError::Budget(BudgetError::BelowHostFloor { quota }) if (quota - 1.5).abs() < f64::EPSILON),
306            "below-2-cores refuses with the typed host-floor error, got {err:?}"
307        );
308        // plan(2.0) -> Serial: the pinned derivation refuses (floor 2 <
309        // the 6-core pinned-role floor), the host keeps the 2-core minimum.
310        let p = plan(2.0, FleetProfile::Auto, &overrides()).expect("2-core host plans");
311        assert_eq!(p.binding, Binding::Serial);
312        assert!(!p.oversubscribed);
313        assert_eq!(p.id, PLAN_ID);
314        // plan(5.99) -> Serial: tier eligibility FLOORS the fraction.
315        let p = plan(5.99, FleetProfile::Auto, &overrides()).expect("5.99-core host plans");
316        assert_eq!(p.binding, Binding::Serial);
317        // plan(6.0) -> Pinned: the pinned derivation succeeds exactly
318        // there (H1+A1+R1+M1+S2 = 6).
319        let p = plan(6.0, FleetProfile::Auto, &overrides()).expect("6-core host plans");
320        assert_eq!(p.binding, Binding::Pinned);
321        assert!(!p.oversubscribed);
322        // Forced pinned on a 2-core quota -> Pinned + oversubscribed
323        // (latency contract void, correctness intact, LOUD).
324        let p = plan(2.0, FleetProfile::Pinned, &overrides()).expect("forced pinned plans");
325        assert_eq!(p.binding, Binding::Pinned);
326        assert!(
327            p.oversubscribed,
328            "a forced pinned binding below the floor is marked"
329        );
330        // Forced serial on a 24-core quota -> Serial (forced bindings
331        // run on any host with 2 or more cores).
332        let p = plan(24.0, FleetProfile::Serial, &overrides()).expect("forced serial plans");
333        assert_eq!(p.binding, Binding::Serial);
334        assert!(!p.oversubscribed);
335        assert!(p.tier_refusal.is_none());
336    }
337
338    /// The auto serial tier CARRIES the pinned refusal that placed the
339    /// host there: the boot re-raises it while the arm is pending
340    /// (FF-T4), keeping the CI-stable typed message.
341    #[test]
342    fn the_auto_serial_tier_carries_the_pinned_refusal() {
343        let p = plan(4.0, FleetProfile::Auto, &overrides()).expect("4-core host plans");
344        assert_eq!(p.binding, Binding::Serial);
345        let refusal = p.tier_refusal.as_ref().expect("the tier refusal rides");
346        assert!(
347            matches!(
348                refusal,
349                BudgetError::QuotaTooSmallForPinnedRoles { quota, required }
350                    if (*quota - 4.0).abs() < f64::EPSILON && *required == 6
351            ),
352            "the carried refusal is the pinned derivation's own, got {refusal:?}"
353        );
354        // The boot-facing refusal is the SAME budget error (the
355        // pending-arm gate), and a forced serial profile gets the
356        // named pending invariant instead.
357        assert!(matches!(
358            p.pending_serial_refusal(),
359            BootError::Budget(BudgetError::QuotaTooSmallForPinnedRoles { .. })
360        ));
361        let forced = plan(24.0, FleetProfile::Serial, &overrides()).expect("forced serial plans");
362        assert!(matches!(
363            forced.pending_serial_refusal(),
364            BootError::Invariant(_)
365        ));
366    }
367
368    /// FF-T5 addendum (452GZC): a forced pinned binding below the
369    /// pinned-role floor is MARKED and KEEPS the typed refusal it overrode —
370    /// the runtime status names the pinned floor it fell from, exactly like
371    /// the auto serial tier carries its placing refusal. At/above the floor
372    /// nothing was refused.
373    #[test]
374    fn the_forced_pinned_oversubscription_carries_the_pinned_floor_refusal() {
375        let p = plan(4.0, FleetProfile::Pinned, &overrides()).expect("forced pinned plans");
376        assert_eq!(p.binding, Binding::Pinned);
377        assert!(p.oversubscribed);
378        let refusal = p
379            .tier_refusal
380            .as_ref()
381            .expect("the overridden refusal rides");
382        assert!(
383            matches!(
384                refusal,
385                BudgetError::QuotaTooSmallForPinnedRoles { quota, required }
386                    if (*quota - 4.0).abs() < f64::EPSILON && *required == 6
387            ),
388            "the carried refusal is the pinned derivation's own, got {refusal:?}"
389        );
390        for quota in [6.0_f64, 8.0, 24.0] {
391            let p = plan(quota, FleetProfile::Pinned, &overrides()).expect("eligible host plans");
392            assert!(!p.oversubscribed);
393            assert!(p.tier_refusal.is_none());
394        }
395    }
396
397    /// `runtime.io_workers` is validated against the plan bounds: an
398    /// out-of-bounds override raises a typed refusal with a hint, never
399    /// a silent clamp. The pinned binding keeps the ambient floor
400    /// (A >= 1); the serial binding owns exactly ONE ambient I/O lane.
401    #[test]
402    fn io_workers_overrides_are_validated_against_plan_bounds() {
403        // Pinned + A=0: below the ambient floor.
404        let ov = BudgetOverrides {
405            ambient_io_workers: Some(0),
406            ..overrides()
407        };
408        let err = plan(8.0, FleetProfile::Pinned, &ov).expect_err("A=0 refuses");
409        assert!(
410            matches!(
411                err,
412                BootError::Budget(BudgetError::IoWorkersOutOfBounds {
413                    requested: 0,
414                    binding: "pinned"
415                })
416            ),
417            "the out-of-bounds override names the request and the binding, got {err:?}"
418        );
419        // Serial + A=2: off the exactly-one ambient I/O lane (the auto
420        // serial tier validates the same bounds).
421        let ov = BudgetOverrides {
422            ambient_io_workers: Some(2),
423            ..overrides()
424        };
425        let err = plan(4.0, FleetProfile::Auto, &ov).expect_err("serial A=2 refuses");
426        assert!(
427            matches!(
428                err,
429                BootError::Budget(BudgetError::IoWorkersOutOfBounds {
430                    requested: 2,
431                    binding: "serial"
432                })
433            ),
434            "the serial tier validates its one-lane bound, got {err:?}"
435        );
436        // A legal override rides untouched (never a clamp).
437        let ov = BudgetOverrides {
438            ambient_io_workers: Some(2),
439            ..overrides()
440        };
441        let p = plan(8.0, FleetProfile::Auto, &ov).expect("8-core A=2 plans");
442        assert_eq!(p.binding, Binding::Pinned);
443        assert_eq!(
444            p.projected_budget(&ov)
445                .expect("projection derives")
446                .ambient_cpus,
447            2
448        );
449    }
450
451    /// The plan-tier invariants (the `SlotLayout` dead-station family
452    /// extended into tiers): a serial-plan projection must yield a LEGAL,
453    /// non-empty layout — 1 solver seat, 1 resolve, >= 1 poolupd, sim per
454    /// budget; the forced-pinned marked projection must stay legal too.
455    #[test]
456    fn plan_projections_yield_legal_non_empty_layouts() {
457        for quota in [2.0_f64, 2.5, 4.0, 5.99, 24.0] {
458            let p = plan(quota, FleetProfile::Serial, &overrides()).expect("serial plans");
459            let b = p
460                .projected_budget(&overrides())
461                .expect("serial projection is total");
462            assert_eq!(b.solver_pin_count, 1, "serial-0: exactly one solve seat");
463            assert_eq!(b.resolve_cpus, 1);
464            assert!(b.pool_state_updater_slots >= 1);
465            assert!(b.sim_slot_cap >= 1, "sim per budget");
466            // The layout is legal (every hosted range non-empty, the merge
467            // sidecar last); the seat counts were asserted on the budget
468            // fields above — the ONE geometry derivation accepted them.
469            SlotLayout::of(&b).expect("the serial projection hosts a legal layout");
470        }
471        // The forced-pinned marked projection: legal stations, the pin
472        // count floors at 1, the mark carries the deficit story.
473        for quota in [2.0_f64, 4.0, 5.99] {
474            let p = plan(quota, FleetProfile::Pinned, &overrides()).expect("forced pinned plans");
475            assert!(p.oversubscribed);
476            let b = p
477                .projected_budget(&overrides())
478                .expect("the marked projection is total");
479            assert!(
480                b.solver_pin_count >= 1,
481                "station legality: the pin count floors at 1"
482            );
483            assert!(
484                (b.fractional_remainder - 0.0).abs() < 1e-9,
485                "oversubscribed: no spendable remainder"
486            );
487            SlotLayout::of(&b).expect("the marked projection hosts a legal layout");
488        }
489    }
490
491    /// The pinned eligible path is the ONE derivation, byte-stable: the
492    /// projection IS `FleetBudget::derive` (no second arithmetic).
493    #[test]
494    fn the_pinned_eligible_projection_is_the_one_derivation() {
495        for quota in [6.0_f64, 8.0, 24.0, 6.5] {
496            let p = plan(quota, FleetProfile::Auto, &overrides()).expect("eligible host plans");
497            assert_eq!(p.binding, Binding::Pinned);
498            assert_eq!(
499                p.projected_budget(&overrides())
500                    .expect("projection derives"),
501                FleetBudget::derive(quota, &overrides()).expect("the derivation")
502            );
503        }
504    }
505}