Skip to main content

subc_daemon/
control.rs

1use std::{
2    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
3    fmt,
4    path::{Path, PathBuf},
5    sync::{Arc, Mutex, RwLock},
6    time::{Duration, Instant as StdInstant},
7};
8
9use serde::{Deserialize, Serialize};
10use subc_control::{
11    ops, CapabilityRequirementStatus, CatalogEntry, ClientControlPush, ClientControlRequest,
12    ClientControlResponse, ConsumerIdentity, DaemonBuildProvenance, DaemonObservedProcess,
13    ModuleDeclaredProvenance, ModuleProtocol, NotReadyReason, PendingReloadVerdict, PollKind,
14    ReloadPathAgreement, ReloadPathUnavailableReason, RouteCloseReason, SpawnCursor,
15    StderrCaptureState, StderrTail, StderrTailEntry, SupervisorDaemonProvenance, SupervisorEntry,
16    SupervisorHealthEntry, SupervisorModuleProvenance, SupervisorObservedProcess,
17    SupervisorRescanResult, SupervisorRoute, SupervisorRouteConsumer, SupervisorRouteModule,
18};
19use subc_protocol::{
20    error_codes,
21    manifest::{
22        validate_hello_capability_grammar, validate_hello_event_declarations,
23        validate_hello_self_signal_declarations, CapabilityDeclarations, CapabilityNeed,
24        Concurrency, ManifestProvenance, ModuleManifest, ProviderRole,
25    },
26    scope::{
27        ScopeRecord, ScopeRecordOutcome, ScopeRecordResult, ScopeSelector,
28        CAP_ROUTE_ROLE_VERSIONS_V1, CAP_SCOPES_V1, SCOPE_DESCRIBE_OP, SCOPE_SYNC_OP,
29    },
30    session::{
31        validate_role_versions, HealthReport, ModuleControlPush, ModuleControlRequest,
32        ModuleControlRequestFromModule, ModuleControlResponse, ModuleControlResponseToModule,
33        MODULE_CONTROL_OP_HEALTH_CHECK, MODULE_TO_SUBC_OP_CATALOG_UPDATE, ROLE_VERSIONS_FIELD,
34    },
35    BindIdentity, ErrorBody, Flags, FrameType, ModuleHelloAckBody, ModuleHelloBody, Principal,
36    Priority, RouteTarget, PROTOCOL_VERSION,
37};
38use tokio::time::{timeout_at, Instant};
39use tracing::{debug, info, warn};
40
41use crate::{
42    capability_requirements::{
43        log_duplicate_claim_events, log_requirement_events, CapabilityRequirementEvaluator,
44        CapabilityVerdict, DuplicateClaimSource, RegisteredModule, RequirementStatus,
45        RuntimeModule,
46    },
47    daemon_config::RestartRequiredSection,
48    forwarding::{
49        CloseReason, EndpointRoute, ForwardingError, ForwardingTable, GoodbyeTarget,
50        ModuleControlRpcCompletion, ModuleControlRpcOutcome, ModuleEndpointId,
51        PendingModuleControlRpc, RouteBindRelayOutcome, RoutePollSnapshot, RouteRelease,
52    },
53    observability::{
54        ROUTE_OPEN_REFUSED_DECLARED_NOT_READY, ROUTE_OPEN_REFUSED_REQUIRED_CAPABILITY_UNPROVIDED,
55    },
56    provenance::{
57        process_start_time, spawned_file_identity, ExecutableIdentityProbe, SpawnedFileIdentity,
58    },
59    registry::{ChannelState, ConnectionId, Registry, RegistryError},
60    router::{RouteCtx, RouterError},
61    scopes::{BoundScope, HelloLaunchNonces, ScopeTable},
62    server::MAX_PENDING_ROUTE_BINDS_PER_TARGET,
63    stderr_tail::{CaptureState, TailEntry},
64    supervise::{
65        validate_spec, ModuleProcessLiveness, ReservedHelloRejection, SpawnSubscribeRefusal,
66        SupervisorHandle, SwapHelloAdmission,
67    },
68    ConnectedClients, DaemonCounters, Frame, ProjectRootId, Supervisor,
69};
70
71/// Lowest envelope version this subc build will negotiate.
72///
73/// Module HELLO negotiation is exact: peers must use the daemon's locked
74/// protocol version. Older and newer peers receive `version_unsupported` and
75/// are not registered.
76pub const MIN_SUPPORTED_VERSION: u8 = PROTOCOL_VERSION;
77
78const CAP_MANIFEST_REGISTRATION: &str = "manifest_registration_v1";
79const CAP_CHANNEL_LIFECYCLE: &str = "channel_lifecycle_v1";
80const CAP_PING_PONG: &str = "ping_pong_v1";
81const CAP_SESSION_ATTACH: &str = "session_attach_v1";
82const CAP_ADMISSION_FACTS_RELAY: &str = "admission_facts_relay_v1";
83
84const SUBC_CONTROL_OPS: &[&str] = &[
85    ops::SERVER_DESCRIBE,
86    ops::CATALOG_LIST,
87    ops::ROUTE_OPEN,
88    ops::ROUTE_POLL,
89    ops::ROUTE_CLOSING,
90    ops::ROUTE_CLOSED,
91    ops::SUPERVISOR_LIST,
92    ops::SUPERVISOR_RESTART,
93    ops::SUPERVISOR_SWAP,
94    ops::SUPERVISOR_RELOAD,
95    ops::SUPERVISOR_RESCAN,
96    ops::SUPERVISOR_RELEASE_RESERVED,
97    ops::SUPERVISOR_SET_ENABLED,
98    ops::SUPERVISOR_HEALTH_PROBE,
99    ops::SUPERVISOR_HEALTH,
100    ops::SUPERVISOR_STDERR_TAIL,
101    ops::SUPERVISOR_TERMINALS,
102    ops::SUPERVISOR_ROUTES,
103    ops::SUPERVISOR_PROVENANCE,
104    ops::SUPERVISOR_SPAWN_SNAPSHOT,
105    ops::SUPERVISOR_SPAWN_SUBSCRIBE,
106];
107
108const MODULE_TO_SUBC_CONTROL_OPS: &[&str] = &[
109    MODULE_TO_SUBC_OP_CATALOG_UPDATE,
110    "supervisor.live_roots",
111    SCOPE_SYNC_OP,
112    SCOPE_DESCRIBE_OP,
113];
114
115/// Module-originated ops the daemon answers but does not advertise in
116/// `HELLO_ACK`. Empty today; an op is served from here while the feature it
117/// belongs to is incomplete, so no module is told it works before it does.
118const MODULE_TO_SUBC_UNADVERTISED_OPS: &[&str] = &[];
119
120const MODULE_BASELINE_CONTROL_OPS: &[&str] = &["route.bind", "route.status"];
121
122/// How long subc waits for a module to ack a relayed route.bind before returning
123/// `module_timeout`. The ack waits on the module's own configure, which for AFT
124/// includes a synchronous bounded project walk (up to ~20k files) plus gitignore
125/// and DB-open work — on a cold page cache or a large repo that legitimately
126/// exceeds a couple of seconds. The default is generous because rejecting a VALID
127/// bind is far worse than waiting on a slow one; a consumer that wants a tighter
128/// bound retries the bind itself (the sanctioned warm-bind-retry pattern).
129pub const DEFAULT_ROUTE_BIND_RELAY_TIMEOUT: Duration = Duration::from_secs(12);
130
131/// How many CONSECUTIVE full-budget relay timeouts against one target module
132/// open that module's bind-relay breaker.
133///
134/// Three, so that the breaker is NOT REACHABLE INSIDE ONE CLIENT CALL. Both
135/// SDKs default to a 30s request deadline and the relay budget defaults to 12s,
136/// so three consecutive full-budget timeouts take ~36s to observe: every client
137/// whose open contributed to opening the breaker had already given up on its
138/// own. That is what makes opening the breaker unable to turn a call that would
139/// have succeeded into a refusal — it can only make an already-failing module
140/// fail faster.
141///
142/// Two would be reachable inside one default deadline. One would convict a
143/// module on a single cold-cache bind, which is exactly the valid-but-slow case
144/// `DEFAULT_ROUTE_BIND_RELAY_TIMEOUT`'s own doc comment exists to protect.
145pub const DEFAULT_ROUTE_BIND_BREAKER_THRESHOLD: u32 = 3;
146
147/// How long a module's bind-relay breaker stays open before exactly one
148/// `route.open` is let through as a probe.
149///
150/// Bounded BELOW by the relay budget: a cooldown at or under the 12s budget
151/// re-pays a full-budget stall almost continuously, and the breaker stops being
152/// a saving worth its own state. Bounded ABOVE by the SDKs' 30s default request
153/// deadline: a client that starts retrying after the module recovers has to get
154/// a probe opportunity inside its own deadline, or the breaker converts a
155/// recovered module into a failed call — the failure it exists to prevent,
156/// pointed the other way.
157///
158/// 20s sits between those with room on both sides, and it caps what a wedged
159/// module can cost at one full-budget wait per 20s ACROSS THE WHOLE DAEMON
160/// rather than one per `route.open` per connection. The stall that motivated
161/// this, with its measurements, is written up in
162/// `docs/designs/route-open-head-of-line.md`: 268 opens against one module each
163/// waited the whole budget out.
164pub const DEFAULT_ROUTE_BIND_BREAKER_COOLDOWN: Duration = Duration::from_secs(20);
165
166const DEFAULT_HEALTH_PROBE_TIMEOUT: Duration = Duration::from_secs(5);
167const SLOW_CONTROL_DISPATCH_THRESHOLD: Duration = Duration::from_secs(1);
168
169fn reload_verdict(
170    configured: &Path,
171    spawned_from: Option<&Path>,
172    image: subc_control::RunningImageAgreement,
173) -> PendingReloadVerdict {
174    let path = match spawned_from {
175        Some(spawned_from) if configured == spawned_from => ReloadPathAgreement::Match,
176        Some(spawned_from) => ReloadPathAgreement::Mismatch {
177            configured: configured.to_path_buf(),
178            spawned_from: spawned_from.to_path_buf(),
179        },
180        None => ReloadPathAgreement::Unavailable {
181            reason: if matches!(
182                image,
183                subc_control::RunningImageAgreement::Unavailable {
184                    reason: subc_control::RunningImageUnavailableReason::NotRunning
185                }
186            ) {
187                ReloadPathUnavailableReason::NotRunning
188            } else {
189                ReloadPathUnavailableReason::SpawnedPathUnavailable
190            },
191        },
192    };
193    PendingReloadVerdict { path, image }
194}
195
196#[derive(Clone)]
197struct DaemonProvenanceFacts {
198    build: DaemonBuildProvenance,
199    pid: Option<u32>,
200    started_at_ms: Option<u64>,
201    start_clock: Option<crate::clock::StartClock>,
202    executable_path: Option<PathBuf>,
203    executable_identity: Option<SpawnedFileIdentity>,
204    process_start_time: Option<u64>,
205    probe: ExecutableIdentityProbe,
206}
207
208impl Default for DaemonProvenanceFacts {
209    fn default() -> Self {
210        Self {
211            build: DaemonBuildProvenance {
212                build_git_sha: None,
213                build_lock_digest: None,
214            },
215            pid: None,
216            started_at_ms: None,
217            start_clock: None,
218            executable_path: None,
219            executable_identity: None,
220            process_start_time: None,
221            probe: ExecutableIdentityProbe::default(),
222        }
223    }
224}
225
226#[derive(Debug, Clone)]
227struct SupervisorRescanContext {
228    supervisor: Supervisor,
229    config_path: PathBuf,
230    configured_port: Option<u16>,
231    storage_config: Option<crate::daemon_config::StorageConfig>,
232    admission_facts_carrier_module_id: Option<String>,
233    admission_facts_targets: Option<Vec<String>>,
234    scope_authority_owners: Vec<String>,
235}
236
237/// Refusal labels passed to `observe_route_open_refusal` that mean the target
238/// module is not serving right now, and so open or extend an outage in the
239/// route outage tracker. Every one of them is only reachable after the target
240/// was found in the registry, which is what keeps an arbitrary client-chosen
241/// id from ever creating tracker state.
242///
243/// Deliberately absent: `not_registered` and `removed` (the id may be
244/// anything a client sent, and a removed module is gone on purpose),
245/// `protocol_none` (such a module never serves routes, so nothing is out),
246/// `role_not_provided`, `op_not_allowed`, `bad_consumer_identity`, the
247/// capability and admission-facts refusals (they refuse the caller, not a
248/// module outage), and `relay_reservation_failed` (its code ranges over
249/// capacity limits as well as a vanished connection). Capacity, breaker,
250/// relay-timeout and module-rejection refusals do not pass through that
251/// function at all; the breaker logs its own transitions.
252///
253/// The two not-serving refusals that bypass that function record themselves
254/// at their own sites: `supervised_not_registered` and `declared_not_ready`.
255/// `required_capability_unprovided` is not tracked: the module itself is up,
256/// and the outage belongs to the missing provider.
257const ROUTE_OPEN_NOT_SERVING_REASONS: &[&str] = &[
258    "reloading",
259    "supervisor_not_live",
260    "registration_not_active",
261    "no_forwarding_connection",
262    "relay_send_failed",
263];
264
265/// Real channel-0 control handler for subc itself.
266#[derive(Clone)]
267pub struct ControlHandler {
268    registry: Arc<Registry>,
269    forwarding: Arc<ForwardingTable>,
270    process_liveness: Option<Arc<dyn ModuleProcessLiveness>>,
271    supervisor: SupervisorHandle,
272    subc_capabilities: Arc<[String]>,
273    /// Daemon-wide route.bind relay budget. Used as the fallback when the
274    /// target module has no per-module override in
275    /// `route_bind_relay_timeouts`.
276    route_bind_relay_timeout: Duration,
277    /// Per-module route.bind relay budget overrides, keyed by module id. When
278    /// `handle_route_open` resolves the deadline for a target module, a
279    /// per-module entry wins over the daemon-wide value above.
280    route_bind_relay_timeouts: BTreeMap<String, Duration>,
281    /// Per-target-module bind-relay breaker state. Shared with the forwarding
282    /// table, which is where a new module connection resets it.
283    route_bind_breakers: RouteBindBreakers,
284    /// Live relay admissions keyed by target module. Shared through the
285    /// forwarding table so cloned or separately built handlers enforce one cap.
286    route_bind_concurrency: RouteBindConcurrency,
287    /// Start and end of each module's not-serving period as seen by
288    /// `route.open`, so an outage gets one line at each edge instead of only
289    /// the per-refusal INFO lines. Taken from the forwarding table, so every
290    /// handler built over one table shares it.
291    route_outages: Arc<crate::route_outage::RouteOutageTracker>,
292    /// Consecutive relay timeouts that open a module's breaker.
293    route_bind_breaker_threshold: u32,
294    /// How long a breaker stays open before one probe is admitted.
295    route_bind_breaker_cooldown: Duration,
296    health_probe_timeout: Duration,
297    /// Central storage policy. When set, each registering module receives its
298    /// resolved storage descriptor in HELLO_ACK; `None` leaves the field absent.
299    storage_config: Option<crate::daemon_config::StorageConfig>,
300    /// The machine id established at boot, served on every HELLO_ACK and on
301    /// `server.describe`. Fixed for the daemon's lifetime: `ck machine adopt`
302    /// changes the file, never this value. `None` serves no id.
303    machine_id: Option<crate::machine_id::MachineId>,
304    admission_facts_carrier_module_id: Option<String>,
305    admission_facts_targets: Option<Vec<String>>,
306    /// Scope records with their sync authorities and tombstones; see
307    /// `crate::scopes`. Shared by clones of this handler, so every connection
308    /// reads and writes one table.
309    scopes: Arc<RwLock<ScopeTable>>,
310    /// The configured `scope_authority_owners`, kept so a rescan can report a
311    /// changed value as needing a daemon restart; rescan never applies it.
312    scope_authority_owners: Vec<String>,
313    /// The launch nonce each module connection presented at HELLO, which is how
314    /// a `scope.sync` is matched to the owner's current launch.
315    hello_launch_nonces: Arc<Mutex<HelloLaunchNonces>>,
316    rescan: Option<SupervisorRescanContext>,
317    connected_clients: ConnectedClients,
318    counters: DaemonCounters,
319    capability_evaluator: Arc<CapabilityRequirementEvaluator>,
320    daemon_provenance: DaemonProvenanceFacts,
321    #[cfg(test)]
322    control_dispatch_delay: Option<Duration>,
323    #[cfg(test)]
324    provenance_probe_override: Option<subc_control::RunningImageAgreement>,
325}
326
327impl fmt::Debug for ControlHandler {
328    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329        f.debug_struct("ControlHandler")
330            .field("registry", &self.registry)
331            .field("forwarding", &self.forwarding)
332            .field("process_liveness", &self.process_liveness.is_some())
333            .field("supervisor", &self.supervisor)
334            .field("subc_capabilities", &self.subc_capabilities)
335            .finish()
336    }
337}
338
339struct RouteOpenRequest {
340    target: RouteTarget,
341    identity: BindIdentity,
342    consumer_identity: Option<ConsumerIdentity>,
343    consumer_capabilities: Option<Vec<String>>,
344    role_versions: Option<BTreeMap<String, String>>,
345    admission_facts: Option<serde_json::Value>,
346    scope: Option<ScopeSelector>,
347}
348
349struct RouteBindReservationGuard {
350    forwarding: Arc<ForwardingTable>,
351    endpoint: ModuleEndpointId,
352    relay_corr: u64,
353    armed: bool,
354}
355
356struct ModuleControlRpcGuard {
357    forwarding: Arc<ForwardingTable>,
358    endpoint: ModuleEndpointId,
359    corr: u64,
360    armed: bool,
361}
362
363impl ModuleControlRpcGuard {
364    fn new(forwarding: Arc<ForwardingTable>, endpoint: ModuleEndpointId, corr: u64) -> Self {
365        Self {
366            forwarding,
367            endpoint,
368            corr,
369            armed: true,
370        }
371    }
372
373    fn disarm(&mut self) {
374        self.armed = false;
375    }
376}
377
378impl Drop for ModuleControlRpcGuard {
379    fn drop(&mut self) {
380        if self.armed {
381            let _ = self
382                .forwarding
383                .cancel_module_control_rpc(self.endpoint, self.corr);
384        }
385    }
386}
387
388impl RouteBindReservationGuard {
389    fn new(forwarding: Arc<ForwardingTable>, endpoint: ModuleEndpointId, relay_corr: u64) -> Self {
390        Self {
391            forwarding,
392            endpoint,
393            relay_corr,
394            armed: true,
395        }
396    }
397
398    fn release_and_disarm(&mut self) {
399        if !self.armed {
400            return;
401        }
402        if let Ok(Some(target)) = self.forwarding.abort_pending_relay(
403            self.endpoint,
404            self.relay_corr,
405            RouteBindRelayOutcome::ModuleGone("route.open handler canceled".to_string()),
406        ) {
407            send_goodbye_target_best_effort(
408                &self.forwarding.counters(),
409                &target,
410                "canceled route.bind",
411            );
412        }
413        self.armed = false;
414    }
415
416    fn disarm(&mut self) {
417        self.armed = false;
418    }
419}
420
421impl Drop for RouteBindReservationGuard {
422    fn drop(&mut self) {
423        self.release_and_disarm();
424    }
425}
426
427/// Per-target-module circuit breaker around the `route.bind` relay.
428///
429/// The connection reader is serial per connection, so a module whose `on_bind`
430/// sits on the ack blocks every LATER frame on the connections that call it,
431/// including calls to unrelated modules. This does not make any module's bind
432/// fast; it stops the daemon paying the full budget again and again for a
433/// condition it has already observed.
434///
435/// State is keyed by TARGET MODULE and shared by every connection: a wedged
436/// module wedges everyone, so what one connection learned should protect the
437/// rest.
438///
439/// THE MAP IS EMPTY WHILE THE FLEET IS HEALTHY. An entry appears only when a
440/// relay to that module has actually timed out, and is removed again when a
441/// relay is accepted or the module reconnects, so it cannot grow with traffic
442/// or with modules that behave.
443///
444/// # Why a `std` mutex here is not the head-of-line defect again
445///
446/// Acquisition never awaits. The critical section is a hash lookup plus a few
447/// integer updates, with no I/O and no `.await` inside it, so a reader task
448/// cannot be descheduled behind it the way it can behind
449/// `tokio::sync::Mutex::lock().await` or a semaphore permit. It is the same
450/// primitive, held for the same kind of work, as the refusal counter this very
451/// path already increments.
452///
453/// It is also NOT on the data-plane splice path: only `route.open` and module
454/// registration touch it, so bound-route frames gain no state check and no
455/// contention.
456#[derive(Debug, Clone, Default)]
457pub(crate) struct RouteBindBreakers {
458    modules: Arc<Mutex<HashMap<String, ModuleBreakerState>>>,
459}
460
461#[derive(Debug, Clone, Default)]
462pub(crate) struct RouteBindConcurrency {
463    modules: Arc<Mutex<HashMap<String, usize>>>,
464}
465
466struct RouteBindConcurrencyGuard {
467    concurrency: RouteBindConcurrency,
468    module_id: String,
469}
470
471impl RouteBindConcurrency {
472    /// Admit without waiting. Waiting here would move the bind stall from the
473    /// module reply to a semaphore and restore reader head-of-line blocking.
474    fn try_admit(&self, module_id: &str, limit: usize) -> Result<RouteBindConcurrencyGuard, usize> {
475        let mut modules = self
476            .modules
477            .lock()
478            .expect("route.bind concurrency mutex poisoned");
479        let in_flight = modules.entry(module_id.to_string()).or_default();
480        if *in_flight >= limit {
481            return Err(*in_flight);
482        }
483        *in_flight += 1;
484        Ok(RouteBindConcurrencyGuard {
485            concurrency: self.clone(),
486            module_id: module_id.to_string(),
487        })
488    }
489}
490
491impl Drop for RouteBindConcurrencyGuard {
492    fn drop(&mut self) {
493        let mut modules = self
494            .concurrency
495            .modules
496            .lock()
497            .expect("route.bind concurrency mutex poisoned");
498        let remove = {
499            let in_flight = modules
500                .get_mut(&self.module_id)
501                .expect("admitted route.bind has a concurrency entry");
502            *in_flight -= 1;
503            *in_flight == 0
504        };
505        if remove {
506            modules.remove(&self.module_id);
507        }
508    }
509}
510
511#[derive(Debug, Default)]
512struct ModuleBreakerState {
513    /// Relay timeouts observed with no accepted relay in between.
514    consecutive_timeouts: u32,
515    /// `Some` while the breaker is open: the instant the cooldown expires and
516    /// the next arrival may probe. `None` means closed.
517    cooldown_until: Option<Instant>,
518    /// A half-open probe has been admitted and has not settled yet. This is
519    /// what makes the probe EXACTLY ONE: the flag is set under the same lock
520    /// that read the cooldown, so concurrent opens arriving at the moment the
521    /// cooldown expires cannot all decide that they are the probe.
522    probe_in_flight: Option<Arc<()>>,
523}
524
525/// What the breaker decided for one `route.open`, before any relay work.
526enum RouteBindAdmission<'a> {
527    Admitted {
528        guard: RouteBindBreakerGuard<'a>,
529        /// This open is the single half-open probe, so the transition is worth
530        /// one log line.
531        probe: bool,
532    },
533    Refused {
534        consecutive_timeouts: u32,
535        /// What is left of the cooldown. Zero when the refusal is because the
536        /// one probe is already in flight rather than because the cooldown has
537        /// not elapsed.
538        retry_in: Duration,
539        probe_in_flight: bool,
540    },
541}
542
543/// An outstanding admission, which must be told how its relay settled.
544///
545/// `Drop` settles it as inconclusive, so an early return between admission and
546/// the relay -- or the whole handler being cancelled when the client
547/// disconnects -- releases a half-open probe slot instead of leaving the
548/// breaker wedged half-open with no further probes.
549struct RouteBindBreakerGuard<'a> {
550    breakers: RouteBindBreakers,
551    module_id: &'a str,
552    probe_token: Option<Arc<()>>,
553    settled: bool,
554}
555
556impl RouteBindBreakerGuard<'_> {
557    /// The module answered within the budget and took the bind. THE ONLY
558    /// OUTCOME THAT CLEARS THE COUNT. Returns true when this closed an open
559    /// breaker, which is a transition worth logging.
560    fn record_accepted(&mut self) -> bool {
561        self.settled = true;
562        self.breakers.record_accepted(self.module_id)
563    }
564
565    /// The relay burned the whole budget with no answer. THE ONLY ARM THAT
566    /// COUNTS TOWARD OPENING.
567    fn record_timeout(&mut self, threshold: u32, cooldown: Duration) -> Option<BreakerOpened> {
568        self.settled = true;
569        self.breakers.record_timeout(
570            self.module_id,
571            self.probe_token.as_ref(),
572            threshold,
573            cooldown,
574        )
575    }
576
577    /// Everything else: the module REJECTED the bind, its connection went away
578    /// mid-relay, or the waiter was cancelled.
579    ///
580    /// None of these is evidence that a module is slow, and each already has
581    /// its own refusal with its own code. A module that rejects a bind in
582    /// microseconds is healthy and must never be convicted for it; a module
583    /// that died has said nothing about the module that replaces it. So these
584    /// neither increment nor reset the count -- they only release a probe slot.
585    fn record_inconclusive(&mut self) {
586        self.settled = true;
587        self.breakers
588            .record_inconclusive(self.module_id, self.probe_token.as_ref());
589    }
590}
591
592impl Drop for RouteBindBreakerGuard<'_> {
593    fn drop(&mut self) {
594        if !self.settled {
595            self.breakers
596                .record_inconclusive(self.module_id, self.probe_token.as_ref());
597        }
598    }
599}
600
601/// The breaker moved to open, reported so the caller can log it outside the
602/// lock. Opening is rare and load-bearing; the refusals that follow are
603/// frequent and are counted rather than logged.
604struct BreakerOpened {
605    consecutive_timeouts: u32,
606    /// True when a failed probe re-opened an already-open breaker, which reads
607    /// very differently in a log from a first opening.
608    reopened_after_probe: bool,
609}
610
611impl RouteBindBreakers {
612    fn lock(&self) -> std::sync::MutexGuard<'_, HashMap<String, ModuleBreakerState>> {
613        self.modules
614            .lock()
615            .expect("route.bind breaker mutex poisoned")
616    }
617
618    /// Decide whether this `route.open` may attempt its relay. Takes the map
619    /// lock and nothing else, and never awaits.
620    fn admit<'a>(&self, module_id: &'a str) -> RouteBindAdmission<'a> {
621        let admitted = |probe_token: Option<Arc<()>>| RouteBindAdmission::Admitted {
622            probe: probe_token.is_some(),
623            guard: RouteBindBreakerGuard {
624                breakers: self.clone(),
625                module_id,
626                probe_token,
627                settled: false,
628            },
629        };
630
631        let mut modules = self.lock();
632        let Some(state) = modules.get_mut(module_id) else {
633            return admitted(None);
634        };
635        let Some(cooldown_until) = state.cooldown_until else {
636            return admitted(None);
637        };
638        if state.probe_in_flight.is_some() {
639            return RouteBindAdmission::Refused {
640                consecutive_timeouts: state.consecutive_timeouts,
641                retry_in: Duration::ZERO,
642                probe_in_flight: true,
643            };
644        }
645        let now = Instant::now();
646        if now < cooldown_until {
647            return RouteBindAdmission::Refused {
648                consecutive_timeouts: state.consecutive_timeouts,
649                retry_in: cooldown_until - now,
650                probe_in_flight: false,
651            };
652        }
653        let token = Arc::new(());
654        state.probe_in_flight = Some(Arc::clone(&token));
655        admitted(Some(token))
656    }
657
658    fn record_accepted(&self, module_id: &str) -> bool {
659        self.lock()
660            .remove(module_id)
661            .is_some_and(|state| state.cooldown_until.is_some())
662    }
663
664    fn record_timeout(
665        &self,
666        module_id: &str,
667        probe_token: Option<&Arc<()>>,
668        threshold: u32,
669        cooldown: Duration,
670    ) -> Option<BreakerOpened> {
671        let mut modules = self.lock();
672        let state = modules.entry(module_id.to_string()).or_default();
673        let was_open = state.cooldown_until.is_some();
674        let was_probe = Self::owns_probe(state, probe_token);
675        if was_probe {
676            state.probe_in_flight = None;
677        }
678        state.consecutive_timeouts = state.consecutive_timeouts.saturating_add(1);
679        if state.consecutive_timeouts < threshold {
680            return None;
681        }
682        state.cooldown_until = Some(Instant::now() + cooldown);
683        Some(BreakerOpened {
684            consecutive_timeouts: state.consecutive_timeouts,
685            reopened_after_probe: was_open && was_probe,
686        })
687    }
688
689    fn owns_probe(state: &ModuleBreakerState, token: Option<&Arc<()>>) -> bool {
690        // A relay can finish after the breaker was reset or after it opened
691        // again and started a new probe. Only the guard whose token matches the
692        // active probe may release it, so a late relay never frees a newer probe.
693        state
694            .probe_in_flight
695            .as_ref()
696            .zip(token)
697            .is_some_and(|(active, token)| Arc::ptr_eq(active, token))
698    }
699
700    fn record_inconclusive(&self, module_id: &str, probe_token: Option<&Arc<()>>) {
701        if let Some(state) = self.lock().get_mut(module_id) {
702            if Self::owns_probe(state, probe_token) {
703                state.probe_in_flight = None;
704            }
705        }
706    }
707
708    /// Discard what was learned about a module, because the process it was
709    /// learned about is gone. Returns the discarded count when it was non-zero.
710    ///
711    /// A BREAKER IS A CACHED VERDICT ABOUT A PROCESS, NOT ABOUT A NAME. A
712    /// `module_id` is a configuration identity that outlives any particular
713    /// child; what the breaker observed was the process behind the module
714    /// connection of the moment. When a new connection registers under that id
715    /// the verdict's subject no longer exists, so the verdict is stale by
716    /// construction rather than merely likely to be wrong. Keeping it would
717    /// apply a dead process's record to a live one, which is the same defect
718    /// class this breaker exists to stop the daemon committing.
719    ///
720    /// A half-open probe in flight is discarded with the rest: it was a
721    /// question about the old process.
722    pub(crate) fn reset_for_new_module_connection(&self, module_id: &str) -> Option<u32> {
723        self.lock()
724            .remove(module_id)
725            .map(|state| state.consecutive_timeouts)
726            .filter(|discarded| *discarded > 0)
727    }
728
729    /// Open breakers, for the `server.describe` counters object. `None` when
730    /// none is open, so the key stays absent rather than present-and-empty.
731    ///
732    /// This is the operator's answer to "is this module refusing instantly or
733    /// is it fine?", which look identical from a client that retries and then
734    /// succeeds.
735    fn open_snapshot(&self) -> Option<serde_json::Value> {
736        let now = Instant::now();
737        let modules = self.lock();
738        let open = modules
739            .iter()
740            .filter_map(|(module_id, state)| {
741                let cooldown_until = state.cooldown_until?;
742                Some((
743                    module_id.clone(),
744                    serde_json::json!({
745                        "consecutive_timeouts": state.consecutive_timeouts,
746                        "cooldown_remaining_ms":
747                            cooldown_until.saturating_duration_since(now).as_millis() as u64,
748                        "probe_in_flight": state.probe_in_flight.is_some(),
749                    }),
750                ))
751            })
752            .collect::<serde_json::Map<String, serde_json::Value>>();
753        (!open.is_empty()).then_some(serde_json::Value::Object(open))
754    }
755}
756
757impl ControlHandler {
758    pub fn new(registry: Arc<Registry>) -> Self {
759        Self::with_forwarding(registry, Arc::new(ForwardingTable::default()))
760    }
761
762    pub fn with_forwarding(registry: Arc<Registry>, forwarding: Arc<ForwardingTable>) -> Self {
763        let counters = forwarding.counters();
764        // Taken from the forwarding table rather than created here, so that the
765        // breaker a `route.open` consults is the same one a module's
766        // registration resets, however many handlers are built over one table.
767        let route_bind_breakers = forwarding.route_bind_breakers();
768        let route_bind_concurrency = forwarding.route_bind_concurrency();
769        let route_outages = forwarding.route_outages();
770        Self {
771            registry,
772            forwarding,
773            process_liveness: None,
774            supervisor: SupervisorHandle::new(),
775            subc_capabilities: Arc::from([
776                CAP_MANIFEST_REGISTRATION.to_string(),
777                CAP_CHANNEL_LIFECYCLE.to_string(),
778                CAP_PING_PONG.to_string(),
779                CAP_SESSION_ATTACH.to_string(),
780                CAP_ADMISSION_FACTS_RELAY.to_string(),
781                CAP_SCOPES_V1.to_string(),
782                CAP_ROUTE_ROLE_VERSIONS_V1.to_string(),
783            ]),
784            route_bind_relay_timeout: DEFAULT_ROUTE_BIND_RELAY_TIMEOUT,
785            route_bind_relay_timeouts: BTreeMap::new(),
786            route_bind_breakers,
787            route_bind_concurrency,
788            route_outages,
789            route_bind_breaker_threshold: DEFAULT_ROUTE_BIND_BREAKER_THRESHOLD,
790            route_bind_breaker_cooldown: DEFAULT_ROUTE_BIND_BREAKER_COOLDOWN,
791            health_probe_timeout: DEFAULT_HEALTH_PROBE_TIMEOUT,
792            storage_config: None,
793            machine_id: None,
794            admission_facts_carrier_module_id: None,
795            admission_facts_targets: None,
796            scopes: Arc::new(RwLock::new(ScopeTable::new(
797                crate::daemon_config::default_scope_authority_owners(),
798            ))),
799            scope_authority_owners: crate::daemon_config::default_scope_authority_owners(),
800            hello_launch_nonces: Arc::new(Mutex::new(HelloLaunchNonces::default())),
801            rescan: None,
802            connected_clients: ConnectedClients::new(),
803            counters,
804            capability_evaluator: Arc::new(CapabilityRequirementEvaluator::new()),
805            daemon_provenance: DaemonProvenanceFacts::default(),
806            #[cfg(test)]
807            control_dispatch_delay: None,
808            #[cfg(test)]
809            provenance_probe_override: None,
810        }
811    }
812
813    /// Set the central storage policy: registering modules then receive their
814    /// resolved storage descriptor in HELLO_ACK.
815    pub fn with_storage_config(
816        mut self,
817        storage_config: Option<crate::daemon_config::StorageConfig>,
818    ) -> Self {
819        self.storage_config = storage_config;
820        self
821    }
822
823    /// Set the machine id served to every registering module (HELLO_ACK) and on
824    /// `server.describe`.
825    pub fn with_machine_id(mut self, machine_id: Option<crate::machine_id::MachineId>) -> Self {
826        self.machine_id = machine_id;
827        self
828    }
829
830    /// Configure the exact reserved module and target ids permitted to relay
831    /// opaque admission facts. Config-file loading validates this authority;
832    /// this builder keeps the same policy available to embedded test daemons.
833    pub fn with_admission_facts_config(
834        mut self,
835        carrier_module_id: Option<String>,
836        targets: Option<Vec<String>>,
837    ) -> Self {
838        self.admission_facts_carrier_module_id = carrier_module_id;
839        self.admission_facts_targets = targets;
840        self
841    }
842
843    /// Set the module ids whose scopes may carry `agent_id` and `delegates`.
844    /// Replaces the scope table with an empty one under the new list, so call it
845    /// while building the handler, before any module can sync.
846    pub fn with_scope_authority_owners(mut self, owners: Vec<String>) -> Self {
847        self.scopes = Arc::new(RwLock::new(ScopeTable::new(owners.iter().cloned())));
848        self.scope_authority_owners = owners;
849        self
850    }
851
852    /// Override the route.bind relay timeout. Used by tests that assert the
853    /// timeout path so they don't block on the production-safe default.
854    pub fn with_route_bind_relay_timeout(mut self, timeout: Duration) -> Self {
855        self.route_bind_relay_timeout = timeout;
856        self
857    }
858
859    /// Install per-module route.bind relay budget overrides. A module id
860    /// listed here wins over the daemon-wide default set via
861    /// `with_route_bind_relay_timeout`. Values are pre-resolved at parse time
862    /// from `subc.jsonc` (per-module > daemon-wide > absent), so callers pass
863    /// the same `Duration` the bind path will use.
864    pub fn with_route_bind_relay_timeouts(
865        mut self,
866        timeouts: impl IntoIterator<Item = (String, Duration)>,
867    ) -> Self {
868        self.route_bind_relay_timeouts = timeouts.into_iter().collect();
869        self
870    }
871
872    /// Resolve the route.bind relay budget for a specific target module id.
873    /// Per-module overrides win; the daemon-wide value (set via
874    /// `with_route_bind_relay_timeout` or the built-in default) is the
875    /// fallback. Exposed so config-aware callers (bootstrap, tests) can audit
876    /// the same resolution `handle_route_open` will use.
877    pub fn route_bind_relay_timeout_for(&self, module_id: &str) -> Duration {
878        self.route_bind_relay_timeouts
879            .get(module_id)
880            .copied()
881            .unwrap_or(self.route_bind_relay_timeout)
882    }
883
884    /// Override the per-module bind-relay breaker policy.
885    ///
886    /// Used by tests, which cannot spend three production budgets opening a
887    /// breaker or twenty seconds waiting for its cooldown. The production
888    /// values are `DEFAULT_ROUTE_BIND_BREAKER_THRESHOLD` and
889    /// `DEFAULT_ROUTE_BIND_BREAKER_COOLDOWN`, whose doc comments carry the
890    /// reasoning for the numbers.
891    pub fn with_route_bind_breaker(mut self, threshold: u32, cooldown: Duration) -> Self {
892        self.route_bind_breaker_threshold = threshold.max(1);
893        self.route_bind_breaker_cooldown = cooldown;
894        self
895    }
896
897    #[cfg(test)]
898    pub(crate) fn with_health_probe_timeout(mut self, timeout: Duration) -> Self {
899        self.health_probe_timeout = timeout;
900        self
901    }
902
903    #[cfg(test)]
904    pub(crate) fn with_control_dispatch_delay(mut self, delay: Duration) -> Self {
905        self.control_dispatch_delay = Some(delay);
906        self
907    }
908
909    pub fn with_process_liveness(
910        mut self,
911        process_liveness: Arc<dyn ModuleProcessLiveness>,
912    ) -> Self {
913        self.process_liveness = Some(process_liveness);
914        self
915    }
916
917    pub fn with_supervisor(mut self, supervisor: SupervisorHandle) -> Self {
918        self.supervisor = supervisor;
919        self
920    }
921
922    pub fn with_daemon_provenance(
923        mut self,
924        pid: u32,
925        started_at_ms: u64,
926        executable_path: Option<PathBuf>,
927        build_git_sha: Option<String>,
928        build_lock_digest: Option<String>,
929    ) -> Self {
930        let executable_identity = executable_path.as_deref().and_then(spawned_file_identity);
931        let process_start_time = process_start_time(pid);
932        self.daemon_provenance = DaemonProvenanceFacts {
933            build: DaemonBuildProvenance {
934                build_git_sha,
935                build_lock_digest,
936            },
937            pid: Some(pid),
938            started_at_ms: Some(started_at_ms),
939            start_clock: None,
940            executable_path,
941            executable_identity,
942            process_start_time,
943            probe: ExecutableIdentityProbe::default(),
944        };
945        self
946    }
947
948    pub(crate) fn with_daemon_start_clock(mut self, clock: crate::clock::StartClock) -> Self {
949        self.daemon_provenance.start_clock = Some(clock);
950        self
951    }
952
953    #[cfg(test)]
954    fn with_provenance_probe_result(mut self, result: subc_control::RunningImageAgreement) -> Self {
955        self.provenance_probe_override = Some(result);
956        self
957    }
958
959    /// Install the configured module set and its reserved capability bindings.
960    /// Bindings are configuration-scoped and may point at a provider that has not
961    /// been installed yet, so this does not require the bound module to exist.
962    pub fn with_capability_config(
963        self,
964        modules: impl IntoIterator<Item = (String, bool)>,
965        reserved_capabilities: BTreeMap<String, String>,
966    ) -> Self {
967        self.capability_evaluator
968            .configure(modules, reserved_capabilities);
969        self
970    }
971
972    pub fn with_supervisor_rescan(
973        mut self,
974        supervisor: Supervisor,
975        config_path: impl Into<PathBuf>,
976        configured_port: Option<u16>,
977    ) -> Self {
978        self.rescan = Some(SupervisorRescanContext {
979            supervisor,
980            config_path: config_path.into(),
981            configured_port,
982            storage_config: self.storage_config.clone(),
983            admission_facts_carrier_module_id: self.admission_facts_carrier_module_id.clone(),
984            admission_facts_targets: self.admission_facts_targets.clone(),
985            scope_authority_owners: self.scope_authority_owners.clone(),
986        });
987        self
988    }
989
990    pub fn with_connected_clients(mut self, connected_clients: ConnectedClients) -> Self {
991        self.connected_clients = connected_clients;
992        self
993    }
994
995    pub fn forwarding(&self) -> Arc<ForwardingTable> {
996        Arc::clone(&self.forwarding)
997    }
998
999    pub(crate) fn counters(&self) -> DaemonCounters {
1000        self.counters.clone()
1001    }
1002
1003    /// Wake at each candidate's own deadline so a stalled fresh exec emits its
1004    /// requirement event without depending on an operator polling a status command.
1005    pub fn spawn_capability_deadline_loop(self: Arc<Self>) {
1006        tokio::spawn(async move {
1007            loop {
1008                self.capability_evaluator
1009                    .wait_for_change_or_deadline()
1010                    .await;
1011                self.refresh_capability_requirements();
1012            }
1013        });
1014    }
1015
1016    fn runtime_capability_snapshot(
1017        &self,
1018    ) -> Result<(Vec<RuntimeModule>, Vec<RegisteredModule>), RouterError> {
1019        let runtime = self
1020            .supervisor
1021            .list()
1022            .into_iter()
1023            .map(|module| {
1024                let status = module.status().map_err(|err| {
1025                    RouterError::backend(0, 0, format!("failed to read capability status: {err}"))
1026                })?;
1027                Ok(RuntimeModule {
1028                    module_id: status.module_id,
1029                    state: status.state,
1030                    enabled: status.enabled,
1031                })
1032            })
1033            .collect::<Result<Vec<_>, RouterError>>()?;
1034        let (_, registrations) = self.registry.list_modules().map_err(|err| {
1035            RouterError::backend(
1036                0,
1037                0,
1038                format!("failed to list capability registrations: {err}"),
1039            )
1040        })?;
1041        let registrations = registrations
1042            .into_iter()
1043            .map(|registration| RegisteredModule {
1044                module_id: registration.manifest.module_id,
1045                module_version: registration.manifest.module_version,
1046                capabilities: registration.manifest.capabilities,
1047            })
1048            .collect();
1049        Ok((runtime, registrations))
1050    }
1051
1052    /// The capability side effects of a module becoming the active registration
1053    /// for its id: cache its manifest (warning if its claims drifted), run the
1054    /// deny census when its declarations call for one, and recompute the
1055    /// requirement statuses. An ordinary HELLO does this as it registers; a swap
1056    /// candidate's does not, and the supervisor does it at promotion instead,
1057    /// through [`crate::supervise::SwapPromotionObserver`].
1058    fn apply_registration_capabilities(&self, registration: &crate::registry::ModuleRegistration) {
1059        let cached_registration = RegisteredModule {
1060            module_id: registration.manifest.module_id.clone(),
1061            module_version: registration.manifest.module_version.clone(),
1062            capabilities: registration.manifest.capabilities.clone(),
1063        };
1064        if self.capability_evaluator.record_hello(&cached_registration) {
1065            warn!(
1066                module_id = %cached_registration.module_id,
1067                "capability claims drifted from the cached manifest"
1068            );
1069        }
1070        if capability_census_trigger(None, registration.manifest.capabilities.as_ref()) {
1071            self.enforce_capability_denies();
1072        }
1073        self.refresh_capability_requirements();
1074    }
1075
1076    /// Point the shared supervisor handle at this handler for swap promotions.
1077    /// Called wherever a handler is put behind the `Arc` the router serves, so
1078    /// it can be held weakly.
1079    pub(crate) fn install_swap_promotion_observer(self: &Arc<Self>) {
1080        let observer: std::sync::Weak<dyn crate::supervise::SwapPromotionObserver> =
1081            Arc::downgrade(self) as std::sync::Weak<ControlHandler>;
1082        self.supervisor.set_swap_promotion_observer(observer);
1083    }
1084
1085    pub fn refresh_capability_requirements(&self) {
1086        match self.runtime_capability_snapshot() {
1087            Ok((runtime, registrations)) => {
1088                log_requirement_events(
1089                    self.capability_evaluator
1090                        .evaluate_now(&runtime, &registrations),
1091                );
1092            }
1093            Err(err) => warn!(error = %err, "failed to recompute capability requirements"),
1094        }
1095    }
1096
1097    /// Reconcile only live, attested route bindings after a capability deny edge
1098    /// or target claim was added. This is deliberately a control-plane census:
1099    /// the opaque forwarding hot path must not grow a per-frame capability check.
1100    fn enforce_capability_denies(&self) {
1101        let (_, registrations) = match self.registry.list_modules() {
1102            Ok(snapshot) => snapshot,
1103            Err(err) => {
1104                warn!(error = %err, "failed to read registrations for capability deny census");
1105                return;
1106            }
1107        };
1108        let manifests = registrations
1109            .into_iter()
1110            .map(|registration| {
1111                (
1112                    registration.manifest.module_id.clone(),
1113                    registration.manifest,
1114                )
1115            })
1116            .collect::<BTreeMap<_, _>>();
1117        let census = match self.forwarding.route_census(None) {
1118            Ok(census) => census,
1119            Err(err) => {
1120                warn!(error = %err, "failed to read route census for capability deny enforcement");
1121                return;
1122            }
1123        };
1124
1125        for (target_module_id, routes) in census {
1126            let Some(target_manifest) = manifests.get(&target_module_id) else {
1127                continue;
1128            };
1129            let mut closed_routes = Vec::new();
1130            let mut module_goodbyes = Vec::new();
1131            for route in routes {
1132                let Principal::Reserved {
1133                    module_id: opening_module_id,
1134                } = &route.principal
1135                else {
1136                    continue;
1137                };
1138                let Some(opening_manifest) = manifests.get(opening_module_id) else {
1139                    continue;
1140                };
1141                let Some(capability) = denied_capability(opening_manifest, target_manifest) else {
1142                    continue;
1143                };
1144
1145                match self.forwarding.release_client_route(
1146                    route.goodbye_target.connection_id,
1147                    route.goodbye_target.channel,
1148                    route.goodbye_target.epoch,
1149                ) {
1150                    Ok(RouteRelease::Removed(module_goodbye)) => {
1151                        warn!(
1152                            opening_module_id,
1153                            target_module_id,
1154                            capability,
1155                            "force-closing route because an attested capability deny edge now matches"
1156                        );
1157                        closed_routes.push(route);
1158                        module_goodbyes.push(module_goodbye);
1159                    }
1160                    Ok(RouteRelease::Stale | RouteRelease::Absent) => {}
1161                    Err(err) => warn!(
1162                        opening_module_id,
1163                        target_module_id,
1164                        capability,
1165                        error = %err,
1166                        "failed to force-close capability-denied route"
1167                    ),
1168                }
1169            }
1170
1171            if closed_routes.is_empty() {
1172                continue;
1173            }
1174            send_route_control_pushes(
1175                &self.forwarding,
1176                closed_routes,
1177                ClientControlPush::RouteClosed {
1178                    module_id: target_module_id,
1179                    channels: Vec::new(),
1180                    reason: RouteCloseReason::CapabilityDenied,
1181                    drained: false,
1182                    abandoned: 0,
1183                    excluded_subscriptions: 0,
1184                    terminal: Some(false),
1185                },
1186            );
1187            self.emit_route_goodbyes(module_goodbyes);
1188        }
1189    }
1190
1191    /// Why a registered module is not accepting new route binds, or `None` when
1192    /// it is. This is the module's effective readiness: its declared readiness
1193    /// first, then every `need: required` capability it declares evaluating to
1194    /// `provided`. `route.open` and `catalog.list` both read it here so the
1195    /// catalog never reports a module routable that `route.open` would refuse.
1196    fn not_ready_reason(
1197        &self,
1198        registration: &crate::registry::ModuleRegistration,
1199    ) -> Option<NotReadyReason> {
1200        if !registration.ready {
1201            return Some(NotReadyReason {
1202                reason: NotReadyReason::DECLARED_NOT_READY.to_string(),
1203                capability: None,
1204            });
1205        }
1206        self.first_unprovided_required_capability(registration)
1207            .map(|capability| NotReadyReason {
1208                reason: NotReadyReason::REQUIRED_CAPABILITY_UNPROVIDED.to_string(),
1209                capability: Some(capability),
1210            })
1211    }
1212
1213    /// The lexicographically first capability this registration declares
1214    /// `need: required` whose evaluator verdict is not `provided`.
1215    ///
1216    /// The verdicts are the capability evaluator's own; nothing here decides
1217    /// what "provided" means. The evaluator counts a capability provided as
1218    /// soon as a module claiming it has REGISTERED, not once that module is
1219    /// ready. That distinction is what keeps two modules that require each
1220    /// other's capabilities from deadlocking: if "provided" meant "the claimant
1221    /// is ready", each would wait for the other to become ready first and
1222    /// neither ever would. Do not tighten it to readiness.
1223    ///
1224    /// A required capability with no verdict at all means this registration's
1225    /// HELLO or catalog.update landed after the last recompute; recompute once
1226    /// rather than let a missing verdict read as either answer. If it is still
1227    /// missing (the recompute itself failed) the capability counts as
1228    /// unprovided: the refusal is retryable, and routing a module whose
1229    /// required provider is unknown is the outcome this check exists to stop.
1230    fn first_unprovided_required_capability(
1231        &self,
1232        registration: &crate::registry::ModuleRegistration,
1233    ) -> Option<String> {
1234        let required = registration
1235            .manifest
1236            .capabilities
1237            .iter()
1238            .flat_map(|declarations| declarations.requires.iter())
1239            .filter(|requirement| requirement.need == CapabilityNeed::Required)
1240            .map(|requirement| requirement.capability.as_str())
1241            .collect::<BTreeSet<_>>();
1242        if required.is_empty() {
1243            return None;
1244        }
1245        let module_id = registration.manifest.module_id.as_str();
1246        let verdict = |capability: &str| self.capability_evaluator.verdict(module_id, capability);
1247        if required
1248            .iter()
1249            .any(|capability| verdict(capability).is_none())
1250        {
1251            self.refresh_capability_requirements();
1252        }
1253        required
1254            .into_iter()
1255            .find(|capability| verdict(capability) != Some(CapabilityVerdict::Provided))
1256            .map(str::to_string)
1257    }
1258
1259    fn capability_requirement_statuses(&self) -> Vec<CapabilityRequirementStatus> {
1260        self.capability_evaluator
1261            .statuses()
1262            .into_iter()
1263            .map(capability_requirement_status)
1264            .collect()
1265    }
1266
1267    /// Remove a connection's registry entries WITHOUT signalling the supervisor's
1268    /// registration-release watch. The signal is what the supervisor waits on
1269    /// before spawning a replacement, so it must only fire once forwarding
1270    /// teardown is also done (see [`Self::cleanup_connection`] /
1271    /// [`Self::handle_goodbye`]). Used directly only where there is no forwarding
1272    /// state to tear down (a HELLO that failed before module registration).
1273    fn deregister_connection(
1274        &self,
1275        connection_id: ConnectionId,
1276    ) -> Result<Vec<crate::registry::ModuleRegistration>, RegistryError> {
1277        self.registry.deregister_connection(connection_id)
1278    }
1279
1280    pub(crate) fn route_open_target(&self, frame: &Frame) -> Option<String> {
1281        if frame.header.channel != 0 || frame.header.ty != FrameType::Request {
1282            return None;
1283        }
1284        let Ok(ClientControlRequest::RouteOpen { target, .. }) =
1285            parse_client_control_request(&frame.body)
1286        else {
1287            return None;
1288        };
1289        Some(target_module_id(&target).to_string())
1290    }
1291
1292    pub(crate) fn route_open_capacity_refusal(
1293        &self,
1294        ctx: &RouteCtx,
1295        frame: &Frame,
1296        target_module_id: &str,
1297        in_flight: usize,
1298        limit: usize,
1299    ) -> Result<Frame, RouterError> {
1300        self.route_open_admission_refusal_frame(
1301            ctx,
1302            frame,
1303            target_module_id,
1304            "open_admission_full",
1305            (in_flight, limit),
1306            format!(
1307                "connection already has {in_flight} route.open binds in flight (limit {limit}); retry after one settles"
1308            ),
1309        )
1310    }
1311
1312    fn route_open_target_capacity_refusal(
1313        &self,
1314        ctx: &RouteCtx,
1315        frame: &Frame,
1316        target_module_id: &str,
1317        in_flight: usize,
1318    ) -> Result<Frame, RouterError> {
1319        self.route_open_admission_refusal_frame(
1320            ctx,
1321            frame,
1322            target_module_id,
1323            "target_binds_full",
1324            (in_flight, MAX_PENDING_ROUTE_BINDS_PER_TARGET),
1325            format!(
1326                "module_id '{target_module_id}' already has {in_flight} route.bind relays in flight; retry after one settles"
1327            ),
1328        )
1329    }
1330
1331    /// Admission pressure clears as existing binds settle, so its refusal must
1332    /// remain in the deployed SDKs' closed retryable set: `unknown_module`,
1333    /// `module_reloading`, `module_warming`, `target_unavailable`, or
1334    /// `module_timeout`. `target_unavailable` is honest for an attempt that
1335    /// cannot currently reach its target; `module_timeout` would falsely claim
1336    /// that a wait expired. A new, cleaner code would be terminal to deployed
1337    /// clients, so it requires a client-tolerance rollout before daemon emission.
1338    fn route_open_admission_refusal_frame(
1339        &self,
1340        ctx: &RouteCtx,
1341        frame: &Frame,
1342        target_module_id: &str,
1343        reason: &'static str,
1344        (in_flight, limit): (usize, usize),
1345        message: impl Into<String>,
1346    ) -> Result<Frame, RouterError> {
1347        let code = error_codes::TARGET_UNAVAILABLE;
1348        self.counters.increment_route_open_refused(code);
1349        info!(
1350            target: "control",
1351            code,
1352            reason,
1353            module_id = ?target_module_id,
1354            connection_id = ctx.connection_id.get(),
1355            in_flight,
1356            limit,
1357            "route.open refused"
1358        );
1359        control_error_frame(frame, code, message.into())
1360    }
1361
1362    /// Test-only compatibility entry point for unit control handling that does not have a socket sink.
1363    ///
1364    /// The real server path uses [`Self::handle_control_frame`] so module HELLO registration can
1365    /// record the module connection's [`crate::FrameSink`] and session attach can await the module
1366    /// relay response. This seam stays cfg(test) so production has only one channel-0 path.
1367    #[cfg(test)]
1368    pub fn handle_control(
1369        &self,
1370        connection_id: ConnectionId,
1371        frame: Frame,
1372    ) -> Result<Vec<Frame>, RouterError> {
1373        match frame.header.ty {
1374            FrameType::Ping => Ok(vec![pong(&frame)?]),
1375            FrameType::Hello => self.handle_hello(connection_id, None, frame),
1376            FrameType::Goodbye => self.handle_goodbye(connection_id),
1377            ty => Ok(vec![control_error_frame(
1378                &frame,
1379                "unsupported_control_frame",
1380                format!("unsupported channel-0 frame {ty:?}"),
1381            )?]),
1382        }
1383    }
1384
1385    pub async fn handle_control_frame(
1386        &self,
1387        ctx: &RouteCtx,
1388        frame: Frame,
1389    ) -> Result<Vec<Frame>, RouterError> {
1390        self.handle_control_frame_timed(ctx, frame, None).await
1391    }
1392
1393    pub(crate) async fn handle_control_frame_timed(
1394        &self,
1395        ctx: &RouteCtx,
1396        frame: Frame,
1397        dispatch_started_at: Option<StdInstant>,
1398    ) -> Result<Vec<Frame>, RouterError> {
1399        match frame.header.ty {
1400            FrameType::Ping => Ok(vec![pong(&frame)?]),
1401            FrameType::Hello => {
1402                self.handle_hello(ctx.connection_id, Some(ctx.egress.clone()), frame)
1403            }
1404            FrameType::Goodbye => self.handle_goodbye(ctx.connection_id),
1405            FrameType::Cancel => {
1406                if self
1407                    .supervisor
1408                    .cancel_spawn_subscription(ctx.connection_id, frame.header.corr)
1409                {
1410                    Ok(Vec::new())
1411                } else {
1412                    Ok(vec![control_error_frame(
1413                        &frame,
1414                        "unknown_subscription",
1415                        "no supervisor spawn subscription has this correlation id",
1416                    )?])
1417                }
1418            }
1419            FrameType::Request => {
1420                if self
1421                    .forwarding
1422                    .module_endpoint_for_connection(ctx.connection_id)
1423                    .map_err(RouterError::Forwarding)?
1424                    .is_some()
1425                {
1426                    if !is_known_module_request_op(&frame.body) {
1427                        return Ok(vec![control_error_frame(
1428                            &frame,
1429                            "unsupported_control_frame",
1430                            "module-originated channel-0 REQUEST is not supported",
1431                        )?]);
1432                    }
1433                    let request = match parse_module_control_request_from_module(&frame.body) {
1434                        Ok(request) => request,
1435                        Err((err, ControlRequestBodyError::UnknownOp)) => {
1436                            return Ok(vec![control_error_frame(
1437                                &frame,
1438                                "unsupported_control_frame",
1439                                format!("unsupported module-originated channel-0 REQUEST: {err}"),
1440                            )?])
1441                        }
1442                        Err((err, ControlRequestBodyError::InvalidBody)) => {
1443                            return Ok(vec![control_error_frame(
1444                                &frame,
1445                                "invalid_control_body",
1446                                format!("malformed module control body: {err}"),
1447                            )?])
1448                        }
1449                    };
1450                    let op = module_control_request_op(&request);
1451                    let corr = frame.header.corr;
1452                    log_control_dispatch_arrival(op, ctx.connection_id, corr);
1453                    let result =
1454                        self.handle_module_control_request(ctx.connection_id, frame, request);
1455                    log_slow_control_dispatch(dispatch_started_at, op, ctx.connection_id, corr);
1456                    return result;
1457                }
1458
1459                if is_known_module_request_op(&frame.body) {
1460                    return Ok(vec![control_error_frame(
1461                        &frame,
1462                        "not_registered",
1463                        "catalog.update requires an active module registration owned by this connection",
1464                    )?]);
1465                }
1466
1467                let request = match parse_client_control_request(&frame.body) {
1468                    Ok(request) => request,
1469                    Err((err, ControlRequestBodyError::UnknownOp)) => {
1470                        return Ok(vec![control_error_frame(
1471                            &frame,
1472                            "unknown_control_op",
1473                            format!("unknown client control op: {err}"),
1474                        )?])
1475                    }
1476                    Err((err, ControlRequestBodyError::InvalidBody)) => {
1477                        return Ok(vec![control_error_frame(
1478                            &frame,
1479                            "invalid_control_body",
1480                            format!("malformed client control body: {err}"),
1481                        )?])
1482                    }
1483                };
1484                let op = client_control_request_op(&request);
1485                let corr = frame.header.corr;
1486                log_control_dispatch_arrival(op, ctx.connection_id, corr);
1487                #[cfg(test)]
1488                if let Some(delay) = self.control_dispatch_delay {
1489                    tokio::time::sleep(delay).await;
1490                }
1491                let result = self
1492                    .handle_client_control_request(ctx, frame, request)
1493                    .await;
1494                log_slow_control_dispatch(dispatch_started_at, op, ctx.connection_id, corr);
1495                result
1496            }
1497            FrameType::Push => {
1498                let Some(endpoint) = self
1499                    .forwarding
1500                    .module_endpoint_for_connection(ctx.connection_id)
1501                    .map_err(RouterError::Forwarding)?
1502                else {
1503                    return Ok(vec![control_error_frame(
1504                        &frame,
1505                        "unsupported_control_frame",
1506                        "client-originated channel-0 PUSH is not supported",
1507                    )?]);
1508                };
1509                self.handle_status_update(endpoint, frame)
1510            }
1511            FrameType::Response | FrameType::Error
1512                if self
1513                    .forwarding
1514                    .module_endpoint_for_connection(ctx.connection_id)
1515                    .map_err(RouterError::Forwarding)?
1516                    .is_some() =>
1517            {
1518                self.handle_module_relay_response(ctx.connection_id, frame)
1519            }
1520            ty => Ok(vec![control_error_frame(
1521                &frame,
1522                "unsupported_control_frame",
1523                format!("unsupported channel-0 frame {ty:?}"),
1524            )?]),
1525        }
1526    }
1527
1528    pub fn cleanup_connection(
1529        &self,
1530        connection_id: ConnectionId,
1531    ) -> Result<Vec<crate::registry::ModuleRegistration>, RegistryError> {
1532        let crash_closed = self
1533            .registry
1534            .get_module_by_connection(connection_id)?
1535            .and_then(|registration| {
1536                self.forwarding
1537                    .module_endpoint_for_connection(connection_id)
1538                    .ok()
1539                    .flatten()
1540                    .and_then(|endpoint| self.forwarding.endpoint_routes(endpoint).ok())
1541                    .map(|routes| (registration.manifest.module_id, routes))
1542            });
1543        let crash_closed = crash_closed.map(|(module_id, routes)| {
1544            let terminal = match self.supervisor.get(&module_id) {
1545                None => false,
1546                Some(module) => match module.will_recover_after_connection_loss() {
1547                    Ok(will_recover) => !will_recover,
1548                    Err(err) => {
1549                        warn!(
1550                            %module_id,
1551                            error = %err,
1552                            "failed to read crash recovery verdict; reporting non-terminal conservatively"
1553                        );
1554                        false
1555                    }
1556                },
1557            };
1558            // The forwarding table gates all providers at the start of daemon
1559            // shutdown, before their connections are closed. An ordinary
1560            // module disconnect still reports crash if that gate is not set.
1561            let reason = match self.forwarding.is_daemon_draining() {
1562                Ok(true) => RouteCloseReason::Restart,
1563                Ok(false) => RouteCloseReason::Crash,
1564                Err(err) => {
1565                    warn!(error = %err, "failed to read daemon drain state; reporting crash conservatively");
1566                    RouteCloseReason::Crash
1567                }
1568            };
1569            (module_id, routes, reason, terminal)
1570        });
1571        let registrations = self.deregister_connection(connection_id);
1572        let cleanup = if crash_closed.is_some() {
1573            self.forwarding.cleanup_connection_counted(connection_id)
1574        } else {
1575            // A module connection's teardown needs the count of abandoned
1576            // route.bind relays for its route.closed notice below. Any other
1577            // connection, such as a client's, sends no such notice and needs
1578            // only its routes released, so it uses the route-only wrapper and
1579            // reports zero.
1580            self.forwarding
1581                .cleanup_connection(connection_id)
1582                .map(|released| crate::forwarding::ConnectionCleanup {
1583                    released,
1584                    abandoned_relays: 0,
1585                })
1586        };
1587        // The route.closed push waits for forwarding teardown because only
1588        // teardown knows how many pending route.bind relays it aborted. It still
1589        // goes out before the GOODBYEs for the released routes, and its targets
1590        // were captured above, before teardown removed those routes.
1591        if let Some((module_id, routes, reason, terminal)) = crash_closed {
1592            let abandoned = cleanup
1593                .as_ref()
1594                .map_or(0, |cleanup| cleanup.abandoned_relays);
1595            send_route_control_pushes(
1596                &self.forwarding,
1597                routes,
1598                ClientControlPush::RouteClosed {
1599                    module_id,
1600                    channels: Vec::new(),
1601                    reason,
1602                    drained: false,
1603                    abandoned,
1604                    excluded_subscriptions: 0,
1605                    terminal: Some(terminal),
1606                },
1607            );
1608        }
1609        if let Ok(cleanup) = cleanup {
1610            self.emit_route_goodbyes(cleanup.released);
1611        }
1612        // Signal the registration-release watch only now that BOTH registry and
1613        // forwarding teardown are done, so a supervisor waiting to spawn a
1614        // replacement never observes release while old routes still exist.
1615        if matches!(&registrations, Ok(r) if !r.is_empty()) {
1616            crate::supervise::notify_registration_release();
1617            self.capability_evaluator.wake_deadline_loop();
1618            self.refresh_capability_requirements();
1619        }
1620        self.supervisor.remove_spawn_subscribers(connection_id);
1621        // Sync authority dies with its connection, so the owner's next
1622        // connection can take it; the owner's scopes stay as they are.
1623        self.hello_launch_nonces
1624            .lock()
1625            .unwrap_or_else(|poisoned| poisoned.into_inner())
1626            .forget(connection_id);
1627        self.scopes
1628            .write()
1629            .unwrap_or_else(|poisoned| poisoned.into_inner())
1630            .release_connection(connection_id);
1631        registrations
1632    }
1633
1634    pub(crate) fn handle_route_goodbye(
1635        &self,
1636        connection_id: ConnectionId,
1637        route_channel: u16,
1638        route_epoch: u32,
1639    ) -> Result<bool, RouterError> {
1640        debug!(
1641            connection_id = connection_id.get(),
1642            route_channel, route_epoch, "handling route GOODBYE"
1643        );
1644        let RouteRelease::Removed(released_route) = self
1645            .forwarding
1646            .release_client_route(connection_id, route_channel, route_epoch)
1647            .map_err(RouterError::Forwarding)?
1648        else {
1649            return Ok(false);
1650        };
1651        self.emit_route_goodbyes(vec![released_route]);
1652        Ok(true)
1653    }
1654
1655    fn emit_route_goodbyes(&self, released_routes: Vec<GoodbyeTarget>) {
1656        for released in released_routes {
1657            let frame = match Frame::build_with_version(
1658                released.negotiated_ver,
1659                FrameType::Goodbye,
1660                control_flags(),
1661                released.channel,
1662                released.epoch,
1663                0,
1664                Vec::new(),
1665            ) {
1666                Ok(frame) => frame,
1667                Err(err) => {
1668                    warn!(
1669                        route_channel = released.channel,
1670                        error = %err,
1671                        "failed to build route GOODBYE frame"
1672                    );
1673                    continue;
1674                }
1675            };
1676            if !released.close_on_delivery_failure() {
1677                crate::forwarding::send_module_route_goodbye(
1678                    &self.counters,
1679                    &released.sink,
1680                    frame,
1681                    released.module_id.as_deref(),
1682                    "client route released",
1683                );
1684                continue;
1685            }
1686            if let Err(err) = released.sink.try_send(frame) {
1687                warn!(
1688                    target_connection_id = released.connection_id.get(),
1689                    route_channel = released.channel,
1690                    error = %err,
1691                    "route GOODBYE was not delivered to client; closing target connection"
1692                );
1693                if self
1694                    .forwarding
1695                    .escalate_client_delivery_failure(
1696                        released.connection_id,
1697                        released.channel,
1698                        released.epoch,
1699                        CloseReason::new(
1700                            "route_goodbye_delivery_failed",
1701                            format!(
1702                                "failed to enqueue route GOODBYE for channel {}: {err}",
1703                                released.channel
1704                            ),
1705                        ),
1706                        crate::forwarding::UndeliveredFrame {
1707                            module_id: released.module_id.as_deref(),
1708                            sink: &released.sink,
1709                        },
1710                    )
1711                    .unwrap_or(false)
1712                {
1713                    self.counters.increment_goodbye_relay_client_failed();
1714                }
1715            }
1716        }
1717    }
1718
1719    /// Best-effort GOODBYE to a module for a route channel subc reserved but then
1720    /// abandoned (route.bind relay timed out, its waiter was cancelled, or subc's
1721    /// own commit failed after the module had already accepted). Without this, a
1722    /// module that accepts late keeps a binding subc has torn down, so a later
1723    /// frame on that module channel could misdeliver if the channel is reused.
1724    ///
1725    /// Never closes the shared module connection on failure: a dropped notification
1726    /// only wastes a bounded amount of warm module-side state, which the module's
1727    /// own idle reaper reclaims. Only call this once the route.bind relay was
1728    /// actually enqueued to the module — if the relay send itself failed, the
1729    /// module never created a binding and there is nothing to tear down.
1730    fn send_abandoned_route_bind_goodbye(
1731        &self,
1732        module_sink: &crate::FrameSink,
1733        negotiated_ver: u8,
1734        module_channel: u16,
1735        module_epoch: u32,
1736    ) {
1737        let frame = match Frame::build_with_version(
1738            negotiated_ver,
1739            FrameType::Goodbye,
1740            control_flags(),
1741            module_channel,
1742            module_epoch,
1743            0,
1744            Vec::new(),
1745        ) {
1746            Ok(frame) => frame,
1747            Err(err) => {
1748                warn!(
1749                    route_channel = module_channel,
1750                    error = %err,
1751                    "failed to build GOODBYE for abandoned route.bind"
1752                );
1753                return;
1754            }
1755        };
1756        crate::forwarding::send_module_route_goodbye(
1757            &self.counters,
1758            module_sink,
1759            frame,
1760            None,
1761            "abandoned route.bind",
1762        );
1763    }
1764
1765    fn handle_hello(
1766        &self,
1767        connection_id: ConnectionId,
1768        sink: Option<crate::FrameSink>,
1769        frame: Frame,
1770    ) -> Result<Vec<Frame>, RouterError> {
1771        debug!(
1772            connection_id = connection_id.get(),
1773            corr = frame.header.corr,
1774            "handling HELLO"
1775        );
1776        // A module connection has one identity for its entire lifetime. A second
1777        // registration would leave the old registry owner behind while replacing
1778        // its forwarding endpoint and launch nonce.
1779        if self
1780            .registry
1781            .get_module_by_connection(connection_id)
1782            .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?
1783            .is_some()
1784        {
1785            return Ok(vec![control_error_frame(
1786                &frame,
1787                "invalid_hello",
1788                "connection is already registered as a module",
1789            )?]);
1790        }
1791        let hello_value = match serde_json::from_slice::<serde_json::Value>(&frame.body) {
1792            Ok(value) => value,
1793            Err(err) => {
1794                return Ok(vec![control_error_frame(
1795                    &frame,
1796                    "invalid_hello",
1797                    format!("malformed HELLO body: {err}"),
1798                )?])
1799            }
1800        };
1801        if let Err(err) = validate_hello_capability_grammar(&hello_value) {
1802            return Ok(vec![control_error_frame(
1803                &frame,
1804                "invalid_capability_grammar",
1805                err.to_string(),
1806            )?]);
1807        }
1808        if let Err(err) = validate_hello_self_signal_declarations(&hello_value) {
1809            return Ok(vec![control_error_frame(
1810                &frame,
1811                "invalid_manifest",
1812                err.to_string(),
1813            )?]);
1814        }
1815        if let Err(err) = validate_hello_event_declarations(&hello_value) {
1816            return Ok(vec![control_error_frame(
1817                &frame,
1818                "invalid_event_declaration",
1819                err.to_string(),
1820            )?]);
1821        }
1822        if let Some(provenance) = hello_value
1823            .get("manifest")
1824            .and_then(|manifest| manifest.get("provenance"))
1825        {
1826            if let Err(err) = serde_json::from_value::<ManifestProvenance>(provenance.clone()) {
1827                return Ok(vec![control_error_frame(
1828                    &frame,
1829                    "invalid_manifest",
1830                    format!("malformed manifest provenance: {err}"),
1831                )?]);
1832            }
1833        }
1834        let hello = match serde_json::from_value::<ModuleHelloBody>(hello_value) {
1835            Ok(hello) => hello,
1836            Err(err) => {
1837                return Ok(vec![control_error_frame(
1838                    &frame,
1839                    "invalid_hello",
1840                    format!("malformed HELLO body: {err}"),
1841                )?])
1842            }
1843        };
1844
1845        if hello.protocol_ver != hello.manifest.protocol_ver {
1846            return Ok(vec![control_error_frame(
1847                &frame,
1848                "invalid_manifest",
1849                format!(
1850                    "HELLO protocol_ver {} does not match manifest protocol_ver {}",
1851                    hello.protocol_ver, hello.manifest.protocol_ver
1852                ),
1853            )?]);
1854        }
1855
1856        if hello.manifest.module_id.trim().is_empty() {
1857            return Ok(vec![control_error_frame(
1858                &frame,
1859                "invalid_manifest",
1860                "manifest module_id must not be empty",
1861            )?]);
1862        }
1863
1864        let negotiated_ver = match negotiate_version(hello.protocol_ver) {
1865            Ok(negotiated_ver) => negotiated_ver,
1866            Err(message) => {
1867                return Ok(vec![control_error_frame(
1868                    &frame,
1869                    "version_unsupported",
1870                    message,
1871                )?])
1872            }
1873        };
1874
1875        // Swap gate, ahead of the reserved gate on purpose. While a blue/green
1876        // swap is open for this id, the only HELLO admitted as a second process
1877        // is the one carrying the candidate's launch nonce (the swap token), and
1878        // it registers into the candidate slot rather than being refused as a
1879        // duplicate. Run after the reserved gate, a reserved module's candidate
1880        // would be refused `reserved_module` for presenting a nonce that gate
1881        // does not know. See `SupervisorHandle::swap_hello_admission`.
1882        let swap_admission = self
1883            .supervisor
1884            .swap_hello_admission(&hello.manifest.module_id, hello.launch_nonce.as_deref());
1885        if swap_admission == SwapHelloAdmission::Refused {
1886            warn!(
1887                module_id = %hello.manifest.module_id,
1888                connection_id = connection_id.get(),
1889                "HELLO refused: a swap is open for this module_id and the launch nonce is not one the supervisor minted for it"
1890            );
1891            return Ok(vec![control_error_frame(
1892                &frame,
1893                "swap_token_invalid",
1894                format!(
1895                    "module_id '{}' is being swapped; HELLO without the swap candidate's launch nonce is rejected",
1896                    hello.manifest.module_id
1897                ),
1898            )?]);
1899        }
1900        let swap_candidate = swap_admission == SwapHelloAdmission::Candidate;
1901
1902        // Reserved-module identity gate: a module_id configured `reserved` may be
1903        // registered ONLY by the process subc spawned for it, proven by echoing the
1904        // one-time launch nonce subc injected. A non-reserved id has no recorded
1905        // nonce and always passes. This blocks a key-holder from impersonating a
1906        // security-boundary module (e.g. the credential vault) while the real one is
1907        // down/restarting and its registration slot is momentarily free. A swap
1908        // candidate has already proven the same thing with its own nonce above.
1909        if let Some(rejection) = (!swap_candidate)
1910            .then(|| {
1911                self.supervisor.reserved_hello_rejection(
1912                    &hello.manifest.module_id,
1913                    hello.launch_nonce.as_deref(),
1914                )
1915            })
1916            .flatten()
1917        {
1918            let message = match rejection {
1919                ReservedHelloRejection::Exact { module_id } => format!(
1920                    "module_id '{module_id}' is reserved; HELLO without a valid launch nonce is rejected"
1921                ),
1922                ReservedHelloRejection::Prefix {
1923                    prefix,
1924                    owner_module_id,
1925                } => format!(
1926                    "module_id '{}' matches reserved prefix '{prefix}' owned by '{owner_module_id}'; HELLO without the owner launch nonce is rejected",
1927                    hello.manifest.module_id
1928                ),
1929            };
1930            return Ok(vec![control_error_frame(
1931                &frame,
1932                "reserved_module",
1933                message,
1934            )?]);
1935        }
1936
1937        let reserved_capability_refusals = self.capability_evaluator.reserved_hello_refusals(
1938            &hello.manifest.module_id,
1939            hello.manifest.capabilities.as_ref(),
1940        );
1941        if let Some(refusal) = reserved_capability_refusals.first() {
1942            let capability = refusal.capability.clone();
1943            let bound_module = refusal.claimants[0].clone();
1944            log_duplicate_claim_events(reserved_capability_refusals);
1945            return Ok(vec![control_error_frame(
1946                &frame,
1947                "reserved_capability",
1948                format!(
1949                    "capability '{}' is reserved for module_id '{}'; claimant '{}' was refused",
1950                    capability, bound_module, hello.manifest.module_id
1951                ),
1952            )?]);
1953        }
1954
1955        // A connection that already opened client routes must not also register as
1956        // a module: cleanup would then release only one side and leak the other.
1957        if self
1958            .forwarding
1959            .connection_has_client_routes(connection_id)
1960            .map_err(RouterError::Forwarding)?
1961        {
1962            return Ok(vec![control_error_frame(
1963                &frame,
1964                "invalid_hello",
1965                "connection has open client routes and cannot also register as a module",
1966            )?]);
1967        }
1968
1969        // Kept for scope sync authority, which goes only to the connection that
1970        // presented the module's current launch nonce. Recorded before the
1971        // registration is attempted: a connection whose registration then fails
1972        // has no registration, so it cannot sync anyway, and cleanup forgets it.
1973        self.hello_launch_nonces
1974            .lock()
1975            .unwrap_or_else(|poisoned| poisoned.into_inner())
1976            .record(connection_id, hello.launch_nonce.as_deref());
1977        let control_ops = effective_module_control_ops(hello.control_ops);
1978        // Built before anything is registered so an encoding failure leaves no
1979        // registry or forwarding state behind.
1980        let hello_ack = self.build_hello_ack(&frame, negotiated_ver, &hello.manifest.module_id)?;
1981        if swap_candidate {
1982            return self.register_swap_candidate(
1983                connection_id,
1984                sink,
1985                &frame,
1986                hello.manifest,
1987                negotiated_ver,
1988                control_ops,
1989                hello_ack,
1990            );
1991        }
1992        let registration = match self.registry.register_with_control_ops(
1993            hello.manifest,
1994            negotiated_ver,
1995            connection_id,
1996            control_ops,
1997        ) {
1998            Ok(registration) => registration,
1999            Err(RegistryError::DuplicateModuleId { module_id }) => {
2000                return Ok(vec![control_error_frame(
2001                    &frame,
2002                    "duplicate_module_id",
2003                    format!(
2004                        "module_id '{module_id}' is already registered; duplicate HELLO rejected"
2005                    ),
2006                )?])
2007            }
2008            Err(err @ RegistryError::PathHazardModuleId { .. }) => {
2009                return Ok(vec![control_error_frame(
2010                    &frame,
2011                    "invalid_module_id",
2012                    err.to_string(),
2013                )?])
2014            }
2015            Err(err) => {
2016                return Ok(vec![control_error_frame(
2017                    &frame,
2018                    "registry_error",
2019                    err.to_string(),
2020                )?])
2021            }
2022        };
2023
2024        let reply = if let Some(sink) = sink {
2025            // The forwarding table's module store is also the daemon-to-module
2026            // control-RPC lane, so every HELLO gets a live endpoint even when the
2027            // manifest has no routable provider role. Non-routable modules still
2028            // cannot receive route.bind in production: `handle_route_open` checks
2029            // the registry manifest with `target_has_required_role` before the
2030            // only production call to `begin_route_bind_relay_for` below that
2031            // route.open path. The remaining direct relay callers are unit tests
2032            // and benchmark harnesses that construct forwarding state explicitly.
2033            //
2034            // The HELLO_ACK is queued by the forwarding table itself, before the
2035            // endpoint becomes visible, and is NOT returned as a reply. A module
2036            // reads HELLO_ACK first and exits on anything else; a reply is only
2037            // written after this handler returns, by which time a route.open on
2038            // another connection could already have queued a route.bind request
2039            // for this module ahead of it.
2040            let concurrency = manifest_concurrency(&registration.manifest);
2041            if let Err(err) = self.forwarding.register_module_connection_acked(
2042                connection_id,
2043                registration.manifest.module_id.clone(),
2044                negotiated_ver,
2045                concurrency,
2046                sink,
2047                hello_ack,
2048            ) {
2049                // Forwarding registration failed, so there is no forwarding
2050                // state to tear down. Remove the registry entry and signal the
2051                // release watch directly.
2052                if matches!(self.deregister_connection(connection_id), Ok(r) if !r.is_empty()) {
2053                    crate::supervise::notify_registration_release();
2054                }
2055                return Ok(vec![control_error_frame(
2056                    &frame,
2057                    if matches!(err, ForwardingError::ConnectionRoleConflict { .. }) {
2058                        "invalid_hello"
2059                    } else {
2060                        forwarding_error_code(&err)
2061                    },
2062                    err.to_string(),
2063                )?]);
2064            }
2065            Vec::new()
2066        } else {
2067            // No sink means no forwarding endpoint, so nothing can be routed
2068            // ahead of the ack; it goes out as the reply.
2069            vec![hello_ack]
2070        };
2071
2072        // Exposure over assumption: Concurrency's serde default is pinned to the
2073        // pre-field behavior (ModuleManaged), so a management surface that is
2074        // genuinely Serial and just never declared it inherits concurrent
2075        // delivery silently. Logging which registrations RESOLVED BY DEFAULT
2076        // turns "no module has been bitten yet" into the checkable claim "no
2077        // module is exposed" -- one read of the boot log instead of a fleet
2078        // audit. Detected from the raw HELLO bytes because the serde default
2079        // deliberately erases the absent/declared distinction from the type.
2080        if manifest_concurrency_was_defaulted(&frame.body, &registration.manifest) {
2081            info!(
2082                module_id = %registration.manifest.module_id,
2083                "management surface registered with DEFAULTED concurrency=module_managed (manifest predates the field; declare the real lane)"
2084            );
2085        }
2086
2087        self.apply_registration_capabilities(&registration);
2088
2089        info!(
2090            module_id = %registration.manifest.module_id,
2091            module_version = %registration.manifest.module_version,
2092            negotiated_ver,
2093            routable_provider = manifest_provides_routable_role(&registration.manifest),
2094            connection_id = connection_id.get(),
2095            "module registered"
2096        );
2097
2098        Ok(reply)
2099    }
2100
2101    /// Register a HELLO the swap gate admitted into the candidate slot of the
2102    /// registry and of forwarding, where it is reachable over its own
2103    /// connection (its `catalog.update` finds it) but by no by-id lookup, so
2104    /// nothing routes to it until the supervisor cuts over.
2105    ///
2106    /// Registry first, then forwarding, the same order as an ordinary HELLO;
2107    /// a forwarding failure removes the registry entry again. The capability
2108    /// census is not run: it describes routable modules, and this one is not
2109    /// routable until promotion.
2110    #[allow(clippy::too_many_arguments)]
2111    fn register_swap_candidate(
2112        &self,
2113        connection_id: ConnectionId,
2114        sink: Option<crate::FrameSink>,
2115        frame: &Frame,
2116        manifest: ModuleManifest,
2117        negotiated_ver: u8,
2118        control_ops: Vec<String>,
2119        hello_ack: Frame,
2120    ) -> Result<Vec<Frame>, RouterError> {
2121        let module_id = manifest.module_id.clone();
2122        let registration = match self.registry.register_candidate_with_control_ops(
2123            manifest,
2124            negotiated_ver,
2125            connection_id,
2126            control_ops,
2127        ) {
2128            Ok(registration) => registration,
2129            Err(RegistryError::DuplicateModuleId { module_id }) => {
2130                return Ok(vec![control_error_frame(
2131                    frame,
2132                    "duplicate_module_id",
2133                    format!(
2134                        "module_id '{module_id}' already has a swap candidate registered; duplicate HELLO rejected"
2135                    ),
2136                )?])
2137            }
2138            Err(err @ RegistryError::PathHazardModuleId { .. }) => {
2139                return Ok(vec![control_error_frame(
2140                    frame,
2141                    "invalid_module_id",
2142                    err.to_string(),
2143                )?])
2144            }
2145            Err(err) => {
2146                return Ok(vec![control_error_frame(
2147                    frame,
2148                    "registry_error",
2149                    err.to_string(),
2150                )?])
2151            }
2152        };
2153        let reply = if let Some(sink) = sink {
2154            // Same ordering as an ordinary HELLO: the forwarding table queues
2155            // the HELLO_ACK before the candidate endpoint is inserted, because
2156            // a module exits if its first frame after HELLO is anything else.
2157            let concurrency = manifest_concurrency(&registration.manifest);
2158            if let Err(err) = self.forwarding.register_candidate_module_connection_acked(
2159                connection_id,
2160                module_id.clone(),
2161                negotiated_ver,
2162                concurrency,
2163                sink,
2164                hello_ack,
2165            ) {
2166                if matches!(self.deregister_connection(connection_id), Ok(r) if !r.is_empty()) {
2167                    crate::supervise::notify_registration_release();
2168                }
2169                return Ok(vec![control_error_frame(
2170                    frame,
2171                    forwarding_error_code(&err),
2172                    err.to_string(),
2173                )?]);
2174            }
2175            Vec::new()
2176        } else {
2177            vec![hello_ack]
2178        };
2179        self.supervisor.mark_swap_candidate_admitted(&module_id);
2180        info!(
2181            module_id = %module_id,
2182            module_version = %registration.manifest.module_version,
2183            negotiated_ver,
2184            ready = registration.ready,
2185            connection_id = connection_id.get(),
2186            "swap candidate registered; not routable until cutover"
2187        );
2188        Ok(reply)
2189    }
2190
2191    fn build_hello_ack(
2192        &self,
2193        frame: &Frame,
2194        negotiated_ver: u8,
2195        module_id: &str,
2196    ) -> Result<Frame, RouterError> {
2197        let ack = ModuleHelloAckBody {
2198            negotiated_ver,
2199            subc_ops: module_subc_ops(),
2200            subc_capabilities: self.subc_capabilities.as_ref().to_vec(),
2201            storage: self
2202                .storage_config
2203                .as_ref()
2204                .map(|cfg| cfg.descriptor_for(module_id)),
2205            machine_id: self.machine_id.as_ref().map(|id| id.as_str().to_owned()),
2206        };
2207        let body = serde_json::to_vec(&ack).map_err(|err| {
2208            RouterError::backend(
2209                0,
2210                frame.header.corr,
2211                format!("failed to encode HELLO_ACK: {err}"),
2212            )
2213        })?;
2214
2215        Frame::build_with_version(
2216            negotiated_ver,
2217            FrameType::HelloAck,
2218            control_flags(),
2219            0,
2220            0,
2221            frame.header.corr,
2222            body,
2223        )
2224        .map_err(RouterError::FrameBuild)
2225    }
2226
2227    async fn handle_client_control_request(
2228        &self,
2229        ctx: &RouteCtx,
2230        frame: Frame,
2231        request: ClientControlRequest,
2232    ) -> Result<Vec<Frame>, RouterError> {
2233        match request {
2234            ClientControlRequest::ServerDescribe {} => self.handle_server_describe(frame),
2235            ClientControlRequest::CatalogList { module_id } => {
2236                self.handle_catalog_list(frame, module_id)
2237            }
2238            ClientControlRequest::RouteOpen {
2239                target,
2240                identity,
2241                consumer_identity,
2242                consumer_capabilities,
2243                role_versions,
2244                admission_facts,
2245                scope,
2246            } => {
2247                self.handle_route_open(
2248                    ctx,
2249                    frame,
2250                    RouteOpenRequest {
2251                        target,
2252                        identity,
2253                        consumer_identity,
2254                        consumer_capabilities,
2255                        role_versions,
2256                        admission_facts,
2257                        scope,
2258                    },
2259                )
2260                .await
2261            }
2262            ClientControlRequest::RoutePoll {
2263                route_channel,
2264                route_epoch,
2265                kind,
2266            } => self.handle_route_poll(ctx, frame, route_channel, route_epoch, kind),
2267            ClientControlRequest::SupervisorList {} => self.handle_supervisor_list(frame).await,
2268            ClientControlRequest::SupervisorSpawnSnapshot {} => {
2269                self.handle_supervisor_spawn_snapshot(frame)
2270            }
2271            ClientControlRequest::SupervisorSpawnSubscribe { since } => {
2272                self.handle_supervisor_spawn_subscribe(ctx, frame, since)
2273            }
2274            ClientControlRequest::SupervisorRestart {
2275                module_id,
2276                drain_timeout_ms,
2277            } => {
2278                self.handle_supervisor_restart(frame, module_id, drain_timeout_ms)
2279                    .await
2280            }
2281            ClientControlRequest::SupervisorSwap {
2282                module_id,
2283                ready_timeout_ms,
2284            } => {
2285                self.handle_supervisor_swap(frame, module_id, ready_timeout_ms)
2286                    .await
2287            }
2288            ClientControlRequest::SupervisorReload { module_id } => {
2289                self.handle_supervisor_reload(frame, module_id).await
2290            }
2291            ClientControlRequest::SupervisorRescan { preview } => {
2292                self.handle_supervisor_rescan(frame, preview).await
2293            }
2294            ClientControlRequest::SupervisorReleaseReserved { module_id } => {
2295                self.handle_supervisor_release_reserved(frame, module_id)
2296                    .await
2297            }
2298            ClientControlRequest::SupervisorSetEnabled { module_id, enabled } => {
2299                self.handle_supervisor_set_enabled(frame, module_id, enabled)
2300                    .await
2301            }
2302            ClientControlRequest::SupervisorHealthProbe { module_id } => {
2303                self.handle_supervisor_health_probe(frame, module_id).await
2304            }
2305            ClientControlRequest::SupervisorHealth {} => self.handle_supervisor_health(frame),
2306            ClientControlRequest::SupervisorRoutes { module_id } => {
2307                self.handle_supervisor_routes(frame, module_id)
2308            }
2309            ClientControlRequest::SupervisorProvenance { module_id } => {
2310                self.handle_supervisor_provenance(frame, module_id).await
2311            }
2312            ClientControlRequest::SupervisorStderrTail {
2313                module_id,
2314                max_lines,
2315                max_bytes,
2316            } => self.handle_supervisor_stderr_tail(frame, module_id, max_lines, max_bytes),
2317            ClientControlRequest::SupervisorTerminals { module_id } => {
2318                self.handle_supervisor_terminals(frame, module_id).await
2319            }
2320        }
2321    }
2322
2323    fn handle_module_control_request(
2324        &self,
2325        connection_id: ConnectionId,
2326        frame: Frame,
2327        request: ModuleControlRequestFromModule,
2328    ) -> Result<Vec<Frame>, RouterError> {
2329        match request {
2330            ModuleControlRequestFromModule::CatalogUpdate {
2331                provides,
2332                capabilities,
2333                ready,
2334            } => self.handle_catalog_update(connection_id, frame, provides, capabilities, ready),
2335            ModuleControlRequestFromModule::LiveRoots {} => {
2336                let registered = self
2337                    .registry
2338                    .get_module_by_connection(connection_id)
2339                    .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?;
2340                let Some(registration) = registered else {
2341                    return Ok(vec![control_error_frame(&frame, "not_registered", "supervisor.live_roots requires an active module registration owned by this connection")?]);
2342                };
2343                let response = self
2344                    .forwarding
2345                    .live_roots(&registration.manifest.module_id)
2346                    .map_err(RouterError::Forwarding)?;
2347                Ok(vec![control_response_body_frame(
2348                    &frame,
2349                    &response,
2350                    "ModuleControlResponseToModule::LiveRoots",
2351                )?])
2352            }
2353            ModuleControlRequestFromModule::ScopeSync { generation, scopes } => {
2354                self.handle_scope_sync(connection_id, frame, generation, scopes)
2355            }
2356            ModuleControlRequestFromModule::ScopeDescribe { owner, scope_ref } => {
2357                self.handle_scope_describe(connection_id, frame, owner, scope_ref)
2358            }
2359        }
2360    }
2361
2362    /// `scope.sync`: the owner is the module registered on this connection.
2363    /// A connection with no registration (every client connection, `direct`
2364    /// included) is refused `not_registered` before the table is consulted.
2365    fn handle_scope_sync(
2366        &self,
2367        connection_id: ConnectionId,
2368        frame: Frame,
2369        generation: u64,
2370        scopes: Vec<ScopeRecord>,
2371    ) -> Result<Vec<Frame>, RouterError> {
2372        let Some(registration) = self
2373            .registry
2374            .get_module_by_connection(connection_id)
2375            .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?
2376        else {
2377            return Ok(vec![control_error_frame(
2378                &frame,
2379                "not_registered",
2380                "scope.sync requires an active module registration owned by this connection",
2381            )?]);
2382        };
2383        let owner = registration.manifest.module_id;
2384        let current_nonce = self.supervisor.spawn_launch_nonce_for(&owner);
2385        let is_current_launch = |connection: ConnectionId| {
2386            self.hello_launch_nonces
2387                .lock()
2388                .unwrap_or_else(|poisoned| poisoned.into_inner())
2389                .presented(connection, current_nonce.as_deref())
2390        };
2391        // Lock order is the scope table, then the forwarding table: the new
2392        // tags are published, and the routes the change closes are selected,
2393        // while the scope table is still write-locked, so no admission can read
2394        // a record whose tag is not yet published.
2395        let mut table = self
2396            .scopes
2397            .write()
2398            .unwrap_or_else(|poisoned| poisoned.into_inner());
2399        let outcome = table.sync(&owner, connection_id, is_current_launch, generation, scopes);
2400        let drained = match &outcome {
2401            Ok(applied) => self
2402                .forwarding
2403                .publish_scope_changes(&applied.tag_changes)
2404                .map_err(RouterError::Forwarding)?,
2405            Err(_) => Vec::new(),
2406        };
2407        drop(table);
2408        match outcome {
2409            Ok(applied) => {
2410                let counts = ScopeOutcomeCounts::of(&applied.results);
2411                info!(
2412                    owner = %owner,
2413                    generation,
2414                    records = applied.results.len(),
2415                    created = counts.created,
2416                    replaced = counts.replaced,
2417                    updated = counts.updated,
2418                    unchanged = counts.unchanged,
2419                    refused = counts.refused,
2420                    ended = applied.ended.len(),
2421                    tag_changes = applied.tag_changes.len(),
2422                    routes_closed = drained.len(),
2423                    "scope sync accepted"
2424                );
2425                // An accepted sync can still refuse individual records, and the
2426                // owner is the only party that sees the reply. Name them here so
2427                // an operator can tell a refused session from a missing one
2428                // without the owner's logs. Capped so a sync that refuses
2429                // thousands cannot flood the log; the count above is complete.
2430                for refused in applied
2431                    .results
2432                    .iter()
2433                    .filter(|result| result.outcome == ScopeRecordOutcome::Refused)
2434                    .take(MAX_LOGGED_REFUSED_SCOPE_RECORDS)
2435                {
2436                    warn!(
2437                        owner = %owner,
2438                        generation,
2439                        scope_ref = %refused.scope_ref,
2440                        scope_epoch = refused.scope_epoch,
2441                        code = refused.code.as_deref().unwrap_or(""),
2442                        "scope record refused"
2443                    );
2444                }
2445                self.close_scope_drained_routes(drained);
2446                let response = ModuleControlResponseToModule::ScopeSync {
2447                    generation,
2448                    results: applied.results,
2449                    ended: applied.ended,
2450                };
2451                Ok(vec![control_response_body_frame(
2452                    &frame,
2453                    &response,
2454                    "ModuleControlResponseToModule::ScopeSync",
2455                )?])
2456            }
2457            Err(refusal) => {
2458                info!(
2459                    owner = %owner,
2460                    generation,
2461                    code = refusal.code,
2462                    "scope sync refused"
2463                );
2464                Ok(vec![control_error_frame(
2465                    &frame,
2466                    refusal.code,
2467                    refusal.message,
2468                )?])
2469            }
2470        }
2471    }
2472
2473    /// Tell both ends of each route a scope change closed. The module gets a
2474    /// channel-scoped GOODBYE and so does the client: the GOODBYE is what ends
2475    /// the client's route handle. The client also gets `route.closed` with the
2476    /// scope reason, one push per module and reason, so it can tell a revoked
2477    /// route from an ordinary close and not reopen it.
2478    fn close_scope_drained_routes(&self, drained: Vec<crate::forwarding::ScopeDrainedRoute>) {
2479        if drained.is_empty() {
2480            return;
2481        }
2482        let mut pushes: BTreeMap<(String, String), (RouteCloseReason, Vec<EndpointRoute>)> =
2483            BTreeMap::new();
2484        let mut goodbyes = Vec::with_capacity(drained.len() * 2);
2485        for route in drained {
2486            warn!(
2487                module_id = %route.module_id,
2488                reason = ?route.reason,
2489                client_connection_id = route.client.connection_id.get(),
2490                route_channel = route.client.channel,
2491                "closing route because its scope changed"
2492            );
2493            pushes
2494                .entry((route.module_id.clone(), format!("{:?}", route.reason)))
2495                .or_insert_with(|| (route.reason, Vec::new()))
2496                .1
2497                .push(EndpointRoute {
2498                    goodbye_target: route.client.clone(),
2499                    principal: Principal::Unverified,
2500                    bound_at: Instant::now(),
2501                    draining: false,
2502                    drain_reason: None,
2503                });
2504            goodbyes.push(route.module);
2505            goodbyes.push(route.client);
2506        }
2507        for ((module_id, _), (reason, routes)) in pushes {
2508            send_route_control_pushes(
2509                &self.forwarding,
2510                routes,
2511                ClientControlPush::RouteClosed {
2512                    module_id,
2513                    channels: Vec::new(),
2514                    reason,
2515                    drained: false,
2516                    abandoned: 0,
2517                    excluded_subscriptions: 0,
2518                    terminal: Some(false),
2519                },
2520            );
2521        }
2522        self.emit_route_goodbyes(goodbyes);
2523    }
2524
2525    /// `scope.describe`: any registered module may read any scope, because a
2526    /// provider must read the scope a route it serves is stamped with.
2527    fn handle_scope_describe(
2528        &self,
2529        connection_id: ConnectionId,
2530        frame: Frame,
2531        owner: Principal,
2532        scope_ref: String,
2533    ) -> Result<Vec<Frame>, RouterError> {
2534        let registered = self
2535            .registry
2536            .get_module_by_connection(connection_id)
2537            .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?;
2538        if registered.is_none() {
2539            return Ok(vec![control_error_frame(
2540                &frame,
2541                "not_registered",
2542                "scope.describe requires an active module registration owned by this connection",
2543            )?]);
2544        }
2545        let description = self
2546            .scopes
2547            .read()
2548            .unwrap_or_else(|poisoned| poisoned.into_inner())
2549            .describe(&owner, &scope_ref);
2550        let owner_configured = match &owner {
2551            // Ask whether the owner is configured (`is_configured`), not
2552            // whether it is on the roster (`get(..).is_some()`): a supervised
2553            // module's process can register and describe a scope before the
2554            // supervisor has put it on the roster.
2555            Principal::Reserved { module_id } => self.supervisor.is_configured(module_id),
2556            _ => false,
2557        };
2558        let response = ModuleControlResponseToModule::ScopeDescribe {
2559            status: description.status,
2560            scope_epoch: description.scope_epoch,
2561            daemon_incarnation: self.supervisor.spawn_snapshot().cursor.daemon_incarnation,
2562            owner_synced: description.owner_synced,
2563            owner_configured,
2564            scope: description.stamp,
2565        };
2566        Ok(vec![control_response_body_frame(
2567            &frame,
2568            &response,
2569            "ModuleControlResponseToModule::ScopeDescribe",
2570        )?])
2571    }
2572
2573    fn handle_catalog_update(
2574        &self,
2575        connection_id: ConnectionId,
2576        frame: Frame,
2577        provides: Vec<ProviderRole>,
2578        capabilities: Option<CapabilityDeclarations>,
2579        ready: Option<bool>,
2580    ) -> Result<Vec<Frame>, RouterError> {
2581        self.refresh_capability_requirements();
2582        let Some(registration) = self
2583            .registry
2584            .get_module_by_connection(connection_id)
2585            .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?
2586        else {
2587            return Ok(vec![control_error_frame(
2588                &frame,
2589                "not_registered",
2590                "catalog.update requires an active module registration owned by this connection",
2591            )?]);
2592        };
2593
2594        if let Some(message) =
2595            catalog_update_frozen_field_message(&registration.manifest, &provides)
2596        {
2597            return Ok(vec![control_error_frame(
2598                &frame,
2599                "catalog_update_frozen_field",
2600                message,
2601            )?]);
2602        }
2603
2604        let mut candidate = registration.manifest.clone();
2605        candidate.provides = provides.clone();
2606        candidate.capabilities = capabilities
2607            .clone()
2608            .or_else(|| registration.manifest.capabilities.clone());
2609        if let Err(err) = candidate.validate_capability_grammar() {
2610            return Ok(vec![control_error_frame(
2611                &frame,
2612                "invalid_capability_grammar",
2613                err.to_string(),
2614            )?]);
2615        }
2616
2617        // Updates must honor the same reserved owner as initial registration;
2618        // otherwise an empty HELLO could acquire the claim after admission.
2619        let mut conflicts = self
2620            .capability_evaluator
2621            .reserved_hello_refusals(&candidate.module_id, candidate.capabilities.as_ref());
2622        if let Some(conflict) = conflicts.first() {
2623            let message = format!(
2624                "capability '{}' is reserved for module_id '{}'; claimant '{}' was refused",
2625                conflict.capability, conflict.claimants[0], candidate.module_id
2626            );
2627            for conflict in &mut conflicts {
2628                conflict.source = DuplicateClaimSource::CatalogUpdate;
2629            }
2630            log_duplicate_claim_events(conflicts);
2631            return Ok(vec![control_error_frame(
2632                &frame,
2633                "reserved_capability",
2634                message,
2635            )?]);
2636        }
2637
2638        let updated = self
2639            .registry
2640            .replace_catalog_for_connection(connection_id, provides, capabilities, ready)
2641            .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?;
2642        if updated.is_none() {
2643            return Ok(vec![control_error_frame(
2644                &frame,
2645                "not_registered",
2646                "catalog.update requires an active module registration owned by this connection",
2647            )?]);
2648        }
2649        if let Ok((_, registrations)) = self.runtime_capability_snapshot() {
2650            log_duplicate_claim_events(
2651                self.capability_evaluator
2652                    .duplicate_claims(DuplicateClaimSource::CatalogUpdate, &registrations),
2653            );
2654        }
2655        if capability_census_trigger(
2656            registration.manifest.capabilities.as_ref(),
2657            updated
2658                .as_ref()
2659                .and_then(|entry| entry.manifest.capabilities.as_ref()),
2660        ) {
2661            self.enforce_capability_denies();
2662        }
2663        self.refresh_capability_requirements();
2664
2665        let response = ModuleControlResponseToModule::CatalogUpdate {};
2666        control_response_body_frame(
2667            &frame,
2668            &response,
2669            "ModuleControlResponseToModule::CatalogUpdate",
2670        )
2671        .map(|frame| vec![frame])
2672    }
2673
2674    fn handle_server_describe(&self, frame: Frame) -> Result<Vec<Frame>, RouterError> {
2675        self.refresh_capability_requirements();
2676        // A bare connection count is ambiguous between many clients holding a
2677        // route each and one client accumulating hundreds, so publish the
2678        // concentration alongside it. Route state is best-effort here: a
2679        // diagnostic endpoint must still answer if the forwarding lock is
2680        // contended.
2681        let mut counters = self.counters.snapshot();
2682        if let (Ok((connections_with_routes, max)), Some(obj)) = (
2683            self.forwarding.client_route_concentration(),
2684            counters.as_object_mut(),
2685        ) {
2686            obj.insert(
2687                "client_connections_with_routes".into(),
2688                connections_with_routes.into(),
2689            );
2690            obj.insert("max_routes_on_one_connection".into(), max.into());
2691        }
2692        // A module that is being fast-refused and a module that is fine look
2693        // identical from a client that retries and succeeds, so name the open
2694        // breakers here. This rides the existing free-form counters object
2695        // rather than a new wire field, so no sibling that deserializes
2696        // `ServerDescribe` has to be rebuilt to keep reading it.
2697        if let (Some(open_breakers), Some(obj)) = (
2698            self.route_bind_breakers.open_snapshot(),
2699            counters.as_object_mut(),
2700        ) {
2701            obj.insert("route_bind_breakers_open".into(), open_breakers);
2702        }
2703        let response = ClientControlResponse::ServerDescribe {
2704            protocol_ver: PROTOCOL_VERSION,
2705            subc_ops: subc_ops(),
2706            capabilities: self.subc_capabilities.as_ref().to_vec(),
2707            connected_clients: self.connected_clients.count(),
2708            counters: Some(counters),
2709            build_git_sha: Some(env!("SUBC_BUILD_GIT_SHA").to_string()),
2710            build_lock_digest: Some(env!("SUBC_BUILD_LOCK_DIGEST").to_string()),
2711            capability_requirements: self.capability_requirement_statuses(),
2712            machine_id: self.machine_id.as_ref().map(|id| id.as_str().to_owned()),
2713        };
2714        Ok(vec![control_response_body_frame(
2715            &frame,
2716            &response,
2717            "ClientControlResponse::ServerDescribe",
2718        )?])
2719    }
2720
2721    fn handle_catalog_list(
2722        &self,
2723        frame: Frame,
2724        module_id: Option<String>,
2725    ) -> Result<Vec<Frame>, RouterError> {
2726        let (generation, modules) = self.registry.list_modules().map_err(|err| {
2727            RouterError::backend(0, frame.header.corr, format!("registry error: {err}"))
2728        })?;
2729        let entries = modules
2730            .into_iter()
2731            .filter(|registration| {
2732                module_id
2733                    .as_deref()
2734                    .map(|wanted| registration.manifest.module_id == wanted)
2735                    .unwrap_or(true)
2736            })
2737            .map(|registration| {
2738                let not_ready = self.not_ready_reason(&registration);
2739                let roles = registration.manifest.provides;
2740                CatalogEntry {
2741                    module_id: registration.manifest.module_id,
2742                    ready: not_ready.is_none(),
2743                    not_ready,
2744                    module_version: Some(registration.manifest.module_version),
2745                    roles,
2746                    control_ops: registration.control_ops,
2747                    capabilities: registration.manifest.capabilities,
2748                    self_signals: registration.manifest.self_signals,
2749                }
2750            })
2751            .collect();
2752        let response = ClientControlResponse::CatalogList {
2753            generation,
2754            modules: entries,
2755            subc_ops: subc_ops(),
2756        };
2757        Ok(vec![control_response_body_frame(
2758            &frame,
2759            &response,
2760            "ClientControlResponse::CatalogList",
2761        )?])
2762    }
2763
2764    fn route_open_principal(
2765        &self,
2766        frame: &Frame,
2767        consumer_identity: Option<ConsumerIdentity>,
2768    ) -> Result<Result<Principal, Frame>, RouterError> {
2769        let Some(consumer_identity) = consumer_identity else {
2770            return Ok(Ok(Principal::Direct));
2771        };
2772
2773        if self.supervisor.spawned_consumer_authorized(
2774            &consumer_identity.module_id,
2775            &consumer_identity.launch_nonce,
2776        ) {
2777            return Ok(Ok(Principal::Reserved {
2778                module_id: consumer_identity.module_id,
2779            }));
2780        }
2781
2782        Ok(Err(control_error_frame(
2783            frame,
2784            "bad_consumer_identity",
2785            format!(
2786                "consumer_identity for module_id '{}' did not match a supervised launch nonce",
2787                consumer_identity.module_id
2788            ),
2789        )?))
2790    }
2791
2792    /// Ordinary `route.open` refusals go through here; admission and breaker
2793    /// refusals log separately with their capacity or breaker state. The daemon can
2794    /// attest which code it sent: without the event, a client's "the daemon
2795    /// refused me" and the daemon's own view could only be reconciled by
2796    /// argument. Malformed input (`invalid_project_root`) does not come here;
2797    /// rejecting a request that was never a valid open is not a refusal of one.
2798    fn route_open_refusal_frame(
2799        &self,
2800        ctx: &RouteCtx,
2801        frame: &Frame,
2802        module_id: &str,
2803        reason: &'static str,
2804        code: &'static str,
2805        message: impl Into<String>,
2806    ) -> Result<Frame, RouterError> {
2807        self.observe_route_open_refusal(ctx, module_id, reason, code);
2808        control_error_frame(frame, code, message.into())
2809    }
2810
2811    /// Refuse a `route.open` because the target module's bind-relay breaker is
2812    /// open, without attempting the relay.
2813    ///
2814    /// The wire code is `module_timeout`, which is the truth (the module has
2815    /// not been answering binds) and which both SDKs already classify as
2816    /// retryable with capped backoff. Reusing it is what keeps this change out
2817    /// of both SDKs; the daemon-side distinction lives in the counter key
2818    /// instead.
2819    ///
2820    /// DELIBERATELY NOT LOGGED PER OCCURRENCE, unlike every other refusal.
2821    /// While a breaker is open this fires on every open to that module, and the
2822    /// stall written up in `docs/designs/route-open-head-of-line.md` already
2823    /// produced 261 lines about a single module inside 3000 lines of daemon
2824    /// log. The rare transitions are logged at warn/info instead and the volume
2825    /// is carried by the counter, so the evidence survives without the flood.
2826    /// The debug line keeps a per-refusal record reachable for whoever turns
2827    /// the level up.
2828    fn route_open_breaker_refusal_frame(
2829        &self,
2830        ctx: &RouteCtx,
2831        frame: &Frame,
2832        module_id: &str,
2833        consecutive_timeouts: u32,
2834        retry_in: Duration,
2835        probe_in_flight: bool,
2836    ) -> Result<Frame, RouterError> {
2837        self.counters
2838            .increment_route_open_refused(crate::observability::ROUTE_OPEN_REFUSED_BREAKER_OPEN);
2839        debug!(
2840            target: "control",
2841            code = "module_timeout",
2842            module_id = ?module_id,
2843            connection_id = ctx.connection_id.get(),
2844            consecutive_timeouts,
2845            retry_in_ms = retry_in.as_millis() as u64,
2846            probe_in_flight,
2847            "route.open refused by open bind-relay breaker"
2848        );
2849        // Say what a caller can act on. An open bind-relay breaker means the
2850        // module timed out accepting several new routes in a row. The module
2851        // is still running and its established routes keep working; only new
2852        // route.open requests are refused until the cooldown ends and one
2853        // test route (the probe) gets through. A message that only counts
2854        // failed relays reads as "the module is down" to a worker that sees it.
2855        let detail = if probe_in_flight {
2856            "one test route is already being tried; retry once it settles".to_string()
2857        } else {
2858            format!("retrying new routes in {}s", retry_in.as_secs().max(1))
2859        };
2860        control_error_frame(
2861            frame,
2862            "module_timeout",
2863            format!(
2864                "module '{module_id}' is slow to accept new routes ({consecutive_timeouts} \
2865                 timed out in a row); {detail}; its established routes are unaffected"
2866            ),
2867        )
2868    }
2869
2870    /// `code` is daemon vocabulary and prints plainly; `module_id` is the
2871    /// requester's bytes (an unknown target is whatever the client sent) and
2872    /// is Debug-formatted so control characters land in the log escaped
2873    /// rather than as terminal sequences for whoever tails it.
2874    ///
2875    /// `reason` names the check that refused, because one wire code has
2876    /// several senders: after a module registers, `target_unavailable` can
2877    /// come from a missing role, an inactive registration, a supervisor that
2878    /// has not marked the process live, a missing forwarding connection, or a
2879    /// failed relay, and a log that records only the code cannot say which of
2880    /// them fired. It is a static, daemon-chosen label per branch, so it is
2881    /// safe to print plainly and stays a closed set.
2882    fn observe_route_open_refusal(
2883        &self,
2884        ctx: &RouteCtx,
2885        module_id: &str,
2886        reason: &'static str,
2887        code: &'static str,
2888    ) {
2889        self.counters.increment_route_open_refused(code);
2890        info!(
2891            target: "control",
2892            code,
2893            reason,
2894            module_id = ?module_id,
2895            connection_id = ctx.connection_id.get(),
2896            "route.open refused"
2897        );
2898        if ROUTE_OPEN_NOT_SERVING_REASONS.contains(&reason) {
2899            self.route_outages.record_not_serving(module_id, reason);
2900        }
2901    }
2902
2903    /// Record an ACCEPTED route.open.
2904    ///
2905    /// Refusals have been logged and counted since the attestation work; accepts
2906    /// were invisible, so the daemon knew every principal it stamped and wrote
2907    /// none of them down. The party that attests the identity was the only party
2908    /// not recording it, which left a credential vault unable to name the sender
2909    /// of a call that reached it (claustrum #43) and left the launch-nonce
2910    /// concurrency question unanswerable from the outside.
2911    ///
2912    /// FIELD NAMES MATCH `route.open refused` DELIBERATELY, so one grep over
2913    /// `code`/`module_id`/`connection_id` returns both directions of the same
2914    /// decision rather than two shapes a reader has to join by hand.
2915    ///
2916    /// `module_id` IS RENDERED BARE HERE AND DEBUG-ESCAPED ON THE REFUSAL PATH,
2917    /// and the difference carries information rather than being an
2918    /// inconsistency. This line is only reachable after a successful bind to a
2919    /// REGISTERED module, so the value has already passed HELLO validation
2920    /// including the path-hazard refusal and cannot contain control bytes. A
2921    /// refused id may be arbitrary attacker-chosen bytes and must stay escaped.
2922    /// So A QUOTED `module_id` IN THE LOG MEANS THE VALUE WAS NEVER VALIDATED.
2923    ///
2924    /// Bare is also what every other daemon line already emits (`module
2925    /// registered`, `configured module supervised`). Shipping `?module_id` here
2926    /// made this instrument the only one in the file whose ids did not answer
2927    /// `grep module_id=broca` -- 3 hits against 342 for the escaped form, in a
2928    /// line whose whole purpose is being grepped beside its sibling.
2929    ///
2930    /// THIS RENDERING IS UNFENCED AND THE REASON IS WORTH KNOWING: the in-crate
2931    /// `EventCapture` test layer implements only `record_debug`, so `Visit`
2932    /// forwards every field type through it and a bare `&str` and a `?`-escaped
2933    /// one are recorded identically. A test written against that harness passes
2934    /// either way -- I wrote one, measured it, and deleted it rather than ship a
2935    /// green assertion that cannot fail. The same limit applies to the escaping
2936    /// assertion in `route_open_supervised_absence_emits_refusal_fields_and_counts_code`:
2937    /// it reads as a guard on the Debug escaping and cannot detect its removal.
2938    /// Fencing either needs the real formatter, not the capture layer.
2939    ///
2940    /// `peer_addr` is NOT here and cannot be: `SO_PEERCRED`/`LOCAL_PEERPID` are
2941    /// unix-socket options and subc is loopback TCP, so there is no peer identity
2942    /// to record. The ephemeral port would decay within minutes and answer only a
2943    /// live question. The identity question is instead answered by counting
2944    /// distinct live connections presenting one module's `consumer_identity` --
2945    /// "is anyone else holding this secret" rather than "is this the right
2946    /// process".
2947    fn observe_route_open_accept(&self, ctx: &RouteCtx, module_id: &str, principal: &str) {
2948        self.route_outages.record_accepted(module_id);
2949        self.counters.increment_route_open_accepted(principal);
2950        info!(
2951            target: "control",
2952            principal,
2953            module_id,
2954            connection_id = ctx.connection_id.get(),
2955            "route.open accepted"
2956        );
2957    }
2958
2959    fn supervised_absent_route_open_refusal_frame(
2960        &self,
2961        ctx: &RouteCtx,
2962        frame: &Frame,
2963        module_id: &str,
2964        code: &'static str,
2965        status: &crate::supervise::ModuleStatus,
2966    ) -> Result<Frame, RouterError> {
2967        self.counters.increment_route_open_refused(code);
2968        info!(
2969            target: "control",
2970            code,
2971            reason = "supervised_not_registered",
2972            module_id = ?module_id,
2973            connection_id = ctx.connection_id.get(),
2974            state = %status.state,
2975            enabled = status.enabled,
2976            live = status.live,
2977            "route.open refused"
2978        );
2979        // A supervised module whose process has not registered is not
2980        // serving, whatever the reason; the supervisor knows this id, so it is
2981        // safe to track.
2982        self.route_outages
2983            .record_not_serving(module_id, "supervised_not_registered");
2984        control_error_frame(
2985            frame,
2986            code,
2987            format!(
2988                "module_id '{module_id}' is supervised but not available (state={}, enabled={}, live={})",
2989                status.state, status.enabled, status.live
2990            ),
2991        )
2992    }
2993
2994    async fn handle_route_open(
2995        &self,
2996        ctx: &RouteCtx,
2997        frame: Frame,
2998        request: RouteOpenRequest,
2999    ) -> Result<Vec<Frame>, RouterError> {
3000        let RouteOpenRequest {
3001            target,
3002            mut identity,
3003            consumer_identity,
3004            consumer_capabilities,
3005            role_versions,
3006            admission_facts,
3007            scope,
3008        } = request;
3009        let target_module_id = target_module_id(&target).to_string();
3010        if self
3011            .registry
3012            .get_module_by_connection(ctx.connection_id)
3013            .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?
3014            .is_some()
3015        {
3016            return Ok(vec![control_error_frame(
3017                &frame,
3018                "invalid_request",
3019                "module connections cannot open client routes",
3020            )?]);
3021        }
3022        debug!(
3023            connection_id = ctx.connection_id.get(),
3024            corr = frame.header.corr,
3025            module_id = %target_module_id,
3026            "handling route.open"
3027        );
3028
3029        // A malformed declaration is refused first, before anything about the
3030        // target is looked up: the same body would be refused against any
3031        // module, so the caller learns nothing by retrying or waiting. An empty
3032        // map declares nothing and travels as no field at all, so a provider
3033        // only ever sees a missing field or a non-empty one.
3034        let role_versions = role_versions.filter(|role_versions| !role_versions.is_empty());
3035        if let Some(Err(error)) = role_versions.as_ref().map(validate_role_versions) {
3036            self.observe_route_open_refusal(
3037                ctx,
3038                &target_module_id,
3039                "invalid_role_versions",
3040                error_codes::INVALID_REQUEST,
3041            );
3042            return Ok(vec![control_error_body_frame(
3043                &frame,
3044                ErrorBody {
3045                    code: error_codes::INVALID_REQUEST.to_string(),
3046                    message: error.to_string(),
3047                    detail: Some(serde_json::json!({ "field": ROLE_VERSIONS_FIELD })),
3048                },
3049            )?]);
3050        }
3051
3052        // WHY THESE REPLIES DISCRIMINATE FREELY, since the usual rule is the
3053        // opposite. Below, a caller learns whether a module is unregistered,
3054        // supervised-but-down (with state/enabled/live), or registered without the
3055        // requested role. Elsewhere that is an enumeration leak: a probe learning
3056        // the shape of a fleet it cannot otherwise see.
3057        //
3058        // It is not one here, and the reason is the ACCESS MODEL rather than
3059        // anything about these errors. Reaching route.open requires the
3060        // pre-envelope HMAC handshake, whose key lives in a 0600 user-owned
3061        // connection file, so any caller who completes it already runs as this
3062        // user -- and can read subc.jsonc for the module list and `ck module
3063        // status` for live state. The reply discloses nothing the caller cannot
3064        // read more easily from disk, while the precision is load-bearing:
3065        // `unknown_module` is retryable and a missing role is not.
3066        //
3067        // IF THE HANDSHAKE EVER ADMITS A PRINCIPAL THAT IS NOT THIS USER -- a
3068        // remote transport, a sandboxed caller, a shared-host mode -- THAT
3069        // PREMISE DIES AND THESE THREE REPLIES MUST COLLAPSE INTO ONE.
3070        let Some(registration) = self
3071            .registry
3072            .get_module(&target_module_id)
3073            .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?
3074        else {
3075            if let Some((status, warming)) =
3076                self.supervisor_status(&target_module_id, frame.header.corr)?
3077            {
3078                // BEFORE the two availability codes below, because for a module
3079                // that speaks no subc wire both of them are false comfort: they
3080                // say "not right now" and are retried, and this module will
3081                // never register no matter how long the caller waits. The
3082                // absence here is the declaration being honoured, not a module
3083                // that is late.
3084                if status.protocol == ModuleProtocol::None {
3085                    return Ok(vec![self.route_open_refusal_frame(
3086                        ctx,
3087                        &frame,
3088                        &target_module_id,
3089                        "protocol_none",
3090                        error_codes::MODULE_NO_PROTOCOL,
3091                        format!(
3092                            "module_id '{target_module_id}' is declared protocol: none; \
3093                             it speaks no subc wire and serves no routes"
3094                        ),
3095                    )?]);
3096                }
3097                let code = if warming {
3098                    "module_warming"
3099                } else {
3100                    "target_unavailable"
3101                };
3102                return Ok(vec![self.supervised_absent_route_open_refusal_frame(
3103                    ctx,
3104                    &frame,
3105                    &target_module_id,
3106                    code,
3107                    &status,
3108                )?]);
3109            }
3110            if let Some(removed_ago_ms) =
3111                self.supervisor.removal_tombstone_age_ms(&target_module_id)
3112            {
3113                return Ok(vec![self.route_open_refusal_frame(
3114                    ctx,
3115                    &frame,
3116                    &target_module_id,
3117                    "removed",
3118                    error_codes::MODULE_REMOVED,
3119                    format!("module_id '{target_module_id}' was removed {removed_ago_ms} ms ago"),
3120                )?]);
3121            }
3122            return Ok(vec![self.route_open_refusal_frame(
3123                ctx,
3124                &frame,
3125                &target_module_id,
3126                "not_registered",
3127                error_codes::UNKNOWN_MODULE,
3128                format!("module_id '{target_module_id}' is not registered"),
3129            )?]);
3130        };
3131
3132        // Best-effort only: registry readiness and forwarding reservation use
3133        // different locks, so a module can flip readiness between this read and
3134        // the relay. Modules must still tolerate an `on_bind` while not ready.
3135        if !registration.ready {
3136            self.counters
3137                .increment_route_open_refused(ROUTE_OPEN_REFUSED_DECLARED_NOT_READY);
3138            info!(
3139                target: "control",
3140                code = error_codes::MODULE_WARMING,
3141                module_id = ?target_module_id,
3142                connection_id = ctx.connection_id.get(),
3143                reason = "declared_not_ready",
3144                "route.open refused"
3145            );
3146            // The module is registered but says it cannot take work, which is
3147            // an outage from the caller's side even though its process is up.
3148            self.route_outages
3149                .record_not_serving(&target_module_id, "declared_not_ready");
3150            return Ok(vec![control_error_body_frame(
3151                &frame,
3152                ErrorBody {
3153                    code: error_codes::MODULE_WARMING.to_string(),
3154                    message: format!(
3155                        "module_id '{target_module_id}' is registered and has declared itself not ready; retry"
3156                    ),
3157                    detail: Some(serde_json::json!({
3158                        "reason": "declared_not_ready"
3159                    })),
3160                },
3161            )?]);
3162        }
3163
3164        // Effective readiness, second half: a module that declares a capability
3165        // `need: required` is not routable while that capability has no
3166        // registered provider. It is enforced HERE, as a retryable routing
3167        // refusal, and deliberately not as spawn ordering or a boot block. The
3168        // module is still started and registered and can make its own calls;
3169        // spawn ordering is a promise that cannot be kept once a provider
3170        // crashes at runtime, and refusing to boot would stop the whole
3171        // machine, including the tools needed to fix its configuration.
3172        //
3173        // "Provided" is the evaluator's verdict, which counts a provider as
3174        // soon as it has REGISTERED, not once it is ready. Two modules that
3175        // require each other's capabilities are therefore both routable once
3176        // both register; counting readiness instead would deadlock them.
3177        //
3178        // Only new opens are refused. Routes already bound when a provider
3179        // goes away stay bound: nothing here tears them down, and the module
3180        // answers them as it can. Like the readiness read above this is
3181        // best-effort against a provider registering or leaving concurrently.
3182        if let Some(capability) = self.first_unprovided_required_capability(&registration) {
3183            self.counters
3184                .increment_route_open_refused(ROUTE_OPEN_REFUSED_REQUIRED_CAPABILITY_UNPROVIDED);
3185            info!(
3186                target: "control",
3187                code = error_codes::MODULE_WARMING,
3188                module_id = ?target_module_id,
3189                connection_id = ctx.connection_id.get(),
3190                reason = NotReadyReason::REQUIRED_CAPABILITY_UNPROVIDED,
3191                capability = %capability,
3192                "route.open refused"
3193            );
3194            return Ok(vec![control_error_body_frame(
3195                &frame,
3196                ErrorBody {
3197                    code: error_codes::MODULE_WARMING.to_string(),
3198                    message: format!(
3199                        "module_id '{target_module_id}' requires capability '{capability}', \
3200                         which no registered module provides; retry"
3201                    ),
3202                    detail: Some(serde_json::json!({
3203                        "reason": NotReadyReason::REQUIRED_CAPABILITY_UNPROVIDED,
3204                        "capability": capability,
3205                    })),
3206                },
3207            )?]);
3208        }
3209
3210        if !target_has_required_role(&target, &registration.manifest.provides) {
3211            return Ok(vec![self.route_open_refusal_frame(
3212                ctx,
3213                &frame,
3214                &target_module_id,
3215                "role_not_provided",
3216                "target_unavailable",
3217                format!("module_id '{target_module_id}' does not provide the requested target"),
3218            )?]);
3219        }
3220
3221        if registration.state != ChannelState::Active {
3222            return Ok(vec![self.route_open_refusal_frame(
3223                ctx,
3224                &frame,
3225                &target_module_id,
3226                "registration_not_active",
3227                "target_unavailable",
3228                format!("module_id '{target_module_id}' is not active"),
3229            )?]);
3230        }
3231
3232        if self
3233            .forwarding
3234            .module_is_draining(&target_module_id)
3235            .map_err(RouterError::Forwarding)?
3236        {
3237            return Ok(vec![self.route_open_refusal_frame(
3238                ctx,
3239                &frame,
3240                &target_module_id,
3241                "reloading",
3242                "module_reloading",
3243                format!("module_id '{target_module_id}' is reloading"),
3244            )?]);
3245        }
3246
3247        if let Some(process_liveness) = self.process_liveness.as_ref().filter(|process_liveness| {
3248            process_liveness.process_live(&target_module_id) == Some(false)
3249        }) {
3250            // A module the supervisor is restarting or reloading can still hold
3251            // a registration: the old process before its connection closes, or
3252            // a new one that registered while the supervisor was draining. The
3253            // forwarding table does not see that as draining, but the consumer
3254            // should still be told to retry soon, exactly as for the drain
3255            // above, rather than that the target is unavailable.
3256            if process_liveness.process_replacing(&target_module_id) {
3257                return Ok(vec![self.route_open_refusal_frame(
3258                    ctx,
3259                    &frame,
3260                    &target_module_id,
3261                    "reloading",
3262                    "module_reloading",
3263                    format!("module_id '{target_module_id}' is reloading"),
3264                )?]);
3265            }
3266            return Ok(vec![self.route_open_refusal_frame(
3267                ctx,
3268                &frame,
3269                &target_module_id,
3270                "supervisor_not_live",
3271                "target_unavailable",
3272                format!("module_id '{target_module_id}' is not live"),
3273            )?]);
3274        }
3275
3276        if !self
3277            .forwarding
3278            .has_live_module_connection(&target_module_id)
3279            .map_err(RouterError::Forwarding)?
3280        {
3281            return Ok(vec![self.route_open_refusal_frame(
3282                ctx,
3283                &frame,
3284                &target_module_id,
3285                "no_forwarding_connection",
3286                "target_unavailable",
3287                format!("module_id '{target_module_id}' has no live forwarding connection"),
3288            )?]);
3289        }
3290
3291        if let Some(error) =
3292            self.guard_module_control_op(&frame, &target_module_id, "route.bind")?
3293        {
3294            self.observe_route_open_refusal(
3295                ctx,
3296                &target_module_id,
3297                "op_not_allowed",
3298                "op_not_allowed",
3299            );
3300            return Ok(vec![error]);
3301        }
3302
3303        let principal = match self.route_open_principal(&frame, consumer_identity)? {
3304            Ok(principal) => principal,
3305            Err(error) => {
3306                self.observe_route_open_refusal(
3307                    ctx,
3308                    &target_module_id,
3309                    "bad_consumer_identity",
3310                    "bad_consumer_identity",
3311                );
3312                return Ok(vec![error]);
3313            }
3314        };
3315
3316        // This is attested, control-plane policy for supervised module origins.
3317        // Keep it before route reservation and out of the opaque forwarding hot
3318        // path: data frames must never acquire a per-frame capability check.
3319        if let Principal::Reserved {
3320            module_id: opening_module_id,
3321        } = &principal
3322        {
3323            if let Some(opening_registration) = self
3324                .registry
3325                .get_module(opening_module_id)
3326                .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?
3327            {
3328                if let Some(capability) =
3329                    denied_capability(&opening_registration.manifest, &registration.manifest)
3330                {
3331                    warn!(
3332                        opening_module_id,
3333                        target_module_id,
3334                        capability,
3335                        "refusing route.open because an attested capability deny edge matches"
3336                    );
3337                    return Ok(vec![self.route_open_refusal_frame(
3338                        ctx,
3339                        &frame,
3340                        &target_module_id,
3341                        "capability_deny_edge",
3342                        "capability_forbidden",
3343                        format!(
3344                            "module_id '{opening_module_id}' must never reach capability '{capability}' provided by '{target_module_id}'"
3345                        ),
3346                    )?]);
3347                }
3348            }
3349        }
3350
3351        if admission_facts.is_some() {
3352            let carrier_matches = matches!(
3353                &principal,
3354                Principal::Reserved { module_id }
3355                    if self.admission_facts_carrier_module_id.as_deref() == Some(module_id)
3356            );
3357            if !carrier_matches {
3358                return Ok(vec![self.route_open_refusal_frame(
3359                    ctx,
3360                    &frame,
3361                    &target_module_id,
3362                    "admission_facts_carrier_not_permitted",
3363                    "admission_facts_not_permitted",
3364                    "admission facts may only be carried by the configured reserved module",
3365                )?]);
3366            }
3367
3368            let target_allowed = self
3369                .admission_facts_targets
3370                .as_ref()
3371                .is_some_and(|targets| targets.iter().any(|id| id == &target_module_id));
3372            if !target_allowed {
3373                return Ok(vec![self.route_open_refusal_frame(
3374                    ctx,
3375                    &frame,
3376                    &target_module_id,
3377                    "admission_facts_target_not_listed",
3378                    "admission_facts_target_not_allowed",
3379                    format!(
3380                        "admission facts are not permitted for target module_id '{target_module_id}'"
3381                    ),
3382                )?]);
3383            }
3384
3385            // Keep the value opaque to subc. The downstream admission validator owns
3386            // schema and semantic checks; this daemon only enforces carrier authority
3387            // and the configured destination allowlist.
3388        }
3389
3390        // Scope admission, on the attested principal above and never on the
3391        // request body. The tag read here travels with the pending bind and is
3392        // compared with the published one at commit, so a sync between here
3393        // and the module's ack refuses the open instead of binding a stamp
3394        // that is no longer true.
3395        let (bound_scope, scope_stamp) = match scope {
3396            None => (None, None),
3397            Some(selector) => {
3398                let owner_configured = match &selector.owner {
3399                    // A reserved owner counts as configured from before its
3400                    // process is spawned (see `SupervisorHandle::is_configured`).
3401                    // So an owner that has not synced its scopes yet is refused
3402                    // as retryable (`scope_not_synced`), not as one that will
3403                    // never sync.
3404                    Principal::Reserved { module_id } => self.supervisor.is_configured(module_id),
3405                    _ => false,
3406                };
3407                let admitted = self
3408                    .scopes
3409                    .read()
3410                    .unwrap_or_else(|poisoned| poisoned.into_inner())
3411                    .admit(&principal, &target_module_id, &selector, owner_configured)
3412                    .and_then(|admission| {
3413                        crate::scopes::check_target_flow_support(
3414                            &admission.stamp,
3415                            &target_module_id,
3416                            registration.manifest.capabilities.as_ref(),
3417                        )?;
3418                        Ok(admission)
3419                    });
3420                match admitted {
3421                    Ok(admission) => (
3422                        Some(BoundScope {
3423                            owner: admission.owner,
3424                            scope_ref: admission.stamp.scope_ref.clone(),
3425                            tag: admission.tag,
3426                        }),
3427                        Some(admission.stamp),
3428                    ),
3429                    Err(refusal) => {
3430                        return Ok(vec![self.route_open_refusal_frame(
3431                            ctx,
3432                            &frame,
3433                            &target_module_id,
3434                            refusal.code,
3435                            refusal.code,
3436                            refusal.message,
3437                        )?]);
3438                    }
3439                }
3440            }
3441        };
3442
3443        // Bind admits a root that no longer exists on disk, because refusing here
3444        // closes the only exit from a paused run: cancel needs a bound route, and a
3445        // renamed or reclaimed directory makes that route unopenable forever. The
3446        // run itself is intact and still addressable by its recorded identity.
3447        //
3448        // This does NOT relax the rule the strict constructor protects. That rule is
3449        // that no root is ever aliased into NEW durable state -- a missing component
3450        // can reappear as a symlink elsewhere, which would move the identity and
3451        // split a session's history across two of them. The engine now refuses the
3452        // two operations that create such state (send and import) at admission,
3453        // which is a narrower way to hold the same invariant: reads and terminations
3454        // are admitted, writes are not. That refusal had to ship before this line
3455        // changed, or there is an interval where a send commits under a provisional
3456        // identity -- the exact failure the original policy existed to prevent.
3457        //
3458        // Resolution follows realpath rather than lexical cleanup: the longest
3459        // existing ancestor is canonicalized and the missing tail re-appended, so a
3460        // live root is unchanged and a vanished leaf keeps the identity it was
3461        // admitted under. Lexical cleanup would mint a DIFFERENT identity for the
3462        // same caller the moment the directory vanished, which strands the run more
3463        // quietly than refusing it.
3464        let project_root = match ProjectRootId::from_path_allowing_missing(&identity.project_root) {
3465            Ok(project_root) => project_root,
3466            Err(err) => {
3467                return Ok(vec![control_error_frame(
3468                    &frame,
3469                    "invalid_project_root",
3470                    err.to_string(),
3471                )?])
3472            }
3473        };
3474        identity.project_root = project_root.as_path().to_path_buf();
3475
3476        // Last gate before any relay work, and deliberately after the cheap
3477        // registry and availability checks above: those name a more precise
3478        // condition (unknown, removed, reloading) and a caller is better served
3479        // by the precise code than by this one.
3480        //
3481        // Everything below this point costs an egress permit, a reserved handle
3482        // pair and, if the module does not answer, the whole relay budget. The
3483        // reader no longer waits for that budget, so cap each target explicitly;
3484        // serial dispatch used to provide the accidental cap of one relay per
3485        // connection. Admission is a mutex-protected count and never waits.
3486        let _concurrency_guard = match self
3487            .route_bind_concurrency
3488            .try_admit(&target_module_id, MAX_PENDING_ROUTE_BINDS_PER_TARGET)
3489        {
3490            Ok(guard) => guard,
3491            Err(in_flight) => {
3492                return Ok(vec![self.route_open_target_capacity_refusal(
3493                    ctx,
3494                    &frame,
3495                    &target_module_id,
3496                    in_flight,
3497                )?]);
3498            }
3499        };
3500
3501        // A module that has already burned the whole budget `threshold` times
3502        // in a row does not get to charge it again until a probe says it recovered.
3503        let mut breaker = match self.route_bind_breakers.admit(&target_module_id) {
3504            RouteBindAdmission::Admitted { guard, probe } => {
3505                if probe {
3506                    info!(
3507                        module_id = %target_module_id,
3508                        connection_id = ctx.connection_id.get(),
3509                        "route.bind breaker half-open: admitting one probe"
3510                    );
3511                }
3512                guard
3513            }
3514            RouteBindAdmission::Refused {
3515                consecutive_timeouts,
3516                retry_in,
3517                probe_in_flight,
3518            } => {
3519                return Ok(vec![self.route_open_breaker_refusal_frame(
3520                    ctx,
3521                    &frame,
3522                    &target_module_id,
3523                    consecutive_timeouts,
3524                    retry_in,
3525                    probe_in_flight,
3526                )?]);
3527            }
3528        };
3529
3530        // Resolve the per-module budget here so the wait matches the operator's
3531        // intent for this specific target. A per-module override in
3532        // `subc.jsonc` (or `with_route_bind_relay_timeouts` for embedded
3533        // daemons) wins over the daemon-wide default.
3534        let route_bind_relay_timeout = self.route_bind_relay_timeout_for(&target_module_id);
3535        let relay_deadline = Instant::now() + route_bind_relay_timeout;
3536        let pending = match self
3537            .forwarding
3538            .begin_route_bind_relay_for(
3539                ctx.connection_id,
3540                ctx.egress.clone(),
3541                response_version(&frame),
3542                frame.header.corr,
3543                &target_module_id,
3544                principal.clone(),
3545                bound_scope,
3546                Some(project_root),
3547                relay_deadline,
3548            )
3549            .await
3550        {
3551            Ok(pending) => pending,
3552            Err(err) => {
3553                return Ok(vec![self.route_open_refusal_frame(
3554                    ctx,
3555                    &frame,
3556                    &target_module_id,
3557                    "relay_reservation_failed",
3558                    forwarding_error_code(&err),
3559                    err.to_string(),
3560                )?])
3561            }
3562        };
3563        let crate::forwarding::PendingRouteBindRelay {
3564            endpoint,
3565            module_sink,
3566            negotiated_ver,
3567            client_channel,
3568            client_epoch,
3569            module_channel,
3570            module_epoch,
3571            corr: relay_corr,
3572            receiver,
3573        } = pending;
3574        let mut reservation =
3575            RouteBindReservationGuard::new(Arc::clone(&self.forwarding), endpoint, relay_corr);
3576
3577        // Reserving egress can wait while a module reconnects or a swap cuts
3578        // over. Check the connection the relay actually captured, not the
3579        // earlier by-id lookup: a flow-aware module must not vouch for a
3580        // replacement. The captured sink cannot turn into another connection.
3581        if let Some(stamp) = scope_stamp
3582            .as_ref()
3583            .filter(|stamp| stamp.attributes.flow_id.is_some())
3584        {
3585            let relay_registration = self
3586                .registry
3587                .get_module_by_connection(endpoint.connection_id)
3588                .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?;
3589            if let Err(refusal) = crate::scopes::check_target_flow_support(
3590                stamp,
3591                &target_module_id,
3592                relay_registration
3593                    .as_ref()
3594                    .and_then(|registration| registration.manifest.capabilities.as_ref()),
3595            ) {
3596                reservation.release_and_disarm();
3597                return Ok(vec![self.route_open_refusal_frame(
3598                    ctx,
3599                    &frame,
3600                    &target_module_id,
3601                    refusal.code,
3602                    refusal.code,
3603                    refusal.message,
3604                )?]);
3605            }
3606        }
3607
3608        debug!(
3609            connection_id = ctx.connection_id.get(),
3610            client_channel,
3611            client_epoch,
3612            module_channel,
3613            module_epoch,
3614            "reserved route handle pair"
3615        );
3616        // Rendered BEFORE the move into the relay, because the accept arm below
3617        // is where it is logged and the principal is gone by then.
3618        let principal_label = match &principal {
3619            Principal::Reserved { module_id } => format!("reserved:{module_id}"),
3620            Principal::Direct => "direct".to_string(),
3621            other => format!("{other:?}"),
3622        };
3623        let relay = ModuleControlRequest::RouteBind {
3624            route_channel: module_channel,
3625            epoch: module_epoch,
3626            target,
3627            identity,
3628            principal: Some(principal),
3629            consumer_capabilities,
3630            role_versions,
3631            admission_facts,
3632            scope: scope_stamp,
3633        };
3634        let relay_body = serde_json::to_vec(&relay).map_err(|err| {
3635            RouterError::backend(
3636                0,
3637                frame.header.corr,
3638                format!("failed to encode route.bind request: {err}"),
3639            )
3640        })?;
3641        let relay_frame = Frame::build_with_version(
3642            negotiated_ver,
3643            FrameType::Request,
3644            control_flags(),
3645            0,
3646            0,
3647            relay_corr,
3648            relay_body,
3649        )
3650        .map_err(RouterError::FrameBuild)?;
3651
3652        if let Err(err) = module_sink.send(relay_frame).await {
3653            reservation.release_and_disarm();
3654            return Ok(vec![self.route_open_refusal_frame(
3655                ctx,
3656                &frame,
3657                &target_module_id,
3658                "relay_send_failed",
3659                "target_unavailable",
3660                err.to_string(),
3661            )?]);
3662        }
3663
3664        if !self
3665            .forwarding
3666            .mark_route_bind_relay_enqueued(endpoint, relay_corr)
3667            .map_err(RouterError::Forwarding)?
3668        {
3669            self.send_abandoned_route_bind_goodbye(
3670                &module_sink,
3671                negotiated_ver,
3672                module_channel,
3673                module_epoch,
3674            );
3675        }
3676
3677        match timeout_at(relay_deadline, receiver).await {
3678            Ok(Ok(RouteBindRelayOutcome::Accepted)) => {
3679                reservation.disarm();
3680                if breaker.record_accepted() {
3681                    info!(
3682                        module_id = %target_module_id,
3683                        "route.bind breaker closed: the probe was accepted"
3684                    );
3685                }
3686                self.observe_route_open_accept(ctx, &target_module_id, &principal_label);
3687                Ok(Vec::new())
3688            }
3689            Ok(Ok(RouteBindRelayOutcome::Rejected(body))) => {
3690                reservation.release_and_disarm();
3691                // A module that says no in microseconds is healthy. Rejection
3692                // is a different condition with its own refusal and must not
3693                // move the breaker.
3694                breaker.record_inconclusive();
3695                // The daemon's own commit re-check refused the bind because the
3696                // scope ended or changed after admission. The module accepted;
3697                // counting it as a module rejection would blame the module.
3698                let scope_code = match body.code.as_str() {
3699                    error_codes::SCOPE_CHANGED => Some(error_codes::SCOPE_CHANGED),
3700                    error_codes::SCOPE_ENDED => Some(error_codes::SCOPE_ENDED),
3701                    _ => None,
3702                };
3703                if let Some(code) = scope_code {
3704                    self.observe_route_open_refusal(
3705                        ctx,
3706                        &target_module_id,
3707                        "scope_changed_before_commit",
3708                        code,
3709                    );
3710                    return Ok(vec![control_error_body_frame(&frame, body)?]);
3711                }
3712                self.counters
3713                    .increment_route_open_refused("module_rejected");
3714                info!(
3715                    target: "control",
3716                    code = "module_rejected",
3717                    module_code = ?body.code,
3718                    module_id = ?target_module_id,
3719                    connection_id = ctx.connection_id.get(),
3720                    "route.open refused"
3721                );
3722                Ok(vec![control_error_body_frame(&frame, body)?])
3723            }
3724            Ok(Ok(RouteBindRelayOutcome::ModuleGone(message))) => {
3725                reservation.release_and_disarm();
3726                breaker.record_inconclusive();
3727                // Fires when the module's connection closes while a relayed
3728                // bind is pending -- typically a caller racing a module restart
3729                // whose bind was relayed BEFORE the drain mark went up. Logged
3730                // because the caller sees only its own error and the fleet has
3731                // already spent one diagnosis round unable to tell this arm
3732                // from a relay timeout without daemon-side evidence.
3733                tracing::warn!(
3734                    module_id = %target_module_id,
3735                    "route.bind relay abandoned: {message}"
3736                );
3737                Ok(vec![self.route_open_refusal_frame(
3738                    ctx,
3739                    &frame,
3740                    &target_module_id,
3741                    "relay_abandoned",
3742                    "target_unavailable",
3743                    message,
3744                )?])
3745            }
3746            Ok(Err(_)) => {
3747                reservation.release_and_disarm();
3748                breaker.record_inconclusive();
3749                Ok(vec![self.route_open_refusal_frame(
3750                    ctx,
3751                    &frame,
3752                    &target_module_id,
3753                    "relay_waiter_canceled",
3754                    "target_unavailable",
3755                    "route.bind relay waiter was canceled before the module responded",
3756                )?])
3757            }
3758            Err(_) => {
3759                reservation.release_and_disarm();
3760                // THE ONLY ARM THAT MOVES THE BREAKER. Budget exhausted with no
3761                // answer at all is the one condition a fast refusal can
3762                // usefully stand in for; every other arm already answered.
3763                if let Some(opened) = breaker.record_timeout(
3764                    self.route_bind_breaker_threshold,
3765                    self.route_bind_breaker_cooldown,
3766                ) {
3767                    warn!(
3768                        module_id = %target_module_id,
3769                        consecutive_timeouts = opened.consecutive_timeouts,
3770                        cooldown_ms = self.route_bind_breaker_cooldown.as_millis() as u64,
3771                        reopened_after_probe = opened.reopened_after_probe,
3772                        "route.bind breaker open: refusing route.open for this module without relaying until one probe says it recovered"
3773                    );
3774                }
3775                // The generous budget just burned to no answer: the module is
3776                // registered and its connection is up, but its bind handler sat
3777                // on the ack for the full budget (warm-on-bind, cold configure,
3778                // or a wedged handler). Every earlier unavailability shape
3779                // fast-refuses BEFORE the relay, so this arm firing means the
3780                // slowness is module-side -- log it so the per-module timeline
3781                // is reconstructable without client audit rows.
3782                tracing::warn!(
3783                    module_id = %target_module_id,
3784                    timeout_ms = route_bind_relay_timeout.as_millis() as u64,
3785                    "route.bind relay timed out: module did not ack within budget"
3786                );
3787                Ok(vec![self.route_open_refusal_frame(
3788                    ctx,
3789                    &frame,
3790                    &target_module_id,
3791                    "relay_timed_out",
3792                    "module_timeout",
3793                    format!(
3794                        "module_id '{target_module_id}' did not answer route.bind within {:?}",
3795                        route_bind_relay_timeout
3796                    ),
3797                )?])
3798            }
3799        }
3800    }
3801
3802    fn handle_supervisor_spawn_snapshot(&self, frame: Frame) -> Result<Vec<Frame>, RouterError> {
3803        let response = ClientControlResponse::SupervisorSpawnSnapshot {
3804            snapshot: self.supervisor.spawn_snapshot(),
3805        };
3806        Ok(vec![control_response_body_frame(
3807            &frame,
3808            &response,
3809            "ClientControlResponse::SupervisorSpawnSnapshot",
3810        )?])
3811    }
3812
3813    fn handle_supervisor_spawn_subscribe(
3814        &self,
3815        ctx: &RouteCtx,
3816        frame: Frame,
3817        since: Option<SpawnCursor>,
3818    ) -> Result<Vec<Frame>, RouterError> {
3819        match self.supervisor.subscribe_spawns(
3820            ctx.connection_id,
3821            frame.header.corr,
3822            response_version(&frame),
3823            since,
3824            ctx.egress.clone(),
3825        ) {
3826            Ok(()) => Ok(Vec::new()),
3827            Err(SpawnSubscribeRefusal::ForeignIncarnation { current }) => {
3828                Ok(vec![control_error_body_frame(
3829                    &frame,
3830                    ErrorBody {
3831                        code: "spawn_cursor_incarnation_mismatch".to_string(),
3832                        message: "spawn cursor belongs to a different daemon incarnation"
3833                            .to_string(),
3834                        detail: Some(serde_json::json!({
3835                            "current_daemon_incarnation": current
3836                        })),
3837                    },
3838                )?])
3839            }
3840            Err(SpawnSubscribeRefusal::TooOld { oldest }) => Ok(vec![control_error_body_frame(
3841                &frame,
3842                ErrorBody {
3843                    code: "spawn_cursor_too_old".to_string(),
3844                    message: "spawn cursor predates the retained event ring".to_string(),
3845                    detail: Some(serde_json::json!({
3846                        "oldest_retained_cursor": oldest
3847                    })),
3848                },
3849            )?]),
3850            Err(SpawnSubscribeRefusal::Frame(error)) => Err(RouterError::backend(
3851                0,
3852                frame.header.corr,
3853                format!("failed to open supervisor spawn subscription: {error}"),
3854            )),
3855        }
3856    }
3857
3858    async fn handle_supervisor_list(&self, frame: Frame) -> Result<Vec<Frame>, RouterError> {
3859        let generation = self
3860            .registry
3861            .generation()
3862            .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?;
3863        let mut modules = Vec::new();
3864        for module in self.supervisor.list() {
3865            let status = module.status_for_control("list").map_err(|err| {
3866                RouterError::backend(
3867                    0,
3868                    frame.header.corr,
3869                    format!("failed to read supervisor status: {err}"),
3870                )
3871            })?;
3872            let (configured, _) = module.configuration().map_err(|err| {
3873                RouterError::backend(
3874                    0,
3875                    frame.header.corr,
3876                    format!("failed to read module configuration: {err}"),
3877                )
3878            })?;
3879            // Status and configuration snapshots release their locks before the image probe awaits.
3880            let image = module.running_image_agreement().await;
3881            // Read per request so the figure is current when the operator asks;
3882            // the daemon samples nothing in between.
3883            let resources = Some(module.child_resource_usage());
3884            let pending_reload = Some(reload_verdict(
3885                &configured.program,
3886                status.spawned_from.as_deref(),
3887                image,
3888            ));
3889            modules.push(SupervisorEntry {
3890                // Keep the retired policy field on the wire for one release so
3891                // existing status consumers still receive the platform policy.
3892                launch_nonce_env: Some(!cfg!(unix)),
3893                module_id: status.module_id,
3894                state: status.state.to_string(),
3895                enabled: status.enabled,
3896                live: status.live,
3897                protocol: status.protocol,
3898                health: status.health.status,
3899                pending_reload,
3900                last_probe_ms: status.health.last_probe_ms,
3901                last_exit_code: status.last_exit.as_ref().and_then(|e| e.code),
3902                last_exit_signal: status.last_exit.as_ref().and_then(|e| e.signal),
3903                last_exit_ms: status.last_exit.as_ref().map(|e| e.at_ms),
3904                last_exit_kind: status.last_exit.as_ref().map(|e| e.kind.into()),
3905                restart_count: Some(status.restart_count),
3906                max_restarts: Some(status.max_restarts),
3907                lifetime_restarts: Some(status.lifetime_restarts),
3908                spawn_generation: Some(status.spawn_generation),
3909                restart_window_secs: Some(status.restart_window.as_secs()),
3910                drain_timeout_ms: Some(status.drain_timeout.as_millis() as u64),
3911                restart_backoff_ms: Some(status.restart_backoff.as_millis() as u64),
3912                restart_max_backoff_ms: Some(status.restart_max_backoff.as_millis() as u64),
3913                resources,
3914            });
3915        }
3916        let response = ClientControlResponse::SupervisorList {
3917            generation,
3918            modules,
3919        };
3920        Ok(vec![control_response_body_frame(
3921            &frame,
3922            &response,
3923            "ClientControlResponse::SupervisorList",
3924        )?])
3925    }
3926
3927    fn handle_supervisor_stderr_tail(
3928        &self,
3929        frame: Frame,
3930        module_id: String,
3931        max_lines: Option<u32>,
3932        max_bytes: Option<u32>,
3933    ) -> Result<Vec<Frame>, RouterError> {
3934        let Some(module) = self.supervisor.get(&module_id) else {
3935            return Ok(vec![control_error_frame(
3936                &frame,
3937                "unknown_module",
3938                format!("module_id '{module_id}' is not supervised"),
3939            )?]);
3940        };
3941
3942        let snapshot = module.stderr_tail(
3943            max_lines.map(|value| value as usize),
3944            max_bytes.map(|value| value as usize),
3945        );
3946
3947        let response = ClientControlResponse::SupervisorStderrTail {
3948            module_id,
3949            tail: StderrTail {
3950                capture: match snapshot.capture {
3951                    CaptureState::Captured => StderrCaptureState::Captured,
3952                    CaptureState::Incomplete { reason } => {
3953                        StderrCaptureState::Incomplete { reason }
3954                    }
3955                    CaptureState::NotCaptured { reason } => {
3956                        StderrCaptureState::NotCaptured { reason }
3957                    }
3958                },
3959                entries: snapshot
3960                    .entries
3961                    .into_iter()
3962                    .map(|entry| match entry {
3963                        TailEntry::Line {
3964                            text,
3965                            truncated,
3966                            at_ms,
3967                        } => StderrTailEntry::Line {
3968                            text,
3969                            truncated,
3970                            at_ms,
3971                        },
3972                        TailEntry::ProcessStart => StderrTailEntry::ProcessStart,
3973                    })
3974                    .collect(),
3975                dropped_lines: snapshot.dropped_lines,
3976            },
3977        };
3978        Ok(vec![control_response_body_frame(
3979            &frame,
3980            &response,
3981            "ClientControlResponse::SupervisorStderrTail",
3982        )?])
3983    }
3984
3985    async fn handle_supervisor_terminals(
3986        &self,
3987        frame: Frame,
3988        module_id: String,
3989    ) -> Result<Vec<Frame>, RouterError> {
3990        let Some(module) = self.supervisor.get(&module_id) else {
3991            return Ok(vec![control_error_frame(
3992                &frame,
3993                "unknown_module",
3994                format!("module_id '{module_id}' is not supervised"),
3995            )?]);
3996        };
3997
3998        // The journal read runs on a blocking thread: it can be megabytes of
3999        // file I/O and must not occupy a runtime worker.
4000        let terminals = module
4001            .read_durable_terminal_history()
4002            .await
4003            .map_err(|error| {
4004                RouterError::backend(
4005                    0,
4006                    frame.header.corr,
4007                    format!("failed to read terminal history: {error}"),
4008                )
4009            })?;
4010        let response = ClientControlResponse::SupervisorTerminals {
4011            module_id,
4012            terminals,
4013        };
4014        Ok(vec![control_response_body_frame(
4015            &frame,
4016            &response,
4017            "ClientControlResponse::SupervisorTerminals",
4018        )?])
4019    }
4020
4021    fn handle_supervisor_routes(
4022        &self,
4023        frame: Frame,
4024        module_id: Option<String>,
4025    ) -> Result<Vec<Frame>, RouterError> {
4026        let modules = self
4027            .forwarding
4028            .route_census(module_id.as_deref())
4029            .map_err(RouterError::Forwarding)?
4030            .into_iter()
4031            .map(|(module_id, routes)| SupervisorRouteModule {
4032                module_id,
4033                routes: routes
4034                    .into_iter()
4035                    .map(|route| SupervisorRoute {
4036                        consumer: match route.principal {
4037                            Principal::Reserved { module_id } => {
4038                                SupervisorRouteConsumer::Reserved { module_id }
4039                            }
4040                            Principal::Direct | Principal::Unverified => {
4041                                SupervisorRouteConsumer::Direct {
4042                                    connection_id: route.goodbye_target.connection_id.get(),
4043                                }
4044                            }
4045                        },
4046                        age_ms: Instant::now()
4047                            .saturating_duration_since(route.bound_at)
4048                            .as_millis()
4049                            .try_into()
4050                            .unwrap_or(u64::MAX),
4051                        draining: route.draining,
4052                        drain_reason: route.drain_reason,
4053                    })
4054                    .collect(),
4055            })
4056            .collect();
4057        let response = ClientControlResponse::SupervisorRoutes { modules };
4058        Ok(vec![control_response_body_frame(
4059            &frame,
4060            &response,
4061            "ClientControlResponse::SupervisorRoutes",
4062        )?])
4063    }
4064
4065    async fn handle_supervisor_provenance(
4066        &self,
4067        frame: Frame,
4068        module_id: Option<String>,
4069    ) -> Result<Vec<Frame>, RouterError> {
4070        let mut selected = if let Some(module_id) = module_id {
4071            let Some(module) = self.supervisor.get(&module_id) else {
4072                return Ok(vec![control_error_frame(
4073                    &frame,
4074                    "unknown_module",
4075                    format!("module_id '{module_id}' is not supervised"),
4076                )?]);
4077            };
4078            vec![module]
4079        } else {
4080            self.supervisor.list()
4081        };
4082
4083        let mut modules = Vec::with_capacity(selected.len());
4084        for module in selected.drain(..) {
4085            let (status, observed_image) = module
4086                .status_and_running_image_agreement()
4087                .await
4088                .map_err(|err| {
4089                    RouterError::backend(
4090                        0,
4091                        frame.header.corr,
4092                        format!("failed to read supervisor status: {err}"),
4093                    )
4094                })?;
4095            let module_declared = self
4096                .registry
4097                .get_module(&status.module_id)
4098                .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?
4099                .and_then(|registration| registration.manifest.provenance)
4100                .map(|build| ModuleDeclaredProvenance::Reported { build })
4101                .unwrap_or(ModuleDeclaredProvenance::Unverifiable);
4102            #[cfg(test)]
4103            let running_image = match &self.provenance_probe_override {
4104                Some(result) => result.clone(),
4105                None => observed_image,
4106            };
4107            #[cfg(not(test))]
4108            let running_image = observed_image;
4109            modules.push(SupervisorModuleProvenance {
4110                module_id: status.module_id,
4111                module_declared,
4112                daemon_observed: SupervisorObservedProcess {
4113                    pid: status.pid,
4114                    spawned_at_ms: status.spawned_at_ms,
4115                    spawned_from: status.spawned_from,
4116                    running_image,
4117                },
4118            });
4119        }
4120        let daemon = SupervisorDaemonProvenance {
4121            daemon_build: self.daemon_provenance.build.clone(),
4122            daemon_observed: DaemonObservedProcess {
4123                pid: self.daemon_provenance.pid,
4124                started_at_ms: self
4125                    .daemon_provenance
4126                    .start_clock
4127                    .map(|clock| clock.started_at_ms())
4128                    .or(self.daemon_provenance.started_at_ms),
4129                running_image: self
4130                    .daemon_provenance
4131                    .probe
4132                    .observe(
4133                        self.daemon_provenance.pid,
4134                        self.daemon_provenance.executable_path.as_deref(),
4135                        self.daemon_provenance.executable_identity,
4136                        self.daemon_provenance.process_start_time,
4137                    )
4138                    .await,
4139            },
4140        };
4141        let response = ClientControlResponse::SupervisorProvenance { daemon, modules };
4142        Ok(vec![control_response_body_frame(
4143            &frame,
4144            &response,
4145            "ClientControlResponse::SupervisorProvenance",
4146        )?])
4147    }
4148
4149    fn handle_supervisor_health(&self, frame: Frame) -> Result<Vec<Frame>, RouterError> {
4150        self.refresh_capability_requirements();
4151        let generation = self
4152            .registry
4153            .generation()
4154            .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?;
4155        let modules = self
4156            .supervisor
4157            .list()
4158            .into_iter()
4159            .map(|module| {
4160                let status = module.status_for_control("health").map_err(|err| {
4161                    RouterError::backend(
4162                        0,
4163                        frame.header.corr,
4164                        format!("failed to read supervisor health: {err}"),
4165                    )
4166                })?;
4167                let module_id = status.module_id;
4168                let capability_detail = self
4169                    .capability_evaluator
4170                    .required_problem_detail(&module_id);
4171                Ok(SupervisorHealthEntry {
4172                    module_id,
4173                    status: status.health.status,
4174                    detail: append_capability_problem_detail(
4175                        status.health.detail,
4176                        capability_detail,
4177                    ),
4178                    metrics: status.health.metrics,
4179                    consecutive_failures: status.health.consecutive_failures,
4180                    late_answer_count: status.health.late_answer_count,
4181                    last_late_answer_latency_ms: status.health.last_late_answer_latency_ms,
4182                    last_action: status.health.last_action,
4183                    last_action_ms: status.health.last_action_ms,
4184                    last_probe_ms: status.health.last_probe_ms,
4185                })
4186            })
4187            .collect::<Result<Vec<_>, RouterError>>()?;
4188        let response = ClientControlResponse::SupervisorHealth {
4189            generation,
4190            modules,
4191        };
4192        Ok(vec![control_response_body_frame(
4193            &frame,
4194            &response,
4195            "ClientControlResponse::SupervisorHealth",
4196        )?])
4197    }
4198
4199    async fn handle_supervisor_restart(
4200        &self,
4201        frame: Frame,
4202        module_id: String,
4203        drain_timeout_ms: Option<u64>,
4204    ) -> Result<Vec<Frame>, RouterError> {
4205        let operation_lock = self.supervisor.operation_lock();
4206        let _operation_guard = operation_lock.lock().await;
4207        let Some(module) = self.supervisor.get(&module_id) else {
4208            return Ok(vec![control_error_frame(
4209                &frame,
4210                "unknown_module",
4211                format!("module_id '{module_id}' is not supervised"),
4212            )?]);
4213        };
4214
4215        self.route_outages.mark_operator_action(&module_id);
4216        if let Err(err) = module.restart(drain_timeout_ms).await {
4217            self.route_outages
4218                .operator_action_ended_unrefused(&module_id);
4219            let (code, message) = match err {
4220                crate::supervise::SuperviseError::Disabled { .. } => {
4221                    ("module_disabled", err.to_string())
4222                }
4223                crate::supervise::SuperviseError::SwapInProgress { .. } => {
4224                    ("swap_in_progress", err.to_string())
4225                }
4226                _ => (
4227                    "target_unavailable",
4228                    format!("failed to restart module_id '{module_id}': {err}"),
4229                ),
4230            };
4231            return Ok(vec![control_error_frame(&frame, code, message)?]);
4232        }
4233
4234        let response = ClientControlResponse::SupervisorAck {
4235            module_id,
4236            applied: true,
4237        };
4238        Ok(vec![control_response_body_frame(
4239            &frame,
4240            &response,
4241            "ClientControlResponse::SupervisorAck",
4242        )?])
4243    }
4244
4245    /// `supervisor.swap`. Answered when the swap has cut over or failed, not
4246    /// when the old process has finished draining: a caller whose own lane
4247    /// rides the old process must get its reply before that drain waits on it.
4248    async fn handle_supervisor_swap(
4249        &self,
4250        frame: Frame,
4251        module_id: String,
4252        ready_timeout_ms: Option<u64>,
4253    ) -> Result<Vec<Frame>, RouterError> {
4254        // The daemon-wide operation lock is held only to resolve the handle,
4255        // not across the swap. The swap can take its whole readiness budget,
4256        // and `supervisor.set_enabled` (ck module stop) takes the same lock:
4257        // holding it here would park an operator's stop behind the swap it is
4258        // meant to abort. A rescan or stop that reaches the module during the
4259        // swap is served by the swap itself (see `supervise_swap`).
4260        let module = {
4261            let operation_lock = self.supervisor.operation_lock();
4262            let _operation_guard = operation_lock.lock().await;
4263            self.supervisor.get(&module_id)
4264        };
4265        let Some(module) = module else {
4266            return Ok(vec![control_error_frame(
4267                &frame,
4268                "unknown_module",
4269                format!("module_id '{module_id}' is not supervised"),
4270            )?]);
4271        };
4272
4273        self.route_outages.mark_operator_action(&module_id);
4274        if let Err(err) = module
4275            .swap(ready_timeout_ms.map(Duration::from_millis))
4276            .await
4277        {
4278            self.route_outages
4279                .operator_action_ended_unrefused(&module_id);
4280            use crate::supervise::SuperviseError;
4281            let message = err.to_string();
4282            let error = match err {
4283                SuperviseError::Disabled { .. } => ErrorBody::new("module_disabled", message),
4284                SuperviseError::SwapRefused { reason, .. } => ErrorBody {
4285                    code: "swap_refused".to_string(),
4286                    message,
4287                    detail: Some(serde_json::json!({ "reason": reason.as_str() })),
4288                },
4289                SuperviseError::SwapFailed {
4290                    arm,
4291                    candidate_exit,
4292                    ..
4293                } => ErrorBody {
4294                    code: "swap_failed".to_string(),
4295                    message,
4296                    detail: Some(serde_json::json!({
4297                        "arm": arm.as_str(),
4298                        "candidate_exit_code": candidate_exit.as_ref().and_then(|exit| exit.code),
4299                        "candidate_exit_signal": candidate_exit.as_ref().and_then(|exit| exit.signal),
4300                    })),
4301                },
4302                _ => ErrorBody::new(
4303                    "target_unavailable",
4304                    format!("failed to swap module_id '{module_id}': {message}"),
4305                ),
4306            };
4307            return Ok(vec![control_error_body_frame(&frame, error)?]);
4308        }
4309        // A completed swap kept the incumbent serving until cutover, so it
4310        // usually opened no outage; a mark left behind would make the next,
4311        // unrelated outage read as requested.
4312        self.route_outages
4313            .operator_action_ended_unrefused(&module_id);
4314
4315        let response = ClientControlResponse::SupervisorAck {
4316            module_id,
4317            applied: true,
4318        };
4319        Ok(vec![control_response_body_frame(
4320            &frame,
4321            &response,
4322            "ClientControlResponse::SupervisorAck",
4323        )?])
4324    }
4325
4326    async fn handle_supervisor_reload(
4327        &self,
4328        frame: Frame,
4329        module_id: String,
4330    ) -> Result<Vec<Frame>, RouterError> {
4331        let operation_lock = self.supervisor.operation_lock();
4332        let _operation_guard = operation_lock.lock().await;
4333        let Some(module) = self.supervisor.get(&module_id) else {
4334            return Ok(vec![control_error_frame(
4335                &frame,
4336                "unknown_module",
4337                format!("module_id '{module_id}' is not supervised"),
4338            )?]);
4339        };
4340
4341        self.route_outages.mark_operator_action(&module_id);
4342        if let Err(err) = module.reload().await {
4343            self.route_outages
4344                .operator_action_ended_unrefused(&module_id);
4345            let (code, message) = match err {
4346                crate::supervise::SuperviseError::Disabled { .. } => {
4347                    ("module_disabled", err.to_string())
4348                }
4349                crate::supervise::SuperviseError::SwapInProgress { .. } => {
4350                    ("swap_in_progress", err.to_string())
4351                }
4352                _ => (
4353                    "reload_failed",
4354                    format!("failed to reload module_id '{module_id}': {err}"),
4355                ),
4356            };
4357            return Ok(vec![control_error_frame(&frame, code, message)?]);
4358        }
4359
4360        let response = ClientControlResponse::SupervisorAck {
4361            module_id,
4362            applied: true,
4363        };
4364        Ok(vec![control_response_body_frame(
4365            &frame,
4366            &response,
4367            "ClientControlResponse::SupervisorAck",
4368        )?])
4369    }
4370
4371    async fn handle_supervisor_rescan(
4372        &self,
4373        frame: Frame,
4374        preview: bool,
4375    ) -> Result<Vec<Frame>, RouterError> {
4376        let Some(context) = self.rescan.clone() else {
4377            return Ok(vec![control_error_frame(
4378                &frame,
4379                "rescan_unavailable",
4380                "the daemon was not started with a reloadable config path".to_string(),
4381            )?]);
4382        };
4383
4384        let operation_lock = self.supervisor.operation_lock();
4385        let _operation_guard = operation_lock.lock().await;
4386        let loaded = match crate::daemon_config::load(&context.config_path) {
4387            Ok(config) => config,
4388            Err(err) => {
4389                return Ok(vec![control_error_frame(
4390                    &frame,
4391                    "invalid_daemon_config",
4392                    format!("supervisor rescan rejected daemon config: {err}"),
4393                )?])
4394            }
4395        };
4396        // `load` reports a missing file as Ok(None), which is correct at boot
4397        // (no config, nothing to supervise) and catastrophic here: rescan treats
4398        // "not in the config" as "remove it", so an absent file would read as an
4399        // empty module list and retire the entire running fleet. An editor
4400        // writing via write-new-then-rename, or a half-finished edit, is enough
4401        // to open that window. Refuse instead: a config that cannot be read
4402        // carries no instruction to remove anything.
4403        let Some(config) = loaded else {
4404            return Ok(vec![control_error_frame(
4405                &frame,
4406                "invalid_daemon_config",
4407                format!(
4408                    "daemon config not found at {}; refusing to rescan (an absent config would \
4409                     retire every supervised module)",
4410                    context.config_path.display()
4411                ),
4412            )?]);
4413        };
4414        let (
4415            configured_port,
4416            storage_config,
4417            admission_facts_carrier_module_id,
4418            admission_facts_targets,
4419            scope_authority_owners,
4420            modules,
4421            reserved_capabilities,
4422        ) = (
4423            config.port,
4424            config.storage,
4425            config.admission_facts_carrier_module_id,
4426            config.admission_facts_targets,
4427            config.scope_authority_owners,
4428            config.modules,
4429            config.reserved_capabilities,
4430        );
4431
4432        // Collect the sections rescan cannot apply, so the REPLY carries them.
4433        //
4434        // The warning below has always been correct and has always gone only to
4435        // the journal -- addressed to whoever reads logs, while the person who
4436        // just edited the config is looking at the CLI. Naming each section
4437        // individually rather than setting a flag: "something outside modules
4438        // changed" sends the operator back to diffing their own file, which is
4439        // the work this is meant to save.
4440        let mut restart_required = Vec::new();
4441        for section in RestartRequiredSection::ALL {
4442            let changed = match section {
4443                RestartRequiredSection::Port => configured_port != context.configured_port,
4444                RestartRequiredSection::Storage => storage_config != context.storage_config,
4445                RestartRequiredSection::AdmissionFactsCarrierModuleId => {
4446                    admission_facts_carrier_module_id != context.admission_facts_carrier_module_id
4447                }
4448                RestartRequiredSection::AdmissionFactsTargets => {
4449                    admission_facts_targets != context.admission_facts_targets
4450                }
4451                RestartRequiredSection::ScopeAuthorityOwners => {
4452                    scope_authority_owners != context.scope_authority_owners
4453                }
4454            };
4455            if changed {
4456                restart_required.push(section.label().to_string());
4457            }
4458        }
4459        if !restart_required.is_empty() {
4460            warn!(
4461                config_path = %context.config_path.display(),
4462                sections = %restart_required.join(", "),
4463                "daemon config changed outside the modules section; restart the daemon to apply those changes"
4464            );
4465        }
4466
4467        for configured in &modules {
4468            if let Err(err) = validate_spec(&configured.module_spec()) {
4469                return Ok(vec![control_error_frame(
4470                    &frame,
4471                    "invalid_daemon_config",
4472                    format!("supervisor rescan rejected daemon config: {err}"),
4473                )?]);
4474            }
4475        }
4476
4477        let configured_capabilities = modules
4478            .iter()
4479            .map(|module| (module.module_id.clone(), module.enabled))
4480            .collect::<Vec<_>>();
4481        let preview_capability_warnings = if preview {
4482            let (_, registrations) = self.runtime_capability_snapshot()?;
4483            let current_modules = self
4484                .supervisor
4485                .list()
4486                .into_iter()
4487                .map(|module| module.module_id().to_string())
4488                .collect::<BTreeSet<_>>();
4489            let resulting_modules = configured_capabilities.clone();
4490            let removed = current_modules
4491                .into_iter()
4492                .filter(|module_id| {
4493                    !resulting_modules
4494                        .iter()
4495                        .any(|(configured_id, _)| configured_id == module_id)
4496                })
4497                .collect::<Vec<_>>();
4498            self.capability_evaluator.preview_removal_warnings(
4499                resulting_modules,
4500                &removed,
4501                &registrations,
4502            )
4503        } else {
4504            Vec::new()
4505        };
4506        let result = match self
4507            .reconcile_supervised_modules(&context.supervisor, modules, preview)
4508            .await
4509        {
4510            Ok(result) => result,
4511            Err(message) => {
4512                return Ok(vec![control_error_frame(&frame, "rescan_failed", message)?])
4513            }
4514        };
4515        if !preview {
4516            self.capability_evaluator
4517                .configure(configured_capabilities, reserved_capabilities);
4518            self.capability_evaluator.wake_deadline_loop();
4519            self.refresh_capability_requirements();
4520        }
4521        let mut result = result;
4522        result.restart_required = restart_required;
4523        result.capability_warnings = preview_capability_warnings;
4524        let response = ClientControlResponse::SupervisorRescan { result };
4525        Ok(vec![control_response_body_frame(
4526            &frame,
4527            &response,
4528            "ClientControlResponse::SupervisorRescan",
4529        )?])
4530    }
4531
4532    async fn handle_supervisor_release_reserved(
4533        &self,
4534        frame: Frame,
4535        module_id: String,
4536    ) -> Result<Vec<Frame>, RouterError> {
4537        let Some(context) = self.rescan.clone() else {
4538            return Ok(vec![control_error_frame(
4539                &frame,
4540                "release_unavailable",
4541                "reserved-id release requires a daemon started with a reloadable config path",
4542            )?]);
4543        };
4544        let operation_lock = self.supervisor.operation_lock();
4545        let _operation_guard = operation_lock.lock().await;
4546        let loaded = match crate::daemon_config::load(&context.config_path) {
4547            Ok(Some(config)) => config,
4548            Ok(None) => {
4549                return Ok(vec![control_error_frame(
4550                    &frame,
4551                    "invalid_daemon_config",
4552                    format!(
4553                        "daemon config not found at {}; refusing to release reserved module_id '{module_id}'",
4554                        context.config_path.display()
4555                    ),
4556                )?])
4557            }
4558            Err(err) => {
4559                return Ok(vec![control_error_frame(
4560                    &frame,
4561                    "invalid_daemon_config",
4562                    format!("unable to verify reserved-id release against daemon config: {err}"),
4563                )?])
4564            }
4565        };
4566        if loaded
4567            .modules
4568            .iter()
4569            .any(|configured| configured.module_id == module_id)
4570        {
4571            return Ok(vec![control_error_frame(
4572                &frame,
4573                "reserved_module_configured",
4574                format!(
4575                    "module_id '{module_id}' remains configured; remove its config entry and rescan before releasing its reserved id"
4576                ),
4577            )?]);
4578        }
4579        if !self.supervisor.release_retained_reserved_gate(&module_id) {
4580            return Ok(vec![control_error_frame(
4581                &frame,
4582                "reserved_gate_not_retained",
4583                format!(
4584                    "module_id '{module_id}' has no retired reserved-id gate to release; rescan its removed reserved configuration first"
4585                ),
4586            )?]);
4587        }
4588
4589        let response = ClientControlResponse::SupervisorAck {
4590            module_id,
4591            applied: true,
4592        };
4593        Ok(vec![control_response_body_frame(
4594            &frame,
4595            &response,
4596            "ClientControlResponse::SupervisorAck",
4597        )?])
4598    }
4599
4600    /// Reconcile the running module set against the configured one.
4601    ///
4602    /// With `preview` set, the diff is computed and returned WITHOUT applying any
4603    /// of it: nothing is retired, reconfigured, enabled or spawned. The preview
4604    /// deliberately shares this function with the executing path rather than
4605    /// computing the same diff somewhere else -- two implementations of one
4606    /// decision agree until they do not, and the whole value of a preview is that
4607    /// it describes the operation that will actually run.
4608    async fn reconcile_supervised_modules(
4609        &self,
4610        supervisor: &Supervisor,
4611        configured_modules: Vec<crate::daemon_config::ConfiguredModule>,
4612        preview: bool,
4613    ) -> Result<SupervisorRescanResult, String> {
4614        let mut current = BTreeMap::new();
4615        for module in self.supervisor.list() {
4616            let (spec, health) = module.configuration().map_err(|err| {
4617                format!(
4618                    "failed to read configuration for module_id '{}': {err}",
4619                    module.module_id()
4620                )
4621            })?;
4622            let enabled = module
4623                .status()
4624                .map_err(|err| {
4625                    format!(
4626                        "failed to read status for module_id '{}': {err}",
4627                        module.module_id()
4628                    )
4629                })?
4630                .enabled;
4631            current.insert(
4632                module.module_id().to_string(),
4633                (module, spec, health, enabled),
4634            );
4635        }
4636        let configured = configured_modules
4637            .into_iter()
4638            .map(|module| (module.module_id.clone(), module))
4639            .collect::<BTreeMap<_, _>>();
4640
4641        let added = configured
4642            .keys()
4643            .filter(|module_id| !current.contains_key(*module_id))
4644            .cloned()
4645            .collect::<Vec<_>>();
4646        let removed = current
4647            .keys()
4648            .filter(|module_id| !configured.contains_key(*module_id))
4649            .cloned()
4650            .collect::<Vec<_>>();
4651        let mut changed_pending_reload = Vec::new();
4652        let mut configuration_changes = BTreeSet::new();
4653        let mut enabled_changes = BTreeSet::new();
4654        let mut unchanged = 0_u32;
4655
4656        for (module_id, configured_module) in &configured {
4657            let Some((_, current_spec, current_health, current_enabled)) = current.get(module_id)
4658            else {
4659                continue;
4660            };
4661            // Compare the whole launch spec so a future launch field cannot
4662            // accidentally become a live-only policy change. Health is stored
4663            // separately and applies live without replacing the process.
4664            let launch_changed = *current_spec != configured_module.module_spec();
4665            let configuration_changed =
4666                launch_changed || *current_health != configured_module.health;
4667            let enabled_changed = *current_enabled != configured_module.enabled;
4668            if configuration_changed {
4669                configuration_changes.insert(module_id.clone());
4670            }
4671            if launch_changed {
4672                changed_pending_reload.push(module_id.clone());
4673            }
4674            if enabled_changed {
4675                enabled_changes.insert(module_id.clone());
4676            }
4677            if !configuration_changed && !enabled_changed {
4678                unchanged = unchanged.saturating_add(1);
4679            }
4680        }
4681
4682        // Everything above this point is pure computation over two snapshots.
4683        // Everything below MUTATES. The preview returns here so the boundary is a
4684        // single early return rather than a condition repeated at each mutation
4685        // site, where one missed guard would apply part of a change the caller was
4686        // told would not happen.
4687        if preview {
4688            return Ok(SupervisorRescanResult {
4689                added,
4690                removed,
4691                changed_pending_reload,
4692                enabled_changes: enabled_changes.iter().cloned().collect(),
4693                unchanged,
4694                preview: true,
4695                // Filled by the caller on both paths, so the preview reports
4696                // restart-required sections identically to an executed rescan --
4697                // the preview is where an operator is most likely to be looking.
4698                restart_required: Vec::new(),
4699                capability_warnings: Vec::new(),
4700            });
4701        }
4702
4703        for module_id in &removed {
4704            let module = &current
4705                .get(module_id)
4706                .expect("removed module came from current supervisor state")
4707                .0;
4708            module.retire().await.map_err(|err| {
4709                format!("failed to retire module_id '{module_id}' during rescan: {err}")
4710            })?;
4711            // TOMBSTONE BEFORE RETIRE, and the order is the whole fix.
4712            //
4713            // `handle_route_open` resolves an absent module in three steps:
4714            // registry, then supervisor status, then tombstone. Retiring first
4715            // opens a window where ALL THREE ARE ABSENT -- the registry entry
4716            // went with the teardown above, the supervisor entry went with
4717            // `retire`, and the tombstone does not exist yet -- so a route.open
4718            // landing in it gets `unknown_module` (RETRYABLE, "never heard of
4719            // it") for a module that was deliberately removed and whose caller
4720            // should get `module_removed` (TERMINAL, carrying a removal age).
4721            //
4722            // Writing the tombstone first closes it: during the window the
4723            // supervisor entry still answers, so the caller gets
4724            // `target_unavailable` -- retryable, and TRUE, because the module
4725            // is mid-teardown. After both statements it is `module_removed`.
4726            // No instant remains where a removed module reads as one that
4727            // never existed.
4728            //
4729            // NOT DETERMINISTICALLY TESTABLE FROM HERE, said plainly because
4730            // the absence of a test beside a fix invites deletion: these are
4731            // two sync statements with no await between them, so reaching the
4732            // window needs a second worker thread to land exactly between them
4733            // and there is no hook to force it. MEASURED: the 25 daemon_config
4734            // tests pass identically with the old order and the new one, so
4735            // the existing suite cannot see this and a green run is not
4736            // evidence either way. What the suite does hold is the
4737            // post-condition -- a removed module answers `module_removed` --
4738            // which this preserves.
4739            //
4740            // Found by an Athena panel reading the shipped tree against a
4741            // design note (2026-09-19), as the one concrete instance of that
4742            // note's class that survived contact with source. Direction is
4743            // benign: retryable where terminal was intended, never the reverse.
4744            self.supervisor.record_rescan_removal(module_id);
4745            self.supervisor.retire(module_id);
4746            self.route_outages.forget(module_id);
4747        }
4748
4749        for module_id in configured.keys() {
4750            let Some((module, _, _, _)) = current.get(module_id) else {
4751                continue;
4752            };
4753            let configured_module = configured
4754                .get(module_id)
4755                .expect("configured module id came from configured map");
4756            if configuration_changes.contains(module_id) {
4757                module
4758                    .update_configuration(
4759                        configured_module.module_spec(),
4760                        configured_module.health.clone(),
4761                        configured_module.drain_timeout_ms,
4762                    )
4763                    .await
4764                    .map_err(|err| {
4765                        format!(
4766                            "failed to update module_id '{module_id}' configuration during rescan: {err}"
4767                        )
4768                    })?;
4769            }
4770            if enabled_changes.contains(module_id) {
4771                // A rescan that starts or stops a module applies an operator's
4772                // edit to the config, so the resulting outage was asked for.
4773                self.route_outages.mark_operator_action(module_id);
4774                module
4775                    .set_enabled(configured_module.enabled)
4776                    .await
4777                    .map_err(|err| {
4778                        self.route_outages.operator_action_ended_unrefused(module_id);
4779                        format!(
4780                            "failed to apply module_id '{module_id}' enabled={} during rescan: {err}",
4781                            configured_module.enabled
4782                        )
4783                    })?;
4784            }
4785        }
4786
4787        for module_id in &added {
4788            let configured_module = configured
4789                .get(module_id)
4790                .expect("added module id came from configured map");
4791            supervisor
4792                .supervise_configured_with_health(
4793                    configured_module.module_spec(),
4794                    configured_module.enabled,
4795                    configured_module.health.clone(),
4796                    configured_module.drain_timeout_ms,
4797                    configured_module.restart,
4798                )
4799                .map_err(|err| {
4800                    format!("failed to add module_id '{module_id}' during rescan: {err}")
4801                })?;
4802        }
4803
4804        Ok(SupervisorRescanResult {
4805            added,
4806            removed,
4807            changed_pending_reload,
4808            enabled_changes: enabled_changes.iter().cloned().collect(),
4809            unchanged,
4810            preview: false,
4811            // Filled by the caller, which is the only layer that can see the
4812            // previous config to diff against.
4813            restart_required: Vec::new(),
4814            capability_warnings: Vec::new(),
4815        })
4816    }
4817
4818    async fn handle_supervisor_set_enabled(
4819        &self,
4820        frame: Frame,
4821        module_id: String,
4822        enabled: bool,
4823    ) -> Result<Vec<Frame>, RouterError> {
4824        let operation_lock = self.supervisor.operation_lock();
4825        let _operation_guard = operation_lock.lock().await;
4826        let Some(module) = self.supervisor.get(&module_id) else {
4827            return Ok(vec![control_error_frame(
4828                &frame,
4829                "unknown_module",
4830                format!("module_id '{module_id}' is not supervised"),
4831            )?]);
4832        };
4833
4834        // Enabling counts as well as disabling: a module an operator starts
4835        // is refused until it registers, and that wait was asked for.
4836        self.route_outages.mark_operator_action(&module_id);
4837        let applied = match module.set_enabled(enabled).await {
4838            Ok(applied) => applied,
4839            Err(err) => {
4840                self.route_outages
4841                    .operator_action_ended_unrefused(&module_id);
4842                return Ok(vec![control_error_frame(
4843                    &frame,
4844                    "target_unavailable",
4845                    format!("failed to set module_id '{module_id}' enabled={enabled}: {err}"),
4846                )?]);
4847            }
4848        };
4849        if !applied {
4850            // Already in the requested state: nothing was made unavailable,
4851            // so the mark must not outlive this request.
4852            self.route_outages
4853                .operator_action_ended_unrefused(&module_id);
4854        }
4855
4856        self.capability_evaluator.wake_deadline_loop();
4857        self.refresh_capability_requirements();
4858        let response = ClientControlResponse::SupervisorAck { module_id, applied };
4859        Ok(vec![control_response_body_frame(
4860            &frame,
4861            &response,
4862            "ClientControlResponse::SupervisorAck",
4863        )?])
4864    }
4865
4866    async fn handle_supervisor_health_probe(
4867        &self,
4868        frame: Frame,
4869        module_id: String,
4870    ) -> Result<Vec<Frame>, RouterError> {
4871        self.refresh_capability_requirements();
4872        let Some(registration) = self
4873            .registry
4874            .get_module(&module_id)
4875            .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?
4876        else {
4877            return Ok(vec![control_error_frame(
4878                &frame,
4879                "unknown_module",
4880                format!("module_id '{module_id}' is not registered"),
4881            )?]);
4882        };
4883
4884        // This guard's ACCEPT direction is fenced, but only INCIDENTALLY: no test is
4885        // named for it. Making `module_registration_grants_op` return false
4886        // unconditionally reddens five tests, and every one is named for something
4887        // else -- capability relay, probe/bind demultiplexing, supervision-only
4888        // probing. They exercise a successful advertisement check on the way to their
4889        // own subject.
4890        //
4891        // Real protection, fragile in a specific way: narrowing any of those tests to
4892        // focus on its stated subject would silently remove coverage nobody knows
4893        // they are carrying. Recorded here rather than as a sixth test, because the
4894        // useful fact is WHICH tests hold the guard up -- a new test would add
4895        // coverage without telling the next person what the existing ones quietly do.
4896        if !module_registration_grants_op(&registration.control_ops, MODULE_CONTROL_OP_HEALTH_CHECK)
4897        {
4898            return Ok(vec![control_error_frame(
4899                &frame,
4900                "health_not_advertised",
4901                format!("module_id '{module_id}' did not advertise health.check"),
4902            )?]);
4903        }
4904
4905        let deadline = Instant::now() + self.health_probe_timeout;
4906        let pending = match self.forwarding.begin_module_control_rpc_for(
4907            &module_id,
4908            MODULE_CONTROL_OP_HEALTH_CHECK,
4909            deadline,
4910        ) {
4911            Ok(pending) => pending,
4912            Err(err) => {
4913                return Ok(vec![control_error_frame(
4914                    &frame,
4915                    forwarding_error_code(&err),
4916                    err.to_string(),
4917                )?])
4918            }
4919        };
4920
4921        let PendingModuleControlRpc {
4922            endpoint,
4923            module_sink,
4924            negotiated_ver,
4925            corr: probe_corr,
4926            receiver,
4927        } = pending;
4928        let mut guard =
4929            ModuleControlRpcGuard::new(Arc::clone(&self.forwarding), endpoint, probe_corr);
4930        let probe_body =
4931            serde_json::to_vec(&ModuleControlRequest::HealthCheck {}).map_err(|err| {
4932                RouterError::backend(
4933                    0,
4934                    frame.header.corr,
4935                    format!("failed to encode health.check request: {err}"),
4936                )
4937            })?;
4938        let probe_frame = Frame::build_with_version(
4939            negotiated_ver,
4940            FrameType::Request,
4941            control_flags(),
4942            0,
4943            0,
4944            probe_corr,
4945            probe_body,
4946        )
4947        .map_err(RouterError::FrameBuild)?;
4948
4949        if let Err(err) = module_sink.send(probe_frame).await {
4950            return Ok(vec![control_error_frame(
4951                &frame,
4952                "target_unavailable",
4953                err.to_string(),
4954            )?]);
4955        }
4956
4957        match timeout_at(deadline, receiver).await {
4958            Ok(Ok(ModuleControlRpcOutcome::Response(response))) => {
4959                guard.disarm();
4960                let Some(report) = response.health_report() else {
4961                    return Ok(vec![control_error_frame(
4962                        &frame,
4963                        "invalid_control_body",
4964                        "health.check RPC returned a non-health response",
4965                    )?]);
4966                };
4967                // Metrics go out whole here. The supervisor's cached snapshot
4968                // caps this blob (see truncate_health_metrics), and this path
4969                // exists precisely to answer without that cap -- so applying it
4970                // here would leave no way to see what the cached view drops.
4971                let HealthReport {
4972                    status,
4973                    detail,
4974                    metrics,
4975                } = report;
4976                let capability_detail = self
4977                    .capability_evaluator
4978                    .required_problem_detail(&module_id);
4979                let response = ClientControlResponse::SupervisorHealthProbe {
4980                    module_id,
4981                    status,
4982                    detail: append_capability_problem_detail(detail, capability_detail),
4983                    metrics,
4984                };
4985                Ok(vec![control_response_body_frame(
4986                    &frame,
4987                    &response,
4988                    "ClientControlResponse::SupervisorHealthProbe",
4989                )?])
4990            }
4991            Ok(Ok(ModuleControlRpcOutcome::Rejected(body))) => {
4992                guard.disarm();
4993                Ok(vec![control_error_body_frame(&frame, body)?])
4994            }
4995            Ok(Ok(ModuleControlRpcOutcome::ModuleGone(message))) => {
4996                guard.disarm();
4997                Ok(vec![control_error_frame(
4998                    &frame,
4999                    "target_unavailable",
5000                    message,
5001                )?])
5002            }
5003            Ok(Ok(ModuleControlRpcOutcome::MalformedResponse(message))) => {
5004                guard.disarm();
5005                Ok(vec![control_error_frame(
5006                    &frame,
5007                    "invalid_control_body",
5008                    message,
5009                )?])
5010            }
5011            Ok(Ok(ModuleControlRpcOutcome::UnexpectedOp { expected, actual })) => {
5012                guard.disarm();
5013                Ok(vec![control_error_frame(
5014                    &frame,
5015                    "invalid_control_body",
5016                    format!("expected module-control op '{expected}', got '{actual}'"),
5017                )?])
5018            }
5019            Ok(Ok(ModuleControlRpcOutcome::DeadlineElapsed)) => {
5020                guard.disarm();
5021                Ok(vec![control_error_frame(
5022                    &frame,
5023                    "module_timeout",
5024                    format!(
5025                        "module_id '{module_id}' answered health.check after {:?}",
5026                        self.health_probe_timeout
5027                    ),
5028                )?])
5029            }
5030            Ok(Err(_)) => Ok(vec![control_error_frame(
5031                &frame,
5032                "target_unavailable",
5033                "health.check waiter was canceled before the module responded",
5034            )?]),
5035            Err(_) => Ok(vec![control_error_frame(
5036                &frame,
5037                "module_timeout",
5038                format!(
5039                    "module_id '{module_id}' did not answer health.check within {:?}",
5040                    self.health_probe_timeout
5041                ),
5042            )?]),
5043        }
5044    }
5045
5046    fn supervisor_status(
5047        &self,
5048        module_id: &str,
5049        corr: u64,
5050    ) -> Result<Option<(crate::supervise::ModuleStatus, bool)>, RouterError> {
5051        self.supervisor
5052            .get(module_id)
5053            .map(|module| {
5054                let warming = module.is_warming_for_control("status").map_err(|err| {
5055                    RouterError::backend(
5056                        0,
5057                        corr,
5058                        format!(
5059                            "failed to read supervisor warming state for module_id '{module_id}': {err}"
5060                        ),
5061                    )
5062                })?;
5063                module.status_for_control("status").map_err(|err| {
5064                    RouterError::backend(
5065                        0,
5066                        corr,
5067                        format!(
5068                            "failed to read supervisor status for module_id '{module_id}': {err}"
5069                        ),
5070                    )
5071                }).map(|status| (status, warming))
5072            })
5073            .transpose()
5074    }
5075
5076    fn guard_module_control_op(
5077        &self,
5078        frame: &Frame,
5079        module_id: &str,
5080        op: &str,
5081    ) -> Result<Option<Frame>, RouterError> {
5082        if self.module_grants_op(module_id, op, frame.header.corr)? {
5083            return Ok(None);
5084        }
5085
5086        Ok(Some(control_error_frame(
5087            frame,
5088            "op_not_allowed",
5089            format!("module_id '{module_id}' did not grant control op '{op}'"),
5090        )?))
5091    }
5092
5093    fn module_grants_op(&self, module_id: &str, op: &str, corr: u64) -> Result<bool, RouterError> {
5094        let Some(registration) = self
5095            .registry
5096            .get_module(module_id)
5097            .map_err(|err| RouterError::backend(0, corr, err.to_string()))?
5098        else {
5099            return Ok(false);
5100        };
5101        Ok(module_registration_grants_op(&registration.control_ops, op))
5102    }
5103
5104    fn handle_status_update(
5105        &self,
5106        endpoint: ModuleEndpointId,
5107        frame: Frame,
5108    ) -> Result<Vec<Frame>, RouterError> {
5109        let update = match serde_json::from_slice::<ModuleControlPush>(&frame.body) {
5110            Ok(update) => update,
5111            Err(err) => {
5112                // Forward-compat: a newer module may push a channel-0 op this subc
5113                // version doesn't know. The control contract says unknown push ops
5114                // are IGNORED, never answered with an error. Only a malformed body
5115                // for an op we DO know is a real error worth surfacing.
5116                if is_known_module_push_op(&frame.body) {
5117                    return Ok(vec![control_error_frame(
5118                        &frame,
5119                        "invalid_control_body",
5120                        format!("malformed module control push body: {err}"),
5121                    )?]);
5122                }
5123                return Ok(Vec::new());
5124            }
5125        };
5126
5127        match update {
5128            ModuleControlPush::RouteStatus {
5129                route_channel,
5130                route_epoch,
5131                status,
5132            } => {
5133                self.forwarding
5134                    .cache_status(endpoint, route_channel, route_epoch, status)
5135                    .map_err(RouterError::Forwarding)?;
5136            }
5137        }
5138        Ok(Vec::new())
5139    }
5140
5141    fn handle_route_poll(
5142        &self,
5143        ctx: &RouteCtx,
5144        frame: Frame,
5145        route_channel: u16,
5146        route_epoch: u32,
5147        kind: PollKind,
5148    ) -> Result<Vec<Frame>, RouterError> {
5149        let snapshot = self
5150            .forwarding
5151            .route_poll_snapshot(ctx.connection_id, route_channel, route_epoch)
5152            .map_err(RouterError::Forwarding)?;
5153        let response = match (kind, snapshot) {
5154            (PollKind::Status, RoutePollSnapshot::Bound { status, .. }) => {
5155                ClientControlResponse::RoutePoll {
5156                    route_channel,
5157                    route_epoch,
5158                    status,
5159                    live: None,
5160                }
5161            }
5162            (PollKind::Status, RoutePollSnapshot::Absent) => ClientControlResponse::RoutePoll {
5163                route_channel,
5164                route_epoch,
5165                status: None,
5166                live: None,
5167            },
5168            (PollKind::Liveness, RoutePollSnapshot::Bound { module_id, .. }) => {
5169                // ABSENCE HERE MEANS "NOT SUPERVISED", NOT "UNKNOWN", and that
5170                // is what makes reporting `true` correct rather than a
5171                // confident guess. `process_live` returns None only when the
5172                // module id has no supervisor snapshot at all -- an
5173                // externally-started module the daemon did not spawn -- and
5174                // for those the supervisor has no opinion to offer, ever. It
5175                // is never None for a supervised module in an unknown state:
5176                // a supervised module always has a snapshot, and the answer
5177                // comes from `state == Running && process_alive`.
5178                //
5179                // The route is Bound, so the module completed a HELLO on a
5180                // live connection; "the process this route points at is
5181                // running" is therefore attested by the binding rather than
5182                // assumed. Reporting `false` for an unsupervised module would
5183                // be the actual lie -- it would tell a client its healthy
5184                // route is dead because the daemon does not manage the
5185                // process.
5186                //
5187                // IF `process_live` EVER GAINS A THIRD CASE -- a supervised
5188                // module whose liveness is genuinely unknown, e.g. a snapshot
5189                // that has not been populated yet -- THIS DEFAULT BECOMES
5190                // WRONG and must split: unsupervised stays true, unknown
5191                // becomes null so the client can tell the two apart. The
5192                // response field is already `Option<bool>`, so the wire can
5193                // carry that distinction today.
5194                let live = self
5195                    .process_liveness
5196                    .as_ref()
5197                    .and_then(|source| source.process_live(&module_id))
5198                    .unwrap_or(true);
5199                ClientControlResponse::RoutePoll {
5200                    route_channel,
5201                    route_epoch,
5202                    status: None,
5203                    live: Some(live),
5204                }
5205            }
5206            (PollKind::Liveness, RoutePollSnapshot::Absent) => ClientControlResponse::RoutePoll {
5207                route_channel,
5208                route_epoch,
5209                status: None,
5210                live: Some(false),
5211            },
5212        };
5213
5214        Ok(vec![control_response_body_frame(
5215            &frame,
5216            &response,
5217            "ClientControlResponse::RoutePoll",
5218        )?])
5219    }
5220
5221    pub(crate) fn observe_module_control_completion(
5222        &self,
5223        completion: ModuleControlRpcCompletion,
5224    ) -> bool {
5225        match completion {
5226            ModuleControlRpcCompletion::Unknown => false,
5227            ModuleControlRpcCompletion::Settled => true,
5228            ModuleControlRpcCompletion::LateHealthAnswer { module_id, latency } => {
5229                let latency_ms = latency.as_millis().min(u128::from(u64::MAX)) as u64;
5230                info!(
5231                    module_id = %module_id,
5232                    latency_ms,
5233                    "late health.check answer proves the module is alive"
5234                );
5235                match self
5236                    .supervisor
5237                    .record_late_health_answer(&module_id, latency_ms)
5238                {
5239                    Ok(true) => {}
5240                    Ok(false) => debug!(
5241                        module_id = %module_id,
5242                        latency_ms,
5243                        "late health.check answer has no active supervisor snapshot"
5244                    ),
5245                    Err(err) => warn!(
5246                        module_id = %module_id,
5247                        latency_ms,
5248                        error = %err,
5249                        "failed to record late health.check answer"
5250                    ),
5251                }
5252                true
5253            }
5254        }
5255    }
5256
5257    /// Decide whether a failure while settling a relayed `route.bind` belongs to
5258    /// the module connection whose frame is being handled, or to the client that
5259    /// relay was opened for.
5260    ///
5261    /// This runs on the MODULE connection's frame handler, where returning `Err`
5262    /// ends that connection -- and a module connection carries every client's
5263    /// routes to that module, so ending it costs the whole fleet its tools.
5264    /// `ConnectionClosing` carries the id of the connection that is closing, and
5265    /// when that id is a CLIENT's, the condition is entirely about that one
5266    /// client's route.open. A client-scoped condition has no authority over a
5267    /// shared module connection, so it is logged and the single relay is dropped:
5268    /// the client is going away, and `complete_pending_relay` already removed the
5269    /// relay before failing, so there is nothing left to settle. Anything that
5270    /// relay still reserved is released by that client's own connection teardown,
5271    /// which is already under way -- that is what "closing" means.
5272    ///
5273    /// Every other failure is a statement about THIS connection and stays fatal:
5274    /// a poisoned forwarding lock, a stale module endpoint, and the module's own
5275    /// id in `ConnectionClosing` all mean this connection cannot keep serving
5276    /// frames correctly.
5277    fn refuse_to_end_module_connection_for_a_client(
5278        &self,
5279        module_connection_id: ConnectionId,
5280        corr: u64,
5281        err: ForwardingError,
5282    ) -> Result<(), RouterError> {
5283        if let ForwardingError::ConnectionClosing { connection_id } = err {
5284            if connection_id != module_connection_id {
5285                warn!(
5286                    module_connection_id = module_connection_id.get(),
5287                    client_connection_id = connection_id.get(),
5288                    corr,
5289                    "dropping a route.bind response for a closing client; the module connection keeps serving"
5290                );
5291                return Ok(());
5292            }
5293        }
5294        Err(RouterError::Forwarding(err))
5295    }
5296
5297    fn handle_module_relay_response(
5298        &self,
5299        connection_id: ConnectionId,
5300        frame: Frame,
5301    ) -> Result<Vec<Frame>, RouterError> {
5302        let mut secondary_error = None;
5303        let outcome = match frame.header.ty {
5304            FrameType::Response => match serde_json::from_slice::<ControlOpProbe>(&frame.body) {
5305                Ok(probe) if probe.op == "route.bind" => {
5306                    match serde_json::from_slice::<ModuleControlResponse>(&frame.body) {
5307                        Ok(ModuleControlResponse::RouteBindAck {}) => {
5308                            RouteBindRelayOutcome::Accepted
5309                        }
5310                        Ok(other) => {
5311                            let message =
5312                                format!("route.bind response carried unexpected body: {other:?}");
5313                            secondary_error = Some(control_error_frame(
5314                                &frame,
5315                                "invalid_control_body",
5316                                message.clone(),
5317                            )?);
5318                            RouteBindRelayOutcome::ModuleGone(message)
5319                        }
5320                        Err(err) => {
5321                            let message = format!("malformed route.bind response body: {err}");
5322                            secondary_error = Some(control_error_frame(
5323                                &frame,
5324                                "invalid_control_body",
5325                                message.clone(),
5326                            )?);
5327                            RouteBindRelayOutcome::ModuleGone(message)
5328                        }
5329                    }
5330                }
5331                Ok(probe) => {
5332                    let outcome = match serde_json::from_slice::<ModuleControlResponse>(&frame.body)
5333                    {
5334                        Ok(response) => ModuleControlRpcOutcome::Response(response),
5335                        Err(err) => ModuleControlRpcOutcome::MalformedResponse(format!(
5336                            "malformed {} response body: {err}",
5337                            probe.op
5338                        )),
5339                    };
5340                    let completion = self
5341                        .forwarding
5342                        .complete_module_control_rpc(
5343                            connection_id,
5344                            frame.header.corr,
5345                            Some(&probe.op),
5346                            outcome,
5347                        )
5348                        .map_err(RouterError::Forwarding)?;
5349                    if !self.observe_module_control_completion(completion) {
5350                        debug!(
5351                            connection_id = connection_id.get(),
5352                            corr = frame.header.corr,
5353                            op = %probe.op,
5354                            "dropping late or unknown module-control RPC response"
5355                        );
5356                    }
5357                    return Ok(Vec::new());
5358                }
5359                Err(err) => {
5360                    if let Some(expected_op) = self
5361                        .forwarding
5362                        .pending_module_control_op(connection_id, frame.header.corr)
5363                        .map_err(RouterError::Forwarding)?
5364                    {
5365                        let completion = self
5366                            .forwarding
5367                            .complete_module_control_rpc(
5368                                connection_id,
5369                                frame.header.corr,
5370                                None,
5371                                ModuleControlRpcOutcome::MalformedResponse(format!(
5372                                    "malformed {expected_op} response body: {err}"
5373                                )),
5374                            )
5375                            .map_err(RouterError::Forwarding)?;
5376                        if !self.observe_module_control_completion(completion) {
5377                            debug!(
5378                                connection_id = connection_id.get(),
5379                                corr = frame.header.corr,
5380                                "dropping late malformed module-control RPC response"
5381                            );
5382                        }
5383                        return Ok(Vec::new());
5384                    }
5385                    let message = format!("malformed route.bind response body: {err}");
5386                    secondary_error = Some(control_error_frame(
5387                        &frame,
5388                        "invalid_control_body",
5389                        message.clone(),
5390                    )?);
5391                    RouteBindRelayOutcome::ModuleGone(message)
5392                }
5393            },
5394            FrameType::Error => {
5395                if self
5396                    .forwarding
5397                    .pending_module_control_op(connection_id, frame.header.corr)
5398                    .map_err(RouterError::Forwarding)?
5399                    .is_some()
5400                {
5401                    let outcome = match serde_json::from_slice::<ErrorBody>(&frame.body) {
5402                        Ok(body) => ModuleControlRpcOutcome::Rejected(body),
5403                        Err(err) => ModuleControlRpcOutcome::MalformedResponse(format!(
5404                            "malformed module-control ERROR body: {err}"
5405                        )),
5406                    };
5407                    let completion = self
5408                        .forwarding
5409                        .complete_module_control_rpc(
5410                            connection_id,
5411                            frame.header.corr,
5412                            None,
5413                            outcome,
5414                        )
5415                        .map_err(RouterError::Forwarding)?;
5416                    if !self.observe_module_control_completion(completion) {
5417                        debug!(
5418                            connection_id = connection_id.get(),
5419                            corr = frame.header.corr,
5420                            "dropping late or unknown module-control RPC error"
5421                        );
5422                    }
5423                    return Ok(Vec::new());
5424                }
5425                match serde_json::from_slice::<ErrorBody>(&frame.body) {
5426                    Ok(body) => RouteBindRelayOutcome::Rejected(body),
5427                    Err(err) => {
5428                        let message = format!("malformed route.bind ERROR body: {err}");
5429                        secondary_error = Some(control_error_frame(
5430                            &frame,
5431                            "invalid_control_body",
5432                            message.clone(),
5433                        )?);
5434                        RouteBindRelayOutcome::ModuleGone(message)
5435                    }
5436                }
5437            }
5438            ty => {
5439                return Ok(vec![control_error_frame(
5440                    &frame,
5441                    "unsupported_control_frame",
5442                    format!("unsupported module channel-0 frame {ty:?}"),
5443                )?])
5444            }
5445        };
5446
5447        let settled =
5448            self.forwarding
5449                .complete_pending_relay(connection_id, frame.header.corr, outcome);
5450        let completion = match settled {
5451            Ok(completion) => completion,
5452            Err(err) => {
5453                self.refuse_to_end_module_connection_for_a_client(
5454                    connection_id,
5455                    frame.header.corr,
5456                    err,
5457                )?;
5458                return Ok(secondary_error.into_iter().collect());
5459            }
5460        };
5461        if let Some(target) = completion.abandoned.as_ref() {
5462            send_goodbye_target_best_effort(&self.counters, target, "late accepted route.bind");
5463        }
5464        if !completion.settled {
5465            debug!(
5466                connection_id = connection_id.get(),
5467                corr = frame.header.corr,
5468                frame_type = ?frame.header.ty,
5469                "dropping late or unknown route.bind relay response"
5470            );
5471        }
5472        Ok(secondary_error.into_iter().collect())
5473    }
5474
5475    fn handle_goodbye(&self, connection_id: ConnectionId) -> Result<Vec<Frame>, RouterError> {
5476        debug!(connection_id = connection_id.get(), "handling GOODBYE");
5477        // GOODBYE ends the connection's logical session even when its socket
5478        // stays open. Use disconnect teardown so verdicts, client notices and
5479        // scope authority are released at the same lifecycle boundary.
5480        self.cleanup_connection(connection_id)
5481            .map_err(|err| RouterError::backend(0, 0, err.to_string()))?;
5482        Ok(Vec::new())
5483    }
5484}
5485
5486impl Default for ControlHandler {
5487    fn default() -> Self {
5488        Self::new(Arc::new(Registry::default()))
5489    }
5490}
5491
5492impl crate::supervise::SwapPromotionObserver for ControlHandler {
5493    fn swap_promoted(&self, registration: &crate::registry::ModuleRegistration) {
5494        self.apply_registration_capabilities(registration);
5495    }
5496}
5497
5498fn capability_requirement_status(status: RequirementStatus) -> CapabilityRequirementStatus {
5499    CapabilityRequirementStatus {
5500        consumer: status.consumer,
5501        capability: status.capability,
5502        need: match status.need {
5503            subc_protocol::manifest::CapabilityNeed::Required => "required".to_string(),
5504            subc_protocol::manifest::CapabilityNeed::Optional => "optional".to_string(),
5505        },
5506        verdict: status.verdict.as_str().to_string(),
5507        episode_seq: status.episode_seq,
5508        config_satisfiable: status.config_satisfiable,
5509        runtime_available: status.runtime_available,
5510        detail: status.detail,
5511    }
5512}
5513
5514fn append_capability_problem_detail(
5515    detail: Option<String>,
5516    capability_detail: Option<String>,
5517) -> Option<String> {
5518    match (detail, capability_detail) {
5519        (Some(detail), Some(capability_detail)) => Some(format!("{detail}; {capability_detail}")),
5520        (Some(detail), None) => Some(detail),
5521        (None, Some(capability_detail)) => Some(capability_detail),
5522        (None, None) => None,
5523    }
5524}
5525
5526fn subc_ops() -> Vec<String> {
5527    SUBC_CONTROL_OPS
5528        .iter()
5529        .map(|op| (*op).to_string())
5530        .collect()
5531}
5532
5533fn module_subc_ops() -> Vec<String> {
5534    SUBC_CONTROL_OPS
5535        .iter()
5536        .chain(MODULE_TO_SUBC_CONTROL_OPS.iter())
5537        .map(|op| (*op).to_string())
5538        .collect()
5539}
5540
5541#[cfg(test)]
5542fn module_baseline_control_ops() -> Vec<String> {
5543    MODULE_BASELINE_CONTROL_OPS
5544        .iter()
5545        .map(|op| (*op).to_string())
5546        .collect()
5547}
5548
5549fn effective_module_control_ops(declared: Option<Vec<String>>) -> Vec<String> {
5550    let mut seen = HashSet::new();
5551    let mut effective = Vec::new();
5552    for op in MODULE_BASELINE_CONTROL_OPS {
5553        if seen.insert((*op).to_string()) {
5554            effective.push((*op).to_string());
5555        }
5556    }
5557    for op in declared.unwrap_or_default() {
5558        if seen.insert(op.clone()) {
5559            effective.push(op);
5560        }
5561    }
5562    effective
5563}
5564
5565fn module_registration_grants_op(control_ops: &[String], op: &str) -> bool {
5566    MODULE_BASELINE_CONTROL_OPS.contains(&op) || control_ops.iter().any(|granted| granted == op)
5567}
5568
5569fn target_module_id(target: &RouteTarget) -> &str {
5570    match target {
5571        RouteTarget::ToolProvider { module_id }
5572        | RouteTarget::ManagementSurface { module_id }
5573        | RouteTarget::InternalService { module_id, .. } => module_id,
5574    }
5575}
5576
5577fn target_has_required_role(target: &RouteTarget, roles: &[ProviderRole]) -> bool {
5578    roles.iter().any(|role| match (target, role) {
5579        (RouteTarget::ToolProvider { .. }, ProviderRole::ToolProvider { .. }) => true,
5580        (RouteTarget::ManagementSurface { .. }, ProviderRole::ManagementSurface { .. }) => true,
5581        (
5582            RouteTarget::InternalService { service_id, .. },
5583            ProviderRole::InternalService {
5584                service_id: provided,
5585                ..
5586            },
5587        ) => service_id == provided,
5588        _ => false,
5589    })
5590}
5591
5592fn is_routable_role(role: &ProviderRole) -> bool {
5593    matches!(
5594        role,
5595        ProviderRole::ToolProvider { .. }
5596            | ProviderRole::ManagementSurface { .. }
5597            | ProviderRole::InternalService { .. }
5598    )
5599}
5600
5601#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5602enum ControlRequestBodyError {
5603    UnknownOp,
5604    InvalidBody,
5605}
5606
5607#[derive(Debug, Deserialize)]
5608struct ControlOpProbe {
5609    op: String,
5610}
5611
5612/// Channel-0 push ops this subc version understands. A push whose `op` is not in
5613/// this set is treated as a forward-compat unknown and ignored rather than errored.
5614const MODULE_PUSH_OPS: &[&str] = &["route.status"];
5615
5616fn is_known_module_push_op(body: &[u8]) -> bool {
5617    serde_json::from_slice::<ControlOpProbe>(body)
5618        .map(|probe| MODULE_PUSH_OPS.contains(&probe.op.as_str()))
5619        .unwrap_or(false)
5620}
5621
5622fn is_known_module_request_op(body: &[u8]) -> bool {
5623    serde_json::from_slice::<ControlOpProbe>(body)
5624        .map(|probe| is_module_to_subc_op(&probe.op))
5625        .unwrap_or(false)
5626}
5627
5628fn is_module_to_subc_op(op: &str) -> bool {
5629    MODULE_TO_SUBC_CONTROL_OPS.contains(&op) || MODULE_TO_SUBC_UNADVERTISED_OPS.contains(&op)
5630}
5631
5632fn log_control_dispatch_arrival(op: &'static str, connection_id: ConnectionId, corr: u64) {
5633    debug!(
5634        op = %op,
5635        connection_id = connection_id.get(),
5636        corr,
5637        "control dispatch"
5638    );
5639}
5640
5641fn log_slow_control_dispatch(
5642    dispatch_started_at: Option<StdInstant>,
5643    op: &'static str,
5644    connection_id: ConnectionId,
5645    corr: u64,
5646) {
5647    let Some(dispatch_started_at) = dispatch_started_at else {
5648        return;
5649    };
5650    let elapsed = dispatch_started_at.elapsed();
5651    if elapsed >= SLOW_CONTROL_DISPATCH_THRESHOLD {
5652        warn!(
5653            op = %op,
5654            connection_id = connection_id.get(),
5655            corr,
5656            elapsed_ms = elapsed.as_millis() as u64,
5657            "slow control dispatch"
5658        );
5659    }
5660}
5661
5662fn client_control_request_op(request: &ClientControlRequest) -> &'static str {
5663    match request {
5664        ClientControlRequest::ServerDescribe {} => ops::SERVER_DESCRIBE,
5665        ClientControlRequest::SupervisorProvenance { .. } => ops::SUPERVISOR_PROVENANCE,
5666        ClientControlRequest::CatalogList { .. } => ops::CATALOG_LIST,
5667        ClientControlRequest::RouteOpen { .. } => ops::ROUTE_OPEN,
5668        ClientControlRequest::RoutePoll { .. } => ops::ROUTE_POLL,
5669        ClientControlRequest::SupervisorList {} => ops::SUPERVISOR_LIST,
5670        ClientControlRequest::SupervisorSpawnSnapshot {} => ops::SUPERVISOR_SPAWN_SNAPSHOT,
5671        ClientControlRequest::SupervisorSpawnSubscribe { .. } => ops::SUPERVISOR_SPAWN_SUBSCRIBE,
5672        ClientControlRequest::SupervisorRestart { .. } => ops::SUPERVISOR_RESTART,
5673        ClientControlRequest::SupervisorSwap { .. } => ops::SUPERVISOR_SWAP,
5674        ClientControlRequest::SupervisorReload { .. } => ops::SUPERVISOR_RELOAD,
5675        ClientControlRequest::SupervisorRescan { .. } => ops::SUPERVISOR_RESCAN,
5676        ClientControlRequest::SupervisorReleaseReserved { .. } => ops::SUPERVISOR_RELEASE_RESERVED,
5677        ClientControlRequest::SupervisorSetEnabled { .. } => ops::SUPERVISOR_SET_ENABLED,
5678        ClientControlRequest::SupervisorHealthProbe { .. } => ops::SUPERVISOR_HEALTH_PROBE,
5679        ClientControlRequest::SupervisorHealth {} => ops::SUPERVISOR_HEALTH,
5680        ClientControlRequest::SupervisorRoutes { .. } => ops::SUPERVISOR_ROUTES,
5681        ClientControlRequest::SupervisorStderrTail { .. } => ops::SUPERVISOR_STDERR_TAIL,
5682        ClientControlRequest::SupervisorTerminals { .. } => ops::SUPERVISOR_TERMINALS,
5683    }
5684}
5685
5686fn module_control_request_op(request: &ModuleControlRequestFromModule) -> &'static str {
5687    match request {
5688        ModuleControlRequestFromModule::CatalogUpdate { .. } => MODULE_TO_SUBC_OP_CATALOG_UPDATE,
5689        ModuleControlRequestFromModule::LiveRoots {} => "supervisor.live_roots",
5690        ModuleControlRequestFromModule::ScopeSync { .. } => SCOPE_SYNC_OP,
5691        ModuleControlRequestFromModule::ScopeDescribe { .. } => SCOPE_DESCRIBE_OP,
5692    }
5693}
5694
5695fn parse_client_control_request(
5696    body: &[u8],
5697) -> Result<ClientControlRequest, (serde_json::Error, ControlRequestBodyError)> {
5698    serde_json::from_slice::<ClientControlRequest>(body).map_err(|err| {
5699        let classification = match serde_json::from_slice::<ControlOpProbe>(body) {
5700            Ok(probe) if SUBC_CONTROL_OPS.contains(&probe.op.as_str()) => {
5701                ControlRequestBodyError::InvalidBody
5702            }
5703            Ok(_) => ControlRequestBodyError::UnknownOp,
5704            Err(_) => ControlRequestBodyError::InvalidBody,
5705        };
5706        (err, classification)
5707    })
5708}
5709
5710fn parse_module_control_request_from_module(
5711    body: &[u8],
5712) -> Result<ModuleControlRequestFromModule, (serde_json::Error, ControlRequestBodyError)> {
5713    serde_json::from_slice::<ModuleControlRequestFromModule>(body).map_err(|err| {
5714        let classification = match serde_json::from_slice::<ControlOpProbe>(body) {
5715            Ok(probe) if is_module_to_subc_op(&probe.op) => ControlRequestBodyError::InvalidBody,
5716            Ok(_) => ControlRequestBodyError::UnknownOp,
5717            Err(_) => ControlRequestBodyError::InvalidBody,
5718        };
5719        (err, classification)
5720    })
5721}
5722
5723#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
5724enum ProviderRoleKind {
5725    ToolProvider,
5726    PipelineStage,
5727    ManagementSurface,
5728    InternalService,
5729}
5730
5731fn provider_role_kind(role: &ProviderRole) -> ProviderRoleKind {
5732    match role {
5733        ProviderRole::ToolProvider { .. } => ProviderRoleKind::ToolProvider,
5734        ProviderRole::PipelineStage { .. } => ProviderRoleKind::PipelineStage,
5735        ProviderRole::ManagementSurface { .. } => ProviderRoleKind::ManagementSurface,
5736        ProviderRole::InternalService { .. } => ProviderRoleKind::InternalService,
5737    }
5738}
5739
5740fn provider_role_kind_set(roles: &[ProviderRole]) -> BTreeSet<ProviderRoleKind> {
5741    roles.iter().map(provider_role_kind).collect()
5742}
5743
5744/// Most refused scope records named individually in the log per sync; the
5745/// `refused` count on the accepted line is always complete.
5746const MAX_LOGGED_REFUSED_SCOPE_RECORDS: usize = 8;
5747
5748/// Per-outcome counts of one accepted `scope.sync`, for its log line.
5749#[derive(Debug, Default, PartialEq, Eq)]
5750struct ScopeOutcomeCounts {
5751    created: usize,
5752    replaced: usize,
5753    updated: usize,
5754    unchanged: usize,
5755    refused: usize,
5756}
5757
5758impl ScopeOutcomeCounts {
5759    fn of(results: &[ScopeRecordResult]) -> Self {
5760        let mut counts = Self::default();
5761        for result in results {
5762            let slot = match result.outcome {
5763                ScopeRecordOutcome::Created => &mut counts.created,
5764                ScopeRecordOutcome::Replaced => &mut counts.replaced,
5765                ScopeRecordOutcome::Updated => &mut counts.updated,
5766                ScopeRecordOutcome::Unchanged => &mut counts.unchanged,
5767                ScopeRecordOutcome::Refused => &mut counts.refused,
5768            };
5769            *slot += 1;
5770        }
5771        counts
5772    }
5773}
5774
5775#[cfg(test)]
5776mod scope_outcome_count_tests {
5777    use super::*;
5778
5779    fn result(outcome: ScopeRecordOutcome) -> ScopeRecordResult {
5780        ScopeRecordResult {
5781            scope_ref: "r".to_string(),
5782            scope_epoch: 1,
5783            outcome,
5784            code: None,
5785            message: None,
5786            version: None,
5787            parent_state: None,
5788        }
5789    }
5790
5791    /// Each outcome lands in its own count, so a refused record can never be
5792    /// hidden inside the total the log already printed.
5793    #[test]
5794    fn every_outcome_is_counted_in_its_own_field() {
5795        let results = [
5796            result(ScopeRecordOutcome::Created),
5797            result(ScopeRecordOutcome::Created),
5798            result(ScopeRecordOutcome::Replaced),
5799            result(ScopeRecordOutcome::Updated),
5800            result(ScopeRecordOutcome::Unchanged),
5801            result(ScopeRecordOutcome::Refused),
5802            result(ScopeRecordOutcome::Refused),
5803            result(ScopeRecordOutcome::Refused),
5804        ];
5805        assert_eq!(
5806            ScopeOutcomeCounts::of(&results),
5807            ScopeOutcomeCounts {
5808                created: 2,
5809                replaced: 1,
5810                updated: 1,
5811                unchanged: 1,
5812                refused: 3,
5813            }
5814        );
5815    }
5816}
5817
5818/// Return whether a catalog change can create a newly violating live route.
5819/// Removing an attested claim is intentionally excluded: it makes fewer routes
5820/// forbidden and therefore must leave the existing route census untouched.
5821fn capability_census_trigger(
5822    old: Option<&CapabilityDeclarations>,
5823    new: Option<&CapabilityDeclarations>,
5824) -> bool {
5825    let old_provides = old
5826        .map(|capabilities| capabilities.provides.iter().collect::<HashSet<_>>())
5827        .unwrap_or_default();
5828    let old_denies = old
5829        .map(|capabilities| capabilities.must_never_reach.iter().collect::<HashSet<_>>())
5830        .unwrap_or_default();
5831    let new = new.cloned().unwrap_or(CapabilityDeclarations {
5832        provides: Vec::new(),
5833        requires: Vec::new(),
5834        must_never_reach: Vec::new(),
5835    });
5836
5837    new.provides
5838        .iter()
5839        .any(|capability| !old_provides.contains(capability))
5840        || new
5841            .must_never_reach
5842            .iter()
5843            .any(|capability| !old_denies.contains(capability))
5844}
5845
5846/// Find the first capability an attested opener denies that an attested target
5847/// claims. Both manifests are live registry records, never cached or client data.
5848fn denied_capability<'a>(
5849    opening_manifest: &'a ModuleManifest,
5850    target_manifest: &ModuleManifest,
5851) -> Option<&'a str> {
5852    let opening_capabilities = opening_manifest.capabilities.as_ref()?;
5853    let target_capabilities = target_manifest.capabilities.as_ref()?;
5854    opening_capabilities
5855        .must_never_reach
5856        .iter()
5857        .find(|denied| {
5858            target_capabilities
5859                .provides
5860                .iter()
5861                .any(|provided| provided == *denied)
5862        })
5863        .map(String::as_str)
5864}
5865
5866fn catalog_update_frozen_field_message(
5867    registered: &ModuleManifest,
5868    provides: &[ProviderRole],
5869) -> Option<String> {
5870    let old_has_provides = !registered.provides.is_empty();
5871    let new_has_provides = !provides.is_empty();
5872    if old_has_provides != new_has_provides {
5873        return Some(format!(
5874            "catalog.update cannot change module '{}' between supervision-only and routable; routability is fixed at HELLO",
5875            registered.module_id
5876        ));
5877    }
5878
5879    if provider_role_kind_set(&registered.provides) != provider_role_kind_set(provides) {
5880        return Some(format!(
5881            "catalog.update cannot change provider role kinds for module '{}'; role kinds are fixed at HELLO",
5882            registered.module_id
5883        ));
5884    }
5885
5886    let registered_concurrency = manifest_concurrency(registered);
5887    let mut candidate = registered.clone();
5888    candidate.provides = provides.to_vec();
5889    let candidate_concurrency = manifest_concurrency(&candidate);
5890    if candidate_concurrency != registered_concurrency {
5891        return Some(format!(
5892            "catalog.update cannot change module '{}' concurrency from {:?} to {:?}; concurrency is fixed at HELLO",
5893            registered.module_id, registered_concurrency, candidate_concurrency
5894        ));
5895    }
5896
5897    // control_ops live beside the manifest in the HELLO body, not inside
5898    // ModuleManifest, so a provides-only catalog.update cannot change them.
5899    None
5900}
5901
5902fn manifest_provides_routable_role(manifest: &ModuleManifest) -> bool {
5903    manifest.provides.iter().any(is_routable_role)
5904}
5905
5906/// Returns the routable-provider concurrency subc should enforce for this manifest.
5907///
5908/// ToolProvider and ManagementSurface store their delivery concurrency directly.
5909/// InternalService has no role-specific concurrency field, so it retains the
5910/// existing ModuleManaged default for backward compatibility.
5911fn manifest_concurrency(manifest: &ModuleManifest) -> Concurrency {
5912    manifest
5913        .provides
5914        .iter()
5915        .find_map(|provider| match provider {
5916            ProviderRole::ToolProvider { concurrency, .. }
5917            | ProviderRole::ManagementSurface { concurrency, .. } => Some(concurrency.clone()),
5918            ProviderRole::PipelineStage { .. } | ProviderRole::InternalService { .. } => None,
5919        })
5920        .unwrap_or(Concurrency::ModuleManaged)
5921}
5922
5923/// True when the manifest carries a ManagementSurface role whose concurrency
5924/// was RESOLVED BY SERDE DEFAULT rather than declared. Reads the raw HELLO
5925/// bytes because the typed manifest deliberately erases that distinction: the
5926/// default exists for wire compatibility, and this probe exists so the default
5927/// stays observable. Any parse irregularity returns false -- the caller only
5928/// logs, and a malformed body already failed registration upstream.
5929fn manifest_concurrency_was_defaulted(raw_hello: &[u8], manifest: &ModuleManifest) -> bool {
5930    let has_management_surface = manifest
5931        .provides
5932        .iter()
5933        .any(|provider| matches!(provider, ProviderRole::ManagementSurface { .. }));
5934    if !has_management_surface {
5935        return false;
5936    }
5937    let Ok(raw) = serde_json::from_slice::<serde_json::Value>(raw_hello) else {
5938        return false;
5939    };
5940    let Some(provides) = raw
5941        .get("manifest")
5942        .and_then(|manifest| manifest.get("provides"))
5943        .and_then(serde_json::Value::as_array)
5944    else {
5945        return false;
5946    };
5947    // ProviderRole is internally tagged (`tag = "role"`), so the wire shape is
5948    // flat: {"role": "management_surface", ..., "concurrency": ...} -- verified
5949    // against the management_surface_manifest_without_concurrency golden, not
5950    // recalled (the externally-tagged guess was this function's first bug).
5951    provides.iter().any(|role| {
5952        role.get("role").and_then(serde_json::Value::as_str) == Some("management_surface")
5953            && role.get("concurrency").is_none()
5954    })
5955}
5956
5957fn negotiate_version(peer_version: u8) -> Result<u8, String> {
5958    if peer_version != PROTOCOL_VERSION {
5959        return Err(format!(
5960            "protocol_ver {peer_version} is unsupported; this daemon requires exactly {PROTOCOL_VERSION}"
5961        ));
5962    }
5963    Ok(PROTOCOL_VERSION)
5964}
5965
5966fn pong(frame: &Frame) -> Result<Frame, RouterError> {
5967    Frame::build_with_version(
5968        response_version(frame),
5969        FrameType::Pong,
5970        frame.header.flags,
5971        0,
5972        0,
5973        frame.header.corr,
5974        Vec::new(),
5975    )
5976    .map_err(RouterError::FrameBuild)
5977}
5978
5979fn control_error_frame(
5980    frame: &Frame,
5981    code: &'static str,
5982    message: impl Into<String>,
5983) -> Result<Frame, RouterError> {
5984    control_error_body_frame(
5985        frame,
5986        ErrorBody {
5987            code: code.to_string(),
5988            message: message.into(),
5989            detail: None,
5990        },
5991    )
5992}
5993
5994fn control_error_body_frame(frame: &Frame, error: ErrorBody) -> Result<Frame, RouterError> {
5995    let body = serde_json::to_vec(&error).map_err(|err| {
5996        RouterError::backend(
5997            0,
5998            frame.header.corr,
5999            format!("failed to encode control ERROR: {err}"),
6000        )
6001    })?;
6002
6003    Frame::build_with_version(
6004        response_version(frame),
6005        FrameType::Error,
6006        control_flags(),
6007        0,
6008        0,
6009        frame.header.corr,
6010        body,
6011    )
6012    .map_err(RouterError::FrameBuild)
6013}
6014
6015fn control_response_body_frame<T: Serialize>(
6016    frame: &Frame,
6017    reply: &T,
6018    label: &'static str,
6019) -> Result<Frame, RouterError> {
6020    let body = serde_json::to_vec(reply).map_err(|err| {
6021        RouterError::backend(
6022            0,
6023            frame.header.corr,
6024            format!("failed to encode {label}: {err}"),
6025        )
6026    })?;
6027
6028    Frame::build_with_version(
6029        response_version(frame),
6030        FrameType::Response,
6031        control_flags(),
6032        0,
6033        0,
6034        frame.header.corr,
6035        body,
6036    )
6037    .map_err(RouterError::FrameBuild)
6038}
6039
6040/// Map a forwarding failure to the wire code a client sees.
6041///
6042/// The code is not a label: clients BRANCH on it. Both SDKs decide "retry in
6043/// place" with `subc_protocol::error_codes::is_retryable_route_open`, so a code
6044/// chosen here decides whether a caller retries or gives up.
6045///
6046/// That makes attribution the load-bearing property, not merely having a code. A
6047/// permanent fault published as a retryable one produces a fleet-wide retry storm
6048/// against something that can never recover; a transient fault published as
6049/// permanent gives up on work that would have succeeded. Both look correct in a
6050/// log, which is why `retryability_of_forwarding_codes_matches_the_failure` pins
6051/// the mapping per variant rather than merely asserting that some code exists.
6052///
6053/// That fence partitions by RETRYABILITY, which is coarser than identity: swapping
6054/// two codes on the same side of the boundary passes it. Measured rather than
6055/// assumed — `NoModuleConnection` re-pointed at `module_reloading` is caught only
6056/// by `supervision_only_module_health_probe_does_not_enable_route_open_and_cleans_up`,
6057/// a test named for something else that happens to assert the string.
6058///
6059/// That accidental coverage is deliberately left alone rather than promoted to a
6060/// named test, because it guards a property this function does not promise.
6061/// Checked at source: every consumer branches on the RETRYABLE SET and none on a
6062/// specific code within a class, so identity is free to change and only the
6063/// partition is a contract. Splitting it out would assert a guarantee nothing
6064/// depends on — and a suite that promises more than the code does is the harder
6065/// thing to correct later, because the next reader cannot tell which assertions
6066/// are load-bearing.
6067///
6068/// Pin identity here the moment a consumer branches on a specific code.
6069fn forwarding_error_code(err: &ForwardingError) -> &'static str {
6070    match err {
6071        ForwardingError::ConnectionRoleConflict { .. } => "invalid_request",
6072        ForwardingError::NoModuleConnection => "target_unavailable",
6073        ForwardingError::ModuleReloading { .. } => "module_reloading",
6074        ForwardingError::ClientRouteChannelExhausted { .. }
6075        | ForwardingError::ModuleRouteChannelExhausted { .. } => "route_limit",
6076        ForwardingError::StaleModuleEndpoint
6077        | ForwardingError::UnknownReservation { .. }
6078        | ForwardingError::ConnectionClosing { .. }
6079        | ForwardingError::ClientEgressClosed { .. }
6080        | ForwardingError::ModuleEgressUnavailable { .. } => "target_unavailable",
6081        // Only a swap candidate's registration can produce this, and it means
6082        // exactly what a second active HELLO for a live id means.
6083        ForwardingError::CandidateSlotOccupied { .. } => "duplicate_module_id",
6084        ForwardingError::RelayCorrelationExhausted
6085        | ForwardingError::RouteOpenBuild(_)
6086        | ForwardingError::Poisoned => "forwarding_error",
6087    }
6088}
6089
6090fn response_version(frame: &Frame) -> u8 {
6091    if (MIN_SUPPORTED_VERSION..=PROTOCOL_VERSION).contains(&frame.header.ver) {
6092        frame.header.ver
6093    } else {
6094        PROTOCOL_VERSION
6095    }
6096}
6097
6098fn control_flags() -> Flags {
6099    Flags::new(false, Priority::Passive, false)
6100}
6101
6102/// GOODBYE for a route.bind the daemon gave up on after reserving the module's
6103/// channel. The target is the module (a client never saw the route), so this
6104/// takes the module path: delivered late rather than dropped when the module's
6105/// queue is momentarily full, and never closing its connection.
6106fn send_goodbye_target_best_effort(
6107    counters: &DaemonCounters,
6108    target: &GoodbyeTarget,
6109    context: &'static str,
6110) {
6111    let Ok(frame) = Frame::build_with_version(
6112        target.negotiated_ver,
6113        FrameType::Goodbye,
6114        control_flags(),
6115        target.channel,
6116        target.epoch,
6117        0,
6118        Vec::new(),
6119    ) else {
6120        return;
6121    };
6122    crate::forwarding::send_module_route_goodbye(
6123        counters,
6124        &target.sink,
6125        frame,
6126        target.module_id.as_deref(),
6127        context,
6128    );
6129}
6130
6131pub(crate) fn send_route_control_pushes(
6132    forwarding: &ForwardingTable,
6133    routes: Vec<EndpointRoute>,
6134    push: ClientControlPush,
6135) {
6136    let mut targets: Vec<(GoodbyeTarget, Vec<u16>)> = Vec::new();
6137    for route in routes {
6138        let target = route.goodbye_target;
6139        if let Some((existing, channels)) = targets
6140            .iter_mut()
6141            .find(|(existing, _)| existing.connection_id == target.connection_id)
6142        {
6143            debug_assert_eq!(
6144                existing.negotiated_ver, target.negotiated_ver,
6145                "one connection cannot negotiate multiple frame versions"
6146            );
6147            if !channels.contains(&target.channel) {
6148                channels.push(target.channel);
6149            }
6150            continue;
6151        }
6152        let channel = target.channel;
6153        targets.push((target, vec![channel]));
6154    }
6155    for (target, mut channels) in targets {
6156        channels.sort_unstable();
6157        let mut push = push.clone();
6158        match &mut push {
6159            ClientControlPush::RouteClosing {
6160                channels: covered, ..
6161            }
6162            | ClientControlPush::RouteClosed {
6163                channels: covered, ..
6164            } => *covered = channels,
6165        }
6166        let body = match serde_json::to_vec(&push) {
6167            Ok(body) => body,
6168            Err(err) => {
6169                warn!(error = %err, "failed to serialize route lifecycle control PUSH");
6170                continue;
6171            }
6172        };
6173        let frame = match Frame::build_with_version(
6174            target.negotiated_ver,
6175            FrameType::Push,
6176            control_flags(),
6177            0,
6178            0,
6179            0,
6180            body.clone(),
6181        ) {
6182            Ok(frame) => frame,
6183            Err(err) => {
6184                warn!(
6185                    route_channel = target.channel,
6186                    error = %err,
6187                    "failed to build route lifecycle control PUSH frame"
6188                );
6189                continue;
6190            }
6191        };
6192        if let Err(err) = target.sink.try_send(frame) {
6193            if target.close_on_delivery_failure() {
6194                warn!(
6195                    target_connection_id = target.connection_id.get(),
6196                    route_channel = target.channel,
6197                    error = %err,
6198                    "route lifecycle control PUSH was not delivered to client; closing target connection"
6199                );
6200                let _ = forwarding.escalate_client_delivery_failure(
6201                    target.connection_id,
6202                    target.channel,
6203                    target.epoch,
6204                    CloseReason::new(
6205                        "route_lifecycle_push_delivery_failed",
6206                        format!(
6207                            "failed to enqueue route lifecycle control PUSH for channel {}: {err}",
6208                            target.channel
6209                        ),
6210                    ),
6211                    crate::forwarding::UndeliveredFrame {
6212                        module_id: target.module_id.as_deref(),
6213                        sink: &target.sink,
6214                    },
6215                );
6216            }
6217        }
6218    }
6219}
6220
6221#[cfg(test)]
6222mod tests {
6223    #[cfg(unix)]
6224    #[tokio::test]
6225    async fn rescan_health_only_is_live_but_launch_edits_need_reload() {
6226        let dir = subc_test_support::TestTempDir::new("rescan-live-health");
6227        let path = dir.join("subc.jsonc");
6228        std::fs::write(&path, serde_json::json!({"version":1,"modules":{"stock":{
6229            "program":"/bin/sleep","args":["60"],"protocol":"none",
6230            "env":{"XDG_DATA_HOME":dir.path(),"XDG_RUNTIME_DIR":dir.path(),"XDG_CONFIG_HOME":dir.path()}
6231        }}}).to_string()).unwrap();
6232        let mut configured = crate::daemon_config::load(&path)
6233            .unwrap()
6234            .unwrap()
6235            .modules
6236            .pop()
6237            .unwrap();
6238        let registry = std::sync::Arc::new(crate::Registry::default());
6239        let handle = crate::SupervisorHandle::new();
6240        let supervisor = crate::Supervisor::new(registry.clone(), crate::RestartPolicy::default())
6241            .with_handle(handle.clone());
6242        let module = supervisor
6243            .supervise_configured_with_health(
6244                configured.module_spec(),
6245                true,
6246                configured.health.clone(),
6247                None,
6248                configured.restart,
6249            )
6250            .unwrap();
6251        let handler = super::ControlHandler::new(registry).with_supervisor(handle);
6252        let before = module.status().unwrap().pid;
6253        configured.health.http = Some("http://127.0.0.1:1/healthz".into());
6254        configured.health.cadence = std::time::Duration::from_secs(3600);
6255        let health_only = handler
6256            .reconcile_supervised_modules(&supervisor, vec![configured.clone()], false)
6257            .await
6258            .unwrap();
6259        assert!(
6260            health_only.changed_pending_reload.is_empty(),
6261            "health policy is already applied live"
6262        );
6263        assert_eq!(module.status().unwrap().pid, before);
6264        assert_eq!(
6265            module.configuration().unwrap().1.http,
6266            configured.health.http
6267        );
6268        configured.args = vec!["61".into()];
6269        let launch = handler
6270            .reconcile_supervised_modules(&supervisor, vec![configured], false)
6271            .await
6272            .unwrap();
6273        assert_eq!(launch.changed_pending_reload, ["stock"]);
6274        assert_eq!(
6275            module.status().unwrap().pid,
6276            before,
6277            "a launch edit is stored until reload"
6278        );
6279        module.drain().await.unwrap();
6280    }
6281    use std::{
6282        collections::BTreeMap,
6283        fmt,
6284        path::PathBuf,
6285        sync::{Arc, Mutex},
6286        time::Duration,
6287    };
6288    use subc_test_support::TestTempDir;
6289
6290    use serde_json::{json, Value};
6291    use subc_protocol::{
6292        manifest::{
6293            Concurrency, ExecutionMode, IdentityScope, ManagementOperation,
6294            ManagementOperationKind, ObservabilityKind, ObservabilitySurface, ProviderRole, Tool,
6295        },
6296        session::HealthStatus,
6297        FrameType,
6298    };
6299
6300    use super::*;
6301    use crate::{
6302        forwarding::{DataRoute, DataRouteState},
6303        registry::ChannelState,
6304        router::FrameSink,
6305        stderr_tail::DEFAULT_MAX_LINE_BYTES,
6306        supervise::{ModuleSpec, ModuleState, RestartPolicy, Supervisor, SupervisorHandle},
6307        RouteCtx, Router,
6308    };
6309    use tokio::{
6310        sync::mpsc,
6311        time::{sleep, Instant},
6312    };
6313    use tracing::{
6314        field::{Field, Visit},
6315        Event, Subscriber,
6316    };
6317    use tracing_subscriber::{layer::Context, prelude::*, Layer};
6318
6319    /// Locates the `fake-aft-stub` binary from a `src/lib.rs` unit test.
6320    ///
6321    /// `CARGO_BIN_EXE_*` (compile-time `env!` and runtime `std::env::var` alike)
6322    /// is only populated for `tests/*.rs` integration test binaries -- this file
6323    /// compiles as part of the library target, which gets neither. This test's
6324    /// own executable path is `<target-dir>/<profile>/deps/subc_core-<hash>`,
6325    /// and the sibling binary lives two directories up at
6326    /// `<target-dir>/<profile>/fake-aft-stub`.
6327    ///
6328    /// THE BINARY IS NOT ALWAYS THERE, and the existence check below is why.
6329    /// `cargo test -p subc-core` builds every target including `[[bin]]`, so the
6330    /// stub is on disk; `cargo test -p subc-core --lib` builds ONLY the library
6331    /// test and leaves the stub unbuilt. A bare spawn then fails with a raw
6332    /// `NotFound`, which reads as a broken test rather than an unbuilt
6333    /// dependency -- so state the cause and the remedy instead. Deliberately a
6334    /// panic and not a silent skip: a test that quietly passes when it could not
6335    /// run is worse than one that fails, because it reports health it never
6336    /// verified.
6337    fn fake_aft_stub_path() -> PathBuf {
6338        let mut path = std::env::current_exe().expect("current_exe available in tests");
6339        path.pop(); // .../deps/
6340        path.pop(); // .../<profile>/
6341        path.push(if cfg!(windows) {
6342            "fake-aft-stub.exe"
6343        } else {
6344            "fake-aft-stub"
6345        });
6346        assert!(
6347            path.exists(),
6348            "fake-aft-stub not built at {}: run `cargo test -p subc-core` (which builds \
6349             [[bin]] targets) rather than `cargo test -p subc-core --lib` (which does not)",
6350            path.display()
6351        );
6352        path
6353    }
6354
6355    /// Whether clients retry `code` in place: the predicate itself, never a copy
6356    /// of its set. A copied list breaks silently when a code is added to or
6357    /// removed from the real one, and a stale copy here would let exactly the
6358    /// failure this test exists to catch pass.
6359    fn client_retries(code: &str) -> bool {
6360        subc_protocol::error_codes::is_retryable_route_open(code)
6361    }
6362
6363    /// A code is not a label — clients branch on it, so publishing the wrong KIND
6364    /// of failure is worse than publishing none. A permanent fault dressed as
6365    /// retryable makes every client in the fleet retry forever against something
6366    /// that cannot recover; a transient fault dressed as permanent abandons work
6367    /// that would have succeeded.
6368    ///
6369    /// Asserting "a code exists" cannot catch either, because the string is free
6370    /// to say anything. This enumerates every variant and pins which side of the
6371    /// retry boundary it lands on, so a new variant must be classified here
6372    /// deliberately rather than inheriting whichever arm it was appended to.
6373    #[test]
6374    fn retryability_of_forwarding_codes_matches_the_failure() {
6375        // Transient by nature: the target is booting, reloading, or its endpoint
6376        // was swapped mid-flight. Retrying is how these resolve.
6377        let transient = [
6378            ForwardingError::NoModuleConnection,
6379            ForwardingError::ModuleReloading {
6380                module_id: "m".into(),
6381            },
6382            ForwardingError::StaleModuleEndpoint,
6383            ForwardingError::UnknownReservation {
6384                client_channel: 1,
6385                module_channel: 1,
6386            },
6387            ForwardingError::ConnectionClosing {
6388                connection_id: ConnectionId::new(1),
6389            },
6390            ForwardingError::ClientEgressClosed {
6391                connection_id: ConnectionId::new(1),
6392            },
6393            ForwardingError::ModuleEgressUnavailable {
6394                connection_id: ConnectionId::new(1),
6395            },
6396        ];
6397        for err in transient {
6398            let code = forwarding_error_code(&err);
6399            assert!(
6400                client_retries(code),
6401                "{err:?} is transient but publishes {code:?}, which clients treat as permanent"
6402            );
6403        }
6404
6405        // Not fixed by retrying. Channel and correlation exhaustion need the
6406        // caller to close routes, and a poisoned lock is a daemon that cannot
6407        // recover at all — the worst thing to advertise as retryable, since every
6408        // client would storm a daemon that will never answer.
6409        let permanent = [
6410            ForwardingError::ConnectionRoleConflict {
6411                connection_id: ConnectionId::new(1),
6412            },
6413            ForwardingError::ClientRouteChannelExhausted {
6414                connection_id: ConnectionId::new(1),
6415            },
6416            ForwardingError::ModuleRouteChannelExhausted {
6417                endpoint: ModuleEndpointId {
6418                    connection_id: ConnectionId::new(1),
6419                    generation: 1,
6420                },
6421            },
6422            ForwardingError::RelayCorrelationExhausted,
6423            ForwardingError::RouteOpenBuild("x".into()),
6424            ForwardingError::Poisoned,
6425        ];
6426        for err in permanent {
6427            let code = forwarding_error_code(&err);
6428            assert!(
6429                !client_retries(code),
6430                "{err:?} cannot be fixed by retrying but publishes {code:?}, which clients retry"
6431            );
6432        }
6433    }
6434
6435    /// The principal is the daemon's answer to "who is calling", and modules
6436    /// branch on it: aft gates bash on it, cerebellum gates browser control,
6437    /// plexus gates connector invocation. So a stamp is an authorization input in
6438    /// another process, not a label — and both possible answers SUCCEED, which is
6439    /// what makes a wrong one quiet. An unattested caller stamped `Reserved` hands
6440    /// first-party capability to something that never proved it; a supervised one
6441    /// stamped `Direct` silently strips a module of capability it is entitled to.
6442    ///
6443    /// Neither shows up in a test that only checks the bind succeeded. Before this
6444    /// test the only coverage was accidental —
6445    /// `route_open_round_trip_via_tagged_shape_forwards_through_stub` asserts the
6446    /// stamped principal on its way past, so narrowing that wire-shape test to its
6447    /// stated subject would have deleted the last assertion on this value. It
6448    /// still asserts the stamp, which is now redundancy rather than the only
6449    /// guard: both fail under the same mutation, and this one names the reason.
6450    /// SCOPE: this handler's supervisor has spawned nothing, so
6451    /// `spawned_consumer_authorized` can only ever return false and the GRANT arm
6452    /// is unreachable here. Both assertions below are refusals, and a mutant that
6453    /// refuses everything would satisfy them.
6454    ///
6455    /// The grant side is covered where a real nonce exists: `tests/forwarding.rs`
6456    /// spawns a supervised consumer, reads its live nonce, and asserts the module
6457    /// observed `principal.kind == "reserved"` carrying that module_id — verified
6458    /// at source rather than assumed, since a citation is a claim about another
6459    /// file and ages like one. Recorded because a harness that structurally
6460    /// cannot reach an arm reports "none" for that arm identically to one that
6461    /// covers it and found nothing.
6462    #[tokio::test]
6463    async fn an_unattested_caller_is_never_stamped_as_a_supervised_module() {
6464        let handler = ControlHandler::default();
6465        let frame =
6466            Frame::build(FrameType::Request, control_flags(), 0, 0, 900, Vec::new()).unwrap();
6467
6468        // Absent consumer_identity is the ordinary case: a human at a terminal, or
6469        // any process holding the connection file. Nothing was proved, so nothing
6470        // may be granted beyond the unattested floor.
6471        let stamped = handler.route_open_principal(&frame, None).unwrap().unwrap();
6472        assert_eq!(
6473            stamped,
6474            Principal::Direct,
6475            "a caller that proved nothing must not be stamped as a supervised module"
6476        );
6477
6478        // A claimed module_id with a nonce no supervised child was given is a
6479        // forgery attempt, not a weaker caller: it must be REFUSED rather than
6480        // quietly demoted to Direct, or an impersonation attempt looks identical
6481        // to an ordinary unattested connection.
6482        let forged = handler
6483            .route_open_principal(
6484                &frame,
6485                Some(ConsumerIdentity {
6486                    module_id: "aft".to_string(),
6487                    launch_nonce: "not-a-real-nonce".to_string(),
6488                }),
6489            )
6490            .unwrap();
6491        let refusal = forged.expect_err("an unmatched launch nonce must not yield a principal");
6492        assert_eq!(parse_error(&refusal)["code"], "bad_consumer_identity");
6493    }
6494
6495    /// The test above hands `route_open_principal` an identity it built itself,
6496    /// which proves the stamping rule and nothing about where the identity comes
6497    /// from. The real producer is a wire body, and the two are joined by a serde
6498    /// field name that nothing else asserts.
6499    ///
6500    /// That join fails quietly in one specific way: an unrecognised key is simply
6501    /// absent after parsing, so a renamed or misspelled `consumer_identity`
6502    /// yields `None` and every supervised module silently drops to `Direct`.
6503    /// Capability-wise that is the safe direction, but it surfaces far from its
6504    /// cause — as a module mysteriously refused bash — and it would pass every
6505    /// test that builds its own input.
6506    ///
6507    /// Deliberately NOT closed with `deny_unknown_fields`: refusing unknown keys
6508    /// would break every client the moment the daemon gains a field, trading a
6509    /// quiet demotion for a hard refusal on additive change. Asserting the join
6510    /// instead means a rename breaks a test here rather than the fleet.
6511    #[test]
6512    fn a_wire_body_actually_yields_the_consumer_identity_the_daemon_stamps_from() {
6513        let body = br#"{"op":"route.open","target":{"kind":"tool_provider","module_id":"m"},"identity":{"session":"s","project_root":"/p","harness":"h"},"consumer_identity":{"module_id":"aft","launch_nonce":"n"}}"#;
6514        let parsed: ClientControlRequest = serde_json::from_slice(body).unwrap();
6515        let ClientControlRequest::RouteOpen {
6516            consumer_identity, ..
6517        } = parsed
6518        else {
6519            panic!("route.open body must parse as RouteOpen");
6520        };
6521        assert_eq!(
6522            consumer_identity,
6523            Some(ConsumerIdentity {
6524                module_id: "aft".to_string(),
6525                launch_nonce: "n".to_string(),
6526            }),
6527            "the wire field name must reach the value route_open_principal reads"
6528        );
6529    }
6530
6531    fn manifest(module_id: &str, protocol_ver: u8) -> ModuleManifest {
6532        ModuleManifest::builder(module_id, "0.1.0")
6533            .protocol_ver(protocol_ver)
6534            .provides(vec![ProviderRole::ToolProvider {
6535                tools: vec![Tool {
6536                    name: "read".to_string(),
6537                    description: None,
6538                    execution_mode: ExecutionMode::Pure,
6539                    schema: json!({"type": "object"}),
6540                }],
6541                identity_scope: vec![IdentityScope::Project, IdentityScope::Session],
6542                concurrency: Concurrency::ModuleManaged,
6543                emits_push: true,
6544                sub_supervises: true,
6545            }])
6546            .build()
6547    }
6548
6549    fn hello_frame(module_id: &str, protocol_ver: u8, corr: u64) -> Frame {
6550        hello_frame_with_control_ops(module_id, protocol_ver, corr, None)
6551    }
6552
6553    fn hello_frame_with_control_ops(
6554        module_id: &str,
6555        protocol_ver: u8,
6556        corr: u64,
6557        control_ops: Option<Vec<String>>,
6558    ) -> Frame {
6559        hello_frame_full(module_id, protocol_ver, corr, control_ops, None)
6560    }
6561
6562    fn hello_frame_with_nonce(
6563        module_id: &str,
6564        protocol_ver: u8,
6565        corr: u64,
6566        launch_nonce: Option<&str>,
6567    ) -> Frame {
6568        hello_frame_full(
6569            module_id,
6570            protocol_ver,
6571            corr,
6572            None,
6573            launch_nonce.map(ToOwned::to_owned),
6574        )
6575    }
6576
6577    fn hello_frame_full(
6578        module_id: &str,
6579        protocol_ver: u8,
6580        corr: u64,
6581        control_ops: Option<Vec<String>>,
6582        launch_nonce: Option<String>,
6583    ) -> Frame {
6584        let body = serde_json::to_vec(&ModuleHelloBody {
6585            manifest: manifest(module_id, protocol_ver),
6586            protocol_ver,
6587            control_ops,
6588            launch_nonce,
6589        })
6590        .unwrap();
6591        Frame::build(FrameType::Hello, control_flags(), 0, 0, corr, body).unwrap()
6592    }
6593
6594    fn non_routable_hello_frame_with_control_ops(
6595        module_id: &str,
6596        corr: u64,
6597        control_ops: Option<Vec<String>>,
6598    ) -> Frame {
6599        let mut manifest = manifest(module_id, PROTOCOL_VERSION);
6600        manifest.provides.clear();
6601        let body = serde_json::to_vec(&ModuleHelloBody {
6602            manifest,
6603            protocol_ver: PROTOCOL_VERSION,
6604            control_ops,
6605            launch_nonce: None,
6606        })
6607        .unwrap();
6608        Frame::build(FrameType::Hello, control_flags(), 0, 0, corr, body).unwrap()
6609    }
6610
6611    fn capability_grammar_hello_frame(
6612        capabilities: Value,
6613        runtime_computed: Option<Value>,
6614        corr: u64,
6615    ) -> Frame {
6616        let mut body = serde_json::to_value(ModuleHelloBody {
6617            manifest: manifest("capability-grammar-test", PROTOCOL_VERSION),
6618            protocol_ver: PROTOCOL_VERSION,
6619            control_ops: None,
6620            launch_nonce: None,
6621        })
6622        .expect("HELLO body serializes");
6623        body["manifest"]["capabilities"] = capabilities;
6624        if let Some(runtime_computed) = runtime_computed {
6625            body["runtime_computed"] = runtime_computed;
6626        }
6627        Frame::build(
6628            FrameType::Hello,
6629            control_flags(),
6630            0,
6631            0,
6632            corr,
6633            serde_json::to_vec(&body).expect("HELLO body reserializes"),
6634        )
6635        .expect("HELLO frame builds")
6636    }
6637
6638    fn channel_request(channel: u16, corr: u64) -> Frame {
6639        Frame::build(
6640            FrameType::Request,
6641            Flags::new(true, Priority::Interactive, false),
6642            channel,
6643            0,
6644            corr,
6645            b"opaque".to_vec(),
6646        )
6647        .unwrap()
6648    }
6649
6650    fn route_ctx(
6651        connection_id: ConnectionId,
6652    ) -> (RouteCtx, mpsc::Receiver<crate::router::OutboundFrame>) {
6653        let (tx, rx) = mpsc::channel(8);
6654        (
6655            RouteCtx {
6656                connection_id,
6657                egress: FrameSink::new(tx),
6658            },
6659            rx,
6660        )
6661    }
6662
6663    fn parse_ack(frame: &Frame) -> ModuleHelloAckBody {
6664        serde_json::from_slice(&frame.body).unwrap()
6665    }
6666
6667    /// Register a module over a connection that has a sink and return the
6668    /// HELLO_ACK the module reads. A successful HELLO queues its ack on the
6669    /// module's own sink rather than returning it as a reply, so the ack is
6670    /// taken off `rx` here and whatever the test reads next is what followed it.
6671    async fn hello_via_sink(
6672        handler: &ControlHandler,
6673        ctx: &RouteCtx,
6674        rx: &mut mpsc::Receiver<crate::router::OutboundFrame>,
6675        hello: Frame,
6676    ) -> Frame {
6677        let replies = handler.handle_control_frame(ctx, hello).await.unwrap();
6678        assert!(
6679            replies.is_empty(),
6680            "a registered HELLO replies with nothing; its ack is already queued: {replies:?}"
6681        );
6682        let ack = rx
6683            .try_recv()
6684            .expect("HELLO_ACK is queued on the module sink")
6685            .frame;
6686        assert_eq!(ack.header.ty, FrameType::HelloAck);
6687        ack
6688    }
6689
6690    fn parse_error(frame: &Frame) -> Value {
6691        serde_json::from_slice(&frame.body).unwrap()
6692    }
6693
6694    fn parse_route_poll(frame: &Frame) -> ClientControlResponse {
6695        serde_json::from_slice(&frame.body).unwrap()
6696    }
6697
6698    fn route_poll_frame(corr: u64, kind: PollKind, route_channel: u16) -> Frame {
6699        let body = serde_json::to_vec(&ClientControlRequest::RoutePoll {
6700            route_channel,
6701            route_epoch: 0,
6702            kind,
6703        })
6704        .unwrap();
6705        Frame::build(FrameType::Request, control_flags(), 0, 0, corr, body).unwrap()
6706    }
6707
6708    fn supervisor_health_probe_frame(corr: u64, module_id: &str) -> Frame {
6709        let body = serde_json::to_vec(&ClientControlRequest::SupervisorHealthProbe {
6710            module_id: module_id.to_string(),
6711        })
6712        .unwrap();
6713        Frame::build(FrameType::Request, control_flags(), 0, 0, corr, body).unwrap()
6714    }
6715
6716    fn route_open_frame(corr: u64, module_id: &str, project_root: TestTempDir) -> Frame {
6717        route_open_frame_with_consumer_capabilities(corr, module_id, project_root, None)
6718    }
6719
6720    fn route_open_frame_with_consumer_capabilities(
6721        corr: u64,
6722        module_id: &str,
6723        project_root: TestTempDir,
6724        consumer_capabilities: Option<Vec<String>>,
6725    ) -> Frame {
6726        let body = serde_json::to_vec(&ClientControlRequest::RouteOpen {
6727            target: RouteTarget::ToolProvider {
6728                module_id: module_id.to_string(),
6729            },
6730            identity: BindIdentity::new(
6731                project_root.path().to_path_buf(),
6732                "unit".to_string(),
6733                "session".to_string(),
6734            ),
6735            consumer_identity: None,
6736            consumer_capabilities,
6737            role_versions: None,
6738            admission_facts: None,
6739            scope: None,
6740        })
6741        .unwrap();
6742        Frame::build(FrameType::Request, control_flags(), 0, 0, corr, body).unwrap()
6743    }
6744
6745    fn route_open_frame_with_role_versions(
6746        corr: u64,
6747        module_id: &str,
6748        project_root: TestTempDir,
6749        role_versions: Option<BTreeMap<String, String>>,
6750    ) -> Frame {
6751        let body = serde_json::to_vec(&ClientControlRequest::RouteOpen {
6752            target: RouteTarget::ToolProvider {
6753                module_id: module_id.to_string(),
6754            },
6755            identity: BindIdentity::new(
6756                project_root.path().to_path_buf(),
6757                "unit".to_string(),
6758                format!("session-{corr}"),
6759            ),
6760            consumer_identity: None,
6761            consumer_capabilities: None,
6762            role_versions,
6763            admission_facts: None,
6764            scope: None,
6765        })
6766        .unwrap();
6767        Frame::build(FrameType::Request, control_flags(), 0, 0, corr, body).unwrap()
6768    }
6769
6770    fn role_versions(entries: &[(&str, &str)]) -> BTreeMap<String, String> {
6771        entries
6772            .iter()
6773            .map(|(role, version)| (role.to_string(), version.to_string()))
6774            .collect()
6775    }
6776
6777    fn route_open_frame_with_admission_facts(
6778        corr: u64,
6779        module_id: &str,
6780        project_root: TestTempDir,
6781        consumer_identity: Option<subc_control::ConsumerIdentity>,
6782        facts: Option<Value>,
6783    ) -> Frame {
6784        let body = serde_json::to_vec(&ClientControlRequest::RouteOpen {
6785            target: RouteTarget::ToolProvider {
6786                module_id: module_id.to_string(),
6787            },
6788            identity: BindIdentity::new(
6789                project_root.path().to_path_buf(),
6790                "unit".to_string(),
6791                format!("session-{corr}"),
6792            ),
6793            consumer_identity,
6794            consumer_capabilities: None,
6795            role_versions: None,
6796            admission_facts: facts,
6797            scope: None,
6798        })
6799        .unwrap();
6800        Frame::build(FrameType::Request, control_flags(), 0, 0, corr, body).unwrap()
6801    }
6802
6803    #[derive(Clone, Default)]
6804    struct EventCapture {
6805        events: Arc<Mutex<Vec<CapturedEvent>>>,
6806    }
6807
6808    #[derive(Clone, Debug)]
6809    struct CapturedEvent {
6810        target: String,
6811        level: tracing::Level,
6812        fields: BTreeMap<String, String>,
6813    }
6814
6815    impl EventCapture {
6816        fn events(&self) -> Vec<CapturedEvent> {
6817            self.events.lock().unwrap().clone()
6818        }
6819    }
6820
6821    impl<S> Layer<S> for EventCapture
6822    where
6823        S: Subscriber,
6824    {
6825        fn on_event(&self, event: &Event<'_>, _context: Context<'_, S>) {
6826            let mut visitor = EventFieldVisitor::default();
6827            event.record(&mut visitor);
6828            self.events.lock().unwrap().push(CapturedEvent {
6829                target: event.metadata().target().to_string(),
6830                level: *event.metadata().level(),
6831                fields: visitor.fields,
6832            });
6833        }
6834    }
6835
6836    #[derive(Default)]
6837    struct EventFieldVisitor {
6838        fields: BTreeMap<String, String>,
6839    }
6840
6841    impl Visit for EventFieldVisitor {
6842        fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
6843            self.fields
6844                .insert(field.name().to_string(), format!("{value:?}"));
6845        }
6846    }
6847
6848    fn health_response(corr: u64, status: HealthStatus) -> Frame {
6849        let body = serde_json::to_vec(&ModuleControlResponse::HealthCheck {
6850            status,
6851            detail: Some("warming".to_string()),
6852            metrics: Some(json!({"queue_depth": 3})),
6853        })
6854        .unwrap();
6855        Frame::build(FrameType::Response, control_flags(), 0, 0, corr, body).unwrap()
6856    }
6857
6858    fn route_bind_ack(corr: u64) -> Frame {
6859        let body = serde_json::to_vec(&ModuleControlResponse::RouteBindAck {}).unwrap();
6860        Frame::build(FrameType::Response, control_flags(), 0, 0, corr, body).unwrap()
6861    }
6862
6863    fn unique_project_root(label: &str) -> TestTempDir {
6864        TestTempDir::new(label)
6865    }
6866
6867    fn assert_route_poll_liveness(frame: &Frame, expected_live: bool) {
6868        match parse_route_poll(frame) {
6869            ClientControlResponse::RoutePoll {
6870                status: None,
6871                live: Some(live),
6872                ..
6873            } => assert_eq!(live, expected_live),
6874            other => panic!("unexpected route.poll response: {other:?}"),
6875        }
6876    }
6877
6878    fn bind_liveness_route(
6879        registry: &Registry,
6880        forwarding: &ForwardingTable,
6881        module_id: &str,
6882    ) -> (RouteCtx, u16, u32) {
6883        let module_connection = ConnectionId::new(101);
6884        let client_connection = ConnectionId::new(202);
6885        let registration = registry
6886            .register_with_control_ops(
6887                manifest(module_id, PROTOCOL_VERSION),
6888                PROTOCOL_VERSION,
6889                module_connection,
6890                module_baseline_control_ops(),
6891            )
6892            .unwrap();
6893        let (module_tx, _module_rx) = mpsc::channel(8);
6894        let endpoint = forwarding
6895            .register_module_connection(
6896                module_connection,
6897                module_id.to_string(),
6898                PROTOCOL_VERSION,
6899                manifest_concurrency(&registration.manifest),
6900                FrameSink::new(module_tx),
6901            )
6902            .unwrap();
6903        let (client_ctx, _client_rx) = route_ctx(client_connection);
6904        let pending = forwarding
6905            .begin_route_bind_relay_for_test(
6906                client_connection,
6907                client_ctx.egress.clone(),
6908                1,
6909                module_id,
6910            )
6911            .unwrap();
6912        assert_eq!(pending.endpoint, endpoint);
6913        let route_channel = pending.client_channel;
6914        let route_epoch = pending.client_epoch;
6915        forwarding
6916            .complete_pending_relay(
6917                module_connection,
6918                pending.corr,
6919                RouteBindRelayOutcome::Accepted,
6920            )
6921            .unwrap();
6922        (client_ctx, route_channel, route_epoch)
6923    }
6924
6925    struct FakeProcessLiveness {
6926        live: Option<bool>,
6927    }
6928
6929    impl ModuleProcessLiveness for FakeProcessLiveness {
6930        fn process_live(&self, _module_id: &str) -> Option<bool> {
6931            self.live
6932        }
6933    }
6934
6935    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
6936    async fn supervisor_stderr_tail_converts_a_real_truncated_ring_entry_to_prefix_only_wire_data()
6937    {
6938        let registry = Arc::new(Registry::default());
6939        let supervisor_handle = SupervisorHandle::new();
6940        let supervisor = Supervisor::new_for_test(
6941            Arc::clone(&registry),
6942            RestartPolicy::new(1, Duration::from_millis(10)),
6943        )
6944        .with_handle(supervisor_handle.clone());
6945        let source_line = format!("config error: {}", "x".repeat(DEFAULT_MAX_LINE_BYTES));
6946        let module = supervisor
6947            .spawn(ModuleSpec {
6948                module_id: "stderr-tail-wire".to_string(),
6949                program: fake_aft_stub_path(),
6950                args: Vec::new(),
6951                env: vec![
6952                    ("FAKE_AFT_STDERR_LINE".to_string(), source_line.clone()),
6953                    ("FAKE_AFT_EXIT_CODE".to_string(), "1".to_string()),
6954                ],
6955                reserved: false,
6956                reserved_prefixes: Vec::new(),
6957                protocol: ModuleProtocol::Subc,
6958                overlap: Default::default(),
6959            })
6960            .unwrap();
6961
6962        let deadline = Instant::now() + Duration::from_secs(5);
6963        loop {
6964            let tail = module.stderr_tail(None, None);
6965            if tail
6966                .entries
6967                .iter()
6968                .any(|entry| matches!(entry, TailEntry::ProcessStart))
6969                && tail.entries.iter().any(|entry| {
6970                    matches!(
6971                        entry,
6972                        TailEntry::Line {
6973                            truncated: true,
6974                            ..
6975                        }
6976                    )
6977                })
6978            {
6979                break;
6980            }
6981            assert!(
6982                Instant::now() < deadline,
6983                "module did not produce a truncated line and restart boundary: {tail:?}"
6984            );
6985            sleep(Duration::from_millis(10)).await;
6986        }
6987
6988        let handler = ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor_handle);
6989        let request = ClientControlRequest::SupervisorStderrTail {
6990            module_id: "stderr-tail-wire".to_string(),
6991            max_lines: None,
6992            max_bytes: None,
6993        };
6994        let frame = Frame::build(
6995            FrameType::Request,
6996            control_flags(),
6997            0,
6998            0,
6999            1,
7000            serde_json::to_vec(&request).unwrap(),
7001        )
7002        .unwrap();
7003        let (ctx, _egress) = route_ctx(ConnectionId::new(1));
7004        let responses = handler.handle_control_frame(&ctx, frame).await.unwrap();
7005        let ClientControlResponse::SupervisorStderrTail { tail, .. } =
7006            serde_json::from_slice(&responses[0].body).unwrap()
7007        else {
7008            panic!("expected supervisor.stderr_tail response");
7009        };
7010
7011        assert!(
7012            tail.entries
7013                .iter()
7014                .any(|entry| matches!(entry, StderrTailEntry::ProcessStart)),
7015            "the control response lost the restart boundary"
7016        );
7017        let Some(StderrTailEntry::Line {
7018            text,
7019            truncated,
7020            at_ms,
7021        }) = tail.entries.iter().find(|entry| {
7022            matches!(
7023                entry,
7024                StderrTailEntry::Line {
7025                    truncated: true,
7026                    ..
7027                }
7028            )
7029        })
7030        else {
7031            panic!("the control response lost the truncated line");
7032        };
7033        assert_eq!(text, &source_line[..DEFAULT_MAX_LINE_BYTES]);
7034        assert!(*truncated);
7035        assert!(
7036            at_ms.is_some(),
7037            "the control response lost the line's capture time"
7038        );
7039    }
7040
7041    /// `supervisor.terminals` reads journal files. On a single-worker runtime a
7042    /// read done on the worker thread would stall every other task until it
7043    /// finished; the read must run off the worker so this test's own task keeps
7044    /// running while the read is paused.
7045    #[tokio::test(flavor = "current_thread")]
7046    async fn supervisor_terminals_reads_the_journal_off_the_runtime_worker() {
7047        let dir = TestTempDir::new("terminals-off-worker");
7048        let journal_path = dir.join("terminals.jsonl");
7049        let registry = Arc::new(Registry::default());
7050        let supervisor_handle = SupervisorHandle::new();
7051        let supervisor =
7052            Supervisor::new_for_test(Arc::clone(&registry), RestartPolicy::new(1, Duration::ZERO))
7053                .with_handle(supervisor_handle.clone())
7054                .with_terminal_journal(journal_path.clone(), "off-worker-daemon".to_string());
7055        let module = supervisor
7056            .spawn(ModuleSpec {
7057                module_id: "terminal-off-worker".to_string(),
7058                program: fake_aft_stub_path(),
7059                args: Vec::new(),
7060                env: vec![("FAKE_AFT_EXIT_CODE".to_string(), "23".to_string())],
7061                reserved: false,
7062                reserved_prefixes: Vec::new(),
7063                protocol: ModuleProtocol::Subc,
7064                overlap: Default::default(),
7065            })
7066            .unwrap();
7067        let deadline = Instant::now() + Duration::from_secs(5);
7068        while module.terminal_history().entries.len() != 2 {
7069            assert!(Instant::now() < deadline, "module did not record two exits");
7070            sleep(Duration::from_millis(10)).await;
7071        }
7072
7073        let (started, release) = crate::terminal_journal::read_pause::install(&journal_path);
7074        let handler =
7075            Arc::new(ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor_handle));
7076        let frame = Frame::build(
7077            FrameType::Request,
7078            control_flags(),
7079            0,
7080            0,
7081            1,
7082            serde_json::to_vec(&ClientControlRequest::SupervisorTerminals {
7083                module_id: "terminal-off-worker".to_string(),
7084            })
7085            .unwrap(),
7086        )
7087        .unwrap();
7088        let (ctx, _egress) = route_ctx(ConnectionId::new(1));
7089        let spawned_at = std::time::Instant::now();
7090        let read = tokio::spawn({
7091            let handler = Arc::clone(&handler);
7092            async move { handler.handle_control_frame(&ctx, frame).await }
7093        });
7094        // Waiting for the pause from a blocking thread keeps this task pending,
7095        // so the runtime's single worker is free to run the read task.
7096        tokio::task::spawn_blocking(move || started.recv_timeout(Duration::from_secs(5)))
7097            .await
7098            .unwrap()
7099            .expect("the history read reached its pause");
7100        let elapsed = spawned_at.elapsed();
7101        assert!(
7102            elapsed < Duration::from_secs(2) && !read.is_finished(),
7103            "this task could not run while the history read was paused \
7104             (resumed after {elapsed:?}, read finished: {})",
7105            read.is_finished()
7106        );
7107
7108        drop(release);
7109        let responses = read.await.unwrap().unwrap();
7110        let response: ClientControlResponse = serde_json::from_slice(&responses[0].body).unwrap();
7111        let ClientControlResponse::SupervisorTerminals { terminals, .. } = response else {
7112            panic!("expected supervisor.terminals response");
7113        };
7114        assert_eq!(terminals.entries.len(), 2);
7115        assert_eq!(terminals.journal_skipped_lines, 0);
7116        assert_eq!(terminals.journal_read_errors, 0);
7117    }
7118
7119    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
7120    async fn supervisor_terminals_golden_is_generated_through_the_real_handler() {
7121        let registry = Arc::new(Registry::default());
7122        let supervisor_handle = SupervisorHandle::new();
7123        let supervisor =
7124            Supervisor::new_for_test(Arc::clone(&registry), RestartPolicy::new(1, Duration::ZERO))
7125                .with_handle(supervisor_handle.clone());
7126        let module = supervisor
7127            .spawn(ModuleSpec {
7128                module_id: "terminal-golden".to_string(),
7129                program: fake_aft_stub_path(),
7130                args: Vec::new(),
7131                env: vec![("FAKE_AFT_EXIT_CODE".to_string(), "23".to_string())],
7132                reserved: false,
7133                reserved_prefixes: Vec::new(),
7134                protocol: ModuleProtocol::Subc,
7135                overlap: Default::default(),
7136            })
7137            .unwrap();
7138
7139        let deadline = Instant::now() + Duration::from_secs(5);
7140        while module.terminal_history().entries.len() != 2 {
7141            assert!(
7142                Instant::now() < deadline,
7143                "module did not retain two terminal exits: {:?}",
7144                module.terminal_history()
7145            );
7146            sleep(Duration::from_millis(10)).await;
7147        }
7148
7149        let handler = ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor_handle);
7150        let request = ClientControlRequest::SupervisorTerminals {
7151            module_id: "terminal-golden".to_string(),
7152        };
7153        let frame = Frame::build(
7154            FrameType::Request,
7155            control_flags(),
7156            0,
7157            0,
7158            1,
7159            serde_json::to_vec(&request).unwrap(),
7160        )
7161        .unwrap();
7162        let (ctx, _egress) = route_ctx(ConnectionId::new(1));
7163        let responses = handler.handle_control_frame(&ctx, frame).await.unwrap();
7164        let response: ClientControlResponse = serde_json::from_slice(&responses[0].body).unwrap();
7165        let ClientControlResponse::SupervisorTerminals { terminals, .. } = &response else {
7166            panic!("expected supervisor.terminals response");
7167        };
7168        assert_eq!(terminals.entries.len(), 2);
7169        assert_eq!(terminals.dropped, 0);
7170
7171        let mut rendered = serde_json::to_value(response).unwrap();
7172        // Wall-clock fields are the observation contract, but not stable fixture
7173        // bytes; normalize only them after the real handler has shaped the response.
7174        rendered["daemon_started_at_ms"] = json!(1_700_000_000_000u64);
7175        for (index, entry) in rendered["entries"]
7176            .as_array_mut()
7177            .expect("terminal response entries array")
7178            .iter_mut()
7179            .enumerate()
7180        {
7181            entry["at_ms"] = json!(1_700_000_000_001u64 + index as u64);
7182        }
7183
7184        let golden_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7185            .join("../subc-control/tests/golden/client_control_response_supervisor_terminals.json");
7186        let serialized = serde_json::to_string_pretty(&rendered).unwrap() + "\n";
7187        if std::env::var_os("UPDATE_GOLDEN").is_some() {
7188            std::fs::write(&golden_path, &serialized).unwrap();
7189        }
7190        let expected: Value =
7191            serde_json::from_str(&std::fs::read_to_string(&golden_path).unwrap()).unwrap();
7192        assert_eq!(rendered, expected);
7193    }
7194
7195    #[test]
7196    fn hello_registers_manifest_and_returns_ack() {
7197        let registry = Arc::new(Registry::default());
7198        let handler = ControlHandler::new(Arc::clone(&registry));
7199        let conn = ConnectionId::new(1);
7200
7201        let responses = handler
7202            .handle_control(conn, hello_frame("aft", PROTOCOL_VERSION, 7))
7203            .unwrap();
7204
7205        assert_eq!(responses.len(), 1);
7206        assert_eq!(responses[0].header.ty, FrameType::HelloAck);
7207        assert_eq!(responses[0].header.channel, 0);
7208        assert_eq!(responses[0].header.corr, 7);
7209        let ack = parse_ack(&responses[0]);
7210        assert_eq!(ack.negotiated_ver, PROTOCOL_VERSION);
7211        assert!(ack
7212            .subc_capabilities
7213            .contains(&CAP_MANIFEST_REGISTRATION.to_string()));
7214        assert!(ack.subc_ops.contains(&ops::SUPERVISOR_LIST.to_string()));
7215        assert!(ack.subc_ops.contains(&ops::SUPERVISOR_RESTART.to_string()));
7216        assert!(ack
7217            .subc_ops
7218            .contains(&ops::SUPERVISOR_SET_ENABLED.to_string()));
7219        assert!(ack
7220            .subc_ops
7221            .contains(&MODULE_TO_SUBC_OP_CATALOG_UPDATE.to_string()));
7222
7223        let registration = registry.get_module("aft").unwrap().unwrap();
7224        assert_eq!(registration.negotiated_ver, PROTOCOL_VERSION);
7225        assert_eq!(registration.state, ChannelState::Active);
7226        assert_eq!(registration.connection_id, conn);
7227        assert_eq!(registration.control_ops, module_baseline_control_ops());
7228    }
7229
7230    #[test]
7231    fn capability_grammar_refusals_name_the_field_and_leave_no_catalog_entry() {
7232        let invalid_identifiers = [
7233            ("case_change", "credentials-Provider/v1"),
7234            ("leading_zero", "credentials-provider/v01"),
7235            ("trailing_hyphen", "credentials-provider-/v1"),
7236            ("consecutive_hyphens", "credentials--provider/v1"),
7237            ("uppercase", "Credentials-provider/v1"),
7238            ("missing_v", "credentials-provider/1"),
7239            ("whitespace", "credentials provider/v1"),
7240            ("zero_version", "credentials-provider/v0"),
7241            ("out_of_range_version", "credentials-provider/v4294967296"),
7242            (
7243                "overlength_name",
7244                "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/v1",
7245            ),
7246        ];
7247        let mut cases = invalid_identifiers
7248            .into_iter()
7249            .map(|(name, identifier)| {
7250                (
7251                    format!("identifier_{name}"),
7252                    "capabilities.provides[0]".to_string(),
7253                    identifier.to_string(),
7254                    json!({ "provides": [identifier] }),
7255                    None,
7256                )
7257            })
7258            .collect::<Vec<_>>();
7259        cases.extend([
7260            (
7261                "unknown_need".to_string(),
7262                "capabilities.requires[0].need".to_string(),
7263                "deferred".to_string(),
7264                json!({ "requires": [{ "capability": "credentials-provider/v1", "need": "deferred" }] }),
7265                None,
7266            ),
7267            (
7268                "duplicate_provides".to_string(),
7269                "capabilities.provides[1]".to_string(),
7270                "credentials-provider/v1".to_string(),
7271                json!({ "provides": ["credentials-provider/v1", "credentials-provider/v1"] }),
7272                None,
7273            ),
7274            (
7275                "duplicate_must_never_reach".to_string(),
7276                "capabilities.must_never_reach[1]".to_string(),
7277                "credentials-provider/v1".to_string(),
7278                json!({ "must_never_reach": ["credentials-provider/v1", "credentials-provider/v1"] }),
7279                None,
7280            ),
7281            (
7282                "duplicate_requires_same_need".to_string(),
7283                "capabilities.requires[1]".to_string(),
7284                "credentials-provider/v1".to_string(),
7285                json!({ "requires": [
7286                    { "capability": "credentials-provider/v1", "need": "required" },
7287                    { "capability": "credentials-provider/v1", "need": "required" }
7288                ] }),
7289                None,
7290            ),
7291            (
7292                "duplicate_requires_conflicting_need".to_string(),
7293                "capabilities.requires[1]".to_string(),
7294                "credentials-provider/v1".to_string(),
7295                json!({ "requires": [
7296                    { "capability": "credentials-provider/v1", "need": "required" },
7297                    { "capability": "credentials-provider/v1", "need": "optional" }
7298                ] }),
7299                None,
7300            ),
7301            (
7302                "capabilities_root_pointer".to_string(),
7303                "runtime_computed[0]".to_string(),
7304                "/capabilities".to_string(),
7305                json!({}),
7306                Some(json!(["/capabilities"])),
7307            ),
7308            (
7309                "capabilities_descendant_pointer".to_string(),
7310                "runtime_computed[0]".to_string(),
7311                "/capabilities/provides".to_string(),
7312                json!({}),
7313                Some(json!(["/capabilities/provides"])),
7314            ),
7315            (
7316                "malformed_pointer_without_leading_slash".to_string(),
7317                "runtime_computed[0]".to_string(),
7318                "capabilities".to_string(),
7319                json!({}),
7320                Some(json!(["capabilities"])),
7321            ),
7322            (
7323                "malformed_pointer_escape".to_string(),
7324                "runtime_computed[0]".to_string(),
7325                "/roles/~2/tools".to_string(),
7326                json!({}),
7327                Some(json!(["/roles/~2/tools"])),
7328            ),
7329            (
7330                "unknown_capabilities_field".to_string(),
7331                "capabilities.future".to_string(),
7332                "<array>".to_string(),
7333                json!({ "future": [] }),
7334                None,
7335            ),
7336        ]);
7337
7338        for (index, (name, field, value, capabilities, runtime_computed)) in
7339            cases.into_iter().enumerate()
7340        {
7341            let registry = Arc::new(Registry::default());
7342            let handler = ControlHandler::new(Arc::clone(&registry));
7343            let response = handler
7344                .handle_control(
7345                    ConnectionId::new((index + 1) as u64),
7346                    capability_grammar_hello_frame(
7347                        capabilities,
7348                        runtime_computed,
7349                        index as u64 + 1,
7350                    ),
7351                )
7352                .expect("invalid HELLO returns a refusal");
7353
7354            assert_eq!(response.len(), 1, "{name} must emit one refusal");
7355            let error = parse_error(&response[0]);
7356            assert_eq!(error["code"], "invalid_capability_grammar", "{name}");
7357            let message = error["message"]
7358                .as_str()
7359                .expect("error message is a string");
7360            assert!(
7361                message.contains(&field),
7362                "{name}: field missing from {message}"
7363            );
7364            assert!(
7365                message.contains(&value),
7366                "{name}: value missing from {message}"
7367            );
7368            assert_eq!(
7369                registry
7370                    .active_registration_count()
7371                    .expect("registry reads"),
7372                0,
7373                "{name}: refused HELLO must not create a catalog entry"
7374            );
7375        }
7376    }
7377
7378    #[test]
7379    fn legal_runtime_pointer_and_capabilities_are_mirrored_in_catalog_list() {
7380        let registry = Arc::new(Registry::default());
7381        let handler = ControlHandler::new(Arc::clone(&registry));
7382        let capabilities = json!({
7383            "provides": ["credentials-provider/v1"],
7384            "requires": [{ "capability": "context-transform/v1", "need": "optional" }],
7385            "must_never_reach": ["federation-transport/v1"]
7386        });
7387        let response = handler
7388            .handle_control(
7389                ConnectionId::new(99),
7390                capability_grammar_hello_frame(
7391                    capabilities.clone(),
7392                    Some(json!(["/roles/0/tools"])),
7393                    99,
7394                ),
7395            )
7396            .expect("valid HELLO registers");
7397        assert_eq!(response[0].header.ty, FrameType::HelloAck);
7398
7399        let request = Frame::build(
7400            FrameType::Request,
7401            control_flags(),
7402            0,
7403            0,
7404            100,
7405            serde_json::to_vec(&ClientControlRequest::CatalogList { module_id: None })
7406                .expect("catalog request serializes"),
7407        )
7408        .expect("catalog request frame builds");
7409        let response = handler
7410            .handle_catalog_list(request, None)
7411            .expect("catalog list succeeds");
7412        let ClientControlResponse::CatalogList { modules, .. } =
7413            serde_json::from_slice(&response[0].body).expect("catalog response decodes")
7414        else {
7415            panic!("catalog request must return catalog.list");
7416        };
7417        assert_eq!(modules.len(), 1);
7418        assert_eq!(
7419            serde_json::to_value(&modules[0].capabilities).expect("catalog capabilities serialize"),
7420            capabilities
7421        );
7422    }
7423
7424    #[test]
7425    fn catalog_list_mirrors_management_operation_description() {
7426        let registry = Arc::new(Registry::default());
7427        let handler = ControlHandler::new(Arc::clone(&registry));
7428        let description = "List managed records and return their identifiers and metadata.";
7429        let mut manifest = manifest("described-management", PROTOCOL_VERSION);
7430        manifest.provides = vec![ProviderRole::ManagementSurface {
7431            operations: vec![ManagementOperation {
7432                name: "records.list".to_string(),
7433                kind: ManagementOperationKind::Query,
7434                description: Some(description.to_string()),
7435            }],
7436            config_schema: json!({"type": "object"}),
7437            observability: vec![ObservabilitySurface {
7438                name: "records.stats".to_string(),
7439                kind: ObservabilityKind::Snapshot,
7440            }],
7441            identity_scope: vec![IdentityScope::Project],
7442            concurrency: Concurrency::ModuleManaged,
7443        }];
7444        registry
7445            .register_with_control_ops(
7446                manifest,
7447                PROTOCOL_VERSION,
7448                ConnectionId::new(99),
7449                Vec::new(),
7450            )
7451            .expect("described management manifest registers");
7452
7453        let request = Frame::build(
7454            FrameType::Request,
7455            control_flags(),
7456            0,
7457            0,
7458            100,
7459            serde_json::to_vec(&ClientControlRequest::CatalogList { module_id: None })
7460                .expect("catalog request serializes"),
7461        )
7462        .expect("catalog request frame builds");
7463        let response = handler
7464            .handle_catalog_list(request, None)
7465            .expect("catalog list succeeds");
7466        let body: Value = serde_json::from_slice(&response[0].body).expect("catalog response JSON");
7467        assert_eq!(
7468            body["modules"][0]["roles"][0]["operations"][0]["description"], description,
7469            "catalog.list must preserve the declared operation description verbatim"
7470        );
7471    }
7472
7473    #[test]
7474    fn reserved_capability_refusal_mutation_proof_leaves_no_catalog_entry() {
7475        let registry = Arc::new(Registry::default());
7476        let handler = ControlHandler::new(Arc::clone(&registry)).with_capability_config(
7477            [("vault".to_string(), true), ("squatter".to_string(), true)],
7478            BTreeMap::from([("credentials-provider/v1".to_string(), "vault".to_string())]),
7479        );
7480        let mut squatter = manifest("squatter", PROTOCOL_VERSION);
7481        squatter.capabilities = Some(subc_protocol::manifest::CapabilityDeclarations {
7482            provides: vec!["credentials-provider/v1".to_string()],
7483            requires: Vec::new(),
7484            must_never_reach: Vec::new(),
7485        });
7486        let frame = Frame::build(
7487            FrameType::Hello,
7488            control_flags(),
7489            0,
7490            0,
7491            77,
7492            serde_json::to_vec(&ModuleHelloBody {
7493                manifest: squatter,
7494                protocol_ver: PROTOCOL_VERSION,
7495                control_ops: None,
7496                launch_nonce: None,
7497            })
7498            .expect("HELLO serializes"),
7499        )
7500        .expect("HELLO frame builds");
7501        let response = handler
7502            .handle_control(ConnectionId::new(77), frame)
7503            .expect("reserved claim receives a typed refusal");
7504        assert_eq!(parse_error(&response[0])["code"], "reserved_capability");
7505        assert_eq!(
7506            registry
7507                .active_registration_count()
7508                .expect("registry reads"),
7509            0,
7510            "a reserved capability refusal must not leave a catalog entry"
7511        );
7512    }
7513
7514    #[test]
7515    fn stale_relay_settlement_cannot_release_the_half_open_probe() {
7516        for settlement in ["timeout", "inconclusive", "drop"] {
7517            let breakers = RouteBindBreakers::default();
7518            let RouteBindAdmission::Admitted {
7519                guard: mut old,
7520                probe: false,
7521            } = breakers.admit("prov")
7522            else {
7523                panic!("ordinary relay admitted")
7524            };
7525            let RouteBindAdmission::Admitted {
7526                guard: mut opener, ..
7527            } = breakers.admit("prov")
7528            else {
7529                panic!("second relay admitted")
7530            };
7531            assert!(
7532                !opener
7533                    .record_timeout(1, Duration::ZERO)
7534                    .unwrap()
7535                    .reopened_after_probe
7536            );
7537            let RouteBindAdmission::Admitted {
7538                guard: mut probe,
7539                probe: true,
7540            } = breakers.admit("prov")
7541            else {
7542                panic!("one cooldown probe admitted")
7543            };
7544            match settlement {
7545                "timeout" => assert!(
7546                    !old.record_timeout(1, Duration::ZERO)
7547                        .unwrap()
7548                        .reopened_after_probe
7549                ),
7550                "inconclusive" => old.record_inconclusive(),
7551                "drop" => drop(old),
7552                _ => unreachable!(),
7553            }
7554            assert!(
7555                matches!(
7556                    breakers.admit("prov"),
7557                    RouteBindAdmission::Refused {
7558                        probe_in_flight: true,
7559                        ..
7560                    }
7561                ),
7562                "{settlement} of a pre-open relay cannot release the real probe"
7563            );
7564            assert!(
7565                probe
7566                    .record_timeout(1, Duration::ZERO)
7567                    .unwrap()
7568                    .reopened_after_probe
7569            );
7570            assert!(matches!(
7571                breakers.admit("prov"),
7572                RouteBindAdmission::Admitted { probe: true, .. }
7573            ));
7574        }
7575        let breakers = RouteBindBreakers::default();
7576        let admit = || match breakers.admit("prov") {
7577            RouteBindAdmission::Admitted { guard, .. } => guard,
7578            _ => panic!("relay admitted"),
7579        };
7580        admit().record_timeout(1, Duration::ZERO);
7581        let mut old_probe = admit();
7582        breakers.reset_for_new_module_connection("prov");
7583        admit().record_timeout(1, Duration::ZERO);
7584        let _new_probe = admit();
7585        old_probe.record_inconclusive();
7586        assert!(matches!(
7587            breakers.admit("prov"),
7588            RouteBindAdmission::Refused {
7589                probe_in_flight: true,
7590                ..
7591            }
7592        ));
7593    }
7594
7595    #[tokio::test]
7596    async fn catalog_update_refuses_reserved_capabilities_for_active_and_candidate() {
7597        for candidate in [false, true] {
7598            let registry = Arc::new(Registry::default());
7599            let handler = ControlHandler::new(Arc::clone(&registry)).with_capability_config(
7600                [("vault".to_string(), true), ("squatter".to_string(), true)],
7601                BTreeMap::from([("credentials-provider/v1".to_string(), "vault".to_string())]),
7602            );
7603            let conn = ConnectionId::new(77);
7604            let (ctx, mut rx) = route_ctx(conn);
7605            let initial = capability_manifest("squatter", &[], &[]);
7606            if candidate {
7607                registry
7608                    .register_candidate_with_control_ops(
7609                        initial.clone(),
7610                        PROTOCOL_VERSION,
7611                        conn,
7612                        module_baseline_control_ops(),
7613                    )
7614                    .unwrap();
7615                handler
7616                    .forwarding
7617                    .register_candidate_module_connection(
7618                        conn,
7619                        "squatter".to_string(),
7620                        PROTOCOL_VERSION,
7621                        manifest_concurrency(&initial),
7622                        ctx.egress.clone(),
7623                    )
7624                    .unwrap();
7625            } else {
7626                hello_via_sink(
7627                    &handler,
7628                    &ctx,
7629                    &mut rx,
7630                    hello_frame_with_manifest(initial.clone(), 1),
7631                )
7632                .await;
7633            }
7634            let response = handler
7635                .handle_control_frame(
7636                    &ctx,
7637                    catalog_update_with_capabilities_frame(
7638                        2,
7639                        capability_manifest("squatter", &["credentials-provider/v1"], &[])
7640                            .capabilities
7641                            .unwrap(),
7642                    ),
7643                )
7644                .await
7645                .unwrap();
7646            assert_eq!(parse_error(&response[0])["code"], "reserved_capability");
7647            assert_eq!(
7648                registry
7649                    .get_module_by_connection(conn)
7650                    .unwrap()
7651                    .unwrap()
7652                    .manifest,
7653                initial
7654            );
7655        }
7656    }
7657
7658    #[test]
7659    fn server_describe_surfaces_required_capability_verdict_fields() {
7660        let registry = Arc::new(Registry::default());
7661        let handler = ControlHandler::new(Arc::clone(&registry)).with_capability_config(
7662            [
7663                ("consumer".to_string(), true),
7664                ("provider".to_string(), false),
7665            ],
7666            BTreeMap::new(),
7667        );
7668        let mut consumer = manifest("consumer", PROTOCOL_VERSION);
7669        consumer.capabilities = Some(subc_protocol::manifest::CapabilityDeclarations {
7670            provides: Vec::new(),
7671            requires: vec![subc_protocol::manifest::CapabilityRequirement {
7672                capability: "credentials-provider/v1".to_string(),
7673                need: subc_protocol::manifest::CapabilityNeed::Required,
7674            }],
7675            must_never_reach: Vec::new(),
7676        });
7677        let hello = Frame::build(
7678            FrameType::Hello,
7679            control_flags(),
7680            0,
7681            0,
7682            78,
7683            serde_json::to_vec(&ModuleHelloBody {
7684                manifest: consumer,
7685                protocol_ver: PROTOCOL_VERSION,
7686                control_ops: None,
7687                launch_nonce: None,
7688            })
7689            .expect("HELLO serializes"),
7690        )
7691        .expect("HELLO frame builds");
7692        handler
7693            .handle_control(ConnectionId::new(78), hello)
7694            .expect("consumer registers");
7695        let describe = Frame::build(
7696            FrameType::Request,
7697            control_flags(),
7698            0,
7699            0,
7700            79,
7701            serde_json::to_vec(&ClientControlRequest::ServerDescribe {})
7702                .expect("request serializes"),
7703        )
7704        .expect("describe frame builds");
7705        let response = handler
7706            .handle_server_describe(describe)
7707            .expect("server.describe succeeds");
7708        let rendered: Value = serde_json::from_slice(&response[0].body).expect("response JSON");
7709        let requirement = &rendered["capability_requirements"][0];
7710        assert_eq!(requirement["consumer"], "consumer");
7711        assert_eq!(requirement["verdict"], "never_provided");
7712        assert_eq!(requirement["episode_seq"], 1);
7713        assert_eq!(requirement["config_satisfiable"], false);
7714        assert_eq!(requirement["runtime_available"], false);
7715        assert!(requirement["detail"]
7716            .as_str()
7717            .expect("detail string")
7718            .contains("credentials-provider/v1"));
7719    }
7720
7721    #[test]
7722    fn catalog_list_omits_capabilities_for_legacy_manifest() {
7723        let registry = Arc::new(Registry::default());
7724        let handler = ControlHandler::new(Arc::clone(&registry));
7725        let hello = handler
7726            .handle_control(
7727                ConnectionId::new(101),
7728                hello_frame("legacy-capability-manifest", PROTOCOL_VERSION, 101),
7729            )
7730            .expect("legacy HELLO registers");
7731        assert_eq!(hello[0].header.ty, FrameType::HelloAck);
7732
7733        let request = Frame::build(
7734            FrameType::Request,
7735            control_flags(),
7736            0,
7737            0,
7738            102,
7739            serde_json::to_vec(&ClientControlRequest::CatalogList { module_id: None })
7740                .expect("catalog request serializes"),
7741        )
7742        .expect("catalog request frame builds");
7743        let response = handler
7744            .handle_catalog_list(request, None)
7745            .expect("catalog list succeeds");
7746        let body: Value = serde_json::from_slice(&response[0].body).expect("catalog response JSON");
7747        assert!(
7748            body["modules"][0].get("capabilities").is_none(),
7749            "legacy manifest must retain an absent capabilities field on catalog.list"
7750        );
7751    }
7752
7753    #[test]
7754    fn hello_ack_omits_storage_when_no_storage_config() {
7755        let registry = Arc::new(Registry::default());
7756        let handler = ControlHandler::new(Arc::clone(&registry));
7757        let responses = handler
7758            .handle_control(
7759                ConnectionId::new(1),
7760                hello_frame("aft", PROTOCOL_VERSION, 7),
7761            )
7762            .unwrap();
7763        let ack = parse_ack(&responses[0]);
7764        assert_eq!(ack.storage, None, "no storage config -> no descriptor");
7765        assert_eq!(ack.machine_id, None, "no machine id configured -> no field");
7766    }
7767
7768    #[tokio::test]
7769    async fn hello_ack_and_server_describe_carry_the_configured_machine_id() {
7770        let id = crate::machine_id::MachineId::parse("0123456789abcdef0123456789abcdef").unwrap();
7771        let registry = Arc::new(Registry::default());
7772        let handler = ControlHandler::new(Arc::clone(&registry)).with_machine_id(Some(id.clone()));
7773        let responses = handler
7774            .handle_control(
7775                ConnectionId::new(1),
7776                hello_frame("aft", PROTOCOL_VERSION, 7),
7777            )
7778            .unwrap();
7779        let ack = parse_ack(&responses[0]);
7780        assert_eq!(ack.machine_id.as_deref(), Some(id.as_str()));
7781
7782        let described = handler
7783            .handle_control_frame(
7784                &route_ctx(ConnectionId::new(2)).0,
7785                Frame::build(
7786                    FrameType::Request,
7787                    control_flags(),
7788                    0,
7789                    0,
7790                    9,
7791                    serde_json::to_vec(&ClientControlRequest::ServerDescribe {}).unwrap(),
7792                )
7793                .unwrap(),
7794            )
7795            .await
7796            .unwrap();
7797        let ClientControlResponse::ServerDescribe { machine_id, .. } =
7798            serde_json::from_slice(&described[0].body).unwrap()
7799        else {
7800            panic!("server.describe answered with another shape");
7801        };
7802        assert_eq!(machine_id.as_deref(), Some(id.as_str()));
7803    }
7804
7805    #[test]
7806    fn hello_ack_delivers_resolved_storage_descriptor_per_module() {
7807        // With a central sqlite storage policy, each registering module gets its
7808        // own resolved descriptor in HELLO_ACK, keyed by its module id.
7809        let registry = Arc::new(Registry::default());
7810        let handler = ControlHandler::new(Arc::clone(&registry)).with_storage_config(Some(
7811            crate::daemon_config::StorageConfig::Sqlite {
7812                data_home: std::path::PathBuf::from("/data"),
7813            },
7814        ));
7815
7816        let responses = handler
7817            .handle_control(
7818                ConnectionId::new(1),
7819                hello_frame("alfonso-routing", PROTOCOL_VERSION, 7),
7820            )
7821            .unwrap();
7822        let ack = parse_ack(&responses[0]);
7823        assert_eq!(
7824            ack.storage,
7825            Some(serde_json::json!({
7826                "module_id": "alfonso-routing",
7827                "storage_namespace": "default",
7828                "isolation": { "kind": "module" },
7829                "backend": {
7830                    "backend": "sqlite",
7831                    "path": "/data/cortexkit/alfonso-routing/store.db"
7832                }
7833            })),
7834            "the delivered descriptor is the module's own sqlite store path"
7835        );
7836    }
7837
7838    #[test]
7839    fn hello_control_ops_none_is_baseline_and_guard_rejects_synthetic_gated_op() {
7840        let registry = Arc::new(Registry::default());
7841        let handler = ControlHandler::new(Arc::clone(&registry));
7842        let conn = ConnectionId::new(1);
7843        let responses = handler
7844            .handle_control(
7845                conn,
7846                hello_frame_with_control_ops("aft", PROTOCOL_VERSION, 7, None),
7847            )
7848            .unwrap();
7849        assert_eq!(responses[0].header.ty, FrameType::HelloAck);
7850        let registration = registry.get_module("aft").unwrap().unwrap();
7851        assert_eq!(registration.control_ops, module_baseline_control_ops());
7852
7853        let frame =
7854            Frame::build(FrameType::Request, control_flags(), 0, 0, 77, Vec::new()).unwrap();
7855        assert!(handler
7856            .guard_module_control_op(&frame, "aft", "route.bind")
7857            .unwrap()
7858            .is_none());
7859        let error = handler
7860            .guard_module_control_op(&frame, "aft", "test.synthetic")
7861            .unwrap()
7862            .expect("synthetic ungranted op should be rejected");
7863        assert_eq!(error.header.ty, FrameType::Error);
7864        assert_eq!(parse_error(&error)["code"], "op_not_allowed");
7865    }
7866
7867    #[test]
7868    fn hello_control_ops_some_adds_optional_grants() {
7869        let registry = Arc::new(Registry::default());
7870        let handler = ControlHandler::new(Arc::clone(&registry));
7871        handler
7872            .handle_control(
7873                ConnectionId::new(1),
7874                hello_frame_with_control_ops(
7875                    "aft",
7876                    PROTOCOL_VERSION,
7877                    7,
7878                    Some(vec![
7879                        "future.synthetic".to_string(),
7880                        "route.bind".to_string(),
7881                    ]),
7882                ),
7883            )
7884            .unwrap();
7885        let registration = registry.get_module("aft").unwrap().unwrap();
7886        assert_eq!(
7887            registration.control_ops,
7888            vec![
7889                "route.bind".to_string(),
7890                "route.status".to_string(),
7891                "future.synthetic".to_string(),
7892            ]
7893        );
7894        let frame =
7895            Frame::build(FrameType::Request, control_flags(), 0, 0, 78, Vec::new()).unwrap();
7896        assert!(handler
7897            .guard_module_control_op(&frame, "aft", "future.synthetic")
7898            .unwrap()
7899            .is_none());
7900    }
7901
7902    #[tokio::test]
7903    async fn health_probe_refuses_unadvertised_module_without_sending_frame() {
7904        let registry = Arc::new(Registry::default());
7905        let forwarding = Arc::new(ForwardingTable::default());
7906        let handler =
7907            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
7908        let (module_ctx, mut module_rx) = route_ctx(ConnectionId::new(10));
7909        hello_via_sink(
7910            &handler,
7911            &module_ctx,
7912            &mut module_rx,
7913            hello_frame_with_control_ops("aft", PROTOCOL_VERSION, 7, None),
7914        )
7915        .await;
7916
7917        let (client_ctx, _client_rx) = route_ctx(ConnectionId::new(20));
7918        let responses = handler
7919            .handle_control_frame(&client_ctx, supervisor_health_probe_frame(77, "aft"))
7920            .await
7921            .unwrap();
7922        assert_eq!(responses.len(), 1);
7923        assert_eq!(responses[0].header.ty, FrameType::Error);
7924        assert_eq!(parse_error(&responses[0])["code"], "health_not_advertised");
7925        assert!(module_rx.try_recv().is_err());
7926    }
7927
7928    #[tokio::test]
7929    async fn health_probe_demuxes_while_route_bind_relay_is_in_flight() {
7930        let registry = Arc::new(Registry::default());
7931        let forwarding = Arc::new(ForwardingTable::default());
7932        let handler =
7933            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
7934        let (module_ctx, mut module_rx) = route_ctx(ConnectionId::new(30));
7935        hello_via_sink(
7936            &handler,
7937            &module_ctx,
7938            &mut module_rx,
7939            hello_frame_with_control_ops(
7940                "aft",
7941                PROTOCOL_VERSION,
7942                7,
7943                Some(vec![MODULE_CONTROL_OP_HEALTH_CHECK.to_string()]),
7944            ),
7945        )
7946        .await;
7947
7948        let project_root = unique_project_root("demux");
7949        let (route_client_ctx, mut route_client_rx) = route_ctx(ConnectionId::new(31));
7950        let route_handler = handler.clone();
7951        let route_task = tokio::spawn(async move {
7952            route_handler
7953                .handle_control_frame(
7954                    &route_client_ctx,
7955                    route_open_frame(100, "aft", project_root),
7956                )
7957                .await
7958                .unwrap()
7959        });
7960        let bind_frame = tokio::time::timeout(Duration::from_secs(1), module_rx.recv())
7961            .await
7962            .unwrap()
7963            .unwrap();
7964        assert!(matches!(
7965            serde_json::from_slice::<ModuleControlRequest>(&bind_frame.body).unwrap(),
7966            ModuleControlRequest::RouteBind { .. }
7967        ));
7968
7969        let (health_client_ctx, _health_client_rx) = route_ctx(ConnectionId::new(32));
7970        let health_handler = handler.clone();
7971        let health_task = tokio::spawn(async move {
7972            health_handler
7973                .handle_control_frame(
7974                    &health_client_ctx,
7975                    supervisor_health_probe_frame(101, "aft"),
7976                )
7977                .await
7978                .unwrap()
7979        });
7980        let health_frame = tokio::time::timeout(Duration::from_secs(1), module_rx.recv())
7981            .await
7982            .unwrap()
7983            .unwrap();
7984        assert_eq!(
7985            serde_json::from_slice::<ModuleControlRequest>(&health_frame.body).unwrap(),
7986            ModuleControlRequest::HealthCheck {}
7987        );
7988
7989        handler
7990            .handle_control_frame(
7991                &module_ctx,
7992                health_response(health_frame.header.corr, HealthStatus::Degraded),
7993            )
7994            .await
7995            .unwrap();
7996        let health_response = health_task.await.unwrap();
7997        assert_eq!(health_response.len(), 1);
7998        match serde_json::from_slice::<ClientControlResponse>(&health_response[0].body).unwrap() {
7999            ClientControlResponse::SupervisorHealthProbe {
8000                module_id,
8001                status,
8002                detail,
8003                metrics,
8004            } => {
8005                assert_eq!(module_id, "aft");
8006                assert_eq!(status, HealthStatus::Degraded);
8007                assert_eq!(detail.as_deref(), Some("warming"));
8008                assert_eq!(metrics, Some(json!({"queue_depth": 3})));
8009            }
8010            other => panic!("unexpected health response: {other:?}"),
8011        }
8012
8013        handler
8014            .handle_control_frame(&module_ctx, route_bind_ack(bind_frame.header.corr))
8015            .await
8016            .unwrap();
8017        let route_response = route_task.await.unwrap();
8018        assert!(route_response.is_empty());
8019        let published = route_client_rx.recv().await.unwrap();
8020        assert!(matches!(
8021            serde_json::from_slice::<ClientControlResponse>(&published.body).unwrap(),
8022            ClientControlResponse::RouteOpen { .. }
8023        ));
8024    }
8025
8026    /// Start one `route.open` on `client_connection` and return its still-running
8027    /// handler task together with the `route.bind` the module received for it.
8028    /// The handler blocks until the module answers, so it has to run as a task
8029    /// while the test drives the module side.
8030    async fn relay_route_open(
8031        handler: &ControlHandler,
8032        client_connection: ConnectionId,
8033        client_egress: &FrameSink,
8034        module_rx: &mut mpsc::Receiver<crate::router::OutboundFrame>,
8035        corr: u64,
8036        module_id: &str,
8037        project_root_label: &str,
8038    ) -> (tokio::task::JoinHandle<Vec<Frame>>, Frame) {
8039        let ctx = RouteCtx {
8040            connection_id: client_connection,
8041            egress: client_egress.clone(),
8042        };
8043        let handler = handler.clone();
8044        let project_root = unique_project_root(project_root_label);
8045        let module_id = module_id.to_string();
8046        let dispatch = tracing::dispatcher::get_default(|dispatch| dispatch.clone());
8047        let task = tokio::spawn(async move {
8048            let _guard = tracing::dispatcher::set_default(&dispatch);
8049            handler
8050                .handle_control_frame(&ctx, route_open_frame(corr, &module_id, project_root))
8051                .await
8052                .unwrap()
8053        });
8054        let bind = tokio::time::timeout(Duration::from_secs(2), module_rx.recv())
8055            .await
8056            .expect("module receives the relayed route.bind")
8057            .expect("module egress is open");
8058        (task, bind.frame)
8059    }
8060
8061    fn route_bind_channel(frame: &Frame) -> (u16, u32) {
8062        match serde_json::from_slice::<ModuleControlRequest>(&frame.body).unwrap() {
8063            ModuleControlRequest::RouteBind {
8064                route_channel,
8065                epoch,
8066                ..
8067            } => (route_channel, epoch),
8068            other => panic!("expected a route.bind request, got {other:?}"),
8069        }
8070    }
8071
8072    fn published_route(frame: &Frame) -> (u16, u32) {
8073        match serde_json::from_slice::<ClientControlResponse>(&frame.body).unwrap() {
8074            ClientControlResponse::RouteOpen {
8075                route_channel,
8076                route_epoch,
8077            } => (route_channel, route_epoch),
8078            other => panic!("expected a route.open response, got {other:?}"),
8079        }
8080    }
8081
8082    /// Reproduction of a production outage. A client had `route.open`s in
8083    /// flight to a module and was already marked closing -- its egress had refused a
8084    /// module frame, so the daemon asked its connection to end -- while its sink
8085    /// was still open. When the module acked those binds, the daemon refused to
8086    /// commit a route for a closing client, and that refusal was returned from
8087    /// the MODULE connection's frame handler, where a router error that has no
8088    /// ERROR-frame translation ends the connection. The module saw EOF, exited 0,
8089    /// the supervisor correctly did not respawn a clean exit, and every seat lost
8090    /// its tools for hours -- one client's teardown took down a connection
8091    /// carrying ~170 other routes.
8092    ///
8093    /// The window is opened here by calling the production path that opens it
8094    /// (`escalate_client_delivery_failure`) rather than by closing a socket. The
8095    /// state that matters is "in `closing_connections`, sink still open, relay
8096    /// still pending", and it lasts only from the close request until the
8097    /// connection loop reacts to it; a socket-level test can flood a client into
8098    /// that escalation but cannot pin the module's ack inside the window. Closing
8099    /// the socket instead takes the other path entirely -- connection teardown
8100    /// removes the pending relay under the same lock, so the ack finds nothing.
8101    #[tokio::test]
8102    async fn late_bind_ack_for_a_closing_client_keeps_the_module_connection_serving() {
8103        let registry = Arc::new(Registry::default());
8104        let forwarding = Arc::new(ForwardingTable::default());
8105        let handler =
8106            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
8107
8108        let module_connection = ConnectionId::new(30);
8109        let (module_ctx, mut module_rx) = route_ctx(module_connection);
8110        hello_via_sink(
8111            &handler,
8112            &module_ctx,
8113            &mut module_rx,
8114            hello_frame("aft", PROTOCOL_VERSION, 7),
8115        )
8116        .await;
8117
8118        let dying_client = ConnectionId::new(31);
8119        let (dying_ctx, mut dying_rx) = route_ctx(dying_client);
8120
8121        // A published route on the dying client. The escalation below only marks
8122        // a connection closing for a route it has already published.
8123        let (first_task, first_bind) = relay_route_open(
8124            &handler,
8125            dying_client,
8126            &dying_ctx.egress,
8127            &mut module_rx,
8128            100,
8129            "aft",
8130            "closing-first",
8131        )
8132        .await;
8133        handler
8134            .handle_control_frame(&module_ctx, route_bind_ack(first_bind.header.corr))
8135            .await
8136            .unwrap();
8137        assert!(first_task.await.unwrap().is_empty());
8138        let (first_channel, first_epoch) = published_route(&dying_rx.recv().await.unwrap());
8139
8140        // A second route.open from the same client, relayed and awaiting its ack.
8141        let (second_task, second_bind) = relay_route_open(
8142            &handler,
8143            dying_client,
8144            &dying_ctx.egress,
8145            &mut module_rx,
8146            101,
8147            "aft",
8148            "closing-second",
8149        )
8150        .await;
8151        let (abandoned_channel, abandoned_epoch) = route_bind_channel(&second_bind);
8152
8153        // The window: the client is closing, its sink is still open, and its
8154        // second bind is still pending.
8155        assert!(forwarding
8156            .escalate_client_delivery_failure(
8157                dying_client,
8158                first_channel,
8159                first_epoch,
8160                CloseReason::new(
8161                    "module_to_client_delivery_failed",
8162                    "client egress refused a module frame",
8163                ),
8164                crate::forwarding::UndeliveredFrame {
8165                    module_id: None,
8166                    sink: &dying_ctx.egress,
8167                },
8168            )
8169            .unwrap());
8170        assert!(!dying_ctx.egress.is_closed());
8171
8172        // The frame that used to end the module connection.
8173        let ack = handler
8174            .handle_control_frame(&module_ctx, route_bind_ack(second_bind.header.corr))
8175            .await;
8176        let module_loop_error = ack.as_ref().err().map(ToString::to_string);
8177        if module_loop_error.is_some() {
8178            // What the server's connection loop does with a router error that has
8179            // no ERROR-frame translation: end the connection, which releases the
8180            // module's registration and every route on it.
8181            handler.cleanup_connection(module_connection).unwrap();
8182        }
8183        // Read the module's next frame before opening the co-tenant's route, so
8184        // the GOODBYE assertion below is about THIS ack and not about later
8185        // traffic. `None` means the module was told nothing.
8186        let post_ack_module_frame = tokio::time::timeout(Duration::from_secs(1), module_rx.recv())
8187            .await
8188            .ok()
8189            .flatten();
8190
8191        // 1. The module connection is still registered.
8192        assert!(
8193            registry
8194                .get_module_by_connection(module_connection)
8195                .unwrap()
8196                .is_some(),
8197            "one client's closing connection ended the shared module connection: \
8198             {module_loop_error:?}"
8199        );
8200        // ...and still serving: another client can open and use a route on it.
8201        let cotenant = ConnectionId::new(32);
8202        let (cotenant_ctx, mut cotenant_rx) = route_ctx(cotenant);
8203        let (cotenant_task, cotenant_bind) = relay_route_open(
8204            &handler,
8205            cotenant,
8206            &cotenant_ctx.egress,
8207            &mut module_rx,
8208            102,
8209            "aft",
8210            "closing-cotenant",
8211        )
8212        .await;
8213        handler
8214            .handle_control_frame(&module_ctx, route_bind_ack(cotenant_bind.header.corr))
8215            .await
8216            .unwrap();
8217        assert!(cotenant_task.await.unwrap().is_empty());
8218        let (cotenant_channel, cotenant_epoch) =
8219            published_route(&cotenant_rx.recv().await.unwrap());
8220        assert!(matches!(
8221            forwarding
8222                .lookup_data_route(cotenant, cotenant_channel, cotenant_epoch)
8223                .unwrap(),
8224            DataRoute::Client(DataRouteState::Bound(_))
8225        ));
8226
8227        // 2. The module was told to drop the binding it created for the route
8228        //    that will never be published.
8229        let goodbye = post_ack_module_frame
8230            .expect("module receives a GOODBYE for the abandoned route channel");
8231        assert_eq!(goodbye.header.ty, FrameType::Goodbye);
8232        assert_eq!(goodbye.header.channel, abandoned_channel);
8233        assert_eq!(goodbye.header.epoch, abandoned_epoch);
8234
8235        // 3. The dying client received nothing: no route was ever published to
8236        //    it. Its route.open is answered as unavailable, which the connection
8237        //    loop would write to a socket that is already going away.
8238        assert!(dying_rx.try_recv().is_err());
8239        let second_response = second_task.await.unwrap();
8240        assert_eq!(second_response.len(), 1);
8241        assert_eq!(
8242            parse_error(&second_response[0])["code"],
8243            "target_unavailable"
8244        );
8245    }
8246
8247    /// The fence at the module-loop boundary, stated as its own contract: which
8248    /// forwarding failures are allowed to end the module connection that is being
8249    /// served. A `ConnectionClosing` naming some client is about that client, and
8250    /// a module connection is shared; the same error naming the module's own
8251    /// connection is about this connection and must stay fatal, as must failures
8252    /// that are about the forwarding table itself.
8253    #[test]
8254    fn only_the_modules_own_closing_connection_ends_the_module_loop() {
8255        let handler = ControlHandler::default();
8256        let module_connection = ConnectionId::new(30);
8257        let client_connection = ConnectionId::new(31);
8258
8259        handler
8260            .refuse_to_end_module_connection_for_a_client(
8261                module_connection,
8262                77,
8263                ForwardingError::ConnectionClosing {
8264                    connection_id: client_connection,
8265                },
8266            )
8267            .expect("a closing client must never end the module connection");
8268
8269        assert!(matches!(
8270            handler.refuse_to_end_module_connection_for_a_client(
8271                module_connection,
8272                78,
8273                ForwardingError::ConnectionClosing {
8274                    connection_id: module_connection,
8275                },
8276            ),
8277            Err(RouterError::Forwarding(ForwardingError::ConnectionClosing {
8278                connection_id
8279            })) if connection_id == module_connection
8280        ));
8281        assert!(matches!(
8282            handler.refuse_to_end_module_connection_for_a_client(
8283                module_connection,
8284                79,
8285                ForwardingError::Poisoned,
8286            ),
8287            Err(RouterError::Forwarding(ForwardingError::Poisoned))
8288        ));
8289        assert!(matches!(
8290            handler.refuse_to_end_module_connection_for_a_client(
8291                module_connection,
8292                80,
8293                ForwardingError::StaleModuleEndpoint,
8294            ),
8295            Err(RouterError::Forwarding(
8296                ForwardingError::StaleModuleEndpoint
8297            ))
8298        ));
8299    }
8300
8301    /// The spawn-attestation guard is what stops a connected module from claiming
8302    /// another module's identity and being stamped `Reserved` for it. Every other
8303    /// test that supplies a consumer_identity supplies a CORRECT one, because a
8304    /// correct one is what the rest of the flow needs -- so the guard's rejection
8305    /// branch was never the subject of an assertion, only its acceptance branch.
8306    ///
8307    /// Deleting the guard's EFFECT (granting Reserved unconditionally) leaves the
8308    /// whole subc-core library suite green; only the forwarding integration tests
8309    /// notice, and they notice for unrelated reasons. This test exists so the
8310    /// refusal itself is asserted where the guard lives: it fails if the identity
8311    /// check stops refusing, which is the direction that matters, since a guard
8312    /// that wrongly ACCEPTS is silent while one that wrongly REJECTS is loud.
8313    #[tokio::test]
8314    async fn route_open_refuses_consumer_identity_that_fails_spawn_attestation() {
8315        let registry = Arc::new(Registry::default());
8316        let forwarding = Arc::new(ForwardingTable::default());
8317        let supervisor = SupervisorHandle::new();
8318        supervisor.set_spawn_nonce("fed", "fed-nonce".to_string());
8319        let handler =
8320            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
8321                .with_supervisor(supervisor);
8322
8323        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(90));
8324        hello_via_sink(
8325            &handler,
8326            &target_ctx,
8327            &mut target_rx,
8328            hello_frame("target", PROTOCOL_VERSION, 1),
8329        )
8330        .await;
8331
8332        // A real supervised module id presenting the wrong nonce. This is the
8333        // impersonation case: the attacker knows a privileged module_id, which is
8334        // public, and guesses at the nonce, which is not.
8335        let wrong_nonce = handler
8336            .handle_control_frame(
8337                &route_ctx(ConnectionId::new(91)).0,
8338                route_open_frame_with_admission_facts(
8339                    20,
8340                    "target",
8341                    unique_project_root("admission-facts"),
8342                    Some(subc_control::ConsumerIdentity {
8343                        module_id: "fed".to_string(),
8344                        launch_nonce: "not-the-real-nonce".to_string(),
8345                    }),
8346                    None,
8347                ),
8348            )
8349            .await
8350            .unwrap();
8351        assert_eq!(
8352            parse_error(&wrong_nonce[0])["code"],
8353            "bad_consumer_identity",
8354            "a mismatched launch nonce must be refused, not stamped Reserved"
8355        );
8356
8357        // A module id the supervisor never spawned at all, so no nonce exists to
8358        // compare against. An implementation that treats "no record" as "nothing
8359        // to check" fails open here while passing the case above.
8360        let never_spawned = handler
8361            .handle_control_frame(
8362                &route_ctx(ConnectionId::new(92)).0,
8363                route_open_frame_with_admission_facts(
8364                    21,
8365                    "target",
8366                    unique_project_root("admission-facts"),
8367                    Some(subc_control::ConsumerIdentity {
8368                        module_id: "never-spawned".to_string(),
8369                        launch_nonce: "any-nonce".to_string(),
8370                    }),
8371                    None,
8372                ),
8373            )
8374            .await
8375            .unwrap();
8376        assert_eq!(
8377            parse_error(&never_spawned[0])["code"],
8378            "bad_consumer_identity",
8379            "an unspawned module_id must be refused rather than accepted for lack of a record"
8380        );
8381    }
8382
8383    /// The refusal test above proves the guard says NO. Nothing proved it can say
8384    /// YES, and the difference is not academic: replacing the whole authorization
8385    /// with `false` -- admitting no consumer identity at all, revoking Reserved
8386    /// standing for every supervised module in the fleet -- leaves 110 of the 111
8387    /// library tests GREEN. The one that notices does so by HANGING, because it
8388    /// waits for a bind that can no longer happen.
8389    ///
8390    /// A hang is the weakest signal a suite can produce. In CI it reads as a slow
8391    /// or flaky test, invites a RETRY rather than an investigation, and the retry
8392    /// hangs too and gets blamed on the runner. So a total revocation of the
8393    /// daemon's trust grant would have shipped behind a symptom nobody attributes
8394    /// to code.
8395    ///
8396    /// The bias is structural rather than accidental. A REFUSAL looks like a
8397    /// failure someone writes a test for; a GRANT looks like the happy path. Every
8398    /// binary-outcome guard whose STRICTNESS is the point acquires a refusal-heavy
8399    /// suite for that reason, and this one is the purest case in the daemon.
8400    ///
8401    /// This test asserts the EFFECT rather than the absence of an error: the module
8402    /// receives a RouteBind and it carries `Reserved` naming the attested module.
8403    /// A guard that admitted nobody would produce no bind at all; one that admitted
8404    /// everybody would stamp the wrong principal, which the refusal test catches.
8405    #[tokio::test]
8406    async fn route_open_stamps_reserved_for_a_correctly_attested_consumer() {
8407        let registry = Arc::new(Registry::default());
8408        let forwarding = Arc::new(ForwardingTable::default());
8409        let supervisor = SupervisorHandle::new();
8410        supervisor.set_spawn_nonce("fed", "fed-nonce".to_string());
8411        let handler =
8412            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
8413                .with_supervisor(supervisor);
8414
8415        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(95));
8416        hello_via_sink(
8417            &handler,
8418            &target_ctx,
8419            &mut target_rx,
8420            hello_frame("target", PROTOCOL_VERSION, 1),
8421        )
8422        .await;
8423
8424        let (client_ctx, mut client_rx) = route_ctx(ConnectionId::new(96));
8425        let route_handler = handler.clone();
8426        let route_task = tokio::spawn(async move {
8427            route_handler
8428                .handle_control_frame(
8429                    &client_ctx,
8430                    route_open_frame_with_admission_facts(
8431                        30,
8432                        "target",
8433                        unique_project_root("admission-facts"),
8434                        Some(subc_control::ConsumerIdentity {
8435                            module_id: "fed".to_string(),
8436                            launch_nonce: "fed-nonce".to_string(),
8437                        }),
8438                        None,
8439                    ),
8440                )
8441                .await
8442                .unwrap()
8443        });
8444
8445        // BOUND THE WAIT. The first version of this test recv'd unbounded, and under
8446        // the very mutation it exists to catch -- a guard that admits nobody -- no
8447        // bind is ever sent, so it HUNG rather than failing. That reproduces the
8448        // exact defect being fixed: a total revocation detected only as a stalled
8449        // suite, which reads as flakiness and invites a retry. An acceptance test
8450        // that waits for an effect must bound the wait, or a red becomes a hang.
8451        let bind_frame = tokio::time::timeout(Duration::from_secs(5), target_rx.recv())
8452            .await
8453            .expect("no route.bind within 5s: the consumer-identity guard refused a correctly attested consumer")
8454            .expect("module control channel closed before route.bind");
8455        let bind: ModuleControlRequest = serde_json::from_slice(&bind_frame.body).unwrap();
8456        let ModuleControlRequest::RouteBind { principal, .. } = bind else {
8457            panic!("expected route.bind")
8458        };
8459        assert_eq!(
8460            principal,
8461            Some(Principal::Reserved {
8462                module_id: "fed".to_string()
8463            }),
8464            "a correctly attested consumer must be stamped Reserved for its own id"
8465        );
8466
8467        handler
8468            .handle_control_frame(&target_ctx, route_bind_ack(bind_frame.header.corr))
8469            .await
8470            .unwrap();
8471        assert!(route_task.await.unwrap().is_empty());
8472        assert!(
8473            matches!(
8474                serde_json::from_slice::<ClientControlResponse>(
8475                    &client_rx.recv().await.unwrap().body
8476                )
8477                .unwrap(),
8478                ClientControlResponse::RouteOpen { .. }
8479            ),
8480            "the route must actually open, not merely avoid an error"
8481        );
8482    }
8483
8484    #[tokio::test(start_paused = true)]
8485    async fn supervisor_routes_serializes_live_draining_bindings_from_the_real_handler() {
8486        let registry = Arc::new(Registry::default());
8487        let forwarding = Arc::new(ForwardingTable::default());
8488        let supervisor = SupervisorHandle::new();
8489        supervisor.set_spawn_nonce("fed", "fed-nonce".to_string());
8490        let handler =
8491            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
8492                .with_supervisor(supervisor);
8493
8494        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(101));
8495        hello_via_sink(
8496            &handler,
8497            &target_ctx,
8498            &mut target_rx,
8499            hello_frame("target", PROTOCOL_VERSION, 1),
8500        )
8501        .await;
8502
8503        let (direct_ctx, mut direct_rx) = route_ctx(ConnectionId::new(102));
8504        let direct_handler = handler.clone();
8505        let direct_open = tokio::spawn(async move {
8506            direct_handler
8507                .handle_control_frame(
8508                    &direct_ctx,
8509                    route_open_frame(2, "target", unique_project_root("route-census-direct")),
8510                )
8511                .await
8512                .unwrap()
8513        });
8514        let direct_bind = tokio::time::timeout(Duration::from_secs(5), target_rx.recv())
8515            .await
8516            .expect("no direct route.bind within 5s")
8517            .expect("target control channel closed before direct route.bind");
8518        handler
8519            .handle_control_frame(&target_ctx, route_bind_ack(direct_bind.header.corr))
8520            .await
8521            .unwrap();
8522        assert!(direct_open.await.unwrap().is_empty());
8523        let _ = direct_rx.recv().await.unwrap();
8524
8525        let (reserved_ctx, mut reserved_rx) = route_ctx(ConnectionId::new(103));
8526        let reserved_handler = handler.clone();
8527        let reserved_open = tokio::spawn(async move {
8528            reserved_handler
8529                .handle_control_frame(
8530                    &reserved_ctx,
8531                    route_open_frame_with_admission_facts(
8532                        3,
8533                        "target",
8534                        unique_project_root("admission-facts"),
8535                        Some(ConsumerIdentity {
8536                            module_id: "fed".to_string(),
8537                            launch_nonce: "fed-nonce".to_string(),
8538                        }),
8539                        None,
8540                    ),
8541                )
8542                .await
8543                .unwrap()
8544        });
8545        let reserved_bind = tokio::time::timeout(Duration::from_secs(5), target_rx.recv())
8546            .await
8547            .expect("no reserved route.bind within 5s")
8548            .expect("target control channel closed before reserved route.bind");
8549        handler
8550            .handle_control_frame(&target_ctx, route_bind_ack(reserved_bind.header.corr))
8551            .await
8552            .unwrap();
8553        assert!(reserved_open.await.unwrap().is_empty());
8554        let _ = reserved_rx.recv().await.unwrap();
8555
8556        forwarding
8557            .begin_module_drain("target", subc_control::RouteCloseReason::Reload)
8558            .unwrap();
8559        let (census_ctx, _census_rx) = route_ctx(ConnectionId::new(104));
8560        let census_body = serde_json::to_vec(&ClientControlRequest::SupervisorRoutes {
8561            module_id: Some("target".to_string()),
8562        })
8563        .unwrap();
8564        let census_frame =
8565            Frame::build(FrameType::Request, control_flags(), 0, 0, 4, census_body).unwrap();
8566        let response = handler
8567            .handle_control_frame(&census_ctx, census_frame)
8568            .await
8569            .unwrap()
8570            .pop()
8571            .unwrap();
8572        let actual: Value = serde_json::from_slice(&response.body).unwrap();
8573        let decoded: ClientControlResponse = serde_json::from_value(actual.clone()).unwrap();
8574        assert!(matches!(
8575            decoded,
8576            ClientControlResponse::SupervisorRoutes { .. }
8577        ));
8578        let routes = actual["modules"][0]["routes"].as_array().unwrap();
8579        assert_eq!(routes.len(), 2);
8580        assert!(routes.iter().all(|route| route["draining"] == true));
8581        // The census carries WHY: the reason the drain was begun with, in the
8582        // route.closing vocabulary, on every draining route this drain marked.
8583        assert!(
8584            routes.iter().all(|route| route["drain_reason"] == "reload"),
8585            "draining routes must name the drain's reason: {routes:?}"
8586        );
8587        assert!(routes.iter().any(|route| {
8588            route["consumer"] == serde_json::json!({"kind": "direct", "connection_id": 102})
8589        }));
8590        assert!(routes.iter().any(|route| {
8591            route["consumer"] == serde_json::json!({"kind": "reserved", "module_id": "fed"})
8592        }));
8593
8594        let golden_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
8595            .join("../subc-control/tests/golden/client_control_response_supervisor_routes.json");
8596        if std::env::var_os("UPDATE_GOLDEN").is_some() {
8597            std::fs::write(
8598                &golden_path,
8599                format!("{}\n", serde_json::to_string_pretty(&actual).unwrap()),
8600            )
8601            .unwrap();
8602        }
8603        let expected: Value =
8604            serde_json::from_str(&std::fs::read_to_string(golden_path).unwrap()).unwrap();
8605        assert_eq!(actual, expected);
8606    }
8607
8608    async fn query_live_roots(
8609        handler: &ControlHandler,
8610        module_ctx: &RouteCtx,
8611    ) -> ModuleControlResponseToModule {
8612        let body = serde_json::to_vec(&ModuleControlRequestFromModule::LiveRoots {}).unwrap();
8613        let frame = Frame::build(FrameType::Request, control_flags(), 0, 0, 900, body).unwrap();
8614        let response = handler
8615            .handle_control_frame(module_ctx, frame)
8616            .await
8617            .unwrap()
8618            .pop()
8619            .unwrap();
8620        serde_json::from_slice(&response.body).unwrap()
8621    }
8622
8623    #[tokio::test(start_paused = true)]
8624    async fn supervisor_live_roots_root_known_arm_counts_bound_and_pending_from_real_handler() {
8625        let registry = Arc::new(Registry::default());
8626        let forwarding = Arc::new(ForwardingTable::default());
8627        let handler = ControlHandler::with_forwarding(registry, forwarding);
8628        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(301));
8629        hello_via_sink(
8630            &handler,
8631            &target_ctx,
8632            &mut target_rx,
8633            hello_frame("target", PROTOCOL_VERSION, 1),
8634        )
8635        .await;
8636        let root = unique_project_root("live-roots-known");
8637        let path = ProjectRootId::from_path_allowing_missing(root.path())
8638            .unwrap()
8639            .as_path()
8640            .to_path_buf();
8641        let (client_ctx, mut client_rx) = route_ctx(ConnectionId::new(302));
8642        let open_handler = handler.clone();
8643        let opened = tokio::spawn(async move {
8644            open_handler
8645                .handle_control_frame(&client_ctx, route_open_frame(2, "target", root))
8646                .await
8647                .unwrap()
8648        });
8649        let bind = tokio::time::timeout(Duration::from_secs(5), target_rx.recv())
8650            .await
8651            .unwrap()
8652            .unwrap();
8653        handler
8654            .handle_control_frame(&target_ctx, route_bind_ack(bind.header.corr))
8655            .await
8656            .unwrap();
8657        assert!(opened.await.unwrap().is_empty());
8658        let _ = client_rx.recv().await.unwrap();
8659
8660        let root = unique_project_root("live-roots-pending");
8661        let pending_path = ProjectRootId::from_path_allowing_missing(root.path())
8662            .unwrap()
8663            .as_path()
8664            .to_path_buf();
8665        let (client_ctx, _client_rx) = route_ctx(ConnectionId::new(303));
8666        let open_handler = handler.clone();
8667        let pending = tokio::spawn(async move {
8668            open_handler
8669                .handle_control_frame(&client_ctx, route_open_frame(3, "target", root))
8670                .await
8671                .unwrap()
8672        });
8673        let pending_bind = tokio::time::timeout(Duration::from_secs(5), target_rx.recv())
8674            .await
8675            .unwrap()
8676            .unwrap();
8677        let actual = query_live_roots(&handler, &target_ctx).await;
8678        let ModuleControlResponseToModule::LiveRoots {
8679            roots,
8680            unknown_root_bindings,
8681            total_bindings,
8682        } = actual
8683        else {
8684            panic!("expected live roots")
8685        };
8686        assert_eq!(total_bindings, 2, "root-known arm must count live routes");
8687        assert_eq!(unknown_root_bindings, 0);
8688        assert_eq!(
8689            roots.len(),
8690            2,
8691            "root-known arm must retain each canonical root"
8692        );
8693        assert_eq!(
8694            total_bindings,
8695            roots.iter().map(|r| r.bound + r.pending).sum::<u64>() + unknown_root_bindings
8696        );
8697        let counts = roots
8698            .iter()
8699            .map(|root| (root.project_root.clone(), root.bound, root.pending))
8700            .collect::<Vec<_>>();
8701        let mut expected = vec![(path, 1, 0), (pending_path, 0, 1)];
8702        expected.sort_by(|a, b| a.0.cmp(&b.0));
8703        assert_eq!(
8704            counts, expected,
8705            "roots must sort by path and count pending separately"
8706        );
8707        handler
8708            .handle_control_frame(&target_ctx, route_bind_ack(pending_bind.header.corr))
8709            .await
8710            .unwrap();
8711        assert!(pending.await.unwrap().is_empty());
8712    }
8713
8714    #[tokio::test(start_paused = true)]
8715    async fn supervisor_live_roots_unknown_root_arm_is_not_no_bindings() {
8716        let forwarding = Arc::new(ForwardingTable::default());
8717        let handler =
8718            ControlHandler::with_forwarding(Arc::new(Registry::default()), Arc::clone(&forwarding));
8719        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(311));
8720        hello_via_sink(
8721            &handler,
8722            &target_ctx,
8723            &mut target_rx,
8724            hello_frame("target", PROTOCOL_VERSION, 1),
8725        )
8726        .await;
8727        let (client_ctx, _client_rx) = route_ctx(ConnectionId::new(312));
8728        let pending = forwarding
8729            .begin_route_bind_relay_for_test(
8730                client_ctx.connection_id,
8731                client_ctx.egress.clone(),
8732                2,
8733                "target",
8734            )
8735            .unwrap();
8736        forwarding
8737            .complete_pending_relay(
8738                target_ctx.connection_id,
8739                pending.corr,
8740                RouteBindRelayOutcome::Accepted,
8741            )
8742            .unwrap();
8743        let actual = query_live_roots(&handler, &target_ctx).await;
8744        let ModuleControlResponseToModule::LiveRoots {
8745            roots,
8746            unknown_root_bindings,
8747            total_bindings,
8748        } = actual
8749        else {
8750            panic!("expected live roots")
8751        };
8752        assert!(roots.is_empty(), "unknown-root arm must not invent a root");
8753        assert_eq!(
8754            unknown_root_bindings, 1,
8755            "unknown-root arm must not read as no bindings"
8756        );
8757        assert_eq!(total_bindings, 1, "unknown-root arm has a live binding");
8758        assert_eq!(
8759            total_bindings,
8760            roots.iter().map(|r| r.bound + r.pending).sum::<u64>() + unknown_root_bindings
8761        );
8762    }
8763
8764    /// A module reads HELLO_ACK as its first frame and exits on anything else,
8765    /// so the ack has to be on its outbound queue before the module is
8766    /// routable. The connection loop writes a handler's replies only after the
8767    /// handler returns; this test stops in exactly that gap, runs a real
8768    /// route.open from another connection, and only then writes whatever the
8769    /// HELLO handler returned, the way the loop would. If the ack were still a
8770    /// reply, the route.bind request would reach the module first.
8771    #[tokio::test(start_paused = true)]
8772    async fn hello_ack_reaches_the_module_before_a_route_bind_raced_into_the_reply_gap() {
8773        let forwarding = Arc::new(ForwardingTable::default());
8774        let handler =
8775            ControlHandler::with_forwarding(Arc::new(Registry::default()), Arc::clone(&forwarding));
8776        let (module_ctx, mut module_rx) = route_ctx(ConnectionId::new(341));
8777        let replies = handler
8778            .handle_control_frame(&module_ctx, hello_frame("raced", PROTOCOL_VERSION, 7))
8779            .await
8780            .unwrap();
8781        let queued_by_hello = module_rx.len();
8782
8783        let (client_ctx, mut client_rx) = route_ctx(ConnectionId::new(342));
8784        let open_handler = handler.clone();
8785        let open = tokio::spawn(async move {
8786            open_handler
8787                .handle_control_frame(
8788                    &client_ctx,
8789                    route_open_frame(2, "raced", unique_project_root("hello-ack-race")),
8790                )
8791                .await
8792                .unwrap()
8793        });
8794        // Let the route.open run until its route.bind is on the module's queue.
8795        let mut spins = 0;
8796        while module_rx.len() == queued_by_hello {
8797            spins += 1;
8798            assert!(spins < 10_000, "route.open never queued a route.bind");
8799            tokio::task::yield_now().await;
8800        }
8801
8802        // Now the connection loop's half: write the HELLO handler's replies.
8803        for reply in replies {
8804            module_ctx.egress.send(reply).await.unwrap();
8805        }
8806
8807        let first = module_rx.recv().await.unwrap().frame;
8808        assert_eq!(
8809            first.header.ty,
8810            FrameType::HelloAck,
8811            "the first frame a registering module reads must be its HELLO_ACK"
8812        );
8813        assert_eq!(first.header.corr, 7);
8814        let second = module_rx.recv().await.unwrap().frame;
8815        assert_eq!(second.header.ty, FrameType::Request);
8816        assert!(
8817            matches!(
8818                serde_json::from_slice::<ModuleControlRequest>(&second.body).unwrap(),
8819                ModuleControlRequest::RouteBind { .. }
8820            ),
8821            "the route.bind follows the ack"
8822        );
8823        assert!(module_rx.try_recv().is_err(), "nothing else was queued");
8824
8825        handler
8826            .handle_control_frame(&module_ctx, route_bind_ack(second.header.corr))
8827            .await
8828            .unwrap();
8829        assert!(open.await.unwrap().is_empty());
8830        let _ = client_rx.recv().await.unwrap();
8831    }
8832
8833    #[tokio::test(start_paused = true)]
8834    async fn supervisor_live_roots_cross_module_scope_uses_requesting_connection() {
8835        let handler = ControlHandler::with_forwarding(
8836            Arc::new(Registry::default()),
8837            Arc::new(ForwardingTable::default()),
8838        );
8839        let (first_ctx, mut first_rx) = route_ctx(ConnectionId::new(315));
8840        let (second_ctx, mut second_rx) = route_ctx(ConnectionId::new(316));
8841        hello_via_sink(
8842            &handler,
8843            &first_ctx,
8844            &mut first_rx,
8845            hello_frame("first", PROTOCOL_VERSION, 1),
8846        )
8847        .await;
8848        hello_via_sink(
8849            &handler,
8850            &second_ctx,
8851            &mut second_rx,
8852            hello_frame("second", PROTOCOL_VERSION, 2),
8853        )
8854        .await;
8855        let root = unique_project_root("second-only");
8856        let (client_ctx, _client_rx) = route_ctx(ConnectionId::new(317));
8857        let cloned = handler.clone();
8858        let open = tokio::spawn(async move {
8859            cloned
8860                .handle_control_frame(&client_ctx, route_open_frame(3, "second", root))
8861                .await
8862                .unwrap()
8863        });
8864        let bind = tokio::time::timeout(Duration::from_secs(5), second_rx.recv())
8865            .await
8866            .unwrap()
8867            .unwrap();
8868        let first = query_live_roots(&handler, &first_ctx).await;
8869        let second = query_live_roots(&handler, &second_ctx).await;
8870        assert!(
8871            matches!(
8872                first,
8873                ModuleControlResponseToModule::LiveRoots {
8874                    total_bindings: 0,
8875                    ..
8876                }
8877            ),
8878            "cross-module scope must not expose another module's roots"
8879        );
8880        assert!(
8881            matches!(
8882                second,
8883                ModuleControlResponseToModule::LiveRoots {
8884                    total_bindings: 1,
8885                    ..
8886                }
8887            ),
8888            "second module must see its pending route"
8889        );
8890        handler
8891            .handle_control_frame(&second_ctx, route_bind_ack(bind.header.corr))
8892            .await
8893            .unwrap();
8894        assert!(open.await.unwrap().is_empty());
8895    }
8896
8897    #[tokio::test(start_paused = true)]
8898    async fn supervisor_live_roots_no_bindings_arm_is_empty() {
8899        let handler = ControlHandler::with_forwarding(
8900            Arc::new(Registry::default()),
8901            Arc::new(ForwardingTable::default()),
8902        );
8903        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(321));
8904        hello_via_sink(
8905            &handler,
8906            &target_ctx,
8907            &mut target_rx,
8908            hello_frame("target", PROTOCOL_VERSION, 1),
8909        )
8910        .await;
8911        let actual = query_live_roots(&handler, &target_ctx).await;
8912        let ModuleControlResponseToModule::LiveRoots {
8913            roots,
8914            unknown_root_bindings,
8915            total_bindings,
8916        } = actual
8917        else {
8918            panic!("expected live roots")
8919        };
8920        assert!(roots.is_empty());
8921        assert_eq!(unknown_root_bindings, 0);
8922        assert_eq!(total_bindings, 0);
8923        assert_eq!(
8924            total_bindings,
8925            roots.iter().map(|r| r.bound + r.pending).sum::<u64>() + unknown_root_bindings
8926        );
8927    }
8928
8929    /// Read the vendored fed corpus rather than hand-building a package.
8930    ///
8931    /// A hand-built object encodes what the test author believed the carrier
8932    /// emits. These vectors are what it actually emits, and one of them exists
8933    /// specifically to pin OUR side of the seam: its note reads "SUBC relay
8934    /// ignores additive unknown fields at the traversal emit terminus."
8935    fn fed_admission_facts_vectors() -> Vec<(String, Value)> {
8936        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
8937            .join("tests/fixtures/fed/admission-facts-emit.jsonl");
8938        let text = std::fs::read_to_string(&path)
8939            .unwrap_or_else(|err| panic!("vendored fed corpus unreadable at {path:?}: {err}"));
8940        let vectors: Vec<(String, Value)> = text
8941            .lines()
8942            .filter(|line| !line.trim().is_empty())
8943            .map(|line| {
8944                let entry: Value = serde_json::from_str(line).expect("corpus line must be JSON");
8945                let id = entry["corpus_id"]
8946                    .as_str()
8947                    .expect("every vector carries a corpus_id")
8948                    .to_string();
8949                (id, entry["package"].clone())
8950            })
8951            .collect();
8952        // Pin the count: a corpus that silently shrinks would take its coverage
8953        // with it, and a suite reading N-1 vectors reports the same clean pass
8954        // as one reading N.
8955        assert_eq!(
8956            vectors.len(),
8957            3,
8958            "vendored fed corpus changed size; re-sync from subc-federation"
8959        );
8960
8961        // Pin what makes the corpus DISCRIMINATING, not just present.
8962        //
8963        // The relay test below takes its expected value from the corpus, so the
8964        // corpus supplies the test's power to detect a lossy relay rather than
8965        // its correctness. A relay that dropped unrecognised fields would still
8966        // be caught -- but only by a package carrying fields it does not know.
8967        // Shrink every package to the handful of keys any implementation would
8968        // recognise and the test keeps passing over an input that can no longer
8969        // fail, which is the same clean green as a corpus that shrank away.
8970        //
8971        // So assert the precondition rather than duplicating the packages here:
8972        // at least one vector must carry a field beyond the small common set.
8973        // That is one claim to maintain instead of nine, and it fails loudly if
8974        // a re-sync ever flattens the corpus.
8975        const COMMONLY_MODELLED: [&str; 3] = ["schema", "verified_class", "org"];
8976        let richest = vectors
8977            .iter()
8978            .filter_map(|(_, package)| package.as_object())
8979            .map(|object| {
8980                object
8981                    .keys()
8982                    .filter(|key| !COMMONLY_MODELLED.contains(&key.as_str()))
8983                    .count()
8984            })
8985            .max()
8986            .unwrap_or(0);
8987        assert!(
8988            richest >= 2,
8989            "vendored corpus no longer carries a package with unmodelled fields, \
8990             so the relay test can no longer distinguish a verbatim relay from a lossy one"
8991        );
8992
8993        vectors
8994    }
8995
8996    /// The relay must carry the carrier's package through BYTE-FOR-BYTE.
8997    ///
8998    /// The gate test below proves the ACCESS RULE (who may send facts, to whom).
8999    /// This proves the PAYLOAD RULE, which the gate cannot: it hand-builds a
9000    /// three-key object, so a relay that quietly dropped fields it did not
9001    /// recognise would satisfy it. These vectors carry nine keys including ones
9002    /// this crate has no type for, so a typed relay fails here and only here.
9003    #[tokio::test]
9004    async fn admission_facts_relay_carries_vendored_packages_verbatim() {
9005        for (corpus_id, package) in fed_admission_facts_vectors() {
9006            let registry = Arc::new(Registry::default());
9007            let forwarding = Arc::new(ForwardingTable::default());
9008            let supervisor = SupervisorHandle::new();
9009            supervisor.set_spawn_nonce("fed", "fed-nonce".to_string());
9010            let handler =
9011                ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
9012                    .with_supervisor(supervisor)
9013                    .with_admission_facts_config(
9014                        Some("fed".to_string()),
9015                        Some(vec!["target".to_string()]),
9016                    );
9017
9018            let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(90));
9019            hello_via_sink(
9020                &handler,
9021                &target_ctx,
9022                &mut target_rx,
9023                hello_frame("target", PROTOCOL_VERSION, 1),
9024            )
9025            .await;
9026
9027            let (client_ctx, _client_rx) = route_ctx(ConnectionId::new(91));
9028            let route_handler = handler.clone();
9029            let expected = package.clone();
9030            let route_task = tokio::spawn(async move {
9031                route_handler
9032                    .handle_control_frame(
9033                        &client_ctx,
9034                        route_open_frame_with_admission_facts(
9035                            20,
9036                            "target",
9037                            unique_project_root("admission-facts"),
9038                            Some(subc_control::ConsumerIdentity {
9039                                module_id: "fed".to_string(),
9040                                launch_nonce: "fed-nonce".to_string(),
9041                            }),
9042                            Some(package),
9043                        ),
9044                    )
9045                    .await
9046                    .unwrap()
9047            });
9048
9049            let bind_frame = target_rx.recv().await.unwrap();
9050            let bind: ModuleControlRequest = serde_json::from_slice(&bind_frame.body).unwrap();
9051            let ModuleControlRequest::RouteBind {
9052                admission_facts, ..
9053            } = bind
9054            else {
9055                panic!("{corpus_id}: expected route.bind")
9056            };
9057            assert_eq!(
9058                admission_facts,
9059                Some(expected),
9060                "{corpus_id}: relay must not add, drop or reshape any field"
9061            );
9062
9063            handler
9064                .handle_control_frame(&target_ctx, route_bind_ack(bind_frame.header.corr))
9065                .await
9066                .unwrap();
9067            route_task.await.unwrap();
9068        }
9069    }
9070
9071    #[tokio::test]
9072    async fn admission_facts_gate_checks_carrier_target_and_precedence() {
9073        let registry = Arc::new(Registry::default());
9074        let forwarding = Arc::new(ForwardingTable::default());
9075        let supervisor = SupervisorHandle::new();
9076        supervisor.set_spawn_nonce("fed", "fed-nonce".to_string());
9077        supervisor.set_spawn_nonce("other", "other-nonce".to_string());
9078        let handler =
9079            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
9080                .with_supervisor(supervisor)
9081                .with_admission_facts_config(
9082                    Some("fed".to_string()),
9083                    Some(vec!["target".to_string()]),
9084                );
9085
9086        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(70));
9087        hello_via_sink(
9088            &handler,
9089            &target_ctx,
9090            &mut target_rx,
9091            hello_frame("target", PROTOCOL_VERSION, 1),
9092        )
9093        .await;
9094        let (other_ctx, mut other_rx) = route_ctx(ConnectionId::new(71));
9095        hello_via_sink(
9096            &handler,
9097            &other_ctx,
9098            &mut other_rx,
9099            hello_frame("other", PROTOCOL_VERSION, 2),
9100        )
9101        .await;
9102
9103        let facts = json!({"schema": 1, "verified_class": "member", "org": "01H"});
9104        let expected_facts = facts.clone();
9105        let (client_ctx, mut client_rx) = route_ctx(ConnectionId::new(72));
9106        let route_handler = handler.clone();
9107        let route_task = tokio::spawn(async move {
9108            route_handler
9109                .handle_control_frame(
9110                    &client_ctx,
9111                    route_open_frame_with_admission_facts(
9112                        10,
9113                        "target",
9114                        unique_project_root("admission-facts"),
9115                        Some(subc_control::ConsumerIdentity {
9116                            module_id: "fed".to_string(),
9117                            launch_nonce: "fed-nonce".to_string(),
9118                        }),
9119                        Some(facts.clone()),
9120                    ),
9121                )
9122                .await
9123                .unwrap()
9124        });
9125        let bind_frame = target_rx.recv().await.unwrap();
9126        let bind: ModuleControlRequest = serde_json::from_slice(&bind_frame.body).unwrap();
9127        let ModuleControlRequest::RouteBind {
9128            admission_facts, ..
9129        } = bind
9130        else {
9131            panic!("expected route.bind")
9132        };
9133        assert_eq!(admission_facts, Some(expected_facts));
9134        handler
9135            .handle_control_frame(&target_ctx, route_bind_ack(bind_frame.header.corr))
9136            .await
9137            .unwrap();
9138        assert!(route_task.await.unwrap().is_empty());
9139        assert!(matches!(
9140            serde_json::from_slice::<ClientControlResponse>(&client_rx.recv().await.unwrap().body)
9141                .unwrap(),
9142            ClientControlResponse::RouteOpen { .. }
9143        ));
9144
9145        let direct = handler
9146            .handle_control_frame(
9147                &route_ctx(ConnectionId::new(73)).0,
9148                route_open_frame_with_admission_facts(
9149                    11,
9150                    "target",
9151                    unique_project_root("admission-facts"),
9152                    None,
9153                    Some(json!({"x": 1})),
9154                ),
9155            )
9156            .await
9157            .unwrap();
9158        assert_eq!(
9159            parse_error(&direct[0])["code"],
9160            "admission_facts_not_permitted"
9161        );
9162
9163        let different_reserved = handler
9164            .handle_control_frame(
9165                &route_ctx(ConnectionId::new(77)).0,
9166                route_open_frame_with_admission_facts(
9167                    15,
9168                    "target",
9169                    unique_project_root("admission-facts"),
9170                    Some(subc_control::ConsumerIdentity {
9171                        module_id: "other".to_string(),
9172                        launch_nonce: "other-nonce".to_string(),
9173                    }),
9174                    Some(json!({"x": 1})),
9175                ),
9176            )
9177            .await
9178            .unwrap();
9179        assert_eq!(
9180            parse_error(&different_reserved[0])["code"],
9181            "admission_facts_not_permitted"
9182        );
9183
9184        let other_target = handler
9185            .handle_control_frame(
9186                &route_ctx(ConnectionId::new(74)).0,
9187                route_open_frame_with_admission_facts(
9188                    12,
9189                    "other",
9190                    unique_project_root("admission-facts"),
9191                    Some(subc_control::ConsumerIdentity {
9192                        module_id: "fed".to_string(),
9193                        launch_nonce: "fed-nonce".to_string(),
9194                    }),
9195                    Some(json!({"x": 1})),
9196                ),
9197            )
9198            .await
9199            .unwrap();
9200        assert_eq!(
9201            parse_error(&other_target[0])["code"],
9202            "admission_facts_target_not_allowed"
9203        );
9204
9205        let nonexistent = handler
9206            .handle_control_frame(
9207                &route_ctx(ConnectionId::new(75)).0,
9208                route_open_frame_with_admission_facts(
9209                    13,
9210                    "missing",
9211                    unique_project_root("admission-facts"),
9212                    None,
9213                    Some(json!({"x": 1})),
9214                ),
9215            )
9216            .await
9217            .unwrap();
9218        assert_eq!(parse_error(&nonexistent[0])["code"], "unknown_module");
9219
9220        let described = handler
9221            .handle_control_frame(
9222                &route_ctx(ConnectionId::new(76)).0,
9223                Frame::build(
9224                    FrameType::Request,
9225                    control_flags(),
9226                    0,
9227                    0,
9228                    14,
9229                    serde_json::to_vec(&ClientControlRequest::ServerDescribe {}).unwrap(),
9230                )
9231                .unwrap(),
9232            )
9233            .await
9234            .unwrap();
9235        let ClientControlResponse::ServerDescribe { capabilities, .. } =
9236            serde_json::from_slice(&described[0].body).unwrap()
9237        else {
9238            panic!("expected server.describe response")
9239        };
9240        assert!(capabilities
9241            .iter()
9242            .any(|cap| cap == "admission_facts_relay_v1"));
9243    }
9244
9245    #[tokio::test]
9246    async fn admission_facts_without_configured_carrier_are_rejected() {
9247        let registry = Arc::new(Registry::default());
9248        let forwarding = Arc::new(ForwardingTable::default());
9249        let handler = ControlHandler::with_forwarding(registry, forwarding);
9250        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(78));
9251        hello_via_sink(
9252            &handler,
9253            &target_ctx,
9254            &mut target_rx,
9255            hello_frame("target", PROTOCOL_VERSION, 1),
9256        )
9257        .await;
9258
9259        let responses = handler
9260            .handle_control_frame(
9261                &route_ctx(ConnectionId::new(79)).0,
9262                route_open_frame_with_admission_facts(
9263                    16,
9264                    "target",
9265                    unique_project_root("admission-facts"),
9266                    None,
9267                    Some(json!({"x": 1})),
9268                ),
9269            )
9270            .await
9271            .unwrap();
9272        assert_eq!(
9273            parse_error(&responses[0])["code"],
9274            "admission_facts_not_permitted"
9275        );
9276    }
9277
9278    #[tokio::test]
9279    async fn route_open_relays_consumer_capabilities_verbatim() {
9280        let registry = Arc::new(Registry::default());
9281        let forwarding = Arc::new(ForwardingTable::default());
9282        let handler =
9283            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
9284        let (module_ctx, mut module_rx) = route_ctx(ConnectionId::new(37));
9285        hello_via_sink(
9286            &handler,
9287            &module_ctx,
9288            &mut module_rx,
9289            hello_frame("aft", PROTOCOL_VERSION, 7),
9290        )
9291        .await;
9292
9293        let expected = vec!["elicitation".to_string(), "roots".to_string()];
9294        let expected_for_request = expected.clone();
9295        let project_root = unique_project_root("consumer-capabilities-present");
9296        let (client_ctx, mut client_rx) = route_ctx(ConnectionId::new(38));
9297        let route_handler = handler.clone();
9298        let route_task = tokio::spawn(async move {
9299            route_handler
9300                .handle_control_frame(
9301                    &client_ctx,
9302                    route_open_frame_with_consumer_capabilities(
9303                        401,
9304                        "aft",
9305                        project_root,
9306                        Some(expected_for_request),
9307                    ),
9308                )
9309                .await
9310                .unwrap()
9311        });
9312        let bind_frame = tokio::time::timeout(Duration::from_secs(1), module_rx.recv())
9313            .await
9314            .unwrap()
9315            .unwrap();
9316        let bind: ModuleControlRequest = serde_json::from_slice(&bind_frame.body).unwrap();
9317        let ModuleControlRequest::RouteBind {
9318            consumer_capabilities,
9319            ..
9320        } = bind
9321        else {
9322            panic!("expected route.bind request, got {bind:?}");
9323        };
9324        assert_eq!(consumer_capabilities, Some(expected.clone()));
9325
9326        handler
9327            .handle_control_frame(&module_ctx, route_bind_ack(bind_frame.header.corr))
9328            .await
9329            .unwrap();
9330        let route_response = route_task.await.unwrap();
9331        assert!(route_response.is_empty());
9332        let published = client_rx.recv().await.unwrap();
9333        assert!(matches!(
9334            serde_json::from_slice::<ClientControlResponse>(&published.body).unwrap(),
9335            ClientControlResponse::RouteOpen { .. }
9336        ));
9337    }
9338
9339    #[tokio::test]
9340    async fn route_open_without_consumer_capabilities_relays_none() {
9341        let registry = Arc::new(Registry::default());
9342        let forwarding = Arc::new(ForwardingTable::default());
9343        let handler =
9344            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
9345        let (module_ctx, mut module_rx) = route_ctx(ConnectionId::new(39));
9346        hello_via_sink(
9347            &handler,
9348            &module_ctx,
9349            &mut module_rx,
9350            hello_frame("aft", PROTOCOL_VERSION, 7),
9351        )
9352        .await;
9353
9354        let project_root = unique_project_root("consumer-capabilities-absent");
9355        let (client_ctx, mut client_rx) = route_ctx(ConnectionId::new(40));
9356        let route_handler = handler.clone();
9357        let route_task = tokio::spawn(async move {
9358            route_handler
9359                .handle_control_frame(&client_ctx, route_open_frame(402, "aft", project_root))
9360                .await
9361                .unwrap()
9362        });
9363        let bind_frame = tokio::time::timeout(Duration::from_secs(1), module_rx.recv())
9364            .await
9365            .unwrap()
9366            .unwrap();
9367        let bind: ModuleControlRequest = serde_json::from_slice(&bind_frame.body).unwrap();
9368        let ModuleControlRequest::RouteBind {
9369            consumer_capabilities,
9370            ..
9371        } = bind
9372        else {
9373            panic!("expected route.bind request, got {bind:?}");
9374        };
9375        assert_eq!(consumer_capabilities, None);
9376
9377        handler
9378            .handle_control_frame(&module_ctx, route_bind_ack(bind_frame.header.corr))
9379            .await
9380            .unwrap();
9381        let route_response = route_task.await.unwrap();
9382        assert!(route_response.is_empty());
9383        let published = client_rx.recv().await.unwrap();
9384        assert!(matches!(
9385            serde_json::from_slice::<ClientControlResponse>(&published.body).unwrap(),
9386            ClientControlResponse::RouteOpen { .. }
9387        ));
9388    }
9389
9390    /// Opens a route to a freshly registered `aft` with `sent` as the
9391    /// route.open's role_versions, acks the bind, and returns the role_versions
9392    /// the module's bind carried.
9393    async fn bind_role_versions_for(
9394        sent: Option<BTreeMap<String, String>>,
9395        connection: u64,
9396    ) -> Option<BTreeMap<String, String>> {
9397        let registry = Arc::new(Registry::default());
9398        let forwarding = Arc::new(ForwardingTable::default());
9399        let handler =
9400            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
9401        let (module_ctx, mut module_rx) = route_ctx(ConnectionId::new(connection));
9402        hello_via_sink(
9403            &handler,
9404            &module_ctx,
9405            &mut module_rx,
9406            hello_frame("aft", PROTOCOL_VERSION, 7),
9407        )
9408        .await;
9409        let project_root = unique_project_root("role-versions");
9410        let (client_ctx, mut client_rx) = route_ctx(ConnectionId::new(connection + 1));
9411        let route_handler = handler.clone();
9412        let route_task = tokio::spawn(async move {
9413            route_handler
9414                .handle_control_frame(
9415                    &client_ctx,
9416                    route_open_frame_with_role_versions(403, "aft", project_root, sent),
9417                )
9418                .await
9419                .unwrap()
9420        });
9421        let bind_frame = tokio::time::timeout(Duration::from_secs(1), module_rx.recv())
9422            .await
9423            .expect("a well-formed route.open reaches the module as a bind")
9424            .unwrap();
9425        let bind: ModuleControlRequest = serde_json::from_slice(&bind_frame.body).unwrap();
9426        let ModuleControlRequest::RouteBind { role_versions, .. } = bind else {
9427            panic!("expected route.bind request, got {bind:?}");
9428        };
9429        handler
9430            .handle_control_frame(&module_ctx, route_bind_ack(bind_frame.header.corr))
9431            .await
9432            .unwrap();
9433        assert!(route_task.await.unwrap().is_empty());
9434        let published = client_rx.recv().await.unwrap();
9435        assert!(matches!(
9436            serde_json::from_slice::<ClientControlResponse>(&published.body).unwrap(),
9437            ClientControlResponse::RouteOpen { .. }
9438        ));
9439        role_versions
9440    }
9441
9442    #[tokio::test]
9443    async fn route_open_relays_role_versions_verbatim() {
9444        let sent = role_versions(&[("tool-provider", "v1"), ("management-surface", "v12")]);
9445        assert_eq!(
9446            bind_role_versions_for(Some(sent.clone()), 141).await,
9447            Some(sent)
9448        );
9449    }
9450
9451    /// An empty map declares nothing, so the provider sees no field rather
9452    /// than an empty object it would have to treat as a second "none".
9453    #[tokio::test]
9454    async fn route_open_with_empty_or_absent_role_versions_relays_none() {
9455        assert_eq!(bind_role_versions_for(None, 143).await, None);
9456        assert_eq!(
9457            bind_role_versions_for(Some(BTreeMap::new()), 145).await,
9458            None
9459        );
9460    }
9461
9462    #[tokio::test]
9463    async fn route_open_refuses_malformed_role_versions_before_any_bind() {
9464        let registry = Arc::new(Registry::default());
9465        let forwarding = Arc::new(ForwardingTable::default());
9466        let handler =
9467            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
9468        let (module_ctx, mut module_rx) = route_ctx(ConnectionId::new(147));
9469        hello_via_sink(
9470            &handler,
9471            &module_ctx,
9472            &mut module_rx,
9473            hello_frame("aft", PROTOCOL_VERSION, 7),
9474        )
9475        .await;
9476
9477        let nine: BTreeMap<String, String> = (0..9)
9478            .map(|index| (format!("role-{index}"), "v1".to_string()))
9479            .collect();
9480        for (label, malformed) in [
9481            (
9482                "invalid role name",
9483                role_versions(&[("Tool_Provider", "v1")]),
9484            ),
9485            ("invalid version", role_versions(&[("tool-provider", "v0")])),
9486            ("nine entries", nine),
9487        ] {
9488            // A refused open answers at once; one that reached the module
9489            // would wait for its bind ack and trip this timeout.
9490            let responses = tokio::time::timeout(
9491                Duration::from_secs(1),
9492                handler.handle_control_frame(
9493                    &route_ctx(ConnectionId::new(148)).0,
9494                    route_open_frame_with_role_versions(
9495                        404,
9496                        "aft",
9497                        unique_project_root("role-versions-malformed"),
9498                        Some(malformed),
9499                    ),
9500                ),
9501            )
9502            .await
9503            .unwrap_or_else(|_| panic!("{label}: the open was relayed instead of refused"))
9504            .unwrap();
9505            assert_eq!(responses.len(), 1, "{label}");
9506            assert_eq!(responses[0].header.ty, FrameType::Error, "{label}");
9507            let error = parse_error(&responses[0]);
9508            assert_eq!(error["code"], "invalid_request", "{label}: {error}");
9509            assert_eq!(
9510                error["detail"]["field"], "role_versions",
9511                "{label}: {error}"
9512            );
9513            assert!(
9514                !error_codes::is_retryable_route_open(error["code"].as_str().unwrap()),
9515                "{label}: a malformed declaration is terminal"
9516            );
9517            assert!(
9518                module_rx.try_recv().is_err(),
9519                "{label}: the module must never see a bind"
9520            );
9521        }
9522    }
9523
9524    /// `route-role-versions/v1` is in HELLO_ACK and `server.describe`, so a
9525    /// consumer can tell this daemon forwards the field from one that would
9526    /// drop it.
9527    #[tokio::test]
9528    async fn route_role_versions_capability_is_advertised() {
9529        let handler = ControlHandler::new(Arc::new(Registry::default()));
9530        let (ctx, mut rx) = route_ctx(ConnectionId::new(149));
9531        let ack = hello_via_sink(
9532            &handler,
9533            &ctx,
9534            &mut rx,
9535            hello_frame("m", PROTOCOL_VERSION, 1),
9536        )
9537        .await;
9538        let ack = parse_ack(&ack);
9539        assert!(
9540            ack.subc_capabilities
9541                .iter()
9542                .any(|c| c == "route-role-versions/v1"),
9543            "{:?}",
9544            ack.subc_capabilities
9545        );
9546
9547        let body = serde_json::to_vec(&ClientControlRequest::ServerDescribe {}).unwrap();
9548        let frame = Frame::build(FrameType::Request, control_flags(), 0, 0, 5, body).unwrap();
9549        let reply = handler
9550            .handle_control_frame(&route_ctx(ConnectionId::new(150)).0, frame)
9551            .await
9552            .unwrap()
9553            .pop()
9554            .unwrap();
9555        let ClientControlResponse::ServerDescribe { capabilities, .. } =
9556            serde_json::from_slice(&reply.body).unwrap()
9557        else {
9558            panic!("not a server.describe reply");
9559        };
9560        assert!(
9561            capabilities.iter().any(|c| c == CAP_ROUTE_ROLE_VERSIONS_V1),
9562            "{capabilities:?}"
9563        );
9564    }
9565
9566    #[tokio::test]
9567    async fn supervision_only_module_health_probe_does_not_enable_route_open_and_cleans_up() {
9568        let registry = Arc::new(Registry::default());
9569        let forwarding = Arc::new(ForwardingTable::default());
9570        let handler =
9571            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
9572                .with_health_probe_timeout(Duration::from_secs(5));
9573        let (module_ctx, mut module_rx) = route_ctx(ConnectionId::new(35));
9574        hello_via_sink(
9575            &handler,
9576            &module_ctx,
9577            &mut module_rx,
9578            non_routable_hello_frame_with_control_ops(
9579                "mcp",
9580                300,
9581                Some(vec![MODULE_CONTROL_OP_HEALTH_CHECK.to_string()]),
9582            ),
9583        )
9584        .await;
9585        assert!(registry
9586            .get_module("mcp")
9587            .unwrap()
9588            .unwrap()
9589            .manifest
9590            .provides
9591            .is_empty());
9592
9593        let (route_client_ctx, _route_client_rx) = route_ctx(ConnectionId::new(36));
9594        let route_response = handler
9595            .handle_control_frame(
9596                &route_client_ctx,
9597                route_open_frame(301, "mcp", unique_project_root("non-routable-mcp")),
9598            )
9599            .await
9600            .unwrap();
9601        assert_eq!(route_response[0].header.ty, FrameType::Error);
9602        assert_eq!(
9603            parse_error(&route_response[0])["code"],
9604            "target_unavailable"
9605        );
9606        assert!(parse_error(&route_response[0])["message"]
9607            .as_str()
9608            .unwrap()
9609            .contains("does not provide the requested target"));
9610        assert!(module_rx.try_recv().is_err());
9611
9612        let (health_client_ctx, _health_client_rx) = route_ctx(ConnectionId::new(37));
9613        let health_handler = handler.clone();
9614        let health_task = tokio::spawn(async move {
9615            health_handler
9616                .handle_control_frame(
9617                    &health_client_ctx,
9618                    supervisor_health_probe_frame(302, "mcp"),
9619                )
9620                .await
9621                .unwrap()
9622        });
9623        let health_frame = tokio::time::timeout(Duration::from_secs(1), module_rx.recv())
9624            .await
9625            .unwrap()
9626            .unwrap();
9627        assert_eq!(
9628            serde_json::from_slice::<ModuleControlRequest>(&health_frame.body).unwrap(),
9629            ModuleControlRequest::HealthCheck {}
9630        );
9631        handler
9632            .handle_control_frame(
9633                &module_ctx,
9634                health_response(health_frame.header.corr, HealthStatus::Ok),
9635            )
9636            .await
9637            .unwrap();
9638        let health_response = health_task.await.unwrap();
9639        assert_eq!(health_response[0].header.ty, FrameType::Response);
9640        match serde_json::from_slice::<ClientControlResponse>(&health_response[0].body).unwrap() {
9641            ClientControlResponse::SupervisorHealthProbe {
9642                module_id, status, ..
9643            } => {
9644                assert_eq!(module_id, "mcp");
9645                assert_eq!(status, HealthStatus::Ok);
9646            }
9647            other => panic!("unexpected health response: {other:?}"),
9648        }
9649
9650        // Exercise the forwarding cleanup path directly while leaving the registry
9651        // advertisement in place. If cleanup leaves a stale control sink behind,
9652        // the next probe will enqueue onto it and wait for the long probe timeout
9653        // instead of returning an immediate no-connection error.
9654        forwarding
9655            .cleanup_connection(module_ctx.connection_id)
9656            .unwrap();
9657        let (cleanup_probe_ctx, _cleanup_probe_rx) = route_ctx(ConnectionId::new(38));
9658        let cleanup_response = tokio::time::timeout(
9659            Duration::from_millis(200),
9660            handler.handle_control_frame(
9661                &cleanup_probe_ctx,
9662                supervisor_health_probe_frame(303, "mcp"),
9663            ),
9664        )
9665        .await
9666        .expect("probe should fail immediately when the control lane is gone")
9667        .unwrap();
9668        assert_eq!(cleanup_response[0].header.ty, FrameType::Error);
9669        assert_eq!(
9670            parse_error(&cleanup_response[0])["code"],
9671            "target_unavailable"
9672        );
9673        assert!(parse_error(&cleanup_response[0])["message"]
9674            .as_str()
9675            .unwrap()
9676            .contains("no module connection"));
9677
9678        handler
9679            .cleanup_connection(module_ctx.connection_id)
9680            .unwrap();
9681    }
9682
9683    #[tokio::test]
9684    async fn route_open_classifies_unregistered_running_supervised_module_as_warming() {
9685        let registry = Arc::new(Registry::default());
9686        let supervisor_handle = SupervisorHandle::new();
9687        let supervisor =
9688            Supervisor::new_for_test(Arc::clone(&registry), RestartPolicy::new(0, Duration::ZERO))
9689                .with_handle(supervisor_handle.clone())
9690                .with_connection_file_path(
9691                    std::env::temp_dir()
9692                        .join(format!("subc-route-open-warming-{}", std::process::id())),
9693                );
9694        let module = supervisor
9695            .supervise_configured(
9696                ModuleSpec {
9697                    module_id: "warming".to_string(),
9698                    program: fake_aft_stub_path(),
9699                    args: Vec::new(),
9700                    env: Vec::new(),
9701                    reserved: false,
9702                    reserved_prefixes: Vec::new(),
9703                    protocol: ModuleProtocol::Subc,
9704                    overlap: Default::default(),
9705                },
9706                true,
9707            )
9708            .unwrap();
9709        assert_eq!(module.state().unwrap(), ModuleState::Running);
9710
9711        let handler = ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor_handle);
9712        let (ctx, _rx) = route_ctx(ConnectionId::new(39));
9713        let response = handler
9714            .handle_control_frame(
9715                &ctx,
9716                route_open_frame(304, "warming", unique_project_root("warming")),
9717            )
9718            .await
9719            .unwrap();
9720        module.stop().await.unwrap();
9721
9722        assert_eq!(response[0].header.ty, FrameType::Error);
9723        let error = parse_error(&response[0]);
9724        assert_eq!(error["code"], "module_warming");
9725        assert!(error["message"]
9726            .as_str()
9727            .unwrap()
9728            .contains("state=running, enabled=true, live=false"));
9729    }
9730
9731    #[test]
9732    fn route_open_connection_cap_logs_admission_reason_and_capacity() {
9733        let handler = ControlHandler::new(Arc::new(Registry::default()));
9734        let capture = EventCapture::default();
9735        let _subscriber =
9736            tracing::subscriber::set_default(tracing_subscriber::registry().with(capture.clone()));
9737        let (ctx, _rx) = route_ctx(ConnectionId::new(96));
9738        let limit = crate::server::MAX_PENDING_ROUTE_OPENS_PER_CONNECTION;
9739        let pending = (0..limit).collect::<Vec<_>>();
9740        let response = handler
9741            .route_open_capacity_refusal(
9742                &ctx,
9743                &route_open_frame(396, "busy", unique_project_root("connection-cap")),
9744                "busy",
9745                pending.len(),
9746                limit,
9747            )
9748            .unwrap();
9749        assert_eq!(parse_error(&response)["code"], "target_unavailable");
9750        let event = capture
9751            .events()
9752            .into_iter()
9753            .find(|event| {
9754                event.target == "control"
9755                    && event.fields.get("reason") == Some(&"\"open_admission_full\"".to_string())
9756            })
9757            .expect("connection admission refusal event");
9758        assert_eq!(event.fields.get("in_flight"), Some(&limit.to_string()));
9759        assert_eq!(event.fields.get("limit"), Some(&limit.to_string()));
9760    }
9761
9762    #[test]
9763    fn route_open_target_cap_logs_admission_reason_and_capacity() {
9764        let handler = ControlHandler::new(Arc::new(Registry::default()));
9765        let capture = EventCapture::default();
9766        let _subscriber =
9767            tracing::subscriber::set_default(tracing_subscriber::registry().with(capture.clone()));
9768        let (ctx, _rx) = route_ctx(ConnectionId::new(97));
9769        let limit = MAX_PENDING_ROUTE_BINDS_PER_TARGET;
9770        let guards = (0..limit)
9771            .map(|_| {
9772                handler
9773                    .route_bind_concurrency
9774                    .try_admit("busy", limit)
9775                    .unwrap()
9776            })
9777            .collect::<Vec<_>>();
9778        let in_flight = match handler.route_bind_concurrency.try_admit("busy", limit) {
9779            Err(in_flight) => in_flight,
9780            Ok(_) => panic!("target cap must refuse after {limit} admissions"),
9781        };
9782        let response = handler
9783            .route_open_target_capacity_refusal(
9784                &ctx,
9785                &route_open_frame(397, "busy", unique_project_root("target-cap")),
9786                "busy",
9787                in_flight,
9788            )
9789            .unwrap();
9790        assert_eq!(parse_error(&response)["code"], "target_unavailable");
9791        let event = capture
9792            .events()
9793            .into_iter()
9794            .find(|event| {
9795                event.target == "control"
9796                    && event.fields.get("reason") == Some(&"\"target_binds_full\"".to_string())
9797            })
9798            .expect("target admission refusal event");
9799        assert_eq!(event.fields.get("in_flight"), Some(&limit.to_string()));
9800        assert_eq!(event.fields.get("limit"), Some(&limit.to_string()));
9801        drop(guards);
9802    }
9803
9804    /// One wire code has several senders, so the refusal line names the check
9805    /// that refused. This drives the shared refusal path for ordinary refusals
9806    /// with an unregistered
9807    /// target and requires the branch label on the event.
9808    #[tokio::test]
9809    async fn route_open_refusal_names_the_check_that_refused() {
9810        let handler = ControlHandler::new(Arc::new(Registry::default()));
9811        let capture = EventCapture::default();
9812        let _subscriber =
9813            tracing::subscriber::set_default(tracing_subscriber::registry().with(capture.clone()));
9814        let (ctx, _rx) = route_ctx(ConnectionId::new(95));
9815        let response = handler
9816            .handle_control_frame(
9817                &ctx,
9818                route_open_frame(395, "nobody", unique_project_root("refusal-reason")),
9819            )
9820            .await
9821            .unwrap();
9822
9823        assert_eq!(parse_error(&response[0])["code"], "unknown_module");
9824        let event = capture
9825            .events()
9826            .into_iter()
9827            .find(|event| {
9828                event.target == "control"
9829                    && event.fields.get("code") == Some(&"\"unknown_module\"".to_string())
9830            })
9831            .expect("route.open refusal event");
9832        assert_eq!(
9833            event.fields.get("reason"),
9834            Some(&"\"not_registered\"".to_string())
9835        );
9836    }
9837
9838    #[tokio::test]
9839    async fn route_open_supervised_absence_emits_refusal_fields_and_counts_code() {
9840        let registry = Arc::new(Registry::default());
9841        let supervisor_handle = SupervisorHandle::new();
9842        let supervisor =
9843            Supervisor::new_for_test(Arc::clone(&registry), RestartPolicy::new(0, Duration::ZERO))
9844                .with_handle(supervisor_handle.clone())
9845                .with_connection_file_path(std::env::temp_dir().join(format!(
9846                    "subc-route-open-refusal-info-{}",
9847                    std::process::id()
9848                )));
9849        let module = supervisor
9850            .supervise_configured(
9851                ModuleSpec {
9852                    module_id: "warming".to_string(),
9853                    program: fake_aft_stub_path(),
9854                    args: Vec::new(),
9855                    env: Vec::new(),
9856                    reserved: false,
9857                    reserved_prefixes: Vec::new(),
9858                    protocol: ModuleProtocol::Subc,
9859                    overlap: Default::default(),
9860                },
9861                true,
9862            )
9863            .unwrap();
9864        assert_eq!(module.state().unwrap(), ModuleState::Running);
9865
9866        let handler = ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor_handle);
9867        assert!(handler
9868            .counters()
9869            .snapshot()
9870            .get("route_open_refused_by_code")
9871            .is_none());
9872        let capture = EventCapture::default();
9873        let _subscriber =
9874            tracing::subscriber::set_default(tracing_subscriber::registry().with(capture.clone()));
9875        let (ctx, _rx) = route_ctx(ConnectionId::new(94));
9876        let response = handler
9877            .handle_control_frame(
9878                &ctx,
9879                route_open_frame(394, "warming", unique_project_root("refusal-info")),
9880            )
9881            .await
9882            .unwrap();
9883        module.stop().await.unwrap();
9884
9885        assert_eq!(parse_error(&response[0])["code"], "module_warming");
9886        let event = capture
9887            .events()
9888            .into_iter()
9889            .find(|event| {
9890                event.target == "control"
9891                    && event.fields.get("code") == Some(&"\"module_warming\"".to_string())
9892            })
9893            .expect("route.open refusal event");
9894        assert_eq!(
9895            event.fields.get("module_id"),
9896            Some(&"\"warming\"".to_string())
9897        );
9898        assert_eq!(event.fields.get("connection_id"), Some(&"94".to_string()));
9899        assert_eq!(
9900            event.fields.get("reason"),
9901            Some(&"\"supervised_not_registered\"".to_string())
9902        );
9903        assert_eq!(event.fields.get("state"), Some(&"running".to_string()));
9904        assert_eq!(event.fields.get("enabled"), Some(&"true".to_string()));
9905        assert_eq!(event.fields.get("live"), Some(&"false".to_string()));
9906        assert_eq!(
9907            handler.counters().snapshot()["route_open_refused_by_code"],
9908            json!({ "module_warming": 1 })
9909        );
9910    }
9911
9912    const OUTAGE_START: &str = "route.open refusing module: not serving";
9913    const OUTAGE_RECOVERED: &str = "route.open accepted again after module outage";
9914
9915    fn outage_lines(capture: &EventCapture, message: &str) -> Vec<CapturedEvent> {
9916        capture
9917            .events()
9918            .into_iter()
9919            .filter(|event| event.fields.get("message").map(String::as_str) == Some(message))
9920            .collect()
9921    }
9922
9923    fn supervise_stub(
9924        registry: &Arc<Registry>,
9925        module_id: &str,
9926        enabled: bool,
9927    ) -> (SupervisorHandle, crate::supervise::SupervisedModule) {
9928        let supervisor_handle = SupervisorHandle::new();
9929        let supervisor =
9930            Supervisor::new_for_test(Arc::clone(registry), RestartPolicy::new(0, Duration::ZERO))
9931                .with_handle(supervisor_handle.clone())
9932                .with_connection_file_path(std::env::temp_dir().join(format!(
9933                    "subc-route-outage-{module_id}-{}",
9934                    std::process::id()
9935                )));
9936        let module = supervisor
9937            .supervise_configured(
9938                ModuleSpec {
9939                    module_id: module_id.to_string(),
9940                    program: fake_aft_stub_path(),
9941                    args: Vec::new(),
9942                    env: Vec::new(),
9943                    reserved: false,
9944                    reserved_prefixes: Vec::new(),
9945                    protocol: ModuleProtocol::Subc,
9946                    overlap: Default::default(),
9947                },
9948                enabled,
9949            )
9950            .unwrap();
9951        (supervisor_handle, module)
9952    }
9953
9954    fn supervisor_restart_frame(corr: u64, module_id: &str) -> Frame {
9955        let body = serde_json::to_vec(&ClientControlRequest::SupervisorRestart {
9956            module_id: module_id.to_string(),
9957            drain_timeout_ms: Some(50),
9958        })
9959        .unwrap();
9960        Frame::build(FrameType::Request, control_flags(), 0, 0, corr, body).unwrap()
9961    }
9962
9963    /// Two handlers built over one forwarding table must share one outage
9964    /// tracker; separate trackers would each log their own opening line for
9965    /// the same outage.
9966    #[test]
9967    fn handlers_over_one_forwarding_table_share_the_outage_tracker() {
9968        let registry = Arc::new(Registry::default());
9969        let forwarding = Arc::new(ForwardingTable::default());
9970        let first = ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
9971        let second = ControlHandler::with_forwarding(registry, forwarding);
9972        assert!(Arc::ptr_eq(&first.route_outages, &second.route_outages));
9973    }
9974
9975    /// A client can name any module id it likes. Refusing an unknown one,
9976    /// however often, must not create outage state or outage lines, or the
9977    /// tracker would be a memory sink any client could fill.
9978    #[tokio::test(flavor = "current_thread")]
9979    async fn route_open_unknown_module_refusals_add_no_outage_state() {
9980        let handler = ControlHandler::new(Arc::new(Registry::default()));
9981        let capture = EventCapture::default();
9982        let _subscriber =
9983            tracing::subscriber::set_default(tracing_subscriber::registry().with(capture.clone()));
9984        let (ctx, _rx) = route_ctx(ConnectionId::new(90));
9985        for corr in 0..8 {
9986            let response = handler
9987                .handle_control_frame(
9988                    &ctx,
9989                    route_open_frame(
9990                        380 + corr,
9991                        &format!("nobody-{corr}"),
9992                        unique_project_root("outage-unknown"),
9993                    ),
9994                )
9995                .await
9996                .unwrap();
9997            assert_eq!(parse_error(&response[0])["code"], "unknown_module");
9998        }
9999
10000        assert_eq!(handler.route_outages.tracked_module_count(), 0);
10001        assert!(outage_lines(&capture, OUTAGE_START).is_empty());
10002        assert!(outage_lines(&capture, OUTAGE_RECOVERED).is_empty());
10003    }
10004
10005    /// Drives the refusal path end to end: a supervised module that served
10006    /// before and stopped being registered with no instruction to stop is a
10007    /// WARN, and the same module refused after an operator `supervisor.restart`
10008    /// is an INFO.
10009    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10010    async fn route_open_outage_level_separates_operator_restart_from_unexplained() {
10011        let registry = Arc::new(Registry::default());
10012        let (supervisor_handle, module) = supervise_stub(&registry, "outage-restart", true);
10013        let handler = ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor_handle);
10014        let capture = EventCapture::default();
10015        let _subscriber =
10016            tracing::subscriber::set_default(tracing_subscriber::registry().with(capture.clone()));
10017        let (ctx, _rx) = route_ctx(ConnectionId::new(91));
10018        // The stub never registers, so pretend it served once: otherwise every
10019        // refusal would fall in its startup window.
10020        handler.route_outages.record_accepted("outage-restart");
10021
10022        let response = handler
10023            .handle_control_frame(
10024                &ctx,
10025                route_open_frame(391, "outage-restart", unique_project_root("outage-a")),
10026            )
10027            .await
10028            .unwrap();
10029        assert_eq!(response[0].header.ty, FrameType::Error);
10030        let starts = outage_lines(&capture, OUTAGE_START);
10031        assert_eq!(starts.len(), 1, "{starts:?}");
10032        assert_eq!(starts[0].level, tracing::Level::WARN);
10033        assert_eq!(starts[0].fields["initiated_by"], "\"unexplained\"");
10034        assert_eq!(starts[0].fields["reason"], "\"supervised_not_registered\"");
10035        assert_eq!(starts[0].fields["module_id"], "\"outage-restart\"");
10036        handler.route_outages.record_accepted("outage-restart");
10037        assert_eq!(outage_lines(&capture, OUTAGE_RECOVERED).len(), 1);
10038
10039        let restart = handler
10040            .handle_control_frame(&ctx, supervisor_restart_frame(392, "outage-restart"))
10041            .await
10042            .unwrap();
10043        assert_eq!(
10044            restart[0].header.ty,
10045            FrameType::Response,
10046            "{:?}",
10047            parse_error(&restart[0])
10048        );
10049        handler
10050            .handle_control_frame(
10051                &ctx,
10052                route_open_frame(393, "outage-restart", unique_project_root("outage-b")),
10053            )
10054            .await
10055            .unwrap();
10056        module.stop().await.unwrap();
10057
10058        let starts = outage_lines(&capture, OUTAGE_START);
10059        assert_eq!(starts.len(), 2, "{starts:?}");
10060        assert_eq!(starts[1].level, tracing::Level::INFO);
10061        assert_eq!(starts[1].fields["initiated_by"], "\"operator\"");
10062    }
10063
10064    /// A restart refused before it touched the module (here: the module is
10065    /// disabled) must clear its operator mark, so the next real outage is
10066    /// still reported as a warning.
10067    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10068    async fn failed_operator_restart_leaves_no_operator_mark() {
10069        let registry = Arc::new(Registry::default());
10070        let (supervisor_handle, _module) = supervise_stub(&registry, "outage-disabled", false);
10071        let handler = ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor_handle);
10072        let capture = EventCapture::default();
10073        let _subscriber =
10074            tracing::subscriber::set_default(tracing_subscriber::registry().with(capture.clone()));
10075        let (ctx, _rx) = route_ctx(ConnectionId::new(92));
10076        handler.route_outages.record_accepted("outage-disabled");
10077
10078        let restart = handler
10079            .handle_control_frame(&ctx, supervisor_restart_frame(394, "outage-disabled"))
10080            .await
10081            .unwrap();
10082        assert_eq!(parse_error(&restart[0])["code"], "module_disabled");
10083        assert!(!handler.route_outages.has_operator_mark("outage-disabled"));
10084
10085        handler
10086            .handle_control_frame(
10087                &ctx,
10088                route_open_frame(395, "outage-disabled", unique_project_root("outage-c")),
10089            )
10090            .await
10091            .unwrap();
10092        let starts = outage_lines(&capture, OUTAGE_START);
10093        assert_eq!(starts.len(), 1, "{starts:?}");
10094        assert_eq!(starts[0].level, tracing::Level::WARN);
10095    }
10096
10097    #[tokio::test(flavor = "current_thread")]
10098    async fn route_open_unknown_module_escapes_target_module_id() {
10099        let handler = ControlHandler::new(Arc::new(Registry::default()));
10100        let capture = EventCapture::default();
10101        let _subscriber =
10102            tracing::subscriber::set_default(tracing_subscriber::registry().with(capture.clone()));
10103        let hostile_module_id = "\u{1b}]52;c;AAAA\u{07}";
10104        let (ctx, _rx) = route_ctx(ConnectionId::new(95));
10105        let response = handler
10106            .handle_control_frame(
10107                &ctx,
10108                route_open_frame(
10109                    395,
10110                    hostile_module_id,
10111                    unique_project_root("hostile-target-module-id"),
10112                ),
10113            )
10114            .await
10115            .unwrap();
10116
10117        assert_eq!(parse_error(&response[0])["code"], "unknown_module");
10118        let event = capture
10119            .events()
10120            .into_iter()
10121            .find(|event| {
10122                event.target == "control"
10123                    && event.fields.get("code") == Some(&"\"unknown_module\"".to_string())
10124            })
10125            .expect("route.open unknown-module refusal event");
10126        let logged = event.fields.get("module_id").expect("module_id field");
10127        assert!(!logged.bytes().any(|byte| byte < 0x20));
10128        assert_eq!(logged, r#""\u{1b}]52;c;AAAA\u{7}""#);
10129    }
10130
10131    #[tokio::test(flavor = "current_thread")]
10132    async fn route_open_module_rejection_uses_daemon_counter_key() {
10133        let registry = Arc::new(Registry::default());
10134        let forwarding = Arc::new(ForwardingTable::default());
10135        let handler =
10136            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
10137        let module_connection = ConnectionId::new(95);
10138        let (module_ctx, mut module_rx) = route_ctx(module_connection);
10139        hello_via_sink(
10140            &handler,
10141            &module_ctx,
10142            &mut module_rx,
10143            hello_frame("aft", PROTOCOL_VERSION, 395),
10144        )
10145        .await;
10146
10147        let client_connection = ConnectionId::new(96);
10148        let (client_ctx, _client_rx) = route_ctx(client_connection);
10149        let capture = EventCapture::default();
10150        let _subscriber =
10151            tracing::subscriber::set_default(tracing_subscriber::registry().with(capture.clone()));
10152        let (route_task, bind) = relay_route_open(
10153            &handler,
10154            client_connection,
10155            &client_ctx.egress,
10156            &mut module_rx,
10157            396,
10158            "aft",
10159            "hostile-module-code",
10160        )
10161        .await;
10162        let hostile_code = "\u{1b}]52;c;AAAA\u{07}";
10163        let rejection = Frame::build(
10164            FrameType::Error,
10165            control_flags(),
10166            0,
10167            0,
10168            bind.header.corr,
10169            serde_json::to_vec(&ErrorBody::new(hostile_code, "module refused route.bind")).unwrap(),
10170        )
10171        .unwrap();
10172        handler
10173            .handle_control_frame(&module_ctx, rejection)
10174            .await
10175            .unwrap();
10176
10177        let response = route_task.await.unwrap();
10178        assert_eq!(parse_error(&response[0])["code"], hostile_code);
10179        let counters = handler.counters().snapshot();
10180        assert_eq!(
10181            counters["route_open_refused_by_code"],
10182            json!({ "module_rejected": 1 })
10183        );
10184        assert!(counters["route_open_refused_by_code"]
10185            .get(hostile_code)
10186            .is_none());
10187
10188        let event = capture
10189            .events()
10190            .into_iter()
10191            .find(|event| {
10192                event.target == "control"
10193                    && event.fields.get("code") == Some(&"\"module_rejected\"".to_string())
10194            })
10195            .expect("route.open module-rejection refusal event");
10196        let logged = event.fields.get("module_code").expect("module_code field");
10197        assert!(!logged.bytes().any(|byte| byte < 0x20));
10198        assert_eq!(logged, r#""\u{1b}]52;c;AAAA\u{7}""#);
10199    }
10200
10201    #[tokio::test]
10202    async fn route_open_keeps_failed_unregistered_supervised_module_unavailable() {
10203        let registry = Arc::new(Registry::default());
10204        let supervisor_handle = SupervisorHandle::new();
10205        let missing_program = std::env::temp_dir().join(format!(
10206            "subc-route-open-missing-program-{}",
10207            std::process::id()
10208        ));
10209        let supervisor =
10210            Supervisor::new_for_test(Arc::clone(&registry), RestartPolicy::new(0, Duration::ZERO))
10211                .with_handle(supervisor_handle.clone());
10212        let module = supervisor
10213            .supervise_configured(
10214                ModuleSpec {
10215                    module_id: "failed".to_string(),
10216                    program: missing_program,
10217                    args: Vec::new(),
10218                    env: Vec::new(),
10219                    reserved: false,
10220                    reserved_prefixes: Vec::new(),
10221                    protocol: ModuleProtocol::Subc,
10222                    overlap: Default::default(),
10223                },
10224                true,
10225            )
10226            .unwrap();
10227        assert_eq!(module.state().unwrap(), ModuleState::Failed);
10228
10229        let handler = ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor_handle);
10230        let (ctx, _rx) = route_ctx(ConnectionId::new(40));
10231        let response = handler
10232            .handle_control_frame(
10233                &ctx,
10234                route_open_frame(305, "failed", unique_project_root("failed")),
10235            )
10236            .await
10237            .unwrap();
10238
10239        assert_eq!(response[0].header.ty, FrameType::Error);
10240        let error = parse_error(&response[0]);
10241        assert_eq!(error["code"], "target_unavailable");
10242        assert!(error["message"]
10243            .as_str()
10244            .unwrap()
10245            .contains("state=failed, enabled=true, live=false"));
10246    }
10247
10248    #[tokio::test]
10249    async fn route_open_role_mismatch_remains_target_unavailable() {
10250        let registry = Arc::new(Registry::default());
10251        let handler = ControlHandler::new(Arc::clone(&registry));
10252        handler
10253            .handle_control(
10254                ConnectionId::new(41),
10255                non_routable_hello_frame_with_control_ops("health-only", 306, None),
10256            )
10257            .unwrap();
10258
10259        let (ctx, _rx) = route_ctx(ConnectionId::new(42));
10260        let response = handler
10261            .handle_control_frame(
10262                &ctx,
10263                route_open_frame(307, "health-only", unique_project_root("role-mismatch")),
10264            )
10265            .await
10266            .unwrap();
10267
10268        assert_eq!(parse_error(&response[0])["code"], "target_unavailable");
10269        assert!(parse_error(&response[0])["message"]
10270            .as_str()
10271            .unwrap()
10272            .contains("does not provide the requested target"));
10273    }
10274
10275    #[tokio::test]
10276    async fn route_open_inactive_registration_remains_target_unavailable() {
10277        let registry = Arc::new(Registry::default());
10278        let handler = ControlHandler::new(Arc::clone(&registry));
10279        handler
10280            .handle_control(
10281                ConnectionId::new(43),
10282                hello_frame("inactive", PROTOCOL_VERSION, 308),
10283            )
10284            .unwrap();
10285        assert!(registry
10286            .set_module_state_for_test("inactive", ChannelState::Closed)
10287            .unwrap());
10288
10289        let (ctx, _rx) = route_ctx(ConnectionId::new(44));
10290        let response = handler
10291            .handle_control_frame(
10292                &ctx,
10293                route_open_frame(309, "inactive", unique_project_root("inactive")),
10294            )
10295            .await
10296            .unwrap();
10297
10298        assert_eq!(parse_error(&response[0])["code"], "target_unavailable");
10299        assert!(parse_error(&response[0])["message"]
10300            .as_str()
10301            .unwrap()
10302            .contains("is not active"));
10303    }
10304
10305    #[tokio::test]
10306    async fn late_health_reply_is_recorded_through_the_module_response_path() {
10307        let registry = Arc::new(Registry::default());
10308        let forwarding = Arc::new(ForwardingTable::default());
10309        let supervisor_handle = SupervisorHandle::new();
10310        let supervisor =
10311            Supervisor::new_for_test(Arc::clone(&registry), crate::RestartPolicy::default())
10312                .with_forwarding(Arc::clone(&forwarding))
10313                .with_handle(supervisor_handle.clone());
10314        let module = supervisor
10315            .supervise_configured(
10316                crate::ModuleSpec {
10317                    module_id: "late-health-response".to_string(),
10318                    program: PathBuf::from("disabled-module"),
10319                    args: Vec::new(),
10320                    env: Vec::new(),
10321                    reserved: false,
10322                    reserved_prefixes: Vec::new(),
10323                    protocol: ModuleProtocol::Subc,
10324                    overlap: Default::default(),
10325                },
10326                false,
10327            )
10328            .unwrap();
10329        let handler =
10330            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
10331                .with_supervisor(supervisor_handle);
10332        let (module_ctx, _module_rx) = route_ctx(ConnectionId::new(39));
10333        handler
10334            .handle_control_frame(
10335                &module_ctx,
10336                hello_frame_with_control_ops(
10337                    "late-health-response",
10338                    PROTOCOL_VERSION,
10339                    7,
10340                    Some(vec![MODULE_CONTROL_OP_HEALTH_CHECK.to_string()]),
10341                ),
10342            )
10343            .await
10344            .unwrap();
10345        let probe_started_at = Instant::now() - Duration::from_millis(80);
10346        let pending = forwarding
10347            .begin_health_probe_rpc_for(
10348                "late-health-response",
10349                MODULE_CONTROL_OP_HEALTH_CHECK,
10350                probe_started_at,
10351                Instant::now() - Duration::from_millis(1),
10352            )
10353            .unwrap();
10354        assert!(forwarding
10355            .tombstone_health_probe_rpc(pending.endpoint, pending.corr)
10356            .unwrap());
10357
10358        let responses = handler
10359            .handle_control_frame(&module_ctx, health_response(pending.corr, HealthStatus::Ok))
10360            .await
10361            .unwrap();
10362
10363        assert!(responses.is_empty());
10364        let health = module.status().unwrap().health;
10365        assert_eq!(health.late_answer_count, 1);
10366        assert!(health.last_late_answer_latency_ms.unwrap() >= 80);
10367    }
10368
10369    #[tokio::test]
10370    async fn health_probe_timeout_and_module_death_are_typed() {
10371        let registry = Arc::new(Registry::default());
10372        let forwarding = Arc::new(ForwardingTable::default());
10373        let handler =
10374            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
10375                .with_health_probe_timeout(Duration::from_millis(50));
10376        let (module_ctx, mut module_rx) = route_ctx(ConnectionId::new(40));
10377        hello_via_sink(
10378            &handler,
10379            &module_ctx,
10380            &mut module_rx,
10381            hello_frame_with_control_ops(
10382                "aft",
10383                PROTOCOL_VERSION,
10384                7,
10385                Some(vec![MODULE_CONTROL_OP_HEALTH_CHECK.to_string()]),
10386            ),
10387        )
10388        .await;
10389
10390        let (client_ctx, _client_rx) = route_ctx(ConnectionId::new(41));
10391        let responses = handler
10392            .handle_control_frame(&client_ctx, supervisor_health_probe_frame(201, "aft"))
10393            .await
10394            .unwrap();
10395        assert_eq!(responses[0].header.ty, FrameType::Error);
10396        assert_eq!(parse_error(&responses[0])["code"], "module_timeout");
10397        let _ = module_rx.try_recv();
10398
10399        let (client_ctx, _client_rx) = route_ctx(ConnectionId::new(42));
10400        let health_handler = handler.clone();
10401        let death_task = tokio::spawn(async move {
10402            health_handler
10403                .handle_control_frame(&client_ctx, supervisor_health_probe_frame(202, "aft"))
10404                .await
10405                .unwrap()
10406        });
10407        tokio::time::timeout(Duration::from_secs(1), module_rx.recv())
10408            .await
10409            .unwrap()
10410            .unwrap();
10411        handler
10412            .cleanup_connection(module_ctx.connection_id)
10413            .unwrap();
10414        let responses = death_task.await.unwrap();
10415        assert_eq!(responses[0].header.ty, FrameType::Error);
10416        assert_eq!(parse_error(&responses[0])["code"], "target_unavailable");
10417    }
10418
10419    #[test]
10420    fn hello_requires_exact_protocol_version() {
10421        for (connection, offered) in [(1, PROTOCOL_VERSION - 1), (2, PROTOCOL_VERSION + 1)] {
10422            let registry = Arc::new(Registry::default());
10423            let handler = ControlHandler::new(Arc::clone(&registry));
10424            let responses = handler
10425                .handle_control(
10426                    ConnectionId::new(connection),
10427                    hello_frame("aft", offered, 9),
10428                )
10429                .unwrap();
10430
10431            assert_eq!(responses.len(), 1);
10432            assert_eq!(responses[0].header.ty, FrameType::Error);
10433            let error = parse_error(&responses[0]);
10434            assert_eq!(error["code"], "version_unsupported");
10435            assert!(registry.get_module("aft").unwrap().is_none());
10436            assert_eq!(registry.active_registration_count().unwrap(), 0);
10437        }
10438    }
10439
10440    #[test]
10441    fn unknown_module_push_op_is_ignored_but_malformed_known_op_errors() {
10442        let registry = Arc::new(Registry::default());
10443        let forwarding = Arc::new(ForwardingTable::default());
10444        let handler =
10445            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
10446        let module_connection = ConnectionId::new(301);
10447        let registration = registry
10448            .register_with_control_ops(
10449                manifest("aft-push", PROTOCOL_VERSION),
10450                PROTOCOL_VERSION,
10451                module_connection,
10452                module_baseline_control_ops(),
10453            )
10454            .unwrap();
10455        let (module_tx, _module_rx) = mpsc::channel(8);
10456        let endpoint = forwarding
10457            .register_module_connection(
10458                module_connection,
10459                "aft-push".to_string(),
10460                PROTOCOL_VERSION,
10461                manifest_concurrency(&registration.manifest),
10462                FrameSink::new(module_tx),
10463            )
10464            .unwrap();
10465
10466        // A push op this version does not know is ignored (forward-compat), not errored.
10467        let unknown = Frame::build(
10468            FrameType::Push,
10469            control_flags(),
10470            0,
10471            0,
10472            5,
10473            serde_json::to_vec(&json!({"op": "route.future.v2", "extra": 1})).unwrap(),
10474        )
10475        .unwrap();
10476        let out = handler.handle_status_update(endpoint, unknown).unwrap();
10477        assert!(
10478            out.is_empty(),
10479            "unknown push op must be ignored, got {out:?}"
10480        );
10481
10482        // A malformed body for a KNOWN op is a real error worth surfacing.
10483        let malformed = Frame::build(
10484            FrameType::Push,
10485            control_flags(),
10486            0,
10487            0,
10488            6,
10489            serde_json::to_vec(&json!({"op": "route.status"})).unwrap(),
10490        )
10491        .unwrap();
10492        let out = handler.handle_status_update(endpoint, malformed).unwrap();
10493        assert_eq!(out.len(), 1);
10494        assert_eq!(out[0].header.ty, FrameType::Error);
10495        assert_eq!(parse_error(&out[0])["code"], "invalid_control_body");
10496    }
10497
10498    #[test]
10499    fn hello_rejected_when_connection_already_owns_client_routes() {
10500        let registry = Arc::new(Registry::default());
10501        let forwarding = Arc::new(ForwardingTable::default());
10502        let handler =
10503            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
10504        // Commits a client route on connection 202 (bound to a module on conn 101).
10505        let _ = bind_liveness_route(&registry, &forwarding, "aft-module");
10506        let client_connection = ConnectionId::new(202);
10507
10508        // That same connection now tries to register as a module: rejected, so one
10509        // connection never holds both client-route and module-endpoint state.
10510        let responses = handler
10511            .handle_control(
10512                client_connection,
10513                hello_frame("aft-second", PROTOCOL_VERSION, 9),
10514            )
10515            .unwrap();
10516        assert_eq!(responses[0].header.ty, FrameType::Error);
10517        assert_eq!(parse_error(&responses[0])["code"], "invalid_hello");
10518        assert!(registry.get_module("aft-second").unwrap().is_none());
10519    }
10520
10521    #[tokio::test]
10522    async fn second_hello_preserves_registration_routes_and_launch_nonce() {
10523        let registry = Arc::new(Registry::default());
10524        let forwarding = Arc::new(ForwardingTable::default());
10525        let handler = ControlHandler::with_forwarding(registry.clone(), forwarding.clone());
10526        let (module_ctx, mut module_rx) = route_ctx(ConnectionId::new(101));
10527        hello_via_sink(
10528            &handler,
10529            &module_ctx,
10530            &mut module_rx,
10531            hello_frame_with_nonce("alpha", PROTOCOL_VERSION, 1, Some("alpha-nonce")),
10532        )
10533        .await;
10534        let (client_ctx, mut client_rx) = route_ctx(ConnectionId::new(202));
10535        let pending = forwarding
10536            .begin_route_bind_relay_for_test(
10537                client_ctx.connection_id,
10538                client_ctx.egress.clone(),
10539                2,
10540                "alpha",
10541            )
10542            .unwrap();
10543        forwarding
10544            .complete_pending_relay(
10545                module_ctx.connection_id,
10546                pending.corr,
10547                RouteBindRelayOutcome::Accepted,
10548            )
10549            .unwrap();
10550        client_rx.try_recv().unwrap();
10551        for module_id in ["beta", "alpha"] {
10552            let replies = handler
10553                .handle_control_frame(
10554                    &module_ctx,
10555                    hello_frame_with_nonce(module_id, PROTOCOL_VERSION, 3, Some("replacement")),
10556                )
10557                .await
10558                .unwrap();
10559            assert_eq!(replies.len(), 1, "second HELLO must be refused");
10560            assert_eq!(parse_error(&replies[0])["code"], "invalid_hello");
10561        }
10562        assert_eq!(registry.list_modules().unwrap().1.len(), 1);
10563        assert!(registry.get_module("beta").unwrap().is_none());
10564        assert!(matches!(
10565            forwarding
10566                .lookup_data_route(
10567                    client_ctx.connection_id,
10568                    pending.client_channel,
10569                    pending.client_epoch,
10570                )
10571                .unwrap(),
10572            DataRoute::Client(DataRouteState::Bound(_))
10573        ));
10574        assert!(handler
10575            .hello_launch_nonces
10576            .lock()
10577            .unwrap()
10578            .presented(module_ctx.connection_id, Some("alpha-nonce")));
10579        assert!(module_rx.try_recv().is_err());
10580    }
10581
10582    #[test]
10583    fn reserved_module_hello_requires_matching_launch_nonce() {
10584        let registry = Arc::new(Registry::default());
10585        let supervisor = SupervisorHandle::new();
10586        // The supervisor recorded the nonce it injected when it spawned the reserved
10587        // module; the HELLO verifier checks against the same shared handle.
10588        supervisor.set_reserved_nonce("vault", "the-real-nonce".to_string());
10589        let handler = ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor);
10590
10591        // A HELLO with NO nonce is rejected.
10592        let no_nonce = handler
10593            .handle_control(
10594                ConnectionId::new(1),
10595                hello_frame("vault", PROTOCOL_VERSION, 1),
10596            )
10597            .unwrap();
10598        assert_eq!(no_nonce[0].header.ty, FrameType::Error);
10599        assert_eq!(parse_error(&no_nonce[0])["code"], "reserved_module");
10600        assert!(registry.get_module("vault").unwrap().is_none());
10601
10602        // A HELLO with the WRONG nonce is rejected.
10603        let wrong = handler
10604            .handle_control(
10605                ConnectionId::new(2),
10606                hello_frame_with_nonce("vault", PROTOCOL_VERSION, 2, Some("forged")),
10607            )
10608            .unwrap();
10609        assert_eq!(wrong[0].header.ty, FrameType::Error);
10610        assert_eq!(parse_error(&wrong[0])["code"], "reserved_module");
10611        assert!(registry.get_module("vault").unwrap().is_none());
10612
10613        // A HELLO with the CORRECT nonce registers.
10614        let ok = handler
10615            .handle_control(
10616                ConnectionId::new(3),
10617                hello_frame_with_nonce("vault", PROTOCOL_VERSION, 3, Some("the-real-nonce")),
10618            )
10619            .unwrap();
10620        assert_eq!(ok[0].header.ty, FrameType::HelloAck);
10621        assert!(registry.get_module("vault").unwrap().is_some());
10622    }
10623
10624    #[test]
10625    fn reserved_prefix_hello_uses_delimiter_sensitive_owner_nonce() {
10626        let registry = Arc::new(Registry::default());
10627        let supervisor = SupervisorHandle::new();
10628        supervisor.set_spawn_nonce("federation", "owner-nonce".to_string());
10629        supervisor.set_reserved_prefixes("federation", &["fed:".to_string()]);
10630        let handler = ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor);
10631
10632        let squat = handler
10633            .handle_control(
10634                ConnectionId::new(1),
10635                hello_frame("fed:peerA:tool", PROTOCOL_VERSION, 1),
10636            )
10637            .unwrap();
10638        assert_eq!(squat[0].header.ty, FrameType::Error);
10639        assert_eq!(parse_error(&squat[0])["code"], "reserved_module");
10640        assert!(parse_error(&squat[0])["message"]
10641            .as_str()
10642            .unwrap()
10643            .contains("fed:"));
10644
10645        let accepted_peer = handler
10646            .handle_control(
10647                ConnectionId::new(2),
10648                hello_frame_with_nonce("fed:peerA:tool", PROTOCOL_VERSION, 2, Some("owner-nonce")),
10649            )
10650            .unwrap();
10651        assert_eq!(accepted_peer[0].header.ty, FrameType::HelloAck);
10652
10653        let accepted_short = handler
10654            .handle_control(
10655                ConnectionId::new(3),
10656                hello_frame_with_nonce("fed:x", PROTOCOL_VERSION, 3, Some("owner-nonce")),
10657            )
10658            .unwrap();
10659        assert_eq!(accepted_short[0].header.ty, FrameType::HelloAck);
10660
10661        for (conn, module_id) in [(4, "fedx:tool"), (5, "fed"), (6, "FED:x")] {
10662            let response = handler
10663                .handle_control(
10664                    ConnectionId::new(conn),
10665                    hello_frame(module_id, PROTOCOL_VERSION, conn),
10666                )
10667                .unwrap();
10668            assert_eq!(response[0].header.ty, FrameType::HelloAck, "{module_id}");
10669        }
10670    }
10671
10672    #[test]
10673    fn exact_reserved_module_takes_precedence_over_reserved_prefix() {
10674        let registry = Arc::new(Registry::default());
10675        let supervisor = SupervisorHandle::new();
10676        supervisor.set_spawn_nonce("federation", "owner-nonce".to_string());
10677        supervisor.set_reserved_prefixes("federation", &["fed:".to_string()]);
10678        supervisor.set_reserved_nonce("fed:special", "exact-nonce".to_string());
10679        let handler = ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor);
10680
10681        let owner_nonce = handler
10682            .handle_control(
10683                ConnectionId::new(1),
10684                hello_frame_with_nonce("fed:special", PROTOCOL_VERSION, 1, Some("owner-nonce")),
10685            )
10686            .unwrap();
10687        assert_eq!(owner_nonce[0].header.ty, FrameType::Error);
10688        assert_eq!(parse_error(&owner_nonce[0])["code"], "reserved_module");
10689        assert!(registry.get_module("fed:special").unwrap().is_none());
10690
10691        let exact_nonce = handler
10692            .handle_control(
10693                ConnectionId::new(2),
10694                hello_frame_with_nonce("fed:special", PROTOCOL_VERSION, 2, Some("exact-nonce")),
10695            )
10696            .unwrap();
10697        assert_eq!(exact_nonce[0].header.ty, FrameType::HelloAck);
10698        assert!(registry.get_module("fed:special").unwrap().is_some());
10699    }
10700
10701    #[test]
10702    fn non_reserved_module_ignores_launch_nonce() {
10703        let registry = Arc::new(Registry::default());
10704        // No reserved nonce recorded for these ids: they are not reserved, so HELLO
10705        // registration succeeds whether a spawned process echoes a nonce or not.
10706        let handler = ControlHandler::new(Arc::clone(&registry));
10707        let no_nonce = handler
10708            .handle_control(
10709                ConnectionId::new(1),
10710                hello_frame("aft-no-nonce", PROTOCOL_VERSION, 1),
10711            )
10712            .unwrap();
10713        assert_eq!(no_nonce[0].header.ty, FrameType::HelloAck);
10714        assert!(registry.get_module("aft-no-nonce").unwrap().is_some());
10715
10716        let echoed_nonce = handler
10717            .handle_control(
10718                ConnectionId::new(2),
10719                hello_frame_with_nonce("aft-with-nonce", PROTOCOL_VERSION, 2, Some("spawn-nonce")),
10720            )
10721            .unwrap();
10722        assert_eq!(echoed_nonce[0].header.ty, FrameType::HelloAck);
10723        assert!(registry.get_module("aft-with-nonce").unwrap().is_some());
10724    }
10725
10726    #[test]
10727    fn malformed_hello_returns_error_and_handler_still_answers_ping() {
10728        let handler = ControlHandler::default();
10729        let conn = ConnectionId::new(1);
10730        let malformed = Frame::build(
10731            FrameType::Hello,
10732            control_flags(),
10733            0,
10734            0,
10735            3,
10736            b"{not json".to_vec(),
10737        )
10738        .unwrap();
10739
10740        let error = handler.handle_control(conn, malformed).unwrap();
10741        assert_eq!(error[0].header.ty, FrameType::Error);
10742        assert_eq!(parse_error(&error[0])["code"], "invalid_hello");
10743
10744        let ping = Frame::build(FrameType::Ping, control_flags(), 0, 0, 4, Vec::new()).unwrap();
10745        let pong = handler.handle_control(conn, ping).unwrap();
10746        assert_eq!(pong[0].header.ty, FrameType::Pong);
10747        assert_eq!(pong[0].header.corr, 4);
10748    }
10749
10750    #[test]
10751    fn duplicate_module_id_is_rejected_without_replacing_active_registration() {
10752        let registry = Arc::new(Registry::default());
10753        let handler = ControlHandler::new(Arc::clone(&registry));
10754
10755        handler
10756            .handle_control(
10757                ConnectionId::new(1),
10758                hello_frame("aft", PROTOCOL_VERSION, 1),
10759            )
10760            .unwrap();
10761        let duplicate = handler
10762            .handle_control(
10763                ConnectionId::new(2),
10764                hello_frame("aft", PROTOCOL_VERSION, 2),
10765            )
10766            .unwrap();
10767
10768        assert_eq!(duplicate[0].header.ty, FrameType::Error);
10769        assert_eq!(parse_error(&duplicate[0])["code"], "duplicate_module_id");
10770        let registration = registry.get_module("aft").unwrap().unwrap();
10771        assert_eq!(registration.connection_id, ConnectionId::new(1));
10772    }
10773
10774    #[test]
10775    fn liveness_poll_reports_false_when_process_liveness_reports_dead() {
10776        let registry = Arc::new(Registry::default());
10777        let forwarding = Arc::new(ForwardingTable::default());
10778        let process_liveness = Arc::new(FakeProcessLiveness { live: Some(false) });
10779        let handler =
10780            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
10781                .with_process_liveness(process_liveness);
10782        let (ctx, route_channel, route_epoch) =
10783            bind_liveness_route(&registry, &forwarding, "aft-dead");
10784        let responses = handler
10785            .handle_route_poll(
10786                &ctx,
10787                route_poll_frame(41, PollKind::Liveness, route_channel),
10788                route_channel,
10789                route_epoch,
10790                PollKind::Liveness,
10791            )
10792            .unwrap();
10793
10794        assert_eq!(responses.len(), 1);
10795        assert_eq!(responses[0].header.ty, FrameType::Response);
10796        assert_route_poll_liveness(&responses[0], false);
10797    }
10798
10799    #[test]
10800    fn liveness_poll_without_process_source_uses_bound_route() {
10801        let registry = Arc::new(Registry::default());
10802        let forwarding = Arc::new(ForwardingTable::default());
10803        let handler =
10804            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
10805        let (ctx, route_channel, route_epoch) =
10806            bind_liveness_route(&registry, &forwarding, "aft-bound-only");
10807        let responses = handler
10808            .handle_route_poll(
10809                &ctx,
10810                route_poll_frame(42, PollKind::Liveness, route_channel),
10811                route_channel,
10812                route_epoch,
10813                PollKind::Liveness,
10814            )
10815            .unwrap();
10816
10817        assert_route_poll_liveness(&responses[0], true);
10818    }
10819
10820    #[test]
10821    fn liveness_poll_untracked_process_source_uses_bound_route() {
10822        let registry = Arc::new(Registry::default());
10823        let forwarding = Arc::new(ForwardingTable::default());
10824        let process_liveness = Arc::new(FakeProcessLiveness { live: None });
10825        let handler =
10826            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
10827                .with_process_liveness(process_liveness);
10828        let (ctx, route_channel, route_epoch) =
10829            bind_liveness_route(&registry, &forwarding, "aft-untracked");
10830        let responses = handler
10831            .handle_route_poll(
10832                &ctx,
10833                route_poll_frame(43, PollKind::Liveness, route_channel),
10834                route_channel,
10835                route_epoch,
10836                PollKind::Liveness,
10837            )
10838            .unwrap();
10839
10840        assert_route_poll_liveness(&responses[0], true);
10841    }
10842
10843    #[tokio::test]
10844    async fn unknown_op_returns_unknown_control_op() {
10845        let handler = ControlHandler::default();
10846        let (ctx, _rx) = route_ctx(ConnectionId::new(77));
10847        let request = Frame::build(
10848            FrameType::Request,
10849            control_flags(),
10850            0,
10851            0,
10852            55,
10853            br#"{"op":"route.nope","route_channel":1}"#.to_vec(),
10854        )
10855        .unwrap();
10856
10857        let response = handler.handle_control_frame(&ctx, request).await.unwrap();
10858
10859        assert_eq!(response.len(), 1);
10860        assert_eq!(response[0].header.ty, FrameType::Error);
10861        assert_eq!(response[0].header.corr, 55);
10862        assert_eq!(parse_error(&response[0])["code"], "unknown_control_op");
10863    }
10864
10865    #[tokio::test]
10866    async fn supervisor_provenance_rejects_unknown_exact_module() {
10867        let handler = ControlHandler::default();
10868        let (ctx, _rx) = route_ctx(ConnectionId::new(79));
10869        let request = Frame::build(
10870            FrameType::Request,
10871            control_flags(),
10872            0,
10873            0,
10874            57,
10875            br#"{"op":"supervisor.provenance","module_id":"missing"}"#.to_vec(),
10876        )
10877        .unwrap();
10878
10879        let response = handler.handle_control_frame(&ctx, request).await.unwrap();
10880
10881        assert_eq!(response.len(), 1);
10882        assert_eq!(response[0].header.ty, FrameType::Error);
10883        assert_eq!(response[0].header.corr, 57);
10884        let error = parse_error(&response[0]);
10885        assert_eq!(error["code"], "unknown_module");
10886        assert_eq!(error["message"], "module_id 'missing' is not supervised");
10887    }
10888
10889    #[test]
10890    fn provenance_probe_override_keeps_handler_tests_deterministic() {
10891        let expected = subc_control::RunningImageAgreement::Unavailable {
10892            reason: subc_control::RunningImageUnavailableReason::HashFailed,
10893        };
10894        let handler = ControlHandler::default().with_provenance_probe_result(expected.clone());
10895        assert_eq!(handler.provenance_probe_override, Some(expected));
10896    }
10897
10898    #[test]
10899    fn reload_verdict_detects_configured_program_different_from_spawned_path() {
10900        let verdict = reload_verdict(
10901            std::path::Path::new("/bin/new"),
10902            Some(std::path::Path::new("/bin/old")),
10903            subc_control::RunningImageAgreement::Unavailable {
10904                reason: subc_control::RunningImageUnavailableReason::HashFailed,
10905            },
10906        );
10907        assert!(matches!(
10908            verdict.path,
10909            subc_control::ReloadPathAgreement::Mismatch { configured, spawned_from }
10910                if configured == std::path::Path::new("/bin/new")
10911                    && spawned_from == std::path::Path::new("/bin/old")
10912        ));
10913    }
10914
10915    #[test]
10916    fn reload_verdict_detects_replaced_image_at_same_path() {
10917        let image = subc_control::RunningImageAgreement::Mismatch {
10918            running: subc_control::RunningImageEvidence::LinuxProcSha256 {
10919                digest: "old".into(),
10920            },
10921            disk: subc_control::RunningImageEvidence::LinuxProcSha256 {
10922                digest: "new".into(),
10923            },
10924        };
10925        let verdict = reload_verdict(
10926            std::path::Path::new("/bin/same"),
10927            Some(std::path::Path::new("/bin/same")),
10928            image.clone(),
10929        );
10930        assert_eq!(verdict.path, subc_control::ReloadPathAgreement::Match);
10931        assert_eq!(verdict.image, image);
10932    }
10933
10934    #[test]
10935    fn reload_verdict_preserves_stopped_and_unavailable_reasons() {
10936        let image = subc_control::RunningImageAgreement::Unavailable {
10937            reason: subc_control::RunningImageUnavailableReason::NotRunning,
10938        };
10939        let verdict = reload_verdict(std::path::Path::new("/bin/same"), None, image.clone());
10940        assert_eq!(
10941            verdict.path,
10942            subc_control::ReloadPathAgreement::Unavailable {
10943                reason: subc_control::ReloadPathUnavailableReason::NotRunning,
10944            }
10945        );
10946        assert_eq!(verdict.image, image);
10947
10948        let unconfirmed = subc_control::RunningImageAgreement::Unavailable {
10949            reason: subc_control::RunningImageUnavailableReason::ProcessIdentityUnconfirmed,
10950        };
10951        let verdict = reload_verdict(
10952            std::path::Path::new("/bin/same"),
10953            Some(std::path::Path::new("/bin/same")),
10954            unconfirmed.clone(),
10955        );
10956        assert_eq!(verdict.path, subc_control::ReloadPathAgreement::Match);
10957        assert_eq!(verdict.image, unconfirmed);
10958    }
10959
10960    #[test]
10961    fn reload_verdict_preserves_each_image_unavailability_reason() {
10962        use subc_control::RunningImageUnavailableReason as Reason;
10963
10964        for reason in [
10965            Reason::NotRunning,
10966            Reason::UnsupportedPlatform,
10967            Reason::RunningExecutableUnreadable,
10968            Reason::SpawnedPathUnreadable,
10969            Reason::HashFailed,
10970            Reason::ProcessIdentityUnconfirmed,
10971            Reason::Unknown("future_probe_reason".to_string()),
10972        ] {
10973            let image = subc_control::RunningImageAgreement::Unavailable {
10974                reason: reason.clone(),
10975            };
10976            let verdict = reload_verdict(
10977                std::path::Path::new("/bin/same"),
10978                Some(std::path::Path::new("/bin/same")),
10979                image.clone(),
10980            );
10981            assert_eq!(verdict.path, subc_control::ReloadPathAgreement::Match);
10982            assert_eq!(verdict.image, image, "{reason:?}");
10983        }
10984    }
10985
10986    #[tokio::test]
10987    async fn malformed_control_bodies_return_invalid_control_body() {
10988        let handler = ControlHandler::default();
10989        let (ctx, _rx) = route_ctx(ConnectionId::new(78));
10990
10991        for (corr, body) in [
10992            (56, br#"{"route_channel":1}"#.as_slice()),
10993            (57, br#"{"op":17,"route_channel":1}"#.as_slice()),
10994            (
10995                58,
10996                br#"{"op":"route.poll","route_channel":"bad","kind":"status"}"#.as_slice(),
10997            ),
10998        ] {
10999            let request = Frame::build(
11000                FrameType::Request,
11001                control_flags(),
11002                0,
11003                0,
11004                corr,
11005                body.to_vec(),
11006            )
11007            .unwrap();
11008            let response = handler.handle_control_frame(&ctx, request).await.unwrap();
11009
11010            assert_eq!(response.len(), 1);
11011            assert_eq!(response[0].header.ty, FrameType::Error);
11012            assert_eq!(response[0].header.corr, corr);
11013            assert_eq!(parse_error(&response[0])["code"], "invalid_control_body");
11014        }
11015    }
11016
11017    #[tokio::test]
11018    async fn goodbye_tears_down_registration_and_later_channel_is_unknown() {
11019        let registry = Arc::new(Registry::default());
11020        let control = Arc::new(ControlHandler::new(Arc::clone(&registry)));
11021        let router = Router::with_control_handler(Arc::clone(&control));
11022        let connection = router.begin_connection();
11023        let (ctx, mut rx) = route_ctx(connection.id());
11024
11025        router
11026            .route_for_connection(&ctx, hello_frame("aft", PROTOCOL_VERSION, 11))
11027            .await
11028            .unwrap();
11029        let response = rx.recv().await.unwrap();
11030        let ack = parse_ack(&response);
11031        assert_eq!(ack.negotiated_ver, PROTOCOL_VERSION);
11032        let channel = 1;
11033
11034        let goodbye =
11035            Frame::build(FrameType::Goodbye, control_flags(), 0, 0, 12, Vec::new()).unwrap();
11036        router.route_for_connection(&ctx, goodbye).await.unwrap();
11037        assert!(rx.try_recv().is_err());
11038        assert!(registry.get_module("aft").unwrap().is_none());
11039
11040        router
11041            .route_for_connection(&ctx, channel_request(channel, 13))
11042            .await
11043            .unwrap();
11044        let error_frame = rx.recv().await.unwrap();
11045        assert_eq!(error_frame.header.ty, FrameType::Error);
11046        assert_eq!(error_frame.header.channel, channel);
11047    }
11048
11049    #[tokio::test]
11050    async fn module_goodbye_refreshes_requirements_and_pushes_route_closed() {
11051        let registry = Arc::new(Registry::default());
11052        let handler = ControlHandler::new(registry).with_capability_config(
11053            [("prov".to_string(), true), ("cons".to_string(), true)],
11054            BTreeMap::new(),
11055        );
11056        let (provider_ctx, mut provider_rx) = route_ctx(ConnectionId::new(701));
11057        register_capability_manifest(
11058            &handler,
11059            &provider_ctx,
11060            &mut provider_rx,
11061            capability_manifest("prov", &["thing/v1"], &[]),
11062            1,
11063        )
11064        .await;
11065        let mut consumer = capability_manifest("cons", &[], &[]);
11066        consumer.capabilities.as_mut().unwrap().requires.push(
11067            subc_protocol::manifest::CapabilityRequirement {
11068                capability: "thing/v1".to_string(),
11069                need: subc_protocol::manifest::CapabilityNeed::Required,
11070            },
11071        );
11072        let (consumer_ctx, mut consumer_rx) = route_ctx(ConnectionId::new(702));
11073        register_capability_manifest(&handler, &consumer_ctx, &mut consumer_rx, consumer, 2).await;
11074        assert_eq!(
11075            handler.capability_evaluator.verdict("cons", "thing/v1"),
11076            Some(CapabilityVerdict::Provided)
11077        );
11078        let (mut client_rx, _) = open_route_for_capability_test(
11079            &handler,
11080            &provider_ctx,
11081            &mut provider_rx,
11082            703,
11083            3,
11084            "prov",
11085            None,
11086        )
11087        .await;
11088        let goodbye =
11089            Frame::build(FrameType::Goodbye, control_flags(), 0, 0, 4, Vec::new()).unwrap();
11090        handler
11091            .handle_control_frame(&provider_ctx, goodbye)
11092            .await
11093            .unwrap();
11094        assert_eq!(
11095            handler.capability_evaluator.verdict("cons", "thing/v1"),
11096            Some(CapabilityVerdict::NeverProvided)
11097        );
11098        let closed = client_rx
11099            .try_recv()
11100            .expect("GOODBYE pushes route.closed before route GOODBYE");
11101        assert!(
11102            matches!(serde_json::from_slice::<ClientControlPush>(&closed.body).unwrap(),
11103            ClientControlPush::RouteClosed { module_id, channels, .. } if module_id == "prov" && channels.len() == 1)
11104        );
11105        assert_eq!(client_rx.try_recv().unwrap().header.ty, FrameType::Goodbye);
11106        assert_eq!(handler.forwarding.active_binding_count().unwrap(), 0);
11107    }
11108
11109    #[tokio::test]
11110    async fn dropping_router_connection_releases_registration() {
11111        let registry = Arc::new(Registry::default());
11112        let control = Arc::new(ControlHandler::new(Arc::clone(&registry)));
11113        let router = Router::with_control_handler(control);
11114        let connection = router.begin_connection();
11115        let (ctx, mut rx) = route_ctx(connection.id());
11116
11117        router
11118            .route_for_connection(&ctx, hello_frame("aft", PROTOCOL_VERSION, 31))
11119            .await
11120            .unwrap();
11121        let response = rx.recv().await.unwrap();
11122        let ack = parse_ack(&response);
11123        assert_eq!(ack.negotiated_ver, PROTOCOL_VERSION);
11124        assert!(registry.get_module("aft").unwrap().is_some());
11125
11126        drop(connection);
11127
11128        assert!(registry.get_module("aft").unwrap().is_none());
11129        assert_eq!(registry.active_registration_count().unwrap(), 0);
11130    }
11131
11132    fn capability_manifest(
11133        module_id: &str,
11134        provides: &[&str],
11135        must_never_reach: &[&str],
11136    ) -> ModuleManifest {
11137        let mut manifest = manifest(module_id, PROTOCOL_VERSION);
11138        manifest.capabilities = Some(CapabilityDeclarations {
11139            provides: provides
11140                .iter()
11141                .map(|capability| (*capability).to_string())
11142                .collect(),
11143            requires: Vec::new(),
11144            must_never_reach: must_never_reach
11145                .iter()
11146                .map(|capability| (*capability).to_string())
11147                .collect(),
11148        });
11149        manifest
11150    }
11151
11152    fn hello_frame_with_manifest(manifest: ModuleManifest, corr: u64) -> Frame {
11153        Frame::build(
11154            FrameType::Hello,
11155            control_flags(),
11156            0,
11157            0,
11158            corr,
11159            serde_json::to_vec(&ModuleHelloBody {
11160                protocol_ver: manifest.protocol_ver,
11161                manifest,
11162                control_ops: None,
11163                launch_nonce: None,
11164            })
11165            .expect("capability test HELLO serializes"),
11166        )
11167        .expect("capability test HELLO frame builds")
11168    }
11169
11170    fn catalog_update_with_capabilities_frame(
11171        corr: u64,
11172        capabilities: CapabilityDeclarations,
11173    ) -> Frame {
11174        Frame::build(
11175            FrameType::Request,
11176            control_flags(),
11177            0,
11178            0,
11179            corr,
11180            serde_json::to_vec(&ModuleControlRequestFromModule::CatalogUpdate {
11181                provides: manifest("catalog-update-placeholder", PROTOCOL_VERSION).provides,
11182                capabilities: Some(capabilities),
11183                ready: None,
11184            })
11185            .expect("capability catalog.update serializes"),
11186        )
11187        .expect("capability catalog.update frame builds")
11188    }
11189
11190    async fn register_capability_manifest(
11191        handler: &ControlHandler,
11192        ctx: &RouteCtx,
11193        rx: &mut mpsc::Receiver<crate::router::OutboundFrame>,
11194        manifest: ModuleManifest,
11195        corr: u64,
11196    ) {
11197        hello_via_sink(handler, ctx, rx, hello_frame_with_manifest(manifest, corr)).await;
11198    }
11199
11200    async fn open_route_for_capability_test(
11201        handler: &ControlHandler,
11202        target_ctx: &RouteCtx,
11203        target_rx: &mut mpsc::Receiver<crate::router::OutboundFrame>,
11204        client_connection_id: u64,
11205        corr: u64,
11206        target_module_id: &str,
11207        consumer_identity: Option<ConsumerIdentity>,
11208    ) -> (
11209        mpsc::Receiver<crate::router::OutboundFrame>,
11210        ModuleControlRequest,
11211    ) {
11212        let (client_ctx, mut client_rx) = route_ctx(ConnectionId::new(client_connection_id));
11213        let route_handler = handler.clone();
11214        let target_module_id = target_module_id.to_string();
11215        let route_task = tokio::spawn(async move {
11216            route_handler
11217                .handle_control_frame(
11218                    &client_ctx,
11219                    route_open_frame_with_admission_facts(
11220                        corr,
11221                        &target_module_id,
11222                        unique_project_root("admission-facts"),
11223                        consumer_identity,
11224                        None,
11225                    ),
11226                )
11227                .await
11228                .expect("capability test route.open succeeds")
11229        });
11230        let bind = tokio::time::timeout(Duration::from_secs(1), target_rx.recv())
11231            .await
11232            .expect("capability test route.open must reach route.bind")
11233            .expect("target control receiver stays open");
11234        let bind_request: ModuleControlRequest =
11235            serde_json::from_slice(&bind.body).expect("route.bind decodes");
11236        handler
11237            .handle_control_frame(target_ctx, route_bind_ack(bind.header.corr))
11238            .await
11239            .expect("capability test route.bind ACK succeeds");
11240        assert!(route_task.await.expect("route.open task joins").is_empty());
11241        let opened = client_rx
11242            .recv()
11243            .await
11244            .expect("successful route.open publishes a response");
11245        assert!(matches!(
11246            serde_json::from_slice::<ClientControlResponse>(&opened.body),
11247            Ok(ClientControlResponse::RouteOpen { .. })
11248        ));
11249        (client_rx, bind_request)
11250    }
11251
11252    fn assert_capability_denied_push(frame: Frame, target_module_id: &str) {
11253        assert_eq!(frame.header.ty, FrameType::Push);
11254        assert_eq!(frame.header.channel, 0);
11255        let push = serde_json::from_slice::<ClientControlPush>(&frame.body)
11256            .expect("route.closed control push decodes");
11257        let ClientControlPush::RouteClosed { channels, .. } = &push else {
11258            panic!("expected route.closed");
11259        };
11260        assert_eq!(channels.len(), 1, "exactly one violating route closed");
11261        let channels = channels.clone();
11262        assert_eq!(
11263            push,
11264            ClientControlPush::RouteClosed {
11265                module_id: target_module_id.to_string(),
11266                channels,
11267                reason: RouteCloseReason::CapabilityDenied,
11268                drained: false,
11269                abandoned: 0,
11270                excluded_subscriptions: 0,
11271                terminal: Some(false),
11272            }
11273        );
11274    }
11275
11276    #[tokio::test]
11277    async fn route_open_capability_forbidden_mutation_proof_creates_no_route() {
11278        let registry = Arc::new(Registry::default());
11279        let forwarding = Arc::new(ForwardingTable::default());
11280        let supervisor = SupervisorHandle::new();
11281        supervisor.set_spawn_nonce("opener", "opener-nonce".to_string());
11282        let handler =
11283            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
11284                .with_supervisor(supervisor);
11285        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(700));
11286        let (opener_ctx, mut opener_rx) = route_ctx(ConnectionId::new(701));
11287        register_capability_manifest(
11288            &handler,
11289            &target_ctx,
11290            &mut target_rx,
11291            capability_manifest("target", &["credentials-provider/v1"], &[]),
11292            1,
11293        )
11294        .await;
11295        register_capability_manifest(
11296            &handler,
11297            &opener_ctx,
11298            &mut opener_rx,
11299            capability_manifest("opener", &[], &["credentials-provider/v1"]),
11300            2,
11301        )
11302        .await;
11303
11304        let (client_ctx, _client_rx) = route_ctx(ConnectionId::new(702));
11305        let replies = handler
11306            .handle_control_frame(
11307                &client_ctx,
11308                route_open_frame_with_admission_facts(
11309                    3,
11310                    "target",
11311                    unique_project_root("admission-facts"),
11312                    Some(ConsumerIdentity {
11313                        module_id: "opener".to_string(),
11314                        launch_nonce: "opener-nonce".to_string(),
11315                    }),
11316                    None,
11317                ),
11318            )
11319            .await
11320            .expect("denied route.open returns a typed frame");
11321        assert_eq!(parse_error(&replies[0])["code"], "capability_forbidden");
11322        assert_eq!(forwarding.active_binding_count().unwrap(), 0);
11323        assert!(
11324            target_rx.try_recv().is_err(),
11325            "forbidden route.open must not relay route.bind"
11326        );
11327    }
11328
11329    #[tokio::test]
11330    async fn capability_deny_edge_hello_mutation_proof_force_closes_existing_route() {
11331        let registry = Arc::new(Registry::default());
11332        let forwarding = Arc::new(ForwardingTable::default());
11333        let supervisor = SupervisorHandle::new();
11334        supervisor.set_spawn_nonce("opener", "opener-nonce".to_string());
11335        let handler =
11336            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
11337                .with_supervisor(supervisor);
11338        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(710));
11339        let (old_opener_ctx, mut old_opener_rx) = route_ctx(ConnectionId::new(711));
11340        register_capability_manifest(
11341            &handler,
11342            &target_ctx,
11343            &mut target_rx,
11344            capability_manifest("target", &["credentials-provider/v1"], &[]),
11345            1,
11346        )
11347        .await;
11348        register_capability_manifest(
11349            &handler,
11350            &old_opener_ctx,
11351            &mut old_opener_rx,
11352            capability_manifest("opener", &[], &[]),
11353            2,
11354        )
11355        .await;
11356        let (mut client_rx, _) = open_route_for_capability_test(
11357            &handler,
11358            &target_ctx,
11359            &mut target_rx,
11360            712,
11361            3,
11362            "target",
11363            Some(ConsumerIdentity {
11364                module_id: "opener".to_string(),
11365                launch_nonce: "opener-nonce".to_string(),
11366            }),
11367        )
11368        .await;
11369        assert_eq!(forwarding.active_binding_count().unwrap(), 1);
11370
11371        handler
11372            .cleanup_connection(old_opener_ctx.connection_id)
11373            .expect("old opener registration cleans up");
11374        let (new_opener_ctx, mut new_opener_rx) = route_ctx(ConnectionId::new(713));
11375        register_capability_manifest(
11376            &handler,
11377            &new_opener_ctx,
11378            &mut new_opener_rx,
11379            capability_manifest("opener", &[], &["credentials-provider/v1"]),
11380            4,
11381        )
11382        .await;
11383
11384        assert_capability_denied_push(
11385            client_rx
11386                .try_recv()
11387                .expect("HELLO deny addition must emit route.closed")
11388                .frame,
11389            "target",
11390        );
11391        assert_eq!(forwarding.active_binding_count().unwrap(), 0);
11392        assert!(matches!(
11393            target_rx.try_recv(),
11394            Ok(outbound) if outbound.header.ty == FrameType::Goodbye
11395        ));
11396    }
11397
11398    #[tokio::test]
11399    async fn capability_claim_catalog_update_mutation_proof_force_closes_existing_route() {
11400        let registry = Arc::new(Registry::default());
11401        let forwarding = Arc::new(ForwardingTable::default());
11402        let supervisor = SupervisorHandle::new();
11403        supervisor.set_spawn_nonce("opener", "opener-nonce".to_string());
11404        let handler =
11405            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
11406                .with_supervisor(supervisor);
11407        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(720));
11408        let (opener_ctx, mut opener_rx) = route_ctx(ConnectionId::new(721));
11409        register_capability_manifest(
11410            &handler,
11411            &target_ctx,
11412            &mut target_rx,
11413            capability_manifest("target", &[], &[]),
11414            1,
11415        )
11416        .await;
11417        register_capability_manifest(
11418            &handler,
11419            &opener_ctx,
11420            &mut opener_rx,
11421            capability_manifest("opener", &[], &["credentials-provider/v1"]),
11422            2,
11423        )
11424        .await;
11425        let (mut client_rx, _) = open_route_for_capability_test(
11426            &handler,
11427            &target_ctx,
11428            &mut target_rx,
11429            722,
11430            3,
11431            "target",
11432            Some(ConsumerIdentity {
11433                module_id: "opener".to_string(),
11434                launch_nonce: "opener-nonce".to_string(),
11435            }),
11436        )
11437        .await;
11438        assert_eq!(forwarding.active_binding_count().unwrap(), 1);
11439
11440        let replies = handler
11441            .handle_control_frame(
11442                &target_ctx,
11443                catalog_update_with_capabilities_frame(
11444                    4,
11445                    CapabilityDeclarations {
11446                        provides: vec!["credentials-provider/v1".to_string()],
11447                        requires: Vec::new(),
11448                        must_never_reach: Vec::new(),
11449                    },
11450                ),
11451            )
11452            .await
11453            .expect("claim catalog.update succeeds");
11454        assert!(matches!(
11455            serde_json::from_slice::<ModuleControlResponseToModule>(&replies[0].body),
11456            Ok(ModuleControlResponseToModule::CatalogUpdate {})
11457        ));
11458        assert_capability_denied_push(
11459            client_rx
11460                .try_recv()
11461                .expect("claim addition must emit route.closed")
11462                .frame,
11463            "target",
11464        );
11465        assert_eq!(forwarding.active_binding_count().unwrap(), 0);
11466        assert!(matches!(
11467            target_rx.try_recv(),
11468            Ok(outbound) if outbound.header.ty == FrameType::Goodbye
11469        ));
11470    }
11471
11472    #[tokio::test]
11473    async fn capability_claim_removal_mutation_proof_keeps_route_open_without_close_frame() {
11474        let registry = Arc::new(Registry::default());
11475        let forwarding = Arc::new(ForwardingTable::default());
11476        let supervisor = SupervisorHandle::new();
11477        supervisor.set_spawn_nonce("opener", "opener-nonce".to_string());
11478        let handler =
11479            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
11480                .with_supervisor(supervisor);
11481        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(730));
11482        let (opener_ctx, mut opener_rx) = route_ctx(ConnectionId::new(731));
11483        register_capability_manifest(
11484            &handler,
11485            &target_ctx,
11486            &mut target_rx,
11487            capability_manifest("target", &["credentials-provider/v1"], &[]),
11488            1,
11489        )
11490        .await;
11491        register_capability_manifest(
11492            &handler,
11493            &opener_ctx,
11494            &mut opener_rx,
11495            capability_manifest("opener", &[], &[]),
11496            2,
11497        )
11498        .await;
11499        let (mut client_rx, _) = open_route_for_capability_test(
11500            &handler,
11501            &target_ctx,
11502            &mut target_rx,
11503            732,
11504            3,
11505            "target",
11506            Some(ConsumerIdentity {
11507                module_id: "opener".to_string(),
11508                launch_nonce: "opener-nonce".to_string(),
11509            }),
11510        )
11511        .await;
11512        assert_eq!(forwarding.active_binding_count().unwrap(), 1);
11513
11514        handler
11515            .handle_control_frame(
11516                &target_ctx,
11517                catalog_update_with_capabilities_frame(
11518                    4,
11519                    CapabilityDeclarations {
11520                        provides: Vec::new(),
11521                        requires: Vec::new(),
11522                        must_never_reach: Vec::new(),
11523                    },
11524                ),
11525            )
11526            .await
11527            .expect("claim removal catalog.update succeeds");
11528        assert_eq!(
11529            forwarding.active_binding_count().unwrap(),
11530            1,
11531            "removing an attested target claim must leave the route census unchanged"
11532        );
11533        assert!(
11534            client_rx.try_recv().is_err(),
11535            "claim removal must not emit route.closed capability_denied"
11536        );
11537        assert!(
11538            target_rx.try_recv().is_err(),
11539            "claim removal must not send the target a route GOODBYE"
11540        );
11541    }
11542
11543    /// A direct client may open a route to a denied capability provider; this
11544    /// policy applies only to attested supervised module origins, not to direct clients.
11545    #[tokio::test]
11546    async fn direct_client_scope_honesty_mutation_proof_opens_denied_capability_provider() {
11547        let registry = Arc::new(Registry::default());
11548        let forwarding = Arc::new(ForwardingTable::default());
11549        let supervisor = SupervisorHandle::new();
11550        supervisor.set_spawn_nonce("opener", "opener-nonce".to_string());
11551        let handler =
11552            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
11553                .with_supervisor(supervisor);
11554        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(740));
11555        let (opener_ctx, mut opener_rx) = route_ctx(ConnectionId::new(741));
11556        register_capability_manifest(
11557            &handler,
11558            &target_ctx,
11559            &mut target_rx,
11560            capability_manifest("target", &["credentials-provider/v1"], &[]),
11561            1,
11562        )
11563        .await;
11564        register_capability_manifest(
11565            &handler,
11566            &opener_ctx,
11567            &mut opener_rx,
11568            capability_manifest("opener", &[], &["credentials-provider/v1"]),
11569            2,
11570        )
11571        .await;
11572
11573        let (_client_rx, bind) = open_route_for_capability_test(
11574            &handler,
11575            &target_ctx,
11576            &mut target_rx,
11577            742,
11578            3,
11579            "target",
11580            None,
11581        )
11582        .await;
11583        let ModuleControlRequest::RouteBind { principal, .. } = bind else {
11584            panic!("direct scope-honesty route must bind");
11585        };
11586        assert_eq!(principal, Some(Principal::Direct));
11587        assert_eq!(forwarding.active_binding_count().unwrap(), 1);
11588    }
11589
11590    /// A module that denies a capability receives no self-route exemption when it
11591    /// also attestedly provides that capability.
11592    #[tokio::test]
11593    async fn must_never_reach_self_route_is_capability_forbidden() {
11594        let registry = Arc::new(Registry::default());
11595        let forwarding = Arc::new(ForwardingTable::default());
11596        let supervisor = SupervisorHandle::new();
11597        supervisor.set_spawn_nonce("self-provider", "self-nonce".to_string());
11598        let handler =
11599            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
11600                .with_supervisor(supervisor);
11601        let (self_ctx, mut self_rx) = route_ctx(ConnectionId::new(750));
11602        register_capability_manifest(
11603            &handler,
11604            &self_ctx,
11605            &mut self_rx,
11606            capability_manifest(
11607                "self-provider",
11608                &["credentials-provider/v1"],
11609                &["credentials-provider/v1"],
11610            ),
11611            1,
11612        )
11613        .await;
11614
11615        let (client_ctx, _client_rx) = route_ctx(ConnectionId::new(751));
11616        let replies = handler
11617            .handle_control_frame(
11618                &client_ctx,
11619                route_open_frame_with_admission_facts(
11620                    2,
11621                    "self-provider",
11622                    unique_project_root("admission-facts"),
11623                    Some(ConsumerIdentity {
11624                        module_id: "self-provider".to_string(),
11625                        launch_nonce: "self-nonce".to_string(),
11626                    }),
11627                    None,
11628                ),
11629            )
11630            .await
11631            .expect("self-route refusal returns a typed frame");
11632        assert_eq!(parse_error(&replies[0])["code"], "capability_forbidden");
11633        assert_eq!(forwarding.active_binding_count().unwrap(), 0);
11634        assert!(
11635            self_rx.try_recv().is_err(),
11636            "self denial must not relay route.bind"
11637        );
11638    }
11639
11640    #[test]
11641    fn unsupported_channel_zero_frame_returns_error() {
11642        let handler = ControlHandler::default();
11643        let request = Frame::build(
11644            FrameType::Request,
11645            control_flags(),
11646            0,
11647            0,
11648            21,
11649            b"opaque".to_vec(),
11650        )
11651        .unwrap();
11652
11653        let response = handler
11654            .handle_control(ConnectionId::new(1), request)
11655            .unwrap();
11656
11657        assert_eq!(response[0].header.ty, FrameType::Error);
11658        assert_eq!(
11659            parse_error(&response[0])["code"],
11660            "unsupported_control_frame"
11661        );
11662    }
11663
11664    /// Blue/green swap at the control-plane boundary. The supervisor that opens
11665    /// a swap is not wired yet, so the candidate is registered here directly
11666    /// into the registry and forwarding candidate slots, the way the swap's
11667    /// HELLO admission will.
11668    mod swap {
11669        use super::*;
11670
11671        const INCUMBENT: ConnectionId = ConnectionId::new(30);
11672        const CANDIDATE: ConnectionId = ConnectionId::new(40);
11673
11674        struct Swap {
11675            registry: Arc<Registry>,
11676            forwarding: Arc<ForwardingTable>,
11677            handler: ControlHandler,
11678            incumbent_ctx: RouteCtx,
11679            incumbent_rx: mpsc::Receiver<crate::router::OutboundFrame>,
11680            candidate_ctx: RouteCtx,
11681            candidate_rx: mpsc::Receiver<crate::router::OutboundFrame>,
11682        }
11683
11684        async fn swap_with_incumbent() -> Swap {
11685            let registry = Arc::new(Registry::default());
11686            let forwarding = Arc::new(ForwardingTable::default());
11687            let handler =
11688                ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
11689            let (incumbent_ctx, mut incumbent_rx) = route_ctx(INCUMBENT);
11690            hello_via_sink(
11691                &handler,
11692                &incumbent_ctx,
11693                &mut incumbent_rx,
11694                hello_frame("aft", PROTOCOL_VERSION, 7),
11695            )
11696            .await;
11697            let (candidate_ctx, candidate_rx) = route_ctx(CANDIDATE);
11698            Swap {
11699                registry,
11700                forwarding,
11701                handler,
11702                incumbent_ctx,
11703                incumbent_rx,
11704                candidate_ctx,
11705                candidate_rx,
11706            }
11707        }
11708
11709        fn register_candidate(swap: &Swap, ready: Option<bool>) {
11710            let mut candidate_manifest = manifest("aft", PROTOCOL_VERSION);
11711            candidate_manifest.ready = ready;
11712            let registration = swap
11713                .registry
11714                .register_candidate_with_control_ops(
11715                    candidate_manifest,
11716                    PROTOCOL_VERSION,
11717                    CANDIDATE,
11718                    module_baseline_control_ops(),
11719                )
11720                .unwrap();
11721            swap.forwarding
11722                .register_candidate_module_connection(
11723                    CANDIDATE,
11724                    "aft".to_string(),
11725                    PROTOCOL_VERSION,
11726                    manifest_concurrency(&registration.manifest),
11727                    swap.candidate_ctx.egress.clone(),
11728                )
11729                .unwrap();
11730        }
11731
11732        fn cutover(swap: &Swap) -> crate::forwarding::ModuleEndpointId {
11733            let cutover = swap.forwarding.cutover_candidate("aft").unwrap().unwrap();
11734            swap.registry.promote_candidate("aft").unwrap().unwrap();
11735            cutover.incumbent.unwrap()
11736        }
11737
11738        fn keyed_total(counters: &Value, key: &str) -> u64 {
11739            counters[key]
11740                .as_object()
11741                .map(|counts| counts.values().filter_map(Value::as_u64).sum())
11742                .unwrap_or(0)
11743        }
11744
11745        /// An ack from the incumbent for a bind it was sent before cutover,
11746        /// arriving before the incumbent is drained. The incumbent is the live
11747        /// connection carrying every other client's routes, so the ack must
11748        /// not end it: the waiting client is told to retry, the reservation is
11749        /// given back, and the incumbent is told to drop just that binding.
11750        #[tokio::test]
11751        async fn incumbent_ack_between_promotion_and_drain_keeps_the_incumbent_serving() {
11752            let mut swap = swap_with_incumbent().await;
11753            let handler = swap.handler.clone();
11754
11755            // A co-tenant route, bound on the incumbent before the swap.
11756            let cotenant = ConnectionId::new(31);
11757            let (cotenant_ctx, mut cotenant_rx) = route_ctx(cotenant);
11758            let (cotenant_task, cotenant_bind) = relay_route_open(
11759                &handler,
11760                cotenant,
11761                &cotenant_ctx.egress,
11762                &mut swap.incumbent_rx,
11763                100,
11764                "aft",
11765                "swap-cotenant",
11766            )
11767            .await;
11768            handler
11769                .handle_control_frame(
11770                    &swap.incumbent_ctx,
11771                    route_bind_ack(cotenant_bind.header.corr),
11772                )
11773                .await
11774                .unwrap();
11775            assert!(cotenant_task.await.unwrap().is_empty());
11776            let (cotenant_channel, cotenant_epoch) =
11777                published_route(&cotenant_rx.recv().await.unwrap());
11778
11779            // A second route.open, relayed to the incumbent and not yet acked.
11780            let caller = ConnectionId::new(32);
11781            let (caller_ctx, mut caller_rx) = route_ctx(caller);
11782            let (caller_task, caller_bind) = relay_route_open(
11783                &handler,
11784                caller,
11785                &caller_ctx.egress,
11786                &mut swap.incumbent_rx,
11787                101,
11788                "aft",
11789                "swap-caller",
11790            )
11791            .await;
11792            let (abandoned_channel, abandoned_epoch) = route_bind_channel(&caller_bind);
11793
11794            register_candidate(&swap, None);
11795            cutover(&swap);
11796
11797            // The incumbent acks after promotion and before any drain.
11798            let ack = handler
11799                .handle_control_frame(&swap.incumbent_ctx, route_bind_ack(caller_bind.header.corr))
11800                .await;
11801            let module_loop_error = ack.as_ref().err().map(ToString::to_string);
11802            if module_loop_error.is_some() {
11803                // What the connection loop does with an untranslated router
11804                // error: end the connection, releasing every route on it.
11805                handler.cleanup_connection(INCUMBENT).unwrap();
11806            }
11807
11808            // 1. The incumbent's other routes survive.
11809            assert!(
11810                cotenant_rx.try_recv().is_err(),
11811                "the co-tenant route on the incumbent was torn down by one late ack: \
11812                 {module_loop_error:?}"
11813            );
11814            assert!(matches!(
11815                swap.forwarding
11816                    .lookup_data_route(cotenant, cotenant_channel, cotenant_epoch)
11817                    .unwrap(),
11818                DataRoute::Client(DataRouteState::Bound(_))
11819            ));
11820            assert_eq!(module_loop_error, None);
11821            assert!(swap
11822                .registry
11823                .get_module_by_connection(INCUMBENT)
11824                .unwrap()
11825                .is_some());
11826
11827            // 2. Exactly one channel-scoped GOODBYE to the incumbent.
11828            let goodbye = tokio::time::timeout(Duration::from_secs(1), swap.incumbent_rx.recv())
11829                .await
11830                .expect("the incumbent is told to drop the abandoned binding")
11831                .unwrap()
11832                .frame;
11833            assert_eq!(goodbye.header.ty, FrameType::Goodbye);
11834            assert_eq!(goodbye.header.channel, abandoned_channel);
11835            assert_eq!(goodbye.header.epoch, abandoned_epoch);
11836            assert!(swap.incumbent_rx.try_recv().is_err());
11837
11838            // 3. The waiting client gets a retryable refusal and no route.
11839            let response = caller_task.await.unwrap();
11840            assert_eq!(response.len(), 1);
11841            assert_eq!(parse_error(&response[0])["code"], "module_reloading");
11842            assert!(caller_rx.try_recv().is_err());
11843
11844            // 4. The reservation pair is given back, and the pending bind
11845            //    settled exactly once: one accepted open (the co-tenant) and one
11846            //    refused open (the caller), nothing counted twice.
11847            assert_eq!(swap.forwarding.reserved_route_count().unwrap(), (0, 0));
11848            let counters = handler.counters().snapshot();
11849            assert_eq!(
11850                keyed_total(&counters, "route_open_accepted_by_principal"),
11851                1
11852            );
11853            assert_eq!(keyed_total(&counters, "route_open_refused_by_code"), 1);
11854            assert_eq!(counters["route_open_refused_by_code"]["module_rejected"], 1);
11855        }
11856
11857        /// After cutover the incumbent is drained BY ENDPOINT. Draining by module
11858        /// id would resolve to the promoted candidate and every new route.open
11859        /// would be refused as reloading, leaving neither process routable.
11860        #[tokio::test]
11861        async fn route_open_after_cutover_and_incumbent_drain_is_relayed_to_the_candidate() {
11862            let mut swap = swap_with_incumbent().await;
11863            register_candidate(&swap, None);
11864            let incumbent = cutover(&swap);
11865            swap.forwarding
11866                .begin_endpoint_drain(incumbent, RouteCloseReason::Restart)
11867                .unwrap()
11868                .expect("the incumbent is still registered");
11869
11870            let client = ConnectionId::new(33);
11871            let (client_ctx, mut client_rx) = route_ctx(client);
11872            let route_handler = swap.handler.clone();
11873            let open_ctx = RouteCtx {
11874                connection_id: client,
11875                egress: client_ctx.egress.clone(),
11876            };
11877            let mut route_task = tokio::spawn(async move {
11878                route_handler
11879                    .handle_control_frame(
11880                        &open_ctx,
11881                        route_open_frame(90, "aft", unique_project_root("swap-after-drain")),
11882                    )
11883                    .await
11884                    .unwrap()
11885            });
11886            let bind = tokio::select! {
11887                bind = swap.candidate_rx.recv() => bind.expect("candidate egress is open").frame,
11888                response = &mut route_task => {
11889                    let response = response.unwrap();
11890                    panic!(
11891                        "post-cutover route.open was refused instead of relayed to the candidate: {}",
11892                        parse_error(&response[0])["code"]
11893                    );
11894                }
11895            };
11896            swap.handler
11897                .handle_control_frame(&swap.candidate_ctx, route_bind_ack(bind.header.corr))
11898                .await
11899                .unwrap();
11900            assert!(route_task.await.unwrap().is_empty());
11901            let (channel, epoch) = published_route(&client_rx.recv().await.unwrap());
11902            match swap
11903                .forwarding
11904                .lookup_data_route(client, channel, epoch)
11905                .unwrap()
11906            {
11907                DataRoute::Client(DataRouteState::Bound(route)) => {
11908                    assert_eq!(route.module_endpoint.connection_id, CANDIDATE)
11909                }
11910                other => panic!("expected a bound route on the candidate, got {other:?}"),
11911            }
11912            assert!(swap.incumbent_rx.try_recv().is_err());
11913        }
11914
11915        /// A candidate declares itself ready with `catalog.update` on its own
11916        /// connection. If the connection-keyed registry lookups searched only the
11917        /// active slot, this would answer `not_registered` and the candidate
11918        /// would never become ready.
11919        #[tokio::test]
11920        async fn candidate_catalog_update_ready_reaches_the_candidate_registration() {
11921            let swap = swap_with_incumbent().await;
11922            register_candidate(&swap, Some(false));
11923            let update = Frame::build(
11924                FrameType::Request,
11925                control_flags(),
11926                0,
11927                0,
11928                55,
11929                serde_json::to_vec(&ModuleControlRequestFromModule::CatalogUpdate {
11930                    provides: manifest("aft", PROTOCOL_VERSION).provides,
11931                    capabilities: None,
11932                    ready: Some(true),
11933                })
11934                .unwrap(),
11935            )
11936            .unwrap();
11937
11938            let replies = swap
11939                .handler
11940                .handle_control_frame(&swap.candidate_ctx, update)
11941                .await
11942                .unwrap();
11943
11944            assert_eq!(replies.len(), 1);
11945            assert_eq!(
11946                replies[0].header.ty,
11947                FrameType::Response,
11948                "candidate catalog.update was refused: {:?}",
11949                serde_json::from_slice::<Value>(&replies[0].body).ok()
11950            );
11951            assert!(swap.registry.get_candidate("aft").unwrap().unwrap().ready);
11952            assert_eq!(
11953                swap.registry
11954                    .get_module("aft")
11955                    .unwrap()
11956                    .unwrap()
11957                    .connection_id,
11958                INCUMBENT
11959            );
11960        }
11961    }
11962
11963    /// The HELLO gate while the supervisor has a swap open: only the nonce it
11964    /// minted for the candidate admits a second process, into the candidate
11965    /// slot, and that check runs ahead of the reserved-module gate.
11966    mod swap_admission {
11967        use super::*;
11968
11969        const INCUMBENT_NONCE: &str = "incumbent-nonce";
11970        const CANDIDATE_NONCE: &str = "candidate-nonce";
11971
11972        fn handler_with_incumbent(
11973            module_id: &str,
11974            reserved: bool,
11975        ) -> (Arc<Registry>, SupervisorHandle, ControlHandler) {
11976            let registry = Arc::new(Registry::default());
11977            let supervisor = SupervisorHandle::new();
11978            supervisor.set_spawn_nonce(module_id, INCUMBENT_NONCE.to_string());
11979            if reserved {
11980                supervisor.set_reserved_nonce(module_id, INCUMBENT_NONCE.to_string());
11981            }
11982            let handler =
11983                ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor.clone());
11984            let incumbent = handler
11985                .handle_control(
11986                    ConnectionId::new(1),
11987                    hello_frame_with_nonce(module_id, PROTOCOL_VERSION, 1, Some(INCUMBENT_NONCE)),
11988                )
11989                .unwrap();
11990            assert_eq!(incumbent[0].header.ty, FrameType::HelloAck);
11991            supervisor.open_swap(module_id, CANDIDATE_NONCE.to_string());
11992            (registry, supervisor, handler)
11993        }
11994
11995        /// Design mutation arm (ii). On an UNRESERVED id the reserved gate
11996        /// admits every nonce, so while a swap is open the swap gate is the only
11997        /// thing between a key-holder and the candidate slot. A nonce the
11998        /// supervisor did not mint, or none at all, is refused, and neither the
11999        /// incumbent's registration nor the candidate slot moves.
12000        #[test]
12001        fn unminted_nonce_on_an_unreserved_id_with_an_open_swap_is_refused() {
12002            let (registry, _supervisor, handler) = handler_with_incumbent("aft", false);
12003
12004            for (connection, nonce) in [(2, Some("forged")), (3, None)] {
12005                let replies = handler
12006                    .handle_control(
12007                        ConnectionId::new(connection),
12008                        hello_frame_with_nonce("aft", PROTOCOL_VERSION, connection, nonce),
12009                    )
12010                    .unwrap();
12011                assert_eq!(replies[0].header.ty, FrameType::Error);
12012                assert_eq!(
12013                    parse_error(&replies[0])["code"],
12014                    "swap_token_invalid",
12015                    "nonce {nonce:?}"
12016                );
12017            }
12018            assert!(registry.get_candidate("aft").unwrap().is_none());
12019            assert_eq!(
12020                registry.get_module("aft").unwrap().unwrap().connection_id,
12021                ConnectionId::new(1)
12022            );
12023
12024            // Control: the minted token is admitted, into the candidate slot,
12025            // and only once.
12026            let admitted = handler
12027                .handle_control(
12028                    ConnectionId::new(4),
12029                    hello_frame_with_nonce("aft", PROTOCOL_VERSION, 4, Some(CANDIDATE_NONCE)),
12030                )
12031                .unwrap();
12032            assert_eq!(admitted[0].header.ty, FrameType::HelloAck);
12033            assert_eq!(
12034                registry
12035                    .get_candidate("aft")
12036                    .unwrap()
12037                    .unwrap()
12038                    .connection_id,
12039                ConnectionId::new(4)
12040            );
12041            assert_eq!(
12042                registry.get_module("aft").unwrap().unwrap().connection_id,
12043                ConnectionId::new(1),
12044                "the candidate must not take the active slot"
12045            );
12046            let replayed = handler
12047                .handle_control(
12048                    ConnectionId::new(5),
12049                    hello_frame_with_nonce("aft", PROTOCOL_VERSION, 5, Some(CANDIDATE_NONCE)),
12050                )
12051                .unwrap();
12052            assert_eq!(parse_error(&replayed[0])["code"], "swap_token_invalid");
12053
12054            // The case only this gate covers: the incumbent has died mid-swap,
12055            // so its duplicate refusal is gone too, and without the gate a
12056            // key-holder would take the id's ACTIVE slot.
12057            handler.cleanup_connection(ConnectionId::new(1)).unwrap();
12058            let squatter = handler
12059                .handle_control(
12060                    ConnectionId::new(6),
12061                    hello_frame_with_nonce("aft", PROTOCOL_VERSION, 6, Some("forged")),
12062                )
12063                .unwrap();
12064            assert_eq!(parse_error(&squatter[0])["code"], "swap_token_invalid");
12065            assert!(
12066                registry.get_module("aft").unwrap().is_none(),
12067                "a squatter took the active slot of an id being swapped"
12068            );
12069        }
12070
12071        /// Design mutation arm (iii). A reserved module's candidate presents a
12072        /// nonce the reserved gate has never seen (that gate holds the
12073        /// incumbent's), so the swap gate must run first or the candidate is
12074        /// refused `reserved_module` and a reserved module can never be swapped.
12075        #[test]
12076        fn reserved_module_candidate_is_admitted_ahead_of_the_reserved_gate() {
12077            let (registry, _supervisor, handler) = handler_with_incumbent("vault", true);
12078
12079            let replies = handler
12080                .handle_control(
12081                    ConnectionId::new(2),
12082                    hello_frame_with_nonce("vault", PROTOCOL_VERSION, 2, Some(CANDIDATE_NONCE)),
12083                )
12084                .unwrap();
12085
12086            assert_eq!(
12087                replies[0].header.ty,
12088                FrameType::HelloAck,
12089                "reserved candidate refused: {:?}",
12090                serde_json::from_slice::<Value>(&replies[0].body).ok()
12091            );
12092            assert_eq!(
12093                registry
12094                    .get_candidate("vault")
12095                    .unwrap()
12096                    .unwrap()
12097                    .connection_id,
12098                ConnectionId::new(2)
12099            );
12100        }
12101
12102        /// With no swap open the gate is inert: the incumbent's reserved gate
12103        /// and duplicate refusal behave exactly as before.
12104        #[test]
12105        fn without_an_open_swap_the_ordinary_gates_decide() {
12106            let (registry, supervisor, handler) = handler_with_incumbent("vault", true);
12107            supervisor.close_swap("vault");
12108
12109            let candidate = handler
12110                .handle_control(
12111                    ConnectionId::new(2),
12112                    hello_frame_with_nonce("vault", PROTOCOL_VERSION, 2, Some(CANDIDATE_NONCE)),
12113                )
12114                .unwrap();
12115            assert_eq!(parse_error(&candidate[0])["code"], "reserved_module");
12116            let duplicate = handler
12117                .handle_control(
12118                    ConnectionId::new(3),
12119                    hello_frame_with_nonce("vault", PROTOCOL_VERSION, 3, Some(INCUMBENT_NONCE)),
12120                )
12121                .unwrap();
12122            assert_eq!(parse_error(&duplicate[0])["code"], "duplicate_module_id");
12123            assert!(registry.get_candidate("vault").unwrap().is_none());
12124        }
12125    }
12126
12127    /// `scope.sync` and `scope.describe` through the real control handler: who
12128    /// may sync is decided by the registration and launch nonce of the module
12129    /// connection, never by the request body.
12130    mod scopes {
12131        use subc_protocol::scope::{
12132            ParentState, ScopeCarrier, ScopeKind, ScopeParent, ScopeRecordOutcome, ScopeStamp,
12133            ScopeStatus,
12134        };
12135
12136        use super::*;
12137
12138        const OWNER: &str = "prefrontal-core";
12139
12140        fn head(scope_ref: &str, scope_epoch: u64) -> ScopeRecord {
12141            ScopeRecord {
12142                scope_ref: scope_ref.to_string(),
12143                scope_epoch,
12144                kind: ScopeKind::Head,
12145                parent: None,
12146                child_owners: Vec::new(),
12147                carriers: Vec::new(),
12148                attributes: Default::default(),
12149            }
12150        }
12151
12152        async fn call(
12153            handler: &ControlHandler,
12154            ctx: &RouteCtx,
12155            request: &ModuleControlRequestFromModule,
12156        ) -> Frame {
12157            let body = serde_json::to_vec(request).unwrap();
12158            let frame = Frame::build(FrameType::Request, control_flags(), 0, 0, 77, body).unwrap();
12159            let mut replies = handler.handle_control_frame(ctx, frame).await.unwrap();
12160            assert_eq!(replies.len(), 1, "{replies:?}");
12161            replies.pop().unwrap()
12162        }
12163
12164        async fn sync(
12165            handler: &ControlHandler,
12166            ctx: &RouteCtx,
12167            generation: u64,
12168            scopes: Vec<ScopeRecord>,
12169        ) -> Result<ModuleControlResponseToModule, String> {
12170            let reply = call(
12171                handler,
12172                ctx,
12173                &ModuleControlRequestFromModule::ScopeSync { generation, scopes },
12174            )
12175            .await;
12176            match reply.header.ty {
12177                FrameType::Response => Ok(serde_json::from_slice(&reply.body).unwrap()),
12178                _ => Err(parse_error(&reply)["code"].as_str().unwrap().to_string()),
12179            }
12180        }
12181
12182        async fn describe(
12183            handler: &ControlHandler,
12184            ctx: &RouteCtx,
12185            owner: &str,
12186            scope_ref: &str,
12187        ) -> ModuleControlResponseToModule {
12188            let reply = call(
12189                handler,
12190                ctx,
12191                &ModuleControlRequestFromModule::ScopeDescribe {
12192                    owner: Principal::Reserved {
12193                        module_id: owner.to_string(),
12194                    },
12195                    scope_ref: scope_ref.to_string(),
12196                },
12197            )
12198            .await;
12199            assert_eq!(
12200                reply.header.ty,
12201                FrameType::Response,
12202                "{:?}",
12203                parse_error(&reply)
12204            );
12205            serde_json::from_slice(&reply.body).unwrap()
12206        }
12207
12208        /// Register `module_id` on `connection` with `nonce`, returning its ctx.
12209        async fn module(
12210            handler: &ControlHandler,
12211            connection: u64,
12212            module_id: &str,
12213            nonce: Option<&str>,
12214        ) -> (RouteCtx, mpsc::Receiver<crate::router::OutboundFrame>) {
12215            let (ctx, mut rx) = route_ctx(ConnectionId::new(connection));
12216            hello_via_sink(
12217                handler,
12218                &ctx,
12219                &mut rx,
12220                hello_frame_with_nonce(module_id, PROTOCOL_VERSION, connection, nonce),
12221            )
12222            .await;
12223            (ctx, rx)
12224        }
12225
12226        /// `direct` and every other client connection has no registration, so
12227        /// it can neither sync nor own a scope.
12228        #[tokio::test]
12229        async fn a_client_connection_cannot_sync_or_describe() {
12230            let handler = ControlHandler::new(Arc::new(Registry::default()));
12231            let (ctx, _rx) = route_ctx(ConnectionId::new(9));
12232            for request in [
12233                ModuleControlRequestFromModule::ScopeSync {
12234                    generation: 1,
12235                    scopes: vec![head("s", 1)],
12236                },
12237                ModuleControlRequestFromModule::ScopeDescribe {
12238                    owner: Principal::Direct,
12239                    scope_ref: "s".to_string(),
12240                },
12241            ] {
12242                let reply = call(&handler, &ctx, &request).await;
12243                assert_eq!(parse_error(&reply)["code"], "not_registered", "{request:?}");
12244            }
12245            assert!(
12246                !handler
12247                    .scopes
12248                    .read()
12249                    .unwrap()
12250                    .describe(
12251                        &Principal::Reserved {
12252                            module_id: OWNER.to_string()
12253                        },
12254                        "s"
12255                    )
12256                    .owner_synced
12257            );
12258        }
12259
12260        /// A module the supervisor did not spawn registers without a launch
12261        /// nonce, so it is never an owner's current launch.
12262        #[tokio::test]
12263        async fn a_module_without_a_supervised_launch_cannot_sync() {
12264            let handler = ControlHandler::new(Arc::new(Registry::default()));
12265            let (ctx, _rx) = module(&handler, 1, OWNER, None).await;
12266            assert_eq!(
12267                sync(&handler, &ctx, 1, vec![head("s", 1)]).await,
12268                Err(error_codes::SCOPE_SYNC_NOT_AUTHORITY.to_string())
12269            );
12270        }
12271
12272        #[tokio::test]
12273        async fn sync_authority_follows_the_supervisors_recorded_spawn_nonce_across_a_swap() {
12274            let supervisor = SupervisorHandle::new();
12275            supervisor.set_spawn_nonce(OWNER, "n1".to_string());
12276            let handler = ControlHandler::new(Arc::new(Registry::default()))
12277                .with_supervisor(supervisor.clone());
12278            let (incumbent, _incumbent_rx) = module(&handler, 1, OWNER, Some("n1")).await;
12279            sync(&handler, &incumbent, 1, vec![head("s", 1)])
12280                .await
12281                .expect("the current launch syncs");
12282
12283            // A swap candidate registers with the swap token and is refused
12284            // while the incumbent keeps syncing.
12285            supervisor.open_swap(OWNER, "n2".to_string());
12286            let (candidate, _candidate_rx) = module(&handler, 2, OWNER, Some("n2")).await;
12287            assert_eq!(
12288                sync(&handler, &candidate, 1, vec![head("x", 1)]).await,
12289                Err(error_codes::SCOPE_SYNC_NOT_AUTHORITY.to_string())
12290            );
12291            sync(&handler, &incumbent, 2, vec![head("s", 1)])
12292                .await
12293                .expect("the serving owner syncs during the swap");
12294
12295            // The swap fails and is rolled back. The candidate never held sync
12296            // authority, and still cannot sync.
12297            supervisor.close_swap(OWNER);
12298            assert_eq!(
12299                sync(&handler, &candidate, 1, vec![head("x", 1)]).await,
12300                Err(error_codes::SCOPE_SYNC_NOT_AUTHORITY.to_string())
12301            );
12302            sync(&handler, &incumbent, 3, vec![head("s", 1)])
12303                .await
12304                .expect("the serving owner syncs after the rollback");
12305            handler.cleanup_connection(candidate.connection_id).unwrap();
12306
12307            // A swap that cuts over. Promotion records the candidate's nonce as
12308            // the module's spawn nonce, which is what `set_spawn_nonce` does
12309            // here; the promoted connection then takes authority at any
12310            // generation and the superseded incumbent is refused.
12311            supervisor.open_swap(OWNER, "n3".to_string());
12312            let (promoted, _promoted_rx) = module(&handler, 3, OWNER, Some("n3")).await;
12313            supervisor.set_spawn_nonce(OWNER, "n3".to_string());
12314            let reply = sync(&handler, &promoted, 1, vec![head("s", 1)])
12315                .await
12316                .expect("the promoted launch takes authority");
12317            let ModuleControlResponseToModule::ScopeSync { results, .. } = reply else {
12318                panic!("unexpected reply {reply:?}");
12319            };
12320            assert_eq!(results[0].outcome, ScopeRecordOutcome::Unchanged);
12321            assert_eq!(
12322                sync(&handler, &incumbent, 4, Vec::new()).await,
12323                Err(error_codes::SCOPE_SYNC_NOT_AUTHORITY.to_string())
12324            );
12325        }
12326
12327        /// Authority dies with its connection: the cleanup path releases it,
12328        /// so the owner's next connection takes it at any generation.
12329        #[tokio::test]
12330        async fn closing_the_authority_connection_frees_sync_authority() {
12331            let supervisor = SupervisorHandle::new();
12332            supervisor.set_spawn_nonce(OWNER, "n1".to_string());
12333            let handler = ControlHandler::new(Arc::new(Registry::default()))
12334                .with_supervisor(supervisor.clone());
12335            let (first, _first_rx) = module(&handler, 1, OWNER, Some("n1")).await;
12336            sync(&handler, &first, 10, vec![head("s", 1)])
12337                .await
12338                .unwrap();
12339            handler.cleanup_connection(first.connection_id).unwrap();
12340
12341            let (second, _second_rx) = module(&handler, 2, OWNER, Some("n1")).await;
12342            sync(&handler, &second, 1, vec![head("s", 1)])
12343                .await
12344                .expect("the next connection takes the released authority");
12345        }
12346
12347        #[tokio::test]
12348        async fn module_goodbye_releases_scope_sync_authority_without_socket_close() {
12349            let supervisor = SupervisorHandle::new();
12350            supervisor.set_spawn_nonce(OWNER, "n1".to_string());
12351            let handler =
12352                ControlHandler::new(Arc::new(Registry::default())).with_supervisor(supervisor);
12353            let (first, _rx) = module(&handler, 1, OWNER, Some("n1")).await;
12354            sync(&handler, &first, 10, vec![head("s", 1)])
12355                .await
12356                .unwrap();
12357            handler
12358                .handle_control_frame(
12359                    &first,
12360                    Frame::build(FrameType::Goodbye, control_flags(), 0, 0, 4, Vec::new()).unwrap(),
12361                )
12362                .await
12363                .unwrap();
12364            let (second, _rx) = module(&handler, 2, OWNER, Some("n1")).await;
12365            sync(&handler, &second, 1, vec![head("s", 1)])
12366                .await
12367                .expect("GOODBYE releases authority even if the old socket remains open");
12368        }
12369
12370        #[tokio::test]
12371        async fn describe_reports_the_incarnation_and_whether_the_owner_is_configured() {
12372            let registry = Arc::new(Registry::default());
12373            let supervisor_handle = SupervisorHandle::new();
12374            let supervisor =
12375                Supervisor::new_for_test(Arc::clone(&registry), RestartPolicy::default())
12376                    .with_handle(supervisor_handle.clone())
12377                    .with_daemon_incarnation("incarnation-7".to_string());
12378            // Configured with enabled: false, so the supervisor lists the
12379            // module without spawning a process for it.
12380            supervisor
12381                .supervise_configured(
12382                    ModuleSpec {
12383                        module_id: OWNER.to_string(),
12384                        program: PathBuf::from("/nonexistent/prefrontal-core"),
12385                        args: Vec::new(),
12386                        env: Vec::new(),
12387                        reserved: false,
12388                        reserved_prefixes: Vec::new(),
12389                        protocol: ModuleProtocol::Subc,
12390                        overlap: Default::default(),
12391                    },
12392                    false,
12393                )
12394                .unwrap();
12395            supervisor_handle.set_spawn_nonce(OWNER, "n1".to_string());
12396            let handler =
12397                ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor_handle);
12398            let (reader, _reader_rx) = module(&handler, 5, "reader", None).await;
12399
12400            // Configured but not yet synced: a reader waits for the owner.
12401            let ModuleControlResponseToModule::ScopeDescribe {
12402                status,
12403                daemon_incarnation,
12404                owner_synced,
12405                owner_configured,
12406                scope,
12407                ..
12408            } = describe(&handler, &reader, OWNER, "s").await
12409            else {
12410                panic!("not a describe reply");
12411            };
12412            assert_eq!(status, ScopeStatus::NotLive);
12413            assert_eq!(daemon_incarnation, "incarnation-7");
12414            assert!(!owner_synced);
12415            assert!(owner_configured);
12416            assert!(scope.is_none());
12417
12418            // Not a supervised module: the owner will never sync, and a reader
12419            // refuses rather than waits.
12420            let ModuleControlResponseToModule::ScopeDescribe {
12421                status,
12422                owner_configured,
12423                ..
12424            } = describe(&handler, &reader, "ghost", "s").await
12425            else {
12426                panic!("not a describe reply");
12427            };
12428            assert_eq!(status, ScopeStatus::NotLive);
12429            assert!(!owner_configured);
12430
12431            // Live, with the stamp fields and the computed owner_authorized.
12432            let (owner, _owner_rx) = module(&handler, 6, OWNER, Some("n1")).await;
12433            sync(&handler, &owner, 1, vec![head("s", 4)]).await.unwrap();
12434            let ModuleControlResponseToModule::ScopeDescribe {
12435                status,
12436                scope_epoch,
12437                owner_synced,
12438                scope,
12439                ..
12440            } = describe(&handler, &reader, OWNER, "s").await
12441            else {
12442                panic!("not a describe reply");
12443            };
12444            assert_eq!(status, ScopeStatus::Live);
12445            assert_eq!(scope_epoch, Some(4));
12446            assert!(owner_synced);
12447            let stamp = scope.expect("a live scope carries its stamp");
12448            assert!(
12449                stamp.owner_authorized,
12450                "prefrontal-core is the default authority"
12451            );
12452            assert_eq!(stamp.kind, ScopeKind::Head);
12453        }
12454
12455        #[tokio::test]
12456        async fn scope_authority_owners_decides_owner_authorized() {
12457            let supervisor = SupervisorHandle::new();
12458            supervisor.set_spawn_nonce("broca", "b1".to_string());
12459            let handler = ControlHandler::new(Arc::new(Registry::default()))
12460                .with_supervisor(supervisor)
12461                .with_scope_authority_owners(vec!["broca".to_string()]);
12462            let (broca, _rx) = module(&handler, 1, "broca", Some("b1")).await;
12463            let mut gated = head("s", 1);
12464            gated.attributes.agent_id = Some("agent".to_string());
12465            sync(&handler, &broca, 1, vec![gated]).await.unwrap();
12466            let ModuleControlResponseToModule::ScopeDescribe { scope, .. } =
12467                describe(&handler, &broca, "broca", "s").await
12468            else {
12469                panic!("not a describe reply");
12470            };
12471            assert!(scope.unwrap().owner_authorized);
12472        }
12473
12474        /// With route admission, the stamp, the commit re-check and drains in
12475        /// place, the feature is advertised: the module ops in HELLO_ACK, and
12476        /// `scopes/v1` in HELLO_ACK and `server.describe`.
12477        #[tokio::test]
12478        async fn scope_ops_and_the_scopes_capability_are_advertised() {
12479            let handler = ControlHandler::new(Arc::new(Registry::default()));
12480            let (ctx, mut rx) = route_ctx(ConnectionId::new(1));
12481            let ack = hello_via_sink(
12482                &handler,
12483                &ctx,
12484                &mut rx,
12485                hello_frame("m", PROTOCOL_VERSION, 1),
12486            )
12487            .await;
12488            let ack = parse_ack(&ack);
12489            for op in [SCOPE_SYNC_OP, SCOPE_DESCRIBE_OP] {
12490                assert!(ack.subc_ops.iter().any(|o| o == op), "{:?}", ack.subc_ops);
12491            }
12492            assert!(ack.subc_capabilities.iter().any(|c| c == CAP_SCOPES_V1));
12493
12494            let (client, _client_rx) = route_ctx(ConnectionId::new(2));
12495            let body = serde_json::to_vec(&ClientControlRequest::ServerDescribe {}).unwrap();
12496            let frame = Frame::build(FrameType::Request, control_flags(), 0, 0, 5, body).unwrap();
12497            let reply = handler
12498                .handle_control_frame(&client, frame)
12499                .await
12500                .unwrap()
12501                .pop()
12502                .unwrap();
12503            let ClientControlResponse::ServerDescribe { capabilities, .. } =
12504                serde_json::from_slice(&reply.body).unwrap()
12505            else {
12506                panic!("not a server.describe reply");
12507            };
12508            assert!(
12509                capabilities.iter().any(|c| c == CAP_SCOPES_V1),
12510                "{capabilities:?}"
12511            );
12512        }
12513
12514        // ---- route admission, stamps, commit re-check and drains ----------
12515
12516        const PLEXUS: &str = "plexus";
12517        const OTHER: &str = "other";
12518        const AFT: &str = "aft";
12519        const BROCA: &str = "broca";
12520        const MAGIC: &str = "magic-context";
12521
12522        fn nonce(module_id: &str) -> String {
12523            format!("nonce-{module_id}")
12524        }
12525
12526        fn wide_ctx(connection: u64) -> (RouteCtx, mpsc::Receiver<crate::router::OutboundFrame>) {
12527            let (tx, rx) = mpsc::channel(64);
12528            (
12529                RouteCtx {
12530                    connection_id: ConnectionId::new(connection),
12531                    egress: FrameSink::new(tx),
12532                },
12533                rx,
12534            )
12535        }
12536
12537        /// A daemon with a configured owner (prefrontal-core) registered on its
12538        /// own module connection, two routable targets (plexus, other), and
12539        /// launch nonces minted for the modules that open routes as carriers.
12540        struct Rig {
12541            handler: ControlHandler,
12542            forwarding: Arc<ForwardingTable>,
12543            owner: RouteCtx,
12544            _owner_rx: mpsc::Receiver<crate::router::OutboundFrame>,
12545            modules: BTreeMap<String, (RouteCtx, mpsc::Receiver<crate::router::OutboundFrame>)>,
12546            generation: u64,
12547            next_connection: u64,
12548            _supervisor: Supervisor,
12549        }
12550
12551        async fn rig() -> Rig {
12552            rig_with_flow_support(true).await
12553        }
12554
12555        async fn rig_with_flow_support(flow_support: bool) -> Rig {
12556            let registry = Arc::new(Registry::default());
12557            let forwarding = Arc::new(ForwardingTable::default());
12558            let supervisor_handle = SupervisorHandle::new();
12559            let supervisor =
12560                Supervisor::new_for_test(Arc::clone(&registry), RestartPolicy::default())
12561                    .with_handle(supervisor_handle.clone());
12562            supervisor
12563                .supervise_configured(
12564                    ModuleSpec {
12565                        module_id: OWNER.to_string(),
12566                        program: PathBuf::from("/nonexistent/prefrontal-core"),
12567                        args: Vec::new(),
12568                        env: Vec::new(),
12569                        reserved: false,
12570                        reserved_prefixes: Vec::new(),
12571                        protocol: ModuleProtocol::Subc,
12572                        overlap: Default::default(),
12573                    },
12574                    false,
12575                )
12576                .unwrap();
12577            for module_id in [OWNER, AFT, BROCA, MAGIC] {
12578                supervisor_handle.set_spawn_nonce(module_id, nonce(module_id));
12579            }
12580            let handler =
12581                ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
12582                    .with_supervisor(supervisor_handle);
12583            let (owner, mut owner_rx) = wide_ctx(1);
12584            hello_via_sink(
12585                &handler,
12586                &owner,
12587                &mut owner_rx,
12588                hello_frame_with_nonce(OWNER, PROTOCOL_VERSION, 1, Some(&nonce(OWNER))),
12589            )
12590            .await;
12591            let mut modules = BTreeMap::new();
12592            for (connection, module_id) in [(2, PLEXUS), (3, OTHER)] {
12593                let (ctx, mut rx) = wide_ctx(connection);
12594                let hello = hello_frame(module_id, PROTOCOL_VERSION, connection);
12595                let mut body: Value = serde_json::from_slice(&hello.body).unwrap();
12596                // A decoder version alone must not admit flow routes. Every
12597                // target here declares wire crate version 0.29.0; only one that
12598                // declares `flow-scopes/v1` promises flow behaviour.
12599                body["manifest"]["provenance"] =
12600                    serde_json::json!({"wire_crate_version": "0.29.0"});
12601                if flow_support {
12602                    body["manifest"]["capabilities"] =
12603                        serde_json::json!({"provides": ["flow-scopes/v1"]});
12604                }
12605                let hello = Frame::build(
12606                    FrameType::Hello,
12607                    control_flags(),
12608                    0,
12609                    0,
12610                    connection,
12611                    serde_json::to_vec(&body).unwrap(),
12612                )
12613                .unwrap();
12614                hello_via_sink(&handler, &ctx, &mut rx, hello).await;
12615                modules.insert(module_id.to_string(), (ctx, rx));
12616            }
12617            Rig {
12618                handler,
12619                forwarding,
12620                owner,
12621                _owner_rx: owner_rx,
12622                modules,
12623                generation: 0,
12624                next_connection: 100,
12625                _supervisor: supervisor,
12626            }
12627        }
12628
12629        fn carrier(module_id: &str, targets: Option<&[&str]>) -> ScopeCarrier {
12630            ScopeCarrier {
12631                principal: Principal::Reserved {
12632                    module_id: module_id.to_string(),
12633                },
12634                targets: targets.map(|targets| targets.iter().map(|t| t.to_string()).collect()),
12635            }
12636        }
12637
12638        /// The scope most tests open under: aft carries to any module, broca
12639        /// only to plexus and other, and the owner delegates as agent-1.
12640        fn session(scope_epoch: u64) -> ScopeRecord {
12641            let mut record = head("s", scope_epoch);
12642            record.carriers = vec![carrier(AFT, None), carrier(BROCA, Some(&[PLEXUS, OTHER]))];
12643            record.attributes.agent_id = Some("agent-1".to_string());
12644            record.attributes.delegates = true;
12645            record
12646        }
12647
12648        impl Rig {
12649            async fn sync(&mut self, scopes: Vec<ScopeRecord>) {
12650                self.generation += 1;
12651                sync(&self.handler, &self.owner, self.generation, scopes)
12652                    .await
12653                    .expect("the owner's sync is accepted");
12654            }
12655
12656            fn selector(&self, scope_ref: &str, scope_epoch: Option<u64>) -> ScopeSelector {
12657                ScopeSelector {
12658                    owner: Principal::Reserved {
12659                        module_id: OWNER.to_string(),
12660                    },
12661                    scope_ref: scope_ref.to_string(),
12662                    scope_epoch,
12663                }
12664            }
12665
12666            fn open_frame(
12667                &mut self,
12668                opener: Option<&str>,
12669                target: &str,
12670                scope: Option<ScopeSelector>,
12671            ) -> (
12672                RouteCtx,
12673                mpsc::Receiver<crate::router::OutboundFrame>,
12674                Frame,
12675            ) {
12676                self.next_connection += 1;
12677                let (ctx, rx) = wide_ctx(self.next_connection);
12678                let root = unique_project_root("scoped-open");
12679                let body = serde_json::to_vec(&ClientControlRequest::RouteOpen {
12680                    target: RouteTarget::ToolProvider {
12681                        module_id: target.to_string(),
12682                    },
12683                    identity: BindIdentity::new(
12684                        root.path().to_path_buf(),
12685                        "unit".to_string(),
12686                        "session".to_string(),
12687                    ),
12688                    consumer_identity: opener.map(|module_id| ConsumerIdentity {
12689                        module_id: module_id.to_string(),
12690                        launch_nonce: nonce(module_id),
12691                    }),
12692                    consumer_capabilities: None,
12693                    role_versions: None,
12694                    admission_facts: None,
12695                    scope,
12696                })
12697                .unwrap();
12698                let frame = Frame::build(
12699                    FrameType::Request,
12700                    control_flags(),
12701                    0,
12702                    0,
12703                    self.next_connection,
12704                    body,
12705                )
12706                .unwrap();
12707                (ctx, rx, frame)
12708            }
12709
12710            /// Open and expect a refusal before anything is relayed.
12711            async fn refused(
12712                &mut self,
12713                opener: Option<&str>,
12714                target: &str,
12715                scope: Option<ScopeSelector>,
12716            ) -> String {
12717                self.refusal_body(opener, target, scope).await["code"]
12718                    .as_str()
12719                    .unwrap()
12720                    .to_string()
12721            }
12722
12723            async fn refusal_body(
12724                &mut self,
12725                opener: Option<&str>,
12726                target: &str,
12727                scope: Option<ScopeSelector>,
12728            ) -> Value {
12729                let (ctx, _rx, frame) = self.open_frame(opener, target, scope);
12730                let replies = tokio::time::timeout(
12731                    Duration::from_secs(2),
12732                    self.handler.handle_control_frame(&ctx, frame),
12733                )
12734                .await
12735                .expect("the open must be refused before waiting for a bind ack")
12736                .unwrap();
12737                assert_eq!(replies.len(), 1, "{replies:?}");
12738                assert_eq!(replies[0].header.ty, FrameType::Error);
12739                let (_, module_rx) = self.modules.get_mut(target).unwrap();
12740                assert!(
12741                    module_rx.try_recv().is_err(),
12742                    "a refused open relays nothing"
12743                );
12744                assert_eq!(self.forwarding.reserved_route_count().unwrap(), (0, 0));
12745                parse_error(&replies[0])
12746            }
12747
12748            /// Start an open and return its task and the bind the target got.
12749            async fn relayed(
12750                &mut self,
12751                opener: Option<&str>,
12752                target: &str,
12753                scope: Option<ScopeSelector>,
12754            ) -> Relayed {
12755                let (ctx, rx, frame) = self.open_frame(opener, target, scope);
12756                let handler = self.handler.clone();
12757                let task_ctx = ctx.clone();
12758                let task = tokio::spawn(async move {
12759                    handler
12760                        .handle_control_frame(&task_ctx, frame)
12761                        .await
12762                        .unwrap()
12763                });
12764                let (_, module_rx) = self.modules.get_mut(target).unwrap();
12765                let bind = tokio::time::timeout(Duration::from_secs(2), module_rx.recv())
12766                    .await
12767                    .expect("the target receives the relayed route.bind")
12768                    .unwrap()
12769                    .frame;
12770                Relayed {
12771                    target: target.to_string(),
12772                    client: ctx,
12773                    client_rx: rx,
12774                    task,
12775                    bind,
12776                }
12777            }
12778
12779            async fn ack(&self, relayed: &Relayed) {
12780                let (module, _) = &self.modules[&relayed.target];
12781                self.handler
12782                    .handle_control_frame(module, route_bind_ack(relayed.bind.header.corr))
12783                    .await
12784                    .unwrap();
12785            }
12786
12787            /// Open, ack and return the bound route.
12788            async fn bound(
12789                &mut self,
12790                opener: Option<&str>,
12791                target: &str,
12792                scope: Option<ScopeSelector>,
12793            ) -> Bound {
12794                let relayed = self.relayed(opener, target, scope).await;
12795                self.ack(&relayed).await;
12796                let Relayed {
12797                    target,
12798                    client,
12799                    mut client_rx,
12800                    task,
12801                    bind,
12802                } = relayed;
12803                assert!(
12804                    task.await.unwrap().is_empty(),
12805                    "the open is answered by commit"
12806                );
12807                let (channel, epoch) = published_route(&client_rx.recv().await.unwrap().frame);
12808                Bound {
12809                    target,
12810                    client,
12811                    client_rx,
12812                    channel,
12813                    epoch,
12814                    bind,
12815                }
12816            }
12817
12818            fn live(&self, route: &Bound) -> bool {
12819                matches!(
12820                    self.forwarding
12821                        .lookup_data_route(route.client.connection_id, route.channel, route.epoch)
12822                        .unwrap(),
12823                    DataRoute::Client(DataRouteState::Bound(_))
12824                )
12825            }
12826        }
12827
12828        struct Relayed {
12829            target: String,
12830            client: RouteCtx,
12831            client_rx: mpsc::Receiver<crate::router::OutboundFrame>,
12832            task: tokio::task::JoinHandle<Vec<Frame>>,
12833            bind: Frame,
12834        }
12835
12836        struct Bound {
12837            target: String,
12838            client: RouteCtx,
12839            client_rx: mpsc::Receiver<crate::router::OutboundFrame>,
12840            channel: u16,
12841            epoch: u32,
12842            bind: Frame,
12843        }
12844
12845        impl Bound {
12846            /// The reason of the `route.closed` this client was sent, after
12847            /// checking it also got a GOODBYE on exactly this route.
12848            fn closed_reason(&mut self) -> RouteCloseReason {
12849                let mut reason = None;
12850                let mut goodbye = false;
12851                while let Ok(outbound) = self.client_rx.try_recv() {
12852                    let frame = outbound.frame;
12853                    match frame.header.ty {
12854                        FrameType::Goodbye => {
12855                            assert_eq!(
12856                                (frame.header.channel, frame.header.epoch),
12857                                (self.channel, self.epoch)
12858                            );
12859                            goodbye = true;
12860                        }
12861                        FrameType::Push => {
12862                            let ClientControlPush::RouteClosed {
12863                                reason: r,
12864                                module_id,
12865                                ..
12866                            } = serde_json::from_slice(&frame.body).unwrap()
12867                            else {
12868                                panic!("unexpected push");
12869                            };
12870                            assert_eq!(module_id, self.target);
12871                            reason = Some(r);
12872                        }
12873                        other => panic!("unexpected frame {other:?}"),
12874                    }
12875                }
12876                assert!(goodbye, "the client is sent a GOODBYE for the closed route");
12877                reason.expect("the client is told why the route closed")
12878            }
12879
12880            fn untouched(&mut self) -> bool {
12881                self.client_rx.try_recv().is_err()
12882            }
12883
12884            fn stamp(&self) -> Option<ScopeStamp> {
12885                match serde_json::from_slice::<ModuleControlRequest>(&self.bind.body).unwrap() {
12886                    ModuleControlRequest::RouteBind { scope, .. } => scope,
12887                    other => panic!("expected a route.bind, got {other:?}"),
12888                }
12889            }
12890        }
12891
12892        #[tokio::test]
12893        async fn only_the_owner_or_a_listed_carrier_is_admitted_and_a_targeted_carrier_only_to_its_modules(
12894        ) {
12895            let mut rig = rig().await;
12896            let mut record = session(1);
12897            record.carriers = vec![carrier(AFT, None), carrier(BROCA, Some(&[PLEXUS]))];
12898            record.child_owners = vec![Principal::Reserved {
12899                module_id: MAGIC.to_string(),
12900            }];
12901            rig.sync(vec![record]).await;
12902            let scope = || Some(rig_selector("s", Some(1)));
12903
12904            // Admitted: the owner, a bare carrier to any module, a targeted
12905            // carrier to its listed module.
12906            rig.bound(Some(OWNER), PLEXUS, scope()).await;
12907            rig.bound(Some(AFT), OTHER, scope()).await;
12908            rig.bound(Some(BROCA), PLEXUS, scope()).await;
12909
12910            // Refused scope_not_carrier: a targeted carrier to an unlisted
12911            // module, a module that is not listed at all (a child owner is not
12912            // a carrier), and a direct key-holder.
12913            for (opener, target) in [(Some(BROCA), OTHER), (Some(MAGIC), PLEXUS), (None, PLEXUS)] {
12914                assert_eq!(
12915                    rig.refused(opener, target, scope()).await,
12916                    error_codes::SCOPE_NOT_CARRIER,
12917                    "{opener:?} -> {target}"
12918                );
12919            }
12920        }
12921
12922        fn rig_selector(scope_ref: &str, scope_epoch: Option<u64>) -> ScopeSelector {
12923            ScopeSelector {
12924                owner: Principal::Reserved {
12925                    module_id: OWNER.to_string(),
12926                },
12927                scope_ref: scope_ref.to_string(),
12928                scope_epoch,
12929            }
12930        }
12931
12932        #[tokio::test]
12933        async fn an_open_without_an_epoch_is_refused_the_owners_included() {
12934            let mut rig = rig().await;
12935            rig.sync(vec![session(1)]).await;
12936            for opener in [OWNER, AFT] {
12937                assert_eq!(
12938                    rig.refused(Some(opener), PLEXUS, Some(rig.selector("s", None)))
12939                        .await,
12940                    error_codes::SCOPE_EPOCH_REQUIRED,
12941                    "{opener}"
12942                );
12943            }
12944        }
12945
12946        #[tokio::test]
12947        async fn admission_separates_not_synced_not_live_and_ended() {
12948            let mut rig = rig().await;
12949            // Before the configured owner's first sync: retryable.
12950            let code = rig
12951                .refused(Some(AFT), PLEXUS, Some(rig_selector("s", Some(1))))
12952                .await;
12953            assert_eq!(code, error_codes::SCOPE_NOT_SYNCED);
12954            assert!(subc_protocol::error_codes::is_retryable_route_open(&code));
12955
12956            // An owner that is not configured will never sync: terminal.
12957            let ghost = ScopeSelector {
12958                owner: Principal::Reserved {
12959                    module_id: "ghost".to_string(),
12960                },
12961                scope_ref: "s".to_string(),
12962                scope_epoch: Some(1),
12963            };
12964            assert_eq!(
12965                rig.refused(Some(AFT), PLEXUS, Some(ghost)).await,
12966                error_codes::SCOPE_NOT_LIVE
12967            );
12968
12969            rig.sync(vec![session(2)]).await;
12970            assert_eq!(
12971                rig.refused(Some(AFT), PLEXUS, Some(rig_selector("missing", Some(1))))
12972                    .await,
12973                error_codes::SCOPE_NOT_LIVE
12974            );
12975            for epoch in [1, 3] {
12976                assert_eq!(
12977                    rig.refused(Some(AFT), PLEXUS, Some(rig_selector("s", Some(epoch))))
12978                        .await,
12979                    error_codes::SCOPE_ENDED,
12980                    "epoch {epoch}"
12981                );
12982            }
12983            // Control: the live epoch is admitted.
12984            rig.bound(Some(AFT), PLEXUS, Some(rig_selector("s", Some(2))))
12985                .await;
12986        }
12987
12988        #[tokio::test]
12989        async fn the_bind_is_stamped_and_owner_authorized_only_for_listed_owners() {
12990            let mut rig = rig().await;
12991            rig.sync(vec![session(1)]).await;
12992            let route = rig
12993                .bound(Some(AFT), PLEXUS, Some(rig_selector("s", Some(1))))
12994                .await;
12995            let stamp = route.stamp().expect("a scoped bind carries the stamp");
12996            assert_eq!(stamp.scope_ref, "s");
12997            assert_eq!(stamp.scope_epoch, 1);
12998            assert_eq!(stamp.kind, ScopeKind::Head);
12999            assert_eq!(stamp.attributes.agent_id.as_deref(), Some("agent-1"));
13000            assert!(stamp.attributes.delegates);
13001            assert!(stamp.owner_authorized);
13002            let unscoped = rig.bound(Some(AFT), PLEXUS, None).await;
13003            assert_eq!(unscoped.stamp(), None, "an unscoped open is not stamped");
13004
13005            // broca owns a scope of its own on its own module connection; it is
13006            // not in scope_authority_owners, so its stamp is not authorized.
13007            let (broca, mut broca_rx) = wide_ctx(50);
13008            hello_via_sink(
13009                &rig.handler,
13010                &broca,
13011                &mut broca_rx,
13012                hello_frame_with_nonce(BROCA, PROTOCOL_VERSION, 50, Some(&nonce(BROCA))),
13013            )
13014            .await;
13015            sync(&rig.handler, &broca, 1, vec![head("b", 1)])
13016                .await
13017                .unwrap();
13018            let own = ScopeSelector {
13019                owner: Principal::Reserved {
13020                    module_id: BROCA.to_string(),
13021                },
13022                scope_ref: "b".to_string(),
13023                scope_epoch: Some(1),
13024            };
13025            let route = rig.bound(Some(BROCA), PLEXUS, Some(own)).await;
13026            assert!(!route.stamp().unwrap().owner_authorized);
13027        }
13028
13029        #[tokio::test]
13030        async fn an_authority_owners_flow_id_without_an_agent_is_stamped_verbatim_on_bind() {
13031            let mut rig = rig().await;
13032            let mut record = head("s", 1);
13033            record.carriers = vec![carrier(AFT, None)];
13034            let flow_id = "Flow:run-7/step_2!~";
13035            record.attributes.flow_id = Some(flow_id.to_string());
13036            let reply = sync(&rig.handler, &rig.owner, 1, vec![record])
13037                .await
13038                .unwrap();
13039            let ModuleControlResponseToModule::ScopeSync { results, .. } = reply else {
13040                panic!("not a sync reply");
13041            };
13042            assert_eq!(results[0].outcome, ScopeRecordOutcome::Created);
13043            let route = rig
13044                .bound(Some(AFT), PLEXUS, Some(rig_selector("s", Some(1))))
13045                .await;
13046            assert!(rig.live(&route), "the stamped bind committed");
13047            let stamp = route.stamp().expect("a flow scope carries a stamp");
13048            assert_eq!(stamp.attributes.flow_id.as_deref(), Some(flow_id));
13049            assert_eq!(stamp.attributes.agent_id, None);
13050            assert!(!stamp.attributes.delegates);
13051            assert!(stamp.owner_authorized);
13052            let unscoped = rig.bound(Some(AFT), PLEXUS, None).await;
13053            assert_eq!(unscoped.stamp(), None);
13054        }
13055
13056        #[tokio::test]
13057        async fn flow_scope_refuses_a_0_29_target_without_flow_capability_and_relays_nothing() {
13058            let mut rig = rig_with_flow_support(false).await;
13059            let mut record = session(1);
13060            record.attributes.flow_id = Some("flow:7".to_string());
13061            rig.sync(vec![record]).await;
13062            for opener in [OWNER, AFT] {
13063                let body = rig
13064                    .refusal_body(Some(opener), PLEXUS, Some(rig_selector("s", Some(1))))
13065                    .await;
13066                assert_eq!(body["code"], "target_flow_unsupported");
13067                let message = body["message"].as_str().unwrap();
13068                for required in [PLEXUS, "flow-scopes/v1"] {
13069                    assert!(message.contains(required), "{message}");
13070                }
13071            }
13072        }
13073
13074        #[tokio::test]
13075        async fn flow_scope_admits_a_capable_target_and_preserves_flow_id_on_bind() {
13076            let mut rig = rig_with_flow_support(true).await;
13077            let mut record = session(1);
13078            record.attributes.flow_id = Some("flow:7".to_string());
13079            rig.sync(vec![record]).await;
13080            for opener in [OWNER, AFT] {
13081                let route = rig
13082                    .bound(Some(opener), PLEXUS, Some(rig_selector("s", Some(1))))
13083                    .await;
13084                assert!(rig.live(&route));
13085                assert_eq!(
13086                    route.stamp().unwrap().attributes.flow_id.as_deref(),
13087                    Some("flow:7")
13088                );
13089            }
13090        }
13091
13092        #[tokio::test]
13093        async fn flow_scope_rechecks_the_relay_target_after_a_reconnect() {
13094            use std::future::Future;
13095
13096            let mut rig = rig_with_flow_support(true).await;
13097            let mut record = session(1);
13098            record.attributes.flow_id = Some("flow:7".to_string());
13099            rig.sync(vec![record]).await;
13100            let (client, mut client_rx, frame) =
13101                rig.open_frame(Some(AFT), PLEXUS, Some(rig_selector("s", Some(1))));
13102            // Hold the route-open response permit so admission sees the first
13103            // target but relay reservation cannot capture an endpoint yet.
13104            for _ in 0..64 {
13105                client.egress.try_send(route_bind_ack(1)).unwrap();
13106            }
13107            let handler = rig.handler.clone();
13108            let mut open = Box::pin(handler.handle_control_frame(&client, frame));
13109            std::future::poll_fn(|cx| {
13110                assert!(open.as_mut().poll(cx).is_pending());
13111                std::task::Poll::Ready(())
13112            })
13113            .await;
13114            assert_eq!(rig.forwarding.reserved_route_count().unwrap(), (0, 0));
13115
13116            let old_connection = rig.modules[PLEXUS].0.connection_id;
13117            rig.handler.cleanup_connection(old_connection).unwrap();
13118            let (replacement, mut replacement_rx) = wide_ctx(200);
13119            let hello = hello_frame(PLEXUS, PROTOCOL_VERSION, 200);
13120            let mut body: Value = serde_json::from_slice(&hello.body).unwrap();
13121            body["manifest"]["provenance"] = serde_json::json!({"wire_crate_version": "0.29.0"});
13122            let hello = Frame::build(
13123                FrameType::Hello,
13124                control_flags(),
13125                0,
13126                0,
13127                200,
13128                serde_json::to_vec(&body).unwrap(),
13129            )
13130            .unwrap();
13131            hello_via_sink(&rig.handler, &replacement, &mut replacement_rx, hello).await;
13132
13133            client_rx.try_recv().unwrap();
13134            let replies = tokio::time::timeout(Duration::from_secs(2), open)
13135                .await
13136                .expect("the replacement is refused without waiting for a bind ack")
13137                .unwrap();
13138            assert_eq!(replies.len(), 1);
13139            let body = parse_error(&replies[0]);
13140            assert_eq!(body["code"], "target_flow_unsupported");
13141            for required in [PLEXUS, "flow-scopes/v1"] {
13142                assert!(body["message"].as_str().unwrap().contains(required));
13143            }
13144            assert!(replacement_rx.try_recv().is_err(), "no bind is relayed");
13145            assert!(rig.modules.get_mut(PLEXUS).unwrap().1.try_recv().is_err());
13146            assert_eq!(rig.forwarding.reserved_route_count().unwrap(), (0, 0));
13147            assert!(rig
13148                .handler
13149                .registry
13150                .get_module_by_connection(replacement.connection_id)
13151                .unwrap()
13152                .is_some());
13153        }
13154
13155        #[tokio::test]
13156        async fn scope_without_flow_id_and_unscoped_routes_admit_a_target_without_flow_capability()
13157        {
13158            let mut rig = rig_with_flow_support(false).await;
13159            rig.sync(vec![session(1)]).await;
13160            let route = rig
13161                .bound(Some(AFT), PLEXUS, Some(rig_selector("s", Some(1))))
13162                .await;
13163            assert!(rig.live(&route));
13164            assert_eq!(route.stamp().unwrap().attributes.flow_id, None);
13165            let mut record = session(1);
13166            record.attributes.flow_id = Some("flow:7".to_string());
13167            rig.sync(vec![record]).await;
13168            let unscoped = rig.bound(Some(AFT), OTHER, None).await;
13169            assert!(rig.live(&unscoped));
13170            assert_eq!(unscoped.stamp(), None);
13171        }
13172
13173        #[tokio::test]
13174        async fn a_same_epoch_flow_id_change_bumps_version_and_drains_all_scoped_routes() {
13175            let mut rig = rig().await;
13176            let mut record = session(1);
13177            record.attributes.flow_id = Some("flow:7".to_string());
13178            rig.sync(vec![record.clone()]).await;
13179            let before = rig.forwarding.published_scope_tag(OWNER, "s");
13180            let mut owner_route = rig
13181                .bound(Some(OWNER), PLEXUS, Some(rig_selector("s", Some(1))))
13182                .await;
13183            let mut carrier_route = rig
13184                .bound(Some(AFT), OTHER, Some(rig_selector("s", Some(1))))
13185                .await;
13186            let mut unscoped = rig.bound(Some(AFT), PLEXUS, None).await;
13187            rig.sync(vec![record.clone()]).await;
13188            assert_eq!(rig.forwarding.published_scope_tag(OWNER, "s"), before);
13189            assert!(rig.live(&owner_route) && owner_route.untouched());
13190            assert!(rig.live(&carrier_route) && carrier_route.untouched());
13191
13192            record.attributes.flow_id = Some("flow:8".to_string());
13193            rig.sync(vec![record]).await;
13194            let after = rig.forwarding.published_scope_tag(OWNER, "s").unwrap();
13195            let before = before.unwrap();
13196            assert_eq!(after.scope_epoch, before.scope_epoch);
13197            assert!(after.version > before.version);
13198            for route in [&mut owner_route, &mut carrier_route] {
13199                assert!(!rig.live(route));
13200                assert_eq!(
13201                    route.closed_reason(),
13202                    RouteCloseReason::ScopeDelegationChanged
13203                );
13204            }
13205            assert!(rig.live(&unscoped) && unscoped.untouched());
13206            // Each provider also receives a GOODBYE for its drained route;
13207            // consume it before expecting the next route.bind on that sink.
13208            for target in [PLEXUS, OTHER] {
13209                let (_, module_rx) = rig.modules.get_mut(target).unwrap();
13210                let goodbye = module_rx
13211                    .try_recv()
13212                    .expect("the provider sees the drain")
13213                    .frame;
13214                assert_eq!(goodbye.header.ty, FrameType::Goodbye);
13215                assert!(module_rx.try_recv().is_err());
13216            }
13217            let rebound = rig
13218                .bound(Some(AFT), PLEXUS, Some(rig_selector("s", Some(1))))
13219                .await;
13220            assert_eq!(
13221                rebound.stamp().unwrap().attributes.flow_id.as_deref(),
13222                Some("flow:8")
13223            );
13224        }
13225
13226        /// The owner's sync lands between admission and the module's ack. The
13227        /// open is refused by name, the module's other routes stay up, and the
13228        /// reserved pair is released. Changed content is retryable; an ended
13229        /// scope is not.
13230        #[tokio::test]
13231        async fn a_scope_changed_or_ended_between_admission_and_commit_refuses_the_open() {
13232            let mut rig = rig().await;
13233            rig.sync(vec![session(1)]).await;
13234            let mut cotenant = rig
13235                .bound(Some(OWNER), PLEXUS, Some(rig_selector("s", Some(1))))
13236                .await;
13237
13238            let mut changed = session(1);
13239            changed.child_owners.push(Principal::Reserved {
13240                module_id: MAGIC.to_string(),
13241            });
13242            let mut ended = None;
13243            for (code, next) in [
13244                (error_codes::SCOPE_CHANGED, vec![changed]),
13245                (error_codes::SCOPE_ENDED, Vec::new()),
13246            ] {
13247                let relayed = rig
13248                    .relayed(Some(AFT), PLEXUS, Some(rig_selector("s", Some(1))))
13249                    .await;
13250                let (bind_channel, bind_epoch) = route_bind_channel(&relayed.bind);
13251                ended = Some(next.is_empty());
13252                rig.sync(next).await;
13253                rig.ack(&relayed).await;
13254                let replies = relayed.task.await.unwrap();
13255                assert_eq!(replies.len(), 1, "{replies:?}");
13256                assert_eq!(parse_error(&replies[0])["code"], code);
13257                assert_eq!(
13258                    subc_protocol::error_codes::is_retryable_route_open(code),
13259                    code == error_codes::SCOPE_CHANGED
13260                );
13261                assert_eq!(rig.forwarding.reserved_route_count().unwrap(), (0, 0));
13262                // The module is told to drop just the binding it created.
13263                let (_, plexus_rx) = rig.modules.get_mut(PLEXUS).unwrap();
13264                // Collected, because ending the scope also closes the co-tenant
13265                // route, whose GOODBYE comes first.
13266                let mut goodbyes = Vec::new();
13267                while let Ok(outbound) = plexus_rx.try_recv() {
13268                    assert_eq!(outbound.frame.header.ty, FrameType::Goodbye);
13269                    goodbyes.push((outbound.frame.header.channel, outbound.frame.header.epoch));
13270                }
13271                assert!(
13272                    goodbyes.contains(&(bind_channel, bind_epoch)),
13273                    "{goodbyes:?}"
13274                );
13275                assert!(rig
13276                    .handler
13277                    .registry
13278                    .get_module_by_connection(rig.modules[PLEXUS].0.connection_id)
13279                    .unwrap()
13280                    .is_some());
13281            }
13282            assert_eq!(ended, Some(true));
13283            // The co-tenant stayed up through the change, and closed only when
13284            // the scope ended, by the drain rule rather than by the commit.
13285            assert_eq!(cotenant.closed_reason(), RouteCloseReason::ScopeEnded);
13286        }
13287
13288        /// Each row of the drain table on one set of routes: the owner's, a
13289        /// bare carrier's, and a targeted carrier's to each of its targets.
13290        #[tokio::test]
13291        async fn each_revocation_drains_exactly_the_affected_routes_with_its_own_reason() {
13292            struct Case {
13293                name: &'static str,
13294                change: fn(&mut ScopeRecord),
13295                /// Closed routes by index: owner->plexus, aft->plexus,
13296                /// broca->plexus, broca->other.
13297                closed: [Option<RouteCloseReason>; 4],
13298            }
13299            use RouteCloseReason::*;
13300            let cases = [
13301                Case {
13302                    name: "a carrier entry removed",
13303                    change: |r| {
13304                        r.carriers.retain(|c| {
13305                            c.principal
13306                                != Principal::Reserved {
13307                                    module_id: AFT.to_string(),
13308                                }
13309                        })
13310                    },
13311                    closed: [None, Some(ScopeCarrierRemoved), None, None],
13312                },
13313                Case {
13314                    name: "a target removed from a carrier",
13315                    change: |r| r.carriers[1].targets = Some(vec![PLEXUS.to_string()]),
13316                    closed: [None, None, None, Some(ScopeCarrierRemoved)],
13317                },
13318                Case {
13319                    name: "a bare carrier narrowed to targets",
13320                    change: |r| r.carriers[0].targets = Some(vec![OTHER.to_string()]),
13321                    closed: [None, Some(ScopeCarrierRemoved), None, None],
13322                },
13323                Case {
13324                    name: "delegates turned off",
13325                    change: |r| r.attributes.delegates = false,
13326                    closed: [Some(ScopeDelegationChanged); 4],
13327                },
13328                Case {
13329                    name: "agent_id changed",
13330                    change: |r| r.attributes.agent_id = Some("agent-2".to_string()),
13331                    closed: [Some(ScopeDelegationChanged); 4],
13332                },
13333                Case {
13334                    name: "a carrier added, child owners changed, the record re-sent",
13335                    change: |r| {
13336                        r.carriers.push(carrier(MAGIC, None));
13337                        r.child_owners.push(Principal::Reserved {
13338                            module_id: MAGIC.to_string(),
13339                        });
13340                    },
13341                    closed: [None; 4],
13342                },
13343                Case {
13344                    name: "a target added",
13345                    change: |r| {
13346                        r.carriers[1]
13347                            .targets
13348                            .as_mut()
13349                            .unwrap()
13350                            .push("third".to_string())
13351                    },
13352                    closed: [None; 4],
13353                },
13354                Case {
13355                    name: "delegates turned on",
13356                    change: |r| r.attributes.delegates = true,
13357                    closed: [None; 4],
13358                },
13359            ];
13360            for case in cases {
13361                let mut rig = rig().await;
13362                rig.sync(vec![session(1)]).await;
13363                let scope = || Some(rig_selector("s", Some(1)));
13364                let mut routes = [
13365                    rig.bound(Some(OWNER), PLEXUS, scope()).await,
13366                    rig.bound(Some(AFT), PLEXUS, scope()).await,
13367                    rig.bound(Some(BROCA), PLEXUS, scope()).await,
13368                    rig.bound(Some(BROCA), OTHER, scope()).await,
13369                ];
13370                let mut record = session(1);
13371                (case.change)(&mut record);
13372                rig.sync(vec![record]).await;
13373                for (index, expected) in case.closed.iter().enumerate() {
13374                    let route = &mut routes[index];
13375                    match expected {
13376                        Some(reason) => {
13377                            assert!(!rig.live(route), "{}: route {index} still live", case.name);
13378                            assert_eq!(
13379                                route.closed_reason(),
13380                                *reason,
13381                                "{}: route {index}",
13382                                case.name
13383                            );
13384                        }
13385                        None => {
13386                            assert!(rig.live(route), "{}: route {index} closed", case.name);
13387                            assert!(
13388                                route.untouched(),
13389                                "{}: route {index} was told something",
13390                                case.name
13391                            );
13392                        }
13393                    }
13394                }
13395            }
13396        }
13397
13398        #[tokio::test]
13399        async fn ending_or_replacing_a_scope_and_a_parent_ending_drain_every_route_under_it() {
13400            // Removed, and replaced by a higher epoch.
13401            for next in [Vec::new(), vec![session(2)]] {
13402                let mut rig = rig().await;
13403                rig.sync(vec![session(1)]).await;
13404                let mut route = rig
13405                    .bound(Some(AFT), PLEXUS, Some(rig_selector("s", Some(1))))
13406                    .await;
13407                rig.sync(next).await;
13408                assert!(!rig.live(&route));
13409                assert_eq!(route.closed_reason(), RouteCloseReason::ScopeEnded);
13410            }
13411
13412            // A child whose parent ends: its routes close as parent-ended, the
13413            // child stays live, and routes under the parent close as ended.
13414            let mut rig = rig().await;
13415            let mut child = session(1);
13416            child.scope_ref = "child".to_string();
13417            child.kind = ScopeKind::Worker;
13418            child.parent = Some(ScopeParent {
13419                owner: Principal::Reserved {
13420                    module_id: OWNER.to_string(),
13421                },
13422                scope_ref: "s".to_string(),
13423                scope_epoch: 1,
13424            });
13425            rig.sync(vec![session(1), child.clone()]).await;
13426            let mut child_route = rig
13427                .bound(Some(AFT), PLEXUS, Some(rig_selector("child", Some(1))))
13428                .await;
13429            assert_eq!(
13430                child_route.stamp().unwrap().parent_state,
13431                Some(ParentState::Linked)
13432            );
13433            rig.sync(vec![child]).await;
13434            assert!(!rig.live(&child_route));
13435            assert_eq!(
13436                child_route.closed_reason(),
13437                RouteCloseReason::ScopeParentEnded
13438            );
13439        }
13440
13441        #[tokio::test]
13442        async fn re_sending_an_unchanged_record_drains_nothing_and_a_new_carrier_leaves_in_flight_calls(
13443        ) {
13444            let mut rig = rig().await;
13445            rig.sync(vec![session(1)]).await;
13446            let mut route = rig
13447                .bound(Some(AFT), PLEXUS, Some(rig_selector("s", Some(1))))
13448                .await;
13449            let before = rig.forwarding.published_scope_tag(OWNER, "s");
13450            rig.sync(vec![session(1)]).await;
13451            assert_eq!(rig.forwarding.published_scope_tag(OWNER, "s"), before);
13452            assert!(rig.live(&route) && route.untouched());
13453
13454            // A call in flight on the route when another carrier is added. A
13455            // forwarded REQUEST holds one credit on the route's flow until the
13456            // module answers; the router takes it exactly like this.
13457            let DataRoute::Client(DataRouteState::Bound(binding)) = rig
13458                .forwarding
13459                .lookup_data_route(route.client.connection_id, route.channel, route.epoch)
13460                .unwrap()
13461            else {
13462                panic!("the route is bound");
13463            };
13464            binding.flow.acquire_tagged(9, false).await.unwrap();
13465            let mut widened = session(1);
13466            widened.carriers.push(carrier(MAGIC, None));
13467            rig.sync(vec![widened]).await;
13468            assert!(rig.live(&route) && route.untouched());
13469            let (_, plexus_rx) = rig.modules.get_mut(PLEXUS).unwrap();
13470            assert!(plexus_rx.try_recv().is_err(), "the module is told nothing");
13471            // The call's credit is still held on an open flow, so its answer
13472            // will be delivered: closing the route would have closed the flow.
13473            assert_eq!(binding.flow.in_flight(), 1);
13474            binding
13475                .flow
13476                .acquire_tagged(10, false)
13477                .await
13478                .expect("the flow is still open");
13479        }
13480
13481        /// A swap's superseded endpoint keeps its routes until drained; ending
13482        /// the scope closes them there too.
13483        #[tokio::test]
13484        async fn ending_a_scope_drains_its_routes_on_a_superseded_endpoint() {
13485            let mut rig = rig().await;
13486            rig.sync(vec![session(1)]).await;
13487            let mut on_incumbent = rig
13488                .bound(Some(AFT), PLEXUS, Some(rig_selector("s", Some(1))))
13489                .await;
13490
13491            // Swap plexus: register a candidate and cut over, leaving the
13492            // incumbent superseded with the route still on it.
13493            let (candidate, _candidate_rx) = wide_ctx(9);
13494            let registration = rig
13495                .handler
13496                .registry
13497                .register_candidate_with_control_ops(
13498                    manifest(PLEXUS, PROTOCOL_VERSION),
13499                    PROTOCOL_VERSION,
13500                    candidate.connection_id,
13501                    module_baseline_control_ops(),
13502                )
13503                .unwrap();
13504            rig.forwarding
13505                .register_candidate_module_connection(
13506                    candidate.connection_id,
13507                    PLEXUS.to_string(),
13508                    PROTOCOL_VERSION,
13509                    manifest_concurrency(&registration.manifest),
13510                    candidate.egress.clone(),
13511                )
13512                .unwrap();
13513            rig.forwarding.cutover_candidate(PLEXUS).unwrap().unwrap();
13514            rig.handler
13515                .registry
13516                .promote_candidate(PLEXUS)
13517                .unwrap()
13518                .unwrap();
13519            assert!(rig.live(&on_incumbent), "cutover alone does not drain");
13520
13521            rig.sync(Vec::new()).await;
13522            assert!(!rig.live(&on_incumbent));
13523            assert_eq!(on_incumbent.closed_reason(), RouteCloseReason::ScopeEnded);
13524            let (_, incumbent_rx) = rig.modules.get_mut(PLEXUS).unwrap();
13525            let goodbye = incumbent_rx
13526                .try_recv()
13527                .expect("the superseded endpoint is told")
13528                .frame;
13529            assert_eq!(goodbye.header.ty, FrameType::Goodbye);
13530        }
13531    }
13532}
13533
13534#[cfg(test)]
13535mod concurrency_default_exposure_tests {
13536    use super::*;
13537
13538    fn hello_body(role_json: &str) -> Vec<u8> {
13539        format!(
13540            r#"{{"protocol_ver":2,"module_id":"m","manifest":{{"module_id":"m","module_version":"1.0.0","protocol_ver":2,"trust_tier":"first_party","provides":[{role_json}],"consumes":[],"bindings":{{"storage":{{"kind":"sqlite","scope":"project","owns_schema":false}},"vault_grants":[],"identity":{{"requires":[],"optional":[]}}}}}}}}"#
13541        )
13542        .into_bytes()
13543    }
13544
13545    fn manifest_from(body: &[u8]) -> ModuleManifest {
13546        let value: serde_json::Value = serde_json::from_slice(body).expect("hello parses");
13547        serde_json::from_value(value.get("manifest").expect("manifest key").clone())
13548            .expect("manifest parses")
13549    }
13550
13551    const SURFACE_TAIL: &str = r#""operations":[],"config_schema":{"type":"object"},"observability":[],"identity_scope":[]"#;
13552
13553    #[test]
13554    fn absent_concurrency_on_management_surface_is_reported_as_defaulted() {
13555        let body = hello_body(&format!(
13556            r#"{{"role":"management_surface",{SURFACE_TAIL}}}"#
13557        ));
13558        let manifest = manifest_from(&body);
13559        // Precondition: serde really resolved it to the default, so the typed
13560        // manifest alone cannot answer the question this probe exists for.
13561        assert_eq!(manifest_concurrency(&manifest), Concurrency::ModuleManaged);
13562        assert!(manifest_concurrency_was_defaulted(&body, &manifest));
13563    }
13564
13565    #[test]
13566    fn declared_concurrency_is_not_reported_even_when_it_equals_the_default() {
13567        let body = hello_body(&format!(
13568            r#"{{"role":"management_surface",{SURFACE_TAIL},"concurrency":"module_managed"}}"#
13569        ));
13570        let manifest = manifest_from(&body);
13571        assert_eq!(manifest_concurrency(&manifest), Concurrency::ModuleManaged);
13572        assert!(!manifest_concurrency_was_defaulted(&body, &manifest));
13573    }
13574
13575    #[test]
13576    fn non_management_roles_are_never_reported() {
13577        let body = hello_body(
13578            r#"{"role":"internal_service","service_id":"s","transport":"bulk","agent_facing":false,"operations":[]}"#,
13579        );
13580        let manifest = manifest_from(&body);
13581        assert!(!manifest_concurrency_was_defaulted(&body, &manifest));
13582    }
13583}