pub struct InnerDoorPlan {
pub service: String,
pub mounts: Vec<InnerDoorMount>,
}Expand description
A service’s inner door, as configuration — everything but the addresses.
Fields§
§service: StringService name, for error messages and the workload name.
mounts: Vec<InnerDoorMount>Mounts in declaration order. Precedence is PathRouter’s (longest
mount wins), not this vector’s, so the order is presentational.
Implementations§
Source§impl InnerDoorPlan
@yah:ticket(R931-B6, “Inner-door render refuses noisetable-marketing route table: Bundle mount has no resolved address yet”)
@yah:status(review)
@yah:at(2026-09-22T05:45:52Z)
@yah:assignee(agent:bundle-anthropic-ashguard)
@yah:parent(R931)
@yah:severity(high)
@yah:gotcha(“FOUND BY THE NOISETABLE CAMP (R733-T26, @Miravel:libra) while re-running yah cloud mirror up noisetable-marketing --env prod after yah R931-B5’s kamaji.service fix (StateDirectory/ReadWritePaths for passway/routes) was rolled to us-east-001 and kamaji restarted (2026-09-22). R931-B5 IS CONFIRMED WORKING for its own scope — the read-only-filesystem EROFS on /var/lib/passway/routes/*.routes.json is GONE. This is the next layer down, not a regression of B5.”)
@yah:assumes(“The site/app mesofact-spa/mesofact-static components published and went Running cleanly (bundle digest 8e77c1b448fafabe172bf774f931c1426cace9fe1f0f8f5eebee658c1af21dd6, build_id 9481d540db93867ce8727ce2b7bffd8b, runtime mesofact/0.8.32) throughout this run; only the issues (binary/inner-door) component failed.”)
@yah:next(“Gate for R733-T26: once this reaches review, re-run yah cloud mirror up noisetable-marketing --env prod and redo the five-point verification (root 200+title, /app/ 200, GET /api/issues 200 body-free projection, POST tests/fixtures/chloro_edit_failure_post.json -> 201+ULID, /issues renders the list).”)
@yah:gotcha(“Exact error from yah cloud mirror up noisetable-marketing --env prod (run 2026-09-22, ready service records ["us-east-001", "us-south-001", "us-west-001"]): Error: rendering the inner door for noisetable-marketing/prod from the ready service records of [\"us-east-001\", \"us-south-001\", \"us-west-001\"]: service noisetable-marketing: mount \"\" is served by Bundle, which has no resolved address yet. Refusing to write a partial route table -- a missing mount does not 503, it falls through to the root mount and serves the wrong component with a 200. Source: oss/yubaba/crates/cloud/src/inner_door.rs:348-349. Live verification after the failed attempt: https://noisetable.com/ 200 (title correct), /app/ 200, GET /api/issues 404 (body is the marketing SPA HTML shell, not the issues service’s body-free projection), GET /issues 500 (F25’s client error page). No rollback needed – / and /app/ never left 200.”)
@yah:handoff(“FIXED in app/yah/cli/src/cloud.rs. Root cause is R931-B7 defect (2), not an ident or config fault: deploy_inner_door read ready service records ONCE right after the deploy phase (re)deployed the bundle, so the bundle ident had no record yet and InnerDoorPlan::workload refused the table. B7’s own measurements show the identical error passing on a re-run a minute later. FIX: ReadyRecordWait gained a generic resolve_with<T>(read, attempt, idle) loop (resolve_plan is now a thin wrapper over it — one loop, two consumers), and deploy_inner_door runs resolve_addresses + workload() inside it under ReadyRecordWait::APPLY (90s budget, 5s interval), with a one-time stderr notice. The refusal in inner_door.rs:349 is unchanged — a partial table is still never written; it is now retried rather than surfaced on the first read. This also closes B7’s defect (2); B7 keeps only defect (1), the needless re-fork.”)
@yah:verify(“LIVE GATE (not run by me, prod-facing): re-run yah cloud mirror up noisetable-marketing --env prod, then the five-point check in this ticket’s next — R733-T26’s gate.”)
@yah:assumes(“resolve_workload_ident(mirror, ‘noisetable-marketing’, ‘prod’) yields the ident the bundle actually registers (‘noisetable’ per the west passway log in B7). Inferred from B7’s ‘next run a minute later succeeded’, not re-read against the mirror toml.”)
@yah:verify(“cargo test -p yah --lib ready_record_wait = 5 passed / 0 failed, EXIT 0 (new: the_inner_door_waits_for_the_bundle_record_instead_of_refusing_the_table). Log /tmp/r931b6-t2.log.”)
@yah:verify(“LIVE, 2026-09-22, CLI installed via cargo xtask install: five yah cloud mirror up noisetable-marketing --env prod runs from ~/ss/noisetable, all EXIT 0. Each rendered the inner door (/ and /app → 100.64.0.3:41507, /api/issues → 10.128.1.2:4333) and ended ‘noisetable.com is serving this bundle’. Not one hit the ‘no resolved address yet’ refusal, including the runs where the bundle was re-forked just before.”)
impl InnerDoorPlan
@yah:ticket(R931-B6, “Inner-door render refuses noisetable-marketing route table: Bundle mount has no resolved address yet”)
@yah:status(review)
@yah:at(2026-09-22T05:45:52Z)
@yah:assignee(agent:bundle-anthropic-ashguard)
@yah:parent(R931)
@yah:severity(high)
@yah:gotcha(“FOUND BY THE NOISETABLE CAMP (R733-T26, @Miravel:libra) while re-running yah cloud mirror up noisetable-marketing --env prod after yah R931-B5’s kamaji.service fix (StateDirectory/ReadWritePaths for passway/routes) was rolled to us-east-001 and kamaji restarted (2026-09-22). R931-B5 IS CONFIRMED WORKING for its own scope — the read-only-filesystem EROFS on /var/lib/passway/routes/*.routes.json is GONE. This is the next layer down, not a regression of B5.”)
@yah:assumes(“The site/app mesofact-spa/mesofact-static components published and went Running cleanly (bundle digest 8e77c1b448fafabe172bf774f931c1426cace9fe1f0f8f5eebee658c1af21dd6, build_id 9481d540db93867ce8727ce2b7bffd8b, runtime mesofact/0.8.32) throughout this run; only the issues (binary/inner-door) component failed.”)
@yah:next(“Gate for R733-T26: once this reaches review, re-run yah cloud mirror up noisetable-marketing --env prod and redo the five-point verification (root 200+title, /app/ 200, GET /api/issues 200 body-free projection, POST tests/fixtures/chloro_edit_failure_post.json -> 201+ULID, /issues renders the list).”)
@yah:gotcha(“Exact error from yah cloud mirror up noisetable-marketing --env prod (run 2026-09-22, ready service records ["us-east-001", "us-south-001", "us-west-001"]): Error: rendering the inner door for noisetable-marketing/prod from the ready service records of [\"us-east-001\", \"us-south-001\", \"us-west-001\"]: service noisetable-marketing: mount \"\" is served by Bundle, which has no resolved address yet. Refusing to write a partial route table -- a missing mount does not 503, it falls through to the root mount and serves the wrong component with a 200. Source: oss/yubaba/crates/cloud/src/inner_door.rs:348-349. Live verification after the failed attempt: https://noisetable.com/ 200 (title correct), /app/ 200, GET /api/issues 404 (body is the marketing SPA HTML shell, not the issues service’s body-free projection), GET /issues 500 (F25’s client error page). No rollback needed – / and /app/ never left 200.”)
@yah:handoff(“FIXED in app/yah/cli/src/cloud.rs. Root cause is R931-B7 defect (2), not an ident or config fault: deploy_inner_door read ready service records ONCE right after the deploy phase (re)deployed the bundle, so the bundle ident had no record yet and InnerDoorPlan::workload refused the table. B7’s own measurements show the identical error passing on a re-run a minute later. FIX: ReadyRecordWait gained a generic resolve_with<T>(read, attempt, idle) loop (resolve_plan is now a thin wrapper over it — one loop, two consumers), and deploy_inner_door runs resolve_addresses + workload() inside it under ReadyRecordWait::APPLY (90s budget, 5s interval), with a one-time stderr notice. The refusal in inner_door.rs:349 is unchanged — a partial table is still never written; it is now retried rather than surfaced on the first read. This also closes B7’s defect (2); B7 keeps only defect (1), the needless re-fork.”)
@yah:verify(“LIVE GATE (not run by me, prod-facing): re-run yah cloud mirror up noisetable-marketing --env prod, then the five-point check in this ticket’s next — R733-T26’s gate.”)
@yah:assumes(“resolve_workload_ident(mirror, ‘noisetable-marketing’, ‘prod’) yields the ident the bundle actually registers (‘noisetable’ per the west passway log in B7). Inferred from B7’s ‘next run a minute later succeeded’, not re-read against the mirror toml.”)
@yah:verify(“cargo test -p yah --lib ready_record_wait = 5 passed / 0 failed, EXIT 0 (new: the_inner_door_waits_for_the_bundle_record_instead_of_refusing_the_table). Log /tmp/r931b6-t2.log.”)
@yah:verify(“LIVE, 2026-09-22, CLI installed via cargo xtask install: five yah cloud mirror up noisetable-marketing --env prod runs from ~/ss/noisetable, all EXIT 0. Each rendered the inner door (/ and /app → 100.64.0.3:41507, /api/issues → 10.128.1.2:4333) and ended ‘noisetable.com is serving this bundle’. Not one hit the ‘no resolved address yet’ refusal, including the runs where the bundle was re-forked just before.”)
Sourcepub fn units(&self) -> Vec<DeployedUnit>
pub fn units(&self) -> Vec<DeployedUnit>
Every distinct unit this door proxies to, in a stable order. What a caller resolving addresses has to answer for.
Sourcepub fn routes_file(
&self,
bundle_ident: &str,
yubaba: &[String],
) -> Result<String>
pub fn routes_file( &self, bundle_ident: &str, yubaba: &[String], ) -> Result<String>
Render the JSON passway reads: each mount names its unit’s mesh ident and every yubaba that could hold that unit’s service record.
No addresses (R936-B12). The table used to bake each unit’s
host:port in at deploy time, so it pointed at whichever node ran the
unit THEN, and a unit the cluster later moved — or a node that died —
left every door routing to an address nothing answered. noisetable.com
went 503 on all three doors that way when us-east-001 was powered off.
Discovering by ident makes the door follow the unit on its next poll.
yubaba must name every node a unit may run on: a unit is only found
where it is polled for. An empty list is refused, since every mount
would then discover nothing and 503.
Source§impl InnerDoorPlan
impl InnerDoorPlan
Sourcepub fn workload_name(&self) -> String
pub fn workload_name(&self) -> String
The workload name / mesh identity for this service’s inner door.
Sourcepub fn routes_path(&self) -> PathBuf
pub fn routes_path(&self) -> PathBuf
Where this door’s route table is materialized on the node.
Sourcepub fn listen_port(&self) -> u16
pub fn listen_port(&self) -> u16
This door’s loopback port — listen_port of the service name.
Sourcepub fn unit_ident(&self, unit: &DeployedUnit, bundle_ident: &str) -> String
pub fn unit_ident(&self, unit: &DeployedUnit, bundle_ident: &str) -> String
The mesh identity whose ready service record carries unit’s address.
The two arms come from different places on purpose, and neither is
derivable from the other. A bundle’s ident is a mirror fact —
BundleSlot::workload_name, which a slot may rename with name = "…" —
so it is handed in. A workload-tier component has no slot to rename it,
so its ident is derived (component_workload_ident).
Sourcepub fn workload(
&self,
listen_port: u16,
bundle_ident: &str,
yubaba: &[String],
) -> Result<Workload>
pub fn workload( &self, listen_port: u16, bundle_ident: &str, yubaba: &[String], ) -> Result<Workload>
Render the supervisable workload: a passway process serving this service’s mount table on loopback.
§Cleartext, and the invariant that makes it safe
PASSWAY_TLS_MODE=plaintext (operator call, 2026-09-09 — see
passway::tls::parse_listener_tls_mode for the full argument). The
short version: no CA issues for 127.0.0.1, so “TLS everywhere” here
means a self-signed leaf plus a way to switch OFF upstream certificate
verification on the public door — a real trust-boundary knob traded
for encrypting a hop that never leaves the loopback interface.
This function cannot violate that invariant even if listen_port is
wrong, because it binds 127.0.0.1 literally and passway refuses the
mode on anything else. The bind is not a parameter.
§Why listen_port is an argument
It is placement-time knowledge, exactly like the upstream addresses: which port is free is a property of the node, not of the config. The caller allocates and passes it, so this stays a pure function of (plan, port, addresses) and is testable without a node.
§The route table travels IN the spec
Not written beside it: WorkloadSpec::files makes the table and the
process that reads it one deploy rather than two, so a redeploy cannot
leave a door serving a stale table. Only kamaji’s native backend
materializes those; every other backend refuses the spec by name rather
than starting the door against a file that is not there.
§Why Workload::Container and not a new Workload variant
TenantPasswayWorkload is a typed variant, so the precedent for one
exists — but it earns that by carrying config kamaji itself must act on
(a domain to match, a PEM pair to re-read on every cold start, an idle
TTL to reap against). An inner door carries none of it: its entire
configuration is an argv, three env vars and one file, all of which
WorkloadSpec already expresses. A variant would buy nothing but
exhaustive-match churn in peer-owned kamaji-proto, which is the trade
R572-F1 already made and recorded.
Trait Implementations§
Source§impl Clone for InnerDoorPlan
impl Clone for InnerDoorPlan
Source§impl Debug for InnerDoorPlan
impl Debug for InnerDoorPlan
impl Eq for InnerDoorPlan
Source§impl PartialEq for InnerDoorPlan
impl PartialEq for InnerDoorPlan
impl StructuralPartialEq for InnerDoorPlan
Auto Trait Implementations§
impl Freeze for InnerDoorPlan
impl RefUnwindSafe for InnerDoorPlan
impl Send for InnerDoorPlan
impl Sync for InnerDoorPlan
impl Unpin for InnerDoorPlan
impl UnsafeUnpin for InnerDoorPlan
impl UnwindSafe for InnerDoorPlan
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,
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
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<K, Q> Equivalent<Q> for K
impl<K, Q> Equivalent<Q> for K
Source§fn equivalent(&self, key: &Q) -> bool
fn equivalent(&self, key: &Q) -> bool
key and return true if they are equal.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