fix(push): persist presented activity across native restarts #94

Merged
morten-olsen merged 4 commits from feat/01a155ed-presentation-state into main 2026-10-22 10:40:26 +02:00
Owner

Summary

Prevent previously presented activity from returning as a fresh Android alert after process reconstruction or dismissal. Snapshots identify the latest eligible activity independently of refresh revisions, preview text and capped unread counts.

current-unread activity ID -> native reconciliation -> bounded encrypted presentation history
same already-presented version + absent OS notification -> no repeat
unseen version -> new notification; existing notification -> current count/preview
session replacement/logout -> retire metadata; permission denial -> keep activity eligible

The history holds at most512 source/version identities under noBackupFilesDir with the existing AES-GCM codec, bounded at128KiB. It stores no preview/credential. Foreground reconciliation receives all groups plus focused source keys, so focused sources are withheld without assuming focus proves a read. Unpresented activity remains eligible after leaving if the server still reports it unread. Independent focus registrations preserve simultaneous task/conversation suppression and clear on background/teardown.

Evidence

  • 25 focused server/HTTP cases passed; the stable-version regression was red before implementation. It covers repeated refreshes, identical hidden-preview activity at the Inbox cap, same-millisecond activity and removal returning an earlier version.
  • App/server typechecks, changed TypeScript lint, dependency checks and diff review pass. No visual flow change.
  • New Robolectric cases exercise dismissal/recreation, existing-alert preservation, older-version replay, session retirement, simultaneous foreground sources, permission re-grant, metadata encryption/no-backup/bounds/corruption and missing keys. Exact-head PR649/push648 are fully green, including generic/provider debug/release JVM checks and production output assertions. All1,025 repository tests, checks/docs and image builds pass. Java/Android are unavailable locally.
  • The generic census is2248/2,300 physical production lines. Production changes163 lines; tests232, generated API2 and docs12 are reported separately. No migration/dependency/lockfile change; generated API and tests/docs reported separately on the task.

Merge danger

Door: two-way for source, preserving applied migration history and the unchanged existing session/ACK filenames, aliases and default codec bounds.
Blast radius: app/server notification snapshot contract.

Requires the matching app/server version field and native bridge array signature. All callers are in this private app. Previously presented versions remain consumed when their OS notification is absent; this is not an instruction to rebuild old alerts after reboot. Actual hardware Keystore, reboot, JobScheduler and bundled-JS behavior still need device acceptance.

Rollback and verification

  • Revert this slice coherently; existing session/ACK ciphertext layout and default2KiB write/4KiB read compatibility remain. The added presentation file is optional metadata under a new alias, never credentials.
  • Security Engineer reviews stored metadata, codec bounds/compatibility and session/focus retirement after exact-head CI passes. No Designer sign-off requested.
  • Refresh the disposable provider-removal proof against this head. Then produce the installable artifact and verify background receipt, current-only catch-up, dismissal/restart/reboot, read-elsewhere, permission and logout on the established device path. Production043/044 and installation remain Morten's decisions. No release, migration, installation or rollout occurs in this PR.
