From 965e1a35fbad4069b7519fe346c6f70af833f20a Mon Sep 17 00:00:00 2001 From: Nils Lehnen <30603423+iderex@users.noreply.github.com> Date: Wed, 2 Sep 2026 19:17:01 +0200 Subject: [PATCH 1/2] Hold 0069's destination set and its redirect rule as values (#69) 0069 decides that the set of destinations is exactly the origins of the servers the operator configured, that a redirect is followed only where it stays inside the origin the request was sent to, and that a name is resolved only for a host in the set. None of the three was anywhere in the tree: every use of Origin outside the module defining it was 0027's connection accounting, which counts requests against an origin rather than deciding whether one may be reached at all. src/server/destinations.rs holds the set, the comparison and the redirect rule. A set nobody configured admits nobody, which is 0068's sentence and what makes the check in #70 unambiguous. The comparison is over the whole origin, so a scheme downgrade and a second port on one host are two origins rather than one host. A redirect is compared against the origin the request went to and never against the whole set, so one configured server cannot move a request onto another one: 0101 places that decision with the operator, and a server asking is not the operator answering. The refusal is built at 0037's mapping point and carries ReadingSite::CrossOriginRedirectRefused, which is the value that keeps 0004's reversal measurement three populations rather than one count. The failure this prevents is the one 0069 was written before the code to stop: following a redirect anywhere is one line of configuration on an HTTP client, it is invisible in every test against a fake server that does not redirect, and the first time it matters is an operator whose server redirects artwork to a content network, at which point an address that frequently says where somebody lives has been going to a third party for as long as that deployment existed. AdmittedOrigin is produced by the comparison and by nothing else, so a later connect written against Origin is one that skipped it. Nothing consumes one today, because nothing here connects, resolves or requests. What #69 still needs is its fourth condition, which is the check in #70, and that check does not exist. #70's own last reading says what it waits on is the comparison being written rather than an issue; this is that comparison. Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com> --- src/lib.rs | 4 + src/server/destinations.rs | 497 +++++++++++++++++++++++++++++++++++++ src/server/mod.rs | 7 + tests/thread_statements.rs | 11 + 4 files changed, 519 insertions(+) create mode 100644 src/server/destinations.rs diff --git a/src/lib.rs b/src/lib.rs index 286a0a8..18f053d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -141,6 +141,10 @@ const _: () = { any_thread::(); any_thread::(); any_thread::>(); + any_thread::(); + any_thread::(); + any_thread::(); + any_thread::(); any_thread::(); any_thread::>(); any_thread::>(); diff --git a/src/server/destinations.rs b/src/server/destinations.rs new file mode 100644 index 0000000..6a11044 --- /dev/null +++ b/src/server/destinations.rs @@ -0,0 +1,497 @@ +//! The set of destinations the core may contact, and what a redirect does. +//! +//! `docs/decisions/0069-every-host-the-core-may-contact.md` decides both. The +//! set is exactly the origins of the servers the operator configured, one row +//! per configured server and no row the core adds on its own; a redirect is +//! followed only where it stays inside the origin the request was sent to, and +//! one leaving it is refused before a request is sent to the new location. +//! +//! # Why the set is a value here rather than a list somewhere +//! +//! 0069 derives its table rather than writing it out, and the number of rows in +//! a running core is the number of servers the operator added. So the authority +//! for the set is a register the core holds, and this is it. It starts empty, +//! which `docs/decisions/0068-the-data-locality-position.md` fixes and which is +//! what makes the check in #70 unambiguous: any host at all in a run that +//! configured none is the defect, with nothing to reason about. +//! +//! # Where a name may be handed to a resolver +//! +//! 0069 puts name resolution inside the set rather than in front of it, because +//! a lookup is itself a request that carries the name being looked up, and a +//! core that resolved first and compared afterwards has already told a third +//! party which server the person uses. [`AdmittedOrigin`] is that sentence as a +//! type: it is produced by [`Destinations::admit`] and by nothing else, so a +//! call that takes one has had the comparison made for it, and one that takes an +//! [`Origin`] has not. +//! +//! WHAT THAT IS WORTH TODAY IS AVAILABLE RATHER THAN HELD, and the difference is +//! the whole of it. Nothing in this tree connects, resolves or requests, so no +//! call site consumes an [`AdmittedOrigin`] and nothing is kept to the order by +//! it. What it buys is that the day the transport in #27 lands, a connect +//! written against [`Origin`] is a connect that skipped this comparison, and the +//! reviewer of that change is reading a signature rather than searching for a +//! call. [`Origin`] itself still hands out its host to anybody holding one; this +//! module does not narrow that, because the parse in +//! [`crate::server::address`] is 0028's rather than 0069's. +//! +//! # What this module does not do +//! +//! IT REFUSES NOTHING THAT IS HAPPENING. The refusal 0069 wants is a request not +//! being sent, and no request is sent from anywhere in this crate. What is here +//! answers correctly about an origin and produces the failure value a caller +//! would hand back; whether any caller asks is #27 for the transport and #70 for +//! the test that fails when the core reaches a host nobody configured. + +use crate::failure::{Expected, Failure, ReadingSite}; +use crate::server::address::Origin; +use std::sync::{Mutex, MutexGuard}; + +/// Where a refused redirect says reading stopped. +/// +/// 0004 fixes the payload of `answer-not-understood` as the site, what was +/// expected there, and an offset, and it forbids the bytes at that offset +/// because an answer holds library contents and may hold a token. A refused +/// redirect consumed none of the location it was handed - it read the whole of +/// it and declined it - so the offset is the start of it rather than a position +/// inside something that was partly parsed. +pub const A_REFUSED_REDIRECT_CONSUMED_NOTHING: usize = 0; + +/// An origin the configured set admits, and therefore one a name may be resolved +/// for. +/// +/// There is no constructor. [`Destinations::admit`] and [`Destinations::follow`] +/// are the only two things that make one, and both make it out of a comparison +/// against the set rather than out of an address, so a value of this type is +/// evidence that the comparison happened. +/// +/// It carries the origin rather than borrowing the register, because 0069's +/// answer is about the origin, and a value that held the register would make +/// every request the core made a reader of it for as long as the request lasted. +/// +/// Thread safety, from 0009: immutable once made, safe from any thread. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AdmittedOrigin { + origin: Origin, +} + +impl AdmittedOrigin { + /// The origin this admits, whole. + #[must_use] + pub const fn origin(&self) -> &Origin { + &self.origin + } + + /// The host a resolver may be given. + /// + /// This accessor exists so that the one place a name leaves the core towards + /// a resolver is reached through a value that has been compared against the + /// set. It is the same string [`Origin::host`] answers with; what is + /// different is what a caller had to hold to get here. + #[must_use] + pub fn host(&self) -> &str { + self.origin.host() + } +} + +/// What adding a configured server did to the set. +/// +/// The set is closed and exhaustive, for 0004's reason applied to a different +/// subject: a caller matching on it is told by the compiler when a case appears +/// rather than falling into a branch somebody wrote for something else. +/// +/// Thread safety, from 0009: a plain value, safe from any thread. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WhatConfiguringDid { + /// The origin was not in the set and is now. This is a row arriving. + AddedARow, + /// The origin was already in the set, so nothing moved. + /// + /// 0069 fixes one row per configured server, so a second sign-in against a + /// server the operator already added widens nothing and duplicates nothing. + LeftTheSetAsItWas, +} + +/// What the core does with a redirect a server answered with. +/// +/// Thread safety, from 0009: a value handed back to a caller, safe from any +/// thread. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum WhatARedirectDoes { + /// It stays inside the origin the request went to, so it is followed. + /// + /// This is the ordinary case 0069 names: a server correcting a path, a + /// trailing separator or a version prefix. + Followed(AdmittedOrigin), + /// It leaves that origin, so nothing is sent to it. + /// + /// The failure is built here rather than by the caller, so that a refusal + /// cannot be turned into some other kind on the way back. + Refused(Failure), +} + +/// Every origin the operator configured, and nothing else. +/// +/// Thread safety, from 0009: safe from any thread. Every request would consult +/// it, and a register that were not would make each of them a synchronisation +/// point. +#[derive(Debug)] +pub struct Destinations { + held: Mutex>, +} + +impl Default for Destinations { + fn default() -> Self { + Self::new() + } +} + +impl Destinations { + /// A set with nothing in it. + /// + /// This is the whole of "the set is empty before anything is configured". + /// There is no other constructor, nothing is added by a request succeeding, + /// and no host is here because the core needed one: the only way a row + /// arrives is [`Destinations::configured`], which a caller supplies the + /// origin to. + #[must_use] + pub const fn new() -> Self { + Self { + held: Mutex::new(Vec::new()), + } + } + + /// Records that the operator configured a server at this origin. + /// + /// 0069 names two acts that reach here and no third. The first is the + /// sign-in that adds the first server, which is #30. The second is the + /// deliberate per-server act + /// `docs/decisions/0072-federation-is-a-deliberate-per-server-act.md` + /// defines, performed the same way by the same person, which adds a row + /// rather than widening one. + /// + /// It takes an [`Origin`] rather than an address, because 0069 compares the + /// origin as it was resolved on the way in rather than the address as it was + /// typed, and [`crate::server::address::BaseAddress::origin`] is where that + /// resolution has already happened. + pub fn configured(&self, origin: Origin) -> WhatConfiguringDid { + let mut held = self.held(); + if held.contains(&origin) { + return WhatConfiguringDid::LeftTheSetAsItWas; + } + held.push(origin); + WhatConfiguringDid::AddedARow + } + + /// Removes one origin from the set, and answers whether it was in it. + /// + /// It reaches one row. A set holding several servers loses the named one and + /// keeps the rest, because a person removing one server is not saying + /// anything about another, and the shortest implementation empties the set. + pub fn forgotten(&self, origin: &Origin) -> bool { + let mut held = self.held(); + let before = held.len(); + held.retain(|kept| kept != origin); + held.len() != before + } + + /// How many servers the operator configured. + /// + /// 0069 says the number of rows in a running core is this number, so it is + /// read out of the register rather than stated anywhere. + #[must_use] + pub fn rows(&self) -> usize { + self.held().len() + } + + /// Every origin in the set. + /// + /// A person can be shown where their core may go, which is the half of + /// 0068's position a client can act on. + #[must_use] + pub fn all(&self) -> Vec { + self.held().clone() + } + + /// Whether this origin is one the operator configured, and a name may + /// therefore be resolved for it. + /// + /// An empty set admits nobody, which is 0068's sentence and #70's + /// unambiguous failure. + /// + /// It compares the whole origin. 0069 refuses the host alone by name: a + /// server behind a reverse proxy shares its machine with whatever else is on + /// another port, and a typed `http://` is honoured rather than upgraded, so + /// the scheme is part of what the operator chose. An absent port is not the + /// scheme's default filled in either, which is [`Origin`]'s own rule and + /// reaches this comparison unchanged. + #[must_use] + pub fn admit(&self, origin: &Origin) -> Option { + self.held() + .iter() + .find(|held| *held == origin) + .map(|held| AdmittedOrigin { + origin: held.clone(), + }) + } + + /// What a redirect answered by a request sent to `sent_to` does. + /// + /// IT COMPARES AGAINST THE ORIGIN THE REQUEST WENT TO AND NEVER AGAINST THE + /// WHOLE SET, and that is 0069 read exactly rather than read conveniently. + /// That record follows a redirect only where it stays inside the origin that + /// was already in the set and refuses one going anywhere else, so a server + /// the operator configured redirecting to a SECOND server the operator also + /// configured is refused here. The convenient reading admits it, and what it + /// admits is one configured server deciding that another one answers for it, + /// which is a decision + /// `docs/decisions/0101-what-the-core-trusts.md` places with the operator: + /// the person is trusted for which servers the core may talk to, and a + /// server is not trusted to move a request onto one of them. + /// + /// Nothing is sent to a refused location: not a credential, not the device + /// identity, not the query. The refusal is the answer, and there is no + /// variant carrying a location a caller could still reach. + #[must_use] + pub fn follow(&self, sent_to: &AdmittedOrigin, location: &Origin) -> WhatARedirectDoes { + if location == sent_to.origin() { + return WhatARedirectDoes::Followed(sent_to.clone()); + } + WhatARedirectDoes::Refused(Failure::answer_not_understood( + ReadingSite::CrossOriginRedirectRefused, + Expected::AnOriginTheCoreMayContact, + A_REFUSED_REDIRECT_CONSUMED_NOTHING, + )) + } + + fn held(&self) -> MutexGuard<'_, Vec> { + self.held.lock().expect("the set holds no poisoned lock") + } +} + +#[cfg(test)] +mod tests { + //! Every origin below is produced by parsing an address, rather than built + //! from parts here, because 0069 compares the origin as it was resolved on + //! the way in and a value assembled in this file would prove the comparison + //! against something no address could produce. + + use super::{ + A_REFUSED_REDIRECT_CONSUMED_NOTHING, Destinations, WhatARedirectDoes, WhatConfiguringDid, + }; + use crate::failure::{Expected, Failure, Kind, ReadingSite}; + use crate::server::address::{BaseAddress, Origin}; + + fn origin(typed: &str) -> Origin { + BaseAddress::parse(typed).expect("usable").origin() + } + + fn configured(typed: &str) -> Destinations { + let set = Destinations::new(); + set.configured(origin(typed)); + set + } + + /// 0068 fixes that the set is empty before anything is configured, and 0069 + /// names that as what makes #70's failure unambiguous. + #[test] + fn a_set_nobody_configured_admits_nobody() { + let set = Destinations::new(); + + assert_eq!(set.rows(), 0); + assert!(set.admit(&origin("https://films.example")).is_none()); + assert!(set.admit(&origin("http://127.0.0.1:8096")).is_none()); + } + + /// The one row 0069's table has, arriving the way that record says it does. + #[test] + fn the_origin_the_operator_configured_is_the_one_that_is_admitted() { + let set = configured("https://films.example"); + + assert_eq!(set.rows(), 1); + assert!(set.admit(&origin("https://films.example")).is_some()); + assert!(set.admit(&origin("https://music.example")).is_none()); + } + + /// 0069 compares the origin as it was resolved on the way in rather than the + /// address as it was typed, so two spellings of one server are one row. + #[test] + fn what_was_typed_is_compared_after_the_parse_rather_than_before_it() { + let set = configured("HTTPS://Films.Example/jellyfin/"); + + assert!(set.admit(&origin("https://films.example")).is_some()); + assert_eq!( + set.configured(origin("https://FILMS.example/other")), + WhatConfiguringDid::LeftTheSetAsItWas, + ); + assert_eq!(set.rows(), 1); + } + + /// 0069 refuses the host alone by name, because a typed `http://` is + /// honoured rather than upgraded and the scheme is part of what the operator + /// chose. The cost of the shorter comparison is in that record: an `https` + /// origin redirected to `http` on the same host sends the session token in + /// the clear on a rule written to protect the address. + #[test] + fn the_scheme_is_part_of_the_comparison() { + let set = configured("https://films.example"); + + assert!(set.admit(&origin("http://films.example")).is_none()); + } + + /// 0028 keeps what was typed and supplies no default port, so an origin that + /// named one and an origin that did not are two. + #[test] + fn a_port_that_was_typed_and_a_port_that_was_not_are_two_origins() { + let set = configured("https://films.example"); + + assert!(set.admit(&origin("https://films.example:443")).is_none()); + assert!(set.admit(&origin("https://films.example:8920")).is_none()); + } + + /// A path is not part of an origin, which is 0069's own sentence and the + /// half a reader gets wrong. + #[test] + fn a_server_at_a_sub_path_is_the_same_origin_as_one_at_the_root() { + let set = configured("https://films.example/jellyfin"); + + assert!(set.admit(&origin("https://films.example")).is_some()); + assert!( + set.admit(&origin("https://films.example/anything")) + .is_some() + ); + } + + /// 0069 adds a row per configured server rather than widening one, and + /// 0072's act is the second way one arrives. + #[test] + fn a_second_configured_server_adds_a_row_and_widens_nothing() { + let set = configured("https://films.example"); + + assert_eq!( + set.configured(origin("https://music.example")), + WhatConfiguringDid::AddedARow, + ); + assert_eq!(set.rows(), 2); + assert!(set.admit(&origin("https://films.example")).is_some()); + assert!(set.admit(&origin("https://music.example")).is_some()); + assert!(set.admit(&origin("https://photos.example")).is_none()); + } + + /// Removing one server says nothing about another, and the shortest + /// implementation of it empties the set. + #[test] + fn forgetting_one_server_leaves_every_other_row_standing() { + let set = configured("https://films.example"); + set.configured(origin("https://music.example")); + + assert!(set.forgotten(&origin("https://films.example"))); + assert!(!set.forgotten(&origin("https://films.example"))); + assert_eq!(set.rows(), 1); + assert!(set.admit(&origin("https://music.example")).is_some()); + } + + /// The ordinary case 0069 names: a server correcting a path, a trailing + /// separator or a version prefix. + #[test] + fn a_redirect_inside_the_origin_it_was_sent_to_is_followed() { + let set = configured("https://films.example"); + let sent_to = set + .admit(&origin("https://films.example")) + .expect("in the set"); + + let followed = set.follow(&sent_to, &origin("https://films.example/Items/1")); + + assert_eq!(followed, WhatARedirectDoes::Followed(sent_to)); + } + + /// The failure this board came within one line of configuration of, which is + /// 0069's own reason for being written before the transport: every HTTP + /// client anybody would reach for follows a redirect anywhere by default. + #[test] + fn a_redirect_leaving_that_origin_is_refused_with_the_kind_0069_takes() { + let set = configured("https://films.example"); + let sent_to = set + .admit(&origin("https://films.example")) + .expect("in the set"); + + let refused = set.follow(&sent_to, &origin("https://images.cdn.example/poster.jpg")); + + let WhatARedirectDoes::Refused(failure) = refused else { + panic!("0069 refuses a redirect leaving the origin the request was sent to"); + }; + assert_eq!(failure.kind(), Kind::AnswerNotUnderstood); + let Failure::AnswerNotUnderstood { + site, + expected, + stopped_at, + .. + } = failure + else { + panic!("the refusal mapped onto something other than the catch-all"); + }; + assert_eq!(site, ReadingSite::CrossOriginRedirectRefused); + assert_eq!(expected, Expected::AnOriginTheCoreMayContact); + assert_eq!(stopped_at, A_REFUSED_REDIRECT_CONSUMED_NOTHING); + } + + /// The refusal is against the origin the request went to and never against + /// the whole set. A configured server is not trusted to move a request onto + /// another configured server, because 0101 places that decision with the + /// operator. + #[test] + fn a_redirect_to_a_second_configured_server_is_refused_too() { + let set = configured("https://films.example"); + set.configured(origin("https://music.example")); + let sent_to = set + .admit(&origin("https://films.example")) + .expect("in the set"); + + let refused = set.follow(&sent_to, &origin("https://music.example/Audio/1")); + + assert!(matches!(refused, WhatARedirectDoes::Refused(_))); + assert!(set.admit(&origin("https://music.example")).is_some()); + } + + /// The near miss for the comparison the redirect rule makes: one port apart + /// on the same host and scheme is a different origin, and 0069's alternative + /// that compares hosts is what admits it. + #[test] + fn a_redirect_to_another_port_on_the_same_host_is_refused() { + let set = configured("https://films.example"); + let sent_to = set + .admit(&origin("https://films.example")) + .expect("in the set"); + + let refused = set.follow(&sent_to, &origin("https://films.example:8920/Items/1")); + + assert!(matches!(refused, WhatARedirectDoes::Refused(_))); + } + + /// A downgrade to the clear is the case 0069 prices for the shorter + /// comparison, and it is a redirect rather than only a configuration. + #[test] + fn a_redirect_downgrading_the_scheme_on_the_same_host_is_refused() { + let set = configured("https://films.example"); + let sent_to = set + .admit(&origin("https://films.example")) + .expect("in the set"); + + let refused = set.follow(&sent_to, &origin("http://films.example/Items/1")); + + assert!(matches!(refused, WhatARedirectDoes::Refused(_))); + } + + /// The host a resolver may be given is reached through the admission and not + /// beside it, which is 0069 putting resolution inside the set. + #[test] + fn the_name_a_resolver_is_given_comes_off_an_admitted_origin() { + let set = configured("HTTPS://Films.Example"); + let admitted = set + .admit(&origin("https://films.example")) + .expect("in the set"); + + assert_eq!(admitted.host(), "films.example"); + assert_eq!(admitted.origin(), &origin("https://films.example")); + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 3ad3f9c..9943a0d 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -49,9 +49,16 @@ //! through an act a person performed, against one server, naming what it shares, //! and revocable without the network. Which hosts may be contacted at all is //! 0069 and #69, and this is the register that would add one to that list. +//! +//! [`destinations`] holds that list: the origins the operator configured, the +//! comparison an origin is admitted by, and what 0069 does with a redirect that +//! leaves the origin a request was sent to. It sends nothing and refuses no +//! request, for the same reason [`transport`] holds no socket, and its own +//! documentation says which issue each half of that waits on. pub mod address; pub mod certificate; +pub mod destinations; pub mod federation; pub mod recovery; pub mod retry; diff --git a/tests/thread_statements.rs b/tests/thread_statements.rs index c7d51a7..63bad87 100644 --- a/tests/thread_statements.rs +++ b/tests/thread_statements.rs @@ -60,6 +60,9 @@ use flowfin_core::playback::cadence::{ }; use flowfin_core::server::QueryResult; use flowfin_core::server::address::{AddressNotUsable, BaseAddress}; +use flowfin_core::server::destinations::{ + AdmittedOrigin, Destinations, WhatARedirectDoes, WhatConfiguringDid, +}; use flowfin_core::server::federation::Federation; use flowfin_core::server::retry::{ Attempts, Jitter, TheWait, WhatAFailureDoes, WhatTheCallDoesNext, WhatTheRequestDoes, @@ -193,6 +196,14 @@ fn the_federation_register_is_safe_from_any_thread() { const _: () = any_thread::>(); } +#[test] +fn the_set_of_destinations_and_what_it_answers_are_safe_from_any_thread() { + const _: () = any_thread::(); + const _: () = any_thread::(); + const _: () = any_thread::(); + const _: () = any_thread::(); +} + #[test] fn the_diagnostics_facility_is_safe_from_any_thread() { const _: () = any_thread::>(); From 7797c444a4b945e6c55eb437a1adbfcac9d80ef2 Mon Sep 17 00:00:00 2001 From: Nils Lehnen <30603423+iderex@users.noreply.github.com> Date: Wed, 2 Sep 2026 19:20:58 +0200 Subject: [PATCH 2/2] Put the destination set on the pinned coverage surface (#69) src/server/ is an area in .github/coverage/pinned-surface, and the rule that makes that register hold is that every tracked .rs file under an area is on the surface and the run refuses one that is not listed. The module the previous commit added was not, and the check said so rather than measuring it: src/server/destinations.rs is on the pinned surface and is not listed in .github/coverage/pinned-surface. The row carries the reason on the same line as the path, which is what that register asks for: what a defect there does is send an address and a device identity to a host the operator never chose, or admit a host on a core nothing has been configured on, where 0068 says the set is empty. Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com> --- .github/coverage/pinned-surface | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/coverage/pinned-surface b/.github/coverage/pinned-surface index 77cb1f5..639ace6 100644 --- a/.github/coverage/pinned-surface +++ b/.github/coverage/pinned-surface @@ -78,6 +78,7 @@ module src/session/device.rs The device identity, whose identifier is one module src/server/mod.rs The server module and what it places at its own boundary. module src/server/address.rs The address a person typed, parsed and refused where it is not usable. module src/server/certificate.rs The one exception 0029 admits to certificate validation: which certificate an operator pinned for which server, and what a pin never vouches for. A defect here accepts a certificate nobody asserted was theirs, or turns one server's key into something that can answer for a name the person never typed. +module src/server/destinations.rs 0069's set of destinations, the comparison an origin is admitted by, and what a redirect leaving the origin a request was sent to does. A defect here follows a server's redirect out to a host nobody configured, which is an address and a device identity reaching somewhere the operator never chose, or admits a host on a core nothing has been configured on, where 0068 says the set is empty. module src/server/federation.rs Which second host a request may reach, and the deliberate act that admits one. module src/server/recovery.rs 0045's schedule for a server that is gone: when the next probe is due, where the doubling stops, and the hour after which the core stops asking. A defect here is a radio kept busy overnight on a device in somebody's bag, or a server that came back and was noticed hours later. module src/server/retry.rs 0038's one policy for every request the core makes: which kinds are retried inside a call, how many attempts one call may spend, the interval each wait is drawn over, and the deadline that ends it. A defect here retries something that changed the server, or keeps a caller waiting past the point 0007 says an answer is not coming.