WIP: perf(yahoo): respect Yahoo/AOL IMAP limits & avoid the 1-hour auth lockout #472

Draft
JMR-dev wants to merge 5 commits from feat-362-yahoo-imap-limits into main
5 Commits
Author SHA1 Message Date
JMR-dev d18c99280e fix(yahoo): sync AccountDataMigrator DDL to AccountDatabase v3 (authError)
AccountDataMigrator hardcoded the v2 accounts DDL (schema/2.json), so
copyAccountTables created a table missing the authError column PR #472
added in v3 (schema/3.json). Room then rejected the pre-packaged
accounts table as an invalid schema, failing every
AccountDataMigratorTest instrumented test on every API level.

Add `authError` TEXT to CREATE_TABLE_SQL["accounts"] to match the
exported v3 schema exactly, and repoint the DDL-guard test
(migratorDdlMatchesExportedAccountDatabaseSchema) at 3.json so it
would have caught this drift.
2026-07-09 12:40:42 -05:00
JMR-dev 98d2a7c0db Merge remote-tracking branch 'origin/main' into feat-362-yahoo-imap-limits
# Conflicts:
#	app/src/test/kotlin/org/libremail/data/sync/MailBackfillerTest.kt
#	config/detekt/detekt.yml
2026-07-08 23:40:34 -05:00
JMR-dev 4d1f2ae9ca fix(yahoo): fail loud and error the account after 4 auth failures (#362)
Change the Yahoo/AOL proactive auth circuit-breaker's after-threshold
behaviour from a silent, self-clearing 30-min open-circuit window to a
fail-loud, permanent stop that surfaces to the user.

- AuthThrottleGate: past circuitOpenThreshold the circuit now LATCHES
  (blockedUntilMillis = Long.MAX_VALUE) instead of opening a self-clearing
  window. onAuthSuccess never clears a latch; only onAccountReadded does.
  The 1-4 spaced-retry ramp and all thresholds/timings are unchanged, and
  only genuine AuthenticationFailedExceptions still count.
- Persistent, user-visible error state: new nullable AccountEntity.authError
  (AccountDatabase migration v2 -> v3 + exported schema) + Account domain
  field + mappers + AccountDao.setAuthError (idempotent conditional write).
  markAccountErroredIfLatched bridges the in-memory latch to the row from
  MailSyncer/MailBackfiller (which hold the Account), keeping the DAO out of
  the mail layer. Both loops also skip a latched/errored account entirely
  (durable across restarts), and a re-add clears the latch + the row error.
- UI: a persistent red indicator + the message on the Settings account row
  (AccountReorderList) and the drawer switcher (FolderDrawer), plus a
  persistent banner atop the mailbox for the shown account (MailboxScreen +
  MailboxViewModel.accountAuthError). String: "Please remove and re-add this
  account with valid credentials".
- All AppLog breadcrumbs stay PII-free (hashed accountLogRef only).

Also merges origin/main, composing authGate with #469's GmailBandwidthTracker
in MailBackfiller/MailSyncer constructors and every test construction site.

Tests: gate latch (no self-clear / success can't clear / re-add clears),
markAccountErroredIfLatched, MailSyncer/MailBackfiller errored+latched skips,
repository reset-on-re-add, mapper round-trip, v2->v3 migration, DAO
set/clear, the mailbox banner + Settings-row Compose renders, and the
on-device gate latch. Full fast gate green (JDK 21).
2026-07-08 23:32:56 -05:00
JMR-dev b1489e35da Merge remote-tracking branch 'origin/main' into feat-362-yahoo-imap-limits
# Conflicts:
#	app/src/main/kotlin/org/libremail/data/sync/MailBackfiller.kt
#	app/src/test/kotlin/org/libremail/data/sync/MailBackfillerTest.kt
#	app/src/test/kotlin/org/libremail/data/sync/MailMaintenanceGateTest.kt
#	app/src/test/kotlin/org/libremail/data/sync/MailSyncConcurrencyTest.kt
2026-07-08 22:49:37 -05:00
JMR-dev b03e363888 perf(yahoo): respect Yahoo/AOL IMAP limits & avoid the 1-hour auth lockout
Yahoo/AOL trip an automated ~1-hour service lockout after too many rapid or
failed authentication attempts. An unguarded login repeater (notably the IDLE
reconnect loop, which starts at a 5s backoff) could fire several failed LOGINs
within the first minute and lock out a real user for an hour. Add a proactive,
Yahoo/AOL-scoped auth circuit-breaker that spaces out login attempts so we never
reach the lockout, building on the #360 reactive throttle framework rather than
reinventing it.

New (org.libremail.mail), all host-keyed so only Yahoo/AOL are gated:
- ProviderAuthPolicy: per-host AuthCadencePolicy (Yahoo/AOL enabled, everything
  else DISABLED). Also exposes the documented 5-connection and 10k-folder caps.
- AuthBackoff: pure schedule — exponential equal-jitter ramp (base 60s -> 30s
  floor after jitter, cap 15m) up to a 4-failure threshold, then a fixed 30m
  open-circuit window. Every wait stays well under the ~1h lockout.
- AuthThrottleGate (@Singleton): per-account state; onAuthFailure / onAuthSuccess
  / remainingAuthBlockMillis, PII-free logging.

Enforcement:
- ImapClient guards every real LOGIN (connect-per-op, reuse connect/reconnect,
  and the IDLE connection): skips the login (AuthBackoffException) while backing
  off, arms the gate on an AuthenticationFailedException, clears it on success.
  A transient (non-auth) connect error never arms the backoff.
- MailBackfiller skips an auth-blocked account exactly as it skips a reactively
  throttled one (#360), so BackfillPacer (#356) never spins a cooldown on it.

The 5-connection cap is already satisfied by connection reuse (#125/#357: ~1 warm
socket + 1 IDLE per account); the 10k folder truncation is respected for free by
backfill stopping when the server returns nothing older.

Tests: pure-schedule, policy-resolution, gate (virtual time, incl. composition
with #360), GreenMail enforcement (auth-fail arms / transient does not / blocked
skips), and an on-device instrumented gate test.

Closes #362
2026-07-08 19:39:13 -05:00