You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(sqlite): safe cross-process VACUUM/rekey file swap to prevent SQLITE_CORRUPT
Root cause of the mass SQLITE_NOTADB/SQLITE_CORRUPT incident (and the mass
"needs password reset" / "Database backup fix" notification emails): three
code paths renamed a new SQLite file over a LIVE database without any
cross-process quiesce (worker vacuum(), inline vacuum in
_runDeferredMaintenance, worker rekey()). Stale handles in other PM2
cluster workers kept writing to the old inode and the orphaned encrypted
-wal file was replayed onto the new file.
- add helpers/safe-vacuum.js: shared safe-swap (kill switch, vacuum_lock +
db_swap_lock owner-token locks, db_cache_evict quiesce broadcast,
fail-closed WAL checkpoint busy check, tmp verify, -wal/-shm exclusivity
proof, lock ownership re-check before rename, atomic rename, post-rename
eviction broadcast, Lua CAS releases, per-pid tmp paths)
- get-database: gate opens on db_swap_lock (pre-open 60s poll-wait + mget
check, retryable SQLITE_BUSY), don't cache custom-path handles, skip
maintenance for custom paths, reuse resolved dbFilePath, replace inline
vacuum with safeVacuum, DEL db_swap_lock on recovery, skip destructive
recovery for custom-path (tmp/backup) files, fail-closed corruption
notifications when Redis is down, atomic fleet-wide hourly throttle
(50/h) on corrupt alerts, extend db_open_lock past migrateSchema, gate
vacuum offload publish on the kill switch
- worker: vacuum() uses safeVacuum, backup() verifies the tmp backup file
(5-arg getDatabase arity fix), rekey() quiesces via db_swap_lock +
db_cache_evict and removes -wal/-shm before the rename
- parse-payload: reset action publishes db_cache_evict
- env: add SQLITE_AUTO_VACUUM_MIGRATION_ENABLED kill switch (default false)
- ansible: tune /mnt/<SQLITE_STORAGE_PATH> instead of /home/deploy/sqlite
and hard-fail I/O tuning when data_directory is missing
Copy file name to clipboardExpand all lines: PUSH_NOTIFICATIONS.md
+9-8Lines changed: 9 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -20,26 +20,27 @@ This split has to be decided on the server. A push carrying an FCM `notification
20
20
21
21
Being a `newMessage` is not on its own enough to raise an alert. The event fires for *any* message appended to *any* mailbox, so saving a draft or filing a Sent copy looks identical to incoming mail at the event level. `isAlertWorthyNewMessage` silences three cases:
22
22
23
-
- the mailbox is one of `SILENT_MAILBOX_PATHS` (Drafts, Sent, Archive, All Mail, Junk, Spam, Trash and their common aliases), matched case-insensitively against `data.mailbox` or `data.message.folder_path`;
24
-
- the message carries `\Draft`, whatever folder it landed in;
25
-
- the message arrives already `\Seen`, which a real delivery never is — that is another client copying or migrating existing mail.
23
+
* the mailbox is one of `SILENT_MAILBOX_PATHS` (Drafts, Sent, Archive, All Mail, Junk, Spam, Trash and their common aliases), matched case-insensitively against `data.mailbox` or `data.message.folder_path`;
24
+
* the message carries `\Draft`, whatever folder it landed in;
25
+
* the message arrives already `\Seen`, which a real delivery never is — that is another client copying or migrating existing mail.
26
26
27
27
A payload that says nothing about its folder stays visible: a stray alert is better than a swallowed delivery. This list is kept in step with `SILENT_FOLDERS` in the mail app's `utils/notification-manager.js`, which applies the same rules to the WebSocket path.
28
28
29
29
`buildPayload` sets `silent` on the payload, and each transport honors it:
| UnifiedPush |`title` and `body` in the encrypted body |`silent: true`, no `title` or `body`|
36
36
37
37
Silent events carry no `title` or `body` at all, rather than unused strings. A transport that forwards whatever it is given — the UnifiedPush body reaches an Android client that renders it directly — will otherwise display them.
38
38
39
39
> **APNs background pushes are best effort.** Apple throttles them and only delivers them to an app that declares the `remote-notification` background mode. Treat the WebSocket as the reliable path for state a client needs promptly, and silent push as an optimization.
40
40
41
41
Adding an event to `USER_VISIBLE_PUSH_EVENTS` also needs a matching client change: the Android UnifiedPush plugin keeps its own allowlist and suppresses anything outside it, and FCM's `android.notification.channel_id` is currently hardcoded to `new-mail`, which is only correct while mail is the sole visible category.
42
42
43
+
43
44
## Environment variable summary
44
45
45
46
| Variable | Required for | Value source | Secret |
0 commit comments