Commit Graph
681 Commits
Author SHA1 Message Date
Jason Ross a069188b65 Merge pull request #366 from JMR-dev/fix-354-idleservice-fgs
fix(push): stop IdleService dataSync FGS crash-loop on exhausted 24h cap (#354)
2026-07-05 19:36:27 -05:00
JMR-devandClaude Opus 4.8 52503c000c fix(push): stop IdleService dataSync FGS crash-loop on exhausted 24h cap
Root cause: after #302's runtime-cap fallBackToPeriodicSync() stops the
dataSync foreground service, IdleService was restarted (START_STICKY
null-intent redelivery + explicit startForegroundService) and onStartCommand
unconditionally called startForeground(DATA_SYNC) while the rolling-24h budget
was still exhausted. The platform rejected the start with
ForegroundServiceStartNotAllowedException; it was uncaught, the process
crashed, and START_STICKY restarted straight back into the same rejection -- a
crash loop until the 24h window freed budget (#354).

Fix (IdleService.kt):
- onStartCommand now returns START_NOT_STICKY. Push is app-managed
  (LibreMailApplication.ensurePushStarted deterministically restarts it), so the
  sticky null-intent auto-restart was redundant and fired exactly when a dataSync
  FGS start is illegal.
- Guard the foreground start via a new JVM-testable IdleForegroundStarter seam:
  a ForegroundServiceStartNotAllowedException (caught via its IllegalStateException
  supertype, so no minSdk-29 class load) degrades like the cap handler --
  schedulePeriodicSync(), keep the degraded POLLING notification, stopSelf()
  promptly (avoids the "did not call startForeground in time" ANR) -- instead of
  propagating.
- Record the cap event (elapsedRealtime); while still inside the cap window,
  onStartCommand skips the now-guaranteed-illegal foreground start entirely.
- onTimeout stop path kept fast so ForegroundServiceDidNotStopInTimeException
  stays mitigated.

PII-free AppLog.w/i on the degrade paths.

Tests:
- Unit (IdleForegroundStarterTest): onStartCommand returns START_NOT_STICKY; a
  rejected start is caught and routed to degrade without propagating; the cap
  window skips the attempt; a non-ISE propagates.
- Instrumented (IdleServiceForegroundStartInstrumentedTest): the degrade path on
  a real Context -- rejection caught, periodic-sync fallback scheduled, degraded
  "instant delivery paused" notification built, watching skipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 19:17:19 -05:00
Jason Ross 6cb179bd8b Merge pull request #365 from JMR-dev/feat-358-reader-perf-logging
feat(reporting): PII-free latency breadcrumbs on the message-open path (#358)
2026-07-05 18:54:32 -05:00
JMR-devandClaude Opus 4.8 35b869944e feat(reporting): PII-free latency breadcrumbs on the message-open path (#358)
Adds AppLog breadcrumbs to the message-open path so a debug report can show
where the reader's spinner time goes:

- ImapClient.withStore: per-op connect vs. work timing plus a live
  connect-per-op connection gauge (issue #125's provider-ceiling context).
- fetchBodyMarkingSeen: select/body/flag phase timings plus PII-free size
  counts (RFC822 size, body chars, attachment count).
- MailRepositoryImpl.openMessage: end-to-end open latency plus the
  cached-vs-fetched branch, keyed by accountLogRef and logSafeFolderLabel.
- ReaderViewModel: spinner-to-ready latency, split success vs. failure.

All breadcrumbs are PII-free: accounts are logged via the existing
accountLogRef hash, folders via the existing logSafeFolderLabel allowlist,
and everything else is sizes/durations/booleans only.

Fixes the 4 unit-test classes that exercise this code without mocking
android.util.Log (a throwing stub under plain JVM tests): mockkStatic(Log)
is now installed in MailRepositoryImplCoverageTest, ImapClientBackfillTest,
ImapFolderOpenLatencyTest, and ReaderViewModelActionsTest, following the
existing MailBackfillerTest/ImapClientTest conventions. detekt.yml gains two
more ForbiddenImport excludes for the newly Log-importing test files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:46:48 -05:00
Jason Ross f93e2dc2f0 Merge pull request #353 from JMR-dev/ci-extract-traffic-control
ci: extract traffic-control into its own workflow file
2026-07-05 16:17:10 -05:00
JMR-devandClaude Opus 4.8 939906986b ci: extract traffic-control into its own workflow file
Move the traffic-control (runner-priority orchestration) job verbatim out of
.github/workflows/ci.yml into a new standalone workflow,
.github/workflows/traffic-control.yml, so the heavy CI jobs no longer depend
on it. The job's YAML (name, runs-on, timeout-minutes, permissions, env,
steps) and its documentation comment move unchanged; the decision core
.github/scripts/traffic_control.py is untouched and still unit-tested by the
traffic-control-tests job in ci.yml.

In ci.yml: removed the traffic-control job, dropped needs: traffic-control
from the five heavy jobs (static-analysis, debug-build, unit-tests, e2e,
e2e-preview) and from traffic-control-tests (its only needs, which would
otherwise dangle at a now-deleted job), and updated the now-stale header and
ci-passed comments to point at the extracted workflow.

The new workflow will be disabled pending a rebuild as a published GitHub
Action.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 14:48:03 -05:00
Jason Ross 6118b6ded0 Merge pull request #285 from JMR-dev/perf-187-covering-index
perf(db): covering index for unified-inbox summary scans
2026-07-05 14:31:22 -05:00
github-actions[bot] 1cd0d66fa4 Merge main into perf-187-covering-index 2026-07-05 18:48:15 +00:00
Jason Ross c3933a3612 Merge pull request #352 from JMR-dev/ci-351-pat-triggering
ci: trigger CI with the PAT so dispatches don't need manual approval (#351)
2026-07-05 13:47:39 -05:00
JMR-devandClaude Opus 4.8 2fcee291ce ci: trigger CI with the PAT so dispatches don't need manual approval (#351)
#350 made ci-trigger.yml dispatch ci.yml with the built-in GITHUB_TOKEN, on the
claim that a workflow_dispatch is anti-recursion-exempt so no PAT is needed. In
practice a GITHUB_TOKEN-triggered run is held in `action_required` awaiting manual
approval and never runs un-attended, so auto-updated PRs' CI never ran (stalled
#285). The original #349 design was right: dispatch with a PAT so the run executes
as the authorized owner with no approval gate.

- ci-trigger.yml: the trigger step's GH_TOKEN is now
  `${{ secrets.AUTOUPDATE_TOKEN || github.token }}` (was `${{ github.token }}`).
  AUTOUPDATE_TOKEN (the PAT) is REQUIRED for the scheduler; the `|| github.token`
  fallback stays fail-open but only starts CI if repo settings don't gate
  GITHUB_TOKEN-triggered runs.
- autoupdate.yml: branch update stays on GITHUB_TOKEN (must NOT retrigger CI --
  that would re-introduce the cascade). Clarified that AUTOUPDATE_TOKEN is still
  required by the repo (by ci-trigger.yml) so the secret isn't deleted.
- Corrected the now-wrong "no PAT needed / workflow_dispatch anti-recursion-exempt"
  comments in ci-trigger.yml and the traffic_control.py docstrings.

updates = GITHUB_TOKEN, triggering = PAT.

Validation: all three workflow YAMLs parse clean; traffic-control unit tests still
pass (59 tests) -- the change is workflow-env only, script logic unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 13:28:10 -05:00
github-actions[bot] 33217c6cb2 Merge main into perf-187-covering-index 2026-07-05 18:10:50 +00:00
Jason Ross d8f6a66856 Merge pull request #317 from JMR-dev/fix-306-outlook-redundant-token
fix(auth): drop redundant second Outlook token request on sign-in
2026-07-05 13:10:19 -05:00
JMR-devandClaude Opus 4.8 46bc0e2258 Merge main into perf-187-covering-index
Resolve DatabaseModule conflict from #320: main replaced the explicit .addMigrations(...) chain with .addMigrations(*ALL_MIGRATIONS) plus an introspectable ALL_MIGRATIONS list guarded by databaseModuleRegistersEveryDeclaredMigration (registered == declared). Add MIGRATION_19_20 to ALL_MIGRATIONS so the unified-inbox covering-index migration (cache schema v19->v20) is both registered on the Room builder and satisfies that safety-net test. Schema 20.json, the v20 @Database version, and DatabaseEncryptionTest's schema-version assertion (20) are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 13:05:07 -05:00
github-actions[bot] 44d6d1edb6 Merge main into fix-306-outlook-redundant-token 2026-07-05 17:52:20 +00:00
Jason Ross 6f4275ee1e Merge pull request #350 from JMR-dev/ci-349-traffic-owns-triggering
ci: traffic-controller owns CI triggering (GITHUB_TOKEN updates, priority-ordered dispatch)
2026-07-05 12:51:48 -05:00
JMR-devandClaude Opus 4.8 05d06eb45b ci: traffic-controller owns CI triggering (GITHUB_TOKEN updates, priority-ordered dispatch)
End the merge cascade and give the traffic-controller ownership of CI *triggering*.

- autoupdate.yml updates PR branches with the built-in GITHUB_TOKEN instead of a PAT,
  so an update push no longer auto-retriggers CI (GitHub's anti-recursion rule) — the
  cascade (every merge re-runs every PR, cancel-in-progress thrashing them) is gone.
- New scheduler ci-trigger.yml -> traffic_control.py --mode trigger (re-)triggers CI
  for the highest-priority PR(s) whose head SHA has absent/stale checks, a few at a
  time (inflight cap), in the existing P0-P9 / broken-draft priority order — a
  poor-man's merge queue reusing the priority core. It runs after autoupdate finishes
  (workflow_run, race-free) plus a cron backstop plus manual dispatch.
- Triggering uses workflow_dispatch, which is EXEMPT from anti-recursion, so the
  built-in GITHUB_TOKEN (actions: write) starts the run — NO PAT / secret change needed.
- ci.yml gains a workflow_dispatch trigger (pr/head_sha/reason inputs) and a per-PR
  concurrency group unifying pull_request and dispatch runs; its on: pull_request path
  is kept so brand-new PRs, human pushes, and fork PRs always get CI (fail-open).

Pure select_triggers / classify_sha_runs decision core added to traffic_control.py with
24 new unit tests (priority order, oldest-first fairness, inflight cap, fork skip, P0
bypass+preempt, head-SHA needy classification, and a liveness/anti-starvation simulation).

Closes #349

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 01:27:40 -05:00
Jason Ross 4bf6fa5535 Merge main into fix-306-outlook-redundant-token 2026-07-05 00:55:24 -05:00
Jason Ross 833dfc030a Merge pull request #339 from JMR-dev/chore-claudemd-dod-logging
chore(docs): require app logging in the definition of done
2026-07-05 00:54:55 -05:00
Jason Ross 11e0a85475 Merge main into fix-306-outlook-redundant-token 2026-07-05 00:37:36 -05:00
Jason Ross d795639a32 Merge main into chore-claudemd-dod-logging 2026-07-05 00:37:35 -05:00
Jason Ross 94a7562256 Merge pull request #344 from JMR-dev/docs-343-workflows-readme
docs: plain-English README for the CI traffic-controller
2026-07-05 00:37:08 -05:00
Jason Ross 7d60c7ac60 Merge main into docs-343-workflows-readme 2026-07-05 00:05:37 -05:00
Jason Ross 88b8f6cb07 Merge main into fix-306-outlook-redundant-token 2026-07-05 00:05:35 -05:00
Jason Ross dd9f6bb8d9 Merge main into chore-claudemd-dod-logging 2026-07-05 00:05:34 -05:00
Jason Ross 348d7adc28 Merge pull request #348 from JMR-dev/feat-331-detekt-log-guard
feat(logging): detekt guard banning android.util.Log outside AppLog (#331)
2026-07-05 00:05:06 -05:00
Jason Ross 45df6c7709 Merge main into chore-claudemd-dod-logging 2026-07-04 23:16:54 -05:00
Jason Ross cefd92864c Merge main into fix-306-outlook-redundant-token 2026-07-04 23:16:53 -05:00
Jason Ross b3ec24d9f3 Merge main into docs-343-workflows-readme 2026-07-04 23:16:52 -05:00
Jason Ross 5d50b60f68 Merge main into feat-331-detekt-log-guard 2026-07-04 23:16:52 -05:00
Jason Ross 5047e3eb2a Merge pull request #340 from JMR-dev/feat-326-logging-authlock
feat(logging): auth/lock -> AppLog + breadcrumbs (#326)
2026-07-04 23:16:23 -05:00
Jason Ross 494649b7d8 Merge main into feat-331-detekt-log-guard 2026-07-04 23:05:20 -05:00
JMR-devandClaude Opus 4.8 faab0e3260 feat(logging): detekt guard banning android.util.Log outside AppLog (#331)
Add a detekt style>ForbiddenImport rule that forbids `import android.util.Log`
so all logging flows through org.libremail.reporting.AppLog, which mirrors each
line into the debug-report RingLogBuffer. A raw android.util.Log import writes to
Logcat only and never reaches a user-reviewed DebugReport (epic #324, strangler
final step).

Excludes the AppLog facade itself (the one sanctioned wrapper) and the unit tests
that mockkStatic(Log) to verify forwarding — AppLog forwards to Log, a throwing
stub under plain JVM unit tests, so those tests must mock it; they do not bypass
the facade.

Closes #331
Part of #324

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 23:02:13 -05:00
Jason Ross a9f0c220da Merge main into docs-343-workflows-readme 2026-07-04 22:59:33 -05:00
Jason Ross a44f568f9c Merge main into feat-326-logging-authlock 2026-07-04 22:59:32 -05:00
Jason Ross c65fb26ad0 Merge main into chore-claudemd-dod-logging 2026-07-04 22:59:31 -05:00
Jason Ross 17d40119ca Merge main into fix-306-outlook-redundant-token 2026-07-04 22:59:31 -05:00
Jason Ross d0ec1949a2 Merge pull request #347 from JMR-dev/ci-346-traffic-control-tests
ci: run traffic-controller unit tests as a gate job
2026-07-04 22:59:01 -05:00
Jason Ross b2c017b52d Merge main into fix-306-outlook-redundant-token 2026-07-04 22:38:21 -05:00
Jason Ross 02153b5287 Merge main into chore-claudemd-dod-logging 2026-07-04 22:38:20 -05:00
Jason Ross ef16b1ef2c Merge main into feat-326-logging-authlock 2026-07-04 22:38:19 -05:00
Jason Ross 76caaddd33 Merge main into docs-343-workflows-readme 2026-07-04 22:38:18 -05:00
JMR-devandClaude Opus 4.8 8b89219734 ci: run traffic-controller unit tests as a gate job
Adds a fast traffic-control-tests job (ubuntu, actions/checkout +
actions/setup-python, no emulator/Gradle) that runs the 37 pure-stdlib
unit tests for .github/scripts/traffic_control.py on every PR, and
wires it into ci-passed's needs so a regression blocks merge instead
of only being caught locally.

Closes #346

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 22:35:39 -05:00
Jason Ross dc21411aed Merge pull request #345 from JMR-dev/ci-342-traffic-control-python
ci: extract traffic-controller into a testable Python module (#342)
2026-07-04 22:33:37 -05:00
Jason Ross a13bc9eb07 Merge main into docs-343-workflows-readme 2026-07-04 22:29:35 -05:00
Jason Ross 37bdaae304 Merge main into feat-326-logging-authlock 2026-07-04 22:29:34 -05:00
Jason Ross 527e31fb5c Merge main into chore-claudemd-dod-logging 2026-07-04 22:29:33 -05:00
Jason Ross 28745827c2 Merge main into fix-306-outlook-redundant-token 2026-07-04 22:29:32 -05:00
Jason Ross 647490c1a8 Merge main into ci-342-traffic-control-python 2026-07-04 22:29:32 -05:00
Jason Ross 785c6aa6c6 Merge pull request #338 from JMR-dev/feat-328-logging-connsend
feat(logging): connectivity/send → AppLog + breadcrumbs, scrub email PII (#328)
2026-07-04 22:29:04 -05:00
JMR-devandClaude Opus 4.8 a34db54b97 fix(ci): let any strictly-higher PR reclaim a broken/draft run (#342)
Restore (and extend to drafts) the old bash's broken-reclaim behaviour that the
initial Python refactor had dropped. runs_to_cancel now cancels an OTHER PR's
active/queued runs when EITHER:
  (a) THIS PR is P0 and that PR is strictly-lower (reclaim every lower runner); OR
  (b) that PR is broken/draft (effective priority 10) and THIS PR is strictly-higher
      (effective priority < 10) — a wasted run any ready PR may reclaim.

P1-P9 still never bump a *normal* (non-broken/draft) lower run; a broken/draft PR
(P10) preempts nothing (nothing is strictly-lower than the bottom, and the
equal-or-higher invariant means a P10 never cancels another P10). Self / main-push /
equal-or-higher invariants unchanged.

Updates the module docstring + ci.yml comments (the "only P0 preempts" wording
becomes: P0 preempts everything strictly-lower; additionally, any strictly-higher PR
preempts a broken/draft run) and the job step/permission/needs comments. Adds unit
tests: P3 reclaims a broken P10 run and a draft P10 run; P3 does not bump a normal P5
run; a P10 self preempts nothing; plus an end-to-end P5-reclaims-draft-then-waits
scenario. 37 unit tests pass; ci.yml parses clean; --dry-run shows a P3 cancelling a
draft (and broken) run while still yielding to a higher P1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 22:19:01 -05:00