enforce checkpoint policy compatibility · Entire

enforce checkpoint policy compatibility

a5fac5d·

pfleidi·3w ago·17 files·+365 added/-3 removed

Warn interactive users when the repo policy requires newer checkpoint support.

Reject user-driven checkpoint reads for unsupported formats, and skip checkpoint writes or pushes from hooks when the configured policy cannot be written by this CLI.

Sessions

b2c7f027b944View transcript

Changes

17

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

package cli

import (
    "context"
    "testing"

"github.com/entireio/cli/cmd/entire/cli/checkpoint"
    "github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
    "github.com/stretchr/testify/require"
)

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

cpID := id.MustCheckpointID("111111111111")
    _, err := readCheckpointInfoFromStore(context.Background(), checkpointInfoPolicyStub{
        summary: &checkpoint.CheckpointSummary{
            CheckpointID:      cpID,
            CheckpointVersion: "refs-v1",
        },
    }, cpID)

require.ErrorContains(t, err, `checkpoint 111111111111 uses unsupported checkpoint_version "refs-v1"`)
}

type checkpointInfoPolicyStub struct {
    summary *checkpoint.CheckpointSummary
}

func (s checkpointInfoPolicyStub) ReadCommitted(context.Context, id.CheckpointID) (*checkpoint.CheckpointSummary, error) {
    return s.summary, nil
}

func (s checkpointInfoPolicyStub) ReadSessionContent(context.Context, id.CheckpointID, int) (*checkpoint.SessionContent, error) {
    return nil, checkpoint.ErrCheckpointNotFound
}

func (s checkpointInfoPolicyStub) ReadSessionMetadata(context.Context, id.CheckpointID, int) (*checkpoint.CommittedMetadata, error) {
    return nil, checkpoint.ErrCheckpointNotFound
}

Acmd/entire/cli/checkpoint_policy_read_test.go+40

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

package cli

import (
    "context"
    "fmt"
    "io"

"github.com/entireio/cli/cmd/entire/cli/checkpointpolicy"
    "github.com/entireio/cli/cmd/entire/cli/gitrepo"
    "github.com/entireio/cli/cmd/entire/cli/versioncheck"
    "github.com/spf13/cobra"
)

func ShouldCheckCheckpointPolicyWarning(cmd *cobra.Command) bool {
    if cmd == nil {
        return false
    }
    for c := cmd; c != nil; c = c.Parent() {
        if c.Hidden || c.Name() == "hooks" {
            return false
        }
    }
    return true
}

func WarnCheckpointPolicyIfNeeded(ctx context.Context, w io.Writer, currentVersion string) {
    repo, err := gitrepo.OpenCurrent(ctx)
    if err != nil {
        return
    }
    defer repo.Close()

state, err := checkpointpolicy.ReadLocal(ctx, repo)
    if err != nil {
        return
    }
    if !checkpointpolicy.RequiresUpgrade(state.Policy) && !checkpointpolicy.UnsupportedWrite(state.Policy) {
        return
    }

fmt.Fprint(w, checkpointpolicy.UpgradeWarning(versioncheck.UpdateCommandForCurrentBinary(currentVersion)))
}

Acmd/entire/cli/checkpoint_policy_warning.go+42

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

package cli

import (
    "bytes"
    "context"
    "testing"

"github.com/entireio/cli/cmd/entire/cli/checkpointpolicy"
    "github.com/go-git/go-git/v6"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/spf13/cobra"
    "github.com/stretchr/testify/require"
)

func TestWarnCheckpointPolicyIfNeeded(t *testing.T) {
    _, _ = setupPolicyCheckpointRepo(t)
    repo, err := git.PlainOpen(".")
    require.NoError(t, err)

t.Cleanup(func() {
        _ = repo.Close()
    })
    _, err = checkpointpolicy.WriteLocal(t.Context(), repo, plumbing.ZeroHash, checkpointpolicy.Policy{
        CheckpointVersion:    "refs-v1",
        CheckpointMinVersion: "refs-v1",
    })
    require.NoError(t, err)

var buf bytes.Buffer
    WarnCheckpointPolicyIfNeeded(context.Background(), &buf, "1.0.0")

require.Contains(t, buf.String(), "requires checkpoint support newer than this Entire CLI")
}