## Summary Prevent previously presented activity from returning as a fresh Android alert after process reconstruction or dismissal. Snapshots identify the latest eligible activity independently of refresh revisions, preview text and capped unread counts. ```text current-unread activity ID -> native reconciliation -> bounded encrypted presentation history same already-presented version + absent OS notification -> no repeat unseen version -> new notification; existing notification -> current count/preview session replacement/logout -> retire metadata; permission denial -> keep activity eligible ``` The history holds at most512 source/version identities under noBackupFilesDir with the existing AES-GCM codec, bounded at128KiB. It stores no preview/credential. Foreground reconciliation receives all groups plus focused source keys, so focused sources are withheld without assuming focus proves a read. Unpresented activity remains eligible after leaving if the server still reports it unread. Independent focus registrations preserve simultaneous task/conversation suppression and clear on background/teardown. ## Evidence - 25 focused server/HTTP cases passed; the stable-version regression was red before implementation. It covers repeated refreshes, identical hidden-preview activity at the Inbox cap, same-millisecond activity and removal returning an earlier version. - App/server typechecks, changed TypeScript lint, dependency checks and diff review pass. No visual flow change. - New Robolectric cases exercise dismissal/recreation, existing-alert preservation, older-version replay, session retirement, simultaneous foreground sources, permission re-grant, metadata encryption/no-backup/bounds/corruption and missing keys. Exact-head PR649/push648 are fully green, including generic/provider debug/release JVM checks and production output assertions. All1,025 repository tests, checks/docs and image builds pass. Java/Android are unavailable locally. - The generic census is2248/2,300 physical production lines. Production changes163 lines; tests232, generated API2 and docs12 are reported separately. No migration/dependency/lockfile change; generated API and tests/docs reported separately on the task. ## Merge danger **Door:** two-way for source, preserving applied migration history and the unchanged existing session/ACK filenames, aliases and default codec bounds. **Blast radius:** app/server notification snapshot contract. Requires the matching app/server version field and native bridge array signature. All callers are in this private app. Previously presented versions remain consumed when their OS notification is absent; this is not an instruction to rebuild old alerts after reboot. Actual hardware Keystore, reboot, JobScheduler and bundled-JS behavior still need device acceptance. ## Rollback and verification - Revert this slice coherently; existing session/ACK ciphertext layout and default2KiB write/4KiB read compatibility remain. The added presentation file is optional metadata under a new alias, never credentials. - Security Engineer reviews stored metadata, codec bounds/compatibility and session/focus retirement after exact-head CI passes. No Designer sign-off requested. - Refresh the disposable provider-removal proof against this head. Then produce the installable artifact and verify background receipt, current-only catch-up, dismissal/restart/reboot, read-elsewhere, permission and logout on the established device path. Production043/044 and installation remain Morten's decisions. No release, migration, installation or rollout occurs in this PR.
fix(push): persist presented activity across native restarts
Some checks failed
ci/woodpecker/push/docs Pipeline is pending
ci/woodpecker/push/deploy-tools Pipeline was successful
ci/woodpecker/push/quality Pipeline was canceled
149c8e1370
docs(push): describe bounded native presentation history
Some checks failed
ci/woodpecker/push/docs Pipeline is pending
ci/woodpecker/push/deploy-tools Pipeline was successful
ci/woodpecker/push/quality Pipeline was canceled
6e562ce6e7
test(push): reject malformed activity versions
Some checks failed
ci/woodpecker/push/docs Pipeline is pending
ci/woodpecker/pr/deploy-tools Pipeline was successful
ci/woodpecker/push/deploy-tools Pipeline was successful
ci/woodpecker/pr/image-agent Pipeline was successful
ci/woodpecker/pr/quality Pipeline was successful
ci/woodpecker/push/quality Pipeline was successful
ci/woodpecker/pr/native-push Pipeline was canceled
ci/woodpecker/pr/docs Pipeline was canceled
ci/woodpecker/pr/image Pipeline was canceled
d397aaf7a6
fix(push): preserve unread activity suppressed by foreground focus
All checks were successful
ci/woodpecker/pr/deploy-tools Pipeline was successful
ci/woodpecker/pr/image-agent Pipeline was successful
ci/woodpecker/pr/quality Pipeline was successful
ci/woodpecker/pr/native-push Pipeline was successful
ci/woodpecker/pr/docs Pipeline was successful
ci/woodpecker/pr/image Pipeline was successful
ci/woodpecker/push/draft-release Pipeline was successful
ci/woodpecker/push/deploy-access Pipeline was successful
ci/woodpecker/push/deploy-tools Pipeline was successful
ci/woodpecker/push/quality Pipeline was successful
ci/woodpecker/push/docs Pipeline was successful
ci/woodpecker/push/image Pipeline was successful
ci/woodpecker/push/image-smoke Pipeline was successful
ci/woodpecker/tag/image-release Pipeline was successful
ci/woodpecker/tag/image-agent Pipeline was successful
ci/woodpecker/tag/deploy-release Pipeline was successful
ci/woodpecker/tag/release-android Pipeline was successful
d53cf5567d
morten-olsen left a comment

