feat(desktop/account-health): treat active/expiring_soon probes as live and dedupe in-flight checks
Why: avoid showing accounts as not-live during stale-window probes; keep publish gating consistent with runtime view. - collapse FIRST_PROBE window so initial probe runs immediately - skip due-probe selection when a probe is already queued/in-flight - mirror auth-state→health projection on both client report and tenant ingest paths Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,13 +22,11 @@ import {
|
||||
import type { PublishAccountIdentity } from "./account-binder";
|
||||
|
||||
const PROBE_TICK_MS = 10_000;
|
||||
const FIRST_PROBE_MIN_MS = 5_000;
|
||||
const FIRST_PROBE_MAX_MS = 15_000;
|
||||
const STALE_AFTER_MS = 45 * 60_000;
|
||||
const REGULAR_PROBE_MIN_MS = 30 * 60_000;
|
||||
const REGULAR_PROBE_MAX_MS = 60 * 60_000;
|
||||
const REGULAR_PROBE_MAX_MS = STALE_AFTER_MS;
|
||||
const CONFIRM_BACKOFF_STEPS_MS = [30_000, 120_000] as const;
|
||||
const NETWORK_RETRY_MS = 2 * 60_000;
|
||||
const STALE_AFTER_MS = 45 * 60_000;
|
||||
const PREFLIGHT_MAX_AGE_MS = 15 * 60_000;
|
||||
const MAX_CONCURRENT_ACCOUNT_PROBES = 3;
|
||||
const MANUAL_PROBE_CACHE_TTL_MS = 5 * 60_000;
|
||||
@@ -100,7 +98,7 @@ function resolvePartition(accountId: string): string {
|
||||
}
|
||||
|
||||
function nextInitialProbeAt(now = Date.now()): number {
|
||||
return now + randomInt(FIRST_PROBE_MIN_MS, FIRST_PROBE_MAX_MS);
|
||||
return now;
|
||||
}
|
||||
|
||||
function nextRegularProbeAt(now = Date.now()): number {
|
||||
@@ -113,9 +111,9 @@ function normalizePersisted(
|
||||
return {
|
||||
accountId: record.accountId,
|
||||
platform: record.platform,
|
||||
authState: "unknown",
|
||||
authState: record.lastVerifiedAt ? "active" : "unknown",
|
||||
probeState: "idle",
|
||||
authReason: null,
|
||||
authReason: record.lastVerifiedAt ? "probe_success" : null,
|
||||
lastVerifiedAt: record.lastVerifiedAt ?? null,
|
||||
lastProbeAt: null,
|
||||
nextProbeAt: nextInitialProbeAt(),
|
||||
@@ -176,15 +174,7 @@ function ensureProbeTimer(): void {
|
||||
}, PROBE_TICK_MS);
|
||||
}
|
||||
|
||||
function coerceDerivedAuthState(record: AccountHealthRecord, now = Date.now()): AccountAuthState {
|
||||
if (
|
||||
record.authState === "active"
|
||||
&& record.lastVerifiedAt
|
||||
&& now - record.lastVerifiedAt >= STALE_AFTER_MS
|
||||
) {
|
||||
return "expiring_soon";
|
||||
}
|
||||
|
||||
function coerceDerivedAuthState(record: AccountHealthRecord): AccountAuthState {
|
||||
return record.authState;
|
||||
}
|
||||
|
||||
@@ -442,6 +432,18 @@ function markProbeQueued(record: AccountHealthRecord): AccountHealthSnapshot {
|
||||
return commitRecord(record, previousSignature);
|
||||
}
|
||||
|
||||
function isProbeInFlight(record: AccountHealthRecord): boolean {
|
||||
return record.probeState === "queued" || record.probeState === "probing";
|
||||
}
|
||||
|
||||
function isStaleActiveRecord(record: AccountHealthRecord, now = Date.now()): boolean {
|
||||
return (
|
||||
record.authState === "active"
|
||||
&& Boolean(record.lastVerifiedAt)
|
||||
&& now - (record.lastVerifiedAt ?? 0) >= STALE_AFTER_MS
|
||||
);
|
||||
}
|
||||
|
||||
function maxAttemptsForTrigger(trigger: ProbeTrigger): number {
|
||||
return trigger === "preflight" ? 1 : ACCOUNT_PROBE_RETRY_BACKOFF_MS.length + 1;
|
||||
}
|
||||
@@ -569,7 +571,10 @@ async function runDueProbes(): Promise<void> {
|
||||
const now = Date.now();
|
||||
const dueAccounts = [...trackedAccounts.values()].filter((account) => {
|
||||
const record = records.get(account.id);
|
||||
return Boolean(record?.nextProbeAt && record.nextProbeAt <= now);
|
||||
if (!record || isProbeInFlight(record)) {
|
||||
return false;
|
||||
}
|
||||
return Boolean((record.nextProbeAt && record.nextProbeAt <= now) || isStaleActiveRecord(record, now));
|
||||
});
|
||||
|
||||
for (const account of dueAccounts) {
|
||||
@@ -653,6 +658,11 @@ function syncTrackedAccountsInternal(
|
||||
if (removed) {
|
||||
emitRuntimeInvalidated("account-health");
|
||||
}
|
||||
if (pruneMissing) {
|
||||
queueMicrotask(() => {
|
||||
void runDueProbes();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function forgetTrackedAccountHealth(
|
||||
|
||||
Reference in New Issue
Block a user