cloud/reconciler/ingress_verify.rs
1//! Is the backend reachable at the port its service record advertises? — the
2//! half [`collate_workspace_ingress`](crate::validate::collate_workspace_ingress)
3//! deliberately cannot answer (R844-F16).
4//!
5//! **Read [§What this does not prove](#what-this-does-not-prove) before
6//! treating a green result as a deploy gate.** That question is narrower than
7//! "does the front door work", and on 2026-09-03 the difference took yah.dev
8//! down for four minutes.
9//!
10//! `yah cloud ingress collate` is pure: no network, no credentials. That purity
11//! is load-bearing — it is what lets `xtask/tests/mirror_ingress.rs` plan this
12//! camp's real `.yah/services/` tree as a unit test — but it means the
13//! `100.64.0.3:8080` it prints is an **echo of declarations**: the mirror's
14//! `upstream_host` pin, or since R844-F12 the placement machine's declared
15//! `[registration].mesh_ipv4`. Collate attests that the mirrors *cohere*. It
16//! has never attested that anything answers.
17//!
18//! That distinction is not academic in this repo. The apex's `upstream_host`
19//! was once left at `127.0.0.1` after a second front door existed, and collate
20//! rendered it exactly as confidently as it renders a correct one — for the
21//! nineteen days the site was frozen.
22//!
23//! ## Four claims, and this module measures only the third
24//!
25//! | claim | evidence | who says it |
26//! |---|---|---|
27//! | "the mirrors agree on what fronts what" | the declaration tree | `collate` |
28//! | "a ready record exists for it" | `GET /service-records?ready=true` | [`ServiceRecordFanout`] |
29//! | "that record's address answers" | a TCP connect | **this module** |
30//! | "the front door is configured to dial it" | an HTTPS GET of the hostname, compared against the backend | **this module**, [`apply_public_path`] (R844-F18) |
31//!
32//! The second is *yubaba's opinion*, and R844-B11 is the proof it can be wrong
33//! while looking right: us-west-001 advertised `100.64.0.3:4325` as `Ready`
34//! while the workload answered on `100.64.0.1:4325`. The record was healthy,
35//! the record's own address refused connections, and every layer above it
36//! reported success. A connect is the only step that can catch that, because it
37//! is the only step that asks the world instead of asking a declaration.
38//!
39//! ## What this does not prove
40//!
41//! **Updated by R844-F18 — the gap this section describes is now closed by
42//! [`apply_public_path`], and the history below is why that check exists and
43//! what it is still not.** [`verify_collation`] alone proves **the backend is
44//! reachable at the port the record advertises**; on its own it never proves
45//! **the front door is configured to dial that port**. Those two coincide only
46//! while a pin forces them to — so that pass is *weakest in exactly the
47//! portless configuration it was built to certify*.
48//!
49//! That is not a theoretical gap. R844-T10 deleted the apex's `port` pin on the
50//! strength of a green run from this verb, on 2026-09-03:
51//!
52//! * kamaji allocated a **new** port for the redeployed workload — 34759;
53//! * the service record correctly advertised 34759;
54//! * this verb dialed 34759, found it open, and printed
55//! *"2 rule(s) … 2 proven to serve, 0 not"*;
56//! * the public got **HTTP 503** for four minutes, because the running passway
57//! was still configured for 8080 and **nothing reconfigures it**.
58//!
59//! So the verb reported success during the live outage it was built to prevent.
60//! The prior measurement that authorised the edit was green for a reason that
61//! did not survive a real deploy: it stripped the pins from a *copy* of the
62//! config while the old workload was still bound to 8080, which is the one
63//! arrangement in which the record and the front door cannot disagree.
64//!
65//! This is [`collate`]'s own limitation one level up — collate attests
66//! coherence and not reachability; a dial attests reachability of a *record*,
67//! and not that the front door agrees with that record.
68//!
69//! [`apply_public_path`] closes it by traversing the **public path** and
70//! comparing: it fetches the publish beacon
71//! ([`publish_beacon`](crate::reconciler::publish_beacon)) from
72//! `https://<hostname>/` and from each discovered backend, and fails the rule
73//! when the two serve different publishes. A bare `GET /` would not have done —
74//! a door pointed at the wrong backend answers 200 with a plausible page, which
75//! is how the apex stayed frozen for nineteen days. The beacon is the only
76//! object on either side that says *which publish this is*.
77//!
78//! **Two things that check is still not.** It is a **detector of the current
79//! state**, not a simulation of a pending edit — run it before and after an
80//! apply and require both green. And the public fetch goes wherever **DNS**
81//! sends it, so a hostname on two front doors is measured at one of them; the
82//! verdict says so in a note rather than implying it covered both.
83//!
84//! **So: a green [`verify_collation`] alone is necessary, not sufficient. Do
85//! not use it without the public-path pass as the gate on removing a pin.**
86//!
87//! [`collate`]: crate::validate::collate_workspace_ingress
88//!
89//! ## Why this is a separate verb and not a flag on `collate`
90//!
91//! Because the purity above is the feature. A `--live` flag would put a network
92//! read inside the function eleven offline tests call, and the pressure to make
93//! those tests pass would then push the network read towards being optional in
94//! a way that silently degrades. A sibling verb costs nothing that flag would
95//! not cost more.
96//!
97//! ## Pure, like everything else on this seam
98//!
99//! Nothing here opens a socket. The caller does the fanout read and the dial,
100//! and hands both in as data — the fifth instance of the shape
101//! [`resolve_ingress_placements`](crate::reconciler::resolve_ingress_placements),
102//! [`IngressPlan::resolve_upstreams`], [`IngressPlan::resolve_ports`] and
103//! [`IngressPlan::resolve_upstreams_from_config`] already use. So the verdict
104//! logic — which is where the interesting mistakes live — is unit-testable
105//! against a fake fleet with no network at all.
106//!
107//! ## A subset renders like a success, so a subset is a failure
108//!
109//! The failure class this whole relay exists to remove is a partial answer that
110//! looks complete. A rule placed on two nodes that resolves one address is
111//! *half a front door*: it renders, it dials, it serves — and half the fleet's
112//! traffic capacity is silently absent. [`RuleVerdict`] therefore fails a rule
113//! whose resolved backends do not cover its whole declared placement, and names
114//! the node that went missing along with why.
115//!
116//! @yah:ticket(R844-F18, "Verify the PUBLIC path — an HTTPS GET of the hostname through the real front door, compared against what the record claims")
117//! @yah:status(review)
118//! @yah:assignee(agent:bundle-anthropic-ashguard)
119//! @yah:at(2026-09-04T01:55:44Z)
120//! @yah:parent(R844)
121//! @yah:next("KEEP `collate` PURE (R772, R844-F5, R844-F12, R844-F16 each fought for this) and prefer a third verb or a flag on `verify` over touching it. `cargo test -p xtask --test main mirror_ingress` planning the camp's REAL .yah/services tree with no network is the property being protected; it is currently 11 green.")
122//! @yah:verify("And it must still be green on the healthy fleet: https://yah.dev/ through both declared front doors, agreeing with what `yah cloud ingress verify` resolves.")
123//! @yah:gotcha("A LIVE-OUTAGE-DETECTOR IS NOT AUTOMATICALLY A PRE-FLIGHT GATE, and this ticket should be honest about which it is building. An HTTPS GET proves the CURRENT front door serves; it cannot tell you what a config change is ABOUT to do, because the front door has not been reconfigured yet. That may still be enough — run it before and after an apply and require both green — but say so explicitly rather than letting a future reader assume it gates the edit. The failure that started this was precisely someone (twice) treating a green from the wrong vantage point as authorisation.")
124//! @yah:next("A SHAPE FOR THIS, from @Ashguard:griffin (session:75f87e36, the session that caused the outage behind it) — offered as a starting point, not a settled design. The open question on this ticket is whether an HTTPS GET is a pre-flight GATE or only an outage DETECTOR. I think it is only ever a detector, and that the ticket is really TWO checks answering two different questions:\n\n 1. PRE-FLIGHT, and it is not a probe at all — it is a COMPARISON. Read what the front door is actually configured to dial, FROM THE DOOR, and compare it against what the service record says the backend is. That is the check that would have caught tonight's outage BEFORE it happened, because the two disagreed (passway held 8080, the record advertised 34759) at a moment when every probe of either side in isolation was green. Note what makes it different from R844-F16's verify: verify reads the record and dials the port the record names, so both of its inputs come from the same side of the disagreement. The door's own configured value is the input nobody currently reads, and it is the only one that makes the comparison possible.\n\n 2. POST-CONDITION, which is where the HTTPS GET belongs — an unauthenticated GET of the public hostname through the real front door, asserted AFTER an apply, once R844-B19 guarantees the door has actually been repointed. Today that assertion cannot be trusted to mean anything, because B19's ordering bug means the door may never have been updated at all; the GET would just be re-measuring the old configuration and calling it a pass.\n\nWHY THIS PAIRS F18 WITH B19 RATHER THAN DUPLICATING IT: B19 makes the front-door update reliably HAPPEN; (2) is the assertion that it DID; (1) is the only one of the three that can speak before a change is applied. Sequencing follows from that — do not land (2) before B19, or it encodes today's broken ordering as the expected one.\n\nTHE CAVEAT I CANNOT RESOLVE AND WHOEVER TAKES THIS SHOULD NOT ASSUME AWAY: even (1) compares two CURRENT states. It does not simulate what a config change is about to do, which is what we actually wanted to know tonight. It catches an existing divergence, and it would catch this specific class because the divergence appears the moment the workload is redeployed — but it is not a general \"is this edit safe\" oracle, and nothing in this design is. If someone needs that, it is a different and much larger ticket, and it should be filed as one rather than smuggled in here.")
125//! @yah:handoff("LANDED. `yah cloud ingress verify` now takes the fourth step, on by default. For every hostname in the collation it fetches `https://<hostname>/.well-known/yah-publish.json`, for every discovered backend it fetches the same object over the mesh, and it FAILS the rule when the two name different publishes. Verdict logic is `apply_public_path` in oss/yubaba/crates/cloud/src/reconciler/ingress_verify.rs, pure like the rest of that seam — the CLI does both fetches and hands them in as `PublicReadings`, so the interesting mistakes are unit-testable against a fake fleet. New surface: `BeaconFetch`, `PublicReadings`, `apply_public_path`, three `VerifyFinding` arms, `fetch_beacon` and `--skip-public` in app/yah/cli/src/cloud.rs.")
126//! @yah:handoff("THE COMPARISON IS THE CONTENT, NOT THE GET — this is the design decision, and the ticket title's \\\"HTTPS GET\\\" understates it. A bare `GET /` proves only that something answered: a door pointed at the wrong backend returns 200 with a plausible page, which is exactly how the apex stayed frozen for nineteen days with every signal green. The publish beacon (`publish_beacon.rs`, `BEACON_KEY = .well-known/yah-publish.json`, R703-B4) is the one object on either side of the door that says WHICH PUBLISH THIS IS, so fetching it from both and comparing digests is what turns \\\"something answered\\\" into \\\"the front door is serving the backend the records name\\\". Reused rather than invented — the object already exists, `mesofact serve` already answers it out of the bundle, and R2 static publishes already write it.")
127//! @yah:handoff("THE TICKET'S OWN OPEN QUESTION — GATE OR DETECTOR — ANSWERED, AND ANSWERED THE WAY ITS FILER EXPECTED: **detector**, and the code says so in its own output rather than leaving a reader to infer it. `apply_public_path`'s doc, the CLI `--help`, the summary line printed on every run, W267 and the service-toml guide all now carry the same sentence: it compares two CURRENT states, cannot simulate an edit you have not applied, and the protocol is run-before-and-after-and-require-both-green. That is enough for the failure it was built for — the divergence appears the moment the workload is redeployed onto a new port — and it is deliberately NOT sold as an \\\"is this edit safe\\\" oracle. @Ashguard:griffin's caveat on this ticket was right and is preserved as the design, not assumed away.")
128//! @yah:handoff("GRIFFIN'S PART (1), THE PRE-FLIGHT \\\"READ THE DOOR'S OWN CONFIG AND COMPARE\\\", IS NOT WHAT SHIPPED — say so plainly rather than letting the ticket read as fully covered. Their shape proposed reading what passway is CONFIGURED to dial, from the door, and comparing that against the record. This ships the equivalent comparison one layer out: what the door ACTUALLY SERVES versus what the backend serves. Why that substitution rather than the config read: the running passway holds its upstreams in container env (`PASSWAY_UPSTREAMS`, `PASSWAY_UPSTREAM_SOURCE=static` — oss/passway/crates/passway/src/main.rs), so reading it means an SSH or a docker inspect per door, i.e. credentials and a shell on a production box inside a read-only verb. The served comparison needs neither, catches the same divergence class (it is true exactly when the door is dialing something else), and additionally catches a stale edge cache, which a config read cannot see. What the config read would still buy is naming WHY they diverge; that is a genuinely separable ticket and is not smuggled in here.")
129//! @yah:handoff("A FAILURE IS ONLY A FAILURE WHEN SOMETHING WAS ACTUALLY COMPARED — the design care, and the thing a careless version of this gets wrong in the direction that matters. Three arms produce a NOTE and leave the rule clean rather than a finding: a hostname that answers 200 with no beacon (it fronts something that is not a mesofact publish — a limit on the check, not a fault of the host, and failing it would red every non-bundle hostname in the fleet); a public 200 with no comparable backend (the note says verbatim that this proves the hostname is up and NOT that it is fronting the discovered backend); and a hostname nobody measured. Two arms fail: a transport failure, and a non-2xx. One arm is the point: `PublicBackendDivergence`, which names both digests and the backend address it compared against. And a backend that answers without a beacon adds nothing — `verify_collation` already dialed it and said what it found, so a second opinion phrased as an error would double-count one fact.")
130//! @yah:gotcha("THE LIMIT I COULD NOT DESIGN AWAY, AND DID NOT HIDE: the public fetch goes wherever DNS sends it, so a hostname published through two front doors is measured at ONE of them and this pass cannot say which. A divergence affecting only the other door reads as clean. Every verdict for such a hostname carries a note saying so — but only once a comparison actually happened, since on a rule where nothing could be compared that note is noise stacked on the finding. Closing it needs a fetch pinned to each door's public address with a `Host` override, which needs a public IP per door that nothing in the `Collation` carries today. Pinned by `a_hostname_on_two_front_doors_is_noted_as_measured_at_only_one`.")
131//! @yah:verify("UNIT: `cargo test --manifest-path oss/yubaba/Cargo.toml -p yah-cloud --lib ingress_verify` = 19 passed / 0 failed (11 before, +8 new). THE ONE THAT MATTERS IS `a_503_at_the_apex_fails_a_rule_whose_mesh_side_is_entirely_green` — it reproduces the 2026-09-03 outage as a unit test, asserting FIRST that `verify_collation` alone reports the rule clean (that assertion is the precondition, because a green mesh side is what reported success during the outage) and THEN that the public leg fails it. The other seven cover the stale-serve shape (200 serving a different digest than the backend), a transport failure, a hostname with no beacon, a public 200 with nothing to compare, an unmeasured hostname, a hostname on two doors, and the fully-clean case where the two digests match and NOTHING is caveated. Wider: `-p yah-cloud --lib ingress` = 94 passed / 0 failed; `-p yah-cloud --lib` = 1019 passed / 0 failed / 4 ignored (1011 before, so +8 and nothing lost).")
132//! @yah:verify("THE PURITY CANARY, which this ticket's own `next` named as the property to protect: `cargo test -p xtask --test main mirror_ingress` = 11 passed / 0 failed. `collate` was not touched — the public leg is a fourth step on `verify`, per the same reasoning R844-F16 used to make `verify` a sibling verb rather than a flag. `cargo check --workspace --all-targets` cargo-exit=0, zero `^error` lines. Installed and re-installed with `cargo xtask install` (sha256 b751f470e329253765075e5c050256f4e848ae1e9265f55fd6965f024bafbf24, `PATH resolves here`), per this relay's standing gotcha that `cargo build` does not update the binary an operator runs.")
133//! @yah:verify("THIS TICKET'S STATED ACCEPTANCE TEST — \\\"green on the healthy fleet, https://yah.dev/ through both declared front doors, agreeing with what verify resolves\\\" — WAS **NOT** MET, AND NOT BECAUSE OF THIS CHANGE. The fleet is not healthy right now: the mesh coordination server is down (`cloud.mesh.yah.dev` -> 15.204.89.240 REFUSES :443 and :80 while :22 answers, and `tailscale status` reports this machine logged out with \\\"fetch control key ... connection refused\\\"), so NO 100.64.0.0/10 address is reachable from here and the mesh half of the check cannot run at all. Filed as R858 with the full measurement chain. I am recording this as unmet rather than reporting a partial green.")
134//! @yah:verify("WHAT THE LIVE RUN DID PROVE, and it is more than nothing: `yah cloud ingress verify --path .` from the freshly installed binary fetched `https://yah.dev/.well-known/yah-publish.json` over the real public internet, parsed it, and — because no backend beacon could be read across the dead mesh — printed exactly the right sentence instead of a pass: \\\"https://yah.dev answers with a publish beacon, but no discovered backend served one to compare it against, so this proves the hostname is up and NOT that it is fronting the discovered backend\\\". Independently confirmed by hand: `curl https://yah.dev/` = HTTP 200 in 0.81s and the beacon is `{\\\"prefix\\\":\\\"bundle/yah-marketing\\\",\\\"digest\\\":\\\"bfb47cd42468b080c474193fa6091ec273b6c99ab5a8a3b91d9c847cd7278551\\\",\\\"files\\\":39}`. So the public leg ran end to end against production and, on a real unplanned failure it was never designed for, refused to overclaim — which is the behaviour this ticket exists to install. `--skip-public` also exercised live: every verdict then reads \\\"the public path was not checked for yah.dev — this verdict speaks only for the mesh side, which is necessary and not sufficient\\\", and the summary names the dropped claim.")
135//! @yah:next("RE-RUN THE ACCEPTANCE TEST ONCE R858 CLEARS — it is one command and it is the only thing outstanding on this ticket: `yah cloud ingress verify --path .` must exit 0 with both front doors' rules reporting the mesh dial open AND the public beacon matching the backend's. Until the mesh is reachable that run measures nothing about the fourth claim.")
136//! @yah:notify_on(R858, "The mesh is reachable again — run this ticket's outstanding acceptance test: `yah cloud ingress verify --path .` must exit 0 with BOTH front doors reporting the mesh dial open AND the public beacon digest matching the backend's. It could not be run at landing time because no 100.64.0.0/10 address answered. If it passes, that closes the last open item here; if it fails, read which of the two legs failed before touching the code — the mesh leg is R844-F16's and predates this change.")
137
138use std::collections::{BTreeMap, BTreeSet};
139use std::fmt;
140
141use anyhow::Result;
142
143use crate::config::{IngressProvider, MachineConfig};
144use crate::reconciler::domain::{public_origins, PasswayOrigin};
145use crate::reconciler::ingress::{Collation, IngressPlan, IngressRule};
146use crate::reconciler::service_discovery::ServiceRecordFanout;
147
148/// What a TCP connect to one resolved `host:port` actually did.
149///
150/// Two arms, no `Unknown`: unlike a discovery read — which can fail to *ask* a
151/// node, and whose whole vocabulary
152/// ([`RecordVisibility`](crate::reconciler::RecordVisibility)) exists to keep
153/// that apart from an empty answer — a dial either completed or it did not.
154/// The attempt is the evidence.
155#[derive(Debug, Clone, PartialEq, Eq)]
156pub enum DialOutcome {
157 /// The connect completed. This is the only positive evidence in the whole
158 /// ingress stack that anything is listening.
159 Open {
160 /// How long the connect took, for an operator eyeballing a slow path.
161 millis: u128,
162 },
163 /// The connect did not complete, in the transport's own words — refused,
164 /// timed out, no route.
165 Closed(String),
166}
167
168impl DialOutcome {
169 /// One short label for a summary line.
170 pub fn as_str(&self) -> &'static str {
171 match self {
172 Self::Open { .. } => "open",
173 Self::Closed(_) => "CLOSED",
174 }
175 }
176
177 /// Did anything answer?
178 pub fn is_open(&self) -> bool {
179 matches!(self, Self::Open { .. })
180 }
181}
182
183impl fmt::Display for DialOutcome {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 match self {
186 Self::Open { millis } => write!(f, "open ({millis}ms)"),
187 Self::Closed(why) => write!(f, "CLOSED — {why}"),
188 }
189 }
190}
191
192/// One dialed address and what came back.
193#[derive(Debug, Clone, PartialEq, Eq)]
194pub struct EndpointCheck {
195 /// The `host:port` dialed — exactly what the front door would dial.
196 pub address: String,
197 pub outcome: DialOutcome,
198}
199
200/// How one rule's placement resolved against a live discovery read.
201///
202/// Recorded *during* resolution rather than reconstructed after it, because two
203/// of these three facts are unrecoverable from the resolved plan: whether the
204/// address came from a pin or from the fleet, and which placement node supplied
205/// it. Both are exactly what an operator needs to act on a failure.
206#[derive(Debug, Clone, PartialEq, Eq)]
207pub struct RuleResolution {
208 pub hostname: String,
209 pub slot: String,
210 /// The rule already carried an address before the read — the slot pins
211 /// `upstream_host`, and the pin wins everywhere (R844-F5/F12).
212 ///
213 /// Reported because it changes what the dial *means*: a pinned rule's
214 /// connect measures whether a **declaration** answers, and the fleet has no
215 /// say in what was dialed. That is the 127.0.0.1 case above.
216 pub pinned: bool,
217 /// Placement nodes that answered with a ready record on this rule's port.
218 pub covered: Vec<String>,
219 /// Placement nodes that did not, each with the reason — an `Unknown` node's
220 /// own words, or "answered, and has no such record".
221 pub missing: Vec<(String, String)>,
222}
223
224impl RuleResolution {
225 /// Key under which a resolution is looked up once collation has grouped the
226 /// rules by node.
227 ///
228 /// `(hostname, slot)` rather than hostname alone: one hostname is fronted
229 /// through exactly one provider (`collate_front_doors` rejects otherwise),
230 /// but the slot is what an operator opens to fix a finding, so carrying it
231 /// costs nothing and a message without it points at no file.
232 pub fn key(&self) -> (String, String) {
233 (self.hostname.clone(), self.slot.clone())
234 }
235}
236
237/// Everything the resolution pass learned, keyed for the verify pass.
238pub type RuleResolutions = BTreeMap<(String, String), RuleResolution>;
239
240/// Why one rule is not proven to serve.
241#[derive(Debug, Clone, PartialEq, Eq)]
242pub enum VerifyFinding {
243 /// No port resolved — the slot declares `fronted = true`, pins no number,
244 /// and no ready record supplied one.
245 PortUnresolved,
246 /// No address resolved. The detail carries whether the read was complete,
247 /// because "the workload is not up" and "the node that has it could not be
248 /// seen" are different facts (R844-F4).
249 NoBackend { detail: String },
250 /// Fewer backends than the rule's declared placement. Fatal on purpose —
251 /// see the module doc.
252 PartialPlacement {
253 covered: Vec<String>,
254 missing: Vec<(String, String)>,
255 },
256 /// A resolved address did not answer. The one finding no offline pass could
257 /// ever produce.
258 Unreachable {
259 address: String,
260 why: String,
261 /// The address came from the slot's `upstream_host` rather than from a
262 /// service record. Carried because it changes *which* claim just got
263 /// falsified — a discovered address that refuses means the record and
264 /// the world disagree, a pinned one means the TOML is wrong — and a
265 /// message that names the wrong one sends the operator to the wrong
266 /// file. Caught by running this verb against a mirror with a bogus pin.
267 from_pin: bool,
268 },
269 /// No resolution was recorded for this rule at all — a bug in the caller's
270 /// wiring rather than a fact about the fleet, reported instead of silently
271 /// rendering the rule as fine.
272 Unresolved,
273 /// The public hostname did not answer at all — DNS, TLS, connect, timeout
274 /// (R844-F18). The one finding that speaks for the public rather than for
275 /// the mesh.
276 PublicPathFailed { hostname: String, why: String },
277 /// The public hostname answered with a status the public would read as
278 /// broken. `503` here is the 2026-09-03 outage exactly: every mesh dial
279 /// open, every record correct, and this the only signal that disagreed.
280 PublicPathStatus { hostname: String, status: u16 },
281 /// The public path and the backend the service record names are serving
282 /// **different publishes** (R844-F18). The finding this ticket exists for:
283 /// it is true precisely when the front door is dialing something other than
284 /// the backend the rest of this report just proved reachable.
285 PublicBackendDivergence {
286 hostname: String,
287 public_digest: String,
288 address: String,
289 backend_digest: String,
290 },
291}
292
293impl VerifyFinding {
294 /// Human-readable finding, in the imperative where there is something to do.
295 pub fn message(&self) -> String {
296 match self {
297 Self::PortUnresolved => "no port resolved — the slot declares `fronted = true` \
298 without `port`, and no in-scope node reported a ready record naming one. \
299 Either the workload is not up, or its yubaba predates named ports and the \
300 record is ambiguous; pin `port = <n>` on the slot to publish it anyway."
301 .to_string(),
302 Self::NoBackend { detail } => {
303 format!("no backend resolved — {detail}")
304 }
305 Self::PartialPlacement { covered, missing } => format!(
306 "resolves {} of {} declared placement node(s) — a SUBSET that renders like a \
307 whole front door. Serving: {}. Missing: {}.",
308 covered.len(),
309 covered.len() + missing.len(),
310 covered.join(", "),
311 missing
312 .iter()
313 .map(|(m, why)| format!("{m} ({why})"))
314 .collect::<Vec<_>>()
315 .join("; ")
316 ),
317 Self::Unreachable {
318 address,
319 why,
320 from_pin,
321 } => {
322 let origin = if *from_pin {
323 "The slot PINS this address in `upstream_host`, so nothing discovered it and \
324 nothing but this connect could have contradicted it — fix the pin, or drop \
325 it and let the fleet answer."
326 } else {
327 "A ready service record is yubaba's OPINION; this connect is the measurement, \
328 and they disagree (R844-B11)."
329 };
330 format!("{address} did not answer — {why}. {origin}")
331 }
332 Self::Unresolved => "no discovery resolution was recorded for this rule — the \
333 verify pass planned it but never resolved it, which is a wiring bug in the \
334 caller, not a fact about the fleet."
335 .to_string(),
336 Self::PublicPathFailed { hostname, why } => format!(
337 "https://{hostname}/ did not answer — {why}. Every line above measures the \
338 MESH side; this is the only one that measures what the public gets, so a \
339 report that is otherwise clean means the front door is not reaching the \
340 backend the records name."
341 ),
342 Self::PublicPathStatus { hostname, status } => format!(
343 "https://{hostname}/ answered HTTP {status}. The backend above is reachable at \
344 the port its service record advertises, so the front door is configured to \
345 dial something else — that pair of facts is exactly the 2026-09-03 outage \
346 (R844-T10), where a redeploy moved the port and nothing reconfigured the door."
347 ),
348 Self::PublicBackendDivergence {
349 hostname,
350 public_digest,
351 address,
352 backend_digest,
353 } => format!(
354 "https://{hostname}/ and the backend its service record names are serving \
355 DIFFERENT publishes: the public path returns digest {public_digest}, {address} \
356 returns {backend_digest}. The hostname answers, so nothing else in this report \
357 can see it — the front door is dialing a backend other than the discovered one, \
358 or an edge cache is still holding the previous publish."
359 ),
360 }
361 }
362}
363
364/// One rule's verdict on one front door.
365#[derive(Debug, Clone, PartialEq, Eq)]
366pub struct RuleVerdict {
367 /// Node whose front door publishes this rule.
368 pub machine: String,
369 /// Front-door provider, as `IngressProvider::as_str`.
370 pub provider: String,
371 pub hostname: String,
372 pub slot: String,
373 pub port: Option<u16>,
374 /// The address came from the slot's `upstream_host` pin, so the dial below
375 /// measured a declaration rather than a discovered fact.
376 pub pinned: bool,
377 /// Every resolved backend, dialed.
378 pub endpoints: Vec<EndpointCheck>,
379 /// Empty means proven: every declared placement node resolved and every
380 /// resolved address answered.
381 pub findings: Vec<VerifyFinding>,
382 /// Non-fatal context — today, the placement gaps of a *pinned* rule, where
383 /// the pin overrides placement so a gap is worth saying and not worth
384 /// failing.
385 pub notes: Vec<String>,
386}
387
388impl RuleVerdict {
389 /// Proven to serve.
390 pub fn is_ok(&self) -> bool {
391 self.findings.is_empty()
392 }
393
394 /// Every address this verdict dialed, in resolution order.
395 pub fn addresses(&self) -> Vec<String> {
396 self.endpoints
397 .iter()
398 .map(|e| e.address.clone())
399 .collect()
400 }
401
402 /// This rule's port for a message, or `<unresolved>` — the same spelling
403 /// [`IngressRule::port_label`] uses, so a verify line and a collate line
404 /// describing one unresolved rule read identically.
405 pub fn port_label(&self) -> String {
406 self.port
407 .map(|p| p.to_string())
408 .unwrap_or_else(|| "<unresolved>".to_string())
409 }
410}
411
412/// Every rule on every collated front door, verified.
413#[derive(Debug, Clone, Default, PartialEq, Eq)]
414pub struct VerifyReport {
415 pub verdicts: Vec<RuleVerdict>,
416}
417
418impl VerifyReport {
419 /// Rules that are not proven to serve.
420 pub fn failures(&self) -> impl Iterator<Item = &RuleVerdict> {
421 self.verdicts.iter().filter(|v| !v.is_ok())
422 }
423
424 /// Nothing to fix.
425 pub fn is_clean(&self) -> bool {
426 self.failures().next().is_none()
427 }
428}
429
430// ── The public path (R844-F18) ───────────────────────────────────────────────
431
432/// What one HTTP GET of a publish beacon returned — the caller's measurement,
433/// handed in as data like every other input on this seam.
434///
435/// The URL is always
436/// `<base>/.well-known/yah-publish.json` ([`publish_beacon::BEACON_KEY`]),
437/// because that object is the only thing on either side of the comparison that
438/// *identifies which publish is being served*. A bare `GET /` cannot do this
439/// job: a front door pointed at the wrong backend still answers 200 with a
440/// plausible page, which is how `yah.dev` stayed frozen for nineteen days with
441/// every signal green.
442///
443/// [`publish_beacon::BEACON_KEY`]: crate::reconciler::publish_beacon::BEACON_KEY
444#[derive(Debug, Clone, PartialEq, Eq)]
445pub enum BeaconFetch {
446 /// The request completed.
447 Answered {
448 status: u16,
449 /// The `digest` field of the beacon, when the body parsed as one.
450 /// `None` when it did not — a 404, or a hostname fronting something
451 /// that is not a mesofact publish. That is a limit on the comparison,
452 /// not a failure of the host, and is reported as a note.
453 digest: Option<String>,
454 },
455 /// The request did not complete — DNS, TLS, connect, timeout.
456 Failed(String),
457}
458
459/// Everything the caller measured on the public path, keyed for the pure pass.
460///
461/// Two maps rather than one because the two sides are addressed differently and
462/// deduplicated differently: a hostname is fetched once however many front doors
463/// publish it, and a backend address is fetched once however many hostnames
464/// resolve to it.
465#[derive(Debug, Clone, Default, PartialEq, Eq)]
466pub struct PublicReadings {
467 /// `hostname` → what `https://<hostname>/.well-known/yah-publish.json`
468 /// returned. A hostname absent from this map was not measured, and
469 /// [`apply_public_path`] says so rather than passing it.
470 pub public: BTreeMap<String, BeaconFetch>,
471 /// `host:port` → what `http://<host:port>/.well-known/yah-publish.json`
472 /// returned, read over the mesh. This is "what the record claims", made
473 /// comparable.
474 pub backends: BTreeMap<String, BeaconFetch>,
475}
476
477/// Add the public-path verdict to a report that so far only knows the mesh
478/// side (R844-F18).
479///
480/// ## The claim this closes
481///
482/// [`verify_collation`] answers *"is the backend reachable at the port its
483/// record advertises"*. This answers *"and does the front door actually reach
484/// it"* — the fourth row of the table in the module docs, which read `nothing
485/// yet` until this landed. It closes it by comparing two publish beacons: the
486/// one the **public** gets through the real front door, and the one the
487/// **backend the record names** serves. They agree only if the door is dialing
488/// that backend.
489///
490/// ## Detector, not oracle — read this before using it as a gate
491///
492/// It compares two *current* states. It cannot tell you what a config change is
493/// about to do, because the front door has not been reconfigured yet: run it
494/// before and after an apply and require both green. That is enough for the
495/// failure it was built for — the divergence appears the moment the workload is
496/// redeployed onto a new port — and it is not a general "is this edit safe"
497/// oracle. Nothing in this module is.
498///
499/// ## The limit that cannot be designed away here
500///
501/// The public fetch goes wherever **DNS** sends it. A hostname published
502/// through two front doors is measured at one of them, and this pass cannot say
503/// which — so a divergence that affects only the other door reads as clean. The
504/// verdicts for such a hostname carry a note saying so; closing it needs a fetch
505/// pinned to each door's public address with a `Host` override, which needs a
506/// public IP per door that nothing in the collation carries today.
507pub fn apply_public_path(report: &mut VerifyReport, readings: &PublicReadings) {
508 let doors_per_hostname = report.verdicts.iter().fold(
509 BTreeMap::<String, usize>::new(),
510 |mut acc, v| {
511 *acc.entry(v.hostname.clone()).or_default() += 1;
512 acc
513 },
514 );
515
516 for verdict in &mut report.verdicts {
517 let Some(fetched) = readings.public.get(&verdict.hostname) else {
518 verdict.notes.push(format!(
519 "the public path was not checked for {} — this verdict speaks only for the mesh \
520 side, which is necessary and not sufficient",
521 verdict.hostname
522 ));
523 continue;
524 };
525
526 let public_digest = match fetched {
527 BeaconFetch::Failed(why) => {
528 verdict.findings.push(VerifyFinding::PublicPathFailed {
529 hostname: verdict.hostname.clone(),
530 why: why.clone(),
531 });
532 continue;
533 }
534 BeaconFetch::Answered { status, .. } if !(200..300).contains(status) => {
535 verdict.findings.push(VerifyFinding::PublicPathStatus {
536 hostname: verdict.hostname.clone(),
537 status: *status,
538 });
539 continue;
540 }
541 BeaconFetch::Answered { digest, .. } => digest,
542 };
543
544 let Some(public_digest) = public_digest else {
545 verdict.notes.push(format!(
546 "https://{} answers, but serves no publish beacon, so the public path could not \
547 be COMPARED against the backend — only that something is there",
548 verdict.hostname
549 ));
550 continue;
551 };
552
553 let mut compared = false;
554 for endpoint in &verdict.endpoints {
555 match readings.backends.get(&endpoint.address) {
556 Some(BeaconFetch::Answered {
557 digest: Some(backend_digest),
558 ..
559 }) => {
560 compared = true;
561 if backend_digest != public_digest {
562 verdict.findings.push(VerifyFinding::PublicBackendDivergence {
563 hostname: verdict.hostname.clone(),
564 public_digest: public_digest.clone(),
565 address: endpoint.address.clone(),
566 backend_digest: backend_digest.clone(),
567 });
568 }
569 }
570 // A backend that answers without a beacon, or does not answer
571 // at all, leaves nothing to compare against. Not a finding of
572 // its own: `verify_collation` already dialed it and said what
573 // it found, and a second opinion phrased as an error would
574 // double-count one fact.
575 _ => {}
576 }
577 }
578
579 if !compared {
580 verdict.notes.push(format!(
581 "https://{} answers with a publish beacon, but no discovered backend served one \
582 to compare it against, so this proves the hostname is up and NOT that it is \
583 fronting the discovered backend",
584 verdict.hostname
585 ));
586 } else if doors_per_hostname
587 .get(&verdict.hostname)
588 .copied()
589 .unwrap_or(0)
590 > 1
591 {
592 // Only worth saying once a comparison actually happened: on a rule
593 // where nothing could be compared, the note above is the finding
594 // and this one is noise stacked on top of it.
595 verdict.notes.push(format!(
596 "{} is published through more than one front door and the public fetch went \
597 wherever DNS sent it, so this compares ONE of them",
598 verdict.hostname
599 ));
600 }
601 }
602}
603
604/// Fill one plan's rules from a live fanout **without stopping at the first
605/// rule that cannot be dialed**, recording how each one resolved.
606///
607/// Deliberately not [`IngressPlan::resolve_upstreams_from`], though it applies
608/// the same precedence (a rule that already has an address keeps it — the pin
609/// always wins) and reads the same `upstreams_for`. That method `bail!`s on the
610/// first undialable rule, which is right for an *apply*: publishing a front door
611/// that is 80% correct is worse than publishing none. It is wrong for a
612/// *verifier*, whose entire job is the complete picture — an operator who fixes
613/// one rule and re-runs only to be told about the next one has been handed a
614/// linked list instead of a report.
615///
616/// Ports must already be resolved ([`IngressPlan::resolve_ports_from`]):
617/// discovery matches records by port, so a portless rule resolves no address
618/// either and is reported as both.
619pub fn resolve_upstreams_reporting(
620 plan: &mut IngressPlan,
621 fanout: &ServiceRecordFanout,
622) -> Vec<RuleResolution> {
623 let mut out = Vec::new();
624 for rule in &mut plan.rules {
625 let pinned = !rule.upstream_hosts.is_empty();
626 if !pinned {
627 rule.upstream_hosts = fanout.upstreams_for(rule);
628 }
629 let (covered, missing) = placement_coverage(rule, fanout);
630 out.push(RuleResolution {
631 hostname: rule.hostname.clone(),
632 slot: rule.slot.clone(),
633 pinned,
634 covered,
635 missing,
636 });
637 }
638 out
639}
640
641/// Which of a rule's declared placement nodes actually supplied a backend, and
642/// why each of the others did not.
643///
644/// Mirrors [`ServiceRecordFanout::upstreams_for`]'s matching (by port, scoped to
645/// the rule's placement) so the two cannot disagree about what "covered" means —
646/// it answers *which nodes* produced that method's addresses, one level of
647/// detail below what it returns.
648fn placement_coverage(
649 rule: &IngressRule,
650 fanout: &ServiceRecordFanout,
651) -> (Vec<String>, Vec<(String, String)>) {
652 let mut covered = Vec::new();
653 let mut missing = Vec::new();
654 for machine in &rule.machines {
655 match fanout.nodes.get(machine.as_str()) {
656 None => missing.push((
657 machine.clone(),
658 "was never asked — it is not in the set this read fanned out over".to_string(),
659 )),
660 Some(visibility) => match visibility.unknown_reason() {
661 Some(reason) => missing.push((machine.clone(), reason.to_string())),
662 None => {
663 let serving = rule.port.is_some_and(|port| {
664 visibility
665 .records()
666 .iter()
667 .any(|r| r.ports.contains(&port))
668 });
669 if serving {
670 covered.push(machine.clone());
671 } else {
672 missing.push((
673 machine.clone(),
674 match rule.port {
675 Some(port) => format!(
676 "answered, and reports no ready record on port {port}"
677 ),
678 None => "the rule has no resolved port to match a record on"
679 .to_string(),
680 },
681 ));
682 }
683 }
684 },
685 }
686 }
687 (covered, missing)
688}
689
690/// Verify every rule on every collated front door: check the resolution, then
691/// dial what it produced.
692///
693/// `dial` is the caller's TCP connect. Each distinct address is dialed **once**
694/// — a rule published through two front doors is the same backend twice, and
695/// two connects would be two chances to disagree about one fact.
696///
697/// `read_note` is [`ServiceRecordFanout::unknown_note`]: when the fanout could
698/// not see part of the fleet, an empty resolution is `UNKNOWN`, not `absent`,
699/// and every [`VerifyFinding::NoBackend`] says so rather than asserting the
700/// workload is down.
701///
702/// Verdicts come back in front-door order, then rule order — the same walk the
703/// collation itself renders in, so a caller may stream the two side by side
704/// rather than looking each verdict up.
705pub fn verify_collation<D>(
706 collation: &Collation,
707 resolutions: &RuleResolutions,
708 read_note: Option<&str>,
709 mut dial: D,
710) -> VerifyReport
711where
712 D: FnMut(&str) -> DialOutcome,
713{
714 let mut dialed: BTreeMap<String, DialOutcome> = BTreeMap::new();
715 let mut verdicts = Vec::new();
716
717 for door in &collation.front_doors {
718 for rule in &door.rules {
719 let mut findings = Vec::new();
720 let mut notes = Vec::new();
721 let key = (rule.hostname.clone(), rule.slot.clone());
722 let resolution = resolutions.get(&key);
723
724 let pinned = resolution.is_some_and(|r| r.pinned);
725 if resolution.is_none() {
726 findings.push(VerifyFinding::Unresolved);
727 }
728
729 if rule.port.is_none() {
730 findings.push(VerifyFinding::PortUnresolved);
731 }
732
733 if rule.upstream_hosts.is_empty() {
734 findings.push(VerifyFinding::NoBackend {
735 detail: match read_note {
736 Some(note) => format!(
737 "and the discovery read was PARTIAL, so this is UNKNOWN rather than \
738 empty: {note}"
739 ),
740 None => "every node in scope answered and none reports a ready record \
741 for it, so the workload is not serving"
742 .to_string(),
743 },
744 });
745 } else if let Some(res) = resolution {
746 if !res.missing.is_empty() {
747 if pinned {
748 // The pin overrides placement, so a gap is not a
749 // shortfall in what gets published — but it IS the
750 // shape that hid the 127.0.0.1 drift, so it is said
751 // out loud rather than dropped.
752 notes.push(format!(
753 "slot pins `upstream_host`, so this dialed a DECLARATION, not a \
754 discovered address; {} placement node(s) report no ready record \
755 for it: {}",
756 res.missing.len(),
757 res.missing
758 .iter()
759 .map(|(m, why)| format!("{m} ({why})"))
760 .collect::<Vec<_>>()
761 .join("; ")
762 ));
763 } else {
764 findings.push(VerifyFinding::PartialPlacement {
765 covered: res.covered.clone(),
766 missing: res.missing.clone(),
767 });
768 }
769 }
770 }
771
772 let mut endpoints = Vec::new();
773 if let Ok(addrs) = rule.upstreams() {
774 for address in addrs {
775 let outcome = match dialed.get(&address) {
776 Some(prior) => prior.clone(),
777 None => {
778 let outcome = dial(&address);
779 dialed.insert(address.clone(), outcome.clone());
780 outcome
781 }
782 };
783 if let DialOutcome::Closed(why) = &outcome {
784 findings.push(VerifyFinding::Unreachable {
785 address: address.clone(),
786 why: why.clone(),
787 from_pin: pinned,
788 });
789 }
790 endpoints.push(EndpointCheck { address, outcome });
791 }
792 }
793
794 verdicts.push(RuleVerdict {
795 machine: door.machine.clone(),
796 provider: door.provider.as_str().to_string(),
797 hostname: rule.hostname.clone(),
798 slot: rule.slot.clone(),
799 port: rule.port,
800 pinned,
801 endpoints,
802 findings,
803 notes,
804 });
805 }
806 }
807
808 VerifyReport { verdicts }
809}
810
811// ── Undeclared front doors (R858-B27) ────────────────────────────────────────
812
813/// A public origin that is **not** a declared passway front door for
814/// `hostname`, and so should not be able to serve it.
815#[derive(Debug, Clone, PartialEq, Eq)]
816pub struct UndeclaredDoorProbe {
817 pub hostname: String,
818 pub origin: PasswayOrigin,
819}
820
821impl UndeclaredDoorProbe {
822 /// The failure line for a probe whose origin answered with HTTP `status`
823 /// over a TLS handshake that verified a certificate for the hostname.
824 pub fn message(&self, status: u16) -> String {
825 format!(
826 "{} ({}) serves {} with a valid certificate (HTTP {status}) but no mirror declares \
827 it a front door for that hostname — DNS, rendered from the declared doors, will \
828 never send it traffic, and no other yah surface can see it. Declare it in the \
829 mirror's `ingress_machines`, or withdraw the enrollment on the box.",
830 self.origin.machine, self.origin.address, self.hostname
831 )
832 }
833}
834
835/// Every `(hostname, public origin)` pair where the origin is **not** a declared
836/// passway front door for the hostname — the pairs that must fail to serve.
837///
838/// ## Why this exists
839///
840/// On 2026-09-12 us-west-001 answered `yah.dev`, `noisetable.com` and
841/// `api.noisetable.com` with valid certificates while every declaration left it
842/// out: R870 had enrolled it as a third origin by hand, the mirror still had it
843/// commented out, and DNS — rendered from the mirror since R859-F1 — agreed with
844/// the mirror. Every probe in the camp dials the public hostname, so it can only
845/// ever sample the origins DNS hands it, and an undeclared door is invisible by
846/// construction: a spare nobody knows they have and an enrollment that outlived
847/// its reason read identically. The demux's route table is also its TLS
848/// allowlist (W267 Decision 2), which makes *who serves what* a security fact,
849/// not a capacity one.
850///
851/// So the caller dials each pair pinned to the origin's public address, and any
852/// pair that completes a verified TLS handshake is an undeclared door.
853///
854/// ## Scope
855///
856/// Only hostnames **this workspace's mirrors** collate, and only passway edges —
857/// a Cloudflare-tunnel hostname is not served off a node's public address, so
858/// probing one would measure nothing. Tenant hostnames enrolled from another
859/// camp (noisetable, scrabcake) are that camp's collation to check.
860///
861/// Candidates are every machine carrying the `public-ip` taint, resolved through
862/// [`public_origins`], so a tainted machine with no public address is the same
863/// hard error it is on the apex render.
864pub fn undeclared_door_probes(
865 collation: &Collation,
866 machines: &[MachineConfig],
867) -> Result<Vec<UndeclaredDoorProbe>> {
868 let names: Vec<String> = machines.iter().map(|m| m.name.clone()).collect();
869 let mut origins = public_origins(&names, machines, &[])?.origins;
870 origins.sort_by(|a, b| a.machine.cmp(&b.machine));
871
872 let mut declared: BTreeMap<&str, BTreeSet<&str>> = BTreeMap::new();
873 for door in &collation.front_doors {
874 if door.provider != IngressProvider::Passway {
875 continue;
876 }
877 for rule in &door.rules {
878 declared
879 .entry(rule.hostname.as_str())
880 .or_default()
881 .insert(door.machine.as_str());
882 }
883 }
884
885 let mut probes = Vec::new();
886 for (hostname, doors) in &declared {
887 for origin in &origins {
888 if !doors.contains(origin.machine.as_str()) {
889 probes.push(UndeclaredDoorProbe {
890 hostname: hostname.to_string(),
891 origin: origin.clone(),
892 });
893 }
894 }
895 }
896 Ok(probes)
897}
898
899#[cfg(test)]
900mod tests {
901 use super::*;
902 use crate::config::ConnectSpec;
903 use crate::reconciler::ingress::{collate_front_doors, PlannedEdge};
904 use crate::reconciler::service_discovery::{DiscoveredRecord, UnknownReason};
905
906 fn machine(name: &str, address: &str, taints: &[&str]) -> MachineConfig {
907 MachineConfig {
908 name: name.into(),
909 provider: "ovh".into(),
910 location: None,
911 server_type: None,
912 hosts_mirrors: vec![],
913 mesh_tags: vec![],
914 region: None,
915 zone: None,
916 arch: None,
917 bucket: None,
918 vendor: None,
919 nickname: None,
920 legacy_hostkey_fingerprint: None,
921 registration: Default::default(),
922 ssh_keys: vec![],
923 cloudflared: None,
924 hosts_operator_bridge: false,
925 connect: Some(ConnectSpec {
926 address: address.into(),
927 ssh: format!("root@{address}"),
928 identity_file: "~/.ssh/yah".into(),
929 yubaba_port: None,
930 yubaba: None,
931 }),
932 allocatable: None,
933 taints: taints.iter().map(|t| t.to_string()).collect(),
934 sovereign_group: None,
935 sovereign_role: None,
936 ingress_floating_ip: None,
937 }
938 }
939
940 /// The live fleet on 2026-09-14: three public doors and a mesh-only box.
941 fn fleet() -> Vec<MachineConfig> {
942 vec![
943 machine("us-east-001", "51.81.85.145", &["public-ip"]),
944 machine("us-south-001", "45.32.194.254", &["public-ip"]),
945 machine("us-west-001", "15.204.89.240", &["public-ip"]),
946 machine("us-west-011", "100.64.0.11", &[]),
947 ]
948 }
949
950 fn apex(front_doors: &[&str], provider: IngressProvider) -> Collation {
951 let mut p = plan(
952 vec![rule("yah.dev", Some(8080), &["us-east-001"])],
953 front_doors,
954 );
955 p.provider = provider;
956 collate_front_doors(&[PlannedEdge {
957 service: "yah-marketing".into(),
958 env: "prod".into(),
959 plan: p,
960 }])
961 .unwrap()
962 }
963
964 #[test]
965 fn a_public_origin_left_out_of_the_declared_doors_is_probed() {
966 let probes = undeclared_door_probes(
967 &apex(&["us-east-001", "us-south-001"], IngressProvider::Passway),
968 &fleet(),
969 )
970 .unwrap();
971
972 let got: Vec<_> = probes
973 .iter()
974 .map(|p| (p.hostname.as_str(), p.origin.machine.as_str()))
975 .collect();
976 assert_eq!(got, vec![("yah.dev", "us-west-001")]);
977 assert!(probes[0].message(200).contains("15.204.89.240"));
978 }
979
980 #[test]
981 fn declaring_the_door_leaves_nothing_to_probe() {
982 let probes = undeclared_door_probes(
983 &apex(
984 &["us-east-001", "us-south-001", "us-west-001"],
985 IngressProvider::Passway,
986 ),
987 &fleet(),
988 )
989 .unwrap();
990 assert!(probes.is_empty(), "{probes:#?}");
991 }
992
993 #[test]
994 fn a_tunnel_hostname_is_never_probed_against_public_origins() {
995 let probes = undeclared_door_probes(
996 &apex(&["us-east-001"], IngressProvider::CloudflareTunnel),
997 &fleet(),
998 )
999 .unwrap();
1000 assert!(probes.is_empty(), "{probes:#?}");
1001 }
1002
1003 fn rule(hostname: &str, port: Option<u16>, machines: &[&str]) -> IngressRule {
1004 IngressRule {
1005 hostname: hostname.to_string(),
1006 port,
1007 slot: "bundle".to_string(),
1008 provider_id: None,
1009 machines: machines.iter().map(|m| m.to_string()).collect(),
1010 upstream_hosts: vec![],
1011 }
1012 }
1013
1014 fn plan(rules: Vec<IngressRule>, front_doors: &[&str]) -> IngressPlan {
1015 IngressPlan {
1016 provider: IngressProvider::Passway,
1017 rules,
1018 front_doors: front_doors.iter().map(|m| m.to_string()).collect(),
1019 tunnel_id: None,
1020 edge_provider_id: None,
1021 image: None,
1022 auth: None,
1023 via: None,
1024 behind_tunnel: false,
1025 tunnel_door: None,
1026 }
1027 }
1028
1029 fn record(ident: &str, mesh_ip: &str, ports: &[u16]) -> DiscoveredRecord {
1030 DiscoveredRecord {
1031 ident: ident.to_string(),
1032 mesh_ip: mesh_ip.to_string(),
1033 ports: ports.to_vec(),
1034 named_ports: Default::default(),
1035 }
1036 }
1037
1038 /// Plan → resolve → collate → verify, the whole pipeline the CLI runs.
1039 fn run(
1040 mut plans: Vec<(&str, &str, IngressPlan)>,
1041 fanout: &ServiceRecordFanout,
1042 dial: impl FnMut(&str) -> DialOutcome,
1043 ) -> VerifyReport {
1044 let mut resolutions = RuleResolutions::new();
1045 let mut planned = Vec::new();
1046 for (service, env, plan) in &mut plans {
1047 for res in resolve_upstreams_reporting(plan, fanout) {
1048 resolutions.insert(res.key(), res);
1049 }
1050 planned.push(PlannedEdge {
1051 service: service.to_string(),
1052 env: env.to_string(),
1053 plan: plan.clone(),
1054 });
1055 }
1056 let collation = collate_front_doors(&planned).unwrap();
1057 let note = fanout.unknown_note();
1058 verify_collation(&collation, &resolutions, note.as_deref(), dial)
1059 }
1060
1061 fn always_open(_: &str) -> DialOutcome {
1062 DialOutcome::Open { millis: 1 }
1063 }
1064
1065 #[test]
1066 fn a_resolved_rule_whose_endpoint_answers_is_clean() {
1067 let mut fanout = ServiceRecordFanout::default();
1068 fanout.push_answer(
1069 "us-east-001",
1070 vec![record("yah-marketing", "100.64.0.3", &[8080])],
1071 );
1072
1073 let report = run(
1074 vec![(
1075 "yah-marketing",
1076 "prod",
1077 plan(
1078 vec![rule("yah.dev", Some(8080), &["us-east-001"])],
1079 &["us-east-001"],
1080 ),
1081 )],
1082 &fanout,
1083 always_open,
1084 );
1085
1086 assert!(report.is_clean(), "{:#?}", report.verdicts);
1087 assert_eq!(report.verdicts.len(), 1);
1088 assert_eq!(report.verdicts[0].addresses(), vec!["100.64.0.3:8080"]);
1089 assert!(!report.verdicts[0].pinned);
1090 }
1091
1092 #[test]
1093 fn a_ready_record_whose_address_refuses_is_a_failure() {
1094 // R844-B11 in miniature: the record is Ready and every offline check
1095 // passes; only the connect knows.
1096 let mut fanout = ServiceRecordFanout::default();
1097 fanout.push_answer(
1098 "us-west-001",
1099 vec![record("yah-marketing", "100.64.0.3", &[4325])],
1100 );
1101
1102 let report = run(
1103 vec![(
1104 "yah-marketing",
1105 "prod",
1106 plan(
1107 vec![rule("yah.dev", Some(4325), &["us-west-001"])],
1108 &["us-west-001"],
1109 ),
1110 )],
1111 &fanout,
1112 |_| DialOutcome::Closed("connection refused".to_string()),
1113 );
1114
1115 assert!(!report.is_clean());
1116 let msg = report.verdicts[0].findings[0].message();
1117 assert!(msg.contains("100.64.0.3:4325"), "{msg}");
1118 assert!(msg.contains("connection refused"), "{msg}");
1119 assert!(
1120 msg.contains("OPINION"),
1121 "a DISCOVERED address that refuses means the record and the world \
1122 disagree, and the message has to say which: {msg}"
1123 );
1124 }
1125
1126 #[test]
1127 fn an_unreachable_pin_blames_the_toml_not_a_service_record() {
1128 // Found by running the verb against a mirror pinning a dead address:
1129 // the message told the operator a service record disagreed with the
1130 // world, when nothing had discovered anything — the address came
1131 // straight off the slot, and that is the file to open.
1132 let mut fanout = ServiceRecordFanout::default();
1133 fanout.push_answer(
1134 "us-east-001",
1135 vec![record("yah-marketing", "100.64.0.3", &[8080])],
1136 );
1137
1138 let mut pinned_rule = rule("yah.dev", Some(8080), &["us-east-001"]);
1139 pinned_rule.upstream_hosts = vec!["100.64.0.99".to_string()];
1140
1141 let report = run(
1142 vec![(
1143 "yah-marketing",
1144 "prod",
1145 plan(vec![pinned_rule], &["us-east-001"]),
1146 )],
1147 &fanout,
1148 |_| DialOutcome::Closed("connection timed out".to_string()),
1149 );
1150
1151 let msg = report.verdicts[0].findings[0].message();
1152 assert!(msg.contains("upstream_host"), "{msg}");
1153 assert!(
1154 !msg.contains("OPINION"),
1155 "no record was consulted, so none can be blamed: {msg}"
1156 );
1157 }
1158
1159 #[test]
1160 fn resolving_a_subset_of_the_declared_placement_fails() {
1161 // The failure class this verb exists to remove: one of two nodes
1162 // answers, the rule renders and dials fine, and half the front door is
1163 // silently absent.
1164 let mut fanout = ServiceRecordFanout::default();
1165 fanout.push_answer(
1166 "us-east-001",
1167 vec![record("yah-marketing", "100.64.0.3", &[8080])],
1168 );
1169 fanout.push_answer("us-west-001", vec![]);
1170
1171 let report = run(
1172 vec![(
1173 "yah-marketing",
1174 "prod",
1175 plan(
1176 vec![rule("yah.dev", Some(8080), &["us-east-001", "us-west-001"])],
1177 &["us-east-001"],
1178 ),
1179 )],
1180 &fanout,
1181 always_open,
1182 );
1183
1184 assert!(!report.is_clean(), "a subset must not pass");
1185 let msg = report.verdicts[0].findings[0].message();
1186 assert!(msg.contains("1 of 2"), "{msg}");
1187 assert!(msg.contains("us-west-001"), "{msg}");
1188 // The address it DID resolve is still reported — a failure that hides
1189 // the working half is a worse report, not a stricter one.
1190 assert_eq!(report.verdicts[0].addresses(), vec!["100.64.0.3:8080"]);
1191 }
1192
1193 #[test]
1194 fn an_unseen_placement_node_fails_as_unknown_not_as_down() {
1195 let mut fanout = ServiceRecordFanout::default();
1196 fanout.push_answer(
1197 "us-east-001",
1198 vec![record("yah-marketing", "100.64.0.3", &[8080])],
1199 );
1200 fanout.push_unknown(
1201 "us-west-001",
1202 UnknownReason::Unreachable("no route to host".to_string()),
1203 );
1204
1205 let report = run(
1206 vec![(
1207 "yah-marketing",
1208 "prod",
1209 plan(
1210 vec![rule("yah.dev", Some(8080), &["us-east-001", "us-west-001"])],
1211 &["us-east-001"],
1212 ),
1213 )],
1214 &fanout,
1215 always_open,
1216 );
1217
1218 assert!(!report.is_clean());
1219 let msg = report.verdicts[0].findings[0].message();
1220 assert!(msg.contains("us-west-001"), "{msg}");
1221 assert!(msg.contains("no route to host"), "{msg}");
1222 }
1223
1224 #[test]
1225 fn a_rule_with_no_record_anywhere_reports_no_backend_on_a_complete_read() {
1226 let mut fanout = ServiceRecordFanout::default();
1227 fanout.push_answer("us-east-001", vec![]);
1228
1229 let report = run(
1230 vec![(
1231 "yah-marketing",
1232 "prod",
1233 plan(
1234 vec![rule("yah.dev", Some(8080), &["us-east-001"])],
1235 &["us-east-001"],
1236 ),
1237 )],
1238 &fanout,
1239 always_open,
1240 );
1241
1242 assert!(!report.is_clean());
1243 let msg = report.verdicts[0].findings[0].message();
1244 assert!(msg.contains("not serving"), "{msg}");
1245 assert!(
1246 !msg.contains("PARTIAL"),
1247 "a complete read must not hedge: {msg}"
1248 );
1249 }
1250
1251 #[test]
1252 fn a_rule_with_no_record_on_a_partial_read_says_unknown_rather_than_down() {
1253 let mut fanout = ServiceRecordFanout::default();
1254 fanout.push_unknown(
1255 "us-east-001",
1256 UnknownReason::EndpointAbsent("GET … returned 404".to_string()),
1257 );
1258
1259 let report = run(
1260 vec![(
1261 "yah-marketing",
1262 "prod",
1263 plan(
1264 vec![rule("yah.dev", Some(8080), &["us-east-001"])],
1265 &["us-east-001"],
1266 ),
1267 )],
1268 &fanout,
1269 always_open,
1270 );
1271
1272 assert!(!report.is_clean());
1273 let msg = report.verdicts[0].findings[0].message();
1274 assert!(msg.contains("UNKNOWN"), "{msg}");
1275 assert!(msg.contains("404"), "{msg}");
1276 }
1277
1278 #[test]
1279 fn a_portless_rule_reports_both_halves_rather_than_only_the_address() {
1280 // The `fronted = true` shape with nothing to match on: it must not read
1281 // as "the address is missing" alone.
1282 let mut fanout = ServiceRecordFanout::default();
1283 fanout.push_answer("us-east-001", vec![]);
1284
1285 let report = run(
1286 vec![(
1287 "yah-marketing",
1288 "prod",
1289 plan(vec![rule("yah.dev", None, &["us-east-001"])], &["us-east-001"]),
1290 )],
1291 &fanout,
1292 always_open,
1293 );
1294
1295 let findings = &report.verdicts[0].findings;
1296 assert!(
1297 findings
1298 .iter()
1299 .any(|f| matches!(f, VerifyFinding::PortUnresolved)),
1300 "{findings:#?}"
1301 );
1302 assert!(
1303 findings
1304 .iter()
1305 .any(|f| matches!(f, VerifyFinding::NoBackend { .. })),
1306 "{findings:#?}"
1307 );
1308 }
1309
1310 #[test]
1311 fn a_pinned_upstream_is_dialed_and_flagged_as_a_declaration() {
1312 // The 127.0.0.1 case. The pin wins, so the dial is still performed —
1313 // but the verdict has to say the address came from a TOML, and the
1314 // placement gap it papers over has to be visible.
1315 let mut fanout = ServiceRecordFanout::default();
1316 fanout.push_answer("us-east-001", vec![]);
1317
1318 let mut pinned_rule = rule("yah.dev", Some(8080), &["us-east-001"]);
1319 pinned_rule.upstream_hosts = vec!["127.0.0.1".to_string()];
1320
1321 let mut dialed: Vec<String> = Vec::new();
1322 let report = run(
1323 vec![(
1324 "yah-marketing",
1325 "prod",
1326 plan(vec![pinned_rule], &["us-east-001"]),
1327 )],
1328 &fanout,
1329 |addr| {
1330 dialed.push(addr.to_string());
1331 DialOutcome::Open { millis: 1 }
1332 },
1333 );
1334
1335 assert_eq!(dialed, vec!["127.0.0.1:8080"]);
1336 let v = &report.verdicts[0];
1337 assert!(v.pinned);
1338 assert!(v.is_ok(), "a pin that answers is not a failure: {v:#?}");
1339 assert_eq!(v.notes.len(), 1, "{:#?}", v.notes);
1340 assert!(v.notes[0].contains("DECLARATION"), "{}", v.notes[0]);
1341 assert!(v.notes[0].contains("us-east-001"), "{}", v.notes[0]);
1342 }
1343
1344 #[test]
1345 fn one_backend_published_through_two_front_doors_is_dialed_once() {
1346 let mut fanout = ServiceRecordFanout::default();
1347 fanout.push_answer(
1348 "us-east-001",
1349 vec![record("yah-marketing", "100.64.0.3", &[8080])],
1350 );
1351
1352 let mut dialed: Vec<String> = Vec::new();
1353 let report = run(
1354 vec![(
1355 "yah-marketing",
1356 "prod",
1357 plan(
1358 vec![rule("yah.dev", Some(8080), &["us-east-001"])],
1359 &["us-east-001", "us-west-001"],
1360 ),
1361 )],
1362 &fanout,
1363 |addr| {
1364 dialed.push(addr.to_string());
1365 DialOutcome::Open { millis: 1 }
1366 },
1367 );
1368
1369 assert_eq!(
1370 report.verdicts.len(),
1371 2,
1372 "both front doors publish the rule, so both are verified"
1373 );
1374 assert_eq!(dialed, vec!["100.64.0.3:8080"], "dialed twice");
1375 assert!(report.is_clean());
1376 }
1377
1378 #[test]
1379 fn every_rule_is_reported_even_after_one_of_them_fails() {
1380 // The reason this does not call `resolve_upstreams_from`: an operator
1381 // fixing one rule at a time is being handed a linked list.
1382 let mut fanout = ServiceRecordFanout::default();
1383 fanout.push_answer(
1384 "us-east-001",
1385 vec![record("yah-marketing", "100.64.0.3", &[8080])],
1386 );
1387
1388 let report = run(
1389 vec![(
1390 "yah-marketing",
1391 "prod",
1392 plan(
1393 vec![
1394 rule("a.yah.dev", Some(9999), &["us-east-001"]),
1395 rule("b.yah.dev", Some(8080), &["us-east-001"]),
1396 ],
1397 &["us-east-001"],
1398 ),
1399 )],
1400 &fanout,
1401 always_open,
1402 );
1403
1404 assert_eq!(report.verdicts.len(), 2);
1405 let a = report
1406 .verdicts
1407 .iter()
1408 .find(|v| v.hostname == "a.yah.dev")
1409 .unwrap();
1410 let b = report
1411 .verdicts
1412 .iter()
1413 .find(|v| v.hostname == "b.yah.dev")
1414 .unwrap();
1415 assert!(!a.is_ok(), "the unresolvable rule fails");
1416 assert!(
1417 b.is_ok(),
1418 "and the rule after it is still resolved and dialed: {b:#?}"
1419 );
1420 }
1421
1422 // ── the public path (R844-F18) ───────────────────────────────────────────
1423
1424 /// The healthy apex: one hostname, one door, one backend, both sides
1425 /// serving the same publish.
1426 fn healthy_report() -> VerifyReport {
1427 let mut fanout = ServiceRecordFanout::default();
1428 fanout.push_answer(
1429 "us-east-001",
1430 vec![record("yah-marketing", "100.64.0.3", &[8080])],
1431 );
1432 run(
1433 vec![(
1434 "yah-marketing",
1435 "prod",
1436 plan(
1437 vec![rule("yah.dev", Some(8080), &["us-east-001"])],
1438 &["us-east-001"],
1439 ),
1440 )],
1441 &fanout,
1442 always_open,
1443 )
1444 }
1445
1446 fn served(digest: &str) -> BeaconFetch {
1447 BeaconFetch::Answered {
1448 status: 200,
1449 digest: Some(digest.to_string()),
1450 }
1451 }
1452
1453 #[test]
1454 fn matching_beacons_on_both_sides_leave_the_verdict_clean() {
1455 let mut report = healthy_report();
1456 let readings = PublicReadings {
1457 public: [("yah.dev".to_string(), served("abc123"))]
1458 .into_iter()
1459 .collect(),
1460 backends: [("100.64.0.3:8080".to_string(), served("abc123"))]
1461 .into_iter()
1462 .collect(),
1463 };
1464 apply_public_path(&mut report, &readings);
1465 assert!(report.is_clean(), "{:#?}", report.verdicts);
1466 assert!(
1467 report.verdicts[0].notes.is_empty(),
1468 "a fully compared rule has nothing to caveat: {:?}",
1469 report.verdicts[0].notes
1470 );
1471 }
1472
1473 /// THE 2026-09-03 OUTAGE, reproduced as a unit test. Every mesh signal is
1474 /// green — the record is correct, the address answers, `verify_collation`
1475 /// alone reports the rule clean — and the public gets a 503 because the
1476 /// running front door is still dialing the port the workload left.
1477 #[test]
1478 fn a_503_at_the_apex_fails_a_rule_whose_mesh_side_is_entirely_green() {
1479 let mut report = healthy_report();
1480 assert!(
1481 report.is_clean(),
1482 "precondition: the mesh side is what reported success during the outage"
1483 );
1484
1485 let readings = PublicReadings {
1486 public: [(
1487 "yah.dev".to_string(),
1488 BeaconFetch::Answered {
1489 status: 503,
1490 digest: None,
1491 },
1492 )]
1493 .into_iter()
1494 .collect(),
1495 backends: [("100.64.0.3:8080".to_string(), served("abc123"))]
1496 .into_iter()
1497 .collect(),
1498 };
1499 apply_public_path(&mut report, &readings);
1500
1501 assert!(!report.is_clean());
1502 let msg = report.verdicts[0].findings[0].message();
1503 assert!(msg.contains("503"), "{msg}");
1504 assert!(
1505 msg.contains("yah.dev"),
1506 "the finding names the hostname the public dials: {msg}"
1507 );
1508 }
1509
1510 /// The stale-serve shape: the hostname answers 200 with a real page, so
1511 /// nothing short of comparing publishes can see it. This is the failure
1512 /// that froze the apex for nineteen days.
1513 #[test]
1514 fn a_200_serving_a_different_publish_than_the_backend_is_a_failure() {
1515 let mut report = healthy_report();
1516 let readings = PublicReadings {
1517 public: [("yah.dev".to_string(), served("old-digest"))]
1518 .into_iter()
1519 .collect(),
1520 backends: [("100.64.0.3:8080".to_string(), served("new-digest"))]
1521 .into_iter()
1522 .collect(),
1523 };
1524 apply_public_path(&mut report, &readings);
1525
1526 assert!(!report.is_clean());
1527 let msg = report.verdicts[0].findings[0].message();
1528 assert!(msg.contains("old-digest"), "{msg}");
1529 assert!(msg.contains("new-digest"), "{msg}");
1530 assert!(
1531 msg.contains("100.64.0.3:8080"),
1532 "and it names the backend it compared against: {msg}"
1533 );
1534 }
1535
1536 #[test]
1537 fn a_public_path_that_does_not_answer_at_all_is_a_failure() {
1538 let mut report = healthy_report();
1539 let readings = PublicReadings {
1540 public: [(
1541 "yah.dev".to_string(),
1542 BeaconFetch::Failed("dns error: no record".to_string()),
1543 )]
1544 .into_iter()
1545 .collect(),
1546 backends: [("100.64.0.3:8080".to_string(), served("abc123"))]
1547 .into_iter()
1548 .collect(),
1549 };
1550 apply_public_path(&mut report, &readings);
1551
1552 assert!(!report.is_clean());
1553 assert!(report.verdicts[0].findings[0]
1554 .message()
1555 .contains("dns error"));
1556 }
1557
1558 /// A hostname fronting something that is not a mesofact publish has no
1559 /// beacon to compare. That is a limit on the CHECK, not a fault of the
1560 /// host — so it is a note, and the rule stays clean rather than failing
1561 /// every non-bundle hostname in the fleet.
1562 #[test]
1563 fn a_hostname_with_no_beacon_is_noted_and_not_failed() {
1564 let mut report = healthy_report();
1565 let readings = PublicReadings {
1566 public: [(
1567 "yah.dev".to_string(),
1568 BeaconFetch::Answered {
1569 status: 200,
1570 digest: None,
1571 },
1572 )]
1573 .into_iter()
1574 .collect(),
1575 backends: [("100.64.0.3:8080".to_string(), served("abc123"))]
1576 .into_iter()
1577 .collect(),
1578 };
1579 apply_public_path(&mut report, &readings);
1580
1581 assert!(report.is_clean(), "{:#?}", report.verdicts);
1582 assert!(
1583 report.verdicts[0]
1584 .notes
1585 .iter()
1586 .any(|n| n.contains("no publish beacon")),
1587 "{:?}",
1588 report.verdicts[0].notes
1589 );
1590 }
1591
1592 /// The distinction this whole relay is about: "answers" is not "answers
1593 /// with the backend we just proved". A public 200 with no comparable
1594 /// backend must not read as a full pass.
1595 #[test]
1596 fn a_public_200_with_nothing_to_compare_says_so_rather_than_implying_a_match() {
1597 let mut report = healthy_report();
1598 let readings = PublicReadings {
1599 public: [("yah.dev".to_string(), served("abc123"))]
1600 .into_iter()
1601 .collect(),
1602 backends: BTreeMap::new(),
1603 };
1604 apply_public_path(&mut report, &readings);
1605
1606 assert!(report.is_clean());
1607 assert!(
1608 report.verdicts[0]
1609 .notes
1610 .iter()
1611 .any(|n| n.contains("NOT that it is fronting the discovered backend")),
1612 "{:?}",
1613 report.verdicts[0].notes
1614 );
1615 }
1616
1617 #[test]
1618 fn an_unmeasured_hostname_is_marked_as_unmeasured_not_as_passing() {
1619 let mut report = healthy_report();
1620 apply_public_path(&mut report, &PublicReadings::default());
1621
1622 assert!(report.is_clean(), "not checking is not failing");
1623 assert!(
1624 report.verdicts[0]
1625 .notes
1626 .iter()
1627 .any(|n| n.contains("was not checked")),
1628 "{:?}",
1629 report.verdicts[0].notes
1630 );
1631 }
1632
1633 /// DNS picks one door, so the comparison covers one door. Saying that is
1634 /// the difference between a caveat and a false claim of coverage.
1635 #[test]
1636 fn a_hostname_on_two_front_doors_is_noted_as_measured_at_only_one() {
1637 let mut fanout = ServiceRecordFanout::default();
1638 fanout.push_answer(
1639 "us-east-001",
1640 vec![record("yah-marketing", "100.64.0.3", &[8080])],
1641 );
1642 fanout.push_answer(
1643 "us-south-001",
1644 vec![record("yah-marketing", "100.64.0.2", &[8080])],
1645 );
1646 let mut report = run(
1647 vec![(
1648 "yah-marketing",
1649 "prod",
1650 plan(
1651 vec![rule(
1652 "yah.dev",
1653 Some(8080),
1654 &["us-east-001", "us-south-001"],
1655 )],
1656 &["us-east-001", "us-south-001"],
1657 ),
1658 )],
1659 &fanout,
1660 always_open,
1661 );
1662 assert_eq!(report.verdicts.len(), 2, "one verdict per front door");
1663
1664 let readings = PublicReadings {
1665 public: [("yah.dev".to_string(), served("abc123"))]
1666 .into_iter()
1667 .collect(),
1668 backends: [
1669 ("100.64.0.3:8080".to_string(), served("abc123")),
1670 ("100.64.0.2:8080".to_string(), served("abc123")),
1671 ]
1672 .into_iter()
1673 .collect(),
1674 };
1675 apply_public_path(&mut report, &readings);
1676
1677 assert!(report.is_clean(), "{:#?}", report.verdicts);
1678 assert!(
1679 report
1680 .verdicts
1681 .iter()
1682 .all(|v| v.notes.iter().any(|n| n.contains("wherever DNS sent it"))),
1683 "{:#?}",
1684 report.verdicts
1685 );
1686 }
1687}