enforce checkpoint policy during writes · Entire

enforce checkpoint policy during writes

83bbbfc→main·

pfleidi·3w ago·18 files·+627 added/-25 removed

Read the repo checkpoint policy before committed checkpoint writes and skip unsupported hook writes without failing ordinary git operations.

Refresh policy during pre-push, warn user-driven commands when the local policy requires a newer CLI, and document the offline/online behavior.

Sessions

8712032f8b90View transcript

[?
Implement Checkpoint Policy Management SystemCodex·GPT-5.5·2 steps](/content/gh/entireio/cli/session/019ef111-70d5-7203-b653-e4834b8b92c0#timeline-8712032f8b90/index.html)

Changes

18

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

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

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 isCheckpointPolicyWarningExcludedCommand(c.Name()) { return false; } } return true; }

func isCheckpointPolicyWarningExcludedCommand(name string) bool { switch name { case "hooks", "__send_analytics", "curl-bash-post-install": return true; default: return false; } }

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+51

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

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

hiddenAlias := &cobra.Command{Use: "explain", Hidden: true}; root.AddCommand(hiddenAlias);

sendAnalytics := &cobra.Command{Use: "__send_analytics", Hidden: true}; root.AddCommand(sendAnalytics);

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


Acmd/entire/cli/checkpoint_policy_warning_test.go+54

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

51 unmodified lines

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

51 unmodified lines

return nil; }

func RequiresUpgrade(policy Policy) bool { policy = Normalize(policy); minVersion, err := ParseFormat(policy.CheckpointMinVersion); if err != nil { return true; } return !CanRead(minVersion); }

func UnsupportedWrite(policy Policy) bool { policy = Normalize(policy); version, err := ParseFormat(policy.CheckpointVersion); if err != nil { return true; } 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); }


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

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

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 TestRequiresUpgrade(t *testing.T) { t.Parallel();

require.False(t, checkpointpolicy.RequiresUpgrade(checkpointpolicy.DefaultPolicy())); require.True(t, checkpointpolicy.RequiresUpgrade(checkpointpolicy.Policy{ CheckpointVersion: checkpoint.CheckpointVersionBranchV1, CheckpointMinVersion: "refs-v1", })); require.True(t, checkpointpolicy.RequiresUpgrade(checkpointpolicy.Policy{ CheckpointVersion: checkpoint.CheckpointVersionBranchV1, CheckpointMinVersion: "invalid", })); }

func TestUnsupportedWrite(t *testing.T) { t.Parallel();

require.False(t, checkpointpolicy.UnsupportedWrite(checkpointpolicy.DefaultPolicy())); require.True(t, checkpointpolicy.UnsupportedWrite(checkpointpolicy.Policy{ CheckpointVersion: "refs-v1", CheckpointMinVersion: checkpoint.CheckpointVersionBranchV1, })); require.True(t, checkpointpolicy.UnsupportedWrite(checkpointpolicy.Policy{ CheckpointVersion: "invalid", CheckpointMinVersion: checkpoint.CheckpointVersionBranchV1, })); }

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"); }


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

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

20 unmodified lines

21 22 23 24 25 26 27 967 unmodified lines

995 996 997 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 998 1016 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 19 unmodified lines

1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1077 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1079 1103 1104 1081 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135

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"; 967 unmodified lines

} }

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

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

lookup, err := newExplainCheckpointLookup(context.Background()); require.NoError(t, err); defer lookup.Close();

_, _, err = loadCheckpointForExplain(context.Background(), lookup, cpID); require.ErrorContains(t, err, checkpoint bbbbccccdddd uses unsupported checkpoint_version "refs-v1"); }

// 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) { 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); v1After, err := fixture.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/strategy/checkpoint_policy.go+100

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

package strategy;

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

"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) 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); 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 state.Source == checkpointpolicy.SourceLocalDiverged { warnOrLogCheckpointPolicyDiverged(ctx, state); return false; } 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 warnOrLogCheckpointPolicyDiverged(ctx context.Context, state checkpointpolicy.State) { if interactive.CanPromptInteractively() { fmt.Fprintf( stderrWriter, "[entire] Could not reconcile checkpoint policy: local checkpoint policy %s diverges from remote %s\n", state.Hash, state.RemoteHash, ); 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() { 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), ); }


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

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

