Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions src/core/observability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,23 @@ pub fn expected_error_kind(message: &str) -> Option<ExpectedErrorKind> {
/// `channels::providers::web::run_chat_task` (OPENHUMAN-TAURI-26). The
/// `"session expired"` substring anchors the match to the OpenHuman
/// backend's session-renewal body, not the bare numeric status.
/// - `"OpenHuman API error (401 Unauthorized): {…\"error\":\"Invalid token\"…}"`
/// — same emit site, same wire shape as the `Session expired` body, but the
/// OpenHuman backend swaps in `"Invalid token"` for the JWT-validity
/// rejection branch (vs. the explicit session-renewal branch).
/// OPENHUMAN-TAURI-4P0. The conjunctive anchor — `"OpenHuman API error
/// (401"` **and** the envelope-shaped `"\"error\":\"Invalid token\""` —
/// keeps the #2286 contract intact: bare `"Invalid token"`, OpenAI /
/// Anthropic BYO-key 401s, Discord upstream-bot-token rejections, and
/// provider scope errors still route to Sentry as actionable.
/// - `"Embedding API error (401 Unauthorized): {…\"error\":\"Invalid token\"…}"`
/// — TAURI-RUST-4K5 (~118 events, escalating on 0.56.0). Same OpenHuman
/// backend session-expired envelope as 4P0, but the embedding client at
/// `src/openhuman/embeddings/openai.rs:139` wraps it with the
/// `"Embedding API error"` prefix instead of `"OpenHuman API error"`.
/// Uses the same conjunctive-anchor pattern so BYO-key embedding 401s
/// from third-party providers (OpenAI / Voyage / Cohere) still escalate
/// — guarded by `does_not_classify_embedding_byo_key_401_as_session_expired`.
/// - `"SESSION_EXPIRED: backend session not active — sign in to resume LLM work"`
/// — the `scheduler_gate::is_signed_out` sentinel from
/// `providers::openhuman_backend::resolve_bearer`.
Expand All @@ -247,6 +264,26 @@ pub fn is_session_expired_message(msg: &str) -> bool {
|| lower.contains("no backend session token")
|| lower.contains("session jwt required")
|| msg.contains("SESSION_EXPIRED")
// OPENHUMAN-TAURI-4P0 — OpenHuman backend's "Invalid token" 401
// envelope. Both anchors must be present: the OpenHuman-scoped
// `"OpenHuman API error (401"` prefix (so a third-party provider's
// `"OpenAI API error (401 Unauthorized): invalid_api_key"` cannot
// match), AND the envelope-shaped `"\"error\":\"Invalid token\""`
// (so bare prose mentions of "invalid token" — Discord OAuth
// failures, generic upstream errors covered by #2286 — stay
// actionable in Sentry).
|| (msg.contains("OpenHuman API error (401")
&& msg.contains("\"error\":\"Invalid token\""))
// TAURI-RUST-4K5 — same OpenHuman backend "Invalid token" envelope
// wrapped by `src/openhuman/embeddings/openai.rs:139` with the
// `"Embedding API error"` prefix instead of `"OpenHuman API error"`.
// Same conjunctive-anchor pattern as 4P0: the embedding-scoped
// prefix gates the match so a third-party BYO-key embedding 401
// (e.g. OpenAI/Voyage/Cohere rejecting the user's own API key)
// stays actionable — guarded by
// `does_not_classify_embedding_byo_key_401_as_session_expired`.
|| (msg.contains("Embedding API error (401")
&& msg.contains("\"error\":\"Invalid token\""))
}

/// Detect the in-process-core boot-window shape: a sibling component
Expand Down Expand Up @@ -2454,6 +2491,105 @@ mod tests {
}
}

