Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3ba0901
select checkpoint version from policy
pfleidi Jun 26, 2026
652c278
make checkpoint policy advisory
pfleidi Jun 26, 2026
5173da4
document checkpoint policy fallback
pfleidi Jun 26, 2026
b0eed5c
remove stale checkpoint summary repo parameter
pfleidi Jun 26, 2026
f4bced8
support unsetting checkpoint policy fields
pfleidi Jun 26, 2026
d995bcb
use empty policy flag values to unset
pfleidi Jun 26, 2026
8fb1567
Merge branch 'main' into checkpoint-policy-fixes
pfleidi Jun 29, 2026
e5feacd
simplify checkpoint policy pre-push sync
pfleidi Jun 29, 2026
fa54b81
document checkpoint policy unset downgrades
pfleidi Jun 29, 2026
c3f89a9
surface checkpoint policy fallbacks
pfleidi Jun 29, 2026
65d3736
clarify checkpoint policy satisfaction
pfleidi Jun 29, 2026
c553b61
block checkpoint data writers on policy
pfleidi Jun 29, 2026
a117171
gate hooks on checkpoint policy
pfleidi Jun 29, 2026
6f06a73
document checkpoint policy enforcement
pfleidi Jun 29, 2026
93b03f9
Merge branch 'main' of entire://aws-us-east-2.entire.io/gh/entireio/c…
pfleidi Jun 29, 2026
9762bcb
fail closed on unreadable checkpoint policy
pfleidi Jun 29, 2026
9994d75
remove checkpoint version fallback helper
pfleidi Jun 29, 2026
39d9209
move checkpoint policy test helpers
pfleidi Jun 29, 2026
450426a
reuse pre-push repo for policy checks
pfleidi Jun 30, 2026
7ed08ed
dedupe checkpoint version policy formatting
pfleidi Jun 30, 2026
88eb877
Merge branch 'main' into checkpoint-policy-fixes
pfleidi Jun 30, 2026
8127cf5
fix hook policy open failures
pfleidi Jun 30, 2026
342c6fd
Merge remote-tracking branch 'origin/checkpoint-policy-fixes' into ch…
pfleidi Jun 30, 2026
d958fb7
Merge origin/main into checkpoint-policy-fixes
pfleidi Jun 30, 2026
2842d49
limit hook policy gate to checkpoint writes
pfleidi Jun 30, 2026
623aa66
Merge remote-tracking branch 'origin/main' into checkpoint-policy-fixes
pfleidi Jun 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions api/checkpoint/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ type WriteOptions struct {
// Strategy is the name of the strategy that created this checkpoint
Strategy string

// CheckpointVersion is the checkpoint metadata format for new root summaries.
CheckpointVersion string

// Branch is the branch name where the checkpoint was created (empty if detached HEAD)
Branch string

Expand Down
26 changes: 14 additions & 12 deletions cmd/entire/cli/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,8 @@ func runAttach(ctx context.Context, w io.Writer, sessionID string, agentName typ
return nil
}

if err := ensureCommittedCheckpointWritePolicy(ctx, repo); err != nil {
checkpointVersion, err := checkpointVersionForNewCheckpoint(ctx, repo)
if err != nil {
return err
}

Expand Down Expand Up @@ -349,17 +350,18 @@ func runAttach(ctx context.Context, w io.Writer, sessionID string, agentName typ
}

writeOpts := cpkg.WriteOptions{
CheckpointID: checkpointID,
SessionID: sessionID,
Strategy: strategy.StrategyNameManualCommit,
Transcript: redactedTranscript,
Prompts: attachPrompts(meta),
CheckpointsCount: attachStepCount(meta.TurnCount),
AuthorName: author.Name,
AuthorEmail: author.Email,
Agent: ag.Type(),
Model: meta.Model,
TokenUsage: tokenUsage,
CheckpointID: checkpointID,
SessionID: sessionID,
Strategy: strategy.StrategyNameManualCommit,
CheckpointVersion: checkpointVersion,
Transcript: redactedTranscript,
Prompts: attachPrompts(meta),
CheckpointsCount: attachStepCount(meta.TurnCount),
AuthorName: author.Name,
AuthorEmail: author.Email,
Agent: ag.Type(),
Model: meta.Model,
TokenUsage: tokenUsage,
}
if opts.Review {
writeOpts.Kind = string(session.KindAgentReview)
Expand Down
28 changes: 14 additions & 14 deletions cmd/entire/cli/attach_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"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/paths"
cliReview "github.com/entireio/cli/cmd/entire/cli/review"
"github.com/entireio/cli/cmd/entire/cli/session"
Expand Down Expand Up @@ -69,20 +68,16 @@ func TestAttach_TranscriptNotFound(t *testing.T) {
}
}

func TestAttachRejectsUnsupportedCheckpointWritePolicy(t *testing.T) {
func TestAttachBlocksWhenPolicyWriteUnsupported(t *testing.T) {
setupAttachTestRepo(t)

repoRoot := mustGetwd(t)
repo, err := git.PlainOpen(repoRoot)
if err != nil {
t.Fatal(err)
}
if _, err := checkpointpolicy.WriteLocal(context.Background(), repo, plumbing.ZeroHash, checkpointpolicy.Policy{
CheckpointVersion: "refs-v1",
CheckpointMinVersion: "branch-v1",
}); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = repo.Close() })
writeUnsupportedCheckpointPolicyForCLITest(t, repo)

sessionID := "test-attach-policy-unsupported"
setupClaudeTranscript(t, sessionID, `{"type":"user","message":{"role":"user","content":"create a file"},"uuid":"uuid-1"}
Expand All @@ -91,14 +86,19 @@ func TestAttachRejectsUnsupportedCheckpointWritePolicy(t *testing.T) {

var out bytes.Buffer
err = runAttach(context.Background(), &out, sessionID, agent.AgentNameClaudeCode, attachOptions{Force: true})
if err == nil {
t.Fatal("expected unsupported checkpoint policy error")
if err == nil || !strings.Contains(err.Error(), "checkpoint policy cannot be satisfied by this Entire CLI") {
t.Fatalf("runAttach error = %v, want unsupported checkpoint policy", err)
}
if !strings.Contains(err.Error(), `checkpoint_version "refs-v1"`) {
t.Fatalf("error = %v, want checkpoint policy version", err)
stateStore, err := session.NewStateStore(context.Background())
if err != nil {
t.Fatal(err)
}
state, err := stateStore.Load(context.Background(), sessionID)
if err != nil {
t.Fatal(err)
}
if _, refErr := repo.Reference(plumbing.NewBranchReferenceName(paths.MetadataBranchName), true); refErr == nil {
t.Fatal("metadata branch exists after rejected attach")
if state != nil {
t.Fatalf("expected attach not to record checkpoint state, got %+v", state)
}
}

Expand Down
6 changes: 5 additions & 1 deletion cmd/entire/cli/checkpoint/persistent.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@ func (s *GitStore) writeSession(ctx context.Context, opts WriteOptions) error {
if err != nil {
return err
}
checkpointSubtree, taskMetadataPath, err := s.applySessionWrite(ctx, opts, existing, opts.CheckpointID.Path()+"/", CheckpointVersionBranchV1)
checkpointVersion := CheckpointVersionBranchV1
if opts.CheckpointVersion != "" {
checkpointVersion = opts.CheckpointVersion
}
checkpointSubtree, taskMetadataPath, err := s.applySessionWrite(ctx, opts, existing, opts.CheckpointID.Path()+"/", checkpointVersion)
if err != nil {
return err
}
Expand Down
27 changes: 27 additions & 0 deletions cmd/entire/cli/checkpoint/persistent_write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,33 @@ func TestWriteCommittedWritesBranchCheckpointVersion(t *testing.T) {
}
}

func TestWriteCommittedUsesExplicitCheckpointVersion(t *testing.T) {
t.Parallel()
repo, _ := setupBranchTestRepo(t)
store := NewGitStore(repo, DefaultV1Refs())
ctx := context.Background()
cpID := id.MustCheckpointID("c1c2c3d4e5f6")
const configuredVersion = "refs-v1"

if err := store.Write(ctx, Session{
CheckpointID: cpID,
SessionID: "session-001",
Strategy: "manual-commit",
Transcript: redact.AlreadyRedacted([]byte("transcript\n")),
Prompts: []string{"initial"},
AuthorName: "Test",
AuthorEmail: "test@test.com",
CheckpointVersion: configuredVersion,
}); err != nil {
t.Fatalf("WriteCommitted() error = %v", err)
}

rawSummary := readSummaryFromBranch(t, repo, cpID)
if rawSummary.CheckpointVersion != configuredVersion {
t.Fatalf("raw checkpoint_version = %q, want %q", rawSummary.CheckpointVersion, configuredVersion)
}
}

// TestWrite_BackfillSummaryNotFound verifies error propagation through dispatch.
func TestWrite_BackfillSummaryNotFound(t *testing.T) {
t.Parallel()
Expand Down
66 changes: 54 additions & 12 deletions cmd/entire/cli/checkpoint_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,40 @@ type checkpointPolicyOptions struct {
force bool
}

const (
checkpointVersionFlag = "checkpoint-version"
checkpointMinVersionFlag = "checkpoint-min-version"
)

func newCheckpointPolicyCmd() *cobra.Command {
var opts checkpointPolicyOptions
cmd := &cobra.Command{
Use: "policy",
Short: "Inspect and update checkpoint policy",
Use: "policy",
Short: "Inspect and update checkpoint policy",
Long: `Inspect and update checkpoint policy.

checkpoint_version selects the checkpoint metadata format used for new writes.
If no policy is configured, Entire uses the CLI default.
If another client configures a checkpoint_version this CLI cannot write,
commands that create checkpoint data fail until the CLI is upgraded. Other commands warn and
continue. Set checkpoint_version to "" to inherit the CLI default.

checkpoint_min_version is an upgrade nudge and checkpoint-data write guard.
Clients that cannot read that version warn users to upgrade. Commands that
create checkpoint data fail until the CLI is upgraded. Other commands warn
and continue. Set checkpoint_min_version to "" to inherit the CLI default.

Unsetting a field still uses the normal downgrade guard. If inheriting the
default would lower the field's effective version, pass --force to allow it.`,
Hidden: true,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
return runCheckpointPolicy(cmd, opts)
},
}

cmd.Flags().StringVar(&opts.version, "checkpoint-version", "", "Set the checkpoint version written by this repository")
cmd.Flags().StringVar(&opts.minVersion, "checkpoint-min-version", "", "Set the minimum checkpoint version required by this repository")
cmd.Flags().StringVar(&opts.version, checkpointVersionFlag, "", `Set checkpoint_version. Use "" to inherit the CLI default; --force may be required`)
cmd.Flags().StringVar(&opts.minVersion, checkpointMinVersionFlag, "", `Set checkpoint_min_version. Use "" to inherit the CLI default; --force may be required`)
cmd.Flags().BoolVar(&opts.force, "force", false, "Allow checkpoint policy version downgrades")
return cmd
}
Expand All @@ -51,11 +71,15 @@ func runCheckpointPolicy(cmd *cobra.Command, opts checkpointPolicyOptions) error
}

