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: HostLabelOperator-chosen host identity retained even for MonitorOnly policies.
cache_policy: CachePolicyImplementations§
Source§impl ScalePolicy
impl ScalePolicy
Sourcepub fn new(
id: PolicyId,
target: ScaleTarget,
installation_id: u64,
host_id: HostId,
mode: PolicyMode,
cache_policy: CachePolicy,
) -> Self
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.
Sourcepub 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
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.
Sourcepub fn from_persisted(fields: PersistedPolicy) -> Result<Self, PolicyError>
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.
Sourcepub fn to_persisted(&self) -> PersistedPolicy
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.
pub const fn mode(&self) -> &PolicyMode
pub const fn state(&self) -> PolicyState
Sourcepub const fn enabled(&self) -> bool
pub const fn enabled(&self) -> bool
Operator intent, independent of state
(04-subsystem-contracts.md: “enabled records operator intent;
state records observed lifecycle”).
Sourcepub const fn revision(&self) -> u64
pub const fn revision(&self) -> u64
Optimistic-concurrency token. Every successful mutation below bumps it;
b2 rejects a write against a stale value.
pub const fn routing_labels(&self) -> Option<&RoutingLabels>
Sourcepub const fn workspace_policy(&self) -> &WorkspacePolicy
pub const fn workspace_policy(&self) -> &WorkspacePolicy
D4: this repository’s configured workspace behaviour.
Sourcepub fn set_workspace_policy(
&mut self,
workspace: WorkspacePolicy,
) -> Result<(), PolicyError>
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.
pub const fn min_capacity(&self) -> u16
pub const fn max_capacity(&self) -> Option<NonZeroU16>
Sourcepub fn is_owned_by(&self, host_id: HostId) -> bool
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.
Sourcepub const fn owns_runners(&self) -> bool
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.
Sourcepub const fn may_start_runners(&self) -> bool
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).
Sourcepub fn transition_to(&mut self, next: PolicyState) -> Result<(), PolicyError>
pub fn transition_to(&mut self, next: PolicyState) -> Result<(), PolicyError>
§Errors
PolicyError::IllegalTransition for anything outside
PolicyState::LEGAL.
Sourcepub fn can_activate(&self) -> bool
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.
Sourcepub fn can_request_disable(&self) -> bool
pub fn can_request_disable(&self) -> bool
Whether Self::request_disable would succeed right now.
Sourcepub fn activate(&mut self) -> Result<(), PolicyError>
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.
Sourcepub fn request_disable(&mut self) -> Result<PolicyState, PolicyError>
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.
Sourcepub fn drain_completed(
&mut self,
active_attempts: u16,
) -> Result<PolicyState, PolicyError>
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.
Sourcepub fn authentication_failed(&mut self) -> Result<(), PolicyError>
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.
Sourcepub fn reauthenticated(&mut self) -> Result<(), PolicyError>
pub fn reauthenticated(&mut self) -> Result<(), PolicyError>
“(recoverable by re-authentication)”.
§Errors
PolicyError::IllegalTransition unless the policy is in
AuthenticationFailed.
Sourcepub fn repair_required(&mut self) -> Result<(), PolicyError>
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.
Sourcepub fn promote_to_autoscale(
&mut self,
routing_labels: RoutingLabels,
min_capacity: u16,
max_capacity: NonZeroU16,
) -> Result<(), PolicyError>
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.
Sourcepub fn set_max_capacity(
&mut self,
max_capacity: NonZeroU16,
) -> Result<(), PolicyError>
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.
Sourcepub fn add_routing_label(&mut self, label: Label) -> Result<bool, PolicyError>
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.
Sourcepub fn remove_routing_label(
&mut self,
label: &Label,
) -> Result<bool, PolicyError>
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.
Sourcepub fn tally<'a>(
&self,
jobs: impl IntoIterator<Item = &'a RunsOn>,
) -> DemandTally
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
impl Clone for ScalePolicy
Source§fn clone(&self) -> ScalePolicy
fn clone(&self) -> ScalePolicy
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more