fix(onboarding): History rung no longer reads a stale scan cache under a git-refs primary · Entire
fix(onboarding): History rung no longer reads a stale scan cache under a git-refs primary
826c6ce·
peyton-alt·2d ago·2 files·+93 added/-3 removed
The import-scan cache fingerprint only watched the v1 metadata branch tip, but under a git-refs checkpoint primary an import writes per-checkpoint refs (refs/entire/checkpoints/...) and never moves that branch. The post-import re-check then served the stale pre-import cache entry, so enable printed 'Imported 2 turn(s)' directly above '✗ History ... not imported'.
Fold an order-independent digest of the per-checkpoint refs into the fingerprint; a ref-iteration failure marks the scan uncacheable so a partial digest can never produce a false cache hit.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Sessions
01KXM1F8EQ6RFZK44H4VFB9Y1ZView transcript
Changes
2
cmd/entire/cli
Monboarding_rungs.go+47/-3
Monboarding_rungs_test.go+46
1 unmodified line
2
3
4
5
6
7
8
9
10
11
12
13
2 unmodified lines
16
17
18
19
20
21
22
265 unmodified lines
288
289
290
287
291
292
293
294
295
296
297
298
299
300
34 unmodified lines
335
336
337
328
329
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
2 unmodified lines
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
1 unmodified line
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"os"
"sort"
"strings"
"time"
2 unmodified lines
"github.com/entireio/cli/cmd/entire/cli/agentimport"
"github.com/entireio/cli/cmd/entire/cli/auth"
"github.com/entireio/cli/cmd/entire/cli/checkpoint"
"github.com/entireio/cli/cmd/entire/cli/gitremote"
"github.com/entireio/cli/cmd/entire/cli/logging"
"github.com/entireio/cli/cmd/entire/cli/onboarding"
265 unmodified lines
return nil, fmt.Errorf("%w: %w", errImportsPolicyRestricted, policyErr)
}
fingerprint := importScanFingerprint(inputs, metadataBranchTip(repo))
tip, tipErr := metadataTip(repo)
if tipErr != nil {
// A digest computed without the checkpoint refs could collide with the
// pre-import fingerprint and serve a stale "not imported" result.
cacheable = false
}
fingerprint := importScanFingerprint(inputs, tip)
cache := defaultImportScanCache()
if cacheable {
if statuses, ok := cache.get(repoRoot, fingerprint); ok {
34 unmodified lines
return statuses, nil
}
// metadataBranchTip identifies the local checkpoint-metadata state for the
// import-scan fingerprint: any new checkpoint or import moves the ref.
// metadataTip identifies the local checkpoint-metadata state for the
// import-scan fingerprint: any new checkpoint or import moves it. Imports land
// on whichever backend is the configured primary, so both are covered — the
// v1 metadata branch tip plus the per-checkpoint refs the git-refs store
// writes (refs/entire/checkpoints/...), which never move the branch tip. The
// error reports a digest computed without the checkpoint refs; callers must
// not cache under it.
func metadataTip(repo *git.Repository) (string, error) {
digest, err := checkpointRefsDigest(repo)
return metadataBranchTip(repo) + "+" + digest, err
}
// metadataBranchTip is the git-branch backend's tip (the v1 metadata branch).
func metadataBranchTip(repo *git.Repository) string {
ref, err := repo.Reference(plumbing.NewBranchReferenceName(paths.MetadataBranchName), true)
if err != nil {
2 unmodified lines
return ref.Hash().String()
}
// checkpointRefsDigest hashes the git-refs backend's per-checkpoint refs
// (name and hash), order-independently. An iteration failure returns the
// digest of whatever was seen plus the error.
func checkpointRefsDigest(repo *git.Repository) (string, error) {
var lines []string
iter, err := repo.References()
if err == nil {
err = iter.ForEach(func(ref *plumbing.Reference) error {
if name := ref.Name().String(); strings.HasPrefix(name, checkpoint.CheckpointRefPrefix) {
lines = append(lines, name+"|"+ref.Hash().String())
}
return nil
})
}
sort.Strings(lines)
h := sha256.New()
for _, l := range lines {
h.Write([]byte(l))
h.Write([]byte{‘\n’})
}
return hex.EncodeToString(h.Sum(nil)), err
}
func hooksRung(deps onboardingRungDeps) onboarding.Rung {
return onboarding.Rung{
Key: onboarding.KeyHooks,
Mcmd/entire/cli/onboarding_rungs.go+47/-3
9 unmodified lines
10
11
12
13
14
15
16
17
18
19
20
21
660 unmodified lines
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
9 unmodified lines
testing"
"time"
"github.com/go-git/go-git/v6"
"github.com/go-git/go-git/v6/plumbing"
"github.com/entireio/cli/cmd/entire/cli/checkpoint"
"github.com/entireio/cli/cmd/entire/cli/onboarding"
"github.com/entireio/cli/cmd/entire/cli/testutil"
"github.com/entireio/cli/internal/coreapi"
"github.com/entireio/cli/internal/entireclient/contexts"
)
660 unmodified lines
}
// Regression: under a git-refs checkpoint primary, an import writes
// refs/entire/checkpoints/<shard>/<id> and never moves the v1 metadata
// branch. The scan fingerprint's metadata tip must still change, or the
// post-import re-check serves the stale pre-import cache entry — `entire
// enable` printed "Imported 2 turn(s)" directly above "✗ History ... not
// imported".
func TestMetadataTip_MovesWithCheckpointRefs(t *testing.T) {
t.Parallel()
dir := t.TempDir()
testutil.InitRepo(t, dir)
testutil.WriteFile(t, dir, "f.txt", "init")
testutil.GitAdd(t, dir, "f.txt")
testutil.GitCommit(t, dir, "init")
repo, err := git.PlainOpen(dir)
if err != nil {
t.Fatalf("open repo: %v", err)
}
before, err := metadataTip(repo)
if err != nil {
t.Fatalf("metadataTip before: %v", err)
}
head, err := repo.Head()
if err != nil {
t.Fatalf("head: %v", err)
}
refName := plumbing.ReferenceName(checkpoint.CheckpointRefPrefix + "3q/01hzxw2e8g0000000000003q3q")
if err := repo.Storer.SetReference(plumbing.NewHashReference(refName, head.Hash())); err != nil {
t.Fatalf("set checkpoint ref: %v", err)
}
after, err := metadataTip(repo)
if err != nil {
t.Fatalf("metadataTip after: %v", err)
}
if after == before {
t.Error("a new per-checkpoint ref (git-refs import) must change the metadata tip")
}
}
func TestImportScanCache_HitRequiresMatchingFingerprint(t *testing.T) {
t.Parallel()
cache := importScanCache{path: filepath.Join(t.TempDir(), "imports.json")}