fix checkpoint policy enforcement · Entire

fix checkpoint policy enforcement

5f081d9·

pfleidi·3w ago·16 files·+468 added/-43 removed

Reject unsupported checkpoint reads and user-driven checkpoint writes consistently.

Resolve policy refs through the configured checkpoint remote and skip checkpoint pushes when local policy state diverges from remote policy.

Sessions

9fc06b382e3eView transcript

Changes

16

227 unmodified lines

228
229
230
231
232
233
234
235
236
237

227 unmodified lines

return nil
 }

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

// Resolve agent and transcript path.
 ag, transcriptPath, err := resolveAgentAndTranscript(logCtx, w, sessionID, agentName, existingState)
 if err != nil {

Mcmd/entire/cli/attach.go+4

20 unmodified lines

21
22
23
24
25
26
27
41 unmodified lines

69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107

20 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/paths";
 cliReview "github.com/entireio/cli/cmd/entire/cli/review";
 "github.com/entireio/cli/cmd/entire/cli/session";
41 unmodified lines

}
 }

func TestAttachRejectsUnsupportedCheckpointWritePolicy(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)
 }

sessionID := "test-attach-policy-unsupported"
 setupClaudeTranscript(t, sessionID, `{"type":"user","message":{"role":"user","content":"create a file"},"uuid":"uuid-1"}
{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Done"}]},"uuid":"uuid-2"}`)

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 !strings.Contains(err.Error(), `checkpoint_version "refs-v1"`) {
 t.Fatalf("error = %v, want checkpoint policy version", err)
 }
 if _, refErr := repo.Reference(plumbing.NewBranchReferenceName(paths.MetadataBranchName), true); refErr == nil {
 t.Fatal("metadata branch exists after rejected attach")
 }
 }
 
 func TestAttach_Success(t *testing.T) {
 setupAttachTestRepo(t)

Mcmd/entire/cli/attach_test.go+34

185 unmodified lines

186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
189
190
191
192
26 unmodified lines

219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
48 unmodified lines

305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328

185 unmodified lines

}
 return "", true, fmt.Errorf("no push URL found: %w", err)
 }
 if strings.TrimSpace(os.Getenv(CheckpointTokenEnvVar)) != "" && isDerivableProtocol(pushInfo.Protocol) {
 // Coerce a derivable (ssh/https) remote to HTTPS so the token applies,
 // keeping the host so enterprise installations stay on their own host.
 // A non-derivable protocol (e.g. entire://) carries a host that isn't a
 // usable HTTPS host, so it's left untouched and falls through to the
 // providerCheckpointURL fallback below.
 // 
 // Keep the port only when the source was already HTTPS. SSH ports
 // (e.g., :2222) don't map to HTTPS ports on the same host.
 port := ""
 if pushInfo.Protocol == ProtocolHTTPS {
 port = pushInfo.Port
 }
 pushInfo = &Info{
 Protocol: ProtocolHTTPS,
 Host: pushInfo.Host,
 Port: port,
 Owner: pushInfo.Owner,
 Repo: pushInfo.Repo,
 }
 }
 pushInfo = checkpointTokenTransport(pushInfo)

checkpointOwner := config.Owner()
 if pushInfo.Owner != "" && checkpointOwner != "" && !strings.EqualFold(pushInfo.Owner, checkpointOwner) {
26 unmodified lines

return pushURL, true, nil
}

// ConfiguredURL returns the configured checkpoint_remote URL without the
// push-owner fallback used by PushURL. The boolean reports whether a
// checkpoint_remote is configured.
func ConfiguredURL(ctx context.Context, remoteName, dir string) (string, bool, error) {
 s, err := settings.Load(ctx)
 if err != nil {
 return "", false, fmt.Errorf("load settings: %w", err)
 }
 config := s.GetCheckpointRemote()
 if config == nil {
 return "", false, nil
 }

remoteURL, remoteErr := GetRemoteURLInDir(ctx, dir, remoteName)
 if remoteErr == nil {
 info, parseErr := ParseURL(remoteURL)
 if parseErr == nil {
 if checkpointURL, deriveErr := deriveCheckpointURLFromInfo(checkpointTokenTransport(info), config); deriveErr == nil {
 return checkpointURL, true, nil
 }
 }
 }

if providerURL, ok := resolveProviderCheckpointURL(config, remoteName, dir); ok {
 return providerURL, true, nil
 }
 if remoteErr != nil {
 return "", true, fmt.Errorf("get %s remote URL: %w", remoteName, remoteErr)
 }
 return "", true, fmt.Errorf("resolve checkpoint remote URL from %s", remoteName)
}

