pub struct MirrorConfig {
pub schema_version: u32,
pub shape: MirrorShape,
pub ingress: IngressDecl,
pub ingress_machines: Vec<String>,
pub providers: BTreeMap<String, MirrorProviderSlot>,
pub drivers: BTreeMap<String, MirrorProviderSlot>,
pub asset_aliases: BTreeMap<String, String>,
}Expand description
A service mirror — the projection of a ServiceConfig onto concrete
infra. Lives at .yah/services/<svc>/mirrors/<env>.toml.
Fields§
§schema_version: u32§shape: MirrorShape§ingress: IngressDeclPublic-ingress edges fronting this mirror (W267, W305 F2). Defaults to none.
Two spellings, one meaning — see IngressDecl. ingress = "passway"
is one edge fronting everything; [[ingress]] entries declare several,
each naming its provider plus the slots or hostnames it fronts. Read it
through ingress_edges, never by matching on the
enum, so the two spellings cannot drift apart.
Declared at mirror scope rather than per provider slot because a front
door does fan-in: one cloudflared (or one passway) on a node
multiplexes every hostname→port rule it fronts, so pinning one to a
single slot would mint one edge connection per slot for no gain. An
edge’s slots selector is the general form of that — it groups slots
behind one front door, it does not split a front door per slot.
ingress_machines: Vec<String>Machines the front door is placed on — independent of where the fronted workload runs (R330-F37).
The single-edge spelling of IngressEdge::machines: it applies to the
one edge ingress = "<provider>" declares, and combining it with
[[ingress]] entries is an error rather than a silent precedence rule.
Empty (the default) keeps the pre-existing behaviour: the front door is
co-located with the fronted slot’s own machine / machines. That was
never a design choice, it was an artifact of bundles binding
127.0.0.1 — nothing off-node could reach a workload, so a proxy had to
sit on top of it. R599-F12 landed mesh binding, which removes the
constraint: passway is a reverse proxy, and a valid front door needs a
cert and an upstream it can reach, not a local copy of the service.
Listing several machines is what lets the ingress tier and the service tier scale independently — N front doors over ONE deployment. There is still exactly one rendered copy of the site, so fanning the front door out introduces no cache-coherence problem; that only appears if you deploy the workload to every node instead.
ingress = "passway"
ingress_machines = ["us-east-001", "us-west-001"]Declaring this without ingress is an error, not a
no-op — it always means the operator expected a front door somewhere.
providers: BTreeMap<String, MirrorProviderSlot>Provider slots, keyed by role ("static", "compute", …). Each value
either references a provider declared under .yah/infra/providers/ or
inlines a local-only provider (no creds, no infra file).
A role is normally service-wide — one slot serves every component that
shares it — but ReconcileCtx::slot
looks up the component-qualified key "<role>:<component id>" first.
A service with two components of the same role (e.g. two
mesofact-static components under one mirror) declares
providers."static:<id>" per component to give each its own port;
omitting the qualifier keeps the pre-existing single-slot behavior.
drivers: BTreeMap<String, MirrorProviderSlot>Capability→driver bindings, keyed by capability ("pg", "s3", …)
rather than by slot role (W265 §Drivers).
This is the generalization of Self::providers: providers.static /
providers.object_store are the special case where the slot name and
the capability happen to coincide, and keying by capability is what stops
the slot enum growing one arm per tier-specific implementation. A service
says “I need pg”; the mirror says which implementation of pg this tier
uses; the app talks the same wire protocol either way and never forks.
[drivers.pg]
kind = "local-pg-dev" # dev — kamaji-supervised loopback postgresAdditive in P1: drivers lands alongside providers, and migrating
the existing providers.static / providers.object_store declarations
over is a separate pass (W265 §“Open follow-ups”). A mirror that declares
neither is unchanged.
asset_aliases: BTreeMap<String, String>Per-environment alias overrides for kind = "static-asset" components.
Keys are logical names (e.g. "whisper-default"); values must be
filenames present in the component’s workload.toml catalog.
Resolution only — this table may never introduce a filename absent
from the catalog. Validated against the workload catalog at sync time.
Implementations§
Source§impl MirrorConfig
impl MirrorConfig
Sourcepub fn driver(&self, capability: Capability) -> Option<&MirrorProviderSlot>
pub fn driver(&self, capability: Capability) -> Option<&MirrorProviderSlot>
The driver bound to capability in this mirror, if any.
None means the tier declares no implementation. In P1 that’s simply
“this mirror doesn’t use that capability”; P2’s binding-error surface is
what turns it into a diagnosable failure for a service that needs it.
Source§impl MirrorConfig
impl MirrorConfig
Sourcepub fn ingress_edge_slice(&self) -> &[IngressEdge]
pub fn ingress_edge_slice(&self) -> &[IngressEdge]
This mirror’s declared edges, with both spellings normalized (W305 F2).
The single place ingress + ingress_machines are reconciled, so no
consumer has to know which spelling was written. Returns an empty vec
when the mirror declares no front door.
Errors are the declarations that cannot mean anything:
ingress_machineswith noingress— front-door placement with no front door to place, always a typo (R330-F37);ingress_machinesalongside[[ingress]]— placement declared twice, in a form where one silently wins;provider = "none"on an edge — an edge that fronts with nothing. The[[ingress]]entries exactly as written, without normalizing the scalar spelling or validating anything.
ingress_edges is the one to reach for; this
exists for the checks that must run before a mirror is known to be
well-formed — cross-reference validation walks every mirror in the
workspace, and hard-failing there on an unrelated mirror’s shape error
would report the wrong file. Empty for the scalar spelling, which has no
edge table to carry per-edge fields.
pub fn ingress_edges(&self) -> Result<Vec<IngressEdge>>
Sourcepub fn passway_machines(&self) -> Option<Vec<String>>
pub fn passway_machines(&self) -> Option<Vec<String>>
Nodes this mirror’s passway front doors are placed on, in
declaration order and de-duplicated — or None when the mirror declares
no passway edge at all.
Some(vec![]) is a real and different answer from None: a passway edge
is declared but names no machine, so its placement falls back to the
fronted slot’s own. That fallback is placement resolution — it belongs
to IngressRule::machines
and the plan it is built from, not to a mirror read in isolation — so it
is reported as “declared, placement unknown from here” rather than
half-derived. A caller that needs a node to dial has to say so.
Passway-only because the caller is tenant DNS onboarding: only a passway
node serves yubaba’s GET /domains/{domain}/onboarding. A
cloudflare-tunnel edge publishes through Cloudflare’s own DNS and has no
such record to hand a tenant, so folding its machines in would point the
UI at a node that cannot answer.
Read through ingress_edges, so both spellings
are covered by construction. A declaration that cannot mean anything
(ingress_machines with no ingress, or both spellings at once) reads
as None rather than propagating an error: those are reported by
cross-reference validation, which can name the offending file.
Sourcepub fn save(
&self,
workspace_root: &Path,
service: &str,
env: &str,
) -> Result<()>
pub fn save( &self, workspace_root: &Path, service: &str, env: &str, ) -> Result<()>
Persist to .yah/services/<service>/mirrors/<env>.toml, creating the
mirrors/ directory if needed. Create-or-overwrite. The mirror file is
named by env (its stem); service selects the owning service dir.
Sourcepub fn delete(workspace_root: &Path, service: &str, env: &str) -> Result<bool>
pub fn delete(workspace_root: &Path, service: &str, env: &str) -> Result<bool>
Remove .yah/services/<service>/mirrors/<env>.toml. Returns false
when the file was already absent. Leaves the service and its other
mirrors untouched.
Also checks legacy stems (e.g. local-sim when env = "pond") so
deleting a canonical tier name removes whichever file exists on disk.
Trait Implementations§
Source§impl Clone for MirrorConfig
impl Clone for MirrorConfig
Source§fn clone(&self) -> MirrorConfig
fn clone(&self) -> MirrorConfig
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for MirrorConfig
impl Debug for MirrorConfig
Source§impl<'de> Deserialize<'de> for MirrorConfig
impl<'de> Deserialize<'de> for MirrorConfig
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>,
Auto Trait Implementations§
impl Freeze for MirrorConfig
impl RefUnwindSafe for MirrorConfig
impl Send for MirrorConfig
impl Sync for MirrorConfig
impl Unpin for MirrorConfig
impl UnsafeUnpin for MirrorConfig
impl UnwindSafe for MirrorConfig
Blanket Implementations§
impl<T> Allocation for T
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> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSend for T
impl<T> DowncastSend for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
impl<T> ErasedDestructor for Twhere
T: 'static,
impl<T> Fruit for T
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
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 more