package storage import ( "fmt" "strconv" "github.com/metazla/meta-core/internal/kv" ) // SchemaVersion is the key-layout version this build of meta-core expects. // Distinct from the snapshot manifest version (which is on-disk format); // this number describes the record layout — primarily, "are roots UUIDs or // content-hash tokens, and do reverse-index keys cid:* exist?" // // The Redis → SQLite move did not change the record layout (file:/, // cid:), so it did not bump this: the one-time migration copied records // verbatim. // // Bump only when a layout change can't be detected from the data itself or // healed by the write path. Each bump is a flag day in alpha: operators wipe // and restart. const SchemaVersion = 2 // SchemaVersionKey is the sentinel under which we record SchemaVersion. // Checked on every meta-core boot. const SchemaVersionKey = "meta-core:schema-version" // EnsureSchemaVersion verifies the store matches the expected schema version. // Called once at startup, before any other reads/writes. // // Behavior: // - sentinel present and matches expected → OK // - sentinel missing AND no roots → fresh store, stamp sentinel // - sentinel missing AND roots exist → legacy data, refuse // - sentinel present but mismatched → refuse, point operator at clean:all // // Refusal is hard-fail; the caller is expected to log.Fatalf and exit. // The error message names the wipe remediation because in alpha there // is no automated migration — operators bring up a clean store. func (c *Client) EnsureSchemaVersion(expected int) error { return c.update(func(tx *kv.Tx) error { ctx := tx.Context() have, ok, err := tx.Get(ctx, SchemaVersionKey) if err != nil { return fmt.Errorf("get schema version: %w", err) } if !ok { n, err := tx.Count(ctx, rootsNS) if err != nil { return fmt.Errorf("count roots: %w", err) } if n > 0 { return fmt.Errorf( "the store has %d indexed roots but no %q sentinel — this is alpha "+ "data from before the UUID-rooted schema landed. Delete the "+ "database file to wipe and restart, or downgrade meta-core to a compatible build", n, SchemaVersionKey) } return tx.Put(SchemaVersionKey, strconv.Itoa(expected)) } if have == strconv.Itoa(expected) { return nil } return fmt.Errorf( "store schema version is %q, this build of meta-core expects %d. "+ "Delete the database file to wipe and restart, or use a meta-core "+ "build that matches the existing data", have, expected) }) }