// Configured reports whether a structured checkpoint_remote is configured.
func Configured(ctx context.Context) bool {
 s, err := settings.Load(ctx)
48 unmodified lines

return protocol == ProtocolSSH || protocol == ProtocolHTTPS
}

func checkpointTokenTransport(info *Info) *Info {
 if strings.TrimSpace(os.Getenv(CheckpointTokenEnvVar)) == "" || !isDerivableProtocol(info.Protocol) {
 return info
 }

port := ""
 if info.Protocol == ProtocolHTTPS {
 port = info.Port
 }
 return &Info{
 Protocol: ProtocolHTTPS,
 Host: info.Host,
 Port: port,
 Owner: info.Owner,
 Repo: info.Repo,
 }
}

func deriveCheckpointURLFromInfo(info *Info, config *settings.CheckpointRemoteConfig) (string, error) {
 switch info.Protocol {
 case ProtocolSSH:

Mcmd/entire/cli/checkpoint/remote/util.go+51/-21

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

package cli

import (
 "context"
 "fmt"

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

func ensureCommittedCheckpointWritePolicy(ctx context.Context, repo *git.Repository) error {
 state, err := checkpointpolicy.ReadLocal(ctx, repo)
 if err != nil {
 return fmt.Errorf("read checkpoint policy: %w", err)
 }
 if !checkpointpolicy.UnsupportedWrite(state.Policy) {
 return nil
 }
 return fmt.Errorf(
 "checkpoint policy requires checkpoint_version %q, which this Entire CLI cannot write; upgrade Entire and rerun the command: %s",
 state.Policy.CheckpointVersion,
 versioncheck.UpdateCommandForCurrentBinary(versioninfo.Version),
 )
}

Acmd/entire/cli/checkpoint_policy_write.go+26

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

package checkpointpolicy

import (
 "errors"
 "fmt"

"github.com/entireio/cli/cmd/entire/cli/checkpoint"
)

func IsUnsupportedVersion(err error) bool {
 var unsupported *unsupportedVersionError
 return errors.As(err, &unsupported)
}

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)
 return &unsupportedVersionError{
 CheckpointID: checkpointID,
 Version: policy.CheckpointMinVersion,
 Err: 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 &unsupportedVersionError{
 CheckpointID: checkpointID,
 Version: policy.CheckpointMinVersion,
 Err: errors.New("not read-supported by this Entire CLI"),
 }
 }
 return nil
}

type unsupportedVersionError struct {
 CheckpointID string
 Version      string
 Err          error
}

func (e unsupportedVersionError) Error() string {
 return fmt.Sprintf("checkpoint %s uses unsupported checkpoint_version %q: %v", e.CheckpointID, e.Version, e.Err)
}

func (e unsupportedVersionError) Unwrap() error {
 return e.Err
}

Mcmd/entire/cli/checkpointpolicy/policy.go+31/-2

37 unmodified lines

38
39
40
41
42
43
44
45
46
47
48

37 unmodified lines

if err != nil {
 return Target{}, fmt.Errorf("resolve worktree root: %w", err)
 }
 if target, dedicated, err := remote.ConfiguredURL(ctx, baseRemote, dir); err != nil {
 return Target{}, fmt.Errorf("resolve checkpoint remote URL: %w", err)
 } else if dedicated {
 return Target{Remote: target, Label: "checkpoint remote", Dir: dir}, nil
 }
 target, dedicated, err := remote.PushURL(ctx, baseRemote)
 if err != nil {
 return Target{}, fmt.Errorf("resolve checkpoint push URL: %w", err)
 }

Mcmd/entire/cli/checkpointpolicy/remote.go+5

1 unmodified line

2
3
4
5
6
7
8
9
10
11
12
13
14
96 unmodified lines

111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
15 unmodified lines

160
161
162
133
163
164
165
166
167
168
169
170
171

1 unmodified line

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

"github.com/entireio/cli/cmd/entire/cli/checkpointpolicy"
 "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"
)

