package storage import ( "context" "database/sql" "errors" "fmt" "strconv" "github.com/metazla/meta-core/internal/kv" ) // SchemaVersion is the truth-database layout this build of meta-core expects. // // 2 — v1.x: one kv table holding records, the CID index and UDL cells // 3 — v2.0.0: the claims model (truth: principal, claim, cid_group, meta) // // Bump only when a layout change can't be detected from the data itself. Each // bump is a flag day: there is no migration code in meta-core (the one-off // v2 → v3 conversion of watch.nsl.sh is a manual tool, tools/claims-migrate). const SchemaVersion = 3 // SchemaVersionKey is the truth `meta` row recording SchemaVersion. const SchemaVersionKey = "schema-version" // EnsureSchemaVersion verifies the truth database matches the expected // version. Called once at startup, before Start. // // - a v1.x store (the old kv table, which the default path still points at) // → refuse // - sentinel present and matching → OK // - sentinel missing and no claims → fresh store, stamp it // - sentinel missing with claims, or mismatched → refuse func (c *Client) EnsureSchemaVersion(expected int) error { c.mu.RLock() truth := c.truth c.mu.RUnlock() if truth == nil { return fmt.Errorf("not connected") } ctx, cancel := context.WithTimeout(context.Background(), opTimeout) defer cancel() return truth.Update(ctx, func(tx *kv.Tx) error { var name string err := tx.QueryRow(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'kv'`).Scan(&name) if err == nil { return fmt.Errorf( "%s holds a meta-core v1.x store (schema version 2, the kv table). v2.0.0 "+ "does not migrate it: convert it with tools/claims-migrate, or move it "+ "away to start empty", truth.Path()) } if !errors.Is(err, sql.ErrNoRows) { return err } var have string err = tx.QueryRow(`SELECT value FROM meta WHERE name = ?`, SchemaVersionKey).Scan(&have) if errors.Is(err, sql.ErrNoRows) { var n int64 if err := tx.QueryRow(`SELECT count(*) FROM claim`).Scan(&n); err != nil { return err } if n > 0 { return fmt.Errorf("truth database has %d claims but no %q sentinel; refusing to guess its layout", n, SchemaVersionKey) } _, err := tx.Exec(`INSERT INTO meta(name, value) VALUES(?, ?)`, SchemaVersionKey, strconv.Itoa(expected)) return err } if err != nil { return fmt.Errorf("get schema version: %w", err) } if have == strconv.Itoa(expected) { return nil } return fmt.Errorf("truth schema version is %q, this build of meta-core expects %d", have, expected) }) }