degenbot_workers/budget.rs
1//! `FleetBudget` — the ONE budget authority bounding the SUM (design doc
2//! §5; ADR-042 §3).
3//!
4//! Every consumer declares a peak-CPU share and a thread/slot count; the
5//! fleet refuses to boot when the declared shares exceed the quota —
6//! oversubscription is a configuration bug surfaced at boot, never a runtime
7//! throttle storm. Fractional-quota policy (reviewed in the ADR): detection
8//! keeps `degenbot_core::cpu_budget`'s ceil for worker-existence sizing,
9//! while allocation arithmetic FLOORS — integer shares sum against
10//! `floor(Q)` and the fractional remainder is spendable only by I/O-dominant
11//! consumers (`SimDriver` slots, ambient I/O), whose measured duty is
12//! partial-core by construction. `Q` comes from [`crate::quota`].
13//!
14//! # Discrepancy note (recorded, not redesigned)
15//!
16//! The design doc's worked 8-core table lists ambient `A = 2` while the
17//! rule column reads `max(1, floor((Q−H)/4))`, which evaluates to 1 at
18//! `Q = 8, H = 1` (leaving `S = 4`). This implementation follows the RULE
19//! literally; a deployed operator recovers the table's `A = 2, S = 3` split
20//! with `DEGENBOT_IO_WORKERS=2` (the terminal override both the table and
21//! this code honor). The SUM invariant holds under either assignment.
22//!
23//! # Pin count = the LPT bin count (P6YXA6 sizing reconciliation)
24//!
25//! Solver pins are STRUCTURAL, not a share multiple: one seat per LPT bin,
26//! the bin count following the same policy as
27//! `degenbot_core::cpu_budget`'s solve bins — `floor(Q)` minus
28//! [`degenbot_core::cpu_budget::DEFAULT_SOLVE_HEADROOM`]. At the deployed
29//! Q = 8 that is 6 pins, exactly the bin count every dispatch arm binds at
30//! (the ad-hoc `shares x 2` pin derivation — 8 seats at Q = 8 against
31//! 6 bins — retires with the hard cutover). Walk ADMISSION stays the
32//! share `S`: a gated bin parks, per design doc §5.
33//!
34//! # Cross-authority contract: allocation floors, detection ceils
35//!
36//! `degenbot_core::cpu_budget` CEILS fractional cgroup quotas for
37//! worker-existence sizing (a 4.5-core quota still buys a 5th worker);
38//! this authority FLOORS (`floor(Q) − headroom`), because Solver threads
39//! are never I/O-dominant and must never spend the fractional remainder.
40//! Consequence (property-tested in this file, `cross_authority`): the two
41//! sizing authorities agree at integer quotas with `affinity >= Q`; under
42//! a fractional quota with `affinity >= ceil(Q)` the legacy-stance solve
43//! bins sit exactly one ABOVE the fleet seats. Integer quotas are the
44//! deployment norm, and fleet-hosted cycles bind bins at the seat count
45//! anyway, so the divergence is inert in production.
46
47use degenbot_config::FleetConfig;
48
49/// Fixed reserve share `H` (Python bridge, pump, `OTel`, async GC): the fleet
50/// must never starve I/O.
51pub const DEFAULT_RESERVE_CPUS: u64 = 1;
52/// Minimum Solver share the fleet will host.
53pub const MIN_SOLVER_CPUS: u64 = 2;
54/// Today's `SimSlots` cap, preserved as the `SimDriver` slot cap (design doc §5).
55pub const DEFAULT_SIM_SLOT_CAP: usize = 4;
56/// The registration intake station's `PoolStateUpdater` slot cap (PRG-3,
57/// ADR-042 F2: the registration crawl's pool-build consumers hosted as
58/// keyed deferrable units). I/O-dominant by construction (RPC-bound
59/// builds), so the billing follows the `SimDriver` model exactly.
60pub const DEFAULT_POOL_STATE_UPDATER_SLOTS: usize = 4;
61
62/// Terminal, typed overrides (config/env) consumed by [`FleetBudget::derive`]
63/// a configured value wins, is logged, and participates in the same sum
64/// check (design doc §5: "overrides are terminal").
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
66pub struct BudgetOverrides {
67 /// `fleet.reserve_cpus` — reserve share `H`.
68 pub reserve_cpus: Option<u64>,
69 /// `runtime.io_workers` — ambient I/O runtime `A`.
70 pub ambient_io_workers: Option<u64>,
71 /// `fleet.solver_cpus` — Solver share `S`.
72 pub solver_cpus: Option<u64>,
73 /// `fleet.sim_slot_cap` — `SimDriver` slot count.
74 pub sim_slot_cap: Option<usize>,
75 /// `fleet.pool_state_updater_slots` — `PoolStateUpdater` slot count.
76 pub pool_state_updater_slots: Option<usize>,
77 /// Solve headroom `H_s` in the seat formula `floor(Q) − H_s` (the
78 /// documented floor-allocation constant; LW-T4 makes it an explicit
79 /// boot-authority input).
80 pub solve_headroom: Option<usize>,
81}
82
83impl BudgetOverrides {
84 /// The typed-config projection (env vars land in the same fields via
85 /// the loader's env layer).
86 #[must_use]
87 pub fn from_config(cfg: °enbot_config::BotConfig) -> Self {
88 Self {
89 reserve_cpus: cfg.fleet.reserve_cpus.and_then(|v| u64::try_from(v).ok()),
90 ambient_io_workers: cfg.runtime.io_workers.and_then(|v| u64::try_from(v).ok()),
91 solver_cpus: cfg.fleet.solver_cpus.and_then(|v| u64::try_from(v).ok()),
92 sim_slot_cap: cfg.fleet.sim_slot_cap,
93 pool_state_updater_slots: cfg.fleet.pool_state_updater_slots,
94 // The typed T8 follow-through: the headroom override
95 // reaches the budget derive; unset stays None and the documented
96 // constant rules below.
97 solve_headroom: cfg.solve.solve_headroom,
98 }
99 }
100}
101
102/// Why a budget was refused.
103#[derive(Debug, Clone, Copy, PartialEq, thiserror::Error)]
104pub enum BudgetError {
105 /// Declared peak shares exceed the quota floor.
106 #[error(
107 "fleet budget oversubscribed: declared peak shares {declared} cores exceed \
108 floor(quota {quota}) = {floor} — oversubscription is a configuration bug, failed at boot"
109 )]
110 Oversubscribed {
111 /// The fractional quota (cores).
112 quota: f64,
113 /// `floor(quota)` the integer shares must sum to.
114 floor: u64,
115 /// The declared sum that exceeded it.
116 declared: u64,
117 },
118 /// Fractional quota below the pinned-role floor (`H+A+R+M+2`).
119 #[error(
120 "fractional quota {quota:.2} below the pinned-role floor of {required} cores \
121 (reserve H + ambient A + resolve R + merge M + the 2-core Solver minimum); \
122 the fleet cannot host the pinned latency roles there — the \
123 serial/sequential fallback DECISION POINT is here (reth \
124 has_enough_parallelism(): lane capability is explicit, never silently \
125 narrower); the fallback arm itself is a DOWNSTREAM decision (LW-T7)"
126 )]
127 QuotaTooSmallForPinnedRoles {
128 /// The fractional quota (cores).
129 quota: f64,
130 /// The minimum usable quota.
131 required: u64,
132 },
133 /// Solver share derives below the minimum.
134 #[error("fleet Solver share derives to {solver} < the {min}-core minimum")]
135 TooFewSolverCpus {
136 /// The derived/declared Solver share.
137 solver: u64,
138 /// [`MIN_SOLVER_CPUS`].
139 min: u64,
140 },
141 /// Fractional quota below the 2-core host floor (FLEETFLOOR FF-T2: one
142 /// core for I/O work, one core for solve work) — no binding can host
143 /// the fleet there, forced or not.
144 #[error(
145 "fractional quota {quota:.2} below the 2-core host floor (one core for I/O \
146 work, one core for solve work) — no binding can host the fleet there \
147 (raise the host CPU budget / affinity to at least 2 cores)"
148 )]
149 BelowHostFloor {
150 /// The fractional quota (cores).
151 quota: f64,
152 },
153 /// A `runtime.io_workers` override the resolved plan cannot honor
154 /// (FF-T2): below the ambient floor (A >= 1) on the pinned
155 /// binding, or off the serial binding\u0027s exactly-one ambient I/O lane.
156 /// Refused with a hint, never a silent clamp.
157 #[error(
158 "runtime.io_workers override {requested} is out of bounds for the {binding} \
159 binding (pinned: A >= 1, the ambient floor; serial: exactly one \
160 ambient I/O lane) — fix or drop the override, never a silent clamp"
161 )]
162 IoWorkersOutOfBounds {
163 /// The requested override.
164 requested: u64,
165 /// The binding the plan resolved (the label).
166 binding: &'static str,
167 },
168}
169
170impl BudgetError {
171 /// The typed variant NAME (the closed, greppable refusal vocabulary).
172 /// `runtime_status()`'s `tier_refused` string names the family the plan
173 /// fell from (FF-T5 addendum, 452GZC); the Display wording stays the
174 /// operator sentence with the quota, the floor, and the hint.
175 #[must_use]
176 pub const fn name(&self) -> &'static str {
177 match self {
178 Self::Oversubscribed { .. } => "Oversubscribed",
179 Self::QuotaTooSmallForPinnedRoles { .. } => "QuotaTooSmallForPinnedRoles",
180 Self::TooFewSolverCpus { .. } => "TooFewSolverCpus",
181 Self::BelowHostFloor { .. } => "BelowHostFloor",
182 Self::IoWorkersOutOfBounds { .. } => "IoWorkersOutOfBounds",
183 }
184 }
185}
186
187/// One consumer row of the boot allocation table (design doc §5) — declared
188/// `(peak_cpus, thread_count)` per the ADR's sum-bounding contract.
189#[derive(Debug, Clone, Copy, PartialEq)]
190pub struct ConsumerShare {
191 /// Consumer name for the boot log.
192 pub consumer: &'static str,
193 /// Declared peak CPU share (cores).
194 pub peak_cpus: u64,
195 /// Declared thread/slot count (may exceed the share for I/O-dominant
196 /// roles; the CPU share is what the authority bounds).
197 pub thread_count: usize,
198 /// Sizing rule in words (mirrors the worker-census `sizing` text).
199 pub sizing: &'static str,
200}
201
202/// The derived fleet allocation — every consumer's declared share, the sum
203/// check, and the fractional remainder policy (design doc §5).
204#[derive(Debug, Clone, Copy, PartialEq)]
205pub struct FleetBudget {
206 /// The fractional quota `Q` this budget was derived from (cores).
207 pub quota_cpus: f64,
208 /// `floor(Q)` — integer shares sum against this.
209 pub quota_floor: u64,
210 /// Reserve `H` (Python bridge, pump, `OTel`, async GC). Fixed default 1.
211 pub reserve_cpus: u64,
212 /// Ambient I/O runtime `A` = `max(1, floor((Q−H)/4))`, terminal override
213 /// `DEGENBOT_IO_WORKERS`.
214 pub ambient_cpus: u64,
215 /// Resolve `R` — fixed v1 (1 core, 12.4 ms/cycle measured).
216 pub resolve_cpus: u64,
217 /// Merge `M` — exactly one sidecar.
218 pub merge_cpus: u64,
219 /// Solver pins `S` = `floor(Q) − H − A − R − M` (or the terminal
220 /// override); `>= 2` or the boot fails.
221 pub solver_cpus: u64,
222 /// Solver pin seats: STRUCTURAL — one per LPT bin
223 /// (`floor(Q)` − `cpu_budget::DEFAULT_SOLVE_HEADROOM`; concurrent walk
224 /// admission stays the share `S`).
225 pub solver_pin_count: usize,
226 /// `SimDriver` slots (duty-counted, spendable from the fractional
227 /// remainder only), capped at today's `SimSlots` cap by default.
228 pub sim_slot_cap: usize,
229 /// `PoolStateUpdater` slots (duty-counted, spendable from the
230 /// fractional remainder only) — the registration intake station's
231 /// bounded per-role unit pool. Behind Solver precedence at dispatch.
232 pub pool_state_updater_slots: usize,
233 /// The fractional remainder `Q − Σ(shares)` — spendable ONLY by
234 /// I/O-dominant consumers, enforced by construction: it is never
235 /// included in the integer sum check.
236 pub fractional_remainder: f64,
237}
238
239/// The per-tier projection mode: ONE owner
240/// (`FleetBudget::project`) derives all three tier tables. The tier is MODE
241/// DATA, not a second derivation — plan.rs's pinned/marked/serial arms
242/// select a mode.
243#[derive(Debug, Clone, Copy, PartialEq, Eq)]
244pub(crate) enum BudgetMode {
245 /// The pinned-role derivation: integer shares sum-checked against
246 /// `floor(Q)`, failing fast with the typed [`BudgetError`]s.
247 Pinned,
248 /// The forced-pinned-below-floor projection: pinned arithmetic with no
249 /// sum enforcement (the plan's `oversubscribed` mark declares the
250 /// deficit).
251 PinnedMarked,
252 /// The serial (2-5 core) projection: one ambient I/O lane, one cycle
253 /// thread, exactly one solve seat.
254 Serial,
255}
256
257/// The 2-core host floor: one core for I/O work, one core for solve work.
258/// The plan's tier gate and the serial projection's logical thread count
259/// share this ONE owner.
260pub const HOST_FLOOR_CORES: u64 = 2;
261
262impl FleetBudget {
263 /// Derive the table for `quota_cpus` under the terminal overrides.
264 /// Fails loudly (the typed [`BudgetError`]s) on over-subscription or a
265 /// quota too small for the pinned latency roles.
266 ///
267 /// # Errors
268 /// Any of the [`BudgetError`] fail-fast conditions.
269 pub fn derive(quota_cpus: f64, overrides: &BudgetOverrides) -> Result<Self, BudgetError> {
270 Self::project(quota_cpus, overrides, BudgetMode::Pinned)
271 }
272
273 /// The ONE tier projection: `mode` selects the pinned,
274 /// forced-pinned-marked, or serial table, all built from the SAME shared
275 /// formulas (H, A, R, M, the structural pin count, the slot caps). Only
276 /// the pinned arm enforces the sum check; the other two are total by
277 /// contract (the mark / the shared-thread topology carry the deficit).
278 ///
279 /// # Errors
280 /// Any of the [`BudgetError`] fail-fast conditions — produced only by
281 /// [`BudgetMode::Pinned`].
282 #[expect(
283 clippy::cast_possible_truncation,
284 reason = "quota floors are small positive values (core counts)"
285 )]
286 #[expect(
287 clippy::cast_precision_loss,
288 reason = "core counts are exact in f64 at any realistic quota"
289 )]
290 #[expect(
291 clippy::cast_sign_loss,
292 reason = "the floor is clamped to >= 1.0 before the cast"
293 )]
294 pub(crate) fn project(
295 quota_cpus: f64,
296 overrides: &BudgetOverrides,
297 mode: BudgetMode,
298 ) -> Result<Self, BudgetError> {
299 let quota_floor = quota_cpus.max(1.0).floor() as u64;
300
301 // H — fixed default 1, terminal override.
302 let reserve_cpus = overrides.reserve_cpus.unwrap_or(DEFAULT_RESERVE_CPUS);
303
304 // A — the leftover-share rule, terminal DEGENBOT_IO_WORKERS override.
305 let ambient_cpus = overrides
306 .ambient_io_workers
307 .unwrap_or_else(|| ((quota_floor.saturating_sub(reserve_cpus)) / 4).max(1));
308
309 // R, M — fixed v1 consumers.
310 let (resolve_cpus, merge_cpus) = (1, 1);
311 let base = reserve_cpus + ambient_cpus + resolve_cpus + merge_cpus;
312
313 let solve_headroom = overrides
314 .solve_headroom
315 .unwrap_or(degenbot_core::cpu_budget::DEFAULT_SOLVE_HEADROOM);
316 let sim_slot_cap = overrides.sim_slot_cap.unwrap_or(DEFAULT_SIM_SLOT_CAP);
317 let pool_state_updater_slots = overrides
318 .pool_state_updater_slots
319 .unwrap_or(DEFAULT_POOL_STATE_UPDATER_SLOTS);
320 // Pin seats are STRUCTURAL (P6YXA6 reconciliation): one per LPT
321 // bin, the bin count following cpu_budget's solve-bin POLICY —
322 // minus the solve headroom, floored at 1 — but FLOORING the quota:
323 // cpu_budget ceils fractional quotas for worker-existence; Solver
324 // threads never spend the fractional remainder (§5).
325 // Walk admission stays the share S — a gated bin parks (§5).
326 let pinned_pin_count = usize::try_from(quota_floor)
327 .unwrap_or(usize::MAX)
328 .saturating_sub(solve_headroom)
329 .max(1);
330
331 match mode {
332 BudgetMode::Pinned => {
333 if base + MIN_SOLVER_CPUS > quota_floor {
334 return Err(BudgetError::QuotaTooSmallForPinnedRoles {
335 quota: quota_cpus,
336 required: base + MIN_SOLVER_CPUS,
337 });
338 }
339 // S — the leftover of the floor after the fixed consumers
340 // (or the terminal override, checked against the same sum).
341 let solver_cpus = overrides.solver_cpus.unwrap_or(quota_floor - base);
342 if base + solver_cpus > quota_floor {
343 return Err(BudgetError::Oversubscribed {
344 quota: quota_cpus,
345 floor: quota_floor,
346 declared: base + solver_cpus,
347 });
348 }
349 if solver_cpus < MIN_SOLVER_CPUS {
350 return Err(BudgetError::TooFewSolverCpus {
351 solver: solver_cpus,
352 min: MIN_SOLVER_CPUS,
353 });
354 }
355 Ok(Self {
356 quota_cpus,
357 quota_floor,
358 reserve_cpus,
359 ambient_cpus,
360 resolve_cpus,
361 merge_cpus,
362 solver_cpus,
363 solver_pin_count: pinned_pin_count,
364 sim_slot_cap,
365 pool_state_updater_slots,
366 fractional_remainder: quota_cpus - (base + solver_cpus) as f64,
367 })
368 }
369 BudgetMode::PinnedMarked => {
370 let solver_cpus = overrides
371 .solver_cpus
372 .unwrap_or_else(|| quota_floor.saturating_sub(base).max(MIN_SOLVER_CPUS));
373 Ok(Self {
374 quota_cpus,
375 quota_floor,
376 reserve_cpus,
377 ambient_cpus,
378 resolve_cpus,
379 merge_cpus,
380 solver_cpus,
381 solver_pin_count: pinned_pin_count,
382 sim_slot_cap,
383 pool_state_updater_slots,
384 // Oversubscribed: no spendable remainder exists (the
385 // deficit is the plan's mark, not a budget field) —
386 // clamp at zero.
387 fractional_remainder: (quota_cpus - (base + solver_cpus) as f64).max(0.0),
388 })
389 }
390 BudgetMode::Serial => Ok(Self {
391 quota_cpus,
392 quota_floor,
393 reserve_cpus,
394 // Exactly one ambient I/O lane (validated by the plan).
395 ambient_cpus: 1,
396 resolve_cpus,
397 merge_cpus,
398 // The logical 2-core solve minimum: the serial cycle thread
399 // runs solve work on the second core.
400 solver_cpus: MIN_SOLVER_CPUS,
401 // serial-0: exactly one solve seat (the FF-T4 contract).
402 solver_pin_count: 1,
403 sim_slot_cap,
404 pool_state_updater_slots,
405 // The binding owns two threads; everything above is spendable.
406 fractional_remainder: (quota_cpus - HOST_FLOOR_CORES as f64).max(0.0),
407 }),
408 }
409 }
410
411 /// H + A + R + M + S — the declared sum the authority bounds.
412 #[must_use]
413 pub const fn declared_sum(&self) -> u64 {
414 self.reserve_cpus
415 + self.ambient_cpus
416 + self.resolve_cpus
417 + self.merge_cpus
418 + self.solver_cpus
419 }
420
421 /// The boot allocation table (log/boot-dump consumer order).
422 #[must_use]
423 pub fn allocation_table(&self) -> Vec<ConsumerShare> {
424 vec![
425 ConsumerShare {
426 consumer: "reserve",
427 peak_cpus: self.reserve_cpus,
428 thread_count: 0,
429 sizing: "fixed; must never starve I/O",
430 },
431 ConsumerShare {
432 consumer: "ambient_io",
433 peak_cpus: self.ambient_cpus,
434 thread_count: usize::try_from(self.ambient_cpus).unwrap_or(1),
435 sizing: "max(1, floor((Q-H)/4)); DEGENBOT_IO_WORKERS override is terminal",
436 },
437 ConsumerShare {
438 consumer: "resolve",
439 peak_cpus: self.resolve_cpus,
440 thread_count: usize::try_from(self.resolve_cpus).unwrap_or(1),
441 sizing: "fixed v1 (12.4 ms/cycle measured)",
442 },
443 ConsumerShare {
444 consumer: "merge",
445 peak_cpus: self.merge_cpus,
446 thread_count: usize::try_from(self.merge_cpus).unwrap_or(1),
447 sizing: "exactly one sidecar",
448 },
449 ConsumerShare {
450 consumer: "solver",
451 peak_cpus: self.solver_cpus,
452 thread_count: self.solver_pin_count,
453 sizing: "Q - H - A - R - M (>= 2 or fail-fast); pins = one per LPT bin (floor(Q) - solve headroom)",
454 },
455 ]
456 }
457
458 /// Re-declare the shares under a changed quota (posture/logged event).
459 /// Pure re-derivation; the HOST decides which pins change (T9) and never
460 /// re-keys mid-cycle.
461 ///
462 /// # Errors
463 /// Any of the [`BudgetError`] fail-fast conditions under the new quota.
464 pub fn resize(
465 &self,
466 new_quota_cpus: f64,
467 new_overrides: &BudgetOverrides,
468 ) -> Result<Self, BudgetError> {
469 Self::derive(new_quota_cpus, new_overrides)
470 }
471
472 /// Whether moving from this budget to `next` requires pin re-keying
473 /// (a pin-count change) — the epoch-boundary rebalance trigger (T9).
474 #[must_use]
475 pub const fn pins_require_rekey(&self, next: &FleetBudget) -> bool {
476 self.solver_pin_count != next.solver_pin_count
477 }
478}
479
480/// Fractional quota detection seam: the typed `fleet.quota_cpus` override
481/// wins (terminal, per the §5 override rule); unset detects the real cgroup
482/// via [`crate::quota::fractional_cpu_budget`]. Tests inject values
483/// directly into [`FleetBudget::derive`].
484#[must_use]
485pub fn detected_quota_cpus(cfg: &FleetConfig) -> f64 {
486 cfg.quota_cpus
487 .filter(|q| *q >= 1.0)
488 .unwrap_or_else(crate::quota::fractional_cpu_budget)
489}
490
491#[cfg(test)]
492#[expect(clippy::expect_used)]
493mod tests {
494 use super::*;
495
496 fn overrides() -> BudgetOverrides {
497 BudgetOverrides::default()
498 }
499
500 #[test]
501 fn the_8_core_quota_table_sums_exactly_against_the_floor() {
502 let b = FleetBudget::derive(8.0, &overrides()).expect("8-core quota hostable");
503 assert_eq!(b.quota_floor, 8);
504 // SUM invariant: H + A + R + M + S = Q (design doc §5).
505 assert_eq!(b.declared_sum(), b.quota_floor);
506 assert_eq!(b.reserve_cpus, 1);
507 assert_eq!(b.resolve_cpus, 1);
508 assert_eq!(b.merge_cpus, 1);
509 // The rule column: A = max(1, floor((Q-H)/4)) = 1, leaving S = 4.
510 // (The doc TABLE's A=2/S=3 split is reachable via the terminal
511 // DEGENBOT_IO_WORKERS override — see the module discrepancy note.)
512 assert_eq!(b.ambient_cpus, 1);
513 assert_eq!(b.solver_cpus, 4);
514 // Pins are STRUCTURAL: one seat per LPT bin = floor(Q) - the solve
515 // headroom (allocation FLOORS the quota; cpu_budget's worker-
516 // existence detection ceils it — the cross-authority property in this file
517 // pins that split) — not the 2:1 parked-wait over-subscription
518 // (P6YXA6 sizing note).
519 assert_eq!(b.solver_pin_count, 6);
520 }
521
522 #[test]
523 fn the_worked_8_core_table_split_is_reachable_via_the_terminal_override() {
524 // DEGENBOT_IO_WORKERS=2 (the current deployment) reproduces the
525 // doc §5 worked table exactly: A=2, S=3, sum 8.
526 let b = FleetBudget::derive(
527 8.0,
528 &BudgetOverrides {
529 ambient_io_workers: Some(2),
530 ..overrides()
531 },
532 )
533 .expect("hostable");
534 assert_eq!(b.ambient_cpus, 2);
535 assert_eq!(b.solver_cpus, 3);
536 assert_eq!(b.solver_pin_count, 6);
537 assert_eq!(b.declared_sum(), 8);
538 }
539
540 #[test]
541 fn a_quota_below_the_pinned_role_floor_fails_fast() {
542 // 4.5-core quota: floor is 4 but H+A+R+M+2 = 6 > 4 — the two pinned
543 // latency roles cannot be hosted there (the integer sum floors; the
544 // fractional remainder is never part of the sum check).
545 let err = FleetBudget::derive(4.5, &overrides()).expect_err("too small");
546 assert!(matches!(
547 err,
548 BudgetError::QuotaTooSmallForPinnedRoles { .. }
549 ));
550 }
551
552 /// FF-T5 addendum (452GZC): the typed refusal family carries a closed,
553 /// greppable NAME vocabulary — the runtime status names the family it
554 /// fell from (never a free-text parse); the wording stays the operator
555 /// sentence.
556 #[test]
557 fn the_refusal_family_names_its_typed_variants() {
558 let cases: [(BudgetError, &str); 5] = [
559 (
560 BudgetError::Oversubscribed {
561 quota: 4.0,
562 floor: 4,
563 declared: 7,
564 },
565 "Oversubscribed",
566 ),
567 (
568 BudgetError::QuotaTooSmallForPinnedRoles {
569 quota: 4.0,
570 required: 6,
571 },
572 "QuotaTooSmallForPinnedRoles",
573 ),
574 (
575 BudgetError::TooFewSolverCpus {
576 solver: 1,
577 min: MIN_SOLVER_CPUS,
578 },
579 "TooFewSolverCpus",
580 ),
581 (BudgetError::BelowHostFloor { quota: 1.5 }, "BelowHostFloor"),
582 (
583 BudgetError::IoWorkersOutOfBounds {
584 requested: 0,
585 binding: "pinned",
586 },
587 "IoWorkersOutOfBounds",
588 ),
589 ];
590 for (err, expected) in cases {
591 assert_eq!(err.name(), expected, "{expected} names itself");
592 // The Display sentence never doubles as the family name — the
593 // tier_refused string composes them ("Name: message").
594 assert!(
595 !err.to_string().contains(expected),
596 "the wording stays the sentence; the name rides explicitly: {err}"
597 );
598 }
599 }
600
601 #[test]
602 fn fractional_quota_banks_the_remainder_outside_the_integer_sum() {
603 // 6.5-core quota: floor 6, base (H1+A1+R1+M1) = 4, S = 2; the 0.5
604 // remainder banks outside the sum check (I/O-dominant spend only).
605 let b = FleetBudget::derive(6.5, &overrides()).expect("hostable");
606 assert_eq!(b.quota_floor, 6);
607 assert_eq!(b.declared_sum(), 6);
608 assert!((b.fractional_remainder - 0.5).abs() < 1e-9);
609 assert_eq!(b.solver_cpus, 2);
610 assert_eq!(b.solver_pin_count, 4);
611 }
612
613 // ---- LW-T4 (Seam A+G1): boot authority — quota-derived sizing, no ambient fallback
614
615 /// The documented floor-allocation formula is the SOLE seat authority:
616 /// `pins == floor(Q) − solve_headroom` (floored at 1) with the shares
617 /// and the fractional remainder derived from the SAME quota — over the
618 /// matrix quota × headroom, under INJECTED quotas only (no detector may
619 /// run in tests): the test passes host-hardware-independently (cgroup-
620 /// limited CI and a bare host alike). A quota below the capacity floor
621 /// refuses TYPED, naming the floor and the serial/sequential fallback
622 /// decision point — never a silently narrower lane.
623 #[test]
624 fn seat_count_is_quota_headroom_derived_under_the_documented_formula_only() {
625 for quota in [1.0_f64, 1.5, 4.0, 8.0, 24.0] {
626 for headroom in [1_usize, 2] {
627 let overrides = BudgetOverrides {
628 solve_headroom: Some(headroom),
629 ..overrides()
630 };
631 // Test-quotas are positive reals (1.0..24.0): floor() is exact
632 // and the cast cannot lose sign or magnitude here.
633 #[expect(
634 clippy::cast_possible_truncation,
635 reason = "quota floors are exact for the injected positive reals"
636 )]
637 #[expect(
638 clippy::cast_sign_loss,
639 reason = "the injected quotas are all strictly positive"
640 )]
641 let q_floor = quota.max(1.0).floor() as u64;
642 // Boot OR refuse, never a silent mis-size: the pinned-role
643 // floor (H+A+R+M+2) and the headroom+1 floor both refuse
644 // TYPED, naming the floor and the serial fallback point.
645 let b = match FleetBudget::derive(quota, &overrides) {
646 Ok(b) => b,
647 Err(err @ BudgetError::QuotaTooSmallForPinnedRoles { .. }) => {
648 let msg = err.to_string();
649 assert!(
650 msg.contains(&format!("{q_floor} numerically"))
651 || msg.contains("pinned-role floor"),
652 "the capacity-floor error must name the floor: {msg}"
653 );
654 assert!(
655 msg.contains("serial/sequential"),
656 "the typed floor error must name the serial/sequential fallback decision point (reth has_enough_parallelism): {msg}"
657 );
658 continue;
659 }
660 Err(other) => {
661 // Test-fixture tripwire: an unexpected refusal here is
662 // the failure — panic IS the assertion.
663 #[expect(
664 clippy::panic,
665 reason = "an unexpected typed refusal is the fixture's failure mode"
666 )]
667 {
668 panic!("unexpected typed refusal at quota {quota}: {other}")
669 }
670 }
671 };
672 assert!(
673 q_floor > u64::try_from(headroom).unwrap_or(0),
674 "a quota below headroom+1 must have taken the typed floor arm above"
675 );
676 assert_eq!(
677 b.solver_pin_count as u64,
678 q_floor
679 .saturating_sub(u64::try_from(headroom).unwrap_or(0))
680 .max(1),
681 "seat count must be f(quota, headroom): quota {quota}, headroom {headroom}"
682 );
683 assert_eq!(
684 b.declared_sum(),
685 q_floor,
686 "the integer share sum must be exactly floor(Q)"
687 );
688 // Exact-by-construction: the injected quotas are binary-exact
689 // (1.0/1.5/4.0/8.0/24.0) and floor(Q) is an integer, so the
690 // subtraction loses nothing — a strict comparison is exact.
691 {
692 // The fills are an exact small integer; q_floor < 2^24 for
693 // every injected quota, so the u32 conversion cannot lose.
694 let q = u32::try_from(q_floor).expect("injected quotas are < 2^24");
695 let quoted = quota - f64::from(q);
696 assert!(
697 (b.fractional_remainder - quoted).abs() < 1e-12,
698 "the fractional remainder is Q − floor(Q), spendable only by I/O: {} vs {quoted}",
699 b.fractional_remainder
700 );
701 }
702 }
703 }
704 }
705
706 /// LW-T4 (reth research lesson-4): the boot authority never falls back to
707 /// ambient host state — `available_parallelism` must not appear in the
708 /// sizing paths (the DETECTION ceilings live in degenbot-core / quota.rs;
709 /// the seat authority floors purely from the injected quota). This is a
710 /// DELIBERATE regular-expression tripwire mirroring the pyo3-free
711 /// dependency assertion — replace it with a behavioral assertion if
712 /// budget.rs ever sizes off a parameterizable source instead of the
713 /// injected quota.
714 #[test]
715 fn seat_sizing_never_reads_ambient_parallelism() {
716 let src = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/src/budget.rs"))
717 .expect("crate source readable");
718 // Scan ONLY the lib code: the tests module legitimately names the
719 // identifier (this assertion's own text).
720 let lib = src
721 .split("#[cfg(test)]")
722 .next()
723 .expect("the lib segment always exists");
724 assert!(
725 !lib.contains("available_parallelism"),
726 "the boot authority must never size lanes off ambient parallelism (reth lesson-4)"
727 );
728 }
729
730 /// LW-T4: oversubscription is TYPED at boot (derive returns the error —
731 /// `abort_executor` is reserved for RUNTIME strand abandonment only)
732 /// and the message names BOTH numbers (declared sum AND quota floor).
733 #[test]
734 fn oversubscription_is_typed_at_boot_and_names_both_numbers() {
735 let err = FleetBudget::derive(
736 8.0,
737 &BudgetOverrides {
738 ambient_io_workers: Some(2),
739 solver_cpus: Some(4),
740 ..overrides()
741 },
742 )
743 .expect_err("H1+A2+R1+M1+S4 = 9 > 8");
744 assert!(matches!(err, BudgetError::Oversubscribed { .. }));
745 let msg = err.to_string();
746 assert!(
747 msg.contains('9'),
748 "the message must name the declared sum: {msg}"
749 );
750 assert!(
751 msg.contains('8'),
752 "the message must name the quota floor: {msg}"
753 );
754 }
755
756 /// LW-T4: quota below the capacity floor is a TYPED refusal naming the
757 /// floor and pointing at the serial/sequential fallback decision point
758 /// (reth `has_enough_parallelism()` lesson: lane capability is explicit,
759 /// never silently narrower). The fallback arm itself is LW-T7's.
760 #[test]
761 fn quota_below_the_capacity_floor_refuses_typed_naming_the_fallback_point() {
762 let err = FleetBudget::derive(2.0, &overrides())
763 .expect_err("quota 2.0 < the pinned-role floor must refuse typed");
764 assert!(matches!(
765 err,
766 BudgetError::QuotaTooSmallForPinnedRoles { .. }
767 ));
768 let msg = err.to_string();
769 assert!(
770 msg.contains("serial"),
771 "the typed floor error must name the serial/sequential fallback decision point: {msg}"
772 );
773 }
774
775 #[test]
776 fn oversubscription_by_overrides_fails_at_boot() {
777 let err = FleetBudget::derive(
778 8.0,
779 &BudgetOverrides {
780 ambient_io_workers: Some(2),
781 solver_cpus: Some(4),
782 ..overrides()
783 },
784 )
785 .expect_err("H1+A2+R1+M1+S4 = 9 > 8");
786 assert!(matches!(err, BudgetError::Oversubscribed { .. }));
787 }
788
789 #[test]
790 fn a_sub_minimum_solver_share_is_refused() {
791 let err = FleetBudget::derive(
792 8.0,
793 &BudgetOverrides {
794 solver_cpus: Some(1),
795 ..overrides()
796 },
797 )
798 .expect_err("S=1 < the 2-core minimum");
799 assert!(matches!(err, BudgetError::TooFewSolverCpus { .. }));
800 }
801
802 #[test]
803 fn resize_redeclares_shares_and_keeps_the_sum_invariant() {
804 let big = FleetBudget::derive(8.0, &overrides()).expect("8");
805 let small = big.resize(6.5, &overrides()).expect("6.5 hostable");
806 assert_eq!(small.declared_sum(), small.quota_floor);
807 // Pin-count change flags the T9 rebalance (never a mid-cycle re-key).
808 assert!(big.pins_require_rekey(&small));
809 // Idempotent re-declaration under an unchanged quota.
810 let same = big.resize(8.0, &overrides()).expect("8 again");
811 assert!(!big.pins_require_rekey(&same));
812 }
813
814 #[test]
815 fn the_registration_intake_station_is_duty_counted_like_sim() {
816 // PoolStateUpdater slots default to the ADR-042 F2 station size and
817 // stay OUTSIDE the declared integer sum (I/O-dominant billing —
818 // exactly the SimDriver model).
819 let b = FleetBudget::derive(8.0, &overrides()).expect("hostable");
820 assert_eq!(b.pool_state_updater_slots, DEFAULT_POOL_STATE_UPDATER_SLOTS);
821 assert_eq!(b.declared_sum(), b.quota_floor);
822 // The terminal override wins (same terminal rule as the rest).
823 let b2 = FleetBudget::derive(
824 8.0,
825 &BudgetOverrides {
826 pool_state_updater_slots: Some(6),
827 ..overrides()
828 },
829 )
830 .expect("hostable");
831 assert_eq!(b2.pool_state_updater_slots, 6);
832 assert_eq!(b2.declared_sum(), b2.quota_floor);
833 }
834
835 #[test]
836 fn the_pool_state_updater_override_projects_from_the_typed_config() {
837 let mut cfg = degenbot_config::BotConfig::default();
838 cfg.fleet.pool_state_updater_slots = Some(6);
839 let o = BudgetOverrides::from_config(&cfg);
840 assert_eq!(o.pool_state_updater_slots, Some(6));
841 let b = FleetBudget::derive(8.0, &o).expect("hostable");
842 assert_eq!(b.pool_state_updater_slots, 6);
843 }
844
845 #[test]
846 fn allocation_table_covers_every_consumer_row() {
847 let b = FleetBudget::derive(8.0, &overrides()).expect("8");
848 let consumers: Vec<&str> = b
849 .allocation_table()
850 .into_iter()
851 .map(|s| s.consumer)
852 .collect();
853 for want in ["reserve", "ambient_io", "resolve", "merge", "solver"] {
854 assert!(consumers.contains(&want), "missing {want}");
855 }
856 }
857
858 #[test]
859 fn typed_config_projection_carries_the_override_fields() {
860 let mut cfg = degenbot_config::BotConfig::default();
861 cfg.fleet.solver_cpus = Some(3);
862 cfg.runtime.io_workers = Some(2);
863 cfg.fleet.sim_slot_cap = Some(6);
864 let o = BudgetOverrides::from_config(&cfg);
865 assert_eq!(o.solver_cpus, Some(3));
866 assert_eq!(o.ambient_io_workers, Some(2));
867 assert_eq!(o.sim_slot_cap, Some(6));
868 let b = FleetBudget::derive(8.0, &o).expect("hostable");
869 assert_eq!(b.solver_cpus, 3);
870 assert_eq!(b.sim_slot_cap, 6);
871 }
872
873 /// the `solve.solve_headroom` typed key reaches the budget
874 /// derive end-to-end — through the LOADER route (not just a hand-built
875 /// struct) — while the default stays `None` (the documented constant
876 /// rules below).
877 #[test]
878 fn the_solve_headroom_override_projects_from_the_typed_config() {
879 let loaded = degenbot_config::BotConfigLoader::new()
880 .without_env()
881 .with_cli("solve.solve_headroom", "2")
882 .load()
883 .expect("the headroom override must load from the explicit CLI layer");
884 assert_eq!(loaded.config.solve.solve_headroom, Some(2));
885 let o = BudgetOverrides::from_config(&loaded.config);
886 assert_eq!(o.solve_headroom, Some(2));
887 let b = FleetBudget::derive(8.0, &o).expect("hostable");
888 // The documented formula: pins = floor(Q) - H_s (8 - 2 = 6).
889 assert_eq!(b.solver_pin_count, 6);
890 assert_eq!(b.declared_sum(), b.quota_floor);
891 // The default config projects None — the documented constant rules.
892 assert_eq!(
893 BudgetOverrides::from_config(°enbot_config::BotConfig::default()).solve_headroom,
894 None
895 );
896 }
897
898 /// GAXX2Z helper: the shared ambient formula A = max(1, (floor(Q)-H)/4).
899 fn ambient_formula(floor: u64, reserve: u64) -> u64 {
900 ((floor.saturating_sub(reserve)) / 4).max(1)
901 }
902
903 /// GAXX2Z helper: the structural pin formula floor(Q) - headroom, >= 1.
904 fn pin_formula(floor: u64, headroom: usize) -> usize {
905 usize::try_from(floor)
906 .unwrap_or(usize::MAX)
907 .saturating_sub(headroom)
908 .max(1)
909 }
910
911 /// GAXX2Z helper: the pinned arm's sum-checked invariants.
912 #[expect(
913 clippy::cast_precision_loss,
914 reason = "test quotas and small share sums are exact in f64"
915 )]
916 fn assert_pinned_projection(
917 quota: f64,
918 floor: u64,
919 ov: &BudgetOverrides,
920 reserve: u64,
921 ambient: u64,
922 pins: usize,
923 ) {
924 let base = reserve + ambient + 2;
925 match FleetBudget::project(quota, ov, BudgetMode::Pinned) {
926 Ok(b) => {
927 assert_eq!(b.reserve_cpus, reserve, "pinned reserve H");
928 assert_eq!(
929 b.ambient_cpus, ambient,
930 "pinned ambient A = max(1, (Q-H)/4)"
931 );
932 assert_eq!(
933 b.solver_cpus,
934 ov.solver_cpus.unwrap_or(floor - base),
935 "pinned solver S = floor(Q) - H - A - R - M"
936 );
937 assert_eq!(
938 b.solver_pin_count, pins,
939 "pinned pins = floor(Q) - headroom"
940 );
941 assert!(
942 b.declared_sum() <= floor,
943 "pinned sum-check never oversubscribes floor(quota)"
944 );
945 if ov.solver_cpus.is_none() {
946 assert_eq!(b.declared_sum(), floor, "pinned default fills floor(quota)");
947 }
948 assert!(
949 (b.fractional_remainder - (quota - b.declared_sum() as f64)).abs() < 1e-9,
950 "pinned remainder banks Q - declared_sum"
951 );
952 }
953 Err(refusal) => assert!(
954 matches!(
955 refusal,
956 BudgetError::QuotaTooSmallForPinnedRoles { .. }
957 | BudgetError::Oversubscribed { .. }
958 | BudgetError::TooFewSolverCpus { .. }
959 ),
960 "pinned refusal is a typed budget error, got {refusal:?}"
961 ),
962 }
963 }
964
965 /// GAXX2Z helper: the marked arm's total pinned arithmetic.
966 fn assert_marked_projection(
967 quota: f64,
968 floor: u64,
969 ov: &BudgetOverrides,
970 reserve: u64,
971 ambient: u64,
972 pins: usize,
973 ) {
974 let base = reserve + ambient + 2;
975 let b = FleetBudget::project(quota, ov, BudgetMode::PinnedMarked).expect("marked total");
976 assert_eq!(b.reserve_cpus, reserve, "marked reserve H");
977 assert_eq!(
978 b.ambient_cpus, ambient,
979 "marked ambient A = max(1, (Q-H)/4)"
980 );
981 assert_eq!(
982 b.solver_cpus,
983 ov.solver_cpus
984 .unwrap_or_else(|| floor.saturating_sub(base).max(MIN_SOLVER_CPUS)),
985 "marked solver S = max(floor(Q) - base, MIN)"
986 );
987 assert_eq!(
988 b.solver_pin_count, pins,
989 "marked pins = floor(Q) - headroom"
990 );
991 assert!(
992 b.fractional_remainder >= 0.0,
993 "marked remainder clamps at zero"
994 );
995 }
996
997 /// GAXX2Z helper: the serial arm's one-lane / one-seat topology.
998 #[expect(
999 clippy::cast_precision_loss,
1000 reason = "the host floor is a tiny core count, exact in f64"
1001 )]
1002 fn assert_serial_projection(quota: f64, ov: &BudgetOverrides, reserve: u64) {
1003 let b = FleetBudget::project(quota, ov, BudgetMode::Serial).expect("serial total");
1004 assert_eq!(b.reserve_cpus, reserve, "serial reserve H");
1005 assert_eq!(
1006 b.ambient_cpus, 1,
1007 "serial owns exactly one ambient I/O lane"
1008 );
1009 assert_eq!(
1010 b.solver_cpus, MIN_SOLVER_CPUS,
1011 "serial logical 2-core solve"
1012 );
1013 assert_eq!(b.solver_pin_count, 1, "serial-0: exactly one solve seat");
1014 assert!(
1015 (b.fractional_remainder - (quota - HOST_FLOOR_CORES as f64).max(0.0)).abs() < 1e-9,
1016 "serial remainder is Q - the 2-core host floor"
1017 );
1018 }
1019
1020 /// ONE projection owner. All three tier projections are produced
1021 /// by `FleetBudget::project(mode)` from the SAME shared formulas; this
1022 /// pin walks representative quotas and override shapes and asserts each
1023 /// mode's invariants against the derive formulas (the sum-check vs
1024 /// floor(quota), the reserve/ambient/solver/pin relationships) — not
1025 /// against the function's own output.
1026 #[test]
1027 #[expect(
1028 clippy::cast_possible_truncation,
1029 reason = "quota floors are small positive values (core counts)"
1030 )]
1031 #[expect(
1032 clippy::cast_sign_loss,
1033 reason = "the test quota is floored at 1.0 before the cast"
1034 )]
1035 fn every_projection_mode_shares_the_derive_formulas() {
1036 let cases: [BudgetOverrides; 3] = [
1037 BudgetOverrides::default(),
1038 BudgetOverrides {
1039 ambient_io_workers: Some(2),
1040 ..BudgetOverrides::default()
1041 },
1042 BudgetOverrides {
1043 reserve_cpus: Some(2),
1044 solver_cpus: Some(3),
1045 ..BudgetOverrides::default()
1046 },
1047 ];
1048 for quota in [2.0_f64, 2.5, 4.0, 5.99, 6.0, 6.5, 8.0, 24.0, 33.25] {
1049 let floor = quota.max(1.0).floor() as u64;
1050 for ov in &cases {
1051 let reserve = ov.reserve_cpus.unwrap_or(DEFAULT_RESERVE_CPUS);
1052 let ambient = ov
1053 .ambient_io_workers
1054 .unwrap_or_else(|| ambient_formula(floor, reserve));
1055 let headroom = ov
1056 .solve_headroom
1057 .unwrap_or(degenbot_core::cpu_budget::DEFAULT_SOLVE_HEADROOM);
1058 let pins = pin_formula(floor, headroom);
1059 assert_pinned_projection(quota, floor, ov, reserve, ambient, pins);
1060 assert_marked_projection(quota, floor, ov, reserve, ambient, pins);
1061 assert_serial_projection(quota, ov, reserve);
1062 }
1063 }
1064 }
1065
1066 mod cross_authority {
1067 //! The settled contract between the two CPU
1068 //! sizing authorities over ARBITRARY quota shapes. Fleet allocation
1069 //! floors: `seats = max(1, floor(Q) - headroom)`. `cpu_budget`
1070 //! worker-existence ceils: `solve = max(1, min(ceil(cgroup Q),
1071 //! affinity) - headroom)`. Consequences (see the module-note
1072 //! addendum above): agreement iff the quota is integer AND affinity
1073 //! covers it; a fractional quota under affinity >= ceil(Q) leaves
1074 //! legacy-stance solve bins exactly ONE above the fleet seats.
1075 use super::*;
1076 use proptest::prelude::*;
1077
1078 prop_compose! {
1079 fn quota_shape()(base in 1u64..=512u64, half in 0u8..2u8, affinity in 1u64..=1024u64)
1080 -> (u64, u8, u64) {
1081 (base, half, affinity)
1082 }
1083 }
1084
1085 proptest! {
1086 #![proptest_config(proptest::test_runner::Config::with_cases(512))]
1087 #[test]
1088 #[expect(
1089 clippy::cast_precision_loss,
1090 reason = "quota units (1e6 scale, <= 5e8) are exact in f64"
1091 )]
1092 fn pins_and_solve_workers_follow_the_documented_floor_ceil_split(
1093 (base, half, affinity) in quota_shape(),
1094 ) {
1095 // cgroup encodes the quota at 1_000_000-unit periods; a
1096 // half-step quota is a fractional f64 core count.
1097 let units = base * 1_000_000 + u64::from(half) * 500_000;
1098 let floor_q = units / 1_000_000;
1099 let fractional = units % 1_000_000 != 0;
1100 // effective_budget_from_with_roots: ceil the cgroup quota,
1101 // then min with affinity (the budget floored at 1).
1102 let budget = units.div_ceil(1_000_000).min(affinity).max(1);
1103 let budget_usize = usize::try_from(budget).unwrap_or(usize::MAX);
1104 let solve = degenbot_core::cpu_budget::solve_worker_count_from(
1105 None, None, budget_usize,
1106 );
1107 let q = (units as f64) / 1_000_000.0;
1108
1109 if floor_q < 6 {
1110 // Below the pinned-role floor (H+A+R+M+2) nothing hosts.
1111 // (bound first: prop_assert stringifies its expression,
1112 // and `{ .. }` from `matches!` would break the format
1113 // string)
1114 let refused = matches!(
1115 FleetBudget::derive(q, &BudgetOverrides::default()),
1116 Err(BudgetError::QuotaTooSmallForPinnedRoles { .. })
1117 );
1118 prop_assert!(refused);
1119 } else {
1120 // derive fails only on over-subscription or a
1121 // too-small quota; our defaults cannot oversubscribe
1122 // (floor_q >= 6 => base + MIN_SOLVER_CPUS <= floor).
1123 let Ok(b) = FleetBudget::derive(q, &BudgetOverrides::default()) else {
1124 return Err(TestCaseError::fail(format!(
1125 "hostable shape refused: q = {q}"
1126 )));
1127 };
1128 // The P6YXA6 formula, as written.
1129 prop_assert_eq!(
1130 b.solver_pin_count,
1131 usize::try_from((floor_q - 2).max(1)).unwrap_or(usize::MAX)
1132 );
1133 if !fractional && affinity >= floor_q {
1134 // Integer quota, adequate affinity: agreement.
1135 prop_assert_eq!(b.solver_pin_count, solve);
1136 } else if fractional && budget == floor_q + 1 {
1137 // Fractional quota under affinity >= ceil(Q):
1138 // worker-existence buys exactly one more core than
1139 // allocation spends; pins never bank the remainder.
1140 prop_assert_eq!(solve, b.solver_pin_count + 1);
1141 } else if !fractional && affinity < floor_q {
1142 // Affinity-take-over: the (smaller) budget rules
1143 // the solve bins; the seat count never shrinks.
1144 prop_assert!(solve <= b.solver_pin_count);
1145 } else if fractional && budget <= floor_q {
1146 // Affinity clamped below the ceiling.
1147 prop_assert!(solve <= b.solver_pin_count);
1148 }
1149 let _ = fractional; // documented above
1150 }
1151 }
1152 }
1153 }
1154
1155 mod derivation {
1156 //! CVURM7 : `derive` over ARBITRARY quotas AND
1157 //! overrides. Total over the input space: a typed `Ok` holding the
1158 //! invariants (integer shares sum to at most the floor; the
1159 //! fractional remainder banks exactly `Q - declared_sum`; seats
1160 //! follow the structural formula) or a typed `BudgetError`
1161 //! classifying the refusal. Never a panic, never a silently mis-sized
1162 //! budget. NOTE: with a terminal `solver_cpus` override BELOW the
1163 //! recorded leftover the banked remainder legitimately exceeds 1
1164 //! core (the sum check bounds over-subscription only; under-declared
1165 //! shares bank for I/O-dominant spend) — so no `< 1` bound here.
1166 use super::*;
1167 use proptest::prelude::*;
1168
1169 fn overrides_shape() -> impl Strategy<Value = BudgetOverrides> {
1170 (
1171 1u64..=64u64,
1172 0u64..=16u64,
1173 0u64..=64u64,
1174 0usize..=16usize,
1175 0usize..=16usize,
1176 )
1177 .prop_map(|(h, a, s, sim, psu)| BudgetOverrides {
1178 reserve_cpus: Some(h),
1179 ambient_io_workers: Some(a),
1180 solver_cpus: Some(s),
1181 sim_slot_cap: Some(sim),
1182 pool_state_updater_slots: Some(psu),
1183 solve_headroom: None,
1184 })
1185 }
1186
1187 proptest! {
1188 #![proptest_config(proptest::test_runner::Config::with_cases(512))]
1189 #[test]
1190 #[expect(
1191 clippy::cast_precision_loss,
1192 reason = "quota units (1e6 scale) and share sums (<= 640) are exact in f64"
1193 )]
1194 fn derive_is_total_and_typed_over_the_whole_input_space(
1195 quota_units in 1u64..=64_000_000u64,
1196 ov in overrides_shape(),
1197 ) {
1198 let q = (quota_units as f64) / 1_000_000.0;
1199 let floor_q = quota_units / 1_000_000;
1200 match FleetBudget::derive(q, &ov) {
1201 Ok(b) => {
1202 // Integer shares sum against (never over) the floor.
1203 prop_assert!(b.declared_sum() <= b.quota_floor);
1204 prop_assert_eq!(b.quota_floor, floor_q);
1205 // The fractional remainder is banked exactly,
1206 // never negative and never beyond the quota.
1207 let sum_f = b.declared_sum() as f64;
1208 prop_assert!((b.fractional_remainder - (q - sum_f)).abs() < 1e-9);
1209 prop_assert!(b.fractional_remainder >= 0.0 && b.fractional_remainder <= q);
1210 // Seats are structural: overrides never move them.
1211 prop_assert_eq!(
1212 b.solver_pin_count,
1213 usize::try_from(floor_q).unwrap_or(usize::MAX)
1214 .saturating_sub(degenbot_core::cpu_budget::DEFAULT_SOLVE_HEADROOM)
1215 .max(1)
1216 );
1217 }
1218 Err(
1219 BudgetError::QuotaTooSmallForPinnedRoles { .. }
1220 | BudgetError::Oversubscribed { .. }
1221 | BudgetError::TooFewSolverCpus { .. }
1222 // FF-T2: the plan-tier refusal classes.
1223 // The derive itself never produces them (the plan
1224 // does); listed so the match stays exhaustive and
1225 // a future arm is still a compile error.
1226 | BudgetError::BelowHostFloor { .. }
1227 | BudgetError::IoWorkersOutOfBounds { .. },
1228 ) => {
1229 // Exhaustive typed refusal classes; the match arms
1230 // above make any NEW error variant a compile error.
1231 }
1232 }
1233 }
1234 }
1235 }
1236}