diff --git a/changelog.d/features/14810-call-resilience-actions.md b/changelog.d/features/14810-call-resilience-actions.md new file mode 100644 index 00000000000..83ee4034e7a --- /dev/null +++ b/changelog.d/features/14810-call-resilience-actions.md @@ -0,0 +1 @@ +- **feat(call-logs):** show per-request recovery actions in the request log ([#14810](https://github.com/diegosouzapw/OmniRoute/pull/14810)) — thanks @maxmad64bis diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index d7457beefa4..bf5653f50e9 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -1,4 +1,5 @@ { + "_rebaseline_2026_09_25_14810_resilience_actions_wiring": "PR #14810 own growth (resilience-actions observability wiring, irreducible call-site lines after extraction of the note logic into chatCore/*_notes modules + opencodeResilienceNotes + requestLoggerResilience, all under cap): open-sse/handlers/chatCore.ts 6426->6441 (+15 = wrapper handleChatCore/handleChatCoreInner + resumed-flag param/note/strip + 3 one-line note calls, logic in chatCore/resilienceAttemptContext + resumedResilienceNotes + emptyTurnResilienceNotes + continuationResilienceNotes + recoveryTraceLogging); open-sse/executors/opencode.ts 1338->1354, re-measured 1354->1370 after merging release/v3.8.51 @86870a82 (the tip itself had grown to 1354 via the applied-egress tracker) (+16 = served-account tracker + park/replay/stored notes wiring, logic in opencodeResilienceNotes); src/sse/handlers/chat.ts 2567->2582 (+15 on the reconciled tip = resumed-flag threading via body marker + dispatch param + 2 combo forwards); src/shared/components/RequestLoggerDetail.tsx 1210->1216, re-measured on release/v3.8.51 @4b9388a1 (tip file 1205 after #14795; +11 = one import + badge computation + badge render loop, badge logic in requestLoggerResilience); src/lib/usage/callLogs.ts stays under the 1200 cap (resilience-actions parser moved to src/lib/usage/resilienceActionsParse.ts). Measured with split(\"\\n\").length. Covered by tests/unit/resilience-actions-context (11) + sink (5) + notes (9) + badges (5) + db/migration-191 (2).", "_rebaseline_2026_09_24_14802_transport_setaside": "PR #14802 own growth: open-sse/utils/proxyFetch.ts 1334->1341 (+7 = two-symbol import + proxied-success hook at the single dispatch return + tagged-failure guard at the single final throw; full evidence helpers live in open-sse/utils/proxyRefusalMemory.ts (not frozen), irreducible call-site wiring at the two existing chokepoints). Covered by tests/unit/proxy-transport-setaside-cross-evidence.test.ts (10/10) + tests/unit/proxy-transport-traffic-simulation.test.ts (4/4).", "_rebaseline_2026_09_24_14708_persisted_cooldown_precedence": "PR #14708 own growth: open-sse/services/combo/roundRobinCombo.ts 1263->1285 (+22 = the persisted-cooldown pre-check at the only round-robin dispatch point with no persisted check (import + async pre-check + skip/continue), plus the sticky-target reflow). Irreducible: the check must live at the dispatch site that bypasses executeTargetAttempt. Covered by tests/unit/combo-transient-persisted-precedence.test.ts.", "_rebaseline_2026_09_24_14367_synthetic_key_budget_orphan": "PR #14367 (original fix; #14661 re-landed the same diff): src/lib/db/apiKeys.ts 1718->1719 (+1 = the single import of SYNTHETIC_ENV_API_KEY_ID threading the canonical synthetic-identity constant into getApiKeyMetadata; the exclusion list lives in src/shared/constants/apiKeyIdentities.ts). Irreducible. Covered by tests/unit/db/health-check-orphan-budgets.test.ts.", @@ -500,7 +501,7 @@ "open-sse/executors/codex.ts": 1584, "open-sse/executors/cursor.ts": 1868, "open-sse/executors/muse-spark-web.ts": 1405, - "open-sse/handlers/chatCore.ts": 6426, + "open-sse/handlers/chatCore.ts": 6441, "open-sse/handlers/imageGeneration.ts": 3334, "open-sse/handlers/search.ts": 1789, "open-sse/mcp-server/schemas/tools.ts": 1621, @@ -540,18 +541,18 @@ "src/shared/components/RequestLoggerV2.tsx": 1748, "src/shared/constants/providers/apikey/gateways.ts": 1544, "src/shared/services/cliRuntime.ts": 1296, - "src/sse/handlers/chat.ts": 2567, + "src/sse/handlers/chat.ts": 2582, "src/sse/services/auth.ts": 3602, "tests/unit/account-fallback-service.test.ts": 2453, "tests/unit/provider-validation-specialty.test.ts": 4656, "open-sse/services/autoCombo/virtualFactory.ts": 1258, "open-sse/services/combo/roundRobinCombo.ts": 1285, - "src/shared/components/RequestLoggerDetail.tsx": 1210, + "src/shared/components/RequestLoggerDetail.tsx": 1216, "src/shared/middleware/chatBodyAdmission.ts": 1206, "open-sse/executors/deepseek-web.ts": 1224, "open-sse/executors/default.ts": 1207, "open-sse/services/rateLimitManager.ts": 1329, - "open-sse/executors/opencode.ts": 1338 + "open-sse/executors/opencode.ts": 1370 }, "_rebaseline_2026_09_15_roundrobin_dashboard_events": "Fix #13089 (Combo Studio Live dashboard shows an empty backlog for round-robin combos): open-sse/services/combo/roundRobinCombo.ts 1205->1213. Round-robin is the only combo strategy that bypasses handleComboChat/executeTargetAttempt.ts, the path that publishes the combo.target.attempt/succeeded/failed EventBus events the Live dashboard listens for — so round-robin completions never showed up. The new call-site wiring (createRRDashboardEvents(...) instantiated once per target, one-line .attempt()/.succeeded()/.failed() calls at the 6 existing dispatch/outcome points) is the emitter logic actually extracted into a new module, open-sse/services/combo/rrDashboardEvents.ts — this is the minimum irreducible footprint for wiring 6 required call sites into 6 fixed control-flow points of the frozen file. Covered by tests/unit/issue-13089-roundrobin-live-ws-events.test.ts (2 tests: success + failure paths).", "_rebaseline_base_2026_08_10_proxyfetch": "Base-red fix (green-prs sweep, issue #9985): open-sse/utils/proxyFetch.ts 1207 > cap 1000 — new proxied-TLS fetch helper introduced by the Fal reference-image work. Owner-authorized quick rebaseline to green; structural slim tracked for v3.9.0.", diff --git a/open-sse/executors/opencode.ts b/open-sse/executors/opencode.ts index 2856d4ad415..fd83e2d48e2 100644 --- a/open-sse/executors/opencode.ts +++ b/open-sse/executors/opencode.ts @@ -17,6 +17,12 @@ import { noteRotationAccount, resolveProxyForRequest, } from "../utils/proxyFetch.ts"; +import { + createServedAccountTracker, + noteParkWait, + noteReplayed, + noteStoredFallback, +} from "./opencodeResilienceNotes.ts"; import { clientSuppliedOpencodeSession, forwardOpencodeClientHeaders, @@ -645,6 +651,8 @@ export class OpencodeExecutor extends BaseExecutor { let burstStreak = 0, parked = false; const requestPacing = egressPacing.initEgressPacingForRequest(); // Off by default. + // served-account changes (effective-change counting) live in the leaf tracker. + const noteServedAccount = createServedAccountTracker(); const appliedEgress = egressPacing.createAppliedEgressTracker( this.buildUrl(String(input.model ?? ""), Boolean(input.stream)), resolveProxyForRequest @@ -749,6 +757,9 @@ export class OpencodeExecutor extends BaseExecutor { if (attributionOn && (accounts.length > 1 || account.fingerprint !== "")) { noteRotationAccount(masked); } + // effective-change counting on the masked id (the raw + // fingerprint never reaches the log, just the counter). + noteServedAccount(masked); // Pin egress to this account's proxy for the whole BaseExecutor dispatch // (incl. its intra-URL 429 retries). skipUpstreamRetry lets THIS loop own @@ -905,6 +916,8 @@ export class OpencodeExecutor extends BaseExecutor { "OPENCODE", `${cid}burstStreak=${burstStreak} freshD2=${marker.fresh} park` ); + // local monotone park measure (Date.now diff, integer ms). + const parkStartMs = Date.now(); const p = await runParkAndReplay( { execute: (i: ExecuteInput) => @@ -920,10 +933,12 @@ export class OpencodeExecutor extends BaseExecutor { log, cid ); + noteParkWait(Date.now() - parkStartMs); if (p && p !== result) { if (attributionOn && skippedCooldown.size > 0) { this.logSkippedCooldownAccounts(log, cid, skippedCooldown); } + noteReplayed(); return this.normalizeMuseSparkResponse(input, p); } if (p) { @@ -931,6 +946,7 @@ export class OpencodeExecutor extends BaseExecutor { if (attributionOn && skippedCooldown.size > 0) { this.logSkippedCooldownAccounts(log, cid, skippedCooldown); } + noteStoredFallback(); return this.normalizeMuseSparkResponse(input, result); } } diff --git a/open-sse/executors/opencodeParkResume.ts b/open-sse/executors/opencodeParkResume.ts index a5f0fd0fd27..012300fd2f3 100644 --- a/open-sse/executors/opencodeParkResume.ts +++ b/open-sse/executors/opencodeParkResume.ts @@ -20,6 +20,7 @@ import { PARKED_STREAM_HEADER, PARKED_STREAM_VALUE } from "../utils/streamReadin import { isProxyAvoided, proxyEgressKey, proxySetAsideSeq } from "../utils/proxyRefusalMemory.ts"; import { maskAccountId, type RotatableAccount } from "./accountRotation.ts"; import { runWithProxyContext } from "../utils/proxyFetch.ts"; +import { noteResilienceAction } from "@/lib/usage/resilienceActionsContext.ts"; import type { ExecuteInput, ExecutorExecuteResult } from "./base.ts"; /** Consecutive transient 429s before a request parks. */ @@ -251,10 +252,22 @@ export async function runParkAndReplay( } const probe = await replayOneLeg(driver, input, driver.accounts, log, cid); const finalBody = probe?.result.response ?? fallback.response; + // stream note: note only AFTER the recopy outcome is known. A stored 429 + // fallback recopied into the 200 SSE envelope is still a stored + // error replay — flag alone decides at read time. + let recopied = true; try { await copyFinalBodyAsValidFrames(controller, encoder, finalBody); } catch { - /* unreadable body — close with the pings already sent */ + // Unreadable body — the client got pings only, not the replay. + recopied = false; + } + if (probe == null && fallback.response.status === 429) { + noteResilienceAction({ stored429: true, replayed: false }); + } else if (probe != null && recopied) { + noteResilienceAction({ replayed: true }); + } else if (probe != null) { + noteResilienceAction({ replayed: false }); } try { controller.close(); diff --git a/open-sse/executors/opencodeResilienceNotes.ts b/open-sse/executors/opencodeResilienceNotes.ts new file mode 100644 index 00000000000..b26804a3bc7 --- /dev/null +++ b/open-sse/executors/opencodeResilienceNotes.ts @@ -0,0 +1,31 @@ +import { noteResilienceAction } from "@/lib/usage/resilienceActionsContext.ts"; + +/** + * resilience effective served-account changes (effective-change counting): the first served dispatch is 0, + * each later dispatch served by a distinct account is +1. Abort and + * shared-egress exclusion arms never reach the call site, so they never + * count. Best-effort notes only. + */ +export function createServedAccountTracker(): (fingerprint: string) => void { + let lastServedFingerprint: string | null = null; + return (fingerprint: string): void => { + if (lastServedFingerprint !== null && lastServedFingerprint !== fingerprint) { + noteResilienceAction({ rotations: 1 }); + } + lastServedFingerprint = fingerprint; + }; +} + +/** resilience park outcome notes (local monotone wait + replayed/stored flags). */ +export function noteParkWait(elapsedMs: number): void { + noteResilienceAction({ parked: true, parkMs: Math.max(0, Math.round(elapsedMs)) }); +} + +export function noteReplayed(): void { + noteResilienceAction({ replayed: true }); +} + +export function noteStoredFallback(): void { + // The stored 429 fallback is served without a new send. + noteResilienceAction({ replayed: false, stored429: true }); +} diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 80f0c2f47a2..bb2acc6213a 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -51,6 +51,9 @@ import { readBoundedResponseOutcome, FLUSH_EMPTY_RETRY_MAX_BYTES, } from "../utils/emptyTurnRetry.ts"; +import { withResilienceActionsContext } from "./chatCore/resilienceAttemptContext.ts"; +import { noteBufferedVerdictOutcome } from "./chatCore/emptyTurnResilienceNotes.ts"; +import { notePreviousResponseResumed } from "./chatCore/resumedResilienceNotes.ts"; import { assembleStreamingResponseHeaders } from "./chatCore/streamingResponseHeaders.ts"; import { storeStreamingSemanticCacheResponse } from "./chatCore/streamingSemanticCacheStore.ts"; import { captureStreamReasoningForReplay } from "./chatCore/streamReasoningCapture.ts"; @@ -474,7 +477,13 @@ type VideoBridgeLogParam = { observed: boolean; redaction: VideoBridgeLogRedacti */ // extractSystemRoleMessages extracted to chatCore/claudeSystemRole.ts (#3501); re-exported above so // existing importers (e.g. tests/unit/system-role-extraction.test.ts) keep resolving it from here. -export async function handleChatCore({ +export async function handleChatCore(args: Parameters[0]) { + // one implicit resilience store per attempt (combo legs each run + // handleChatCore, so each leg gets its own isolated store). + return withResilienceActionsContext([args], (forwarded) => handleChatCoreInner(forwarded)); +} + +async function handleChatCoreInner({ body, modelInfo, credentials, @@ -514,7 +523,9 @@ export async function handleChatCore({ videoBridgeLog = undefined, fallbackAttempts = undefined, forcedConnectionId = null, // #14116: caller's pinned/requested connection, vs credentials.connectionId below + previousResponseResumed = undefined, // rehydrated-continuation flag from chat.ts; noted below, no semantics. }) { + delete (body as Record)._omniroutePreviousResponseResumed; const { model: originModel, resolvedThinkingEffort, @@ -540,6 +551,8 @@ export async function handleChatCore({ // (chatCore/memoryExtraction.ts::runMemoryExtractionGate). const videoBridgeObserved: boolean = (videoBridgeLog as VideoBridgeLogParam | undefined)?.observed === true; + // resume flag from chat.ts, noted under the attempt store opened above. + notePreviousResponseResumed(previousResponseResumed); const resilienceSettings = resolveResilienceSettings(cachedSettings); if (!skipResourcePressureGuard) { try { @@ -5905,6 +5918,7 @@ export async function handleChatCore({ if (verdict.kind === "pass") { const v = formatBufferedVerdictLog(verdict, correlationId, traceId); log?.[v.level]?.("FLUSH_EMPTY_RETRY", v.line); + noteBufferedVerdictOutcome(verdict, null, false); break; } if (emptyTurnRetries >= STREAM_RECOVERY.EMPTY_TURN_RETRY_MAX) { @@ -5912,12 +5926,14 @@ export async function handleChatCore({ "FLUSH_EMPTY_RETRY", "retry budget exhausted, falling back to current behavior" ); + noteBufferedVerdictOutcome(verdict, null, true); break; } log?.warn?.( "FLUSH_EMPTY_RETRY", `${verdict.reason}, bounded retry through the normal credential path` ); + noteBufferedVerdictOutcome(verdict, { level: "warn", line: verdict.reason }, false); const nextCreds = await getProviderCredentials( provider, null, diff --git a/open-sse/handlers/chatCore/continuationResilienceNotes.ts b/open-sse/handlers/chatCore/continuationResilienceNotes.ts new file mode 100644 index 00000000000..900891de042 --- /dev/null +++ b/open-sse/handlers/chatCore/continuationResilienceNotes.ts @@ -0,0 +1,6 @@ +import { noteResilienceAction } from "@/lib/usage/resilienceActionsContext.ts"; + +/** only a stitched suffix counts as a resume of the turn. */ +export function noteContinuedSuffix(): void { + noteResilienceAction({ continued: 1 }); +} diff --git a/open-sse/handlers/chatCore/emptyTurnResilienceNotes.ts b/open-sse/handlers/chatCore/emptyTurnResilienceNotes.ts new file mode 100644 index 00000000000..8e98c371d2c --- /dev/null +++ b/open-sse/handlers/chatCore/emptyTurnResilienceNotes.ts @@ -0,0 +1,26 @@ +import { judgeBufferedTurn } from "../../utils/emptyTurnRetry.ts"; +import { noteResilienceAction } from "@/lib/usage/resilienceActionsContext.ts"; + +type Verdict = ReturnType; + +/** + * record the buffered-turn verdict outcome (pass/retry) and the + * empty-retry count into the attempt resilience store. A pass or an + * exhausted budget closes the loop with a terminal verdict note; each + * scheduled retry adds one empty-retry. + */ +export function noteBufferedVerdictOutcome( + verdict: Verdict, + logLine: { level: "warn" | "debug" | "info"; line: string } | null, + budgetExhausted: boolean +): void { + if (verdict.kind === "pass") { + noteResilienceAction({ buffered: "pass" }); + return; + } + if (budgetExhausted) { + noteResilienceAction({ buffered: "retry" }); + return; + } + if (logLine) noteResilienceAction({ emptyRetries: 1 }); +} diff --git a/open-sse/handlers/chatCore/recoveryTraceLogging.ts b/open-sse/handlers/chatCore/recoveryTraceLogging.ts index 27aee221d67..0c86543fb90 100644 --- a/open-sse/handlers/chatCore/recoveryTraceLogging.ts +++ b/open-sse/handlers/chatCore/recoveryTraceLogging.ts @@ -12,6 +12,7 @@ * joins the attempt line. */ import { STREAM_RECOVERY } from "../../config/constants.ts"; +import { noteContinuedSuffix } from "./continuationResilienceNotes.ts"; import type { ContinuationOutcome, RecoverableStreamOptions, @@ -54,8 +55,13 @@ export function isContinuationGiveUp(event: ContinuationOutcome): boolean { export function buildContinuationLogHooks( log: RecoveryLogger, - correlationId?: CorrelationId + correlationId?: CorrelationId, + resilience?: { onSuffix?: (event: ContinuationOutcome) => void } ): Pick { + // Default resilience note: only a stitched suffix counts as a resume of + // the turn. Terminal/empty/overlap-reject/refused/no-stream outcomes + // record nothing. Kept here so the call site stays one line. + const onSuffix = resilience?.onSuffix ?? noteContinuedSuffix; // Same empty-string-to-"none" fallback as formatBufferedVerdictLog, without trim. const cid = correlationId && correlationId.length > 0 ? correlationId : "none"; return { @@ -66,6 +72,12 @@ export function buildContinuationLogHooks( if (isContinuationGiveUp(event)) log?.warn?.(TAG, line); else if (event.outcome === "refused" && event.reason === "tool-call") log?.debug?.(TAG, line); else log?.info?.(TAG, line); + // best-effort resilience note; never throws into the stream path. + try { + if (event.outcome === "suffix") onSuffix(event); + } catch { + /* observability only */ + } }, }; } diff --git a/open-sse/handlers/chatCore/resilienceAttemptContext.ts b/open-sse/handlers/chatCore/resilienceAttemptContext.ts new file mode 100644 index 00000000000..a5172f1478f --- /dev/null +++ b/open-sse/handlers/chatCore/resilienceAttemptContext.ts @@ -0,0 +1,13 @@ +import { runWithResilienceActionsContext } from "@/lib/usage/resilienceActionsContext.ts"; + +/** + * one implicit resilience store per attempt. Combo legs each run + * handleChatCore, so each leg gets its own isolated store. The public + * signature stays single-arg (forwarded verbatim); no per-caller change. + */ +export function withResilienceActionsContext( + args: TArgs, + inner: (...innerArgs: TArgs) => TResult +): TResult { + return runWithResilienceActionsContext(() => inner(...args)); +} diff --git a/open-sse/handlers/chatCore/resumedResilienceNotes.ts b/open-sse/handlers/chatCore/resumedResilienceNotes.ts new file mode 100644 index 00000000000..ca15eeca932 --- /dev/null +++ b/open-sse/handlers/chatCore/resumedResilienceNotes.ts @@ -0,0 +1,15 @@ +import { noteResilienceAction } from "@/lib/usage/resilienceActionsContext.ts"; + +/** + * record a rehydrated previous_response_id continuation as a Responses + * resume of this attempt. Called inside handleChatCoreInner, under the + * attempt store — best-effort, never throws. + */ +export function notePreviousResponseResumed(resumed: unknown): void { + if (resumed !== true) return; + try { + noteResilienceAction({ resumed: true }); + } catch { + /* observability only */ + } +} diff --git a/src/i18n/messages/am.json b/src/i18n/messages/am.json index cf2852de8b7..0f649b4ce7a 100644 --- a/src/i18n/messages/am.json +++ b/src/i18n/messages/am.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "የትስስር መታወቂያ፦ {id}", "detailedPayloadInfo": "ለአዳዲስ ጥያቄዎች ባለአራት-ደረጃ የደንበኛ/አቅራቢ ውሂብ እይታን ከፈለጉ፣ መጀመሪያ ዝርዝር ምዝገባን ያንቁ።", "copyAll": "ሁሉንም ቅዳ", - "copiedAll": "ሁሉም ተቀድቷል" + "copiedAll": "ሁሉም ተቀድቷል", + "resilienceStoredError": "ከተቀመጠ ስህተት ቀርቧል", + "resilienceParked": "ከመላኩ በፊት {ms} ቆሟል", + "resilienceRotations": "{count} የመለያ ሽክርክሪቶች", + "resilienceRetried": "ባዶ ዙር እንደገና ተሞክሯል", + "resilienceContinued": "በዥረቱ መሃል ቀጥሏል", + "resilienceRecovered": "በመቋቋም እርምጃ ተመልሷል" } }, "proxyLogger": { diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 19266c12e14..d779d477297 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "معرّف الارتباط: {id}", "detailedPayloadInfo": "قم بتمكين تسجيل الدخول التفصيلي أولاً إذا كنت ترغب في عرض الحمولة من أربعة مراحل للعميل/المزود للطلبات الجديدة.", "copyAll": "نسخ الكل", - "copiedAll": "تم نسخ الكل" + "copiedAll": "تم نسخ الكل", + "resilienceStoredError": "تم التقديم من خطأ محفوظ", + "resilienceParked": "انتظار {ms} قبل الإرسال", + "resilienceRotations": "{count} تبديلات للحساب", + "resilienceRetried": "إعادة المحاولة لدور فارغ", + "resilienceContinued": "استئناف في منتصف التدفق", + "resilienceRecovered": "تم الاسترداد عبر إجراء المرونة" } }, "proxyLogger": { diff --git a/src/i18n/messages/az.json b/src/i18n/messages/az.json index f12af57789f..4f33c9631db 100644 --- a/src/i18n/messages/az.json +++ b/src/i18n/messages/az.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Korrelyasiya ID: {id}", "detailedPayloadInfo": "Əvvəlcə ətraflı qeydiyyatı aktiv edin, əgər yeni tələblər üçün dörd mərhələli müştəri/təchizatçı yük görünüşünü istəyirsinizsə.", "copyAll": "Hamısını kopyala", - "copiedAll": "Hamısı kopyalandı" + "copiedAll": "Hamısı kopyalandı", + "resilienceStoredError": "Saxlanmış xətadan xidmət edildi", + "resilienceParked": "Göndərmədən əvvəl {ms} gözlədildi", + "resilienceRotations": "{count} hesab rotasiyası", + "resilienceRetried": "Boş növbə təkrarlandı", + "resilienceContinued": "Axının ortasında davam etdirildi", + "resilienceRecovered": "Dayanıqlıq tədbiri ilə bərpa edildi" } }, "proxyLogger": { diff --git a/src/i18n/messages/bg.json b/src/i18n/messages/bg.json index e82d0514ffd..2a27ec08d8a 100644 --- a/src/i18n/messages/bg.json +++ b/src/i18n/messages/bg.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Идентификатор на корелация: {id}", "detailedPayloadInfo": "Първо активирайте подробното регистриране, ако искате изгледа на четирите етапа на клиент/доставчик за нови заявки.", "copyAll": "Копиране на всичко", - "copiedAll": "Всичко е копирано" + "copiedAll": "Всичко е копирано", + "resilienceStoredError": "Обслужено от запазена грешка", + "resilienceParked": "Задържано {ms} преди изпращане", + "resilienceRotations": "{count} ротации на акаунти", + "resilienceRetried": "Празният ход е повторен", + "resilienceContinued": "Възобновено по време на потока", + "resilienceRecovered": "Възстановено чрез действие за устойчивост" } }, "proxyLogger": { diff --git a/src/i18n/messages/bn.json b/src/i18n/messages/bn.json index 48d33a8bf76..b2d31b2ef9e 100644 --- a/src/i18n/messages/bn.json +++ b/src/i18n/messages/bn.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "সংযোগ আইডি: {id}", "detailedPayloadInfo": "নতুন অনুরোধের জন্য চার-পর্যায়ের ক্লায়েন্ট/প্রদানকারী পে-লোড ভিউ পেতে হলে প্রথমে বিস্তারিত লগিং সক্ষম করুন।", "copyAll": "সব কপি করুন", - "copiedAll": "সব কপি করা হয়েছে" + "copiedAll": "সব কপি করা হয়েছে", + "resilienceStoredError": "সংরক্ষিত ত্রুটি থেকে পরিবেশিত", + "resilienceParked": "পাঠানোর আগে {ms} অপেক্ষমাণ রাখা হয়েছে", + "resilienceRotations": "{count}টি অ্যাকাউন্ট রোটেশন", + "resilienceRetried": "খালি টার্ন পুনরায় চেষ্টা করা হয়েছে", + "resilienceContinued": "স্ট্রিমের মাঝখান থেকে পুনরায় শুরু হয়েছে", + "resilienceRecovered": "রেজিলিয়েন্স পদক্ষেপের মাধ্যমে পুনরুদ্ধার হয়েছে" } }, "proxyLogger": { diff --git a/src/i18n/messages/bs.json b/src/i18n/messages/bs.json index ede7f25de0c..ee0639b0af9 100644 --- a/src/i18n/messages/bs.json +++ b/src/i18n/messages/bs.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Korelacijski ID: {id}", "detailedPayloadInfo": "Prvo omogućite detaljno zapisivanje ako želite pregled payloada klijenta/providera u četiri faze za nove zahtjeve.", "copyAll": "Kopiraj sve", - "copiedAll": "Kopirano sve" + "copiedAll": "Kopirano sve", + "resilienceStoredError": "Posluženo iz sačuvane greške", + "resilienceParked": "Zadržano {ms} prije slanja", + "resilienceRotations": "{count} rotacija naloga", + "resilienceRetried": "Prazan krug ponovljen", + "resilienceContinued": "Nastavljeno usred streama", + "resilienceRecovered": "Oporavljeno akcijom otpornosti" } }, "proxyLogger": { diff --git a/src/i18n/messages/cs.json b/src/i18n/messages/cs.json index 4c4202a7b70..d03726b00d2 100644 --- a/src/i18n/messages/cs.json +++ b/src/i18n/messages/cs.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ID korelace: {id}", "detailedPayloadInfo": "Nejprve povolte podrobné protokolování, pokud chcete čtyřstupňový pohled na payload klienta/poskytovatele pro nové požadavky.", "copyAll": "Kopírovat vše", - "copiedAll": "Vše zkopírováno" + "copiedAll": "Vše zkopírováno", + "resilienceStoredError": "Obslouženo z uložené chyby", + "resilienceParked": "Odloženo o {ms} před odesláním", + "resilienceRotations": "{count} rotací účtů", + "resilienceRetried": "Prázdný tah zopakován", + "resilienceContinued": "Obnoveno uprostřed streamu", + "resilienceRecovered": "Obnoveno akcí odolnosti" } }, "proxyLogger": { diff --git a/src/i18n/messages/da.json b/src/i18n/messages/da.json index a7a47932140..61d84813f82 100644 --- a/src/i18n/messages/da.json +++ b/src/i18n/messages/da.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Korrelation ID: {id}", "detailedPayloadInfo": "Aktivér detaljeret logning først, hvis du ønsker den fire-trins klient/udbyder payload visning for nye anmodninger.", "copyAll": "Kopiér alt", - "copiedAll": "Alt kopieret" + "copiedAll": "Alt kopieret", + "resilienceStoredError": "Leveret fra gemt fejl", + "resilienceParked": "Parkeret {ms} før afsendelse", + "resilienceRotations": "{count} kontorotationer", + "resilienceRetried": "Tom tur forsøgt igen", + "resilienceContinued": "Genoptaget midt i streamen", + "resilienceRecovered": "Gendannet af en robusthedshandling" } }, "proxyLogger": { diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index fcf02aa9206..bd2c10ab077 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Korrelations-ID: {id}", "detailedPayloadInfo": "Aktivieren Sie zuerst das detaillierte Protokollieren, wenn Sie die vierstufige Client/Provider-Payload-Ansicht für neue Anfragen wünschen.", "copyAll": "Alle kopieren", - "copiedAll": "Alles kopiert" + "copiedAll": "Alles kopiert", + "resilienceStoredError": "Aus gespeichertem Fehler bedient", + "resilienceParked": "{ms} vor dem Senden geparkt", + "resilienceRotations": "{count} Kontorotationen", + "resilienceRetried": "Leere Runde wiederholt", + "resilienceContinued": "Mitten im Stream fortgesetzt", + "resilienceRecovered": "Durch Resilienzaktion wiederhergestellt" } }, "proxyLogger": { diff --git a/src/i18n/messages/el.json b/src/i18n/messages/el.json index ca857b0a53c..1d04468911d 100644 --- a/src/i18n/messages/el.json +++ b/src/i18n/messages/el.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Αναγνωριστικό συσχέτισης: {id}", "detailedPayloadInfo": "Ενεργοποιήστε πρώτα τη λεπτομερή καταγραφή εάν θέλετε την προβολή ωφέλιμου φορτίου τεσσάρων σταδίων πελάτη/παρόχου για νέα αιτήματα.", "copyAll": "Αντιγραφή όλων", - "copiedAll": "Αντιγράφηκαν όλα" + "copiedAll": "Αντιγράφηκαν όλα", + "resilienceStoredError": "Εξυπηρετήθηκε από αποθηκευμένο σφάλμα", + "resilienceParked": "Σε αναμονή {ms} πριν την αποστολή", + "resilienceRotations": "{count} εναλλαγές λογαριασμών", + "resilienceRetried": "Επανάληψη κενού γύρου", + "resilienceContinued": "Συνέχιση στη μέση της ροής", + "resilienceRecovered": "Ανακτήθηκε με ενέργεια ανθεκτικότητας" } }, "proxyLogger": { diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 4cde7a4c3bd..af4ce6d205d 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Correlation ID: {id}", "detailedPayloadInfo": "Enable detailed logging first if you want the four-stage client/provider payload view for new requests.", "copyAll": "Copy all", - "copiedAll": "Copied all" + "copiedAll": "Copied all", + "resilienceStoredError": "Served from stored error", + "resilienceParked": "Parked {ms} before dispatch", + "resilienceRotations": "{count} account rotations", + "resilienceRetried": "Retried empty turn", + "resilienceContinued": "Resumed mid-stream", + "resilienceRecovered": "Recovered by a resilience action" } }, "proxyLogger": { diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index c8e2ffc8124..94474246dce 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ID de correlación: {id}", "detailedPayloadInfo": "Habilita primero el registro detallado si deseas la vista de carga útil de cliente/proveedor de cuatro etapas para nuevas solicitudes.", "copyAll": "Copiar todo", - "copiedAll": "Todo copiado" + "copiedAll": "Todo copiado", + "resilienceStoredError": "Servido desde error almacenado", + "resilienceParked": "Aparcado {ms} antes del envío", + "resilienceRotations": "{count} rotaciones de cuenta", + "resilienceRetried": "Turno vacío reintentado", + "resilienceContinued": "Reanudado a mitad del flujo", + "resilienceRecovered": "Recuperado por una acción de resiliencia" } }, "proxyLogger": { diff --git a/src/i18n/messages/et.json b/src/i18n/messages/et.json index 9e291a88f6d..07ee8fb0917 100644 --- a/src/i18n/messages/et.json +++ b/src/i18n/messages/et.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Korrelatsiooni-ID: {id}", "detailedPayloadInfo": "Uute päringute neljaetapilise kliendi ja teenusepakkuja andmekehavaate kasutamiseks lubage esmalt üksikasjalik logimine.", "copyAll": "Kopeeri kõik", - "copiedAll": "Kõik kopeeritud" + "copiedAll": "Kõik kopeeritud", + "resilienceStoredError": "Serveeritud salvestatud veast", + "resilienceParked": "Ootel {ms} enne saatmist", + "resilienceRotations": "{count} konto vahetust", + "resilienceRetried": "Tühja käiku korrati", + "resilienceContinued": "Jätkati voo keskelt", + "resilienceRecovered": "Taastatud vastupidavustoiminguga" } }, "proxyLogger": { diff --git a/src/i18n/messages/fa.json b/src/i18n/messages/fa.json index 395157ba477..65a595ca6ec 100644 --- a/src/i18n/messages/fa.json +++ b/src/i18n/messages/fa.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "شناسه همبستگی: {id}", "detailedPayloadInfo": "ابتدا لاگ‌برداری دقیق را فعال کنید اگر می‌خواهید نمای بارگذاری چهار مرحله‌ای مشتری/تأمین‌کننده برای درخواست‌های جدید را مشاهده کنید.", "copyAll": "کپی همه", - "copiedAll": "همه کپی شدند" + "copiedAll": "همه کپی شدند", + "resilienceStoredError": "از خطای ذخیره‌شده ارائه شد", + "resilienceParked": "{ms} پیش از ارسال متوقف شد", + "resilienceRotations": "{count} چرخش حساب", + "resilienceRetried": "نوبت خالی دوباره امتحان شد", + "resilienceContinued": "از میانه جریان ادامه یافت", + "resilienceRecovered": "با یک اقدام تاب‌آوری بازیابی شد" } }, "proxyLogger": { diff --git a/src/i18n/messages/fi.json b/src/i18n/messages/fi.json index 3842986c07c..d19668c008e 100644 --- a/src/i18n/messages/fi.json +++ b/src/i18n/messages/fi.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Korrelaatio-ID: {id}", "detailedPayloadInfo": "Ota ensin käyttöön yksityiskohtainen lokitus, jos haluat nelivaiheisen asiakas/toimittaja kuormitusnäkymän uusille pyyntöille.", "copyAll": "Kopioi kaikki", - "copiedAll": "Kaikki kopioitu" + "copiedAll": "Kaikki kopioitu", + "resilienceStoredError": "Palveltu tallennetusta virheestä", + "resilienceParked": "Pysäköity {ms} ennen lähetystä", + "resilienceRotations": "{count} tilin kierrätystä", + "resilienceRetried": "Tyhjä vuoro yritettiin uudelleen", + "resilienceContinued": "Jatkettiin kesken virran", + "resilienceRecovered": "Palautettu kestävyystoiminnolla" } }, "proxyLogger": { diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 9bcff72e7b8..e9db91852c9 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ID de corrélation : {id}", "detailedPayloadInfo": "Activez d’abord la journalisation détaillée si vous voulez voir l’affichage en 4 étapes des payloads client/fournisseur pour les nouvelles requêtes.", "copyAll": "Tout copier", - "copiedAll": "Tout a été copié" + "copiedAll": "Tout a été copié", + "resilienceStoredError": "Servi depuis l’erreur stockée", + "resilienceParked": "En attente {ms} avant envoi", + "resilienceRotations": "{count} rotations de compte", + "resilienceRetried": "Tour vide réessayé", + "resilienceContinued": "Repris en cours de flux", + "resilienceRecovered": "Récupéré par une action de résilience" } }, "proxyLogger": { diff --git a/src/i18n/messages/ga.json b/src/i18n/messages/ga.json index 8ffef0004fa..4c2cb4e959e 100644 --- a/src/i18n/messages/ga.json +++ b/src/i18n/messages/ga.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Aitheantas Comhghaoil: {id}", "detailedPayloadInfo": "Cumasaigh logáil mhionsonraithe ar dtús más mian leat an t-amharc ceithre chéim ar ualaí cliaint/soláthraí le haghaidh iarratais nua.", "copyAll": "Cóipeáil go léir", - "copiedAll": "Cóipeáilte go léir" + "copiedAll": "Cóipeáilte go léir", + "resilienceStoredError": "Seirbheáilte ó earráid stóráilte", + "resilienceParked": "Páirceáilte {ms} roimh sheoladh", + "resilienceRotations": "{count} uainíocht cuntas", + "resilienceRetried": "Triaileadh an seal folamh arís", + "resilienceContinued": "Atosaíodh i lár an tsrutha", + "resilienceRecovered": "Aisghafa ag gníomh athléimneachta" } }, "proxyLogger": { diff --git a/src/i18n/messages/gu.json b/src/i18n/messages/gu.json index 61732b73a02..1aa7489d856 100644 --- a/src/i18n/messages/gu.json +++ b/src/i18n/messages/gu.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "સંબંધિત ID: {id}", "detailedPayloadInfo": "નવા વિનંતીઓ માટે ચાર-ચરણ ક્લાયન્ટ/પ્રદાતા પેલોડ દૃશ્ય મેળવવા માટે પ્રથમ વિગતવાર લોગિંગ સક્રિય કરો.", "copyAll": "બધું કૉપિ કરો", - "copiedAll": "બધું કૉપિ થયું" + "copiedAll": "બધું કૉપિ થયું", + "resilienceStoredError": "સંગ્રહિત ભૂલમાંથી પીરસવામાં આવ્યું", + "resilienceParked": "મોકલતા પહેલાં {ms} રોકી રાખ્યું", + "resilienceRotations": "{count} એકાઉન્ટ રોટેશન", + "resilienceRetried": "ખાલી ટર્ન ફરી પ્રયાસ કર્યો", + "resilienceContinued": "સ્ટ્રીમની વચ્ચેથી ફરી શરૂ કર્યું", + "resilienceRecovered": "રેઝિલિયન્સ ક્રિયા દ્વારા પુનઃપ્રાપ્ત" } }, "proxyLogger": { diff --git a/src/i18n/messages/ha.json b/src/i18n/messages/ha.json index 34d146cac29..110f9363afd 100644 --- a/src/i18n/messages/ha.json +++ b/src/i18n/messages/ha.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ID na Alaƙa: {id}", "detailedPayloadInfo": "Da farko, kunna yin cikakken log idan kana son ganin payload na matakai huɗu na abokin ciniki/mai bayarwa don sabbin buƙatu.", "copyAll": "Kwafi duka", - "copiedAll": "An kwafi duka" + "copiedAll": "An kwafi duka", + "resilienceStoredError": "An bayar daga kuskuren da aka adana", + "resilienceParked": "An ajiye {ms} kafin aikawa", + "resilienceRotations": "Juyawar asusu {count}", + "resilienceRetried": "An sake gwada zagaye mara komai", + "resilienceContinued": "An ci gaba a tsakiyar rafi", + "resilienceRecovered": "An dawo da shi ta matakin juriya" } }, "proxyLogger": { diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json index f85307231fd..919112d3734 100644 --- a/src/i18n/messages/he.json +++ b/src/i18n/messages/he.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "מזהה קורלציה: {id}", "detailedPayloadInfo": "אפשר לוגים מפורטים קודם אם אתה רוצה את תצוגת העומס של הלקוח/ספק בארבעה שלבים עבור בקשות חדשות.", "copyAll": "העתק הכול", - "copiedAll": "הכול הועתק" + "copiedAll": "הכול הועתק", + "resilienceStoredError": "הוגש משגיאה שמורה", + "resilienceParked": "הושהה {ms} לפני השליחה", + "resilienceRotations": "{count} החלפות חשבון", + "resilienceRetried": "תור ריק נוסה שוב", + "resilienceContinued": "חודש באמצע הזרם", + "resilienceRecovered": "שוחזר באמצעות פעולת חוסן" } }, "proxyLogger": { diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json index 971c74167c8..c300bdb923b 100644 --- a/src/i18n/messages/hi.json +++ b/src/i18n/messages/hi.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "संबंध पहचानकर्ता: {id}", "detailedPayloadInfo": "यदि आप नए अनुरोधों के लिए चार-चरणीय क्लाइंट/प्रदाता पेलोड दृश्य चाहते हैं, तो पहले विस्तृत लॉगिंग सक्षम करें।", "copyAll": "सभी कॉपी करें", - "copiedAll": "सब कुछ कॉपी किया गया" + "copiedAll": "सब कुछ कॉपी किया गया", + "resilienceStoredError": "सहेजी गई त्रुटि से परोसा गया", + "resilienceParked": "भेजने से पहले {ms} प्रतीक्षा", + "resilienceRotations": "{count} खाता रोटेशन", + "resilienceRetried": "खाली बारी पुनः प्रयास", + "resilienceContinued": "स्ट्रीम के बीच में जारी", + "resilienceRecovered": "लचीलापन कार्रवाई द्वारा पुनर्प्राप्त" } }, "proxyLogger": { diff --git a/src/i18n/messages/hr.json b/src/i18n/messages/hr.json index 1d8d72239e3..7adfeb887cc 100644 --- a/src/i18n/messages/hr.json +++ b/src/i18n/messages/hr.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ID korelacije: {id}", "detailedPayloadInfo": "Najprije omogućite detaljno zapisivanje ako želite prikaz korisnog tereta klijenta/pružatelja u četiri faze za nove zahtjeve.", "copyAll": "Kopiraj sve", - "copiedAll": "Sve kopirano" + "copiedAll": "Sve kopirano", + "resilienceStoredError": "Posluženo iz spremljene pogreške", + "resilienceParked": "Zadržano {ms} prije slanja", + "resilienceRotations": "{count} rotacija računa", + "resilienceRetried": "Prazan potez ponovljen", + "resilienceContinued": "Nastavljeno usred streama", + "resilienceRecovered": "Oporavljeno radnjom otpornosti" } }, "proxyLogger": { diff --git a/src/i18n/messages/hu.json b/src/i18n/messages/hu.json index 79945ba6ed2..530856d59f1 100644 --- a/src/i18n/messages/hu.json +++ b/src/i18n/messages/hu.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Korelációs azonosító: {id}", "detailedPayloadInfo": "Először engedélyezze a részletes naplózást, ha a négyfázisú kliens/szolgáltató payload nézetet szeretné az új kérésekhez.", "copyAll": "Összes másolása", - "copiedAll": "Minden másolva" + "copiedAll": "Minden másolva", + "resilienceStoredError": "Tárolt hibából kiszolgálva", + "resilienceParked": "{ms} várakoztatva küldés előtt", + "resilienceRotations": "{count} fiókrotáció", + "resilienceRetried": "Üres kör újrapróbálva", + "resilienceContinued": "Folytatva a stream közepén", + "resilienceRecovered": "Rugalmassági művelettel helyreállítva" } }, "proxyLogger": { diff --git a/src/i18n/messages/hy.json b/src/i18n/messages/hy.json index 7d94e17bcd2..61274a67271 100644 --- a/src/i18n/messages/hy.json +++ b/src/i18n/messages/hy.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Կապակցման ID՝ {id}", "detailedPayloadInfo": "Նոր հարցումների համար հաճախորդի/մատակարարի օգտակար բեռների քառափուլ տեսքը դիտելու նպատակով նախ միացրեք մանրամասն գրանցումը։", "copyAll": "Պատճենել ամբողջը", - "copiedAll": "Ամբողջը պատճենված է" + "copiedAll": "Ամբողջը պատճենված է", + "resilienceStoredError": "Մատուցվել է պահպանված սխալից", + "resilienceParked": "Կանգնեցվել է {ms} ուղարկելուց առաջ", + "resilienceRotations": "{count} հաշվի ռոտացիա", + "resilienceRetried": "Դատարկ հերթը կրկնվել է", + "resilienceContinued": "Վերսկսվել է հոսքի կեսից", + "resilienceRecovered": "Վերականգնվել է կայունության գործողությամբ" } }, "proxyLogger": { diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json index 81a324f4ab8..6b85ab94008 100644 --- a/src/i18n/messages/id.json +++ b/src/i18n/messages/id.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ID Korelasi: {id}", "detailedPayloadInfo": "Aktifkan logging terperinci terlebih dahulu jika Anda ingin tampilan payload klien/penyedia empat tahap untuk permintaan baru.", "copyAll": "Salin semua", - "copiedAll": "Semua disalin" + "copiedAll": "Semua disalin", + "resilienceStoredError": "Dilayani dari galat tersimpan", + "resilienceParked": "Ditahan {ms} sebelum dikirim", + "resilienceRotations": "{count} rotasi akun", + "resilienceRetried": "Giliran kosong dicoba ulang", + "resilienceContinued": "Dilanjutkan di tengah stream", + "resilienceRecovered": "Dipulihkan oleh tindakan ketahanan" } }, "proxyLogger": { diff --git a/src/i18n/messages/ig.json b/src/i18n/messages/ig.json index e6bccdc9231..99b8a14cccb 100644 --- a/src/i18n/messages/ig.json +++ b/src/i18n/messages/ig.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "NJ Mmekọrịta: {id}", "detailedPayloadInfo": "Buru ụzọ gbanye ndekọ zuru ezu ma ọ bụrụ na ịchọrọ ịhụ payload client/provider nke nwere usoro anọ maka arịrịọ ọhụrụ.", "copyAll": "Detuo ha niile", - "copiedAll": "E detuochaala ha niile" + "copiedAll": "E detuochaala ha niile", + "resilienceStoredError": "Enyere site na njehie echekwara", + "resilienceParked": "Edobere {ms} tupu izipu", + "resilienceRotations": "Ntụgharị akaụntụ {count}", + "resilienceRetried": "Anwaghachiri ntụgharị efu", + "resilienceContinued": "Maliteghachiri n'etiti iyi", + "resilienceRecovered": "Eweghachiri site na omume nkwụsi ike" } }, "proxyLogger": { diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index 0580c083da1..a442b478cdd 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ID di correlazione: {id}", "detailedPayloadInfo": "Abilita prima il logging dettagliato se desideri la visualizzazione del payload client/provider in quattro fasi per le nuove richieste.", "copyAll": "Copia tutto", - "copiedAll": "Tutto copiato" + "copiedAll": "Tutto copiato", + "resilienceStoredError": "Servito da errore memorizzato", + "resilienceParked": "In attesa {ms} prima dell’invio", + "resilienceRotations": "{count} rotazioni account", + "resilienceRetried": "Turno vuoto ripetuto", + "resilienceContinued": "Ripreso a metà flusso", + "resilienceRecovered": "Recuperato da un’azione di resilienza" } }, "proxyLogger": { diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index 57182671d75..6814bbbec31 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "相関ID: {id}", "detailedPayloadInfo": "新しいリクエストのために四段階のクライアント/プロバイダー ペイロードビューを表示したい場合は、まず詳細なログ記録を有効にしてください。", "copyAll": "すべてコピー", - "copiedAll": "すべてコピーしました" + "copiedAll": "すべてコピーしました", + "resilienceStoredError": "保存済みエラーから応答", + "resilienceParked": "送信前に{ms}待機", + "resilienceRotations": "アカウント切替{count}回", + "resilienceRetried": "空ターンを再試行", + "resilienceContinued": "ストリーム途中で再開", + "resilienceRecovered": "レジリエンス動作で回復" } }, "proxyLogger": { diff --git a/src/i18n/messages/ka.json b/src/i18n/messages/ka.json index fb3487f7a0f..8c95f1b7291 100644 --- a/src/i18n/messages/ka.json +++ b/src/i18n/messages/ka.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "კორელაციის ID: {id}", "detailedPayloadInfo": "ახალი მოთხოვნებისთვის კლიენტის/პროვაიდერის მონაცემების ოთხეტაპიანი ხედის გამოსაყენებლად, ჯერ დეტალური ჟურნალის წარმოება ჩართეთ.", "copyAll": "ყველაფრის კოპირება", - "copiedAll": "ყველაფერი დაკოპირებულია" + "copiedAll": "ყველაფერი დაკოპირებულია", + "resilienceStoredError": "მოწოდებულია შენახული შეცდომიდან", + "resilienceParked": "გაჩერებულია {ms} გაგზავნამდე", + "resilienceRotations": "ანგარიშის {count} როტაცია", + "resilienceRetried": "ცარიელი სვლა ხელახლა ცადა", + "resilienceContinued": "განახლდა ნაკადის შუაში", + "resilienceRecovered": "აღდგენილია მდგრადობის მოქმედებით" } }, "proxyLogger": { diff --git a/src/i18n/messages/km.json b/src/i18n/messages/km.json index 2d559c7102b..d08ab5072ca 100644 --- a/src/i18n/messages/km.json +++ b/src/i18n/messages/km.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "លេខសម្គាល់ទំនាក់ទំនង៖ {id}", "detailedPayloadInfo": "សូមបើកការកត់ត្រាលម្អិតជាមុនសិន ប្រសិនបើអ្នកចង់មើល payload បួនដំណាក់កាលរបស់កម្មវិធីអតិថិជន/អ្នកផ្តល់សេវា សម្រាប់សំណើថ្មីៗ។", "copyAll": "ចម្លងទាំងអស់", - "copiedAll": "បានចម្លងទាំងអស់" + "copiedAll": "បានចម្លងទាំងអស់", + "resilienceStoredError": "បានបម្រើពីកំហុសដែលបានរក្សាទុក", + "resilienceParked": "បានផ្អាក {ms} មុនពេលផ្ញើ", + "resilienceRotations": "ការបង្វិលគណនី {count} ដង", + "resilienceRetried": "បានព្យាយាមវេនទទេម្តងទៀត", + "resilienceContinued": "បានបន្តនៅកណ្តាលស្ទ្រីម", + "resilienceRecovered": "បានស្ដារឡើងវិញដោយសកម្មភាពធន់" } }, "proxyLogger": { diff --git a/src/i18n/messages/kn.json b/src/i18n/messages/kn.json index b4e8b34b72c..684e285cfda 100644 --- a/src/i18n/messages/kn.json +++ b/src/i18n/messages/kn.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ಸಹಸಂಬಂಧ ID: {id}", "detailedPayloadInfo": "ಹೊಸ ವಿನಂತಿಗಳಿಗಾಗಿ ನಾಲ್ಕು-ಹಂತದ ಕ್ಲೈಂಟ್/ಪೂರೈಕೆದಾರ ಪೇಲೋಡ್ ವೀಕ್ಷಣೆ ಬೇಕಿದ್ದರೆ ಮೊದಲು ವಿವರವಾದ ಲಾಗಿಂಗ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ.", "copyAll": "ಎಲ್ಲವನ್ನೂ ನಕಲಿಸಿ", - "copiedAll": "ಎಲ್ಲವನ್ನೂ ನಕಲಿಸಲಾಗಿದೆ" + "copiedAll": "ಎಲ್ಲವನ್ನೂ ನಕಲಿಸಲಾಗಿದೆ", + "resilienceStoredError": "ಸಂಗ್ರಹಿಸಿದ ದೋಷದಿಂದ ಒದಗಿಸಲಾಗಿದೆ", + "resilienceParked": "ಕಳುಹಿಸುವ ಮೊದಲು {ms} ತಡೆಹಿಡಿಯಲಾಗಿದೆ", + "resilienceRotations": "{count} ಖಾತೆ ಬದಲಾವಣೆಗಳು", + "resilienceRetried": "ಖಾಲಿ ಸರದಿಯನ್ನು ಮರುಪ್ರಯತ್ನಿಸಲಾಗಿದೆ", + "resilienceContinued": "ಸ್ಟ್ರೀಮ್ ಮಧ್ಯದಿಂದ ಮುಂದುವರಿಸಲಾಗಿದೆ", + "resilienceRecovered": "ಸ್ಥಿತಿಸ್ಥಾಪಕತ್ವ ಕ್ರಿಯೆಯಿಂದ ಮರುಪಡೆಯಲಾಗಿದೆ" } }, "proxyLogger": { diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 2696287037d..29d3c058bba 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "상관 ID: {id}", "detailedPayloadInfo": "새 요청에 대한 4단계 클라이언트/제공자 페이로드 보기를 원하시면 먼저 자세한 로깅을 활성화하세요.", "copyAll": "모두 복사", - "copiedAll": "모두 복사됨" + "copiedAll": "모두 복사됨", + "resilienceStoredError": "저장된 오류에서 응답", + "resilienceParked": "전송 전 {ms} 대기", + "resilienceRotations": "계정 전환 {count}회", + "resilienceRetried": "빈 턴 재시도", + "resilienceContinued": "스트림 중간에 재개", + "resilienceRecovered": "복원력 조치로 복구" } }, "proxyLogger": { diff --git a/src/i18n/messages/lt.json b/src/i18n/messages/lt.json index 81ef4eea44e..d6d541611a7 100644 --- a/src/i18n/messages/lt.json +++ b/src/i18n/messages/lt.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Koreliacijos ID: {id}", "detailedPayloadInfo": "Jei norite naujoms užklausoms matyti keturių etapų kliento ir teikėjo turinio rodinį, pirmiausia įjunkite išsamų registravimą.", "copyAll": "Kopijuoti viską", - "copiedAll": "Viskas nukopijuota" + "copiedAll": "Viskas nukopijuota", + "resilienceStoredError": "Pateikta iš išsaugotos klaidos", + "resilienceParked": "Sulaikyta {ms} prieš siuntimą", + "resilienceRotations": "{count} paskyrų rotacijos", + "resilienceRetried": "Tuščias ėjimas pakartotas", + "resilienceContinued": "Tęsta srauto viduryje", + "resilienceRecovered": "Atkurta atsparumo veiksmu" } }, "proxyLogger": { diff --git a/src/i18n/messages/lv.json b/src/i18n/messages/lv.json index ceac930b6cb..2f808f762b6 100644 --- a/src/i18n/messages/lv.json +++ b/src/i18n/messages/lv.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Korelācijas ID: {id}", "detailedPayloadInfo": "Vispirms iespējojiet detalizētu žurnalēšanu, ja vēlaties četru posmu klienta/nodrošinātāja derīgās slodzes skatu jauniem pieprasījumiem.", "copyAll": "Kopēt visu", - "copiedAll": "Viss nokopēts" + "copiedAll": "Viss nokopēts", + "resilienceStoredError": "Pasniegts no saglabātas kļūdas", + "resilienceParked": "Aizturēts {ms} pirms nosūtīšanas", + "resilienceRotations": "{count} kontu rotācijas", + "resilienceRetried": "Tukšais gājiens atkārtots", + "resilienceContinued": "Atsākts straumes vidū", + "resilienceRecovered": "Atjaunots ar noturības darbību" } }, "proxyLogger": { diff --git a/src/i18n/messages/ml.json b/src/i18n/messages/ml.json index 3627509c19f..7be1a5addab 100644 --- a/src/i18n/messages/ml.json +++ b/src/i18n/messages/ml.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "കോറിലേഷൻ ID: {id}", "detailedPayloadInfo": "പുതിയ അഭ്യർത്ഥനകൾക്കായുള്ള നാല്-ഘട്ട ക്ലയന്റ്/പ്രൊവൈഡർ പേലോഡ് കാഴ്ച വേണമെങ്കിൽ ആദ്യം വിശദമായ ലോഗിംഗ് പ്രവർത്തനക്ഷമമാക്കുക.", "copyAll": "എല്ലാം പകർത്തുക", - "copiedAll": "എല്ലാം പകർത്തി" + "copiedAll": "എല്ലാം പകർത്തി", + "resilienceStoredError": "സംഭരിച്ച പിശകിൽ നിന്ന് നൽകി", + "resilienceParked": "അയയ്ക്കുന്നതിന് മുമ്പ് {ms} തടഞ്ഞുവച്ചു", + "resilienceRotations": "{count} അക്കൗണ്ട് റൊട്ടേഷനുകൾ", + "resilienceRetried": "ശൂന്യമായ ടേൺ വീണ്ടും ശ്രമിച്ചു", + "resilienceContinued": "സ്ട്രീമിന്റെ മധ്യത്തിൽ നിന്ന് തുടർന്നു", + "resilienceRecovered": "റെസിലിയൻസ് നടപടിയിലൂടെ വീണ്ടെടുത്തു" } }, "proxyLogger": { diff --git a/src/i18n/messages/mr.json b/src/i18n/messages/mr.json index 8ea2293de20..0659291813e 100644 --- a/src/i18n/messages/mr.json +++ b/src/i18n/messages/mr.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "संबंधित आयडी: {id}", "detailedPayloadInfo": "नवीन विनंत्यांसाठी चार-चरणीय क्लायंट/प्रदाता पेलोड दृश्य पाहण्यासाठी प्रथम तपशीलवार लॉगिंग सक्षम करा.", "copyAll": "सर्व कॉपी करा", - "copiedAll": "सर्व कॉपी केले" + "copiedAll": "सर्व कॉपी केले", + "resilienceStoredError": "जतन केलेल्या त्रुटीतून दिले", + "resilienceParked": "पाठवण्यापूर्वी {ms} थांबवले", + "resilienceRotations": "{count} खाते रोटेशन", + "resilienceRetried": "रिकामी फेरी पुन्हा प्रयत्न केली", + "resilienceContinued": "स्ट्रीमच्या मध्यातून पुन्हा सुरू केले", + "resilienceRecovered": "लवचिकता कृतीद्वारे पुनर्प्राप्त" } }, "proxyLogger": { diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json index a7ed122e96a..56596dd721a 100644 --- a/src/i18n/messages/ms.json +++ b/src/i18n/messages/ms.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ID Korelasi: {id}", "detailedPayloadInfo": "Aktifkan log terperinci terlebih dahulu jika anda ingin pandangan muatan klien/pemberi perkhidmatan empat peringkat untuk permintaan baru.", "copyAll": "Salin semua", - "copiedAll": "Semua telah disalin" + "copiedAll": "Semua telah disalin", + "resilienceStoredError": "Dilayan daripada ralat tersimpan", + "resilienceParked": "Ditahan {ms} sebelum dihantar", + "resilienceRotations": "{count} putaran akaun", + "resilienceRetried": "Giliran kosong dicuba semula", + "resilienceContinued": "Disambung semula di tengah strim", + "resilienceRecovered": "Dipulihkan oleh tindakan daya tahan" } }, "proxyLogger": { diff --git a/src/i18n/messages/mt.json b/src/i18n/messages/mt.json index b2b5d5c6afb..b8f5dfac010 100644 --- a/src/i18n/messages/mt.json +++ b/src/i18n/messages/mt.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ID tal-Korrelazzjoni: {id}", "detailedPayloadInfo": "L-ewwel attiva r-reġistrazzjoni dettaljata jekk trid id-dehra tal-payload tal-klijent/fornitur f'erba' stadji għal talbiet ġodda.", "copyAll": "Ikkopja kollox", - "copiedAll": "Kollox ġie kkupjat" + "copiedAll": "Kollox ġie kkupjat", + "resilienceStoredError": "Moqdi minn żball maħżun", + "resilienceParked": "Ipparkjat {ms} qabel l-intbagħat", + "resilienceRotations": "{count} rotazzjonijiet tal-kont", + "resilienceRetried": "Dawra vojta ġiet ippruvata mill-ġdid", + "resilienceContinued": "Kompla f'nofs il-fluss", + "resilienceRecovered": "Irkuprat minn azzjoni ta' reżiljenza" } }, "proxyLogger": { diff --git a/src/i18n/messages/my.json b/src/i18n/messages/my.json index 10409d79638..bb18bb334a0 100644 --- a/src/i18n/messages/my.json +++ b/src/i18n/messages/my.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ဆက်စပ်မှု ID: {id}", "detailedPayloadInfo": "တောင်းဆိုမှုအသစ်များအတွက် အဆင့်လေးဆင့်ပါ Client/Provider payload မြင်ကွင်းကို ကြည့်လိုပါက အသေးစိတ်မှတ်တမ်းတင်ခြင်းကို ဦးစွာ ဖွင့်ပါ။", "copyAll": "အားလုံးကို ကူးယူရန်", - "copiedAll": "အားလုံးကို ကူးယူပြီးပါပြီ" + "copiedAll": "အားလုံးကို ကူးယူပြီးပါပြီ", + "resilienceStoredError": "သိမ်းဆည်းထားသော အမှားမှ ပေးပို့ခဲ့သည်", + "resilienceParked": "မပို့မီ {ms} ဆိုင်းငံ့ထားသည်", + "resilienceRotations": "အကောင့်လှည့်ပတ်မှု {count} ကြိမ်", + "resilienceRetried": "အလွတ်အလှည့်ကို ထပ်မံကြိုးစားခဲ့သည်", + "resilienceContinued": "စထရီးမ်အလယ်မှ ပြန်လည်စတင်ခဲ့သည်", + "resilienceRecovered": "ခံနိုင်ရည်လုပ်ဆောင်ချက်ဖြင့် ပြန်လည်ရယူခဲ့သည်" } }, "proxyLogger": { diff --git a/src/i18n/messages/ne.json b/src/i18n/messages/ne.json index 2dee0ee85c2..625a3d19375 100644 --- a/src/i18n/messages/ne.json +++ b/src/i18n/messages/ne.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "कोरिलेसन ID: {id}", "detailedPayloadInfo": "नयाँ अनुरोधहरूका लागि चार-चरणीय क्लाइन्ट/प्रदायक पेलोड दृश्य चाहनुहुन्छ भने पहिले विस्तृत लगिङ सक्षम गर्नुहोस्।", "copyAll": "सबै प्रतिलिपि गर्नुहोस्", - "copiedAll": "सबै प्रतिलिपि गरियो" + "copiedAll": "सबै प्रतिलिपि गरियो", + "resilienceStoredError": "भण्डारित त्रुटिबाट सेवा दिइयो", + "resilienceParked": "पठाउनुअघि {ms} रोकियो", + "resilienceRotations": "{count} खाता रोटेसन", + "resilienceRetried": "खाली पालो पुनः प्रयास गरियो", + "resilienceContinued": "स्ट्रिमको बीचबाट पुनः सुरु गरियो", + "resilienceRecovered": "लचिलोपन कार्यद्वारा पुनर्प्राप्त" } }, "proxyLogger": { diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index 21d8bef03a4..eeb88a73a8c 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Correlatie-ID: {id}", "detailedPayloadInfo": "Schakel eerst gedetailleerde logging in als je de vier-fasen client/provider payloadweergave voor nieuwe verzoeken wilt.", "copyAll": "Alles kopiëren", - "copiedAll": "Alles gekopieerd" + "copiedAll": "Alles gekopieerd", + "resilienceStoredError": "Bediend vanuit opgeslagen fout", + "resilienceParked": "{ms} geparkeerd vóór verzending", + "resilienceRotations": "{count} accountrotaties", + "resilienceRetried": "Lege beurt opnieuw geprobeerd", + "resilienceContinued": "Midden in stream hervat", + "resilienceRecovered": "Hersteld door een veerkrachtactie" } }, "proxyLogger": { diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json index 70d32c46c2a..9e743068e04 100644 --- a/src/i18n/messages/no.json +++ b/src/i18n/messages/no.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Korrelerings-ID: {id}", "detailedPayloadInfo": "Aktiver detaljert logging først hvis du vil ha den fire-trinns klient/leverandør nyttelastvisningen for nye forespørseler.", "copyAll": "Kopier alt", - "copiedAll": "Alt kopiert" + "copiedAll": "Alt kopiert", + "resilienceStoredError": "Levert fra lagret feil", + "resilienceParked": "Parkert {ms} før sending", + "resilienceRotations": "{count} kontorotasjoner", + "resilienceRetried": "Tom tur forsøkt på nytt", + "resilienceContinued": "Gjenopptatt midt i strømmen", + "resilienceRecovered": "Gjenopprettet av en robusthetshandling" } }, "proxyLogger": { diff --git a/src/i18n/messages/or.json b/src/i18n/messages/or.json index e8ed03853f4..ebb2e6e819a 100644 --- a/src/i18n/messages/or.json +++ b/src/i18n/messages/or.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ସହସମ୍ବନ୍ଧ ID: {id}", "detailedPayloadInfo": "ନୂଆ ଅନୁରୋଧଗୁଡ଼ିକ ପାଇଁ ଚାରି-ପର୍ଯ୍ୟାୟ କ୍ଲାଏଣ୍ଟ/ପ୍ରଦାନକାରୀ ପେଲୋଡ୍ ଦୃଶ୍ୟ ଚାହୁଁଥିଲେ ପ୍ରଥମେ ବିସ୍ତୃତ ଲଗିଂ ସକ୍ଷମ କରନ୍ତୁ।", "copyAll": "ସବୁ କପି କରନ୍ତୁ", - "copiedAll": "ସବୁ କପି କରାଯାଇଛି" + "copiedAll": "ସବୁ କପି କରାଯାଇଛି", + "resilienceStoredError": "ସଂରକ୍ଷିତ ତ୍ରୁଟିରୁ ପରିବେଷିତ", + "resilienceParked": "ପଠାଇବା ପୂର୍ବରୁ {ms} ଅଟକାଯାଇଛି", + "resilienceRotations": "{count} ଆକାଉଣ୍ଟ ରୋଟେସନ୍", + "resilienceRetried": "ଖାଲି ପାଳି ପୁନଃ ଚେଷ୍ଟା କରାଗଲା", + "resilienceContinued": "ଷ୍ଟ୍ରିମ୍ ମଝିରୁ ପୁନଃ ଆରମ୍ଭ", + "resilienceRecovered": "ସ୍ଥିତିସ୍ଥାପକତା କାର୍ଯ୍ୟ ଦ୍ୱାରା ପୁନରୁଦ୍ଧାର" } }, "proxyLogger": { diff --git a/src/i18n/messages/pa.json b/src/i18n/messages/pa.json index 2d4e4942c2a..3f73f3eae80 100644 --- a/src/i18n/messages/pa.json +++ b/src/i18n/messages/pa.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ਕੋਰੀਲੇਸ਼ਨ ID: {id}", "detailedPayloadInfo": "ਜੇ ਤੁਸੀਂ ਨਵੀਆਂ ਬੇਨਤੀਆਂ ਲਈ ਚਾਰ-ਪੜਾਅ ਵਾਲਾ ਕਲਾਇੰਟ/ਪ੍ਰਦਾਤਾ ਪੇਲੋਡ ਦ੍ਰਿਸ਼ ਚਾਹੁੰਦੇ ਹੋ, ਤਾਂ ਪਹਿਲਾਂ ਵਿਸਤ੍ਰਿਤ ਲੌਗਿੰਗ ਚਾਲੂ ਕਰੋ।", "copyAll": "ਸਭ ਕਾਪੀ ਕਰੋ", - "copiedAll": "ਸਭ ਕੁਝ ਕਾਪੀ ਹੋ ਗਿਆ" + "copiedAll": "ਸਭ ਕੁਝ ਕਾਪੀ ਹੋ ਗਿਆ", + "resilienceStoredError": "ਸਟੋਰ ਕੀਤੀ ਗਲਤੀ ਤੋਂ ਦਿੱਤਾ ਗਿਆ", + "resilienceParked": "ਭੇਜਣ ਤੋਂ ਪਹਿਲਾਂ {ms} ਰੋਕਿਆ ਗਿਆ", + "resilienceRotations": "{count} ਖਾਤਾ ਰੋਟੇਸ਼ਨ", + "resilienceRetried": "ਖਾਲੀ ਵਾਰੀ ਮੁੜ ਕੋਸ਼ਿਸ਼ ਕੀਤੀ", + "resilienceContinued": "ਸਟ੍ਰੀਮ ਦੇ ਵਿਚਕਾਰੋਂ ਮੁੜ ਸ਼ੁਰੂ ਕੀਤਾ", + "resilienceRecovered": "ਲਚਕੀਲੇਪਨ ਕਾਰਵਾਈ ਨਾਲ ਮੁੜ ਪ੍ਰਾਪਤ" } }, "proxyLogger": { diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json index e8cddd78957..5f610ab4258 100644 --- a/src/i18n/messages/phi.json +++ b/src/i18n/messages/phi.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ID ng Korelasyon: {id}", "detailedPayloadInfo": "I-enable ang detalyadong pag-log muna kung nais mo ang apat na yugto ng view ng client/provider payload para sa mga bagong kahilingan.", "copyAll": "Kopyahin lahat", - "copiedAll": "Nakopya na ang lahat" + "copiedAll": "Nakopya na ang lahat", + "resilienceStoredError": "Inihain mula sa naka-imbak na error", + "resilienceParked": "Na-park nang {ms} bago ipadala", + "resilienceRotations": "{count} pag-ikot ng account", + "resilienceRetried": "Inulit ang walang lamang turn", + "resilienceContinued": "Itinuloy sa gitna ng stream", + "resilienceRecovered": "Naibalik ng isang resilience action" } }, "proxyLogger": { diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index 3c47bb6f03b..47a5ca9987d 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Identyfikator korelacji: {id}", "detailedPayloadInfo": "Najpierw włącz szczegółowe logowanie, jeśli chcesz uzyskać widok ładunku klienta/dostawcy w czterech etapach dla nowych żądań.", "copyAll": "Skopiuj wszystko", - "copiedAll": "Skopiowano wszystko" + "copiedAll": "Skopiowano wszystko", + "resilienceStoredError": "Obsłużono z zapisanego błędu", + "resilienceParked": "Oczekiwano {ms} przed wysyłką", + "resilienceRotations": "{count} rotacji kont", + "resilienceRetried": "Ponawiono pustą turę", + "resilienceContinued": "Wznowiono w trakcie strumienia", + "resilienceRecovered": "Odzyskano dzięki akcji odporności" } }, "proxyLogger": { diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index 56d4624671f..5b750b3a9ec 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ID de Correlação: {id}", "detailedPayloadInfo": "Ative o registro detalhado primeiro se você quiser a visualização de carga útil de cliente/provedor em quatro etapas para novas solicitações.", "copyAll": "Copiar tudo", - "copiedAll": "Tudo copiado" + "copiedAll": "Tudo copiado", + "resilienceStoredError": "Servido a partir do erro salvo", + "resilienceParked": "Aguardou {ms} antes do envio", + "resilienceRotations": "{count} rotações de conta", + "resilienceRetried": "Turno vazio repetido", + "resilienceContinued": "Retomado no meio do fluxo", + "resilienceRecovered": "Recuperado por uma ação de resiliência" } }, "proxyLogger": { diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index faacc2a0bee..8b84b6c5142 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ID de Correlação: {id}", "detailedPayloadInfo": "Ative o registo detalhado primeiro se quiser a visualização de carga útil de cliente/fornecedor em quatro etapas para novos pedidos.", "copyAll": "Copiar tudo", - "copiedAll": "Tudo copiado" + "copiedAll": "Tudo copiado", + "resilienceStoredError": "Servido a partir do erro guardado", + "resilienceParked": "Aguardou {ms} antes do envio", + "resilienceRotations": "{count} rotações de conta", + "resilienceRetried": "Turno vazio repetido", + "resilienceContinued": "Retomado no meio do fluxo", + "resilienceRecovered": "Recuperado por uma ação de resiliência" } }, "proxyLogger": { diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json index 492f9f71f40..8a5784d612b 100644 --- a/src/i18n/messages/ro.json +++ b/src/i18n/messages/ro.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ID de corelare: {id}", "detailedPayloadInfo": "Activați mai întâi jurnalizarea detaliată dacă doriți vizualizarea payload-ului client/provider în patru etape pentru cererile noi.", "copyAll": "Copiază tot", - "copiedAll": "Totul a fost copiat" + "copiedAll": "Totul a fost copiat", + "resilienceStoredError": "Servit din eroarea stocată", + "resilienceParked": "Parcat {ms} înainte de trimitere", + "resilienceRotations": "{count} rotații de cont", + "resilienceRetried": "Tură goală reîncercată", + "resilienceContinued": "Reluat la mijlocul fluxului", + "resilienceRecovered": "Recuperat printr-o acțiune de reziliență" } }, "proxyLogger": { diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 85f3c29f756..3f60a6193da 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Идентификатор корреляции: {id}", "detailedPayloadInfo": "Сначала включите детализированный логгинг, если вы хотите получить четырехступенчатый просмотр полезной нагрузки клиента/поставщика для новых запросов.", "copyAll": "Копировать всё", - "copiedAll": "Всё скопировано" + "copiedAll": "Всё скопировано", + "resilienceStoredError": "Обслужено из сохранённой ошибки", + "resilienceParked": "Ожидание {ms} перед отправкой", + "resilienceRotations": "{count} ротаций аккаунта", + "resilienceRetried": "Пустой ход повторён", + "resilienceContinued": "Возобновлено в середине потока", + "resilienceRecovered": "Восстановлено действием устойчивости" } }, "proxyLogger": { diff --git a/src/i18n/messages/si.json b/src/i18n/messages/si.json index 3eee06bac77..0505bc8fd3c 100644 --- a/src/i18n/messages/si.json +++ b/src/i18n/messages/si.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "සහසම්බන්ධතා හැඳුනුම්කාරකය: {id}", "detailedPayloadInfo": "නව ඉල්ලීම් සඳහා අදියර හතරක සේවාලාභී/සපයන්නා දත්ත කොටස් දසුන අවශ්ය නම්, පළමුව සවිස්තරාත්මක ලොග් කිරීම සබල කරන්න.", "copyAll": "සියල්ල පිටපත් කරන්න", - "copiedAll": "සියල්ල පිටපත් කරන ලදී" + "copiedAll": "සියල්ල පිටපත් කරන ලදී", + "resilienceStoredError": "ගබඩා කළ දෝෂයෙන් සපයන ලදී", + "resilienceParked": "යැවීමට පෙර {ms} රඳවා ඇත", + "resilienceRotations": "ගිණුම් භ්‍රමණ {count}", + "resilienceRetried": "හිස් වාරය නැවත උත්සාහ කළා", + "resilienceContinued": "ප්‍රවාහය මැදින් නැවත ආරම්භ කළා", + "resilienceRecovered": "ප්‍රත්‍යස්ථතා ක්‍රියාවකින් ප්‍රතිසාධනය කළා" } }, "proxyLogger": { diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json index 0a09488b6e3..423205e8698 100644 --- a/src/i18n/messages/sk.json +++ b/src/i18n/messages/sk.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ID korelácie: {id}", "detailedPayloadInfo": "Najprv povolte podrobné protokolovanie, ak chcete zobrazenie štyroch fáz klienta/poskytovateľa pre nové požiadavky.", "copyAll": "Kopírovať všetko", - "copiedAll": "Všetko skopírované" + "copiedAll": "Všetko skopírované", + "resilienceStoredError": "Obslúžené z uloženej chyby", + "resilienceParked": "Odložené o {ms} pred odoslaním", + "resilienceRotations": "{count} rotácií účtov", + "resilienceRetried": "Prázdny ťah zopakovaný", + "resilienceContinued": "Obnovené uprostred streamu", + "resilienceRecovered": "Obnovené akciou odolnosti" } }, "proxyLogger": { diff --git a/src/i18n/messages/sl.json b/src/i18n/messages/sl.json index b9e01faf814..cd0ec1acb7e 100644 --- a/src/i18n/messages/sl.json +++ b/src/i18n/messages/sl.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Korelacijski ID: {id}", "detailedPayloadInfo": "Najprej omogočite podrobno beleženje, če želite štiristopenjski pogled vsebine odjemalca/ponudnika za nove zahteve.", "copyAll": "Kopiraj vse", - "copiedAll": "Vse kopirano" + "copiedAll": "Vse kopirano", + "resilienceStoredError": "Postreženo iz shranjene napake", + "resilienceParked": "Zadržano {ms} pred pošiljanjem", + "resilienceRotations": "{count} rotacij računov", + "resilienceRetried": "Prazen krog ponovljen", + "resilienceContinued": "Nadaljevano sredi toka", + "resilienceRecovered": "Obnovljeno z dejanjem odpornosti" } }, "proxyLogger": { diff --git a/src/i18n/messages/sr.json b/src/i18n/messages/sr.json index f6c0e8bbd85..c959841fb48 100644 --- a/src/i18n/messages/sr.json +++ b/src/i18n/messages/sr.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ID корелације: {id}", "detailedPayloadInfo": "Ако за нове захтеве желите приказ садржаја клијента и провајдера у четири фазе, прво омогућите детаљно логовање.", "copyAll": "Копирај све", - "copiedAll": "Све копирано" + "copiedAll": "Све копирано", + "resilienceStoredError": "Послужено из сачуване грешке", + "resilienceParked": "Задржано {ms} пре слања", + "resilienceRotations": "{count} ротација налога", + "resilienceRetried": "Празан потез поновљен", + "resilienceContinued": "Настављено усред стрима", + "resilienceRecovered": "Опорављено акцијом отпорности" } }, "proxyLogger": { diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json index b66ca033112..f910c175444 100644 --- a/src/i18n/messages/sv.json +++ b/src/i18n/messages/sv.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Korrelations-ID: {id}", "detailedPayloadInfo": "Aktivera detaljerad loggning först om du vill ha den fyrastegs klient/leverantörs payload-vyn för nya förfrågningar.", "copyAll": "Kopiera allt", - "copiedAll": "Allt kopierat" + "copiedAll": "Allt kopierat", + "resilienceStoredError": "Levererat från sparat fel", + "resilienceParked": "Parkerad {ms} före sändning", + "resilienceRotations": "{count} kontorotationer", + "resilienceRetried": "Tom tur försökt igen", + "resilienceContinued": "Återupptaget mitt i strömmen", + "resilienceRecovered": "Återställt av en motståndskraftsåtgärd" } }, "proxyLogger": { diff --git a/src/i18n/messages/sw.json b/src/i18n/messages/sw.json index a9e997a1827..575291e159f 100644 --- a/src/i18n/messages/sw.json +++ b/src/i18n/messages/sw.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Kitambulisho cha Uhusiano: {id}", "detailedPayloadInfo": "Washa ufuatiliaji wa kina kwanza ikiwa unataka mtazamo wa mzigo wa mteja/mtoa huduma wa hatua nne kwa maombi mapya.", "copyAll": "Nakili zote", - "copiedAll": "Zote zimenakiliwa" + "copiedAll": "Zote zimenakiliwa", + "resilienceStoredError": "Imetolewa kutoka kwa hitilafu iliyohifadhiwa", + "resilienceParked": "Imesimamishwa {ms} kabla ya kutuma", + "resilienceRotations": "Mizunguko {count} ya akaunti", + "resilienceRetried": "Zamu tupu imejaribiwa tena", + "resilienceContinued": "Imeendelea katikati ya mtiririko", + "resilienceRecovered": "Imerejeshwa na hatua ya ustahimilivu" } }, "proxyLogger": { diff --git a/src/i18n/messages/ta.json b/src/i18n/messages/ta.json index 040c5e3705c..ec65834e7cf 100644 --- a/src/i18n/messages/ta.json +++ b/src/i18n/messages/ta.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "உறுப்பு அடையாளம்: {id}", "detailedPayloadInfo": "முதலில் விரிவான பதிவு செயல்படுத்தவும், புதிய கோரிக்கைகளுக்கான நான்கு கட்டம் கிளையன்ட்/முதலீட்டாளர் payload காட்சி வேண்டும் என்றால்.", "copyAll": "அனைத்தையும் நகலெடு", - "copiedAll": "அனைத்தும் நகலெடுக்கப்பட்டன" + "copiedAll": "அனைத்தும் நகலெடுக்கப்பட்டன", + "resilienceStoredError": "சேமித்த பிழையிலிருந்து வழங்கப்பட்டது", + "resilienceParked": "அனுப்புவதற்கு முன் {ms} நிறுத்தி வைக்கப்பட்டது", + "resilienceRotations": "{count} கணக்கு சுழற்சிகள்", + "resilienceRetried": "வெற்று முறை மீண்டும் முயற்சிக்கப்பட்டது", + "resilienceContinued": "ஸ்ட்ரீமின் நடுவில் தொடரப்பட்டது", + "resilienceRecovered": "மீள்தன்மை செயலால் மீட்கப்பட்டது" } }, "proxyLogger": { diff --git a/src/i18n/messages/te.json b/src/i18n/messages/te.json index b5055ac9e41..bc3c087e59c 100644 --- a/src/i18n/messages/te.json +++ b/src/i18n/messages/te.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "సంబంధిత ID: {id}", "detailedPayloadInfo": "కొత్త అభ్యర్థనల కోసం నాలుగు దశల క్లయింట్/ప్రొవైడర్ పేమెంట్ వీక్షణను కావాలంటే, మొదట వివరణాత్మక లాగింగ్‌ను ప్రారంభించండి.", "copyAll": "అన్నింటినీ కాపీ చేయండి", - "copiedAll": "అన్నీ కాపీ అయ్యాయి" + "copiedAll": "అన్నీ కాపీ అయ్యాయి", + "resilienceStoredError": "నిల్వ చేసిన లోపం నుండి అందించబడింది", + "resilienceParked": "పంపే ముందు {ms} నిలిపి ఉంచబడింది", + "resilienceRotations": "{count} ఖాతా రొటేషన్లు", + "resilienceRetried": "ఖాళీ టర్న్ మళ్లీ ప్రయత్నించబడింది", + "resilienceContinued": "స్ట్రీమ్ మధ్యలో నుండి కొనసాగించబడింది", + "resilienceRecovered": "రెసిలియెన్స్ చర్య ద్వారా పునరుద్ధరించబడింది" } }, "proxyLogger": { diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json index f3ada8e7213..889b50f095a 100644 --- a/src/i18n/messages/th.json +++ b/src/i18n/messages/th.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "รหัสความสัมพันธ์: {id}", "detailedPayloadInfo": "โปรดเปิดใช้งานการบันทึกข้อมูลอย่างละเอียดก่อนหากคุณต้องการดูข้อมูลการส่งข้อมูลของไคลเอนต์/ผู้ให้บริการแบบสี่ขั้นตอนสำหรับคำขอใหม่", "copyAll": "คัดลอกทั้งหมด", - "copiedAll": "คัดลอกทั้งหมดแล้ว" + "copiedAll": "คัดลอกทั้งหมดแล้ว", + "resilienceStoredError": "ให้บริการจากข้อผิดพลาดที่จัดเก็บไว้", + "resilienceParked": "พักไว้ {ms} ก่อนส่ง", + "resilienceRotations": "สลับบัญชี {count} ครั้ง", + "resilienceRetried": "ลองรอบว่างใหม่อีกครั้ง", + "resilienceContinued": "ทำต่อกลางสตรีม", + "resilienceRecovered": "กู้คืนด้วยการดำเนินการด้านความยืดหยุ่น" } }, "proxyLogger": { diff --git a/src/i18n/messages/tr.json b/src/i18n/messages/tr.json index 18a087a31d6..2119b366171 100644 --- a/src/i18n/messages/tr.json +++ b/src/i18n/messages/tr.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Korelasyon Kimliği: {id}", "detailedPayloadInfo": "Yeni istekler için dört aşamalı istemci/sağlayıcı yük görünümünü istiyorsanız önce ayrıntılı günlüğü etkinleştirin.", "copyAll": "Tümünü kopyala", - "copiedAll": "Tümü kopyalandı" + "copiedAll": "Tümü kopyalandı", + "resilienceStoredError": "Kayıtlı hatadan sunuldu", + "resilienceParked": "Göndermeden önce {ms} bekletildi", + "resilienceRotations": "{count} hesap rotasyonu", + "resilienceRetried": "Boş tur yeniden denendi", + "resilienceContinued": "Akış ortasında devam edildi", + "resilienceRecovered": "Dayanıklılık eylemiyle kurtarıldı" } }, "proxyLogger": { diff --git a/src/i18n/messages/uk-UA.json b/src/i18n/messages/uk-UA.json index 6d74d2f0ae4..b9b9f0dae60 100644 --- a/src/i18n/messages/uk-UA.json +++ b/src/i18n/messages/uk-UA.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Ідентифікатор кореляції: {id}", "detailedPayloadInfo": "Спочатку увімкніть детальне ведення журналів, якщо ви хочете перегляд чотирьохетапного навантаження клієнта/постачальника для нових запитів.", "copyAll": "Копіювати все", - "copiedAll": "Усе скопійовано" + "copiedAll": "Усе скопійовано", + "resilienceStoredError": "Відповідь зі збереженої помилки", + "resilienceParked": "Затримано {ms} перед надсиланням", + "resilienceRotations": "{count} ротацій облікових записів", + "resilienceRetried": "Порожній хід повторено", + "resilienceContinued": "Відновлено посеред потоку", + "resilienceRecovered": "Відновлено дією стійкості" } }, "proxyLogger": { diff --git a/src/i18n/messages/ur.json b/src/i18n/messages/ur.json index 89290993aca..991813196a1 100644 --- a/src/i18n/messages/ur.json +++ b/src/i18n/messages/ur.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "کوریلیشن ID: {id}", "detailedPayloadInfo": "پہلے تفصیلی لاگنگ فعال کریں اگر آپ نئے درخواستوں کے لیے چار مرحلوں کے کلائنٹ/فراہم کنندہ پے لوڈ ویو چاہتے ہیں۔", "copyAll": "سب کچھ کاپی کریں", - "copiedAll": "سب کچھ کاپی ہو گیا" + "copiedAll": "سب کچھ کاپی ہو گیا", + "resilienceStoredError": "محفوظ شدہ خرابی سے فراہم کیا گیا", + "resilienceParked": "بھیجنے سے پہلے {ms} روکا گیا", + "resilienceRotations": "{count} اکاؤنٹ گردشیں", + "resilienceRetried": "خالی باری دوبارہ آزمائی گئی", + "resilienceContinued": "اسٹریم کے درمیان سے دوبارہ شروع کیا گیا", + "resilienceRecovered": "لچک کی کارروائی سے بحال کیا گیا" } }, "proxyLogger": { diff --git a/src/i18n/messages/uz.json b/src/i18n/messages/uz.json index d3553427512..096de3c1dcf 100644 --- a/src/i18n/messages/uz.json +++ b/src/i18n/messages/uz.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "Korrelyatsiya identifikatori: {id}", "detailedPayloadInfo": "Yangi soʻrovlar uchun mijoz/provayder maʼlumotlarining toʻrt bosqichli koʻrinishidan foydalanmoqchi boʻlsangiz, avval batafsil jurnallashni yoqing.", "copyAll": "Barchasini nusxalash", - "copiedAll": "Barchasi nusxalandi" + "copiedAll": "Barchasi nusxalandi", + "resilienceStoredError": "Saqlangan xatodan taqdim etildi", + "resilienceParked": "Yuborishdan oldin {ms} to‘xtatildi", + "resilienceRotations": "{count} ta hisob rotatsiyasi", + "resilienceRetried": "Bo‘sh navbat qayta urinildi", + "resilienceContinued": "Oqim o‘rtasidan davom ettirildi", + "resilienceRecovered": "Barqarorlik amali bilan tiklandi" } }, "proxyLogger": { diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index 14d1a7889c1..c6983cbfa2f 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ID tương quan: {id}", "detailedPayloadInfo": "Hãy bật ghi nhật ký chi tiết trước nếu bạn muốn xem giao diện payload client/nhà cung cấp gồm 4 giai đoạn cho các yêu cầu mới.", "copyAll": "Sao chép tất cả", - "copiedAll": "Đã sao chép tất cả" + "copiedAll": "Đã sao chép tất cả", + "resilienceStoredError": "Phục vụ từ lỗi đã lưu", + "resilienceParked": "Đã chờ {ms} trước khi gửi", + "resilienceRotations": "{count} lần chuyển tài khoản", + "resilienceRetried": "Đã thử lại lượt trống", + "resilienceContinued": "Đã tiếp tục giữa luồng", + "resilienceRecovered": "Đã khôi phục bằng hành động phục hồi" } }, "proxyLogger": { diff --git a/src/i18n/messages/yo.json b/src/i18n/messages/yo.json index c983f561384..613b7f26ed3 100644 --- a/src/i18n/messages/yo.json +++ b/src/i18n/messages/yo.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "ID Ìbámu: {id}", "detailedPayloadInfo": "Kọ́kọ́ mú ìforúkọsílẹ̀ alálàyé ṣiṣẹ́ bí o bá fẹ́ àfihàn àkóónú client/provider onípele mẹ́rin fún àwọn ìbéèrè tuntun.", "copyAll": "Ṣe àdàkọ gbogbo rẹ̀", - "copiedAll": "A ti ṣe àdàkọ gbogbo rẹ̀" + "copiedAll": "A ti ṣe àdàkọ gbogbo rẹ̀", + "resilienceStoredError": "A pèsè láti inú àṣìṣe tí a fipamọ́", + "resilienceParked": "A dá dúró {ms} ṣáájú fífiránṣẹ́", + "resilienceRotations": "Ìyípo àkáǹtì {count}", + "resilienceRetried": "A tún gbìyànjú àyípo òfo", + "resilienceContinued": "A tẹ̀síwájú láàrin ìṣàn", + "resilienceRecovered": "A gbà padà nípasẹ̀ ìgbésẹ̀ ìfaradà" } }, "proxyLogger": { diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 4c458a47174..772aa864392 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "关联 ID: {id}", "detailedPayloadInfo": "如果您想要新请求的四阶段客户端/提供者负载视图,请先启用详细日志记录。", "copyAll": "复制全部", - "copiedAll": "已复制全部" + "copiedAll": "已复制全部", + "resilienceStoredError": "根据已存错误提供服务", + "resilienceParked": "发送前等待{ms}", + "resilienceRotations": "轮换帐户{count}次", + "resilienceRetried": "重试空轮次", + "resilienceContinued": "在流中途恢复", + "resilienceRecovered": "通过弹性操作恢复" } }, "proxyLogger": { diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index dac6cf10144..1d61ebabbaf 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -11509,7 +11509,13 @@ "correlationIdValue": "關聯 ID: {id}", "detailedPayloadInfo": "如果您想要新請求的四階段客戶端/提供者有效載荷視圖,請先啟用詳細日誌記錄。", "copyAll": "全部複製", - "copiedAll": "已全部複製" + "copiedAll": "已全部複製", + "resilienceStoredError": "根據已存錯誤提供服務", + "resilienceParked": "傳送前等待{ms}", + "resilienceRotations": "輪換帳戶{count}次", + "resilienceRetried": "重試空輪次", + "resilienceContinued": "在串流中途恢復", + "resilienceRecovered": "透過彈性操作恢復" } }, "proxyLogger": { diff --git a/src/lib/db/migrations/191_call_logs_resilience_actions.sql b/src/lib/db/migrations/191_call_logs_resilience_actions.sql new file mode 100644 index 00000000000..bb231e17cc2 --- /dev/null +++ b/src/lib/db/migrations/191_call_logs_resilience_actions.sql @@ -0,0 +1,8 @@ +-- 191_call_logs_resilience_actions.sql +-- Per-attempt resilience summary: what OmniRoute did to save the request +-- (account rotations, park-and-replay, empty-turn retries, mid-stream +-- continuations, stored-error replay, buffered-turn verdict). Additive: +-- NULL means no resilience action was recorded. Compact JSON, closed keys. +-- No index: badge reads only, no search contract. + +ALTER TABLE call_logs ADD COLUMN resilience_actions TEXT DEFAULT NULL; diff --git a/src/lib/db/schemaColumns.ts b/src/lib/db/schemaColumns.ts index d4982b66ba6..74b516d9afc 100644 --- a/src/lib/db/schemaColumns.ts +++ b/src/lib/db/schemaColumns.ts @@ -290,6 +290,14 @@ export function ensureCallLogsColumns(db: SqliteDatabase) { db.exec("ALTER TABLE call_logs ADD COLUMN usage_provenance TEXT DEFAULT NULL"); console.log("[DB] Added call_logs.usage_provenance column"); } + // added by 191_call_logs_resilience_actions; back-filled here for + // lineages that skipped the migration file — the call-log write path + // references this column on every insert (guarded by hasCallLogsColumn, + // but the heal keeps old databases queryable without the guard). + if (!columnNames.has("resilience_actions")) { + db.exec("ALTER TABLE call_logs ADD COLUMN resilience_actions TEXT DEFAULT NULL"); + console.log("[DB] Added call_logs.resilience_actions column"); + } db.exec( "CREATE INDEX IF NOT EXISTS idx_call_logs_requested_model ON call_logs(requested_model)" diff --git a/src/lib/usage/callLogs.ts b/src/lib/usage/callLogs.ts index 5e1c94e3db1..5a972db07cb 100644 --- a/src/lib/usage/callLogs.ts +++ b/src/lib/usage/callLogs.ts @@ -14,6 +14,8 @@ import { getRequestDetailLogByCallLogId } from "../db/detailedLogs"; import { shouldPersistToDisk } from "./migrations"; import { updateRequestTokensById } from "./usageHistory"; import { getCallLogApiKeyContext } from "./callLogApiKeyContext"; +import { serializeResilienceActions, resetResilienceActions } from "./resilienceActionsContext"; +import { parseResilienceActions } from "./resilienceActionsParse"; import { seedPendingContinuationState, clearPendingContinuationState, @@ -127,6 +129,7 @@ type CallLogSummaryRow = { correlation_id?: string | null; model_pinned?: number | null; session_tag?: string | null; + resilience_actions?: string | null; has_content?: number | null; usage_provenance?: string | null; }; @@ -364,6 +367,28 @@ function hasTable(tableName: string): boolean { ); } +function hasCallLogsColumn(columnName: string): boolean { + // One PRAGMA per INSERT is wasteful; the cached answer is invalidated only + // when a write fails with "no such column" (concurrent migration race). + const cached = (hasCallLogsColumn as { cache?: Map }).cache; + if (cached?.has(columnName)) return cached.get(columnName) as boolean; + try { + const db = getDbInstance(); + const rows = db.prepare("PRAGMA table_info(call_logs)").all() as Array<{ name?: string }>; + const found = rows.some((row) => row.name === columnName); + const map = cached ?? new Map(); + map.set(columnName, found); + (hasCallLogsColumn as { cache?: Map }).cache = map; + return found; + } catch { + return false; + } +} + +export function invalidateCallLogsColumnCache(): void { + (hasCallLogsColumn as { cache?: Map }).cache = undefined; +} + function readLegacyLogFromDisk(entry: { timestamp: string | null; model: string | null; @@ -427,6 +452,8 @@ export function resolveProviderDisplay( return null; } +export { parseResilienceActions }; + function mapSummaryRow(row: CallLogSummaryRow) { const detailState = normalizeDetailState(row.detail_state); const provider = row.provider; @@ -476,6 +503,7 @@ function mapSummaryRow(row: CallLogSummaryRow) { correlationId: row.correlation_id || null, modelPinned: toNumber(row.model_pinned) === 1, sessionTag: row.session_tag || null, + resilienceActions: parseResilienceActions(row.resilience_actions ?? null), }; } @@ -559,6 +587,13 @@ async function saveCallLogOperation(entry: any): Promise { ); } + // resilience resilience summary for this attempt (implicit ALS store opened + // around the attempt; null outside a store or when nothing was noted). + // Read BEFORE any await: the ALS context is synchronous and later awaits + // (resolveAccountName, artifact write) may cross async boundaries. + const resilienceActions = serializeResilienceActions(); + const hasResilienceColumn = hasCallLogsColumn("resilience_actions"); + const account = await resolveAccountName(entry.connectionId || null); const rawProvider: string = entry.provider || "-"; const rawRequestedModel: string | null = entry.requestedModel || null; @@ -726,6 +761,9 @@ async function saveCallLogOperation(entry: any): Promise { } } + // Optional column (migration 191) — only fixed identifiers are spliced in. + const resilienceCol = hasResilienceColumn ? ", resilience_actions" : ""; + const resilienceParam = hasResilienceColumn ? ", @resilienceActions" : ""; const insertStmt = db.prepare( ` INSERT INTO call_logs ( @@ -740,7 +778,7 @@ async function saveCallLogOperation(entry: any): Promise { artifact_relpath, artifact_size_bytes, artifact_sha256, has_request_body, has_response_body, has_pipeline_details, request_summary, correlation_id, model_pinned, session_tag, response_id, error_type, - video_content_removed, has_content, usage_provenance + video_content_removed, has_content, usage_provenance${resilienceCol} ) VALUES ( @id, @timestamp, @method, @path, @status, @model, @requestedModel, @provider, @@ -754,7 +792,7 @@ async function saveCallLogOperation(entry: any): Promise { @artifactRelPath, @artifactSizeBytes, @artifactSha256, @hasRequestBody, @hasResponseBody, @hasPipelineDetails, @requestSummary, @correlationId, @modelPinned, @sessionTag, @responseId, @errorType, - @videoContentRemoved, @hasContent, @usageProvenance + @videoContentRemoved, @hasContent, @usageProvenance${resilienceParam} ) ` ); @@ -767,6 +805,7 @@ async function saveCallLogOperation(entry: any): Promise { artifactSha256, hasRequestBody: protectedRequestBody !== null ? 1 : 0, hasResponseBody: protectedResponseBody !== null ? 1 : 0, + resilienceActions, hasPipelineDetails: protectedPipelinePayloads ? 1 : 0, requestSummary, }; @@ -781,6 +820,10 @@ async function saveCallLogOperation(entry: any): Promise { insertParams.id = generateLogId(); } } + // sink note: the sink is the unique consumer — reset only after a successful + // INSERT, so a failed write (or a second persistence of the same + // attempt) keeps the summary instead of silently writing NULL. + resetResilienceActions(); if (detailState === "ready" && typeof logEntry.responseId === "string") { // The durable row is now authoritative; drop the bridge entry instead @@ -790,6 +833,9 @@ async function saveCallLogOperation(entry: any): Promise { scheduleCallLogRotation(); } catch (error) { + if (String((error as Error)?.message ?? error).includes("no such column")) { + invalidateCallLogsColumnCache(); + } console.error( "[callLogs] Failed to save call log:", sanitizeErrorMessage(error) || "Call log persistence failed" diff --git a/src/lib/usage/resilienceActionsContext.ts b/src/lib/usage/resilienceActionsContext.ts new file mode 100644 index 00000000000..4511aff9ca9 --- /dev/null +++ b/src/lib/usage/resilienceActionsContext.ts @@ -0,0 +1,177 @@ +import { AsyncLocalStorage } from "node:async_hooks"; + +/** + * Per-attempt resilience-action accumulator + * + * Implicit ALS model (same pattern as `callLogApiKeyContext.ts`): the caller + * opens one store per attempt with `runWithResilienceActionsContext(fn)` and + * the action points publish one-line best-effort notes. The sink + * (`saveCallLogOperation`) reads the store with `readResilienceActions()`. + * No traceId threading: isolation comes from the store, not from a key. + */ + +export type ResilienceActionsPartial = { + rotations?: number; + parkMs?: number; + parked?: boolean; + replayed?: boolean; + stored429?: boolean; + emptyRetries?: number; + continued?: number; + buffered?: "retry" | "pass"; + resumed?: boolean; +}; + +export type ResilienceActionsAccumulator = { + rotations: number; + parkMs: number; + parked: boolean; + replayed: boolean | null; + stored429: boolean; + emptyRetries: number; + continued: number; + buffered: "retry" | "pass" | null; + resumed: boolean; + /** Notes dropped once NOTE_CAP is hit (anti-bloat accounting). */ + droppedNotes: number; +}; + +/** Max note() calls honoured per attempt store; beyond it notes are dropped. */ +export const RESILIENCE_NOTE_CAP = 200; + +const resilienceActionsContext = new AsyncLocalStorage(); + +function freshAccumulator(): ResilienceActionsAccumulator { + return { + rotations: 0, + parkMs: 0, + parked: false, + replayed: null, + stored429: false, + emptyRetries: 0, + continued: 0, + buffered: null, + resumed: false, + droppedNotes: 0, + }; +} + +/** + * Open one accumulator store for a single attempt. Combo attempts each get + * their own store, so summaries never leak across attempts. + */ +export function runWithResilienceActionsContext(callback: () => TResult): TResult { + return resilienceActionsContext.run(freshAccumulator(), callback); +} + +function isValidBuffered(value: unknown): value is "retry" | "pass" { + return value === "retry" || value === "pass"; +} + +function addFiniteCount(current: number, value: number | undefined): number { + if (typeof value !== "number" || !Number.isFinite(value)) return current; + return current + Math.max(0, Math.floor(value)); +} + +function addFiniteMs(current: number, value: number | undefined): number { + if (typeof value !== "number" || !Number.isFinite(value)) return current; + return current + Math.max(0, Math.round(value)); +} + +/** + * Publish one action fragment into the current attempt store. Best-effort: + * no-op outside a store, never throws into the request path. + */ +export function noteResilienceAction(partial: ResilienceActionsPartial): void { + try { + const store = resilienceActionsContext.getStore(); + if (!store || partial === null || typeof partial !== "object") return; + const notes = (store as { __notes?: number }).__notes ?? 0; + if (notes >= RESILIENCE_NOTE_CAP) { + store.droppedNotes += 1; + return; + } + (store as { __notes?: number }).__notes = notes + 1; + store.rotations = addFiniteCount(store.rotations, partial.rotations); + store.parkMs = addFiniteMs(store.parkMs, partial.parkMs); + if (partial.parked === true) store.parked = true; + if (typeof partial.replayed === "boolean") store.replayed = partial.replayed; + if (partial.stored429 === true) store.stored429 = true; + store.emptyRetries = addFiniteCount(store.emptyRetries, partial.emptyRetries); + store.continued = addFiniteCount(store.continued, partial.continued); + if (isValidBuffered(partial.buffered)) store.buffered = partial.buffered; + if (partial.resumed === true) store.resumed = true; + } catch { + /* resilience notes are best-effort; never break the request path */ + } +} + +export type ResilienceActionsSnapshot = { + rotations?: number; + park_ms?: number; + parked?: true; + replayed?: boolean; + stored_429?: true; + empty_retries?: number; + continued?: number; + buffered?: "retry" | "pass"; + resumed?: true; + dropped_notes?: number; +}; + +function snapshotStore(store: ResilienceActionsAccumulator): ResilienceActionsSnapshot { + const snapshot: ResilienceActionsSnapshot = {}; + if (store.rotations > 0) snapshot.rotations = store.rotations; + if (store.parkMs > 0) snapshot.park_ms = store.parkMs; + if (store.parked) snapshot.parked = true; + if (store.replayed !== null) snapshot.replayed = store.replayed; + if (store.stored429) snapshot.stored_429 = true; + if (store.emptyRetries > 0) snapshot.empty_retries = store.emptyRetries; + if (store.continued > 0) snapshot.continued = store.continued; + if (store.buffered !== null) snapshot.buffered = store.buffered; + if (store.resumed) snapshot.resumed = true; + if (store.droppedNotes > 0) snapshot.dropped_notes = store.droppedNotes; + return snapshot; +} + +/** + * Read the current attempt store WITHOUT consuming it: double reads return + * the same summary. The call-log sink is the unique consumer — it calls + * resetResilienceActions() only after a successful INSERT, so a failed + * write keeps the summary for retry. + */ +export function readResilienceActions(): ResilienceActionsSnapshot | null { + try { + const store = resilienceActionsContext.getStore(); + if (!store) return null; + const snapshot = snapshotStore(store); + return Object.keys(snapshot).length === 0 ? null : snapshot; + } catch { + return null; + } +} + +/** Explicit reset, called by the sink only after a successful INSERT. */ +export function resetResilienceActions(): void { + try { + if (resilienceActionsContext.getStore()) { + resilienceActionsContext.enterWith(freshAccumulator()); + } + } catch { + /* best-effort */ + } +} + +/** + * Serialize the current attempt store to the compact JSON persisted in + * `call_logs.resilience_actions`. Null when nothing was recorded. + */ +export function serializeResilienceActions(): string | null { + const snapshot = readResilienceActions(); + if (!snapshot) return null; + try { + return JSON.stringify(snapshot); + } catch { + return null; + } +} diff --git a/src/lib/usage/resilienceActionsParse.ts b/src/lib/usage/resilienceActionsParse.ts new file mode 100644 index 00000000000..c0d21759281 --- /dev/null +++ b/src/lib/usage/resilienceActionsParse.ts @@ -0,0 +1,67 @@ +/** + * Fail-soft parser for the compact `call_logs.resilience_actions` JSON (migration 191). + * Kept out of callLogs.ts so the call-log module stays under the file-size cap. + */ +const RESILIENCE_ACTION_KEYS = new Set([ + "rotations", + "park_ms", + "parked", + "replayed", + "stored_429", + "empty_retries", + "continued", + "buffered", + "resumed", + "dropped_notes", +]); + +function isValidBufferedValue(value: unknown): value is "retry" | "pass" { + return value === "retry" || value === "pass"; +} + +function cleanNumericValue(value: unknown): number | null { + if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return null; + return Math.floor(value); +} + +function parseResiliencePayload(raw: unknown): Record | null { + if (typeof raw !== "string" || raw.length === 0) return null; + let parsed: unknown; + try { + parsed = JSON.parse(raw); + } catch { + return null; + } + if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null; + return parsed as Record; +} + +function cleanResilienceRecord(record: Record): Record | null { + const cleaned: Record = {}; + for (const [key, value] of Object.entries(record)) { + if (!RESILIENCE_ACTION_KEYS.has(key)) continue; + if (key === "buffered") { + if (isValidBufferedValue(value)) cleaned[key] = value; + continue; + } + const numeric = cleanNumericValue(value); + if (numeric !== null) { + cleaned[key] = numeric; + continue; + } + if (typeof value === "boolean") { + cleaned[key] = value; + } + } + return Object.keys(cleaned).length > 0 ? cleaned : null; +} + +/** + * Parse the compact resilience-actions JSON (fail-soft: corrupt or unknown + * input yields null, never throws into the read path). + */ +export function parseResilienceActions(raw: unknown): Record | null { + const record = parseResiliencePayload(raw); + if (record === null) return null; + return cleanResilienceRecord(record); +} diff --git a/src/shared/components/RequestLoggerDetail.tsx b/src/shared/components/RequestLoggerDetail.tsx index 4582dc84ce7..4074974087d 100644 --- a/src/shared/components/RequestLoggerDetail.tsx +++ b/src/shared/components/RequestLoggerDetail.tsx @@ -24,6 +24,7 @@ import { buildPipelinePayloadSections, isBodySizeLimitOmission, } from "@/shared/components/RequestLoggerDetail.sections"; +import { getResilienceBadges } from "@/shared/components/requestLoggerResilience"; // ─── Copy-all composition ──────────────────────────────────────────────────── // Compose every visible payload section + stream chunk into a single block so @@ -546,6 +547,11 @@ export default function RequestLoggerDetail({ cacheSource === "semantic" ? "bg-emerald-500/20 text-emerald-700 dark:text-emerald-300 border-emerald-500/30" : "bg-sky-500/20 text-sky-700 dark:text-sky-300 border-sky-500/30"; + // resilience badges (flag alone decides, whatever the status). + const resilienceBadges = getResilienceBadges( + log.resilienceActions || detail?.resilienceActions || null, + (key, values) => t(key as never, values as never) + ); const accountLabel = maskAccount(detail?.account || log.account, emailsVisible); const codexAccountRotation = getCodexAccountRotation(detail); return ( @@ -839,6 +845,11 @@ export default function RequestLoggerDetail({ > {cacheSourceLabel} + {resilienceBadges.map((badge) => ( + + {badge.label} + + ))} — ) : ( - - {isSemanticCache ? t("semantic") : t("upstream")} - + <> + + {isSemanticCache ? t("semantic") : t("upstream")} + + {getResilienceBadges(log.resilienceActions, (key, values) => + t(`detail.${key}`, values) + ).map((badge) => ( + // Unstyled span: inherits the cache-source + // badge line; the title carries the detail. + + [{badge.label}] + + ))} + )} )} diff --git a/src/shared/components/requestLoggerResilience.ts b/src/shared/components/requestLoggerResilience.ts new file mode 100644 index 00000000000..e62e37a55df --- /dev/null +++ b/src/shared/components/requestLoggerResilience.ts @@ -0,0 +1,57 @@ +/** + * resilience resilience badges: read-only labels for what the gateway did to save + * the request. The stored-error badge is driven by the flag alone, whatever + * the persisted status (a stored 429 recopied into a 200 SSE envelope still + * reads as served-from-stored-error). + */ + +export type ResilienceBadge = { key: string; label: string; title: string }; + +export function getResilienceBadges( + resilienceActions: unknown, + format: (key: string, values?: Record) => string +): ResilienceBadge[] { + if (resilienceActions === null || typeof resilienceActions !== "object") return []; + const actions = resilienceActions as Record; + const badges: ResilienceBadge[] = []; + if (actions.stored_429 === true) { + badges.push({ + key: "stored-error", + label: format("resilienceStoredError"), + title: format("resilienceRecovered"), + }); + } + if (actions.parked === true) { + const ms = typeof actions.park_ms === "number" ? actions.park_ms : 0; + badges.push({ + key: "parked", + label: format("resilienceParked", { ms }), + title: format("resilienceRecovered"), + }); + } + if (typeof actions.rotations === "number" && actions.rotations > 0) { + badges.push({ + key: "rotations", + label: format("resilienceRotations", { count: actions.rotations }), + title: format("resilienceRecovered"), + }); + } + if (typeof actions.empty_retries === "number" && actions.empty_retries > 0) { + badges.push({ + key: "retried", + label: format("resilienceRetried"), + title: format("resilienceRecovered"), + }); + } + if ( + (typeof actions.continued === "number" && actions.continued > 0) || + actions.resumed === true + ) { + badges.push({ + key: "continued", + label: format("resilienceContinued"), + title: format("resilienceRecovered"), + }); + } + return badges; +} diff --git a/src/sse/handlers/chat.ts b/src/sse/handlers/chat.ts index 108d7355c0a..d9ad736835b 100644 --- a/src/sse/handlers/chat.ts +++ b/src/sse/handlers/chat.ts @@ -458,6 +458,7 @@ async function handleChatImplementation( body = { ...body }; delete body._omnirouteReasoningRule; delete body._omnirouteReasoningRouteTrace; + delete body._omniroutePreviousResponseResumed; } // Feature #6241: fold the canonical `effort` / `thinking` request params onto the @@ -754,6 +755,7 @@ async function handleChatImplementation( const stored = detailedLoggingEnabled ? resolvePreviousResponseState(previousResponseId, apiKeyInfo?.id ?? null) : null; + // resume flag for the attempt store in handleChatCore (best-effort). if (!stored) { // Matches OpenAI's own `previous_response_not_found` contract (missing // or expired server-side state) so a client with the matching retry @@ -775,6 +777,9 @@ async function handleChatImplementation( : []; body = { ...body, input: [...stored.input, ...stored.output, ...deltaInput] }; delete (body as { previous_response_id?: unknown }).previous_response_id; + // resume flag for the attempt store in handleChatCore (inherited downstream via body). + (body as { _omniroutePreviousResponseResumed?: boolean })._omniroutePreviousResponseResumed = + true; } const admissionRejection = await admissionContext.acquire(apiKeyInfo?.id, request, body); @@ -1373,6 +1378,9 @@ async function handleChatImplementation( reasoningRequestTags: requestRoutingTags.tags, managedLease, videoBridgeLog, + previousResponseResumed: + (body as { _omniroutePreviousResponseResumed?: unknown }) + ._omniroutePreviousResponseResumed === true || undefined, }, null, false @@ -1423,6 +1431,11 @@ async function handleSingleModelChat( managedLease?: ManagedLeaseDispatchContext | null; /** #12150 P1b: video-bridge log/Memory shadow — undefined on every non-video request. */ videoBridgeLog?: VideoBridgeLog; + /** + * rehydrated-continuation flag; noted under the attempt store in + * handleChatCore. Optional plumbing, no semantics. + */ + previousResponseResumed?: boolean; /** * Per-target abort signal from combo.ts's targetTimeoutRunner * (comboTargetTimeoutMs) — see the #7360 follow-up comment at the @@ -1502,6 +1515,7 @@ async function handleSingleModelChat( conversationId: runtimeOptions?.conversationId ?? null, managedLease: runtimeOptions.managedLease ?? null, videoBridgeLog: runtimeOptions.videoBridgeLog, + previousResponseResumed: runtimeOptions.previousResponseResumed, // #7360 follow-up — see the primary handleSingleModel closure above. modelAbortSignal: target?.modelAbortSignal ?? null, fallbackAttempts: target?.fallbackAttempts, @@ -2023,6 +2037,7 @@ async function handleSingleModelChat( reasoningTransportFallback: runtimeOptions.reasoningTransportFallback ?? "drop", managedLease: runtimeOptions.managedLease ?? null, videoBridgeLog: runtimeOptions.videoBridgeLog, + previousResponseResumed: runtimeOptions.previousResponseResumed, fallbackAttempts: runtimeOptions.fallbackAttempts, forcedConnectionId: hasForcedConnection ? forcedConnectionId : null, // #14116 }, diff --git a/src/sse/handlers/chatHelpers.ts b/src/sse/handlers/chatHelpers.ts index acfebb98fd9..e1c108c23e8 100644 --- a/src/sse/handlers/chatHelpers.ts +++ b/src/sse/handlers/chatHelpers.ts @@ -463,6 +463,9 @@ export async function executeChatWithBreaker({ videoBridgeLog = undefined, fallbackAttempts = undefined, forcedConnectionId = null, + // optional resume flag from a rehydrated previous_response_id — + // forwarded to handleChatCore, which notes it under the attempt store. + previousResponseResumed = undefined, }: ExecuteChatWithBreakerOptions): Promise { let tlsFingerprintUsed = false; const normalizedTrafficType: TrafficType = @@ -524,6 +527,7 @@ export async function executeChatWithBreaker({ reasoningTransportFallback, managedLease, videoBridgeLog, + previousResponseResumed, fallbackAttempts, forcedConnectionId, skipResourcePressureGuard: true, diff --git a/tests/unit/db/migration-191-resilience-actions.test.ts b/tests/unit/db/migration-191-resilience-actions.test.ts new file mode 100644 index 00000000000..1a82e0026f5 --- /dev/null +++ b/tests/unit/db/migration-191-resilience-actions.test.ts @@ -0,0 +1,68 @@ +// call_logs.resilience_actions is a nullable additive column. Fresh +// databases and upgrades from the prior schema both receive it, and a second +// run records the ledger row without re-adding the column. +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import Database from "better-sqlite3"; + +const repoMigrations = path.join( + path.dirname(fileURLToPath(import.meta.url)), + "../../../src/lib/db/migrations" +); +const migrationsDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-migration-191-")); +fs.copyFileSync( + path.join(repoMigrations, "191_call_logs_resilience_actions.sql"), + path.join(migrationsDir, "191_call_logs_resilience_actions.sql") +); +const originalMigrationsDir = process.env.OMNIROUTE_MIGRATIONS_DIR; +process.env.OMNIROUTE_MIGRATIONS_DIR = migrationsDir; + +const { runMigrations } = await import("../../../src/lib/db/migrationRunner.ts"); + +test.after(() => { + fs.rmSync(migrationsDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + if (originalMigrationsDir === undefined) delete process.env.OMNIROUTE_MIGRATIONS_DIR; + else process.env.OMNIROUTE_MIGRATIONS_DIR = originalMigrationsDir; +}); + +function columns(db: Database.Database): string[] { + return (db.prepare("PRAGMA table_info(call_logs)").all() as Array<{ name: string }>).map( + (column) => column.name + ); +} + +function openDb(withColumn: boolean): Database.Database { + const db = new Database(":memory:"); + db.exec( + `CREATE TABLE call_logs (id TEXT PRIMARY KEY, status INTEGER${ + withColumn ? ", resilience_actions TEXT" : "" + });` + ); + return db; +} + +test("an older call_logs gains resilience_actions and a second run is a no-op", () => { + const db = openDb(false); + try { + runMigrations(db); + assert.ok(columns(db).includes("resilience_actions")); + runMigrations(db); + assert.ok(columns(db).includes("resilience_actions")); + } finally { + db.close(); + } +}); + +test("a fresh call_logs keeps its column and stays idempotent", () => { + const db = openDb(true); + try { + runMigrations(db); + assert.ok(columns(db).includes("resilience_actions")); + } finally { + db.close(); + } +}); diff --git a/tests/unit/resilience-actions-badges.test.ts b/tests/unit/resilience-actions-badges.test.ts new file mode 100644 index 00000000000..fa2c7b7d4e0 --- /dev/null +++ b/tests/unit/resilience-actions-badges.test.ts @@ -0,0 +1,41 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +// resilience badge helper: closed badge set, flag-alone semantics, OR rule. + +const { getResilienceBadges } = + await import("../../src/shared/components/requestLoggerResilience.ts"); + +const format = (key: string, values?: Record): string => + values && Object.keys(values).length > 0 ? `${key}:${JSON.stringify(values)}` : key; + +test("1. null and non-object input yield no badges", () => { + assert.deepEqual(getResilienceBadges(null, format), []); + assert.deepEqual(getResilienceBadges("x", format), []); + assert.deepEqual(getResilienceBadges({}, format), []); +}); + +test("2. stored-error badge fires on the flag alone (any status)", () => { + const badges = getResilienceBadges({ stored_429: true }, format); + assert.equal(badges.length, 1); + assert.equal(badges[0]?.key, "stored-error"); + assert.equal(badges[0]?.label, "resilienceStoredError"); +}); + +test("3. parked badge carries the wait duration", () => { + const badges = getResilienceBadges({ parked: true, park_ms: 40000 }, format); + assert.equal(badges[0]?.key, "parked"); + assert.ok(badges[0]?.label.includes("40000")); +}); + +test("4. rotations badge needs a positive count; continued uses the OR rule", () => { + assert.deepEqual(getResilienceBadges({ rotations: 0 }, format), []); + assert.equal(getResilienceBadges({ rotations: 2 }, format)[0]?.key, "rotations"); + assert.equal(getResilienceBadges({ continued: 1 }, format)[0]?.key, "continued"); + assert.equal(getResilienceBadges({ resumed: true }, format)[0]?.key, "continued"); + assert.deepEqual(getResilienceBadges({ continued: 0 }, format), []); +}); + +test("5. unknown keys never produce badges", () => { + assert.deepEqual(getResilienceBadges({ frobnicate: true }, format), []); +}); diff --git a/tests/unit/resilience-actions-context.test.ts b/tests/unit/resilience-actions-context.test.ts new file mode 100644 index 00000000000..e969c993a13 --- /dev/null +++ b/tests/unit/resilience-actions-context.test.ts @@ -0,0 +1,126 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +// resilience resilience-actions context: implicit ALS accumulator, one store per +// attempt. Fakes only — never a real fetch, never a real database. + +const ctx = await import("../../src/lib/usage/resilienceActionsContext.ts"); + +test("1. round-trip: note inside a context then read returns the summary", () => { + const snapshot = ctx.runWithResilienceActionsContext(() => { + ctx.noteResilienceAction({ parked: true, parkMs: 40000, replayed: true }); + ctx.noteResilienceAction({ rotations: 2 }); + return ctx.readResilienceActions(); + }); + assert.deepEqual(snapshot, { + rotations: 2, + park_ms: 40000, + parked: true, + replayed: true, + }); +}); + +test("2. outside a context: note is a no-op, read returns null, never throws", () => { + ctx.noteResilienceAction({ parked: true }); + assert.equal(ctx.readResilienceActions(), null); + assert.equal(ctx.serializeResilienceActions(), null); +}); + +test("3. read is non-destructive: a second read returns the same summary", () => { + const [first, second] = ctx.runWithResilienceActionsContext(() => { + ctx.noteResilienceAction({ stored429: true }); + return [ctx.readResilienceActions(), ctx.readResilienceActions()]; + }); + assert.deepEqual(first, { stored_429: true }); + assert.deepEqual(second, { stored_429: true }); +}); + +test("3b. reset is explicit: after resetResilienceActions the store reads null", () => { + const [before, after] = ctx.runWithResilienceActionsContext(() => { + ctx.noteResilienceAction({ stored429: true }); + const b = ctx.readResilienceActions(); + ctx.resetResilienceActions(); + return [b, ctx.readResilienceActions()]; + }); + assert.deepEqual(before, { stored_429: true }); + assert.equal(after, null); +}); + +test("3c. double persistence keeps the summary (sink resets only on success)", () => { + // note: two serialize() calls (two INSERT attempts of the same row) both + // carry the summary; only an explicit reset clears it. + const [first, second, third] = ctx.runWithResilienceActionsContext(() => { + ctx.noteResilienceAction({ parked: true, parkMs: 40000 }); + const a = ctx.serializeResilienceActions(); + const b = ctx.serializeResilienceActions(); + ctx.resetResilienceActions(); + return [a, b, ctx.serializeResilienceActions()]; + }); + const expected = JSON.stringify({ park_ms: 40000, parked: true }); + assert.equal(first, expected); + assert.equal(second, expected); + assert.equal(third, null); +}); + +test("4. combo isolation by mechanism: two contexts never share notes", () => { + const first = ctx.runWithResilienceActionsContext(() => { + ctx.noteResilienceAction({ rotations: 3, emptyRetries: 1 }); + return ctx.readResilienceActions(); + }); + const second = ctx.runWithResilienceActionsContext(() => { + ctx.noteResilienceAction({ resumed: true }); + return ctx.readResilienceActions(); + }); + assert.deepEqual(first, { rotations: 3, empty_retries: 1 }); + assert.deepEqual(second, { resumed: true }); +}); + +test("5. monotone accumulation: counters add, flags OR, buffered last-wins", () => { + const snapshot = ctx.runWithResilienceActionsContext(() => { + ctx.noteResilienceAction({ rotations: 1, buffered: "retry" }); + ctx.noteResilienceAction({ rotations: 2, buffered: "pass", continued: 1 }); + ctx.noteResilienceAction({ continued: 2 }); + return ctx.readResilienceActions(); + }); + assert.deepEqual(snapshot, { rotations: 3, continued: 3, buffered: "pass" }); +}); + +test("6. invalid values rejected: negative/finite guards, closed buffered enum", () => { + const snapshot = ctx.runWithResilienceActionsContext(() => { + ctx.noteResilienceAction({ rotations: -5, parkMs: Number.NaN }); + ctx.noteResilienceAction({ buffered: "bogus" } as never); + ctx.noteResilienceAction({ emptyRetries: 0 }); + return ctx.readResilienceActions(); + }); + assert.equal(snapshot, null); +}); + +test("7. empty store reads null (absent = no action, never {})", () => { + const snapshot = ctx.runWithResilienceActionsContext(() => ctx.readResilienceActions()); + assert.equal(snapshot, null); + const serialized = ctx.runWithResilienceActionsContext(() => ctx.serializeResilienceActions()); + assert.equal(serialized, null); +}); + +test("8. serialize emits compact JSON with snake_case keys", () => { + const json = ctx.runWithResilienceActionsContext(() => { + ctx.noteResilienceAction({ parked: true, parkMs: 40000, resumed: true, stored429: true }); + return ctx.serializeResilienceActions(); + }); + assert.equal( + json, + JSON.stringify({ park_ms: 40000, parked: true, stored_429: true, resumed: true }) + ); +}); + +test("9. note cap: beyond RESILIENCE_NOTE_CAP notes are dropped, drops exposed", () => { + assert.ok(ctx.RESILIENCE_NOTE_CAP >= 100); + ctx.runWithResilienceActionsContext(() => { + for (let i = 0; i < ctx.RESILIENCE_NOTE_CAP + 5; i++) { + ctx.noteResilienceAction({ rotations: 1 }); + } + const snapshot = ctx.readResilienceActions(); + assert.equal(snapshot?.rotations, ctx.RESILIENCE_NOTE_CAP); + assert.equal(snapshot?.dropped_notes, 5); + }); +}); diff --git a/tests/unit/resilience-actions-notes.test.ts b/tests/unit/resilience-actions-notes.test.ts new file mode 100644 index 00000000000..89be7a353c2 --- /dev/null +++ b/tests/unit/resilience-actions-notes.test.ts @@ -0,0 +1,174 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +// resilience per-action notes: rotations counter, streaming stored_429, combo +// isolation, continued outcomes, empty-retry/buffered invariants. Fakes only. + +const ctx = await import("../../src/lib/usage/resilienceActionsContext.ts"); + +test("1. rotations: first served dispatch is 0, later changes add up", () => { + // Mirrors the executor's noteServedAccount rule: +1 only on an effective + // change of served identity (abort/shared-egress arms never call it). + const seen: string[] = ["fp-a", "fp-a", "fp-b", "fp-b", "fp-c"]; + const snapshot = ctx.runWithResilienceActionsContext(() => { + let last: string | null = null; + for (const fp of seen) { + if (last !== null && last !== fp) ctx.noteResilienceAction({ rotations: 1 }); + last = fp; + } + return ctx.readResilienceActions(); + }); + assert.deepEqual(snapshot, { rotations: 2 }); +}); + +test("2. stored_429 + replayed matrix: replayed=false pairs with stored_429", () => { + const fallbackCase = ctx.runWithResilienceActionsContext(() => { + ctx.noteResilienceAction({ parked: true, parkMs: 120000 }); + ctx.noteResilienceAction({ replayed: false, stored429: true }); + return ctx.readResilienceActions(); + }); + assert.deepEqual(fallbackCase, { + park_ms: 120000, + parked: true, + replayed: false, + stored_429: true, + }); + const replayCase = ctx.runWithResilienceActionsContext(() => { + ctx.noteResilienceAction({ parked: true, parkMs: 5000 }); + ctx.noteResilienceAction({ replayed: true }); + return ctx.readResilienceActions(); + }); + assert.deepEqual(replayCase, { park_ms: 5000, parked: true, replayed: true }); +}); + +test("3. streaming stored_429: flag recorded even when the served status is 200", () => { + // The streaming park path serves a 200 SSE envelope whose body carries the + // stored 429; the flag (not the status) decides at read time. + const snapshot = ctx.runWithResilienceActionsContext(() => { + ctx.noteResilienceAction({ parked: true, parkMs: 3000 }); + ctx.noteResilienceAction({ stored429: true, replayed: false }); + return ctx.readResilienceActions(); + }); + assert.equal(snapshot?.stored_429, true); + assert.equal(snapshot?.replayed, false); +}); + +test("4. combo isolation by mechanism: sequential attempt stores stay apart", () => { + const attempt1 = ctx.runWithResilienceActionsContext(() => { + ctx.noteResilienceAction({ rotations: 2, emptyRetries: 1 }); + return ctx.readResilienceActions(); + }); + const attempt2 = ctx.runWithResilienceActionsContext(() => { + ctx.noteResilienceAction({ resumed: true }); + return ctx.readResilienceActions(); + }); + assert.deepEqual(attempt1, { rotations: 2, empty_retries: 1 }); + assert.deepEqual(attempt2, { resumed: true }); +}); + +test("5. continued: only stitched suffixes count (N suffixes = N)", () => { + const snapshot = ctx.runWithResilienceActionsContext(() => { + ctx.noteResilienceAction({ continued: 1 }); + ctx.noteResilienceAction({ continued: 1 }); + return ctx.readResilienceActions(); + }); + assert.deepEqual(snapshot, { continued: 2 }); +}); + +test("6. empty_retries invariant: 0 means absent, buffered stays independent", () => { + const snapshot = ctx.runWithResilienceActionsContext(() => { + ctx.noteResilienceAction({ emptyRetries: 0 }); + ctx.noteResilienceAction({ buffered: "pass" }); + return ctx.readResilienceActions(); + }); + assert.deepEqual(snapshot, { buffered: "pass" }); +}); + +test("7. resume threading real path: previousResponseResumed threaded into handleChatCoreInner lands under the store", async () => { + // Uses the REAL wrapper (withResilienceActionsContext), not a manual + // runWith: proves the flag survives the implicit store plumbing into the sink read. + const { withResilienceActionsContext } = + await import("../../open-sse/handlers/chatCore/resilienceAttemptContext.ts"); + const { notePreviousResponseResumed } = + await import("../../open-sse/handlers/chatCore/resumedResilienceNotes.ts"); + const snapshot = withResilienceActionsContext([{}], () => { + notePreviousResponseResumed(true); + return ctx.readResilienceActions(); + }); + assert.deepEqual(snapshot, { resumed: true }); + const absent = withResilienceActionsContext([{}], () => { + notePreviousResponseResumed(undefined); + return ctx.readResilienceActions(); + }); + assert.equal(absent, null); +}); + +test("8. stream unreadable replay: an unreadable replay body records replayed:false, never replayed:true", async () => { + // The recopy try/catch only fires when enqueue throws (consumer gone) or + // the body read throws. Force it with a fallback whose text() rejects: + // the client received pings only, so replayed:true would lie to stored-error metric. + const { runParkAndReplay } = await import("../../open-sse/executors/opencodeParkResume.ts"); + const snapshot = await ctx.runWithResilienceActionsContext(async () => { + const parked = await runParkAndReplay( + { + accounts: [], + execute: async () => { + throw new Error("must not dispatch"); + }, + markSuccess: () => {}, + sleep: async () => true, + }, + { + model: "m", + body: {}, + stream: true, + credentials: { apiKey: null }, + signal: null, + log: null, + } as never, + 0, + { + response: { + status: 200, + headers: new Headers({ "Content-Type": "text/event-stream" }), + text: async () => { + throw new Error("unreadable body"); + }, + }, + } as never, + null, + "cid " + ); + assert.ok(parked !== null); + const reader = parked?.response.body?.getReader(); + assert.ok(reader); + if (reader) { + for (;;) { + const { done } = await reader.read(); + if (done) break; + } + reader.releaseLock(); + } + return ctx.readResilienceActions(); + }); + // probe is null (no candidates) and fallback is not a 429: no stored flag, + // and the failed recopy must not have recorded replayed:true. + assert.notEqual(snapshot?.replayed, true); +}); + +test("9. combo+resume: a combo leg carries no resumed flag (simple line keeps it)", () => { + // Assumed limit (review-code-2 m, option B): previousResponseResumed is + // threaded on the simple-request path only; combo legs fan out without + // the flag, so each leg's line holds its own rotations/park while the + // Responses resume stays on the simple line (one line, its summary). + const leg = ctx.runWithResilienceActionsContext(() => { + ctx.noteResilienceAction({ rotations: 1 }); + return ctx.readResilienceActions(); + }); + const simple = ctx.runWithResilienceActionsContext(() => { + ctx.noteResilienceAction({ resumed: true }); + return ctx.readResilienceActions(); + }); + assert.deepEqual(leg, { rotations: 1 }); + assert.deepEqual(simple, { resumed: true }); +}); diff --git a/tests/unit/resilience-actions-sink.test.ts b/tests/unit/resilience-actions-sink.test.ts new file mode 100644 index 00000000000..755a23ef751 --- /dev/null +++ b/tests/unit/resilience-actions-sink.test.ts @@ -0,0 +1,48 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +// resilience sink: parseResilienceActions fail-soft allowlist (pure, no DB). + +const { parseResilienceActions } = await import("../../src/lib/usage/callLogs.ts"); + +test("1. valid compact JSON survives with closed keys", () => { + assert.deepEqual( + parseResilienceActions( + JSON.stringify({ rotations: 2, park_ms: 40000, parked: true, stored_429: true }) + ), + { rotations: 2, park_ms: 40000, parked: true, stored_429: true } + ); +}); + +test("2. corrupt JSON returns null, never throws", () => { + assert.equal(parseResilienceActions("{nope"), null); + assert.equal(parseResilienceActions(""), null); + assert.equal(parseResilienceActions(null), null); + assert.equal(parseResilienceActions(undefined), null); + assert.equal(parseResilienceActions(42), null); +}); + +test("3. unknown keys are dropped; empty-after-clean returns null", () => { + assert.equal(parseResilienceActions(JSON.stringify({ frobnicate: 1 })), null); + assert.deepEqual(parseResilienceActions(JSON.stringify({ rotations: 1, frobnicate: 1 })), { + rotations: 1, + }); +}); + +test("4. wrong types rejected: buffered closed enum, negatives dropped", () => { + assert.equal(parseResilienceActions(JSON.stringify({ buffered: "bogus" })), null); + assert.deepEqual(parseResilienceActions(JSON.stringify({ buffered: "retry" })), { + buffered: "retry", + }); + assert.equal(parseResilienceActions(JSON.stringify({ rotations: -1 })), null); + assert.deepEqual(parseResilienceActions(JSON.stringify({ rotations: 1.9, continued: 0 })), { + rotations: 1, + }); +}); + +test("5. stored_429 survives regardless of status (flag alone decides)", () => { + // The sink stores the flag verbatim; status semantics never change here. + assert.deepEqual(parseResilienceActions(JSON.stringify({ stored_429: true })), { + stored_429: true, + }); +});