Scoped review of d53cf5567d against b5f2133fec: no blocking Standards or Spec findings. Full review follows in a comments-only review record; hardware/full-APK acceptance remains outstanding and no operational approval is implied.

Scoped review of d53cf5567dbc087cf96067c5bd188e842e5cbbc8 against b5f2133fec6defc77bfa1950c49a4f8ea90e8d33: no blocking Standards or Spec findings. Full review follows in a comments-only review record; hardware/full-APK acceptance remains outstanding and no operational approval is implied.
morten-olsen left a comment

Scoped security review completed for PR94 at d53cf5567d against b5f2133fec. Prior reviews unchanged.

Standards

No blocking findings. Checked the three-dot diff and surrounding implementation against Mission Control AGENTS.md, Homelab secret/perimeter/operations rules and the role checklist. No new exposure, permissions, image sources, schema or copied activity history. Snapshot version subqueries use the existing eligible organization/destination/member and unread predicates; conversation ownership and task subscription/read/archive filtering remain intact. Versions are strings bounded to 1–64 characters at server schema/native parsing. Native response/page/group limits remain.

The new file contains generation plus at most 512 source/version identities, no preview, endpoint or credential. AES-GCM uses the file-specific alias/AAD and noBackupFilesDir; corruption/wrong-key reads discard metadata. Generation replacement/logout clears history and alerts; same-generation reconstruction preserves existing alerts. Session/ACK filenames, aliases, ciphertext layout and default 2KiB write/4KiB read bounds remain unchanged. New codec: 128KiB plaintext write limit, 28-byte ciphertext overhead, 130KiB defensive read ceiling.

Non-blocking, UNKNOWN (verification limit): hardware Keystore, reboot, JobScheduler and bundled-JS/full-APK behavior were not verified here. This is not evidence of an exploitable defect or a waiver of device acceptance. No cluster mutation or secret reads performed; no live deployment claim.

Spec

No blocking findings. Latest eligible UUID selection has explicit same-millisecond tie ordering (conversation rowid; task kind then rowid). Stable versions are independent of revision, preview visibility and capped counts. Reviewed regression cases cover stable/new/removed activity, malformed versions, dismissal/reconstruction, existing-alert updates, 512-entry eviction, corrupt/wrong-key history and permission re-grant. Absent previously presented versions are suppressed; active alerts can update. Focused groups are excluded before remembering, so focus alone does not consume unread activity. JS tracks independent focus registrations and sends only source-key arrays; background/cleanup and native background/destroy clear suppression. The interface adds no read, credential or arbitrary execution capability.

Non-blocking, UNKNOWN (acceptance limit): inspected lifecycle source and native adapter tests, not actual React Native bridge/device execution. Owner must retain the explicit device gate and bounded-history limitation: evicted identities may reappear. Permission denial clears history so unread activity remains eligible after grant.

Evidence and next steps

Read-only tea api repos/ai/mission-control/pulls/94 confirmed exact head/base, open/unmerged. tea api repos/ai/mission-control/commits/d53cf5567dbc087cf96067c5bd188e842e5cbbc8/status returned success for all nine current contexts, including PR649 native-push/quality and push648. git diff b5f2133...HEAD --check passed. Tests were inspected, not rerun locally (no Java or installed worktree dependencies). Woodpecker CLI exists but is unconfigured; pipeline-detail queries failed. PR651/test counts are author evidence; never merge PR93.

CI: https://woodpecker.olsen.cloud/repos/32/pipeline/649

After separately approved migration/install, owner verifies real-device background receipt, dismissal/restart/reboot, simultaneous focus/leave/background, read-elsewhere, permission re-grant and logout. Rollback this slice coherently across server/app/native bridge; preserve applied migrations and session/ACK compatibility. Production 043/044, release and installation decisions remain separate. Owner retains merge/build/rollout; no operational approval or waiver given.

