fix(THU-637): version-gate models reconcile + OTA + THU-645 lineup#1049
Conversation
Semgrep Security ScanNo security issues found. |
PR Metrics
Updated Mon, 06 Jul 2026 21:24:38 GMT · run #2202 |
There was a problem hiding this comment.
🔭 thunder-deep-review (advisory)
Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: 5d91ec025bcc · mode: single · deferred 3 item(s) already reported by other bots (best-effort dedup)
There was a problem hiding this comment.
🔭 thunder-deep-review (advisory)
Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: d450d47cf6bd · mode: deep · deferred 4 item(s) already reported by other bots (best-effort dedup)
Additional notes (couldn't anchor to a diff line)
src/lib/reconcile-defaults.ts— 🚫 Blocking — Editing a model-profile default silently won't propagate unless someone bumps the models version
866d552 to
6175532
Compare
There was a problem hiding this comment.
🔭 thunder-deep-review (advisory)
Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: 6175532ac300 · mode: deep · deferred 9 item(s) already reported by other bots (best-effort dedup)
There was a problem hiding this comment.
🔭 thunder-deep-review (advisory)
Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: be630ff76ebf · mode: deep · deferred 10 item(s) already reported by other bots (best-effort dedup)
There was a problem hiding this comment.
🔭 thunder-deep-review (advisory)
Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: a50c71628cf2 · mode: deep · deferred 10 item(s) already reported by other bots (best-effort dedup)
…orce model compat
a50c716 to
107513d
Compare
|
Preview environment destroyed 🧹 Stack |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 107513d. Configure here.
There was a problem hiding this comment.
🔭 thunder-deep-review (advisory)
Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: 107513d0ee56 · mode: deep · deferred 11 item(s) already reported by other bots (best-effort dedup)

Fixes THU-637 (models flap) and lands THU-645 (new lineup) atomically
The two changes ship together because the version-gate introduced for THU-637 is the mechanism that makes the THU-645 lineup change safe against multi-device races.
The race — THU-637
modelsis a PowerSync-synced table, but the shipped defaults live in the client binary. Every launch,reconcileDefaultstreats rows whose storeddefaultHashmatches their current content as "unedited" and rewrites them with the local bundle's defaults. When two devices (or the same device across app updates) ship different defaults, they ping-pong via sync every boot: a V2 device writes V2 rows → a V1 device reconciles them back to V1 → V2 upgrades again → ...Even single-device:
waitForInitialSynctiming out (10 s cap) can drop reconcile back to local V1 while newer rows arrive post-reconcile — next launch downgrades.The fix — per-table monotonic version gate
Ship
defaultModelsVersionalongsidedefaultModels. Store the highest ever applied version in the already-syncedsettingsTableunderdefaults_version.models:canOverwrite = false(my bundle is older than what's already applied on this account) → skip in-place updates AND skipcleanupRemovedDefaults.canOverwrite = true→ normal reconcile, bump stored version at the end only when at least one row was actually written.Hash-based user-edit protection still runs alongside — user edits survive under both older-bundle and newer-bundle passes.
Only
models(and pairedmodel_profiles) are gated in this PR; other reconciled tables (modes, tasks, skills, settings) keep their current behavior viacanOverwrite: truedefaults. No new synced column — the version marker piggybacks on the existingsettingssync, so no PowerSync two-PR flow.Additional guards layered on top of the base gate:
/configas OTA channel for defaultsBackend
/confignow returns:Frontend picks between server and bundle by
max(version)with a payload-sanity guard (Number.isFinite(version) && Array.isArray(data) && data.length > 0).>(no wasted swap)./configviazustand/persist, or bundle on first-ever fresh install.The picker awaits the
/configfetch that was already fired at boot Step 0, bounded by its 5 s internal timeout. On cache-hit boots (persisted/configpresent) it uses the cached value immediately and lets the fetch resolve in the background.THU-645 — new model lineup
defaultModels).isConfidential1 → 0 on threads created encrypted, stranding them because the picker and send-guard enforceisEncrypted === isConfidential. Cleanup soft-deletes the Pro row instead, so encrypted threads bound to it surface as "model retired" rather than broken chats.deepseek-v4-flashadded to backendsupportedModels, routed to Fireworks.defaultModelsVersionbumped 1 → 2. Snapshot test pinned to{ version: 2, hash: … }.Race guard in action for THU-645 rollout
stored=2.cleanupRemovedDefaultsdoesn't recognize Flash's id, so it soft-deletes Flash. Cloud now has Flash deleted.Without the resurrect branch, Flash would be permanently soft-deleted on any account touched by a lagging pre-THU-637 device —
stored=2plusdeletedAt-included hashing would make every future reconcile treat the row as user-edited.Version-bump discipline
A colocated snapshot test in
shared/defaults/models.test.tsfails on any change todefaultModelswithout a matching bump todefaultModelsVersion. AGENTS.md documents the convention.Also in this PR (ported from #1012)
fix(backend): keep pglite in-memory when DATABASE_URL is a connection string— prevents pglite tests from bootstrapping a spurious data dir when.envhasDATABASE_URL=postgresql://….fix(tauri): allow localhost:8080 in CSP connect-src— needed for the local dev backend on 8080.Supersedes #1044
PR #1044 shipped the THU-645 lineup change against the pre-THU-637 branch. Its open comments — pre-THU-637 cleanup deleting Flash across the account, retired Tinfoil ids on user-edited rows, count-based profile tests — are addressed here (race by version-gate + resurrect guard; test brittleness by the new structural profile↔model check). PR #1044 can be closed as superseded.
Test plan
bun tsc --noEmit: cleanbun run test:5x: 15,470 frontend + 440 shared, 0 fail across 5 iterationsbun run lint: 0 errorsdefaults_version.models = 2writtendefaults.models.versionwith a different content payload → confirm client adopts (OTA)Follow-up
Same version-gate +
/configtreatment formodes/tasks/skills/settings— pure repetition of the pattern established here. In-app "model retired" hint for user-edited retired rows (ital0's #2 on 1044) also for follow-up.Note
High Risk
Touches PowerSync-synced models reconciliation, multi-device convergence, and default model IDs/confidentiality—incorrect gating or OTA picking could wipe or flap user-visible models across accounts.
Overview
Stops multi-device models reconcile ping-pong (THU-637) by gating writes on a monotonic
defaults_version.modelsin synced settings and only advancing that marker when models/profiles actually change. Reconcile now takes authority flags (canOverwrite,canResurrect,insertMissing), skips mutations when initial sync is incomplete but the DB already has rows, can resurrect cleanup-shaped soft-deletes, and keeps model↔profile pairing (includinginsertMissingprofiles and dropping OTA-only models without bundled profiles).OTA: Default models move to
shared/defaults/models.tswithdefaultModelsVersion(now 2) and a snapshot test;GET /configexposesdefaults.models, the config store persists it, boot usespickModelsDefaults(sanity checks + bundle floor) before reconcile.Lineup (THU-645): Kimi retired; DeepSeek V4 Pro → V4 Flash on Thunderbolt/Fireworks under a new id (avoids flipping
isConfidentialon encrypted threads);deepseek-v4-flashadded to inference routing; profiles/tests updated.Also: shared
hashValues, PGlite ignores postgresDATABASE_URLfor in-memory tests, Tauri CSP allows localhost:8080, AGENTS.md documents version-bump discipline.Reviewed by Cursor Bugbot for commit 107513d. Bugbot is set up for automated code reviews on this repo. Configure here.