Skip to main content

ScalePolicy

Struct ScalePolicy 

Source
pub struct ScalePolicy {
    pub id: PolicyId,
    pub target: ScaleTarget,
    pub installation_id: u64,
    pub host_id: HostId,
    pub requested_host_label: HostLabel,
    pub cache_policy: CachePolicy,
    /* private fields */
}
Expand description

One target, scaled (or merely watched) by one host.

mode, enabled, state, workspace_policy, and revision are private: each is governed by an invariant that a direct assignment would bypass. id, target, installation_id, host_id, and cache_policy are public because they are either immutable identity or self-validating values.

Fields§

§id: PolicyId§target: ScaleTarget§installation_id: u64§host_id: HostId§requested_host_label: HostLabel

Operator-chosen host identity retained even for MonitorOnly policies.

§cache_policy: CachePolicy

Implementations§

Source§

impl ScalePolicy

Source

pub fn new( id: PolicyId, target: ScaleTarget, installation_id: u64, host_id: HostId, mode: PolicyMode, cache_policy: CachePolicy, ) -> Self

A newly added policy.

D20: add never arms a host. The policy starts Pending with enabled == false, and only an explicit set-scale moves it on. That is true for a policy created with --max-capacity too, which is why this is a property of the constructor rather than of the caller.

This is the add path, not the load path. It resets state to Pending, enabled to false and revision to 0, so calling it on a row read back from storage silently disarms a live policy and resets its concurrency token. Self::from_persisted is the one that reloads a stored policy; it sits directly below this and takes all three.

Source

pub fn new_for_host_label( id: PolicyId, target: ScaleTarget, installation_id: u64, host_id: HostId, requested_host_label: HostLabel, mode: PolicyMode, cache_policy: CachePolicy, ) -> Self

New policy retaining the exact operator-requested host identity.

Source

pub fn from_persisted(fields: PersistedPolicy) -> Result<Self, PolicyError>

Rebuild a stored policy, re-validating D19’s shape.

This is the load path. Unlike Self::new it preserves state, enabled and revision exactly as stored.

§Errors

Any illegal PolicyMode shape, per PolicyMode::from_persisted.

Source

pub fn to_persisted(&self) -> PersistedPolicy

Every stored column of this policy, for b2 to write back.

The exact inverse of Self::from_persisted, so a round trip through storage is expressible without this type exposing mode, enabled, state and revision for writing.

Source

pub const fn mode(&self) -> &PolicyMode

Source

pub const fn state(&self) -> PolicyState

Source

pub const fn enabled(&self) -> bool

Operator intent, independent of state (04-subsystem-contracts.md: “enabled records operator intent; state records observed lifecycle”).

Source

pub const fn revision(&self) -> u64

Optimistic-concurrency token. Every successful mutation below bumps it; b2 rejects a write against a stale value.

Source

pub const fn routing_labels(&self) -> Option<&RoutingLabels>

Source

pub const fn workspace_policy(&self) -> &WorkspacePolicy

D4: this repository’s configured workspace behaviour.

Source

pub fn set_workspace_policy( &mut self, workspace: WorkspacePolicy, ) -> Result<(), PolicyError>

repo set-workspace --mode … (D4).

The refusal of a persistent workspace for an organization target is D7, and it lives here rather than in the command layer because Self::from_persisted has to apply the identical rule to a stored row: one place, one message, one test.

Like every other mutation on this type it bumps the revision, so a2’s optimistic guard rejects a write built from a stale read. It does not check for active attempts — D9’s “a path change is refused while affected attempts are active” needs the uncleaned-attempt count in the same write transaction, which is a2’s fence and not something the domain can see.

§Errors

PolicyError::Workspace wrapping WorkspaceError::PersistentRequiresRepositoryScope for an organization target.

Source

pub const fn min_capacity(&self) -> u16

Source

pub const fn max_capacity(&self) -> Option<NonZeroU16>

Source

pub fn is_owned_by(&self, host_id: HostId) -> bool

Ownership rule 1: a policy’s host_id and its host-scoped routing_labels determine ownership.

Source

pub const fn owns_runners(&self) -> bool

Ownership rule 1, second half: “A MonitorOnly policy owns nothing and can never be the reason a runner starts.”

e1 is required to assert this directly rather than to rely on max_capacity being absent, which is why it is a predicate on the mode and not an arithmetic accident.

Source

pub const fn may_start_runners(&self) -> bool

Whether reconciliation may start a runner for this policy right now.

All three conditions matter: monitor-only owns nothing (D19), a disabled or draining policy takes no new work (03-control-flows.md, flow 5), and a user-requested disable beats demand (precedence rule 4).

Source

pub fn transition_to(&mut self, next: PolicyState) -> Result<(), PolicyError>

Source

pub fn can_activate(&self) -> bool

Whether Self::activate would succeed right now.

Exposed so f2 can implement the idempotent CLI behaviour described on Self::activate without either duplicating the state table or calling and discarding an error.

Source

pub fn can_request_disable(&self) -> bool

Whether Self::request_disable would succeed right now.

