atlas-iac/dockerfiles/switchyard-capability-fallback.patch

99 lines
4.0 KiB
Diff
Raw Normal View History

--- a/src/algorithms/fall_through.rs
+++ b/src/algorithms/fall_through.rs
@@ -1016,0 +1017,43 @@
+ async fn automatic_fallback_keeps_capability_and_effort_floor() -> Result<()> {
+ for (frontier, alternate, lower_role) in [
+ (
+ "route/codex/auto-frontier/xhigh",
+ "route/claude/auto-frontier/xhigh",
+ "route/codex/auto-advanced/xhigh",
+ ),
+ (
+ "worker/codex/auto-frontier/xhigh",
+ "worker/claude/auto-frontier/xhigh",
+ "worker/codex/auto-advanced/xhigh",
+ ),
+ ] {
+ let router = FallThrough::<()>::new(target_set_with_overflow(
+ &[frontier, lower_role, alternate],
+ &[frontier],
+ ))
+ .with_classifier(fixed(vec![score(frontier, 1.0)]));
+
+ let (model, _) = run(router).await?;
+ assert_eq!(model, alternate);
+ }
+ Ok(())
+ }
+
+ #[tokio::test]
+ async fn automatic_fallback_does_not_reset_its_floor_after_an_alternate_fails() {
+ let frontier = "route/codex/auto-frontier/xhigh";
+ let alternate = "route/claude/auto-frontier/xhigh";
+ let lower_role = "route/codex/auto-advanced/xhigh";
+ let calls = Arc::new(Mutex::new(Vec::new()));
+ let router = FallThrough::<()>::new(counting_overflow_targets(
+ &[frontier, lower_role, alternate],
+ &[frontier, alternate],
+ Arc::clone(&calls),
+ ))
+ .with_classifier(fixed(vec![score(frontier, 1.0)]));
+
+ assert!(run(router).await.is_err());
+ assert_eq!(&*calls.lock(), &[frontier, alternate]);
+ }
+
+ #[tokio::test]
--- a/src/core/algorithm.rs
+++ b/src/core/algorithm.rs
@@ -396,2 +396,8 @@
- /// The named target, or the first one this request is not barred from when it has been
- /// excluded (see [`Context::exclude_target`]). Errors if every target is excluded.
+ /// The named target, or the first compatible target not barred for this request.
+ ///
+ /// Automatic capability selectors have two independent components: the
+ /// stable selector (`auto-frontier`, for example) and the effort floor.
+ /// A provider outage may switch provider, but it must not turn that target
+ /// into an unrelated lower capability or effort merely because it appears
+ /// earlier in the route's complete candidate list. Non-AUTO target names
+ /// retain the existing first-eligible fallback behavior.
@@ -402,0 +409,12 @@
+ if let Some((scope, selector, effort)) = automatic_floor(name) {
+ return self
+ .targets
+ .iter()
+ .find(|candidate| {
+ !ctx.is_excluded(&candidate.semantic_name)
+ && automatic_floor(&candidate.semantic_name)
+ == Some((scope, selector, effort))
+ })
+ .cloned()
+ .ok_or(LibsyError::AllTargetsExcluded);
+ }
@@ -408,0 +427,25 @@
+}
+
+/// Parse the Switchyard semantic names used by AUTO capability routes.
+///
+/// `route/<provider>/<selector>/<effort>` serves hosted calls and
+/// `worker/<provider>/<selector>/<effort>` serves durable workers. The provider
+/// is deliberately excluded from this floor so infrastructure failover can use
+/// the other provider without changing capability or reasoning effort.
+fn automatic_floor(name: &str) -> Option<(&str, &str, &str)> {
+ let mut parts = name.split('/');
+ let scope = parts.next()?;
+ let _provider = parts.next()?;
+ let selector = parts.next()?;
+ let effort = parts.next()?;
+ if parts.next().is_some()
+ || !matches!(scope, "route" | "worker")
+ || !matches!(
+ selector,
+ "auto" | "auto-economy" | "auto-balanced" | "auto-advanced" | "auto-frontier"
+ )
+ || !matches!(effort, "low" | "medium" | "high" | "xhigh")
+ {
+ return None;
+ }
+ Some((scope, selector, effort))