#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CordonClass {
Never,
SimPool,
Deferrable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum WorkerRole {
Solver,
SimDriver,
Resolve,
Merge,
PoolStateUpdater,
Registrar,
Verifier,
Submitter,
}
pub const ALL_ROLES: [WorkerRole; 8] = [
WorkerRole::Solver,
WorkerRole::SimDriver,
WorkerRole::Resolve,
WorkerRole::Merge,
WorkerRole::PoolStateUpdater,
WorkerRole::Registrar,
WorkerRole::Verifier,
WorkerRole::Submitter,
];
pub const V1_ACTIVE_ROLES: [WorkerRole; 5] = [
WorkerRole::Solver,
WorkerRole::SimDriver,
WorkerRole::Resolve,
WorkerRole::Merge,
WorkerRole::PoolStateUpdater,
];
impl WorkerRole {
#[must_use]
pub const fn cordon_class(self) -> CordonClass {
match self {
Self::SimDriver => CordonClass::SimPool,
Self::PoolStateUpdater | Self::Registrar | Self::Verifier => CordonClass::Deferrable,
Self::Solver | Self::Resolve | Self::Merge | Self::Submitter => CordonClass::Never,
}
}
#[must_use]
pub const fn v1_active(self) -> bool {
matches!(
self,
Self::Solver | Self::SimDriver | Self::Resolve | Self::Merge | Self::PoolStateUpdater
)
}
#[must_use]
pub const fn is_pinnable(self) -> bool {
matches!(self, Self::Solver | Self::Merge)
}
#[must_use]
pub const fn census_resource(self) -> &'static str {
match self {
Self::Solver => "fleet_solver_slots",
Self::SimDriver => "fleet_simdriver_slots",
Self::Resolve => "fleet_resolve_slots",
Self::Merge => "fleet_merge_slots",
Self::PoolStateUpdater => "fleet_pool_state_updater_slots",
Self::Registrar => "fleet_registrar_slots",
Self::Verifier => "fleet_verifier_slots",
Self::Submitter => "fleet_submitter_slots",
}
}
#[must_use]
pub const fn thread_name(self) -> &'static str {
match self {
Self::Solver => "work-fleet-solver-{n}",
Self::SimDriver => "work-fleet-sim-{n}",
Self::Resolve => "work-fleet-resolve-{n}",
Self::Merge => "work-fleet-merge-{n}",
Self::PoolStateUpdater => "work-fleet-poolupd-{n}",
Self::Registrar => "work-fleet-registrar-{n}",
Self::Verifier => "work-fleet-verifier-{n}",
Self::Submitter => "work-fleet-submit-{n}",
}
}
#[must_use]
pub const fn census_kind(self) -> &'static str {
match self {
Self::Solver => "fleet worker slot: LPT-bin pin",
Self::SimDriver => "fleet worker slot: pooled sim driver",
Self::Resolve => "fleet worker slot: resolve chunk",
Self::Merge => "fleet worker slot: merge sidecar pin",
Self::PoolStateUpdater => "fleet worker slot: pool-state updater",
Self::Registrar => "fleet worker slot: registrar",
Self::Verifier => "fleet worker slot: verifier",
Self::Submitter => "fleet worker slot: submitter",
}
}
#[must_use]
pub const fn census_sizing(self) -> &'static str {
match self {
Self::Solver => "pins = one seat per LPT bin (floor(Q) minus the solve headroom, the structural bin count; walk admission stays the Solver share)",
Self::SimDriver => "slot cap = 4 (today's SimSlots cap), duty-counted; fractional-quota remainder spendable here",
Self::Resolve => "fixed v1 (1 slot, 12.4 ms/cycle measured)",
Self::Merge => "exactly one sidecar",
Self::PoolStateUpdater => {
"slot cap = fleet.pool_state_updater_slots (default 4), duty-counted; \
fractional-quota remainder spendable here; Deferrable cordon class"
}
Self::Registrar | Self::Verifier | Self::Submitter => "declared, unhosted in v1",
}
}
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Solver => "solver",
Self::SimDriver => "sim_driver",
Self::Resolve => "resolve",
Self::Merge => "merge",
Self::PoolStateUpdater => "pool_state_updater",
Self::Registrar => "registrar",
Self::Verifier => "verifier",
Self::Submitter => "submitter",
}
}
#[must_use]
pub const fn index_in_all_roles(self) -> Option<u8> {
match self {
Self::Solver => Some(0),
Self::SimDriver => Some(1),
Self::Resolve => Some(2),
Self::Merge => Some(3),
Self::PoolStateUpdater => Some(4),
Self::Registrar => Some(5),
Self::Verifier => Some(6),
Self::Submitter => Some(7),
}
}
}
#[cfg(test)]
#[expect(clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn all_roles_is_the_sized_8_role_table() {
assert_eq!(ALL_ROLES.len(), 8, "ADR-042 Q2: exactly 8 declared roles");
assert_eq!(
ALL_ROLES,
[
WorkerRole::Solver,
WorkerRole::SimDriver,
WorkerRole::Resolve,
WorkerRole::Merge,
WorkerRole::PoolStateUpdater,
WorkerRole::Registrar,
WorkerRole::Verifier,
WorkerRole::Submitter,
],
"ALL_ROLES must list every variant exactly once, positions are the conformance script"
);
}
#[test]
fn u8_index_round_trips_through_the_table() {
for (i, role) in ALL_ROLES.iter().enumerate() {
let idx = usize::from(role.index_in_all_roles().expect("all roles indexed"));
assert_eq!(idx, i, "index_in_all_roles must match ALL_ROLES position");
assert_eq!(ALL_ROLES[idx], *role, "round trip");
}
}
#[test]
fn v1_active_is_exactly_the_hosted_prefix() {
for (i, role) in ALL_ROLES.iter().enumerate() {
assert_eq!(
role.v1_active(),
i < V1_ACTIVE_ROLES.len(),
"v1-active must be exactly {V1_ACTIVE_ROLES:?}"
);
}
assert_eq!(V1_ACTIVE_ROLES, ALL_ROLES[..V1_ACTIVE_ROLES.len()]);
}
#[test]
fn cordon_classes_match_the_sign_off_table() {
let class = |r: WorkerRole| match r.cordon_class() {
CordonClass::Never => "never",
CordonClass::SimPool => "sim-pool",
CordonClass::Deferrable => "deferrable",
};
assert_eq!(class(WorkerRole::SimDriver), "sim-pool");
for r in [
WorkerRole::PoolStateUpdater,
WorkerRole::Registrar,
WorkerRole::Verifier,
] {
assert_eq!(class(r), "deferrable", "declared background set: {r:?}");
}
for r in [
WorkerRole::Solver,
WorkerRole::Resolve,
WorkerRole::Merge,
WorkerRole::Submitter,
] {
assert_eq!(class(r), "never", "cordon-invariant role: {r:?}");
}
}
#[test]
fn only_solver_and_merge_are_pinnable() {
for role in ALL_ROLES {
assert_eq!(
role.is_pinnable(),
matches!(role, WorkerRole::Solver | WorkerRole::Merge)
);
}
}
#[test]
fn census_and_thread_names_are_distinct_and_greppable() {
let mut resources = Vec::new();
let mut threads = Vec::new();
for role in ALL_ROLES {
resources.push(role.census_resource());
threads.push(role.thread_name());
assert!(role.thread_name().starts_with("work-fleet-"));
assert!(role.census_kind().starts_with("fleet worker slot"));
assert!(!role.census_sizing().is_empty());
}
let n = resources.len();
resources.sort_unstable();
threads.sort_unstable();
resources.dedup();
threads.dedup();
assert_eq!(resources.len(), n, "census resource ids must be unique");
assert_eq!(threads.len(), n, "thread-name patterns must be unique");
}
#[test]
fn gauge_labels_are_snake_case() {
for role in ALL_ROLES {
let label = role.label();
assert!(!label.is_empty());
assert!(label.chars().all(|c| c.is_ascii_lowercase() || c == '_'));
}
}
}