require.ErrorContains(t, err, "push checkpoint policy")

func TestResolveTargetUsesConfiguredCheckpointRemoteWithOriginOwnerMismatch(t *testing.T) {
 localDir, _ := initPolicyRepoWithDir(t)
 runPolicyGit(t, localDir, "remote", "add", "origin", "git@github.com:fork/cli.git")
 require.NoError(t, os.MkdirAll(filepath.Join(localDir, ".entire"), 0o750))
 require.NoError(t, os.WriteFile(filepath.Join(localDir, ".entire", "settings.json"), []byte(`{
  "enabled": true,
  "strategy_options": {
    "checkpoint_remote": {
      "provider": "github",
      "repo": "org/checkpoints"
    }
  }
}`), 0o600))

t.Chdir(localDir)
 paths.ClearWorktreeRootCache()

target, err := checkpointpolicy.ResolveTarget(t.Context(), "origin")
 require.NoError(t, err)
 require.Equal(t, "git@github.com:org/checkpoints.git", target.Remote)
 require.Equal(t, "checkpoint remote", target.Label)
 wantDir, err := filepath.EvalSymlinks(localDir)
 require.NoError(t, err)
 gotDir, err := filepath.EvalSymlinks(target.Dir)
 require.NoError(t, err)
 require.Equal(t, wantDir, gotDir)
}

