pub struct PoolSpec {
pub desired_size: u32,
pub min_size: u32,
pub max_size: u32,
pub return_policy: ReturnPolicy,
pub selector: PoolSelector,
pub template: EphemeralSpec,
pub free_ttl: String,
pub max_allocation_ttl: String,
pub desired: u32,
pub replacement_policy: ReplacementPolicy,
pub stable_name_claim: bool,
}Expand description
EphemeralPool CRD spec — typed pool of warm Processes.
apiVersion: tatara.pleme.io/v1alpha1
kind: EphemeralPool
metadata:
name: attest-pool
namespace: ephemeral-pools
spec:
desiredSize: 3
minSize: 1
maxSize: 5
returnPolicy: Reset
selector:
repos: ["pleme-io/demo-*"]
branches: ["main", "release-*"]
prLabels: ["needs-ephemeral"]
template:
aplicacao:
chartRef: "oci://ghcr.io/pleme-io/charts/lareira-demo-app"
version: "0.5.5"
profile: "all-in-one"
…
ttl: "2h"
teardown: OnAttested
postconditions: [ … ]Fields§
§desired_size: u32Target number of warm Processes the pool maintains in Free
state (sum of Free + Spawning targets desired_size).
min_size: u32Hard floor on the free count. The reconciler refuses to scale below this even on cost-pressure signals. Default = 0.
max_size: u32Hard ceiling on total pool members (free + allocated + spawning).
0 = no cap. Default = 0.
return_policy: ReturnPolicyWhat to do when an allocation releases.
selector: PoolSelectorRouting selector — which allocation requests this pool serves.
The reconciler matches incoming EphemeralAllocation CRs
against this selector (most-specific wins across pools sharing
a namespace).
template: EphemeralSpecTemplate for each pool member — a typed EphemeralSpec that
the reconciler lowers to ProcessSpec and instantiates.
While in the free list each member’s lifetime is overridden
to Permanent; allocation flips it back to Ephemeral with
the requestor’s TTL.
free_ttl: StringHow long a pool member may sit in Free before the reconciler
recycles it (humantime). Defends against drift / stale state.
Default "24h".
max_allocation_ttl: StringMax time the reconciler allows a single allocation to hold a
member before forcibly returning it (humantime). Hard cap
independent of the allocation’s own TTL. Default "4h".
desired: u32R5 desired-count loop — when set non-zero, the pool
reconciler maintains exactly this many healthy (Running or
Attested) Processes regardless of allocation pressure. Drives
the “always seeking stability” property: failed members are
replaced per replacement_policy. 0 keeps the legacy
allocation-driven sizing (desired = floor of free + allocated).
Operator usage: desired: 5 means “always have 5 of these
running”; failures auto-replace.
replacement_policy: ReplacementPolicyR5 — what the pool reconciler does when a member reaches
Failed phase.
stable_name_claim: boolR5 — when true, exactly one healthy member of the pool
holds the unprefixed-form DNS hostnames declared in
template.routing at any moment. The claim arbiter (see
tatara-reconciler::claim) transfers atomically when the
holder fails.
Implementations§
Source§impl PoolSpec
impl PoolSpec
Sourcepub fn free_ttl_duration(&self) -> Option<Duration>
pub fn free_ttl_duration(&self) -> Option<Duration>
Humantime-parsed std::time::Duration projection of the
Self::free_ttl slot — the ONE-line collapse of the paired
humantime::parse_duration(&<pool>.spec.free_ttl).ok()
incantation the pool reconciler’s stale-free bucket loop
hand-authored pre-lift, sibling to
crate::lifetime::EphemeralLifetime::ttl_duration on the
SAME (humantime string field × Option<Duration>) → Option< Duration> substrate axis.
Pre-lift the humantime::parse_duration(&<field>).ok() shape
was owned at ONE substrate primitive on
crate::lifetime::EphemeralLifetime (the spec.lifetime .ephemeral.ttl axis, feeding
crate::lifetime_clock::evaluate’s TTL-expiry gate + the
requeue_with_ttl sleep-budget picker) AND hand-authored at
ONE peer consumer site — tatara-pool-reconciler::pool_decide ::decide_pool, which parses pool.spec.free_ttl with the
byte-identical shape (humantime::parse_duration(&spec .free_ttl).unwrap_or_default()) and gates the stale-free
bucket loop on the result. That’s ONE substrate owner + ONE
hand-authored chain on a peer humantime field of a peer spec
type past the ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger —
two surfaces spelling the SAME projection with the SAME drift
risk (a per-fleet minimum TTL floor before the humantime cast,
a canonical unit-normalization pass, a warn-log on
unparseable strings would have had to land at every surface
plus stay coherent between them).
Post-lift both peer humantime fields
(crate::lifetime::EphemeralLifetime::ttl +
Self::free_ttl) publish the SAME shape at TWO peer
inherent methods on peer spec types — the tatara-pool-
reconciler’s stale-free bucket loop reads pool.spec.free_ttl _duration().unwrap_or_default() and the produced [std::time ::Duration] feeds the same !free_ttl.is_zero() guard +
elapsed > free_ttl comparator unchanged. A future
normalization (per-fleet minimum floor, canonical unit
normalization, warn-log on unparseable strings) lands at TWO
substrate methods here + on
crate::lifetime::EphemeralLifetime::ttl_duration, reachable
via ONE workspace-wide sweep across the peer axis rather than
as a per-callsite hand-edit at every downstream humantime-ttl
consumer.
Return-form axis: Option<std::time::Duration> matches the
peer primitive on
crate::lifetime::EphemeralLifetime::ttl_duration and the
downstream comparator’s type. The peer projection
crate::time::elapsed_since returns the SAME Option<std ::time::Duration> shape, so the stale-free gate’s elapsed > free_ttl comparator lands with both operands on the same
axis without a per-consumer conversion step.
The None arm is the “operator’s free_ttl string doesn’t
parse” corner — a typo ("1our"), an unsupported unit, a
non-humantime literal that reached the field. The pool
reconciler’s stale-free bucket loop collapses the corner via
.unwrap_or_default(), yielding the Duration::ZERO value
that already gates its follow-on !free_ttl.is_zero() check
— post-lift semantics is byte-identical to the pre-lift
hand-authored humantime::parse_duration(&spec.free_ttl) .unwrap_or_default() shape.
Theory anchor: THEORY.md §VI.1 (generation over composition —
the humantime::parse_duration(&<field>).ok() shape recurred
at ONE substrate owner + ONE hand-authored peer site past the
★★ PRIME-DIRECTIVE ≥ 2 duplication trigger, and is lifted onto
TWO peer inherent methods on peer spec types here + on
crate::lifetime::EphemeralLifetime::ttl_duration).
THEORY.md §II.1 invariant 5 (composition preserves proofs —
the pins below bind the parse-failure corner, the empty-ttl
corner, the humantime edge shapes, the return-form parity with
crate::lifetime::EphemeralLifetime::ttl_duration, and the
byte-identical parity with the pre-lift .ok() chain on the
SAME spec.free_ttl value, so a regression that drifts any
surface fails at tests::pool_spec_free_ttl_duration_* here
rather than as silent operator-facing skew between the pool
stale-free bucket loop and the ephemeral TTL-expiry gate on
the two peer humantime-string fields).
Sourcepub fn with_template(template: EphemeralSpec) -> Self
pub fn with_template(template: EphemeralSpec) -> Self
Compose a PoolSpec for the given member template, stamping
every non-template slot at the #[serde(default …)] value the
wire-schema publishes above — the ONE substrate composer that
closes the 11-slot PoolSpec { desired_size: 1, min_size: 0, max_size: 0, return_policy: ReturnPolicy::Replace, selector: PoolSelector::default(), template, free_ttl: "24h".into(), max_allocation_ttl: "4h".into(), desired: 0, replacement_policy: Default::default(), stable_name_claim: false } struct-literal
every test-side + reconciler-side seed hand-authored pre-lift.
Sibling to crate::crd::ProcessSpec::gate_compute_defaults on
the (spec-type × full-baseline-composer) axis — that primitive
owns the 11-slot crate::crd::ProcessSpec baseline composer;
this one owns the peer 11-slot PoolSpec baseline composer.
Both take a caller-supplied slot (there: the classification
baseline via Classification::gate_compute(); here: the
template EphemeralSpec, which has no natural default) and
fill every other slot at its wire-published default so a caller
composes with struct-update syntax (PoolSpec { desired_size: 1, ..PoolSpec::with_template(empty_template()) }) rather than
re-spelling the 10 defaulted slots at every seed. A future
promotion of a defaulted slot to a non-default (a per-fleet
minimum min_size floor, a shifted default_free_ttl,
a widened ReturnPolicy default) lands at ONE substrate
composer here and every downstream seed inherits the upgrade
mechanically.
Pre-lift the 11-slot struct-literal was hand-authored at EIGHT sites across TWO crates past the ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger:
tatara-process::lib::tests::pool_fixture— thequalified_process_ref+ trait-pin fixture seed;tatara-process::lib::tests::empty_pool_spec(×2) — the two sibling fixtures inside separate pin modules;tatara-process::pool::tests::pool_spec— thename_or_empty/namespace_or_emptypin fixture;tatara-pool-reconciler::router::tests::pool— the router- candidate-arbiter pin fixture (overridesselector);tatara-pool-reconciler::desired::tests::pool_with_desired— the desired-count-loop pin fixture (overridesdesired+replacement_policy);tatara-pool-reconciler::pool_decide::tests::pool— the pure-decision pin fixture (overrides sizes);tatara-pool-reconciler::allocation_decide::tests::pool— the allocation-router pin fixture (overridesselector).
The three fields the wire-schema does NOT default (desired_size
carries no #[serde(default)] above; template is the caller-
supplied slot) are stamped at their operator-friendly seed
values here — desired_size = 0 matches every other reset
slot’s 0 / false / Default stamp, so a caller can compose
PoolSpec { desired_size: 1, ..PoolSpec::with_template(t) } for
the single-slot pool the majority of pre-lift seeds spelled, or
PoolSpec { desired_size: 0, desired: 5, ..with_template(t) }
for the desired-count-loop shape one seed spelled.
Theory anchor: THEORY.md §VI.1 (generation over composition — the
11-slot PoolSpec struct-literal recurred at EIGHT hand-
authored sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
trigger and is lifted onto ONE workspace-wide owner here).
THEORY.md §II.1 invariant 5 (composition preserves proofs — a
regression that drifted a wire-published default at only one
consumer, or that broke the sibling-default correspondence with
crate::crd::ProcessSpec::gate_compute_defaults, surfaces at
this primitive’s tests rather than as silent operator-visible
skew across the eight fixtures whose assertions key on the
shape).
Trait Implementations§
Source§impl<'de> Deserialize<'de> for PoolSpec
impl<'de> Deserialize<'de> for PoolSpec
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl JsonSchema for PoolSpec
impl JsonSchema for PoolSpec
Source§fn schema_name() -> String
fn schema_name() -> String
Source§fn schema_id() -> Cow<'static, str>
fn schema_id() -> Cow<'static, str>
Source§fn json_schema(generator: &mut SchemaGenerator) -> Schema
fn json_schema(generator: &mut SchemaGenerator) -> Schema
Source§fn is_referenceable() -> bool
fn is_referenceable() -> bool
$ref keyword. Read moreAuto Trait Implementations§
impl Freeze for PoolSpec
impl RefUnwindSafe for PoolSpec
impl Send for PoolSpec
impl Sync for PoolSpec
impl Unpin for PoolSpec
impl UnsafeUnpin for PoolSpec
impl UnwindSafe for PoolSpec
Blanket Implementations§
impl<T> AppData for Twhere
T: OptionalSend + OptionalSync + 'static + OptionalSerde,
impl<T> AppDataResponse for Twhere
T: OptionalSend + OptionalSync + 'static + OptionalSerde,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreimpl<T> OptionalSend for T
impl<T> OptionalSync for T
Source§impl<T> Paint for Twhere
T: ?Sized,
impl<T> Paint for Twhere
T: ?Sized,
Source§fn fg(&self, value: Color) -> Painted<&T>
fn fg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the foreground set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like red() and
green(), which have the same functionality but are
pithier.
§Example
Set foreground color to white using fg():
use yansi::{Paint, Color};
painted.fg(Color::White);Set foreground color to white using white().
use yansi::Paint;
painted.white();Source§fn bright_black(&self) -> Painted<&T>
fn bright_black(&self) -> Painted<&T>
Source§fn bright_red(&self) -> Painted<&T>
fn bright_red(&self) -> Painted<&T>
Source§fn bright_green(&self) -> Painted<&T>
fn bright_green(&self) -> Painted<&T>
Source§fn bright_yellow(&self) -> Painted<&T>
fn bright_yellow(&self) -> Painted<&T>
Source§fn bright_blue(&self) -> Painted<&T>
fn bright_blue(&self) -> Painted<&T>
Source§fn bright_magenta(&self) -> Painted<&T>
fn bright_magenta(&self) -> Painted<&T>
Source§fn bright_cyan(&self) -> Painted<&T>
fn bright_cyan(&self) -> Painted<&T>
Source§fn bright_white(&self) -> Painted<&T>
fn bright_white(&self) -> Painted<&T>
Source§fn bg(&self, value: Color) -> Painted<&T>
fn bg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the background set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like on_red() and
on_green(), which have the same functionality but
are pithier.
§Example
Set background color to red using fg():
use yansi::{Paint, Color};
painted.bg(Color::Red);Set background color to red using on_red().
use yansi::Paint;
painted.on_red();Source§fn on_primary(&self) -> Painted<&T>
fn on_primary(&self) -> Painted<&T>
Source§fn on_magenta(&self) -> Painted<&T>
fn on_magenta(&self) -> Painted<&T>
Source§fn on_bright_black(&self) -> Painted<&T>
fn on_bright_black(&self) -> Painted<&T>
Source§fn on_bright_red(&self) -> Painted<&T>
fn on_bright_red(&self) -> Painted<&T>
Source§fn on_bright_green(&self) -> Painted<&T>
fn on_bright_green(&self) -> Painted<&T>
Source§fn on_bright_yellow(&self) -> Painted<&T>
fn on_bright_yellow(&self) -> Painted<&T>
Source§fn on_bright_blue(&self) -> Painted<&T>
fn on_bright_blue(&self) -> Painted<&T>
Source§fn on_bright_magenta(&self) -> Painted<&T>
fn on_bright_magenta(&self) -> Painted<&T>
Source§fn on_bright_cyan(&self) -> Painted<&T>
fn on_bright_cyan(&self) -> Painted<&T>
Source§fn on_bright_white(&self) -> Painted<&T>
fn on_bright_white(&self) -> Painted<&T>
Source§fn attr(&self, value: Attribute) -> Painted<&T>
fn attr(&self, value: Attribute) -> Painted<&T>
Enables the styling Attribute value.
This method should be used rarely. Instead, prefer to use
attribute-specific builder methods like bold() and
underline(), which have the same functionality
but are pithier.
§Example
Make text bold using attr():
use yansi::{Paint, Attribute};
painted.attr(Attribute::Bold);Make text bold using using bold().
use yansi::Paint;
painted.bold();Source§fn rapid_blink(&self) -> Painted<&T>
fn rapid_blink(&self) -> Painted<&T>
Source§fn quirk(&self, value: Quirk) -> Painted<&T>
fn quirk(&self, value: Quirk) -> Painted<&T>
Enables the yansi Quirk value.
This method should be used rarely. Instead, prefer to use quirk-specific
builder methods like mask() and
wrap(), which have the same functionality but are
pithier.
§Example
Enable wrapping using .quirk():
use yansi::{Paint, Quirk};
painted.quirk(Quirk::Wrap);Enable wrapping using wrap().
use yansi::Paint;
painted.wrap();Source§fn clear(&self) -> Painted<&T>
👎Deprecated since 1.0.1: renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
fn clear(&self) -> Painted<&T>
renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
Source§fn whenever(&self, value: Condition) -> Painted<&T>
fn whenever(&self, value: Condition) -> Painted<&T>
Conditionally enable styling based on whether the Condition value
applies. Replaces any previous condition.
See the crate level docs for more details.
§Example
Enable styling painted only when both stdout and stderr are TTYs:
use yansi::{Paint, Condition};
painted.red().on_yellow().whenever(Condition::STDOUTERR_ARE_TTY);