func TestShouldCheckCheckpointPolicyWarning(t *testing.T) {
    root := &cobra.Command{Use: "entire"}
    visible := &cobra.Command{Use: "status"}
    root.AddCommand(visible)

hooks := &cobra.Command{Use: "hooks", Hidden: true}
    gitHook := &cobra.Command{Use: "git"}
    hooks.AddCommand(gitHook)
    root.AddCommand(hooks)

require.True(t, ShouldCheckCheckpointPolicyWarning(visible))
    require.False(t, ShouldCheckCheckpointPolicyWarning(gitHook))
}

Acmd/entire/cli/checkpoint_policy_warning_test.go+46

69 unmodified lines

70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

69 unmodified lines

}
    return !CanWrite(version)
}

func UpgradeWarning(updateCommand string) string {
    return fmt.Sprintf("[entire] This repository requires checkpoint support newer than this Entire CLI.\n[entire] Upgrade Entire, then rerun the command:\n[entire]   %s\n", updateCommand)
}

func EnsureCanReadVersion(checkpointID, version string) error {
    policy := Normalize(Policy{CheckpointMinVersion: version})
    format, err := ParseFormat(policy.CheckpointMinVersion)
    if err != nil {
        return fmt.Errorf("checkpoint %s uses unsupported checkpoint_version %q: %w", checkpointID, policy.CheckpointMinVersion, err)
    }
    if !CanRead(format) {
        return fmt.Errorf("checkpoint %s uses unsupported checkpoint_version %q: not read-supported by this Entire CLI", checkpointID, policy.CheckpointMinVersion)
    }
    return nil
}

Mcmd/entire/cli/checkpointpolicy/policy.go+16

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

package checkpointpolicy_test

import (
    "testing"

"github.com/entireio/cli/cmd/entire/cli/checkpoint"
    "github.com/entireio/cli/cmd/entire/cli/checkpointpolicy"
    "github.com/stretchr/testify/require"
)

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

got := checkpointpolicy.UpgradeWarning("brew upgrade entire")

require.Contains(t, got, "[entire] This repository requires checkpoint support newer than this Entire CLI.")
    require.Contains(t, got, "[entire] Upgrade Entire, then rerun the command:")
    require.Contains(t, got, "[entire]   brew upgrade entire")
}

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

require.NoError(t, checkpointpolicy.EnsureCanReadVersion("abc123", checkpoint.CheckpointVersionBranchV1))
    require.NoError(t, checkpointpolicy.EnsureCanReadVersion("abc123", ""))

err := checkpointpolicy.EnsureCanReadVersion("abc123", "refs-v1")
    require.ErrorContains(t, err, `checkpoint abc123 uses unsupported checkpoint_version "refs-v1"`)
    require.ErrorContains(t, err, "not read-supported")
}

Acmd/entire/cli/checkpointpolicy/warning_test.go+30

22 unmodified lines

23
24
25
26
27
28
29
725 unmodified lines

755
756
757
758
759
760
761
762
763

22 unmodified lines

"github.com/entireio/cli/cmd/entire/cli/agent/types"
    "github.com/entireio/cli/cmd/entire/cli/checkpoint"
    "github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
    "github.com/entireio/cli/cmd/entire/cli/checkpointpolicy"
    "github.com/entireio/cli/cmd/entire/cli/interactive"
    "github.com/entireio/cli/cmd/entire/cli/logging"
    "github.com/entireio/cli/cmd/entire/cli/paths"
725 unmodified lines

if err != nil {
        return nil, nil, fmt.Errorf("failed to read checkpoint: %w", err)
    }
    if err := checkpointpolicy.EnsureCanReadVersion(cpID.String(), summary.CheckpointVersion); err != nil {
        return nil, nil, fmt.Errorf("check checkpoint version: %w", err)
    }

content, contentErr := checkpoint.ReadLatestSessionContent(ctx, store, cpID, summary)
    if contentErr != nil {
        return fmt.Errorf("failed to get session log: %w", err)
    }
}

Mcmd/entire/cli/explain.go+4

14 unmodified lines

15
16
17
18
19
20
21
338 unmodified lines