func initPolicyRemoteFixture(t *testing.T) (string, *git.Repository, string) {
 t.Helper()
 localDir, repo := initPolicyRepoWithDir(t)
15 unmodified lines

func pushPolicyRefWithGit(t *testing.T, dir, remote string) {
 t.Helper()
 refspec := checkpointpolicy.RefName.String() + ":" + checkpointpolicy.RefName.String()
 cmd := exec.CommandContext(context.Background(), "git", "push", remote, refspec)
 runPolicyGit(t, dir, "push", remote, refspec)
}

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

Mcmd/entire/cli/checkpointpolicy/remote_test.go+36/-1

686 unmodified lines

687
688
689
690
690
691
692
693
205 unmodified lines

899
900
901
902
902
903
904
905
16 unmodified lines

922
923
924
925
926
927
928
929
930

686 unmodified lines

if openErr != nil {
 return fmt.Errorf("open checkpoint store: %w", openErr)
 }
 if err := generateCheckpointSummary(ctx, w, errW, writeStores.Primary, fullCheckpointID, summary, content, force, summaryTimeoutSeconds); err != nil {
 if err := generateCheckpointSummary(ctx, w, errW, lookup.repo, writeStores.Primary, fullCheckpointID, summary, content, force, summaryTimeoutSeconds); err != nil {
 return err
 }
 // Reload to get the updated summary.
205 unmodified lines

// summaryTimeoutSeconds is the per-invocation --summary-timeout-seconds flag
// value (0 = unset). Effective precedence for the deadline: flag > settings >
// package default. See resolveSummaryTimeout for the resolution.
func generateCheckpointSummary(ctx context.Context, w, errW io.Writer, store checkpoint.Writer, checkpointID id.CheckpointID, cpSummary *checkpoint.CheckpointSummary, content *checkpoint.SessionContent, force bool, summaryTimeoutSeconds int) error {
func generateCheckpointSummary(ctx context.Context, w, errW io.Writer, repo *git.Repository, store checkpoint.Writer, checkpointID id.CheckpointID, cpSummary *checkpoint.CheckpointSummary, content *checkpoint.SessionContent, force bool, summaryTimeoutSeconds int) error {
 // Check if summary already exists
 if content.Metadata.Summary != nil && !force {
 return renderExplainFailure(errW, "Summary already exists", []explainRow{
16 unmodified lines

{Label: "id", Value: checkpointID.String()},
}, fmt.Errorf("checkpoint %s has no transcript content for this checkpoint (scoped)", checkpointID))
 }
 if err := ensureCommittedCheckpointWritePolicy(ctx, repo); err != nil {
 return err
 }
 provider, err := resolveCheckpointSummaryProvider(ctx, w)
 if err != nil {
 return fmt.Errorf("failed to resolve summary provider: %w", err)
 }

Mcmd/entire/cli/explain.go+5/-2

10 unmodified lines

11
12
13
14
15
16
17
248 unmodified lines

266
267
268
269
270
271
272
273
274
88 unmodified lines

363
364
365
366
367
368
369
370
371

10 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/checkpointpolicy";
 "github.com/entireio/cli/cmd/entire/cli/strategy";
 "github.com/entireio/cli/cmd/entire/cli/trailers";
)
248 unmodified lines

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

idx, err := resolveSessionIndex(summary, opts.sessionIndex)
 if err != nil {
88 unmodified lines

envelope, failedSessions := buildCheckpointJSONEnvelope(ctx, store, summary, cpID)
}

Mcmd/entire/cli/explain_export.go+7/-2

16 unmodified lines

17 18 19 20 21 22 23 55 unmodified lines

79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 21 unmodified lines

144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 121 unmodified lines

291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323

16 unmodified lines

"github.com/entireio/cli/cmd/entire/cli/testutil"; "github.com/entireio/cli/redact"; "github.com/go-git/go-git/v6"; "github.com/go-git/go-git/v6/plumbing"; "github.com/go-git/go-git/v6/plumbing/object"; "github.com/stretchr/testify/require"; ) 55 unmodified lines

require.NoError(t, store.WriteCommitted(context.Background(), opts)) }

func rewriteExportCheckpointVersion(t *testing.T, repo *git.Repository, cpID id.CheckpointID, version string) { t.Helper() ctx := context.Background() refName := plumbing.NewBranchReferenceName(paths.MetadataBranchName) ref, err := repo.Reference(refName, true) require.NoError(t, err) commit, err := repo.CommitObject(ref.Hash()) require.NoError(t, err) tree, err := commit.Tree() require.NoError(t, err)

metadataPath := cpID.Path() + "/" + paths.MetadataFileName metadataFile, err := tree.File(metadataPath) require.NoError(t, err) content, err := metadataFile.Contents() require.NoError(t, err) var summary checkpoint.CheckpointSummary require.NoError(t, json.Unmarshal([]byte(content), &summary)) summary.CheckpointVersion = version metadataJSON, err := json.Marshal(summary) require.NoError(t, err) metadataHash, err := checkpoint.CreateBlobFromContent(repo, metadataJSON) require.NoError(t, err)

entries := make(map[string]object.TreeEntry) require.NoError(t, tree.Files().ForEach(func(file *object.File) error { entries[file.Name] = object.TreeEntry{Name: file.Name, Mode: file.Mode, Hash: file.Hash} return nil })) entries[metadataPath] = object.TreeEntry{Name: metadataPath, Mode: metadataFile.Mode, Hash: metadataHash}

treeHash, err := checkpoint.BuildTreeFromEntries(ctx, repo, entries) require.NoError(t, err) commitHash, err := checkpoint.CreateCommit(ctx, repo, treeHash, ref.Hash(), "rewrite checkpoint summary\n", exportTestAuthorName, exportTestAuthorEmail) require.NoError(t, err) require.NoError(t, repo.Storer.SetReference(plumbing.NewHashReference(refName, commitHash))) }

func TestRunExplainExport_JSONSingleCheckpoint(t *testing.T) { repo := setupExportRepo(t)

require.Equal(t, 0, envelope.Sessions[0].Index) }

func TestRunExplainExportJSONRejectsUnsupportedCheckpointVersion(t *testing.T) { repo := setupExportRepo(t)

cpID := id.MustCheckpointID("aaaabbbbcccc") writeCheckpointForExport(t, repo, cpID, checkpoint.WriteCommittedOptions{ SessionID: "session-json-unsupported", Transcript: redact.AlreadyRedacted([]byte({"type":"user","message":{"content":[{"type":"text","text":"hi"}]}} + "\n")), }) rewriteExportCheckpointVersion(t, repo, cpID, "refs-v1")

var stdout, stderr bytes.Buffer err := runExplainExport(context.Background(), &stdout, &stderr, explainExportOptions{ target: "aaaabbbb", json: true, sessionIndex: -1, }) require.ErrorContains(t, err, checkpoint aaaabbbbcccc uses unsupported checkpoint_version "refs-v1") require.Empty(t, stdout.String()) }

func TestRunExplainExport_JSONFetchesRemoteV1Metadata(t *testing.T) { tmpDir := t.TempDir() bareDir := filepath.Join(tmpDir, "origin.git") 121 unmodified lines

require.Equal(t, raw, stdout.Bytes()) }

func TestRunExplainExportTranscriptRejectsUnsupportedCheckpointVersion(t *testing.T) { tests := []struct { name string opts explainExportOptions }{ {name: "transcript", opts: explainExportOptions{target: "abcd1111", transcript: true, sessionIndex: -1}}, {name: "raw transcript", opts: explainExportOptions{target: "abcd1111", rawTranscript: true, sessionIndex: -1}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { repo := setupExportRepo(t) cpID := id.MustCheckpointID("abcd11112222") raw := []byte({"type":"user","message":{"content":[{"type":"text","text":"stored line"}]}} + "\n") writeCheckpointForExport(t, repo, cpID, checkpoint.WriteCommittedOptions{ SessionID: "session-unsupported-transcript", Transcript: redact.AlreadyRedacted(raw), }) rewriteExportCheckpointVersion(t, repo, cpID, "refs-v1")

var stdout, stderr bytes.Buffer err := runExplainExport(context.Background(), &stdout, &stderr, tt.opts) require.ErrorContains(t, err, checkpoint abcd11112222 uses unsupported checkpoint_version "refs-v1") require.Empty(t, stdout.String()) }) } }

func TestRunExplainExport_RawTranscriptStreamsRawBytes(t *testing.T) { repo := setupExportRepo(t)


Mcmd/entire/cli/explain_export_test.go+86

20 unmodified lines

21 22 23 24 25 26 27 968 unmodified lines

996 997 998 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 27 unmodified lines

1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1131 1132 1133 1134 1135 1136

20 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/paths"; "github.com/entireio/cli/cmd/entire/cli/settings"; "github.com/entireio/cli/cmd/entire/cli/strategy"; 968 unmodified lines

}

// Not parallel: uses t.Chdir() and package-level var stubs. func TestGenerateCheckpointSummary_AdvancesV1Metadata(t *testing.T) { type generateSummaryFixture struct { ctx context.Context repo *git.Repository store checkpoint.CommittedStore cpID id.CheckpointID cpSummary *checkpoint.CheckpointSummary content *checkpoint.SessionContent v1Hash plumbing.Hash }

func setupGenerateSummaryFixture(t *testing.T) generateSummaryFixture { t.Helper() ctx := context.Background() tmpDir := t.TempDir() testutil.InitRepo(t, tmpDir) 27 unmodified lines

v1Before, err := repo.Reference(plumbing.NewBranchReferenceName(paths.MetadataBranchName), true) require.NoError(t, err)

return generateSummaryFixture{ ctx: ctx, repo: repo, store: store, cpID: cpID, cpSummary: cpSummary, content: content, v1Hash: v1Before.Hash(), } }

func stubSummaryProviderForTest(t *testing.T) { t.Helper()

origLoad := loadSummarySettings origGet := getSummaryAgent origCLI := isSummaryCLIAvailable 19 unmodified lines

generateTranscriptSummary = func(context.Context, redact.RedactedBytes, []string, types.AgentType, summarize.Generator) (*checkpoint.Summary, error) { return &checkpoint.Summary{Intent: "i", Outcome: "o"}, nil } }

func TestGenerateCheckpointSummary_AdvancesV1Metadata(t *testing.T) { fixture := setupGenerateSummaryFixture(t) stubSummaryProviderForTest(t)

var stdout, stderr bytes.Buffer require.NoError(t, generateCheckpointSummary(ctx, &stdout, &stderr, stores.Primary, cpID, cpSummary, content, false, 0)) require.NoError(t, generateCheckpointSummary( fixture.ctx, &stdout, &stderr, fixture.repo, fixture.store, fixture.cpID, fixture.cpSummary, fixture.content, false, 0, ))

v1After, err := repo.Reference(plumbing.NewBranchReferenceName(paths.MetadataBranchName), true) require.NoError(t, err) require.NotEqual(t, v1Before.Hash(), v1After.Hash(), "v1 metadata branch must advance after UpdateSummary") require.NotEqual(t, fixture.v1Hash, v1After.Hash(), "v1 metadata branch must advance after UpdateSummary") }

func TestGenerateCheckpointSummaryRejectsUnsupportedCheckpointWritePolicy(t *testing.T) { fixture := setupGenerateSummaryFixture(t) _, err := checkpointpolicy.WriteLocal(fixture.ctx, fixture.repo, plumbing.ZeroHash, checkpointpolicy.Policy{ CheckpointVersion: "refs-v1", CheckpointMinVersion: "branch-v1", }) require.NoError(t, err)

var stdout, stderr bytes.Buffer err = generateCheckpointSummary( fixture.ctx, &stdout, &stderr, fixture.repo, fixture.store, fixture.cpID, fixture.cpSummary, fixture.content, false, 0, ) require.ErrorContains(t, err, checkpoint_version "refs-v1")

v1After, refErr := fixture.repo.Reference(plumbing.NewBranchReferenceName(paths.MetadataBranchName), true) require.NoError(t, refErr) require.Equal(t, fixture.v1Hash, v1After.Hash(), "v1 metadata branch must not advance after rejected summary write") }

func TestGenerateCheckpointAISummary_ClampsLongParentDeadlineToDefaultTimeout(t *testing.T) {


Mcmd/entire/cli/explain_test.go+74/-4

214 unmodified lines

215 216 217 218 219 220 221 222 223 63 unmodified lines

287 288 289 287 290 291 292 293 294 295 296 297 292 298 299 300 11 unmodified lines

312 313 314 315 316 317 318 319 320 17 unmodified lines

338 339 340 333 341 342 343 344 345 346 347 348 349 350 351 4 unmodified lines

356 357 358 348 359 360 350 361 362 363 364

214 unmodified lines

metadata, err := readCheckpointInfoFromStore(ctx, store, checkpointID) if err != nil { if checkpointpolicy.IsUnsupportedVersion(err) { return err } logging.Debug(ctx, "resume by checkpoint: metadata read failed, checking remote", slog.String("checkpoint_id", checkpointID.String()), slog.String("error", err.Error()), ) 63 unmodified lines

// Multiple checkpoints (squash merge): resolve latest by CreatedAt timestamp. if len(result.checkpointIDs) > 1 { latestMetadata, err := resolveLatestCheckpoint(ctx, store, result.checkpointIDs) latestMetadata, found, err := resolveLatestCheckpoint(ctx, store, result.checkpointIDs) if err != nil { return err } if !found { // No metadata available — nothing to resume from logging.Warn(logCtx, "resolveLatestCheckpoint failed", slog.Int("checkpoint_count", len(result.checkpointIDs)), slog.String("error", err.Error()), ) fmt.Fprintf(w, "Found %d checkpoints for commit %s but metadata is not available\n", len(result.checkpointIDs), result.commitHash[:7]) 11 unmodified lines

if storeErr == nil { metadata = storeInfo } else { if checkpointpolicy.IsUnsupportedVersion(storeErr) { return storeErr } logging.Debug(ctx, "checkpoint store metadata read failed", slog.String("checkpoint_id", checkpointID.String()), slog.String("error", storeErr.Error()), ) 17 unmodified lines

// resolveLatestCheckpoint reads metadata for each checkpoint ID and returns // the checkpoint with the latest CreatedAt. func resolveLatestCheckpoint(ctx context.Context, store checkpointInfoReader, checkpointIDs []id.CheckpointID) (*strategy.CheckpointInfo, error) { func resolveLatestCheckpoint(ctx context.Context, store checkpointInfoReader, checkpointIDs []id.CheckpointID) (*strategy.CheckpointInfo, bool, error) { infoMap := make(map[id.CheckpointID]strategy.CheckpointInfo, len(checkpointIDs)) for _, cpID := range checkpointIDs { metadata, readErr := readCheckpointInfoFromStore(ctx, store, cpID) if readErr != nil { if checkpointpolicy.IsUnsupportedVersion(readErr) { return nil, false, readErr } logging.Debug(ctx, "resolveLatestCheckpoint: checkpoint metadata read failed", slog.String("checkpoint_id", cpID.String()), slog.String("error", readErr.Error()), ) 4 unmodified lines

} latest, found := strategy.ResolveLatestCheckpointFromMap(checkpointIDs, infoMap) if !found { return nil, errors.New("no checkpoint metadata found") return nil, false, nil } return &latest, nil return &latest, true, nil }

type checkpointInfoReader interface {


Mcmd/entire/cli/resume.go+16/-5

570 unmodified lines

571 572 573 574 574 575 576 577 578 579 580 581 582 583 2 unmodified lines

586 587 588 586 589 590 591 592 593 594 595 596 597 598 21 unmodified lines

620 621 622 617 623 624 625 626 627 628 629 630 631 632 92 unmodified lines

725 726 727 719 728 729 730 731 732 733 734 735 736 737

570 unmodified lines

// simulating git CLI squash merge trailer order. reverseOrderIDs := []id.CheckpointID{cpID3, cpID2, cpID1} reader := checkpoint.NewGitStore(repo, checkpoint.DefaultV1Refs()) latest, err := resolveLatestCheckpoint(context.Background(), reader, reverseOrderIDs) latest, found, err := resolveLatestCheckpoint(context.Background(), reader, reverseOrderIDs) if err != nil { t.Fatalf("resolveLatestCheckpoint() error = %v", err) } if !found { t.Fatal("resolveLatestCheckpoint() found = false") }

// Should return the newest checkpoint regardless of input order if latest.CheckpointID.String() != cpID3.String() { 2 unmodified lines

// Also verify with chronological order chronologicalIDs := []id.CheckpointID{cpID1, cpID2, cpID3} latest2, err := resolveLatestCheckpoint(context.Background(), reader, chronologicalIDs) latest2, found, err := resolveLatestCheckpoint(context.Background(), reader, chronologicalIDs) if err != nil { t.Fatalf("resolveLatestCheckpoint() error = %v", err) } if !found { t.Fatal("resolveLatestCheckpoint() found = false") } if latest2.CheckpointID.String() != cpID3.String() { t.Errorf("resolveLatestCheckpoint() = %s, want newest %s", latest2.CheckpointID, cpID3) } 21 unmodified lines

latest, err := resolveLatestCheckpoint(context.Background(), store, []id.CheckpointID{targetID}) latest, found, err := resolveLatestCheckpoint(context.Background(), store, []id.CheckpointID{targetID}) if err != nil { t.Fatalf("resolveLatestCheckpoint() error = %v", err) } if !found { t.Fatal("resolveLatestCheckpoint() found = false") } if latest.CheckpointID != targetID { t.Errorf("resolveLatestCheckpoint() = %s, want %s", latest.CheckpointID, targetID) }


Mcmd/entire/cli/resume_test.go+16/-4

17 unmodified lines

18 19 20 21 22 23 24 306 unmodified lines

331 332 333 333 334 335 336 337 338 339 340 341 342 194 unmodified lines

537 538 539 536 540 541 542 543 544 545 546 547 548 180 unmodified lines

729 730 731 725 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 9 unmodified lines

760 761 762 747 763 764 765 766

17 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/gitrepo"; "github.com/entireio/cli/cmd/entire/cli/jsonutil"; "github.com/entireio/cli/cmd/entire/cli/logging"; 306 unmodified lines

var restored bool if !selectedPoint.CheckpointID.IsEmpty() { // Try checkpoint storage first for committed checkpoints if returnedSessionID, err := restoreSessionTranscriptFromStrategy(ctx, selectedPoint.CheckpointID, sessionID, agent); err == nil { returnedSessionID, err := restoreSessionTranscriptFromStrategy(ctx, selectedPoint.CheckpointID, sessionID, agent) if err == nil { sessionID = returnedSessionID restored = true } else if checkpointpolicy.IsUnsupportedVersion(err) { return err } }

194 unmodified lines

180 unmodified lines

if err != nil { return "", fmt.Errorf("open checkpoint store: %w", err) } content, returnedSessionID, err := checkpoint.ReadRawSessionLogForCheckpoint(ctx, stores.Primary, cpID) summary, err := checkpoint.ReadCommittedCheckpoint(ctx, stores.Primary, cpID) if err != nil { return "", fmt.Errorf("failed to read checkpoint: %w", err) } if err := checkpointpolicy.EnsureCanReadVersion(cpID.String(), summary.CheckpointVersion); err != nil { return "", fmt.Errorf("check checkpoint version: %w", err) }

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

// Use session ID returned from checkpoint if available // Otherwise fall back to the passed-in sessionID returnedSessionID := content.Metadata.SessionID if returnedSessionID != "" { sessionID = returnedSessionID }

}

Mcmd/entire/cli/rewind.go+20/-4

49 unmodified lines

50
51
52
53
54
55
56
57
58
59
11 unmodified lines

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

49 unmodified lines

warnOrLogCheckpointPolicySyncFailure(ctx, err)
 return true
 }
 if state.Source == checkpointpolicy.SourceLocalDiverged {
 warnOrLogCheckpointPolicyDiverged(ctx, state)
 return false
 }
 if !checkpointpolicy.UnsupportedWrite(state.Policy) {
 return true
 }
11 unmodified lines

)
}

