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
cmd/entire/cli/checkpoint
Mopen.go+8/-7
Aregistry.go+91
Aregistry_test.go+40
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")
}