360
361
362
363
364
365
366
367
368
577 unmodified lines

946
947
948
945
949
950
951
952
953
954
955
956
957
958
959
12 unmodified lines

972
973
974
975
976
977
978

14 unmodified lines
    "github.com/entireio/cli/cmd/entire/cli/checkpoint"
    "github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
    "github.com/entireio/cli/cmd/entire/cli/checkpoint/remote"
    "github.com/entireio/cli/cmd/entire/cli/checkpointpolicy"
    "github.com/entireio/cli/cmd/entire/cli/logging"
    "github.com/entireio/cli/cmd/entire/cli/paths"
    "github.com/entireio/cli/cmd/entire/cli/strategy"
338 unmodified lines

if err != nil {
        return nil, fmt.Errorf("read checkpoint: %w", err)
    }
    if err := checkpointpolicy.EnsureCanReadVersion(checkpointID.String(), summary.CheckpointVersion); err != nil {
        return nil, fmt.Errorf("check checkpoint version: %w", err)
    }
    info := &strategy.CheckpointInfo{
        CheckpointID:     checkpointID,
        CheckpointsCount: summary.CheckpointsCount,
    }
    return info, nil
}

Mcmd/entire/cli/resume.go+13/-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

package strategy

import (
    "context"
    "fmt"
    "log/slog"

"github.com/entireio/cli/cmd/entire/cli/checkpointpolicy"
    "github.com/entireio/cli/cmd/entire/cli/interactive"
    "github.com/entireio/cli/cmd/entire/cli/logging"
    "github.com/entireio/cli/cmd/entire/cli/versioncheck"
    "github.com/entireio/cli/cmd/entire/cli/versioninfo"
    "github.com/go-git/go-git/v6"
)

func committedCheckpointWriteAllowed(ctx context.Context, repo *git.Repository) bool {
    state, err := checkpointpolicy.ReadLocal(ctx, repo)
    if err != nil {
        logging.Warn(ctx, "checkpoint policy read failed; allowing checkpoint write",
            slog.String("error", err.Error()),
        )
        return true
    }
    if !checkpointpolicy.UnsupportedWrite(state.Policy) {
        return true
    }
    warnOrLogUnsupportedCheckpointWrite(ctx, state.Policy)
    return false
}

func syncCheckpointPolicyForPrePush(ctx context.Context, remoteName string) bool {
    repo, err := OpenRepository(ctx)
    if err != nil {
        logging.Warn(ctx, "checkpoint policy pre-push: failed to open repository; allowing checkpoint push",
            slog.String("error", err.Error()),
        )
        return true
    }
    defer repo.Close()

target, err := checkpointpolicy.ResolveTarget(ctx, remoteName)
    if err != nil {
        logging.Warn(ctx, "checkpoint policy pre-push: failed to resolve policy remote; allowing checkpoint push",
            slog.String("error", err.Error()),
        )
        return true
    }
    state, err := checkpointpolicy.Sync(ctx, repo, target)
    if err != nil {
        warnOrLogCheckpointPolicySyncFailure(ctx, err)
        return true
    }
    if !checkpointpolicy.UnsupportedWrite(state.Policy) {
        return true
    }
    warnOrLogUnsupportedCheckpointWrite(ctx, state.Policy)
    return false
}

func warnOrLogCheckpointPolicySyncFailure(ctx context.Context, err error) {
    if interactive.CanPromptInteractively() {
        fmt.Fprintf(stderrWriter, "[entire] Could not refresh checkpoint policy: %v\n", err)
        return
    }
    logging.Warn(ctx, "checkpoint policy sync failed",
        slog.String("error", err.Error()),
    )
}

func warnOrLogUnsupportedCheckpointWrite(ctx context.Context, policy checkpointpolicy.Policy) {
    warning := checkpointpolicy.UpgradeWarning(versioncheck.UpdateCommandForCurrentBinary(versioninfo.Version))
    if interactive.CanPromptInteractively() {
        fmt.Fprint(stderrWriter, warning)
        return
    }
    logging.Warn(ctx, "checkpoint write skipped by policy",
        slog.String("checkpoint_version", policy.CheckpointVersion),
            slog.String("checkpoint_min_version", policy.CheckpointMinVersion),
    )
}

