@@ -43,6 +43,11 @@ class ErrorCategory(str, Enum):
4343 #: Authentication/authorization failed (e.g. a wrong or cycled key). Does
4444 #: not heal on retry: terminate loudly.
4545 AUTH_FAILURE = "auth_failure"
46+ #: The requested model is not deployed on the configured upstream. A
47+ #: permanent misconfiguration, not the candidate's doing: never scored as a
48+ #: sample, and terminating, because every remaining case will fail the same
49+ #: way and the run has nothing left to measure.
50+ UPSTREAM_MODEL_UNAVAILABLE = "upstream_model_unavailable"
4651 #: Transient external infrastructure (rate limit, timeout, connection, 5xx,
4752 #: overloaded). Retryable; if it persists it renders the aggregate invalid.
4853 TRANSIENT_INFRA = "transient_infra"
@@ -91,6 +96,16 @@ class CategoryPolicy:
9196 is_informative_sample = False ,
9297 diagnostic_code = "auth_failure" ,
9398 ),
99+ ErrorCategory .UPSTREAM_MODEL_UNAVAILABLE : CategoryPolicy (
100+ retryable = False ,
101+ terminating = True ,
102+ # Unlike auth, keep counting these toward invalidity: if the terminating
103+ # path is ever bypassed, the aggregate must still come out invalid
104+ # rather than silently averaging a shrinking set of survivors.
105+ counts_toward_invalidity = True ,
106+ is_informative_sample = False ,
107+ diagnostic_code = "upstream_model_unavailable" ,
108+ ),
94109 ErrorCategory .TRANSIENT_INFRA : CategoryPolicy (
95110 retryable = True ,
96111 terminating = False ,
@@ -149,6 +164,21 @@ def policy(category: ErrorCategory) -> CategoryPolicy:
149164 ),
150165 ErrorCategory .AUTH_FAILURE ,
151166 ),
167+ (
168+ # A model that is configured but not provisioned upstream. Left
169+ # unclassified this is the worst kind of silent failure: the agent's
170+ # every call 404s, it writes no answer, and the case is recorded as an
171+ # informative task failure: the harness blaming the candidate for a
172+ # model that does not exist. Matched on the provider's own error codes
173+ # and on the exact Azure sentence, never on a bare "does not exist",
174+ # which would also swallow "loading container: file does not exist".
175+ re .compile (
176+ r"deploymentnotfound|model_not_found|"
177+ r"the api deployment for this resource does not exist" ,
178+ re .IGNORECASE ,
179+ ),
180+ ErrorCategory .UPSTREAM_MODEL_UNAVAILABLE ,
181+ ),
152182 (
153183 re .compile (
154184 r"rate.?limit|too.?many.?requests|time(?:d.?)?out|connection|"
@@ -205,6 +235,9 @@ def classify_case(signals: list[str]) -> ErrorCategory:
205235 for category in (
206236 ErrorCategory .INFERENCE_BUDGET_EXHAUSTED ,
207237 ErrorCategory .AUTH_FAILURE ,
238+ # Ahead of infra: a case that saw both a dead sandbox and a missing
239+ # deployment should report the deterministic, operator-fixable one.
240+ ErrorCategory .UPSTREAM_MODEL_UNAVAILABLE ,
208241 ErrorCategory .TRANSIENT_INFRA ,
209242 ):
210243 if category in categories :
0 commit comments