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