func warnOrLogCheckpointPolicyDiverged(ctx context.Context, state checkpointpolicy.State) {
 if interactive.CanPromptInteractively() {
 fmt.Fprintf(stderrWriter, "[entire] Could not reconcile checkpoint policy: %s\n", state.Warning)
 return
 }
 logging.Warn(ctx, "checkpoint policy diverged; skipping checkpoint push",
 slog.String("local_hash", state.Hash.String()),
 slog.String("remote_hash", state.RemoteHash.String()),
)
}

func warnOrLogUnsupportedCheckpointWrite(ctx context.Context, policy checkpointpolicy.Policy) {
 warning := checkpointpolicy.UpgradeWarning(versioncheck.UpdateCommandForCurrentBinary(versioninfo.Version))
 if interactive.CanPromptInteractively() {

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

50 unmodified lines

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98

50 unmodified lines

require.Empty(t, strings.TrimSpace(out))
}

func TestPrePushSkipsCheckpointPushWhenPolicyDiverged(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()
 })
 baseHash, err := checkpointpolicy.WriteLocal(t.Context(), repo, plumbing.ZeroHash, checkpointpolicy.DefaultPolicy())
 require.NoError(t, err)
 runCheckpointPolicyGit(t, workDir, "push", bareDir, checkpointpolicy.RefName.String()+":"+checkpointpolicy.RefName.String())

localHash, err := checkpointpolicy.WriteLocal(t.Context(), repo, baseHash, checkpointpolicy.DefaultPolicy())
 require.NoError(t, err)
 _, err = checkpointpolicy.WriteLocal(t.Context(), repo, baseHash, checkpointpolicy.Policy{
 CheckpointVersion: "refs-v1",
 CheckpointMinVersion: "branch-v1",
 })
 require.NoError(t, err)
 runCheckpointPolicyGit(t, workDir, "push", bareDir, checkpointpolicy.RefName.String()+":"+checkpointpolicy.RefName.String())
 require.NoError(t, checkpointpolicy.SetRef(repo, checkpointpolicy.RefName, localHash))

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(), "Could not reconcile checkpoint policy")

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...)