Skip to main content

MirrorConfig

Struct MirrorConfig 

Source
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>,
    pub build: BTreeMap<String, MirrorBuildOverride>,
}
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: IngressDecl

Public-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 postgres

[drivers.smtp]
kind = "local-mailcrab"   # dev/pond — a catcher with a browsable inbox

Additive 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.

§build: BTreeMap<String, MirrorBuildOverride>

Per-environment build overrides, keyed by component id (R905).

A [build] block lives on the component’s workload.toml, and a component is declared exactly once in service.toml — so without this table a service that deploys the same component to two environments builds it identically for both. That is wrong for any bundle whose contents depend on the tier it is being built for: noisetable’s landing site bakes NOISETABLE_API_ORIGIN into the shipped JS, so the staging site was served a production API origin and every call from it was blocked by production CORS.

# .yah/services/noisetable-marketing/mirrors/staging.toml
[build.site]
command = "bun run build:staging"

# or, without a sibling script per environment:
[build.site.env]
NOISETABLE_API_ORIGIN = "https://api-staging.noisetable.com"

The environment axis stays on the mirror, where providers, drivers and ingress already live, rather than growing an env-keyed table on the component’s own BuildConfig — a per-component struct is the wrong place to enumerate environments, and doing it there would have made the mirror the second per-environment surface instead of the only one.

Deliberately NOT overridable here: out_dir. Where a bundler writes is a property of the project’s own toolchain, not of the tier it is built for, and it is read independently of [build] by the publish path (read_workload_out_dir, collect_component_files) — making it per-environment would mean threading the mirror into every one of those readers to buy a knob no tier needs.

Keys are validated against the service’s declared component ids at config load (cross_ref_validate), so a typo is a refusal rather than an override that silently never fires.

Implementations§

Source§

impl MirrorConfig

Source

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

Source

pub fn build_override(&self, component_id: &str) -> Option<&MirrorBuildOverride>

This environment’s build override for component_id, if any.

Returns None for an override that exists but changes nothing, so callers can treat Some(_) as “something differs here” without re-checking each field.

Source§

impl MirrorConfig

Source

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_machines with no ingress — front-door placement with no front door to place, always a typo (R330-F37);
  • ingress_machines alongside [[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.

Source

pub fn ingress_edges(&self) -> Result<Vec<IngressEdge>>

Source

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.

Source

pub fn load(path: &Path) -> Result<Self>

Parse a single mirrors/<env>.toml file.

Source

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.

Source

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

Source§

fn clone(&self) -> Self

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 MirrorConfig

Source§

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

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

impl<'de> Deserialize<'de> for MirrorConfig

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 Serialize for MirrorConfig

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

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Allocation for T
where T: RefUnwindSafe + Send + Sync,

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<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

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<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert 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>

Convert 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)

Convert &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)

Convert &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 T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Converts 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>

Converts 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)

Converts &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)

Converts &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
where T: Any + Send,

Source§

fn into_any_send(self: Box<T>) -> Box<dyn Any + Send>

Converts Box<Trait> (where Trait: DowncastSend) to Box<dyn Any + Send>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send> ⓘ

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_sync(self: Box<T>) -> Box<dyn Any + Sync + Send>

Converts Box<Trait> (where Trait: DowncastSync) to Box<dyn Any + Send + Sync>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send> ⓘ

Converts Arc<Trait> (where Trait: DowncastSync) to Arc<Any>, which can then be downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

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

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

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

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<T> Fruit for T
where T: Send + Downcast,

Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

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> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

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

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> Serialize for T
where T: Serialize + ?Sized,

Source§

fn erased_serialize(&self, serializer: &mut dyn Serializer) -> Result<(), Error>

Source§

fn do_erased_serialize( &self, serializer: &mut dyn Serializer, ) -> Result<(), ErrorImpl>

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<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

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