feat(ai): durable provider-health ledger and credential-expired surfacing

Promote the volatile per-worker last-working cache to a Postgres
provider-health ledger so the fallback chain shares one health signal
across every instance, modelled on the atomic-upsert rate limiter.

The ledger holds a durable negative cache: an auth-class failure
(401/403) benches that provider for a cooldown instead of re-burning the
dead round-trip on every generation; a sustained hard failure backs off
briefly. The local model is never benched — it stays the guaranteed
floor of the chain. Every ledger interaction fails open, so a DB error
falls back to today's chain walk exactly.

When the user's primary provider fails auth-class, the Coach emits a
distinct credential_expired frame that deep-links to reconnect rather
than a generic "try again later".

Adds the provider_health table (migration 0112) and the
errorCredentialExpired copy across all locales.
This commit is contained in:
Marc-André Bombeck committed 2026-06-04 00:50:13 +02:00
1 parent 6db3bef413
commit fbd45f3044
15 files changed
+949 -3

No files matched your search

+12
View File
@@ -336,8 +336,20 @@ Reply now as the assistant, in ${locale === "de" ? "German" : "English"}.`;
meta: {
attempts: err.attempts.length,
firstStatus: err.attempts[0]?.httpStatus ?? null,
credentialExpired: err.primaryCredentialExpired,
},
});
// v1.11.0 W1 — when the user's PRIMARY provider failed with an
// auth-class status (401/403), the credential is dead, not the
// service. Surface a distinct `credential_expired` frame so the
// drawer can deep-link the user to reconnect rather than telling
// them to "try again later" — the gap that let an expired codex
// token silently kill all generation.
if (err.primaryCredentialExpired) {
return streamProviderError({
code: "coach.provider.credential_expired",
});
}
// v1.4.25 W5 — distinguish provider rate-limit (every attempt
// landed on 429) from generic unavailability. The drawer's
// error-decoder surfaces the rate-limit copy with a warning