Commit Graph
810 Commits
Author SHA1 Message Date
Jason Ross b9863b16ca Merge pull request #450 from JMR-dev/ci-pin-gradle-dist-sha
ci(gradle): pin Gradle distribution against published SHA-256
2026-07-08 13:39:22 -05:00
mergify[bot] 677512760a Merge pull request #437 from JMR-dev/refactor-308-ui-nits
fix(ui): address below-cut UI/Compose review nits (#308)
2026-07-08 17:24:42 +00:00
mergify[bot] 6ce9274ff6 Merge pull request #446 from JMR-dev/ci-443-emulator-corrupt-zip-guard
ci(e2e): harden matrix emulator + system-image install against corrupt-zip (extend #389)
2026-07-08 17:24:38 +00:00
JMR-dev 7a0cc3145c ci(gradle): pin Gradle distribution against published SHA-256
The Gradle wrapper had no distributionSha256Sum, so the Gradle 9.6.0
distribution was the one unpinned download in CI (validateDistributionUrl
only checks the URL shape, not content). Pin it to Gradle's published
SHA-256 so the wrapper fails closed on any corrupt/tampered distribution,
matching the SHA-256 integrity pin cmdline-tools already gets (#389).
2026-07-08 12:05:00 -05:00
mergify[bot] 487c3b36a5 Merge pull request #438 from JMR-dev/refactor-313-data-core-nits
refactor(data): tighten data-core atomicity, chunking, and dead code (#313)
2026-07-08 16:42:27 +00:00
JMR-dev 5c00a8da70 ci(e2e): route matrix emulator + system-image install through the #389-hardened installer
The E2E (33) matrix leg deadlocked the merge queue for ~2h when
android-emulator-runner's un-guarded "Create AVD and generate snapshot" step
died with "Error on ZipFile unknown archive" installing a corrupt Android
Emulator SDK zip. #389 hardened the platform/build-tools install (SHA-verify ->
reject-corrupt -> purge -> re-download) but left the emulator + system-image
install to the action, un-guarded.

Pre-install "emulator" + "system-images;android-<api>;google_apis;x86_64"
through setup_android_sdk.py before the emulator-runner steps, so a corrupt zip
is self-healed here and the action then finds both packages already installed
and skips its fragile fetch. Runs on both AVD-cache hit and miss (the emulator
binary + image live under the SDK root, not the ~/.android AVD-snapshot cache,
so they must be present for even a cached AVD to boot). Not added to the
android-sdk-v1 cache (kept small); re-install is a fast sdkmanager no-op when
already present.

The e2e-preview (API 37) job already routes emulator + system image through the
hardened installer, so no change there. setup_android_sdk.py already handles
these package ids generically; add a unit assertion pinning the matrix's
google_apis/x86_64 id to the correct purge path.

Closes #443
2026-07-08 10:39:38 -05:00
JMR-dev 91f2f7105b Merge origin/main into refactor-308-ui-nits (resolve BatteryOptimizationStepTest import conflict with #174) 2026-07-08 10:35:12 -05:00
mergify[bot] 08be7e6541 Merge pull request #439 from JMR-dev/feat-174-battery-usage-help
feat(onboarding): Battery -> Unrestricted help animation (#174)
2026-07-08 15:19:10 +00:00
JMR-dev b038c3bdae feat(onboarding): add Battery -> Unrestricted help animation (#174)
Show a lightweight, dependency-free looping illustration of the
"tap Battery -> choose Unrestricted" path on BatteryOptimizationScreen,
before the user is sent to system settings (complements the #150 deep
link, which cannot guarantee the exact per-OEM screen).

- BatteryGuideAnimation: a stylized Compose illustration driven by
  rememberInfiniteTransition (no Lottie / AnimatedVectorDrawable, no new
  dependency). A highlight moves from a "Battery" row to an
  "Unrestricted" option while a tap dot pulses.
- Reduced motion: rememberReducedMotion() reads ANIMATOR_DURATION_SCALE;
  when animations are off it renders the same card at rest (no motion).
- TalkBack: the illustration exposes a single contentDescription
  mirroring the retained onboarding_battery_guidance text (additive, not
  a replacement). Screen made scrollable so the actions stay reachable.
- PII-free AppLog breadcrumbs: shown / opening-settings / skipped.
- Robolectric JVM tests (animated + static + reduced-motion decision)
  and the instrumented step test exercise the guide; the step test
  disables device animations so the static path renders on-device.
2026-07-08 08:34:00 -05:00
JMR-dev d0ed168fcb refactor(data): tighten data-core atomicity, chunking, and dead code (#313)
Addresses the below-cut data-core review nits from #313:

- SignatureRepository.delete: wrap delete + default-promotion in one SignatureDao
  @Transaction (deletePromotingDefault) so a crash between them can't leave an
  account with signatures but no default; log the promotion (PII-free).
- SignatureRepository.create: move the count-then-default check-then-act into a
  SignatureDao @Transaction (insertMakingFirstDefault) so two concurrent
  first-creates can't both become default.
- AccountSettingsRepository.update: route the read-modify-write through an
  AccountSettingsDao @Transaction (readModifyWrite) so concurrent per-field
  setters can't clobber each other.
- MailRepositoryImpl expunge/move/move-by-role: chunk the unbounded
  getRoutingByIds/deleteByIds IN(:ids) queries (500/chunk) like MailPruner,
  removing the latent SQLITE_MAX_VARIABLE_NUMBER crash.
- MessageDao.observeSummaries: remove the dead whole-table projection (superseded
  by Paging #124/#214); migrate test/debug-probe callers to getById or the paged
  query (which now guards the #51 CursorWindow regression).
- AccountDataMigrator: fix stale KDoc (schema is v2 with sortOrder, not v1).
- DatabaseEncryption.migrate: also sweep the stale -journal sidecar (journal_mode
  = DELETE), matching AccountDataMigrator's sweep.

Unit tests updated for the repository delegations; instrumented DAO tests cover
the new @Transaction behaviour; MailRepositoryImplTest covers the chunk split;
DatabaseEncryptionTest covers the -journal sweep.

Closes #313
2026-07-08 08:09:12 -05:00
JMR-dev 2c0ca30919 fix(ui): address below-cut UI/Compose review nits (#308)
Six of the seven LOW findings collected in #308; the seventh is
deliberately skipped (see below).

- SettingsViewModel: run the app-lock disable-path Keystore/DataStore
  reseal off the main dispatcher (withContext(Dispatchers.Default)),
  matching AppLockViewModel's threading policy - no Keystore crypto on
  Main.
- AppLockGateHost: clear the covered app content out of the semantics
  tree while locked so TalkBack can't traverse the occluded mailbox/
  compose nodes behind the opaque cover; content stays composed so its
  state still survives a re-lock.
- ReaderViewModel.toggleStar: reconcile the optimistic star on a failed
  persist - roll it back and surface a one-shot StarFailed event instead
  of leaving the star stuck in a state the store rejected.
- OnboardingViewModel: persist firstAddedAccountId in SavedStateHandle so
  a process kill mid-onboarding still finishes onto the first account's
  inbox rather than the unfiltered mailbox (preserves #30).
- RichTextEditor: memoize the formatting toolbar's parse + selection
  scans with remember(value) so the per-keystroke hot path isn't
  re-derived on every recomposition.
- AccountSetupViewModel.onOutlookResult: treat a normal OAuth cancel
  (null result) as a no-op instead of surfacing an error snackbar.

Skipped: MailboxViewModel per-keystroke search re-paging - the finding
is documented-intentional and only a "could". The local pager narrows
cached results instantly as you type while the expensive server search
is already debounced (400ms); debouncing the local pager would add lag
for no clear win, and correct scoping (query only, not account/folder)
adds risk to a hot, well-tested path.

Each behavioral change ships a JVM/Robolectric test; the toolbar
memoization (a pure refactor) adds a toolbarStateOf test. PII-free
AppLog breadcrumbs added on the new fallback/state-change paths.

Closes #308
2026-07-08 08:07:48 -05:00
mergify[bot] 6802b60c42 Merge pull request #433 from JMR-dev/ci-mergify-phase2-batching
ci(mergify): Phase 2 - enable batching (batch_size 5) (#410)
2026-07-08 13:01:17 +00:00
mergify[bot] 68e45df0ea Merge branch 'main' into ci-mergify-phase2-batching 2026-07-08 12:34:48 +00:00
mergify[bot] 4d724a52a8 Merge pull request #428 from JMR-dev/fix-319-uidplus-fallback
fix(mail): gracefully fall back when the IMAP server lacks UIDPLUS
2026-07-08 12:09:10 +00:00
JMR-dev f629091b18 ci(mergify): Phase 2 - enable batching (batch_size 5) (#410) 2026-07-08 07:07:35 -05:00
Jason Ross b5b789f6c7 Merge branch 'main' into fix-319-uidplus-fallback 2026-07-08 06:49:27 -05:00
mergify[bot] d9e0d6410c Merge pull request #429 from JMR-dev/perf-322-batched-persistbatch
perf(data): route MailBackfiller.persistBatch through batched updateHeaderContents
2026-07-08 06:04:46 +00:00
mergify[bot] 1d17f76b15 Merge branch 'main' into perf-322-batched-persistbatch 2026-07-08 05:43:43 +00:00
mergify[bot] d55a1c3fae Merge pull request #430 from JMR-dev/feat-390-imap-disabled-detection
feat(auth): detect "IMAP disabled" AUTHENTICATE failures and prompt the user to enable IMAP
2026-07-08 05:43:40 +00:00
mergify[bot] d6665fe8c3 Merge branch 'main' into feat-390-imap-disabled-detection 2026-07-08 05:23:50 +00:00
mergify[bot] 9095cf190f Merge branch 'main' into perf-322-batched-persistbatch 2026-07-08 05:11:32 +00:00
mergify[bot] 63c0d5f9f9 Merge pull request #427 from JMR-dev/fix-403-idleservice-credential-race
fix(push): persist credentials before IdleService watches a new account (#403)
2026-07-08 05:05:40 +00:00
JMR-dev 8f7926886c Merge origin/main into feat-390-imap-disabled-detection (resolve additive strings.xml conflict; keep both #390 imap_disabled_* and #426 outlook_imap_* strings) 2026-07-08 00:00:39 -05:00
JMR-dev 60f822a63c feat(auth): detect "IMAP disabled" AUTHENTICATE failures and prompt to enable IMAP
When account setup obtains a valid credential but the IMAP AUTHENTICATE step is
rejected because IMAP access is switched off for the mailbox, surface an
actionable "turn on IMAP" dialog (with the provider's enable-IMAP help link)
instead of the opaque generic auth error (#390).

- ImapAuthError.isImapDisabled classifies the failure on two signals: explicit
  provider "IMAP is disabled/not enabled" server text (e.g. Gmail's "not enabled
  for IMAP use"), and -- for the Outlook XOAUTH2 path -- a valid-token
  AUTHENTICATE rejection, which outlook.office365.com reports only as a generic
  "AUTHENTICATE failed". Ordinary wrong-password / expired-token / network errors
  are deliberately not matched, so they keep the generic error.
- imapDisabledPromptFor resolves a provider-aware prompt (brand via
  MailProvider.brandFor; Outlook + Gmail enable-IMAP help URLs, generic
  otherwise).
- Shared ImapDisabledDialog reused by the Outlook picker, the app-password form,
  and manual setup -- the three points where the auth failure surfaces. The
  reactive complement to the pre-auth Outlook notice (#411/#426).
- PII-free AppLog breadcrumbs at the classification/prompt points (accountLogRef
  only; never the email/host/token).

Tests: ImapAuthErrorTest (provider text + OAuth inference + a real GreenMail
wrong-password negative), ImapDisabledPromptTest (brand/URL resolution),
ImapDisabledDialogJvmTest (Robolectric), per-view-model + per-screen wiring
tests, and an instrumented AppPasswordSetupScreenTest case driving the failure
end to end.

Closes #390
2026-07-07 23:54:57 -05:00
JMR-dev 8632500cf6 perf(data): route MailBackfiller.persistBatch through batched updateHeaderContents
persistBatch refreshed each pre-existing backfilled header with a per-row
updateHeaderContent in its own implicit transaction — the same N-commits-per-page
anti-pattern #310 fixed in MailSyncer. Route the whole pre-existing subset through
the batched MessageDao.updateHeaderContents(List) @Transaction so a page costs one
commit instead of one fsync per message (amplified on the encrypted cache).

Semantics are unchanged: updateHeaderContents applies updateHeaderContent to each
row in list order, so the same rows get the same values (and the same casefold
columns); the isNotEmpty guard still skips an empty refresh batch; brand-new rows
stay insert-only. No schema change.

Adds a PII-free, counts-only AppLog breadcrumb at the persist point.

Tests:
- MailBackfillerTest: the refresh routes through the batched update and never the
  per-row one; a partial page refreshes only its pre-existing subset in one batched
  call; an all-new page skips the batch entirely (empty boundary); breadcrumb counts.
- MessageDaoTest (real Room): the batch writes byte-for-byte the same row as the
  per-row path; an empty batch is a no-op.

Closes #322
2026-07-07 23:42:58 -05:00
mergify[bot] 51dd278441 Merge branch 'main' into fix-403-idleservice-credential-race 2026-07-08 04:42:25 +00:00
mergify[bot] 27851c14be Merge pull request #426 from JMR-dev/feat-411-outlook-imap-onboarding
feat(onboarding): pre-auth Outlook IMAP-enablement screen (help/settings links + Sign In)
2026-07-08 04:28:27 +00:00
JMR-dev 08290dc85f fix(mail): gracefully fall back when the IMAP server lacks UIDPLUS
The targeted UID EXPUNGE (IMAPFolder.expunge(Message[])) from #295/#318 throws
"UID EXPUNGE not supported" on a server without the UIDPLUS extension, which
broke delete/move entirely on rare self-hosted/legacy IMAP servers (#319).

expungeTargeted now probes UIDPLUS from the folder's own already-open protocol
(via IMAPFolder.doCommand, so it never opens a second connection + LOGIN and
keeps the one-connection-per-batch invariant of #125/#295). With UIDPLUS it
still uses the targeted UID EXPUNGE. Without it, it falls back to a plain,
untargeted EXPUNGE only when provably safe: the messages we just flagged are the
only \Deleted ones in the folder. When unrelated \Deleted mail is present a plain
EXPUNGE would destroy it, and there is no UIDPLUS-free way to expunge a single
UID, so we refuse and fail loud, preserving the #295 "never touch unrelated
\Deleted mail" invariant.

PII-free AppLog breadcrumbs record the fallback decision. Covered by GreenMail
unit tests for both branches (with UIDPLUS via the default probe; without via an
injected capability seam, since GreenMail always advertises UIDPLUS).

Closes #319
2026-07-07 23:18:40 -05:00
JMR-dev 8fac4c1125 fix(push): persist credentials before IdleService watches a new account (#403)
At account-add both LibreMailApplication's push collector and
IdleService.reconcileWatchers react to the accounts table. The account row was
inserted before its credential was saved, so a watcher could observe the new
account and call MailConnectionFactory.resolveSecret before the secret existed,
logging "No stored credentials" on the first IDLE attempt (it self-healed on
retry, but fired a failed IDLE + log noise on every add).

Primary fix: reorder the writes so the credential is committed before the account
row (the credentials table has no FK to accounts; account_settings does, so its
ensureDefaults still follows the insert). Any reactive observer of the account row
is then guaranteed to see the credential.

Defense-in-depth: resolveSecret now throws a typed MissingCredentialsException and
the IDLE watcher treats it as a transient miss, deferring quietly (short flat
re-check, PII-free info log) instead of the warn + exponential backoff a real
connection drop gets. Genuinely-absent credentials keep deferring without noise.

Tests: AccountRepositoryImplTest pins the credential-before-row order
(coVerifyOrder); MailConnectionFactoryTest asserts the typed exception; a new
instrumented test drives the real repository add path against a real
AccountDatabase + Keystore-backed CredentialStore and proves the secret is
resolvable the instant the account row becomes observable.
2026-07-07 23:17:52 -05:00
mergify[bot] 410e742b20 Merge branch 'main' into feat-411-outlook-imap-onboarding 2026-07-08 04:09:08 +00:00
JMR-dev 5531a6a0c0 feat(onboarding): pre-auth Outlook IMAP-enablement screen before sign-in
New personal outlook.com accounts ship with IMAP OFF by default, so
Microsoft OAuth succeeds while the later IMAP AUTHENTICATE step fails — a
confusing dead-end (#390 handles this reactively). This adds a proactive
interstitial shown when the user taps Outlook during onboarding, before
the OAuth browser launches.

The screen asks whether IMAP is enabled (with a short why-we-need-it
explanation), links Microsoft's canonical "POP, IMAP, and SMTP settings
for Outlook.com" help article and the Outlook.com IMAP settings page
(opened via UriHandler/Custom Tab), and puts a "Sign in" button at the
bottom that continues the existing Outlook OAuth flow unchanged.

- New OutlookImapNoticeScreen (onboarding package) reusing the picker's
  exact AppAuth launch wiring via AccountSetupViewModel.
- AccountPickerScreen gains an optional onPickOutlook callback: onboarding
  routes the Outlook tap to the notice; the standalone "Add account" entry
  still launches auth inline (unchanged).
- New ONBOARDING_OUTLOOK_IMAP route; onboarding setup-form destinations
  extracted into onboardingSetupDestinations() for readability.
- PII-free AppLog breadcrumbs: notice shown, help/settings link tapped,
  sign-in continued.
- Robolectric JVM Compose test (render, both help links, sign-in
  continuation, done/error/busy states) + instrumented E2E (Espresso-
  Intents for the help ACTION_VIEW and the AppAuth sign-in launch) +
  OnboardingFlowTest coverage of picker -> notice navigation.

Closes #411
2026-07-07 22:42:25 -05:00
mergify[bot] ab5a6b334e Merge pull request #425 from JMR-dev/fix-359-encryption-gate-coverage
fix(cache): close SQLCipher nativeOpen crash-loop gaps beyond resolveOpenMode (#359)
2026-07-08 03:06:47 +00:00
Jason Ross 8cacff6b4a Merge branch 'main' into fix-359-encryption-gate-coverage 2026-07-07 21:41:31 -05:00
Jason Ross 5d593f683c Merge pull request #422 from JMR-dev/ci-mergify-autoqueue-fix
ci(mergify): fix auto-queue - migrate to merge_protections_settings/auto_merge_conditions (deprecation 2026-07-16)
2026-07-07 21:31:14 -05:00
Jason Ross a75e66ea3d Merge branch 'main' into ci-mergify-autoqueue-fix 2026-07-07 21:16:53 -05:00
JMR-dev a95b6f0b3f fix(cache): extend fail-closed SQLCipher handling beyond resolveOpenMode (#359)
Preserve the in-flight #359 crash-loop fix recovered from an orphaned agent
worktree (host crashed before it committed). Widens the fail-closed
LinkageError handling to the keyed opens that previously escaped it
(AccountDataMigrator, deferred Room open, headless workers/IdleService via a
new EncryptedCacheWorkerGuard), plus unit + instrumented regression tests.

Not yet validated end-to-end; gate + E2E run to follow.
2026-07-07 21:13:09 -05:00
JMR-dev 0c8a9e688f fix(ci): add queue_conditions identical to merge_conditions for in-place checks
The prior fix matched merge_conditions to auto_merge_conditions, but Mergify's
ruleset-compatibility check kept flagging "Configuration not compatible with
required_status_checks ruleset rule". The actual in-place-checks requirement is
that queue_conditions == merge_conditions, and this config had no
queue_conditions block at all, which Mergify reads as a two-step-CI mismatch.

Add a queue_conditions block to queue_rules.default identical (same conditions,
same order) to merge_conditions:
  - base = main
  - -draft
  - -conflict
  - label != broken
  - check-success = CI passed

Mergify runs three condition sets sequentially: auto_merge_conditions triggers
queueing, queue_conditions validates queue entry, merge_conditions validates the
merge. All three are now identical. With batch_size 1 and max_parallel_checks 1,
this makes Mergify validate PRs in place on the real branch, keeping the strict
require-up-to-date ruleset enabled (hard invariant). No other settings changed.
2026-07-07 16:17:10 -05:00
Jason Ross 6be3b41dd1 Merge pull request #424 from JMR-dev/feat-369-encrypt-reports-at-rest
feat(security): encrypt persisted reports at rest when cache encryption is on (#369)
2026-07-07 16:02:03 -05:00
JMR-dev 146d39e2cb fix(ci): make mergify merge_conditions identical to auto_merge_conditions
Mergify flagged the strict require_status_checks ruleset
(require-branches-up-to-date) as incompatible with speculative draft-PR
checks. Per Mergify, staying compatible requires in-place checks: this repo
already has merge_queue.max_parallel_checks: 1 and queue_rules batch_size: 1,
but queue_rules.default.merge_conditions was missing `base = main` and thus
did not match merge_protections_settings.auto_merge_conditions.

Add `base = main` to merge_conditions and order both lists identically so
Mergify validates PRs in place instead of via a speculative draft PR.
require-up-to-date stays enabled; batch_size, merge_method, and
max_parallel_checks are unchanged.
2026-07-07 15:49:43 -05:00
JMR-dev 099474d32a feat(security): encrypt persisted reports at rest when cache encryption is on (#369)
When the opt-in cache encryption (encryptCache) is ON, persist crash and
"Report a problem" reports encrypted at rest, decrypting them on read; when
OFF they stay plaintext exactly as before.

- ReportStore gains a ReportEncryption collaborator (default None = plaintext,
  so existing call sites are unchanged). On write it seals the storage JSON with
  AES-256-GCM and tags it with a marker prefix; on read it sniffs the prefix, so
  pre-toggle plaintext and post-toggle sealed reports coexist. Writes FAIL
  CLOSED: a sealing failure drops the report rather than leaving plaintext on
  disk. Decrypt failures are logged (PII-free) and skipped.
- KeystoreReportEncryption reuses the vetted KeystoreCrypto (non-auth master
  key, so a crash while the app is locked can still seal), and mirrors the
  encryptCache setting into a crash-safe in-memory flag warmed at startup (no
  DataStore read on the crashing thread).
- PII-free AppLog logging at the enable/disable transition and both fallback
  paths; never logs report contents.

Tests: JVM unit tests for the ReportStore branching (seal-on-write, plaintext
when off, crash persistence, fail-closed, mixed files, decrypt-failure skip,
markSurfaced re-seal) and for KeystoreReportEncryption; an instrumented test
proves real Keystore ciphertext on disk + round-trip on device.
2026-07-07 14:55:28 -05:00
JMR-devandClaude Opus 4.8 a4d1bd6fc4 ci(mergify): fix auto-queue - migrate to merge_protections_settings/auto_merge_conditions (deprecation 2026-07-16)
The `pull_request_rules` `queue` action no longer auto-queues PRs in current
Mergify: a green, matching PR just reported "Merge queue is ready - use
`@Mergifyio queue`" and sat there, never merging. Automatic queueing now lives
in `auto_merge_conditions` under `merge_protections_settings`; the old
`autoqueue`/queue-action auto path is deprecated and stops working 2026-07-16.

Replace the non-functional `pull_request_rules` block with
`merge_protections_settings.auto_merge_conditions`, mirroring the exact same
gating conditions the old queue action used (base = main, -draft, -conflict,
label != broken, check-success = CI passed).

This changes ONLY the trigger (manual -> automatic). It does not touch the
queue's merge semantics: queue_rules (batch_size 1, merge_method merge,
merge_conditions), merge_queue (max_parallel_checks 1), and priority_rules
(P0-P9) are all unchanged. require-up-to-date stays ON - only batching
(batch_size > 1) would force that GitHub checkbox off, and we keep batch_size 1.
When a merge queue is configured, a matched PR is auto-queued (not merged
directly), so it still goes through the serial queue, is updated onto latest
main, re-runs CI, and merges on the real green "CI passed".

Structure verified against the live Mergify docs (file-format, merge-queue
rules/priority/lifecycle/batches, merge-protections auto-merge). YAML parses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 12:32:49 -05:00
Jason Ross 41dca1840d Merge pull request #406 from JMR-dev/ci-404-wedge-diagnostics
ci: capture thread-dump + service state on an E2E wedge to prove the root cause (#404)
2026-07-07 11:55:11 -05:00
JMR-dev 90dfb189e6 fix(ci): drop matrix E2E wedge-capture that hangs all 8 legs
The `timeout -k 30s` wrapper + `capture_wedge()` added in 2f32657 for the
matrix `e2e` job (API 29-36) reproducibly wedges every leg, while the
manually-provisioned API 37 preview shard running the identical capture
logic passes. Revert the two matrix "Run E2E tests" steps' `script:`
blocks to main's plain script (just the backgrounded logcat stream +
`./gradlew connectedDebugAndroidTest`) and drop the now-dead "Upload wedge
diagnostics" step from the matrix job.

Kept untouched: the job-level `timeout-minutes: 50` backstop added in
27ede55, and the entire `e2e-preview` job (its own capture_wedge/watchdog
and wedge-diagnostics-api37-preview-shard* upload are unaffected).
2026-07-07 11:29:41 -05:00
JMR-dev 27ede55dbd ci(e2e): add job-level timeout to matrix E2E job (#404)
The matrix E2E job (api-level 29-36) had no timeout-minutes, so a wedge
hangs until GitHub's 6-hour default instead of being force-killed. The
sibling e2e-preview job already sets timeout-minutes: 35. A normal
matrix run is ~15-20 min and a retry-inclusive run ~40 min, so set
timeout-minutes: 50 to give headroom above the in-step wedge-capture
timeout (1200s) while still bounding worst-case runtime.
2026-07-07 07:46:33 -05:00
JMR-devandClaude Opus 4.8 2f32657aff ci: capture thread-dump + service state on an E2E wedge to prove the root cause (#404)
E2E legs intermittently WEDGE (hang) with no fast-fail until the job force-kill,
and GitHub's post-force-kill step behavior is unreliable, so #388's diagnostics
don't reliably capture the wedge — and don't capture wedge-specific state anyway.

Wrap the `connectedDebugAndroidTest` run (both the `e2e` matrix first-attempt +
retry, and each `e2e-preview` shard) in an explicit `timeout -k 30s 1200`
(20 min) — comfortably above a normal run (~13-15 min), well below the hard cap —
so a wedge trips the wrapper (exit 124), NOT the force-kill, GUARANTEEING the
capture runs while the emulator is still alive. On 124, capture_wedge grabs the
smoking gun into a `wedge-diagnostics-api<level>` artifact: the running/last test
(logcat TestRunner), SIGQUIT (kill -3) thread dumps of the app + instrumentation
processes (ART -> logcat + /data/anr), dumpsys activity/window, `service list` +
`service check input/window/activity` (the boot-race crux), sys.boot_completed +
init.svc.* state, the snapshot cache-hit note, and accel/kvm/mem/disk. Then it
exits with the real status so #388's diagnostics + the existing retry still fire;
a normal run finishes before the wrapper and is unaffected.

EVIDENCE ONLY — no boot-readiness guard/fix (maintainer: prove the cause first).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 07:46:33 -05:00
Jason Ross 5656b3cd99 Merge pull request #414 from JMR-dev/mergify/configuration-deprecated-update
ci(mergify): upgrade configuration to current format
2026-07-07 07:41:55 -05:00
Jason Ross d3264db920 Merge branch 'main' into mergify/configuration-deprecated-update 2026-07-07 07:20:22 -05:00
Jason Ross 404c107aef Merge pull request #419 from JMR-dev/refactor-405-harness-coldfetch
refactor(scripts): fold cold-fetch pause-hook A/B into device-testing harness (#405)
2026-07-07 05:29:48 -05:00
JMR-devandClaude Opus 4.8 55f1f59e3d refactor(scripts): fold cold-fetch pause-hook A/B into device-testing harness (#405)
Bakes the proven 2026-07-06 cold-vs-warm pause-hook flow into
scripts/device-testing/ as a first-class, reproducible `cold-fetch-ab`
scenario, upstreaming the scratchpad driver.

- fetchgate.py: FETCH_GATE pause/resume/query helpers through the guarded
  adb wrapper, with ordered-broadcast read-back parsing (paused=[...]).
- scenarios.cold_fetch_ab: pre-arm halt -> detect sign-in (sync all
  breadcrumb) -> confirm halt (prefetch skipped) -> wait for header sync ->
  measure cold opens -> resume -> measure warm opens. ALWAYS resumes on exit
  (finally), even on error -- never leaves fetch paused.
- report.render_cold_fetch_ab: gate summary, cold/warm tables, cold-vs-warm
  delta, connect=0ms reuse proof, throttle signature.
- Portability (subsumes #392): file-based uiautomator dump (not /dev/tty),
  UTF-8 adb decode + PYTHONUTF8/console I/O, openMessage-breadcrumb readiness,
  row-selection hardening (skip non-message rows).

The pause hook is debug-build-only (#393/#395), so the scenario needs a debug
APK. Automated validation: mocked unittest coverage (adb/breadcrumbs/gate) for
the helpers and the A/B scenario incl. restore-on-error, plus a --dry-run path
exercised end-to-end through perf_harness.main. A full on-device run is a
follow-up. Dev-tooling only; no app/src changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 05:07:11 -05:00
Jason Ross 582d1f3077 Merge pull request #418 from JMR-dev/test-386-ratchet-floor
test(compose): re-ratchet JaCoCo line-coverage floor to 0.84 (#386)
2026-07-07 03:33:12 -05:00