v1.11.0
LatestApril 24, 2026AES-256-GCM chunk encryption is now hardware-accelerated via dart:ffi into the OS crypto library. Pointycastle (pure Dart) was the only cipher backend since v1.4.2 at ~80 MB/s per core in production AOT builds — the main bottleneck for encrypted backups on large VMs. v1.11.0 replaces that with platform-native code: bcrypt.dll CNG on Windows, libcrypto.so.{3,1.1} on Linux, and CommonCrypto (CCCryptorCreateWithMode + kCCModeGCM) on macOS. Benchmark on Windows Server 2025: 255 MB/s in the Dart VM test harness — a 391× improvement over VM-mode pure Dart, ≥3× over production AOT pure Dart. Expected real-world benefit: on a 200 GB encrypted backup, crypto overhead drops from ~40 minutes to <5 minutes.
Zero caller changes. The existing `AesGcmEncryptor(key)` constructor is now a factory that silently picks the fastest available backend: native if loadable, pure-Dart if the native library is missing / fails to load / crashes at init. The "native unavailable" decision caches per-isolate so a missing DLL doesn't get re-probed 1000 times per backup. An explicit `AesGcmEncryptor.pureDart(key)` constructor and a `debugForcePureDartCrypto` flag let tests exercise either path on demand.
On-wire format is unchanged: every chunk still emits `IV(12) || ciphertext || tag(16)`. Every backup ever written by v1.4.2 → v1.10.0 continues to decrypt identically whether the restore agent runs the native or pure-Dart path — verified by new cross-implementation round-trip tests (native encrypt → pure-Dart decrypt and vice versa).
Context pooling inside the native backends: on Windows, the BCRYPT_ALG_HANDLE (opened + switched to GCM mode) is a process-wide singleton; each encryptor owns its own BCRYPT_KEY_HANDLE for its lifetime. On Linux, each encryptor owns one EVP_CIPHER_CTX* that's reset+re-init'd per op (drops per-op cost from ~2µs ctx-new to ~50ns ctx-reset). On macOS, CommonCrypto doesn't support reset so it's a per-op cryptor with release; best we can do on that platform.
Graceful fallback: any native load failure, FFI error, or unsupported platform (macOS < 10.13, hypothetically BSD) drops to the pure-Dart path silently. Log goes to debugPrint only, never propagates to the backup runner. The factory is guaranteed to return a working encryptor on every platform Flutter desktop supports.
18 backup-core tests pass (5 original round-trip + 13 new covering cross-impl compatibility, forced-pure-Dart path, payload size matrix from 1 byte to 1 MB, native-status exposure). 1 tagged benchmark test skipped by default (run via `flutter test --tags benchmark` for on-demand profiling). Desktop suite: 370 tests pass. Only pre-existing widget_test.dart MyApp failure unchanged.
Windows is the only platform runtime-tested in this release. Linux + macOS bindings are code-complete with matching cross-impl tests but untested on those platforms — same situation as v1.5.1 (Mac/Linux scheduler) and v1.6.0 (Hyper-V CBT). The native-path fallback to pure-Dart guarantees no-regression behavior on Linux/macOS even if the native loader has bugs. A Linux/macOS smoke pass is the recommended follow-up before tagging v1.11.0 GA.
Follow-up optimization deferred: the native throughput of 255 MB/s is well below the theoretical 2-3 GB/s AES-NI ceiling, capped by per-op plaintext/ciphertext buffer churn (6-8 calloc/free pairs per encrypt). Pooling scratch buffers sized to FastCDC max chunk (64 KB) on the encryptor instance would push throughput another 5-10×. Not urgent at 255 MB/s — backups are now network/disk-bound, not crypto-bound.
v1.10.0
April 24, 2026Desktop auto-update checker. The agent now checks downloads.backupengine.com/latest-version.json on launch (10s after login to avoid competing with registration/heartbeat) and every 6 hours while the dashboard is open. A new amber banner at the top of every screen surfaces the update with four actions: "What's new" (opens the changelog in your browser), "Update now" (platform-aware install flow), "Remind tomorrow" (24h snooze), and "Skip this version" (never nag for this version again).
Install-type-aware upgrade paths. Detection uses Platform.resolvedExecutable — paths containing \WindowsApps\ or matching net.techbench.backupengine_ are MSIX; anything else on Windows is ZIP. (1) MSIX: "Update now" opens the ms-appinstaller: URI so the Windows App Installer handles the upgrade. (2) Windows ZIP: downloads the new ZIP to %APPDATA%\Backup Engine\updates\, writes a PowerShell updater script, launches it hidden, and the app exits — the script waits for the PID to clear, unzips over the install dir, and launches the updated EXE. (3) Linux tar.gz and (4) macOS zip: downloads to ~/Downloads/ with an "extract + run to finish" toast; in-place replacement would require knowing the user's packaging (tarball vs .deb vs .dmg vs brew cask) so manual extraction is the safe default.
Don't-check safety guards. The checker never fires while a backup is actively running (via AppState.hasActiveJobs), never runs in CLI or service mode (GUI-only), and never auto-triggers the updater — every install is user-initiated. Clicking "Update now" during an active backup surfaces a snackbar "Cannot update during backup. Will retry later." instead of silently doing nothing.
Skip/snooze persistence via SQLite (LocalDb v13 → v14 — new update_skip and update_snooze tables). Skipping v1.10.1 means you won't see the banner until v1.10.2 releases. Snoozing mutes for exactly 24 hours. A min_supported_version field in the manifest bypasses both — EOL builds always surface as a red (danger) banner with only "What's new" and "Update now" actions.
Backend: latest-version.json manifest on R2 root. scripts/upload-installer.mjs fetches the existing manifest, merges the new platform's entry (preserves other platforms' URLs + sizes), and re-uploads. The GHA cross-platform workflow does the same for Linux + macOS via a new refresh-version-manifest job. Same-version re-uploads preserve released_at so the UI doesn't flap.
37 new tests: 13 UpdateChecker (semver compare, install-type detection, malformed manifest rejection, network failure fallback), 8 UpdateService (skip/snooze persistence, stream emission, guard predicate), 8 UpdaterService (platform dispatch, PowerShell script content verification, Linux/macOS download-only path), 5 UpdateBanner widget (renders, buttons wire to correct callbacks, min_supported_version renders red). Suite: 370/371 pass (only pre-existing widget_test.dart MyApp failure unchanged).
Architectural choice: no new package dependencies. Reused existing http for fetch, path_provider for download paths, and the Process.run pattern from about_screen.dart for opening URLs (matches how other screens already handle the ms-appinstaller: and web links — avoids pulling url_launcher just for this feature).
v1.9.0
April 23, 2026Admin Portal: "Add Customer" end-to-end. Admins can provision a customer account directly from the /users page without waiting for the customer to self-register. Modal form: email + plan + region + encryption mode + delivery method (invite email by default via Supabase magic link, or set temporary password as an advanced option) + billing decision (comp by default, charge immediately as opt-in) + optional organization name + admin-only notes. New Edge Function `admin-create-customer` handles auth user creation via the Auth Admin API, profile patch, and audit log. Rollback on partial-failure keeps the auth.users + profiles tables consistent.
Admin Portal: wire Impersonate / Suspend / Unsuspend / Reset MFA on /users/[id]. Impersonate generates a 10-minute magic link via auth.admin.generateLink and opens the customer portal in a new tab (every session is audit-logged with a required reason). Suspend sets profiles.status='suspended', flips the red banner on the detail page, and force-logs-out all existing sessions via auth.admin.signOut global. Unsuspend reverses. Reset MFA deletes all TOTP factors so the customer can re-enroll on next login.
Admin Portal: inline admin-notes editor (dirty-tracking, Save button) + customer-scoped Audit Log section at the bottom of /users/[id]. Shows the last 200 admin_customer_edits rows for that customer: timestamp, action, admin who performed it, reason, billing decision chip. Client-side CSV export via Blob — no backend call, safe for compliance exports.
Database schema: profiles.status enum (active | pending_invite | suspended), profiles.notes, profiles.created_by_admin_id, suspended_at/by/reason. RLS policies rewritten so suspended customers cannot self-read or self-update their profile even if their JWT is still cached client-side; service-role admin calls bypass RLS so unsuspend still works. admin_users.can_impersonate BOOLEAN (defaults FALSE for everyone, including super_admin — deliberate stricter default).
Desktop agent: signup screen lets customers who downloaded the agent from the website create their account directly in the app, no web-form round-trip. Fields mirror the web signup: email + password (≥12 chars, strength meter) + confirm + region + plan dropdown + optional organization name + ToS checkbox + Turnstile captcha (on Windows). "Sign up" link on the login screen; "Back to Login" on the signup screen. Auto-confirmed accounts flow straight into the main shell; email-verification-required accounts see a confirmation screen with a Resend button.
Desktop on Mac/Linux: captcha is skipped (the v1.8.1 stub renders an explainer; signup submits without a captcha token). If the Supabase project has captcha ENFORCED at the server side, non-Windows signups fail with a clear error — documented with a TODO for a future cross-platform captcha story. TurnstileCaptcha stub already ships with the Linux/macOS builds via the GHA workflow.
9 new desktop widget tests for the signup screen (form validation, strength meter, ToS gating, Supabase signUp mock with success/error paths). Desktop suite: 333/334 pass with --concurrency=1 (only pre-existing widget_test.dart MyApp failure remains). Admin portal tsc + next build both clean.
Backend schema reality check: admin_customer_edits uses customer_id (not target_user_id) and stores reason in billing_note/new_value JSONB rather than a dedicated reason column — the new wrappers handle this so the UI stays clean. encryption_mode enum at the DB level is zero_knowledge | managed (not managed_recovery); the Edge Function accepts managed_recovery in the API shape and maps on write.
v1.8.1
April 22, 2026Encryption audit log: SQLite-backed timeline of every encryption mutation (set passphrase, unlock attempts including wrong-passphrase attempts, lock, change passphrase, factory reset, import-from-manifest, DEK rotation begin/progress/complete/abort). Compliance + incident response now have a chronological "who/when did what" record. New table encryption_audit_log (LocalDb v12 → v13) with timestamp + event_type + outcome + detail + structured metadata_json. NEVER logs key material — passphrases, KEKs, DEKs, salts are explicitly excluded.
Audit log viewer at Settings → Security → Backup Encryption → "View Audit Log" — chip-coded rows by category (green=success, amber=neutral, red=failure), expandable metadata, CSV export to a user-picked path, triple-confirmed Clear Log (Dangerous) action that re-appends a "log_cleared" marker so the post-clear log isn't silent.
Audit-write never breaks the underlying op. Every record() call is wrapped in try/catch + debugPrint on failure; the original encryption mutation always succeeds even if the audit DB is unreachable. Verified by a synthetic _ThrowingLocalDb test.
Windows toast on DEK rotation completion + abort. The already-running tray icon fires a notification when the rotation stream completes ("Encryption rotation complete — All N manifests rotated under the new key.") or halts ("Encryption rotation halted — Stopped after manifest X. Click Resume in Settings → Security to continue."). The DekRotationService also emits a rotationProgress audit event every 10 manifests for forensic review.
New `backupengine encryption status` CLI subcommand for ops monitoring. Plain-text output shows configured/unlocked/verifier-version/manifest-body-encryption/KDF params/active DEK fingerprint/active rotation. Add --json for structured ops-script consumption. Active DEK fingerprint = SHA-256(dek_bytes)[:8] hex — gives ops a "did the DEK actually change after rotation?" check WITHOUT exposing the key (pre-image-resistant).
Selftest hyperv backup-set wiring (completes the v1.8.0 SKIP-marks). `backupengine selftest hyperv --vm <name> --confirm` now runs a real full backup → incremental → chain-promotion (forced via chainLengthCap=2) → restore → sampled byte-compare against the source VHDX (head 64 KiB + tail 64 KiB + 3 deterministic 1 MiB samples). Local-filesystem backend; no cloud cost. LIFO cleanup deletes the test storage dir + every BackupEngine-Reference-<setId>-* checkpoint regardless of partial failure.
25 new tests for v1.8.1 (11 EncryptionAuditService + 5 LocalDb audit table + 5 EncryptionKeyService instrumentation [including the _ThrowingLocalDb resilience test] + 2 DekRotationService rotation success/failure audit + toast + 2 selftest auditLog step) plus 2 new tests for v1.8.2 (selftest hyperv wiring SKIP path on non-Windows).
Test infrastructure note: audit-log tests use SQLite directly so the suite must run with `flutter test --concurrency=1` to avoid "database is locked" contention. Production is single-process so this only affects test ordering. Suite: 324/325 pass with --concurrency=1; only the pre-existing widget_test.dart MyApp failure remains.
Three new TODO(hyperv-validation) markers added by the v1.8.2 wiring for in-VM marker seeding via WinRM/shared-folder + VSS-quiesced source comparison + the existing reference-checkpoint failure mode now exercised by the chain-promotion step.
v1.8.0
April 22, 2026New `backupengine selftest` CLI command for end-to-end smoke validation. v1.5.1 (Mac/Linux scheduler) and v1.6.0/v1.7.1 (Hyper-V CBT chains + chain promotion) shipped with TODO(mac-validation) / TODO(linux-validation) / TODO(hyperv-validation) markers because the dev environment was Windows-only without Hyper-V infra. Selftest gives a one-command tool to confirm those assumptions hold when you reach the right hardware.
`backupengine selftest encryption` (safe, no flags needed): exercises full Argon2id KDF + verifier round-trip, lock/unlock cycles, wrong-passphrase rejection, master-key indirection (encrypt under v1, change passphrase, decrypt under v2 — same DEK), and v1.7.0 DEK rotation through the lifecycle. ~30s on a temp dir, no real backend or system state touched.
`backupengine selftest service --confirm` (DESTRUCTIVE): install/probe/start/stop/uninstall round-trip on the platform service. Real launchd LaunchAgent on Mac, real systemd --user unit on Linux, real Scheduled Task on Windows. Requires --confirm. Fail-fast if the service is already installed (use --force to overwrite).
`backupengine selftest hyperv --vm <name> --confirm` (DESTRUCTIVE): scaffold for the Hyper-V backup → incremental → chain promotion → restore → byte-compare round-trip. v1.8.0 ships with the orchestrator + checkpoint + cleanup harness (real); the actual backup-set construction steps are SKIP-marked pending stable backend client wiring. The TODO(hyperv-validation) markers in the runners stay relevant.
`backupengine selftest --all`: chains all three suites with appropriate skip semantics (Hyper-V skipped if not detected on the host). Useful for a comprehensive validation pass after deployment.
Output formats: plain text (default) shows [PASS]/[FAIL]/[SKIP] per step with timing; --json emits structured output for CI consumption. Exit codes: 0=all pass, 1=any fail, 2=any skip without fail.
Cleanup is LIFO via accumulated cleanup tasks: every side-effect (e.g. checkpoint creation) pushes a teardown closure, and a finally block drains them in reverse order regardless of partial failure. Even if a later step throws, prior cleanup runs.
18 new unit tests covering encryption end-to-end, dispatch routing, arg parsing, exit-code semantics. Stub-based; real Argon2id derivation included so the test suite gets ~22s slower but exercises real crypto. Suite: 299/300 pass (only pre-existing widget_test.dart MyApp failure remains).
v1.7.1
April 22, 2026Hyper-V chain promotion (synthetic full): when an incremental chain hits its length cap, instead of re-uploading the entire live VHDX as a fresh full, BackupEngine now reassembles the existing chain locally (full + N diffs) and re-streams it as a new "synthetic full" manifest. Every chunk that's already in cloud storage gets matched by hash and deduped — typically zero new bytes uploaded. For a 100 GB VM with weekly chain rolls, this saves ~5 TB/year of bandwidth.
Wizard control: a new "Synthetic full when chain reaches cap (recommended)" toggle in the Hyper-V wizard's Schedule step. Default ON for new sets. Off reverts to v1.6.0 behavior (live-VHDX full at chain cap).
Refactor: chain reassembly extracted from the restore runner into a shared HypervChainReassembler. Both restore (single VHDX out to user-picked folder) and chain promotion (single VHDX out to temp dir, then re-streamed) call the same well-tested code. Restore behavior is byte-identical for legacy single-full chains.
Reference checkpoint after promotion: a fresh BackupEngine-Reference-<setId>-synthetic-<ts> checkpoint is created on the host so the next incremental can chain off the synthetic full. If the VM was deleted between reassembly and checkpoint creation, the manifest is still written but flagged "no incrementals possible until next full".
7 new tests (4 reassembler unit + 3 backup-runner integration covering synthetic_full=true zero-byte re-upload via dedup, synthetic_full=false fallback to live-VHDX, and chain-walk failure → live-VHDX downgrade). Suite: 281/281 application tests pass + only the pre-existing widget_test.dart MyApp issue remains.
Three new TODO(hyperv-validation) markers (Merge-VHD ParentPath header preservation across chunked streams, VM-deleted-mid-promotion failure path for Checkpoint-VM, FastCDC-determinism assumption for the dedup-zero-bytes claim). Recommend smoke pass on real Hyper-V before tagging GA.
v1.7.0
April 22, 2026DEK rotation: every chunk + manifest body in every backup set can now be re-encrypted under a fresh Data Encryption Key. Previously the DEK was generated once at setPassphrase and stayed constant forever — a process-memory leak or side-channel compromise of the unlocked DEK exposed every chunk ever encrypted. v1.7.0 lets you periodically rotate the DEK to limit blast radius. After rotation, an attacker who captured the OLD DEK can no longer decrypt anything new.
Triple-confirmed UX (Settings → Security → "Rotate Encryption Key (Advanced)"): warning + checkbox → interruption-acknowledgement + checkbox → type ROTATE to confirm. Then a full-page progress screen shows current manifest / total manifests + abort button. Rotating a large backup set can take hours and consumes significant bandwidth — this is a deliberate maintenance action, not a casual one.
Resumable: a new SQLite table (dek_rotation_progress) durably records every manifest's rotation state. If the rotation is interrupted (crash, network drop, intentional abort), reopening the screen offers Resume which picks up where it left off — no manifest is re-rotated unnecessarily. Plan() seeds rows pessimistically; rotate() walks them in deterministic storage-key order.
Atomic per-manifest swap with dual-DEK transition state. While rotation is in flight, every manifest carries BOTH the OLD wrapped DEK (in a new previous_master_key_envelope field) AND the NEW one. The restore-side resolveActiveDek probe tries the current DEK first and falls back to the previous, so a mid-rotation crash leaves all manifests restorable in either state. After cleanup the previous_master_key_envelope is dropped.
Schema bumps: LocalDb v11→v12 (dek_rotation_progress table), manifest field previous_master_key_envelope (optional, present only mid-rotation). Backward-compat for any manifest without the field is byte-identical to v1.6.x behavior.
Constant-time DEK comparison in EncryptionKeyService to prevent timing-side-channel leaks during rotation state checks. abort() reverts the active key but does NOT rewrite already-migrated chunks — the dual-envelope makes restore work in either direction so storage-level rollback is unnecessary. Rotation is purely additive at the storage layer.
13 new tests across resolveActiveDek probe, manifest field round-trip, EncryptionKeyService rotation lifecycle, DekRotationService rotateOnce + resumability + abort. Suite: 274 pass / 1 pre-existing widget_test.dart MyApp failure unchanged.
v1.6.2
April 22, 2026Inline cross-machine encryption import in the Restore flow. v1.6.0 shipped a standalone Settings → Security → Import path; v1.6.2 catches the same case at the Restore button click. When you select a manifest from another machine and the local agent isn't keyed for it, a passphrase dialog pops immediately — no round-trip through Settings, no "you need to set up encryption first" snackbar. After successful import the restore continues with the freshly-imported key cached for the session (and persisted to disk for future restores under the same passphrase).
Wired into all 5 restore screens: file restore, SQL restore, Hyper-V restore, System State restore, BMR restore. Single shared helper (cross_machine_import_helper.dart) gates each runner so the UX is identical across workloads.
Aborted-vs-failed semantics: cancelling the dialog returns silently (no snackbar — the user backed out on purpose); a wrong passphrase or import error surfaces a clear snackbar with retry guidance. The local config stays untouched on failure (no half-imported state).
Helper accepts nullable manifestRaw — pre-v1.4.2 manifests with no rawJson at all are treated as legacy plaintext = proceed, which preserves back-compat for sets created before encryption shipped.
v1.6.1
April 22, 2026Manifest body encryption: closes the metadata leak that v1.4.2 → v1.6.0 left open. Today the chunk content is encrypted but the manifest itself is plaintext JSON, so anyone with backend access can see file count, file paths, VM/database names, sizes, and chunk-hash fingerprints. v1.6.1 wraps the manifest body in AES-256-GCM under the same DEK that encrypts chunks, so the server now sees only an opaque envelope: {encryption_version, kdf_params, master_key_envelope, manifest_body_encryption: "v1", encrypted_body: "..."}.
Three-way back-compat preserved: legacy plaintext manifests (pre-v1.4.2), v1.4.2 → v1.6.0 manifests (encrypted chunks, plaintext body), and v1.6.1+ manifests (encrypted chunks AND encrypted body) all restore correctly. The manifest-fetch helper detects the wire format from the outer JSON and unwraps when needed.
No new key, no new KDF round, no new envelope: body encryption uses the SAME DEK that encrypts chunks. The manifest_body_encryption marker is a wire-format concern only — once the agent has the key (via local config or v1.6.0 cross-machine import), it has everything it needs to decrypt both body and chunks.
Forward-compat refusal: unknown manifest_body_encryption values (e.g., a future "v2" written by a newer agent) throw rather than silently treating the wrapper as plaintext. Older agents fail loudly instead of corrupting a restore.
22 new tests: 20 unit (round-trip wrap/unwrap, state-read for all three wire formats, malformed envelope rejection, wrong-key rejection, forward-compat refusal) + 2 integration (real_backup + sql_backup end-to-end produce body-encrypted manifests with only the 5 outer keys visible). Suite: 261/262 pass (only pre-existing widget_test.dart MyApp failure remains).
v1.6.0
April 22, 2026Hyper-V differential backup + chain restore (CODE-COMPLETE; runtime validation pending). Today the agent walks through full → diff1 → diff2 → … → leaf manifests via parent_manifest_id pointers. The wizard's new "Backup type" step under Schedule lets you pick between full-only and incremental chain (default 1 full + 6 daily incrementals before a new chain starts). Each incremental captures only the regions of the VHDX that changed since the previous reference checkpoint. Restore validates the chain up front (same source set, same VMs/disks, contiguous reference checkpoints, encryption gate per entry) before reassembling the base VHDX from the full + applying each diff via PowerShell Merge-VHD in order.
Master-key indirection (DEK wrapped under a passphrase-derived KEK). Today's passphrase rotation no longer orphans existing chunks: changing your passphrase derives a new KEK that re-wraps the SAME DEK, so chunks encrypted before the rotation continue to decrypt with the new passphrase. Manifest schema bumped to encryption_version=2 with a master_key_envelope block; v1.4.2 manifests (encryption_version=1, direct passphrase derivation) keep restoring under the legacy path for full back-compat.
Cross-machine encryption import. Settings → Security → "Import Encryption from Manifest" lets a fresh agent import another machine's encryption setup by pointing at one of its v2 manifests + entering the source-machine passphrase. The agent unwraps the master key from the manifest envelope and persists the local config — subsequent backups + restores under the same passphrase work without ever touching the source machine.
Hyper-V manifest schema v3 (chain-aware): backup_kind ("full" | "incremental"), parent_manifest_id pointer, reference_checkpoint name, chain_index (0 for full, 1+ for each diff). Older v2 manifests without backup_kind are treated as full for back-compat. Hyper-V chain restore inherits the v1.4.2 per-entry encryption gate, so a chain mixing pre-v1.4.2 plaintext with v1.4.2 / v1.6.0 encrypted manifests restores correctly.
Passphrase rotation no longer requires multi-paragraph "your data will be unrecoverable" warnings — under master-key indirection the warning becomes a one-liner because the DEK is preserved across rotation. The Change Passphrase flow drops the confirm-checkbox.
Backup-runner integration: every encrypted backup written by v1.6.0+ embeds master_key_envelope + kdf_params in its manifest. The agent that wrote the backup AND any agent that imports the encryption setup can decrypt it. Today's scheme rotates KEKs without rotating DEKs; a future v1.x release adds DEK rotation (re-encrypt all chunks under a fresh DEK) for periodic key hygiene.
35 new tests: 14 Hyper-V (7 backup + 7 restore covering full/incremental fallbacks, single-entry/legacy/chain-7/VM-mismatch/contiguity-break/in-order/mixed-encryption), 21 encryption (11 master-key + 10 manifest-schema) — all stub PowerShell + file IO so they run on Windows without Hyper-V infra. Suite: 239/240 pass (only the pre-existing widget_test.dart MyApp issue remains).
UNVERIFIED on Hyper-V infra: three TODO(hyperv-validation) markers in source flag specific assumptions — long-lived BackupEngine-Reference-* checkpoint vs. unbounded VHDX delta growth on running VMs (hyperv_backup_runner.dart:354), Export-VMSnapshot vs. alternative diff-export verbs (hyperv_backup_runner.dart:683), Merge-VHD acceptance of our diff VHDX layout (hyperv_restore_runner.dart:643). Recommended: smoke pass on a real Hyper-V Server before tagging GA.
v1.5.1
April 22, 2026Mac and Linux background scheduler parity (CODE-COMPLETE; runtime validation pending). ServiceManager refactored from Windows-only schtasks into a platform-dispatching facade with three impls: WindowsServiceManager (existing), MacServiceManager (launchd LaunchAgent at ~/Library/LaunchAgents/net.techbench.backupengine.plist with RunAtLoad + KeepAlive, modern bootstrap/bootout with legacy load/unload fallback), LinuxServiceManager (systemd --user unit at ~/.config/systemd/user/backup-engine.service with Restart=always). Per-user on both platforms — install does NOT require sudo.
Public ServiceManager API is byte-identical to v1.5.0 — every consumer (Schedule screen banner, Settings install button, AppState lifecycle, CLI service subcommand) keeps working unchanged. The platform dispatch lives entirely inside the facade.
Stubbable shell-out runners on every platform (mirrors the v1.5.0 AutostartService pattern). 27 new tests cover install/probe/start/stop/uninstall round-trips on both Mac and Linux — all run on Windows because every shell-out is mocked.
Hardcoded full paths for platform binaries (/bin/launchctl on Mac, /usr/bin/systemctl with /bin/systemctl fallback on Linux). Prevents PATH-injection attacks the same way Windows runners use System32 absolute paths.
Server-class workloads (Hyper-V, SQL, BMR, System State) remain Windows-only by Windows API constraint — those wizards stay hidden in the UI on Mac/Linux. Mac/Linux ship file-backup parity only.
Honesty disclaimer: this release was authored on a Windows-only dev environment. Code analyzes clean, all stubbed unit tests pass, and the spec follows the launchd / systemd documentation. Six specific assumptions (launchctl print exit codes, bootstrap-vs-load fallback chain, systemctl is-active semantics, StandardOutput=append: requires systemd ≥ 240, loginctl enable-linger surfacing, kickstart -k availability on minimum-supported macOS) are flagged with TODO(mac-validation)/TODO(linux-validation) markers in source. A follow-up smoke pass on real Mac and Linux hosts is recommended before tagging v1.5.1 GA.
v1.5.0
April 22, 2026Closes the closed-GUI silent-toast gap. Previously, if the GUI was fully quit (not minimized to tray), scheduled backups fired by the background service ran silently — no Windows toast, no animated tray icon, no visible cue. Now, "Start in tray at user login" auto-launches Backup Engine in tray-only mode at every Windows login (per-user HKCU\Run registry entry, no admin prompt). The tray icon picks up service-initiated backups via the existing cross-process job poll, so toasts and animation work whether the user has the app open or not.
New `--tray-only` CLI flag: same as GUI mode but starts hidden — only the system tray icon appears. Right-click the tray icon to open the window. The "Start in tray at user login" toggle in Settings → Application enables it. Lifts the previous "Start on system boot" preference from a dead toggle into a real registry-backed setting.
AutostartService: shells out to reg.exe (no new dep) to manage the HKCU\Software\Microsoft\Windows\CurrentVersion\Run entry. probe() returns notRegistered / registered / registeredStale (so the UI can detect when an old install path is referenced after the user reinstalls). Idempotent — re-toggling on never accumulates duplicate entries.
Per-user-only registration. Other "start at login" implementations sometimes use HKLM (admin-required, system-wide) — we deliberately use HKCU so a malicious or accidental click can't enable autostart for other users on shared workstations.
8 unit tests cover AutostartService probe, enable, disable, and stale-path detection. The "value not found" branch on disable is treated as success (already-absent = idempotent). Tests stub reg.exe so the real registry is never touched.
v1.4.2
April 22, 2026Zero-knowledge backup encryption goes live: every chunk uploaded to every destination is now encrypted with AES-256-GCM under a key derived from your passphrase via Argon2id (OWASP 2024 baseline — m=64 MiB, t=3, p=1, 32-byte key). The server stores only ciphertext + a manifest fragment containing the KDF salt, so even with full backend access, your data cannot be read without your passphrase. Wires into ALL five backup runners (file, Hyper-V, SQL, System State, BMR) and ALL five restore runners.
Settings → Security → Backup Encryption: status banner (Unlocked / Locked / Not configured), Set Passphrase / Unlock / Change Passphrase / Lock Now actions, plus a triple-confirmed Reset Encryption (Dangerous) action that requires typing the word "RESET" before completing. Lock Now also fires automatically on app quit so the in-memory key never survives the GUI process.
First-backup encryption prompt: when you create your first backup set without encryption configured, a one-time modal explains the zero-knowledge trade-off (only you can read your data; forgetting the passphrase = data loss) and offers Configure Encryption / Skip (insecure). The "Skip" choice is recorded so you don't get nagged on every subsequent set; the "Configure" choice routes you to the Security panel and aborts the current backup so you can re-trigger it under encryption.
Pre-flight check on the Backup Now button: if encryption is configured but locked, the runner refuses to start and shows a "Go to Settings" dialog. Backup runners refuse on the back end too — fail-fast policy means a locked encryption state can never silently fall back to plaintext upload.
Manifest schema v1.4.2: every encrypted manifest carries `encryption_version: 1` plus a `kdf_params` block with the salt + Argon2id tunables. Cross-machine restore reads these to know what KDF produced the key (today's MVP requires the local agent to be unlocked with the same passphrase; a future release lets you import the encryption setup from a manifest).
Backward compatibility: backups captured before v1.4.2 (no encryption fields on the manifest) still restore as plaintext. New backups are encrypted whenever a passphrase is set; the two formats coexist within the same backup set without re-encrypting historical chunks.
SQL chain restore inherits the per-entry gate: a chain mixing pre-v1.4.2 manifests with v1.4.2 encrypted manifests restores correctly — each entry is classified independently and the right decryptor is used per RESTORE statement.
Passphrase rotation re-derives the key, which means existing chunks encrypted under the old passphrase become unrecoverable. The Change Passphrase flow shows a multi-line warning + confirm-checkbox before completing. (A "wrap the master key under the passphrase" design that lets passphrase rotation NOT break old chunks is tracked as a v1.4.x follow-up.)
37 new tests across 4 phases lock down the contract: 13 EncryptionKeyService tests (KDF, verifier round-trip, lock semantics, rotation), 4 backup-runner fail-fast tests, 12 restore-runner tests (legacy back-compat + locked-service refusals + round-trips + malformed-manifest rejection), 8 PassphraseDialog widget tests. 169/170 desktop tests pass (only the pre-existing widget_test.dart MyApp issue is unchanged).
v1.4.1
April 22, 2026AesGcmEncryptor round-trip bug in backup-core: the manual processBytes+doFinal pattern over-allocated to a block boundary and then incorrectly sliced 16 bytes off the result, so any encrypt-then-decrypt of a non-block-aligned payload returned 16 bytes short (or, for sub-block payloads, failed GCM auth verification entirely). Replaced with cipher.process() which handles tag append/verify internally and returns a buffer sized to the exact output. Fix matches the workaround already in place in the v1.4.0 SQL credential vault.
backup-core ships its first test suite: 5 round-trip tests against AesGcmEncryptor (1 byte, 16 bytes, 1 KB, 64 KB, and tampered-ciphertext rejection) — all green. Catches the regression class above and locks down GCM auth-tag behavior for future changes.
No production cloud data is at risk. Today's chunk pipeline currently writes chunks gzip-only (encryption is opt-in via StreamingBackupPipeline's encryptor parameter, not yet wired into the backup wizards). The encryptor was used in the AI-asset and admin-directive restore paths — both correctly rejected truncated chunks via SHA-256 hash verification on round-trip, so no silently corrupted data shipped. Wiring chunk encryption into the main backup wizards is tracked as a separate roadmap item.
v1.4.0
April 22, 2026SQL credential vault: SQL Server Authentication is now first-class. Wizard "Authentication" step lets you pick Windows or SQL auth, enter username + password (obscured + show/hide toggle), and "Test connection" runs SELECT 1 before save. Credentials encrypt with AES-256-GCM (per-device salt + hostname-bound key) and live at %APPDATA%\net.techbench.backupengine\Backup Engine\credentials\vault.enc. Backup sets store only the credential ID — never the password. Settings → Security → Manage SQL Credentials lets you list, edit, and delete saved credentials.
SQL chain restore: pick a "restore point" in the SQL Restore screen and the runner walks the full backup chain — full → diff → log — issuing RESTORE … WITH NORECOVERY for every entry except the last, then RECOVERY on the tail. Optional STOPAT picker for point-in-time log restore. Validates up front that every entry in the chain shares the same source instance and the same database set, so a typoed selection fails before the first sqlcmd call.
SQL WITH MOVE for cross-machine restore: "Advanced: file moves" panel queries RESTORE FILELISTONLY on the source .bak, lets you remap each logical data/log file to a new physical path on the target machine, and emits MOVE clauses in the generated T-SQL. Defensive validation rejects parent-directory traversal, non-data-file extensions (.exe, .dll, etc.), and logical names that don't appear in FILELISTONLY output (typo guard).
sqlcmd argument logger now redacts -P <password> to -P *** in diagnostic logs and console traces — passwords from the new SQL auth flow never hit disk in plaintext anywhere.
WITH MOVE physical-path validator enforces drive-letter prefix + .mdf/.ldf/.ndf extension + no NUL/wildcard chars. Logical-name mismatches between user input and FILELISTONLY are silently dropped (with a warning in the log) rather than emitted as MOVE clauses that would fail at restore time.
v1.3.3
April 22, 2026System State backup + restore runners. Wizard saves were inert since v1.0.9; the backup runner now invokes wbadmin start systemstatebackup, captures the WindowsImageBackup tree (boot files, registry, AD, IIS, COM+, certificates), streams it through FastCDC + chunked upload. Restore reassembles the tree and invokes wbadmin start systemstaterecovery (DSRM warning + reboot prompt for AD-bearing manifests).
Bare-Metal Recovery backup + restore runners. Backup runner invokes wbadmin start backup -allCritical -vssFull, streams the produced .vhdx images. Restore is a "create recovery image" workflow — reassembles to a user-picked external drive + drops RESTORE_INSTRUCTIONS.txt with the exact wbadmin sysrecovery commands the user runs from Windows Recovery Environment.
Server Backup tab now executes ALL four workloads (Hyper-V, SQL, System State, BMR) end-to-end. Wizards no longer save sets that just sit there; every backup type runs, and three of four (Hyper-V, SQL, System State) restore in-place. BMR uses the offline-restore pattern by Windows constraint.
CLI backup start no longer prints "runner not yet available" for ANY backup type — every wizard's output is now a real, executable backup set.
v1.3.2
April 22, 2026Chunk garbage collection: v1.3.0 prunes manifests; v1.3.2 sweeps the orphan chunks those pruned manifests left behind. After every successful backup, ChunkGarbageCollector reads every remaining manifest, computes the union of referenced chunks across file/Hyper-V/SQL types, and deletes any chunk no longer referenced. Defensive: an unreadable manifest pauses GC for that round (better to over-keep than over-delete).
Storage cost from prior backup cycles now actually goes down over time. Combined with v1.3.0's manifest pruning, retention behavior is finally complete: keep N manifests, garbage-collect everything else.
v1.3.1
April 22, 2026AI Assets wizard wiring: the AI Assets screen has been informational since v1.0.5. Toggles now create a real backup set — pick which AI assets to protect (Claude Code transcripts, Cursor caches, HuggingFace models, Ollama, LM Studio, MLflow, W&B, etc.), choose destinations + schedule + review, and the existing file backup pipeline takes over with multi-destination fan-out, retention pruning, and diagnostic logs.
AI Assets screen status banner: shows green "AI Backup configured · runs {schedule} to {destinations}" with Edit / Backup Now / Delete actions when set up; amber "Configure AI Backup" CTA when not.
v1.3.0
April 22, 2026SQL Server restore engine: backup ships in v1.2.0, restore lands now. Pick any SQL manifest in the Restore tab → database checklist → optional target instance + Replace Existing toggle → reassembles each .bak from chunks (decompress + SHA-256 verify per chunk) → calls native RESTORE DATABASE/LOG via sqlcmd. Per-database fault isolation; one DB failing doesn't skip the others.
Configurable retention pruning: wizards have collected a "retain N versions" value since v1.0.9 — orchestration now actually prunes. After every successful run, manifests beyond the keep limit are deleted from each destination. Stale chunks are intentionally NOT deleted yet (chunks are content-addressed and may be referenced by multiple manifests; safe deletion needs a reference-count pass shipping in a follow-up).
ManifestInfo.totalBytes / fileCount now handle SQL manifests too (sum databases[].size_bytes / databases[].chunk_count). Restore source-card line shows "N databases · M GB" for SQL manifests instead of the file-tree default.
v1.2.0
April 22, 2026SQL Server runner: backup wizards have been able to save SQL sets since v1.0.9, but the runner finally executes them. Uses sqlcmd + native BACKUP DATABASE/LOG with COMPRESSION + CHECKSUM. Streams the .bak through the existing FastCDC + dedup pipeline. Per-database fault isolation — one DB failing doesn't skip the others.
Multi-destination reporting end-to-end: backup reports now carry a destinations array (one entry per destination with per-entry status, bytes uploaded, and error message). Admin and customer portals render the array as status-colored chips. Backfill is automatic — older single-destination reports show as 1-element arrays.
Service-aware toast notifications: when the GUI is alive (visible or in tray) and the background service starts/completes a scheduled backup, you get a Windows toast — "Scheduled backup started: My Set" / "completed" / "failed". Closes the visibility gap from v1.1.3 where the tray icon animated but no toast told you why.
Supabase schema v1.2.0: backup_reports.destinations JSONB column with GIN index on the destinations array — supports admin queries like "show me every report that wrote to OneDrive" without table scans.
Edge Function send-backup-report accepts both shapes: new clients send the destinations array; older app builds (pre-v1.2.0) keep working via destination_label fallback that auto-synthesizes a 1-element array on the server side.
v1.1.4
April 22, 2026CLI: new `connectivity test` command runs an end-to-end iDrive PUT/GET/verify/DELETE round-trip and prints SUCCESS/FAIL + latency_ms + timestamp. Returns non-zero exit code on failure for cron-driven monitoring.
CLI: new `logs list [setId]` and `logs show <path>` commands surface per-run diagnostic logs from the command line. Pipes cleanly into jq via `--json` so support workflows can grep, sort, and forward without opening the GUI.
`sets list` now renders all destinations on a multi-destination backup set instead of silently showing only the first. Catches up the human-readable output to match the JSON output, which already included the full destinations array.
Hyper-V backup runner's checkpoint create/remove PowerShell calls now use absolute System32 paths and explicit UTF-8 encoding — same hardening as the detection service. Eliminates a class of false-failure modes when Windows Server PATH is non-standard.
New KB articles: Multi-Destination Backup, Background Service, Diagnostic Logs, Restore Hyper-V VMs. FAQ expanded with 8 entries covering the v1.0.9 → v1.1.3 features.
v1.1.3
April 22, 2026Tray icon now animates when the background service runs a scheduled backup, not just when the GUI launches one. Drives off a 5-second cross-process poll of the backup_jobs table — when the GUI is in the tray and the service starts a backup, the icon picks it up within a few seconds.
Tray tooltip distinguishes service-initiated work: "Service is running 1 backup" / "3 backups running (2 in service)" / "1 backup running" depending on which process is doing the work.
AppState.hasActiveJobs now reflects ANY in-flight backup (GUI or service), so the dashboard "backup running" indicators and the close-guard dialog react correctly when the service is mid-backup.
v1.1.2
April 22, 2026Schedule screen now shows a clear status banner: green when the background service is installed and running (schedules will fire when the app is closed), red when installed-but-stopped (with one-click Start), or amber when not installed (with one-click Install — UAC prompt).
Closing the window with scheduled backups but no background service installed now prompts: Install Service, Don't Ask Again, or Stay Open. Removes the silent failure mode where users assumed schedules would keep running after closing the GUI.
AppState now caches and exposes the live ServiceStatus so any surface (Settings, Schedule, close-guard) reads the same source — refreshes after install/start/stop so the UI reflects current state without a relaunch.
v1.1.1
April 22, 2026Closing the window during a backup now prompts for an action: Minimize to tray (backup keeps running), Keep open, or Quit anyway. With no backup running the window hides to the tray instead of killing the process — click the tray icon to bring it back.
System tray icon plays an animated logo whenever a backup is running, idle icon when nothing is in flight. Hover tooltip shows "Backup Engine — N backups running" so you can check status from the corner of your screen.
Re-enabled the close interception that was disabled in v1.0.7 — the existing 3-choice dialog and tray-animation code were already wired but inert because the close event was never being intercepted.
v1.1.0
April 22, 2026Multi-destination backup sets: a backup can now fan out to BackupEngine Cloud + Local PC + Network PC + Google Drive + OneDrive simultaneously. Each destination produces its own independent restore point — classic 3-2-1 strategy out of the box.
Wizards (file, server: Hyper-V/SQL/BMR/System State) all share a multi-select destination step with per-entry path field, Browse button, auto-detect of cloud sync folders, and a fan-out summary line so you see exactly where your data is going before clicking Save.
Restore screen now lists each (backup set, destination) pair as its own card. Pick which destination to restore FROM — local for speed, cloud for offsite, etc.
Fault-isolated execution: if cloud is down, the local copy still completes. Job result rolls up across destinations: success only when every destination succeeds; failures list which destination(s) failed and why.
Database schema migration v10 → v11 adds a destinations column with full back-compat. Existing single-destination sets keep working unchanged; they appear as 1-element lists in the new model.
v1.0.9
April 22, 2026Hyper-V VM restore: Restore screen now branches on backup type — Hyper-V manifests open a dedicated VM-image restore wizard that reassembles each VHD from chunks and writes a RESTORE_INFO.txt with the Add-VM/Add-VMHardDiskDrive snippets to re-attach the VM
iDrive cloud connectivity test: new Cloud Storage section in Settings + Cloud Storage row in About show last test result with latency, status, and timestamp + a refresh icon for on-demand re-testing
About screen: dedicated page with build info, OS info, cloud connectivity, support links, and legal section
Per-run diagnostic logs: every backup run writes a timestamped log to %APPDATA%\Backup Engine\diagnostic-runs\ capturing PowerShell commands, chunk hashes, backend put/get latency, and errors. "View diagnostic log" button on each backup set card opens it in Notepad
Hyper-V backup runner now writes manifests at the same .../sets/{id}/manifests/{ts}.json path file backups use, so the Restore screen can actually find them (was previously writing to .../hyperv/manifests/ which Restore never scanned)
Hyper-V manifest v2 schema: each disk entry now carries the full ordered chunk_hashes list + storage_prefix so VM restore can fully reassemble the VHD without consulting the live host
Hyper-V disk detection now uses Get-VM | Select -ExpandProperty HardDrives + Get-Item for sizes — works for users with Get-VM access who lack Hyper-V Administrators (Get-VHD elevation). Eliminates "0 disks · 0 B" false negatives
Manifest write failure now propagates to the runner result — previously a failed manifest upload silently reported success against zero restorable chunks
Path traversal hardening on VM restore: malicious manifest disk file_name / storage_prefix values can no longer escape the restore folder or inject backend keys
Per-chunk SHA-256 verification on Hyper-V restore catches backend bit-rot, MITM tampering, and cross-tenant key collisions
Destination wizard now offers a Browse button for Local PC, Network PC, Google Drive (auto-detects sync folder), and OneDrive (reads %OneDrive% env var)
Server backup wizards show an animated sonar-pulse scanning placeholder during the initial probe (Hyper-V VM enumeration, SQL instance discovery, BMR volume scan, System State role detection)
Detection services for Hyper-V / SQL / BMR / System State now use absolute System32 paths and explicit UTF-8 stdout encoding — fixes false negatives on configurations where PATH was unusual or PowerShell defaulted to UTF-16
Version footer at the bottom of the login screen so support can ask "what version are you on?" at a glance
v1.0.8
April 21, 2026Missing-runtime warnings now show the download link as selectable monospace text with a copy-to-clipboard button — applies to WebView2 today and any future bundled-dependency prompts
v1.0.7
April 21, 2026Window close (X) button now actually closes the app — the previous build intercepted the event without consuming it, so X did nothing
Sign-in security check shows a clear "install Microsoft Edge WebView2" message on Windows Server 2019 and older Win10 builds where the runtime is missing (was previously a cryptic environment_creation_failed error)
Portable ZIP bundles the Visual C++ Redistributable runtime so the agent launches on Windows Server 2019 without a separate redist install
v1.0.6
April 21, 2026Server Backup hub: clickable cards for Hyper-V, SQL Server, Bare-Metal Recovery, and System State, each opening a multi-step wizard that creates a scheduled backup set
Hyper-V wizard: VM checklist, application-aware VSS toggle, retention, destination, schedule, review
SQL Server wizard: instance auto-discovery (Windows authentication in v1), database selection (all user DBs or named list), Full / Differential / Transaction-log backup type
System State wizard: components surface (AD, registry, boot, COM+, IIS, certificates), atomic capture via wbadmin
Bare-Metal Recovery wizard: volume picker (system+boot pre-selected, data volumes opt-in), dissimilar-hardware restore notes
Embedded Cloudflare Turnstile in the desktop login (WebView2 over /embed/captcha) — restores login under Supabase CAPTCHA enforcement
OAuth (Google / Microsoft / Apple) on website + portal login
Password recovery via /reset-password — verifies recovery session, signs out after update so the recovery session can't be reused
Profiles now hydrate full_name + avatar_url from OAuth metadata; portal + admin sidebars show user card
CLI: sets list shows Type column with per-type config; backup start dispatches by backupType
Plan label: login pulls profiles.plan_id joined with plans(name) so upgraded accounts see "Server Edition" instead of "Free"
v1.0.5
April 20, 2026AI Infra Backup tier ($395/month, 10 TB, 20 devices) — back up vector databases, AI models, prompt libraries, notebooks, and agent transcripts
Restore Assistant: natural-language restore via Anthropic Claude with read-only / dry-run / write tools and a directive processor
Configurable heartbeat interval (default 60 minutes) with manual "Sync now" refresh
Cloudflare Turnstile bot protection on signup, login, and forgot-password
AI Assets tab moved into Backup section alongside Files and Server Backup
Concurrent backup jobs with per-set progress cards
Backup and restore honor selected destination (cloud vs local)
v1.0.3
March 28, 2026Added team member management with role-based permissions (Owner, Admin, Operator, Viewer)
Added notification email management — configure multiple recipients for backup reports
Added billing & subscription management in desktop agent (upgrade, add-ons, team seats)
CLI: added users, billing, and notifications command groups
Customer Portal: Team Members page with invite, role assignment, and custom permissions
Admin Portal: 19 pages including Revenue, Invoices, Payments, A/R, A/P, Reports, Forecasting, Affiliates, Partners, Staff Management
Fixed SQLite FFI initialization on Windows desktop agent
Fixed navigation rail alignment — menu and content now top-aligned
v1.0.2
March 15, 2026Customer Portal with 10 pages: Dashboard, Devices, File Browser, Restore, Activity, Billing, Settings, Download
Admin Control Center with Dashboard, Users, Devices, Backup Jobs, Storage, Billing, Audit Logs, Settings
Desktop agent: 8 screens with full backup/restore workflow
RBAC system with account-level roles and 20 granular permissions
Fixed Tailwind CSS not loading in Customer Portal (missing PostCSS config)
Fixed locale routing — added root redirect to /en for Portal and Admin
v1.0.1
March 1, 2026Company website redesigned with professional UI, animations, and full content
Pricing page with desktop/mobile plan tabs, add-ons, and FAQ
Features page with core technology, personal, server, and management sections
Download page with platform-specific agents and quick start guide
Multi-language support: English, French, Spanish
v1.0.0
February 15, 2026Initial release — BackupEngine MVP
FastCDC content-defined chunking engine (2-64 KB variable chunks)
AES-256-GCM client-side encryption with Argon2id key derivation
Gzip compression with adaptive skip
Backup and restore pipelines with deduplication
Supabase backend: 14 Edge Functions, 4 database migrations
Desktop agent scaffold: CLI + GUI dual-mode
Row-level security policies on all database tables