Add checkpoint backend registry and factory · Entire

Add checkpoint backend registry and factory

82ccc32→main·

Introduce a backend registry in the checkpoint package: a Factory type, an OpenEnv construction context, Register/build, and a built-in "git" backend. Open now builds its primary store through the registry (defaulting to the git backend) rather than calling NewGitStore directly.

This is the seam Phase 2 needs: a new persistent backend becomes a factory registered under a type name. Production registers only the git backend; test-only backends will register through their own RegisterForTesting helpers so a production binary can never select them. The registry default of git matches today's Open exactly, so there is no behavior change at default config.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Sessions

56c7fecd2a55View transcript

Changes

3

30 unmodified lines

// Open resolves the checkpoint storage topology and constructs the backing // store. It keeps ref resolution and blob-fetcher wiring in one place. // //nolint:unparam // Callers treat store construction as fallible at this boundary; the git backend has no fallible setup today. // store(s). It keeps ref resolution, backend selection, and blob-fetcher wiring // in one place. The primary is built through the backend registry; today it // always resolves to the git backend, so default behavior is unchanged. func Open(ctx context.Context, repo *git.Repository, opts OpenOptions) (*Stores, error) { refs := resolveOpenRefs(ctx, opts) store := NewGitStore(repo, refs) if opts.BlobFetcher != nil { store.SetBlobFetcher(opts.BlobFetcher) } env := OpenEnv{Repo: repo, BlobFetcher: opts.BlobFetcher, Refs: refs} primary, err := build(ctx, env, BackendTypeGit, nil) if err != nil { return nil, err } return &Stores{ Persistent: store, Secondary: primary, ephemeral: newEphemeralStore(repo, refs), refs: refs, }, nil }


```go
package checkpoint

import (
    "context"
    "encoding/json"
    "errors"
    "fmt"
    "sort"
    "strings"
    "sync"

"github.com/go-git/go-git/v6"
)

const BackendTypeGit = "git"

type OpenEnv struct {
    Repo        *git.Repository
    BlobFetcher BlobFetchFunc
    Refs        PersistentRefs
}

type Factory func(ctx context.Context, env OpenEnv, cfg json.RawMessage) (PersistentStore, error)

var (
    registryMu sync.RWMutex
    registry = map[string]Factory{BackendTypeGit: gitBackendFactory}
)

func Register(typ string, f Factory) {
    registryMu.Lock()
    defer registryMu.Unlock()
    if _, exists := registry[typ]; exists {
        panic(fmt.Sprintf("checkpoint: backend type %q already registered", typ))
    }
    registry[typ] = f
}

func build(ctx context.Context, env OpenEnv, typ string, cfg json.RawMessage) (PersistentStore, error) {
    registryMu.RLock()
    f, ok := registry[typ]
    registryMu.RUnlock()
    if !ok {
        return nil, fmt.Errorf("unknown checkpoint backend type %q (registered: %s)", typ, registeredTypes())
    }
    store, err := f(ctx, env, cfg)
    if err != nil {
        return nil, fmt.Errorf("construct %q checkpoint backend: %w", typ, err)
    }
    return store, nil
}

func registeredTypes() string {
    registryMu.RLock()
    defer registryMu.RUnlock()
    types := make([]string, 0, len(registry))
    for t := range registry {
        types = append(types, t)
    }
    sort.Strings(types)
    return strings.Join(types, ", ")
}

func gitBackendFactory(_ context.Context, env OpenEnv, _ json.RawMessage) (PersistentStore, error) {
    if env.Repo == nil {
        return nil, errors.New("git checkpoint backend requires a repository")
    }
    store := NewGitStore(env.Repo, env.Refs)
    if env.BlobFetcher != nil {
        store.SetBlobFetcher(env.BlobFetcher)
    }
    return store, nil
}
package checkpoint

import (
    "context"
    "encoding/json"
    "testing"

"github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func TestRegistry_GitBackendRegistered(t *testing.T) {
    t.Parallel()

_, err := build(context.Background(), OpenEnv{}, BackendTypeGit, nil)
    require.Error(t, err)
    assert.Contains(t, err.Error(), "git checkpoint backend requires a repository")
}

func TestRegistry_UnknownType(t *testing.T) {
    t.Parallel()

_, err := build(context.Background(), OpenEnv{}, "definitely-not-a-backend", nil)
    require.Error(t, err)
    assert.Contains(t, err.Error(), `unknown checkpoint backend type "definitely-not-a-backend"`)
    assert.Contains(t, err.Error(), BackendTypeGit)
}

func TestRegistry_GitFactoryIgnoresConfig(t *testing.T) {
    t.Parallel()

_, err := build(context.Background(), OpenEnv{}, BackendTypeGit, json.RawMessage(`{"anything":true}`))
    require.Error(t, err)
    assert.Contains(t, err.Error(), "git checkpoint backend requires a repository")
}