Source

pub fn activate(&mut self) -> Result<(), PolicyError>

set-scale --enabled true on a Pending policy (03-control-flows.md, flow 1.6).

Not idempotent, and that is intended. This is a transition operation, not a desired-state one: it reports what the state machine permits and never silently accepts a call the diagram has no edge for. Calling it on an already-Active policy is PolicyError::IllegalTransition, not a no-op.

The idempotent reading — “make this policy enabled, whatever it is now” — is a command-level behaviour, and it belongs to f2 because the answer depends on what set-scale --enabled true should mean for a draining, disabled, repair_required or authentication_failed policy, and each of those is a product decision rather than a domain one. Collapsing them here would make the domain answer them by accident. f2 should branch on Self::can_activate and report the already-satisfied case as success without calling this at all.

§Errors

PolicyError::IllegalTransition when the policy is not Pending.

Source

pub fn request_disable(&mut self) -> Result<PolicyState, PolicyError>

set-scale --enabled false (03-control-flows.md, flow 5.2).

Precedence rule 4: a user-requested disable beats demand. enabled drops immediately — which alone is enough to stop new runners, because Self::may_start_runners reads it — and the observed state moves to Draining, where busy runners are left to finish.

Not idempotent, for the reason given on Self::activate. In particular set-scale --enabled false on a pending policy is IllegalTransition { from: pending, to: draining } rather than a no-op, even though a pending policy is already enabled == false and so is already starting nothing. f2 translates that through Self::can_request_disable: a policy that cannot legally drain and is already not enabled has nothing to do, which is a successful outcome for the command and not an error to print.

§Errors

PolicyError::IllegalTransition when the policy is not Active.

Source

pub fn drain_completed( &mut self, active_attempts: u16, ) -> Result<PolicyState, PolicyError>

Flow 5.3: “When active local runners reach zero … the policy becomes disabled.”

Returns the state after the call, unchanged when runners remain — a draining policy with work in flight is not an error, it is the normal case for the duration of the last job.

§Errors

PolicyError::IllegalTransition when the policy is not Draining.

Source

pub fn authentication_failed(&mut self) -> Result<(), PolicyError>

Any state -> AuthenticationFailed (flow 4.5).

§Errors

Only when already in AuthenticationFailed; re-reporting the same failure is not a transition.

Source

pub fn reauthenticated(&mut self) -> Result<(), PolicyError>

“(recoverable by re-authentication)”.

§Errors

PolicyError::IllegalTransition unless the policy is in AuthenticationFailed.

Source

pub fn repair_required(&mut self) -> Result<(), PolicyError>

Flow 1.4: a local transaction that did not complete.

§Errors

PolicyError::IllegalTransition unless the policy is Pending.

Source

pub fn promote_to_autoscale( &mut self, routing_labels: RoutingLabels, min_capacity: u16, max_capacity: NonZeroU16, ) -> Result<(), PolicyError>

D19 promotion: set-capacity on a monitor-only policy.

The routing label is derived at this point and not before, because a monitor-only policy reserves none (f2).

§Errors

PolicyError::AlreadyAutoscale when the policy already autoscales, or PolicyError::InvertedCapacityRange.

Source

pub fn set_max_capacity( &mut self, max_capacity: NonZeroU16, ) -> Result<(), PolicyError>

repo set-capacity / org set-capacity on a policy that already autoscales.

§Errors

PolicyError::NotAutoscale when the policy is monitor-only (use Self::promote_to_autoscale), or PolicyError::InvertedCapacityRange.

Source

pub fn add_routing_label(&mut self, label: Label) -> Result<bool, PolicyError>

Add an optional descriptive routing label.

§Errors

PolicyError::NotAutoscale for a monitor-only policy, which owns no label set to add to. This once reported a MonitorOnlyWithRoutingLabels variant, which said that a stored row had an illegal shape — a different claim from “this operation needs an autoscale policy”, and one that had f2 rendering a validation failure for an ordinary wrong-mode refusal. That variant is now gone entirely; see the note where it stood.

Source

pub fn remove_routing_label( &mut self, label: &Label, ) -> Result<bool, PolicyError>

Remove an optional descriptive routing label.

§Errors

PolicyError::HostLabelNotRemovable for the derived host label, or PolicyError::NotAutoscale for a monitor-only policy — see Self::add_routing_label for why that variant.

Source

pub fn tally<'a>( &self, jobs: impl IntoIterator<Item = &'a RunsOn>, ) -> DemandTally

The demand signal for this policy, given one poll’s queued jobs.

A monitor-only policy has no routing labels, so it has no demand at all — not “demand that is then ignored”. D19: it “is skipped entirely by reconciliation”.

Trait Implementations§

Source§

impl Clone for ScalePolicy

Source§

fn clone(&self) -> ScalePolicy

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ScalePolicy

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for ScalePolicy

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Eq for ScalePolicy

Source§

impl PartialEq for ScalePolicy

Source§

fn eq(&self, other: &ScalePolicy) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for ScalePolicy

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for ScalePolicy

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more