Skip to repository content1391 lines · 54.4 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T03:07:45.106Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
router.rs
1//! The route decision. `OMEGA-DELTA-0029`, omega#78.
2//!
3//! Omega Agent is a router. It owns routing, disclosure, and receipts, and it
4//! owns no execution (omega#74, admitted by the owner 2026-07-25). This module
5//! is the decision half of that: a pure function from typed inputs to a typed
6//! decision, with no clock, no randomness, and no map iteration in the path.
7//!
8//! The dispatch half — the `AgentConnection` implementation that hands the turn
9//! to the executor this function names — lives in
10//! `crates/agent_ui/src/omega_router.rs`, because it needs GPUI and the three
11//! executor crates. Keeping the decision here means the routing law can be
12//! checked in a second, and means no decision is ever made inside a widget.
13//!
14//! # Determinism, stated as three rules
15//!
16//! 1. [`route`] reads nothing but its argument. No clock, no environment, no
17//! global.
18//! 2. Every collection it walks is an ordered slice, and every choice among
19//! equals is resolved by a total order on the lane reference — never by the
20//! order the engine happened to answer in, which is not a stable input.
21//! 3. Every decision it can make is recordable, and the record is total: the
22//! canonical form escapes the four characters that carry structure, so no
23//! lane reference is refused for a cosmetic reason. The one lane the router
24//! does refuse is a nameless one, because there is nothing to dispatch to
25//! and nothing to write down.
26//!
27//! `crates/omega_deltas` asserts (1) against the source text as well, so a
28//! later edit that reaches for `SystemTime` or a `HashMap` fails a test rather
29//! than quietly making the router non-reproducible.
30//!
31//! # Why an unpinned thread is never routed to an engine lane
32//!
33//! Owner gate 8: *no model-initiated path can start Full Auto authority; only
34//! an explicit human action can, wherever that action lives.* An engine lane is
35//! Full Auto authority. So a router that preferred an engine lane for an
36//! unpinned thread would be exactly the model-initiated start the gate forbids,
37//! reached through a new door nobody had flagged — which is how
38//! `full_auto_enable` survived until today.
39//!
40//! v1 therefore routes an unpinned thread to the native loop, always. Engine
41//! lanes are reachable only through a pin, and a pin is set by a visible
42//! control a person operates. Model-advisory routing is out of scope for v1 by
43//! the packet's own terms, and this is the shape that keeps it out.
44
45use crate::ExecutorClass;
46
47// -------------------------------------------------------------------------
48// Inputs
49// -------------------------------------------------------------------------
50
51/// A user's explicit choice of executor for a thread.
52///
53/// A pin is the *only* way a thread reaches anything but the native loop. It is
54/// set by a human gesture on a visible control and never by a turn, a slash
55/// command, or a restored draft.
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct ExecutorPin {
58 /// The class the user pinned.
59 pub class: ExecutorClass,
60 /// The engine lane the user pinned, when they named one.
61 ///
62 /// Meaningful only for [`ExecutorClass::EngineLane`]. A pin with no lane
63 /// means "any lane the engine says is ready", resolved by the total order
64 /// in [`select_lane`].
65 pub lane_ref: Option<String>,
66}
67
68impl ExecutorPin {
69 /// Pin a class without naming a lane.
70 #[must_use]
71 pub const fn new(class: ExecutorClass) -> Self {
72 Self {
73 class,
74 lane_ref: None,
75 }
76 }
77
78 /// Pin one named engine lane.
79 #[must_use]
80 pub fn on_lane(lane_ref: impl Into<String>) -> Self {
81 Self {
82 class: ExecutorClass::EngineLane,
83 lane_ref: Some(lane_ref.into()),
84 }
85 }
86
87 /// The stable token this pin is recorded under.
88 #[must_use]
89 pub fn token(&self) -> String {
90 match &self.lane_ref {
91 Some(lane_ref) => format!("{}@{}", self.class.token(), encode_field(lane_ref)),
92 None => self.class.token().to_owned(),
93 }
94 }
95
96 /// Read a pin back from [`token`](Self::token).
97 #[must_use]
98 pub fn parse_token(token: &str) -> Option<Self> {
99 let (class_token, lane_ref) = match token.split_once('@') {
100 Some((class_token, lane_ref)) => (class_token, Some(decode_field(lane_ref)?)),
101 None => (token, None),
102 };
103 let class = *ExecutorClass::all()
104 .iter()
105 .find(|class| class.token() == class_token)?;
106 Some(Self { class, lane_ref })
107 }
108}
109
110/// What one engine lane says about itself in a `get_capacity` answer.
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub enum LaneState {
113 /// Ready and idle.
114 Available,
115 /// Ready, but already serving a run.
116 Busy,
117 /// Anything else the engine says, including a state this build does not
118 /// recognise. Unrecognised is *not* available: a router that read an
119 /// unknown state as ready would route into a lane on the strength of not
120 /// understanding it.
121 Unavailable,
122}
123
124impl LaneState {
125 /// The stable token for this state.
126 #[must_use]
127 pub const fn token(self) -> &'static str {
128 match self {
129 Self::Available => "available",
130 Self::Busy => "busy",
131 Self::Unavailable => "unavailable",
132 }
133 }
134
135 /// Read the engine's `state` string.
136 ///
137 /// Anything unrecognised is [`Unavailable`](Self::Unavailable), which is
138 /// the fail-closed direction.
139 #[must_use]
140 pub fn parse(state: &str) -> Self {
141 match state {
142 "available" => Self::Available,
143 "busy" => Self::Busy,
144 _ => Self::Unavailable,
145 }
146 }
147
148 /// Whether a turn may be routed onto a lane in this state.
149 #[must_use]
150 pub const fn can_serve(self) -> bool {
151 matches!(self, Self::Available)
152 }
153}
154
155/// One lane of the engine's declared capacity.
156#[derive(Debug, Clone, PartialEq, Eq)]
157pub struct EngineLane {
158 /// The lane reference, exactly as the engine reports it.
159 pub lane_ref: String,
160 /// What the engine says about it.
161 pub state: LaneState,
162}
163
164impl EngineLane {
165 /// A lane record.
166 #[must_use]
167 pub fn new(lane_ref: impl Into<String>, state: LaneState) -> Self {
168 Self {
169 lane_ref: lane_ref.into(),
170 state,
171 }
172 }
173}
174
175/// Why the engine could not be asked.
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub enum EngineUnreachable {
178 /// No supervised `omega-effectd` process is running.
179 NotRunning,
180 /// The framed request did not answer in time.
181 Timeout,
182 /// The engine answered something this build could not read.
183 ProtocolError,
184}
185
186impl EngineUnreachable {
187 /// The stable token for this cause.
188 #[must_use]
189 pub const fn token(self) -> &'static str {
190 match self {
191 Self::NotRunning => "not_running",
192 Self::Timeout => "timeout",
193 Self::ProtocolError => "protocol_error",
194 }
195 }
196
197 /// Every admitted cause, in declaration order.
198 #[must_use]
199 pub const fn all() -> &'static [Self] {
200 &[Self::NotRunning, Self::Timeout, Self::ProtocolError]
201 }
202}
203
204/// What `omega-effectd` last said about itself.
205///
206/// A snapshot the router *reads*. The engine remains the sole run authority:
207/// nothing here is written back, and nothing here is treated as run state.
208#[derive(Debug, Clone, PartialEq, Eq)]
209pub enum EngineReadiness {
210 /// The engine answered `get_capacity`.
211 Answered {
212 /// Runs the engine currently has active.
213 active_run_count: u32,
214 /// The engine's own ceiling on active runs.
215 active_run_limit: u32,
216 /// The lanes it declared, in the order it declared them. The order is
217 /// deliberately not trusted; see [`select_lane`].
218 lanes: Vec<EngineLane>,
219 },
220 /// The engine could not be asked, or could not be understood.
221 Unreachable(EngineUnreachable),
222}
223
224impl EngineReadiness {
225 /// Whether the engine could be asked at all.
226 #[must_use]
227 pub const fn answered(&self) -> bool {
228 matches!(self, Self::Answered { .. })
229 }
230}
231
232/// Everything [`route`] is allowed to read.
233///
234/// If a decision cannot be explained from these fields, it is not a decision
235/// this router made — which is omega#78's falsifier, stated as a type.
236#[derive(Debug, Clone, PartialEq, Eq)]
237pub struct RouteInputs {
238 /// What the user pinned for this thread, if anything.
239 pub pin: Option<ExecutorPin>,
240 /// What the engine last said about itself.
241 pub engine: EngineReadiness,
242 /// The external ACP agent connected for this thread, if one is.
243 ///
244 /// `None` means no external agent is connected — not that none is
245 /// configured. A pin to an external agent that is not connected fails
246 /// closed rather than waiting.
247 pub external_acp: Option<String>,
248 /// The executor registered to serve engine lanes, if one is.
249 ///
250 /// Separate from [`engine`](Self::engine) on purpose, because they are two
251 /// different facts. The engine answering `get_capacity` says a lane exists;
252 /// this says Omega has something to hand the turn to. In this build they
253 /// come apart: engine lanes are started by a person on the Full Auto
254 /// surface and driven by the host bridge, not through
255 /// `AgentConnection::prompt`, so the router can *decide* an engine-lane
256 /// route it cannot itself dispatch. Modelling that as an input keeps the
257 /// gap a stated fallback with a reason instead of a panic or a silent
258 /// substitution.
259 pub engine_lane: Option<String>,
260}
261
262impl RouteInputs {
263 /// The inputs of a machine with no engine and no external agent.
264 #[must_use]
265 pub const fn native_only() -> Self {
266 Self {
267 pin: None,
268 engine: EngineReadiness::Unreachable(EngineUnreachable::NotRunning),
269 external_acp: None,
270 engine_lane: None,
271 }
272 }
273
274 /// The same inputs with a pin set.
275 #[must_use]
276 pub fn pinned(mut self, pin: ExecutorPin) -> Self {
277 self.pin = Some(pin);
278 self
279 }
280
281 /// The same inputs with an engine answer.
282 #[must_use]
283 pub fn with_engine(mut self, engine: EngineReadiness) -> Self {
284 self.engine = engine;
285 self
286 }
287
288 /// The same inputs with an external ACP agent connected.
289 #[must_use]
290 pub fn with_external_acp(mut self, agent_id: impl Into<String>) -> Self {
291 self.external_acp = Some(agent_id.into());
292 self
293 }
294
295 /// The same inputs with an executor registered for engine lanes.
296 #[must_use]
297 pub fn with_engine_lane(mut self, agent_id: impl Into<String>) -> Self {
298 self.engine_lane = Some(agent_id.into());
299 self
300 }
301}
302
303// -------------------------------------------------------------------------
304// The decision
305// -------------------------------------------------------------------------
306
307/// Why an executor was chosen.
308///
309/// A closed set of typed reasons, not a message. A route the user cannot have
310/// explained to them is the same defect class as a handoff with no system note.
311#[derive(Debug, Clone, Copy, PartialEq, Eq)]
312pub enum RouteReason {
313 /// Nothing was pinned, so the thread ran on the native loop. v1 never
314 /// routes an unpinned thread anywhere else; see the module docs.
315 UnpinnedDefault,
316 /// The user pinned this executor and it could serve.
317 PinHonored,
318 /// An engine lane was pinned and the engine could not be asked.
319 EngineUnreachable,
320 /// An engine lane was pinned and the engine was at its active-run limit.
321 EngineAtCapacity,
322 /// An engine lane was pinned without naming one, and the engine declared no
323 /// lane that could serve.
324 EngineHasNoReadyLane,
325 /// A named engine lane was pinned and the engine did not declare it ready.
326 PinnedLaneUnavailable,
327 /// An external ACP agent was pinned and none is connected.
328 ExternalAcpUnavailable,
329 /// The engine declared a lane the router could have used, and Omega has no
330 /// executor registered to hand the turn to. See
331 /// [`RouteInputs::engine_lane`].
332 EngineLaneNotConnected,
333 /// The lane that would have been chosen carries a reference the decision
334 /// record cannot hold. A decision that cannot be written down is not one
335 /// the router makes.
336 UnrecordableLane,
337}
338
339impl RouteReason {
340 /// The stable token this reason is recorded under.
341 #[must_use]
342 pub const fn token(self) -> &'static str {
343 match self {
344 Self::UnpinnedDefault => "unpinned_default",
345 Self::PinHonored => "pin_honored",
346 Self::EngineUnreachable => "engine_unreachable",
347 Self::EngineAtCapacity => "engine_at_capacity",
348 Self::EngineHasNoReadyLane => "engine_has_no_ready_lane",
349 Self::PinnedLaneUnavailable => "pinned_lane_unavailable",
350 Self::ExternalAcpUnavailable => "external_acp_unavailable",
351 Self::EngineLaneNotConnected => "engine_lane_not_connected",
352 Self::UnrecordableLane => "unrecordable_lane",
353 }
354 }
355
356 /// Every admitted reason, in declaration order.
357 #[must_use]
358 pub const fn all() -> &'static [Self] {
359 &[
360 Self::UnpinnedDefault,
361 Self::PinHonored,
362 Self::EngineUnreachable,
363 Self::EngineAtCapacity,
364 Self::EngineHasNoReadyLane,
365 Self::PinnedLaneUnavailable,
366 Self::ExternalAcpUnavailable,
367 Self::EngineLaneNotConnected,
368 Self::UnrecordableLane,
369 ]
370 }
371
372 /// Read a reason back from [`token`](Self::token).
373 #[must_use]
374 pub fn parse_token(token: &str) -> Option<Self> {
375 Self::all()
376 .iter()
377 .find(|reason| reason.token() == token)
378 .copied()
379 }
380
381 /// Whether this reason means a pin could not be honoured.
382 ///
383 /// Every fail-closed reason lands on the native loop. That is asserted in
384 /// [`RouteDecision::is_coherent`] rather than merely intended.
385 #[must_use]
386 pub const fn is_fallback(self) -> bool {
387 match self {
388 Self::UnpinnedDefault | Self::PinHonored => false,
389 Self::EngineUnreachable
390 | Self::EngineAtCapacity
391 | Self::EngineHasNoReadyLane
392 | Self::PinnedLaneUnavailable
393 | Self::ExternalAcpUnavailable
394 | Self::EngineLaneNotConnected
395 | Self::UnrecordableLane => true,
396 }
397 }
398
399 /// What the user is told, in their terms.
400 ///
401 /// Derived on every call. Nothing stores it — the reason is the record, and
402 /// this is one rendering of it. A fallback says it fell back, because a
403 /// fallback the user cannot see is the defect this packet exists to avoid.
404 #[must_use]
405 pub const fn phrase(self) -> &'static str {
406 match self {
407 Self::UnpinnedDefault => "unpinned",
408 Self::PinHonored => "pinned",
409 Self::EngineUnreachable => "engine unreachable, fell back to the native loop",
410 Self::EngineAtCapacity => "engine at capacity, fell back to the native loop",
411 Self::EngineHasNoReadyLane => "engine has no ready lane, fell back to the native loop",
412 Self::PinnedLaneUnavailable => {
413 "pinned engine lane unavailable, fell back to the native loop"
414 }
415 Self::ExternalAcpUnavailable => {
416 "pinned external agent not connected, fell back to the native loop"
417 }
418 Self::EngineLaneNotConnected => {
419 "no executor is connected for engine lanes, fell back to the native loop"
420 }
421 Self::UnrecordableLane => {
422 "engine lane could not be recorded, fell back to the native loop"
423 }
424 }
425 }
426}
427
428/// One routing decision, in full.
429///
430/// Durable and inspectable through [`canonical_record`](Self::canonical_record),
431/// which round-trips. It carries no timestamp on purpose: a clock in the record
432/// would make two identical decisions look different, and would put a
433/// non-deterministic value in the decision path this packet exists to keep
434/// reproducible.
435#[derive(Debug, Clone, PartialEq, Eq)]
436pub struct RouteDecision {
437 /// The class the turn was dispatched to.
438 pub chosen: ExecutorClass,
439 /// Why.
440 pub reason: RouteReason,
441 /// What was pinned when the decision was made, if anything. Kept even when
442 /// the pin could not be honoured — an unhonoured pin the record forgot is
443 /// indistinguishable from no pin at all.
444 pub pin: Option<ExecutorPin>,
445 /// The engine lane the turn was dispatched to.
446 ///
447 /// `Some` exactly when [`chosen`](Self::chosen) is
448 /// [`ExecutorClass::EngineLane`].
449 pub lane_ref: Option<String>,
450}
451
452/// Characters the canonical record escapes, and the escape character itself.
453///
454/// The record is a flat `key=value;…` line in which a pin may name a lane after
455/// an `@`, so these three would otherwise give it a second reading. `%` is
456/// escaped too, which is what makes the encoding a bijection rather than merely
457/// a substitution — without it, a literal `%3B` in a lane reference and an
458/// escaped `;` would decode to the same thing.
459pub const RESERVED_RECORD_CHARACTERS: &[char] = &['%', ';', '=', '@'];
460
461/// Write one field of the canonical record.
462///
463/// Percent-escapes the four reserved characters and nothing else, so a lane
464/// reference such as `acp:cursor-agent` is written verbatim and stays legible
465/// to a person reading the journal.
466#[must_use]
467pub fn encode_field(value: &str) -> String {
468 let mut encoded = String::with_capacity(value.len());
469 for character in value.chars() {
470 match character {
471 '%' => encoded.push_str("%25"),
472 ';' => encoded.push_str("%3B"),
473 '=' => encoded.push_str("%3D"),
474 '@' => encoded.push_str("%40"),
475 other => encoded.push(other),
476 }
477 }
478 encoded
479}
480
481/// Read one field of the canonical record.
482///
483/// Returns `None` for a truncated or unknown escape rather than guessing, so a
484/// half-written journal entry is rejected instead of decoded into something
485/// nobody wrote.
486#[must_use]
487pub fn decode_field(value: &str) -> Option<String> {
488 let mut decoded = String::with_capacity(value.len());
489 let mut characters = value.chars();
490 while let Some(character) = characters.next() {
491 if character != '%' {
492 decoded.push(character);
493 continue;
494 }
495 let escape: String = characters.by_ref().take(2).collect();
496 match escape.as_str() {
497 "25" => decoded.push('%'),
498 "3B" => decoded.push(';'),
499 "3D" => decoded.push('='),
500 "40" => decoded.push('@'),
501 _ => return None,
502 }
503 }
504 Some(decoded)
505}
506
507/// Whether a lane reference can be routed to.
508///
509/// Every non-empty reference can: the record escapes anything that would make
510/// it ambiguous. An *empty* one cannot, and that is not a cosmetic problem —
511/// there is nothing to dispatch to and nothing to write down. The router refuses
512/// it rather than routing into a lane it cannot name.
513#[must_use]
514pub fn lane_ref_is_recordable(lane_ref: &str) -> bool {
515 !lane_ref.is_empty()
516}
517
518impl RouteDecision {
519 /// A decision that landed on the native loop.
520 fn native(reason: RouteReason, pin: Option<ExecutorPin>) -> Self {
521 Self {
522 chosen: ExecutorClass::NativeLoop,
523 reason,
524 pin,
525 lane_ref: None,
526 }
527 }
528
529 /// Whether this decision is internally consistent.
530 ///
531 /// Every clause here is a way the router could have lied about itself: a
532 /// fallback that did not land on the native loop, a lane reference on a
533 /// non-engine route, an honoured pin that names a different class from the
534 /// one that ran.
535 #[must_use]
536 pub fn is_coherent(&self) -> bool {
537 let lane_matches_class = match self.chosen {
538 ExecutorClass::EngineLane => self
539 .lane_ref
540 .as_deref()
541 .is_some_and(lane_ref_is_recordable),
542 ExecutorClass::NativeLoop | ExecutorClass::ExternalAcp => self.lane_ref.is_none(),
543 };
544 let fallback_lands_native = !self.reason.is_fallback()
545 || (self.chosen == ExecutorClass::NativeLoop && self.pin.is_some());
546 let unpinned_is_unpinned =
547 self.reason != RouteReason::UnpinnedDefault || self.pin.is_none();
548 let honoured_pin_names_the_class = self.reason != RouteReason::PinHonored
549 || self
550 .pin
551 .as_ref()
552 .is_some_and(|pin| pin.class == self.chosen);
553
554 lane_matches_class
555 && fallback_lands_native
556 && unpinned_is_unpinned
557 && honoured_pin_names_the_class
558 }
559
560 /// The typed reason this decision contributes to a thread's disclosure.
561 #[must_use]
562 pub const fn disclosed_route(&self) -> RouteReason {
563 self.reason
564 }
565
566 /// The durable form. Deterministic, total, and round-trippable.
567 ///
568 /// Written to the route journal verbatim. An empty value is the absent
569 /// marker, which is unambiguous because no pin token and no routable lane
570 /// reference is ever empty — a sentinel such as `-` would collide with a
571 /// lane genuinely named `-`, and escaping `-` would make `claude-local`
572 /// unreadable.
573 #[must_use]
574 pub fn canonical_record(&self) -> String {
575 let pin = match &self.pin {
576 Some(pin) => pin.token(),
577 None => String::new(),
578 };
579 let lane = match &self.lane_ref {
580 Some(lane_ref) => encode_field(lane_ref),
581 None => String::new(),
582 };
583 format!(
584 "chosen={};reason={};pin={pin};lane={lane}",
585 self.chosen.token(),
586 self.reason.token(),
587 )
588 }
589
590 /// Read a decision back from [`canonical_record`](Self::canonical_record).
591 ///
592 /// Returns `None` for anything it cannot read *or* for a record that is
593 /// readable but incoherent, so a hand-edited or half-written journal entry
594 /// is rejected rather than believed. Every one of the four keys must be
595 /// present exactly once: a partial record is a truncated write, and reading
596 /// one as a decision would invent the missing parts.
597 #[must_use]
598 pub fn parse_canonical_record(record: &str) -> Option<Self> {
599 let mut chosen = None;
600 let mut reason = None;
601 let mut pin = None;
602 let mut lane = None;
603 let mut keys = 0usize;
604 for field in record.split(';') {
605 let (key, value) = field.split_once('=')?;
606 keys += 1;
607 match key {
608 "chosen" if chosen.is_none() => {
609 chosen = Some(
610 *ExecutorClass::all()
611 .iter()
612 .find(|class| class.token() == value)?,
613 );
614 }
615 "reason" if reason.is_none() => reason = Some(RouteReason::parse_token(value)?),
616 "pin" if pin.is_none() => {
617 pin = Some(if value.is_empty() {
618 None
619 } else {
620 Some(ExecutorPin::parse_token(value)?)
621 });
622 }
623 "lane" if lane.is_none() => {
624 lane = Some(if value.is_empty() {
625 None
626 } else {
627 Some(decode_field(value)?)
628 });
629 }
630 _ => return None,
631 }
632 }
633 if keys != 4 {
634 return None;
635 }
636 let decision = Self {
637 chosen: chosen?,
638 reason: reason?,
639 pin: pin?,
640 lane_ref: lane?,
641 };
642 decision.is_coherent().then_some(decision)
643 }
644
645 /// One sentence explaining the decision from its parts.
646 ///
647 /// Derived, never stored. Used in logs and in the inspector.
648 #[must_use]
649 pub fn explain(&self) -> String {
650 let mut line = format!("{} ({})", self.chosen.token(), self.reason.phrase());
651 if let Some(lane_ref) = &self.lane_ref {
652 line.push_str(" on ");
653 line.push_str(lane_ref);
654 }
655 if let Some(pin) = &self.pin {
656 line.push_str("; pin ");
657 line.push_str(&pin.token());
658 if self.reason.is_fallback() {
659 line.push_str(" could not be honoured");
660 }
661 }
662 line
663 }
664}
665
666// -------------------------------------------------------------------------
667// The decision itself
668// -------------------------------------------------------------------------
669
670/// Choose the lane a decision would use.
671///
672/// Two rules, both total:
673///
674/// * A pin that names a lane matches that lane and no other, and only when the
675/// engine declared it able to serve.
676/// * A pin that names no lane takes the **lexicographically smallest** lane
677/// able to serve. Not the first the engine listed: the engine's array order
678/// is not a stable input, so trusting it would make the same capacity answer
679/// route two ways on two runs. A total order on the reference is the cheapest
680/// thing that cannot do that.
681#[must_use]
682pub fn select_lane(lanes: &[EngineLane], pinned: Option<&str>) -> Option<String> {
683 match pinned {
684 Some(pinned) => lanes
685 .iter()
686 .find(|lane| lane.lane_ref == pinned && lane.state.can_serve())
687 .map(|lane| lane.lane_ref.clone()),
688 None => lanes
689 .iter()
690 .filter(|lane| lane.state.can_serve())
691 .map(|lane| lane.lane_ref.as_str())
692 .min()
693 .map(str::to_owned),
694 }
695}
696
697/// Decide where a turn runs.
698///
699/// The whole routing law. Pure: same inputs, same decision, every time.
700#[must_use]
701pub fn route(inputs: &RouteInputs) -> RouteDecision {
702 let Some(pin) = inputs.pin.clone() else {
703 // Nothing pinned. The native loop, always — see the module docs on
704 // owner gate 8.
705 return RouteDecision::native(RouteReason::UnpinnedDefault, None);
706 };
707
708 match pin.class {
709 // Pinning the first-party loop is always honourable: it is the
710 // fail-closed target, so it is available whenever Omega is running.
711 ExecutorClass::NativeLoop => RouteDecision {
712 chosen: ExecutorClass::NativeLoop,
713 reason: RouteReason::PinHonored,
714 pin: Some(pin),
715 lane_ref: None,
716 },
717
718 ExecutorClass::ExternalAcp => {
719 if inputs.external_acp.is_some() {
720 RouteDecision {
721 chosen: ExecutorClass::ExternalAcp,
722 reason: RouteReason::PinHonored,
723 pin: Some(pin),
724 lane_ref: None,
725 }
726 } else {
727 RouteDecision::native(RouteReason::ExternalAcpUnavailable, Some(pin))
728 }
729 }
730
731 ExecutorClass::EngineLane => match &inputs.engine {
732 // Engine down. Fail closed to the native loop, and say which way it
733 // was down: `EngineUnreachable` is one reason, but the cause it
734 // carries is what an operator needs.
735 EngineReadiness::Unreachable(_) => {
736 RouteDecision::native(RouteReason::EngineUnreachable, Some(pin))
737 }
738 EngineReadiness::Answered {
739 active_run_count,
740 active_run_limit,
741 lanes,
742 } => {
743 if active_run_count >= active_run_limit {
744 return RouteDecision::native(RouteReason::EngineAtCapacity, Some(pin));
745 }
746 match select_lane(lanes, pin.lane_ref.as_deref()) {
747 Some(lane_ref) if !lane_ref_is_recordable(&lane_ref) => {
748 RouteDecision::native(RouteReason::UnrecordableLane, Some(pin))
749 }
750 // The lane exists and the engine says it is ready. Whether
751 // Omega can hand a turn to it is a separate fact, checked
752 // last so an operator hears about the engine first.
753 Some(_) if inputs.engine_lane.is_none() => {
754 RouteDecision::native(RouteReason::EngineLaneNotConnected, Some(pin))
755 }
756 Some(lane_ref) => RouteDecision {
757 chosen: ExecutorClass::EngineLane,
758 reason: RouteReason::PinHonored,
759 pin: Some(pin),
760 lane_ref: Some(lane_ref),
761 },
762 None if pin.lane_ref.is_some() => {
763 RouteDecision::native(RouteReason::PinnedLaneUnavailable, Some(pin))
764 }
765 None => RouteDecision::native(RouteReason::EngineHasNoReadyLane, Some(pin)),
766 }
767 }
768 },
769 }
770}
771
772#[cfg(test)]
773mod tests {
774 use super::*;
775
776 fn ready_engine() -> EngineReadiness {
777 EngineReadiness::Answered {
778 active_run_count: 1,
779 active_run_limit: 8,
780 lanes: vec![
781 EngineLane::new("codex-local", LaneState::Busy),
782 EngineLane::new("claude-local", LaneState::Available),
783 EngineLane::new("acp:cursor-agent", LaneState::Available),
784 ],
785 }
786 }
787
788 // ---------------------------------------------------------------------
789 // Exit property 1: pins honoured
790 // ---------------------------------------------------------------------
791
792 /// An explicitly pinned, available executor is used. Every time, for every
793 /// class, regardless of what else is ready.
794 ///
795 /// Falsified by making `route` prefer an available engine lane over a
796 /// native pin: this test fails on the native case.
797 #[test]
798 fn a_pin_to_an_available_executor_is_always_used() {
799 let base = RouteInputs::native_only()
800 .with_engine(ready_engine())
801 .with_external_acp("codex-acp")
802 .with_engine_lane("codex-local");
803
804 let native = route(&base.clone().pinned(ExecutorPin::new(ExecutorClass::NativeLoop)));
805 assert_eq!(native.chosen, ExecutorClass::NativeLoop);
806 assert_eq!(native.reason, RouteReason::PinHonored);
807
808 let external = route(
809 &base
810 .clone()
811 .pinned(ExecutorPin::new(ExecutorClass::ExternalAcp)),
812 );
813 assert_eq!(external.chosen, ExecutorClass::ExternalAcp);
814 assert_eq!(external.reason, RouteReason::PinHonored);
815
816 let lane = route(&base.pinned(ExecutorPin::on_lane("claude-local")));
817 assert_eq!(lane.chosen, ExecutorClass::EngineLane);
818 assert_eq!(lane.reason, RouteReason::PinHonored);
819 assert_eq!(lane.lane_ref.as_deref(), Some("claude-local"));
820
821 for decision in [native, external, lane] {
822 assert!(decision.is_coherent(), "{decision:?}");
823 }
824 }
825
826 /// A pin outranks everything the engine is offering. The engine being ready
827 /// and idle is not a reason to move a turn the user placed.
828 #[test]
829 fn a_ready_engine_never_takes_a_thread_away_from_its_pin() {
830 let inputs = RouteInputs::native_only()
831 .with_engine(ready_engine())
832 .with_external_acp("codex-acp")
833 .pinned(ExecutorPin::new(ExecutorClass::NativeLoop));
834 assert_eq!(route(&inputs).chosen, ExecutorClass::NativeLoop);
835 assert!(route(&inputs).lane_ref.is_none());
836 }
837
838 /// Owner gate 8, as a routing law: nothing but a pin reaches an engine
839 /// lane, so no model-initiated path can start Full Auto authority through
840 /// the router.
841 #[test]
842 fn an_unpinned_thread_never_reaches_an_engine_lane() {
843 let inputs = RouteInputs::native_only()
844 .with_engine(ready_engine())
845 .with_external_acp("codex-acp")
846 // Everything an engine-lane route needs is present, so the native
847 // answer below is a decision rather than an absence.
848 .with_engine_lane("codex-local");
849 assert_eq!(
850 route(&inputs.clone().pinned(ExecutorPin::on_lane("claude-local"))).chosen,
851 ExecutorClass::EngineLane,
852 "an engine lane is reachable from these inputs, so the unpinned \
853 case below is not passing by default"
854 );
855 let decision = route(&inputs);
856 assert_eq!(decision.chosen, ExecutorClass::NativeLoop);
857 assert_eq!(decision.reason, RouteReason::UnpinnedDefault);
858 assert!(decision.lane_ref.is_none());
859 assert!(decision.pin.is_none());
860 assert!(decision.is_coherent());
861 }
862
863 // ---------------------------------------------------------------------
864 // Exit property 2: engine-down fails closed to native, visibly
865 // ---------------------------------------------------------------------
866
867 /// Every way the engine can be unavailable lands on the native loop, keeps
868 /// the pin it could not honour, and says so with a fallback reason.
869 ///
870 /// Falsified by returning `EngineLane` from the `Unreachable` arm: this
871 /// test fails on `chosen`, and `a_fallback_is_never_silent` fails too.
872 #[test]
873 fn an_unavailable_engine_falls_closed_to_the_native_loop() {
874 let pin = ExecutorPin::on_lane("claude-local");
875
876 let mut cases: Vec<(EngineReadiness, RouteReason)> = EngineUnreachable::all()
877 .iter()
878 .map(|cause| {
879 (
880 EngineReadiness::Unreachable(*cause),
881 RouteReason::EngineUnreachable,
882 )
883 })
884 .collect();
885 cases.push((
886 EngineReadiness::Answered {
887 active_run_count: 8,
888 active_run_limit: 8,
889 lanes: vec![EngineLane::new("claude-local", LaneState::Available)],
890 },
891 RouteReason::EngineAtCapacity,
892 ));
893 cases.push((
894 EngineReadiness::Answered {
895 active_run_count: 0,
896 active_run_limit: 8,
897 lanes: vec![EngineLane::new("claude-local", LaneState::Busy)],
898 },
899 RouteReason::PinnedLaneUnavailable,
900 ));
901 cases.push((
902 EngineReadiness::Answered {
903 active_run_count: 0,
904 active_run_limit: 8,
905 lanes: vec![],
906 },
907 RouteReason::PinnedLaneUnavailable,
908 ));
909
910 for (engine, expected) in cases {
911 let decision = route(&RouteInputs::native_only().with_engine(engine.clone()).pinned(pin.clone()));
912 assert_eq!(
913 decision.chosen,
914 ExecutorClass::NativeLoop,
915 "engine {engine:?} did not fail closed"
916 );
917 assert_eq!(decision.reason, expected, "engine {engine:?}");
918 assert!(decision.reason.is_fallback());
919 assert_eq!(
920 decision.pin.as_ref(),
921 Some(&pin),
922 "a fallback that forgets the pin it could not honour is \
923 indistinguishable from an unpinned thread"
924 );
925 assert!(decision.is_coherent(), "{decision:?}");
926 }
927 }
928
929 /// An engine lane pinned without naming one, with nothing ready, is a
930 /// different fallback from a named lane that is busy — and the record says
931 /// which.
932 #[test]
933 fn an_engine_with_no_ready_lane_is_its_own_reason() {
934 let decision = route(
935 &RouteInputs::native_only()
936 .with_engine(EngineReadiness::Answered {
937 active_run_count: 0,
938 active_run_limit: 8,
939 lanes: vec![EngineLane::new("codex-local", LaneState::Busy)],
940 })
941 .pinned(ExecutorPin::new(ExecutorClass::EngineLane)),
942 );
943 assert_eq!(decision.reason, RouteReason::EngineHasNoReadyLane);
944 assert_eq!(decision.chosen, ExecutorClass::NativeLoop);
945 }
946
947 /// A pinned external agent that is not connected fails closed the same way
948 /// the engine does, rather than waiting or erroring.
949 #[test]
950 fn a_disconnected_external_agent_falls_closed_to_the_native_loop() {
951 let decision = route(
952 &RouteInputs::native_only().pinned(ExecutorPin::new(ExecutorClass::ExternalAcp)),
953 );
954 assert_eq!(decision.chosen, ExecutorClass::NativeLoop);
955 assert_eq!(decision.reason, RouteReason::ExternalAcpUnavailable);
956 assert!(decision.is_coherent());
957 }
958
959 /// A ready engine with nothing to dispatch onto is its own fallback, and
960 /// the operator hears about the engine first.
961 ///
962 /// This is the gap between "the engine has a lane" and "Omega can hand this
963 /// turn to it". In this build they come apart, and the honest answer is a
964 /// named reason rather than a panic, a hang, or a substitution nobody sees.
965 #[test]
966 fn a_ready_lane_with_no_executor_behind_it_falls_closed() {
967 let decision = route(
968 &RouteInputs::native_only()
969 .with_engine(ready_engine())
970 .pinned(ExecutorPin::on_lane("claude-local")),
971 );
972 assert_eq!(decision.chosen, ExecutorClass::NativeLoop);
973 assert_eq!(decision.reason, RouteReason::EngineLaneNotConnected);
974 assert!(decision.is_coherent());
975
976 // The engine being down outranks it: an operator needs the runtime
977 // fact before the wiring fact.
978 let engine_down = route(
979 &RouteInputs::native_only()
980 .with_engine(EngineReadiness::Unreachable(EngineUnreachable::Timeout))
981 .pinned(ExecutorPin::on_lane("claude-local")),
982 );
983 assert_eq!(engine_down.reason, RouteReason::EngineUnreachable);
984 }
985
986 /// A fallback is always disclosable, because every fallback reason renders
987 /// a phrase that says it fell back.
988 ///
989 /// A fallback the user cannot see is the same defect class as a handoff
990 /// with no system note, which shipped in rc11.
991 #[test]
992 fn a_fallback_is_never_silent() {
993 for reason in RouteReason::all() {
994 if !reason.is_fallback() {
995 continue;
996 }
997 assert!(
998 reason.phrase().contains("fell back to the native loop"),
999 "{} renders as {:?}, which does not tell the reader a pin was \
1000 not honoured",
1001 reason.token(),
1002 reason.phrase()
1003 );
1004 }
1005 }
1006
1007 /// An unrecognised lane state is not a ready lane.
1008 #[test]
1009 fn an_unrecognised_lane_state_is_not_available() {
1010 assert_eq!(LaneState::parse("available"), LaneState::Available);
1011 assert_eq!(LaneState::parse("busy"), LaneState::Busy);
1012 assert_eq!(LaneState::parse("draining"), LaneState::Unavailable);
1013 assert!(!LaneState::parse("draining").can_serve());
1014
1015 let decision = route(
1016 &RouteInputs::native_only()
1017 .with_engine(EngineReadiness::Answered {
1018 active_run_count: 0,
1019 active_run_limit: 8,
1020 lanes: vec![EngineLane::new("codex-local", LaneState::parse("draining"))],
1021 })
1022 .pinned(ExecutorPin::on_lane("codex-local")),
1023 );
1024 assert_eq!(decision.chosen, ExecutorClass::NativeLoop);
1025 }
1026
1027 // ---------------------------------------------------------------------
1028 // Determinism
1029 // ---------------------------------------------------------------------
1030
1031 /// The same inputs give the same route, whatever order the engine listed
1032 /// its lanes in.
1033 ///
1034 /// The engine's array order is not a stable input: the same logical
1035 /// capacity can come back ordered differently. A router that took "the
1036 /// first available lane" would route the same thread two ways on two runs
1037 /// and nothing would be wrong with either.
1038 ///
1039 /// Falsified by replacing `.min()` with `.next()` in `select_lane`: this
1040 /// test fails on the reversed permutation.
1041 #[test]
1042 fn routing_is_invariant_under_lane_order() {
1043 let lanes = vec![
1044 EngineLane::new("harness:opencode", LaneState::Available),
1045 EngineLane::new("claude-local", LaneState::Available),
1046 EngineLane::new("acp:cursor-agent", LaneState::Available),
1047 EngineLane::new("codex-local", LaneState::Busy),
1048 ];
1049 let pin = ExecutorPin::new(ExecutorClass::EngineLane);
1050 let connected = RouteInputs::native_only().with_engine_lane("codex-local");
1051
1052 let mut permutations = vec![lanes.clone()];
1053 let mut reversed = lanes.clone();
1054 reversed.reverse();
1055 permutations.push(reversed);
1056 let mut rotated = lanes.clone();
1057 rotated.rotate_left(2);
1058 permutations.push(rotated);
1059 let mut swapped = lanes;
1060 swapped.swap(0, 1);
1061 permutations.push(swapped);
1062
1063 let expected = RouteDecision {
1064 chosen: ExecutorClass::EngineLane,
1065 reason: RouteReason::PinHonored,
1066 pin: Some(pin.clone()),
1067 // Lexicographically smallest available lane. `codex-local` sorts
1068 // after it but is busy; `harness:opencode` sorts after it too.
1069 lane_ref: Some("acp:cursor-agent".to_owned()),
1070 };
1071
1072 for lanes in permutations {
1073 let decision = route(
1074 &connected
1075 .clone()
1076 .with_engine(EngineReadiness::Answered {
1077 active_run_count: 0,
1078 active_run_limit: 8,
1079 lanes: lanes.clone(),
1080 })
1081 .pinned(pin.clone()),
1082 );
1083 assert_eq!(decision, expected, "lane order {lanes:?} changed the route");
1084 }
1085 }
1086
1087 /// Routing the same inputs repeatedly gives byte-identical records.
1088 ///
1089 /// This is the property a clock in the record would break, which is why
1090 /// there is not one.
1091 #[test]
1092 fn the_same_inputs_give_the_same_record_every_time() {
1093 let inputs = RouteInputs::native_only()
1094 .with_engine(ready_engine())
1095 .with_engine_lane("codex-local")
1096 .pinned(ExecutorPin::on_lane("claude-local"));
1097 let first = route(&inputs).canonical_record();
1098 for _ in 0..64 {
1099 assert_eq!(route(&inputs).canonical_record(), first);
1100 }
1101 assert_eq!(
1102 first,
1103 "chosen=engine_lane;reason=pin_honored;pin=engine_lane@claude-local;lane=claude-local"
1104 );
1105 }
1106
1107 // ---------------------------------------------------------------------
1108 // Exit property 3: the decision is recorded
1109 // ---------------------------------------------------------------------
1110
1111 /// Every decision the router can reach survives being written down and read
1112 /// back.
1113 ///
1114 /// Falsified by dropping the `pin` field from `canonical_record`: this test
1115 /// fails on every fallback, because the pin comes back `None`.
1116 #[test]
1117 fn every_reachable_decision_round_trips_through_its_record() {
1118 let engines = [
1119 EngineReadiness::Unreachable(EngineUnreachable::NotRunning),
1120 EngineReadiness::Unreachable(EngineUnreachable::Timeout),
1121 EngineReadiness::Unreachable(EngineUnreachable::ProtocolError),
1122 EngineReadiness::Answered {
1123 active_run_count: 8,
1124 active_run_limit: 8,
1125 lanes: vec![],
1126 },
1127 ready_engine(),
1128 EngineReadiness::Answered {
1129 active_run_count: 0,
1130 active_run_limit: 8,
1131 lanes: vec![EngineLane::new("codex-local", LaneState::Busy)],
1132 },
1133 EngineReadiness::Answered {
1134 active_run_count: 0,
1135 active_run_limit: 8,
1136 lanes: vec![EngineLane::new("", LaneState::Available)],
1137 },
1138 ];
1139 let pins = [
1140 None,
1141 Some(ExecutorPin::new(ExecutorClass::NativeLoop)),
1142 Some(ExecutorPin::new(ExecutorClass::ExternalAcp)),
1143 Some(ExecutorPin::new(ExecutorClass::EngineLane)),
1144 Some(ExecutorPin::on_lane("claude-local")),
1145 Some(ExecutorPin::on_lane("a;b=c@d")),
1146 ];
1147
1148 let mut seen_reasons = Vec::new();
1149 for engine in &engines {
1150 for pin in &pins {
1151 for (external, engine_lane) in [
1152 (None, None),
1153 (Some("codex-acp".to_owned()), None),
1154 (None, Some("codex-local".to_owned())),
1155 (Some("codex-acp".to_owned()), Some("codex-local".to_owned())),
1156 ] {
1157 let inputs = RouteInputs {
1158 pin: pin.clone(),
1159 engine: engine.clone(),
1160 external_acp: external,
1161 engine_lane,
1162 };
1163 let decision = route(&inputs);
1164 assert!(decision.is_coherent(), "{decision:?} from {inputs:?}");
1165
1166 let record = decision.canonical_record();
1167 assert_eq!(
1168 RouteDecision::parse_canonical_record(&record).as_ref(),
1169 Some(&decision),
1170 "record {record:?} did not read back"
1171 );
1172 if !seen_reasons.contains(&decision.reason) {
1173 seen_reasons.push(decision.reason);
1174 }
1175 }
1176 }
1177 }
1178
1179 // A round-trip suite that never reaches a reason proves nothing about
1180 // it. Every admitted reason has to appear.
1181 for reason in RouteReason::all() {
1182 assert!(
1183 seen_reasons.contains(reason),
1184 "no input in this suite produced {}; the round-trip check is \
1185 vacuous for it",
1186 reason.token()
1187 );
1188 }
1189 }
1190
1191 /// A lane whose reference contains a character the record uses structurally
1192 /// is still routable, because the record escapes it and reads it back
1193 /// unchanged.
1194 ///
1195 /// The first draft of this packet refused such lanes instead. The
1196 /// round-trip suite falsified that immediately: an *unhonoured pin* can
1197 /// carry the same reference, and a refusal at the routing step does not
1198 /// stop it reaching the record, so a decision the router really made could
1199 /// not be written down. Escaping is total; refusal was not.
1200 #[test]
1201 fn a_lane_with_a_structural_character_still_round_trips() {
1202 for lane_ref in ["bad;lane", "bad=lane", "bad@lane", "100%lane", "-"] {
1203 assert!(lane_ref_is_recordable(lane_ref), "{lane_ref:?}");
1204
1205 let decision = route(
1206 &RouteInputs::native_only()
1207 .with_engine_lane("codex-local")
1208 .with_engine(EngineReadiness::Answered {
1209 active_run_count: 0,
1210 active_run_limit: 8,
1211 lanes: vec![EngineLane::new(lane_ref, LaneState::Available)],
1212 })
1213 .pinned(ExecutorPin::on_lane(lane_ref)),
1214 );
1215 assert_eq!(decision.chosen, ExecutorClass::EngineLane, "{lane_ref:?}");
1216 assert_eq!(decision.lane_ref.as_deref(), Some(lane_ref));
1217
1218 let record = decision.canonical_record();
1219 assert_eq!(record.split(';').count(), 4, "{record:?} lost its shape");
1220 assert_eq!(
1221 RouteDecision::parse_canonical_record(&record).as_ref(),
1222 Some(&decision),
1223 "{record:?}"
1224 );
1225 }
1226 }
1227
1228 /// A lane with no name cannot be dispatched to and cannot be written down,
1229 /// so the router refuses it instead of routing into a lane it cannot name.
1230 #[test]
1231 fn a_nameless_lane_is_not_routed_to() {
1232 assert!(!lane_ref_is_recordable(""));
1233 let decision = route(
1234 &RouteInputs::native_only()
1235 .with_engine(EngineReadiness::Answered {
1236 active_run_count: 0,
1237 active_run_limit: 8,
1238 lanes: vec![EngineLane::new("", LaneState::Available)],
1239 })
1240 .pinned(ExecutorPin::new(ExecutorClass::EngineLane)),
1241 );
1242 assert_eq!(decision.chosen, ExecutorClass::NativeLoop);
1243 assert_eq!(decision.reason, RouteReason::UnrecordableLane);
1244 assert!(decision.is_coherent());
1245 }
1246
1247 /// The field encoding is a bijection, which is the property that stops an
1248 /// escaped `;` and a literal `%3B` decoding to the same lane.
1249 #[test]
1250 fn the_field_encoding_round_trips_and_rejects_bad_escapes() {
1251 for value in [
1252 "claude-local",
1253 "acp:cursor-agent",
1254 "a;b=c@d",
1255 "%3B",
1256 "%",
1257 "%%",
1258 "",
1259 ] {
1260 assert_eq!(
1261 decode_field(&encode_field(value)).as_deref(),
1262 Some(value),
1263 "{value:?}"
1264 );
1265 }
1266 assert!(encode_field("%3B") != encode_field(";"));
1267 for broken in ["%", "%3", "%zz", "%3b"] {
1268 assert!(decode_field(broken).is_none(), "{broken:?}");
1269 }
1270 }
1271
1272 /// A record that reads cleanly but describes an impossible decision is
1273 /// rejected, not believed.
1274 #[test]
1275 fn an_incoherent_record_does_not_read_back() {
1276 // A fallback that claims an engine lane.
1277 assert!(
1278 RouteDecision::parse_canonical_record(
1279 "chosen=engine_lane;reason=engine_unreachable;pin=engine_lane;lane=claude-local"
1280 )
1281 .is_none()
1282 );
1283 // An honoured pin that names a different class from the one that ran.
1284 assert!(
1285 RouteDecision::parse_canonical_record(
1286 "chosen=native_loop;reason=pin_honored;pin=external_acp;lane="
1287 )
1288 .is_none()
1289 );
1290 // An unpinned decision that carries a pin.
1291 assert!(
1292 RouteDecision::parse_canonical_record(
1293 "chosen=native_loop;reason=unpinned_default;pin=native_loop;lane="
1294 )
1295 .is_none()
1296 );
1297 // A partial record, as a truncated write leaves behind.
1298 assert!(
1299 RouteDecision::parse_canonical_record("chosen=native_loop;reason=unpinned_default")
1300 .is_none()
1301 );
1302 // A duplicated key, where the second value would silently win.
1303 assert!(
1304 RouteDecision::parse_canonical_record(
1305 "chosen=native_loop;chosen=engine_lane;reason=unpinned_default;pin=;lane="
1306 )
1307 .is_none()
1308 );
1309 // A native route carrying a lane.
1310 assert!(
1311 RouteDecision::parse_canonical_record(
1312 "chosen=native_loop;reason=pin_honored;pin=native_loop;lane=claude-local"
1313 )
1314 .is_none()
1315 );
1316 // Junk.
1317 assert!(RouteDecision::parse_canonical_record("chosen=wat;reason=pin_honored").is_none());
1318 assert!(RouteDecision::parse_canonical_record("").is_none());
1319 }
1320
1321 /// The record holds no rendered explanation, only parts.
1322 ///
1323 /// Same law as `ExecutorDisclosure`: a record of parts can be re-rendered,
1324 /// re-signed, or re-read by a later reader. A stored sentence cannot.
1325 #[test]
1326 fn the_decision_record_holds_no_rendered_explanation() {
1327 let decision = route(&RouteInputs::native_only());
1328 let dumped = format!("{decision:?}");
1329 for caption in ["explain", "label", "line", "text", "summary", "message"] {
1330 assert!(
1331 !dumped.contains(caption),
1332 "RouteDecision grew a `{caption}` field: {dumped}"
1333 );
1334 }
1335 assert!(!decision.canonical_record().contains(' '));
1336 }
1337
1338 /// The explanation is derived from the parts, and names both the pin and
1339 /// the fact it was not honoured.
1340 #[test]
1341 fn a_fallback_explains_itself_from_its_parts() {
1342 let decision = route(
1343 &RouteInputs::native_only()
1344 .with_engine(EngineReadiness::Unreachable(EngineUnreachable::NotRunning))
1345 .pinned(ExecutorPin::on_lane("claude-local")),
1346 );
1347 let explanation = decision.explain();
1348 assert!(explanation.contains("native_loop"), "{explanation}");
1349 assert!(explanation.contains("engine unreachable"), "{explanation}");
1350 assert!(
1351 explanation.contains("engine_lane@claude-local"),
1352 "{explanation}"
1353 );
1354 assert!(
1355 explanation.contains("could not be honoured"),
1356 "{explanation}"
1357 );
1358 }
1359
1360 /// Pin tokens round-trip, including the lane a pin names.
1361 #[test]
1362 fn pin_tokens_round_trip() {
1363 for pin in [
1364 ExecutorPin::new(ExecutorClass::NativeLoop),
1365 ExecutorPin::new(ExecutorClass::ExternalAcp),
1366 ExecutorPin::new(ExecutorClass::EngineLane),
1367 ExecutorPin::on_lane("acp:cursor-agent"),
1368 ] {
1369 assert_eq!(ExecutorPin::parse_token(&pin.token()).as_ref(), Some(&pin));
1370 }
1371 assert!(ExecutorPin::parse_token("zed_agent").is_none());
1372 }
1373
1374 /// The reason set is closed, and every reason has a distinct token.
1375 #[test]
1376 fn the_reason_set_is_closed_and_distinct() {
1377 let mut tokens: Vec<&str> = RouteReason::all().iter().map(|r| r.token()).collect();
1378 let count = tokens.len();
1379 tokens.sort_unstable();
1380 tokens.dedup();
1381 assert_eq!(tokens.len(), count, "two reasons share a token");
1382 assert_eq!(
1383 count,
1384 9,
1385 "the reason set changed. Every reason is a thing the router can \
1386 tell a user; adding one is a deliberate edit, and removing one \
1387 means a route it used to explain is now unexplained."
1388 );
1389 }
1390}
1391