Standards: 0 blocking, 1 non-blocking UNKNOWN verification limit. Spec: 0 blocking, 1 non-blocking UNKNOWN acceptance limit.

Scoped security review completed for PR94 at d53cf5567dbc087cf96067c5bd188e842e5cbbc8 against b5f2133fec6defc77bfa1950c49a4f8ea90e8d33. Prior reviews unchanged. ## Standards No blocking findings. Checked the three-dot diff and surrounding implementation against Mission Control AGENTS.md, Homelab secret/perimeter/operations rules and the role checklist. No new exposure, permissions, image sources, schema or copied activity history. Snapshot version subqueries use the existing eligible organization/destination/member and unread predicates; conversation ownership and task subscription/read/archive filtering remain intact. Versions are strings bounded to 1–64 characters at server schema/native parsing. Native response/page/group limits remain. The new file contains generation plus at most 512 source/version identities, no preview, endpoint or credential. AES-GCM uses the file-specific alias/AAD and noBackupFilesDir; corruption/wrong-key reads discard metadata. Generation replacement/logout clears history and alerts; same-generation reconstruction preserves existing alerts. Session/ACK filenames, aliases, ciphertext layout and default 2KiB write/4KiB read bounds remain unchanged. New codec: 128KiB plaintext write limit, 28-byte ciphertext overhead, 130KiB defensive read ceiling. Non-blocking, UNKNOWN (verification limit): hardware Keystore, reboot, JobScheduler and bundled-JS/full-APK behavior were not verified here. This is not evidence of an exploitable defect or a waiver of device acceptance. No cluster mutation or secret reads performed; no live deployment claim. ## Spec No blocking findings. Latest eligible UUID selection has explicit same-millisecond tie ordering (conversation rowid; task kind then rowid). Stable versions are independent of revision, preview visibility and capped counts. Reviewed regression cases cover stable/new/removed activity, malformed versions, dismissal/reconstruction, existing-alert updates, 512-entry eviction, corrupt/wrong-key history and permission re-grant. Absent previously presented versions are suppressed; active alerts can update. Focused groups are excluded before remembering, so focus alone does not consume unread activity. JS tracks independent focus registrations and sends only source-key arrays; background/cleanup and native background/destroy clear suppression. The interface adds no read, credential or arbitrary execution capability. Non-blocking, UNKNOWN (acceptance limit): inspected lifecycle source and native adapter tests, not actual React Native bridge/device execution. Owner must retain the explicit device gate and bounded-history limitation: evicted identities may reappear. Permission denial clears history so unread activity remains eligible after grant. ## Evidence and next steps Read-only `tea api repos/ai/mission-control/pulls/94` confirmed exact head/base, open/unmerged. `tea api repos/ai/mission-control/commits/d53cf5567dbc087cf96067c5bd188e842e5cbbc8/status` returned success for all nine current contexts, including PR649 native-push/quality and push648. `git diff b5f2133...HEAD --check` passed. Tests were inspected, not rerun locally (no Java or installed worktree dependencies). Woodpecker CLI exists but is unconfigured; pipeline-detail queries failed. PR651/test counts are author evidence; never merge PR93. CI: https://woodpecker.olsen.cloud/repos/32/pipeline/649 After separately approved migration/install, owner verifies real-device background receipt, dismissal/restart/reboot, simultaneous focus/leave/background, read-elsewhere, permission re-grant and logout. Rollback this slice coherently across server/app/native bridge; preserve applied migrations and session/ACK compatibility. Production 043/044, release and installation decisions remain separate. Owner retains merge/build/rollout; no operational approval or waiver given. Standards: 0 blocking, 1 non-blocking UNKNOWN verification limit. Spec: 0 blocking, 1 non-blocking UNKNOWN acceptance limit.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
ai/mission-control!94
No description provided.