var state checkpointpolicy.State
if hasCheckpointPolicyUpdate(opts) {
checkpointVersionSet := cmd.Flags().Changed(checkpointVersionFlag)
checkpointMinVersionSet := cmd.Flags().Changed(checkpointMinVersionFlag)
if hasCheckpointPolicyUpdate(checkpointVersionSet, checkpointMinVersionSet) {
state, err = checkpointpolicy.Update(ctx, repo, target, checkpointpolicy.UpdateOptions{
CheckpointVersion: opts.version,
CheckpointMinVersion: opts.minVersion,
Force: opts.force,
CheckpointVersion: opts.version,
CheckpointVersionSet: checkpointVersionSet,
CheckpointMinVersion: opts.minVersion,
CheckpointMinVersionSet: checkpointMinVersionSet,
Force: opts.force,
})
if err != nil {
return checkpointPolicyError("update checkpoint policy", err)
Expand All @@ -71,14 +95,32 @@ func runCheckpointPolicy(cmd *cobra.Command, opts checkpointPolicyOptions) error
}
}

fmt.Fprintf(cmd.OutOrStdout(), "checkpoint_version: %s\n", state.Policy.CheckpointVersion)
fmt.Fprintf(cmd.OutOrStdout(), "checkpoint_min_version: %s\n", state.Policy.CheckpointMinVersion)
effectivePolicy := checkpointpolicy.Normalize(state.Policy)
fmt.Fprintf(cmd.OutOrStdout(), "checkpoint_version: %s\n", formatCheckpointVersionPolicyValue(state.Policy.CheckpointVersion, effectivePolicy.CheckpointVersion))
fmt.Fprintf(cmd.OutOrStdout(), "checkpoint_min_version: %s\n", formatCheckpointPolicyValue(state.Policy.CheckpointMinVersion, effectivePolicy.CheckpointMinVersion))
fmt.Fprintf(cmd.OutOrStdout(), "source: %s\n", state.Source)
Comment thread
pfleidi marked this conversation as resolved.
return nil
}

