Skip to content

fix(main): preserve local backup opt-out and retention across resets and restores - #1331

Merged
khaira777 merged 2 commits into
mainfrom
fm/ship-backup-optout-reset-paths
Oct 23, 2026
Merged

khaira777 merged 2 commits into
mainfrom
fm/ship-backup-optout-reset-paths

Conversation

@khaira777

Copy link
Copy Markdown
Contributor

Intent

Verify and fix release-audit finding F3 (P2, data safety / privacy) in the daily local backup feature (#1303, #1329):
When an owner disables automatic daily local backups (local_backup_enabled = 'false'), that opt-out is currently wiped and silently re-enabled (true) by:

  1. Currency Reset (POST /api/db-tools/currency-reset -> resetDatabaseForCurrencyChange in main/db.ts), which preserves only ['country', 'currency', 'timezone'], deletes local_backup_enabled = 'false', and re-inserts the store's products so the next localBackup.runIfDue() writes a .db backup to Documents/FloCafe/Backups despite the opt-out; conversely, when local_backup_enabled is enabled (true), POST /api/db-tools/currency-reset (main/routes/database-tools.ts) does not call retainBackupLocally(result.backupPath) like POST /api/db-tools/initialize does.
  2. Electron IPC db-initialize (main/ipc.ts), which calls resetDatabaseWithBackup() without preserving local_backup_enabled = 'false' and without calling retainBackupLocally(backupPath) when enabled.
  3. Backup Restore (restoreBackup / RESTORE_PROTECTED_SETTING_KEYS in main/db.ts), which does not include 'local_backup_enabled' in RESTORE_PROTECTED_SETTING_KEYS, so restoring an older backup overwrites local_backup_enabled = 'false' back to 'true' (or deletes the row, which defaults to true).

What Changed

  • Preserve local_backup_enabled across resetDatabaseWithBackupUnlocked and resetDatabaseForCurrencyChange in main/db.ts, and include 'local_backup_enabled' in RESTORE_PROTECTED_SETTING_KEYS so both direct and data-only restoreBackup runs retain the active setting.
  • Copy the pre-reset backup via retainBackupLocally when local backups are enabled (or keep setLocalBackupEnabled(false) when disabled) and return localBackupFailed from POST /api/db-tools/currency-reset (main/routes/database-tools.ts) and the db-initialize IPC handler (main/ipc.ts).
  • Document the updated reset and restore behavior in docs/architecture/data-and-migrations.md and docs/reference/api.md, and add regression tests in tests/local-backup.test.ts.

Risk Assessment

✅ Low: The change is well-bounded and directly preserves the local_backup_enabled setting and pre-reset local backup retention across database resets, currency resets, and backup restores with comprehensive behavioral test coverage.

Testing

Ran the focused local-backup, database-tools-api, currency, and backup test suites, and stood up the live Express API server, IPC handlers, SQLite database, and LocalBackupService in an isolated disposable environment to drive currency reset (opted-out, enabled, and obstructed backup directory), Electron IPC db-initialize (opted-out and enabled), and HTTP backup restore (direct with enabled setting, direct with missing setting row, and data-only from an older schema) with recorded evidence; all checks passed. This is a backend persistence and IPC/API contract change with no UI/visual surface change.

  • Live validation: ✅ go - 5 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Owner disables daily local backup, runs currency reset with menu products present, and triggers daily backup check without writing a local backup ✅ pass live ~/.no-mistakes/evidence/01M5MS79Q40JXGPC3N3YFJPQCB/live-local-backup-optout-verification.json (scenarios.currencyResetOptedOut) and npm run test:local-backup
Owner enables daily local backup and runs currency reset, retaining the pre-reset backup in Documents/FloCafe/Backups ✅ pass live ~/.no-mistakes/evidence/01M5MS79Q40JXGPC3N3YFJPQCB/live-local-backup-optout-verification.json (scenarios.currencyResetEnabled) and npm run test:local-backup
Adversarial: Owner runs currency reset while daily local backup is enabled but Documents/FloCafe/Backups is obstructed by a regular file ✅ pass live ~/.no-mistakes/evidence/01M5MS79Q40JXGPC3N3YFJPQCB/live-local-backup-optout-verification.json (scenarios.currencyResetObstructedBackupDir)
Owner runs Electron IPC db-initialize while opted out and while enabled, preserving opt-out across reset and retaining pre-reset backup only when enabled ✅ pass live ~/.no-mistakes/evidence/01M5MS79Q40JXGPC3N3YFJPQCB/live-local-backup-optout-verification.json (scenarios.ipcDbInitialize) and npm run test:local-backup
Adversarial: Owner opted out of daily local backup restores a same-schema enabled backup, a backup missing the local_backup_enabled row, and an older-schema data-only backup without re-enabling local… ✅ pass live ~/.no-mistakes/evidence/01M5MS79Q40JXGPC3N3YFJPQCB/live-local-backup-optout-verification.json (scenarios.backupRestoreOptedOut) and npm run test:local-backup
Evidence: Live API, IPC, SQLite, and local backup directory verification transcript

{ "currencyResetOptedOut": { "currencyResetResponse": { "status": 200, "data": { "success": true, "currency": "EUR", "localBackupFailed": false } }, "getLocalBackupAfterReset": { "status": 200, "data": { "enabled": false } }, "dbSettingRowAfterReset": { "key": "local_backup_enabled", "value": "false" }, "survivingProductCount": 1, "localBackupsAfterRunIfDue": [] }, "currencyResetEnabled": { "currencyResetResponse": { "status": 200, "data": { "success": true, "currency": "GBP", "localBackupFailed": false } }, "dbSettingRowAfterReset": { "key": "local_backup_enabled", "value": "true" }, "retainedLocalBackups": ["flo-backup-2026-10-23T16-42-35-838Z.db"] }, "currencyResetObstructedBackupDir": { "currencyResetResponse": { "status": 200, "data": { "success": true, "currency": "CAD", "localBackupFailed": true } } }, "ipcDbInitialize": { "ipcDisabledResult": { "success": true, "localBackupFailed": false }, "getLocalBackupAfterIpcDisabled": { "status": 200, "data": { "enabled": false } }, "localBackupsAfterIpcDisabledRunIfDue": [], "ipcEnabledResult": { "success": true, "localBackupFailed": false }, "retainedLocalBackupsAfterIpcEnabled": ["flo-backup-2026-10-23T16-42-36-748Z.db"] }, "backupRestoreOptedOut": { "directRestoreEnabledSnapshot": { "restoreResponse": { "status": 200, "data": { "mode": "direct" } }, "getLocalBackupAfterRestore": { "data": { "enabled": false } } }, "directRestoreMissingRowSnapshot": { "restoreResponse": { "status": 200, "data": { "mode": "direct" } }, "getLocalBackupAfterRestore": { "data": { "enabled": false } } }, "dataOnlyRestoreOlderSnapshot": { "restoreResponse": { "status": 200, "data": { "mode": "data_only" } }, "getLocalBackupAfterRestore": { "data": { "enabled": false } } }, "dbSettingRowAfterAllRestores": { "key": "local_backup_enabled", "value": "false" }, "localBackupsAfterRunIfDue": [] } }

{
  "startedAt": "2026-10-23T16:42:35.136Z",
  "scenarios": {
    "currencyResetOptedOut": {
      "putLocalBackupOff": {
        "status": 200,
        "data": {
          "enabled": false,
          "directory": "/var/folders/y_/1ltcxtwj0zd_w1dg9jv4jl580000gn/T/flo-live-f3-Xyl4p4/documents/FloCafe/Backups",
          "retention": 5,
          "lastBackupAt": null
        }
      },
      "currencyResetResponse": {
        "status": 200,
        "data": {
          "success": true,
          "currency": "EUR",
          "backupPath": "/var/folders/y_/1ltcxtwj0zd_w1dg9jv4jl580000gn/T/flo-live-f3-Xyl4p4/userData/backups/flo-backup-2026-10-23T16-42-35-433Z-048800e3.db",
          "cleanupPending": false,
          "localBackupFailed": false
        }
      },
      "getLocalBackupAfterReset": {
        "status": 200,
        "data": {
          "enabled": false,
          "directory": "/var/folders/y_/1ltcxtwj0zd_w1dg9jv4jl580000gn/T/flo-live-f3-Xyl4p4/documents/FloCafe/Backups",
          "retention": 5,
          "lastBackupAt": null
        }
      },
      "dbSettingRowAfterReset": {
        "key": "local_backup_enabled",
        "value": "false"
      },
      "survivingProductCount": 1,
      "localBackupsAfterRunIfDue": []
    },
    "currencyResetEnabled": {
      "putLocalBackupOn": {
        "status": 200,
        "data": {
          "enabled": true,
          "directory": "/var/folders/y_/1ltcxtwj0zd_w1dg9jv4jl580000gn/T/flo-live-f3-Xyl4p4/documents/FloCafe/Backups",
          "retention": 5,
          "lastBackupAt": null
        }
      },
      "currencyResetResponse": {
        "status": 200,
        "data": {
          "success": true,
          "currency": "GBP",
          "backupPath": "/var/folders/y_/1ltcxtwj0zd_w1dg9jv4jl580000gn/T/flo-live-f3-Xyl4p4/userData/backups/flo-backup-2026-10-23T16-42-35-725Z-4b16226f.db",
          "cleanupPending": false,
          "localBackupFailed": false
        }
      },
      "dbSettingRowAfterReset": {
        "key": "local_backup_enabled",
        "value": "true"
      },
      "retainedLocalBackups": [
        "flo-backup-2026-10-23T16-42-35-838Z.db"
      ]
    },
    "currencyResetObstructedBackupDir": {
      "currencyResetResponse": {
        "status": 200,
        "data": {
          "success": true,
          "currency": "CAD",
          "backupPath": "/var/folders/y_/1ltcxtwj0zd_w1dg9jv4jl580000gn/T/flo-live-f3-Xyl4p4/userData/backups/flo-backup-2026-10-23T16-42-36-050Z-d587853e.db",
          "cleanupPending": false,
          "localBackupFailed": true
        }
      },
      "dbSettingRowAfterReset": {
        "key": "local_backup_enabled",
        "value": "true"
      }
    },
    "ipcDbInitialize": {
      "ipcDisabledResult": {
        "success": true,
        "backupPath": "/var/folders/y_/1ltcxtwj0zd_w1dg9jv4jl580000gn/T/flo-live-f3-Xyl4p4/userData/backups/flo-backup-2026-10-23T16-42-36-332Z-f772a9e1.db",
        "cleanupPending": false,
        "localBackupFailed": false
      },
      "getLocalBackupAfterIpcDisabled": {
        "status": 200,
        "data": {
          "enabled": false,
          "directory": "/var/folders/y_/1ltcxtwj0zd_w1dg9jv4jl580000gn/T/flo-live-f3-Xyl4p4/documents/FloCafe/Backups",
          "retention": 5,
          "lastBackupAt": null
        }
      },
      "localBackupsAfterIpcDisabledRunIfDue": [],
      "ipcEnabledResult": {
        "success": true,
        "backupPath": "/var/folders/y_/1ltcxtwj0zd_w1dg9jv4jl580000gn/T/flo-live-f3-Xyl4p4/userData/backups/flo-backup-2026-10-23T16-42-36-622Z-2357bae5.db",
        "cleanupPending": false,
        "localBackupFailed": false
      },
      "retainedLocalBackupsAfterIpcEnabled": [
        "flo-backup-2026-10-23T16-42-36-748Z.db"
      ]
    },
    "backupRestoreOptedOut": {
      "directRestoreEnabledSnapshot": {
        "restoreResponse": {
          "status": 200,
          "data": {
            "success": true,
            "mode": "direct",
            "backupVersion": 116,
            "currentVersion": 116,
            "tablesRestored": 66,
            "cleanupPending": false
          }
        },
        "getLocalBackupAfterRestore": {
          "status": 200,
          "data": {
            "enabled": false,
            "directory": "/var/folders/y_/1ltcxtwj0zd_w1dg9jv4jl580000gn/T/flo-live-f3-Xyl4p4/documents/FloCafe/Backups",
            "retention": 5,
            "lastBackupAt": null
          }
        }
      },
      "directRestoreMissingRowSnapshot": {
        "restoreResponse": {
          "status": 200,
          "data": {
            "success": true,
            "mode": "direct",
            "backupVersion": 116,
            "currentVersion": 116,
            "tablesRestored": 66,
            "cleanupPending": false
          }
        },
        "getLocalBackupAfterRestore": {
          "status": 200,
          "data": {
            "enabled": false,
            "directory": "/var/folders/y_/1ltcxtwj0zd_w1dg9jv4jl580000gn/T/flo-live-f3-Xyl4p4/documents/FloCafe/Backups",
            "retention": 5,
            "lastBackupAt": null
          }
        }
      },
      "dataOnlyRestoreOlderSnapshot": {
        "restoreResponse": {
          "status": 200,
          "data": {
            "success": true,
            "mode": "data_only",
            "backupVersion": 115,
            "currentVersion": 116,
            "tablesRestored": 66,
            "cleanupPending": false
          }
        },
        "getLocalBackupAfterRestore": {
          "status": 200,
          "data": {
            "enabled": false,
            "directory": "/var/folders/y_/1ltcxtwj0zd_w1dg9jv4jl580000gn/T/flo-live-f3-Xyl4p4/documents/FloCafe/Backups",
            "retention": 5,
            "lastBackupAt": null
          }
        }
      },
      "dbSettingRowAfterAllRestores": {
        "key": "local_backup_enabled",
        "value": "false"
      },
      "localBackupsAfterRunIfDue": []
    }
  },
  "completedAt": "2026-10-23T16:42:37.549Z"
}

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 5 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Owner disables daily local backup, runs currency reset with menu products present, and triggers daily backup check without writing a local backup ✅ pass live ~/.no-mistakes/evidence/01M5MS79Q40JXGPC3N3YFJPQCB/live-local-backup-optout-verification.json (scenarios.currencyResetOptedOut) and npm run test:local-backup
Owner enables daily local backup and runs currency reset, retaining the pre-reset backup in Documents/FloCafe/Backups ✅ pass live ~/.no-mistakes/evidence/01M5MS79Q40JXGPC3N3YFJPQCB/live-local-backup-optout-verification.json (scenarios.currencyResetEnabled) and npm run test:local-backup
Adversarial: Owner runs currency reset while daily local backup is enabled but Documents/FloCafe/Backups is obstructed by a regular file ✅ pass live ~/.no-mistakes/evidence/01M5MS79Q40JXGPC3N3YFJPQCB/live-local-backup-optout-verification.json (scenarios.currencyResetObstructedBackupDir)
Owner runs Electron IPC db-initialize while opted out and while enabled, preserving opt-out across reset and retaining pre-reset backup only when enabled ✅ pass live ~/.no-mistakes/evidence/01M5MS79Q40JXGPC3N3YFJPQCB/live-local-backup-optout-verification.json (scenarios.ipcDbInitialize) and npm run test:local-backup
Adversarial: Owner opted out of daily local backup restores a same-schema enabled backup, a backup missing the local_backup_enabled row, and an older-schema data-only backup without re-enabling local… ✅ pass live ~/.no-mistakes/evidence/01M5MS79Q40JXGPC3N3YFJPQCB/live-local-backup-optout-verification.json (scenarios.backupRestoreOptedOut) and npm run test:local-backup
  • npm run test:local-backup
  • npm run test:database-tools-api
  • npm run test:currency
  • npm run test:backup
  • node tests/run-electron-node-test.cjs -e '<live server + IPC + SQLite + localBackup scenario verification>'
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@coderabbitai

coderabbitai Bot commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@khaira777
khaira777 merged commit 93b4b28 into main Oct 23, 2026
23 checks passed
@khaira777
khaira777 deleted the fm/ship-backup-optout-reset-paths branch October 23, 2026 16:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant