Skip to content

Notification reads can overwrite confirmed or replacement-session state #3339

Description

@Chris0Jeky

Summary

frontend/taskdeck-web/src/store/notificationStore.ts has two shared data surfaces, inbox notifications and notification preferences, but no request identity, mutation generation, operation-owned loading, or credential/session epoch.

This is a source-traced audit finding on main 307c3b8b50bec1cb0bfaea3e570a942bcb1d4451. Implementation must begin with failing real Pinia regressions.

Reachable races

Out-of-order reads

Two inbox reads for different filters, or two preference reads, may settle in reverse order. Every response commits, so the older request can replace the latest requested view.

Confirmed writes overwritten by older snapshots

An inbox read can begin before markAsRead or markAllRead, then resolve with its pre-mutation snapshot and make confirmed read notifications look unread again. A preference read can similarly resolve after updatePreferences and restore the pre-save values.

Shared loading ownership

Inbox reads, preference reads, and preference writes assign one Boolean directly. If independent operations overlap, the first settlement clears loading while another operation is still pending.

Credential/session replacement

The store has no session watcher or reset epoch. Requests or mutations begun under one token/account can settle after logout, login, or token refresh and populate the replacement notification surfaces or emit stale error/toast UI. This includes token rotation where user ID and authenticated state remain unchanged.

Expected contract

  • Inbox and preferences have independent latest-read owners.
  • Successful notification mutations invalidate older inbox reads; successful preference writes invalidate older preference reads.
  • Loading is derived from current operation tokens rather than whichever call settles first.
  • Identity, token, authentication, or demo replacement synchronously advances an epoch, retires old owners, and clears notification/preference state, error, and loading.
  • Stale success/failure/finally settlement cannot patch replacement state, emit a toast, or clear newer loading ownership.
  • Original transport promises still resolve or reject to their callers; the client does not pretend a sent request was cancelled.
  • Existing demo-mode no-network behavior and legitimate inbox/preferences concurrency remain intact.

Acceptance

  • Deferred Pinia cases for reverse-settling inbox reads and preference reads.
  • Old inbox snapshot after confirmed markAsRead and markAllRead cannot restore unread state.
  • Old preference snapshot after successful updatePreferences cannot restore old values.
  • Overlapping inbox/preference operations retain truthful loading until the final owner settles.
  • Logout/login and same-user token rotation clear both surfaces and suppress late success, failure, error, and toast settlement.
  • Stale mutation success cannot patch replacement-session state.
  • Existing notification store, realtime, integration, demo, inbox-view, and preference-view tests remain green.
  • Frontend lint, typecheck, build, full Vitest, and exact-head hosted CI pass.

Boundaries

This is client-state ownership, not a demonstrated server authorization bypass. It does not serialize overlapping mutations, redesign realtime delivery, change the notification API, or implement the reminder/email work in #2010. Realtime arrival versus authoritative refresh and same-target mutation ordering should be reviewed as separate follow-ups.

Audit provenance

Found during the 2026-09-21 async state-ownership pass after #3327, #3328, and #3337. The only open issue naming notificationStore.ts is broad feature epic #2010; no open PR owns this request/session lifecycle defect.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfrontendPrimary implementation impact in Vue/TypeScript UI and client runtime.

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions