Add schema compatibility probe to prevent silent migration failures (bd-ckvw)

- Implement comprehensive schema probe in sqlite.New() that verifies all
  expected tables and columns after migrations
- Add retry logic: if probe fails, retry migrations once
- Return clear fatal error with missing schema elements if probe still fails
- Enhance daemon version gating: refuse RPC if client has newer minor version
- Improve checkVersionMismatch messaging: verify schema before claiming upgrade
- Add schema compatibility check to bd doctor command
- Add comprehensive tests for schema probing

This prevents the silent migration failure bug where:
1. Migrations fail silently
2. Database queries fail with 'no such column' errors
3. Import logic misinterprets as 'not found' and tries INSERT
4. Results in cryptic UNIQUE constraint errors

Fixes #262

Amp-Thread-ID: https://ampcode.com/threads/T-0d7ae2c0-9f12-4f9b-85d1-1291488af150
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Steve Yegge
2025-11-08 15:40:19 -08:00
parent 54702b59a2
commit f027de93b6
8 changed files with 460 additions and 7 deletions

View File

@@ -56,11 +56,23 @@ func (s *Server) checkVersionCompatibility(clientVersion string) error {
clientVersion, ServerVersion)
}
// Compare full versions - daemon should be >= client for backward compatibility
// Compare full versions - daemon must be >= client (bd-ckvw: strict minor version gating)
// This prevents stale daemons from serving requests with old schema assumptions
cmp := semver.Compare(serverVer, clientVer)
if cmp < 0 {
// Server is older than client within same major version - may be missing features
return fmt.Errorf("version mismatch: daemon %s is older than client %s. Upgrade and restart daemon: 'bd daemon --stop && bd daemon'",
// Server is older than client - refuse connection
// Extract minor versions for clearer error message
serverMinor := semver.MajorMinor(serverVer)
clientMinor := semver.MajorMinor(clientVer)
if serverMinor != clientMinor {
// Minor version mismatch - schema may be incompatible
return fmt.Errorf("version mismatch: client v%s requires daemon upgrade (daemon is v%s). The client may expect schema changes not present in this daemon version. Run: bd daemons killall",
clientVersion, ServerVersion)
}
// Patch version difference - usually safe but warn
return fmt.Errorf("version mismatch: daemon v%s is older than client v%s. Upgrade and restart daemon: bd daemons killall",
ServerVersion, clientVersion)
}

View File

@@ -323,7 +323,7 @@ func TestVersionCheckMessage(t *testing.T) {
serverVersion: testVersion100,
clientVersion: "1.1.0",
expectError: true,
errorContains: "daemon 1.0.0 is older than client 1.1.0",
errorContains: "client v1.1.0 requires daemon upgrade",
},
{
name: "Compatible versions",