/// OPENHUMAN-TAURI-4P0: the OpenHuman backend rejects an expired/
/// revoked JWT with the envelope `{"success":false,"error":"Invalid
/// token"}` (vs. the explicit `"Session expired. Please log in again."`
/// body covered by `classifies_session_expired_messages`). Same emit
/// site, same wrapping by `web_channel.run_chat_task`, but the body
/// substring is different.
///
/// The matcher uses a conjunctive `"OpenHuman API error (401"` +
/// envelope-shaped `"\"error\":\"Invalid token\""` anchor pair so the
/// #2286 contract for bare `"Invalid token"` / BYO-key 401s is
/// preserved — `does_not_classify_byo_key_provider_401_as_session_expired`
/// pins that and must stay green.
#[test]
fn classifies_openhuman_invalid_token_401_as_session_expired() {
// Verbatim wire shape from the OPENHUMAN-TAURI-4P0 event payload.
let msg = r#"run_chat_task failed client_id=lssXhQidBfzGXG9k thread_id=thread-743193ba-f0c1-4008-b665-64d3030d1453 request_id=00696b71-fa05-4574-bcdb-5744a5dac6ea error=OpenHuman API error (401 Unauthorized): {"success":false,"error":"Invalid token"}"#;
assert_eq!(
expected_error_kind(msg),
Some(ExpectedErrorKind::SessionExpired),
"OPENHUMAN-TAURI-4P0 verbatim wire shape must classify as SessionExpired"
);

// Unwrapped emit shape (without the run_chat_task prefix) — also
// appears at provider/agent layers; the substring matcher must
// catch it regardless of caller wrapping.
assert_eq!(
expected_error_kind(
r#"OpenHuman API error (401 Unauthorized): {"success":false,"error":"Invalid token"}"#
),
Some(ExpectedErrorKind::SessionExpired),
"unwrapped OpenHuman invalid-token envelope must classify as SessionExpired"
);
}

/// TAURI-RUST-4K5 (118 events, escalating on 0.56.0): the embedding
/// client at `src/openhuman/embeddings/openai.rs:139` wraps the same
/// OpenHuman backend `{"success":false,"error":"Invalid token"}` 401
/// envelope as 4P0, but with the `"Embedding API error"` prefix
/// instead of `"OpenHuman API error"` (different emit-site format
/// string, same underlying session-expired cause — see breadcrumb
/// `[scheduler_gate] signed_out false -> true` immediately preceding
/// the 401 in the event payload).
///
/// Uses the same conjunctive `"<prefix> (401"` + envelope-shaped
/// `"\"error\":\"Invalid token\""` anchor pattern as 4P0 so the
/// #2286 / BYO-key contract is preserved — covered by
/// `does_not_classify_byo_key_provider_401_as_session_expired` and
/// `does_not_classify_embedding_byo_key_401_as_session_expired`
/// (below).
#[test]
fn classifies_embedding_api_invalid_token_401_as_session_expired() {
// Verbatim wire shape from the TAURI-RUST-4K5 event payload (Sentry
// issue 5230, latest event 2026-05-27 20:49 on openhuman@0.56.0,
// domain=embeddings operation=openai_embed status=401).
let msg =
r#"Embedding API error (401 Unauthorized): {"success":false,"error":"Invalid token"}"#;
assert_eq!(
expected_error_kind(msg),
Some(ExpectedErrorKind::SessionExpired),
"TAURI-RUST-4K5 verbatim wire shape must classify as SessionExpired"
);

// The substring matcher must survive caller wrapping the same way
// the 4P0 web-channel `run_chat_task` test wraps the body — callers
// that re-emit through a tracing field or another layer prepend
// arbitrary context.
let wrapped = r#"openai_embed failed error=Embedding API error (401 Unauthorized): {"success":false,"error":"Invalid token"}"#;
assert_eq!(
expected_error_kind(wrapped),
Some(ExpectedErrorKind::SessionExpired),
"wrapped 4K5 envelope must still classify as SessionExpired"
);
}

/// Polarity guard for the 4K5 arm. The classifier must NOT swallow
/// `"Embedding API error (401 …)"` shapes from third-party BYO-key
/// embedding providers (OpenAI / Voyage / Cohere upstream rejecting
/// the user's own API key). Those are actionable user-config errors
/// that need to reach Sentry — same contract as
/// `does_not_classify_byo_key_provider_401_as_session_expired` for
/// the OpenAI chat API.
#[test]
fn does_not_classify_embedding_byo_key_401_as_session_expired() {
for raw in [
"Embedding API error (401 Unauthorized): invalid_api_key",
r#"Embedding API error (401 Unauthorized): {"error":{"code":"invalid_api_key","message":"Incorrect API key provided"}}"#,
// Wire shape without the OpenHuman envelope — bare provider
// rejection prose. Must reach Sentry as actionable BYO-key
// misconfiguration.
"Embedding API error (401): authentication_error",
] {
assert_eq!(
expected_error_kind(raw),
None,
"BYO-key embedding 401 must reach Sentry as actionable error: {raw}"
);
}
}

#[test]
fn does_not_classify_byo_key_provider_401_as_session_expired() {
// Critical: a BYO-key 401 from OpenAI / Anthropic etc. is an
Expand Down
Loading