2784 unmodified lines

2785 2786 2787 2788 2789 2790 2791 2792 2793 2794

2784 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) { 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

409 unmodified lines

410 411 412 413 414 415 416 417 418 419 420 421 422

409 unmodified lines

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

func UpdateCommandForCurrentBinary(currentVersion string) string { if !canAutoInstall() { return downloadsURL; } 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",


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

345 unmodified lines

346 347 348 349 350 351 352 353 35 unmodified lines

389 390 391 390 392 393 394 395 29 unmodified lines

425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478

345 unmodified lines

// it without tripping goconst on repeated string literals. const brewUpgradeCmd = "brew upgrade entire";

const scoopExecutablePath = C:\Users\test\scoop\apps\cli\current\entire.exe;

func TestUpdateCommand(t *testing.T) { const plainBinPath = "/usr/local/bin/entire"; tests := []struct { 35 unmodified lines

{ name: "scoop path", currentVersion: "1.0.0", execPath: func() (string, error) { return C:\Users\test\scoop\apps\cli\current\entire.exe, nil }, execPath: func() (string, error) { return scoopExecutablePath, nil }, want: "scoop update entire/cli", }, { 29 unmodified lines

} }

func TestUpdateCommandForCurrentBinary(t *testing.T) { tests := []struct { name string; currentVersion string; goos string; execPath func() (string, error); want string; }{ { name: "known installer returns command", currentVersion: "1.2.3", goos: goosWindows, execPath: func() (string, error) { return scoopExecutablePath, nil }, want: "scoop update entire/cli", }, { name: "windows unknown installer returns releases URL", currentVersion: "1.2.3", goos: goosWindows, execPath: func() (string, error) { return C:\Program Files\Entire\entire.exe, nil }, want: downloadsURL, }, { name: "non-windows unknown installer returns curl command", currentVersion: "1.2.3", goos: "linux", execPath: func() (string, error) { return "/usr/local/bin/entire", nil }, want: "curl -fsSL https://entire.io/install.sh | bash", }, }

for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { originalExecPath := executablePath; executablePath = tt.execPath; t.Cleanup(func() { executablePath = originalExecPath });

originalGOOS := goos; goos = tt.goos; t.Cleanup(func() { goos = originalGOOS });

if got := UpdateCommandForCurrentBinary(tt.currentVersion); got != tt.want { t.Errorf("UpdateCommandForCurrentBinary() = %q, want %q", got, tt.want); } }) } }

// 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+51/-1

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

277 unmodified lines

278 279 280 281 282 283 284 285 286 287 288 289 290 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

277 unmodified lines

Checkpoint Policy

Repo-wide checkpoint policy lives at refs/entire/policies/checkpoint. The ref points at a commit whose tree contains policy.json:

{
"checkpoint_version": "branch-v1",
"checkpoint_min_version": "branch-v1"
}

checkpoint_version is the checkpoint format new writes should use. checkpoint_min_version is the oldest checkpoint format clients must be able to read for this repo. Missing policy fields default to branch-v1.

Policy follows the configured checkpoint remote. entire policy checkpoint fetches the latest remote policy before validating requested changes, updates the local policy ref, and pushes only refs/entire/policies/checkpoint. Policy commits use the same signing settings as checkpoint commits.

Hooks that run while ordinary git operations must keep working offline: post-commit and agent lifecycle hooks read only the local policy ref. If the local policy requires checkpoint writes this CLI does not support, they skip writing checkpoint data and warn only when running in an interactive terminal. The pre-push hook is the regular online sync point: it compares the remote policy ref with the local ref, fetches updated policy when needed, and evaluates the refreshed policy before pushing entire/checkpoints/v1. If policy refresh fails, the hook warns or logs the failure and lets the normal push continue.

User-driven commands warn when the local policy indicates the CLI should be upgraded. Commands that need to decode checkpoint contents, such as entire checkpoint explain and entire session resume, fail when the target checkpoint uses an unsupported checkpoint_version.

Checkpoint ID Linking

The checkpoint ID is the stable identifier that links user commits to metadata across branches.