Mcmd/entire/cli/strategy/checkpoint_policy.go+80

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

package strategy

import (
    "bytes"
    "context"
    "os/exec"
    "path/filepath"
    "strings"
    "testing"

"github.com/entireio/cli/cmd/entire/cli/checkpointpolicy"
    "github.com/entireio/cli/cmd/entire/cli/interactive"
    "github.com/entireio/cli/cmd/entire/cli/paths"
    "github.com/entireio/cli/cmd/entire/cli/testutil"
    "github.com/go-git/go-git/v6"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/stretchr/testify/require"
)

func TestPrePushSkipsCheckpointPushWhenPolicyWriteUnsupported(t *testing.T) {
    workDir := setupRepoWithCheckpointBranch(t)
    bareDir := filepath.Join(t.TempDir(), "remote.git")
    _, err := git.PlainInit(bareDir, true)
    require.NoError(t, err)
    runCheckpointPolicyGit(t, workDir, "remote", "add", "origin", bareDir)

repo, err := git.PlainOpen(workDir)
    require.NoError(t, err)

t.Cleanup(func() {
        _ = repo.Close()
    })
    _, err = checkpointpolicy.WriteLocal(t.Context(), repo, plumbing.ZeroHash, checkpointpolicy.Policy{
        CheckpointVersion:    "refs-v1",
        CheckpointMinVersion: "branch-v1",
    })
    require.NoError(t, err)

t.Chdir(workDir)
    paths.ClearWorktreeRootCache()
    t.Setenv(interactive.EnvTestTTY, "1")
    oldWriter := stderrWriter
    var stderr bytes.Buffer
    stderrWriter = &stderr
    t.Cleanup(func() { stderrWriter = oldWriter })

err = NewManualCommitStrategy().PrePush(context.Background(), "origin")
    require.NoError(t, err)
    require.Contains(t, stderr.String(), "requires checkpoint support newer than this Entire CLI")

out := runCheckpointPolicyGit(t, workDir, "ls-remote", bareDir, "refs/heads/"+paths.MetadataBranchName)
    require.Empty(t, strings.TrimSpace(out))
}

func runCheckpointPolicyGit(t *testing.T, dir string, args ...string) string {
    t.Helper()
    cmd := exec.CommandContext(context.Background(), "git", args...)
    cmd.Dir = dir
    cmd.Env = testutil.GitIsolatedEnv()
    output, err := cmd.CombinedOutput()
    require.NoError(t, err, string(output))
    return string(output)
}

Acmd/entire/cli/strategy/checkpoint_policy_test.go+62

51 unmodified lines

52
53
54
55
55
56
57
58
3 unmodified lines

62
63
64
65
65
66
67
68

51 unmodified lines

// topology. Writes target refs.Primary; reads target refs.Read. The strategy's
// blob fetcher is wired in so reads can fetch blobs on demand after a treeless
// fetch.
func (s *ManualCommitStrategy) getCheckpointStore(ctx context.Context, repo *git.Repository) (checkpoint.CommittedStore, error) {
func (s *ManualCommitStrategy) getCheckpointStore(ctx context.Context, repo *git.Repository) (checkpoint.CommittedStore, error) { //nolint:ireturn // committed store capability is the abstraction boundary
    stores, err := s.getCheckpointStores(ctx, repo)
    if err != nil {
        return nil, err
    }
}
// getTemporaryStore returns the git-backed shadow-branch store with the
// strategy's blob fetcher wired in.
func (s *ManualCommitStrategy) getTemporaryStore(ctx context.Context, repo *git.Repository) (checkpoint.TemporaryStore, error) {
func (s *ManualCommitStrategy) getTemporaryStore(ctx context.Context, repo *git.Repository) (checkpoint.TemporaryStore, error) { //nolint:ireturn // temporary store capability is the abstraction boundary
    stores, err := s.getCheckpointStores(ctx, repo)
    if err != nil {
        return nil, err
    }
}

Mcmd/entire/cli/strategy/manual_commit.go+2/-2

146 unmodified lines

147
148
149
150
151
152
153
154
155

146 unmodified lines

}
    logCtx := logging.WithComponent(ctx, "checkpoint")
    condenseStart := time.Now()
    if !committedCheckpointWriteAllowed(ctx, repo) {
        return newSkippedResult(checkpointID, state.SessionID), nil
    }

shadowBranchName := getShadowBranchNameForCommit(state.BaseCommit, state.WorktreeID)
    ref, hasShadowBranch := resolveShadowRef(repo, shadowBranchName, o.shadowRef)
}

