Skip to main content

subc_daemon/
control.rs

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