Skip to main content

subc_daemon/
control.rs

1use std::{
2    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
3    fmt,
4    path::PathBuf,
5    sync::{Arc, Mutex},
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, PollKind, RouteCloseReason,
14    SpawnCursor, StderrCaptureState, StderrTail, StderrTailEntry, SupervisorDaemonProvenance,
15    SupervisorEntry, SupervisorHealthEntry, SupervisorModuleProvenance, SupervisorObservedProcess,
16    SupervisorRescanResult, SupervisorRoute, SupervisorRouteConsumer, SupervisorRouteModule,
17};
18use subc_protocol::{
19    error_codes,
20    manifest::{
21        validate_hello_capability_grammar, validate_hello_self_signal_declarations,
22        CapabilityDeclarations, CapabilityNeed, Concurrency, ManifestProvenance, ModuleManifest,
23        ProviderRole,
24    },
25    session::{
26        HealthReport, ModuleControlPush, ModuleControlRequest, ModuleControlRequestFromModule,
27        ModuleControlResponse, ModuleControlResponseToModule, MODULE_CONTROL_OP_HEALTH_CHECK,
28        MODULE_TO_SUBC_OP_CATALOG_UPDATE,
29    },
30    BindIdentity, ErrorBody, Flags, FrameType, ModuleHelloAckBody, ModuleHelloBody, Principal,
31    Priority, RouteTarget, PROTOCOL_VERSION,
32};
33use tokio::time::{timeout_at, Instant};
34use tracing::{debug, info, warn};
35
36use crate::{
37    capability_requirements::{
38        log_duplicate_claim_events, log_requirement_events, CapabilityRequirementEvaluator,
39        CapabilityVerdict, DuplicateClaimSource, RegisteredModule, RequirementStatus,
40        RuntimeModule,
41    },
42    daemon_config::RestartRequiredSection,
43    forwarding::{
44        CloseReason, EndpointRoute, ForwardingError, ForwardingTable, GoodbyeTarget,
45        ModuleControlRpcCompletion, ModuleControlRpcOutcome, ModuleEndpointId,
46        PendingModuleControlRpc, RouteBindRelayOutcome, RoutePollSnapshot, RouteRelease,
47    },
48    observability::{
49        ROUTE_OPEN_REFUSED_DECLARED_NOT_READY, ROUTE_OPEN_REFUSED_REQUIRED_CAPABILITY_UNPROVIDED,
50    },
51    provenance::{
52        process_start_time, spawned_file_identity, ExecutableIdentityProbe, SpawnedFileIdentity,
53    },
54    registry::{ChannelState, ConnectionId, Registry, RegistryError},
55    router::{RouteCtx, RouterError},
56    server::MAX_PENDING_ROUTE_BINDS_PER_TARGET,
57    stderr_tail::{CaptureState, TailEntry},
58    supervise::{
59        validate_spec, ModuleProcessLiveness, ReservedHelloRejection, SpawnSubscribeRefusal,
60        SupervisorHandle, SwapHelloAdmission,
61    },
62    ConnectedClients, DaemonCounters, Frame, ProjectRootId, Supervisor,
63};
64
65/// Lowest envelope version this subc build will negotiate.
66///
67/// Module HELLO negotiation is exact: peers must use the daemon's locked
68/// protocol version. Older and newer peers receive `version_unsupported` and
69/// are not registered.
70pub const MIN_SUPPORTED_VERSION: u8 = PROTOCOL_VERSION;
71
72const CAP_MANIFEST_REGISTRATION: &str = "manifest_registration_v1";
73const CAP_CHANNEL_LIFECYCLE: &str = "channel_lifecycle_v1";
74const CAP_PING_PONG: &str = "ping_pong_v1";
75const CAP_SESSION_ATTACH: &str = "session_attach_v1";
76const CAP_ADMISSION_FACTS_RELAY: &str = "admission_facts_relay_v1";
77
78const SUBC_CONTROL_OPS: &[&str] = &[
79    ops::SERVER_DESCRIBE,
80    ops::CATALOG_LIST,
81    ops::ROUTE_OPEN,
82    ops::ROUTE_POLL,
83    ops::ROUTE_CLOSING,
84    ops::ROUTE_CLOSED,
85    ops::SUPERVISOR_LIST,
86    ops::SUPERVISOR_RESTART,
87    ops::SUPERVISOR_SWAP,
88    ops::SUPERVISOR_RELOAD,
89    ops::SUPERVISOR_RESCAN,
90    ops::SUPERVISOR_RELEASE_RESERVED,
91    ops::SUPERVISOR_SET_ENABLED,
92    ops::SUPERVISOR_HEALTH_PROBE,
93    ops::SUPERVISOR_HEALTH,
94    ops::SUPERVISOR_STDERR_TAIL,
95    ops::SUPERVISOR_TERMINALS,
96    ops::SUPERVISOR_ROUTES,
97    ops::SUPERVISOR_PROVENANCE,
98    ops::SUPERVISOR_SPAWN_SNAPSHOT,
99    ops::SUPERVISOR_SPAWN_SUBSCRIBE,
100];
101
102const MODULE_TO_SUBC_CONTROL_OPS: &[&str] =
103    &[MODULE_TO_SUBC_OP_CATALOG_UPDATE, "supervisor.live_roots"];
104
105const MODULE_BASELINE_CONTROL_OPS: &[&str] = &["route.bind", "route.status"];
106
107/// How long subc waits for a module to ack a relayed route.bind before returning
108/// `module_timeout`. The ack waits on the module's own configure, which for AFT
109/// includes a synchronous bounded project walk (up to ~20k files) plus gitignore
110/// and DB-open work — on a cold page cache or a large repo that legitimately
111/// exceeds a couple of seconds. The default is generous because rejecting a VALID
112/// bind is far worse than waiting on a slow one; a consumer that wants a tighter
113/// bound retries the bind itself (the sanctioned warm-bind-retry pattern).
114pub const DEFAULT_ROUTE_BIND_RELAY_TIMEOUT: Duration = Duration::from_secs(12);
115
116/// How many CONSECUTIVE full-budget relay timeouts against one target module
117/// open that module's bind-relay breaker.
118///
119/// Three, so that the breaker is NOT REACHABLE INSIDE ONE CLIENT CALL. Both
120/// SDKs default to a 30s request deadline and the relay budget defaults to 12s,
121/// so three consecutive full-budget timeouts take ~36s to observe: every client
122/// whose open contributed to opening the breaker had already given up on its
123/// own. That is what makes opening the breaker unable to turn a call that would
124/// have succeeded into a refusal — it can only make an already-failing module
125/// fail faster.
126///
127/// Two would be reachable inside one default deadline. One would convict a
128/// module on a single cold-cache bind, which is exactly the valid-but-slow case
129/// `DEFAULT_ROUTE_BIND_RELAY_TIMEOUT`'s own doc comment exists to protect.
130pub const DEFAULT_ROUTE_BIND_BREAKER_THRESHOLD: u32 = 3;
131
132/// How long a module's bind-relay breaker stays open before exactly one
133/// `route.open` is let through as a probe.
134///
135/// Bounded BELOW by the relay budget: a cooldown at or under the 12s budget
136/// re-pays a full-budget stall almost continuously, and the breaker stops being
137/// a saving worth its own state. Bounded ABOVE by the SDKs' 30s default request
138/// deadline: a client that starts retrying after the module recovers has to get
139/// a probe opportunity inside its own deadline, or the breaker converts a
140/// recovered module into a failed call — the failure it exists to prevent,
141/// pointed the other way.
142///
143/// 20s sits between those with room on both sides, and it caps what a wedged
144/// module can cost at one full-budget wait per 20s ACROSS THE WHOLE DAEMON
145/// rather than one per `route.open` per connection. The stall that motivated
146/// this, with its measurements, is written up in
147/// `docs/designs/route-open-head-of-line.md`: 268 opens against one module each
148/// waited the whole budget out.
149pub const DEFAULT_ROUTE_BIND_BREAKER_COOLDOWN: Duration = Duration::from_secs(20);
150
151const DEFAULT_HEALTH_PROBE_TIMEOUT: Duration = Duration::from_secs(5);
152const SLOW_CONTROL_DISPATCH_THRESHOLD: Duration = Duration::from_secs(1);
153
154#[derive(Clone)]
155struct DaemonProvenanceFacts {
156    build: DaemonBuildProvenance,
157    pid: Option<u32>,
158    started_at_ms: Option<u64>,
159    start_clock: Option<crate::clock::StartClock>,
160    executable_path: Option<PathBuf>,
161    executable_identity: Option<SpawnedFileIdentity>,
162    process_start_time: Option<u64>,
163    probe: ExecutableIdentityProbe,
164}
165
166impl Default for DaemonProvenanceFacts {
167    fn default() -> Self {
168        Self {
169            build: DaemonBuildProvenance {
170                build_git_sha: None,
171                build_lock_digest: None,
172            },
173            pid: None,
174            started_at_ms: None,
175            start_clock: None,
176            executable_path: None,
177            executable_identity: None,
178            process_start_time: None,
179            probe: ExecutableIdentityProbe::default(),
180        }
181    }
182}
183
184#[derive(Debug, Clone)]
185struct SupervisorRescanContext {
186    supervisor: Supervisor,
187    config_path: PathBuf,
188    configured_port: Option<u16>,
189    storage_config: Option<crate::daemon_config::StorageConfig>,
190    admission_facts_carrier_module_id: Option<String>,
191    admission_facts_targets: Option<Vec<String>>,
192}
193
194/// Real channel-0 control handler for subc itself.
195#[derive(Clone)]
196pub struct ControlHandler {
197    registry: Arc<Registry>,
198    forwarding: Arc<ForwardingTable>,
199    process_liveness: Option<Arc<dyn ModuleProcessLiveness>>,
200    supervisor: SupervisorHandle,
201    subc_capabilities: Arc<[String]>,
202    /// Daemon-wide route.bind relay budget. Used as the fallback when the
203    /// target module has no per-module override in
204    /// `route_bind_relay_timeouts`.
205    route_bind_relay_timeout: Duration,
206    /// Per-module route.bind relay budget overrides, keyed by module id. When
207    /// `handle_route_open` resolves the deadline for a target module, a
208    /// per-module entry wins over the daemon-wide value above.
209    route_bind_relay_timeouts: BTreeMap<String, Duration>,
210    /// Per-target-module bind-relay breaker state. Shared with the forwarding
211    /// table, which is where a new module connection resets it.
212    route_bind_breakers: RouteBindBreakers,
213    /// Live relay admissions keyed by target module. Shared through the
214    /// forwarding table so cloned or separately built handlers enforce one cap.
215    route_bind_concurrency: RouteBindConcurrency,
216    /// Consecutive relay timeouts that open a module's breaker.
217    route_bind_breaker_threshold: u32,
218    /// How long a breaker stays open before one probe is admitted.
219    route_bind_breaker_cooldown: Duration,
220    health_probe_timeout: Duration,
221    /// Central storage policy. When set, each registering module receives its
222    /// resolved storage descriptor in HELLO_ACK; `None` leaves the field absent.
223    storage_config: Option<crate::daemon_config::StorageConfig>,
224    /// The machine id established at boot, served on every HELLO_ACK and on
225    /// `server.describe`. Fixed for the daemon's lifetime: `ck machine adopt`
226    /// changes the file, never this value. `None` serves no id.
227    machine_id: Option<crate::machine_id::MachineId>,
228    admission_facts_carrier_module_id: Option<String>,
229    admission_facts_targets: Option<Vec<String>>,
230    rescan: Option<SupervisorRescanContext>,
231    connected_clients: ConnectedClients,
232    counters: DaemonCounters,
233    capability_evaluator: Arc<CapabilityRequirementEvaluator>,
234    daemon_provenance: DaemonProvenanceFacts,
235    #[cfg(test)]
236    control_dispatch_delay: Option<Duration>,
237    #[cfg(test)]
238    provenance_probe_override: Option<subc_control::RunningImageAgreement>,
239}
240
241impl fmt::Debug for ControlHandler {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        f.debug_struct("ControlHandler")
244            .field("registry", &self.registry)
245            .field("forwarding", &self.forwarding)
246            .field("process_liveness", &self.process_liveness.is_some())
247            .field("supervisor", &self.supervisor)
248            .field("subc_capabilities", &self.subc_capabilities)
249            .finish()
250    }
251}
252
253struct RouteOpenRequest {
254    target: RouteTarget,
255    identity: BindIdentity,
256    consumer_identity: Option<ConsumerIdentity>,
257    consumer_capabilities: Option<Vec<String>>,
258    admission_facts: Option<serde_json::Value>,
259}
260
261struct RouteBindReservationGuard {
262    forwarding: Arc<ForwardingTable>,
263    endpoint: ModuleEndpointId,
264    relay_corr: u64,
265    armed: bool,
266}
267
268struct ModuleControlRpcGuard {
269    forwarding: Arc<ForwardingTable>,
270    endpoint: ModuleEndpointId,
271    corr: u64,
272    armed: bool,
273}
274
275impl ModuleControlRpcGuard {
276    fn new(forwarding: Arc<ForwardingTable>, endpoint: ModuleEndpointId, corr: u64) -> Self {
277        Self {
278            forwarding,
279            endpoint,
280            corr,
281            armed: true,
282        }
283    }
284
285    fn disarm(&mut self) {
286        self.armed = false;
287    }
288}
289
290impl Drop for ModuleControlRpcGuard {
291    fn drop(&mut self) {
292        if self.armed {
293            let _ = self
294                .forwarding
295                .cancel_module_control_rpc(self.endpoint, self.corr);
296        }
297    }
298}
299
300impl RouteBindReservationGuard {
301    fn new(forwarding: Arc<ForwardingTable>, endpoint: ModuleEndpointId, relay_corr: u64) -> Self {
302        Self {
303            forwarding,
304            endpoint,
305            relay_corr,
306            armed: true,
307        }
308    }
309
310    fn release_and_disarm(&mut self) {
311        if !self.armed {
312            return;
313        }
314        if let Ok(Some(target)) = self.forwarding.abort_pending_relay(
315            self.endpoint,
316            self.relay_corr,
317            RouteBindRelayOutcome::ModuleGone("route.open handler canceled".to_string()),
318        ) {
319            send_goodbye_target_best_effort(&target, "canceled route.bind");
320        }
321        self.armed = false;
322    }
323
324    fn disarm(&mut self) {
325        self.armed = false;
326    }
327}
328
329impl Drop for RouteBindReservationGuard {
330    fn drop(&mut self) {
331        self.release_and_disarm();
332    }
333}
334
335/// Per-target-module circuit breaker around the `route.bind` relay.
336///
337/// The connection reader is serial per connection, so a module whose `on_bind`
338/// sits on the ack blocks every LATER frame on the connections that call it,
339/// including calls to unrelated modules. This does not make any module's bind
340/// fast; it stops the daemon paying the full budget again and again for a
341/// condition it has already observed.
342///
343/// State is keyed by TARGET MODULE and shared by every connection: a wedged
344/// module wedges everyone, so what one connection learned should protect the
345/// rest.
346///
347/// THE MAP IS EMPTY WHILE THE FLEET IS HEALTHY. An entry appears only when a
348/// relay to that module has actually timed out, and is removed again when a
349/// relay is accepted or the module reconnects, so it cannot grow with traffic
350/// or with modules that behave.
351///
352/// # Why a `std` mutex here is not the head-of-line defect again
353///
354/// Acquisition never awaits. The critical section is a hash lookup plus a few
355/// integer updates, with no I/O and no `.await` inside it, so a reader task
356/// cannot be descheduled behind it the way it can behind
357/// `tokio::sync::Mutex::lock().await` or a semaphore permit. It is the same
358/// primitive, held for the same kind of work, as the refusal counter this very
359/// path already increments.
360///
361/// It is also NOT on the data-plane splice path: only `route.open` and module
362/// registration touch it, so bound-route frames gain no state check and no
363/// contention.
364#[derive(Debug, Clone, Default)]
365pub(crate) struct RouteBindBreakers {
366    modules: Arc<Mutex<HashMap<String, ModuleBreakerState>>>,
367}
368
369#[derive(Debug, Clone, Default)]
370pub(crate) struct RouteBindConcurrency {
371    modules: Arc<Mutex<HashMap<String, usize>>>,
372}
373
374struct RouteBindConcurrencyGuard {
375    concurrency: RouteBindConcurrency,
376    module_id: String,
377}
378
379impl RouteBindConcurrency {
380    /// Admit without waiting. Waiting here would move the bind stall from the
381    /// module reply to a semaphore and restore reader head-of-line blocking.
382    fn try_admit(&self, module_id: &str, limit: usize) -> Result<RouteBindConcurrencyGuard, usize> {
383        let mut modules = self
384            .modules
385            .lock()
386            .expect("route.bind concurrency mutex poisoned");
387        let in_flight = modules.entry(module_id.to_string()).or_default();
388        if *in_flight >= limit {
389            return Err(*in_flight);
390        }
391        *in_flight += 1;
392        Ok(RouteBindConcurrencyGuard {
393            concurrency: self.clone(),
394            module_id: module_id.to_string(),
395        })
396    }
397}
398
399impl Drop for RouteBindConcurrencyGuard {
400    fn drop(&mut self) {
401        let mut modules = self
402            .concurrency
403            .modules
404            .lock()
405            .expect("route.bind concurrency mutex poisoned");
406        let remove = {
407            let in_flight = modules
408                .get_mut(&self.module_id)
409                .expect("admitted route.bind has a concurrency entry");
410            *in_flight -= 1;
411            *in_flight == 0
412        };
413        if remove {
414            modules.remove(&self.module_id);
415        }
416    }
417}
418
419#[derive(Debug, Default)]
420struct ModuleBreakerState {
421    /// Relay timeouts observed with no accepted relay in between.
422    consecutive_timeouts: u32,
423    /// `Some` while the breaker is open: the instant the cooldown expires and
424    /// the next arrival may probe. `None` means closed.
425    cooldown_until: Option<Instant>,
426    /// A half-open probe has been admitted and has not settled yet. This is
427    /// what makes the probe EXACTLY ONE: the flag is set under the same lock
428    /// that read the cooldown, so concurrent opens arriving at the moment the
429    /// cooldown expires cannot all decide that they are the probe.
430    probe_in_flight: bool,
431}
432
433/// What the breaker decided for one `route.open`, before any relay work.
434enum RouteBindAdmission<'a> {
435    Admitted {
436        guard: RouteBindBreakerGuard<'a>,
437        /// This open is the single half-open probe, so the transition is worth
438        /// one log line.
439        probe: bool,
440    },
441    Refused {
442        consecutive_timeouts: u32,
443        /// What is left of the cooldown. Zero when the refusal is because the
444        /// one probe is already in flight rather than because the cooldown has
445        /// not elapsed.
446        retry_in: Duration,
447        probe_in_flight: bool,
448    },
449}
450
451/// An outstanding admission, which must be told how its relay settled.
452///
453/// `Drop` settles it as inconclusive, so an early return between admission and
454/// the relay -- or the whole handler being cancelled when the client
455/// disconnects -- releases a half-open probe slot instead of leaving the
456/// breaker wedged half-open with no further probes.
457struct RouteBindBreakerGuard<'a> {
458    breakers: RouteBindBreakers,
459    module_id: &'a str,
460    settled: bool,
461}
462
463impl RouteBindBreakerGuard<'_> {
464    /// The module answered within the budget and took the bind. THE ONLY
465    /// OUTCOME THAT CLEARS THE COUNT. Returns true when this closed an open
466    /// breaker, which is a transition worth logging.
467    fn record_accepted(&mut self) -> bool {
468        self.settled = true;
469        self.breakers.record_accepted(self.module_id)
470    }
471
472    /// The relay burned the whole budget with no answer. THE ONLY ARM THAT
473    /// COUNTS TOWARD OPENING.
474    fn record_timeout(&mut self, threshold: u32, cooldown: Duration) -> Option<BreakerOpened> {
475        self.settled = true;
476        self.breakers
477            .record_timeout(self.module_id, threshold, cooldown)
478    }
479
480    /// Everything else: the module REJECTED the bind, its connection went away
481    /// mid-relay, or the waiter was cancelled.
482    ///
483    /// None of these is evidence that a module is slow, and each already has
484    /// its own refusal with its own code. A module that rejects a bind in
485    /// microseconds is healthy and must never be convicted for it; a module
486    /// that died has said nothing about the module that replaces it. So these
487    /// neither increment nor reset the count -- they only release a probe slot.
488    fn record_inconclusive(&mut self) {
489        self.settled = true;
490        self.breakers.record_inconclusive(self.module_id);
491    }
492}
493
494impl Drop for RouteBindBreakerGuard<'_> {
495    fn drop(&mut self) {
496        if !self.settled {
497            self.breakers.record_inconclusive(self.module_id);
498        }
499    }
500}
501
502/// The breaker moved to open, reported so the caller can log it outside the
503/// lock. Opening is rare and load-bearing; the refusals that follow are
504/// frequent and are counted rather than logged.
505struct BreakerOpened {
506    consecutive_timeouts: u32,
507    /// True when a failed probe re-opened an already-open breaker, which reads
508    /// very differently in a log from a first opening.
509    reopened_after_probe: bool,
510}
511
512impl RouteBindBreakers {
513    fn lock(&self) -> std::sync::MutexGuard<'_, HashMap<String, ModuleBreakerState>> {
514        self.modules
515            .lock()
516            .expect("route.bind breaker mutex poisoned")
517    }
518
519    /// Decide whether this `route.open` may attempt its relay. Takes the map
520    /// lock and nothing else, and never awaits.
521    fn admit<'a>(&self, module_id: &'a str) -> RouteBindAdmission<'a> {
522        let admitted = |probe| RouteBindAdmission::Admitted {
523            guard: RouteBindBreakerGuard {
524                breakers: self.clone(),
525                module_id,
526                settled: false,
527            },
528            probe,
529        };
530
531        let mut modules = self.lock();
532        let Some(state) = modules.get_mut(module_id) else {
533            return admitted(false);
534        };
535        let Some(cooldown_until) = state.cooldown_until else {
536            return admitted(false);
537        };
538        if state.probe_in_flight {
539            return RouteBindAdmission::Refused {
540                consecutive_timeouts: state.consecutive_timeouts,
541                retry_in: Duration::ZERO,
542                probe_in_flight: true,
543            };
544        }
545        let now = Instant::now();
546        if now < cooldown_until {
547            return RouteBindAdmission::Refused {
548                consecutive_timeouts: state.consecutive_timeouts,
549                retry_in: cooldown_until - now,
550                probe_in_flight: false,
551            };
552        }
553        state.probe_in_flight = true;
554        admitted(true)
555    }
556
557    fn record_accepted(&self, module_id: &str) -> bool {
558        self.lock()
559            .remove(module_id)
560            .is_some_and(|state| state.cooldown_until.is_some())
561    }
562
563    fn record_timeout(
564        &self,
565        module_id: &str,
566        threshold: u32,
567        cooldown: Duration,
568    ) -> Option<BreakerOpened> {
569        let mut modules = self.lock();
570        let state = modules.entry(module_id.to_string()).or_default();
571        let was_open = state.cooldown_until.is_some();
572        let was_probe = state.probe_in_flight;
573        state.probe_in_flight = false;
574        state.consecutive_timeouts = state.consecutive_timeouts.saturating_add(1);
575        if state.consecutive_timeouts < threshold {
576            return None;
577        }
578        state.cooldown_until = Some(Instant::now() + cooldown);
579        Some(BreakerOpened {
580            consecutive_timeouts: state.consecutive_timeouts,
581            reopened_after_probe: was_open && was_probe,
582        })
583    }
584
585    fn record_inconclusive(&self, module_id: &str) {
586        if let Some(state) = self.lock().get_mut(module_id) {
587            state.probe_in_flight = false;
588        }
589    }
590
591    /// Discard what was learned about a module, because the process it was
592    /// learned about is gone. Returns the discarded count when it was non-zero.
593    ///
594    /// A BREAKER IS A CACHED VERDICT ABOUT A PROCESS, NOT ABOUT A NAME. A
595    /// `module_id` is a configuration identity that outlives any particular
596    /// child; what the breaker observed was the process behind the module
597    /// connection of the moment. When a new connection registers under that id
598    /// the verdict's subject no longer exists, so the verdict is stale by
599    /// construction rather than merely likely to be wrong. Keeping it would
600    /// apply a dead process's record to a live one, which is the same defect
601    /// class this breaker exists to stop the daemon committing.
602    ///
603    /// A half-open probe in flight is discarded with the rest: it was a
604    /// question about the old process.
605    pub(crate) fn reset_for_new_module_connection(&self, module_id: &str) -> Option<u32> {
606        self.lock()
607            .remove(module_id)
608            .map(|state| state.consecutive_timeouts)
609            .filter(|discarded| *discarded > 0)
610    }
611
612    /// Open breakers, for the `server.describe` counters object. `None` when
613    /// none is open, so the key stays absent rather than present-and-empty.
614    ///
615    /// This is the operator's answer to "is this module refusing instantly or
616    /// is it fine?", which look identical from a client that retries and then
617    /// succeeds.
618    fn open_snapshot(&self) -> Option<serde_json::Value> {
619        let now = Instant::now();
620        let modules = self.lock();
621        let open = modules
622            .iter()
623            .filter_map(|(module_id, state)| {
624                let cooldown_until = state.cooldown_until?;
625                Some((
626                    module_id.clone(),
627                    serde_json::json!({
628                        "consecutive_timeouts": state.consecutive_timeouts,
629                        "cooldown_remaining_ms":
630                            cooldown_until.saturating_duration_since(now).as_millis() as u64,
631                        "probe_in_flight": state.probe_in_flight,
632                    }),
633                ))
634            })
635            .collect::<serde_json::Map<String, serde_json::Value>>();
636        (!open.is_empty()).then_some(serde_json::Value::Object(open))
637    }
638}
639
640impl ControlHandler {
641    pub fn new(registry: Arc<Registry>) -> Self {
642        Self::with_forwarding(registry, Arc::new(ForwardingTable::default()))
643    }
644
645    pub fn with_forwarding(registry: Arc<Registry>, forwarding: Arc<ForwardingTable>) -> Self {
646        let counters = forwarding.counters();
647        // Taken from the forwarding table rather than created here, so that the
648        // breaker a `route.open` consults is the same one a module's
649        // registration resets, however many handlers are built over one table.
650        let route_bind_breakers = forwarding.route_bind_breakers();
651        let route_bind_concurrency = forwarding.route_bind_concurrency();
652        Self {
653            registry,
654            forwarding,
655            process_liveness: None,
656            supervisor: SupervisorHandle::new(),
657            subc_capabilities: Arc::from([
658                CAP_MANIFEST_REGISTRATION.to_string(),
659                CAP_CHANNEL_LIFECYCLE.to_string(),
660                CAP_PING_PONG.to_string(),
661                CAP_SESSION_ATTACH.to_string(),
662                CAP_ADMISSION_FACTS_RELAY.to_string(),
663            ]),
664            route_bind_relay_timeout: DEFAULT_ROUTE_BIND_RELAY_TIMEOUT,
665            route_bind_relay_timeouts: BTreeMap::new(),
666            route_bind_breakers,
667            route_bind_concurrency,
668            route_bind_breaker_threshold: DEFAULT_ROUTE_BIND_BREAKER_THRESHOLD,
669            route_bind_breaker_cooldown: DEFAULT_ROUTE_BIND_BREAKER_COOLDOWN,
670            health_probe_timeout: DEFAULT_HEALTH_PROBE_TIMEOUT,
671            storage_config: None,
672            machine_id: None,
673            admission_facts_carrier_module_id: None,
674            admission_facts_targets: None,
675            rescan: None,
676            connected_clients: ConnectedClients::new(),
677            counters,
678            capability_evaluator: Arc::new(CapabilityRequirementEvaluator::new()),
679            daemon_provenance: DaemonProvenanceFacts::default(),
680            #[cfg(test)]
681            control_dispatch_delay: None,
682            #[cfg(test)]
683            provenance_probe_override: None,
684        }
685    }
686
687    /// Set the central storage policy: registering modules then receive their
688    /// resolved storage descriptor in HELLO_ACK.
689    pub fn with_storage_config(
690        mut self,
691        storage_config: Option<crate::daemon_config::StorageConfig>,
692    ) -> Self {
693        self.storage_config = storage_config;
694        self
695    }
696
697    /// Set the machine id served to every registering module (HELLO_ACK) and on
698    /// `server.describe`.
699    pub fn with_machine_id(mut self, machine_id: Option<crate::machine_id::MachineId>) -> Self {
700        self.machine_id = machine_id;
701        self
702    }
703
704    /// Configure the exact reserved module and target ids permitted to relay
705    /// opaque admission facts. Config-file loading validates this authority;
706    /// this builder keeps the same policy available to embedded test daemons.
707    pub fn with_admission_facts_config(
708        mut self,
709        carrier_module_id: Option<String>,
710        targets: Option<Vec<String>>,
711    ) -> Self {
712        self.admission_facts_carrier_module_id = carrier_module_id;
713        self.admission_facts_targets = targets;
714        self
715    }
716
717    /// Override the route.bind relay timeout. Used by tests that assert the
718    /// timeout path so they don't block on the production-safe default.
719    pub fn with_route_bind_relay_timeout(mut self, timeout: Duration) -> Self {
720        self.route_bind_relay_timeout = timeout;
721        self
722    }
723
724    /// Install per-module route.bind relay budget overrides. A module id
725    /// listed here wins over the daemon-wide default set via
726    /// `with_route_bind_relay_timeout`. Values are pre-resolved at parse time
727    /// from `subc.jsonc` (per-module > daemon-wide > absent), so callers pass
728    /// the same `Duration` the bind path will use.
729    pub fn with_route_bind_relay_timeouts(
730        mut self,
731        timeouts: impl IntoIterator<Item = (String, Duration)>,
732    ) -> Self {
733        self.route_bind_relay_timeouts = timeouts.into_iter().collect();
734        self
735    }
736
737    /// Resolve the route.bind relay budget for a specific target module id.
738    /// Per-module overrides win; the daemon-wide value (set via
739    /// `with_route_bind_relay_timeout` or the built-in default) is the
740    /// fallback. Exposed so config-aware callers (bootstrap, tests) can audit
741    /// the same resolution `handle_route_open` will use.
742    pub fn route_bind_relay_timeout_for(&self, module_id: &str) -> Duration {
743        self.route_bind_relay_timeouts
744            .get(module_id)
745            .copied()
746            .unwrap_or(self.route_bind_relay_timeout)
747    }
748
749    /// Override the per-module bind-relay breaker policy.
750    ///
751    /// Used by tests, which cannot spend three production budgets opening a
752    /// breaker or twenty seconds waiting for its cooldown. The production
753    /// values are `DEFAULT_ROUTE_BIND_BREAKER_THRESHOLD` and
754    /// `DEFAULT_ROUTE_BIND_BREAKER_COOLDOWN`, whose doc comments carry the
755    /// reasoning for the numbers.
756    pub fn with_route_bind_breaker(mut self, threshold: u32, cooldown: Duration) -> Self {
757        self.route_bind_breaker_threshold = threshold.max(1);
758        self.route_bind_breaker_cooldown = cooldown;
759        self
760    }
761
762    #[cfg(test)]
763    pub(crate) fn with_health_probe_timeout(mut self, timeout: Duration) -> Self {
764        self.health_probe_timeout = timeout;
765        self
766    }
767
768    #[cfg(test)]
769    pub(crate) fn with_control_dispatch_delay(mut self, delay: Duration) -> Self {
770        self.control_dispatch_delay = Some(delay);
771        self
772    }
773
774    pub fn with_process_liveness(
775        mut self,
776        process_liveness: Arc<dyn ModuleProcessLiveness>,
777    ) -> Self {
778        self.process_liveness = Some(process_liveness);
779        self
780    }
781
782    pub fn with_supervisor(mut self, supervisor: SupervisorHandle) -> Self {
783        self.supervisor = supervisor;
784        self
785    }
786
787    pub fn with_daemon_provenance(
788        mut self,
789        pid: u32,
790        started_at_ms: u64,
791        executable_path: Option<PathBuf>,
792        build_git_sha: Option<String>,
793        build_lock_digest: Option<String>,
794    ) -> Self {
795        let executable_identity = executable_path.as_deref().and_then(spawned_file_identity);
796        let process_start_time = process_start_time(pid);
797        self.daemon_provenance = DaemonProvenanceFacts {
798            build: DaemonBuildProvenance {
799                build_git_sha,
800                build_lock_digest,
801            },
802            pid: Some(pid),
803            started_at_ms: Some(started_at_ms),
804            start_clock: None,
805            executable_path,
806            executable_identity,
807            process_start_time,
808            probe: ExecutableIdentityProbe::default(),
809        };
810        self
811    }
812
813    pub(crate) fn with_daemon_start_clock(mut self, clock: crate::clock::StartClock) -> Self {
814        self.daemon_provenance.start_clock = Some(clock);
815        self
816    }
817
818    #[cfg(test)]
819    fn with_provenance_probe_result(mut self, result: subc_control::RunningImageAgreement) -> Self {
820        self.provenance_probe_override = Some(result);
821        self
822    }
823
824    /// Install the configured module set and its reserved capability bindings.
825    /// Bindings are configuration-scoped and may point at a provider that has not
826    /// been installed yet, so this does not require the bound module to exist.
827    pub fn with_capability_config(
828        self,
829        modules: impl IntoIterator<Item = (String, bool)>,
830        reserved_capabilities: BTreeMap<String, String>,
831    ) -> Self {
832        self.capability_evaluator
833            .configure(modules, reserved_capabilities);
834        self
835    }
836
837    pub fn with_supervisor_rescan(
838        mut self,
839        supervisor: Supervisor,
840        config_path: impl Into<PathBuf>,
841        configured_port: Option<u16>,
842    ) -> Self {
843        self.rescan = Some(SupervisorRescanContext {
844            supervisor,
845            config_path: config_path.into(),
846            configured_port,
847            storage_config: self.storage_config.clone(),
848            admission_facts_carrier_module_id: self.admission_facts_carrier_module_id.clone(),
849            admission_facts_targets: self.admission_facts_targets.clone(),
850        });
851        self
852    }
853
854    pub fn with_connected_clients(mut self, connected_clients: ConnectedClients) -> Self {
855        self.connected_clients = connected_clients;
856        self
857    }
858
859    pub fn forwarding(&self) -> Arc<ForwardingTable> {
860        Arc::clone(&self.forwarding)
861    }
862
863    pub(crate) fn counters(&self) -> DaemonCounters {
864        self.counters.clone()
865    }
866
867    /// Wake at each candidate's own deadline so a stalled fresh exec emits its
868    /// requirement event without depending on an operator polling a status command.
869    pub fn spawn_capability_deadline_loop(self: Arc<Self>) {
870        tokio::spawn(async move {
871            loop {
872                self.capability_evaluator
873                    .wait_for_change_or_deadline()
874                    .await;
875                self.refresh_capability_requirements();
876            }
877        });
878    }
879
880    fn runtime_capability_snapshot(
881        &self,
882    ) -> Result<(Vec<RuntimeModule>, Vec<RegisteredModule>), RouterError> {
883        let runtime = self
884            .supervisor
885            .list()
886            .into_iter()
887            .map(|module| {
888                let status = module.status().map_err(|err| {
889                    RouterError::backend(0, 0, format!("failed to read capability status: {err}"))
890                })?;
891                Ok(RuntimeModule {
892                    module_id: status.module_id,
893                    state: status.state,
894                    enabled: status.enabled,
895                })
896            })
897            .collect::<Result<Vec<_>, RouterError>>()?;
898        let (_, registrations) = self.registry.list_modules().map_err(|err| {
899            RouterError::backend(
900                0,
901                0,
902                format!("failed to list capability registrations: {err}"),
903            )
904        })?;
905        let registrations = registrations
906            .into_iter()
907            .map(|registration| RegisteredModule {
908                module_id: registration.manifest.module_id,
909                module_version: registration.manifest.module_version,
910                capabilities: registration.manifest.capabilities,
911            })
912            .collect();
913        Ok((runtime, registrations))
914    }
915
916    /// The capability side effects of a module becoming the active registration
917    /// for its id: cache its manifest (warning if its claims drifted), run the
918    /// deny census when its declarations call for one, and recompute the
919    /// requirement statuses. An ordinary HELLO does this as it registers; a swap
920    /// candidate's does not, and the supervisor does it at promotion instead,
921    /// through [`crate::supervise::SwapPromotionObserver`].
922    fn apply_registration_capabilities(&self, registration: &crate::registry::ModuleRegistration) {
923        let cached_registration = RegisteredModule {
924            module_id: registration.manifest.module_id.clone(),
925            module_version: registration.manifest.module_version.clone(),
926            capabilities: registration.manifest.capabilities.clone(),
927        };
928        if self.capability_evaluator.record_hello(&cached_registration) {
929            warn!(
930                module_id = %cached_registration.module_id,
931                "capability claims drifted from the cached manifest"
932            );
933        }
934        if capability_census_trigger(None, registration.manifest.capabilities.as_ref()) {
935            self.enforce_capability_denies();
936        }
937        self.refresh_capability_requirements();
938    }
939
940    /// Point the shared supervisor handle at this handler for swap promotions.
941    /// Called wherever a handler is put behind the `Arc` the router serves, so
942    /// it can be held weakly.
943    pub(crate) fn install_swap_promotion_observer(self: &Arc<Self>) {
944        let observer: std::sync::Weak<dyn crate::supervise::SwapPromotionObserver> =
945            Arc::downgrade(self) as std::sync::Weak<ControlHandler>;
946        self.supervisor.set_swap_promotion_observer(observer);
947    }
948
949    pub fn refresh_capability_requirements(&self) {
950        match self.runtime_capability_snapshot() {
951            Ok((runtime, registrations)) => {
952                log_requirement_events(
953                    self.capability_evaluator
954                        .evaluate_now(&runtime, &registrations),
955                );
956            }
957            Err(err) => warn!(error = %err, "failed to recompute capability requirements"),
958        }
959    }
960
961    /// Reconcile only live, attested route bindings after a capability deny edge
962    /// or target claim was added. This is deliberately a control-plane census:
963    /// the opaque forwarding hot path must not grow a per-frame capability check.
964    fn enforce_capability_denies(&self) {
965        let (_, registrations) = match self.registry.list_modules() {
966            Ok(snapshot) => snapshot,
967            Err(err) => {
968                warn!(error = %err, "failed to read registrations for capability deny census");
969                return;
970            }
971        };
972        let manifests = registrations
973            .into_iter()
974            .map(|registration| {
975                (
976                    registration.manifest.module_id.clone(),
977                    registration.manifest,
978                )
979            })
980            .collect::<BTreeMap<_, _>>();
981        let census = match self.forwarding.route_census(None) {
982            Ok(census) => census,
983            Err(err) => {
984                warn!(error = %err, "failed to read route census for capability deny enforcement");
985                return;
986            }
987        };
988
989        for (target_module_id, routes) in census {
990            let Some(target_manifest) = manifests.get(&target_module_id) else {
991                continue;
992            };
993            let mut closed_routes = Vec::new();
994            let mut module_goodbyes = Vec::new();
995            for route in routes {
996                let Principal::Reserved {
997                    module_id: opening_module_id,
998                } = &route.principal
999                else {
1000                    continue;
1001                };
1002                let Some(opening_manifest) = manifests.get(opening_module_id) else {
1003                    continue;
1004                };
1005                let Some(capability) = denied_capability(opening_manifest, target_manifest) else {
1006                    continue;
1007                };
1008
1009                match self.forwarding.release_client_route(
1010                    route.goodbye_target.connection_id,
1011                    route.goodbye_target.channel,
1012                    route.goodbye_target.epoch,
1013                ) {
1014                    Ok(RouteRelease::Removed(module_goodbye)) => {
1015                        warn!(
1016                            opening_module_id,
1017                            target_module_id,
1018                            capability,
1019                            "force-closing route because an attested capability deny edge now matches"
1020                        );
1021                        closed_routes.push(route);
1022                        module_goodbyes.push(module_goodbye);
1023                    }
1024                    Ok(RouteRelease::Stale | RouteRelease::Absent) => {}
1025                    Err(err) => warn!(
1026                        opening_module_id,
1027                        target_module_id,
1028                        capability,
1029                        error = %err,
1030                        "failed to force-close capability-denied route"
1031                    ),
1032                }
1033            }
1034
1035            if closed_routes.is_empty() {
1036                continue;
1037            }
1038            send_route_control_pushes(
1039                &self.forwarding,
1040                closed_routes,
1041                ClientControlPush::RouteClosed {
1042                    module_id: target_module_id,
1043                    reason: RouteCloseReason::CapabilityDenied,
1044                    drained: false,
1045                    abandoned: 0,
1046                    excluded_subscriptions: 0,
1047                    terminal: Some(false),
1048                },
1049            );
1050            self.emit_route_goodbyes(module_goodbyes);
1051        }
1052    }
1053
1054    /// Why a registered module is not accepting new route binds, or `None` when
1055    /// it is. This is the module's effective readiness: its declared readiness
1056    /// first, then every `need: required` capability it declares evaluating to
1057    /// `provided`. `route.open` and `catalog.list` both read it here so the
1058    /// catalog never reports a module routable that `route.open` would refuse.
1059    fn not_ready_reason(
1060        &self,
1061        registration: &crate::registry::ModuleRegistration,
1062    ) -> Option<NotReadyReason> {
1063        if !registration.ready {
1064            return Some(NotReadyReason {
1065                reason: NotReadyReason::DECLARED_NOT_READY.to_string(),
1066                capability: None,
1067            });
1068        }
1069        self.first_unprovided_required_capability(registration)
1070            .map(|capability| NotReadyReason {
1071                reason: NotReadyReason::REQUIRED_CAPABILITY_UNPROVIDED.to_string(),
1072                capability: Some(capability),
1073            })
1074    }
1075
1076    /// The lexicographically first capability this registration declares
1077    /// `need: required` whose evaluator verdict is not `provided`.
1078    ///
1079    /// The verdicts are the capability evaluator's own; nothing here decides
1080    /// what "provided" means. The evaluator counts a capability provided as
1081    /// soon as a module claiming it has REGISTERED, not once that module is
1082    /// ready. That distinction is what keeps two modules that require each
1083    /// other's capabilities from deadlocking: if "provided" meant "the claimant
1084    /// is ready", each would wait for the other to become ready first and
1085    /// neither ever would. Do not tighten it to readiness.
1086    ///
1087    /// A required capability with no verdict at all means this registration's
1088    /// HELLO or catalog.update landed after the last recompute; recompute once
1089    /// rather than let a missing verdict read as either answer. If it is still
1090    /// missing (the recompute itself failed) the capability counts as
1091    /// unprovided: the refusal is retryable, and routing a module whose
1092    /// required provider is unknown is the outcome this check exists to stop.
1093    fn first_unprovided_required_capability(
1094        &self,
1095        registration: &crate::registry::ModuleRegistration,
1096    ) -> Option<String> {
1097        let required = registration
1098            .manifest
1099            .capabilities
1100            .iter()
1101            .flat_map(|declarations| declarations.requires.iter())
1102            .filter(|requirement| requirement.need == CapabilityNeed::Required)
1103            .map(|requirement| requirement.capability.as_str())
1104            .collect::<BTreeSet<_>>();
1105        if required.is_empty() {
1106            return None;
1107        }
1108        let module_id = registration.manifest.module_id.as_str();
1109        let verdict = |capability: &str| self.capability_evaluator.verdict(module_id, capability);
1110        if required
1111            .iter()
1112            .any(|capability| verdict(capability).is_none())
1113        {
1114            self.refresh_capability_requirements();
1115        }
1116        required
1117            .into_iter()
1118            .find(|capability| verdict(capability) != Some(CapabilityVerdict::Provided))
1119            .map(str::to_string)
1120    }
1121
1122    fn capability_requirement_statuses(&self) -> Vec<CapabilityRequirementStatus> {
1123        self.capability_evaluator
1124            .statuses()
1125            .into_iter()
1126            .map(capability_requirement_status)
1127            .collect()
1128    }
1129
1130    /// Remove a connection's registry entries WITHOUT signalling the supervisor's
1131    /// registration-release watch. The signal is what the supervisor waits on
1132    /// before spawning a replacement, so it must only fire once forwarding
1133    /// teardown is also done (see [`Self::cleanup_connection`] /
1134    /// [`Self::handle_goodbye`]). Used directly only where there is no forwarding
1135    /// state to tear down (a HELLO that failed before module registration).
1136    fn deregister_connection(
1137        &self,
1138        connection_id: ConnectionId,
1139    ) -> Result<Vec<crate::registry::ModuleRegistration>, RegistryError> {
1140        self.registry.deregister_connection(connection_id)
1141    }
1142
1143    pub(crate) fn route_open_target(&self, frame: &Frame) -> Option<String> {
1144        if frame.header.channel != 0 || frame.header.ty != FrameType::Request {
1145            return None;
1146        }
1147        let Ok(ClientControlRequest::RouteOpen { target, .. }) =
1148            parse_client_control_request(&frame.body)
1149        else {
1150            return None;
1151        };
1152        Some(target_module_id(&target).to_string())
1153    }
1154
1155    pub(crate) fn route_open_capacity_refusal(
1156        &self,
1157        ctx: &RouteCtx,
1158        frame: &Frame,
1159        target_module_id: &str,
1160        limit: usize,
1161    ) -> Result<Frame, RouterError> {
1162        self.route_open_admission_refusal_frame(
1163            ctx,
1164            frame,
1165            target_module_id,
1166            format!(
1167                "connection already has {limit} route.open binds in flight; retry after one settles"
1168            ),
1169        )
1170    }
1171
1172    /// Admission pressure clears as existing binds settle, so its refusal must
1173    /// remain in the deployed SDKs' closed retryable set: `unknown_module`,
1174    /// `module_reloading`, `module_warming`, `target_unavailable`, or
1175    /// `module_timeout`. `target_unavailable` is honest for an attempt that
1176    /// cannot currently reach its target; `module_timeout` would falsely claim
1177    /// that a wait expired. A new, cleaner code would be terminal to deployed
1178    /// clients, so it requires a client-tolerance rollout before daemon emission.
1179    fn route_open_admission_refusal_frame(
1180        &self,
1181        ctx: &RouteCtx,
1182        frame: &Frame,
1183        target_module_id: &str,
1184        message: impl Into<String>,
1185    ) -> Result<Frame, RouterError> {
1186        self.route_open_refusal_frame(
1187            ctx,
1188            frame,
1189            target_module_id,
1190            "open_admission_full",
1191            error_codes::TARGET_UNAVAILABLE,
1192            message,
1193        )
1194    }
1195
1196    /// Test-only compatibility entry point for unit control handling that does not have a socket sink.
1197    ///
1198    /// The real server path uses [`Self::handle_control_frame`] so module HELLO registration can
1199    /// record the module connection's [`crate::FrameSink`] and session attach can await the module
1200    /// relay response. This seam stays cfg(test) so production has only one channel-0 path.
1201    #[cfg(test)]
1202    pub fn handle_control(
1203        &self,
1204        connection_id: ConnectionId,
1205        frame: Frame,
1206    ) -> Result<Vec<Frame>, RouterError> {
1207        match frame.header.ty {
1208            FrameType::Ping => Ok(vec![pong(&frame)?]),
1209            FrameType::Hello => self.handle_hello(connection_id, None, frame),
1210            FrameType::Goodbye => self.handle_goodbye(connection_id),
1211            ty => Ok(vec![control_error_frame(
1212                &frame,
1213                "unsupported_control_frame",
1214                format!("unsupported channel-0 frame {ty:?}"),
1215            )?]),
1216        }
1217    }
1218
1219    pub async fn handle_control_frame(
1220        &self,
1221        ctx: &RouteCtx,
1222        frame: Frame,
1223    ) -> Result<Vec<Frame>, RouterError> {
1224        self.handle_control_frame_timed(ctx, frame, None).await
1225    }
1226
1227    pub(crate) async fn handle_control_frame_timed(
1228        &self,
1229        ctx: &RouteCtx,
1230        frame: Frame,
1231        dispatch_started_at: Option<StdInstant>,
1232    ) -> Result<Vec<Frame>, RouterError> {
1233        match frame.header.ty {
1234            FrameType::Ping => Ok(vec![pong(&frame)?]),
1235            FrameType::Hello => {
1236                self.handle_hello(ctx.connection_id, Some(ctx.egress.clone()), frame)
1237            }
1238            FrameType::Goodbye => self.handle_goodbye(ctx.connection_id),
1239            FrameType::Cancel => {
1240                if self
1241                    .supervisor
1242                    .cancel_spawn_subscription(ctx.connection_id, frame.header.corr)
1243                {
1244                    Ok(Vec::new())
1245                } else {
1246                    Ok(vec![control_error_frame(
1247                        &frame,
1248                        "unknown_subscription",
1249                        "no supervisor spawn subscription has this correlation id",
1250                    )?])
1251                }
1252            }
1253            FrameType::Request => {
1254                if self
1255                    .forwarding
1256                    .module_endpoint_for_connection(ctx.connection_id)
1257                    .map_err(RouterError::Forwarding)?
1258                    .is_some()
1259                {
1260                    if !is_known_module_request_op(&frame.body) {
1261                        return Ok(vec![control_error_frame(
1262                            &frame,
1263                            "unsupported_control_frame",
1264                            "module-originated channel-0 REQUEST is not supported",
1265                        )?]);
1266                    }
1267                    let request = match parse_module_control_request_from_module(&frame.body) {
1268                        Ok(request) => request,
1269                        Err((err, ControlRequestBodyError::UnknownOp)) => {
1270                            return Ok(vec![control_error_frame(
1271                                &frame,
1272                                "unsupported_control_frame",
1273                                format!("unsupported module-originated channel-0 REQUEST: {err}"),
1274                            )?])
1275                        }
1276                        Err((err, ControlRequestBodyError::InvalidBody)) => {
1277                            return Ok(vec![control_error_frame(
1278                                &frame,
1279                                "invalid_control_body",
1280                                format!("malformed module control body: {err}"),
1281                            )?])
1282                        }
1283                    };
1284                    let op = module_control_request_op(&request);
1285                    let corr = frame.header.corr;
1286                    log_control_dispatch_arrival(op, ctx.connection_id, corr);
1287                    let result =
1288                        self.handle_module_control_request(ctx.connection_id, frame, request);
1289                    log_slow_control_dispatch(dispatch_started_at, op, ctx.connection_id, corr);
1290                    return result;
1291                }
1292
1293                if is_known_module_request_op(&frame.body) {
1294                    return Ok(vec![control_error_frame(
1295                        &frame,
1296                        "not_registered",
1297                        "catalog.update requires an active module registration owned by this connection",
1298                    )?]);
1299                }
1300
1301                let request = match parse_client_control_request(&frame.body) {
1302                    Ok(request) => request,
1303                    Err((err, ControlRequestBodyError::UnknownOp)) => {
1304                        return Ok(vec![control_error_frame(
1305                            &frame,
1306                            "unknown_control_op",
1307                            format!("unknown client control op: {err}"),
1308                        )?])
1309                    }
1310                    Err((err, ControlRequestBodyError::InvalidBody)) => {
1311                        return Ok(vec![control_error_frame(
1312                            &frame,
1313                            "invalid_control_body",
1314                            format!("malformed client control body: {err}"),
1315                        )?])
1316                    }
1317                };
1318                let op = client_control_request_op(&request);
1319                let corr = frame.header.corr;
1320                log_control_dispatch_arrival(op, ctx.connection_id, corr);
1321                #[cfg(test)]
1322                if let Some(delay) = self.control_dispatch_delay {
1323                    tokio::time::sleep(delay).await;
1324                }
1325                let result = self
1326                    .handle_client_control_request(ctx, frame, request)
1327                    .await;
1328                log_slow_control_dispatch(dispatch_started_at, op, ctx.connection_id, corr);
1329                result
1330            }
1331            FrameType::Push => {
1332                let Some(endpoint) = self
1333                    .forwarding
1334                    .module_endpoint_for_connection(ctx.connection_id)
1335                    .map_err(RouterError::Forwarding)?
1336                else {
1337                    return Ok(vec![control_error_frame(
1338                        &frame,
1339                        "unsupported_control_frame",
1340                        "client-originated channel-0 PUSH is not supported",
1341                    )?]);
1342                };
1343                self.handle_status_update(endpoint, frame)
1344            }
1345            FrameType::Response | FrameType::Error
1346                if self
1347                    .forwarding
1348                    .module_endpoint_for_connection(ctx.connection_id)
1349                    .map_err(RouterError::Forwarding)?
1350                    .is_some() =>
1351            {
1352                self.handle_module_relay_response(ctx.connection_id, frame)
1353            }
1354            ty => Ok(vec![control_error_frame(
1355                &frame,
1356                "unsupported_control_frame",
1357                format!("unsupported channel-0 frame {ty:?}"),
1358            )?]),
1359        }
1360    }
1361
1362    pub fn cleanup_connection(
1363        &self,
1364        connection_id: ConnectionId,
1365    ) -> Result<Vec<crate::registry::ModuleRegistration>, RegistryError> {
1366        let crash_closed = self
1367            .registry
1368            .get_module_by_connection(connection_id)?
1369            .and_then(|registration| {
1370                self.forwarding
1371                    .module_endpoint_for_connection(connection_id)
1372                    .ok()
1373                    .flatten()
1374                    .and_then(|endpoint| self.forwarding.endpoint_routes(endpoint).ok())
1375                    .map(|routes| (registration.manifest.module_id, routes))
1376            });
1377        if let Some((module_id, routes)) = crash_closed {
1378            let terminal = match self.supervisor.get(&module_id) {
1379                None => false,
1380                Some(module) => match module.will_recover_after_connection_loss() {
1381                    Ok(will_recover) => !will_recover,
1382                    Err(err) => {
1383                        warn!(
1384                            %module_id,
1385                            error = %err,
1386                            "failed to read crash recovery verdict; reporting non-terminal conservatively"
1387                        );
1388                        false
1389                    }
1390                },
1391            };
1392            send_route_control_pushes(
1393                &self.forwarding,
1394                routes,
1395                ClientControlPush::RouteClosed {
1396                    module_id,
1397                    reason: RouteCloseReason::Crash,
1398                    drained: false,
1399                    abandoned: 0,
1400                    excluded_subscriptions: 0,
1401                    terminal: Some(terminal),
1402                },
1403            );
1404        }
1405        let registrations = self.deregister_connection(connection_id);
1406        if let Ok(released_routes) = self.forwarding.cleanup_connection(connection_id) {
1407            self.emit_route_goodbyes(released_routes);
1408        }
1409        // Signal the registration-release watch only now that BOTH registry and
1410        // forwarding teardown are done, so a supervisor waiting to spawn a
1411        // replacement never observes release while old routes still exist.
1412        if matches!(&registrations, Ok(r) if !r.is_empty()) {
1413            crate::supervise::notify_registration_release();
1414            self.capability_evaluator.wake_deadline_loop();
1415            self.refresh_capability_requirements();
1416        }
1417        self.supervisor.remove_spawn_subscribers(connection_id);
1418        registrations
1419    }
1420
1421    pub(crate) fn handle_route_goodbye(
1422        &self,
1423        connection_id: ConnectionId,
1424        route_channel: u16,
1425        route_epoch: u32,
1426    ) -> Result<bool, RouterError> {
1427        debug!(
1428            connection_id = connection_id.get(),
1429            route_channel, route_epoch, "handling route GOODBYE"
1430        );
1431        let RouteRelease::Removed(released_route) = self
1432            .forwarding
1433            .release_client_route(connection_id, route_channel, route_epoch)
1434            .map_err(RouterError::Forwarding)?
1435        else {
1436            return Ok(false);
1437        };
1438        self.emit_route_goodbyes(vec![released_route]);
1439        Ok(true)
1440    }
1441
1442    fn emit_route_goodbyes(&self, released_routes: Vec<GoodbyeTarget>) {
1443        for released in released_routes {
1444            let frame = match Frame::build_with_version(
1445                released.negotiated_ver,
1446                FrameType::Goodbye,
1447                control_flags(),
1448                released.channel,
1449                released.epoch,
1450                0,
1451                Vec::new(),
1452            ) {
1453                Ok(frame) => frame,
1454                Err(err) => {
1455                    warn!(
1456                        route_channel = released.channel,
1457                        error = %err,
1458                        "failed to build route GOODBYE frame"
1459                    );
1460                    continue;
1461                }
1462            };
1463            if let Err(err) = released.sink.try_send(frame) {
1464                if released.close_on_delivery_failure() {
1465                    warn!(
1466                        target_connection_id = released.connection_id.get(),
1467                        route_channel = released.channel,
1468                        error = %err,
1469                        "route GOODBYE was not delivered to client; closing target connection"
1470                    );
1471                    if self
1472                        .forwarding
1473                        .escalate_client_delivery_failure(
1474                            released.connection_id,
1475                            released.channel,
1476                            released.epoch,
1477                            CloseReason::new(
1478                                "route_goodbye_delivery_failed",
1479                                format!(
1480                                    "failed to enqueue route GOODBYE for channel {}: {err}",
1481                                    released.channel
1482                                ),
1483                            ),
1484                            crate::forwarding::UndeliveredFrame {
1485                                module_id: released.module_id.as_deref(),
1486                                sink: &released.sink,
1487                            },
1488                        )
1489                        .unwrap_or(false)
1490                    {
1491                        self.counters.increment_goodbye_relay_client_failed();
1492                    }
1493                } else {
1494                    self.counters
1495                        .increment_goodbye_relay_module_dropped(released.module_id.as_deref());
1496                    warn!(
1497                        target_connection_id = released.connection_id.get(),
1498                        route_channel = released.channel,
1499                        error = %err,
1500                        "route GOODBYE to module dropped under backpressure; not closing shared module connection"
1501                    );
1502                }
1503            }
1504        }
1505    }
1506
1507    /// Best-effort GOODBYE to a module for a route channel subc reserved but then
1508    /// abandoned (route.bind relay timed out, its waiter was cancelled, or subc's
1509    /// own commit failed after the module had already accepted). Without this, a
1510    /// module that accepts late keeps a binding subc has torn down, so a later
1511    /// frame on that module channel could misdeliver if the channel is reused.
1512    ///
1513    /// Never closes the shared module connection on failure: a dropped notification
1514    /// only wastes a bounded amount of warm module-side state, which the module's
1515    /// own idle reaper reclaims. Only call this once the route.bind relay was
1516    /// actually enqueued to the module — if the relay send itself failed, the
1517    /// module never created a binding and there is nothing to tear down.
1518    fn send_abandoned_route_bind_goodbye(
1519        &self,
1520        module_sink: &crate::FrameSink,
1521        negotiated_ver: u8,
1522        module_channel: u16,
1523        module_epoch: u32,
1524    ) {
1525        let frame = match Frame::build_with_version(
1526            negotiated_ver,
1527            FrameType::Goodbye,
1528            control_flags(),
1529            module_channel,
1530            module_epoch,
1531            0,
1532            Vec::new(),
1533        ) {
1534            Ok(frame) => frame,
1535            Err(err) => {
1536                warn!(
1537                    route_channel = module_channel,
1538                    error = %err,
1539                    "failed to build GOODBYE for abandoned route.bind"
1540                );
1541                return;
1542            }
1543        };
1544        if let Err(err) = module_sink.try_send(frame) {
1545            warn!(
1546                route_channel = module_channel,
1547                error = %err,
1548                "GOODBYE for abandoned route.bind dropped; module idle reaper will reclaim the binding"
1549            );
1550        }
1551    }
1552
1553    fn handle_hello(
1554        &self,
1555        connection_id: ConnectionId,
1556        sink: Option<crate::FrameSink>,
1557        frame: Frame,
1558    ) -> Result<Vec<Frame>, RouterError> {
1559        debug!(
1560            connection_id = connection_id.get(),
1561            corr = frame.header.corr,
1562            "handling HELLO"
1563        );
1564        let hello_value = match serde_json::from_slice::<serde_json::Value>(&frame.body) {
1565            Ok(value) => value,
1566            Err(err) => {
1567                return Ok(vec![control_error_frame(
1568                    &frame,
1569                    "invalid_hello",
1570                    format!("malformed HELLO body: {err}"),
1571                )?])
1572            }
1573        };
1574        if let Err(err) = validate_hello_capability_grammar(&hello_value) {
1575            return Ok(vec![control_error_frame(
1576                &frame,
1577                "invalid_capability_grammar",
1578                err.to_string(),
1579            )?]);
1580        }
1581        if let Err(err) = validate_hello_self_signal_declarations(&hello_value) {
1582            return Ok(vec![control_error_frame(
1583                &frame,
1584                "invalid_manifest",
1585                err.to_string(),
1586            )?]);
1587        }
1588        if let Some(provenance) = hello_value
1589            .get("manifest")
1590            .and_then(|manifest| manifest.get("provenance"))
1591        {
1592            if let Err(err) = serde_json::from_value::<ManifestProvenance>(provenance.clone()) {
1593                return Ok(vec![control_error_frame(
1594                    &frame,
1595                    "invalid_manifest",
1596                    format!("malformed manifest provenance: {err}"),
1597                )?]);
1598            }
1599        }
1600        let hello = match serde_json::from_value::<ModuleHelloBody>(hello_value) {
1601            Ok(hello) => hello,
1602            Err(err) => {
1603                return Ok(vec![control_error_frame(
1604                    &frame,
1605                    "invalid_hello",
1606                    format!("malformed HELLO body: {err}"),
1607                )?])
1608            }
1609        };
1610
1611        if hello.protocol_ver != hello.manifest.protocol_ver {
1612            return Ok(vec![control_error_frame(
1613                &frame,
1614                "invalid_manifest",
1615                format!(
1616                    "HELLO protocol_ver {} does not match manifest protocol_ver {}",
1617                    hello.protocol_ver, hello.manifest.protocol_ver
1618                ),
1619            )?]);
1620        }
1621
1622        if hello.manifest.module_id.trim().is_empty() {
1623            return Ok(vec![control_error_frame(
1624                &frame,
1625                "invalid_manifest",
1626                "manifest module_id must not be empty",
1627            )?]);
1628        }
1629
1630        let negotiated_ver = match negotiate_version(hello.protocol_ver) {
1631            Ok(negotiated_ver) => negotiated_ver,
1632            Err(message) => {
1633                return Ok(vec![control_error_frame(
1634                    &frame,
1635                    "version_unsupported",
1636                    message,
1637                )?])
1638            }
1639        };
1640
1641        // Swap gate, ahead of the reserved gate on purpose. While a blue/green
1642        // swap is open for this id, the only HELLO admitted as a second process
1643        // is the one carrying the candidate's launch nonce (the swap token), and
1644        // it registers into the candidate slot rather than being refused as a
1645        // duplicate. Run after the reserved gate, a reserved module's candidate
1646        // would be refused `reserved_module` for presenting a nonce that gate
1647        // does not know. See `SupervisorHandle::swap_hello_admission`.
1648        let swap_admission = self
1649            .supervisor
1650            .swap_hello_admission(&hello.manifest.module_id, hello.launch_nonce.as_deref());
1651        if swap_admission == SwapHelloAdmission::Refused {
1652            warn!(
1653                module_id = %hello.manifest.module_id,
1654                connection_id = connection_id.get(),
1655                "HELLO refused: a swap is open for this module_id and the launch nonce is not one the supervisor minted for it"
1656            );
1657            return Ok(vec![control_error_frame(
1658                &frame,
1659                "swap_token_invalid",
1660                format!(
1661                    "module_id '{}' is being swapped; HELLO without the swap candidate's launch nonce is rejected",
1662                    hello.manifest.module_id
1663                ),
1664            )?]);
1665        }
1666        let swap_candidate = swap_admission == SwapHelloAdmission::Candidate;
1667
1668        // Reserved-module identity gate: a module_id configured `reserved` may be
1669        // registered ONLY by the process subc spawned for it, proven by echoing the
1670        // one-time launch nonce subc injected. A non-reserved id has no recorded
1671        // nonce and always passes. This blocks a key-holder from impersonating a
1672        // security-boundary module (e.g. the credential vault) while the real one is
1673        // down/restarting and its registration slot is momentarily free. A swap
1674        // candidate has already proven the same thing with its own nonce above.
1675        if let Some(rejection) = (!swap_candidate)
1676            .then(|| {
1677                self.supervisor.reserved_hello_rejection(
1678                    &hello.manifest.module_id,
1679                    hello.launch_nonce.as_deref(),
1680                )
1681            })
1682            .flatten()
1683        {
1684            let message = match rejection {
1685                ReservedHelloRejection::Exact { module_id } => format!(
1686                    "module_id '{module_id}' is reserved; HELLO without a valid launch nonce is rejected"
1687                ),
1688                ReservedHelloRejection::Prefix {
1689                    prefix,
1690                    owner_module_id,
1691                } => format!(
1692                    "module_id '{}' matches reserved prefix '{prefix}' owned by '{owner_module_id}'; HELLO without the owner launch nonce is rejected",
1693                    hello.manifest.module_id
1694                ),
1695            };
1696            return Ok(vec![control_error_frame(
1697                &frame,
1698                "reserved_module",
1699                message,
1700            )?]);
1701        }
1702
1703        let reserved_capability_refusals = self.capability_evaluator.reserved_hello_refusals(
1704            &hello.manifest.module_id,
1705            hello.manifest.capabilities.as_ref(),
1706        );
1707        if let Some(refusal) = reserved_capability_refusals.first() {
1708            let capability = refusal.capability.clone();
1709            let bound_module = refusal.claimants[0].clone();
1710            log_duplicate_claim_events(reserved_capability_refusals);
1711            return Ok(vec![control_error_frame(
1712                &frame,
1713                "reserved_capability",
1714                format!(
1715                    "capability '{}' is reserved for module_id '{}'; claimant '{}' was refused",
1716                    capability, bound_module, hello.manifest.module_id
1717                ),
1718            )?]);
1719        }
1720
1721        // A connection that already opened client routes must not also register as
1722        // a module: cleanup would then release only one side and leak the other.
1723        if self
1724            .forwarding
1725            .connection_has_client_routes(connection_id)
1726            .map_err(RouterError::Forwarding)?
1727        {
1728            return Ok(vec![control_error_frame(
1729                &frame,
1730                "invalid_hello",
1731                "connection has open client routes and cannot also register as a module",
1732            )?]);
1733        }
1734
1735        let control_ops = effective_module_control_ops(hello.control_ops);
1736        // Built before anything is registered so an encoding failure leaves no
1737        // registry or forwarding state behind.
1738        let hello_ack = self.build_hello_ack(&frame, negotiated_ver, &hello.manifest.module_id)?;
1739        if swap_candidate {
1740            return self.register_swap_candidate(
1741                connection_id,
1742                sink,
1743                &frame,
1744                hello.manifest,
1745                negotiated_ver,
1746                control_ops,
1747                hello_ack,
1748            );
1749        }
1750        let registration = match self.registry.register_with_control_ops(
1751            hello.manifest,
1752            negotiated_ver,
1753            connection_id,
1754            control_ops,
1755        ) {
1756            Ok(registration) => registration,
1757            Err(RegistryError::DuplicateModuleId { module_id }) => {
1758                return Ok(vec![control_error_frame(
1759                    &frame,
1760                    "duplicate_module_id",
1761                    format!(
1762                        "module_id '{module_id}' is already registered; duplicate HELLO rejected"
1763                    ),
1764                )?])
1765            }
1766            Err(err @ RegistryError::PathHazardModuleId { .. }) => {
1767                return Ok(vec![control_error_frame(
1768                    &frame,
1769                    "invalid_module_id",
1770                    err.to_string(),
1771                )?])
1772            }
1773            Err(err) => {
1774                return Ok(vec![control_error_frame(
1775                    &frame,
1776                    "registry_error",
1777                    err.to_string(),
1778                )?])
1779            }
1780        };
1781
1782        let reply = if let Some(sink) = sink {
1783            // The forwarding table's module store is also the daemon-to-module
1784            // control-RPC lane, so every HELLO gets a live endpoint even when the
1785            // manifest has no routable provider role. Non-routable modules still
1786            // cannot receive route.bind in production: `handle_route_open` checks
1787            // the registry manifest with `target_has_required_role` before the
1788            // only production call to `begin_route_bind_relay_for` below that
1789            // route.open path. The remaining direct relay callers are unit tests
1790            // and benchmark harnesses that construct forwarding state explicitly.
1791            //
1792            // The HELLO_ACK is queued by the forwarding table itself, before the
1793            // endpoint becomes visible, and is NOT returned as a reply. A module
1794            // reads HELLO_ACK first and exits on anything else; a reply is only
1795            // written after this handler returns, by which time a route.open on
1796            // another connection could already have queued a route.bind request
1797            // for this module ahead of it.
1798            let concurrency = manifest_concurrency(&registration.manifest);
1799            if let Err(err) = self.forwarding.register_module_connection_acked(
1800                connection_id,
1801                registration.manifest.module_id.clone(),
1802                negotiated_ver,
1803                concurrency,
1804                sink,
1805                hello_ack,
1806            ) {
1807                // Forwarding registration failed, so there is no forwarding
1808                // state to tear down. Remove the registry entry and signal the
1809                // release watch directly.
1810                if matches!(self.deregister_connection(connection_id), Ok(r) if !r.is_empty()) {
1811                    crate::supervise::notify_registration_release();
1812                }
1813                return Ok(vec![control_error_frame(
1814                    &frame,
1815                    forwarding_error_code(&err),
1816                    err.to_string(),
1817                )?]);
1818            }
1819            Vec::new()
1820        } else {
1821            // No sink means no forwarding endpoint, so nothing can be routed
1822            // ahead of the ack; it goes out as the reply.
1823            vec![hello_ack]
1824        };
1825
1826        // Exposure over assumption: Concurrency's serde default is pinned to the
1827        // pre-field behavior (ModuleManaged), so a management surface that is
1828        // genuinely Serial and just never declared it inherits concurrent
1829        // delivery silently. Logging which registrations RESOLVED BY DEFAULT
1830        // turns "no module has been bitten yet" into the checkable claim "no
1831        // module is exposed" -- one read of the boot log instead of a fleet
1832        // audit. Detected from the raw HELLO bytes because the serde default
1833        // deliberately erases the absent/declared distinction from the type.
1834        if manifest_concurrency_was_defaulted(&frame.body, &registration.manifest) {
1835            info!(
1836                module_id = %registration.manifest.module_id,
1837                "management surface registered with DEFAULTED concurrency=module_managed (manifest predates the field; declare the real lane)"
1838            );
1839        }
1840
1841        self.apply_registration_capabilities(&registration);
1842
1843        info!(
1844            module_id = %registration.manifest.module_id,
1845            module_version = %registration.manifest.module_version,
1846            negotiated_ver,
1847            routable_provider = manifest_provides_routable_role(&registration.manifest),
1848            connection_id = connection_id.get(),
1849            "module registered"
1850        );
1851
1852        Ok(reply)
1853    }
1854
1855    /// Register a HELLO the swap gate admitted into the candidate slot of the
1856    /// registry and of forwarding, where it is reachable over its own
1857    /// connection (its `catalog.update` finds it) but by no by-id lookup, so
1858    /// nothing routes to it until the supervisor cuts over.
1859    ///
1860    /// Registry first, then forwarding, the same order as an ordinary HELLO;
1861    /// a forwarding failure removes the registry entry again. The capability
1862    /// census is not run: it describes routable modules, and this one is not
1863    /// routable until promotion.
1864    #[allow(clippy::too_many_arguments)]
1865    fn register_swap_candidate(
1866        &self,
1867        connection_id: ConnectionId,
1868        sink: Option<crate::FrameSink>,
1869        frame: &Frame,
1870        manifest: ModuleManifest,
1871        negotiated_ver: u8,
1872        control_ops: Vec<String>,
1873        hello_ack: Frame,
1874    ) -> Result<Vec<Frame>, RouterError> {
1875        let module_id = manifest.module_id.clone();
1876        let registration = match self.registry.register_candidate_with_control_ops(
1877            manifest,
1878            negotiated_ver,
1879            connection_id,
1880            control_ops,
1881        ) {
1882            Ok(registration) => registration,
1883            Err(RegistryError::DuplicateModuleId { module_id }) => {
1884                return Ok(vec![control_error_frame(
1885                    frame,
1886                    "duplicate_module_id",
1887                    format!(
1888                        "module_id '{module_id}' already has a swap candidate registered; duplicate HELLO rejected"
1889                    ),
1890                )?])
1891            }
1892            Err(err @ RegistryError::PathHazardModuleId { .. }) => {
1893                return Ok(vec![control_error_frame(
1894                    frame,
1895                    "invalid_module_id",
1896                    err.to_string(),
1897                )?])
1898            }
1899            Err(err) => {
1900                return Ok(vec![control_error_frame(
1901                    frame,
1902                    "registry_error",
1903                    err.to_string(),
1904                )?])
1905            }
1906        };
1907        let reply = if let Some(sink) = sink {
1908            // Same ordering as an ordinary HELLO: the forwarding table queues
1909            // the HELLO_ACK before the candidate endpoint is inserted, because
1910            // a module exits if its first frame after HELLO is anything else.
1911            let concurrency = manifest_concurrency(&registration.manifest);
1912            if let Err(err) = self.forwarding.register_candidate_module_connection_acked(
1913                connection_id,
1914                module_id.clone(),
1915                negotiated_ver,
1916                concurrency,
1917                sink,
1918                hello_ack,
1919            ) {
1920                if matches!(self.deregister_connection(connection_id), Ok(r) if !r.is_empty()) {
1921                    crate::supervise::notify_registration_release();
1922                }
1923                return Ok(vec![control_error_frame(
1924                    frame,
1925                    forwarding_error_code(&err),
1926                    err.to_string(),
1927                )?]);
1928            }
1929            Vec::new()
1930        } else {
1931            vec![hello_ack]
1932        };
1933        self.supervisor.mark_swap_candidate_admitted(&module_id);
1934        info!(
1935            module_id = %module_id,
1936            module_version = %registration.manifest.module_version,
1937            negotiated_ver,
1938            ready = registration.ready,
1939            connection_id = connection_id.get(),
1940            "swap candidate registered; not routable until cutover"
1941        );
1942        Ok(reply)
1943    }
1944
1945    fn build_hello_ack(
1946        &self,
1947        frame: &Frame,
1948        negotiated_ver: u8,
1949        module_id: &str,
1950    ) -> Result<Frame, RouterError> {
1951        let ack = ModuleHelloAckBody {
1952            negotiated_ver,
1953            subc_ops: module_subc_ops(),
1954            subc_capabilities: self.subc_capabilities.as_ref().to_vec(),
1955            storage: self
1956                .storage_config
1957                .as_ref()
1958                .map(|cfg| cfg.descriptor_for(module_id)),
1959            machine_id: self.machine_id.as_ref().map(|id| id.as_str().to_owned()),
1960        };
1961        let body = serde_json::to_vec(&ack).map_err(|err| {
1962            RouterError::backend(
1963                0,
1964                frame.header.corr,
1965                format!("failed to encode HELLO_ACK: {err}"),
1966            )
1967        })?;
1968
1969        Frame::build_with_version(
1970            negotiated_ver,
1971            FrameType::HelloAck,
1972            control_flags(),
1973            0,
1974            0,
1975            frame.header.corr,
1976            body,
1977        )
1978        .map_err(RouterError::FrameBuild)
1979    }
1980
1981    async fn handle_client_control_request(
1982        &self,
1983        ctx: &RouteCtx,
1984        frame: Frame,
1985        request: ClientControlRequest,
1986    ) -> Result<Vec<Frame>, RouterError> {
1987        match request {
1988            ClientControlRequest::ServerDescribe {} => self.handle_server_describe(frame),
1989            ClientControlRequest::CatalogList { module_id } => {
1990                self.handle_catalog_list(frame, module_id)
1991            }
1992            ClientControlRequest::RouteOpen {
1993                target,
1994                identity,
1995                consumer_identity,
1996                consumer_capabilities,
1997                admission_facts,
1998            } => {
1999                self.handle_route_open(
2000                    ctx,
2001                    frame,
2002                    RouteOpenRequest {
2003                        target,
2004                        identity,
2005                        consumer_identity,
2006                        consumer_capabilities,
2007                        admission_facts,
2008                    },
2009                )
2010                .await
2011            }
2012            ClientControlRequest::RoutePoll {
2013                route_channel,
2014                route_epoch,
2015                kind,
2016            } => self.handle_route_poll(ctx, frame, route_channel, route_epoch, kind),
2017            ClientControlRequest::SupervisorList {} => self.handle_supervisor_list(frame),
2018            ClientControlRequest::SupervisorSpawnSnapshot {} => {
2019                self.handle_supervisor_spawn_snapshot(frame)
2020            }
2021            ClientControlRequest::SupervisorSpawnSubscribe { since } => {
2022                self.handle_supervisor_spawn_subscribe(ctx, frame, since)
2023            }
2024            ClientControlRequest::SupervisorRestart {
2025                module_id,
2026                drain_timeout_ms,
2027            } => {
2028                self.handle_supervisor_restart(frame, module_id, drain_timeout_ms)
2029                    .await
2030            }
2031            ClientControlRequest::SupervisorSwap {
2032                module_id,
2033                ready_timeout_ms,
2034            } => {
2035                self.handle_supervisor_swap(frame, module_id, ready_timeout_ms)
2036                    .await
2037            }
2038            ClientControlRequest::SupervisorReload { module_id } => {
2039                self.handle_supervisor_reload(frame, module_id).await
2040            }
2041            ClientControlRequest::SupervisorRescan { preview } => {
2042                self.handle_supervisor_rescan(frame, preview).await
2043            }
2044            ClientControlRequest::SupervisorReleaseReserved { module_id } => {
2045                self.handle_supervisor_release_reserved(frame, module_id)
2046                    .await
2047            }
2048            ClientControlRequest::SupervisorSetEnabled { module_id, enabled } => {
2049                self.handle_supervisor_set_enabled(frame, module_id, enabled)
2050                    .await
2051            }
2052            ClientControlRequest::SupervisorHealthProbe { module_id } => {
2053                self.handle_supervisor_health_probe(frame, module_id).await
2054            }
2055            ClientControlRequest::SupervisorHealth {} => self.handle_supervisor_health(frame),
2056            ClientControlRequest::SupervisorRoutes { module_id } => {
2057                self.handle_supervisor_routes(frame, module_id)
2058            }
2059            ClientControlRequest::SupervisorProvenance { module_id } => {
2060                self.handle_supervisor_provenance(frame, module_id).await
2061            }
2062            ClientControlRequest::SupervisorStderrTail {
2063                module_id,
2064                max_lines,
2065                max_bytes,
2066            } => self.handle_supervisor_stderr_tail(frame, module_id, max_lines, max_bytes),
2067            ClientControlRequest::SupervisorTerminals { module_id } => {
2068                self.handle_supervisor_terminals(frame, module_id).await
2069            }
2070        }
2071    }
2072
2073    fn handle_module_control_request(
2074        &self,
2075        connection_id: ConnectionId,
2076        frame: Frame,
2077        request: ModuleControlRequestFromModule,
2078    ) -> Result<Vec<Frame>, RouterError> {
2079        match request {
2080            ModuleControlRequestFromModule::CatalogUpdate {
2081                provides,
2082                capabilities,
2083                ready,
2084            } => self.handle_catalog_update(connection_id, frame, provides, capabilities, ready),
2085            ModuleControlRequestFromModule::LiveRoots {} => {
2086                let registered = self
2087                    .registry
2088                    .get_module_by_connection(connection_id)
2089                    .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?;
2090                let Some(registration) = registered else {
2091                    return Ok(vec![control_error_frame(&frame, "not_registered", "supervisor.live_roots requires an active module registration owned by this connection")?]);
2092                };
2093                let response = self
2094                    .forwarding
2095                    .live_roots(&registration.manifest.module_id)
2096                    .map_err(RouterError::Forwarding)?;
2097                Ok(vec![control_response_body_frame(
2098                    &frame,
2099                    &response,
2100                    "ModuleControlResponseToModule::LiveRoots",
2101                )?])
2102            }
2103        }
2104    }
2105
2106    fn handle_catalog_update(
2107        &self,
2108        connection_id: ConnectionId,
2109        frame: Frame,
2110        provides: Vec<ProviderRole>,
2111        capabilities: Option<CapabilityDeclarations>,
2112        ready: Option<bool>,
2113    ) -> Result<Vec<Frame>, RouterError> {
2114        self.refresh_capability_requirements();
2115        let Some(registration) = self
2116            .registry
2117            .get_module_by_connection(connection_id)
2118            .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?
2119        else {
2120            return Ok(vec![control_error_frame(
2121                &frame,
2122                "not_registered",
2123                "catalog.update requires an active module registration owned by this connection",
2124            )?]);
2125        };
2126
2127        if let Some(message) =
2128            catalog_update_frozen_field_message(&registration.manifest, &provides)
2129        {
2130            return Ok(vec![control_error_frame(
2131                &frame,
2132                "catalog_update_frozen_field",
2133                message,
2134            )?]);
2135        }
2136
2137        let mut candidate = registration.manifest.clone();
2138        candidate.provides = provides.clone();
2139        candidate.capabilities = capabilities
2140            .clone()
2141            .or_else(|| registration.manifest.capabilities.clone());
2142        if let Err(err) = candidate.validate_capability_grammar() {
2143            return Ok(vec![control_error_frame(
2144                &frame,
2145                "invalid_capability_grammar",
2146                err.to_string(),
2147            )?]);
2148        }
2149
2150        let updated = self
2151            .registry
2152            .replace_catalog_for_connection(connection_id, provides, capabilities, ready)
2153            .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?;
2154        if updated.is_none() {
2155            return Ok(vec![control_error_frame(
2156                &frame,
2157                "not_registered",
2158                "catalog.update requires an active module registration owned by this connection",
2159            )?]);
2160        }
2161        if let Ok((_, registrations)) = self.runtime_capability_snapshot() {
2162            log_duplicate_claim_events(
2163                self.capability_evaluator
2164                    .duplicate_claims(DuplicateClaimSource::CatalogUpdate, &registrations),
2165            );
2166        }
2167        if capability_census_trigger(
2168            registration.manifest.capabilities.as_ref(),
2169            updated
2170                .as_ref()
2171                .and_then(|entry| entry.manifest.capabilities.as_ref()),
2172        ) {
2173            self.enforce_capability_denies();
2174        }
2175        self.refresh_capability_requirements();
2176
2177        let response = ModuleControlResponseToModule::CatalogUpdate {};
2178        control_response_body_frame(
2179            &frame,
2180            &response,
2181            "ModuleControlResponseToModule::CatalogUpdate",
2182        )
2183        .map(|frame| vec![frame])
2184    }
2185
2186    fn handle_server_describe(&self, frame: Frame) -> Result<Vec<Frame>, RouterError> {
2187        self.refresh_capability_requirements();
2188        // A bare connection count is ambiguous between many clients holding a
2189        // route each and one client accumulating hundreds, so publish the
2190        // concentration alongside it. Route state is best-effort here: a
2191        // diagnostic endpoint must still answer if the forwarding lock is
2192        // contended.
2193        let mut counters = self.counters.snapshot();
2194        if let (Ok((connections_with_routes, max)), Some(obj)) = (
2195            self.forwarding.client_route_concentration(),
2196            counters.as_object_mut(),
2197        ) {
2198            obj.insert(
2199                "client_connections_with_routes".into(),
2200                connections_with_routes.into(),
2201            );
2202            obj.insert("max_routes_on_one_connection".into(), max.into());
2203        }
2204        // A module that is being fast-refused and a module that is fine look
2205        // identical from a client that retries and succeeds, so name the open
2206        // breakers here. This rides the existing free-form counters object
2207        // rather than a new wire field, so no sibling that deserializes
2208        // `ServerDescribe` has to be rebuilt to keep reading it.
2209        if let (Some(open_breakers), Some(obj)) = (
2210            self.route_bind_breakers.open_snapshot(),
2211            counters.as_object_mut(),
2212        ) {
2213            obj.insert("route_bind_breakers_open".into(), open_breakers);
2214        }
2215        let response = ClientControlResponse::ServerDescribe {
2216            protocol_ver: PROTOCOL_VERSION,
2217            subc_ops: subc_ops(),
2218            capabilities: self.subc_capabilities.as_ref().to_vec(),
2219            connected_clients: self.connected_clients.count(),
2220            counters: Some(counters),
2221            build_git_sha: Some(env!("SUBC_BUILD_GIT_SHA").to_string()),
2222            build_lock_digest: Some(env!("SUBC_BUILD_LOCK_DIGEST").to_string()),
2223            capability_requirements: self.capability_requirement_statuses(),
2224            machine_id: self.machine_id.as_ref().map(|id| id.as_str().to_owned()),
2225        };
2226        Ok(vec![control_response_body_frame(
2227            &frame,
2228            &response,
2229            "ClientControlResponse::ServerDescribe",
2230        )?])
2231    }
2232
2233    fn handle_catalog_list(
2234        &self,
2235        frame: Frame,
2236        module_id: Option<String>,
2237    ) -> Result<Vec<Frame>, RouterError> {
2238        let (generation, modules) = self.registry.list_modules().map_err(|err| {
2239            RouterError::backend(0, frame.header.corr, format!("registry error: {err}"))
2240        })?;
2241        let entries = modules
2242            .into_iter()
2243            .filter(|registration| {
2244                module_id
2245                    .as_deref()
2246                    .map(|wanted| registration.manifest.module_id == wanted)
2247                    .unwrap_or(true)
2248            })
2249            .map(|registration| {
2250                let not_ready = self.not_ready_reason(&registration);
2251                let roles = registration.manifest.provides;
2252                CatalogEntry {
2253                    module_id: registration.manifest.module_id,
2254                    ready: not_ready.is_none(),
2255                    not_ready,
2256                    module_version: Some(registration.manifest.module_version),
2257                    roles,
2258                    control_ops: registration.control_ops,
2259                    capabilities: registration.manifest.capabilities,
2260                    self_signals: registration.manifest.self_signals,
2261                }
2262            })
2263            .collect();
2264        let response = ClientControlResponse::CatalogList {
2265            generation,
2266            modules: entries,
2267            subc_ops: subc_ops(),
2268        };
2269        Ok(vec![control_response_body_frame(
2270            &frame,
2271            &response,
2272            "ClientControlResponse::CatalogList",
2273        )?])
2274    }
2275
2276    fn route_open_principal(
2277        &self,
2278        frame: &Frame,
2279        consumer_identity: Option<ConsumerIdentity>,
2280    ) -> Result<Result<Principal, Frame>, RouterError> {
2281        let Some(consumer_identity) = consumer_identity else {
2282            return Ok(Ok(Principal::Direct));
2283        };
2284
2285        if self.supervisor.spawned_consumer_authorized(
2286            &consumer_identity.module_id,
2287            &consumer_identity.launch_nonce,
2288        ) {
2289            return Ok(Ok(Principal::Reserved {
2290                module_id: consumer_identity.module_id,
2291            }));
2292        }
2293
2294        Ok(Err(control_error_frame(
2295            frame,
2296            "bad_consumer_identity",
2297            format!(
2298                "consumer_identity for module_id '{}' did not match a supervised launch nonce",
2299                consumer_identity.module_id
2300            ),
2301        )?))
2302    }
2303
2304    /// Every refusal of a `route.open` goes through here so the daemon can
2305    /// attest which code it sent: without the event, a client's "the daemon
2306    /// refused me" and the daemon's own view could only be reconciled by
2307    /// argument. Malformed input (`invalid_project_root`) does not come here;
2308    /// rejecting a request that was never a valid open is not a refusal of one.
2309    fn route_open_refusal_frame(
2310        &self,
2311        ctx: &RouteCtx,
2312        frame: &Frame,
2313        module_id: &str,
2314        reason: &'static str,
2315        code: &'static str,
2316        message: impl Into<String>,
2317    ) -> Result<Frame, RouterError> {
2318        self.observe_route_open_refusal(ctx, module_id, reason, code);
2319        control_error_frame(frame, code, message.into())
2320    }
2321
2322    /// Refuse a `route.open` because the target module's bind-relay breaker is
2323    /// open, without attempting the relay.
2324    ///
2325    /// The wire code is `module_timeout`, which is the truth (the module has
2326    /// not been answering binds) and which both SDKs already classify as
2327    /// retryable with capped backoff. Reusing it is what keeps this change out
2328    /// of both SDKs; the daemon-side distinction lives in the counter key
2329    /// instead.
2330    ///
2331    /// DELIBERATELY NOT LOGGED PER OCCURRENCE, unlike every other refusal.
2332    /// While a breaker is open this fires on every open to that module, and the
2333    /// stall written up in `docs/designs/route-open-head-of-line.md` already
2334    /// produced 261 lines about a single module inside 3000 lines of daemon
2335    /// log. The rare transitions are logged at warn/info instead and the volume
2336    /// is carried by the counter, so the evidence survives without the flood.
2337    /// The debug line keeps a per-refusal record reachable for whoever turns
2338    /// the level up.
2339    fn route_open_breaker_refusal_frame(
2340        &self,
2341        ctx: &RouteCtx,
2342        frame: &Frame,
2343        module_id: &str,
2344        consecutive_timeouts: u32,
2345        retry_in: Duration,
2346        probe_in_flight: bool,
2347    ) -> Result<Frame, RouterError> {
2348        self.counters
2349            .increment_route_open_refused(crate::observability::ROUTE_OPEN_REFUSED_BREAKER_OPEN);
2350        debug!(
2351            target: "control",
2352            code = "module_timeout",
2353            module_id = ?module_id,
2354            connection_id = ctx.connection_id.get(),
2355            consecutive_timeouts,
2356            retry_in_ms = retry_in.as_millis() as u64,
2357            probe_in_flight,
2358            "route.open refused by open bind-relay breaker"
2359        );
2360        let detail = if probe_in_flight {
2361            "one probe bind is already in flight; retry once it settles".to_string()
2362        } else {
2363            format!("not relaying for another {retry_in:?}")
2364        };
2365        control_error_frame(
2366            frame,
2367            "module_timeout",
2368            format!(
2369                "module_id '{module_id}' failed {consecutive_timeouts} consecutive route.bind \
2370                 relays; {detail}"
2371            ),
2372        )
2373    }
2374
2375    /// `code` is daemon vocabulary and prints plainly; `module_id` is the
2376    /// requester's bytes (an unknown target is whatever the client sent) and
2377    /// is Debug-formatted so control characters land in the log escaped
2378    /// rather than as terminal sequences for whoever tails it.
2379    ///
2380    /// `reason` names the check that refused, because one wire code has
2381    /// several senders: after a module registers, `target_unavailable` can
2382    /// come from a missing role, an inactive registration, a supervisor that
2383    /// has not marked the process live, a missing forwarding connection, or a
2384    /// failed relay, and a log that records only the code cannot say which of
2385    /// them fired. It is a static, daemon-chosen label per branch, so it is
2386    /// safe to print plainly and stays a closed set.
2387    fn observe_route_open_refusal(
2388        &self,
2389        ctx: &RouteCtx,
2390        module_id: &str,
2391        reason: &'static str,
2392        code: &'static str,
2393    ) {
2394        self.counters.increment_route_open_refused(code);
2395        info!(
2396            target: "control",
2397            code,
2398            reason,
2399            module_id = ?module_id,
2400            connection_id = ctx.connection_id.get(),
2401            "route.open refused"
2402        );
2403    }
2404
2405    /// Record an ACCEPTED route.open.
2406    ///
2407    /// Refusals have been logged and counted since the attestation work; accepts
2408    /// were invisible, so the daemon knew every principal it stamped and wrote
2409    /// none of them down. The party that attests the identity was the only party
2410    /// not recording it, which left a credential vault unable to name the sender
2411    /// of a call that reached it (claustrum #43) and left the launch-nonce
2412    /// concurrency question unanswerable from the outside.
2413    ///
2414    /// FIELD NAMES MATCH `route.open refused` DELIBERATELY, so one grep over
2415    /// `code`/`module_id`/`connection_id` returns both directions of the same
2416    /// decision rather than two shapes a reader has to join by hand.
2417    ///
2418    /// `module_id` IS RENDERED BARE HERE AND DEBUG-ESCAPED ON THE REFUSAL PATH,
2419    /// and the difference carries information rather than being an
2420    /// inconsistency. This line is only reachable after a successful bind to a
2421    /// REGISTERED module, so the value has already passed HELLO validation
2422    /// including the path-hazard refusal and cannot contain control bytes. A
2423    /// refused id may be arbitrary attacker-chosen bytes and must stay escaped.
2424    /// So A QUOTED `module_id` IN THE LOG MEANS THE VALUE WAS NEVER VALIDATED.
2425    ///
2426    /// Bare is also what every other daemon line already emits (`module
2427    /// registered`, `configured module supervised`). Shipping `?module_id` here
2428    /// made this instrument the only one in the file whose ids did not answer
2429    /// `grep module_id=broca` -- 3 hits against 342 for the escaped form, in a
2430    /// line whose whole purpose is being grepped beside its sibling.
2431    ///
2432    /// THIS RENDERING IS UNFENCED AND THE REASON IS WORTH KNOWING: the in-crate
2433    /// `EventCapture` test layer implements only `record_debug`, so `Visit`
2434    /// forwards every field type through it and a bare `&str` and a `?`-escaped
2435    /// one are recorded identically. A test written against that harness passes
2436    /// either way -- I wrote one, measured it, and deleted it rather than ship a
2437    /// green assertion that cannot fail. The same limit applies to the escaping
2438    /// assertion in `route_open_supervised_absence_emits_refusal_fields_and_counts_code`:
2439    /// it reads as a guard on the Debug escaping and cannot detect its removal.
2440    /// Fencing either needs the real formatter, not the capture layer.
2441    ///
2442    /// `peer_addr` is NOT here and cannot be: `SO_PEERCRED`/`LOCAL_PEERPID` are
2443    /// unix-socket options and subc is loopback TCP, so there is no peer identity
2444    /// to record. The ephemeral port would decay within minutes and answer only a
2445    /// live question. The identity question is instead answered by counting
2446    /// distinct live connections presenting one module's `consumer_identity` --
2447    /// "is anyone else holding this secret" rather than "is this the right
2448    /// process".
2449    fn observe_route_open_accept(&self, ctx: &RouteCtx, module_id: &str, principal: &str) {
2450        self.counters.increment_route_open_accepted(principal);
2451        info!(
2452            target: "control",
2453            principal,
2454            module_id,
2455            connection_id = ctx.connection_id.get(),
2456            "route.open accepted"
2457        );
2458    }
2459
2460    fn supervised_absent_route_open_refusal_frame(
2461        &self,
2462        ctx: &RouteCtx,
2463        frame: &Frame,
2464        module_id: &str,
2465        code: &'static str,
2466        status: &crate::supervise::ModuleStatus,
2467    ) -> Result<Frame, RouterError> {
2468        self.counters.increment_route_open_refused(code);
2469        info!(
2470            target: "control",
2471            code,
2472            reason = "supervised_not_registered",
2473            module_id = ?module_id,
2474            connection_id = ctx.connection_id.get(),
2475            state = %status.state,
2476            enabled = status.enabled,
2477            live = status.live,
2478            "route.open refused"
2479        );
2480        control_error_frame(
2481            frame,
2482            code,
2483            format!(
2484                "module_id '{module_id}' is supervised but not available (state={}, enabled={}, live={})",
2485                status.state, status.enabled, status.live
2486            ),
2487        )
2488    }
2489
2490    async fn handle_route_open(
2491        &self,
2492        ctx: &RouteCtx,
2493        frame: Frame,
2494        request: RouteOpenRequest,
2495    ) -> Result<Vec<Frame>, RouterError> {
2496        let RouteOpenRequest {
2497            target,
2498            mut identity,
2499            consumer_identity,
2500            consumer_capabilities,
2501            admission_facts,
2502        } = request;
2503        let target_module_id = target_module_id(&target).to_string();
2504        debug!(
2505            connection_id = ctx.connection_id.get(),
2506            corr = frame.header.corr,
2507            module_id = %target_module_id,
2508            "handling route.open"
2509        );
2510
2511        // WHY THESE REPLIES DISCRIMINATE FREELY, since the usual rule is the
2512        // opposite. Below, a caller learns whether a module is unregistered,
2513        // supervised-but-down (with state/enabled/live), or registered without the
2514        // requested role. Elsewhere that is an enumeration leak: a probe learning
2515        // the shape of a fleet it cannot otherwise see.
2516        //
2517        // It is not one here, and the reason is the ACCESS MODEL rather than
2518        // anything about these errors. Reaching route.open requires the
2519        // pre-envelope HMAC handshake, whose key lives in a 0600 user-owned
2520        // connection file, so any caller who completes it already runs as this
2521        // user -- and can read subc.jsonc for the module list and `ck module
2522        // status` for live state. The reply discloses nothing the caller cannot
2523        // read more easily from disk, while the precision is load-bearing:
2524        // `unknown_module` is retryable and a missing role is not.
2525        //
2526        // IF THE HANDSHAKE EVER ADMITS A PRINCIPAL THAT IS NOT THIS USER -- a
2527        // remote transport, a sandboxed caller, a shared-host mode -- THAT
2528        // PREMISE DIES AND THESE THREE REPLIES MUST COLLAPSE INTO ONE.
2529        let Some(registration) = self
2530            .registry
2531            .get_module(&target_module_id)
2532            .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?
2533        else {
2534            if let Some((status, warming)) =
2535                self.supervisor_status(&target_module_id, frame.header.corr)?
2536            {
2537                // BEFORE the two availability codes below, because for a module
2538                // that speaks no subc wire both of them are false comfort: they
2539                // say "not right now" and are retried, and this module will
2540                // never register no matter how long the caller waits. The
2541                // absence here is the declaration being honoured, not a module
2542                // that is late.
2543                if status.protocol == ModuleProtocol::None {
2544                    return Ok(vec![self.route_open_refusal_frame(
2545                        ctx,
2546                        &frame,
2547                        &target_module_id,
2548                        "protocol_none",
2549                        error_codes::MODULE_NO_PROTOCOL,
2550                        format!(
2551                            "module_id '{target_module_id}' is declared protocol: none; \
2552                             it speaks no subc wire and serves no routes"
2553                        ),
2554                    )?]);
2555                }
2556                let code = if warming {
2557                    "module_warming"
2558                } else {
2559                    "target_unavailable"
2560                };
2561                return Ok(vec![self.supervised_absent_route_open_refusal_frame(
2562                    ctx,
2563                    &frame,
2564                    &target_module_id,
2565                    code,
2566                    &status,
2567                )?]);
2568            }
2569            if let Some(removed_ago_ms) =
2570                self.supervisor.removal_tombstone_age_ms(&target_module_id)
2571            {
2572                return Ok(vec![self.route_open_refusal_frame(
2573                    ctx,
2574                    &frame,
2575                    &target_module_id,
2576                    "removed",
2577                    error_codes::MODULE_REMOVED,
2578                    format!("module_id '{target_module_id}' was removed {removed_ago_ms} ms ago"),
2579                )?]);
2580            }
2581            return Ok(vec![self.route_open_refusal_frame(
2582                ctx,
2583                &frame,
2584                &target_module_id,
2585                "not_registered",
2586                error_codes::UNKNOWN_MODULE,
2587                format!("module_id '{target_module_id}' is not registered"),
2588            )?]);
2589        };
2590
2591        // Best-effort only: registry readiness and forwarding reservation use
2592        // different locks, so a module can flip readiness between this read and
2593        // the relay. Modules must still tolerate an `on_bind` while not ready.
2594        if !registration.ready {
2595            self.counters
2596                .increment_route_open_refused(ROUTE_OPEN_REFUSED_DECLARED_NOT_READY);
2597            info!(
2598                target: "control",
2599                code = error_codes::MODULE_WARMING,
2600                module_id = ?target_module_id,
2601                connection_id = ctx.connection_id.get(),
2602                reason = "declared_not_ready",
2603                "route.open refused"
2604            );
2605            return Ok(vec![control_error_body_frame(
2606                &frame,
2607                ErrorBody {
2608                    code: error_codes::MODULE_WARMING.to_string(),
2609                    message: format!(
2610                        "module_id '{target_module_id}' is registered and has declared itself not ready; retry"
2611                    ),
2612                    detail: Some(serde_json::json!({
2613                        "reason": "declared_not_ready"
2614                    })),
2615                },
2616            )?]);
2617        }
2618
2619        // Effective readiness, second half: a module that declares a capability
2620        // `need: required` is not routable while that capability has no
2621        // registered provider. It is enforced HERE, as a retryable routing
2622        // refusal, and deliberately not as spawn ordering or a boot block. The
2623        // module is still started and registered and can make its own calls;
2624        // spawn ordering is a promise that cannot be kept once a provider
2625        // crashes at runtime, and refusing to boot would stop the whole
2626        // machine, including the tools needed to fix its configuration.
2627        //
2628        // "Provided" is the evaluator's verdict, which counts a provider as
2629        // soon as it has REGISTERED, not once it is ready. Two modules that
2630        // require each other's capabilities are therefore both routable once
2631        // both register; counting readiness instead would deadlock them.
2632        //
2633        // Only new opens are refused. Routes already bound when a provider
2634        // goes away stay bound: nothing here tears them down, and the module
2635        // answers them as it can. Like the readiness read above this is
2636        // best-effort against a provider registering or leaving concurrently.
2637        if let Some(capability) = self.first_unprovided_required_capability(&registration) {
2638            self.counters
2639                .increment_route_open_refused(ROUTE_OPEN_REFUSED_REQUIRED_CAPABILITY_UNPROVIDED);
2640            info!(
2641                target: "control",
2642                code = error_codes::MODULE_WARMING,
2643                module_id = ?target_module_id,
2644                connection_id = ctx.connection_id.get(),
2645                reason = NotReadyReason::REQUIRED_CAPABILITY_UNPROVIDED,
2646                capability = %capability,
2647                "route.open refused"
2648            );
2649            return Ok(vec![control_error_body_frame(
2650                &frame,
2651                ErrorBody {
2652                    code: error_codes::MODULE_WARMING.to_string(),
2653                    message: format!(
2654                        "module_id '{target_module_id}' requires capability '{capability}', \
2655                         which no registered module provides; retry"
2656                    ),
2657                    detail: Some(serde_json::json!({
2658                        "reason": NotReadyReason::REQUIRED_CAPABILITY_UNPROVIDED,
2659                        "capability": capability,
2660                    })),
2661                },
2662            )?]);
2663        }
2664
2665        if !target_has_required_role(&target, &registration.manifest.provides) {
2666            return Ok(vec![self.route_open_refusal_frame(
2667                ctx,
2668                &frame,
2669                &target_module_id,
2670                "role_not_provided",
2671                "target_unavailable",
2672                format!("module_id '{target_module_id}' does not provide the requested target"),
2673            )?]);
2674        }
2675
2676        if registration.state != ChannelState::Active {
2677            return Ok(vec![self.route_open_refusal_frame(
2678                ctx,
2679                &frame,
2680                &target_module_id,
2681                "registration_not_active",
2682                "target_unavailable",
2683                format!("module_id '{target_module_id}' is not active"),
2684            )?]);
2685        }
2686
2687        if self
2688            .forwarding
2689            .module_is_draining(&target_module_id)
2690            .map_err(RouterError::Forwarding)?
2691        {
2692            return Ok(vec![self.route_open_refusal_frame(
2693                ctx,
2694                &frame,
2695                &target_module_id,
2696                "reloading",
2697                "module_reloading",
2698                format!("module_id '{target_module_id}' is reloading"),
2699            )?]);
2700        }
2701
2702        if self
2703            .process_liveness
2704            .as_ref()
2705            .and_then(|process_liveness| process_liveness.process_live(&target_module_id))
2706            == Some(false)
2707        {
2708            return Ok(vec![self.route_open_refusal_frame(
2709                ctx,
2710                &frame,
2711                &target_module_id,
2712                "supervisor_not_live",
2713                "target_unavailable",
2714                format!("module_id '{target_module_id}' is not live"),
2715            )?]);
2716        }
2717
2718        if !self
2719            .forwarding
2720            .has_live_module_connection(&target_module_id)
2721            .map_err(RouterError::Forwarding)?
2722        {
2723            return Ok(vec![self.route_open_refusal_frame(
2724                ctx,
2725                &frame,
2726                &target_module_id,
2727                "no_forwarding_connection",
2728                "target_unavailable",
2729                format!("module_id '{target_module_id}' has no live forwarding connection"),
2730            )?]);
2731        }
2732
2733        if let Some(error) =
2734            self.guard_module_control_op(&frame, &target_module_id, "route.bind")?
2735        {
2736            self.observe_route_open_refusal(
2737                ctx,
2738                &target_module_id,
2739                "op_not_allowed",
2740                "op_not_allowed",
2741            );
2742            return Ok(vec![error]);
2743        }
2744
2745        let principal = match self.route_open_principal(&frame, consumer_identity)? {
2746            Ok(principal) => principal,
2747            Err(error) => {
2748                self.observe_route_open_refusal(
2749                    ctx,
2750                    &target_module_id,
2751                    "bad_consumer_identity",
2752                    "bad_consumer_identity",
2753                );
2754                return Ok(vec![error]);
2755            }
2756        };
2757
2758        // This is attested, control-plane policy for supervised module origins.
2759        // Keep it before route reservation and out of the opaque forwarding hot
2760        // path: data frames must never acquire a per-frame capability check.
2761        if let Principal::Reserved {
2762            module_id: opening_module_id,
2763        } = &principal
2764        {
2765            if let Some(opening_registration) = self
2766                .registry
2767                .get_module(opening_module_id)
2768                .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?
2769            {
2770                if let Some(capability) =
2771                    denied_capability(&opening_registration.manifest, &registration.manifest)
2772                {
2773                    warn!(
2774                        opening_module_id,
2775                        target_module_id,
2776                        capability,
2777                        "refusing route.open because an attested capability deny edge matches"
2778                    );
2779                    return Ok(vec![self.route_open_refusal_frame(
2780                        ctx,
2781                        &frame,
2782                        &target_module_id,
2783                        "capability_deny_edge",
2784                        "capability_forbidden",
2785                        format!(
2786                            "module_id '{opening_module_id}' must never reach capability '{capability}' provided by '{target_module_id}'"
2787                        ),
2788                    )?]);
2789                }
2790            }
2791        }
2792
2793        if admission_facts.is_some() {
2794            let carrier_matches = matches!(
2795                &principal,
2796                Principal::Reserved { module_id }
2797                    if self.admission_facts_carrier_module_id.as_deref() == Some(module_id)
2798            );
2799            if !carrier_matches {
2800                return Ok(vec![self.route_open_refusal_frame(
2801                    ctx,
2802                    &frame,
2803                    &target_module_id,
2804                    "admission_facts_carrier_not_permitted",
2805                    "admission_facts_not_permitted",
2806                    "admission facts may only be carried by the configured reserved module",
2807                )?]);
2808            }
2809
2810            let target_allowed = self
2811                .admission_facts_targets
2812                .as_ref()
2813                .is_some_and(|targets| targets.iter().any(|id| id == &target_module_id));
2814            if !target_allowed {
2815                return Ok(vec![self.route_open_refusal_frame(
2816                    ctx,
2817                    &frame,
2818                    &target_module_id,
2819                    "admission_facts_target_not_listed",
2820                    "admission_facts_target_not_allowed",
2821                    format!(
2822                        "admission facts are not permitted for target module_id '{target_module_id}'"
2823                    ),
2824                )?]);
2825            }
2826
2827            // Keep the value opaque to subc. The downstream admission validator owns
2828            // schema and semantic checks; this daemon only enforces carrier authority
2829            // and the configured destination allowlist.
2830        }
2831
2832        // Bind admits a root that no longer exists on disk, because refusing here
2833        // closes the only exit from a paused run: cancel needs a bound route, and a
2834        // renamed or reclaimed directory makes that route unopenable forever. The
2835        // run itself is intact and still addressable by its recorded identity.
2836        //
2837        // This does NOT relax the rule the strict constructor protects. That rule is
2838        // that no root is ever aliased into NEW durable state -- a missing component
2839        // can reappear as a symlink elsewhere, which would move the identity and
2840        // split a session's history across two of them. The engine now refuses the
2841        // two operations that create such state (send and import) at admission,
2842        // which is a narrower way to hold the same invariant: reads and terminations
2843        // are admitted, writes are not. That refusal had to ship before this line
2844        // changed, or there is an interval where a send commits under a provisional
2845        // identity -- the exact failure the original policy existed to prevent.
2846        //
2847        // Resolution follows realpath rather than lexical cleanup: the longest
2848        // existing ancestor is canonicalized and the missing tail re-appended, so a
2849        // live root is unchanged and a vanished leaf keeps the identity it was
2850        // admitted under. Lexical cleanup would mint a DIFFERENT identity for the
2851        // same caller the moment the directory vanished, which strands the run more
2852        // quietly than refusing it.
2853        let project_root = match ProjectRootId::from_path_allowing_missing(&identity.project_root) {
2854            Ok(project_root) => project_root,
2855            Err(err) => {
2856                return Ok(vec![control_error_frame(
2857                    &frame,
2858                    "invalid_project_root",
2859                    err.to_string(),
2860                )?])
2861            }
2862        };
2863        identity.project_root = project_root.as_path().to_path_buf();
2864
2865        // Last gate before any relay work, and deliberately after the cheap
2866        // registry and availability checks above: those name a more precise
2867        // condition (unknown, removed, reloading) and a caller is better served
2868        // by the precise code than by this one.
2869        //
2870        // Everything below this point costs an egress permit, a reserved handle
2871        // pair and, if the module does not answer, the whole relay budget. The
2872        // reader no longer waits for that budget, so cap each target explicitly;
2873        // serial dispatch used to provide the accidental cap of one relay per
2874        // connection. Admission is a mutex-protected count and never waits.
2875        let _concurrency_guard = match self
2876            .route_bind_concurrency
2877            .try_admit(&target_module_id, MAX_PENDING_ROUTE_BINDS_PER_TARGET)
2878        {
2879            Ok(guard) => guard,
2880            Err(in_flight) => {
2881                return Ok(vec![self.route_open_admission_refusal_frame(
2882                    ctx,
2883                    &frame,
2884                    &target_module_id,
2885                    format!(
2886                        "module_id '{target_module_id}' already has {in_flight} route.bind relays in flight; retry after one settles"
2887                    ),
2888                )?]);
2889            }
2890        };
2891
2892        // A module that has already burned the whole budget `threshold` times
2893        // in a row does not get to charge it again until a probe says it recovered.
2894        let mut breaker = match self.route_bind_breakers.admit(&target_module_id) {
2895            RouteBindAdmission::Admitted { guard, probe } => {
2896                if probe {
2897                    info!(
2898                        module_id = %target_module_id,
2899                        connection_id = ctx.connection_id.get(),
2900                        "route.bind breaker half-open: admitting one probe"
2901                    );
2902                }
2903                guard
2904            }
2905            RouteBindAdmission::Refused {
2906                consecutive_timeouts,
2907                retry_in,
2908                probe_in_flight,
2909            } => {
2910                return Ok(vec![self.route_open_breaker_refusal_frame(
2911                    ctx,
2912                    &frame,
2913                    &target_module_id,
2914                    consecutive_timeouts,
2915                    retry_in,
2916                    probe_in_flight,
2917                )?]);
2918            }
2919        };
2920
2921        // Resolve the per-module budget here so the wait matches the operator's
2922        // intent for this specific target. A per-module override in
2923        // `subc.jsonc` (or `with_route_bind_relay_timeouts` for embedded
2924        // daemons) wins over the daemon-wide default.
2925        let route_bind_relay_timeout = self.route_bind_relay_timeout_for(&target_module_id);
2926        let relay_deadline = Instant::now() + route_bind_relay_timeout;
2927        let pending = match self
2928            .forwarding
2929            .begin_route_bind_relay_for(
2930                ctx.connection_id,
2931                ctx.egress.clone(),
2932                response_version(&frame),
2933                frame.header.corr,
2934                &target_module_id,
2935                principal.clone(),
2936                Some(project_root),
2937                relay_deadline,
2938            )
2939            .await
2940        {
2941            Ok(pending) => pending,
2942            Err(err) => {
2943                return Ok(vec![self.route_open_refusal_frame(
2944                    ctx,
2945                    &frame,
2946                    &target_module_id,
2947                    "relay_reservation_failed",
2948                    forwarding_error_code(&err),
2949                    err.to_string(),
2950                )?])
2951            }
2952        };
2953        let crate::forwarding::PendingRouteBindRelay {
2954            endpoint,
2955            module_sink,
2956            negotiated_ver,
2957            client_channel,
2958            client_epoch,
2959            module_channel,
2960            module_epoch,
2961            corr: relay_corr,
2962            receiver,
2963        } = pending;
2964        let mut reservation =
2965            RouteBindReservationGuard::new(Arc::clone(&self.forwarding), endpoint, relay_corr);
2966
2967        debug!(
2968            connection_id = ctx.connection_id.get(),
2969            client_channel,
2970            client_epoch,
2971            module_channel,
2972            module_epoch,
2973            "reserved route handle pair"
2974        );
2975        // Rendered BEFORE the move into the relay, because the accept arm below
2976        // is where it is logged and the principal is gone by then.
2977        let principal_label = match &principal {
2978            Principal::Reserved { module_id } => format!("reserved:{module_id}"),
2979            Principal::Direct => "direct".to_string(),
2980            other => format!("{other:?}"),
2981        };
2982        let relay = ModuleControlRequest::RouteBind {
2983            route_channel: module_channel,
2984            epoch: module_epoch,
2985            target,
2986            identity,
2987            principal: Some(principal),
2988            consumer_capabilities,
2989            admission_facts,
2990        };
2991        let relay_body = serde_json::to_vec(&relay).map_err(|err| {
2992            RouterError::backend(
2993                0,
2994                frame.header.corr,
2995                format!("failed to encode route.bind request: {err}"),
2996            )
2997        })?;
2998        let relay_frame = Frame::build_with_version(
2999            negotiated_ver,
3000            FrameType::Request,
3001            control_flags(),
3002            0,
3003            0,
3004            relay_corr,
3005            relay_body,
3006        )
3007        .map_err(RouterError::FrameBuild)?;
3008
3009        if let Err(err) = module_sink.send(relay_frame).await {
3010            reservation.release_and_disarm();
3011            return Ok(vec![self.route_open_refusal_frame(
3012                ctx,
3013                &frame,
3014                &target_module_id,
3015                "relay_send_failed",
3016                "target_unavailable",
3017                err.to_string(),
3018            )?]);
3019        }
3020
3021        if !self
3022            .forwarding
3023            .mark_route_bind_relay_enqueued(endpoint, relay_corr)
3024            .map_err(RouterError::Forwarding)?
3025        {
3026            self.send_abandoned_route_bind_goodbye(
3027                &module_sink,
3028                negotiated_ver,
3029                module_channel,
3030                module_epoch,
3031            );
3032        }
3033
3034        match timeout_at(relay_deadline, receiver).await {
3035            Ok(Ok(RouteBindRelayOutcome::Accepted)) => {
3036                reservation.disarm();
3037                if breaker.record_accepted() {
3038                    info!(
3039                        module_id = %target_module_id,
3040                        "route.bind breaker closed: the probe was accepted"
3041                    );
3042                }
3043                self.observe_route_open_accept(ctx, &target_module_id, &principal_label);
3044                Ok(Vec::new())
3045            }
3046            Ok(Ok(RouteBindRelayOutcome::Rejected(body))) => {
3047                reservation.release_and_disarm();
3048                // A module that says no in microseconds is healthy. Rejection
3049                // is a different condition with its own refusal and must not
3050                // move the breaker.
3051                breaker.record_inconclusive();
3052                self.counters
3053                    .increment_route_open_refused("module_rejected");
3054                info!(
3055                    target: "control",
3056                    code = "module_rejected",
3057                    module_code = ?body.code,
3058                    module_id = ?target_module_id,
3059                    connection_id = ctx.connection_id.get(),
3060                    "route.open refused"
3061                );
3062                Ok(vec![control_error_body_frame(&frame, body)?])
3063            }
3064            Ok(Ok(RouteBindRelayOutcome::ModuleGone(message))) => {
3065                reservation.release_and_disarm();
3066                breaker.record_inconclusive();
3067                // Fires when the module's connection closes while a relayed
3068                // bind is pending -- typically a caller racing a module restart
3069                // whose bind was relayed BEFORE the drain mark went up. Logged
3070                // because the caller sees only its own error and the fleet has
3071                // already spent one diagnosis round unable to tell this arm
3072                // from a relay timeout without daemon-side evidence.
3073                tracing::warn!(
3074                    module_id = %target_module_id,
3075                    "route.bind relay abandoned: {message}"
3076                );
3077                Ok(vec![self.route_open_refusal_frame(
3078                    ctx,
3079                    &frame,
3080                    &target_module_id,
3081                    "relay_abandoned",
3082                    "target_unavailable",
3083                    message,
3084                )?])
3085            }
3086            Ok(Err(_)) => {
3087                reservation.release_and_disarm();
3088                breaker.record_inconclusive();
3089                Ok(vec![self.route_open_refusal_frame(
3090                    ctx,
3091                    &frame,
3092                    &target_module_id,
3093                    "relay_waiter_canceled",
3094                    "target_unavailable",
3095                    "route.bind relay waiter was canceled before the module responded",
3096                )?])
3097            }
3098            Err(_) => {
3099                reservation.release_and_disarm();
3100                // THE ONLY ARM THAT MOVES THE BREAKER. Budget exhausted with no
3101                // answer at all is the one condition a fast refusal can
3102                // usefully stand in for; every other arm already answered.
3103                if let Some(opened) = breaker.record_timeout(
3104                    self.route_bind_breaker_threshold,
3105                    self.route_bind_breaker_cooldown,
3106                ) {
3107                    warn!(
3108                        module_id = %target_module_id,
3109                        consecutive_timeouts = opened.consecutive_timeouts,
3110                        cooldown_ms = self.route_bind_breaker_cooldown.as_millis() as u64,
3111                        reopened_after_probe = opened.reopened_after_probe,
3112                        "route.bind breaker open: refusing route.open for this module without relaying until one probe says it recovered"
3113                    );
3114                }
3115                // The generous budget just burned to no answer: the module is
3116                // registered and its connection is up, but its bind handler sat
3117                // on the ack for the full budget (warm-on-bind, cold configure,
3118                // or a wedged handler). Every earlier unavailability shape
3119                // fast-refuses BEFORE the relay, so this arm firing means the
3120                // slowness is module-side -- log it so the per-module timeline
3121                // is reconstructable without client audit rows.
3122                tracing::warn!(
3123                    module_id = %target_module_id,
3124                    timeout_ms = route_bind_relay_timeout.as_millis() as u64,
3125                    "route.bind relay timed out: module did not ack within budget"
3126                );
3127                Ok(vec![self.route_open_refusal_frame(
3128                    ctx,
3129                    &frame,
3130                    &target_module_id,
3131                    "relay_timed_out",
3132                    "module_timeout",
3133                    format!(
3134                        "module_id '{target_module_id}' did not answer route.bind within {:?}",
3135                        route_bind_relay_timeout
3136                    ),
3137                )?])
3138            }
3139        }
3140    }
3141
3142    fn handle_supervisor_spawn_snapshot(&self, frame: Frame) -> Result<Vec<Frame>, RouterError> {
3143        let response = ClientControlResponse::SupervisorSpawnSnapshot {
3144            snapshot: self.supervisor.spawn_snapshot(),
3145        };
3146        Ok(vec![control_response_body_frame(
3147            &frame,
3148            &response,
3149            "ClientControlResponse::SupervisorSpawnSnapshot",
3150        )?])
3151    }
3152
3153    fn handle_supervisor_spawn_subscribe(
3154        &self,
3155        ctx: &RouteCtx,
3156        frame: Frame,
3157        since: Option<SpawnCursor>,
3158    ) -> Result<Vec<Frame>, RouterError> {
3159        match self.supervisor.subscribe_spawns(
3160            ctx.connection_id,
3161            frame.header.corr,
3162            response_version(&frame),
3163            since,
3164            ctx.egress.clone(),
3165        ) {
3166            Ok(()) => Ok(Vec::new()),
3167            Err(SpawnSubscribeRefusal::ForeignIncarnation { current }) => {
3168                Ok(vec![control_error_body_frame(
3169                    &frame,
3170                    ErrorBody {
3171                        code: "spawn_cursor_incarnation_mismatch".to_string(),
3172                        message: "spawn cursor belongs to a different daemon incarnation"
3173                            .to_string(),
3174                        detail: Some(serde_json::json!({
3175                            "current_daemon_incarnation": current
3176                        })),
3177                    },
3178                )?])
3179            }
3180            Err(SpawnSubscribeRefusal::TooOld { oldest }) => Ok(vec![control_error_body_frame(
3181                &frame,
3182                ErrorBody {
3183                    code: "spawn_cursor_too_old".to_string(),
3184                    message: "spawn cursor predates the retained event ring".to_string(),
3185                    detail: Some(serde_json::json!({
3186                        "oldest_retained_cursor": oldest
3187                    })),
3188                },
3189            )?]),
3190            Err(SpawnSubscribeRefusal::Frame(error)) => Err(RouterError::backend(
3191                0,
3192                frame.header.corr,
3193                format!("failed to open supervisor spawn subscription: {error}"),
3194            )),
3195        }
3196    }
3197
3198    fn handle_supervisor_list(&self, frame: Frame) -> Result<Vec<Frame>, RouterError> {
3199        let generation = self
3200            .registry
3201            .generation()
3202            .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?;
3203        let modules = self
3204            .supervisor
3205            .list()
3206            .into_iter()
3207            .map(|module| {
3208                let status = module.status_for_control("list").map_err(|err| {
3209                    RouterError::backend(
3210                        0,
3211                        frame.header.corr,
3212                        format!("failed to read supervisor status: {err}"),
3213                    )
3214                })?;
3215                Ok(SupervisorEntry {
3216                    module_id: status.module_id,
3217                    state: status.state.to_string(),
3218                    enabled: status.enabled,
3219                    live: status.live,
3220                    protocol: status.protocol,
3221                    health: status.health.status,
3222                    last_probe_ms: status.health.last_probe_ms,
3223                    last_exit_code: status.last_exit.as_ref().and_then(|e| e.code),
3224                    last_exit_signal: status.last_exit.as_ref().and_then(|e| e.signal),
3225                    last_exit_ms: status.last_exit.as_ref().map(|e| e.at_ms),
3226                    last_exit_kind: status.last_exit.as_ref().map(|e| e.kind.into()),
3227                    restart_count: Some(status.restart_count),
3228                    max_restarts: Some(status.max_restarts),
3229                    lifetime_restarts: Some(status.lifetime_restarts),
3230                    spawn_generation: Some(status.spawn_generation),
3231                    restart_window_secs: Some(status.restart_window.as_secs()),
3232                    drain_timeout_ms: Some(status.drain_timeout.as_millis() as u64),
3233                    restart_backoff_ms: Some(status.restart_backoff.as_millis() as u64),
3234                    restart_max_backoff_ms: Some(status.restart_max_backoff.as_millis() as u64),
3235                })
3236            })
3237            .collect::<Result<Vec<_>, RouterError>>()?;
3238        let response = ClientControlResponse::SupervisorList {
3239            generation,
3240            modules,
3241        };
3242        Ok(vec![control_response_body_frame(
3243            &frame,
3244            &response,
3245            "ClientControlResponse::SupervisorList",
3246        )?])
3247    }
3248
3249    fn handle_supervisor_stderr_tail(
3250        &self,
3251        frame: Frame,
3252        module_id: String,
3253        max_lines: Option<u32>,
3254        max_bytes: Option<u32>,
3255    ) -> Result<Vec<Frame>, RouterError> {
3256        let Some(module) = self.supervisor.get(&module_id) else {
3257            return Ok(vec![control_error_frame(
3258                &frame,
3259                "unknown_module",
3260                format!("module_id '{module_id}' is not supervised"),
3261            )?]);
3262        };
3263
3264        let snapshot = module.stderr_tail(
3265            max_lines.map(|value| value as usize),
3266            max_bytes.map(|value| value as usize),
3267        );
3268
3269        let response = ClientControlResponse::SupervisorStderrTail {
3270            module_id,
3271            tail: StderrTail {
3272                capture: match snapshot.capture {
3273                    CaptureState::Captured => StderrCaptureState::Captured,
3274                    CaptureState::Incomplete { reason } => {
3275                        StderrCaptureState::Incomplete { reason }
3276                    }
3277                    CaptureState::NotCaptured { reason } => {
3278                        StderrCaptureState::NotCaptured { reason }
3279                    }
3280                },
3281                entries: snapshot
3282                    .entries
3283                    .into_iter()
3284                    .map(|entry| match entry {
3285                        TailEntry::Line { text, truncated } => {
3286                            StderrTailEntry::Line { text, truncated }
3287                        }
3288                        TailEntry::ProcessStart => StderrTailEntry::ProcessStart,
3289                    })
3290                    .collect(),
3291                dropped_lines: snapshot.dropped_lines,
3292            },
3293        };
3294        Ok(vec![control_response_body_frame(
3295            &frame,
3296            &response,
3297            "ClientControlResponse::SupervisorStderrTail",
3298        )?])
3299    }
3300
3301    async fn handle_supervisor_terminals(
3302        &self,
3303        frame: Frame,
3304        module_id: String,
3305    ) -> Result<Vec<Frame>, RouterError> {
3306        let Some(module) = self.supervisor.get(&module_id) else {
3307            return Ok(vec![control_error_frame(
3308                &frame,
3309                "unknown_module",
3310                format!("module_id '{module_id}' is not supervised"),
3311            )?]);
3312        };
3313
3314        // The journal read runs on a blocking thread: it can be megabytes of
3315        // file I/O and must not occupy a runtime worker.
3316        let terminals = module
3317            .read_durable_terminal_history()
3318            .await
3319            .map_err(|error| {
3320                RouterError::backend(
3321                    0,
3322                    frame.header.corr,
3323                    format!("failed to read terminal history: {error}"),
3324                )
3325            })?;
3326        let response = ClientControlResponse::SupervisorTerminals {
3327            module_id,
3328            terminals,
3329        };
3330        Ok(vec![control_response_body_frame(
3331            &frame,
3332            &response,
3333            "ClientControlResponse::SupervisorTerminals",
3334        )?])
3335    }
3336
3337    fn handle_supervisor_routes(
3338        &self,
3339        frame: Frame,
3340        module_id: Option<String>,
3341    ) -> Result<Vec<Frame>, RouterError> {
3342        let modules = self
3343            .forwarding
3344            .route_census(module_id.as_deref())
3345            .map_err(RouterError::Forwarding)?
3346            .into_iter()
3347            .map(|(module_id, routes)| SupervisorRouteModule {
3348                module_id,
3349                routes: routes
3350                    .into_iter()
3351                    .map(|route| SupervisorRoute {
3352                        consumer: match route.principal {
3353                            Principal::Reserved { module_id } => {
3354                                SupervisorRouteConsumer::Reserved { module_id }
3355                            }
3356                            Principal::Direct | Principal::Unverified => {
3357                                SupervisorRouteConsumer::Direct {
3358                                    connection_id: route.goodbye_target.connection_id.get(),
3359                                }
3360                            }
3361                        },
3362                        age_ms: Instant::now()
3363                            .saturating_duration_since(route.bound_at)
3364                            .as_millis()
3365                            .try_into()
3366                            .unwrap_or(u64::MAX),
3367                        draining: route.draining,
3368                        drain_reason: route.drain_reason,
3369                    })
3370                    .collect(),
3371            })
3372            .collect();
3373        let response = ClientControlResponse::SupervisorRoutes { modules };
3374        Ok(vec![control_response_body_frame(
3375            &frame,
3376            &response,
3377            "ClientControlResponse::SupervisorRoutes",
3378        )?])
3379    }
3380
3381    async fn handle_supervisor_provenance(
3382        &self,
3383        frame: Frame,
3384        module_id: Option<String>,
3385    ) -> Result<Vec<Frame>, RouterError> {
3386        let mut selected = if let Some(module_id) = module_id {
3387            let Some(module) = self.supervisor.get(&module_id) else {
3388                return Ok(vec![control_error_frame(
3389                    &frame,
3390                    "unknown_module",
3391                    format!("module_id '{module_id}' is not supervised"),
3392                )?]);
3393            };
3394            vec![module]
3395        } else {
3396            self.supervisor.list()
3397        };
3398
3399        let mut modules = Vec::with_capacity(selected.len());
3400        for module in selected.drain(..) {
3401            let status = module.status().map_err(|err| {
3402                RouterError::backend(
3403                    0,
3404                    frame.header.corr,
3405                    format!("failed to read supervisor status: {err}"),
3406                )
3407            })?;
3408            let module_declared = self
3409                .registry
3410                .get_module(&status.module_id)
3411                .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?
3412                .and_then(|registration| registration.manifest.provenance)
3413                .map(|build| ModuleDeclaredProvenance::Reported { build })
3414                .unwrap_or(ModuleDeclaredProvenance::Unverifiable);
3415            #[cfg(test)]
3416            let running_image = match &self.provenance_probe_override {
3417                Some(result) => result.clone(),
3418                None => module.running_image_agreement().await,
3419            };
3420            #[cfg(not(test))]
3421            let running_image = module.running_image_agreement().await;
3422            modules.push(SupervisorModuleProvenance {
3423                module_id: status.module_id,
3424                module_declared,
3425                daemon_observed: SupervisorObservedProcess {
3426                    pid: status.pid,
3427                    spawned_at_ms: status.spawned_at_ms,
3428                    spawned_from: status.spawned_from,
3429                    running_image,
3430                },
3431            });
3432        }
3433        let daemon = SupervisorDaemonProvenance {
3434            daemon_build: self.daemon_provenance.build.clone(),
3435            daemon_observed: DaemonObservedProcess {
3436                pid: self.daemon_provenance.pid,
3437                started_at_ms: self
3438                    .daemon_provenance
3439                    .start_clock
3440                    .map(|clock| clock.started_at_ms())
3441                    .or(self.daemon_provenance.started_at_ms),
3442                running_image: self
3443                    .daemon_provenance
3444                    .probe
3445                    .observe(
3446                        self.daemon_provenance.pid,
3447                        self.daemon_provenance.executable_path.as_deref(),
3448                        self.daemon_provenance.executable_identity,
3449                        self.daemon_provenance.process_start_time,
3450                    )
3451                    .await,
3452            },
3453        };
3454        let response = ClientControlResponse::SupervisorProvenance { daemon, modules };
3455        Ok(vec![control_response_body_frame(
3456            &frame,
3457            &response,
3458            "ClientControlResponse::SupervisorProvenance",
3459        )?])
3460    }
3461
3462    fn handle_supervisor_health(&self, frame: Frame) -> Result<Vec<Frame>, RouterError> {
3463        self.refresh_capability_requirements();
3464        let generation = self
3465            .registry
3466            .generation()
3467            .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?;
3468        let modules = self
3469            .supervisor
3470            .list()
3471            .into_iter()
3472            .map(|module| {
3473                let status = module.status_for_control("health").map_err(|err| {
3474                    RouterError::backend(
3475                        0,
3476                        frame.header.corr,
3477                        format!("failed to read supervisor health: {err}"),
3478                    )
3479                })?;
3480                let module_id = status.module_id;
3481                let capability_detail = self
3482                    .capability_evaluator
3483                    .required_problem_detail(&module_id);
3484                Ok(SupervisorHealthEntry {
3485                    module_id,
3486                    status: status.health.status,
3487                    detail: append_capability_problem_detail(
3488                        status.health.detail,
3489                        capability_detail,
3490                    ),
3491                    metrics: status.health.metrics,
3492                    consecutive_failures: status.health.consecutive_failures,
3493                    late_answer_count: status.health.late_answer_count,
3494                    last_late_answer_latency_ms: status.health.last_late_answer_latency_ms,
3495                    last_action: status.health.last_action,
3496                    last_action_ms: status.health.last_action_ms,
3497                    last_probe_ms: status.health.last_probe_ms,
3498                })
3499            })
3500            .collect::<Result<Vec<_>, RouterError>>()?;
3501        let response = ClientControlResponse::SupervisorHealth {
3502            generation,
3503            modules,
3504        };
3505        Ok(vec![control_response_body_frame(
3506            &frame,
3507            &response,
3508            "ClientControlResponse::SupervisorHealth",
3509        )?])
3510    }
3511
3512    async fn handle_supervisor_restart(
3513        &self,
3514        frame: Frame,
3515        module_id: String,
3516        drain_timeout_ms: Option<u64>,
3517    ) -> Result<Vec<Frame>, RouterError> {
3518        let operation_lock = self.supervisor.operation_lock();
3519        let _operation_guard = operation_lock.lock().await;
3520        let Some(module) = self.supervisor.get(&module_id) else {
3521            return Ok(vec![control_error_frame(
3522                &frame,
3523                "unknown_module",
3524                format!("module_id '{module_id}' is not supervised"),
3525            )?]);
3526        };
3527
3528        if let Err(err) = module.restart(drain_timeout_ms).await {
3529            let (code, message) = match err {
3530                crate::supervise::SuperviseError::Disabled { .. } => {
3531                    ("module_disabled", err.to_string())
3532                }
3533                crate::supervise::SuperviseError::SwapInProgress { .. } => {
3534                    ("swap_in_progress", err.to_string())
3535                }
3536                _ => (
3537                    "target_unavailable",
3538                    format!("failed to restart module_id '{module_id}': {err}"),
3539                ),
3540            };
3541            return Ok(vec![control_error_frame(&frame, code, message)?]);
3542        }
3543
3544        let response = ClientControlResponse::SupervisorAck {
3545            module_id,
3546            applied: true,
3547        };
3548        Ok(vec![control_response_body_frame(
3549            &frame,
3550            &response,
3551            "ClientControlResponse::SupervisorAck",
3552        )?])
3553    }
3554
3555    /// `supervisor.swap`. Answered when the swap has cut over or failed, not
3556    /// when the old process has finished draining: a caller whose own lane
3557    /// rides the old process must get its reply before that drain waits on it.
3558    async fn handle_supervisor_swap(
3559        &self,
3560        frame: Frame,
3561        module_id: String,
3562        ready_timeout_ms: Option<u64>,
3563    ) -> Result<Vec<Frame>, RouterError> {
3564        // The daemon-wide operation lock is held only to resolve the handle,
3565        // not across the swap. The swap can take its whole readiness budget,
3566        // and `supervisor.set_enabled` (ck module stop) takes the same lock:
3567        // holding it here would park an operator's stop behind the swap it is
3568        // meant to abort. A rescan or stop that reaches the module during the
3569        // swap is served by the swap itself (see `supervise_swap`).
3570        let module = {
3571            let operation_lock = self.supervisor.operation_lock();
3572            let _operation_guard = operation_lock.lock().await;
3573            self.supervisor.get(&module_id)
3574        };
3575        let Some(module) = module else {
3576            return Ok(vec![control_error_frame(
3577                &frame,
3578                "unknown_module",
3579                format!("module_id '{module_id}' is not supervised"),
3580            )?]);
3581        };
3582
3583        if let Err(err) = module
3584            .swap(ready_timeout_ms.map(Duration::from_millis))
3585            .await
3586        {
3587            use crate::supervise::SuperviseError;
3588            let message = err.to_string();
3589            let error = match err {
3590                SuperviseError::Disabled { .. } => ErrorBody::new("module_disabled", message),
3591                SuperviseError::SwapRefused { reason, .. } => ErrorBody {
3592                    code: "swap_refused".to_string(),
3593                    message,
3594                    detail: Some(serde_json::json!({ "reason": reason.as_str() })),
3595                },
3596                SuperviseError::SwapFailed {
3597                    arm,
3598                    candidate_exit,
3599                    ..
3600                } => ErrorBody {
3601                    code: "swap_failed".to_string(),
3602                    message,
3603                    detail: Some(serde_json::json!({
3604                        "arm": arm.as_str(),
3605                        "candidate_exit_code": candidate_exit.as_ref().and_then(|exit| exit.code),
3606                        "candidate_exit_signal": candidate_exit.as_ref().and_then(|exit| exit.signal),
3607                    })),
3608                },
3609                _ => ErrorBody::new(
3610                    "target_unavailable",
3611                    format!("failed to swap module_id '{module_id}': {message}"),
3612                ),
3613            };
3614            return Ok(vec![control_error_body_frame(&frame, error)?]);
3615        }
3616
3617        let response = ClientControlResponse::SupervisorAck {
3618            module_id,
3619            applied: true,
3620        };
3621        Ok(vec![control_response_body_frame(
3622            &frame,
3623            &response,
3624            "ClientControlResponse::SupervisorAck",
3625        )?])
3626    }
3627
3628    async fn handle_supervisor_reload(
3629        &self,
3630        frame: Frame,
3631        module_id: String,
3632    ) -> Result<Vec<Frame>, RouterError> {
3633        let operation_lock = self.supervisor.operation_lock();
3634        let _operation_guard = operation_lock.lock().await;
3635        let Some(module) = self.supervisor.get(&module_id) else {
3636            return Ok(vec![control_error_frame(
3637                &frame,
3638                "unknown_module",
3639                format!("module_id '{module_id}' is not supervised"),
3640            )?]);
3641        };
3642
3643        if let Err(err) = module.reload().await {
3644            let (code, message) = match err {
3645                crate::supervise::SuperviseError::Disabled { .. } => {
3646                    ("module_disabled", err.to_string())
3647                }
3648                crate::supervise::SuperviseError::SwapInProgress { .. } => {
3649                    ("swap_in_progress", err.to_string())
3650                }
3651                _ => (
3652                    "reload_failed",
3653                    format!("failed to reload module_id '{module_id}': {err}"),
3654                ),
3655            };
3656            return Ok(vec![control_error_frame(&frame, code, message)?]);
3657        }
3658
3659        let response = ClientControlResponse::SupervisorAck {
3660            module_id,
3661            applied: true,
3662        };
3663        Ok(vec![control_response_body_frame(
3664            &frame,
3665            &response,
3666            "ClientControlResponse::SupervisorAck",
3667        )?])
3668    }
3669
3670    async fn handle_supervisor_rescan(
3671        &self,
3672        frame: Frame,
3673        preview: bool,
3674    ) -> Result<Vec<Frame>, RouterError> {
3675        let Some(context) = self.rescan.clone() else {
3676            return Ok(vec![control_error_frame(
3677                &frame,
3678                "rescan_unavailable",
3679                "the daemon was not started with a reloadable config path".to_string(),
3680            )?]);
3681        };
3682
3683        let operation_lock = self.supervisor.operation_lock();
3684        let _operation_guard = operation_lock.lock().await;
3685        let loaded = match crate::daemon_config::load(&context.config_path) {
3686            Ok(config) => config,
3687            Err(err) => {
3688                return Ok(vec![control_error_frame(
3689                    &frame,
3690                    "invalid_daemon_config",
3691                    format!("supervisor rescan rejected daemon config: {err}"),
3692                )?])
3693            }
3694        };
3695        // `load` reports a missing file as Ok(None), which is correct at boot
3696        // (no config, nothing to supervise) and catastrophic here: rescan treats
3697        // "not in the config" as "remove it", so an absent file would read as an
3698        // empty module list and retire the entire running fleet. An editor
3699        // writing via write-new-then-rename, or a half-finished edit, is enough
3700        // to open that window. Refuse instead: a config that cannot be read
3701        // carries no instruction to remove anything.
3702        let Some(config) = loaded else {
3703            return Ok(vec![control_error_frame(
3704                &frame,
3705                "invalid_daemon_config",
3706                format!(
3707                    "daemon config not found at {}; refusing to rescan (an absent config would \
3708                     retire every supervised module)",
3709                    context.config_path.display()
3710                ),
3711            )?]);
3712        };
3713        let (
3714            configured_port,
3715            storage_config,
3716            admission_facts_carrier_module_id,
3717            admission_facts_targets,
3718            modules,
3719            reserved_capabilities,
3720        ) = (
3721            config.port,
3722            config.storage,
3723            config.admission_facts_carrier_module_id,
3724            config.admission_facts_targets,
3725            config.modules,
3726            config.reserved_capabilities,
3727        );
3728
3729        // Collect the sections rescan cannot apply, so the REPLY carries them.
3730        //
3731        // The warning below has always been correct and has always gone only to
3732        // the journal -- addressed to whoever reads logs, while the person who
3733        // just edited the config is looking at the CLI. Naming each section
3734        // individually rather than setting a flag: "something outside modules
3735        // changed" sends the operator back to diffing their own file, which is
3736        // the work this is meant to save.
3737        let mut restart_required = Vec::new();
3738        for section in RestartRequiredSection::ALL {
3739            let changed = match section {
3740                RestartRequiredSection::Port => configured_port != context.configured_port,
3741                RestartRequiredSection::Storage => storage_config != context.storage_config,
3742                RestartRequiredSection::AdmissionFactsCarrierModuleId => {
3743                    admission_facts_carrier_module_id != context.admission_facts_carrier_module_id
3744                }
3745                RestartRequiredSection::AdmissionFactsTargets => {
3746                    admission_facts_targets != context.admission_facts_targets
3747                }
3748            };
3749            if changed {
3750                restart_required.push(section.label().to_string());
3751            }
3752        }
3753        if !restart_required.is_empty() {
3754            warn!(
3755                config_path = %context.config_path.display(),
3756                sections = %restart_required.join(", "),
3757                "daemon config changed outside the modules section; restart the daemon to apply those changes"
3758            );
3759        }
3760
3761        for configured in &modules {
3762            if let Err(err) = validate_spec(&configured.module_spec()) {
3763                return Ok(vec![control_error_frame(
3764                    &frame,
3765                    "invalid_daemon_config",
3766                    format!("supervisor rescan rejected daemon config: {err}"),
3767                )?]);
3768            }
3769        }
3770
3771        let configured_capabilities = modules
3772            .iter()
3773            .map(|module| (module.module_id.clone(), module.enabled))
3774            .collect::<Vec<_>>();
3775        let preview_capability_warnings = if preview {
3776            let (_, registrations) = self.runtime_capability_snapshot()?;
3777            let current_modules = self
3778                .supervisor
3779                .list()
3780                .into_iter()
3781                .map(|module| module.module_id().to_string())
3782                .collect::<BTreeSet<_>>();
3783            let resulting_modules = configured_capabilities.clone();
3784            let removed = current_modules
3785                .into_iter()
3786                .filter(|module_id| {
3787                    !resulting_modules
3788                        .iter()
3789                        .any(|(configured_id, _)| configured_id == module_id)
3790                })
3791                .collect::<Vec<_>>();
3792            self.capability_evaluator.preview_removal_warnings(
3793                resulting_modules,
3794                &removed,
3795                &registrations,
3796            )
3797        } else {
3798            Vec::new()
3799        };
3800        let result = match self
3801            .reconcile_supervised_modules(&context.supervisor, modules, preview)
3802            .await
3803        {
3804            Ok(result) => result,
3805            Err(message) => {
3806                return Ok(vec![control_error_frame(&frame, "rescan_failed", message)?])
3807            }
3808        };
3809        if !preview {
3810            self.capability_evaluator
3811                .configure(configured_capabilities, reserved_capabilities);
3812            self.capability_evaluator.wake_deadline_loop();
3813            self.refresh_capability_requirements();
3814        }
3815        let mut result = result;
3816        result.restart_required = restart_required;
3817        result.capability_warnings = preview_capability_warnings;
3818        let response = ClientControlResponse::SupervisorRescan { result };
3819        Ok(vec![control_response_body_frame(
3820            &frame,
3821            &response,
3822            "ClientControlResponse::SupervisorRescan",
3823        )?])
3824    }
3825
3826    async fn handle_supervisor_release_reserved(
3827        &self,
3828        frame: Frame,
3829        module_id: String,
3830    ) -> Result<Vec<Frame>, RouterError> {
3831        let Some(context) = self.rescan.clone() else {
3832            return Ok(vec![control_error_frame(
3833                &frame,
3834                "release_unavailable",
3835                "reserved-id release requires a daemon started with a reloadable config path",
3836            )?]);
3837        };
3838        let operation_lock = self.supervisor.operation_lock();
3839        let _operation_guard = operation_lock.lock().await;
3840        let loaded = match crate::daemon_config::load(&context.config_path) {
3841            Ok(Some(config)) => config,
3842            Ok(None) => {
3843                return Ok(vec![control_error_frame(
3844                    &frame,
3845                    "invalid_daemon_config",
3846                    format!(
3847                        "daemon config not found at {}; refusing to release reserved module_id '{module_id}'",
3848                        context.config_path.display()
3849                    ),
3850                )?])
3851            }
3852            Err(err) => {
3853                return Ok(vec![control_error_frame(
3854                    &frame,
3855                    "invalid_daemon_config",
3856                    format!("unable to verify reserved-id release against daemon config: {err}"),
3857                )?])
3858            }
3859        };
3860        if loaded
3861            .modules
3862            .iter()
3863            .any(|configured| configured.module_id == module_id)
3864        {
3865            return Ok(vec![control_error_frame(
3866                &frame,
3867                "reserved_module_configured",
3868                format!(
3869                    "module_id '{module_id}' remains configured; remove its config entry and rescan before releasing its reserved id"
3870                ),
3871            )?]);
3872        }
3873        if !self.supervisor.release_retained_reserved_gate(&module_id) {
3874            return Ok(vec![control_error_frame(
3875                &frame,
3876                "reserved_gate_not_retained",
3877                format!(
3878                    "module_id '{module_id}' has no retired reserved-id gate to release; rescan its removed reserved configuration first"
3879                ),
3880            )?]);
3881        }
3882
3883        let response = ClientControlResponse::SupervisorAck {
3884            module_id,
3885            applied: true,
3886        };
3887        Ok(vec![control_response_body_frame(
3888            &frame,
3889            &response,
3890            "ClientControlResponse::SupervisorAck",
3891        )?])
3892    }
3893
3894    /// Reconcile the running module set against the configured one.
3895    ///
3896    /// With `preview` set, the diff is computed and returned WITHOUT applying any
3897    /// of it: nothing is retired, reconfigured, enabled or spawned. The preview
3898    /// deliberately shares this function with the executing path rather than
3899    /// computing the same diff somewhere else -- two implementations of one
3900    /// decision agree until they do not, and the whole value of a preview is that
3901    /// it describes the operation that will actually run.
3902    async fn reconcile_supervised_modules(
3903        &self,
3904        supervisor: &Supervisor,
3905        configured_modules: Vec<crate::daemon_config::ConfiguredModule>,
3906        preview: bool,
3907    ) -> Result<SupervisorRescanResult, String> {
3908        let mut current = BTreeMap::new();
3909        for module in self.supervisor.list() {
3910            let (spec, health) = module.configuration().map_err(|err| {
3911                format!(
3912                    "failed to read configuration for module_id '{}': {err}",
3913                    module.module_id()
3914                )
3915            })?;
3916            let enabled = module
3917                .status()
3918                .map_err(|err| {
3919                    format!(
3920                        "failed to read status for module_id '{}': {err}",
3921                        module.module_id()
3922                    )
3923                })?
3924                .enabled;
3925            current.insert(
3926                module.module_id().to_string(),
3927                (module, spec, health, enabled),
3928            );
3929        }
3930        let configured = configured_modules
3931            .into_iter()
3932            .map(|module| (module.module_id.clone(), module))
3933            .collect::<BTreeMap<_, _>>();
3934
3935        let added = configured
3936            .keys()
3937            .filter(|module_id| !current.contains_key(*module_id))
3938            .cloned()
3939            .collect::<Vec<_>>();
3940        let removed = current
3941            .keys()
3942            .filter(|module_id| !configured.contains_key(*module_id))
3943            .cloned()
3944            .collect::<Vec<_>>();
3945        let mut changed_pending_reload = Vec::new();
3946        let mut configuration_changes = BTreeSet::new();
3947        let mut enabled_changes = BTreeSet::new();
3948        let mut unchanged = 0_u32;
3949
3950        for (module_id, configured_module) in &configured {
3951            let Some((_, current_spec, current_health, current_enabled)) = current.get(module_id)
3952            else {
3953                continue;
3954            };
3955            let configuration_changed = *current_spec != configured_module.module_spec()
3956                || *current_health != configured_module.health;
3957            let enabled_changed = *current_enabled != configured_module.enabled;
3958            if configuration_changed {
3959                configuration_changes.insert(module_id.clone());
3960                changed_pending_reload.push(module_id.clone());
3961            }
3962            if enabled_changed {
3963                enabled_changes.insert(module_id.clone());
3964            }
3965            if !configuration_changed && !enabled_changed {
3966                unchanged = unchanged.saturating_add(1);
3967            }
3968        }
3969
3970        // Everything above this point is pure computation over two snapshots.
3971        // Everything below MUTATES. The preview returns here so the boundary is a
3972        // single early return rather than a condition repeated at each mutation
3973        // site, where one missed guard would apply part of a change the caller was
3974        // told would not happen.
3975        if preview {
3976            return Ok(SupervisorRescanResult {
3977                added,
3978                removed,
3979                changed_pending_reload,
3980                enabled_changes: enabled_changes.iter().cloned().collect(),
3981                unchanged,
3982                preview: true,
3983                // Filled by the caller on both paths, so the preview reports
3984                // restart-required sections identically to an executed rescan --
3985                // the preview is where an operator is most likely to be looking.
3986                restart_required: Vec::new(),
3987                capability_warnings: Vec::new(),
3988            });
3989        }
3990
3991        for module_id in &removed {
3992            let module = &current
3993                .get(module_id)
3994                .expect("removed module came from current supervisor state")
3995                .0;
3996            module.retire().await.map_err(|err| {
3997                format!("failed to retire module_id '{module_id}' during rescan: {err}")
3998            })?;
3999            // TOMBSTONE BEFORE RETIRE, and the order is the whole fix.
4000            //
4001            // `handle_route_open` resolves an absent module in three steps:
4002            // registry, then supervisor status, then tombstone. Retiring first
4003            // opens a window where ALL THREE ARE ABSENT -- the registry entry
4004            // went with the teardown above, the supervisor entry went with
4005            // `retire`, and the tombstone does not exist yet -- so a route.open
4006            // landing in it gets `unknown_module` (RETRYABLE, "never heard of
4007            // it") for a module that was deliberately removed and whose caller
4008            // should get `module_removed` (TERMINAL, carrying a removal age).
4009            //
4010            // Writing the tombstone first closes it: during the window the
4011            // supervisor entry still answers, so the caller gets
4012            // `target_unavailable` -- retryable, and TRUE, because the module
4013            // is mid-teardown. After both statements it is `module_removed`.
4014            // No instant remains where a removed module reads as one that
4015            // never existed.
4016            //
4017            // NOT DETERMINISTICALLY TESTABLE FROM HERE, said plainly because
4018            // the absence of a test beside a fix invites deletion: these are
4019            // two sync statements with no await between them, so reaching the
4020            // window needs a second worker thread to land exactly between them
4021            // and there is no hook to force it. MEASURED: the 25 daemon_config
4022            // tests pass identically with the old order and the new one, so
4023            // the existing suite cannot see this and a green run is not
4024            // evidence either way. What the suite does hold is the
4025            // post-condition -- a removed module answers `module_removed` --
4026            // which this preserves.
4027            //
4028            // Found by an Athena panel reading the shipped tree against a
4029            // design note (2026-09-19), as the one concrete instance of that
4030            // note's class that survived contact with source. Direction is
4031            // benign: retryable where terminal was intended, never the reverse.
4032            self.supervisor.record_rescan_removal(module_id);
4033            self.supervisor.retire(module_id);
4034        }
4035
4036        for module_id in configured.keys() {
4037            let Some((module, _, _, _)) = current.get(module_id) else {
4038                continue;
4039            };
4040            let configured_module = configured
4041                .get(module_id)
4042                .expect("configured module id came from configured map");
4043            if configuration_changes.contains(module_id) {
4044                module
4045                    .update_configuration(
4046                        configured_module.module_spec(),
4047                        configured_module.health,
4048                        configured_module.drain_timeout_ms,
4049                    )
4050                    .await
4051                    .map_err(|err| {
4052                        format!(
4053                            "failed to update module_id '{module_id}' configuration during rescan: {err}"
4054                        )
4055                    })?;
4056            }
4057            if enabled_changes.contains(module_id) {
4058                module
4059                    .set_enabled(configured_module.enabled)
4060                    .await
4061                    .map_err(|err| {
4062                        format!(
4063                            "failed to apply module_id '{module_id}' enabled={} during rescan: {err}",
4064                            configured_module.enabled
4065                        )
4066                    })?;
4067            }
4068        }
4069
4070        for module_id in &added {
4071            let configured_module = configured
4072                .get(module_id)
4073                .expect("added module id came from configured map");
4074            supervisor
4075                .supervise_configured_with_health(
4076                    configured_module.module_spec(),
4077                    configured_module.enabled,
4078                    configured_module.health,
4079                    configured_module.drain_timeout_ms,
4080                    configured_module.restart,
4081                )
4082                .map_err(|err| {
4083                    format!("failed to add module_id '{module_id}' during rescan: {err}")
4084                })?;
4085        }
4086
4087        Ok(SupervisorRescanResult {
4088            added,
4089            removed,
4090            changed_pending_reload,
4091            enabled_changes: enabled_changes.iter().cloned().collect(),
4092            unchanged,
4093            preview: false,
4094            // Filled by the caller, which is the only layer that can see the
4095            // previous config to diff against.
4096            restart_required: Vec::new(),
4097            capability_warnings: Vec::new(),
4098        })
4099    }
4100
4101    async fn handle_supervisor_set_enabled(
4102        &self,
4103        frame: Frame,
4104        module_id: String,
4105        enabled: bool,
4106    ) -> Result<Vec<Frame>, RouterError> {
4107        let operation_lock = self.supervisor.operation_lock();
4108        let _operation_guard = operation_lock.lock().await;
4109        let Some(module) = self.supervisor.get(&module_id) else {
4110            return Ok(vec![control_error_frame(
4111                &frame,
4112                "unknown_module",
4113                format!("module_id '{module_id}' is not supervised"),
4114            )?]);
4115        };
4116
4117        let applied = match module.set_enabled(enabled).await {
4118            Ok(applied) => applied,
4119            Err(err) => {
4120                return Ok(vec![control_error_frame(
4121                    &frame,
4122                    "target_unavailable",
4123                    format!("failed to set module_id '{module_id}' enabled={enabled}: {err}"),
4124                )?])
4125            }
4126        };
4127
4128        self.capability_evaluator.wake_deadline_loop();
4129        self.refresh_capability_requirements();
4130        let response = ClientControlResponse::SupervisorAck { module_id, applied };
4131        Ok(vec![control_response_body_frame(
4132            &frame,
4133            &response,
4134            "ClientControlResponse::SupervisorAck",
4135        )?])
4136    }
4137
4138    async fn handle_supervisor_health_probe(
4139        &self,
4140        frame: Frame,
4141        module_id: String,
4142    ) -> Result<Vec<Frame>, RouterError> {
4143        self.refresh_capability_requirements();
4144        let Some(registration) = self
4145            .registry
4146            .get_module(&module_id)
4147            .map_err(|err| RouterError::backend(0, frame.header.corr, err.to_string()))?
4148        else {
4149            return Ok(vec![control_error_frame(
4150                &frame,
4151                "unknown_module",
4152                format!("module_id '{module_id}' is not registered"),
4153            )?]);
4154        };
4155
4156        // This guard's ACCEPT direction is fenced, but only INCIDENTALLY: no test is
4157        // named for it. Making `module_registration_grants_op` return false
4158        // unconditionally reddens five tests, and every one is named for something
4159        // else -- capability relay, probe/bind demultiplexing, supervision-only
4160        // probing. They exercise a successful advertisement check on the way to their
4161        // own subject.
4162        //
4163        // Real protection, fragile in a specific way: narrowing any of those tests to
4164        // focus on its stated subject would silently remove coverage nobody knows
4165        // they are carrying. Recorded here rather than as a sixth test, because the
4166        // useful fact is WHICH tests hold the guard up -- a new test would add
4167        // coverage without telling the next person what the existing ones quietly do.
4168        if !module_registration_grants_op(&registration.control_ops, MODULE_CONTROL_OP_HEALTH_CHECK)
4169        {
4170            return Ok(vec![control_error_frame(
4171                &frame,
4172                "health_not_advertised",
4173                format!("module_id '{module_id}' did not advertise health.check"),
4174            )?]);
4175        }
4176
4177        let deadline = Instant::now() + self.health_probe_timeout;
4178        let pending = match self.forwarding.begin_module_control_rpc_for(
4179            &module_id,
4180            MODULE_CONTROL_OP_HEALTH_CHECK,
4181            deadline,
4182        ) {
4183            Ok(pending) => pending,
4184            Err(err) => {
4185                return Ok(vec![control_error_frame(
4186                    &frame,
4187                    forwarding_error_code(&err),
4188                    err.to_string(),
4189                )?])
4190            }
4191        };
4192
4193        let PendingModuleControlRpc {
4194            endpoint,
4195            module_sink,
4196            negotiated_ver,
4197            corr: probe_corr,
4198            receiver,
4199        } = pending;
4200        let mut guard =
4201            ModuleControlRpcGuard::new(Arc::clone(&self.forwarding), endpoint, probe_corr);
4202        let probe_body =
4203            serde_json::to_vec(&ModuleControlRequest::HealthCheck {}).map_err(|err| {
4204                RouterError::backend(
4205                    0,
4206                    frame.header.corr,
4207                    format!("failed to encode health.check request: {err}"),
4208                )
4209            })?;
4210        let probe_frame = Frame::build_with_version(
4211            negotiated_ver,
4212            FrameType::Request,
4213            control_flags(),
4214            0,
4215            0,
4216            probe_corr,
4217            probe_body,
4218        )
4219        .map_err(RouterError::FrameBuild)?;
4220
4221        if let Err(err) = module_sink.send(probe_frame).await {
4222            return Ok(vec![control_error_frame(
4223                &frame,
4224                "target_unavailable",
4225                err.to_string(),
4226            )?]);
4227        }
4228
4229        match timeout_at(deadline, receiver).await {
4230            Ok(Ok(ModuleControlRpcOutcome::Response(response))) => {
4231                guard.disarm();
4232                let Some(report) = response.health_report() else {
4233                    return Ok(vec![control_error_frame(
4234                        &frame,
4235                        "invalid_control_body",
4236                        "health.check RPC returned a non-health response",
4237                    )?]);
4238                };
4239                // Metrics go out whole here. The supervisor's cached snapshot
4240                // caps this blob (see truncate_health_metrics), and this path
4241                // exists precisely to answer without that cap -- so applying it
4242                // here would leave no way to see what the cached view drops.
4243                let HealthReport {
4244                    status,
4245                    detail,
4246                    metrics,
4247                } = report;
4248                let capability_detail = self
4249                    .capability_evaluator
4250                    .required_problem_detail(&module_id);
4251                let response = ClientControlResponse::SupervisorHealthProbe {
4252                    module_id,
4253                    status,
4254                    detail: append_capability_problem_detail(detail, capability_detail),
4255                    metrics,
4256                };
4257                Ok(vec![control_response_body_frame(
4258                    &frame,
4259                    &response,
4260                    "ClientControlResponse::SupervisorHealthProbe",
4261                )?])
4262            }
4263            Ok(Ok(ModuleControlRpcOutcome::Rejected(body))) => {
4264                guard.disarm();
4265                Ok(vec![control_error_body_frame(&frame, body)?])
4266            }
4267            Ok(Ok(ModuleControlRpcOutcome::ModuleGone(message))) => {
4268                guard.disarm();
4269                Ok(vec![control_error_frame(
4270                    &frame,
4271                    "target_unavailable",
4272                    message,
4273                )?])
4274            }
4275            Ok(Ok(ModuleControlRpcOutcome::MalformedResponse(message))) => {
4276                guard.disarm();
4277                Ok(vec![control_error_frame(
4278                    &frame,
4279                    "invalid_control_body",
4280                    message,
4281                )?])
4282            }
4283            Ok(Ok(ModuleControlRpcOutcome::UnexpectedOp { expected, actual })) => {
4284                guard.disarm();
4285                Ok(vec![control_error_frame(
4286                    &frame,
4287                    "invalid_control_body",
4288                    format!("expected module-control op '{expected}', got '{actual}'"),
4289                )?])
4290            }
4291            Ok(Ok(ModuleControlRpcOutcome::DeadlineElapsed)) => {
4292                guard.disarm();
4293                Ok(vec![control_error_frame(
4294                    &frame,
4295                    "module_timeout",
4296                    format!(
4297                        "module_id '{module_id}' answered health.check after {:?}",
4298                        self.health_probe_timeout
4299                    ),
4300                )?])
4301            }
4302            Ok(Err(_)) => Ok(vec![control_error_frame(
4303                &frame,
4304                "target_unavailable",
4305                "health.check waiter was canceled before the module responded",
4306            )?]),
4307            Err(_) => Ok(vec![control_error_frame(
4308                &frame,
4309                "module_timeout",
4310                format!(
4311                    "module_id '{module_id}' did not answer health.check within {:?}",
4312                    self.health_probe_timeout
4313                ),
4314            )?]),
4315        }
4316    }
4317
4318    fn supervisor_status(
4319        &self,
4320        module_id: &str,
4321        corr: u64,
4322    ) -> Result<Option<(crate::supervise::ModuleStatus, bool)>, RouterError> {
4323        self.supervisor
4324            .get(module_id)
4325            .map(|module| {
4326                let warming = module.is_warming_for_control("status").map_err(|err| {
4327                    RouterError::backend(
4328                        0,
4329                        corr,
4330                        format!(
4331                            "failed to read supervisor warming state for module_id '{module_id}': {err}"
4332                        ),
4333                    )
4334                })?;
4335                module.status_for_control("status").map_err(|err| {
4336                    RouterError::backend(
4337                        0,
4338                        corr,
4339                        format!(
4340                            "failed to read supervisor status for module_id '{module_id}': {err}"
4341                        ),
4342                    )
4343                }).map(|status| (status, warming))
4344            })
4345            .transpose()
4346    }
4347
4348    fn guard_module_control_op(
4349        &self,
4350        frame: &Frame,
4351        module_id: &str,
4352        op: &str,
4353    ) -> Result<Option<Frame>, RouterError> {
4354        if self.module_grants_op(module_id, op, frame.header.corr)? {
4355            return Ok(None);
4356        }
4357
4358        Ok(Some(control_error_frame(
4359            frame,
4360            "op_not_allowed",
4361            format!("module_id '{module_id}' did not grant control op '{op}'"),
4362        )?))
4363    }
4364
4365    fn module_grants_op(&self, module_id: &str, op: &str, corr: u64) -> Result<bool, RouterError> {
4366        let Some(registration) = self
4367            .registry
4368            .get_module(module_id)
4369            .map_err(|err| RouterError::backend(0, corr, err.to_string()))?
4370        else {
4371            return Ok(false);
4372        };
4373        Ok(module_registration_grants_op(&registration.control_ops, op))
4374    }
4375
4376    fn handle_status_update(
4377        &self,
4378        endpoint: ModuleEndpointId,
4379        frame: Frame,
4380    ) -> Result<Vec<Frame>, RouterError> {
4381        let update = match serde_json::from_slice::<ModuleControlPush>(&frame.body) {
4382            Ok(update) => update,
4383            Err(err) => {
4384                // Forward-compat: a newer module may push a channel-0 op this subc
4385                // version doesn't know. The control contract says unknown push ops
4386                // are IGNORED, never answered with an error. Only a malformed body
4387                // for an op we DO know is a real error worth surfacing.
4388                if is_known_module_push_op(&frame.body) {
4389                    return Ok(vec![control_error_frame(
4390                        &frame,
4391                        "invalid_control_body",
4392                        format!("malformed module control push body: {err}"),
4393                    )?]);
4394                }
4395                return Ok(Vec::new());
4396            }
4397        };
4398
4399        match update {
4400            ModuleControlPush::RouteStatus {
4401                route_channel,
4402                route_epoch,
4403                status,
4404            } => {
4405                self.forwarding
4406                    .cache_status(endpoint, route_channel, route_epoch, status)
4407                    .map_err(RouterError::Forwarding)?;
4408            }
4409        }
4410        Ok(Vec::new())
4411    }
4412
4413    fn handle_route_poll(
4414        &self,
4415        ctx: &RouteCtx,
4416        frame: Frame,
4417        route_channel: u16,
4418        route_epoch: u32,
4419        kind: PollKind,
4420    ) -> Result<Vec<Frame>, RouterError> {
4421        let snapshot = self
4422            .forwarding
4423            .route_poll_snapshot(ctx.connection_id, route_channel, route_epoch)
4424            .map_err(RouterError::Forwarding)?;
4425        let response = match (kind, snapshot) {
4426            (PollKind::Status, RoutePollSnapshot::Bound { status, .. }) => {
4427                ClientControlResponse::RoutePoll {
4428                    route_channel,
4429                    route_epoch,
4430                    status,
4431                    live: None,
4432                }
4433            }
4434            (PollKind::Status, RoutePollSnapshot::Absent) => ClientControlResponse::RoutePoll {
4435                route_channel,
4436                route_epoch,
4437                status: None,
4438                live: None,
4439            },
4440            (PollKind::Liveness, RoutePollSnapshot::Bound { module_id, .. }) => {
4441                // ABSENCE HERE MEANS "NOT SUPERVISED", NOT "UNKNOWN", and that
4442                // is what makes reporting `true` correct rather than a
4443                // confident guess. `process_live` returns None only when the
4444                // module id has no supervisor snapshot at all -- an
4445                // externally-started module the daemon did not spawn -- and
4446                // for those the supervisor has no opinion to offer, ever. It
4447                // is never None for a supervised module in an unknown state:
4448                // a supervised module always has a snapshot, and the answer
4449                // comes from `state == Running && process_alive`.
4450                //
4451                // The route is Bound, so the module completed a HELLO on a
4452                // live connection; "the process this route points at is
4453                // running" is therefore attested by the binding rather than
4454                // assumed. Reporting `false` for an unsupervised module would
4455                // be the actual lie -- it would tell a client its healthy
4456                // route is dead because the daemon does not manage the
4457                // process.
4458                //
4459                // IF `process_live` EVER GAINS A THIRD CASE -- a supervised
4460                // module whose liveness is genuinely unknown, e.g. a snapshot
4461                // that has not been populated yet -- THIS DEFAULT BECOMES
4462                // WRONG and must split: unsupervised stays true, unknown
4463                // becomes null so the client can tell the two apart. The
4464                // response field is already `Option<bool>`, so the wire can
4465                // carry that distinction today.
4466                let live = self
4467                    .process_liveness
4468                    .as_ref()
4469                    .and_then(|source| source.process_live(&module_id))
4470                    .unwrap_or(true);
4471                ClientControlResponse::RoutePoll {
4472                    route_channel,
4473                    route_epoch,
4474                    status: None,
4475                    live: Some(live),
4476                }
4477            }
4478            (PollKind::Liveness, RoutePollSnapshot::Absent) => ClientControlResponse::RoutePoll {
4479                route_channel,
4480                route_epoch,
4481                status: None,
4482                live: Some(false),
4483            },
4484        };
4485
4486        Ok(vec![control_response_body_frame(
4487            &frame,
4488            &response,
4489            "ClientControlResponse::RoutePoll",
4490        )?])
4491    }
4492
4493    pub(crate) fn observe_module_control_completion(
4494        &self,
4495        completion: ModuleControlRpcCompletion,
4496    ) -> bool {
4497        match completion {
4498            ModuleControlRpcCompletion::Unknown => false,
4499            ModuleControlRpcCompletion::Settled => true,
4500            ModuleControlRpcCompletion::LateHealthAnswer { module_id, latency } => {
4501                let latency_ms = latency.as_millis().min(u128::from(u64::MAX)) as u64;
4502                info!(
4503                    module_id = %module_id,
4504                    latency_ms,
4505                    "late health.check answer proves the module is alive"
4506                );
4507                match self
4508                    .supervisor
4509                    .record_late_health_answer(&module_id, latency_ms)
4510                {
4511                    Ok(true) => {}
4512                    Ok(false) => debug!(
4513                        module_id = %module_id,
4514                        latency_ms,
4515                        "late health.check answer has no active supervisor snapshot"
4516                    ),
4517                    Err(err) => warn!(
4518                        module_id = %module_id,
4519                        latency_ms,
4520                        error = %err,
4521                        "failed to record late health.check answer"
4522                    ),
4523                }
4524                true
4525            }
4526        }
4527    }
4528
4529    /// Decide whether a failure while settling a relayed `route.bind` belongs to
4530    /// the module connection whose frame is being handled, or to the client that
4531    /// relay was opened for.
4532    ///
4533    /// This runs on the MODULE connection's frame handler, where returning `Err`
4534    /// ends that connection -- and a module connection carries every client's
4535    /// routes to that module, so ending it costs the whole fleet its tools.
4536    /// `ConnectionClosing` carries the id of the connection that is closing, and
4537    /// when that id is a CLIENT's, the condition is entirely about that one
4538    /// client's route.open. A client-scoped condition has no authority over a
4539    /// shared module connection, so it is logged and the single relay is dropped:
4540    /// the client is going away, and `complete_pending_relay` already removed the
4541    /// relay before failing, so there is nothing left to settle. Anything that
4542    /// relay still reserved is released by that client's own connection teardown,
4543    /// which is already under way -- that is what "closing" means.
4544    ///
4545    /// Every other failure is a statement about THIS connection and stays fatal:
4546    /// a poisoned forwarding lock, a stale module endpoint, and the module's own
4547    /// id in `ConnectionClosing` all mean this connection cannot keep serving
4548    /// frames correctly.
4549    fn refuse_to_end_module_connection_for_a_client(
4550        &self,
4551        module_connection_id: ConnectionId,
4552        corr: u64,
4553        err: ForwardingError,
4554    ) -> Result<(), RouterError> {
4555        if let ForwardingError::ConnectionClosing { connection_id } = err {
4556            if connection_id != module_connection_id {
4557                warn!(
4558                    module_connection_id = module_connection_id.get(),
4559                    client_connection_id = connection_id.get(),
4560                    corr,
4561                    "dropping a route.bind response for a closing client; the module connection keeps serving"
4562                );
4563                return Ok(());
4564            }
4565        }
4566        Err(RouterError::Forwarding(err))
4567    }
4568
4569    fn handle_module_relay_response(
4570        &self,
4571        connection_id: ConnectionId,
4572        frame: Frame,
4573    ) -> Result<Vec<Frame>, RouterError> {
4574        let mut secondary_error = None;
4575        let outcome = match frame.header.ty {
4576            FrameType::Response => match serde_json::from_slice::<ControlOpProbe>(&frame.body) {
4577                Ok(probe) if probe.op == "route.bind" => {
4578                    match serde_json::from_slice::<ModuleControlResponse>(&frame.body) {
4579                        Ok(ModuleControlResponse::RouteBindAck {}) => {
4580                            RouteBindRelayOutcome::Accepted
4581                        }
4582                        Ok(other) => {
4583                            let message =
4584                                format!("route.bind response carried unexpected body: {other:?}");
4585                            secondary_error = Some(control_error_frame(
4586                                &frame,
4587                                "invalid_control_body",
4588                                message.clone(),
4589                            )?);
4590                            RouteBindRelayOutcome::ModuleGone(message)
4591                        }
4592                        Err(err) => {
4593                            let message = format!("malformed route.bind response body: {err}");
4594                            secondary_error = Some(control_error_frame(
4595                                &frame,
4596                                "invalid_control_body",
4597                                message.clone(),
4598                            )?);
4599                            RouteBindRelayOutcome::ModuleGone(message)
4600                        }
4601                    }
4602                }
4603                Ok(probe) => {
4604                    let outcome = match serde_json::from_slice::<ModuleControlResponse>(&frame.body)
4605                    {
4606                        Ok(response) => ModuleControlRpcOutcome::Response(response),
4607                        Err(err) => ModuleControlRpcOutcome::MalformedResponse(format!(
4608                            "malformed {} response body: {err}",
4609                            probe.op
4610                        )),
4611                    };
4612                    let completion = self
4613                        .forwarding
4614                        .complete_module_control_rpc(
4615                            connection_id,
4616                            frame.header.corr,
4617                            Some(&probe.op),
4618                            outcome,
4619                        )
4620                        .map_err(RouterError::Forwarding)?;
4621                    if !self.observe_module_control_completion(completion) {
4622                        debug!(
4623                            connection_id = connection_id.get(),
4624                            corr = frame.header.corr,
4625                            op = %probe.op,
4626                            "dropping late or unknown module-control RPC response"
4627                        );
4628                    }
4629                    return Ok(Vec::new());
4630                }
4631                Err(err) => {
4632                    if let Some(expected_op) = self
4633                        .forwarding
4634                        .pending_module_control_op(connection_id, frame.header.corr)
4635                        .map_err(RouterError::Forwarding)?
4636                    {
4637                        let completion = self
4638                            .forwarding
4639                            .complete_module_control_rpc(
4640                                connection_id,
4641                                frame.header.corr,
4642                                None,
4643                                ModuleControlRpcOutcome::MalformedResponse(format!(
4644                                    "malformed {expected_op} response body: {err}"
4645                                )),
4646                            )
4647                            .map_err(RouterError::Forwarding)?;
4648                        if !self.observe_module_control_completion(completion) {
4649                            debug!(
4650                                connection_id = connection_id.get(),
4651                                corr = frame.header.corr,
4652                                "dropping late malformed module-control RPC response"
4653                            );
4654                        }
4655                        return Ok(Vec::new());
4656                    }
4657                    let message = format!("malformed route.bind response body: {err}");
4658                    secondary_error = Some(control_error_frame(
4659                        &frame,
4660                        "invalid_control_body",
4661                        message.clone(),
4662                    )?);
4663                    RouteBindRelayOutcome::ModuleGone(message)
4664                }
4665            },
4666            FrameType::Error => {
4667                if self
4668                    .forwarding
4669                    .pending_module_control_op(connection_id, frame.header.corr)
4670                    .map_err(RouterError::Forwarding)?
4671                    .is_some()
4672                {
4673                    let outcome = match serde_json::from_slice::<ErrorBody>(&frame.body) {
4674                        Ok(body) => ModuleControlRpcOutcome::Rejected(body),
4675                        Err(err) => ModuleControlRpcOutcome::MalformedResponse(format!(
4676                            "malformed module-control ERROR body: {err}"
4677                        )),
4678                    };
4679                    let completion = self
4680                        .forwarding
4681                        .complete_module_control_rpc(
4682                            connection_id,
4683                            frame.header.corr,
4684                            None,
4685                            outcome,
4686                        )
4687                        .map_err(RouterError::Forwarding)?;
4688                    if !self.observe_module_control_completion(completion) {
4689                        debug!(
4690                            connection_id = connection_id.get(),
4691                            corr = frame.header.corr,
4692                            "dropping late or unknown module-control RPC error"
4693                        );
4694                    }
4695                    return Ok(Vec::new());
4696                }
4697                match serde_json::from_slice::<ErrorBody>(&frame.body) {
4698                    Ok(body) => RouteBindRelayOutcome::Rejected(body),
4699                    Err(err) => {
4700                        let message = format!("malformed route.bind ERROR body: {err}");
4701                        secondary_error = Some(control_error_frame(
4702                            &frame,
4703                            "invalid_control_body",
4704                            message.clone(),
4705                        )?);
4706                        RouteBindRelayOutcome::ModuleGone(message)
4707                    }
4708                }
4709            }
4710            ty => {
4711                return Ok(vec![control_error_frame(
4712                    &frame,
4713                    "unsupported_control_frame",
4714                    format!("unsupported module channel-0 frame {ty:?}"),
4715                )?])
4716            }
4717        };
4718
4719        let settled =
4720            self.forwarding
4721                .complete_pending_relay(connection_id, frame.header.corr, outcome);
4722        let completion = match settled {
4723            Ok(completion) => completion,
4724            Err(err) => {
4725                self.refuse_to_end_module_connection_for_a_client(
4726                    connection_id,
4727                    frame.header.corr,
4728                    err,
4729                )?;
4730                return Ok(secondary_error.into_iter().collect());
4731            }
4732        };
4733        if let Some(target) = completion.abandoned.as_ref() {
4734            send_goodbye_target_best_effort(target, "late accepted route.bind");
4735        }
4736        if !completion.settled {
4737            debug!(
4738                connection_id = connection_id.get(),
4739                corr = frame.header.corr,
4740                frame_type = ?frame.header.ty,
4741                "dropping late or unknown route.bind relay response"
4742            );
4743        }
4744        Ok(secondary_error.into_iter().collect())
4745    }
4746
4747    fn handle_goodbye(&self, connection_id: ConnectionId) -> Result<Vec<Frame>, RouterError> {
4748        debug!(connection_id = connection_id.get(), "handling GOODBYE");
4749        let registrations = self
4750            .deregister_connection(connection_id)
4751            .map_err(|err| RouterError::backend(0, 0, err.to_string()))?;
4752        let released_routes = self
4753            .forwarding
4754            .cleanup_connection(connection_id)
4755            .map_err(RouterError::Forwarding)?;
4756        self.emit_route_goodbyes(released_routes);
4757        // Notify only after forwarding teardown completes (see cleanup_connection).
4758        if !registrations.is_empty() {
4759            crate::supervise::notify_registration_release();
4760        }
4761        Ok(Vec::new())
4762    }
4763}
4764
4765impl Default for ControlHandler {
4766    fn default() -> Self {
4767        Self::new(Arc::new(Registry::default()))
4768    }
4769}
4770
4771impl crate::supervise::SwapPromotionObserver for ControlHandler {
4772    fn swap_promoted(&self, registration: &crate::registry::ModuleRegistration) {
4773        self.apply_registration_capabilities(registration);
4774    }
4775}
4776
4777fn capability_requirement_status(status: RequirementStatus) -> CapabilityRequirementStatus {
4778    CapabilityRequirementStatus {
4779        consumer: status.consumer,
4780        capability: status.capability,
4781        need: match status.need {
4782            subc_protocol::manifest::CapabilityNeed::Required => "required".to_string(),
4783            subc_protocol::manifest::CapabilityNeed::Optional => "optional".to_string(),
4784        },
4785        verdict: status.verdict.as_str().to_string(),
4786        episode_seq: status.episode_seq,
4787        config_satisfiable: status.config_satisfiable,
4788        runtime_available: status.runtime_available,
4789        detail: status.detail,
4790    }
4791}
4792
4793fn append_capability_problem_detail(
4794    detail: Option<String>,
4795    capability_detail: Option<String>,
4796) -> Option<String> {
4797    match (detail, capability_detail) {
4798        (Some(detail), Some(capability_detail)) => Some(format!("{detail}; {capability_detail}")),
4799        (Some(detail), None) => Some(detail),
4800        (None, Some(capability_detail)) => Some(capability_detail),
4801        (None, None) => None,
4802    }
4803}
4804
4805fn subc_ops() -> Vec<String> {
4806    SUBC_CONTROL_OPS
4807        .iter()
4808        .map(|op| (*op).to_string())
4809        .collect()
4810}
4811
4812fn module_subc_ops() -> Vec<String> {
4813    SUBC_CONTROL_OPS
4814        .iter()
4815        .chain(MODULE_TO_SUBC_CONTROL_OPS.iter())
4816        .map(|op| (*op).to_string())
4817        .collect()
4818}
4819
4820#[cfg(test)]
4821fn module_baseline_control_ops() -> Vec<String> {
4822    MODULE_BASELINE_CONTROL_OPS
4823        .iter()
4824        .map(|op| (*op).to_string())
4825        .collect()
4826}
4827
4828fn effective_module_control_ops(declared: Option<Vec<String>>) -> Vec<String> {
4829    let mut seen = HashSet::new();
4830    let mut effective = Vec::new();
4831    for op in MODULE_BASELINE_CONTROL_OPS {
4832        if seen.insert((*op).to_string()) {
4833            effective.push((*op).to_string());
4834        }
4835    }
4836    for op in declared.unwrap_or_default() {
4837        if seen.insert(op.clone()) {
4838            effective.push(op);
4839        }
4840    }
4841    effective
4842}
4843
4844fn module_registration_grants_op(control_ops: &[String], op: &str) -> bool {
4845    MODULE_BASELINE_CONTROL_OPS.contains(&op) || control_ops.iter().any(|granted| granted == op)
4846}
4847
4848fn target_module_id(target: &RouteTarget) -> &str {
4849    match target {
4850        RouteTarget::ToolProvider { module_id }
4851        | RouteTarget::ManagementSurface { module_id }
4852        | RouteTarget::InternalService { module_id, .. } => module_id,
4853    }
4854}
4855
4856fn target_has_required_role(target: &RouteTarget, roles: &[ProviderRole]) -> bool {
4857    roles.iter().any(|role| match (target, role) {
4858        (RouteTarget::ToolProvider { .. }, ProviderRole::ToolProvider { .. }) => true,
4859        (RouteTarget::ManagementSurface { .. }, ProviderRole::ManagementSurface { .. }) => true,
4860        (
4861            RouteTarget::InternalService { service_id, .. },
4862            ProviderRole::InternalService {
4863                service_id: provided,
4864                ..
4865            },
4866        ) => service_id == provided,
4867        _ => false,
4868    })
4869}
4870
4871fn is_routable_role(role: &ProviderRole) -> bool {
4872    matches!(
4873        role,
4874        ProviderRole::ToolProvider { .. }
4875            | ProviderRole::ManagementSurface { .. }
4876            | ProviderRole::InternalService { .. }
4877    )
4878}
4879
4880#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4881enum ControlRequestBodyError {
4882    UnknownOp,
4883    InvalidBody,
4884}
4885
4886#[derive(Debug, Deserialize)]
4887struct ControlOpProbe {
4888    op: String,
4889}
4890
4891/// Channel-0 push ops this subc version understands. A push whose `op` is not in
4892/// this set is treated as a forward-compat unknown and ignored rather than errored.
4893const MODULE_PUSH_OPS: &[&str] = &["route.status"];
4894
4895fn is_known_module_push_op(body: &[u8]) -> bool {
4896    serde_json::from_slice::<ControlOpProbe>(body)
4897        .map(|probe| MODULE_PUSH_OPS.contains(&probe.op.as_str()))
4898        .unwrap_or(false)
4899}
4900
4901fn is_known_module_request_op(body: &[u8]) -> bool {
4902    serde_json::from_slice::<ControlOpProbe>(body)
4903        .map(|probe| MODULE_TO_SUBC_CONTROL_OPS.contains(&probe.op.as_str()))
4904        .unwrap_or(false)
4905}
4906
4907fn log_control_dispatch_arrival(op: &'static str, connection_id: ConnectionId, corr: u64) {
4908    debug!(
4909        op = %op,
4910        connection_id = connection_id.get(),
4911        corr,
4912        "control dispatch"
4913    );
4914}
4915
4916fn log_slow_control_dispatch(
4917    dispatch_started_at: Option<StdInstant>,
4918    op: &'static str,
4919    connection_id: ConnectionId,
4920    corr: u64,
4921) {
4922    let Some(dispatch_started_at) = dispatch_started_at else {
4923        return;
4924    };
4925    let elapsed = dispatch_started_at.elapsed();
4926    if elapsed >= SLOW_CONTROL_DISPATCH_THRESHOLD {
4927        warn!(
4928            op = %op,
4929            connection_id = connection_id.get(),
4930            corr,
4931            elapsed_ms = elapsed.as_millis() as u64,
4932            "slow control dispatch"
4933        );
4934    }
4935}
4936
4937fn client_control_request_op(request: &ClientControlRequest) -> &'static str {
4938    match request {
4939        ClientControlRequest::ServerDescribe {} => ops::SERVER_DESCRIBE,
4940        ClientControlRequest::SupervisorProvenance { .. } => ops::SUPERVISOR_PROVENANCE,
4941        ClientControlRequest::CatalogList { .. } => ops::CATALOG_LIST,
4942        ClientControlRequest::RouteOpen { .. } => ops::ROUTE_OPEN,
4943        ClientControlRequest::RoutePoll { .. } => ops::ROUTE_POLL,
4944        ClientControlRequest::SupervisorList {} => ops::SUPERVISOR_LIST,
4945        ClientControlRequest::SupervisorSpawnSnapshot {} => ops::SUPERVISOR_SPAWN_SNAPSHOT,
4946        ClientControlRequest::SupervisorSpawnSubscribe { .. } => ops::SUPERVISOR_SPAWN_SUBSCRIBE,
4947        ClientControlRequest::SupervisorRestart { .. } => ops::SUPERVISOR_RESTART,
4948        ClientControlRequest::SupervisorSwap { .. } => ops::SUPERVISOR_SWAP,
4949        ClientControlRequest::SupervisorReload { .. } => ops::SUPERVISOR_RELOAD,
4950        ClientControlRequest::SupervisorRescan { .. } => ops::SUPERVISOR_RESCAN,
4951        ClientControlRequest::SupervisorReleaseReserved { .. } => ops::SUPERVISOR_RELEASE_RESERVED,
4952        ClientControlRequest::SupervisorSetEnabled { .. } => ops::SUPERVISOR_SET_ENABLED,
4953        ClientControlRequest::SupervisorHealthProbe { .. } => ops::SUPERVISOR_HEALTH_PROBE,
4954        ClientControlRequest::SupervisorHealth {} => ops::SUPERVISOR_HEALTH,
4955        ClientControlRequest::SupervisorRoutes { .. } => ops::SUPERVISOR_ROUTES,
4956        ClientControlRequest::SupervisorStderrTail { .. } => ops::SUPERVISOR_STDERR_TAIL,
4957        ClientControlRequest::SupervisorTerminals { .. } => ops::SUPERVISOR_TERMINALS,
4958    }
4959}
4960
4961fn module_control_request_op(request: &ModuleControlRequestFromModule) -> &'static str {
4962    match request {
4963        ModuleControlRequestFromModule::CatalogUpdate { .. } => MODULE_TO_SUBC_OP_CATALOG_UPDATE,
4964        ModuleControlRequestFromModule::LiveRoots {} => "supervisor.live_roots",
4965    }
4966}
4967
4968fn parse_client_control_request(
4969    body: &[u8],
4970) -> Result<ClientControlRequest, (serde_json::Error, ControlRequestBodyError)> {
4971    serde_json::from_slice::<ClientControlRequest>(body).map_err(|err| {
4972        let classification = match serde_json::from_slice::<ControlOpProbe>(body) {
4973            Ok(probe) if SUBC_CONTROL_OPS.contains(&probe.op.as_str()) => {
4974                ControlRequestBodyError::InvalidBody
4975            }
4976            Ok(_) => ControlRequestBodyError::UnknownOp,
4977            Err(_) => ControlRequestBodyError::InvalidBody,
4978        };
4979        (err, classification)
4980    })
4981}
4982
4983fn parse_module_control_request_from_module(
4984    body: &[u8],
4985) -> Result<ModuleControlRequestFromModule, (serde_json::Error, ControlRequestBodyError)> {
4986    serde_json::from_slice::<ModuleControlRequestFromModule>(body).map_err(|err| {
4987        let classification = match serde_json::from_slice::<ControlOpProbe>(body) {
4988            Ok(probe) if MODULE_TO_SUBC_CONTROL_OPS.contains(&probe.op.as_str()) => {
4989                ControlRequestBodyError::InvalidBody
4990            }
4991            Ok(_) => ControlRequestBodyError::UnknownOp,
4992            Err(_) => ControlRequestBodyError::InvalidBody,
4993        };
4994        (err, classification)
4995    })
4996}
4997
4998#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
4999enum ProviderRoleKind {
5000    ToolProvider,
5001    PipelineStage,
5002    ManagementSurface,
5003    InternalService,
5004}
5005
5006fn provider_role_kind(role: &ProviderRole) -> ProviderRoleKind {
5007    match role {
5008        ProviderRole::ToolProvider { .. } => ProviderRoleKind::ToolProvider,
5009        ProviderRole::PipelineStage { .. } => ProviderRoleKind::PipelineStage,
5010        ProviderRole::ManagementSurface { .. } => ProviderRoleKind::ManagementSurface,
5011        ProviderRole::InternalService { .. } => ProviderRoleKind::InternalService,
5012    }
5013}
5014
5015fn provider_role_kind_set(roles: &[ProviderRole]) -> BTreeSet<ProviderRoleKind> {
5016    roles.iter().map(provider_role_kind).collect()
5017}
5018
5019/// Return whether a catalog change can create a newly violating live route.
5020/// Removing an attested claim is intentionally excluded: it makes fewer routes
5021/// forbidden and therefore must leave the existing route census untouched.
5022fn capability_census_trigger(
5023    old: Option<&CapabilityDeclarations>,
5024    new: Option<&CapabilityDeclarations>,
5025) -> bool {
5026    let old_provides = old
5027        .map(|capabilities| capabilities.provides.iter().collect::<HashSet<_>>())
5028        .unwrap_or_default();
5029    let old_denies = old
5030        .map(|capabilities| capabilities.must_never_reach.iter().collect::<HashSet<_>>())
5031        .unwrap_or_default();
5032    let new = new.cloned().unwrap_or(CapabilityDeclarations {
5033        provides: Vec::new(),
5034        requires: Vec::new(),
5035        must_never_reach: Vec::new(),
5036    });
5037
5038    new.provides
5039        .iter()
5040        .any(|capability| !old_provides.contains(capability))
5041        || new
5042            .must_never_reach
5043            .iter()
5044            .any(|capability| !old_denies.contains(capability))
5045}
5046
5047/// Find the first capability an attested opener denies that an attested target
5048/// claims. Both manifests are live registry records, never cached or client data.
5049fn denied_capability<'a>(
5050    opening_manifest: &'a ModuleManifest,
5051    target_manifest: &ModuleManifest,
5052) -> Option<&'a str> {
5053    let opening_capabilities = opening_manifest.capabilities.as_ref()?;
5054    let target_capabilities = target_manifest.capabilities.as_ref()?;
5055    opening_capabilities
5056        .must_never_reach
5057        .iter()
5058        .find(|denied| {
5059            target_capabilities
5060                .provides
5061                .iter()
5062                .any(|provided| provided == *denied)
5063        })
5064        .map(String::as_str)
5065}
5066
5067fn catalog_update_frozen_field_message(
5068    registered: &ModuleManifest,
5069    provides: &[ProviderRole],
5070) -> Option<String> {
5071    let old_has_provides = !registered.provides.is_empty();
5072    let new_has_provides = !provides.is_empty();
5073    if old_has_provides != new_has_provides {
5074        return Some(format!(
5075            "catalog.update cannot change module '{}' between supervision-only and routable; routability is fixed at HELLO",
5076            registered.module_id
5077        ));
5078    }
5079
5080    if provider_role_kind_set(&registered.provides) != provider_role_kind_set(provides) {
5081        return Some(format!(
5082            "catalog.update cannot change provider role kinds for module '{}'; role kinds are fixed at HELLO",
5083            registered.module_id
5084        ));
5085    }
5086
5087    let registered_concurrency = manifest_concurrency(registered);
5088    let mut candidate = registered.clone();
5089    candidate.provides = provides.to_vec();
5090    let candidate_concurrency = manifest_concurrency(&candidate);
5091    if candidate_concurrency != registered_concurrency {
5092        return Some(format!(
5093            "catalog.update cannot change module '{}' concurrency from {:?} to {:?}; concurrency is fixed at HELLO",
5094            registered.module_id, registered_concurrency, candidate_concurrency
5095        ));
5096    }
5097
5098    // control_ops live beside the manifest in the HELLO body, not inside
5099    // ModuleManifest, so a provides-only catalog.update cannot change them.
5100    None
5101}
5102
5103fn manifest_provides_routable_role(manifest: &ModuleManifest) -> bool {
5104    manifest.provides.iter().any(is_routable_role)
5105}
5106
5107/// Returns the routable-provider concurrency subc should enforce for this manifest.
5108///
5109/// ToolProvider and ManagementSurface store their delivery concurrency directly.
5110/// InternalService has no role-specific concurrency field, so it retains the
5111/// existing ModuleManaged default for backward compatibility.
5112fn manifest_concurrency(manifest: &ModuleManifest) -> Concurrency {
5113    manifest
5114        .provides
5115        .iter()
5116        .find_map(|provider| match provider {
5117            ProviderRole::ToolProvider { concurrency, .. }
5118            | ProviderRole::ManagementSurface { concurrency, .. } => Some(concurrency.clone()),
5119            ProviderRole::PipelineStage { .. } | ProviderRole::InternalService { .. } => None,
5120        })
5121        .unwrap_or(Concurrency::ModuleManaged)
5122}
5123
5124/// True when the manifest carries a ManagementSurface role whose concurrency
5125/// was RESOLVED BY SERDE DEFAULT rather than declared. Reads the raw HELLO
5126/// bytes because the typed manifest deliberately erases that distinction: the
5127/// default exists for wire compatibility, and this probe exists so the default
5128/// stays observable. Any parse irregularity returns false -- the caller only
5129/// logs, and a malformed body already failed registration upstream.
5130fn manifest_concurrency_was_defaulted(raw_hello: &[u8], manifest: &ModuleManifest) -> bool {
5131    let has_management_surface = manifest
5132        .provides
5133        .iter()
5134        .any(|provider| matches!(provider, ProviderRole::ManagementSurface { .. }));
5135    if !has_management_surface {
5136        return false;
5137    }
5138    let Ok(raw) = serde_json::from_slice::<serde_json::Value>(raw_hello) else {
5139        return false;
5140    };
5141    let Some(provides) = raw
5142        .get("manifest")
5143        .and_then(|manifest| manifest.get("provides"))
5144        .and_then(serde_json::Value::as_array)
5145    else {
5146        return false;
5147    };
5148    // ProviderRole is internally tagged (`tag = "role"`), so the wire shape is
5149    // flat: {"role": "management_surface", ..., "concurrency": ...} -- verified
5150    // against the management_surface_manifest_without_concurrency golden, not
5151    // recalled (the externally-tagged guess was this function's first bug).
5152    provides.iter().any(|role| {
5153        role.get("role").and_then(serde_json::Value::as_str) == Some("management_surface")
5154            && role.get("concurrency").is_none()
5155    })
5156}
5157
5158fn negotiate_version(peer_version: u8) -> Result<u8, String> {
5159    if peer_version != PROTOCOL_VERSION {
5160        return Err(format!(
5161            "protocol_ver {peer_version} is unsupported; this daemon requires exactly {PROTOCOL_VERSION}"
5162        ));
5163    }
5164    Ok(PROTOCOL_VERSION)
5165}
5166
5167fn pong(frame: &Frame) -> Result<Frame, RouterError> {
5168    Frame::build_with_version(
5169        response_version(frame),
5170        FrameType::Pong,
5171        frame.header.flags,
5172        0,
5173        0,
5174        frame.header.corr,
5175        Vec::new(),
5176    )
5177    .map_err(RouterError::FrameBuild)
5178}
5179
5180fn control_error_frame(
5181    frame: &Frame,
5182    code: &'static str,
5183    message: impl Into<String>,
5184) -> Result<Frame, RouterError> {
5185    control_error_body_frame(
5186        frame,
5187        ErrorBody {
5188            code: code.to_string(),
5189            message: message.into(),
5190            detail: None,
5191        },
5192    )
5193}
5194
5195fn control_error_body_frame(frame: &Frame, error: ErrorBody) -> Result<Frame, RouterError> {
5196    let body = serde_json::to_vec(&error).map_err(|err| {
5197        RouterError::backend(
5198            0,
5199            frame.header.corr,
5200            format!("failed to encode control ERROR: {err}"),
5201        )
5202    })?;
5203
5204    Frame::build_with_version(
5205        response_version(frame),
5206        FrameType::Error,
5207        control_flags(),
5208        0,
5209        0,
5210        frame.header.corr,
5211        body,
5212    )
5213    .map_err(RouterError::FrameBuild)
5214}
5215
5216fn control_response_body_frame<T: Serialize>(
5217    frame: &Frame,
5218    reply: &T,
5219    label: &'static str,
5220) -> Result<Frame, RouterError> {
5221    let body = serde_json::to_vec(reply).map_err(|err| {
5222        RouterError::backend(
5223            0,
5224            frame.header.corr,
5225            format!("failed to encode {label}: {err}"),
5226        )
5227    })?;
5228
5229    Frame::build_with_version(
5230        response_version(frame),
5231        FrameType::Response,
5232        control_flags(),
5233        0,
5234        0,
5235        frame.header.corr,
5236        body,
5237    )
5238    .map_err(RouterError::FrameBuild)
5239}
5240
5241/// Map a forwarding failure to the wire code a client sees.
5242///
5243/// The code is not a label: clients BRANCH on it. Both SDKs decide "retry in
5244/// place" with `subc_protocol::error_codes::is_retryable_route_open`, so a code
5245/// chosen here decides whether a caller retries or gives up.
5246///
5247/// That makes attribution the load-bearing property, not merely having a code. A
5248/// permanent fault published as a retryable one produces a fleet-wide retry storm
5249/// against something that can never recover; a transient fault published as
5250/// permanent gives up on work that would have succeeded. Both look correct in a
5251/// log, which is why `retryability_of_forwarding_codes_matches_the_failure` pins
5252/// the mapping per variant rather than merely asserting that some code exists.
5253///
5254/// That fence partitions by RETRYABILITY, which is coarser than identity: swapping
5255/// two codes on the same side of the boundary passes it. Measured rather than
5256/// assumed — `NoModuleConnection` re-pointed at `module_reloading` is caught only
5257/// by `supervision_only_module_health_probe_does_not_enable_route_open_and_cleans_up`,
5258/// a test named for something else that happens to assert the string.
5259///
5260/// That accidental coverage is deliberately left alone rather than promoted to a
5261/// named test, because it guards a property this function does not promise.
5262/// Checked at source: every consumer branches on the RETRYABLE SET and none on a
5263/// specific code within a class, so identity is free to change and only the
5264/// partition is a contract. Splitting it out would assert a guarantee nothing
5265/// depends on — and a suite that promises more than the code does is the harder
5266/// thing to correct later, because the next reader cannot tell which assertions
5267/// are load-bearing.
5268///
5269/// Pin identity here the moment a consumer branches on a specific code.
5270fn forwarding_error_code(err: &ForwardingError) -> &'static str {
5271    match err {
5272        ForwardingError::NoModuleConnection => "target_unavailable",
5273        ForwardingError::ModuleReloading { .. } => "module_reloading",
5274        ForwardingError::ClientRouteChannelExhausted { .. }
5275        | ForwardingError::ModuleRouteChannelExhausted { .. } => "route_limit",
5276        ForwardingError::StaleModuleEndpoint
5277        | ForwardingError::UnknownReservation { .. }
5278        | ForwardingError::ConnectionClosing { .. }
5279        | ForwardingError::ClientEgressClosed { .. }
5280        | ForwardingError::ModuleEgressUnavailable { .. } => "target_unavailable",
5281        // Only a swap candidate's registration can produce this, and it means
5282        // exactly what a second active HELLO for a live id means.
5283        ForwardingError::CandidateSlotOccupied { .. } => "duplicate_module_id",
5284        ForwardingError::RelayCorrelationExhausted
5285        | ForwardingError::RouteOpenBuild(_)
5286        | ForwardingError::Poisoned => "forwarding_error",
5287    }
5288}
5289
5290fn response_version(frame: &Frame) -> u8 {
5291    if (MIN_SUPPORTED_VERSION..=PROTOCOL_VERSION).contains(&frame.header.ver) {
5292        frame.header.ver
5293    } else {
5294        PROTOCOL_VERSION
5295    }
5296}
5297
5298fn control_flags() -> Flags {
5299    Flags::new(false, Priority::Passive, false)
5300}
5301
5302fn send_goodbye_target_best_effort(target: &GoodbyeTarget, context: &str) {
5303    let Ok(frame) = Frame::build_with_version(
5304        target.negotiated_ver,
5305        FrameType::Goodbye,
5306        control_flags(),
5307        target.channel,
5308        target.epoch,
5309        0,
5310        Vec::new(),
5311    ) else {
5312        return;
5313    };
5314    if let Err(err) = target.sink.try_send(frame) {
5315        warn!(
5316            route_channel = target.channel,
5317            route_epoch = target.epoch,
5318            error = %err,
5319            %context,
5320            "route GOODBYE dropped under backpressure"
5321        );
5322    }
5323}
5324
5325pub(crate) fn send_route_control_pushes(
5326    forwarding: &ForwardingTable,
5327    routes: Vec<EndpointRoute>,
5328    push: ClientControlPush,
5329) {
5330    let body = match serde_json::to_vec(&push) {
5331        Ok(body) => body,
5332        Err(err) => {
5333            warn!(error = %err, "failed to serialize route lifecycle control PUSH");
5334            return;
5335        }
5336    };
5337    let mut targets = Vec::new();
5338    for route in routes {
5339        let target = route.goodbye_target;
5340        if let Some(existing) = targets
5341            .iter()
5342            .find(|existing: &&GoodbyeTarget| existing.connection_id == target.connection_id)
5343        {
5344            debug_assert_eq!(
5345                existing.negotiated_ver, target.negotiated_ver,
5346                "one connection cannot negotiate multiple frame versions"
5347            );
5348            continue;
5349        }
5350        targets.push(target);
5351    }
5352    for target in targets {
5353        let frame = match Frame::build_with_version(
5354            target.negotiated_ver,
5355            FrameType::Push,
5356            control_flags(),
5357            0,
5358            0,
5359            0,
5360            body.clone(),
5361        ) {
5362            Ok(frame) => frame,
5363            Err(err) => {
5364                warn!(
5365                    route_channel = target.channel,
5366                    error = %err,
5367                    "failed to build route lifecycle control PUSH frame"
5368                );
5369                continue;
5370            }
5371        };
5372        if let Err(err) = target.sink.try_send(frame) {
5373            if target.close_on_delivery_failure() {
5374                warn!(
5375                    target_connection_id = target.connection_id.get(),
5376                    route_channel = target.channel,
5377                    error = %err,
5378                    "route lifecycle control PUSH was not delivered to client; closing target connection"
5379                );
5380                let _ = forwarding.escalate_client_delivery_failure(
5381                    target.connection_id,
5382                    target.channel,
5383                    target.epoch,
5384                    CloseReason::new(
5385                        "route_lifecycle_push_delivery_failed",
5386                        format!(
5387                            "failed to enqueue route lifecycle control PUSH for channel {}: {err}",
5388                            target.channel
5389                        ),
5390                    ),
5391                    crate::forwarding::UndeliveredFrame {
5392                        module_id: target.module_id.as_deref(),
5393                        sink: &target.sink,
5394                    },
5395                );
5396            }
5397        }
5398    }
5399}
5400
5401#[cfg(test)]
5402mod tests {
5403    use std::{
5404        collections::BTreeMap,
5405        fmt,
5406        path::PathBuf,
5407        sync::{Arc, Mutex},
5408        time::Duration,
5409    };
5410
5411    use serde_json::{json, Value};
5412    use subc_protocol::{
5413        manifest::{
5414            Concurrency, ExecutionMode, IdentityScope, ManagementOperation,
5415            ManagementOperationKind, ObservabilityKind, ObservabilitySurface, ProviderRole, Tool,
5416        },
5417        session::HealthStatus,
5418        FrameType,
5419    };
5420
5421    use super::*;
5422    use crate::{
5423        forwarding::{DataRoute, DataRouteState},
5424        registry::ChannelState,
5425        router::FrameSink,
5426        stderr_tail::DEFAULT_MAX_LINE_BYTES,
5427        supervise::{ModuleSpec, ModuleState, RestartPolicy, Supervisor, SupervisorHandle},
5428        test_support::TestTempDir,
5429        RouteCtx, Router,
5430    };
5431    use tokio::{
5432        sync::mpsc,
5433        time::{sleep, Instant},
5434    };
5435    use tracing::{
5436        field::{Field, Visit},
5437        Event, Subscriber,
5438    };
5439    use tracing_subscriber::{layer::Context, prelude::*, Layer};
5440
5441    /// Locates the `fake-aft-stub` binary from a `src/lib.rs` unit test.
5442    ///
5443    /// `CARGO_BIN_EXE_*` (compile-time `env!` and runtime `std::env::var` alike)
5444    /// is only populated for `tests/*.rs` integration test binaries -- this file
5445    /// compiles as part of the library target, which gets neither. This test's
5446    /// own executable path is `<target-dir>/<profile>/deps/subc_core-<hash>`,
5447    /// and the sibling binary lives two directories up at
5448    /// `<target-dir>/<profile>/fake-aft-stub`.
5449    ///
5450    /// THE BINARY IS NOT ALWAYS THERE, and the existence check below is why.
5451    /// `cargo test -p subc-core` builds every target including `[[bin]]`, so the
5452    /// stub is on disk; `cargo test -p subc-core --lib` builds ONLY the library
5453    /// test and leaves the stub unbuilt. A bare spawn then fails with a raw
5454    /// `NotFound`, which reads as a broken test rather than an unbuilt
5455    /// dependency -- so state the cause and the remedy instead. Deliberately a
5456    /// panic and not a silent skip: a test that quietly passes when it could not
5457    /// run is worse than one that fails, because it reports health it never
5458    /// verified.
5459    fn fake_aft_stub_path() -> PathBuf {
5460        let mut path = std::env::current_exe().expect("current_exe available in tests");
5461        path.pop(); // .../deps/
5462        path.pop(); // .../<profile>/
5463        path.push(if cfg!(windows) {
5464            "fake-aft-stub.exe"
5465        } else {
5466            "fake-aft-stub"
5467        });
5468        assert!(
5469            path.exists(),
5470            "fake-aft-stub not built at {}: run `cargo test -p subc-core` (which builds \
5471             [[bin]] targets) rather than `cargo test -p subc-core --lib` (which does not)",
5472            path.display()
5473        );
5474        path
5475    }
5476
5477    /// Whether clients retry `code` in place: the predicate itself, never a copy
5478    /// of its set. A copied list breaks silently when a code is added to or
5479    /// removed from the real one, and a stale copy here would let exactly the
5480    /// failure this test exists to catch pass.
5481    fn client_retries(code: &str) -> bool {
5482        subc_protocol::error_codes::is_retryable_route_open(code)
5483    }
5484
5485    /// A code is not a label — clients branch on it, so publishing the wrong KIND
5486    /// of failure is worse than publishing none. A permanent fault dressed as
5487    /// retryable makes every client in the fleet retry forever against something
5488    /// that cannot recover; a transient fault dressed as permanent abandons work
5489    /// that would have succeeded.
5490    ///
5491    /// Asserting "a code exists" cannot catch either, because the string is free
5492    /// to say anything. This enumerates every variant and pins which side of the
5493    /// retry boundary it lands on, so a new variant must be classified here
5494    /// deliberately rather than inheriting whichever arm it was appended to.
5495    #[test]
5496    fn retryability_of_forwarding_codes_matches_the_failure() {
5497        // Transient by nature: the target is booting, reloading, or its endpoint
5498        // was swapped mid-flight. Retrying is how these resolve.
5499        let transient = [
5500            ForwardingError::NoModuleConnection,
5501            ForwardingError::ModuleReloading {
5502                module_id: "m".into(),
5503            },
5504            ForwardingError::StaleModuleEndpoint,
5505            ForwardingError::UnknownReservation {
5506                client_channel: 1,
5507                module_channel: 1,
5508            },
5509            ForwardingError::ConnectionClosing {
5510                connection_id: ConnectionId::new(1),
5511            },
5512            ForwardingError::ClientEgressClosed {
5513                connection_id: ConnectionId::new(1),
5514            },
5515            ForwardingError::ModuleEgressUnavailable {
5516                connection_id: ConnectionId::new(1),
5517            },
5518        ];
5519        for err in transient {
5520            let code = forwarding_error_code(&err);
5521            assert!(
5522                client_retries(code),
5523                "{err:?} is transient but publishes {code:?}, which clients treat as permanent"
5524            );
5525        }
5526
5527        // Not fixed by retrying. Channel and correlation exhaustion need the
5528        // caller to close routes, and a poisoned lock is a daemon that cannot
5529        // recover at all — the worst thing to advertise as retryable, since every
5530        // client would storm a daemon that will never answer.
5531        let permanent = [
5532            ForwardingError::ClientRouteChannelExhausted {
5533                connection_id: ConnectionId::new(1),
5534            },
5535            ForwardingError::ModuleRouteChannelExhausted {
5536                endpoint: ModuleEndpointId {
5537                    connection_id: ConnectionId::new(1),
5538                    generation: 1,
5539                },
5540            },
5541            ForwardingError::RelayCorrelationExhausted,
5542            ForwardingError::RouteOpenBuild("x".into()),
5543            ForwardingError::Poisoned,
5544        ];
5545        for err in permanent {
5546            let code = forwarding_error_code(&err);
5547            assert!(
5548                !client_retries(code),
5549                "{err:?} cannot be fixed by retrying but publishes {code:?}, which clients retry"
5550            );
5551        }
5552    }
5553
5554    /// The principal is the daemon's answer to "who is calling", and modules
5555    /// branch on it: aft gates bash on it, cerebellum gates browser control,
5556    /// plexus gates connector invocation. So a stamp is an authorization input in
5557    /// another process, not a label — and both possible answers SUCCEED, which is
5558    /// what makes a wrong one quiet. An unattested caller stamped `Reserved` hands
5559    /// first-party capability to something that never proved it; a supervised one
5560    /// stamped `Direct` silently strips a module of capability it is entitled to.
5561    ///
5562    /// Neither shows up in a test that only checks the bind succeeded. Before this
5563    /// test the only coverage was accidental —
5564    /// `route_open_round_trip_via_tagged_shape_forwards_through_stub` asserts the
5565    /// stamped principal on its way past, so narrowing that wire-shape test to its
5566    /// stated subject would have deleted the last assertion on this value. It
5567    /// still asserts the stamp, which is now redundancy rather than the only
5568    /// guard: both fail under the same mutation, and this one names the reason.
5569    /// SCOPE: this handler's supervisor has spawned nothing, so
5570    /// `spawned_consumer_authorized` can only ever return false and the GRANT arm
5571    /// is unreachable here. Both assertions below are refusals, and a mutant that
5572    /// refuses everything would satisfy them.
5573    ///
5574    /// The grant side is covered where a real nonce exists: `tests/forwarding.rs`
5575    /// spawns a supervised consumer, reads its live nonce, and asserts the module
5576    /// observed `principal.kind == "reserved"` carrying that module_id — verified
5577    /// at source rather than assumed, since a citation is a claim about another
5578    /// file and ages like one. Recorded because a harness that structurally
5579    /// cannot reach an arm reports "none" for that arm identically to one that
5580    /// covers it and found nothing.
5581    #[tokio::test]
5582    async fn an_unattested_caller_is_never_stamped_as_a_supervised_module() {
5583        let handler = ControlHandler::default();
5584        let frame =
5585            Frame::build(FrameType::Request, control_flags(), 0, 0, 900, Vec::new()).unwrap();
5586
5587        // Absent consumer_identity is the ordinary case: a human at a terminal, or
5588        // any process holding the connection file. Nothing was proved, so nothing
5589        // may be granted beyond the unattested floor.
5590        let stamped = handler.route_open_principal(&frame, None).unwrap().unwrap();
5591        assert_eq!(
5592            stamped,
5593            Principal::Direct,
5594            "a caller that proved nothing must not be stamped as a supervised module"
5595        );
5596
5597        // A claimed module_id with a nonce no supervised child was given is a
5598        // forgery attempt, not a weaker caller: it must be REFUSED rather than
5599        // quietly demoted to Direct, or an impersonation attempt looks identical
5600        // to an ordinary unattested connection.
5601        let forged = handler
5602            .route_open_principal(
5603                &frame,
5604                Some(ConsumerIdentity {
5605                    module_id: "aft".to_string(),
5606                    launch_nonce: "not-a-real-nonce".to_string(),
5607                }),
5608            )
5609            .unwrap();
5610        let refusal = forged.expect_err("an unmatched launch nonce must not yield a principal");
5611        assert_eq!(parse_error(&refusal)["code"], "bad_consumer_identity");
5612    }
5613
5614    /// The test above hands `route_open_principal` an identity it built itself,
5615    /// which proves the stamping rule and nothing about where the identity comes
5616    /// from. The real producer is a wire body, and the two are joined by a serde
5617    /// field name that nothing else asserts.
5618    ///
5619    /// That join fails quietly in one specific way: an unrecognised key is simply
5620    /// absent after parsing, so a renamed or misspelled `consumer_identity`
5621    /// yields `None` and every supervised module silently drops to `Direct`.
5622    /// Capability-wise that is the safe direction, but it surfaces far from its
5623    /// cause — as a module mysteriously refused bash — and it would pass every
5624    /// test that builds its own input.
5625    ///
5626    /// Deliberately NOT closed with `deny_unknown_fields`: refusing unknown keys
5627    /// would break every client the moment the daemon gains a field, trading a
5628    /// quiet demotion for a hard refusal on additive change. Asserting the join
5629    /// instead means a rename breaks a test here rather than the fleet.
5630    #[test]
5631    fn a_wire_body_actually_yields_the_consumer_identity_the_daemon_stamps_from() {
5632        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"}}"#;
5633        let parsed: ClientControlRequest = serde_json::from_slice(body).unwrap();
5634        let ClientControlRequest::RouteOpen {
5635            consumer_identity, ..
5636        } = parsed
5637        else {
5638            panic!("route.open body must parse as RouteOpen");
5639        };
5640        assert_eq!(
5641            consumer_identity,
5642            Some(ConsumerIdentity {
5643                module_id: "aft".to_string(),
5644                launch_nonce: "n".to_string(),
5645            }),
5646            "the wire field name must reach the value route_open_principal reads"
5647        );
5648    }
5649
5650    fn manifest(module_id: &str, protocol_ver: u8) -> ModuleManifest {
5651        ModuleManifest::builder(module_id, "0.1.0")
5652            .protocol_ver(protocol_ver)
5653            .provides(vec![ProviderRole::ToolProvider {
5654                tools: vec![Tool {
5655                    name: "read".to_string(),
5656                    description: None,
5657                    execution_mode: ExecutionMode::Pure,
5658                    schema: json!({"type": "object"}),
5659                }],
5660                identity_scope: vec![IdentityScope::Project, IdentityScope::Session],
5661                concurrency: Concurrency::ModuleManaged,
5662                emits_push: true,
5663                sub_supervises: true,
5664            }])
5665            .build()
5666    }
5667
5668    fn hello_frame(module_id: &str, protocol_ver: u8, corr: u64) -> Frame {
5669        hello_frame_with_control_ops(module_id, protocol_ver, corr, None)
5670    }
5671
5672    fn hello_frame_with_control_ops(
5673        module_id: &str,
5674        protocol_ver: u8,
5675        corr: u64,
5676        control_ops: Option<Vec<String>>,
5677    ) -> Frame {
5678        hello_frame_full(module_id, protocol_ver, corr, control_ops, None)
5679    }
5680
5681    fn hello_frame_with_nonce(
5682        module_id: &str,
5683        protocol_ver: u8,
5684        corr: u64,
5685        launch_nonce: Option<&str>,
5686    ) -> Frame {
5687        hello_frame_full(
5688            module_id,
5689            protocol_ver,
5690            corr,
5691            None,
5692            launch_nonce.map(ToOwned::to_owned),
5693        )
5694    }
5695
5696    fn hello_frame_full(
5697        module_id: &str,
5698        protocol_ver: u8,
5699        corr: u64,
5700        control_ops: Option<Vec<String>>,
5701        launch_nonce: Option<String>,
5702    ) -> Frame {
5703        let body = serde_json::to_vec(&ModuleHelloBody {
5704            manifest: manifest(module_id, protocol_ver),
5705            protocol_ver,
5706            control_ops,
5707            launch_nonce,
5708        })
5709        .unwrap();
5710        Frame::build(FrameType::Hello, control_flags(), 0, 0, corr, body).unwrap()
5711    }
5712
5713    fn non_routable_hello_frame_with_control_ops(
5714        module_id: &str,
5715        corr: u64,
5716        control_ops: Option<Vec<String>>,
5717    ) -> Frame {
5718        let mut manifest = manifest(module_id, PROTOCOL_VERSION);
5719        manifest.provides.clear();
5720        let body = serde_json::to_vec(&ModuleHelloBody {
5721            manifest,
5722            protocol_ver: PROTOCOL_VERSION,
5723            control_ops,
5724            launch_nonce: None,
5725        })
5726        .unwrap();
5727        Frame::build(FrameType::Hello, control_flags(), 0, 0, corr, body).unwrap()
5728    }
5729
5730    fn capability_grammar_hello_frame(
5731        capabilities: Value,
5732        runtime_computed: Option<Value>,
5733        corr: u64,
5734    ) -> Frame {
5735        let mut body = serde_json::to_value(ModuleHelloBody {
5736            manifest: manifest("capability-grammar-test", PROTOCOL_VERSION),
5737            protocol_ver: PROTOCOL_VERSION,
5738            control_ops: None,
5739            launch_nonce: None,
5740        })
5741        .expect("HELLO body serializes");
5742        body["manifest"]["capabilities"] = capabilities;
5743        if let Some(runtime_computed) = runtime_computed {
5744            body["runtime_computed"] = runtime_computed;
5745        }
5746        Frame::build(
5747            FrameType::Hello,
5748            control_flags(),
5749            0,
5750            0,
5751            corr,
5752            serde_json::to_vec(&body).expect("HELLO body reserializes"),
5753        )
5754        .expect("HELLO frame builds")
5755    }
5756
5757    fn channel_request(channel: u16, corr: u64) -> Frame {
5758        Frame::build(
5759            FrameType::Request,
5760            Flags::new(true, Priority::Interactive, false),
5761            channel,
5762            0,
5763            corr,
5764            b"opaque".to_vec(),
5765        )
5766        .unwrap()
5767    }
5768
5769    fn route_ctx(
5770        connection_id: ConnectionId,
5771    ) -> (RouteCtx, mpsc::Receiver<crate::router::OutboundFrame>) {
5772        let (tx, rx) = mpsc::channel(8);
5773        (
5774            RouteCtx {
5775                connection_id,
5776                egress: FrameSink::new(tx),
5777            },
5778            rx,
5779        )
5780    }
5781
5782    fn parse_ack(frame: &Frame) -> ModuleHelloAckBody {
5783        serde_json::from_slice(&frame.body).unwrap()
5784    }
5785
5786    /// Register a module over a connection that has a sink and return the
5787    /// HELLO_ACK the module reads. A successful HELLO queues its ack on the
5788    /// module's own sink rather than returning it as a reply, so the ack is
5789    /// taken off `rx` here and whatever the test reads next is what followed it.
5790    async fn hello_via_sink(
5791        handler: &ControlHandler,
5792        ctx: &RouteCtx,
5793        rx: &mut mpsc::Receiver<crate::router::OutboundFrame>,
5794        hello: Frame,
5795    ) -> Frame {
5796        let replies = handler.handle_control_frame(ctx, hello).await.unwrap();
5797        assert!(
5798            replies.is_empty(),
5799            "a registered HELLO replies with nothing; its ack is already queued: {replies:?}"
5800        );
5801        let ack = rx
5802            .try_recv()
5803            .expect("HELLO_ACK is queued on the module sink")
5804            .frame;
5805        assert_eq!(ack.header.ty, FrameType::HelloAck);
5806        ack
5807    }
5808
5809    fn parse_error(frame: &Frame) -> Value {
5810        serde_json::from_slice(&frame.body).unwrap()
5811    }
5812
5813    fn parse_route_poll(frame: &Frame) -> ClientControlResponse {
5814        serde_json::from_slice(&frame.body).unwrap()
5815    }
5816
5817    fn route_poll_frame(corr: u64, kind: PollKind, route_channel: u16) -> Frame {
5818        let body = serde_json::to_vec(&ClientControlRequest::RoutePoll {
5819            route_channel,
5820            route_epoch: 0,
5821            kind,
5822        })
5823        .unwrap();
5824        Frame::build(FrameType::Request, control_flags(), 0, 0, corr, body).unwrap()
5825    }
5826
5827    fn supervisor_health_probe_frame(corr: u64, module_id: &str) -> Frame {
5828        let body = serde_json::to_vec(&ClientControlRequest::SupervisorHealthProbe {
5829            module_id: module_id.to_string(),
5830        })
5831        .unwrap();
5832        Frame::build(FrameType::Request, control_flags(), 0, 0, corr, body).unwrap()
5833    }
5834
5835    fn route_open_frame(corr: u64, module_id: &str, project_root: TestTempDir) -> Frame {
5836        route_open_frame_with_consumer_capabilities(corr, module_id, project_root, None)
5837    }
5838
5839    fn route_open_frame_with_consumer_capabilities(
5840        corr: u64,
5841        module_id: &str,
5842        project_root: TestTempDir,
5843        consumer_capabilities: Option<Vec<String>>,
5844    ) -> Frame {
5845        let body = serde_json::to_vec(&ClientControlRequest::RouteOpen {
5846            target: RouteTarget::ToolProvider {
5847                module_id: module_id.to_string(),
5848            },
5849            identity: BindIdentity::new(
5850                project_root.path().to_path_buf(),
5851                "unit".to_string(),
5852                "session".to_string(),
5853            ),
5854            consumer_identity: None,
5855            consumer_capabilities,
5856            admission_facts: None,
5857        })
5858        .unwrap();
5859        Frame::build(FrameType::Request, control_flags(), 0, 0, corr, body).unwrap()
5860    }
5861
5862    fn route_open_frame_with_admission_facts(
5863        corr: u64,
5864        module_id: &str,
5865        project_root: TestTempDir,
5866        consumer_identity: Option<subc_control::ConsumerIdentity>,
5867        facts: Option<Value>,
5868    ) -> Frame {
5869        let body = serde_json::to_vec(&ClientControlRequest::RouteOpen {
5870            target: RouteTarget::ToolProvider {
5871                module_id: module_id.to_string(),
5872            },
5873            identity: BindIdentity::new(
5874                project_root.path().to_path_buf(),
5875                "unit".to_string(),
5876                format!("session-{corr}"),
5877            ),
5878            consumer_identity,
5879            consumer_capabilities: None,
5880            admission_facts: facts,
5881        })
5882        .unwrap();
5883        Frame::build(FrameType::Request, control_flags(), 0, 0, corr, body).unwrap()
5884    }
5885
5886    #[derive(Clone, Default)]
5887    struct EventCapture {
5888        events: Arc<Mutex<Vec<CapturedEvent>>>,
5889    }
5890
5891    #[derive(Clone, Debug)]
5892    struct CapturedEvent {
5893        target: String,
5894        fields: BTreeMap<String, String>,
5895    }
5896
5897    impl EventCapture {
5898        fn events(&self) -> Vec<CapturedEvent> {
5899            self.events.lock().unwrap().clone()
5900        }
5901    }
5902
5903    impl<S> Layer<S> for EventCapture
5904    where
5905        S: Subscriber,
5906    {
5907        fn on_event(&self, event: &Event<'_>, _context: Context<'_, S>) {
5908            let mut visitor = EventFieldVisitor::default();
5909            event.record(&mut visitor);
5910            self.events.lock().unwrap().push(CapturedEvent {
5911                target: event.metadata().target().to_string(),
5912                fields: visitor.fields,
5913            });
5914        }
5915    }
5916
5917    #[derive(Default)]
5918    struct EventFieldVisitor {
5919        fields: BTreeMap<String, String>,
5920    }
5921
5922    impl Visit for EventFieldVisitor {
5923        fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
5924            self.fields
5925                .insert(field.name().to_string(), format!("{value:?}"));
5926        }
5927    }
5928
5929    fn health_response(corr: u64, status: HealthStatus) -> Frame {
5930        let body = serde_json::to_vec(&ModuleControlResponse::HealthCheck {
5931            status,
5932            detail: Some("warming".to_string()),
5933            metrics: Some(json!({"queue_depth": 3})),
5934        })
5935        .unwrap();
5936        Frame::build(FrameType::Response, control_flags(), 0, 0, corr, body).unwrap()
5937    }
5938
5939    fn route_bind_ack(corr: u64) -> Frame {
5940        let body = serde_json::to_vec(&ModuleControlResponse::RouteBindAck {}).unwrap();
5941        Frame::build(FrameType::Response, control_flags(), 0, 0, corr, body).unwrap()
5942    }
5943
5944    fn unique_project_root(label: &str) -> TestTempDir {
5945        TestTempDir::new(label)
5946    }
5947
5948    fn assert_route_poll_liveness(frame: &Frame, expected_live: bool) {
5949        match parse_route_poll(frame) {
5950            ClientControlResponse::RoutePoll {
5951                status: None,
5952                live: Some(live),
5953                ..
5954            } => assert_eq!(live, expected_live),
5955            other => panic!("unexpected route.poll response: {other:?}"),
5956        }
5957    }
5958
5959    fn bind_liveness_route(
5960        registry: &Registry,
5961        forwarding: &ForwardingTable,
5962        module_id: &str,
5963    ) -> (RouteCtx, u16, u32) {
5964        let module_connection = ConnectionId::new(101);
5965        let client_connection = ConnectionId::new(202);
5966        let registration = registry
5967            .register_with_control_ops(
5968                manifest(module_id, PROTOCOL_VERSION),
5969                PROTOCOL_VERSION,
5970                module_connection,
5971                module_baseline_control_ops(),
5972            )
5973            .unwrap();
5974        let (module_tx, _module_rx) = mpsc::channel(8);
5975        let endpoint = forwarding
5976            .register_module_connection(
5977                module_connection,
5978                module_id.to_string(),
5979                PROTOCOL_VERSION,
5980                manifest_concurrency(&registration.manifest),
5981                FrameSink::new(module_tx),
5982            )
5983            .unwrap();
5984        let (client_ctx, _client_rx) = route_ctx(client_connection);
5985        let pending = forwarding
5986            .begin_route_bind_relay_for_test(
5987                client_connection,
5988                client_ctx.egress.clone(),
5989                1,
5990                module_id,
5991            )
5992            .unwrap();
5993        assert_eq!(pending.endpoint, endpoint);
5994        let route_channel = pending.client_channel;
5995        let route_epoch = pending.client_epoch;
5996        forwarding
5997            .complete_pending_relay(
5998                module_connection,
5999                pending.corr,
6000                RouteBindRelayOutcome::Accepted,
6001            )
6002            .unwrap();
6003        (client_ctx, route_channel, route_epoch)
6004    }
6005
6006    struct FakeProcessLiveness {
6007        live: Option<bool>,
6008    }
6009
6010    impl ModuleProcessLiveness for FakeProcessLiveness {
6011        fn process_live(&self, _module_id: &str) -> Option<bool> {
6012            self.live
6013        }
6014    }
6015
6016    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
6017    async fn supervisor_stderr_tail_converts_a_real_truncated_ring_entry_to_prefix_only_wire_data()
6018    {
6019        let registry = Arc::new(Registry::default());
6020        let supervisor_handle = SupervisorHandle::new();
6021        let supervisor = Supervisor::new(
6022            Arc::clone(&registry),
6023            RestartPolicy::new(1, Duration::from_millis(10)),
6024        )
6025        .with_handle(supervisor_handle.clone());
6026        let source_line = format!("config error: {}", "x".repeat(DEFAULT_MAX_LINE_BYTES));
6027        let module = supervisor
6028            .spawn(ModuleSpec {
6029                module_id: "stderr-tail-wire".to_string(),
6030                program: fake_aft_stub_path(),
6031                args: Vec::new(),
6032                env: vec![
6033                    ("FAKE_AFT_STDERR_LINE".to_string(), source_line.clone()),
6034                    ("FAKE_AFT_EXIT_CODE".to_string(), "1".to_string()),
6035                ],
6036                reserved: false,
6037                reserved_prefixes: Vec::new(),
6038                protocol: ModuleProtocol::Subc,
6039                overlap: Default::default(),
6040            })
6041            .unwrap();
6042
6043        let deadline = Instant::now() + Duration::from_secs(5);
6044        loop {
6045            let tail = module.stderr_tail(None, None);
6046            if tail
6047                .entries
6048                .iter()
6049                .any(|entry| matches!(entry, TailEntry::ProcessStart))
6050                && tail.entries.iter().any(|entry| {
6051                    matches!(
6052                        entry,
6053                        TailEntry::Line {
6054                            truncated: true,
6055                            ..
6056                        }
6057                    )
6058                })
6059            {
6060                break;
6061            }
6062            assert!(
6063                Instant::now() < deadline,
6064                "module did not produce a truncated line and restart boundary: {tail:?}"
6065            );
6066            sleep(Duration::from_millis(10)).await;
6067        }
6068
6069        let handler = ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor_handle);
6070        let request = ClientControlRequest::SupervisorStderrTail {
6071            module_id: "stderr-tail-wire".to_string(),
6072            max_lines: None,
6073            max_bytes: None,
6074        };
6075        let frame = Frame::build(
6076            FrameType::Request,
6077            control_flags(),
6078            0,
6079            0,
6080            1,
6081            serde_json::to_vec(&request).unwrap(),
6082        )
6083        .unwrap();
6084        let (ctx, _egress) = route_ctx(ConnectionId::new(1));
6085        let responses = handler.handle_control_frame(&ctx, frame).await.unwrap();
6086        let ClientControlResponse::SupervisorStderrTail { tail, .. } =
6087            serde_json::from_slice(&responses[0].body).unwrap()
6088        else {
6089            panic!("expected supervisor.stderr_tail response");
6090        };
6091
6092        assert!(
6093            tail.entries
6094                .iter()
6095                .any(|entry| matches!(entry, StderrTailEntry::ProcessStart)),
6096            "the control response lost the restart boundary"
6097        );
6098        let Some(StderrTailEntry::Line { text, truncated }) = tail.entries.iter().find(|entry| {
6099            matches!(
6100                entry,
6101                StderrTailEntry::Line {
6102                    truncated: true,
6103                    ..
6104                }
6105            )
6106        }) else {
6107            panic!("the control response lost the truncated line");
6108        };
6109        assert_eq!(text, &source_line[..DEFAULT_MAX_LINE_BYTES]);
6110        assert!(*truncated);
6111    }
6112
6113    /// `supervisor.terminals` reads journal files. On a single-worker runtime a
6114    /// read done on the worker thread would stall every other task until it
6115    /// finished; the read must run off the worker so this test's own task keeps
6116    /// running while the read is paused.
6117    #[tokio::test(flavor = "current_thread")]
6118    async fn supervisor_terminals_reads_the_journal_off_the_runtime_worker() {
6119        let dir = TestTempDir::new("terminals-off-worker");
6120        let journal_path = dir.join("terminals.jsonl");
6121        let registry = Arc::new(Registry::default());
6122        let supervisor_handle = SupervisorHandle::new();
6123        let supervisor =
6124            Supervisor::new(Arc::clone(&registry), RestartPolicy::new(1, Duration::ZERO))
6125                .with_handle(supervisor_handle.clone())
6126                .with_terminal_journal(journal_path.clone(), "off-worker-daemon".to_string());
6127        let module = supervisor
6128            .spawn(ModuleSpec {
6129                module_id: "terminal-off-worker".to_string(),
6130                program: fake_aft_stub_path(),
6131                args: Vec::new(),
6132                env: vec![("FAKE_AFT_EXIT_CODE".to_string(), "23".to_string())],
6133                reserved: false,
6134                reserved_prefixes: Vec::new(),
6135                protocol: ModuleProtocol::Subc,
6136                overlap: Default::default(),
6137            })
6138            .unwrap();
6139        let deadline = Instant::now() + Duration::from_secs(5);
6140        while module.terminal_history().entries.len() != 2 {
6141            assert!(Instant::now() < deadline, "module did not record two exits");
6142            sleep(Duration::from_millis(10)).await;
6143        }
6144
6145        let (started, release) = crate::terminal_journal::read_pause::install(&journal_path);
6146        let handler =
6147            Arc::new(ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor_handle));
6148        let frame = Frame::build(
6149            FrameType::Request,
6150            control_flags(),
6151            0,
6152            0,
6153            1,
6154            serde_json::to_vec(&ClientControlRequest::SupervisorTerminals {
6155                module_id: "terminal-off-worker".to_string(),
6156            })
6157            .unwrap(),
6158        )
6159        .unwrap();
6160        let (ctx, _egress) = route_ctx(ConnectionId::new(1));
6161        let spawned_at = std::time::Instant::now();
6162        let read = tokio::spawn({
6163            let handler = Arc::clone(&handler);
6164            async move { handler.handle_control_frame(&ctx, frame).await }
6165        });
6166        // Waiting for the pause from a blocking thread keeps this task pending,
6167        // so the runtime's single worker is free to run the read task.
6168        tokio::task::spawn_blocking(move || started.recv_timeout(Duration::from_secs(5)))
6169            .await
6170            .unwrap()
6171            .expect("the history read reached its pause");
6172        let elapsed = spawned_at.elapsed();
6173        assert!(
6174            elapsed < Duration::from_secs(2) && !read.is_finished(),
6175            "this task could not run while the history read was paused \
6176             (resumed after {elapsed:?}, read finished: {})",
6177            read.is_finished()
6178        );
6179
6180        drop(release);
6181        let responses = read.await.unwrap().unwrap();
6182        let response: ClientControlResponse = serde_json::from_slice(&responses[0].body).unwrap();
6183        let ClientControlResponse::SupervisorTerminals { terminals, .. } = response else {
6184            panic!("expected supervisor.terminals response");
6185        };
6186        assert_eq!(terminals.entries.len(), 2);
6187        assert_eq!(terminals.journal_skipped_lines, 0);
6188        assert_eq!(terminals.journal_read_errors, 0);
6189    }
6190
6191    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
6192    async fn supervisor_terminals_golden_is_generated_through_the_real_handler() {
6193        let registry = Arc::new(Registry::default());
6194        let supervisor_handle = SupervisorHandle::new();
6195        let supervisor =
6196            Supervisor::new(Arc::clone(&registry), RestartPolicy::new(1, Duration::ZERO))
6197                .with_handle(supervisor_handle.clone());
6198        let module = supervisor
6199            .spawn(ModuleSpec {
6200                module_id: "terminal-golden".to_string(),
6201                program: fake_aft_stub_path(),
6202                args: Vec::new(),
6203                env: vec![("FAKE_AFT_EXIT_CODE".to_string(), "23".to_string())],
6204                reserved: false,
6205                reserved_prefixes: Vec::new(),
6206                protocol: ModuleProtocol::Subc,
6207                overlap: Default::default(),
6208            })
6209            .unwrap();
6210
6211        let deadline = Instant::now() + Duration::from_secs(5);
6212        while module.terminal_history().entries.len() != 2 {
6213            assert!(
6214                Instant::now() < deadline,
6215                "module did not retain two terminal exits: {:?}",
6216                module.terminal_history()
6217            );
6218            sleep(Duration::from_millis(10)).await;
6219        }
6220
6221        let handler = ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor_handle);
6222        let request = ClientControlRequest::SupervisorTerminals {
6223            module_id: "terminal-golden".to_string(),
6224        };
6225        let frame = Frame::build(
6226            FrameType::Request,
6227            control_flags(),
6228            0,
6229            0,
6230            1,
6231            serde_json::to_vec(&request).unwrap(),
6232        )
6233        .unwrap();
6234        let (ctx, _egress) = route_ctx(ConnectionId::new(1));
6235        let responses = handler.handle_control_frame(&ctx, frame).await.unwrap();
6236        let response: ClientControlResponse = serde_json::from_slice(&responses[0].body).unwrap();
6237        let ClientControlResponse::SupervisorTerminals { terminals, .. } = &response else {
6238            panic!("expected supervisor.terminals response");
6239        };
6240        assert_eq!(terminals.entries.len(), 2);
6241        assert_eq!(terminals.dropped, 0);
6242
6243        let mut rendered = serde_json::to_value(response).unwrap();
6244        // Wall-clock fields are the observation contract, but not stable fixture
6245        // bytes; normalize only them after the real handler has shaped the response.
6246        rendered["daemon_started_at_ms"] = json!(1_700_000_000_000u64);
6247        for (index, entry) in rendered["entries"]
6248            .as_array_mut()
6249            .expect("terminal response entries array")
6250            .iter_mut()
6251            .enumerate()
6252        {
6253            entry["at_ms"] = json!(1_700_000_000_001u64 + index as u64);
6254        }
6255
6256        let golden_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6257            .join("../subc-control/tests/golden/client_control_response_supervisor_terminals.json");
6258        let serialized = serde_json::to_string_pretty(&rendered).unwrap() + "\n";
6259        if std::env::var_os("UPDATE_GOLDEN").is_some() {
6260            std::fs::write(&golden_path, &serialized).unwrap();
6261        }
6262        let expected: Value =
6263            serde_json::from_str(&std::fs::read_to_string(&golden_path).unwrap()).unwrap();
6264        assert_eq!(rendered, expected);
6265    }
6266
6267    #[test]
6268    fn hello_registers_manifest_and_returns_ack() {
6269        let registry = Arc::new(Registry::default());
6270        let handler = ControlHandler::new(Arc::clone(&registry));
6271        let conn = ConnectionId::new(1);
6272
6273        let responses = handler
6274            .handle_control(conn, hello_frame("aft", PROTOCOL_VERSION, 7))
6275            .unwrap();
6276
6277        assert_eq!(responses.len(), 1);
6278        assert_eq!(responses[0].header.ty, FrameType::HelloAck);
6279        assert_eq!(responses[0].header.channel, 0);
6280        assert_eq!(responses[0].header.corr, 7);
6281        let ack = parse_ack(&responses[0]);
6282        assert_eq!(ack.negotiated_ver, PROTOCOL_VERSION);
6283        assert!(ack
6284            .subc_capabilities
6285            .contains(&CAP_MANIFEST_REGISTRATION.to_string()));
6286        assert!(ack.subc_ops.contains(&ops::SUPERVISOR_LIST.to_string()));
6287        assert!(ack.subc_ops.contains(&ops::SUPERVISOR_RESTART.to_string()));
6288        assert!(ack
6289            .subc_ops
6290            .contains(&ops::SUPERVISOR_SET_ENABLED.to_string()));
6291        assert!(ack
6292            .subc_ops
6293            .contains(&MODULE_TO_SUBC_OP_CATALOG_UPDATE.to_string()));
6294
6295        let registration = registry.get_module("aft").unwrap().unwrap();
6296        assert_eq!(registration.negotiated_ver, PROTOCOL_VERSION);
6297        assert_eq!(registration.state, ChannelState::Active);
6298        assert_eq!(registration.connection_id, conn);
6299        assert_eq!(registration.control_ops, module_baseline_control_ops());
6300    }
6301
6302    #[test]
6303    fn capability_grammar_refusals_name_the_field_and_leave_no_catalog_entry() {
6304        let invalid_identifiers = [
6305            ("case_change", "credentials-Provider/v1"),
6306            ("leading_zero", "credentials-provider/v01"),
6307            ("trailing_hyphen", "credentials-provider-/v1"),
6308            ("consecutive_hyphens", "credentials--provider/v1"),
6309            ("uppercase", "Credentials-provider/v1"),
6310            ("missing_v", "credentials-provider/1"),
6311            ("whitespace", "credentials provider/v1"),
6312            ("zero_version", "credentials-provider/v0"),
6313            ("out_of_range_version", "credentials-provider/v4294967296"),
6314            (
6315                "overlength_name",
6316                "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/v1",
6317            ),
6318        ];
6319        let mut cases = invalid_identifiers
6320            .into_iter()
6321            .map(|(name, identifier)| {
6322                (
6323                    format!("identifier_{name}"),
6324                    "capabilities.provides[0]".to_string(),
6325                    identifier.to_string(),
6326                    json!({ "provides": [identifier] }),
6327                    None,
6328                )
6329            })
6330            .collect::<Vec<_>>();
6331        cases.extend([
6332            (
6333                "unknown_need".to_string(),
6334                "capabilities.requires[0].need".to_string(),
6335                "deferred".to_string(),
6336                json!({ "requires": [{ "capability": "credentials-provider/v1", "need": "deferred" }] }),
6337                None,
6338            ),
6339            (
6340                "duplicate_provides".to_string(),
6341                "capabilities.provides[1]".to_string(),
6342                "credentials-provider/v1".to_string(),
6343                json!({ "provides": ["credentials-provider/v1", "credentials-provider/v1"] }),
6344                None,
6345            ),
6346            (
6347                "duplicate_must_never_reach".to_string(),
6348                "capabilities.must_never_reach[1]".to_string(),
6349                "credentials-provider/v1".to_string(),
6350                json!({ "must_never_reach": ["credentials-provider/v1", "credentials-provider/v1"] }),
6351                None,
6352            ),
6353            (
6354                "duplicate_requires_same_need".to_string(),
6355                "capabilities.requires[1]".to_string(),
6356                "credentials-provider/v1".to_string(),
6357                json!({ "requires": [
6358                    { "capability": "credentials-provider/v1", "need": "required" },
6359                    { "capability": "credentials-provider/v1", "need": "required" }
6360                ] }),
6361                None,
6362            ),
6363            (
6364                "duplicate_requires_conflicting_need".to_string(),
6365                "capabilities.requires[1]".to_string(),
6366                "credentials-provider/v1".to_string(),
6367                json!({ "requires": [
6368                    { "capability": "credentials-provider/v1", "need": "required" },
6369                    { "capability": "credentials-provider/v1", "need": "optional" }
6370                ] }),
6371                None,
6372            ),
6373            (
6374                "capabilities_root_pointer".to_string(),
6375                "runtime_computed[0]".to_string(),
6376                "/capabilities".to_string(),
6377                json!({}),
6378                Some(json!(["/capabilities"])),
6379            ),
6380            (
6381                "capabilities_descendant_pointer".to_string(),
6382                "runtime_computed[0]".to_string(),
6383                "/capabilities/provides".to_string(),
6384                json!({}),
6385                Some(json!(["/capabilities/provides"])),
6386            ),
6387            (
6388                "malformed_pointer_without_leading_slash".to_string(),
6389                "runtime_computed[0]".to_string(),
6390                "capabilities".to_string(),
6391                json!({}),
6392                Some(json!(["capabilities"])),
6393            ),
6394            (
6395                "malformed_pointer_escape".to_string(),
6396                "runtime_computed[0]".to_string(),
6397                "/roles/~2/tools".to_string(),
6398                json!({}),
6399                Some(json!(["/roles/~2/tools"])),
6400            ),
6401            (
6402                "unknown_capabilities_field".to_string(),
6403                "capabilities.future".to_string(),
6404                "<array>".to_string(),
6405                json!({ "future": [] }),
6406                None,
6407            ),
6408        ]);
6409
6410        for (index, (name, field, value, capabilities, runtime_computed)) in
6411            cases.into_iter().enumerate()
6412        {
6413            let registry = Arc::new(Registry::default());
6414            let handler = ControlHandler::new(Arc::clone(&registry));
6415            let response = handler
6416                .handle_control(
6417                    ConnectionId::new((index + 1) as u64),
6418                    capability_grammar_hello_frame(
6419                        capabilities,
6420                        runtime_computed,
6421                        index as u64 + 1,
6422                    ),
6423                )
6424                .expect("invalid HELLO returns a refusal");
6425
6426            assert_eq!(response.len(), 1, "{name} must emit one refusal");
6427            let error = parse_error(&response[0]);
6428            assert_eq!(error["code"], "invalid_capability_grammar", "{name}");
6429            let message = error["message"]
6430                .as_str()
6431                .expect("error message is a string");
6432            assert!(
6433                message.contains(&field),
6434                "{name}: field missing from {message}"
6435            );
6436            assert!(
6437                message.contains(&value),
6438                "{name}: value missing from {message}"
6439            );
6440            assert_eq!(
6441                registry
6442                    .active_registration_count()
6443                    .expect("registry reads"),
6444                0,
6445                "{name}: refused HELLO must not create a catalog entry"
6446            );
6447        }
6448    }
6449
6450    #[test]
6451    fn legal_runtime_pointer_and_capabilities_are_mirrored_in_catalog_list() {
6452        let registry = Arc::new(Registry::default());
6453        let handler = ControlHandler::new(Arc::clone(&registry));
6454        let capabilities = json!({
6455            "provides": ["credentials-provider/v1"],
6456            "requires": [{ "capability": "context-transform/v1", "need": "optional" }],
6457            "must_never_reach": ["federation-transport/v1"]
6458        });
6459        let response = handler
6460            .handle_control(
6461                ConnectionId::new(99),
6462                capability_grammar_hello_frame(
6463                    capabilities.clone(),
6464                    Some(json!(["/roles/0/tools"])),
6465                    99,
6466                ),
6467            )
6468            .expect("valid HELLO registers");
6469        assert_eq!(response[0].header.ty, FrameType::HelloAck);
6470
6471        let request = Frame::build(
6472            FrameType::Request,
6473            control_flags(),
6474            0,
6475            0,
6476            100,
6477            serde_json::to_vec(&ClientControlRequest::CatalogList { module_id: None })
6478                .expect("catalog request serializes"),
6479        )
6480        .expect("catalog request frame builds");
6481        let response = handler
6482            .handle_catalog_list(request, None)
6483            .expect("catalog list succeeds");
6484        let ClientControlResponse::CatalogList { modules, .. } =
6485            serde_json::from_slice(&response[0].body).expect("catalog response decodes")
6486        else {
6487            panic!("catalog request must return catalog.list");
6488        };
6489        assert_eq!(modules.len(), 1);
6490        assert_eq!(
6491            serde_json::to_value(&modules[0].capabilities).expect("catalog capabilities serialize"),
6492            capabilities
6493        );
6494    }
6495
6496    #[test]
6497    fn catalog_list_mirrors_management_operation_description() {
6498        let registry = Arc::new(Registry::default());
6499        let handler = ControlHandler::new(Arc::clone(&registry));
6500        let description = "List managed records and return their identifiers and metadata.";
6501        let mut manifest = manifest("described-management", PROTOCOL_VERSION);
6502        manifest.provides = vec![ProviderRole::ManagementSurface {
6503            operations: vec![ManagementOperation {
6504                name: "records.list".to_string(),
6505                kind: ManagementOperationKind::Query,
6506                description: Some(description.to_string()),
6507            }],
6508            config_schema: json!({"type": "object"}),
6509            observability: vec![ObservabilitySurface {
6510                name: "records.stats".to_string(),
6511                kind: ObservabilityKind::Snapshot,
6512            }],
6513            identity_scope: vec![IdentityScope::Project],
6514            concurrency: Concurrency::ModuleManaged,
6515        }];
6516        registry
6517            .register_with_control_ops(
6518                manifest,
6519                PROTOCOL_VERSION,
6520                ConnectionId::new(99),
6521                Vec::new(),
6522            )
6523            .expect("described management manifest registers");
6524
6525        let request = Frame::build(
6526            FrameType::Request,
6527            control_flags(),
6528            0,
6529            0,
6530            100,
6531            serde_json::to_vec(&ClientControlRequest::CatalogList { module_id: None })
6532                .expect("catalog request serializes"),
6533        )
6534        .expect("catalog request frame builds");
6535        let response = handler
6536            .handle_catalog_list(request, None)
6537            .expect("catalog list succeeds");
6538        let body: Value = serde_json::from_slice(&response[0].body).expect("catalog response JSON");
6539        assert_eq!(
6540            body["modules"][0]["roles"][0]["operations"][0]["description"], description,
6541            "catalog.list must preserve the declared operation description verbatim"
6542        );
6543    }
6544
6545    #[test]
6546    fn reserved_capability_refusal_mutation_proof_leaves_no_catalog_entry() {
6547        let registry = Arc::new(Registry::default());
6548        let handler = ControlHandler::new(Arc::clone(&registry)).with_capability_config(
6549            [("vault".to_string(), true), ("squatter".to_string(), true)],
6550            BTreeMap::from([("credentials-provider/v1".to_string(), "vault".to_string())]),
6551        );
6552        let mut squatter = manifest("squatter", PROTOCOL_VERSION);
6553        squatter.capabilities = Some(subc_protocol::manifest::CapabilityDeclarations {
6554            provides: vec!["credentials-provider/v1".to_string()],
6555            requires: Vec::new(),
6556            must_never_reach: Vec::new(),
6557        });
6558        let frame = Frame::build(
6559            FrameType::Hello,
6560            control_flags(),
6561            0,
6562            0,
6563            77,
6564            serde_json::to_vec(&ModuleHelloBody {
6565                manifest: squatter,
6566                protocol_ver: PROTOCOL_VERSION,
6567                control_ops: None,
6568                launch_nonce: None,
6569            })
6570            .expect("HELLO serializes"),
6571        )
6572        .expect("HELLO frame builds");
6573        let response = handler
6574            .handle_control(ConnectionId::new(77), frame)
6575            .expect("reserved claim receives a typed refusal");
6576        assert_eq!(parse_error(&response[0])["code"], "reserved_capability");
6577        assert_eq!(
6578            registry
6579                .active_registration_count()
6580                .expect("registry reads"),
6581            0,
6582            "a reserved capability refusal must not leave a catalog entry"
6583        );
6584    }
6585
6586    #[test]
6587    fn server_describe_surfaces_required_capability_verdict_fields() {
6588        let registry = Arc::new(Registry::default());
6589        let handler = ControlHandler::new(Arc::clone(&registry)).with_capability_config(
6590            [
6591                ("consumer".to_string(), true),
6592                ("provider".to_string(), false),
6593            ],
6594            BTreeMap::new(),
6595        );
6596        let mut consumer = manifest("consumer", PROTOCOL_VERSION);
6597        consumer.capabilities = Some(subc_protocol::manifest::CapabilityDeclarations {
6598            provides: Vec::new(),
6599            requires: vec![subc_protocol::manifest::CapabilityRequirement {
6600                capability: "credentials-provider/v1".to_string(),
6601                need: subc_protocol::manifest::CapabilityNeed::Required,
6602            }],
6603            must_never_reach: Vec::new(),
6604        });
6605        let hello = Frame::build(
6606            FrameType::Hello,
6607            control_flags(),
6608            0,
6609            0,
6610            78,
6611            serde_json::to_vec(&ModuleHelloBody {
6612                manifest: consumer,
6613                protocol_ver: PROTOCOL_VERSION,
6614                control_ops: None,
6615                launch_nonce: None,
6616            })
6617            .expect("HELLO serializes"),
6618        )
6619        .expect("HELLO frame builds");
6620        handler
6621            .handle_control(ConnectionId::new(78), hello)
6622            .expect("consumer registers");
6623        let describe = Frame::build(
6624            FrameType::Request,
6625            control_flags(),
6626            0,
6627            0,
6628            79,
6629            serde_json::to_vec(&ClientControlRequest::ServerDescribe {})
6630                .expect("request serializes"),
6631        )
6632        .expect("describe frame builds");
6633        let response = handler
6634            .handle_server_describe(describe)
6635            .expect("server.describe succeeds");
6636        let rendered: Value = serde_json::from_slice(&response[0].body).expect("response JSON");
6637        let requirement = &rendered["capability_requirements"][0];
6638        assert_eq!(requirement["consumer"], "consumer");
6639        assert_eq!(requirement["verdict"], "never_provided");
6640        assert_eq!(requirement["episode_seq"], 1);
6641        assert_eq!(requirement["config_satisfiable"], false);
6642        assert_eq!(requirement["runtime_available"], false);
6643        assert!(requirement["detail"]
6644            .as_str()
6645            .expect("detail string")
6646            .contains("credentials-provider/v1"));
6647    }
6648
6649    #[test]
6650    fn catalog_list_omits_capabilities_for_legacy_manifest() {
6651        let registry = Arc::new(Registry::default());
6652        let handler = ControlHandler::new(Arc::clone(&registry));
6653        let hello = handler
6654            .handle_control(
6655                ConnectionId::new(101),
6656                hello_frame("legacy-capability-manifest", PROTOCOL_VERSION, 101),
6657            )
6658            .expect("legacy HELLO registers");
6659        assert_eq!(hello[0].header.ty, FrameType::HelloAck);
6660
6661        let request = Frame::build(
6662            FrameType::Request,
6663            control_flags(),
6664            0,
6665            0,
6666            102,
6667            serde_json::to_vec(&ClientControlRequest::CatalogList { module_id: None })
6668                .expect("catalog request serializes"),
6669        )
6670        .expect("catalog request frame builds");
6671        let response = handler
6672            .handle_catalog_list(request, None)
6673            .expect("catalog list succeeds");
6674        let body: Value = serde_json::from_slice(&response[0].body).expect("catalog response JSON");
6675        assert!(
6676            body["modules"][0].get("capabilities").is_none(),
6677            "legacy manifest must retain an absent capabilities field on catalog.list"
6678        );
6679    }
6680
6681    #[test]
6682    fn hello_ack_omits_storage_when_no_storage_config() {
6683        let registry = Arc::new(Registry::default());
6684        let handler = ControlHandler::new(Arc::clone(&registry));
6685        let responses = handler
6686            .handle_control(
6687                ConnectionId::new(1),
6688                hello_frame("aft", PROTOCOL_VERSION, 7),
6689            )
6690            .unwrap();
6691        let ack = parse_ack(&responses[0]);
6692        assert_eq!(ack.storage, None, "no storage config -> no descriptor");
6693        assert_eq!(ack.machine_id, None, "no machine id configured -> no field");
6694    }
6695
6696    #[tokio::test]
6697    async fn hello_ack_and_server_describe_carry_the_configured_machine_id() {
6698        let id = crate::machine_id::MachineId::parse("0123456789abcdef0123456789abcdef").unwrap();
6699        let registry = Arc::new(Registry::default());
6700        let handler = ControlHandler::new(Arc::clone(&registry)).with_machine_id(Some(id.clone()));
6701        let responses = handler
6702            .handle_control(
6703                ConnectionId::new(1),
6704                hello_frame("aft", PROTOCOL_VERSION, 7),
6705            )
6706            .unwrap();
6707        let ack = parse_ack(&responses[0]);
6708        assert_eq!(ack.machine_id.as_deref(), Some(id.as_str()));
6709
6710        let described = handler
6711            .handle_control_frame(
6712                &route_ctx(ConnectionId::new(2)).0,
6713                Frame::build(
6714                    FrameType::Request,
6715                    control_flags(),
6716                    0,
6717                    0,
6718                    9,
6719                    serde_json::to_vec(&ClientControlRequest::ServerDescribe {}).unwrap(),
6720                )
6721                .unwrap(),
6722            )
6723            .await
6724            .unwrap();
6725        let ClientControlResponse::ServerDescribe { machine_id, .. } =
6726            serde_json::from_slice(&described[0].body).unwrap()
6727        else {
6728            panic!("server.describe answered with another shape");
6729        };
6730        assert_eq!(machine_id.as_deref(), Some(id.as_str()));
6731    }
6732
6733    #[test]
6734    fn hello_ack_delivers_resolved_storage_descriptor_per_module() {
6735        // With a central sqlite storage policy, each registering module gets its
6736        // own resolved descriptor in HELLO_ACK, keyed by its module id.
6737        let registry = Arc::new(Registry::default());
6738        let handler = ControlHandler::new(Arc::clone(&registry)).with_storage_config(Some(
6739            crate::daemon_config::StorageConfig::Sqlite {
6740                data_home: std::path::PathBuf::from("/data"),
6741            },
6742        ));
6743
6744        let responses = handler
6745            .handle_control(
6746                ConnectionId::new(1),
6747                hello_frame("alfonso-routing", PROTOCOL_VERSION, 7),
6748            )
6749            .unwrap();
6750        let ack = parse_ack(&responses[0]);
6751        assert_eq!(
6752            ack.storage,
6753            Some(serde_json::json!({
6754                "module_id": "alfonso-routing",
6755                "storage_namespace": "default",
6756                "isolation": { "kind": "module" },
6757                "backend": {
6758                    "backend": "sqlite",
6759                    "path": "/data/cortexkit/alfonso-routing/store.db"
6760                }
6761            })),
6762            "the delivered descriptor is the module's own sqlite store path"
6763        );
6764    }
6765
6766    #[test]
6767    fn hello_control_ops_none_is_baseline_and_guard_rejects_synthetic_gated_op() {
6768        let registry = Arc::new(Registry::default());
6769        let handler = ControlHandler::new(Arc::clone(&registry));
6770        let conn = ConnectionId::new(1);
6771        let responses = handler
6772            .handle_control(
6773                conn,
6774                hello_frame_with_control_ops("aft", PROTOCOL_VERSION, 7, None),
6775            )
6776            .unwrap();
6777        assert_eq!(responses[0].header.ty, FrameType::HelloAck);
6778        let registration = registry.get_module("aft").unwrap().unwrap();
6779        assert_eq!(registration.control_ops, module_baseline_control_ops());
6780
6781        let frame =
6782            Frame::build(FrameType::Request, control_flags(), 0, 0, 77, Vec::new()).unwrap();
6783        assert!(handler
6784            .guard_module_control_op(&frame, "aft", "route.bind")
6785            .unwrap()
6786            .is_none());
6787        let error = handler
6788            .guard_module_control_op(&frame, "aft", "test.synthetic")
6789            .unwrap()
6790            .expect("synthetic ungranted op should be rejected");
6791        assert_eq!(error.header.ty, FrameType::Error);
6792        assert_eq!(parse_error(&error)["code"], "op_not_allowed");
6793    }
6794
6795    #[test]
6796    fn hello_control_ops_some_adds_optional_grants() {
6797        let registry = Arc::new(Registry::default());
6798        let handler = ControlHandler::new(Arc::clone(&registry));
6799        handler
6800            .handle_control(
6801                ConnectionId::new(1),
6802                hello_frame_with_control_ops(
6803                    "aft",
6804                    PROTOCOL_VERSION,
6805                    7,
6806                    Some(vec![
6807                        "future.synthetic".to_string(),
6808                        "route.bind".to_string(),
6809                    ]),
6810                ),
6811            )
6812            .unwrap();
6813        let registration = registry.get_module("aft").unwrap().unwrap();
6814        assert_eq!(
6815            registration.control_ops,
6816            vec![
6817                "route.bind".to_string(),
6818                "route.status".to_string(),
6819                "future.synthetic".to_string(),
6820            ]
6821        );
6822        let frame =
6823            Frame::build(FrameType::Request, control_flags(), 0, 0, 78, Vec::new()).unwrap();
6824        assert!(handler
6825            .guard_module_control_op(&frame, "aft", "future.synthetic")
6826            .unwrap()
6827            .is_none());
6828    }
6829
6830    #[tokio::test]
6831    async fn health_probe_refuses_unadvertised_module_without_sending_frame() {
6832        let registry = Arc::new(Registry::default());
6833        let forwarding = Arc::new(ForwardingTable::default());
6834        let handler =
6835            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
6836        let (module_ctx, mut module_rx) = route_ctx(ConnectionId::new(10));
6837        hello_via_sink(
6838            &handler,
6839            &module_ctx,
6840            &mut module_rx,
6841            hello_frame_with_control_ops("aft", PROTOCOL_VERSION, 7, None),
6842        )
6843        .await;
6844
6845        let (client_ctx, _client_rx) = route_ctx(ConnectionId::new(20));
6846        let responses = handler
6847            .handle_control_frame(&client_ctx, supervisor_health_probe_frame(77, "aft"))
6848            .await
6849            .unwrap();
6850        assert_eq!(responses.len(), 1);
6851        assert_eq!(responses[0].header.ty, FrameType::Error);
6852        assert_eq!(parse_error(&responses[0])["code"], "health_not_advertised");
6853        assert!(module_rx.try_recv().is_err());
6854    }
6855
6856    #[tokio::test]
6857    async fn health_probe_demuxes_while_route_bind_relay_is_in_flight() {
6858        let registry = Arc::new(Registry::default());
6859        let forwarding = Arc::new(ForwardingTable::default());
6860        let handler =
6861            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
6862        let (module_ctx, mut module_rx) = route_ctx(ConnectionId::new(30));
6863        hello_via_sink(
6864            &handler,
6865            &module_ctx,
6866            &mut module_rx,
6867            hello_frame_with_control_ops(
6868                "aft",
6869                PROTOCOL_VERSION,
6870                7,
6871                Some(vec![MODULE_CONTROL_OP_HEALTH_CHECK.to_string()]),
6872            ),
6873        )
6874        .await;
6875
6876        let project_root = unique_project_root("demux");
6877        let (route_client_ctx, mut route_client_rx) = route_ctx(ConnectionId::new(31));
6878        let route_handler = handler.clone();
6879        let route_task = tokio::spawn(async move {
6880            route_handler
6881                .handle_control_frame(
6882                    &route_client_ctx,
6883                    route_open_frame(100, "aft", project_root),
6884                )
6885                .await
6886                .unwrap()
6887        });
6888        let bind_frame = tokio::time::timeout(Duration::from_secs(1), module_rx.recv())
6889            .await
6890            .unwrap()
6891            .unwrap();
6892        assert!(matches!(
6893            serde_json::from_slice::<ModuleControlRequest>(&bind_frame.body).unwrap(),
6894            ModuleControlRequest::RouteBind { .. }
6895        ));
6896
6897        let (health_client_ctx, _health_client_rx) = route_ctx(ConnectionId::new(32));
6898        let health_handler = handler.clone();
6899        let health_task = tokio::spawn(async move {
6900            health_handler
6901                .handle_control_frame(
6902                    &health_client_ctx,
6903                    supervisor_health_probe_frame(101, "aft"),
6904                )
6905                .await
6906                .unwrap()
6907        });
6908        let health_frame = tokio::time::timeout(Duration::from_secs(1), module_rx.recv())
6909            .await
6910            .unwrap()
6911            .unwrap();
6912        assert_eq!(
6913            serde_json::from_slice::<ModuleControlRequest>(&health_frame.body).unwrap(),
6914            ModuleControlRequest::HealthCheck {}
6915        );
6916
6917        handler
6918            .handle_control_frame(
6919                &module_ctx,
6920                health_response(health_frame.header.corr, HealthStatus::Degraded),
6921            )
6922            .await
6923            .unwrap();
6924        let health_response = health_task.await.unwrap();
6925        assert_eq!(health_response.len(), 1);
6926        match serde_json::from_slice::<ClientControlResponse>(&health_response[0].body).unwrap() {
6927            ClientControlResponse::SupervisorHealthProbe {
6928                module_id,
6929                status,
6930                detail,
6931                metrics,
6932            } => {
6933                assert_eq!(module_id, "aft");
6934                assert_eq!(status, HealthStatus::Degraded);
6935                assert_eq!(detail.as_deref(), Some("warming"));
6936                assert_eq!(metrics, Some(json!({"queue_depth": 3})));
6937            }
6938            other => panic!("unexpected health response: {other:?}"),
6939        }
6940
6941        handler
6942            .handle_control_frame(&module_ctx, route_bind_ack(bind_frame.header.corr))
6943            .await
6944            .unwrap();
6945        let route_response = route_task.await.unwrap();
6946        assert!(route_response.is_empty());
6947        let published = route_client_rx.recv().await.unwrap();
6948        assert!(matches!(
6949            serde_json::from_slice::<ClientControlResponse>(&published.body).unwrap(),
6950            ClientControlResponse::RouteOpen { .. }
6951        ));
6952    }
6953
6954    /// Start one `route.open` on `client_connection` and return its still-running
6955    /// handler task together with the `route.bind` the module received for it.
6956    /// The handler blocks until the module answers, so it has to run as a task
6957    /// while the test drives the module side.
6958    async fn relay_route_open(
6959        handler: &ControlHandler,
6960        client_connection: ConnectionId,
6961        client_egress: &FrameSink,
6962        module_rx: &mut mpsc::Receiver<crate::router::OutboundFrame>,
6963        corr: u64,
6964        module_id: &str,
6965        project_root_label: &str,
6966    ) -> (tokio::task::JoinHandle<Vec<Frame>>, Frame) {
6967        let ctx = RouteCtx {
6968            connection_id: client_connection,
6969            egress: client_egress.clone(),
6970        };
6971        let handler = handler.clone();
6972        let project_root = unique_project_root(project_root_label);
6973        let module_id = module_id.to_string();
6974        let dispatch = tracing::dispatcher::get_default(|dispatch| dispatch.clone());
6975        let task = tokio::spawn(async move {
6976            let _guard = tracing::dispatcher::set_default(&dispatch);
6977            handler
6978                .handle_control_frame(&ctx, route_open_frame(corr, &module_id, project_root))
6979                .await
6980                .unwrap()
6981        });
6982        let bind = tokio::time::timeout(Duration::from_secs(2), module_rx.recv())
6983            .await
6984            .expect("module receives the relayed route.bind")
6985            .expect("module egress is open");
6986        (task, bind.frame)
6987    }
6988
6989    fn route_bind_channel(frame: &Frame) -> (u16, u32) {
6990        match serde_json::from_slice::<ModuleControlRequest>(&frame.body).unwrap() {
6991            ModuleControlRequest::RouteBind {
6992                route_channel,
6993                epoch,
6994                ..
6995            } => (route_channel, epoch),
6996            other => panic!("expected a route.bind request, got {other:?}"),
6997        }
6998    }
6999
7000    fn published_route(frame: &Frame) -> (u16, u32) {
7001        match serde_json::from_slice::<ClientControlResponse>(&frame.body).unwrap() {
7002            ClientControlResponse::RouteOpen {
7003                route_channel,
7004                route_epoch,
7005            } => (route_channel, route_epoch),
7006            other => panic!("expected a route.open response, got {other:?}"),
7007        }
7008    }
7009
7010    /// Reproduction of a production outage. A client had `route.open`s in
7011    /// flight to a module and was already marked closing -- its egress had refused a
7012    /// module frame, so the daemon asked its connection to end -- while its sink
7013    /// was still open. When the module acked those binds, the daemon refused to
7014    /// commit a route for a closing client, and that refusal was returned from
7015    /// the MODULE connection's frame handler, where a router error that has no
7016    /// ERROR-frame translation ends the connection. The module saw EOF, exited 0,
7017    /// the supervisor correctly did not respawn a clean exit, and every seat lost
7018    /// its tools for hours -- one client's teardown took down a connection
7019    /// carrying ~170 other routes.
7020    ///
7021    /// The window is opened here by calling the production path that opens it
7022    /// (`escalate_client_delivery_failure`) rather than by closing a socket. The
7023    /// state that matters is "in `closing_connections`, sink still open, relay
7024    /// still pending", and it lasts only from the close request until the
7025    /// connection loop reacts to it; a socket-level test can flood a client into
7026    /// that escalation but cannot pin the module's ack inside the window. Closing
7027    /// the socket instead takes the other path entirely -- connection teardown
7028    /// removes the pending relay under the same lock, so the ack finds nothing.
7029    #[tokio::test]
7030    async fn late_bind_ack_for_a_closing_client_keeps_the_module_connection_serving() {
7031        let registry = Arc::new(Registry::default());
7032        let forwarding = Arc::new(ForwardingTable::default());
7033        let handler =
7034            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
7035
7036        let module_connection = ConnectionId::new(30);
7037        let (module_ctx, mut module_rx) = route_ctx(module_connection);
7038        hello_via_sink(
7039            &handler,
7040            &module_ctx,
7041            &mut module_rx,
7042            hello_frame("aft", PROTOCOL_VERSION, 7),
7043        )
7044        .await;
7045
7046        let dying_client = ConnectionId::new(31);
7047        let (dying_ctx, mut dying_rx) = route_ctx(dying_client);
7048
7049        // A published route on the dying client. The escalation below only marks
7050        // a connection closing for a route it has already published.
7051        let (first_task, first_bind) = relay_route_open(
7052            &handler,
7053            dying_client,
7054            &dying_ctx.egress,
7055            &mut module_rx,
7056            100,
7057            "aft",
7058            "closing-first",
7059        )
7060        .await;
7061        handler
7062            .handle_control_frame(&module_ctx, route_bind_ack(first_bind.header.corr))
7063            .await
7064            .unwrap();
7065        assert!(first_task.await.unwrap().is_empty());
7066        let (first_channel, first_epoch) = published_route(&dying_rx.recv().await.unwrap());
7067
7068        // A second route.open from the same client, relayed and awaiting its ack.
7069        let (second_task, second_bind) = relay_route_open(
7070            &handler,
7071            dying_client,
7072            &dying_ctx.egress,
7073            &mut module_rx,
7074            101,
7075            "aft",
7076            "closing-second",
7077        )
7078        .await;
7079        let (abandoned_channel, abandoned_epoch) = route_bind_channel(&second_bind);
7080
7081        // The window: the client is closing, its sink is still open, and its
7082        // second bind is still pending.
7083        assert!(forwarding
7084            .escalate_client_delivery_failure(
7085                dying_client,
7086                first_channel,
7087                first_epoch,
7088                CloseReason::new(
7089                    "module_to_client_delivery_failed",
7090                    "client egress refused a module frame",
7091                ),
7092                crate::forwarding::UndeliveredFrame {
7093                    module_id: None,
7094                    sink: &dying_ctx.egress,
7095                },
7096            )
7097            .unwrap());
7098        assert!(!dying_ctx.egress.is_closed());
7099
7100        // The frame that used to end the module connection.
7101        let ack = handler
7102            .handle_control_frame(&module_ctx, route_bind_ack(second_bind.header.corr))
7103            .await;
7104        let module_loop_error = ack.as_ref().err().map(ToString::to_string);
7105        if module_loop_error.is_some() {
7106            // What the server's connection loop does with a router error that has
7107            // no ERROR-frame translation: end the connection, which releases the
7108            // module's registration and every route on it.
7109            handler.cleanup_connection(module_connection).unwrap();
7110        }
7111        // Read the module's next frame before opening the co-tenant's route, so
7112        // the GOODBYE assertion below is about THIS ack and not about later
7113        // traffic. `None` means the module was told nothing.
7114        let post_ack_module_frame = tokio::time::timeout(Duration::from_secs(1), module_rx.recv())
7115            .await
7116            .ok()
7117            .flatten();
7118
7119        // 1. The module connection is still registered.
7120        assert!(
7121            registry
7122                .get_module_by_connection(module_connection)
7123                .unwrap()
7124                .is_some(),
7125            "one client's closing connection ended the shared module connection: \
7126             {module_loop_error:?}"
7127        );
7128        // ...and still serving: another client can open and use a route on it.
7129        let cotenant = ConnectionId::new(32);
7130        let (cotenant_ctx, mut cotenant_rx) = route_ctx(cotenant);
7131        let (cotenant_task, cotenant_bind) = relay_route_open(
7132            &handler,
7133            cotenant,
7134            &cotenant_ctx.egress,
7135            &mut module_rx,
7136            102,
7137            "aft",
7138            "closing-cotenant",
7139        )
7140        .await;
7141        handler
7142            .handle_control_frame(&module_ctx, route_bind_ack(cotenant_bind.header.corr))
7143            .await
7144            .unwrap();
7145        assert!(cotenant_task.await.unwrap().is_empty());
7146        let (cotenant_channel, cotenant_epoch) =
7147            published_route(&cotenant_rx.recv().await.unwrap());
7148        assert!(matches!(
7149            forwarding
7150                .lookup_data_route(cotenant, cotenant_channel, cotenant_epoch)
7151                .unwrap(),
7152            DataRoute::Client(DataRouteState::Bound(_))
7153        ));
7154
7155        // 2. The module was told to drop the binding it created for the route
7156        //    that will never be published.
7157        let goodbye = post_ack_module_frame
7158            .expect("module receives a GOODBYE for the abandoned route channel");
7159        assert_eq!(goodbye.header.ty, FrameType::Goodbye);
7160        assert_eq!(goodbye.header.channel, abandoned_channel);
7161        assert_eq!(goodbye.header.epoch, abandoned_epoch);
7162
7163        // 3. The dying client received nothing: no route was ever published to
7164        //    it. Its route.open is answered as unavailable, which the connection
7165        //    loop would write to a socket that is already going away.
7166        assert!(dying_rx.try_recv().is_err());
7167        let second_response = second_task.await.unwrap();
7168        assert_eq!(second_response.len(), 1);
7169        assert_eq!(
7170            parse_error(&second_response[0])["code"],
7171            "target_unavailable"
7172        );
7173    }
7174
7175    /// The fence at the module-loop boundary, stated as its own contract: which
7176    /// forwarding failures are allowed to end the module connection that is being
7177    /// served. A `ConnectionClosing` naming some client is about that client, and
7178    /// a module connection is shared; the same error naming the module's own
7179    /// connection is about this connection and must stay fatal, as must failures
7180    /// that are about the forwarding table itself.
7181    #[test]
7182    fn only_the_modules_own_closing_connection_ends_the_module_loop() {
7183        let handler = ControlHandler::default();
7184        let module_connection = ConnectionId::new(30);
7185        let client_connection = ConnectionId::new(31);
7186
7187        handler
7188            .refuse_to_end_module_connection_for_a_client(
7189                module_connection,
7190                77,
7191                ForwardingError::ConnectionClosing {
7192                    connection_id: client_connection,
7193                },
7194            )
7195            .expect("a closing client must never end the module connection");
7196
7197        assert!(matches!(
7198            handler.refuse_to_end_module_connection_for_a_client(
7199                module_connection,
7200                78,
7201                ForwardingError::ConnectionClosing {
7202                    connection_id: module_connection,
7203                },
7204            ),
7205            Err(RouterError::Forwarding(ForwardingError::ConnectionClosing {
7206                connection_id
7207            })) if connection_id == module_connection
7208        ));
7209        assert!(matches!(
7210            handler.refuse_to_end_module_connection_for_a_client(
7211                module_connection,
7212                79,
7213                ForwardingError::Poisoned,
7214            ),
7215            Err(RouterError::Forwarding(ForwardingError::Poisoned))
7216        ));
7217        assert!(matches!(
7218            handler.refuse_to_end_module_connection_for_a_client(
7219                module_connection,
7220                80,
7221                ForwardingError::StaleModuleEndpoint,
7222            ),
7223            Err(RouterError::Forwarding(
7224                ForwardingError::StaleModuleEndpoint
7225            ))
7226        ));
7227    }
7228
7229    /// The spawn-attestation guard is what stops a connected module from claiming
7230    /// another module's identity and being stamped `Reserved` for it. Every other
7231    /// test that supplies a consumer_identity supplies a CORRECT one, because a
7232    /// correct one is what the rest of the flow needs -- so the guard's rejection
7233    /// branch was never the subject of an assertion, only its acceptance branch.
7234    ///
7235    /// Deleting the guard's EFFECT (granting Reserved unconditionally) leaves the
7236    /// whole subc-core library suite green; only the forwarding integration tests
7237    /// notice, and they notice for unrelated reasons. This test exists so the
7238    /// refusal itself is asserted where the guard lives: it fails if the identity
7239    /// check stops refusing, which is the direction that matters, since a guard
7240    /// that wrongly ACCEPTS is silent while one that wrongly REJECTS is loud.
7241    #[tokio::test]
7242    async fn route_open_refuses_consumer_identity_that_fails_spawn_attestation() {
7243        let registry = Arc::new(Registry::default());
7244        let forwarding = Arc::new(ForwardingTable::default());
7245        let supervisor = SupervisorHandle::new();
7246        supervisor.set_spawn_nonce("fed", "fed-nonce".to_string());
7247        let handler =
7248            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
7249                .with_supervisor(supervisor);
7250
7251        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(90));
7252        hello_via_sink(
7253            &handler,
7254            &target_ctx,
7255            &mut target_rx,
7256            hello_frame("target", PROTOCOL_VERSION, 1),
7257        )
7258        .await;
7259
7260        // A real supervised module id presenting the wrong nonce. This is the
7261        // impersonation case: the attacker knows a privileged module_id, which is
7262        // public, and guesses at the nonce, which is not.
7263        let wrong_nonce = handler
7264            .handle_control_frame(
7265                &route_ctx(ConnectionId::new(91)).0,
7266                route_open_frame_with_admission_facts(
7267                    20,
7268                    "target",
7269                    unique_project_root("admission-facts"),
7270                    Some(subc_control::ConsumerIdentity {
7271                        module_id: "fed".to_string(),
7272                        launch_nonce: "not-the-real-nonce".to_string(),
7273                    }),
7274                    None,
7275                ),
7276            )
7277            .await
7278            .unwrap();
7279        assert_eq!(
7280            parse_error(&wrong_nonce[0])["code"],
7281            "bad_consumer_identity",
7282            "a mismatched launch nonce must be refused, not stamped Reserved"
7283        );
7284
7285        // A module id the supervisor never spawned at all, so no nonce exists to
7286        // compare against. An implementation that treats "no record" as "nothing
7287        // to check" fails open here while passing the case above.
7288        let never_spawned = handler
7289            .handle_control_frame(
7290                &route_ctx(ConnectionId::new(92)).0,
7291                route_open_frame_with_admission_facts(
7292                    21,
7293                    "target",
7294                    unique_project_root("admission-facts"),
7295                    Some(subc_control::ConsumerIdentity {
7296                        module_id: "never-spawned".to_string(),
7297                        launch_nonce: "any-nonce".to_string(),
7298                    }),
7299                    None,
7300                ),
7301            )
7302            .await
7303            .unwrap();
7304        assert_eq!(
7305            parse_error(&never_spawned[0])["code"],
7306            "bad_consumer_identity",
7307            "an unspawned module_id must be refused rather than accepted for lack of a record"
7308        );
7309    }
7310
7311    /// The refusal test above proves the guard says NO. Nothing proved it can say
7312    /// YES, and the difference is not academic: replacing the whole authorization
7313    /// with `false` -- admitting no consumer identity at all, revoking Reserved
7314    /// standing for every supervised module in the fleet -- leaves 110 of the 111
7315    /// library tests GREEN. The one that notices does so by HANGING, because it
7316    /// waits for a bind that can no longer happen.
7317    ///
7318    /// A hang is the weakest signal a suite can produce. In CI it reads as a slow
7319    /// or flaky test, invites a RETRY rather than an investigation, and the retry
7320    /// hangs too and gets blamed on the runner. So a total revocation of the
7321    /// daemon's trust grant would have shipped behind a symptom nobody attributes
7322    /// to code.
7323    ///
7324    /// The bias is structural rather than accidental. A REFUSAL looks like a
7325    /// failure someone writes a test for; a GRANT looks like the happy path. Every
7326    /// binary-outcome guard whose STRICTNESS is the point acquires a refusal-heavy
7327    /// suite for that reason, and this one is the purest case in the daemon.
7328    ///
7329    /// This test asserts the EFFECT rather than the absence of an error: the module
7330    /// receives a RouteBind and it carries `Reserved` naming the attested module.
7331    /// A guard that admitted nobody would produce no bind at all; one that admitted
7332    /// everybody would stamp the wrong principal, which the refusal test catches.
7333    #[tokio::test]
7334    async fn route_open_stamps_reserved_for_a_correctly_attested_consumer() {
7335        let registry = Arc::new(Registry::default());
7336        let forwarding = Arc::new(ForwardingTable::default());
7337        let supervisor = SupervisorHandle::new();
7338        supervisor.set_spawn_nonce("fed", "fed-nonce".to_string());
7339        let handler =
7340            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
7341                .with_supervisor(supervisor);
7342
7343        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(95));
7344        hello_via_sink(
7345            &handler,
7346            &target_ctx,
7347            &mut target_rx,
7348            hello_frame("target", PROTOCOL_VERSION, 1),
7349        )
7350        .await;
7351
7352        let (client_ctx, mut client_rx) = route_ctx(ConnectionId::new(96));
7353        let route_handler = handler.clone();
7354        let route_task = tokio::spawn(async move {
7355            route_handler
7356                .handle_control_frame(
7357                    &client_ctx,
7358                    route_open_frame_with_admission_facts(
7359                        30,
7360                        "target",
7361                        unique_project_root("admission-facts"),
7362                        Some(subc_control::ConsumerIdentity {
7363                            module_id: "fed".to_string(),
7364                            launch_nonce: "fed-nonce".to_string(),
7365                        }),
7366                        None,
7367                    ),
7368                )
7369                .await
7370                .unwrap()
7371        });
7372
7373        // BOUND THE WAIT. The first version of this test recv'd unbounded, and under
7374        // the very mutation it exists to catch -- a guard that admits nobody -- no
7375        // bind is ever sent, so it HUNG rather than failing. That reproduces the
7376        // exact defect being fixed: a total revocation detected only as a stalled
7377        // suite, which reads as flakiness and invites a retry. An acceptance test
7378        // that waits for an effect must bound the wait, or a red becomes a hang.
7379        let bind_frame = tokio::time::timeout(Duration::from_secs(5), target_rx.recv())
7380            .await
7381            .expect("no route.bind within 5s: the consumer-identity guard refused a correctly attested consumer")
7382            .expect("module control channel closed before route.bind");
7383        let bind: ModuleControlRequest = serde_json::from_slice(&bind_frame.body).unwrap();
7384        let ModuleControlRequest::RouteBind { principal, .. } = bind else {
7385            panic!("expected route.bind")
7386        };
7387        assert_eq!(
7388            principal,
7389            Some(Principal::Reserved {
7390                module_id: "fed".to_string()
7391            }),
7392            "a correctly attested consumer must be stamped Reserved for its own id"
7393        );
7394
7395        handler
7396            .handle_control_frame(&target_ctx, route_bind_ack(bind_frame.header.corr))
7397            .await
7398            .unwrap();
7399        assert!(route_task.await.unwrap().is_empty());
7400        assert!(
7401            matches!(
7402                serde_json::from_slice::<ClientControlResponse>(
7403                    &client_rx.recv().await.unwrap().body
7404                )
7405                .unwrap(),
7406                ClientControlResponse::RouteOpen { .. }
7407            ),
7408            "the route must actually open, not merely avoid an error"
7409        );
7410    }
7411
7412    #[tokio::test(start_paused = true)]
7413    async fn supervisor_routes_serializes_live_draining_bindings_from_the_real_handler() {
7414        let registry = Arc::new(Registry::default());
7415        let forwarding = Arc::new(ForwardingTable::default());
7416        let supervisor = SupervisorHandle::new();
7417        supervisor.set_spawn_nonce("fed", "fed-nonce".to_string());
7418        let handler =
7419            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
7420                .with_supervisor(supervisor);
7421
7422        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(101));
7423        hello_via_sink(
7424            &handler,
7425            &target_ctx,
7426            &mut target_rx,
7427            hello_frame("target", PROTOCOL_VERSION, 1),
7428        )
7429        .await;
7430
7431        let (direct_ctx, mut direct_rx) = route_ctx(ConnectionId::new(102));
7432        let direct_handler = handler.clone();
7433        let direct_open = tokio::spawn(async move {
7434            direct_handler
7435                .handle_control_frame(
7436                    &direct_ctx,
7437                    route_open_frame(2, "target", unique_project_root("route-census-direct")),
7438                )
7439                .await
7440                .unwrap()
7441        });
7442        let direct_bind = tokio::time::timeout(Duration::from_secs(5), target_rx.recv())
7443            .await
7444            .expect("no direct route.bind within 5s")
7445            .expect("target control channel closed before direct route.bind");
7446        handler
7447            .handle_control_frame(&target_ctx, route_bind_ack(direct_bind.header.corr))
7448            .await
7449            .unwrap();
7450        assert!(direct_open.await.unwrap().is_empty());
7451        let _ = direct_rx.recv().await.unwrap();
7452
7453        let (reserved_ctx, mut reserved_rx) = route_ctx(ConnectionId::new(103));
7454        let reserved_handler = handler.clone();
7455        let reserved_open = tokio::spawn(async move {
7456            reserved_handler
7457                .handle_control_frame(
7458                    &reserved_ctx,
7459                    route_open_frame_with_admission_facts(
7460                        3,
7461                        "target",
7462                        unique_project_root("admission-facts"),
7463                        Some(ConsumerIdentity {
7464                            module_id: "fed".to_string(),
7465                            launch_nonce: "fed-nonce".to_string(),
7466                        }),
7467                        None,
7468                    ),
7469                )
7470                .await
7471                .unwrap()
7472        });
7473        let reserved_bind = tokio::time::timeout(Duration::from_secs(5), target_rx.recv())
7474            .await
7475            .expect("no reserved route.bind within 5s")
7476            .expect("target control channel closed before reserved route.bind");
7477        handler
7478            .handle_control_frame(&target_ctx, route_bind_ack(reserved_bind.header.corr))
7479            .await
7480            .unwrap();
7481        assert!(reserved_open.await.unwrap().is_empty());
7482        let _ = reserved_rx.recv().await.unwrap();
7483
7484        forwarding
7485            .begin_module_drain("target", subc_control::RouteCloseReason::Reload)
7486            .unwrap();
7487        let (census_ctx, _census_rx) = route_ctx(ConnectionId::new(104));
7488        let census_body = serde_json::to_vec(&ClientControlRequest::SupervisorRoutes {
7489            module_id: Some("target".to_string()),
7490        })
7491        .unwrap();
7492        let census_frame =
7493            Frame::build(FrameType::Request, control_flags(), 0, 0, 4, census_body).unwrap();
7494        let response = handler
7495            .handle_control_frame(&census_ctx, census_frame)
7496            .await
7497            .unwrap()
7498            .pop()
7499            .unwrap();
7500        let actual: Value = serde_json::from_slice(&response.body).unwrap();
7501        let decoded: ClientControlResponse = serde_json::from_value(actual.clone()).unwrap();
7502        assert!(matches!(
7503            decoded,
7504            ClientControlResponse::SupervisorRoutes { .. }
7505        ));
7506        let routes = actual["modules"][0]["routes"].as_array().unwrap();
7507        assert_eq!(routes.len(), 2);
7508        assert!(routes.iter().all(|route| route["draining"] == true));
7509        // The census carries WHY: the reason the drain was begun with, in the
7510        // route.closing vocabulary, on every draining route this drain marked.
7511        assert!(
7512            routes.iter().all(|route| route["drain_reason"] == "reload"),
7513            "draining routes must name the drain's reason: {routes:?}"
7514        );
7515        assert!(routes.iter().any(|route| {
7516            route["consumer"] == serde_json::json!({"kind": "direct", "connection_id": 102})
7517        }));
7518        assert!(routes.iter().any(|route| {
7519            route["consumer"] == serde_json::json!({"kind": "reserved", "module_id": "fed"})
7520        }));
7521
7522        let golden_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7523            .join("../subc-control/tests/golden/client_control_response_supervisor_routes.json");
7524        if std::env::var_os("UPDATE_GOLDEN").is_some() {
7525            std::fs::write(
7526                &golden_path,
7527                format!("{}\n", serde_json::to_string_pretty(&actual).unwrap()),
7528            )
7529            .unwrap();
7530        }
7531        let expected: Value =
7532            serde_json::from_str(&std::fs::read_to_string(golden_path).unwrap()).unwrap();
7533        assert_eq!(actual, expected);
7534    }
7535
7536    async fn query_live_roots(
7537        handler: &ControlHandler,
7538        module_ctx: &RouteCtx,
7539    ) -> ModuleControlResponseToModule {
7540        let body = serde_json::to_vec(&ModuleControlRequestFromModule::LiveRoots {}).unwrap();
7541        let frame = Frame::build(FrameType::Request, control_flags(), 0, 0, 900, body).unwrap();
7542        let response = handler
7543            .handle_control_frame(module_ctx, frame)
7544            .await
7545            .unwrap()
7546            .pop()
7547            .unwrap();
7548        serde_json::from_slice(&response.body).unwrap()
7549    }
7550
7551    #[tokio::test(start_paused = true)]
7552    async fn supervisor_live_roots_root_known_arm_counts_bound_and_pending_from_real_handler() {
7553        let registry = Arc::new(Registry::default());
7554        let forwarding = Arc::new(ForwardingTable::default());
7555        let handler = ControlHandler::with_forwarding(registry, forwarding);
7556        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(301));
7557        hello_via_sink(
7558            &handler,
7559            &target_ctx,
7560            &mut target_rx,
7561            hello_frame("target", PROTOCOL_VERSION, 1),
7562        )
7563        .await;
7564        let root = unique_project_root("live-roots-known");
7565        let path = ProjectRootId::from_path_allowing_missing(root.path())
7566            .unwrap()
7567            .as_path()
7568            .to_path_buf();
7569        let (client_ctx, mut client_rx) = route_ctx(ConnectionId::new(302));
7570        let open_handler = handler.clone();
7571        let opened = tokio::spawn(async move {
7572            open_handler
7573                .handle_control_frame(&client_ctx, route_open_frame(2, "target", root))
7574                .await
7575                .unwrap()
7576        });
7577        let bind = tokio::time::timeout(Duration::from_secs(5), target_rx.recv())
7578            .await
7579            .unwrap()
7580            .unwrap();
7581        handler
7582            .handle_control_frame(&target_ctx, route_bind_ack(bind.header.corr))
7583            .await
7584            .unwrap();
7585        assert!(opened.await.unwrap().is_empty());
7586        let _ = client_rx.recv().await.unwrap();
7587
7588        let root = unique_project_root("live-roots-pending");
7589        let pending_path = ProjectRootId::from_path_allowing_missing(root.path())
7590            .unwrap()
7591            .as_path()
7592            .to_path_buf();
7593        let (client_ctx, _client_rx) = route_ctx(ConnectionId::new(303));
7594        let open_handler = handler.clone();
7595        let pending = tokio::spawn(async move {
7596            open_handler
7597                .handle_control_frame(&client_ctx, route_open_frame(3, "target", root))
7598                .await
7599                .unwrap()
7600        });
7601        let pending_bind = tokio::time::timeout(Duration::from_secs(5), target_rx.recv())
7602            .await
7603            .unwrap()
7604            .unwrap();
7605        let actual = query_live_roots(&handler, &target_ctx).await;
7606        let ModuleControlResponseToModule::LiveRoots {
7607            roots,
7608            unknown_root_bindings,
7609            total_bindings,
7610        } = actual
7611        else {
7612            panic!("expected live roots")
7613        };
7614        assert_eq!(total_bindings, 2, "root-known arm must count live routes");
7615        assert_eq!(unknown_root_bindings, 0);
7616        assert_eq!(
7617            roots.len(),
7618            2,
7619            "root-known arm must retain each canonical root"
7620        );
7621        assert_eq!(
7622            total_bindings,
7623            roots.iter().map(|r| r.bound + r.pending).sum::<u64>() + unknown_root_bindings
7624        );
7625        let counts = roots
7626            .iter()
7627            .map(|root| (root.project_root.clone(), root.bound, root.pending))
7628            .collect::<Vec<_>>();
7629        let mut expected = vec![(path, 1, 0), (pending_path, 0, 1)];
7630        expected.sort_by(|a, b| a.0.cmp(&b.0));
7631        assert_eq!(
7632            counts, expected,
7633            "roots must sort by path and count pending separately"
7634        );
7635        handler
7636            .handle_control_frame(&target_ctx, route_bind_ack(pending_bind.header.corr))
7637            .await
7638            .unwrap();
7639        assert!(pending.await.unwrap().is_empty());
7640    }
7641
7642    #[tokio::test(start_paused = true)]
7643    async fn supervisor_live_roots_unknown_root_arm_is_not_no_bindings() {
7644        let forwarding = Arc::new(ForwardingTable::default());
7645        let handler =
7646            ControlHandler::with_forwarding(Arc::new(Registry::default()), Arc::clone(&forwarding));
7647        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(311));
7648        hello_via_sink(
7649            &handler,
7650            &target_ctx,
7651            &mut target_rx,
7652            hello_frame("target", PROTOCOL_VERSION, 1),
7653        )
7654        .await;
7655        let (client_ctx, _client_rx) = route_ctx(ConnectionId::new(312));
7656        let pending = forwarding
7657            .begin_route_bind_relay_for_test(
7658                client_ctx.connection_id,
7659                client_ctx.egress.clone(),
7660                2,
7661                "target",
7662            )
7663            .unwrap();
7664        forwarding
7665            .complete_pending_relay(
7666                target_ctx.connection_id,
7667                pending.corr,
7668                RouteBindRelayOutcome::Accepted,
7669            )
7670            .unwrap();
7671        let actual = query_live_roots(&handler, &target_ctx).await;
7672        let ModuleControlResponseToModule::LiveRoots {
7673            roots,
7674            unknown_root_bindings,
7675            total_bindings,
7676        } = actual
7677        else {
7678            panic!("expected live roots")
7679        };
7680        assert!(roots.is_empty(), "unknown-root arm must not invent a root");
7681        assert_eq!(
7682            unknown_root_bindings, 1,
7683            "unknown-root arm must not read as no bindings"
7684        );
7685        assert_eq!(total_bindings, 1, "unknown-root arm has a live binding");
7686        assert_eq!(
7687            total_bindings,
7688            roots.iter().map(|r| r.bound + r.pending).sum::<u64>() + unknown_root_bindings
7689        );
7690    }
7691
7692    /// A module reads HELLO_ACK as its first frame and exits on anything else,
7693    /// so the ack has to be on its outbound queue before the module is
7694    /// routable. The connection loop writes a handler's replies only after the
7695    /// handler returns; this test stops in exactly that gap, runs a real
7696    /// route.open from another connection, and only then writes whatever the
7697    /// HELLO handler returned, the way the loop would. If the ack were still a
7698    /// reply, the route.bind request would reach the module first.
7699    #[tokio::test(start_paused = true)]
7700    async fn hello_ack_reaches_the_module_before_a_route_bind_raced_into_the_reply_gap() {
7701        let forwarding = Arc::new(ForwardingTable::default());
7702        let handler =
7703            ControlHandler::with_forwarding(Arc::new(Registry::default()), Arc::clone(&forwarding));
7704        let (module_ctx, mut module_rx) = route_ctx(ConnectionId::new(341));
7705        let replies = handler
7706            .handle_control_frame(&module_ctx, hello_frame("raced", PROTOCOL_VERSION, 7))
7707            .await
7708            .unwrap();
7709        let queued_by_hello = module_rx.len();
7710
7711        let (client_ctx, mut client_rx) = route_ctx(ConnectionId::new(342));
7712        let open_handler = handler.clone();
7713        let open = tokio::spawn(async move {
7714            open_handler
7715                .handle_control_frame(
7716                    &client_ctx,
7717                    route_open_frame(2, "raced", unique_project_root("hello-ack-race")),
7718                )
7719                .await
7720                .unwrap()
7721        });
7722        // Let the route.open run until its route.bind is on the module's queue.
7723        let mut spins = 0;
7724        while module_rx.len() == queued_by_hello {
7725            spins += 1;
7726            assert!(spins < 10_000, "route.open never queued a route.bind");
7727            tokio::task::yield_now().await;
7728        }
7729
7730        // Now the connection loop's half: write the HELLO handler's replies.
7731        for reply in replies {
7732            module_ctx.egress.send(reply).await.unwrap();
7733        }
7734
7735        let first = module_rx.recv().await.unwrap().frame;
7736        assert_eq!(
7737            first.header.ty,
7738            FrameType::HelloAck,
7739            "the first frame a registering module reads must be its HELLO_ACK"
7740        );
7741        assert_eq!(first.header.corr, 7);
7742        let second = module_rx.recv().await.unwrap().frame;
7743        assert_eq!(second.header.ty, FrameType::Request);
7744        assert!(
7745            matches!(
7746                serde_json::from_slice::<ModuleControlRequest>(&second.body).unwrap(),
7747                ModuleControlRequest::RouteBind { .. }
7748            ),
7749            "the route.bind follows the ack"
7750        );
7751        assert!(module_rx.try_recv().is_err(), "nothing else was queued");
7752
7753        handler
7754            .handle_control_frame(&module_ctx, route_bind_ack(second.header.corr))
7755            .await
7756            .unwrap();
7757        assert!(open.await.unwrap().is_empty());
7758        let _ = client_rx.recv().await.unwrap();
7759    }
7760
7761    #[tokio::test(start_paused = true)]
7762    async fn supervisor_live_roots_cross_module_scope_uses_requesting_connection() {
7763        let handler = ControlHandler::with_forwarding(
7764            Arc::new(Registry::default()),
7765            Arc::new(ForwardingTable::default()),
7766        );
7767        let (first_ctx, mut first_rx) = route_ctx(ConnectionId::new(315));
7768        let (second_ctx, mut second_rx) = route_ctx(ConnectionId::new(316));
7769        hello_via_sink(
7770            &handler,
7771            &first_ctx,
7772            &mut first_rx,
7773            hello_frame("first", PROTOCOL_VERSION, 1),
7774        )
7775        .await;
7776        hello_via_sink(
7777            &handler,
7778            &second_ctx,
7779            &mut second_rx,
7780            hello_frame("second", PROTOCOL_VERSION, 2),
7781        )
7782        .await;
7783        let root = unique_project_root("second-only");
7784        let (client_ctx, _client_rx) = route_ctx(ConnectionId::new(317));
7785        let cloned = handler.clone();
7786        let open = tokio::spawn(async move {
7787            cloned
7788                .handle_control_frame(&client_ctx, route_open_frame(3, "second", root))
7789                .await
7790                .unwrap()
7791        });
7792        let bind = tokio::time::timeout(Duration::from_secs(5), second_rx.recv())
7793            .await
7794            .unwrap()
7795            .unwrap();
7796        let first = query_live_roots(&handler, &first_ctx).await;
7797        let second = query_live_roots(&handler, &second_ctx).await;
7798        assert!(
7799            matches!(
7800                first,
7801                ModuleControlResponseToModule::LiveRoots {
7802                    total_bindings: 0,
7803                    ..
7804                }
7805            ),
7806            "cross-module scope must not expose another module's roots"
7807        );
7808        assert!(
7809            matches!(
7810                second,
7811                ModuleControlResponseToModule::LiveRoots {
7812                    total_bindings: 1,
7813                    ..
7814                }
7815            ),
7816            "second module must see its pending route"
7817        );
7818        handler
7819            .handle_control_frame(&second_ctx, route_bind_ack(bind.header.corr))
7820            .await
7821            .unwrap();
7822        assert!(open.await.unwrap().is_empty());
7823    }
7824
7825    #[tokio::test(start_paused = true)]
7826    async fn supervisor_live_roots_no_bindings_arm_is_empty() {
7827        let handler = ControlHandler::with_forwarding(
7828            Arc::new(Registry::default()),
7829            Arc::new(ForwardingTable::default()),
7830        );
7831        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(321));
7832        hello_via_sink(
7833            &handler,
7834            &target_ctx,
7835            &mut target_rx,
7836            hello_frame("target", PROTOCOL_VERSION, 1),
7837        )
7838        .await;
7839        let actual = query_live_roots(&handler, &target_ctx).await;
7840        let ModuleControlResponseToModule::LiveRoots {
7841            roots,
7842            unknown_root_bindings,
7843            total_bindings,
7844        } = actual
7845        else {
7846            panic!("expected live roots")
7847        };
7848        assert!(roots.is_empty());
7849        assert_eq!(unknown_root_bindings, 0);
7850        assert_eq!(total_bindings, 0);
7851        assert_eq!(
7852            total_bindings,
7853            roots.iter().map(|r| r.bound + r.pending).sum::<u64>() + unknown_root_bindings
7854        );
7855    }
7856
7857    /// Read the vendored fed corpus rather than hand-building a package.
7858    ///
7859    /// A hand-built object encodes what the test author believed the carrier
7860    /// emits. These vectors are what it actually emits, and one of them exists
7861    /// specifically to pin OUR side of the seam: its note reads "SUBC relay
7862    /// ignores additive unknown fields at the traversal emit terminus."
7863    fn fed_admission_facts_vectors() -> Vec<(String, Value)> {
7864        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7865            .join("tests/fixtures/fed/admission-facts-emit.jsonl");
7866        let text = std::fs::read_to_string(&path)
7867            .unwrap_or_else(|err| panic!("vendored fed corpus unreadable at {path:?}: {err}"));
7868        let vectors: Vec<(String, Value)> = text
7869            .lines()
7870            .filter(|line| !line.trim().is_empty())
7871            .map(|line| {
7872                let entry: Value = serde_json::from_str(line).expect("corpus line must be JSON");
7873                let id = entry["corpus_id"]
7874                    .as_str()
7875                    .expect("every vector carries a corpus_id")
7876                    .to_string();
7877                (id, entry["package"].clone())
7878            })
7879            .collect();
7880        // Pin the count: a corpus that silently shrinks would take its coverage
7881        // with it, and a suite reading N-1 vectors reports the same clean pass
7882        // as one reading N.
7883        assert_eq!(
7884            vectors.len(),
7885            3,
7886            "vendored fed corpus changed size; re-sync from subc-federation"
7887        );
7888
7889        // Pin what makes the corpus DISCRIMINATING, not just present.
7890        //
7891        // The relay test below takes its expected value from the corpus, so the
7892        // corpus supplies the test's power to detect a lossy relay rather than
7893        // its correctness. A relay that dropped unrecognised fields would still
7894        // be caught -- but only by a package carrying fields it does not know.
7895        // Shrink every package to the handful of keys any implementation would
7896        // recognise and the test keeps passing over an input that can no longer
7897        // fail, which is the same clean green as a corpus that shrank away.
7898        //
7899        // So assert the precondition rather than duplicating the packages here:
7900        // at least one vector must carry a field beyond the small common set.
7901        // That is one claim to maintain instead of nine, and it fails loudly if
7902        // a re-sync ever flattens the corpus.
7903        const COMMONLY_MODELLED: [&str; 3] = ["schema", "verified_class", "org"];
7904        let richest = vectors
7905            .iter()
7906            .filter_map(|(_, package)| package.as_object())
7907            .map(|object| {
7908                object
7909                    .keys()
7910                    .filter(|key| !COMMONLY_MODELLED.contains(&key.as_str()))
7911                    .count()
7912            })
7913            .max()
7914            .unwrap_or(0);
7915        assert!(
7916            richest >= 2,
7917            "vendored corpus no longer carries a package with unmodelled fields, \
7918             so the relay test can no longer distinguish a verbatim relay from a lossy one"
7919        );
7920
7921        vectors
7922    }
7923
7924    /// The relay must carry the carrier's package through BYTE-FOR-BYTE.
7925    ///
7926    /// The gate test below proves the ACCESS RULE (who may send facts, to whom).
7927    /// This proves the PAYLOAD RULE, which the gate cannot: it hand-builds a
7928    /// three-key object, so a relay that quietly dropped fields it did not
7929    /// recognise would satisfy it. These vectors carry nine keys including ones
7930    /// this crate has no type for, so a typed relay fails here and only here.
7931    #[tokio::test]
7932    async fn admission_facts_relay_carries_vendored_packages_verbatim() {
7933        for (corpus_id, package) in fed_admission_facts_vectors() {
7934            let registry = Arc::new(Registry::default());
7935            let forwarding = Arc::new(ForwardingTable::default());
7936            let supervisor = SupervisorHandle::new();
7937            supervisor.set_spawn_nonce("fed", "fed-nonce".to_string());
7938            let handler =
7939                ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
7940                    .with_supervisor(supervisor)
7941                    .with_admission_facts_config(
7942                        Some("fed".to_string()),
7943                        Some(vec!["target".to_string()]),
7944                    );
7945
7946            let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(90));
7947            hello_via_sink(
7948                &handler,
7949                &target_ctx,
7950                &mut target_rx,
7951                hello_frame("target", PROTOCOL_VERSION, 1),
7952            )
7953            .await;
7954
7955            let (client_ctx, _client_rx) = route_ctx(ConnectionId::new(91));
7956            let route_handler = handler.clone();
7957            let expected = package.clone();
7958            let route_task = tokio::spawn(async move {
7959                route_handler
7960                    .handle_control_frame(
7961                        &client_ctx,
7962                        route_open_frame_with_admission_facts(
7963                            20,
7964                            "target",
7965                            unique_project_root("admission-facts"),
7966                            Some(subc_control::ConsumerIdentity {
7967                                module_id: "fed".to_string(),
7968                                launch_nonce: "fed-nonce".to_string(),
7969                            }),
7970                            Some(package),
7971                        ),
7972                    )
7973                    .await
7974                    .unwrap()
7975            });
7976
7977            let bind_frame = target_rx.recv().await.unwrap();
7978            let bind: ModuleControlRequest = serde_json::from_slice(&bind_frame.body).unwrap();
7979            let ModuleControlRequest::RouteBind {
7980                admission_facts, ..
7981            } = bind
7982            else {
7983                panic!("{corpus_id}: expected route.bind")
7984            };
7985            assert_eq!(
7986                admission_facts,
7987                Some(expected),
7988                "{corpus_id}: relay must not add, drop or reshape any field"
7989            );
7990
7991            handler
7992                .handle_control_frame(&target_ctx, route_bind_ack(bind_frame.header.corr))
7993                .await
7994                .unwrap();
7995            route_task.await.unwrap();
7996        }
7997    }
7998
7999    #[tokio::test]
8000    async fn admission_facts_gate_checks_carrier_target_and_precedence() {
8001        let registry = Arc::new(Registry::default());
8002        let forwarding = Arc::new(ForwardingTable::default());
8003        let supervisor = SupervisorHandle::new();
8004        supervisor.set_spawn_nonce("fed", "fed-nonce".to_string());
8005        supervisor.set_spawn_nonce("other", "other-nonce".to_string());
8006        let handler =
8007            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
8008                .with_supervisor(supervisor)
8009                .with_admission_facts_config(
8010                    Some("fed".to_string()),
8011                    Some(vec!["target".to_string()]),
8012                );
8013
8014        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(70));
8015        hello_via_sink(
8016            &handler,
8017            &target_ctx,
8018            &mut target_rx,
8019            hello_frame("target", PROTOCOL_VERSION, 1),
8020        )
8021        .await;
8022        let (other_ctx, mut other_rx) = route_ctx(ConnectionId::new(71));
8023        hello_via_sink(
8024            &handler,
8025            &other_ctx,
8026            &mut other_rx,
8027            hello_frame("other", PROTOCOL_VERSION, 2),
8028        )
8029        .await;
8030
8031        let facts = json!({"schema": 1, "verified_class": "member", "org": "01H"});
8032        let expected_facts = facts.clone();
8033        let (client_ctx, mut client_rx) = route_ctx(ConnectionId::new(72));
8034        let route_handler = handler.clone();
8035        let route_task = tokio::spawn(async move {
8036            route_handler
8037                .handle_control_frame(
8038                    &client_ctx,
8039                    route_open_frame_with_admission_facts(
8040                        10,
8041                        "target",
8042                        unique_project_root("admission-facts"),
8043                        Some(subc_control::ConsumerIdentity {
8044                            module_id: "fed".to_string(),
8045                            launch_nonce: "fed-nonce".to_string(),
8046                        }),
8047                        Some(facts.clone()),
8048                    ),
8049                )
8050                .await
8051                .unwrap()
8052        });
8053        let bind_frame = target_rx.recv().await.unwrap();
8054        let bind: ModuleControlRequest = serde_json::from_slice(&bind_frame.body).unwrap();
8055        let ModuleControlRequest::RouteBind {
8056            admission_facts, ..
8057        } = bind
8058        else {
8059            panic!("expected route.bind")
8060        };
8061        assert_eq!(admission_facts, Some(expected_facts));
8062        handler
8063            .handle_control_frame(&target_ctx, route_bind_ack(bind_frame.header.corr))
8064            .await
8065            .unwrap();
8066        assert!(route_task.await.unwrap().is_empty());
8067        assert!(matches!(
8068            serde_json::from_slice::<ClientControlResponse>(&client_rx.recv().await.unwrap().body)
8069                .unwrap(),
8070            ClientControlResponse::RouteOpen { .. }
8071        ));
8072
8073        let direct = handler
8074            .handle_control_frame(
8075                &route_ctx(ConnectionId::new(73)).0,
8076                route_open_frame_with_admission_facts(
8077                    11,
8078                    "target",
8079                    unique_project_root("admission-facts"),
8080                    None,
8081                    Some(json!({"x": 1})),
8082                ),
8083            )
8084            .await
8085            .unwrap();
8086        assert_eq!(
8087            parse_error(&direct[0])["code"],
8088            "admission_facts_not_permitted"
8089        );
8090
8091        let different_reserved = handler
8092            .handle_control_frame(
8093                &route_ctx(ConnectionId::new(77)).0,
8094                route_open_frame_with_admission_facts(
8095                    15,
8096                    "target",
8097                    unique_project_root("admission-facts"),
8098                    Some(subc_control::ConsumerIdentity {
8099                        module_id: "other".to_string(),
8100                        launch_nonce: "other-nonce".to_string(),
8101                    }),
8102                    Some(json!({"x": 1})),
8103                ),
8104            )
8105            .await
8106            .unwrap();
8107        assert_eq!(
8108            parse_error(&different_reserved[0])["code"],
8109            "admission_facts_not_permitted"
8110        );
8111
8112        let other_target = handler
8113            .handle_control_frame(
8114                &route_ctx(ConnectionId::new(74)).0,
8115                route_open_frame_with_admission_facts(
8116                    12,
8117                    "other",
8118                    unique_project_root("admission-facts"),
8119                    Some(subc_control::ConsumerIdentity {
8120                        module_id: "fed".to_string(),
8121                        launch_nonce: "fed-nonce".to_string(),
8122                    }),
8123                    Some(json!({"x": 1})),
8124                ),
8125            )
8126            .await
8127            .unwrap();
8128        assert_eq!(
8129            parse_error(&other_target[0])["code"],
8130            "admission_facts_target_not_allowed"
8131        );
8132
8133        let nonexistent = handler
8134            .handle_control_frame(
8135                &route_ctx(ConnectionId::new(75)).0,
8136                route_open_frame_with_admission_facts(
8137                    13,
8138                    "missing",
8139                    unique_project_root("admission-facts"),
8140                    None,
8141                    Some(json!({"x": 1})),
8142                ),
8143            )
8144            .await
8145            .unwrap();
8146        assert_eq!(parse_error(&nonexistent[0])["code"], "unknown_module");
8147
8148        let described = handler
8149            .handle_control_frame(
8150                &route_ctx(ConnectionId::new(76)).0,
8151                Frame::build(
8152                    FrameType::Request,
8153                    control_flags(),
8154                    0,
8155                    0,
8156                    14,
8157                    serde_json::to_vec(&ClientControlRequest::ServerDescribe {}).unwrap(),
8158                )
8159                .unwrap(),
8160            )
8161            .await
8162            .unwrap();
8163        let ClientControlResponse::ServerDescribe { capabilities, .. } =
8164            serde_json::from_slice(&described[0].body).unwrap()
8165        else {
8166            panic!("expected server.describe response")
8167        };
8168        assert!(capabilities
8169            .iter()
8170            .any(|cap| cap == "admission_facts_relay_v1"));
8171    }
8172
8173    #[tokio::test]
8174    async fn admission_facts_without_configured_carrier_are_rejected() {
8175        let registry = Arc::new(Registry::default());
8176        let forwarding = Arc::new(ForwardingTable::default());
8177        let handler = ControlHandler::with_forwarding(registry, forwarding);
8178        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(78));
8179        hello_via_sink(
8180            &handler,
8181            &target_ctx,
8182            &mut target_rx,
8183            hello_frame("target", PROTOCOL_VERSION, 1),
8184        )
8185        .await;
8186
8187        let responses = handler
8188            .handle_control_frame(
8189                &route_ctx(ConnectionId::new(79)).0,
8190                route_open_frame_with_admission_facts(
8191                    16,
8192                    "target",
8193                    unique_project_root("admission-facts"),
8194                    None,
8195                    Some(json!({"x": 1})),
8196                ),
8197            )
8198            .await
8199            .unwrap();
8200        assert_eq!(
8201            parse_error(&responses[0])["code"],
8202            "admission_facts_not_permitted"
8203        );
8204    }
8205
8206    #[tokio::test]
8207    async fn route_open_relays_consumer_capabilities_verbatim() {
8208        let registry = Arc::new(Registry::default());
8209        let forwarding = Arc::new(ForwardingTable::default());
8210        let handler =
8211            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
8212        let (module_ctx, mut module_rx) = route_ctx(ConnectionId::new(37));
8213        hello_via_sink(
8214            &handler,
8215            &module_ctx,
8216            &mut module_rx,
8217            hello_frame("aft", PROTOCOL_VERSION, 7),
8218        )
8219        .await;
8220
8221        let expected = vec!["elicitation".to_string(), "roots".to_string()];
8222        let expected_for_request = expected.clone();
8223        let project_root = unique_project_root("consumer-capabilities-present");
8224        let (client_ctx, mut client_rx) = route_ctx(ConnectionId::new(38));
8225        let route_handler = handler.clone();
8226        let route_task = tokio::spawn(async move {
8227            route_handler
8228                .handle_control_frame(
8229                    &client_ctx,
8230                    route_open_frame_with_consumer_capabilities(
8231                        401,
8232                        "aft",
8233                        project_root,
8234                        Some(expected_for_request),
8235                    ),
8236                )
8237                .await
8238                .unwrap()
8239        });
8240        let bind_frame = tokio::time::timeout(Duration::from_secs(1), module_rx.recv())
8241            .await
8242            .unwrap()
8243            .unwrap();
8244        let bind: ModuleControlRequest = serde_json::from_slice(&bind_frame.body).unwrap();
8245        let ModuleControlRequest::RouteBind {
8246            consumer_capabilities,
8247            ..
8248        } = bind
8249        else {
8250            panic!("expected route.bind request, got {bind:?}");
8251        };
8252        assert_eq!(consumer_capabilities, Some(expected.clone()));
8253
8254        handler
8255            .handle_control_frame(&module_ctx, route_bind_ack(bind_frame.header.corr))
8256            .await
8257            .unwrap();
8258        let route_response = route_task.await.unwrap();
8259        assert!(route_response.is_empty());
8260        let published = client_rx.recv().await.unwrap();
8261        assert!(matches!(
8262            serde_json::from_slice::<ClientControlResponse>(&published.body).unwrap(),
8263            ClientControlResponse::RouteOpen { .. }
8264        ));
8265    }
8266
8267    #[tokio::test]
8268    async fn route_open_without_consumer_capabilities_relays_none() {
8269        let registry = Arc::new(Registry::default());
8270        let forwarding = Arc::new(ForwardingTable::default());
8271        let handler =
8272            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
8273        let (module_ctx, mut module_rx) = route_ctx(ConnectionId::new(39));
8274        hello_via_sink(
8275            &handler,
8276            &module_ctx,
8277            &mut module_rx,
8278            hello_frame("aft", PROTOCOL_VERSION, 7),
8279        )
8280        .await;
8281
8282        let project_root = unique_project_root("consumer-capabilities-absent");
8283        let (client_ctx, mut client_rx) = route_ctx(ConnectionId::new(40));
8284        let route_handler = handler.clone();
8285        let route_task = tokio::spawn(async move {
8286            route_handler
8287                .handle_control_frame(&client_ctx, route_open_frame(402, "aft", project_root))
8288                .await
8289                .unwrap()
8290        });
8291        let bind_frame = tokio::time::timeout(Duration::from_secs(1), module_rx.recv())
8292            .await
8293            .unwrap()
8294            .unwrap();
8295        let bind: ModuleControlRequest = serde_json::from_slice(&bind_frame.body).unwrap();
8296        let ModuleControlRequest::RouteBind {
8297            consumer_capabilities,
8298            ..
8299        } = bind
8300        else {
8301            panic!("expected route.bind request, got {bind:?}");
8302        };
8303        assert_eq!(consumer_capabilities, None);
8304
8305        handler
8306            .handle_control_frame(&module_ctx, route_bind_ack(bind_frame.header.corr))
8307            .await
8308            .unwrap();
8309        let route_response = route_task.await.unwrap();
8310        assert!(route_response.is_empty());
8311        let published = client_rx.recv().await.unwrap();
8312        assert!(matches!(
8313            serde_json::from_slice::<ClientControlResponse>(&published.body).unwrap(),
8314            ClientControlResponse::RouteOpen { .. }
8315        ));
8316    }
8317
8318    #[tokio::test]
8319    async fn supervision_only_module_health_probe_does_not_enable_route_open_and_cleans_up() {
8320        let registry = Arc::new(Registry::default());
8321        let forwarding = Arc::new(ForwardingTable::default());
8322        let handler =
8323            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
8324                .with_health_probe_timeout(Duration::from_secs(5));
8325        let (module_ctx, mut module_rx) = route_ctx(ConnectionId::new(35));
8326        hello_via_sink(
8327            &handler,
8328            &module_ctx,
8329            &mut module_rx,
8330            non_routable_hello_frame_with_control_ops(
8331                "mcp",
8332                300,
8333                Some(vec![MODULE_CONTROL_OP_HEALTH_CHECK.to_string()]),
8334            ),
8335        )
8336        .await;
8337        assert!(registry
8338            .get_module("mcp")
8339            .unwrap()
8340            .unwrap()
8341            .manifest
8342            .provides
8343            .is_empty());
8344
8345        let (route_client_ctx, _route_client_rx) = route_ctx(ConnectionId::new(36));
8346        let route_response = handler
8347            .handle_control_frame(
8348                &route_client_ctx,
8349                route_open_frame(301, "mcp", unique_project_root("non-routable-mcp")),
8350            )
8351            .await
8352            .unwrap();
8353        assert_eq!(route_response[0].header.ty, FrameType::Error);
8354        assert_eq!(
8355            parse_error(&route_response[0])["code"],
8356            "target_unavailable"
8357        );
8358        assert!(parse_error(&route_response[0])["message"]
8359            .as_str()
8360            .unwrap()
8361            .contains("does not provide the requested target"));
8362        assert!(module_rx.try_recv().is_err());
8363
8364        let (health_client_ctx, _health_client_rx) = route_ctx(ConnectionId::new(37));
8365        let health_handler = handler.clone();
8366        let health_task = tokio::spawn(async move {
8367            health_handler
8368                .handle_control_frame(
8369                    &health_client_ctx,
8370                    supervisor_health_probe_frame(302, "mcp"),
8371                )
8372                .await
8373                .unwrap()
8374        });
8375        let health_frame = tokio::time::timeout(Duration::from_secs(1), module_rx.recv())
8376            .await
8377            .unwrap()
8378            .unwrap();
8379        assert_eq!(
8380            serde_json::from_slice::<ModuleControlRequest>(&health_frame.body).unwrap(),
8381            ModuleControlRequest::HealthCheck {}
8382        );
8383        handler
8384            .handle_control_frame(
8385                &module_ctx,
8386                health_response(health_frame.header.corr, HealthStatus::Ok),
8387            )
8388            .await
8389            .unwrap();
8390        let health_response = health_task.await.unwrap();
8391        assert_eq!(health_response[0].header.ty, FrameType::Response);
8392        match serde_json::from_slice::<ClientControlResponse>(&health_response[0].body).unwrap() {
8393            ClientControlResponse::SupervisorHealthProbe {
8394                module_id, status, ..
8395            } => {
8396                assert_eq!(module_id, "mcp");
8397                assert_eq!(status, HealthStatus::Ok);
8398            }
8399            other => panic!("unexpected health response: {other:?}"),
8400        }
8401
8402        // Exercise the forwarding cleanup path directly while leaving the registry
8403        // advertisement in place. If cleanup leaves a stale control sink behind,
8404        // the next probe will enqueue onto it and wait for the long probe timeout
8405        // instead of returning an immediate no-connection error.
8406        forwarding
8407            .cleanup_connection(module_ctx.connection_id)
8408            .unwrap();
8409        let (cleanup_probe_ctx, _cleanup_probe_rx) = route_ctx(ConnectionId::new(38));
8410        let cleanup_response = tokio::time::timeout(
8411            Duration::from_millis(200),
8412            handler.handle_control_frame(
8413                &cleanup_probe_ctx,
8414                supervisor_health_probe_frame(303, "mcp"),
8415            ),
8416        )
8417        .await
8418        .expect("probe should fail immediately when the control lane is gone")
8419        .unwrap();
8420        assert_eq!(cleanup_response[0].header.ty, FrameType::Error);
8421        assert_eq!(
8422            parse_error(&cleanup_response[0])["code"],
8423            "target_unavailable"
8424        );
8425        assert!(parse_error(&cleanup_response[0])["message"]
8426            .as_str()
8427            .unwrap()
8428            .contains("no module connection"));
8429
8430        handler
8431            .cleanup_connection(module_ctx.connection_id)
8432            .unwrap();
8433    }
8434
8435    #[tokio::test]
8436    async fn route_open_classifies_unregistered_running_supervised_module_as_warming() {
8437        let registry = Arc::new(Registry::default());
8438        let supervisor_handle = SupervisorHandle::new();
8439        let supervisor =
8440            Supervisor::new(Arc::clone(&registry), RestartPolicy::new(0, Duration::ZERO))
8441                .with_handle(supervisor_handle.clone())
8442                .with_connection_file_path(
8443                    std::env::temp_dir()
8444                        .join(format!("subc-route-open-warming-{}", std::process::id())),
8445                );
8446        let module = supervisor
8447            .supervise_configured(
8448                ModuleSpec {
8449                    module_id: "warming".to_string(),
8450                    program: fake_aft_stub_path(),
8451                    args: Vec::new(),
8452                    env: Vec::new(),
8453                    reserved: false,
8454                    reserved_prefixes: Vec::new(),
8455                    protocol: ModuleProtocol::Subc,
8456                    overlap: Default::default(),
8457                },
8458                true,
8459            )
8460            .unwrap();
8461        assert_eq!(module.state().unwrap(), ModuleState::Running);
8462
8463        let handler = ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor_handle);
8464        let (ctx, _rx) = route_ctx(ConnectionId::new(39));
8465        let response = handler
8466            .handle_control_frame(
8467                &ctx,
8468                route_open_frame(304, "warming", unique_project_root("warming")),
8469            )
8470            .await
8471            .unwrap();
8472        module.stop().await.unwrap();
8473
8474        assert_eq!(response[0].header.ty, FrameType::Error);
8475        let error = parse_error(&response[0]);
8476        assert_eq!(error["code"], "module_warming");
8477        assert!(error["message"]
8478            .as_str()
8479            .unwrap()
8480            .contains("state=running, enabled=true, live=false"));
8481    }
8482
8483    /// One wire code has several senders, so the refusal line names the check
8484    /// that refused. This drives the shared refusal path (every non-supervised
8485    /// refusal goes through `route_open_refusal_frame`) with an unregistered
8486    /// target and requires the branch label on the event.
8487    #[tokio::test]
8488    async fn route_open_refusal_names_the_check_that_refused() {
8489        let handler = ControlHandler::new(Arc::new(Registry::default()));
8490        let capture = EventCapture::default();
8491        let _subscriber =
8492            tracing::subscriber::set_default(tracing_subscriber::registry().with(capture.clone()));
8493        let (ctx, _rx) = route_ctx(ConnectionId::new(95));
8494        let response = handler
8495            .handle_control_frame(
8496                &ctx,
8497                route_open_frame(395, "nobody", unique_project_root("refusal-reason")),
8498            )
8499            .await
8500            .unwrap();
8501
8502        assert_eq!(parse_error(&response[0])["code"], "unknown_module");
8503        let event = capture
8504            .events()
8505            .into_iter()
8506            .find(|event| {
8507                event.target == "control"
8508                    && event.fields.get("code") == Some(&"\"unknown_module\"".to_string())
8509            })
8510            .expect("route.open refusal event");
8511        assert_eq!(
8512            event.fields.get("reason"),
8513            Some(&"\"not_registered\"".to_string())
8514        );
8515    }
8516
8517    #[tokio::test]
8518    async fn route_open_supervised_absence_emits_refusal_fields_and_counts_code() {
8519        let registry = Arc::new(Registry::default());
8520        let supervisor_handle = SupervisorHandle::new();
8521        let supervisor =
8522            Supervisor::new(Arc::clone(&registry), RestartPolicy::new(0, Duration::ZERO))
8523                .with_handle(supervisor_handle.clone())
8524                .with_connection_file_path(std::env::temp_dir().join(format!(
8525                    "subc-route-open-refusal-info-{}",
8526                    std::process::id()
8527                )));
8528        let module = supervisor
8529            .supervise_configured(
8530                ModuleSpec {
8531                    module_id: "warming".to_string(),
8532                    program: fake_aft_stub_path(),
8533                    args: Vec::new(),
8534                    env: Vec::new(),
8535                    reserved: false,
8536                    reserved_prefixes: Vec::new(),
8537                    protocol: ModuleProtocol::Subc,
8538                    overlap: Default::default(),
8539                },
8540                true,
8541            )
8542            .unwrap();
8543        assert_eq!(module.state().unwrap(), ModuleState::Running);
8544
8545        let handler = ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor_handle);
8546        assert!(handler
8547            .counters()
8548            .snapshot()
8549            .get("route_open_refused_by_code")
8550            .is_none());
8551        let capture = EventCapture::default();
8552        let _subscriber =
8553            tracing::subscriber::set_default(tracing_subscriber::registry().with(capture.clone()));
8554        let (ctx, _rx) = route_ctx(ConnectionId::new(94));
8555        let response = handler
8556            .handle_control_frame(
8557                &ctx,
8558                route_open_frame(394, "warming", unique_project_root("refusal-info")),
8559            )
8560            .await
8561            .unwrap();
8562        module.stop().await.unwrap();
8563
8564        assert_eq!(parse_error(&response[0])["code"], "module_warming");
8565        let event = capture
8566            .events()
8567            .into_iter()
8568            .find(|event| {
8569                event.target == "control"
8570                    && event.fields.get("code") == Some(&"\"module_warming\"".to_string())
8571            })
8572            .expect("route.open refusal event");
8573        assert_eq!(
8574            event.fields.get("module_id"),
8575            Some(&"\"warming\"".to_string())
8576        );
8577        assert_eq!(event.fields.get("connection_id"), Some(&"94".to_string()));
8578        assert_eq!(
8579            event.fields.get("reason"),
8580            Some(&"\"supervised_not_registered\"".to_string())
8581        );
8582        assert_eq!(event.fields.get("state"), Some(&"running".to_string()));
8583        assert_eq!(event.fields.get("enabled"), Some(&"true".to_string()));
8584        assert_eq!(event.fields.get("live"), Some(&"false".to_string()));
8585        assert_eq!(
8586            handler.counters().snapshot()["route_open_refused_by_code"],
8587            json!({ "module_warming": 1 })
8588        );
8589    }
8590
8591    #[tokio::test(flavor = "current_thread")]
8592    async fn route_open_unknown_module_escapes_target_module_id() {
8593        let handler = ControlHandler::new(Arc::new(Registry::default()));
8594        let capture = EventCapture::default();
8595        let _subscriber =
8596            tracing::subscriber::set_default(tracing_subscriber::registry().with(capture.clone()));
8597        let hostile_module_id = "\u{1b}]52;c;AAAA\u{07}";
8598        let (ctx, _rx) = route_ctx(ConnectionId::new(95));
8599        let response = handler
8600            .handle_control_frame(
8601                &ctx,
8602                route_open_frame(
8603                    395,
8604                    hostile_module_id,
8605                    unique_project_root("hostile-target-module-id"),
8606                ),
8607            )
8608            .await
8609            .unwrap();
8610
8611        assert_eq!(parse_error(&response[0])["code"], "unknown_module");
8612        let event = capture
8613            .events()
8614            .into_iter()
8615            .find(|event| {
8616                event.target == "control"
8617                    && event.fields.get("code") == Some(&"\"unknown_module\"".to_string())
8618            })
8619            .expect("route.open unknown-module refusal event");
8620        let logged = event.fields.get("module_id").expect("module_id field");
8621        assert!(!logged.bytes().any(|byte| byte < 0x20));
8622        assert_eq!(logged, r#""\u{1b}]52;c;AAAA\u{7}""#);
8623    }
8624
8625    #[tokio::test(flavor = "current_thread")]
8626    async fn route_open_module_rejection_uses_daemon_counter_key() {
8627        let registry = Arc::new(Registry::default());
8628        let forwarding = Arc::new(ForwardingTable::default());
8629        let handler =
8630            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
8631        let module_connection = ConnectionId::new(95);
8632        let (module_ctx, mut module_rx) = route_ctx(module_connection);
8633        hello_via_sink(
8634            &handler,
8635            &module_ctx,
8636            &mut module_rx,
8637            hello_frame("aft", PROTOCOL_VERSION, 395),
8638        )
8639        .await;
8640
8641        let client_connection = ConnectionId::new(96);
8642        let (client_ctx, _client_rx) = route_ctx(client_connection);
8643        let capture = EventCapture::default();
8644        let _subscriber =
8645            tracing::subscriber::set_default(tracing_subscriber::registry().with(capture.clone()));
8646        let (route_task, bind) = relay_route_open(
8647            &handler,
8648            client_connection,
8649            &client_ctx.egress,
8650            &mut module_rx,
8651            396,
8652            "aft",
8653            "hostile-module-code",
8654        )
8655        .await;
8656        let hostile_code = "\u{1b}]52;c;AAAA\u{07}";
8657        let rejection = Frame::build(
8658            FrameType::Error,
8659            control_flags(),
8660            0,
8661            0,
8662            bind.header.corr,
8663            serde_json::to_vec(&ErrorBody::new(hostile_code, "module refused route.bind")).unwrap(),
8664        )
8665        .unwrap();
8666        handler
8667            .handle_control_frame(&module_ctx, rejection)
8668            .await
8669            .unwrap();
8670
8671        let response = route_task.await.unwrap();
8672        assert_eq!(parse_error(&response[0])["code"], hostile_code);
8673        let counters = handler.counters().snapshot();
8674        assert_eq!(
8675            counters["route_open_refused_by_code"],
8676            json!({ "module_rejected": 1 })
8677        );
8678        assert!(counters["route_open_refused_by_code"]
8679            .get(hostile_code)
8680            .is_none());
8681
8682        let event = capture
8683            .events()
8684            .into_iter()
8685            .find(|event| {
8686                event.target == "control"
8687                    && event.fields.get("code") == Some(&"\"module_rejected\"".to_string())
8688            })
8689            .expect("route.open module-rejection refusal event");
8690        let logged = event.fields.get("module_code").expect("module_code field");
8691        assert!(!logged.bytes().any(|byte| byte < 0x20));
8692        assert_eq!(logged, r#""\u{1b}]52;c;AAAA\u{7}""#);
8693    }
8694
8695    #[tokio::test]
8696    async fn route_open_keeps_failed_unregistered_supervised_module_unavailable() {
8697        let registry = Arc::new(Registry::default());
8698        let supervisor_handle = SupervisorHandle::new();
8699        let missing_program = std::env::temp_dir().join(format!(
8700            "subc-route-open-missing-program-{}",
8701            std::process::id()
8702        ));
8703        let supervisor =
8704            Supervisor::new(Arc::clone(&registry), RestartPolicy::new(0, Duration::ZERO))
8705                .with_handle(supervisor_handle.clone());
8706        let module = supervisor
8707            .supervise_configured(
8708                ModuleSpec {
8709                    module_id: "failed".to_string(),
8710                    program: missing_program,
8711                    args: Vec::new(),
8712                    env: Vec::new(),
8713                    reserved: false,
8714                    reserved_prefixes: Vec::new(),
8715                    protocol: ModuleProtocol::Subc,
8716                    overlap: Default::default(),
8717                },
8718                true,
8719            )
8720            .unwrap();
8721        assert_eq!(module.state().unwrap(), ModuleState::Failed);
8722
8723        let handler = ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor_handle);
8724        let (ctx, _rx) = route_ctx(ConnectionId::new(40));
8725        let response = handler
8726            .handle_control_frame(
8727                &ctx,
8728                route_open_frame(305, "failed", unique_project_root("failed")),
8729            )
8730            .await
8731            .unwrap();
8732
8733        assert_eq!(response[0].header.ty, FrameType::Error);
8734        let error = parse_error(&response[0]);
8735        assert_eq!(error["code"], "target_unavailable");
8736        assert!(error["message"]
8737            .as_str()
8738            .unwrap()
8739            .contains("state=failed, enabled=true, live=false"));
8740    }
8741
8742    #[tokio::test]
8743    async fn route_open_role_mismatch_remains_target_unavailable() {
8744        let registry = Arc::new(Registry::default());
8745        let handler = ControlHandler::new(Arc::clone(&registry));
8746        handler
8747            .handle_control(
8748                ConnectionId::new(41),
8749                non_routable_hello_frame_with_control_ops("health-only", 306, None),
8750            )
8751            .unwrap();
8752
8753        let (ctx, _rx) = route_ctx(ConnectionId::new(42));
8754        let response = handler
8755            .handle_control_frame(
8756                &ctx,
8757                route_open_frame(307, "health-only", unique_project_root("role-mismatch")),
8758            )
8759            .await
8760            .unwrap();
8761
8762        assert_eq!(parse_error(&response[0])["code"], "target_unavailable");
8763        assert!(parse_error(&response[0])["message"]
8764            .as_str()
8765            .unwrap()
8766            .contains("does not provide the requested target"));
8767    }
8768
8769    #[tokio::test]
8770    async fn route_open_inactive_registration_remains_target_unavailable() {
8771        let registry = Arc::new(Registry::default());
8772        let handler = ControlHandler::new(Arc::clone(&registry));
8773        handler
8774            .handle_control(
8775                ConnectionId::new(43),
8776                hello_frame("inactive", PROTOCOL_VERSION, 308),
8777            )
8778            .unwrap();
8779        assert!(registry
8780            .set_module_state_for_test("inactive", ChannelState::Closed)
8781            .unwrap());
8782
8783        let (ctx, _rx) = route_ctx(ConnectionId::new(44));
8784        let response = handler
8785            .handle_control_frame(
8786                &ctx,
8787                route_open_frame(309, "inactive", unique_project_root("inactive")),
8788            )
8789            .await
8790            .unwrap();
8791
8792        assert_eq!(parse_error(&response[0])["code"], "target_unavailable");
8793        assert!(parse_error(&response[0])["message"]
8794            .as_str()
8795            .unwrap()
8796            .contains("is not active"));
8797    }
8798
8799    #[tokio::test]
8800    async fn late_health_reply_is_recorded_through_the_module_response_path() {
8801        let registry = Arc::new(Registry::default());
8802        let forwarding = Arc::new(ForwardingTable::default());
8803        let supervisor_handle = SupervisorHandle::new();
8804        let supervisor = Supervisor::new(Arc::clone(&registry), crate::RestartPolicy::default())
8805            .with_forwarding(Arc::clone(&forwarding))
8806            .with_handle(supervisor_handle.clone());
8807        let module = supervisor
8808            .supervise_configured(
8809                crate::ModuleSpec {
8810                    module_id: "late-health-response".to_string(),
8811                    program: PathBuf::from("disabled-module"),
8812                    args: Vec::new(),
8813                    env: Vec::new(),
8814                    reserved: false,
8815                    reserved_prefixes: Vec::new(),
8816                    protocol: ModuleProtocol::Subc,
8817                    overlap: Default::default(),
8818                },
8819                false,
8820            )
8821            .unwrap();
8822        let handler =
8823            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
8824                .with_supervisor(supervisor_handle);
8825        let (module_ctx, _module_rx) = route_ctx(ConnectionId::new(39));
8826        handler
8827            .handle_control_frame(
8828                &module_ctx,
8829                hello_frame_with_control_ops(
8830                    "late-health-response",
8831                    PROTOCOL_VERSION,
8832                    7,
8833                    Some(vec![MODULE_CONTROL_OP_HEALTH_CHECK.to_string()]),
8834                ),
8835            )
8836            .await
8837            .unwrap();
8838        let probe_started_at = Instant::now() - Duration::from_millis(80);
8839        let pending = forwarding
8840            .begin_health_probe_rpc_for(
8841                "late-health-response",
8842                MODULE_CONTROL_OP_HEALTH_CHECK,
8843                probe_started_at,
8844                Instant::now() - Duration::from_millis(1),
8845            )
8846            .unwrap();
8847        assert!(forwarding
8848            .tombstone_health_probe_rpc(pending.endpoint, pending.corr)
8849            .unwrap());
8850
8851        let responses = handler
8852            .handle_control_frame(&module_ctx, health_response(pending.corr, HealthStatus::Ok))
8853            .await
8854            .unwrap();
8855
8856        assert!(responses.is_empty());
8857        let health = module.status().unwrap().health;
8858        assert_eq!(health.late_answer_count, 1);
8859        assert!(health.last_late_answer_latency_ms.unwrap() >= 80);
8860    }
8861
8862    #[tokio::test]
8863    async fn health_probe_timeout_and_module_death_are_typed() {
8864        let registry = Arc::new(Registry::default());
8865        let forwarding = Arc::new(ForwardingTable::default());
8866        let handler =
8867            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
8868                .with_health_probe_timeout(Duration::from_millis(50));
8869        let (module_ctx, mut module_rx) = route_ctx(ConnectionId::new(40));
8870        hello_via_sink(
8871            &handler,
8872            &module_ctx,
8873            &mut module_rx,
8874            hello_frame_with_control_ops(
8875                "aft",
8876                PROTOCOL_VERSION,
8877                7,
8878                Some(vec![MODULE_CONTROL_OP_HEALTH_CHECK.to_string()]),
8879            ),
8880        )
8881        .await;
8882
8883        let (client_ctx, _client_rx) = route_ctx(ConnectionId::new(41));
8884        let responses = handler
8885            .handle_control_frame(&client_ctx, supervisor_health_probe_frame(201, "aft"))
8886            .await
8887            .unwrap();
8888        assert_eq!(responses[0].header.ty, FrameType::Error);
8889        assert_eq!(parse_error(&responses[0])["code"], "module_timeout");
8890        let _ = module_rx.try_recv();
8891
8892        let (client_ctx, _client_rx) = route_ctx(ConnectionId::new(42));
8893        let health_handler = handler.clone();
8894        let death_task = tokio::spawn(async move {
8895            health_handler
8896                .handle_control_frame(&client_ctx, supervisor_health_probe_frame(202, "aft"))
8897                .await
8898                .unwrap()
8899        });
8900        tokio::time::timeout(Duration::from_secs(1), module_rx.recv())
8901            .await
8902            .unwrap()
8903            .unwrap();
8904        handler
8905            .cleanup_connection(module_ctx.connection_id)
8906            .unwrap();
8907        let responses = death_task.await.unwrap();
8908        assert_eq!(responses[0].header.ty, FrameType::Error);
8909        assert_eq!(parse_error(&responses[0])["code"], "target_unavailable");
8910    }
8911
8912    #[test]
8913    fn hello_requires_exact_protocol_version() {
8914        for (connection, offered) in [(1, PROTOCOL_VERSION - 1), (2, PROTOCOL_VERSION + 1)] {
8915            let registry = Arc::new(Registry::default());
8916            let handler = ControlHandler::new(Arc::clone(&registry));
8917            let responses = handler
8918                .handle_control(
8919                    ConnectionId::new(connection),
8920                    hello_frame("aft", offered, 9),
8921                )
8922                .unwrap();
8923
8924            assert_eq!(responses.len(), 1);
8925            assert_eq!(responses[0].header.ty, FrameType::Error);
8926            let error = parse_error(&responses[0]);
8927            assert_eq!(error["code"], "version_unsupported");
8928            assert!(registry.get_module("aft").unwrap().is_none());
8929            assert_eq!(registry.active_registration_count().unwrap(), 0);
8930        }
8931    }
8932
8933    #[test]
8934    fn unknown_module_push_op_is_ignored_but_malformed_known_op_errors() {
8935        let registry = Arc::new(Registry::default());
8936        let forwarding = Arc::new(ForwardingTable::default());
8937        let handler =
8938            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
8939        let module_connection = ConnectionId::new(301);
8940        let registration = registry
8941            .register_with_control_ops(
8942                manifest("aft-push", PROTOCOL_VERSION),
8943                PROTOCOL_VERSION,
8944                module_connection,
8945                module_baseline_control_ops(),
8946            )
8947            .unwrap();
8948        let (module_tx, _module_rx) = mpsc::channel(8);
8949        let endpoint = forwarding
8950            .register_module_connection(
8951                module_connection,
8952                "aft-push".to_string(),
8953                PROTOCOL_VERSION,
8954                manifest_concurrency(&registration.manifest),
8955                FrameSink::new(module_tx),
8956            )
8957            .unwrap();
8958
8959        // A push op this version does not know is ignored (forward-compat), not errored.
8960        let unknown = Frame::build(
8961            FrameType::Push,
8962            control_flags(),
8963            0,
8964            0,
8965            5,
8966            serde_json::to_vec(&json!({"op": "route.future.v2", "extra": 1})).unwrap(),
8967        )
8968        .unwrap();
8969        let out = handler.handle_status_update(endpoint, unknown).unwrap();
8970        assert!(
8971            out.is_empty(),
8972            "unknown push op must be ignored, got {out:?}"
8973        );
8974
8975        // A malformed body for a KNOWN op is a real error worth surfacing.
8976        let malformed = Frame::build(
8977            FrameType::Push,
8978            control_flags(),
8979            0,
8980            0,
8981            6,
8982            serde_json::to_vec(&json!({"op": "route.status"})).unwrap(),
8983        )
8984        .unwrap();
8985        let out = handler.handle_status_update(endpoint, malformed).unwrap();
8986        assert_eq!(out.len(), 1);
8987        assert_eq!(out[0].header.ty, FrameType::Error);
8988        assert_eq!(parse_error(&out[0])["code"], "invalid_control_body");
8989    }
8990
8991    #[test]
8992    fn hello_rejected_when_connection_already_owns_client_routes() {
8993        let registry = Arc::new(Registry::default());
8994        let forwarding = Arc::new(ForwardingTable::default());
8995        let handler =
8996            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
8997        // Commits a client route on connection 202 (bound to a module on conn 101).
8998        let _ = bind_liveness_route(&registry, &forwarding, "aft-module");
8999        let client_connection = ConnectionId::new(202);
9000
9001        // That same connection now tries to register as a module: rejected, so one
9002        // connection never holds both client-route and module-endpoint state.
9003        let responses = handler
9004            .handle_control(
9005                client_connection,
9006                hello_frame("aft-second", PROTOCOL_VERSION, 9),
9007            )
9008            .unwrap();
9009        assert_eq!(responses[0].header.ty, FrameType::Error);
9010        assert_eq!(parse_error(&responses[0])["code"], "invalid_hello");
9011        assert!(registry.get_module("aft-second").unwrap().is_none());
9012    }
9013
9014    #[test]
9015    fn reserved_module_hello_requires_matching_launch_nonce() {
9016        let registry = Arc::new(Registry::default());
9017        let supervisor = SupervisorHandle::new();
9018        // The supervisor recorded the nonce it injected when it spawned the reserved
9019        // module; the HELLO verifier checks against the same shared handle.
9020        supervisor.set_reserved_nonce("vault", "the-real-nonce".to_string());
9021        let handler = ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor);
9022
9023        // A HELLO with NO nonce is rejected.
9024        let no_nonce = handler
9025            .handle_control(
9026                ConnectionId::new(1),
9027                hello_frame("vault", PROTOCOL_VERSION, 1),
9028            )
9029            .unwrap();
9030        assert_eq!(no_nonce[0].header.ty, FrameType::Error);
9031        assert_eq!(parse_error(&no_nonce[0])["code"], "reserved_module");
9032        assert!(registry.get_module("vault").unwrap().is_none());
9033
9034        // A HELLO with the WRONG nonce is rejected.
9035        let wrong = handler
9036            .handle_control(
9037                ConnectionId::new(2),
9038                hello_frame_with_nonce("vault", PROTOCOL_VERSION, 2, Some("forged")),
9039            )
9040            .unwrap();
9041        assert_eq!(wrong[0].header.ty, FrameType::Error);
9042        assert_eq!(parse_error(&wrong[0])["code"], "reserved_module");
9043        assert!(registry.get_module("vault").unwrap().is_none());
9044
9045        // A HELLO with the CORRECT nonce registers.
9046        let ok = handler
9047            .handle_control(
9048                ConnectionId::new(3),
9049                hello_frame_with_nonce("vault", PROTOCOL_VERSION, 3, Some("the-real-nonce")),
9050            )
9051            .unwrap();
9052        assert_eq!(ok[0].header.ty, FrameType::HelloAck);
9053        assert!(registry.get_module("vault").unwrap().is_some());
9054    }
9055
9056    #[test]
9057    fn reserved_prefix_hello_uses_delimiter_sensitive_owner_nonce() {
9058        let registry = Arc::new(Registry::default());
9059        let supervisor = SupervisorHandle::new();
9060        supervisor.set_spawn_nonce("federation", "owner-nonce".to_string());
9061        supervisor.set_reserved_prefixes("federation", &["fed:".to_string()]);
9062        let handler = ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor);
9063
9064        let squat = handler
9065            .handle_control(
9066                ConnectionId::new(1),
9067                hello_frame("fed:peerA:tool", PROTOCOL_VERSION, 1),
9068            )
9069            .unwrap();
9070        assert_eq!(squat[0].header.ty, FrameType::Error);
9071        assert_eq!(parse_error(&squat[0])["code"], "reserved_module");
9072        assert!(parse_error(&squat[0])["message"]
9073            .as_str()
9074            .unwrap()
9075            .contains("fed:"));
9076
9077        let accepted_peer = handler
9078            .handle_control(
9079                ConnectionId::new(2),
9080                hello_frame_with_nonce("fed:peerA:tool", PROTOCOL_VERSION, 2, Some("owner-nonce")),
9081            )
9082            .unwrap();
9083        assert_eq!(accepted_peer[0].header.ty, FrameType::HelloAck);
9084
9085        let accepted_short = handler
9086            .handle_control(
9087                ConnectionId::new(3),
9088                hello_frame_with_nonce("fed:x", PROTOCOL_VERSION, 3, Some("owner-nonce")),
9089            )
9090            .unwrap();
9091        assert_eq!(accepted_short[0].header.ty, FrameType::HelloAck);
9092
9093        for (conn, module_id) in [(4, "fedx:tool"), (5, "fed"), (6, "FED:x")] {
9094            let response = handler
9095                .handle_control(
9096                    ConnectionId::new(conn),
9097                    hello_frame(module_id, PROTOCOL_VERSION, conn),
9098                )
9099                .unwrap();
9100            assert_eq!(response[0].header.ty, FrameType::HelloAck, "{module_id}");
9101        }
9102    }
9103
9104    #[test]
9105    fn exact_reserved_module_takes_precedence_over_reserved_prefix() {
9106        let registry = Arc::new(Registry::default());
9107        let supervisor = SupervisorHandle::new();
9108        supervisor.set_spawn_nonce("federation", "owner-nonce".to_string());
9109        supervisor.set_reserved_prefixes("federation", &["fed:".to_string()]);
9110        supervisor.set_reserved_nonce("fed:special", "exact-nonce".to_string());
9111        let handler = ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor);
9112
9113        let owner_nonce = handler
9114            .handle_control(
9115                ConnectionId::new(1),
9116                hello_frame_with_nonce("fed:special", PROTOCOL_VERSION, 1, Some("owner-nonce")),
9117            )
9118            .unwrap();
9119        assert_eq!(owner_nonce[0].header.ty, FrameType::Error);
9120        assert_eq!(parse_error(&owner_nonce[0])["code"], "reserved_module");
9121        assert!(registry.get_module("fed:special").unwrap().is_none());
9122
9123        let exact_nonce = handler
9124            .handle_control(
9125                ConnectionId::new(2),
9126                hello_frame_with_nonce("fed:special", PROTOCOL_VERSION, 2, Some("exact-nonce")),
9127            )
9128            .unwrap();
9129        assert_eq!(exact_nonce[0].header.ty, FrameType::HelloAck);
9130        assert!(registry.get_module("fed:special").unwrap().is_some());
9131    }
9132
9133    #[test]
9134    fn non_reserved_module_ignores_launch_nonce() {
9135        let registry = Arc::new(Registry::default());
9136        // No reserved nonce recorded for these ids: they are not reserved, so HELLO
9137        // registration succeeds whether a spawned process echoes a nonce or not.
9138        let handler = ControlHandler::new(Arc::clone(&registry));
9139        let no_nonce = handler
9140            .handle_control(
9141                ConnectionId::new(1),
9142                hello_frame("aft-no-nonce", PROTOCOL_VERSION, 1),
9143            )
9144            .unwrap();
9145        assert_eq!(no_nonce[0].header.ty, FrameType::HelloAck);
9146        assert!(registry.get_module("aft-no-nonce").unwrap().is_some());
9147
9148        let echoed_nonce = handler
9149            .handle_control(
9150                ConnectionId::new(2),
9151                hello_frame_with_nonce("aft-with-nonce", PROTOCOL_VERSION, 2, Some("spawn-nonce")),
9152            )
9153            .unwrap();
9154        assert_eq!(echoed_nonce[0].header.ty, FrameType::HelloAck);
9155        assert!(registry.get_module("aft-with-nonce").unwrap().is_some());
9156    }
9157
9158    #[test]
9159    fn malformed_hello_returns_error_and_handler_still_answers_ping() {
9160        let handler = ControlHandler::default();
9161        let conn = ConnectionId::new(1);
9162        let malformed = Frame::build(
9163            FrameType::Hello,
9164            control_flags(),
9165            0,
9166            0,
9167            3,
9168            b"{not json".to_vec(),
9169        )
9170        .unwrap();
9171
9172        let error = handler.handle_control(conn, malformed).unwrap();
9173        assert_eq!(error[0].header.ty, FrameType::Error);
9174        assert_eq!(parse_error(&error[0])["code"], "invalid_hello");
9175
9176        let ping = Frame::build(FrameType::Ping, control_flags(), 0, 0, 4, Vec::new()).unwrap();
9177        let pong = handler.handle_control(conn, ping).unwrap();
9178        assert_eq!(pong[0].header.ty, FrameType::Pong);
9179        assert_eq!(pong[0].header.corr, 4);
9180    }
9181
9182    #[test]
9183    fn duplicate_module_id_is_rejected_without_replacing_active_registration() {
9184        let registry = Arc::new(Registry::default());
9185        let handler = ControlHandler::new(Arc::clone(&registry));
9186
9187        handler
9188            .handle_control(
9189                ConnectionId::new(1),
9190                hello_frame("aft", PROTOCOL_VERSION, 1),
9191            )
9192            .unwrap();
9193        let duplicate = handler
9194            .handle_control(
9195                ConnectionId::new(2),
9196                hello_frame("aft", PROTOCOL_VERSION, 2),
9197            )
9198            .unwrap();
9199
9200        assert_eq!(duplicate[0].header.ty, FrameType::Error);
9201        assert_eq!(parse_error(&duplicate[0])["code"], "duplicate_module_id");
9202        let registration = registry.get_module("aft").unwrap().unwrap();
9203        assert_eq!(registration.connection_id, ConnectionId::new(1));
9204    }
9205
9206    #[test]
9207    fn liveness_poll_reports_false_when_process_liveness_reports_dead() {
9208        let registry = Arc::new(Registry::default());
9209        let forwarding = Arc::new(ForwardingTable::default());
9210        let process_liveness = Arc::new(FakeProcessLiveness { live: Some(false) });
9211        let handler =
9212            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
9213                .with_process_liveness(process_liveness);
9214        let (ctx, route_channel, route_epoch) =
9215            bind_liveness_route(&registry, &forwarding, "aft-dead");
9216        let responses = handler
9217            .handle_route_poll(
9218                &ctx,
9219                route_poll_frame(41, PollKind::Liveness, route_channel),
9220                route_channel,
9221                route_epoch,
9222                PollKind::Liveness,
9223            )
9224            .unwrap();
9225
9226        assert_eq!(responses.len(), 1);
9227        assert_eq!(responses[0].header.ty, FrameType::Response);
9228        assert_route_poll_liveness(&responses[0], false);
9229    }
9230
9231    #[test]
9232    fn liveness_poll_without_process_source_uses_bound_route() {
9233        let registry = Arc::new(Registry::default());
9234        let forwarding = Arc::new(ForwardingTable::default());
9235        let handler =
9236            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
9237        let (ctx, route_channel, route_epoch) =
9238            bind_liveness_route(&registry, &forwarding, "aft-bound-only");
9239        let responses = handler
9240            .handle_route_poll(
9241                &ctx,
9242                route_poll_frame(42, PollKind::Liveness, route_channel),
9243                route_channel,
9244                route_epoch,
9245                PollKind::Liveness,
9246            )
9247            .unwrap();
9248
9249        assert_route_poll_liveness(&responses[0], true);
9250    }
9251
9252    #[test]
9253    fn liveness_poll_untracked_process_source_uses_bound_route() {
9254        let registry = Arc::new(Registry::default());
9255        let forwarding = Arc::new(ForwardingTable::default());
9256        let process_liveness = Arc::new(FakeProcessLiveness { live: None });
9257        let handler =
9258            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
9259                .with_process_liveness(process_liveness);
9260        let (ctx, route_channel, route_epoch) =
9261            bind_liveness_route(&registry, &forwarding, "aft-untracked");
9262        let responses = handler
9263            .handle_route_poll(
9264                &ctx,
9265                route_poll_frame(43, PollKind::Liveness, route_channel),
9266                route_channel,
9267                route_epoch,
9268                PollKind::Liveness,
9269            )
9270            .unwrap();
9271
9272        assert_route_poll_liveness(&responses[0], true);
9273    }
9274
9275    #[tokio::test]
9276    async fn unknown_op_returns_unknown_control_op() {
9277        let handler = ControlHandler::default();
9278        let (ctx, _rx) = route_ctx(ConnectionId::new(77));
9279        let request = Frame::build(
9280            FrameType::Request,
9281            control_flags(),
9282            0,
9283            0,
9284            55,
9285            br#"{"op":"route.nope","route_channel":1}"#.to_vec(),
9286        )
9287        .unwrap();
9288
9289        let response = handler.handle_control_frame(&ctx, request).await.unwrap();
9290
9291        assert_eq!(response.len(), 1);
9292        assert_eq!(response[0].header.ty, FrameType::Error);
9293        assert_eq!(response[0].header.corr, 55);
9294        assert_eq!(parse_error(&response[0])["code"], "unknown_control_op");
9295    }
9296
9297    #[tokio::test]
9298    async fn supervisor_provenance_rejects_unknown_exact_module() {
9299        let handler = ControlHandler::default();
9300        let (ctx, _rx) = route_ctx(ConnectionId::new(79));
9301        let request = Frame::build(
9302            FrameType::Request,
9303            control_flags(),
9304            0,
9305            0,
9306            57,
9307            br#"{"op":"supervisor.provenance","module_id":"missing"}"#.to_vec(),
9308        )
9309        .unwrap();
9310
9311        let response = handler.handle_control_frame(&ctx, request).await.unwrap();
9312
9313        assert_eq!(response.len(), 1);
9314        assert_eq!(response[0].header.ty, FrameType::Error);
9315        assert_eq!(response[0].header.corr, 57);
9316        let error = parse_error(&response[0]);
9317        assert_eq!(error["code"], "unknown_module");
9318        assert_eq!(error["message"], "module_id 'missing' is not supervised");
9319    }
9320
9321    #[test]
9322    fn provenance_probe_override_keeps_handler_tests_deterministic() {
9323        let expected = subc_control::RunningImageAgreement::Unavailable {
9324            reason: subc_control::RunningImageUnavailableReason::HashFailed,
9325        };
9326        let handler = ControlHandler::default().with_provenance_probe_result(expected.clone());
9327        assert_eq!(handler.provenance_probe_override, Some(expected));
9328    }
9329
9330    #[tokio::test]
9331    async fn malformed_control_bodies_return_invalid_control_body() {
9332        let handler = ControlHandler::default();
9333        let (ctx, _rx) = route_ctx(ConnectionId::new(78));
9334
9335        for (corr, body) in [
9336            (56, br#"{"route_channel":1}"#.as_slice()),
9337            (57, br#"{"op":17,"route_channel":1}"#.as_slice()),
9338            (
9339                58,
9340                br#"{"op":"route.poll","route_channel":"bad","kind":"status"}"#.as_slice(),
9341            ),
9342        ] {
9343            let request = Frame::build(
9344                FrameType::Request,
9345                control_flags(),
9346                0,
9347                0,
9348                corr,
9349                body.to_vec(),
9350            )
9351            .unwrap();
9352            let response = handler.handle_control_frame(&ctx, request).await.unwrap();
9353
9354            assert_eq!(response.len(), 1);
9355            assert_eq!(response[0].header.ty, FrameType::Error);
9356            assert_eq!(response[0].header.corr, corr);
9357            assert_eq!(parse_error(&response[0])["code"], "invalid_control_body");
9358        }
9359    }
9360
9361    #[tokio::test]
9362    async fn goodbye_tears_down_registration_and_later_channel_is_unknown() {
9363        let registry = Arc::new(Registry::default());
9364        let control = Arc::new(ControlHandler::new(Arc::clone(&registry)));
9365        let router = Router::with_control_handler(Arc::clone(&control));
9366        let connection = router.begin_connection();
9367        let (ctx, mut rx) = route_ctx(connection.id());
9368
9369        router
9370            .route_for_connection(&ctx, hello_frame("aft", PROTOCOL_VERSION, 11))
9371            .await
9372            .unwrap();
9373        let response = rx.recv().await.unwrap();
9374        let ack = parse_ack(&response);
9375        assert_eq!(ack.negotiated_ver, PROTOCOL_VERSION);
9376        let channel = 1;
9377
9378        let goodbye =
9379            Frame::build(FrameType::Goodbye, control_flags(), 0, 0, 12, Vec::new()).unwrap();
9380        router.route_for_connection(&ctx, goodbye).await.unwrap();
9381        assert!(rx.try_recv().is_err());
9382        assert!(registry.get_module("aft").unwrap().is_none());
9383
9384        router
9385            .route_for_connection(&ctx, channel_request(channel, 13))
9386            .await
9387            .unwrap();
9388        let error_frame = rx.recv().await.unwrap();
9389        assert_eq!(error_frame.header.ty, FrameType::Error);
9390        assert_eq!(error_frame.header.channel, channel);
9391    }
9392
9393    #[tokio::test]
9394    async fn dropping_router_connection_releases_registration() {
9395        let registry = Arc::new(Registry::default());
9396        let control = Arc::new(ControlHandler::new(Arc::clone(&registry)));
9397        let router = Router::with_control_handler(control);
9398        let connection = router.begin_connection();
9399        let (ctx, mut rx) = route_ctx(connection.id());
9400
9401        router
9402            .route_for_connection(&ctx, hello_frame("aft", PROTOCOL_VERSION, 31))
9403            .await
9404            .unwrap();
9405        let response = rx.recv().await.unwrap();
9406        let ack = parse_ack(&response);
9407        assert_eq!(ack.negotiated_ver, PROTOCOL_VERSION);
9408        assert!(registry.get_module("aft").unwrap().is_some());
9409
9410        drop(connection);
9411
9412        assert!(registry.get_module("aft").unwrap().is_none());
9413        assert_eq!(registry.active_registration_count().unwrap(), 0);
9414    }
9415
9416    fn capability_manifest(
9417        module_id: &str,
9418        provides: &[&str],
9419        must_never_reach: &[&str],
9420    ) -> ModuleManifest {
9421        let mut manifest = manifest(module_id, PROTOCOL_VERSION);
9422        manifest.capabilities = Some(CapabilityDeclarations {
9423            provides: provides
9424                .iter()
9425                .map(|capability| (*capability).to_string())
9426                .collect(),
9427            requires: Vec::new(),
9428            must_never_reach: must_never_reach
9429                .iter()
9430                .map(|capability| (*capability).to_string())
9431                .collect(),
9432        });
9433        manifest
9434    }
9435
9436    fn hello_frame_with_manifest(manifest: ModuleManifest, corr: u64) -> Frame {
9437        Frame::build(
9438            FrameType::Hello,
9439            control_flags(),
9440            0,
9441            0,
9442            corr,
9443            serde_json::to_vec(&ModuleHelloBody {
9444                protocol_ver: manifest.protocol_ver,
9445                manifest,
9446                control_ops: None,
9447                launch_nonce: None,
9448            })
9449            .expect("capability test HELLO serializes"),
9450        )
9451        .expect("capability test HELLO frame builds")
9452    }
9453
9454    fn catalog_update_with_capabilities_frame(
9455        corr: u64,
9456        capabilities: CapabilityDeclarations,
9457    ) -> Frame {
9458        Frame::build(
9459            FrameType::Request,
9460            control_flags(),
9461            0,
9462            0,
9463            corr,
9464            serde_json::to_vec(&ModuleControlRequestFromModule::CatalogUpdate {
9465                provides: manifest("catalog-update-placeholder", PROTOCOL_VERSION).provides,
9466                capabilities: Some(capabilities),
9467                ready: None,
9468            })
9469            .expect("capability catalog.update serializes"),
9470        )
9471        .expect("capability catalog.update frame builds")
9472    }
9473
9474    async fn register_capability_manifest(
9475        handler: &ControlHandler,
9476        ctx: &RouteCtx,
9477        rx: &mut mpsc::Receiver<crate::router::OutboundFrame>,
9478        manifest: ModuleManifest,
9479        corr: u64,
9480    ) {
9481        hello_via_sink(handler, ctx, rx, hello_frame_with_manifest(manifest, corr)).await;
9482    }
9483
9484    async fn open_route_for_capability_test(
9485        handler: &ControlHandler,
9486        target_ctx: &RouteCtx,
9487        target_rx: &mut mpsc::Receiver<crate::router::OutboundFrame>,
9488        client_connection_id: u64,
9489        corr: u64,
9490        target_module_id: &str,
9491        consumer_identity: Option<ConsumerIdentity>,
9492    ) -> (
9493        mpsc::Receiver<crate::router::OutboundFrame>,
9494        ModuleControlRequest,
9495    ) {
9496        let (client_ctx, mut client_rx) = route_ctx(ConnectionId::new(client_connection_id));
9497        let route_handler = handler.clone();
9498        let target_module_id = target_module_id.to_string();
9499        let route_task = tokio::spawn(async move {
9500            route_handler
9501                .handle_control_frame(
9502                    &client_ctx,
9503                    route_open_frame_with_admission_facts(
9504                        corr,
9505                        &target_module_id,
9506                        unique_project_root("admission-facts"),
9507                        consumer_identity,
9508                        None,
9509                    ),
9510                )
9511                .await
9512                .expect("capability test route.open succeeds")
9513        });
9514        let bind = tokio::time::timeout(Duration::from_secs(1), target_rx.recv())
9515            .await
9516            .expect("capability test route.open must reach route.bind")
9517            .expect("target control receiver stays open");
9518        let bind_request: ModuleControlRequest =
9519            serde_json::from_slice(&bind.body).expect("route.bind decodes");
9520        handler
9521            .handle_control_frame(target_ctx, route_bind_ack(bind.header.corr))
9522            .await
9523            .expect("capability test route.bind ACK succeeds");
9524        assert!(route_task.await.expect("route.open task joins").is_empty());
9525        let opened = client_rx
9526            .recv()
9527            .await
9528            .expect("successful route.open publishes a response");
9529        assert!(matches!(
9530            serde_json::from_slice::<ClientControlResponse>(&opened.body),
9531            Ok(ClientControlResponse::RouteOpen { .. })
9532        ));
9533        (client_rx, bind_request)
9534    }
9535
9536    fn assert_capability_denied_push(frame: Frame, target_module_id: &str) {
9537        assert_eq!(frame.header.ty, FrameType::Push);
9538        assert_eq!(frame.header.channel, 0);
9539        assert_eq!(
9540            serde_json::from_slice::<ClientControlPush>(&frame.body)
9541                .expect("route.closed control push decodes"),
9542            ClientControlPush::RouteClosed {
9543                module_id: target_module_id.to_string(),
9544                reason: RouteCloseReason::CapabilityDenied,
9545                drained: false,
9546                abandoned: 0,
9547                excluded_subscriptions: 0,
9548                terminal: Some(false),
9549            }
9550        );
9551    }
9552
9553    #[tokio::test]
9554    async fn route_open_capability_forbidden_mutation_proof_creates_no_route() {
9555        let registry = Arc::new(Registry::default());
9556        let forwarding = Arc::new(ForwardingTable::default());
9557        let supervisor = SupervisorHandle::new();
9558        supervisor.set_spawn_nonce("opener", "opener-nonce".to_string());
9559        let handler =
9560            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
9561                .with_supervisor(supervisor);
9562        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(700));
9563        let (opener_ctx, mut opener_rx) = route_ctx(ConnectionId::new(701));
9564        register_capability_manifest(
9565            &handler,
9566            &target_ctx,
9567            &mut target_rx,
9568            capability_manifest("target", &["credentials-provider/v1"], &[]),
9569            1,
9570        )
9571        .await;
9572        register_capability_manifest(
9573            &handler,
9574            &opener_ctx,
9575            &mut opener_rx,
9576            capability_manifest("opener", &[], &["credentials-provider/v1"]),
9577            2,
9578        )
9579        .await;
9580
9581        let (client_ctx, _client_rx) = route_ctx(ConnectionId::new(702));
9582        let replies = handler
9583            .handle_control_frame(
9584                &client_ctx,
9585                route_open_frame_with_admission_facts(
9586                    3,
9587                    "target",
9588                    unique_project_root("admission-facts"),
9589                    Some(ConsumerIdentity {
9590                        module_id: "opener".to_string(),
9591                        launch_nonce: "opener-nonce".to_string(),
9592                    }),
9593                    None,
9594                ),
9595            )
9596            .await
9597            .expect("denied route.open returns a typed frame");
9598        assert_eq!(parse_error(&replies[0])["code"], "capability_forbidden");
9599        assert_eq!(forwarding.active_binding_count().unwrap(), 0);
9600        assert!(
9601            target_rx.try_recv().is_err(),
9602            "forbidden route.open must not relay route.bind"
9603        );
9604    }
9605
9606    #[tokio::test]
9607    async fn capability_deny_edge_hello_mutation_proof_force_closes_existing_route() {
9608        let registry = Arc::new(Registry::default());
9609        let forwarding = Arc::new(ForwardingTable::default());
9610        let supervisor = SupervisorHandle::new();
9611        supervisor.set_spawn_nonce("opener", "opener-nonce".to_string());
9612        let handler =
9613            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
9614                .with_supervisor(supervisor);
9615        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(710));
9616        let (old_opener_ctx, mut old_opener_rx) = route_ctx(ConnectionId::new(711));
9617        register_capability_manifest(
9618            &handler,
9619            &target_ctx,
9620            &mut target_rx,
9621            capability_manifest("target", &["credentials-provider/v1"], &[]),
9622            1,
9623        )
9624        .await;
9625        register_capability_manifest(
9626            &handler,
9627            &old_opener_ctx,
9628            &mut old_opener_rx,
9629            capability_manifest("opener", &[], &[]),
9630            2,
9631        )
9632        .await;
9633        let (mut client_rx, _) = open_route_for_capability_test(
9634            &handler,
9635            &target_ctx,
9636            &mut target_rx,
9637            712,
9638            3,
9639            "target",
9640            Some(ConsumerIdentity {
9641                module_id: "opener".to_string(),
9642                launch_nonce: "opener-nonce".to_string(),
9643            }),
9644        )
9645        .await;
9646        assert_eq!(forwarding.active_binding_count().unwrap(), 1);
9647
9648        handler
9649            .cleanup_connection(old_opener_ctx.connection_id)
9650            .expect("old opener registration cleans up");
9651        let (new_opener_ctx, mut new_opener_rx) = route_ctx(ConnectionId::new(713));
9652        register_capability_manifest(
9653            &handler,
9654            &new_opener_ctx,
9655            &mut new_opener_rx,
9656            capability_manifest("opener", &[], &["credentials-provider/v1"]),
9657            4,
9658        )
9659        .await;
9660
9661        assert_capability_denied_push(
9662            client_rx
9663                .try_recv()
9664                .expect("HELLO deny addition must emit route.closed")
9665                .frame,
9666            "target",
9667        );
9668        assert_eq!(forwarding.active_binding_count().unwrap(), 0);
9669        assert!(matches!(
9670            target_rx.try_recv(),
9671            Ok(outbound) if outbound.header.ty == FrameType::Goodbye
9672        ));
9673    }
9674
9675    #[tokio::test]
9676    async fn capability_claim_catalog_update_mutation_proof_force_closes_existing_route() {
9677        let registry = Arc::new(Registry::default());
9678        let forwarding = Arc::new(ForwardingTable::default());
9679        let supervisor = SupervisorHandle::new();
9680        supervisor.set_spawn_nonce("opener", "opener-nonce".to_string());
9681        let handler =
9682            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
9683                .with_supervisor(supervisor);
9684        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(720));
9685        let (opener_ctx, mut opener_rx) = route_ctx(ConnectionId::new(721));
9686        register_capability_manifest(
9687            &handler,
9688            &target_ctx,
9689            &mut target_rx,
9690            capability_manifest("target", &[], &[]),
9691            1,
9692        )
9693        .await;
9694        register_capability_manifest(
9695            &handler,
9696            &opener_ctx,
9697            &mut opener_rx,
9698            capability_manifest("opener", &[], &["credentials-provider/v1"]),
9699            2,
9700        )
9701        .await;
9702        let (mut client_rx, _) = open_route_for_capability_test(
9703            &handler,
9704            &target_ctx,
9705            &mut target_rx,
9706            722,
9707            3,
9708            "target",
9709            Some(ConsumerIdentity {
9710                module_id: "opener".to_string(),
9711                launch_nonce: "opener-nonce".to_string(),
9712            }),
9713        )
9714        .await;
9715        assert_eq!(forwarding.active_binding_count().unwrap(), 1);
9716
9717        let replies = handler
9718            .handle_control_frame(
9719                &target_ctx,
9720                catalog_update_with_capabilities_frame(
9721                    4,
9722                    CapabilityDeclarations {
9723                        provides: vec!["credentials-provider/v1".to_string()],
9724                        requires: Vec::new(),
9725                        must_never_reach: Vec::new(),
9726                    },
9727                ),
9728            )
9729            .await
9730            .expect("claim catalog.update succeeds");
9731        assert!(matches!(
9732            serde_json::from_slice::<ModuleControlResponseToModule>(&replies[0].body),
9733            Ok(ModuleControlResponseToModule::CatalogUpdate {})
9734        ));
9735        assert_capability_denied_push(
9736            client_rx
9737                .try_recv()
9738                .expect("claim addition must emit route.closed")
9739                .frame,
9740            "target",
9741        );
9742        assert_eq!(forwarding.active_binding_count().unwrap(), 0);
9743        assert!(matches!(
9744            target_rx.try_recv(),
9745            Ok(outbound) if outbound.header.ty == FrameType::Goodbye
9746        ));
9747    }
9748
9749    #[tokio::test]
9750    async fn capability_claim_removal_mutation_proof_keeps_route_open_without_close_frame() {
9751        let registry = Arc::new(Registry::default());
9752        let forwarding = Arc::new(ForwardingTable::default());
9753        let supervisor = SupervisorHandle::new();
9754        supervisor.set_spawn_nonce("opener", "opener-nonce".to_string());
9755        let handler =
9756            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
9757                .with_supervisor(supervisor);
9758        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(730));
9759        let (opener_ctx, mut opener_rx) = route_ctx(ConnectionId::new(731));
9760        register_capability_manifest(
9761            &handler,
9762            &target_ctx,
9763            &mut target_rx,
9764            capability_manifest("target", &["credentials-provider/v1"], &[]),
9765            1,
9766        )
9767        .await;
9768        register_capability_manifest(
9769            &handler,
9770            &opener_ctx,
9771            &mut opener_rx,
9772            capability_manifest("opener", &[], &[]),
9773            2,
9774        )
9775        .await;
9776        let (mut client_rx, _) = open_route_for_capability_test(
9777            &handler,
9778            &target_ctx,
9779            &mut target_rx,
9780            732,
9781            3,
9782            "target",
9783            Some(ConsumerIdentity {
9784                module_id: "opener".to_string(),
9785                launch_nonce: "opener-nonce".to_string(),
9786            }),
9787        )
9788        .await;
9789        assert_eq!(forwarding.active_binding_count().unwrap(), 1);
9790
9791        handler
9792            .handle_control_frame(
9793                &target_ctx,
9794                catalog_update_with_capabilities_frame(
9795                    4,
9796                    CapabilityDeclarations {
9797                        provides: Vec::new(),
9798                        requires: Vec::new(),
9799                        must_never_reach: Vec::new(),
9800                    },
9801                ),
9802            )
9803            .await
9804            .expect("claim removal catalog.update succeeds");
9805        assert_eq!(
9806            forwarding.active_binding_count().unwrap(),
9807            1,
9808            "removing an attested target claim must leave the route census unchanged"
9809        );
9810        assert!(
9811            client_rx.try_recv().is_err(),
9812            "claim removal must not emit route.closed capability_denied"
9813        );
9814        assert!(
9815            target_rx.try_recv().is_err(),
9816            "claim removal must not send the target a route GOODBYE"
9817        );
9818    }
9819
9820    /// A direct client may open a route to a denied capability provider; this
9821    /// policy applies only to attested supervised module origins, not to direct clients.
9822    #[tokio::test]
9823    async fn direct_client_scope_honesty_mutation_proof_opens_denied_capability_provider() {
9824        let registry = Arc::new(Registry::default());
9825        let forwarding = Arc::new(ForwardingTable::default());
9826        let supervisor = SupervisorHandle::new();
9827        supervisor.set_spawn_nonce("opener", "opener-nonce".to_string());
9828        let handler =
9829            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
9830                .with_supervisor(supervisor);
9831        let (target_ctx, mut target_rx) = route_ctx(ConnectionId::new(740));
9832        let (opener_ctx, mut opener_rx) = route_ctx(ConnectionId::new(741));
9833        register_capability_manifest(
9834            &handler,
9835            &target_ctx,
9836            &mut target_rx,
9837            capability_manifest("target", &["credentials-provider/v1"], &[]),
9838            1,
9839        )
9840        .await;
9841        register_capability_manifest(
9842            &handler,
9843            &opener_ctx,
9844            &mut opener_rx,
9845            capability_manifest("opener", &[], &["credentials-provider/v1"]),
9846            2,
9847        )
9848        .await;
9849
9850        let (_client_rx, bind) = open_route_for_capability_test(
9851            &handler,
9852            &target_ctx,
9853            &mut target_rx,
9854            742,
9855            3,
9856            "target",
9857            None,
9858        )
9859        .await;
9860        let ModuleControlRequest::RouteBind { principal, .. } = bind else {
9861            panic!("direct scope-honesty route must bind");
9862        };
9863        assert_eq!(principal, Some(Principal::Direct));
9864        assert_eq!(forwarding.active_binding_count().unwrap(), 1);
9865    }
9866
9867    /// A module that denies a capability receives no self-route exemption when it
9868    /// also attestedly provides that capability.
9869    #[tokio::test]
9870    async fn must_never_reach_self_route_is_capability_forbidden() {
9871        let registry = Arc::new(Registry::default());
9872        let forwarding = Arc::new(ForwardingTable::default());
9873        let supervisor = SupervisorHandle::new();
9874        supervisor.set_spawn_nonce("self-provider", "self-nonce".to_string());
9875        let handler =
9876            ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding))
9877                .with_supervisor(supervisor);
9878        let (self_ctx, mut self_rx) = route_ctx(ConnectionId::new(750));
9879        register_capability_manifest(
9880            &handler,
9881            &self_ctx,
9882            &mut self_rx,
9883            capability_manifest(
9884                "self-provider",
9885                &["credentials-provider/v1"],
9886                &["credentials-provider/v1"],
9887            ),
9888            1,
9889        )
9890        .await;
9891
9892        let (client_ctx, _client_rx) = route_ctx(ConnectionId::new(751));
9893        let replies = handler
9894            .handle_control_frame(
9895                &client_ctx,
9896                route_open_frame_with_admission_facts(
9897                    2,
9898                    "self-provider",
9899                    unique_project_root("admission-facts"),
9900                    Some(ConsumerIdentity {
9901                        module_id: "self-provider".to_string(),
9902                        launch_nonce: "self-nonce".to_string(),
9903                    }),
9904                    None,
9905                ),
9906            )
9907            .await
9908            .expect("self-route refusal returns a typed frame");
9909        assert_eq!(parse_error(&replies[0])["code"], "capability_forbidden");
9910        assert_eq!(forwarding.active_binding_count().unwrap(), 0);
9911        assert!(
9912            self_rx.try_recv().is_err(),
9913            "self denial must not relay route.bind"
9914        );
9915    }
9916
9917    #[test]
9918    fn unsupported_channel_zero_frame_returns_error() {
9919        let handler = ControlHandler::default();
9920        let request = Frame::build(
9921            FrameType::Request,
9922            control_flags(),
9923            0,
9924            0,
9925            21,
9926            b"opaque".to_vec(),
9927        )
9928        .unwrap();
9929
9930        let response = handler
9931            .handle_control(ConnectionId::new(1), request)
9932            .unwrap();
9933
9934        assert_eq!(response[0].header.ty, FrameType::Error);
9935        assert_eq!(
9936            parse_error(&response[0])["code"],
9937            "unsupported_control_frame"
9938        );
9939    }
9940
9941    /// Blue/green swap at the control-plane boundary. The supervisor that opens
9942    /// a swap is not wired yet, so the candidate is registered here directly
9943    /// into the registry and forwarding candidate slots, the way the swap's
9944    /// HELLO admission will.
9945    mod swap {
9946        use super::*;
9947
9948        const INCUMBENT: ConnectionId = ConnectionId::new(30);
9949        const CANDIDATE: ConnectionId = ConnectionId::new(40);
9950
9951        struct Swap {
9952            registry: Arc<Registry>,
9953            forwarding: Arc<ForwardingTable>,
9954            handler: ControlHandler,
9955            incumbent_ctx: RouteCtx,
9956            incumbent_rx: mpsc::Receiver<crate::router::OutboundFrame>,
9957            candidate_ctx: RouteCtx,
9958            candidate_rx: mpsc::Receiver<crate::router::OutboundFrame>,
9959        }
9960
9961        async fn swap_with_incumbent() -> Swap {
9962            let registry = Arc::new(Registry::default());
9963            let forwarding = Arc::new(ForwardingTable::default());
9964            let handler =
9965                ControlHandler::with_forwarding(Arc::clone(&registry), Arc::clone(&forwarding));
9966            let (incumbent_ctx, mut incumbent_rx) = route_ctx(INCUMBENT);
9967            hello_via_sink(
9968                &handler,
9969                &incumbent_ctx,
9970                &mut incumbent_rx,
9971                hello_frame("aft", PROTOCOL_VERSION, 7),
9972            )
9973            .await;
9974            let (candidate_ctx, candidate_rx) = route_ctx(CANDIDATE);
9975            Swap {
9976                registry,
9977                forwarding,
9978                handler,
9979                incumbent_ctx,
9980                incumbent_rx,
9981                candidate_ctx,
9982                candidate_rx,
9983            }
9984        }
9985
9986        fn register_candidate(swap: &Swap, ready: Option<bool>) {
9987            let mut candidate_manifest = manifest("aft", PROTOCOL_VERSION);
9988            candidate_manifest.ready = ready;
9989            let registration = swap
9990                .registry
9991                .register_candidate_with_control_ops(
9992                    candidate_manifest,
9993                    PROTOCOL_VERSION,
9994                    CANDIDATE,
9995                    module_baseline_control_ops(),
9996                )
9997                .unwrap();
9998            swap.forwarding
9999                .register_candidate_module_connection(
10000                    CANDIDATE,
10001                    "aft".to_string(),
10002                    PROTOCOL_VERSION,
10003                    manifest_concurrency(&registration.manifest),
10004                    swap.candidate_ctx.egress.clone(),
10005                )
10006                .unwrap();
10007        }
10008
10009        fn cutover(swap: &Swap) -> crate::forwarding::ModuleEndpointId {
10010            let cutover = swap.forwarding.cutover_candidate("aft").unwrap().unwrap();
10011            swap.registry.promote_candidate("aft").unwrap().unwrap();
10012            cutover.incumbent.unwrap()
10013        }
10014
10015        fn keyed_total(counters: &Value, key: &str) -> u64 {
10016            counters[key]
10017                .as_object()
10018                .map(|counts| counts.values().filter_map(Value::as_u64).sum())
10019                .unwrap_or(0)
10020        }
10021
10022        /// An ack from the incumbent for a bind it was sent before cutover,
10023        /// arriving before the incumbent is drained. The incumbent is the live
10024        /// connection carrying every other client's routes, so the ack must
10025        /// not end it: the waiting client is told to retry, the reservation is
10026        /// given back, and the incumbent is told to drop just that binding.
10027        #[tokio::test]
10028        async fn incumbent_ack_between_promotion_and_drain_keeps_the_incumbent_serving() {
10029            let mut swap = swap_with_incumbent().await;
10030            let handler = swap.handler.clone();
10031
10032            // A co-tenant route, bound on the incumbent before the swap.
10033            let cotenant = ConnectionId::new(31);
10034            let (cotenant_ctx, mut cotenant_rx) = route_ctx(cotenant);
10035            let (cotenant_task, cotenant_bind) = relay_route_open(
10036                &handler,
10037                cotenant,
10038                &cotenant_ctx.egress,
10039                &mut swap.incumbent_rx,
10040                100,
10041                "aft",
10042                "swap-cotenant",
10043            )
10044            .await;
10045            handler
10046                .handle_control_frame(
10047                    &swap.incumbent_ctx,
10048                    route_bind_ack(cotenant_bind.header.corr),
10049                )
10050                .await
10051                .unwrap();
10052            assert!(cotenant_task.await.unwrap().is_empty());
10053            let (cotenant_channel, cotenant_epoch) =
10054                published_route(&cotenant_rx.recv().await.unwrap());
10055
10056            // A second route.open, relayed to the incumbent and not yet acked.
10057            let caller = ConnectionId::new(32);
10058            let (caller_ctx, mut caller_rx) = route_ctx(caller);
10059            let (caller_task, caller_bind) = relay_route_open(
10060                &handler,
10061                caller,
10062                &caller_ctx.egress,
10063                &mut swap.incumbent_rx,
10064                101,
10065                "aft",
10066                "swap-caller",
10067            )
10068            .await;
10069            let (abandoned_channel, abandoned_epoch) = route_bind_channel(&caller_bind);
10070
10071            register_candidate(&swap, None);
10072            cutover(&swap);
10073
10074            // The incumbent acks after promotion and before any drain.
10075            let ack = handler
10076                .handle_control_frame(&swap.incumbent_ctx, route_bind_ack(caller_bind.header.corr))
10077                .await;
10078            let module_loop_error = ack.as_ref().err().map(ToString::to_string);
10079            if module_loop_error.is_some() {
10080                // What the connection loop does with an untranslated router
10081                // error: end the connection, releasing every route on it.
10082                handler.cleanup_connection(INCUMBENT).unwrap();
10083            }
10084
10085            // 1. The incumbent's other routes survive.
10086            assert!(
10087                cotenant_rx.try_recv().is_err(),
10088                "the co-tenant route on the incumbent was torn down by one late ack: \
10089                 {module_loop_error:?}"
10090            );
10091            assert!(matches!(
10092                swap.forwarding
10093                    .lookup_data_route(cotenant, cotenant_channel, cotenant_epoch)
10094                    .unwrap(),
10095                DataRoute::Client(DataRouteState::Bound(_))
10096            ));
10097            assert_eq!(module_loop_error, None);
10098            assert!(swap
10099                .registry
10100                .get_module_by_connection(INCUMBENT)
10101                .unwrap()
10102                .is_some());
10103
10104            // 2. Exactly one channel-scoped GOODBYE to the incumbent.
10105            let goodbye = tokio::time::timeout(Duration::from_secs(1), swap.incumbent_rx.recv())
10106                .await
10107                .expect("the incumbent is told to drop the abandoned binding")
10108                .unwrap()
10109                .frame;
10110            assert_eq!(goodbye.header.ty, FrameType::Goodbye);
10111            assert_eq!(goodbye.header.channel, abandoned_channel);
10112            assert_eq!(goodbye.header.epoch, abandoned_epoch);
10113            assert!(swap.incumbent_rx.try_recv().is_err());
10114
10115            // 3. The waiting client gets a retryable refusal and no route.
10116            let response = caller_task.await.unwrap();
10117            assert_eq!(response.len(), 1);
10118            assert_eq!(parse_error(&response[0])["code"], "module_reloading");
10119            assert!(caller_rx.try_recv().is_err());
10120
10121            // 4. The reservation pair is given back, and the pending bind
10122            //    settled exactly once: one accepted open (the co-tenant) and one
10123            //    refused open (the caller), nothing counted twice.
10124            assert_eq!(swap.forwarding.reserved_route_count().unwrap(), (0, 0));
10125            let counters = handler.counters().snapshot();
10126            assert_eq!(
10127                keyed_total(&counters, "route_open_accepted_by_principal"),
10128                1
10129            );
10130            assert_eq!(keyed_total(&counters, "route_open_refused_by_code"), 1);
10131            assert_eq!(counters["route_open_refused_by_code"]["module_rejected"], 1);
10132        }
10133
10134        /// After cutover the incumbent is drained BY ENDPOINT. Draining by module
10135        /// id would resolve to the promoted candidate and every new route.open
10136        /// would be refused as reloading, leaving neither process routable.
10137        #[tokio::test]
10138        async fn route_open_after_cutover_and_incumbent_drain_is_relayed_to_the_candidate() {
10139            let mut swap = swap_with_incumbent().await;
10140            register_candidate(&swap, None);
10141            let incumbent = cutover(&swap);
10142            swap.forwarding
10143                .begin_endpoint_drain(incumbent, RouteCloseReason::Restart)
10144                .unwrap()
10145                .expect("the incumbent is still registered");
10146
10147            let client = ConnectionId::new(33);
10148            let (client_ctx, mut client_rx) = route_ctx(client);
10149            let route_handler = swap.handler.clone();
10150            let open_ctx = RouteCtx {
10151                connection_id: client,
10152                egress: client_ctx.egress.clone(),
10153            };
10154            let mut route_task = tokio::spawn(async move {
10155                route_handler
10156                    .handle_control_frame(
10157                        &open_ctx,
10158                        route_open_frame(90, "aft", unique_project_root("swap-after-drain")),
10159                    )
10160                    .await
10161                    .unwrap()
10162            });
10163            let bind = tokio::select! {
10164                bind = swap.candidate_rx.recv() => bind.expect("candidate egress is open").frame,
10165                response = &mut route_task => {
10166                    let response = response.unwrap();
10167                    panic!(
10168                        "post-cutover route.open was refused instead of relayed to the candidate: {}",
10169                        parse_error(&response[0])["code"]
10170                    );
10171                }
10172            };
10173            swap.handler
10174                .handle_control_frame(&swap.candidate_ctx, route_bind_ack(bind.header.corr))
10175                .await
10176                .unwrap();
10177            assert!(route_task.await.unwrap().is_empty());
10178            let (channel, epoch) = published_route(&client_rx.recv().await.unwrap());
10179            match swap
10180                .forwarding
10181                .lookup_data_route(client, channel, epoch)
10182                .unwrap()
10183            {
10184                DataRoute::Client(DataRouteState::Bound(route)) => {
10185                    assert_eq!(route.module_endpoint.connection_id, CANDIDATE)
10186                }
10187                other => panic!("expected a bound route on the candidate, got {other:?}"),
10188            }
10189            assert!(swap.incumbent_rx.try_recv().is_err());
10190        }
10191
10192        /// A candidate declares itself ready with `catalog.update` on its own
10193        /// connection. If the connection-keyed registry lookups searched only the
10194        /// active slot, this would answer `not_registered` and the candidate
10195        /// would never become ready.
10196        #[tokio::test]
10197        async fn candidate_catalog_update_ready_reaches_the_candidate_registration() {
10198            let swap = swap_with_incumbent().await;
10199            register_candidate(&swap, Some(false));
10200            let update = Frame::build(
10201                FrameType::Request,
10202                control_flags(),
10203                0,
10204                0,
10205                55,
10206                serde_json::to_vec(&ModuleControlRequestFromModule::CatalogUpdate {
10207                    provides: manifest("aft", PROTOCOL_VERSION).provides,
10208                    capabilities: None,
10209                    ready: Some(true),
10210                })
10211                .unwrap(),
10212            )
10213            .unwrap();
10214
10215            let replies = swap
10216                .handler
10217                .handle_control_frame(&swap.candidate_ctx, update)
10218                .await
10219                .unwrap();
10220
10221            assert_eq!(replies.len(), 1);
10222            assert_eq!(
10223                replies[0].header.ty,
10224                FrameType::Response,
10225                "candidate catalog.update was refused: {:?}",
10226                serde_json::from_slice::<Value>(&replies[0].body).ok()
10227            );
10228            assert!(swap.registry.get_candidate("aft").unwrap().unwrap().ready);
10229            assert_eq!(
10230                swap.registry
10231                    .get_module("aft")
10232                    .unwrap()
10233                    .unwrap()
10234                    .connection_id,
10235                INCUMBENT
10236            );
10237        }
10238    }
10239
10240    /// The HELLO gate while the supervisor has a swap open: only the nonce it
10241    /// minted for the candidate admits a second process, into the candidate
10242    /// slot, and that check runs ahead of the reserved-module gate.
10243    mod swap_admission {
10244        use super::*;
10245
10246        const INCUMBENT_NONCE: &str = "incumbent-nonce";
10247        const CANDIDATE_NONCE: &str = "candidate-nonce";
10248
10249        fn handler_with_incumbent(
10250            module_id: &str,
10251            reserved: bool,
10252        ) -> (Arc<Registry>, SupervisorHandle, ControlHandler) {
10253            let registry = Arc::new(Registry::default());
10254            let supervisor = SupervisorHandle::new();
10255            supervisor.set_spawn_nonce(module_id, INCUMBENT_NONCE.to_string());
10256            if reserved {
10257                supervisor.set_reserved_nonce(module_id, INCUMBENT_NONCE.to_string());
10258            }
10259            let handler =
10260                ControlHandler::new(Arc::clone(&registry)).with_supervisor(supervisor.clone());
10261            let incumbent = handler
10262                .handle_control(
10263                    ConnectionId::new(1),
10264                    hello_frame_with_nonce(module_id, PROTOCOL_VERSION, 1, Some(INCUMBENT_NONCE)),
10265                )
10266                .unwrap();
10267            assert_eq!(incumbent[0].header.ty, FrameType::HelloAck);
10268            supervisor.open_swap(module_id, CANDIDATE_NONCE.to_string());
10269            (registry, supervisor, handler)
10270        }
10271
10272        /// Design mutation arm (ii). On an UNRESERVED id the reserved gate
10273        /// admits every nonce, so while a swap is open the swap gate is the only
10274        /// thing between a key-holder and the candidate slot. A nonce the
10275        /// supervisor did not mint, or none at all, is refused, and neither the
10276        /// incumbent's registration nor the candidate slot moves.
10277        #[test]
10278        fn unminted_nonce_on_an_unreserved_id_with_an_open_swap_is_refused() {
10279            let (registry, _supervisor, handler) = handler_with_incumbent("aft", false);
10280
10281            for (connection, nonce) in [(2, Some("forged")), (3, None)] {
10282                let replies = handler
10283                    .handle_control(
10284                        ConnectionId::new(connection),
10285                        hello_frame_with_nonce("aft", PROTOCOL_VERSION, connection, nonce),
10286                    )
10287                    .unwrap();
10288                assert_eq!(replies[0].header.ty, FrameType::Error);
10289                assert_eq!(
10290                    parse_error(&replies[0])["code"],
10291                    "swap_token_invalid",
10292                    "nonce {nonce:?}"
10293                );
10294            }
10295            assert!(registry.get_candidate("aft").unwrap().is_none());
10296            assert_eq!(
10297                registry.get_module("aft").unwrap().unwrap().connection_id,
10298                ConnectionId::new(1)
10299            );
10300
10301            // Control: the minted token is admitted, into the candidate slot,
10302            // and only once.
10303            let admitted = handler
10304                .handle_control(
10305                    ConnectionId::new(4),
10306                    hello_frame_with_nonce("aft", PROTOCOL_VERSION, 4, Some(CANDIDATE_NONCE)),
10307                )
10308                .unwrap();
10309            assert_eq!(admitted[0].header.ty, FrameType::HelloAck);
10310            assert_eq!(
10311                registry
10312                    .get_candidate("aft")
10313                    .unwrap()
10314                    .unwrap()
10315                    .connection_id,
10316                ConnectionId::new(4)
10317            );
10318            assert_eq!(
10319                registry.get_module("aft").unwrap().unwrap().connection_id,
10320                ConnectionId::new(1),
10321                "the candidate must not take the active slot"
10322            );
10323            let replayed = handler
10324                .handle_control(
10325                    ConnectionId::new(5),
10326                    hello_frame_with_nonce("aft", PROTOCOL_VERSION, 5, Some(CANDIDATE_NONCE)),
10327                )
10328                .unwrap();
10329            assert_eq!(parse_error(&replayed[0])["code"], "swap_token_invalid");
10330
10331            // The case only this gate covers: the incumbent has died mid-swap,
10332            // so its duplicate refusal is gone too, and without the gate a
10333            // key-holder would take the id's ACTIVE slot.
10334            handler.cleanup_connection(ConnectionId::new(1)).unwrap();
10335            let squatter = handler
10336                .handle_control(
10337                    ConnectionId::new(6),
10338                    hello_frame_with_nonce("aft", PROTOCOL_VERSION, 6, Some("forged")),
10339                )
10340                .unwrap();
10341            assert_eq!(parse_error(&squatter[0])["code"], "swap_token_invalid");
10342            assert!(
10343                registry.get_module("aft").unwrap().is_none(),
10344                "a squatter took the active slot of an id being swapped"
10345            );
10346        }
10347
10348        /// Design mutation arm (iii). A reserved module's candidate presents a
10349        /// nonce the reserved gate has never seen (that gate holds the
10350        /// incumbent's), so the swap gate must run first or the candidate is
10351        /// refused `reserved_module` and a reserved module can never be swapped.
10352        #[test]
10353        fn reserved_module_candidate_is_admitted_ahead_of_the_reserved_gate() {
10354            let (registry, _supervisor, handler) = handler_with_incumbent("vault", true);
10355
10356            let replies = handler
10357                .handle_control(
10358                    ConnectionId::new(2),
10359                    hello_frame_with_nonce("vault", PROTOCOL_VERSION, 2, Some(CANDIDATE_NONCE)),
10360                )
10361                .unwrap();
10362
10363            assert_eq!(
10364                replies[0].header.ty,
10365                FrameType::HelloAck,
10366                "reserved candidate refused: {:?}",
10367                serde_json::from_slice::<Value>(&replies[0].body).ok()
10368            );
10369            assert_eq!(
10370                registry
10371                    .get_candidate("vault")
10372                    .unwrap()
10373                    .unwrap()
10374                    .connection_id,
10375                ConnectionId::new(2)
10376            );
10377        }
10378
10379        /// With no swap open the gate is inert: the incumbent's reserved gate
10380        /// and duplicate refusal behave exactly as before.
10381        #[test]
10382        fn without_an_open_swap_the_ordinary_gates_decide() {
10383            let (registry, supervisor, handler) = handler_with_incumbent("vault", true);
10384            supervisor.close_swap("vault");
10385
10386            let candidate = handler
10387                .handle_control(
10388                    ConnectionId::new(2),
10389                    hello_frame_with_nonce("vault", PROTOCOL_VERSION, 2, Some(CANDIDATE_NONCE)),
10390                )
10391                .unwrap();
10392            assert_eq!(parse_error(&candidate[0])["code"], "reserved_module");
10393            let duplicate = handler
10394                .handle_control(
10395                    ConnectionId::new(3),
10396                    hello_frame_with_nonce("vault", PROTOCOL_VERSION, 3, Some(INCUMBENT_NONCE)),
10397                )
10398                .unwrap();
10399            assert_eq!(parse_error(&duplicate[0])["code"], "duplicate_module_id");
10400            assert!(registry.get_candidate("vault").unwrap().is_none());
10401        }
10402    }
10403}
10404
10405#[cfg(test)]
10406mod concurrency_default_exposure_tests {
10407    use super::*;
10408
10409    fn hello_body(role_json: &str) -> Vec<u8> {
10410        format!(
10411            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":[]}}}}}}}}"#
10412        )
10413        .into_bytes()
10414    }
10415
10416    fn manifest_from(body: &[u8]) -> ModuleManifest {
10417        let value: serde_json::Value = serde_json::from_slice(body).expect("hello parses");
10418        serde_json::from_value(value.get("manifest").expect("manifest key").clone())
10419            .expect("manifest parses")
10420    }
10421
10422    const SURFACE_TAIL: &str = r#""operations":[],"config_schema":{"type":"object"},"observability":[],"identity_scope":[]"#;
10423
10424    #[test]
10425    fn absent_concurrency_on_management_surface_is_reported_as_defaulted() {
10426        let body = hello_body(&format!(
10427            r#"{{"role":"management_surface",{SURFACE_TAIL}}}"#
10428        ));
10429        let manifest = manifest_from(&body);
10430        // Precondition: serde really resolved it to the default, so the typed
10431        // manifest alone cannot answer the question this probe exists for.
10432        assert_eq!(manifest_concurrency(&manifest), Concurrency::ModuleManaged);
10433        assert!(manifest_concurrency_was_defaulted(&body, &manifest));
10434    }
10435
10436    #[test]
10437    fn declared_concurrency_is_not_reported_even_when_it_equals_the_default() {
10438        let body = hello_body(&format!(
10439            r#"{{"role":"management_surface",{SURFACE_TAIL},"concurrency":"module_managed"}}"#
10440        ));
10441        let manifest = manifest_from(&body);
10442        assert_eq!(manifest_concurrency(&manifest), Concurrency::ModuleManaged);
10443        assert!(!manifest_concurrency_was_defaulted(&body, &manifest));
10444    }
10445
10446    #[test]
10447    fn non_management_roles_are_never_reported() {
10448        let body = hello_body(
10449            r#"{"role":"internal_service","service_id":"s","transport":"bulk","agent_facing":false,"operations":[]}"#,
10450        );
10451        let manifest = manifest_from(&body);
10452        assert!(!manifest_concurrency_was_defaulted(&body, &manifest));
10453    }
10454}