Mcmd/entire/cli/strategy/manual_commit_condensation.go+3

2761 unmodified lines

2762
2763
2764
2765
2766
2767
2768
2769
2770
2771

2761 unmodified lines

return 1 // Count as error - all checkpoints will be skipped
    }
    defer repo.Close()
    if !committedCheckpointWriteAllowed(ctx, repo) {
        state.TurnCheckpointIDs = nil
        return 0
    }

prompts := readPromptsFromShadowBranch(ctx, repo, state)
    if len(prompts) == 0 {
}

Mcmd/entire/cli/strategy/manual_commit_hooks.go+4

43 unmodified lines

44
45
46
47
48
49
50
51
52

43 unmodified lines

}

refs := checkpoint.ResolveCommittedRefs(ctx)
    if !syncCheckpointPolicyForPrePush(ctx, remote) {
        return nil
    }

// OPF pre-push rewrite: if OPF is configured, resolve the user's
    // decision (env > settings > prompt > non-TTY auto-run), then
}

Mcmd/entire/cli/strategy/manual_commit_push.go+3

15 unmodified lines

16
17
18
19
20
21
22
629 unmodified lines

652
653
654
655
656
657
658
659
660

15 unmodified lines

"github.com/entireio/cli/cmd/entire/cli/agent/types"
    cpkg "github.com/entireio/cli/cmd/entire/cli/checkpoint"
    "github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
    "github.com/entireio/cli/cmd/entire/cli/checkpointpolicy"
    "github.com/entireio/cli/cmd/entire/cli/interactive"
    "github.com/entireio/cli/cmd/entire/cli/osroot"
    "github.com/entireio/cli/cmd/entire/cli/paths"
629 unmodified lines

if err != nil {
        return nil, fmt.Errorf("failed to read checkpoint: %w", err)
    }
    if err := checkpointpolicy.EnsureCanReadVersion(point.CheckpointID.String(), summary.CheckpointVersion); err != nil {
        return nil, fmt.Errorf("check checkpoint version: %w", err)
    }

// Get worktree root for agent session directory lookup
    repoRoot, err := paths.WorktreeRoot(ctx)
}

Mcmd/entire/cli/strategy/manual_commit_rewind.go+4

409 unmodified lines

410
411
412
413
414
415
416
417
418
419

409 unmodified lines

return "curl -fsSL https://entire.io/install.sh | bash"
}

func UpdateCommandForCurrentBinary(currentVersion string) string {
    return updateCommand(currentVersion)
}

// printNotification prints the version update notification to the user.
func printNotification(w io.Writer, current, latest string) {
    fmt.Fprintf(w, "\nUpdate available! %s -> %s\nRelease notes: %s\n",
        current, latest, "");
}

Mcmd/entire/cli/versioncheck/versioncheck.go+4

422 unmodified lines

423
424
425
426
427
428
429
430
431
432
433
434
435
436
437

422 unmodified lines

}
}

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

got := UpdateCommandForCurrentBinary("1.2.3")
    if got == "" {
        t.Fatal("UpdateCommandForCurrentBinary returned empty command")
    }
}

// setupCheckAndNotifyTest points the global config dir at a per-test temp
// dir and overrides githubAPIURL. Returns a cobra.Command with captured
// stdout and a cleanup function.

Mcmd/entire/cli/versioncheck/versioncheck_test.go+9

86 unmodified lines

87
88
89
90
91
92
93
94
95

86 unmodified lines

cancel()
    os.Exit(1)
    }
    if cli.ShouldCheckCheckpointPolicyWarning(executed) {
        cli.WarnCheckpointPolicyIfNeeded(ctx, rootCmd.ErrOrStderr(), versioninfo.Version)
    }
    cancel() // Cleanup on successful exit
}

Mcmd/entire/main.go+3