func hasCheckpointPolicyUpdate(opts checkpointPolicyOptions) bool {
return opts.version != "" || opts.minVersion != ""
func hasCheckpointPolicyUpdate(checkpointVersionSet, checkpointMinVersionSet bool) bool {
return checkpointVersionSet || checkpointMinVersionSet
}

func formatCheckpointPolicyValue(configured, effective string) string {
if configured == "" {
return effective + " (default)"
}
return configured
}

func formatCheckpointVersionPolicyValue(configured, effective string) string {
if configured != "" && checkpointpolicy.UnsupportedWrite(checkpointpolicy.Policy{
CheckpointVersion: configured,
CheckpointMinVersion: checkpointpolicy.DefaultCheckpointVersion(),
}) {
return configured + " (unsupported)"
}
return formatCheckpointPolicyValue(configured, effective)
}

func checkpointPolicyError(message string, err error) error {
Expand Down
58 changes: 56 additions & 2 deletions cmd/entire/cli/checkpoint_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,33 @@ func TestCheckpointPolicyCmd_PrintsDefaults(t *testing.T) {

stdout, err := executeCheckpointPolicyCmd(t)
require.NoError(t, err)
require.Contains(t, stdout, "checkpoint_version: branch-v1")
require.Contains(t, stdout, "checkpoint_min_version: branch-v1")
require.Contains(t, stdout, "checkpoint_version: branch-v1 (default)")
require.Contains(t, stdout, "checkpoint_min_version: branch-v1 (default)")
require.Contains(t, stdout, "source: defaults")
}

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

cmd := newCheckpointGroupCmd()
var stdout bytes.Buffer
cmd.SetOut(&stdout)
cmd.SetArgs([]string{"policy", "--help"})

err := cmd.Execute()
require.NoError(t, err)

help := stdout.String()
require.Contains(t, help, "checkpoint_version selects the checkpoint metadata format used for new writes")
require.Contains(t, help, `If another client configures a checkpoint_version this CLI cannot write`)
require.Contains(t, help, "commands that create checkpoint data fail until the CLI is upgraded")
require.Contains(t, help, "checkpoint_min_version is an upgrade nudge")
require.Contains(t, help, `Set checkpoint_version to "" to inherit the CLI default`)
require.Contains(t, help, `Set checkpoint_min_version to "" to inherit the CLI default`)
require.Contains(t, help, "Unsetting a field still uses the normal downgrade guard")
require.NotContains(t, help, "unset-checkpoint-version")
}

func TestCheckpointPolicyCmd_RejectsUnsupportedVersion(t *testing.T) {
tests := []struct {
name string
Expand All @@ -47,6 +69,21 @@ func TestCheckpointPolicyCmd_RejectsUnsupportedVersion(t *testing.T) {
}
}

func TestCheckpointPolicyCmd_PrintsUnsupportedConfiguredVersion(t *testing.T) {
dir, bareDir := setupCheckpointPolicyRepo(t)
seedCheckpointPolicyForCommand(t, dir, checkpointpolicy.Policy{
CheckpointVersion: "refs-v1",
CheckpointMinVersion: "branch-v1",
})
pushCheckpointPolicyRefForCommandTest(t, dir, bareDir)

stdout, err := executeCheckpointPolicyCmd(t)
require.NoError(t, err)
require.Contains(t, stdout, "checkpoint_version: refs-v1 (unsupported)")
require.NotContains(t, stdout, "writing branch-v1")
require.Contains(t, stdout, "checkpoint_min_version: branch-v1")
}

func TestCheckpointPolicyCmd_RejectsDowngradeWithoutForce(t *testing.T) {
dir, bareDir := setupCheckpointPolicyRepo(t)
seedCheckpointPolicyForCommand(t, dir, checkpointpolicy.Policy{
Expand Down Expand Up @@ -83,6 +120,23 @@ func TestCheckpointPolicyCmd_UpdatesAndPushesOnlyPolicyRef(t *testing.T) {
require.Empty(t, strings.TrimSpace(branches))
}

func TestCheckpointPolicyCmd_UnsetsPolicyFields(t *testing.T) {
dir, bareDir := setupCheckpointPolicyRepo(t)
seedCheckpointPolicyForCommand(t, dir, checkpointpolicy.DefaultPolicy())
pushCheckpointPolicyRefForCommandTest(t, dir, bareDir)

stdout, err := executeCheckpointPolicyCmd(t, "--checkpoint-version", "", "--checkpoint-min-version", "")
require.NoError(t, err)
require.Contains(t, stdout, "checkpoint_version: branch-v1 (default)")
require.Contains(t, stdout, "checkpoint_min_version: branch-v1 (default)")

repo := openCheckpointPolicyRepoForCommandTest(t, dir)
localState, err := checkpointpolicy.ReadLocal(t.Context(), repo)
require.NoError(t, err)
require.Empty(t, localState.Policy)
require.Equal(t, checkpointpolicy.DefaultPolicy(), checkpointpolicy.Normalize(localState.Policy))
}

func TestCheckpointPolicyCmd_SilencesContextCanceled(t *testing.T) {
cmd := &cobra.Command{}
var stdout, stderr bytes.Buffer
Expand Down
36 changes: 36 additions & 0 deletions cmd/entire/cli/checkpoint_policy_test_helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cli

import (
"context"
"testing"

"github.com/entireio/cli/cmd/entire/cli/checkpoint"
"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/go-git/go-git/v6/plumbing/filemode"
"github.com/go-git/go-git/v6/plumbing/object"
"github.com/stretchr/testify/require"
)

func writeMalformedCheckpointPolicyForCLITest(t *testing.T, repo *git.Repository) {
t.Helper()
blobHash, err := checkpoint.CreateBlobFromContent(repo, []byte(`{"checkpoint_version":`))
require.NoError(t, err)
treeHash, err := checkpoint.BuildTreeFromEntries(context.Background(), repo, map[string]object.TreeEntry{
checkpointpolicy.PolicyFileName: {Name: checkpointpolicy.PolicyFileName, Mode: filemode.Regular, Hash: blobHash},
})
require.NoError(t, err)
commitHash, err := checkpoint.CreateCommit(context.Background(), repo, treeHash, plumbing.ZeroHash, "malformed checkpoint policy", "Test", "test@example.com")
require.NoError(t, err)
require.NoError(t, checkpointpolicy.SetRef(repo, checkpointpolicy.RefName, commitHash))
}

func writeUnsupportedCheckpointPolicyForCLITest(t *testing.T, repo *git.Repository) {
t.Helper()
_, err := checkpointpolicy.WriteLocal(t.Context(), repo, plumbing.ZeroHash, checkpointpolicy.Policy{
CheckpointVersion: "refs-v1",
CheckpointMinVersion: "branch-v1",
})
require.NoError(t, err)
}
7 changes: 5 additions & 2 deletions cmd/entire/cli/checkpoint_policy_warning.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,12 @@ func WarnCheckpointPolicyIfNeeded(ctx context.Context, w io.Writer, currentVersi
if err != nil {
return
}
if !checkpointpolicy.RequiresUpgrade(state.Policy) && !checkpointpolicy.UnsupportedWrite(state.Policy) {
if checkpointpolicy.CanSatisfyPolicy(state.Policy) {
return
}

fmt.Fprint(w, checkpointpolicy.UpgradeWarning(versioncheck.UpdateCommandForCurrentBinary(currentVersion)))
fmt.Fprint(w, checkpointpolicy.UnsupportedPolicyMessage(
state.Policy,
versioncheck.UpdateCommandForCurrentBinary(currentVersion),
))
}
Loading
Loading