new meta-core api, now redis is not exposed as an "API"
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// SchemaVersion is the Redis-layout version this build of meta-core expects.
|
||||
// Distinct from the snapshot manifest version (which is on-disk format);
|
||||
// this number describes the in-memory Redis schema — primarily, "are roots
|
||||
// UUIDs or content-hash tokens, and do reverse-index keys cid:* exist?"
|
||||
//
|
||||
// Bump only when a Redis-layout change can't be detected from the data
|
||||
// itself or healed by the auto-alias hooks. Each bump is a flag day in
|
||||
// alpha: operators wipe and restart.
|
||||
const SchemaVersion = 2
|
||||
|
||||
// SchemaVersionKey is the sentinel under which we record SchemaVersion in
|
||||
// Redis. Checked on every meta-core boot.
|
||||
const SchemaVersionKey = "meta-core:schema-version"
|
||||
|
||||
// EnsureSchemaVersion verifies the Redis 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 file:__index__ empty → fresh store, stamp sentinel
|
||||
// - sentinel missing AND file:__index__ non-empty → 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 {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.client == nil {
|
||||
return fmt.Errorf("not connected")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
key := c.buildKey(SchemaVersionKey)
|
||||
have, err := c.client.Get(ctx, key).Result()
|
||||
if err != nil && err != redis.Nil {
|
||||
return fmt.Errorf("get schema version: %w", err)
|
||||
}
|
||||
|
||||
if err == redis.Nil {
|
||||
// Sentinel absent. Fresh store iff there's no file:__index__ membership.
|
||||
n, sErr := c.client.SCard(ctx, c.buildIndexKey()).Result()
|
||||
if sErr != nil && sErr != redis.Nil {
|
||||
return fmt.Errorf("scard file:__index__: %w", sErr)
|
||||
}
|
||||
if n > 0 {
|
||||
return fmt.Errorf(
|
||||
"redis has %d indexed roots but no %q sentinel — this is alpha "+
|
||||
"data from before the UUID-rooted schema landed. Run "+
|
||||
"`pnpm run clean:all` (or `docker exec metacore-app redis-cli FLUSHDB`) "+
|
||||
"to wipe and restart, or downgrade meta-core to a compatible build",
|
||||
n, SchemaVersionKey)
|
||||
}
|
||||
if err := c.client.Set(ctx, key, strconv.Itoa(expected), 0).Err(); err != nil {
|
||||
return fmt.Errorf("set schema version: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if have == strconv.Itoa(expected) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(
|
||||
"redis schema version is %q, this build of meta-core expects %d. "+
|
||||
"Run `pnpm run clean:all` to wipe and restart, or use a meta-core "+
|
||||
"build that matches the existing data",
|
||||
have, expected)
|
||||
}
|
||||
Reference in New Issue
Block a user