detached f90cadc

feat(snapshot): key checkpoints by session incarnation

commit
f90cadc897248d3d105491e9b87a50c24d9d6a12
author
brice · · 2026-07-25 20:35 UTC
parent
7f3e337
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
19 files · +2543 -526
A internal/adapters/snapshot/direct_load_test.go +118 -0
@@ -0,0 +1,118 @@
1+package snapshot
2+
3+import (
4+ "context"
5+ "crypto/sha256"
6+ "os"
7+ "path/filepath"
8+ "sync/atomic"
9+ "testing"
10+
11+ "github.com/bnema/vev/internal/domain"
12+ "github.com/bnema/vev/internal/ports"
13+ codec "github.com/bnema/vev/internal/usecase/snapshot"
14+)
15+
16+func TestIncarnationNamespace(t *testing.T) {
17+ repo := NewRepository(privateDir(t))
18+ id := domain.IncarnationID{1}
19+ pub := incarnationPublication(t, id, "old", 1, nil)
20+ if err := repo.Publish(context.Background(), pub); err != nil {
21+ t.Fatal(err)
22+ }
23+ parent := domain.CheckpointRef{Generation: 1, ManifestDigest: sha256.Sum256(pub.Manifest)}
24+ pub = incarnationPublication(t, id, "new", 2, &parent)
25+ if err := repo.Publish(context.Background(), pub); err != nil {
26+ t.Fatal(err)
27+ }
28+ if got := filepath.Base(repo.sessionPath(id)); got != id.String() {
29+ t.Fatalf("namespace = %q", got)
30+ }
31+
32+ wrong := pub
33+ wrong.IncarnationID = domain.IncarnationID{2}
34+ if err := repo.Publish(context.Background(), wrong); err == nil {
35+ t.Fatal("mismatched publication incarnation accepted")
36+ }
37+}
38+
39+func TestDirectCheckpointLoad(t *testing.T) {
40+ repo := NewRepository(privateDir(t))
41+ id := domain.IncarnationID{1}
42+ pub := incarnationPublication(t, id, "work", 1, nil)
43+ if err := repo.Publish(context.Background(), pub); err != nil {
44+ t.Fatal(err)
45+ }
46+ generations := filepath.Dir(repo.manifestPath(id, 1))
47+ for i := 2; i <= 5001; i++ {
48+ if err := os.WriteFile(filepath.Join(generations, generationFilename(uint64(i))), []byte("stale"), 0o600); err != nil {
49+ t.Fatal(err)
50+ }
51+ }
52+ var generationDirectoryReadCount atomic.Int64
53+ repo.hooks.beforeDirectoryRead = func(path string) {
54+ if path == generations {
55+ generationDirectoryReadCount.Add(1)
56+ }
57+ }
58+ ref := domain.CheckpointRef{Generation: 1, ManifestDigest: sha256.Sum256(pub.Manifest)}
59+ got, err := repo.LoadCheckpoint(context.Background(), id, "work", ref)
60+ if err != nil {
61+ t.Fatal(err)
62+ }
63+ if got.Generation != 1 || got.IncarnationID != id {
64+ t.Fatalf("generation = %#v", got)
65+ }
66+ if generationDirectoryReadCount.Load() != 0 {
67+ t.Fatal("direct load enumerated generation directory")
68+ }
69+}
70+
71+func TestRepairHEAD(t *testing.T) {
72+ repo := NewRepository(privateDir(t))
73+ id := domain.IncarnationID{1}
74+ pub := incarnationPublication(t, id, "work", 1, nil)
75+ if err := repo.Publish(context.Background(), pub); err != nil {
76+ t.Fatal(err)
77+ }
78+ valid := domain.CheckpointRef{Generation: 1, ManifestDigest: sha256.Sum256(pub.Manifest)}
79+ if err := repo.RepairHEAD(context.Background(), id, valid); err != nil {
80+ t.Fatal(err)
81+ }
82+ before, err := os.ReadFile(repo.headPath(id))
83+ if err != nil {
84+ t.Fatal(err)
85+ }
86+ invalid := valid
87+ invalid.ManifestDigest[0]++
88+ if err := repo.RepairHEAD(context.Background(), id, invalid); err == nil {
89+ t.Fatal("invalid checkpoint repaired HEAD")
90+ }
91+ after, err := os.ReadFile(repo.headPath(id))
92+ if err != nil {
93+ t.Fatal(err)
94+ }
95+ if string(after) != string(before) {
96+ t.Fatal("invalid repair changed HEAD")
97+ }
98+}
99+
100+func incarnationPublication(t *testing.T, id domain.IncarnationID, name string, generation uint64, parent *domain.CheckpointRef) ports.SnapshotPublication {
101+ t.Helper()
102+ tail, err := codec.MarshalObject(codec.HistoryTail, []byte("tail"))
103+ if err != nil {
104+ t.Fatal(err)
105+ }
106+ visible, err := codec.MarshalObject(codec.Visible, []byte("visible"))
107+ if err != nil {
108+ t.Fatal(err)
109+ }
110+ manifest, err := codec.MarshalManifest(codec.Manifest{
111+ Generation: generation, IncarnationID: id, ParentCheckpoint: parent, Name: name,
112+ Tabs: []codec.ManifestTab{{Cols: 1, Rows: 1, Panes: []codec.ManifestPane{{ID: "p", Tail: codec.ObjectRef{Kind: codec.HistoryTail, Digest: tail.Digest, Size: uint32(len(tail.Data))}, Visible: codec.ObjectRef{Kind: codec.Visible, Digest: visible.Digest, Size: uint32(len(visible.Data))}}}}},
113+ })
114+ if err != nil {
115+ t.Fatal(err)
116+ }
117+ return ports.SnapshotPublication{IncarnationID: id, Name: name, Generation: generation, ParentCheckpoint: parent, Manifest: manifest, Objects: []ports.SnapshotObject{tail, visible}}
118+}
@@ -0,0 +1,118 @@
1+package snapshot
2+
3+import (
4+ "context"
5+ "crypto/sha256"
6+ "os"
7+ "path/filepath"
8+ "sync/atomic"
9+ "testing"
10+
11+ "github.com/bnema/vev/internal/domain"
12+ "github.com/bnema/vev/internal/ports"
13+ codec "github.com/bnema/vev/internal/usecase/snapshot"
14+)
15+
16+func TestIncarnationNamespace(t *testing.T) {
17+ repo := NewRepository(privateDir(t))
18+ id := domain.IncarnationID{1}
19+ pub := incarnationPublication(t, id, "old", 1, nil)
20+ if err := repo.Publish(context.Background(), pub); err != nil {
21+ t.Fatal(err)
22+ }
23+ parent := domain.CheckpointRef{Generation: 1, ManifestDigest: sha256.Sum256(pub.Manifest)}
24+ pub = incarnationPublication(t, id, "new", 2, &parent)
25+ if err := repo.Publish(context.Background(), pub); err != nil {
26+ t.Fatal(err)
27+ }
28+ if got := filepath.Base(repo.sessionPath(id)); got != id.String() {
29+ t.Fatalf("namespace = %q", got)
30+ }
31+
32+ wrong := pub
33+ wrong.IncarnationID = domain.IncarnationID{2}
34+ if err := repo.Publish(context.Background(), wrong); err == nil {
35+ t.Fatal("mismatched publication incarnation accepted")
36+ }
37+}
38+
39+func TestDirectCheckpointLoad(t *testing.T) {
40+ repo := NewRepository(privateDir(t))
41+ id := domain.IncarnationID{1}
42+ pub := incarnationPublication(t, id, "work", 1, nil)
43+ if err := repo.Publish(context.Background(), pub); err != nil {
44+ t.Fatal(err)
45+ }
46+ generations := filepath.Dir(repo.manifestPath(id, 1))
47+ for i := 2; i <= 5001; i++ {
48+ if err := os.WriteFile(filepath.Join(generations, generationFilename(uint64(i))), []byte("stale"), 0o600); err != nil {
49+ t.Fatal(err)
50+ }
51+ }
52+ var generationDirectoryReadCount atomic.Int64
53+ repo.hooks.beforeDirectoryRead = func(path string) {
54+ if path == generations {
55+ generationDirectoryReadCount.Add(1)
56+ }
57+ }
58+ ref := domain.CheckpointRef{Generation: 1, ManifestDigest: sha256.Sum256(pub.Manifest)}
59+ got, err := repo.LoadCheckpoint(context.Background(), id, "work", ref)
60+ if err != nil {
61+ t.Fatal(err)
62+ }
63+ if got.Generation != 1 || got.IncarnationID != id {
64+ t.Fatalf("generation = %#v", got)
65+ }
66+ if generationDirectoryReadCount.Load() != 0 {
67+ t.Fatal("direct load enumerated generation directory")
68+ }
69+}
70+
71+func TestRepairHEAD(t *testing.T) {
72+ repo := NewRepository(privateDir(t))
73+ id := domain.IncarnationID{1}
74+ pub := incarnationPublication(t, id, "work", 1, nil)
75+ if err := repo.Publish(context.Background(), pub); err != nil {
76+ t.Fatal(err)
77+ }
78+ valid := domain.CheckpointRef{Generation: 1, ManifestDigest: sha256.Sum256(pub.Manifest)}
79+ if err := repo.RepairHEAD(context.Background(), id, valid); err != nil {
80+ t.Fatal(err)
81+ }
82+ before, err := os.ReadFile(repo.headPath(id))
83+ if err != nil {
84+ t.Fatal(err)
85+ }
86+ invalid := valid
87+ invalid.ManifestDigest[0]++
88+ if err := repo.RepairHEAD(context.Background(), id, invalid); err == nil {
89+ t.Fatal("invalid checkpoint repaired HEAD")
90+ }
91+ after, err := os.ReadFile(repo.headPath(id))
92+ if err != nil {
93+ t.Fatal(err)
94+ }
95+ if string(after) != string(before) {
96+ t.Fatal("invalid repair changed HEAD")
97+ }
98+}
99+
100+func incarnationPublication(t *testing.T, id domain.IncarnationID, name string, generation uint64, parent *domain.CheckpointRef) ports.SnapshotPublication {
101+ t.Helper()
102+ tail, err := codec.MarshalObject(codec.HistoryTail, []byte("tail"))
103+ if err != nil {
104+ t.Fatal(err)
105+ }
106+ visible, err := codec.MarshalObject(codec.Visible, []byte("visible"))
107+ if err != nil {
108+ t.Fatal(err)
109+ }
110+ manifest, err := codec.MarshalManifest(codec.Manifest{
111+ Generation: generation, IncarnationID: id, ParentCheckpoint: parent, Name: name,
112+ Tabs: []codec.ManifestTab{{Cols: 1, Rows: 1, Panes: []codec.ManifestPane{{ID: "p", Tail: codec.ObjectRef{Kind: codec.HistoryTail, Digest: tail.Digest, Size: uint32(len(tail.Data))}, Visible: codec.ObjectRef{Kind: codec.Visible, Digest: visible.Digest, Size: uint32(len(visible.Data))}}}}},
113+ })
114+ if err != nil {
115+ t.Fatal(err)
116+ }
117+ return ports.SnapshotPublication{IncarnationID: id, Name: name, Generation: generation, ParentCheckpoint: parent, Manifest: manifest, Objects: []ports.SnapshotObject{tail, visible}}
118+}
M internal/adapters/snapshot/load.go +81 -1
@@ -11,6 +11,7 @@ import (
1111 "path/filepath"
1212 "sort"
1313
14+ "github.com/bnema/vev/internal/domain"
1415 "github.com/bnema/vev/internal/ports"
1516 codec "github.com/bnema/vev/internal/usecase/snapshot"
1617 )
@@ -101,7 +102,7 @@ func (r *Repository) load(ctx context.Context, name, key string) (ports.Snapshot
101102 skipped = true
102103 continue
103104 }
104- got.Fallback = skipped || preferred != 0
105+ _ = skipped
105106 return got, nil
106107 }
107108 return ports.SnapshotGeneration{}, fmt.Errorf("no complete snapshot generation for %q", name)
@@ -192,6 +193,9 @@ func (r *Repository) generationCandidates(ctx context.Context, key string, exclu
192193 // File.ReadDir maintains a cursor on one descriptor and returns at most one
193194 // small batch, so no full directory enumeration is retained in memory.
194195 func (r *Repository) walkDirectory(ctx context.Context, dir string, budget *int, visit func(os.DirEntry) error) (err error) {
196+ if hook := r.hooks.beforeDirectoryRead; hook != nil {
197+ hook(dir)
198+ }
195199 file, err := r.openDirectory(dir)
196200 if err != nil {
197201 return err
@@ -224,6 +228,82 @@ func (r *Repository) walkDirectory(ctx context.Context, dir string, budget *int,
224228 }
225229 }
226230
231+func (r *Repository) LoadCheckpoint(ctx context.Context, id domain.IncarnationID, name string, ref ports.CheckpointRef) (ports.SnapshotGeneration, error) {
232+ if err := ctx.Err(); err != nil {
233+ return ports.SnapshotGeneration{}, err
234+ }
235+ key, err := incarnationKey(id)
236+ if err != nil {
237+ return ports.SnapshotGeneration{}, err
238+ }
239+ lock := r.lockSession(key)
240+ defer r.unlockSession(lock)
241+ return r.loadCheckpointLocked(ctx, id, name, ref)
242+}
243+
244+func (r *Repository) loadCheckpointLocked(ctx context.Context, id domain.IncarnationID, name string, ref ports.CheckpointRef) (ports.SnapshotGeneration, error) {
245+ if ref.Generation == 0 || ref.ManifestDigest == ([32]byte{}) {
246+ return ports.SnapshotGeneration{}, fmt.Errorf("invalid checkpoint reference")
247+ }
248+ data, err := r.readBounded(r.manifestPath(id, ref.Generation))
249+ if err != nil {
250+ return ports.SnapshotGeneration{}, err
251+ }
252+ if sha256.Sum256(data) != ref.ManifestDigest {
253+ return ports.SnapshotGeneration{}, fmt.Errorf("manifest digest mismatch")
254+ }
255+ manifest, err := codec.UnmarshalManifest(data)
256+ if err != nil || manifest.IncarnationID != id || manifest.Generation != ref.Generation {
257+ return ports.SnapshotGeneration{}, fmt.Errorf("invalid manifest")
258+ }
259+ refs := manifestRefs(manifest)
260+ if refs == nil || !withinGenerationBudget(len(data), refs) {
261+ return ports.SnapshotGeneration{}, fmt.Errorf("snapshot generation too large")
262+ }
263+ objects := make(map[ports.SnapshotDigest][]byte, len(refs))
264+ for digest, objectRef := range refs {
265+ if err := ctx.Err(); err != nil {
266+ return ports.SnapshotGeneration{}, err
267+ }
268+ object, err := r.readBounded(r.objectPath(id, digest))
269+ if err != nil {
270+ return ports.SnapshotGeneration{}, err
271+ }
272+ if sha256.Sum256(object) != digest || !validObject(object, objectRef) {
273+ return ports.SnapshotGeneration{}, fmt.Errorf("invalid object")
274+ }
275+ objects[digest] = object
276+ }
277+ return ports.SnapshotGeneration{IncarnationID: id, Name: name, Generation: ref.Generation, ParentCheckpoint: manifest.ParentCheckpoint, Manifest: data, Objects: objects}, nil
278+}
279+
280+func (r *Repository) RepairHEAD(ctx context.Context, id domain.IncarnationID, ref ports.CheckpointRef) error {
281+ if err := ctx.Err(); err != nil {
282+ return err
283+ }
284+ key, err := incarnationKey(id)
285+ if err != nil {
286+ return err
287+ }
288+ lock := r.lockSession(key)
289+ defer r.unlockSession(lock)
290+ data, err := r.readBounded(r.manifestPath(id, ref.Generation))
291+ if err != nil {
292+ return fmt.Errorf("validate checkpoint before HEAD repair: %w", err)
293+ }
294+ if sha256.Sum256(data) != ref.ManifestDigest {
295+ return fmt.Errorf("validate checkpoint before HEAD repair: manifest digest mismatch")
296+ }
297+ manifest, err := codec.UnmarshalManifest(data)
298+ if err != nil || manifest.IncarnationID != id || manifest.Generation != ref.Generation {
299+ return fmt.Errorf("validate checkpoint before HEAD repair: invalid manifest")
300+ }
301+ if _, err := r.loadCheckpointLocked(ctx, id, manifest.Name, ref); err != nil {
302+ return err
303+ }
304+ return r.writeMutable(r.headPath(id), marshalHead(ref.Generation, ref.ManifestDigest))
305+}
306+
227307 func (r *Repository) loadGeneration(ctx context.Context, name, key string, generation uint64) (ports.SnapshotGeneration, error) {
228308 data, err := r.readBounded(r.manifestPath(key, generation))
229309 if err != nil {
@@ -11,6 +11,7 @@ import (
11 "path/filepath"11 "path/filepath"
12 "sort"12 "sort"
13 13
14+ "github.com/bnema/vev/internal/domain"
14 "github.com/bnema/vev/internal/ports"15 "github.com/bnema/vev/internal/ports"
15 codec "github.com/bnema/vev/internal/usecase/snapshot"16 codec "github.com/bnema/vev/internal/usecase/snapshot"
16 )17 )
@@ -101,7 +102,7 @@ func (r *Repository) load(ctx context.Context, name, key string) (ports.Snapshot
101 skipped = true102 skipped = true
102 continue103 continue
103 }104 }
104- got.Fallback = skipped || preferred != 0105+ _ = skipped
105 return got, nil106 return got, nil
106 }107 }
107 return ports.SnapshotGeneration{}, fmt.Errorf("no complete snapshot generation for %q", name)108 return ports.SnapshotGeneration{}, fmt.Errorf("no complete snapshot generation for %q", name)
@@ -192,6 +193,9 @@ func (r *Repository) generationCandidates(ctx context.Context, key string, exclu
192 // File.ReadDir maintains a cursor on one descriptor and returns at most one193 // File.ReadDir maintains a cursor on one descriptor and returns at most one
193 // small batch, so no full directory enumeration is retained in memory.194 // small batch, so no full directory enumeration is retained in memory.
194 func (r *Repository) walkDirectory(ctx context.Context, dir string, budget *int, visit func(os.DirEntry) error) (err error) {195 func (r *Repository) walkDirectory(ctx context.Context, dir string, budget *int, visit func(os.DirEntry) error) (err error) {
196+ if hook := r.hooks.beforeDirectoryRead; hook != nil {
197+ hook(dir)
198+ }
195 file, err := r.openDirectory(dir)199 file, err := r.openDirectory(dir)
196 if err != nil {200 if err != nil {
197 return err201 return err
@@ -224,6 +228,82 @@ func (r *Repository) walkDirectory(ctx context.Context, dir string, budget *int,
224 }228 }
225 }229 }
226 230
231+func (r *Repository) LoadCheckpoint(ctx context.Context, id domain.IncarnationID, name string, ref ports.CheckpointRef) (ports.SnapshotGeneration, error) {
232+ if err := ctx.Err(); err != nil {
233+ return ports.SnapshotGeneration{}, err
234+ }
235+ key, err := incarnationKey(id)
236+ if err != nil {
237+ return ports.SnapshotGeneration{}, err
238+ }
239+ lock := r.lockSession(key)
240+ defer r.unlockSession(lock)
241+ return r.loadCheckpointLocked(ctx, id, name, ref)
242+}
243+
244+func (r *Repository) loadCheckpointLocked(ctx context.Context, id domain.IncarnationID, name string, ref ports.CheckpointRef) (ports.SnapshotGeneration, error) {
245+ if ref.Generation == 0 || ref.ManifestDigest == ([32]byte{}) {
246+ return ports.SnapshotGeneration{}, fmt.Errorf("invalid checkpoint reference")
247+ }
248+ data, err := r.readBounded(r.manifestPath(id, ref.Generation))
249+ if err != nil {
250+ return ports.SnapshotGeneration{}, err
251+ }
252+ if sha256.Sum256(data) != ref.ManifestDigest {
253+ return ports.SnapshotGeneration{}, fmt.Errorf("manifest digest mismatch")
254+ }
255+ manifest, err := codec.UnmarshalManifest(data)
256+ if err != nil || manifest.IncarnationID != id || manifest.Generation != ref.Generation {
257+ return ports.SnapshotGeneration{}, fmt.Errorf("invalid manifest")
258+ }
259+ refs := manifestRefs(manifest)
260+ if refs == nil || !withinGenerationBudget(len(data), refs) {
261+ return ports.SnapshotGeneration{}, fmt.Errorf("snapshot generation too large")
262+ }
263+ objects := make(map[ports.SnapshotDigest][]byte, len(refs))
264+ for digest, objectRef := range refs {
265+ if err := ctx.Err(); err != nil {
266+ return ports.SnapshotGeneration{}, err
267+ }
268+ object, err := r.readBounded(r.objectPath(id, digest))
269+ if err != nil {
270+ return ports.SnapshotGeneration{}, err
271+ }
272+ if sha256.Sum256(object) != digest || !validObject(object, objectRef) {
273+ return ports.SnapshotGeneration{}, fmt.Errorf("invalid object")
274+ }
275+ objects[digest] = object
276+ }
277+ return ports.SnapshotGeneration{IncarnationID: id, Name: name, Generation: ref.Generation, ParentCheckpoint: manifest.ParentCheckpoint, Manifest: data, Objects: objects}, nil
278+}
279+
280+func (r *Repository) RepairHEAD(ctx context.Context, id domain.IncarnationID, ref ports.CheckpointRef) error {
281+ if err := ctx.Err(); err != nil {
282+ return err
283+ }
284+ key, err := incarnationKey(id)
285+ if err != nil {
286+ return err
287+ }
288+ lock := r.lockSession(key)
289+ defer r.unlockSession(lock)
290+ data, err := r.readBounded(r.manifestPath(id, ref.Generation))
291+ if err != nil {
292+ return fmt.Errorf("validate checkpoint before HEAD repair: %w", err)
293+ }
294+ if sha256.Sum256(data) != ref.ManifestDigest {
295+ return fmt.Errorf("validate checkpoint before HEAD repair: manifest digest mismatch")
296+ }
297+ manifest, err := codec.UnmarshalManifest(data)
298+ if err != nil || manifest.IncarnationID != id || manifest.Generation != ref.Generation {
299+ return fmt.Errorf("validate checkpoint before HEAD repair: invalid manifest")
300+ }
301+ if _, err := r.loadCheckpointLocked(ctx, id, manifest.Name, ref); err != nil {
302+ return err
303+ }
304+ return r.writeMutable(r.headPath(id), marshalHead(ref.Generation, ref.ManifestDigest))
305+}
306+
227 func (r *Repository) loadGeneration(ctx context.Context, name, key string, generation uint64) (ports.SnapshotGeneration, error) {307 func (r *Repository) loadGeneration(ctx context.Context, name, key string, generation uint64) (ports.SnapshotGeneration, error) {
228 data, err := r.readBounded(r.manifestPath(key, generation))308 data, err := r.readBounded(r.manifestPath(key, generation))
229 if err != nil {309 if err != nil {
M internal/adapters/snapshot/load_bounds_test.go +2 -2
@@ -63,8 +63,8 @@ func TestRepositoryLoadFallsBackNewestToOldestWithoutGenerationEnumeration(t *te
6363 if err != nil {
6464 t.Fatal(err)
6565 }
66- if got.Generation != 2 || !got.Fallback {
67- t.Fatalf("Load = generation %d fallback %v, want generation 2 fallback true", got.Generation, got.Fallback)
66+ if got.Generation != 2 {
67+ t.Fatalf("Load generation = %d, want 2", got.Generation)
6868 }
6969 }
7070
@@ -63,8 +63,8 @@ func TestRepositoryLoadFallsBackNewestToOldestWithoutGenerationEnumeration(t *te
63 if err != nil {63 if err != nil {
64 t.Fatal(err)64 t.Fatal(err)
65 }65 }
66- if got.Generation != 2 || !got.Fallback {66+ if got.Generation != 2 {
67- t.Fatalf("Load = generation %d fallback %v, want generation 2 fallback true", got.Generation, got.Fallback)67+ t.Fatalf("Load generation = %d, want 2", got.Generation)
68 }68 }
69 }69 }
70 70
M internal/adapters/snapshot/load_major_test.go +0 -2
@@ -37,7 +37,6 @@ func TestRepositoryLoadFallsBackFromInvalidHead(t *testing.T) {
3737 got, err := repo.Load(context.Background(), "named")
3838 require.NoError(t, err)
3939 require.Equal(t, uint64(1), got.Generation)
40- require.True(t, got.Fallback)
4140 }
4241
4342 func TestRepositoryLoadFallsBackAcrossMultipleCorruptCandidates(t *testing.T) {
@@ -54,7 +53,6 @@ func TestRepositoryLoadFallsBackAcrossMultipleCorruptCandidates(t *testing.T) {
5453 got, err := repo.Load(context.Background(), "named")
5554 require.NoError(t, err)
5655 require.Equal(t, uint64(2), got.Generation)
57- require.True(t, got.Fallback)
5856 }
5957
6058 func TestRepositoryLoadHoldsSessionLockAcrossTombstoneCheck(t *testing.T) {
@@ -37,7 +37,6 @@ func TestRepositoryLoadFallsBackFromInvalidHead(t *testing.T) {
37 got, err := repo.Load(context.Background(), "named")37 got, err := repo.Load(context.Background(), "named")
38 require.NoError(t, err)38 require.NoError(t, err)
39 require.Equal(t, uint64(1), got.Generation)39 require.Equal(t, uint64(1), got.Generation)
40- require.True(t, got.Fallback)
41 }40 }
42 41
43 func TestRepositoryLoadFallsBackAcrossMultipleCorruptCandidates(t *testing.T) {42 func TestRepositoryLoadFallsBackAcrossMultipleCorruptCandidates(t *testing.T) {
@@ -54,7 +53,6 @@ func TestRepositoryLoadFallsBackAcrossMultipleCorruptCandidates(t *testing.T) {
54 got, err := repo.Load(context.Background(), "named")53 got, err := repo.Load(context.Background(), "named")
55 require.NoError(t, err)54 require.NoError(t, err)
56 require.Equal(t, uint64(2), got.Generation)55 require.Equal(t, uint64(2), got.Generation)
57- require.True(t, got.Fallback)
58 }56 }
59 57
60 func TestRepositoryLoadHoldsSessionLockAcrossTombstoneCheck(t *testing.T) {58 func TestRepositoryLoadHoldsSessionLockAcrossTombstoneCheck(t *testing.T) {
M internal/adapters/snapshot/maintenance_delete.go +221 -0
@@ -2,14 +2,167 @@ package snapshot
22
33 import (
44 "context"
5+ "encoding/binary"
56 "errors"
67 "fmt"
78 "io"
89 "os"
910 "path/filepath"
11+ "sort"
1012 "strings"
13+
14+ "github.com/bnema/vev/internal/domain"
15+ "github.com/bnema/vev/internal/ports"
16+)
17+
18+const (
19+ deletionTombstoneVersion = uint16(1)
20+ deletionTombstoneMagic = "VEVD"
21+ deletionTombstonesDir = "deletions"
1122 )
1223
24+var ErrMaintenanceBudgetTooSmall = errors.New("snapshot: maintenance budget too small")
25+
26+func encodeDeletionTombstone(tombstone domain.DeletionTombstone) ([]byte, error) {
27+ if err := tombstone.Validate(); err != nil {
28+ return nil, err
29+ }
30+ if len(tombstone.Name) > 200 {
31+ return nil, fmt.Errorf("snapshot: deletion tombstone name too long")
32+ }
33+ encoded := make([]byte, 4+2+2+len(tombstone.Name)+len(tombstone.IncarnationID))
34+ copy(encoded, deletionTombstoneMagic)
35+ binary.BigEndian.PutUint16(encoded[4:6], deletionTombstoneVersion)
36+ binary.BigEndian.PutUint16(encoded[6:8], uint16(len(tombstone.Name)))
37+ copy(encoded[8:], tombstone.Name)
38+ copy(encoded[8+len(tombstone.Name):], tombstone.IncarnationID[:])
39+ return encoded, nil
40+}
41+
42+func decodeDeletionTombstone(encoded []byte) (domain.DeletionTombstone, error) {
43+ if len(encoded) < 8 {
44+ return domain.DeletionTombstone{}, fmt.Errorf("snapshot: truncated deletion tombstone")
45+ }
46+ if string(encoded[:4]) != deletionTombstoneMagic || binary.BigEndian.Uint16(encoded[4:6]) != deletionTombstoneVersion {
47+ return domain.DeletionTombstone{}, fmt.Errorf("snapshot: invalid deletion tombstone header")
48+ }
49+ nameLength := int(binary.BigEndian.Uint16(encoded[6:8]))
50+ want := 8 + nameLength + len(domain.IncarnationID{})
51+ if len(encoded) != want {
52+ return domain.DeletionTombstone{}, fmt.Errorf("snapshot: malformed deletion tombstone length")
53+ }
54+ tombstone := domain.DeletionTombstone{Name: string(encoded[8 : 8+nameLength])}
55+ copy(tombstone.IncarnationID[:], encoded[8+nameLength:])
56+ if err := tombstone.Validate(); err != nil {
57+ return domain.DeletionTombstone{}, err
58+ }
59+ return tombstone, nil
60+}
61+
62+func (r *Repository) deletionTombstonePath(id domain.IncarnationID) string {
63+ return filepath.Join(r.dir, deletionTombstonesDir, id.String()+".tombstone")
64+}
65+
66+func (r *Repository) WriteDeletionTombstone(ctx context.Context, tombstone domain.DeletionTombstone) error {
67+ if err := ctx.Err(); err != nil {
68+ return err
69+ }
70+ encoded, err := encodeDeletionTombstone(tombstone)
71+ if err != nil {
72+ return err
73+ }
74+ dir := filepath.Join(r.dir, deletionTombstonesDir)
75+ if err := r.ensurePrivateDirectory(dir); err != nil {
76+ return err
77+ }
78+ path := r.deletionTombstonePath(tombstone.IncarnationID)
79+ return r.writeImmutable(path, encoded, func(existing []byte) error {
80+ decoded, err := decodeDeletionTombstone(existing)
81+ if err != nil || decoded != tombstone {
82+ return fmt.Errorf("snapshot: deletion tombstone identity mismatch")
83+ }
84+ return nil
85+ })
86+}
87+
88+func (r *Repository) ListDeletionTombstones(ctx context.Context, cursor ports.DeletionTombstoneCursor, budget ports.MaintenanceBudget) (ports.DeletionTombstonePage, error) {
89+ if budget.Entries == 0 || budget.Bytes == 0 {
90+ return ports.DeletionTombstonePage{}, ErrMaintenanceBudgetTooSmall
91+ }
92+ dir := filepath.Join(r.dir, deletionTombstonesDir)
93+ entries, err := os.ReadDir(dir)
94+ if errors.Is(err, os.ErrNotExist) {
95+ return ports.DeletionTombstonePage{Done: true}, nil
96+ }
97+ if err != nil {
98+ return ports.DeletionTombstonePage{}, err
99+ }
100+ sort.Slice(entries, func(i, j int) bool { return entries[i].Name() < entries[j].Name() })
101+ page := ports.DeletionTombstonePage{}
102+ seen := make(map[domain.IncarnationID]struct{})
103+ for _, entry := range entries {
104+ if err := ctx.Err(); err != nil {
105+ return ports.DeletionTombstonePage{}, err
106+ }
107+ if entry.Name() <= cursor.After {
108+ continue
109+ }
110+ if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".tombstone") {
111+ return ports.DeletionTombstonePage{}, fmt.Errorf("snapshot: invalid deletion tombstone entry")
112+ }
113+ if uint64(len(page.Tombstones)) >= budget.Entries {
114+ return page, nil
115+ }
116+ data, err := r.readBounded(filepath.Join(dir, entry.Name()))
117+ if err != nil {
118+ return ports.DeletionTombstonePage{}, err
119+ }
120+ decoded, err := decodeDeletionTombstone(data)
121+ if err != nil {
122+ return ports.DeletionTombstonePage{}, err
123+ }
124+ charge := uint64(len(entry.Name()) + len(decoded.Name) + len(decoded.IncarnationID))
125+ if charge > budget.Bytes {
126+ if len(page.Tombstones) == 0 {
127+ return ports.DeletionTombstonePage{}, ErrMaintenanceBudgetTooSmall
128+ }
129+ return page, nil
130+ }
131+ if entry.Name() != decoded.IncarnationID.String()+".tombstone" {
132+ return ports.DeletionTombstonePage{}, fmt.Errorf("snapshot: non-canonical deletion tombstone filename")
133+ }
134+ if _, duplicate := seen[decoded.IncarnationID]; duplicate {
135+ return ports.DeletionTombstonePage{}, fmt.Errorf("snapshot: duplicate deletion tombstone identity")
136+ }
137+ seen[decoded.IncarnationID] = struct{}{}
138+ page.Tombstones = append(page.Tombstones, decoded)
139+ page.Next.After = entry.Name()
140+ budget.Bytes -= charge
141+ }
142+ page.Done = true
143+ return page, nil
144+}
145+
146+func (r *Repository) DeleteDeletionTombstone(ctx context.Context, id domain.IncarnationID) error {
147+ if err := ctx.Err(); err != nil {
148+ return err
149+ }
150+ if _, err := incarnationKey(id); err != nil {
151+ return err
152+ }
153+ path := r.deletionTombstonePath(id)
154+ dir := filepath.Dir(path)
155+ if _, err := os.Lstat(dir); errors.Is(err, os.ErrNotExist) {
156+ return nil
157+ } else if err != nil {
158+ return err
159+ }
160+ if err := r.remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
161+ return err
162+ }
163+ return r.syncDirectory(dir)
164+}
165+
13166 // Delete makes a session unavailable by durably moving it out of the canonical
14167 // namespace. Maintain reaps the private quarantine later; Delete never restores
15168 // a quarantined directory.
@@ -196,3 +349,71 @@ func (r *Repository) maintainQuarantine(ctx context.Context, budget *int) (chang
196349 }
197350 return changed, false, nil
198351 }
352+
353+func (r *Repository) QuarantineDeletionSources(ctx context.Context, tombstone domain.DeletionTombstone, includeLegacyName bool) error {
354+ if err := tombstone.Validate(); err != nil {
355+ return err
356+ }
357+ if err := ctx.Err(); err != nil {
358+ return err
359+ }
360+ quarantineDir := filepath.Join(r.dir, "quarantine", tombstone.IncarnationID.String())
361+ if err := r.ensurePrivateDirectory(quarantineDir); err != nil {
362+ return err
363+ }
364+ if err := r.quarantineSource(r.sessionPath(tombstone.IncarnationID), filepath.Join(quarantineDir, "snapshot")); err != nil {
365+ return err
366+ }
367+ if includeLegacyName {
368+ if err := r.quarantineSource(filepath.Join(r.dir, filenameForName(tombstone.Name)), filepath.Join(quarantineDir, "legacy.snap")); err != nil {
369+ return err
370+ }
371+ }
372+ return nil
373+}
374+
375+func (r *Repository) quarantineSource(source, target string) error {
376+ if _, err := os.Lstat(target); err == nil {
377+ if _, sourceErr := os.Lstat(source); errors.Is(sourceErr, os.ErrNotExist) {
378+ return nil
379+ }
380+ return fmt.Errorf("snapshot: quarantine target already exists")
381+ } else if !errors.Is(err, os.ErrNotExist) {
382+ return err
383+ }
384+ if _, err := os.Lstat(source); errors.Is(err, os.ErrNotExist) {
385+ return nil
386+ } else if err != nil {
387+ return err
388+ }
389+ if err := r.rename(source, target); err != nil {
390+ return err
391+ }
392+ if err := r.syncDirectory(filepath.Dir(source)); err != nil {
393+ return err
394+ }
395+ if filepath.Clean(filepath.Dir(source)) != filepath.Clean(filepath.Dir(target)) {
396+ return r.syncDirectory(filepath.Dir(target))
397+ }
398+ return nil
399+}
400+
401+func (r *Repository) SaveQuarantineDescriptor(context.Context, domain.QuarantineDescriptor) error {
402+ return errors.New("snapshot: quarantine descriptors not implemented")
403+}
404+
405+func (r *Repository) QuarantineIncarnation(ctx context.Context, id domain.IncarnationID) error {
406+ return r.QuarantineDeletionSources(ctx, domain.DeletionTombstone{Name: "quarantined", IncarnationID: id}, false)
407+}
408+
409+func (r *Repository) DeleteIncarnation(context.Context, domain.IncarnationID) error {
410+ return errors.New("snapshot: direct incarnation deletion not implemented")
411+}
412+
413+func (r *Repository) MaintainSession(context.Context, ports.RetentionPlan, ports.MaintenanceBudget) (bool, error) {
414+ return false, errors.New("snapshot: incarnation retention not implemented")
415+}
416+
417+func (r *Repository) Reconcile(_ context.Context, _ []domain.CatalogueRecord, cursor ports.ReconcileCursor, _ ports.MaintenanceBudget) (ports.ReconcileCursor, []ports.ReconcileFinding, error) {
418+ return cursor, nil, errors.New("snapshot: incarnation reconciliation not implemented")
419+}
@@ -2,14 +2,167 @@ package snapshot
2 2
3 import (3 import (
4 "context"4 "context"
5+ "encoding/binary"
5 "errors"6 "errors"
6 "fmt"7 "fmt"
7 "io"8 "io"
8 "os"9 "os"
9 "path/filepath"10 "path/filepath"
11+ "sort"
10 "strings"12 "strings"
13+
14+ "github.com/bnema/vev/internal/domain"
15+ "github.com/bnema/vev/internal/ports"
16+)
17+
18+const (
19+ deletionTombstoneVersion = uint16(1)
20+ deletionTombstoneMagic = "VEVD"
21+ deletionTombstonesDir = "deletions"
11 )22 )
12 23
24+var ErrMaintenanceBudgetTooSmall = errors.New("snapshot: maintenance budget too small")
25+
26+func encodeDeletionTombstone(tombstone domain.DeletionTombstone) ([]byte, error) {
27+ if err := tombstone.Validate(); err != nil {
28+ return nil, err
29+ }
30+ if len(tombstone.Name) > 200 {
31+ return nil, fmt.Errorf("snapshot: deletion tombstone name too long")
32+ }
33+ encoded := make([]byte, 4+2+2+len(tombstone.Name)+len(tombstone.IncarnationID))
34+ copy(encoded, deletionTombstoneMagic)
35+ binary.BigEndian.PutUint16(encoded[4:6], deletionTombstoneVersion)
36+ binary.BigEndian.PutUint16(encoded[6:8], uint16(len(tombstone.Name)))
37+ copy(encoded[8:], tombstone.Name)
38+ copy(encoded[8+len(tombstone.Name):], tombstone.IncarnationID[:])
39+ return encoded, nil
40+}
41+
42+func decodeDeletionTombstone(encoded []byte) (domain.DeletionTombstone, error) {
43+ if len(encoded) < 8 {
44+ return domain.DeletionTombstone{}, fmt.Errorf("snapshot: truncated deletion tombstone")
45+ }
46+ if string(encoded[:4]) != deletionTombstoneMagic || binary.BigEndian.Uint16(encoded[4:6]) != deletionTombstoneVersion {
47+ return domain.DeletionTombstone{}, fmt.Errorf("snapshot: invalid deletion tombstone header")
48+ }
49+ nameLength := int(binary.BigEndian.Uint16(encoded[6:8]))
50+ want := 8 + nameLength + len(domain.IncarnationID{})
51+ if len(encoded) != want {
52+ return domain.DeletionTombstone{}, fmt.Errorf("snapshot: malformed deletion tombstone length")
53+ }
54+ tombstone := domain.DeletionTombstone{Name: string(encoded[8 : 8+nameLength])}
55+ copy(tombstone.IncarnationID[:], encoded[8+nameLength:])
56+ if err := tombstone.Validate(); err != nil {
57+ return domain.DeletionTombstone{}, err
58+ }
59+ return tombstone, nil
60+}
61+
62+func (r *Repository) deletionTombstonePath(id domain.IncarnationID) string {
63+ return filepath.Join(r.dir, deletionTombstonesDir, id.String()+".tombstone")
64+}
65+
66+func (r *Repository) WriteDeletionTombstone(ctx context.Context, tombstone domain.DeletionTombstone) error {
67+ if err := ctx.Err(); err != nil {
68+ return err
69+ }
70+ encoded, err := encodeDeletionTombstone(tombstone)
71+ if err != nil {
72+ return err
73+ }
74+ dir := filepath.Join(r.dir, deletionTombstonesDir)
75+ if err := r.ensurePrivateDirectory(dir); err != nil {
76+ return err
77+ }
78+ path := r.deletionTombstonePath(tombstone.IncarnationID)
79+ return r.writeImmutable(path, encoded, func(existing []byte) error {
80+ decoded, err := decodeDeletionTombstone(existing)
81+ if err != nil || decoded != tombstone {
82+ return fmt.Errorf("snapshot: deletion tombstone identity mismatch")
83+ }
84+ return nil
85+ })
86+}
87+
88+func (r *Repository) ListDeletionTombstones(ctx context.Context, cursor ports.DeletionTombstoneCursor, budget ports.MaintenanceBudget) (ports.DeletionTombstonePage, error) {
89+ if budget.Entries == 0 || budget.Bytes == 0 {
90+ return ports.DeletionTombstonePage{}, ErrMaintenanceBudgetTooSmall
91+ }
92+ dir := filepath.Join(r.dir, deletionTombstonesDir)
93+ entries, err := os.ReadDir(dir)
94+ if errors.Is(err, os.ErrNotExist) {
95+ return ports.DeletionTombstonePage{Done: true}, nil
96+ }
97+ if err != nil {
98+ return ports.DeletionTombstonePage{}, err
99+ }
100+ sort.Slice(entries, func(i, j int) bool { return entries[i].Name() < entries[j].Name() })
101+ page := ports.DeletionTombstonePage{}
102+ seen := make(map[domain.IncarnationID]struct{})
103+ for _, entry := range entries {
104+ if err := ctx.Err(); err != nil {
105+ return ports.DeletionTombstonePage{}, err
106+ }
107+ if entry.Name() <= cursor.After {
108+ continue
109+ }
110+ if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".tombstone") {
111+ return ports.DeletionTombstonePage{}, fmt.Errorf("snapshot: invalid deletion tombstone entry")
112+ }
113+ if uint64(len(page.Tombstones)) >= budget.Entries {
114+ return page, nil
115+ }
116+ data, err := r.readBounded(filepath.Join(dir, entry.Name()))
117+ if err != nil {
118+ return ports.DeletionTombstonePage{}, err
119+ }
120+ decoded, err := decodeDeletionTombstone(data)
121+ if err != nil {
122+ return ports.DeletionTombstonePage{}, err
123+ }
124+ charge := uint64(len(entry.Name()) + len(decoded.Name) + len(decoded.IncarnationID))
125+ if charge > budget.Bytes {
126+ if len(page.Tombstones) == 0 {
127+ return ports.DeletionTombstonePage{}, ErrMaintenanceBudgetTooSmall
128+ }
129+ return page, nil
130+ }
131+ if entry.Name() != decoded.IncarnationID.String()+".tombstone" {
132+ return ports.DeletionTombstonePage{}, fmt.Errorf("snapshot: non-canonical deletion tombstone filename")
133+ }
134+ if _, duplicate := seen[decoded.IncarnationID]; duplicate {
135+ return ports.DeletionTombstonePage{}, fmt.Errorf("snapshot: duplicate deletion tombstone identity")
136+ }
137+ seen[decoded.IncarnationID] = struct{}{}
138+ page.Tombstones = append(page.Tombstones, decoded)
139+ page.Next.After = entry.Name()
140+ budget.Bytes -= charge
141+ }
142+ page.Done = true
143+ return page, nil
144+}
145+
146+func (r *Repository) DeleteDeletionTombstone(ctx context.Context, id domain.IncarnationID) error {
147+ if err := ctx.Err(); err != nil {
148+ return err
149+ }
150+ if _, err := incarnationKey(id); err != nil {
151+ return err
152+ }
153+ path := r.deletionTombstonePath(id)
154+ dir := filepath.Dir(path)
155+ if _, err := os.Lstat(dir); errors.Is(err, os.ErrNotExist) {
156+ return nil
157+ } else if err != nil {
158+ return err
159+ }
160+ if err := r.remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
161+ return err
162+ }
163+ return r.syncDirectory(dir)
164+}
165+
13 // Delete makes a session unavailable by durably moving it out of the canonical166 // Delete makes a session unavailable by durably moving it out of the canonical
14 // namespace. Maintain reaps the private quarantine later; Delete never restores167 // namespace. Maintain reaps the private quarantine later; Delete never restores
15 // a quarantined directory.168 // a quarantined directory.
@@ -196,3 +349,71 @@ func (r *Repository) maintainQuarantine(ctx context.Context, budget *int) (chang
196 }349 }
197 return changed, false, nil350 return changed, false, nil
198 }351 }
352+
353+func (r *Repository) QuarantineDeletionSources(ctx context.Context, tombstone domain.DeletionTombstone, includeLegacyName bool) error {
354+ if err := tombstone.Validate(); err != nil {
355+ return err
356+ }
357+ if err := ctx.Err(); err != nil {
358+ return err
359+ }
360+ quarantineDir := filepath.Join(r.dir, "quarantine", tombstone.IncarnationID.String())
361+ if err := r.ensurePrivateDirectory(quarantineDir); err != nil {
362+ return err
363+ }
364+ if err := r.quarantineSource(r.sessionPath(tombstone.IncarnationID), filepath.Join(quarantineDir, "snapshot")); err != nil {
365+ return err
366+ }
367+ if includeLegacyName {
368+ if err := r.quarantineSource(filepath.Join(r.dir, filenameForName(tombstone.Name)), filepath.Join(quarantineDir, "legacy.snap")); err != nil {
369+ return err
370+ }
371+ }
372+ return nil
373+}
374+
375+func (r *Repository) quarantineSource(source, target string) error {
376+ if _, err := os.Lstat(target); err == nil {
377+ if _, sourceErr := os.Lstat(source); errors.Is(sourceErr, os.ErrNotExist) {
378+ return nil
379+ }
380+ return fmt.Errorf("snapshot: quarantine target already exists")
381+ } else if !errors.Is(err, os.ErrNotExist) {
382+ return err
383+ }
384+ if _, err := os.Lstat(source); errors.Is(err, os.ErrNotExist) {
385+ return nil
386+ } else if err != nil {
387+ return err
388+ }
389+ if err := r.rename(source, target); err != nil {
390+ return err
391+ }
392+ if err := r.syncDirectory(filepath.Dir(source)); err != nil {
393+ return err
394+ }
395+ if filepath.Clean(filepath.Dir(source)) != filepath.Clean(filepath.Dir(target)) {
396+ return r.syncDirectory(filepath.Dir(target))
397+ }
398+ return nil
399+}
400+
401+func (r *Repository) SaveQuarantineDescriptor(context.Context, domain.QuarantineDescriptor) error {
402+ return errors.New("snapshot: quarantine descriptors not implemented")
403+}
404+
405+func (r *Repository) QuarantineIncarnation(ctx context.Context, id domain.IncarnationID) error {
406+ return r.QuarantineDeletionSources(ctx, domain.DeletionTombstone{Name: "quarantined", IncarnationID: id}, false)
407+}
408+
409+func (r *Repository) DeleteIncarnation(context.Context, domain.IncarnationID) error {
410+ return errors.New("snapshot: direct incarnation deletion not implemented")
411+}
412+
413+func (r *Repository) MaintainSession(context.Context, ports.RetentionPlan, ports.MaintenanceBudget) (bool, error) {
414+ return false, errors.New("snapshot: incarnation retention not implemented")
415+}
416+
417+func (r *Repository) Reconcile(_ context.Context, _ []domain.CatalogueRecord, cursor ports.ReconcileCursor, _ ports.MaintenanceBudget) (ports.ReconcileCursor, []ports.ReconcileFinding, error) {
418+ return cursor, nil, errors.New("snapshot: incarnation reconciliation not implemented")
419+}
A internal/adapters/snapshot/maintenance_delete_test.go +115 -0
@@ -0,0 +1,115 @@
1+package snapshot
2+
3+import (
4+ "context"
5+ "encoding/hex"
6+ "errors"
7+ "path/filepath"
8+ "testing"
9+
10+ "github.com/bnema/vev/internal/domain"
11+ "github.com/bnema/vev/internal/ports"
12+)
13+
14+func TestDeletionTombstoneCodec(t *testing.T) {
15+ want := domain.DeletionTombstone{Name: "work", IncarnationID: domain.IncarnationID{1}}
16+ encoded, err := encodeDeletionTombstone(want)
17+ if err != nil {
18+ t.Fatal(err)
19+ }
20+ if got := hex.EncodeToString(encoded); got != "5645564400010004776f726b01000000000000000000000000000000" {
21+ t.Fatalf("encoded tombstone = %s", got)
22+ }
23+ got, err := decodeDeletionTombstone(encoded)
24+ if err != nil {
25+ t.Fatal(err)
26+ }
27+ if got != want {
28+ t.Fatalf("round trip = %#v", got)
29+ }
30+ for n := range len(encoded) {
31+ if _, err := decodeDeletionTombstone(encoded[:n]); err == nil {
32+ t.Fatalf("prefix %d accepted", n)
33+ }
34+ }
35+ if _, err := decodeDeletionTombstone(append(encoded, 0)); err == nil {
36+ t.Fatal("trailing byte accepted")
37+ }
38+
39+ for _, boundary := range []string{"file-sync", "install", "directory-sync"} {
40+ t.Run(boundary, func(t *testing.T) {
41+ repo := NewRepository(privateDir(t))
42+ dir := filepath.Join(repo.dir, deletionTombstonesDir)
43+ if err := repo.ensurePrivateDirectory(dir); err != nil {
44+ t.Fatal(err)
45+ }
46+ injected := errors.New("injected tombstone persistence failure")
47+ switch boundary {
48+ case "file-sync":
49+ repo.hooks.syncFile = func(string) error { return injected }
50+ case "install":
51+ repo.hooks.installImmutable = func(string) error { return injected }
52+ case "directory-sync":
53+ repo.hooks.syncDirectory = func(path string) error {
54+ if path == dir {
55+ return injected
56+ }
57+ return nil
58+ }
59+ }
60+ if err := repo.WriteDeletionTombstone(context.Background(), want); !errors.Is(err, injected) {
61+ t.Fatalf("WriteDeletionTombstone() error = %v", err)
62+ }
63+ repo.hooks = repositoryHooks{}
64+ if err := repo.WriteDeletionTombstone(context.Background(), want); err != nil {
65+ t.Fatalf("retry: %v", err)
66+ }
67+ })
68+ }
69+}
70+
71+func TestDeletionTombstoneListing(t *testing.T) {
72+ repo := NewRepository(privateDir(t))
73+ ctx := context.Background()
74+ first := domain.DeletionTombstone{Name: "a", IncarnationID: domain.IncarnationID{1}}
75+ second := domain.DeletionTombstone{Name: "b", IncarnationID: domain.IncarnationID{2}}
76+ if err := repo.WriteDeletionTombstone(ctx, second); err != nil {
77+ t.Fatal(err)
78+ }
79+ if err := repo.WriteDeletionTombstone(ctx, first); err != nil {
80+ t.Fatal(err)
81+ }
82+ page, err := repo.ListDeletionTombstones(ctx, ports.DeletionTombstoneCursor{}, ports.MaintenanceBudget{Entries: 1, Bytes: 1024})
83+ if err != nil {
84+ t.Fatal(err)
85+ }
86+ if page.Done || len(page.Tombstones) != 1 || page.Tombstones[0] != first || page.Next.After == "" {
87+ t.Fatalf("first page = %#v", page)
88+ }
89+ page, err = repo.ListDeletionTombstones(ctx, page.Next, ports.MaintenanceBudget{Entries: 1, Bytes: 1024})
90+ if err != nil {
91+ t.Fatal(err)
92+ }
93+ if !page.Done || len(page.Tombstones) != 1 || page.Tombstones[0] != second {
94+ t.Fatalf("second page = %#v", page)
95+ }
96+ if _, err := repo.ListDeletionTombstones(ctx, ports.DeletionTombstoneCursor{}, ports.MaintenanceBudget{Entries: 1, Bytes: 1}); err == nil {
97+ t.Fatal("non-advancing budget accepted")
98+ }
99+
100+ injected := errors.New("injected directory sync failure")
101+ tombstoneDir := filepath.Join(repo.dir, deletionTombstonesDir)
102+ repo.hooks.syncDirectory = func(path string) error {
103+ if path == tombstoneDir {
104+ return injected
105+ }
106+ return nil
107+ }
108+ if err := repo.DeleteDeletionTombstone(ctx, first.IncarnationID); !errors.Is(err, injected) {
109+ t.Fatalf("DeleteDeletionTombstone() error = %v", err)
110+ }
111+ repo.hooks = repositoryHooks{}
112+ if err := repo.DeleteDeletionTombstone(ctx, first.IncarnationID); err != nil {
113+ t.Fatalf("DeleteDeletionTombstone() retry = %v", err)
114+ }
115+}
@@ -0,0 +1,115 @@
1+package snapshot
2+
3+import (
4+ "context"
5+ "encoding/hex"
6+ "errors"
7+ "path/filepath"
8+ "testing"
9+
10+ "github.com/bnema/vev/internal/domain"
11+ "github.com/bnema/vev/internal/ports"
12+)
13+
14+func TestDeletionTombstoneCodec(t *testing.T) {
15+ want := domain.DeletionTombstone{Name: "work", IncarnationID: domain.IncarnationID{1}}
16+ encoded, err := encodeDeletionTombstone(want)
17+ if err != nil {
18+ t.Fatal(err)
19+ }
20+ if got := hex.EncodeToString(encoded); got != "5645564400010004776f726b01000000000000000000000000000000" {
21+ t.Fatalf("encoded tombstone = %s", got)
22+ }
23+ got, err := decodeDeletionTombstone(encoded)
24+ if err != nil {
25+ t.Fatal(err)
26+ }
27+ if got != want {
28+ t.Fatalf("round trip = %#v", got)
29+ }
30+ for n := range len(encoded) {
31+ if _, err := decodeDeletionTombstone(encoded[:n]); err == nil {
32+ t.Fatalf("prefix %d accepted", n)
33+ }
34+ }
35+ if _, err := decodeDeletionTombstone(append(encoded, 0)); err == nil {
36+ t.Fatal("trailing byte accepted")
37+ }
38+
39+ for _, boundary := range []string{"file-sync", "install", "directory-sync"} {
40+ t.Run(boundary, func(t *testing.T) {
41+ repo := NewRepository(privateDir(t))
42+ dir := filepath.Join(repo.dir, deletionTombstonesDir)
43+ if err := repo.ensurePrivateDirectory(dir); err != nil {
44+ t.Fatal(err)
45+ }
46+ injected := errors.New("injected tombstone persistence failure")
47+ switch boundary {
48+ case "file-sync":
49+ repo.hooks.syncFile = func(string) error { return injected }
50+ case "install":
51+ repo.hooks.installImmutable = func(string) error { return injected }
52+ case "directory-sync":
53+ repo.hooks.syncDirectory = func(path string) error {
54+ if path == dir {
55+ return injected
56+ }
57+ return nil
58+ }
59+ }
60+ if err := repo.WriteDeletionTombstone(context.Background(), want); !errors.Is(err, injected) {
61+ t.Fatalf("WriteDeletionTombstone() error = %v", err)
62+ }
63+ repo.hooks = repositoryHooks{}
64+ if err := repo.WriteDeletionTombstone(context.Background(), want); err != nil {
65+ t.Fatalf("retry: %v", err)
66+ }
67+ })
68+ }
69+}
70+
71+func TestDeletionTombstoneListing(t *testing.T) {
72+ repo := NewRepository(privateDir(t))
73+ ctx := context.Background()
74+ first := domain.DeletionTombstone{Name: "a", IncarnationID: domain.IncarnationID{1}}
75+ second := domain.DeletionTombstone{Name: "b", IncarnationID: domain.IncarnationID{2}}
76+ if err := repo.WriteDeletionTombstone(ctx, second); err != nil {
77+ t.Fatal(err)
78+ }
79+ if err := repo.WriteDeletionTombstone(ctx, first); err != nil {
80+ t.Fatal(err)
81+ }
82+ page, err := repo.ListDeletionTombstones(ctx, ports.DeletionTombstoneCursor{}, ports.MaintenanceBudget{Entries: 1, Bytes: 1024})
83+ if err != nil {
84+ t.Fatal(err)
85+ }
86+ if page.Done || len(page.Tombstones) != 1 || page.Tombstones[0] != first || page.Next.After == "" {
87+ t.Fatalf("first page = %#v", page)
88+ }
89+ page, err = repo.ListDeletionTombstones(ctx, page.Next, ports.MaintenanceBudget{Entries: 1, Bytes: 1024})
90+ if err != nil {
91+ t.Fatal(err)
92+ }
93+ if !page.Done || len(page.Tombstones) != 1 || page.Tombstones[0] != second {
94+ t.Fatalf("second page = %#v", page)
95+ }
96+ if _, err := repo.ListDeletionTombstones(ctx, ports.DeletionTombstoneCursor{}, ports.MaintenanceBudget{Entries: 1, Bytes: 1}); err == nil {
97+ t.Fatal("non-advancing budget accepted")
98+ }
99+
100+ injected := errors.New("injected directory sync failure")
101+ tombstoneDir := filepath.Join(repo.dir, deletionTombstonesDir)
102+ repo.hooks.syncDirectory = func(path string) error {
103+ if path == tombstoneDir {
104+ return injected
105+ }
106+ return nil
107+ }
108+ if err := repo.DeleteDeletionTombstone(ctx, first.IncarnationID); !errors.Is(err, injected) {
109+ t.Fatalf("DeleteDeletionTombstone() error = %v", err)
110+ }
111+ repo.hooks = repositoryHooks{}
112+ if err := repo.DeleteDeletionTombstone(ctx, first.IncarnationID); err != nil {
113+ t.Fatalf("DeleteDeletionTombstone() retry = %v", err)
114+ }
115+}
M internal/adapters/snapshot/path.go +29 -8
@@ -9,6 +9,7 @@ import (
99 "strconv"
1010 "strings"
1111
12+ "github.com/bnema/vev/internal/domain"
1213 "github.com/bnema/vev/internal/ports"
1314 )
1415
@@ -26,18 +27,38 @@ func filenameForName(name string) string {
2627 return "@" + hex.EncodeToString(sum[:])[:40] + ".snap"
2728 }
2829
29-func (r *Repository) sessionPath(key string) string {
30- return filepath.Join(r.dir, repositorySessionsDir, key)
30+func incarnationKey(id domain.IncarnationID) (string, error) {
31+ if id == (domain.IncarnationID{}) {
32+ return "", fmt.Errorf("snapshot: zero incarnation ID")
33+ }
34+ return id.String(), nil
35+}
36+
37+// repositoryKey accepts string only for pre-migration maintenance code. Normal
38+// publication and loading always pass an IncarnationID.
39+func repositoryKey(identity any) string {
40+ switch value := identity.(type) {
41+ case domain.IncarnationID:
42+ return value.String()
43+ case string:
44+ return value
45+ default:
46+ panic("snapshot: invalid repository identity")
47+ }
48+}
49+
50+func (r *Repository) sessionPath(identity any) string {
51+ return filepath.Join(r.dir, repositorySessionsDir, repositoryKey(identity))
3152 }
32-func (r *Repository) objectPath(key string, digest ports.SnapshotDigest) string {
53+func (r *Repository) objectPath(identity any, digest ports.SnapshotDigest) string {
3354 hexDigest := hex.EncodeToString(digest[:])
34- return filepath.Join(r.sessionPath(key), repositoryObjectsDir, hexDigest[:2], hexDigest)
55+ return filepath.Join(r.sessionPath(identity), repositoryObjectsDir, hexDigest[:2], hexDigest)
3556 }
36-func (r *Repository) manifestPath(key string, generation uint64) string {
37- return filepath.Join(r.sessionPath(key), repositoryGenerations, generationFilename(generation))
57+func (r *Repository) manifestPath(identity any, generation uint64) string {
58+ return filepath.Join(r.sessionPath(identity), repositoryGenerations, generationFilename(generation))
3859 }
39-func (r *Repository) headPath(key string) string {
40- return filepath.Join(r.sessionPath(key), repositoryHead)
60+func (r *Repository) headPath(identity any) string {
61+ return filepath.Join(r.sessionPath(identity), repositoryHead)
4162 }
4263 func sessionKey(name string) string {
4364 if safeNameRE.MatchString(name) && name != "." && name != ".." {
@@ -9,6 +9,7 @@ import (
9 "strconv"9 "strconv"
10 "strings"10 "strings"
11 11
12+ "github.com/bnema/vev/internal/domain"
12 "github.com/bnema/vev/internal/ports"13 "github.com/bnema/vev/internal/ports"
13 )14 )
14 15
@@ -26,18 +27,38 @@ func filenameForName(name string) string {
26 return "@" + hex.EncodeToString(sum[:])[:40] + ".snap"27 return "@" + hex.EncodeToString(sum[:])[:40] + ".snap"
27 }28 }
28 29
29-func (r *Repository) sessionPath(key string) string {30+func incarnationKey(id domain.IncarnationID) (string, error) {
30- return filepath.Join(r.dir, repositorySessionsDir, key)31+ if id == (domain.IncarnationID{}) {
32+ return "", fmt.Errorf("snapshot: zero incarnation ID")
33+ }
34+ return id.String(), nil
35+}
36+
37+// repositoryKey accepts string only for pre-migration maintenance code. Normal
38+// publication and loading always pass an IncarnationID.
39+func repositoryKey(identity any) string {
40+ switch value := identity.(type) {
41+ case domain.IncarnationID:
42+ return value.String()
43+ case string:
44+ return value
45+ default:
46+ panic("snapshot: invalid repository identity")
47+ }
48+}
49+
50+func (r *Repository) sessionPath(identity any) string {
51+ return filepath.Join(r.dir, repositorySessionsDir, repositoryKey(identity))
31 }52 }
32-func (r *Repository) objectPath(key string, digest ports.SnapshotDigest) string {53+func (r *Repository) objectPath(identity any, digest ports.SnapshotDigest) string {
33 hexDigest := hex.EncodeToString(digest[:])54 hexDigest := hex.EncodeToString(digest[:])
34- return filepath.Join(r.sessionPath(key), repositoryObjectsDir, hexDigest[:2], hexDigest)55+ return filepath.Join(r.sessionPath(identity), repositoryObjectsDir, hexDigest[:2], hexDigest)
35 }56 }
36-func (r *Repository) manifestPath(key string, generation uint64) string {57+func (r *Repository) manifestPath(identity any, generation uint64) string {
37- return filepath.Join(r.sessionPath(key), repositoryGenerations, generationFilename(generation))58+ return filepath.Join(r.sessionPath(identity), repositoryGenerations, generationFilename(generation))
38 }59 }
39-func (r *Repository) headPath(key string) string {60+func (r *Repository) headPath(identity any) string {
40- return filepath.Join(r.sessionPath(key), repositoryHead)61+ return filepath.Join(r.sessionPath(identity), repositoryHead)
41 }62 }
42 func sessionKey(name string) string {63 func sessionKey(name string) string {
43 if safeNameRE.MatchString(name) && name != "." && name != ".." {64 if safeNameRE.MatchString(name) && name != "." && name != ".." {
M internal/adapters/snapshot/publish.go +47 -25
@@ -4,8 +4,11 @@ import (
44 "bytes"
55 "context"
66 "crypto/sha256"
7+ "errors"
78 "fmt"
9+ "os"
810
11+ "github.com/bnema/vev/internal/domain"
912 "github.com/bnema/vev/internal/ports"
1013 codec "github.com/bnema/vev/internal/usecase/snapshot"
1114 )
@@ -14,7 +17,10 @@ func (r *Repository) Publish(ctx context.Context, publication ports.SnapshotPubl
1417 if err := ctx.Err(); err != nil {
1518 return err
1619 }
17- key := sessionKey(publication.Name)
20+ key, err := incarnationKey(publication.IncarnationID)
21+ if err != nil {
22+ return err
23+ }
1824 lock := r.lockSession(key)
1925 defer r.unlockSession(lock)
2026 if err := ctx.Err(); err != nil {
@@ -41,7 +47,7 @@ func (r *Repository) Publish(ctx context.Context, publication ports.SnapshotPubl
4147 return nil
4248 }
4349
44- current, currentManifest, currentRefs, err := r.currentPublication(ctx, publication.Name, key)
50+ current, currentManifest, currentRefs, err := r.currentIncarnationPublication(ctx, publication, key)
4551 if err != nil {
4652 return err
4753 }
@@ -163,31 +169,47 @@ func (r *Repository) unchangedPublication(key string, publication ports.Snapshot
163169 return err == nil && bytes.Equal(current, publication.Manifest), nil
164170 }
165171
166-// currentPublication takes the normal HEAD path without loading every object
167-// in the current generation. A damaged HEAD or manifest still uses the full
168-// fallback scan, because only Load's object validation can establish a safe
169-// replacement authoritative generation in that recovery case.
170-func (r *Repository) currentPublication(ctx context.Context, name, key string) (uint64, []byte, map[ports.SnapshotDigest]codec.ObjectRef, error) {
172+func (r *Repository) currentIncarnationPublication(ctx context.Context, publication ports.SnapshotPublication, key string) (uint64, []byte, map[ports.SnapshotDigest]codec.ObjectRef, error) {
171173 generation, digest, err := r.readHead(key)
172- if err == nil {
173- data, readErr := r.readBounded(r.manifestPath(key, generation))
174- if readErr == nil && sha256.Sum256(data) == digest {
175- refs, validateErr := validateManifest(data, name, generation)
176- if validateErr == nil {
177- return generation, data, refs, nil
178- }
174+ if errors.Is(err, os.ErrNotExist) {
175+ if publication.Generation != 1 || publication.ParentCheckpoint != nil {
176+ return 0, nil, nil, fmt.Errorf("first snapshot generation must be 1 with no parent")
179177 }
178+ return 0, nil, nil, nil
180179 }
181-
182- generation, data, err := r.currentGeneration(ctx, name, key)
183- if err != nil || generation == 0 {
184- return generation, data, nil, err
180+ if err != nil {
181+ return 0, nil, nil, fmt.Errorf("read snapshot HEAD: %w", err)
185182 }
186- refs, err := validateManifest(data, name, generation)
183+ data, err := r.readBounded(r.manifestPath(publication.IncarnationID, generation))
187184 if err != nil {
188- return 0, nil, nil, err
185+ return 0, nil, nil, fmt.Errorf("validate current checkpoint: %w", err)
186+ }
187+ if sha256.Sum256(data) != digest {
188+ return 0, nil, nil, fmt.Errorf("validate current checkpoint: manifest digest mismatch")
189+ }
190+ manifest, err := codec.UnmarshalManifest(data)
191+ if err != nil {
192+ return 0, nil, nil, fmt.Errorf("validate current manifest: %w", err)
193+ }
194+ if manifest.IncarnationID != publication.IncarnationID || manifest.Generation != generation {
195+ return 0, nil, nil, fmt.Errorf("current manifest identity mismatch")
196+ }
197+ wantParent := &domain.CheckpointRef{Generation: generation, ManifestDigest: digest}
198+ if publication.Generation == generation+1 && !checkpointRefEqual(publication.ParentCheckpoint, wantParent) {
199+ return 0, nil, nil, fmt.Errorf("publication parent does not match current checkpoint")
200+ }
201+ refs := manifestRefs(manifest)
202+ if refs == nil || !withinGenerationBudget(len(data), refs) {
203+ return 0, nil, nil, fmt.Errorf("invalid current generation")
204+ }
205+ return generation, data, refs, ctx.Err()
206+}
207+
208+func checkpointRefEqual(left, right *domain.CheckpointRef) bool {
209+ if left == nil || right == nil {
210+ return left == nil && right == nil
189211 }
190- return generation, data, refs, nil
212+ return *left == *right
191213 }
192214
193215 func validatePublication(p ports.SnapshotPublication) (map[ports.SnapshotDigest]codec.ObjectRef, map[ports.SnapshotDigest]ports.SnapshotObject, error) {
@@ -211,16 +233,16 @@ func validatePublication(p ports.SnapshotPublication) (map[ports.SnapshotDigest]
211233 }
212234
213235 func validatePublicationManifest(p ports.SnapshotPublication) (map[ports.SnapshotDigest]codec.ObjectRef, error) {
214- return validateManifest(p.Manifest, p.Name, p.Generation)
236+ return validateManifest(p.Manifest, p.IncarnationID, p.Name, p.Generation, p.ParentCheckpoint)
215237 }
216238
217-func validateManifest(data []byte, name string, generation uint64) (map[ports.SnapshotDigest]codec.ObjectRef, error) {
239+func validateManifest(data []byte, incarnationID domain.IncarnationID, name string, generation uint64, parent *domain.CheckpointRef) (map[ports.SnapshotDigest]codec.ObjectRef, error) {
218240 manifest, err := codec.UnmarshalManifest(data)
219241 if err != nil {
220242 return nil, fmt.Errorf("invalid manifest: %w", err)
221243 }
222- if manifest.Name != name || manifest.Generation != generation || generation == 0 {
223- return nil, fmt.Errorf("manifest name or generation does not match publication")
244+ if manifest.IncarnationID != incarnationID || manifest.Name != name || manifest.Generation != generation || generation == 0 || !checkpointRefEqual(manifest.ParentCheckpoint, parent) {
245+ return nil, fmt.Errorf("manifest identity, name, generation, or parent does not match publication")
224246 }
225247 refs := manifestRefs(manifest)
226248 if refs == nil {
@@ -4,8 +4,11 @@ import (
4 "bytes"4 "bytes"
5 "context"5 "context"
6 "crypto/sha256"6 "crypto/sha256"
7+ "errors"
7 "fmt"8 "fmt"
9+ "os"
8 10
11+ "github.com/bnema/vev/internal/domain"
9 "github.com/bnema/vev/internal/ports"12 "github.com/bnema/vev/internal/ports"
10 codec "github.com/bnema/vev/internal/usecase/snapshot"13 codec "github.com/bnema/vev/internal/usecase/snapshot"
11 )14 )
@@ -14,7 +17,10 @@ func (r *Repository) Publish(ctx context.Context, publication ports.SnapshotPubl
14 if err := ctx.Err(); err != nil {17 if err := ctx.Err(); err != nil {
15 return err18 return err
16 }19 }
17- key := sessionKey(publication.Name)20+ key, err := incarnationKey(publication.IncarnationID)
21+ if err != nil {
22+ return err
23+ }
18 lock := r.lockSession(key)24 lock := r.lockSession(key)
19 defer r.unlockSession(lock)25 defer r.unlockSession(lock)
20 if err := ctx.Err(); err != nil {26 if err := ctx.Err(); err != nil {
@@ -41,7 +47,7 @@ func (r *Repository) Publish(ctx context.Context, publication ports.SnapshotPubl
41 return nil47 return nil
42 }48 }
43 49
44- current, currentManifest, currentRefs, err := r.currentPublication(ctx, publication.Name, key)50+ current, currentManifest, currentRefs, err := r.currentIncarnationPublication(ctx, publication, key)
45 if err != nil {51 if err != nil {
46 return err52 return err
47 }53 }
@@ -163,31 +169,47 @@ func (r *Repository) unchangedPublication(key string, publication ports.Snapshot
163 return err == nil && bytes.Equal(current, publication.Manifest), nil169 return err == nil && bytes.Equal(current, publication.Manifest), nil
164 }170 }
165 171
166-// currentPublication takes the normal HEAD path without loading every object172+func (r *Repository) currentIncarnationPublication(ctx context.Context, publication ports.SnapshotPublication, key string) (uint64, []byte, map[ports.SnapshotDigest]codec.ObjectRef, error) {
167-// in the current generation. A damaged HEAD or manifest still uses the full
168-// fallback scan, because only Load's object validation can establish a safe
169-// replacement authoritative generation in that recovery case.
170-func (r *Repository) currentPublication(ctx context.Context, name, key string) (uint64, []byte, map[ports.SnapshotDigest]codec.ObjectRef, error) {
171 generation, digest, err := r.readHead(key)173 generation, digest, err := r.readHead(key)
172- if err == nil {174+ if errors.Is(err, os.ErrNotExist) {
173- data, readErr := r.readBounded(r.manifestPath(key, generation))175+ if publication.Generation != 1 || publication.ParentCheckpoint != nil {
174- if readErr == nil && sha256.Sum256(data) == digest {176+ return 0, nil, nil, fmt.Errorf("first snapshot generation must be 1 with no parent")
175- refs, validateErr := validateManifest(data, name, generation)
176- if validateErr == nil {
177- return generation, data, refs, nil
178- }
179 }177 }
178+ return 0, nil, nil, nil
180 }179 }
181-180+ if err != nil {
182- generation, data, err := r.currentGeneration(ctx, name, key)181+ return 0, nil, nil, fmt.Errorf("read snapshot HEAD: %w", err)
183- if err != nil || generation == 0 {
184- return generation, data, nil, err
185 }182 }
186- refs, err := validateManifest(data, name, generation)183+ data, err := r.readBounded(r.manifestPath(publication.IncarnationID, generation))
187 if err != nil {184 if err != nil {
188- return 0, nil, nil, err185+ return 0, nil, nil, fmt.Errorf("validate current checkpoint: %w", err)
186+ }
187+ if sha256.Sum256(data) != digest {
188+ return 0, nil, nil, fmt.Errorf("validate current checkpoint: manifest digest mismatch")
189+ }
190+ manifest, err := codec.UnmarshalManifest(data)
191+ if err != nil {
192+ return 0, nil, nil, fmt.Errorf("validate current manifest: %w", err)
193+ }
194+ if manifest.IncarnationID != publication.IncarnationID || manifest.Generation != generation {
195+ return 0, nil, nil, fmt.Errorf("current manifest identity mismatch")
196+ }
197+ wantParent := &domain.CheckpointRef{Generation: generation, ManifestDigest: digest}
198+ if publication.Generation == generation+1 && !checkpointRefEqual(publication.ParentCheckpoint, wantParent) {
199+ return 0, nil, nil, fmt.Errorf("publication parent does not match current checkpoint")
200+ }
201+ refs := manifestRefs(manifest)
202+ if refs == nil || !withinGenerationBudget(len(data), refs) {
203+ return 0, nil, nil, fmt.Errorf("invalid current generation")
204+ }
205+ return generation, data, refs, ctx.Err()
206+}
207+
208+func checkpointRefEqual(left, right *domain.CheckpointRef) bool {
209+ if left == nil || right == nil {
210+ return left == nil && right == nil
189 }211 }
190- return generation, data, refs, nil212+ return *left == *right
191 }213 }
192 214
193 func validatePublication(p ports.SnapshotPublication) (map[ports.SnapshotDigest]codec.ObjectRef, map[ports.SnapshotDigest]ports.SnapshotObject, error) {215 func validatePublication(p ports.SnapshotPublication) (map[ports.SnapshotDigest]codec.ObjectRef, map[ports.SnapshotDigest]ports.SnapshotObject, error) {
@@ -211,16 +233,16 @@ func validatePublication(p ports.SnapshotPublication) (map[ports.SnapshotDigest]
211 }233 }
212 234
213 func validatePublicationManifest(p ports.SnapshotPublication) (map[ports.SnapshotDigest]codec.ObjectRef, error) {235 func validatePublicationManifest(p ports.SnapshotPublication) (map[ports.SnapshotDigest]codec.ObjectRef, error) {
214- return validateManifest(p.Manifest, p.Name, p.Generation)236+ return validateManifest(p.Manifest, p.IncarnationID, p.Name, p.Generation, p.ParentCheckpoint)
215 }237 }
216 238
217-func validateManifest(data []byte, name string, generation uint64) (map[ports.SnapshotDigest]codec.ObjectRef, error) {239+func validateManifest(data []byte, incarnationID domain.IncarnationID, name string, generation uint64, parent *domain.CheckpointRef) (map[ports.SnapshotDigest]codec.ObjectRef, error) {
218 manifest, err := codec.UnmarshalManifest(data)240 manifest, err := codec.UnmarshalManifest(data)
219 if err != nil {241 if err != nil {
220 return nil, fmt.Errorf("invalid manifest: %w", err)242 return nil, fmt.Errorf("invalid manifest: %w", err)
221 }243 }
222- if manifest.Name != name || manifest.Generation != generation || generation == 0 {244+ if manifest.IncarnationID != incarnationID || manifest.Name != name || manifest.Generation != generation || generation == 0 || !checkpointRefEqual(manifest.ParentCheckpoint, parent) {
223- return nil, fmt.Errorf("manifest name or generation does not match publication")245+ return nil, fmt.Errorf("manifest identity, name, generation, or parent does not match publication")
224 }246 }
225 refs := manifestRefs(manifest)247 refs := manifestRefs(manifest)
226 if refs == nil {248 if refs == nil {
M internal/adapters/snapshot/repository.go +1 -0
@@ -76,6 +76,7 @@ type repositoryHooks struct {
7676 afterOpenRoot func()
7777 closeRoot func() error
7878 openMaintenanceDirectory func(string) (maintenanceDirectory, error)
79+ beforeDirectoryRead func(string)
7980 // beforeMaintenanceWork observes each budgeted quarantine filesystem step.
8081 // It is test-only instrumentation for hostile traversal bounds.
8182 beforeMaintenanceWork func(string)
@@ -76,6 +76,7 @@ type repositoryHooks struct {
76 afterOpenRoot func()76 afterOpenRoot func()
77 closeRoot func() error77 closeRoot func() error
78 openMaintenanceDirectory func(string) (maintenanceDirectory, error)78 openMaintenanceDirectory func(string) (maintenanceDirectory, error)
79+ beforeDirectoryRead func(string)
79 // beforeMaintenanceWork observes each budgeted quarantine filesystem step.80 // beforeMaintenanceWork observes each budgeted quarantine filesystem step.
80 // It is test-only instrumentation for hostile traversal bounds.81 // It is test-only instrumentation for hostile traversal bounds.
81 beforeMaintenanceWork func(string)82 beforeMaintenanceWork func(string)
M internal/adapters/snapshot/repository_fault_test.go +2 -2
@@ -198,7 +198,7 @@ func TestRepositoryLoadFallsBackFromIncompleteNewestGeneration(t *testing.T) {
198198 if err != nil {
199199 t.Fatal(err)
200200 }
201- if got.Generation != 1 || !got.Fallback {
202- t.Fatalf("Load = generation %d fallback %v, want generation 1 fallback true", got.Generation, got.Fallback)
201+ if got.Generation != 1 {
202+ t.Fatalf("Load generation = %d, want 1", got.Generation)
203203 }
204204 }
@@ -198,7 +198,7 @@ func TestRepositoryLoadFallsBackFromIncompleteNewestGeneration(t *testing.T) {
198 if err != nil {198 if err != nil {
199 t.Fatal(err)199 t.Fatal(err)
200 }200 }
201- if got.Generation != 1 || !got.Fallback {201+ if got.Generation != 1 {
202- t.Fatalf("Load = generation %d fallback %v, want generation 1 fallback true", got.Generation, got.Fallback)202+ t.Fatalf("Load generation = %d, want 1", got.Generation)
203 }203 }
204 }204 }
A internal/ports/mocks/recovery_mock.go +1631 -0
@@ -0,0 +1,1631 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package portsmocks
6+
7+import (
8+ "context"
9+
10+ "github.com/bnema/vev/internal/domain"
11+ "github.com/bnema/vev/internal/ports"
12+ mock "github.com/stretchr/testify/mock"
13+)
14+
15+// NewMockSnapshotMigration creates a new instance of MockSnapshotMigration. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
16+// The first argument is typically a *testing.T value.
17+func NewMockSnapshotMigration(t interface {
18+ mock.TestingT
19+ Cleanup(func())
20+}) *MockSnapshotMigration {
21+ mock := &MockSnapshotMigration{}
22+ mock.Mock.Test(t)
23+
24+ t.Cleanup(func() { mock.AssertExpectations(t) })
25+
26+ return mock
27+}
28+
29+// MockSnapshotMigration is an autogenerated mock type for the SnapshotMigration type
30+type MockSnapshotMigration struct {
31+ mock.Mock
32+}
33+
34+type MockSnapshotMigration_Expecter struct {
35+ mock *mock.Mock
36+}
37+
38+func (_m *MockSnapshotMigration) EXPECT() *MockSnapshotMigration_Expecter {
39+ return &MockSnapshotMigration_Expecter{mock: &_m.Mock}
40+}
41+
42+// HasLegacyState provides a mock function for the type MockSnapshotMigration
43+func (_mock *MockSnapshotMigration) HasLegacyState(context1 context.Context) (bool, error) {
44+ ret := _mock.Called(context1)
45+
46+ if len(ret) == 0 {
47+ panic("no return value specified for HasLegacyState")
48+ }
49+
50+ var r0 bool
51+ var r1 error
52+ if returnFunc, ok := ret.Get(0).(func(context.Context) (bool, error)); ok {
53+ return returnFunc(context1)
54+ }
55+ if returnFunc, ok := ret.Get(0).(func(context.Context) bool); ok {
56+ r0 = returnFunc(context1)
57+ } else {
58+ r0 = ret.Get(0).(bool)
59+ }
60+ if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
61+ r1 = returnFunc(context1)
62+ } else {
63+ r1 = ret.Error(1)
64+ }
65+ return r0, r1
66+}
67+
68+// MockSnapshotMigration_HasLegacyState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HasLegacyState'
69+type MockSnapshotMigration_HasLegacyState_Call struct {
70+ *mock.Call
71+}
72+
73+// HasLegacyState is a helper method to define mock.On call
74+// - context1 context.Context
75+func (_e *MockSnapshotMigration_Expecter) HasLegacyState(context1 any) *MockSnapshotMigration_HasLegacyState_Call {
76+ return &MockSnapshotMigration_HasLegacyState_Call{Call: _e.mock.On("HasLegacyState", context1)}
77+}
78+
79+func (_c *MockSnapshotMigration_HasLegacyState_Call) Run(run func(context1 context.Context)) *MockSnapshotMigration_HasLegacyState_Call {
80+ _c.Call.Run(func(args mock.Arguments) {
81+ var arg0 context.Context
82+ if args[0] != nil {
83+ arg0 = args[0].(context.Context)
84+ }
85+ run(
86+ arg0,
87+ )
88+ })
89+ return _c
90+}
91+
92+func (_c *MockSnapshotMigration_HasLegacyState_Call) Return(b bool, err error) *MockSnapshotMigration_HasLegacyState_Call {
93+ _c.Call.Return(b, err)
94+ return _c
95+}
96+
97+func (_c *MockSnapshotMigration_HasLegacyState_Call) RunAndReturn(run func(context1 context.Context) (bool, error)) *MockSnapshotMigration_HasLegacyState_Call {
98+ _c.Call.Return(run)
99+ return _c
100+}
101+
102+// MigrateV1Checkpoint provides a mock function for the type MockSnapshotMigration
103+func (_mock *MockSnapshotMigration) MigrateV1Checkpoint(context1 context.Context, snapshotMigrationRequest ports.SnapshotMigrationRequest) (domain.CheckpointRef, error) {
104+ ret := _mock.Called(context1, snapshotMigrationRequest)
105+
106+ if len(ret) == 0 {
107+ panic("no return value specified for MigrateV1Checkpoint")
108+ }
109+
110+ var r0 domain.CheckpointRef
111+ var r1 error
112+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.SnapshotMigrationRequest) (domain.CheckpointRef, error)); ok {
113+ return returnFunc(context1, snapshotMigrationRequest)
114+ }
115+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.SnapshotMigrationRequest) domain.CheckpointRef); ok {
116+ r0 = returnFunc(context1, snapshotMigrationRequest)
117+ } else {
118+ r0 = ret.Get(0).(domain.CheckpointRef)
119+ }
120+ if returnFunc, ok := ret.Get(1).(func(context.Context, ports.SnapshotMigrationRequest) error); ok {
121+ r1 = returnFunc(context1, snapshotMigrationRequest)
122+ } else {
123+ r1 = ret.Error(1)
124+ }
125+ return r0, r1
126+}
127+
128+// MockSnapshotMigration_MigrateV1Checkpoint_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MigrateV1Checkpoint'
129+type MockSnapshotMigration_MigrateV1Checkpoint_Call struct {
130+ *mock.Call
131+}
132+
133+// MigrateV1Checkpoint is a helper method to define mock.On call
134+// - context1 context.Context
135+// - snapshotMigrationRequest ports.SnapshotMigrationRequest
136+func (_e *MockSnapshotMigration_Expecter) MigrateV1Checkpoint(context1 any, snapshotMigrationRequest any) *MockSnapshotMigration_MigrateV1Checkpoint_Call {
137+ return &MockSnapshotMigration_MigrateV1Checkpoint_Call{Call: _e.mock.On("MigrateV1Checkpoint", context1, snapshotMigrationRequest)}
138+}
139+
140+func (_c *MockSnapshotMigration_MigrateV1Checkpoint_Call) Run(run func(context1 context.Context, snapshotMigrationRequest ports.SnapshotMigrationRequest)) *MockSnapshotMigration_MigrateV1Checkpoint_Call {
141+ _c.Call.Run(func(args mock.Arguments) {
142+ var arg0 context.Context
143+ if args[0] != nil {
144+ arg0 = args[0].(context.Context)
145+ }
146+ var arg1 ports.SnapshotMigrationRequest
147+ if args[1] != nil {
148+ arg1 = args[1].(ports.SnapshotMigrationRequest)
149+ }
150+ run(
151+ arg0,
152+ arg1,
153+ )
154+ })
155+ return _c
156+}
157+
158+func (_c *MockSnapshotMigration_MigrateV1Checkpoint_Call) Return(checkpointRef domain.CheckpointRef, err error) *MockSnapshotMigration_MigrateV1Checkpoint_Call {
159+ _c.Call.Return(checkpointRef, err)
160+ return _c
161+}
162+
163+func (_c *MockSnapshotMigration_MigrateV1Checkpoint_Call) RunAndReturn(run func(context1 context.Context, snapshotMigrationRequest ports.SnapshotMigrationRequest) (domain.CheckpointRef, error)) *MockSnapshotMigration_MigrateV1Checkpoint_Call {
164+ _c.Call.Return(run)
165+ return _c
166+}
167+
168+// ReadLegacyHEAD provides a mock function for the type MockSnapshotMigration
169+func (_mock *MockSnapshotMigration) ReadLegacyHEAD(context1 context.Context, s string) (domain.CheckpointRef, error) {
170+ ret := _mock.Called(context1, s)
171+
172+ if len(ret) == 0 {
173+ panic("no return value specified for ReadLegacyHEAD")
174+ }
175+
176+ var r0 domain.CheckpointRef
177+ var r1 error
178+ if returnFunc, ok := ret.Get(0).(func(context.Context, string) (domain.CheckpointRef, error)); ok {
179+ return returnFunc(context1, s)
180+ }
181+ if returnFunc, ok := ret.Get(0).(func(context.Context, string) domain.CheckpointRef); ok {
182+ r0 = returnFunc(context1, s)
183+ } else {
184+ r0 = ret.Get(0).(domain.CheckpointRef)
185+ }
186+ if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok {
187+ r1 = returnFunc(context1, s)
188+ } else {
189+ r1 = ret.Error(1)
190+ }
191+ return r0, r1
192+}
193+
194+// MockSnapshotMigration_ReadLegacyHEAD_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadLegacyHEAD'
195+type MockSnapshotMigration_ReadLegacyHEAD_Call struct {
196+ *mock.Call
197+}
198+
199+// ReadLegacyHEAD is a helper method to define mock.On call
200+// - context1 context.Context
201+// - s string
202+func (_e *MockSnapshotMigration_Expecter) ReadLegacyHEAD(context1 any, s any) *MockSnapshotMigration_ReadLegacyHEAD_Call {
203+ return &MockSnapshotMigration_ReadLegacyHEAD_Call{Call: _e.mock.On("ReadLegacyHEAD", context1, s)}
204+}
205+
206+func (_c *MockSnapshotMigration_ReadLegacyHEAD_Call) Run(run func(context1 context.Context, s string)) *MockSnapshotMigration_ReadLegacyHEAD_Call {
207+ _c.Call.Run(func(args mock.Arguments) {
208+ var arg0 context.Context
209+ if args[0] != nil {
210+ arg0 = args[0].(context.Context)
211+ }
212+ var arg1 string
213+ if args[1] != nil {
214+ arg1 = args[1].(string)
215+ }
216+ run(
217+ arg0,
218+ arg1,
219+ )
220+ })
221+ return _c
222+}
223+
224+func (_c *MockSnapshotMigration_ReadLegacyHEAD_Call) Return(checkpointRef domain.CheckpointRef, err error) *MockSnapshotMigration_ReadLegacyHEAD_Call {
225+ _c.Call.Return(checkpointRef, err)
226+ return _c
227+}
228+
229+func (_c *MockSnapshotMigration_ReadLegacyHEAD_Call) RunAndReturn(run func(context1 context.Context, s string) (domain.CheckpointRef, error)) *MockSnapshotMigration_ReadLegacyHEAD_Call {
230+ _c.Call.Return(run)
231+ return _c
232+}
233+
234+// NewMockRecoveryJournal creates a new instance of MockRecoveryJournal. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
235+// The first argument is typically a *testing.T value.
236+func NewMockRecoveryJournal(t interface {
237+ mock.TestingT
238+ Cleanup(func())
239+}) *MockRecoveryJournal {
240+ mock := &MockRecoveryJournal{}
241+ mock.Mock.Test(t)
242+
243+ t.Cleanup(func() { mock.AssertExpectations(t) })
244+
245+ return mock
246+}
247+
248+// MockRecoveryJournal is an autogenerated mock type for the RecoveryJournal type
249+type MockRecoveryJournal struct {
250+ mock.Mock
251+}
252+
253+type MockRecoveryJournal_Expecter struct {
254+ mock *mock.Mock
255+}
256+
257+func (_m *MockRecoveryJournal) EXPECT() *MockRecoveryJournal_Expecter {
258+ return &MockRecoveryJournal_Expecter{mock: &_m.Mock}
259+}
260+
261+// DeleteDiscard provides a mock function for the type MockRecoveryJournal
262+func (_mock *MockRecoveryJournal) DeleteDiscard(context1 context.Context, incarnationID domain.IncarnationID) error {
263+ ret := _mock.Called(context1, incarnationID)
264+
265+ if len(ret) == 0 {
266+ panic("no return value specified for DeleteDiscard")
267+ }
268+
269+ var r0 error
270+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.IncarnationID) error); ok {
271+ r0 = returnFunc(context1, incarnationID)
272+ } else {
273+ r0 = ret.Error(0)
274+ }
275+ return r0
276+}
277+
278+// MockRecoveryJournal_DeleteDiscard_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteDiscard'
279+type MockRecoveryJournal_DeleteDiscard_Call struct {
280+ *mock.Call
281+}
282+
283+// DeleteDiscard is a helper method to define mock.On call
284+// - context1 context.Context
285+// - incarnationID domain.IncarnationID
286+func (_e *MockRecoveryJournal_Expecter) DeleteDiscard(context1 any, incarnationID any) *MockRecoveryJournal_DeleteDiscard_Call {
287+ return &MockRecoveryJournal_DeleteDiscard_Call{Call: _e.mock.On("DeleteDiscard", context1, incarnationID)}
288+}
289+
290+func (_c *MockRecoveryJournal_DeleteDiscard_Call) Run(run func(context1 context.Context, incarnationID domain.IncarnationID)) *MockRecoveryJournal_DeleteDiscard_Call {
291+ _c.Call.Run(func(args mock.Arguments) {
292+ var arg0 context.Context
293+ if args[0] != nil {
294+ arg0 = args[0].(context.Context)
295+ }
296+ var arg1 domain.IncarnationID
297+ if args[1] != nil {
298+ arg1 = args[1].(domain.IncarnationID)
299+ }
300+ run(
301+ arg0,
302+ arg1,
303+ )
304+ })
305+ return _c
306+}
307+
308+func (_c *MockRecoveryJournal_DeleteDiscard_Call) Return(err error) *MockRecoveryJournal_DeleteDiscard_Call {
309+ _c.Call.Return(err)
310+ return _c
311+}
312+
313+func (_c *MockRecoveryJournal_DeleteDiscard_Call) RunAndReturn(run func(context1 context.Context, incarnationID domain.IncarnationID) error) *MockRecoveryJournal_DeleteDiscard_Call {
314+ _c.Call.Return(run)
315+ return _c
316+}
317+
318+// ListDiscards provides a mock function for the type MockRecoveryJournal
319+func (_mock *MockRecoveryJournal) ListDiscards(context1 context.Context) ([]domain.DiscardIntent, error) {
320+ ret := _mock.Called(context1)
321+
322+ if len(ret) == 0 {
323+ panic("no return value specified for ListDiscards")
324+ }
325+
326+ var r0 []domain.DiscardIntent
327+ var r1 error
328+ if returnFunc, ok := ret.Get(0).(func(context.Context) ([]domain.DiscardIntent, error)); ok {
329+ return returnFunc(context1)
330+ }
331+ if returnFunc, ok := ret.Get(0).(func(context.Context) []domain.DiscardIntent); ok {
332+ r0 = returnFunc(context1)
333+ } else {
334+ if ret.Get(0) != nil {
335+ r0 = ret.Get(0).([]domain.DiscardIntent)
336+ }
337+ }
338+ if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
339+ r1 = returnFunc(context1)
340+ } else {
341+ r1 = ret.Error(1)
342+ }
343+ return r0, r1
344+}
345+
346+// MockRecoveryJournal_ListDiscards_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListDiscards'
347+type MockRecoveryJournal_ListDiscards_Call struct {
348+ *mock.Call
349+}
350+
351+// ListDiscards is a helper method to define mock.On call
352+// - context1 context.Context
353+func (_e *MockRecoveryJournal_Expecter) ListDiscards(context1 any) *MockRecoveryJournal_ListDiscards_Call {
354+ return &MockRecoveryJournal_ListDiscards_Call{Call: _e.mock.On("ListDiscards", context1)}
355+}
356+
357+func (_c *MockRecoveryJournal_ListDiscards_Call) Run(run func(context1 context.Context)) *MockRecoveryJournal_ListDiscards_Call {
358+ _c.Call.Run(func(args mock.Arguments) {
359+ var arg0 context.Context
360+ if args[0] != nil {
361+ arg0 = args[0].(context.Context)
362+ }
363+ run(
364+ arg0,
365+ )
366+ })
367+ return _c
368+}
369+
370+func (_c *MockRecoveryJournal_ListDiscards_Call) Return(discardIntents []domain.DiscardIntent, err error) *MockRecoveryJournal_ListDiscards_Call {
371+ _c.Call.Return(discardIntents, err)
372+ return _c
373+}
374+
375+func (_c *MockRecoveryJournal_ListDiscards_Call) RunAndReturn(run func(context1 context.Context) ([]domain.DiscardIntent, error)) *MockRecoveryJournal_ListDiscards_Call {
376+ _c.Call.Return(run)
377+ return _c
378+}
379+
380+// SaveDiscard provides a mock function for the type MockRecoveryJournal
381+func (_mock *MockRecoveryJournal) SaveDiscard(context1 context.Context, discardIntent domain.DiscardIntent) error {
382+ ret := _mock.Called(context1, discardIntent)
383+
384+ if len(ret) == 0 {
385+ panic("no return value specified for SaveDiscard")
386+ }
387+
388+ var r0 error
389+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.DiscardIntent) error); ok {
390+ r0 = returnFunc(context1, discardIntent)
391+ } else {
392+ r0 = ret.Error(0)
393+ }
394+ return r0
395+}
396+
397+// MockRecoveryJournal_SaveDiscard_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SaveDiscard'
398+type MockRecoveryJournal_SaveDiscard_Call struct {
399+ *mock.Call
400+}
401+
402+// SaveDiscard is a helper method to define mock.On call
403+// - context1 context.Context
404+// - discardIntent domain.DiscardIntent
405+func (_e *MockRecoveryJournal_Expecter) SaveDiscard(context1 any, discardIntent any) *MockRecoveryJournal_SaveDiscard_Call {
406+ return &MockRecoveryJournal_SaveDiscard_Call{Call: _e.mock.On("SaveDiscard", context1, discardIntent)}
407+}
408+
409+func (_c *MockRecoveryJournal_SaveDiscard_Call) Run(run func(context1 context.Context, discardIntent domain.DiscardIntent)) *MockRecoveryJournal_SaveDiscard_Call {
410+ _c.Call.Run(func(args mock.Arguments) {
411+ var arg0 context.Context
412+ if args[0] != nil {
413+ arg0 = args[0].(context.Context)
414+ }
415+ var arg1 domain.DiscardIntent
416+ if args[1] != nil {
417+ arg1 = args[1].(domain.DiscardIntent)
418+ }
419+ run(
420+ arg0,
421+ arg1,
422+ )
423+ })
424+ return _c
425+}
426+
427+func (_c *MockRecoveryJournal_SaveDiscard_Call) Return(err error) *MockRecoveryJournal_SaveDiscard_Call {
428+ _c.Call.Return(err)
429+ return _c
430+}
431+
432+func (_c *MockRecoveryJournal_SaveDiscard_Call) RunAndReturn(run func(context1 context.Context, discardIntent domain.DiscardIntent) error) *MockRecoveryJournal_SaveDiscard_Call {
433+ _c.Call.Return(run)
434+ return _c
435+}
436+
437+// NewMockCatalogue creates a new instance of MockCatalogue. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
438+// The first argument is typically a *testing.T value.
439+func NewMockCatalogue(t interface {
440+ mock.TestingT
441+ Cleanup(func())
442+}) *MockCatalogue {
443+ mock := &MockCatalogue{}
444+ mock.Mock.Test(t)
445+
446+ t.Cleanup(func() { mock.AssertExpectations(t) })
447+
448+ return mock
449+}
450+
451+// MockCatalogue is an autogenerated mock type for the Catalogue type
452+type MockCatalogue struct {
453+ mock.Mock
454+}
455+
456+type MockCatalogue_Expecter struct {
457+ mock *mock.Mock
458+}
459+
460+func (_m *MockCatalogue) EXPECT() *MockCatalogue_Expecter {
461+ return &MockCatalogue_Expecter{mock: &_m.Mock}
462+}
463+
464+// Close provides a mock function for the type MockCatalogue
465+func (_mock *MockCatalogue) Close() error {
466+ ret := _mock.Called()
467+
468+ if len(ret) == 0 {
469+ panic("no return value specified for Close")
470+ }
471+
472+ var r0 error
473+ if returnFunc, ok := ret.Get(0).(func() error); ok {
474+ r0 = returnFunc()
475+ } else {
476+ r0 = ret.Error(0)
477+ }
478+ return r0
479+}
480+
481+// MockCatalogue_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
482+type MockCatalogue_Close_Call struct {
483+ *mock.Call
484+}
485+
486+// Close is a helper method to define mock.On call
487+func (_e *MockCatalogue_Expecter) Close() *MockCatalogue_Close_Call {
488+ return &MockCatalogue_Close_Call{Call: _e.mock.On("Close")}
489+}
490+
491+func (_c *MockCatalogue_Close_Call) Run(run func()) *MockCatalogue_Close_Call {
492+ _c.Call.Run(func(args mock.Arguments) {
493+ run()
494+ })
495+ return _c
496+}
497+
498+func (_c *MockCatalogue_Close_Call) Return(err error) *MockCatalogue_Close_Call {
499+ _c.Call.Return(err)
500+ return _c
501+}
502+
503+func (_c *MockCatalogue_Close_Call) RunAndReturn(run func() error) *MockCatalogue_Close_Call {
504+ _c.Call.Return(run)
505+ return _c
506+}
507+
508+// Create provides a mock function for the type MockCatalogue
509+func (_mock *MockCatalogue) Create(catalogueRecord domain.CatalogueRecord) error {
510+ ret := _mock.Called(catalogueRecord)
511+
512+ if len(ret) == 0 {
513+ panic("no return value specified for Create")
514+ }
515+
516+ var r0 error
517+ if returnFunc, ok := ret.Get(0).(func(domain.CatalogueRecord) error); ok {
518+ r0 = returnFunc(catalogueRecord)
519+ } else {
520+ r0 = ret.Error(0)
521+ }
522+ return r0
523+}
524+
525+// MockCatalogue_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
526+type MockCatalogue_Create_Call struct {
527+ *mock.Call
528+}
529+
530+// Create is a helper method to define mock.On call
531+// - catalogueRecord domain.CatalogueRecord
532+func (_e *MockCatalogue_Expecter) Create(catalogueRecord any) *MockCatalogue_Create_Call {
533+ return &MockCatalogue_Create_Call{Call: _e.mock.On("Create", catalogueRecord)}
534+}
535+
536+func (_c *MockCatalogue_Create_Call) Run(run func(catalogueRecord domain.CatalogueRecord)) *MockCatalogue_Create_Call {
537+ _c.Call.Run(func(args mock.Arguments) {
538+ var arg0 domain.CatalogueRecord
539+ if args[0] != nil {
540+ arg0 = args[0].(domain.CatalogueRecord)
541+ }
542+ run(
543+ arg0,
544+ )
545+ })
546+ return _c
547+}
548+
549+func (_c *MockCatalogue_Create_Call) Return(err error) *MockCatalogue_Create_Call {
550+ _c.Call.Return(err)
551+ return _c
552+}
553+
554+func (_c *MockCatalogue_Create_Call) RunAndReturn(run func(catalogueRecord domain.CatalogueRecord) error) *MockCatalogue_Create_Call {
555+ _c.Call.Return(run)
556+ return _c
557+}
558+
559+// Delete provides a mock function for the type MockCatalogue
560+func (_mock *MockCatalogue) Delete(s string) error {
561+ ret := _mock.Called(s)
562+
563+ if len(ret) == 0 {
564+ panic("no return value specified for Delete")
565+ }
566+
567+ var r0 error
568+ if returnFunc, ok := ret.Get(0).(func(string) error); ok {
569+ r0 = returnFunc(s)
570+ } else {
571+ r0 = ret.Error(0)
572+ }
573+ return r0
574+}
575+
576+// MockCatalogue_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
577+type MockCatalogue_Delete_Call struct {
578+ *mock.Call
579+}
580+
581+// Delete is a helper method to define mock.On call
582+// - s string
583+func (_e *MockCatalogue_Expecter) Delete(s any) *MockCatalogue_Delete_Call {
584+ return &MockCatalogue_Delete_Call{Call: _e.mock.On("Delete", s)}
585+}
586+
587+func (_c *MockCatalogue_Delete_Call) Run(run func(s string)) *MockCatalogue_Delete_Call {
588+ _c.Call.Run(func(args mock.Arguments) {
589+ var arg0 string
590+ if args[0] != nil {
591+ arg0 = args[0].(string)
592+ }
593+ run(
594+ arg0,
595+ )
596+ })
597+ return _c
598+}
599+
600+func (_c *MockCatalogue_Delete_Call) Return(err error) *MockCatalogue_Delete_Call {
601+ _c.Call.Return(err)
602+ return _c
603+}
604+
605+func (_c *MockCatalogue_Delete_Call) RunAndReturn(run func(s string) error) *MockCatalogue_Delete_Call {
606+ _c.Call.Return(run)
607+ return _c
608+}
609+
610+// Record provides a mock function for the type MockCatalogue
611+func (_mock *MockCatalogue) Record(s string) (domain.CatalogueRecord, bool) {
612+ ret := _mock.Called(s)
613+
614+ if len(ret) == 0 {
615+ panic("no return value specified for Record")
616+ }
617+
618+ var r0 domain.CatalogueRecord
619+ var r1 bool
620+ if returnFunc, ok := ret.Get(0).(func(string) (domain.CatalogueRecord, bool)); ok {
621+ return returnFunc(s)
622+ }
623+ if returnFunc, ok := ret.Get(0).(func(string) domain.CatalogueRecord); ok {
624+ r0 = returnFunc(s)
625+ } else {
626+ r0 = ret.Get(0).(domain.CatalogueRecord)
627+ }
628+ if returnFunc, ok := ret.Get(1).(func(string) bool); ok {
629+ r1 = returnFunc(s)
630+ } else {
631+ r1 = ret.Get(1).(bool)
632+ }
633+ return r0, r1
634+}
635+
636+// MockCatalogue_Record_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Record'
637+type MockCatalogue_Record_Call struct {
638+ *mock.Call
639+}
640+
641+// Record is a helper method to define mock.On call
642+// - s string
643+func (_e *MockCatalogue_Expecter) Record(s any) *MockCatalogue_Record_Call {
644+ return &MockCatalogue_Record_Call{Call: _e.mock.On("Record", s)}
645+}
646+
647+func (_c *MockCatalogue_Record_Call) Run(run func(s string)) *MockCatalogue_Record_Call {
648+ _c.Call.Run(func(args mock.Arguments) {
649+ var arg0 string
650+ if args[0] != nil {
651+ arg0 = args[0].(string)
652+ }
653+ run(
654+ arg0,
655+ )
656+ })
657+ return _c
658+}
659+
660+func (_c *MockCatalogue_Record_Call) Return(catalogueRecord domain.CatalogueRecord, b bool) *MockCatalogue_Record_Call {
661+ _c.Call.Return(catalogueRecord, b)
662+ return _c
663+}
664+
665+func (_c *MockCatalogue_Record_Call) RunAndReturn(run func(s string) (domain.CatalogueRecord, bool)) *MockCatalogue_Record_Call {
666+ _c.Call.Return(run)
667+ return _c
668+}
669+
670+// Records provides a mock function for the type MockCatalogue
671+func (_mock *MockCatalogue) Records() []domain.CatalogueRecord {
672+ ret := _mock.Called()
673+
674+ if len(ret) == 0 {
675+ panic("no return value specified for Records")
676+ }
677+
678+ var r0 []domain.CatalogueRecord
679+ if returnFunc, ok := ret.Get(0).(func() []domain.CatalogueRecord); ok {
680+ r0 = returnFunc()
681+ } else {
682+ if ret.Get(0) != nil {
683+ r0 = ret.Get(0).([]domain.CatalogueRecord)
684+ }
685+ }
686+ return r0
687+}
688+
689+// MockCatalogue_Records_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Records'
690+type MockCatalogue_Records_Call struct {
691+ *mock.Call
692+}
693+
694+// Records is a helper method to define mock.On call
695+func (_e *MockCatalogue_Expecter) Records() *MockCatalogue_Records_Call {
696+ return &MockCatalogue_Records_Call{Call: _e.mock.On("Records")}
697+}
698+
699+func (_c *MockCatalogue_Records_Call) Run(run func()) *MockCatalogue_Records_Call {
700+ _c.Call.Run(func(args mock.Arguments) {
701+ run()
702+ })
703+ return _c
704+}
705+
706+func (_c *MockCatalogue_Records_Call) Return(catalogueRecords []domain.CatalogueRecord) *MockCatalogue_Records_Call {
707+ _c.Call.Return(catalogueRecords)
708+ return _c
709+}
710+
711+func (_c *MockCatalogue_Records_Call) RunAndReturn(run func() []domain.CatalogueRecord) *MockCatalogue_Records_Call {
712+ _c.Call.Return(run)
713+ return _c
714+}
715+
716+// Rename provides a mock function for the type MockCatalogue
717+func (_mock *MockCatalogue) Rename(s string, catalogueRecord domain.CatalogueRecord) error {
718+ ret := _mock.Called(s, catalogueRecord)
719+
720+ if len(ret) == 0 {
721+ panic("no return value specified for Rename")
722+ }
723+
724+ var r0 error
725+ if returnFunc, ok := ret.Get(0).(func(string, domain.CatalogueRecord) error); ok {
726+ r0 = returnFunc(s, catalogueRecord)
727+ } else {
728+ r0 = ret.Error(0)
729+ }
730+ return r0
731+}
732+
733+// MockCatalogue_Rename_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Rename'
734+type MockCatalogue_Rename_Call struct {
735+ *mock.Call
736+}
737+
738+// Rename is a helper method to define mock.On call
739+// - s string
740+// - catalogueRecord domain.CatalogueRecord
741+func (_e *MockCatalogue_Expecter) Rename(s any, catalogueRecord any) *MockCatalogue_Rename_Call {
742+ return &MockCatalogue_Rename_Call{Call: _e.mock.On("Rename", s, catalogueRecord)}
743+}
744+
745+func (_c *MockCatalogue_Rename_Call) Run(run func(s string, catalogueRecord domain.CatalogueRecord)) *MockCatalogue_Rename_Call {
746+ _c.Call.Run(func(args mock.Arguments) {
747+ var arg0 string
748+ if args[0] != nil {
749+ arg0 = args[0].(string)
750+ }
751+ var arg1 domain.CatalogueRecord
752+ if args[1] != nil {
753+ arg1 = args[1].(domain.CatalogueRecord)
754+ }
755+ run(
756+ arg0,
757+ arg1,
758+ )
759+ })
760+ return _c
761+}
762+
763+func (_c *MockCatalogue_Rename_Call) Return(err error) *MockCatalogue_Rename_Call {
764+ _c.Call.Return(err)
765+ return _c
766+}
767+
768+func (_c *MockCatalogue_Rename_Call) RunAndReturn(run func(s string, catalogueRecord domain.CatalogueRecord) error) *MockCatalogue_Rename_Call {
769+ _c.Call.Return(run)
770+ return _c
771+}
772+
773+// Replace provides a mock function for the type MockCatalogue
774+func (_mock *MockCatalogue) Replace(s string, catalogueRecord domain.CatalogueRecord) error {
775+ ret := _mock.Called(s, catalogueRecord)
776+
777+ if len(ret) == 0 {
778+ panic("no return value specified for Replace")
779+ }
780+
781+ var r0 error
782+ if returnFunc, ok := ret.Get(0).(func(string, domain.CatalogueRecord) error); ok {
783+ r0 = returnFunc(s, catalogueRecord)
784+ } else {
785+ r0 = ret.Error(0)
786+ }
787+ return r0
788+}
789+
790+// MockCatalogue_Replace_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Replace'
791+type MockCatalogue_Replace_Call struct {
792+ *mock.Call
793+}
794+
795+// Replace is a helper method to define mock.On call
796+// - s string
797+// - catalogueRecord domain.CatalogueRecord
798+func (_e *MockCatalogue_Expecter) Replace(s any, catalogueRecord any) *MockCatalogue_Replace_Call {
799+ return &MockCatalogue_Replace_Call{Call: _e.mock.On("Replace", s, catalogueRecord)}
800+}
801+
802+func (_c *MockCatalogue_Replace_Call) Run(run func(s string, catalogueRecord domain.CatalogueRecord)) *MockCatalogue_Replace_Call {
803+ _c.Call.Run(func(args mock.Arguments) {
804+ var arg0 string
805+ if args[0] != nil {
806+ arg0 = args[0].(string)
807+ }
808+ var arg1 domain.CatalogueRecord
809+ if args[1] != nil {
810+ arg1 = args[1].(domain.CatalogueRecord)
811+ }
812+ run(
813+ arg0,
814+ arg1,
815+ )
816+ })
817+ return _c
818+}
819+
820+func (_c *MockCatalogue_Replace_Call) Return(err error) *MockCatalogue_Replace_Call {
821+ _c.Call.Return(err)
822+ return _c
823+}
824+
825+func (_c *MockCatalogue_Replace_Call) RunAndReturn(run func(s string, catalogueRecord domain.CatalogueRecord) error) *MockCatalogue_Replace_Call {
826+ _c.Call.Return(run)
827+ return _c
828+}
829+
830+// NewMockSnapshotRepository creates a new instance of MockSnapshotRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
831+// The first argument is typically a *testing.T value.
832+func NewMockSnapshotRepository(t interface {
833+ mock.TestingT
834+ Cleanup(func())
835+}) *MockSnapshotRepository {
836+ mock := &MockSnapshotRepository{}
837+ mock.Mock.Test(t)
838+
839+ t.Cleanup(func() { mock.AssertExpectations(t) })
840+
841+ return mock
842+}
843+
844+// MockSnapshotRepository is an autogenerated mock type for the SnapshotRepository type
845+type MockSnapshotRepository struct {
846+ mock.Mock
847+}
848+
849+type MockSnapshotRepository_Expecter struct {
850+ mock *mock.Mock
851+}
852+
853+func (_m *MockSnapshotRepository) EXPECT() *MockSnapshotRepository_Expecter {
854+ return &MockSnapshotRepository_Expecter{mock: &_m.Mock}
855+}
856+
857+// DeleteDeletionTombstone provides a mock function for the type MockSnapshotRepository
858+func (_mock *MockSnapshotRepository) DeleteDeletionTombstone(context1 context.Context, incarnationID domain.IncarnationID) error {
859+ ret := _mock.Called(context1, incarnationID)
860+
861+ if len(ret) == 0 {
862+ panic("no return value specified for DeleteDeletionTombstone")
863+ }
864+
865+ var r0 error
866+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.IncarnationID) error); ok {
867+ r0 = returnFunc(context1, incarnationID)
868+ } else {
869+ r0 = ret.Error(0)
870+ }
871+ return r0
872+}
873+
874+// MockSnapshotRepository_DeleteDeletionTombstone_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteDeletionTombstone'
875+type MockSnapshotRepository_DeleteDeletionTombstone_Call struct {
876+ *mock.Call
877+}
878+
879+// DeleteDeletionTombstone is a helper method to define mock.On call
880+// - context1 context.Context
881+// - incarnationID domain.IncarnationID
882+func (_e *MockSnapshotRepository_Expecter) DeleteDeletionTombstone(context1 any, incarnationID any) *MockSnapshotRepository_DeleteDeletionTombstone_Call {
883+ return &MockSnapshotRepository_DeleteDeletionTombstone_Call{Call: _e.mock.On("DeleteDeletionTombstone", context1, incarnationID)}
884+}
885+
886+func (_c *MockSnapshotRepository_DeleteDeletionTombstone_Call) Run(run func(context1 context.Context, incarnationID domain.IncarnationID)) *MockSnapshotRepository_DeleteDeletionTombstone_Call {
887+ _c.Call.Run(func(args mock.Arguments) {
888+ var arg0 context.Context
889+ if args[0] != nil {
890+ arg0 = args[0].(context.Context)
891+ }
892+ var arg1 domain.IncarnationID
893+ if args[1] != nil {
894+ arg1 = args[1].(domain.IncarnationID)
895+ }
896+ run(
897+ arg0,
898+ arg1,
899+ )
900+ })
901+ return _c
902+}
903+
904+func (_c *MockSnapshotRepository_DeleteDeletionTombstone_Call) Return(err error) *MockSnapshotRepository_DeleteDeletionTombstone_Call {
905+ _c.Call.Return(err)
906+ return _c
907+}
908+
909+func (_c *MockSnapshotRepository_DeleteDeletionTombstone_Call) RunAndReturn(run func(context1 context.Context, incarnationID domain.IncarnationID) error) *MockSnapshotRepository_DeleteDeletionTombstone_Call {
910+ _c.Call.Return(run)
911+ return _c
912+}
913+
914+// DeleteIncarnation provides a mock function for the type MockSnapshotRepository
915+func (_mock *MockSnapshotRepository) DeleteIncarnation(context1 context.Context, incarnationID domain.IncarnationID) error {
916+ ret := _mock.Called(context1, incarnationID)
917+
918+ if len(ret) == 0 {
919+ panic("no return value specified for DeleteIncarnation")
920+ }
921+
922+ var r0 error
923+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.IncarnationID) error); ok {
924+ r0 = returnFunc(context1, incarnationID)
925+ } else {
926+ r0 = ret.Error(0)
927+ }
928+ return r0
929+}
930+
931+// MockSnapshotRepository_DeleteIncarnation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteIncarnation'
932+type MockSnapshotRepository_DeleteIncarnation_Call struct {
933+ *mock.Call
934+}
935+
936+// DeleteIncarnation is a helper method to define mock.On call
937+// - context1 context.Context
938+// - incarnationID domain.IncarnationID
939+func (_e *MockSnapshotRepository_Expecter) DeleteIncarnation(context1 any, incarnationID any) *MockSnapshotRepository_DeleteIncarnation_Call {
940+ return &MockSnapshotRepository_DeleteIncarnation_Call{Call: _e.mock.On("DeleteIncarnation", context1, incarnationID)}
941+}
942+
943+func (_c *MockSnapshotRepository_DeleteIncarnation_Call) Run(run func(context1 context.Context, incarnationID domain.IncarnationID)) *MockSnapshotRepository_DeleteIncarnation_Call {
944+ _c.Call.Run(func(args mock.Arguments) {
945+ var arg0 context.Context
946+ if args[0] != nil {
947+ arg0 = args[0].(context.Context)
948+ }
949+ var arg1 domain.IncarnationID
950+ if args[1] != nil {
951+ arg1 = args[1].(domain.IncarnationID)
952+ }
953+ run(
954+ arg0,
955+ arg1,
956+ )
957+ })
958+ return _c
959+}
960+
961+func (_c *MockSnapshotRepository_DeleteIncarnation_Call) Return(err error) *MockSnapshotRepository_DeleteIncarnation_Call {
962+ _c.Call.Return(err)
963+ return _c
964+}
965+
966+func (_c *MockSnapshotRepository_DeleteIncarnation_Call) RunAndReturn(run func(context1 context.Context, incarnationID domain.IncarnationID) error) *MockSnapshotRepository_DeleteIncarnation_Call {
967+ _c.Call.Return(run)
968+ return _c
969+}
970+
971+// ListDeletionTombstones provides a mock function for the type MockSnapshotRepository
972+func (_mock *MockSnapshotRepository) ListDeletionTombstones(context1 context.Context, deletionTombstoneCursor ports.DeletionTombstoneCursor, maintenanceBudget ports.MaintenanceBudget) (ports.DeletionTombstonePage, error) {
973+ ret := _mock.Called(context1, deletionTombstoneCursor, maintenanceBudget)
974+
975+ if len(ret) == 0 {
976+ panic("no return value specified for ListDeletionTombstones")
977+ }
978+
979+ var r0 ports.DeletionTombstonePage
980+ var r1 error
981+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.DeletionTombstoneCursor, ports.MaintenanceBudget) (ports.DeletionTombstonePage, error)); ok {
982+ return returnFunc(context1, deletionTombstoneCursor, maintenanceBudget)
983+ }
984+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.DeletionTombstoneCursor, ports.MaintenanceBudget) ports.DeletionTombstonePage); ok {
985+ r0 = returnFunc(context1, deletionTombstoneCursor, maintenanceBudget)
986+ } else {
987+ r0 = ret.Get(0).(ports.DeletionTombstonePage)
988+ }
989+ if returnFunc, ok := ret.Get(1).(func(context.Context, ports.DeletionTombstoneCursor, ports.MaintenanceBudget) error); ok {
990+ r1 = returnFunc(context1, deletionTombstoneCursor, maintenanceBudget)
991+ } else {
992+ r1 = ret.Error(1)
993+ }
994+ return r0, r1
995+}
996+
997+// MockSnapshotRepository_ListDeletionTombstones_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListDeletionTombstones'
998+type MockSnapshotRepository_ListDeletionTombstones_Call struct {
999+ *mock.Call
1000+}
1001+
1002+// ListDeletionTombstones is a helper method to define mock.On call
1003+// - context1 context.Context
1004+// - deletionTombstoneCursor ports.DeletionTombstoneCursor
1005+// - maintenanceBudget ports.MaintenanceBudget
1006+func (_e *MockSnapshotRepository_Expecter) ListDeletionTombstones(context1 any, deletionTombstoneCursor any, maintenanceBudget any) *MockSnapshotRepository_ListDeletionTombstones_Call {
1007+ return &MockSnapshotRepository_ListDeletionTombstones_Call{Call: _e.mock.On("ListDeletionTombstones", context1, deletionTombstoneCursor, maintenanceBudget)}
1008+}
1009+
1010+func (_c *MockSnapshotRepository_ListDeletionTombstones_Call) Run(run func(context1 context.Context, deletionTombstoneCursor ports.DeletionTombstoneCursor, maintenanceBudget ports.MaintenanceBudget)) *MockSnapshotRepository_ListDeletionTombstones_Call {
1011+ _c.Call.Run(func(args mock.Arguments) {
1012+ var arg0 context.Context
1013+ if args[0] != nil {
1014+ arg0 = args[0].(context.Context)
1015+ }
1016+ var arg1 ports.DeletionTombstoneCursor
1017+ if args[1] != nil {
1018+ arg1 = args[1].(ports.DeletionTombstoneCursor)
1019+ }
1020+ var arg2 ports.MaintenanceBudget
1021+ if args[2] != nil {
1022+ arg2 = args[2].(ports.MaintenanceBudget)
1023+ }
1024+ run(
1025+ arg0,
1026+ arg1,
1027+ arg2,
1028+ )
1029+ })
1030+ return _c
1031+}
1032+
1033+func (_c *MockSnapshotRepository_ListDeletionTombstones_Call) Return(deletionTombstonePage ports.DeletionTombstonePage, err error) *MockSnapshotRepository_ListDeletionTombstones_Call {
1034+ _c.Call.Return(deletionTombstonePage, err)
1035+ return _c
1036+}
1037+
1038+func (_c *MockSnapshotRepository_ListDeletionTombstones_Call) RunAndReturn(run func(context1 context.Context, deletionTombstoneCursor ports.DeletionTombstoneCursor, maintenanceBudget ports.MaintenanceBudget) (ports.DeletionTombstonePage, error)) *MockSnapshotRepository_ListDeletionTombstones_Call {
1039+ _c.Call.Return(run)
1040+ return _c
1041+}
1042+
1043+// LoadCheckpoint provides a mock function for the type MockSnapshotRepository
1044+func (_mock *MockSnapshotRepository) LoadCheckpoint(context1 context.Context, incarnationID domain.IncarnationID, s string, v ports.CheckpointRef) (ports.SnapshotGeneration, error) {
1045+ ret := _mock.Called(context1, incarnationID, s, v)
1046+
1047+ if len(ret) == 0 {
1048+ panic("no return value specified for LoadCheckpoint")
1049+ }
1050+
1051+ var r0 ports.SnapshotGeneration
1052+ var r1 error
1053+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.IncarnationID, string, ports.CheckpointRef) (ports.SnapshotGeneration, error)); ok {
1054+ return returnFunc(context1, incarnationID, s, v)
1055+ }
1056+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.IncarnationID, string, ports.CheckpointRef) ports.SnapshotGeneration); ok {
1057+ r0 = returnFunc(context1, incarnationID, s, v)
1058+ } else {
1059+ r0 = ret.Get(0).(ports.SnapshotGeneration)
1060+ }
1061+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.IncarnationID, string, ports.CheckpointRef) error); ok {
1062+ r1 = returnFunc(context1, incarnationID, s, v)
1063+ } else {
1064+ r1 = ret.Error(1)
1065+ }
1066+ return r0, r1
1067+}
1068+
1069+// MockSnapshotRepository_LoadCheckpoint_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LoadCheckpoint'
1070+type MockSnapshotRepository_LoadCheckpoint_Call struct {
1071+ *mock.Call
1072+}
1073+
1074+// LoadCheckpoint is a helper method to define mock.On call
1075+// - context1 context.Context
1076+// - incarnationID domain.IncarnationID
1077+// - s string
1078+// - v ports.CheckpointRef
1079+func (_e *MockSnapshotRepository_Expecter) LoadCheckpoint(context1 any, incarnationID any, s any, v any) *MockSnapshotRepository_LoadCheckpoint_Call {
1080+ return &MockSnapshotRepository_LoadCheckpoint_Call{Call: _e.mock.On("LoadCheckpoint", context1, incarnationID, s, v)}
1081+}
1082+
1083+func (_c *MockSnapshotRepository_LoadCheckpoint_Call) Run(run func(context1 context.Context, incarnationID domain.IncarnationID, s string, v ports.CheckpointRef)) *MockSnapshotRepository_LoadCheckpoint_Call {
1084+ _c.Call.Run(func(args mock.Arguments) {
1085+ var arg0 context.Context
1086+ if args[0] != nil {
1087+ arg0 = args[0].(context.Context)
1088+ }
1089+ var arg1 domain.IncarnationID
1090+ if args[1] != nil {
1091+ arg1 = args[1].(domain.IncarnationID)
1092+ }
1093+ var arg2 string
1094+ if args[2] != nil {
1095+ arg2 = args[2].(string)
1096+ }
1097+ var arg3 ports.CheckpointRef
1098+ if args[3] != nil {
1099+ arg3 = args[3].(ports.CheckpointRef)
1100+ }
1101+ run(
1102+ arg0,
1103+ arg1,
1104+ arg2,
1105+ arg3,
1106+ )
1107+ })
1108+ return _c
1109+}
1110+
1111+func (_c *MockSnapshotRepository_LoadCheckpoint_Call) Return(snapshotGeneration ports.SnapshotGeneration, err error) *MockSnapshotRepository_LoadCheckpoint_Call {
1112+ _c.Call.Return(snapshotGeneration, err)
1113+ return _c
1114+}
1115+
1116+func (_c *MockSnapshotRepository_LoadCheckpoint_Call) RunAndReturn(run func(context1 context.Context, incarnationID domain.IncarnationID, s string, v ports.CheckpointRef) (ports.SnapshotGeneration, error)) *MockSnapshotRepository_LoadCheckpoint_Call {
1117+ _c.Call.Return(run)
1118+ return _c
1119+}
1120+
1121+// MaintainSession provides a mock function for the type MockSnapshotRepository
1122+func (_mock *MockSnapshotRepository) MaintainSession(context1 context.Context, retentionPlan ports.RetentionPlan, maintenanceBudget ports.MaintenanceBudget) (bool, error) {
1123+ ret := _mock.Called(context1, retentionPlan, maintenanceBudget)
1124+
1125+ if len(ret) == 0 {
1126+ panic("no return value specified for MaintainSession")
1127+ }
1128+
1129+ var r0 bool
1130+ var r1 error
1131+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.RetentionPlan, ports.MaintenanceBudget) (bool, error)); ok {
1132+ return returnFunc(context1, retentionPlan, maintenanceBudget)
1133+ }
1134+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.RetentionPlan, ports.MaintenanceBudget) bool); ok {
1135+ r0 = returnFunc(context1, retentionPlan, maintenanceBudget)
1136+ } else {
1137+ r0 = ret.Get(0).(bool)
1138+ }
1139+ if returnFunc, ok := ret.Get(1).(func(context.Context, ports.RetentionPlan, ports.MaintenanceBudget) error); ok {
1140+ r1 = returnFunc(context1, retentionPlan, maintenanceBudget)
1141+ } else {
1142+ r1 = ret.Error(1)
1143+ }
1144+ return r0, r1
1145+}
1146+
1147+// MockSnapshotRepository_MaintainSession_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MaintainSession'
1148+type MockSnapshotRepository_MaintainSession_Call struct {
1149+ *mock.Call
1150+}
1151+
1152+// MaintainSession is a helper method to define mock.On call
1153+// - context1 context.Context
1154+// - retentionPlan ports.RetentionPlan
1155+// - maintenanceBudget ports.MaintenanceBudget
1156+func (_e *MockSnapshotRepository_Expecter) MaintainSession(context1 any, retentionPlan any, maintenanceBudget any) *MockSnapshotRepository_MaintainSession_Call {
1157+ return &MockSnapshotRepository_MaintainSession_Call{Call: _e.mock.On("MaintainSession", context1, retentionPlan, maintenanceBudget)}
1158+}
1159+
1160+func (_c *MockSnapshotRepository_MaintainSession_Call) Run(run func(context1 context.Context, retentionPlan ports.RetentionPlan, maintenanceBudget ports.MaintenanceBudget)) *MockSnapshotRepository_MaintainSession_Call {
1161+ _c.Call.Run(func(args mock.Arguments) {
1162+ var arg0 context.Context
1163+ if args[0] != nil {
1164+ arg0 = args[0].(context.Context)
1165+ }
1166+ var arg1 ports.RetentionPlan
1167+ if args[1] != nil {
1168+ arg1 = args[1].(ports.RetentionPlan)
1169+ }
1170+ var arg2 ports.MaintenanceBudget
1171+ if args[2] != nil {
1172+ arg2 = args[2].(ports.MaintenanceBudget)
1173+ }
1174+ run(
1175+ arg0,
1176+ arg1,
1177+ arg2,
1178+ )
1179+ })
1180+ return _c
1181+}
1182+
1183+func (_c *MockSnapshotRepository_MaintainSession_Call) Return(b bool, err error) *MockSnapshotRepository_MaintainSession_Call {
1184+ _c.Call.Return(b, err)
1185+ return _c
1186+}
1187+
1188+func (_c *MockSnapshotRepository_MaintainSession_Call) RunAndReturn(run func(context1 context.Context, retentionPlan ports.RetentionPlan, maintenanceBudget ports.MaintenanceBudget) (bool, error)) *MockSnapshotRepository_MaintainSession_Call {
1189+ _c.Call.Return(run)
1190+ return _c
1191+}
1192+
1193+// Publish provides a mock function for the type MockSnapshotRepository
1194+func (_mock *MockSnapshotRepository) Publish(context1 context.Context, snapshotPublication ports.SnapshotPublication) error {
1195+ ret := _mock.Called(context1, snapshotPublication)
1196+
1197+ if len(ret) == 0 {
1198+ panic("no return value specified for Publish")
1199+ }
1200+
1201+ var r0 error
1202+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.SnapshotPublication) error); ok {
1203+ r0 = returnFunc(context1, snapshotPublication)
1204+ } else {
1205+ r0 = ret.Error(0)
1206+ }
1207+ return r0
1208+}
1209+
1210+// MockSnapshotRepository_Publish_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Publish'
1211+type MockSnapshotRepository_Publish_Call struct {
1212+ *mock.Call
1213+}
1214+
1215+// Publish is a helper method to define mock.On call
1216+// - context1 context.Context
1217+// - snapshotPublication ports.SnapshotPublication
1218+func (_e *MockSnapshotRepository_Expecter) Publish(context1 any, snapshotPublication any) *MockSnapshotRepository_Publish_Call {
1219+ return &MockSnapshotRepository_Publish_Call{Call: _e.mock.On("Publish", context1, snapshotPublication)}
1220+}
1221+
1222+func (_c *MockSnapshotRepository_Publish_Call) Run(run func(context1 context.Context, snapshotPublication ports.SnapshotPublication)) *MockSnapshotRepository_Publish_Call {
1223+ _c.Call.Run(func(args mock.Arguments) {
1224+ var arg0 context.Context
1225+ if args[0] != nil {
1226+ arg0 = args[0].(context.Context)
1227+ }
1228+ var arg1 ports.SnapshotPublication
1229+ if args[1] != nil {
1230+ arg1 = args[1].(ports.SnapshotPublication)
1231+ }
1232+ run(
1233+ arg0,
1234+ arg1,
1235+ )
1236+ })
1237+ return _c
1238+}
1239+
1240+func (_c *MockSnapshotRepository_Publish_Call) Return(err error) *MockSnapshotRepository_Publish_Call {
1241+ _c.Call.Return(err)
1242+ return _c
1243+}
1244+
1245+func (_c *MockSnapshotRepository_Publish_Call) RunAndReturn(run func(context1 context.Context, snapshotPublication ports.SnapshotPublication) error) *MockSnapshotRepository_Publish_Call {
1246+ _c.Call.Return(run)
1247+ return _c
1248+}
1249+
1250+// QuarantineDeletionSources provides a mock function for the type MockSnapshotRepository
1251+func (_mock *MockSnapshotRepository) QuarantineDeletionSources(context1 context.Context, deletionTombstone domain.DeletionTombstone, b bool) error {
1252+ ret := _mock.Called(context1, deletionTombstone, b)
1253+
1254+ if len(ret) == 0 {
1255+ panic("no return value specified for QuarantineDeletionSources")
1256+ }
1257+
1258+ var r0 error
1259+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.DeletionTombstone, bool) error); ok {
1260+ r0 = returnFunc(context1, deletionTombstone, b)
1261+ } else {
1262+ r0 = ret.Error(0)
1263+ }
1264+ return r0
1265+}
1266+
1267+// MockSnapshotRepository_QuarantineDeletionSources_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QuarantineDeletionSources'
1268+type MockSnapshotRepository_QuarantineDeletionSources_Call struct {
1269+ *mock.Call
1270+}
1271+
1272+// QuarantineDeletionSources is a helper method to define mock.On call
1273+// - context1 context.Context
1274+// - deletionTombstone domain.DeletionTombstone
1275+// - b bool
1276+func (_e *MockSnapshotRepository_Expecter) QuarantineDeletionSources(context1 any, deletionTombstone any, b any) *MockSnapshotRepository_QuarantineDeletionSources_Call {
1277+ return &MockSnapshotRepository_QuarantineDeletionSources_Call{Call: _e.mock.On("QuarantineDeletionSources", context1, deletionTombstone, b)}
1278+}
1279+
1280+func (_c *MockSnapshotRepository_QuarantineDeletionSources_Call) Run(run func(context1 context.Context, deletionTombstone domain.DeletionTombstone, b bool)) *MockSnapshotRepository_QuarantineDeletionSources_Call {
1281+ _c.Call.Run(func(args mock.Arguments) {
1282+ var arg0 context.Context
1283+ if args[0] != nil {
1284+ arg0 = args[0].(context.Context)
1285+ }
1286+ var arg1 domain.DeletionTombstone
1287+ if args[1] != nil {
1288+ arg1 = args[1].(domain.DeletionTombstone)
1289+ }
1290+ var arg2 bool
1291+ if args[2] != nil {
1292+ arg2 = args[2].(bool)
1293+ }
1294+ run(
1295+ arg0,
1296+ arg1,
1297+ arg2,
1298+ )
1299+ })
1300+ return _c
1301+}
1302+
1303+func (_c *MockSnapshotRepository_QuarantineDeletionSources_Call) Return(err error) *MockSnapshotRepository_QuarantineDeletionSources_Call {
1304+ _c.Call.Return(err)
1305+ return _c
1306+}
1307+
1308+func (_c *MockSnapshotRepository_QuarantineDeletionSources_Call) RunAndReturn(run func(context1 context.Context, deletionTombstone domain.DeletionTombstone, b bool) error) *MockSnapshotRepository_QuarantineDeletionSources_Call {
1309+ _c.Call.Return(run)
1310+ return _c
1311+}
1312+
1313+// QuarantineIncarnation provides a mock function for the type MockSnapshotRepository
1314+func (_mock *MockSnapshotRepository) QuarantineIncarnation(context1 context.Context, incarnationID domain.IncarnationID) error {
1315+ ret := _mock.Called(context1, incarnationID)
1316+
1317+ if len(ret) == 0 {
1318+ panic("no return value specified for QuarantineIncarnation")
1319+ }
1320+
1321+ var r0 error
1322+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.IncarnationID) error); ok {
1323+ r0 = returnFunc(context1, incarnationID)
1324+ } else {
1325+ r0 = ret.Error(0)
1326+ }
1327+ return r0
1328+}
1329+
1330+// MockSnapshotRepository_QuarantineIncarnation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QuarantineIncarnation'
1331+type MockSnapshotRepository_QuarantineIncarnation_Call struct {
1332+ *mock.Call
1333+}
1334+
1335+// QuarantineIncarnation is a helper method to define mock.On call
1336+// - context1 context.Context
1337+// - incarnationID domain.IncarnationID
1338+func (_e *MockSnapshotRepository_Expecter) QuarantineIncarnation(context1 any, incarnationID any) *MockSnapshotRepository_QuarantineIncarnation_Call {
1339+ return &MockSnapshotRepository_QuarantineIncarnation_Call{Call: _e.mock.On("QuarantineIncarnation", context1, incarnationID)}
1340+}
1341+
1342+func (_c *MockSnapshotRepository_QuarantineIncarnation_Call) Run(run func(context1 context.Context, incarnationID domain.IncarnationID)) *MockSnapshotRepository_QuarantineIncarnation_Call {
1343+ _c.Call.Run(func(args mock.Arguments) {
1344+ var arg0 context.Context
1345+ if args[0] != nil {
1346+ arg0 = args[0].(context.Context)
1347+ }
1348+ var arg1 domain.IncarnationID
1349+ if args[1] != nil {
1350+ arg1 = args[1].(domain.IncarnationID)
1351+ }
1352+ run(
1353+ arg0,
1354+ arg1,
1355+ )
1356+ })
1357+ return _c
1358+}
1359+
1360+func (_c *MockSnapshotRepository_QuarantineIncarnation_Call) Return(err error) *MockSnapshotRepository_QuarantineIncarnation_Call {
1361+ _c.Call.Return(err)
1362+ return _c
1363+}
1364+
1365+func (_c *MockSnapshotRepository_QuarantineIncarnation_Call) RunAndReturn(run func(context1 context.Context, incarnationID domain.IncarnationID) error) *MockSnapshotRepository_QuarantineIncarnation_Call {
1366+ _c.Call.Return(run)
1367+ return _c
1368+}
1369+
1370+// Reconcile provides a mock function for the type MockSnapshotRepository
1371+func (_mock *MockSnapshotRepository) Reconcile(context1 context.Context, catalogueRecords []domain.CatalogueRecord, reconcileCursor ports.ReconcileCursor, maintenanceBudget ports.MaintenanceBudget) (ports.ReconcileCursor, []ports.ReconcileFinding, error) {
1372+ ret := _mock.Called(context1, catalogueRecords, reconcileCursor, maintenanceBudget)
1373+
1374+ if len(ret) == 0 {
1375+ panic("no return value specified for Reconcile")
1376+ }
1377+
1378+ var r0 ports.ReconcileCursor
1379+ var r1 []ports.ReconcileFinding
1380+ var r2 error
1381+ if returnFunc, ok := ret.Get(0).(func(context.Context, []domain.CatalogueRecord, ports.ReconcileCursor, ports.MaintenanceBudget) (ports.ReconcileCursor, []ports.ReconcileFinding, error)); ok {
1382+ return returnFunc(context1, catalogueRecords, reconcileCursor, maintenanceBudget)
1383+ }
1384+ if returnFunc, ok := ret.Get(0).(func(context.Context, []domain.CatalogueRecord, ports.ReconcileCursor, ports.MaintenanceBudget) ports.ReconcileCursor); ok {
1385+ r0 = returnFunc(context1, catalogueRecords, reconcileCursor, maintenanceBudget)
1386+ } else {
1387+ r0 = ret.Get(0).(ports.ReconcileCursor)
1388+ }
1389+ if returnFunc, ok := ret.Get(1).(func(context.Context, []domain.CatalogueRecord, ports.ReconcileCursor, ports.MaintenanceBudget) []ports.ReconcileFinding); ok {
1390+ r1 = returnFunc(context1, catalogueRecords, reconcileCursor, maintenanceBudget)
1391+ } else {
1392+ if ret.Get(1) != nil {
1393+ r1 = ret.Get(1).([]ports.ReconcileFinding)
1394+ }
1395+ }
1396+ if returnFunc, ok := ret.Get(2).(func(context.Context, []domain.CatalogueRecord, ports.ReconcileCursor, ports.MaintenanceBudget) error); ok {
1397+ r2 = returnFunc(context1, catalogueRecords, reconcileCursor, maintenanceBudget)
1398+ } else {
1399+ r2 = ret.Error(2)
1400+ }
1401+ return r0, r1, r2
1402+}
1403+
1404+// MockSnapshotRepository_Reconcile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Reconcile'
1405+type MockSnapshotRepository_Reconcile_Call struct {
1406+ *mock.Call
1407+}
1408+
1409+// Reconcile is a helper method to define mock.On call
1410+// - context1 context.Context
1411+// - catalogueRecords []domain.CatalogueRecord
1412+// - reconcileCursor ports.ReconcileCursor
1413+// - maintenanceBudget ports.MaintenanceBudget
1414+func (_e *MockSnapshotRepository_Expecter) Reconcile(context1 any, catalogueRecords any, reconcileCursor any, maintenanceBudget any) *MockSnapshotRepository_Reconcile_Call {
1415+ return &MockSnapshotRepository_Reconcile_Call{Call: _e.mock.On("Reconcile", context1, catalogueRecords, reconcileCursor, maintenanceBudget)}
1416+}
1417+
1418+func (_c *MockSnapshotRepository_Reconcile_Call) Run(run func(context1 context.Context, catalogueRecords []domain.CatalogueRecord, reconcileCursor ports.ReconcileCursor, maintenanceBudget ports.MaintenanceBudget)) *MockSnapshotRepository_Reconcile_Call {
1419+ _c.Call.Run(func(args mock.Arguments) {
1420+ var arg0 context.Context
1421+ if args[0] != nil {
1422+ arg0 = args[0].(context.Context)
1423+ }
1424+ var arg1 []domain.CatalogueRecord
1425+ if args[1] != nil {
1426+ arg1 = args[1].([]domain.CatalogueRecord)
1427+ }
1428+ var arg2 ports.ReconcileCursor
1429+ if args[2] != nil {
1430+ arg2 = args[2].(ports.ReconcileCursor)
1431+ }
1432+ var arg3 ports.MaintenanceBudget
1433+ if args[3] != nil {
1434+ arg3 = args[3].(ports.MaintenanceBudget)
1435+ }
1436+ run(
1437+ arg0,
1438+ arg1,
1439+ arg2,
1440+ arg3,
1441+ )
1442+ })
1443+ return _c
1444+}
1445+
1446+func (_c *MockSnapshotRepository_Reconcile_Call) Return(reconcileCursor1 ports.ReconcileCursor, reconcileFindings []ports.ReconcileFinding, err error) *MockSnapshotRepository_Reconcile_Call {
1447+ _c.Call.Return(reconcileCursor1, reconcileFindings, err)
1448+ return _c
1449+}
1450+
1451+func (_c *MockSnapshotRepository_Reconcile_Call) RunAndReturn(run func(context1 context.Context, catalogueRecords []domain.CatalogueRecord, reconcileCursor ports.ReconcileCursor, maintenanceBudget ports.MaintenanceBudget) (ports.ReconcileCursor, []ports.ReconcileFinding, error)) *MockSnapshotRepository_Reconcile_Call {
1452+ _c.Call.Return(run)
1453+ return _c
1454+}
1455+
1456+// RepairHEAD provides a mock function for the type MockSnapshotRepository
1457+func (_mock *MockSnapshotRepository) RepairHEAD(context1 context.Context, incarnationID domain.IncarnationID, v ports.CheckpointRef) error {
1458+ ret := _mock.Called(context1, incarnationID, v)
1459+
1460+ if len(ret) == 0 {
1461+ panic("no return value specified for RepairHEAD")
1462+ }
1463+
1464+ var r0 error
1465+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.IncarnationID, ports.CheckpointRef) error); ok {
1466+ r0 = returnFunc(context1, incarnationID, v)
1467+ } else {
1468+ r0 = ret.Error(0)
1469+ }
1470+ return r0
1471+}
1472+
1473+// MockSnapshotRepository_RepairHEAD_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RepairHEAD'
1474+type MockSnapshotRepository_RepairHEAD_Call struct {
1475+ *mock.Call
1476+}
1477+
1478+// RepairHEAD is a helper method to define mock.On call
1479+// - context1 context.Context
1480+// - incarnationID domain.IncarnationID
1481+// - v ports.CheckpointRef
1482+func (_e *MockSnapshotRepository_Expecter) RepairHEAD(context1 any, incarnationID any, v any) *MockSnapshotRepository_RepairHEAD_Call {
1483+ return &MockSnapshotRepository_RepairHEAD_Call{Call: _e.mock.On("RepairHEAD", context1, incarnationID, v)}
1484+}
1485+
1486+func (_c *MockSnapshotRepository_RepairHEAD_Call) Run(run func(context1 context.Context, incarnationID domain.IncarnationID, v ports.CheckpointRef)) *MockSnapshotRepository_RepairHEAD_Call {
1487+ _c.Call.Run(func(args mock.Arguments) {
1488+ var arg0 context.Context
1489+ if args[0] != nil {
1490+ arg0 = args[0].(context.Context)
1491+ }
1492+ var arg1 domain.IncarnationID
1493+ if args[1] != nil {
1494+ arg1 = args[1].(domain.IncarnationID)
1495+ }
1496+ var arg2 ports.CheckpointRef
1497+ if args[2] != nil {
1498+ arg2 = args[2].(ports.CheckpointRef)
1499+ }
1500+ run(
1501+ arg0,
1502+ arg1,
1503+ arg2,
1504+ )
1505+ })
1506+ return _c
1507+}
1508+
1509+func (_c *MockSnapshotRepository_RepairHEAD_Call) Return(err error) *MockSnapshotRepository_RepairHEAD_Call {
1510+ _c.Call.Return(err)
1511+ return _c
1512+}
1513+
1514+func (_c *MockSnapshotRepository_RepairHEAD_Call) RunAndReturn(run func(context1 context.Context, incarnationID domain.IncarnationID, v ports.CheckpointRef) error) *MockSnapshotRepository_RepairHEAD_Call {
1515+ _c.Call.Return(run)
1516+ return _c
1517+}
1518+
1519+// SaveQuarantineDescriptor provides a mock function for the type MockSnapshotRepository
1520+func (_mock *MockSnapshotRepository) SaveQuarantineDescriptor(context1 context.Context, quarantineDescriptor domain.QuarantineDescriptor) error {
1521+ ret := _mock.Called(context1, quarantineDescriptor)
1522+
1523+ if len(ret) == 0 {
1524+ panic("no return value specified for SaveQuarantineDescriptor")
1525+ }
1526+
1527+ var r0 error
1528+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.QuarantineDescriptor) error); ok {
1529+ r0 = returnFunc(context1, quarantineDescriptor)
1530+ } else {
1531+ r0 = ret.Error(0)
1532+ }
1533+ return r0
1534+}
1535+
1536+// MockSnapshotRepository_SaveQuarantineDescriptor_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SaveQuarantineDescriptor'
1537+type MockSnapshotRepository_SaveQuarantineDescriptor_Call struct {
1538+ *mock.Call
1539+}
1540+
1541+// SaveQuarantineDescriptor is a helper method to define mock.On call
1542+// - context1 context.Context
1543+// - quarantineDescriptor domain.QuarantineDescriptor
1544+func (_e *MockSnapshotRepository_Expecter) SaveQuarantineDescriptor(context1 any, quarantineDescriptor any) *MockSnapshotRepository_SaveQuarantineDescriptor_Call {
1545+ return &MockSnapshotRepository_SaveQuarantineDescriptor_Call{Call: _e.mock.On("SaveQuarantineDescriptor", context1, quarantineDescriptor)}
1546+}
1547+
1548+func (_c *MockSnapshotRepository_SaveQuarantineDescriptor_Call) Run(run func(context1 context.Context, quarantineDescriptor domain.QuarantineDescriptor)) *MockSnapshotRepository_SaveQuarantineDescriptor_Call {
1549+ _c.Call.Run(func(args mock.Arguments) {
1550+ var arg0 context.Context
1551+ if args[0] != nil {
1552+ arg0 = args[0].(context.Context)
1553+ }
1554+ var arg1 domain.QuarantineDescriptor
1555+ if args[1] != nil {
1556+ arg1 = args[1].(domain.QuarantineDescriptor)
1557+ }
1558+ run(
1559+ arg0,
1560+ arg1,
1561+ )
1562+ })
1563+ return _c
1564+}
1565+
1566+func (_c *MockSnapshotRepository_SaveQuarantineDescriptor_Call) Return(err error) *MockSnapshotRepository_SaveQuarantineDescriptor_Call {
1567+ _c.Call.Return(err)
1568+ return _c
1569+}
1570+
1571+func (_c *MockSnapshotRepository_SaveQuarantineDescriptor_Call) RunAndReturn(run func(context1 context.Context, quarantineDescriptor domain.QuarantineDescriptor) error) *MockSnapshotRepository_SaveQuarantineDescriptor_Call {
1572+ _c.Call.Return(run)
1573+ return _c
1574+}
1575+
1576+// WriteDeletionTombstone provides a mock function for the type MockSnapshotRepository
1577+func (_mock *MockSnapshotRepository) WriteDeletionTombstone(context1 context.Context, deletionTombstone domain.DeletionTombstone) error {
1578+ ret := _mock.Called(context1, deletionTombstone)
1579+
1580+ if len(ret) == 0 {
1581+ panic("no return value specified for WriteDeletionTombstone")
1582+ }
1583+
1584+ var r0 error
1585+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.DeletionTombstone) error); ok {
1586+ r0 = returnFunc(context1, deletionTombstone)
1587+ } else {
1588+ r0 = ret.Error(0)
1589+ }
1590+ return r0
1591+}
1592+
1593+// MockSnapshotRepository_WriteDeletionTombstone_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteDeletionTombstone'
1594+type MockSnapshotRepository_WriteDeletionTombstone_Call struct {
1595+ *mock.Call
1596+}
1597+
1598+// WriteDeletionTombstone is a helper method to define mock.On call
1599+// - context1 context.Context
1600+// - deletionTombstone domain.DeletionTombstone
1601+func (_e *MockSnapshotRepository_Expecter) WriteDeletionTombstone(context1 any, deletionTombstone any) *MockSnapshotRepository_WriteDeletionTombstone_Call {
1602+ return &MockSnapshotRepository_WriteDeletionTombstone_Call{Call: _e.mock.On("WriteDeletionTombstone", context1, deletionTombstone)}
1603+}
1604+
1605+func (_c *MockSnapshotRepository_WriteDeletionTombstone_Call) Run(run func(context1 context.Context, deletionTombstone domain.DeletionTombstone)) *MockSnapshotRepository_WriteDeletionTombstone_Call {
1606+ _c.Call.Run(func(args mock.Arguments) {
1607+ var arg0 context.Context
1608+ if args[0] != nil {
1609+ arg0 = args[0].(context.Context)
1610+ }
1611+ var arg1 domain.DeletionTombstone
1612+ if args[1] != nil {
1613+ arg1 = args[1].(domain.DeletionTombstone)
1614+ }
1615+ run(
1616+ arg0,
1617+ arg1,
1618+ )
1619+ })
1620+ return _c
1621+}
1622+
1623+func (_c *MockSnapshotRepository_WriteDeletionTombstone_Call) Return(err error) *MockSnapshotRepository_WriteDeletionTombstone_Call {
1624+ _c.Call.Return(err)
1625+ return _c
1626+}
1627+
1628+func (_c *MockSnapshotRepository_WriteDeletionTombstone_Call) RunAndReturn(run func(context1 context.Context, deletionTombstone domain.DeletionTombstone) error) *MockSnapshotRepository_WriteDeletionTombstone_Call {
1629+ _c.Call.Return(run)
1630+ return _c
1631+}
@@ -0,0 +1,1631 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package portsmocks
6+
7+import (
8+ "context"
9+
10+ "github.com/bnema/vev/internal/domain"
11+ "github.com/bnema/vev/internal/ports"
12+ mock "github.com/stretchr/testify/mock"
13+)
14+
15+// NewMockSnapshotMigration creates a new instance of MockSnapshotMigration. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
16+// The first argument is typically a *testing.T value.
17+func NewMockSnapshotMigration(t interface {
18+ mock.TestingT
19+ Cleanup(func())
20+}) *MockSnapshotMigration {
21+ mock := &MockSnapshotMigration{}
22+ mock.Mock.Test(t)
23+
24+ t.Cleanup(func() { mock.AssertExpectations(t) })
25+
26+ return mock
27+}
28+
29+// MockSnapshotMigration is an autogenerated mock type for the SnapshotMigration type
30+type MockSnapshotMigration struct {
31+ mock.Mock
32+}
33+
34+type MockSnapshotMigration_Expecter struct {
35+ mock *mock.Mock
36+}
37+
38+func (_m *MockSnapshotMigration) EXPECT() *MockSnapshotMigration_Expecter {
39+ return &MockSnapshotMigration_Expecter{mock: &_m.Mock}
40+}
41+
42+// HasLegacyState provides a mock function for the type MockSnapshotMigration
43+func (_mock *MockSnapshotMigration) HasLegacyState(context1 context.Context) (bool, error) {
44+ ret := _mock.Called(context1)
45+
46+ if len(ret) == 0 {
47+ panic("no return value specified for HasLegacyState")
48+ }
49+
50+ var r0 bool
51+ var r1 error
52+ if returnFunc, ok := ret.Get(0).(func(context.Context) (bool, error)); ok {
53+ return returnFunc(context1)
54+ }
55+ if returnFunc, ok := ret.Get(0).(func(context.Context) bool); ok {
56+ r0 = returnFunc(context1)
57+ } else {
58+ r0 = ret.Get(0).(bool)
59+ }
60+ if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
61+ r1 = returnFunc(context1)
62+ } else {
63+ r1 = ret.Error(1)
64+ }
65+ return r0, r1
66+}
67+
68+// MockSnapshotMigration_HasLegacyState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HasLegacyState'
69+type MockSnapshotMigration_HasLegacyState_Call struct {
70+ *mock.Call
71+}
72+
73+// HasLegacyState is a helper method to define mock.On call
74+// - context1 context.Context
75+func (_e *MockSnapshotMigration_Expecter) HasLegacyState(context1 any) *MockSnapshotMigration_HasLegacyState_Call {
76+ return &MockSnapshotMigration_HasLegacyState_Call{Call: _e.mock.On("HasLegacyState", context1)}
77+}
78+
79+func (_c *MockSnapshotMigration_HasLegacyState_Call) Run(run func(context1 context.Context)) *MockSnapshotMigration_HasLegacyState_Call {
80+ _c.Call.Run(func(args mock.Arguments) {
81+ var arg0 context.Context
82+ if args[0] != nil {
83+ arg0 = args[0].(context.Context)
84+ }
85+ run(
86+ arg0,
87+ )
88+ })
89+ return _c
90+}
91+
92+func (_c *MockSnapshotMigration_HasLegacyState_Call) Return(b bool, err error) *MockSnapshotMigration_HasLegacyState_Call {
93+ _c.Call.Return(b, err)
94+ return _c
95+}
96+
97+func (_c *MockSnapshotMigration_HasLegacyState_Call) RunAndReturn(run func(context1 context.Context) (bool, error)) *MockSnapshotMigration_HasLegacyState_Call {
98+ _c.Call.Return(run)
99+ return _c
100+}
101+
102+// MigrateV1Checkpoint provides a mock function for the type MockSnapshotMigration
103+func (_mock *MockSnapshotMigration) MigrateV1Checkpoint(context1 context.Context, snapshotMigrationRequest ports.SnapshotMigrationRequest) (domain.CheckpointRef, error) {
104+ ret := _mock.Called(context1, snapshotMigrationRequest)
105+
106+ if len(ret) == 0 {
107+ panic("no return value specified for MigrateV1Checkpoint")
108+ }
109+
110+ var r0 domain.CheckpointRef
111+ var r1 error
112+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.SnapshotMigrationRequest) (domain.CheckpointRef, error)); ok {
113+ return returnFunc(context1, snapshotMigrationRequest)
114+ }
115+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.SnapshotMigrationRequest) domain.CheckpointRef); ok {
116+ r0 = returnFunc(context1, snapshotMigrationRequest)
117+ } else {
118+ r0 = ret.Get(0).(domain.CheckpointRef)
119+ }
120+ if returnFunc, ok := ret.Get(1).(func(context.Context, ports.SnapshotMigrationRequest) error); ok {
121+ r1 = returnFunc(context1, snapshotMigrationRequest)
122+ } else {
123+ r1 = ret.Error(1)
124+ }
125+ return r0, r1
126+}
127+
128+// MockSnapshotMigration_MigrateV1Checkpoint_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MigrateV1Checkpoint'
129+type MockSnapshotMigration_MigrateV1Checkpoint_Call struct {
130+ *mock.Call
131+}
132+
133+// MigrateV1Checkpoint is a helper method to define mock.On call
134+// - context1 context.Context
135+// - snapshotMigrationRequest ports.SnapshotMigrationRequest
136+func (_e *MockSnapshotMigration_Expecter) MigrateV1Checkpoint(context1 any, snapshotMigrationRequest any) *MockSnapshotMigration_MigrateV1Checkpoint_Call {
137+ return &MockSnapshotMigration_MigrateV1Checkpoint_Call{Call: _e.mock.On("MigrateV1Checkpoint", context1, snapshotMigrationRequest)}
138+}
139+
140+func (_c *MockSnapshotMigration_MigrateV1Checkpoint_Call) Run(run func(context1 context.Context, snapshotMigrationRequest ports.SnapshotMigrationRequest)) *MockSnapshotMigration_MigrateV1Checkpoint_Call {
141+ _c.Call.Run(func(args mock.Arguments) {
142+ var arg0 context.Context
143+ if args[0] != nil {
144+ arg0 = args[0].(context.Context)
145+ }
146+ var arg1 ports.SnapshotMigrationRequest
147+ if args[1] != nil {
148+ arg1 = args[1].(ports.SnapshotMigrationRequest)
149+ }
150+ run(
151+ arg0,
152+ arg1,
153+ )
154+ })
155+ return _c
156+}
157+
158+func (_c *MockSnapshotMigration_MigrateV1Checkpoint_Call) Return(checkpointRef domain.CheckpointRef, err error) *MockSnapshotMigration_MigrateV1Checkpoint_Call {
159+ _c.Call.Return(checkpointRef, err)
160+ return _c
161+}
162+
163+func (_c *MockSnapshotMigration_MigrateV1Checkpoint_Call) RunAndReturn(run func(context1 context.Context, snapshotMigrationRequest ports.SnapshotMigrationRequest) (domain.CheckpointRef, error)) *MockSnapshotMigration_MigrateV1Checkpoint_Call {
164+ _c.Call.Return(run)
165+ return _c
166+}
167+
168+// ReadLegacyHEAD provides a mock function for the type MockSnapshotMigration
169+func (_mock *MockSnapshotMigration) ReadLegacyHEAD(context1 context.Context, s string) (domain.CheckpointRef, error) {
170+ ret := _mock.Called(context1, s)
171+
172+ if len(ret) == 0 {
173+ panic("no return value specified for ReadLegacyHEAD")
174+ }
175+
176+ var r0 domain.CheckpointRef
177+ var r1 error
178+ if returnFunc, ok := ret.Get(0).(func(context.Context, string) (domain.CheckpointRef, error)); ok {
179+ return returnFunc(context1, s)
180+ }
181+ if returnFunc, ok := ret.Get(0).(func(context.Context, string) domain.CheckpointRef); ok {
182+ r0 = returnFunc(context1, s)
183+ } else {
184+ r0 = ret.Get(0).(domain.CheckpointRef)
185+ }
186+ if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok {
187+ r1 = returnFunc(context1, s)
188+ } else {
189+ r1 = ret.Error(1)
190+ }
191+ return r0, r1
192+}
193+
194+// MockSnapshotMigration_ReadLegacyHEAD_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadLegacyHEAD'
195+type MockSnapshotMigration_ReadLegacyHEAD_Call struct {
196+ *mock.Call
197+}
198+
199+// ReadLegacyHEAD is a helper method to define mock.On call
200+// - context1 context.Context
201+// - s string
202+func (_e *MockSnapshotMigration_Expecter) ReadLegacyHEAD(context1 any, s any) *MockSnapshotMigration_ReadLegacyHEAD_Call {
203+ return &MockSnapshotMigration_ReadLegacyHEAD_Call{Call: _e.mock.On("ReadLegacyHEAD", context1, s)}
204+}
205+
206+func (_c *MockSnapshotMigration_ReadLegacyHEAD_Call) Run(run func(context1 context.Context, s string)) *MockSnapshotMigration_ReadLegacyHEAD_Call {
207+ _c.Call.Run(func(args mock.Arguments) {
208+ var arg0 context.Context
209+ if args[0] != nil {
210+ arg0 = args[0].(context.Context)
211+ }
212+ var arg1 string
213+ if args[1] != nil {
214+ arg1 = args[1].(string)
215+ }
216+ run(
217+ arg0,
218+ arg1,
219+ )
220+ })
221+ return _c
222+}
223+
224+func (_c *MockSnapshotMigration_ReadLegacyHEAD_Call) Return(checkpointRef domain.CheckpointRef, err error) *MockSnapshotMigration_ReadLegacyHEAD_Call {
225+ _c.Call.Return(checkpointRef, err)
226+ return _c
227+}
228+
229+func (_c *MockSnapshotMigration_ReadLegacyHEAD_Call) RunAndReturn(run func(context1 context.Context, s string) (domain.CheckpointRef, error)) *MockSnapshotMigration_ReadLegacyHEAD_Call {
230+ _c.Call.Return(run)
231+ return _c
232+}
233+
234+// NewMockRecoveryJournal creates a new instance of MockRecoveryJournal. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
235+// The first argument is typically a *testing.T value.
236+func NewMockRecoveryJournal(t interface {
237+ mock.TestingT
238+ Cleanup(func())
239+}) *MockRecoveryJournal {
240+ mock := &MockRecoveryJournal{}
241+ mock.Mock.Test(t)
242+
243+ t.Cleanup(func() { mock.AssertExpectations(t) })
244+
245+ return mock
246+}
247+
248+// MockRecoveryJournal is an autogenerated mock type for the RecoveryJournal type
249+type MockRecoveryJournal struct {
250+ mock.Mock
251+}
252+
253+type MockRecoveryJournal_Expecter struct {
254+ mock *mock.Mock
255+}
256+
257+func (_m *MockRecoveryJournal) EXPECT() *MockRecoveryJournal_Expecter {
258+ return &MockRecoveryJournal_Expecter{mock: &_m.Mock}
259+}
260+
261+// DeleteDiscard provides a mock function for the type MockRecoveryJournal
262+func (_mock *MockRecoveryJournal) DeleteDiscard(context1 context.Context, incarnationID domain.IncarnationID) error {
263+ ret := _mock.Called(context1, incarnationID)
264+
265+ if len(ret) == 0 {
266+ panic("no return value specified for DeleteDiscard")
267+ }
268+
269+ var r0 error
270+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.IncarnationID) error); ok {
271+ r0 = returnFunc(context1, incarnationID)
272+ } else {
273+ r0 = ret.Error(0)
274+ }
275+ return r0
276+}
277+
278+// MockRecoveryJournal_DeleteDiscard_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteDiscard'
279+type MockRecoveryJournal_DeleteDiscard_Call struct {
280+ *mock.Call
281+}
282+
283+// DeleteDiscard is a helper method to define mock.On call
284+// - context1 context.Context
285+// - incarnationID domain.IncarnationID
286+func (_e *MockRecoveryJournal_Expecter) DeleteDiscard(context1 any, incarnationID any) *MockRecoveryJournal_DeleteDiscard_Call {
287+ return &MockRecoveryJournal_DeleteDiscard_Call{Call: _e.mock.On("DeleteDiscard", context1, incarnationID)}
288+}
289+
290+func (_c *MockRecoveryJournal_DeleteDiscard_Call) Run(run func(context1 context.Context, incarnationID domain.IncarnationID)) *MockRecoveryJournal_DeleteDiscard_Call {
291+ _c.Call.Run(func(args mock.Arguments) {
292+ var arg0 context.Context
293+ if args[0] != nil {
294+ arg0 = args[0].(context.Context)
295+ }
296+ var arg1 domain.IncarnationID
297+ if args[1] != nil {
298+ arg1 = args[1].(domain.IncarnationID)
299+ }
300+ run(
301+ arg0,
302+ arg1,
303+ )
304+ })
305+ return _c
306+}
307+
308+func (_c *MockRecoveryJournal_DeleteDiscard_Call) Return(err error) *MockRecoveryJournal_DeleteDiscard_Call {
309+ _c.Call.Return(err)
310+ return _c
311+}
312+
313+func (_c *MockRecoveryJournal_DeleteDiscard_Call) RunAndReturn(run func(context1 context.Context, incarnationID domain.IncarnationID) error) *MockRecoveryJournal_DeleteDiscard_Call {
314+ _c.Call.Return(run)
315+ return _c
316+}
317+
318+// ListDiscards provides a mock function for the type MockRecoveryJournal
319+func (_mock *MockRecoveryJournal) ListDiscards(context1 context.Context) ([]domain.DiscardIntent, error) {
320+ ret := _mock.Called(context1)
321+
322+ if len(ret) == 0 {
323+ panic("no return value specified for ListDiscards")
324+ }
325+
326+ var r0 []domain.DiscardIntent
327+ var r1 error
328+ if returnFunc, ok := ret.Get(0).(func(context.Context) ([]domain.DiscardIntent, error)); ok {
329+ return returnFunc(context1)
330+ }
331+ if returnFunc, ok := ret.Get(0).(func(context.Context) []domain.DiscardIntent); ok {
332+ r0 = returnFunc(context1)
333+ } else {
334+ if ret.Get(0) != nil {
335+ r0 = ret.Get(0).([]domain.DiscardIntent)
336+ }
337+ }
338+ if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
339+ r1 = returnFunc(context1)
340+ } else {
341+ r1 = ret.Error(1)
342+ }
343+ return r0, r1
344+}
345+
346+// MockRecoveryJournal_ListDiscards_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListDiscards'
347+type MockRecoveryJournal_ListDiscards_Call struct {
348+ *mock.Call
349+}
350+
351+// ListDiscards is a helper method to define mock.On call
352+// - context1 context.Context
353+func (_e *MockRecoveryJournal_Expecter) ListDiscards(context1 any) *MockRecoveryJournal_ListDiscards_Call {
354+ return &MockRecoveryJournal_ListDiscards_Call{Call: _e.mock.On("ListDiscards", context1)}
355+}
356+
357+func (_c *MockRecoveryJournal_ListDiscards_Call) Run(run func(context1 context.Context)) *MockRecoveryJournal_ListDiscards_Call {
358+ _c.Call.Run(func(args mock.Arguments) {
359+ var arg0 context.Context
360+ if args[0] != nil {
361+ arg0 = args[0].(context.Context)
362+ }
363+ run(
364+ arg0,
365+ )
366+ })
367+ return _c
368+}
369+
370+func (_c *MockRecoveryJournal_ListDiscards_Call) Return(discardIntents []domain.DiscardIntent, err error) *MockRecoveryJournal_ListDiscards_Call {
371+ _c.Call.Return(discardIntents, err)
372+ return _c
373+}
374+
375+func (_c *MockRecoveryJournal_ListDiscards_Call) RunAndReturn(run func(context1 context.Context) ([]domain.DiscardIntent, error)) *MockRecoveryJournal_ListDiscards_Call {
376+ _c.Call.Return(run)
377+ return _c
378+}
379+
380+// SaveDiscard provides a mock function for the type MockRecoveryJournal
381+func (_mock *MockRecoveryJournal) SaveDiscard(context1 context.Context, discardIntent domain.DiscardIntent) error {
382+ ret := _mock.Called(context1, discardIntent)
383+
384+ if len(ret) == 0 {
385+ panic("no return value specified for SaveDiscard")
386+ }
387+
388+ var r0 error
389+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.DiscardIntent) error); ok {
390+ r0 = returnFunc(context1, discardIntent)
391+ } else {
392+ r0 = ret.Error(0)
393+ }
394+ return r0
395+}
396+
397+// MockRecoveryJournal_SaveDiscard_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SaveDiscard'
398+type MockRecoveryJournal_SaveDiscard_Call struct {
399+ *mock.Call
400+}
401+
402+// SaveDiscard is a helper method to define mock.On call
403+// - context1 context.Context
404+// - discardIntent domain.DiscardIntent
405+func (_e *MockRecoveryJournal_Expecter) SaveDiscard(context1 any, discardIntent any) *MockRecoveryJournal_SaveDiscard_Call {
406+ return &MockRecoveryJournal_SaveDiscard_Call{Call: _e.mock.On("SaveDiscard", context1, discardIntent)}
407+}
408+
409+func (_c *MockRecoveryJournal_SaveDiscard_Call) Run(run func(context1 context.Context, discardIntent domain.DiscardIntent)) *MockRecoveryJournal_SaveDiscard_Call {
410+ _c.Call.Run(func(args mock.Arguments) {
411+ var arg0 context.Context
412+ if args[0] != nil {
413+ arg0 = args[0].(context.Context)
414+ }
415+ var arg1 domain.DiscardIntent
416+ if args[1] != nil {
417+ arg1 = args[1].(domain.DiscardIntent)
418+ }
419+ run(
420+ arg0,
421+ arg1,
422+ )
423+ })
424+ return _c
425+}
426+
427+func (_c *MockRecoveryJournal_SaveDiscard_Call) Return(err error) *MockRecoveryJournal_SaveDiscard_Call {
428+ _c.Call.Return(err)
429+ return _c
430+}
431+
432+func (_c *MockRecoveryJournal_SaveDiscard_Call) RunAndReturn(run func(context1 context.Context, discardIntent domain.DiscardIntent) error) *MockRecoveryJournal_SaveDiscard_Call {
433+ _c.Call.Return(run)
434+ return _c
435+}
436+
437+// NewMockCatalogue creates a new instance of MockCatalogue. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
438+// The first argument is typically a *testing.T value.
439+func NewMockCatalogue(t interface {
440+ mock.TestingT
441+ Cleanup(func())
442+}) *MockCatalogue {
443+ mock := &MockCatalogue{}
444+ mock.Mock.Test(t)
445+
446+ t.Cleanup(func() { mock.AssertExpectations(t) })
447+
448+ return mock
449+}
450+
451+// MockCatalogue is an autogenerated mock type for the Catalogue type
452+type MockCatalogue struct {
453+ mock.Mock
454+}
455+
456+type MockCatalogue_Expecter struct {
457+ mock *mock.Mock
458+}
459+
460+func (_m *MockCatalogue) EXPECT() *MockCatalogue_Expecter {
461+ return &MockCatalogue_Expecter{mock: &_m.Mock}
462+}
463+
464+// Close provides a mock function for the type MockCatalogue
465+func (_mock *MockCatalogue) Close() error {
466+ ret := _mock.Called()
467+
468+ if len(ret) == 0 {
469+ panic("no return value specified for Close")
470+ }
471+
472+ var r0 error
473+ if returnFunc, ok := ret.Get(0).(func() error); ok {
474+ r0 = returnFunc()
475+ } else {
476+ r0 = ret.Error(0)
477+ }
478+ return r0
479+}
480+
481+// MockCatalogue_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
482+type MockCatalogue_Close_Call struct {
483+ *mock.Call
484+}
485+
486+// Close is a helper method to define mock.On call
487+func (_e *MockCatalogue_Expecter) Close() *MockCatalogue_Close_Call {
488+ return &MockCatalogue_Close_Call{Call: _e.mock.On("Close")}
489+}
490+
491+func (_c *MockCatalogue_Close_Call) Run(run func()) *MockCatalogue_Close_Call {
492+ _c.Call.Run(func(args mock.Arguments) {
493+ run()
494+ })
495+ return _c
496+}
497+
498+func (_c *MockCatalogue_Close_Call) Return(err error) *MockCatalogue_Close_Call {
499+ _c.Call.Return(err)
500+ return _c
501+}
502+
503+func (_c *MockCatalogue_Close_Call) RunAndReturn(run func() error) *MockCatalogue_Close_Call {
504+ _c.Call.Return(run)
505+ return _c
506+}
507+
508+// Create provides a mock function for the type MockCatalogue
509+func (_mock *MockCatalogue) Create(catalogueRecord domain.CatalogueRecord) error {
510+ ret := _mock.Called(catalogueRecord)
511+
512+ if len(ret) == 0 {
513+ panic("no return value specified for Create")
514+ }
515+
516+ var r0 error
517+ if returnFunc, ok := ret.Get(0).(func(domain.CatalogueRecord) error); ok {
518+ r0 = returnFunc(catalogueRecord)
519+ } else {
520+ r0 = ret.Error(0)
521+ }
522+ return r0
523+}
524+
525+// MockCatalogue_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
526+type MockCatalogue_Create_Call struct {
527+ *mock.Call
528+}
529+
530+// Create is a helper method to define mock.On call
531+// - catalogueRecord domain.CatalogueRecord
532+func (_e *MockCatalogue_Expecter) Create(catalogueRecord any) *MockCatalogue_Create_Call {
533+ return &MockCatalogue_Create_Call{Call: _e.mock.On("Create", catalogueRecord)}
534+}
535+
536+func (_c *MockCatalogue_Create_Call) Run(run func(catalogueRecord domain.CatalogueRecord)) *MockCatalogue_Create_Call {
537+ _c.Call.Run(func(args mock.Arguments) {
538+ var arg0 domain.CatalogueRecord
539+ if args[0] != nil {
540+ arg0 = args[0].(domain.CatalogueRecord)
541+ }
542+ run(
543+ arg0,
544+ )
545+ })
546+ return _c
547+}
548+
549+func (_c *MockCatalogue_Create_Call) Return(err error) *MockCatalogue_Create_Call {
550+ _c.Call.Return(err)
551+ return _c
552+}
553+
554+func (_c *MockCatalogue_Create_Call) RunAndReturn(run func(catalogueRecord domain.CatalogueRecord) error) *MockCatalogue_Create_Call {
555+ _c.Call.Return(run)
556+ return _c
557+}
558+
559+// Delete provides a mock function for the type MockCatalogue
560+func (_mock *MockCatalogue) Delete(s string) error {
561+ ret := _mock.Called(s)
562+
563+ if len(ret) == 0 {
564+ panic("no return value specified for Delete")
565+ }
566+
567+ var r0 error
568+ if returnFunc, ok := ret.Get(0).(func(string) error); ok {
569+ r0 = returnFunc(s)
570+ } else {
571+ r0 = ret.Error(0)
572+ }
573+ return r0
574+}
575+
576+// MockCatalogue_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
577+type MockCatalogue_Delete_Call struct {
578+ *mock.Call
579+}
580+
581+// Delete is a helper method to define mock.On call
582+// - s string
583+func (_e *MockCatalogue_Expecter) Delete(s any) *MockCatalogue_Delete_Call {
584+ return &MockCatalogue_Delete_Call{Call: _e.mock.On("Delete", s)}
585+}
586+
587+func (_c *MockCatalogue_Delete_Call) Run(run func(s string)) *MockCatalogue_Delete_Call {
588+ _c.Call.Run(func(args mock.Arguments) {
589+ var arg0 string
590+ if args[0] != nil {
591+ arg0 = args[0].(string)
592+ }
593+ run(
594+ arg0,
595+ )
596+ })
597+ return _c
598+}
599+
600+func (_c *MockCatalogue_Delete_Call) Return(err error) *MockCatalogue_Delete_Call {
601+ _c.Call.Return(err)
602+ return _c
603+}
604+
605+func (_c *MockCatalogue_Delete_Call) RunAndReturn(run func(s string) error) *MockCatalogue_Delete_Call {
606+ _c.Call.Return(run)
607+ return _c
608+}
609+
610+// Record provides a mock function for the type MockCatalogue
611+func (_mock *MockCatalogue) Record(s string) (domain.CatalogueRecord, bool) {
612+ ret := _mock.Called(s)
613+
614+ if len(ret) == 0 {
615+ panic("no return value specified for Record")
616+ }
617+
618+ var r0 domain.CatalogueRecord
619+ var r1 bool
620+ if returnFunc, ok := ret.Get(0).(func(string) (domain.CatalogueRecord, bool)); ok {
621+ return returnFunc(s)
622+ }
623+ if returnFunc, ok := ret.Get(0).(func(string) domain.CatalogueRecord); ok {
624+ r0 = returnFunc(s)
625+ } else {
626+ r0 = ret.Get(0).(domain.CatalogueRecord)
627+ }
628+ if returnFunc, ok := ret.Get(1).(func(string) bool); ok {
629+ r1 = returnFunc(s)
630+ } else {
631+ r1 = ret.Get(1).(bool)
632+ }
633+ return r0, r1
634+}
635+
636+// MockCatalogue_Record_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Record'
637+type MockCatalogue_Record_Call struct {
638+ *mock.Call
639+}
640+
641+// Record is a helper method to define mock.On call
642+// - s string
643+func (_e *MockCatalogue_Expecter) Record(s any) *MockCatalogue_Record_Call {
644+ return &MockCatalogue_Record_Call{Call: _e.mock.On("Record", s)}
645+}
646+
647+func (_c *MockCatalogue_Record_Call) Run(run func(s string)) *MockCatalogue_Record_Call {
648+ _c.Call.Run(func(args mock.Arguments) {
649+ var arg0 string
650+ if args[0] != nil {
651+ arg0 = args[0].(string)
652+ }
653+ run(
654+ arg0,
655+ )
656+ })
657+ return _c
658+}
659+
660+func (_c *MockCatalogue_Record_Call) Return(catalogueRecord domain.CatalogueRecord, b bool) *MockCatalogue_Record_Call {
661+ _c.Call.Return(catalogueRecord, b)
662+ return _c
663+}
664+
665+func (_c *MockCatalogue_Record_Call) RunAndReturn(run func(s string) (domain.CatalogueRecord, bool)) *MockCatalogue_Record_Call {
666+ _c.Call.Return(run)
667+ return _c
668+}
669+
670+// Records provides a mock function for the type MockCatalogue
671+func (_mock *MockCatalogue) Records() []domain.CatalogueRecord {
672+ ret := _mock.Called()
673+
674+ if len(ret) == 0 {
675+ panic("no return value specified for Records")
676+ }
677+
678+ var r0 []domain.CatalogueRecord
679+ if returnFunc, ok := ret.Get(0).(func() []domain.CatalogueRecord); ok {
680+ r0 = returnFunc()
681+ } else {
682+ if ret.Get(0) != nil {
683+ r0 = ret.Get(0).([]domain.CatalogueRecord)
684+ }
685+ }
686+ return r0
687+}
688+
689+// MockCatalogue_Records_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Records'
690+type MockCatalogue_Records_Call struct {
691+ *mock.Call
692+}
693+
694+// Records is a helper method to define mock.On call
695+func (_e *MockCatalogue_Expecter) Records() *MockCatalogue_Records_Call {
696+ return &MockCatalogue_Records_Call{Call: _e.mock.On("Records")}
697+}
698+
699+func (_c *MockCatalogue_Records_Call) Run(run func()) *MockCatalogue_Records_Call {
700+ _c.Call.Run(func(args mock.Arguments) {
701+ run()
702+ })
703+ return _c
704+}
705+
706+func (_c *MockCatalogue_Records_Call) Return(catalogueRecords []domain.CatalogueRecord) *MockCatalogue_Records_Call {
707+ _c.Call.Return(catalogueRecords)
708+ return _c
709+}
710+
711+func (_c *MockCatalogue_Records_Call) RunAndReturn(run func() []domain.CatalogueRecord) *MockCatalogue_Records_Call {
712+ _c.Call.Return(run)
713+ return _c
714+}
715+
716+// Rename provides a mock function for the type MockCatalogue
717+func (_mock *MockCatalogue) Rename(s string, catalogueRecord domain.CatalogueRecord) error {
718+ ret := _mock.Called(s, catalogueRecord)
719+
720+ if len(ret) == 0 {
721+ panic("no return value specified for Rename")
722+ }
723+
724+ var r0 error
725+ if returnFunc, ok := ret.Get(0).(func(string, domain.CatalogueRecord) error); ok {
726+ r0 = returnFunc(s, catalogueRecord)
727+ } else {
728+ r0 = ret.Error(0)
729+ }
730+ return r0
731+}
732+
733+// MockCatalogue_Rename_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Rename'
734+type MockCatalogue_Rename_Call struct {
735+ *mock.Call
736+}
737+
738+// Rename is a helper method to define mock.On call
739+// - s string
740+// - catalogueRecord domain.CatalogueRecord
741+func (_e *MockCatalogue_Expecter) Rename(s any, catalogueRecord any) *MockCatalogue_Rename_Call {
742+ return &MockCatalogue_Rename_Call{Call: _e.mock.On("Rename", s, catalogueRecord)}
743+}
744+
745+func (_c *MockCatalogue_Rename_Call) Run(run func(s string, catalogueRecord domain.CatalogueRecord)) *MockCatalogue_Rename_Call {
746+ _c.Call.Run(func(args mock.Arguments) {
747+ var arg0 string
748+ if args[0] != nil {
749+ arg0 = args[0].(string)
750+ }
751+ var arg1 domain.CatalogueRecord
752+ if args[1] != nil {
753+ arg1 = args[1].(domain.CatalogueRecord)
754+ }
755+ run(
756+ arg0,
757+ arg1,
758+ )
759+ })
760+ return _c
761+}
762+
763+func (_c *MockCatalogue_Rename_Call) Return(err error) *MockCatalogue_Rename_Call {
764+ _c.Call.Return(err)
765+ return _c
766+}
767+
768+func (_c *MockCatalogue_Rename_Call) RunAndReturn(run func(s string, catalogueRecord domain.CatalogueRecord) error) *MockCatalogue_Rename_Call {
769+ _c.Call.Return(run)
770+ return _c
771+}
772+
773+// Replace provides a mock function for the type MockCatalogue
774+func (_mock *MockCatalogue) Replace(s string, catalogueRecord domain.CatalogueRecord) error {
775+ ret := _mock.Called(s, catalogueRecord)
776+
777+ if len(ret) == 0 {
778+ panic("no return value specified for Replace")
779+ }
780+
781+ var r0 error
782+ if returnFunc, ok := ret.Get(0).(func(string, domain.CatalogueRecord) error); ok {
783+ r0 = returnFunc(s, catalogueRecord)
784+ } else {
785+ r0 = ret.Error(0)
786+ }
787+ return r0
788+}
789+
790+// MockCatalogue_Replace_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Replace'
791+type MockCatalogue_Replace_Call struct {
792+ *mock.Call
793+}
794+
795+// Replace is a helper method to define mock.On call
796+// - s string
797+// - catalogueRecord domain.CatalogueRecord
798+func (_e *MockCatalogue_Expecter) Replace(s any, catalogueRecord any) *MockCatalogue_Replace_Call {
799+ return &MockCatalogue_Replace_Call{Call: _e.mock.On("Replace", s, catalogueRecord)}
800+}
801+
802+func (_c *MockCatalogue_Replace_Call) Run(run func(s string, catalogueRecord domain.CatalogueRecord)) *MockCatalogue_Replace_Call {
803+ _c.Call.Run(func(args mock.Arguments) {
804+ var arg0 string
805+ if args[0] != nil {
806+ arg0 = args[0].(string)
807+ }
808+ var arg1 domain.CatalogueRecord
809+ if args[1] != nil {
810+ arg1 = args[1].(domain.CatalogueRecord)
811+ }
812+ run(
813+ arg0,
814+ arg1,
815+ )
816+ })
817+ return _c
818+}
819+
820+func (_c *MockCatalogue_Replace_Call) Return(err error) *MockCatalogue_Replace_Call {
821+ _c.Call.Return(err)
822+ return _c
823+}
824+
825+func (_c *MockCatalogue_Replace_Call) RunAndReturn(run func(s string, catalogueRecord domain.CatalogueRecord) error) *MockCatalogue_Replace_Call {
826+ _c.Call.Return(run)
827+ return _c
828+}
829+
830+// NewMockSnapshotRepository creates a new instance of MockSnapshotRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
831+// The first argument is typically a *testing.T value.
832+func NewMockSnapshotRepository(t interface {
833+ mock.TestingT
834+ Cleanup(func())
835+}) *MockSnapshotRepository {
836+ mock := &MockSnapshotRepository{}
837+ mock.Mock.Test(t)
838+
839+ t.Cleanup(func() { mock.AssertExpectations(t) })
840+
841+ return mock
842+}
843+
844+// MockSnapshotRepository is an autogenerated mock type for the SnapshotRepository type
845+type MockSnapshotRepository struct {
846+ mock.Mock
847+}
848+
849+type MockSnapshotRepository_Expecter struct {
850+ mock *mock.Mock
851+}
852+
853+func (_m *MockSnapshotRepository) EXPECT() *MockSnapshotRepository_Expecter {
854+ return &MockSnapshotRepository_Expecter{mock: &_m.Mock}
855+}
856+
857+// DeleteDeletionTombstone provides a mock function for the type MockSnapshotRepository
858+func (_mock *MockSnapshotRepository) DeleteDeletionTombstone(context1 context.Context, incarnationID domain.IncarnationID) error {
859+ ret := _mock.Called(context1, incarnationID)
860+
861+ if len(ret) == 0 {
862+ panic("no return value specified for DeleteDeletionTombstone")
863+ }
864+
865+ var r0 error
866+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.IncarnationID) error); ok {
867+ r0 = returnFunc(context1, incarnationID)
868+ } else {
869+ r0 = ret.Error(0)
870+ }
871+ return r0
872+}
873+
874+// MockSnapshotRepository_DeleteDeletionTombstone_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteDeletionTombstone'
875+type MockSnapshotRepository_DeleteDeletionTombstone_Call struct {
876+ *mock.Call
877+}
878+
879+// DeleteDeletionTombstone is a helper method to define mock.On call
880+// - context1 context.Context
881+// - incarnationID domain.IncarnationID
882+func (_e *MockSnapshotRepository_Expecter) DeleteDeletionTombstone(context1 any, incarnationID any) *MockSnapshotRepository_DeleteDeletionTombstone_Call {
883+ return &MockSnapshotRepository_DeleteDeletionTombstone_Call{Call: _e.mock.On("DeleteDeletionTombstone", context1, incarnationID)}
884+}
885+
886+func (_c *MockSnapshotRepository_DeleteDeletionTombstone_Call) Run(run func(context1 context.Context, incarnationID domain.IncarnationID)) *MockSnapshotRepository_DeleteDeletionTombstone_Call {
887+ _c.Call.Run(func(args mock.Arguments) {
888+ var arg0 context.Context
889+ if args[0] != nil {
890+ arg0 = args[0].(context.Context)
891+ }
892+ var arg1 domain.IncarnationID
893+ if args[1] != nil {
894+ arg1 = args[1].(domain.IncarnationID)
895+ }
896+ run(
897+ arg0,
898+ arg1,
899+ )
900+ })
901+ return _c
902+}
903+
904+func (_c *MockSnapshotRepository_DeleteDeletionTombstone_Call) Return(err error) *MockSnapshotRepository_DeleteDeletionTombstone_Call {
905+ _c.Call.Return(err)
906+ return _c
907+}
908+
909+func (_c *MockSnapshotRepository_DeleteDeletionTombstone_Call) RunAndReturn(run func(context1 context.Context, incarnationID domain.IncarnationID) error) *MockSnapshotRepository_DeleteDeletionTombstone_Call {
910+ _c.Call.Return(run)
911+ return _c
912+}
913+
914+// DeleteIncarnation provides a mock function for the type MockSnapshotRepository
915+func (_mock *MockSnapshotRepository) DeleteIncarnation(context1 context.Context, incarnationID domain.IncarnationID) error {
916+ ret := _mock.Called(context1, incarnationID)
917+
918+ if len(ret) == 0 {
919+ panic("no return value specified for DeleteIncarnation")
920+ }
921+
922+ var r0 error
923+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.IncarnationID) error); ok {
924+ r0 = returnFunc(context1, incarnationID)
925+ } else {
926+ r0 = ret.Error(0)
927+ }
928+ return r0
929+}
930+
931+// MockSnapshotRepository_DeleteIncarnation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteIncarnation'
932+type MockSnapshotRepository_DeleteIncarnation_Call struct {
933+ *mock.Call
934+}
935+
936+// DeleteIncarnation is a helper method to define mock.On call
937+// - context1 context.Context
938+// - incarnationID domain.IncarnationID
939+func (_e *MockSnapshotRepository_Expecter) DeleteIncarnation(context1 any, incarnationID any) *MockSnapshotRepository_DeleteIncarnation_Call {
940+ return &MockSnapshotRepository_DeleteIncarnation_Call{Call: _e.mock.On("DeleteIncarnation", context1, incarnationID)}
941+}
942+
943+func (_c *MockSnapshotRepository_DeleteIncarnation_Call) Run(run func(context1 context.Context, incarnationID domain.IncarnationID)) *MockSnapshotRepository_DeleteIncarnation_Call {
944+ _c.Call.Run(func(args mock.Arguments) {
945+ var arg0 context.Context
946+ if args[0] != nil {
947+ arg0 = args[0].(context.Context)
948+ }
949+ var arg1 domain.IncarnationID
950+ if args[1] != nil {
951+ arg1 = args[1].(domain.IncarnationID)
952+ }
953+ run(
954+ arg0,
955+ arg1,
956+ )
957+ })
958+ return _c
959+}
960+
961+func (_c *MockSnapshotRepository_DeleteIncarnation_Call) Return(err error) *MockSnapshotRepository_DeleteIncarnation_Call {
962+ _c.Call.Return(err)
963+ return _c
964+}
965+
966+func (_c *MockSnapshotRepository_DeleteIncarnation_Call) RunAndReturn(run func(context1 context.Context, incarnationID domain.IncarnationID) error) *MockSnapshotRepository_DeleteIncarnation_Call {
967+ _c.Call.Return(run)
968+ return _c
969+}
970+
971+// ListDeletionTombstones provides a mock function for the type MockSnapshotRepository
972+func (_mock *MockSnapshotRepository) ListDeletionTombstones(context1 context.Context, deletionTombstoneCursor ports.DeletionTombstoneCursor, maintenanceBudget ports.MaintenanceBudget) (ports.DeletionTombstonePage, error) {
973+ ret := _mock.Called(context1, deletionTombstoneCursor, maintenanceBudget)
974+
975+ if len(ret) == 0 {
976+ panic("no return value specified for ListDeletionTombstones")
977+ }
978+
979+ var r0 ports.DeletionTombstonePage
980+ var r1 error
981+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.DeletionTombstoneCursor, ports.MaintenanceBudget) (ports.DeletionTombstonePage, error)); ok {
982+ return returnFunc(context1, deletionTombstoneCursor, maintenanceBudget)
983+ }
984+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.DeletionTombstoneCursor, ports.MaintenanceBudget) ports.DeletionTombstonePage); ok {
985+ r0 = returnFunc(context1, deletionTombstoneCursor, maintenanceBudget)
986+ } else {
987+ r0 = ret.Get(0).(ports.DeletionTombstonePage)
988+ }
989+ if returnFunc, ok := ret.Get(1).(func(context.Context, ports.DeletionTombstoneCursor, ports.MaintenanceBudget) error); ok {
990+ r1 = returnFunc(context1, deletionTombstoneCursor, maintenanceBudget)
991+ } else {
992+ r1 = ret.Error(1)
993+ }
994+ return r0, r1
995+}
996+
997+// MockSnapshotRepository_ListDeletionTombstones_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListDeletionTombstones'
998+type MockSnapshotRepository_ListDeletionTombstones_Call struct {
999+ *mock.Call
1000+}
1001+
1002+// ListDeletionTombstones is a helper method to define mock.On call
1003+// - context1 context.Context
1004+// - deletionTombstoneCursor ports.DeletionTombstoneCursor
1005+// - maintenanceBudget ports.MaintenanceBudget
1006+func (_e *MockSnapshotRepository_Expecter) ListDeletionTombstones(context1 any, deletionTombstoneCursor any, maintenanceBudget any) *MockSnapshotRepository_ListDeletionTombstones_Call {
1007+ return &MockSnapshotRepository_ListDeletionTombstones_Call{Call: _e.mock.On("ListDeletionTombstones", context1, deletionTombstoneCursor, maintenanceBudget)}
1008+}
1009+
1010+func (_c *MockSnapshotRepository_ListDeletionTombstones_Call) Run(run func(context1 context.Context, deletionTombstoneCursor ports.DeletionTombstoneCursor, maintenanceBudget ports.MaintenanceBudget)) *MockSnapshotRepository_ListDeletionTombstones_Call {
1011+ _c.Call.Run(func(args mock.Arguments) {
1012+ var arg0 context.Context
1013+ if args[0] != nil {
1014+ arg0 = args[0].(context.Context)
1015+ }
1016+ var arg1 ports.DeletionTombstoneCursor
1017+ if args[1] != nil {
1018+ arg1 = args[1].(ports.DeletionTombstoneCursor)
1019+ }
1020+ var arg2 ports.MaintenanceBudget
1021+ if args[2] != nil {
1022+ arg2 = args[2].(ports.MaintenanceBudget)
1023+ }
1024+ run(
1025+ arg0,
1026+ arg1,
1027+ arg2,
1028+ )
1029+ })
1030+ return _c
1031+}
1032+
1033+func (_c *MockSnapshotRepository_ListDeletionTombstones_Call) Return(deletionTombstonePage ports.DeletionTombstonePage, err error) *MockSnapshotRepository_ListDeletionTombstones_Call {
1034+ _c.Call.Return(deletionTombstonePage, err)
1035+ return _c
1036+}
1037+
1038+func (_c *MockSnapshotRepository_ListDeletionTombstones_Call) RunAndReturn(run func(context1 context.Context, deletionTombstoneCursor ports.DeletionTombstoneCursor, maintenanceBudget ports.MaintenanceBudget) (ports.DeletionTombstonePage, error)) *MockSnapshotRepository_ListDeletionTombstones_Call {
1039+ _c.Call.Return(run)
1040+ return _c
1041+}
1042+
1043+// LoadCheckpoint provides a mock function for the type MockSnapshotRepository
1044+func (_mock *MockSnapshotRepository) LoadCheckpoint(context1 context.Context, incarnationID domain.IncarnationID, s string, v ports.CheckpointRef) (ports.SnapshotGeneration, error) {
1045+ ret := _mock.Called(context1, incarnationID, s, v)
1046+
1047+ if len(ret) == 0 {
1048+ panic("no return value specified for LoadCheckpoint")
1049+ }
1050+
1051+ var r0 ports.SnapshotGeneration
1052+ var r1 error
1053+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.IncarnationID, string, ports.CheckpointRef) (ports.SnapshotGeneration, error)); ok {
1054+ return returnFunc(context1, incarnationID, s, v)
1055+ }
1056+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.IncarnationID, string, ports.CheckpointRef) ports.SnapshotGeneration); ok {
1057+ r0 = returnFunc(context1, incarnationID, s, v)
1058+ } else {
1059+ r0 = ret.Get(0).(ports.SnapshotGeneration)
1060+ }
1061+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.IncarnationID, string, ports.CheckpointRef) error); ok {
1062+ r1 = returnFunc(context1, incarnationID, s, v)
1063+ } else {
1064+ r1 = ret.Error(1)
1065+ }
1066+ return r0, r1
1067+}
1068+
1069+// MockSnapshotRepository_LoadCheckpoint_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LoadCheckpoint'
1070+type MockSnapshotRepository_LoadCheckpoint_Call struct {
1071+ *mock.Call
1072+}
1073+
1074+// LoadCheckpoint is a helper method to define mock.On call
1075+// - context1 context.Context
1076+// - incarnationID domain.IncarnationID
1077+// - s string
1078+// - v ports.CheckpointRef
1079+func (_e *MockSnapshotRepository_Expecter) LoadCheckpoint(context1 any, incarnationID any, s any, v any) *MockSnapshotRepository_LoadCheckpoint_Call {
1080+ return &MockSnapshotRepository_LoadCheckpoint_Call{Call: _e.mock.On("LoadCheckpoint", context1, incarnationID, s, v)}
1081+}
1082+
1083+func (_c *MockSnapshotRepository_LoadCheckpoint_Call) Run(run func(context1 context.Context, incarnationID domain.IncarnationID, s string, v ports.CheckpointRef)) *MockSnapshotRepository_LoadCheckpoint_Call {
1084+ _c.Call.Run(func(args mock.Arguments) {
1085+ var arg0 context.Context
1086+ if args[0] != nil {
1087+ arg0 = args[0].(context.Context)
1088+ }
1089+ var arg1 domain.IncarnationID
1090+ if args[1] != nil {
1091+ arg1 = args[1].(domain.IncarnationID)
1092+ }
1093+ var arg2 string
1094+ if args[2] != nil {
1095+ arg2 = args[2].(string)
1096+ }
1097+ var arg3 ports.CheckpointRef
1098+ if args[3] != nil {
1099+ arg3 = args[3].(ports.CheckpointRef)
1100+ }
1101+ run(
1102+ arg0,
1103+ arg1,
1104+ arg2,
1105+ arg3,
1106+ )
1107+ })
1108+ return _c
1109+}
1110+
1111+func (_c *MockSnapshotRepository_LoadCheckpoint_Call) Return(snapshotGeneration ports.SnapshotGeneration, err error) *MockSnapshotRepository_LoadCheckpoint_Call {
1112+ _c.Call.Return(snapshotGeneration, err)
1113+ return _c
1114+}
1115+
1116+func (_c *MockSnapshotRepository_LoadCheckpoint_Call) RunAndReturn(run func(context1 context.Context, incarnationID domain.IncarnationID, s string, v ports.CheckpointRef) (ports.SnapshotGeneration, error)) *MockSnapshotRepository_LoadCheckpoint_Call {
1117+ _c.Call.Return(run)
1118+ return _c
1119+}
1120+
1121+// MaintainSession provides a mock function for the type MockSnapshotRepository
1122+func (_mock *MockSnapshotRepository) MaintainSession(context1 context.Context, retentionPlan ports.RetentionPlan, maintenanceBudget ports.MaintenanceBudget) (bool, error) {
1123+ ret := _mock.Called(context1, retentionPlan, maintenanceBudget)
1124+
1125+ if len(ret) == 0 {
1126+ panic("no return value specified for MaintainSession")
1127+ }
1128+
1129+ var r0 bool
1130+ var r1 error
1131+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.RetentionPlan, ports.MaintenanceBudget) (bool, error)); ok {
1132+ return returnFunc(context1, retentionPlan, maintenanceBudget)
1133+ }
1134+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.RetentionPlan, ports.MaintenanceBudget) bool); ok {
1135+ r0 = returnFunc(context1, retentionPlan, maintenanceBudget)
1136+ } else {
1137+ r0 = ret.Get(0).(bool)
1138+ }
1139+ if returnFunc, ok := ret.Get(1).(func(context.Context, ports.RetentionPlan, ports.MaintenanceBudget) error); ok {
1140+ r1 = returnFunc(context1, retentionPlan, maintenanceBudget)
1141+ } else {
1142+ r1 = ret.Error(1)
1143+ }
1144+ return r0, r1
1145+}
1146+
1147+// MockSnapshotRepository_MaintainSession_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MaintainSession'
1148+type MockSnapshotRepository_MaintainSession_Call struct {
1149+ *mock.Call
1150+}
1151+
1152+// MaintainSession is a helper method to define mock.On call
1153+// - context1 context.Context
1154+// - retentionPlan ports.RetentionPlan
1155+// - maintenanceBudget ports.MaintenanceBudget
1156+func (_e *MockSnapshotRepository_Expecter) MaintainSession(context1 any, retentionPlan any, maintenanceBudget any) *MockSnapshotRepository_MaintainSession_Call {
1157+ return &MockSnapshotRepository_MaintainSession_Call{Call: _e.mock.On("MaintainSession", context1, retentionPlan, maintenanceBudget)}
1158+}
1159+
1160+func (_c *MockSnapshotRepository_MaintainSession_Call) Run(run func(context1 context.Context, retentionPlan ports.RetentionPlan, maintenanceBudget ports.MaintenanceBudget)) *MockSnapshotRepository_MaintainSession_Call {
1161+ _c.Call.Run(func(args mock.Arguments) {
1162+ var arg0 context.Context
1163+ if args[0] != nil {
1164+ arg0 = args[0].(context.Context)
1165+ }
1166+ var arg1 ports.RetentionPlan
1167+ if args[1] != nil {
1168+ arg1 = args[1].(ports.RetentionPlan)
1169+ }
1170+ var arg2 ports.MaintenanceBudget
1171+ if args[2] != nil {
1172+ arg2 = args[2].(ports.MaintenanceBudget)
1173+ }
1174+ run(
1175+ arg0,
1176+ arg1,
1177+ arg2,
1178+ )
1179+ })
1180+ return _c
1181+}
1182+
1183+func (_c *MockSnapshotRepository_MaintainSession_Call) Return(b bool, err error) *MockSnapshotRepository_MaintainSession_Call {
1184+ _c.Call.Return(b, err)
1185+ return _c
1186+}
1187+
1188+func (_c *MockSnapshotRepository_MaintainSession_Call) RunAndReturn(run func(context1 context.Context, retentionPlan ports.RetentionPlan, maintenanceBudget ports.MaintenanceBudget) (bool, error)) *MockSnapshotRepository_MaintainSession_Call {
1189+ _c.Call.Return(run)
1190+ return _c
1191+}
1192+
1193+// Publish provides a mock function for the type MockSnapshotRepository
1194+func (_mock *MockSnapshotRepository) Publish(context1 context.Context, snapshotPublication ports.SnapshotPublication) error {
1195+ ret := _mock.Called(context1, snapshotPublication)
1196+
1197+ if len(ret) == 0 {
1198+ panic("no return value specified for Publish")
1199+ }
1200+
1201+ var r0 error
1202+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.SnapshotPublication) error); ok {
1203+ r0 = returnFunc(context1, snapshotPublication)
1204+ } else {
1205+ r0 = ret.Error(0)
1206+ }
1207+ return r0
1208+}
1209+
1210+// MockSnapshotRepository_Publish_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Publish'
1211+type MockSnapshotRepository_Publish_Call struct {
1212+ *mock.Call
1213+}
1214+
1215+// Publish is a helper method to define mock.On call
1216+// - context1 context.Context
1217+// - snapshotPublication ports.SnapshotPublication
1218+func (_e *MockSnapshotRepository_Expecter) Publish(context1 any, snapshotPublication any) *MockSnapshotRepository_Publish_Call {
1219+ return &MockSnapshotRepository_Publish_Call{Call: _e.mock.On("Publish", context1, snapshotPublication)}
1220+}
1221+
1222+func (_c *MockSnapshotRepository_Publish_Call) Run(run func(context1 context.Context, snapshotPublication ports.SnapshotPublication)) *MockSnapshotRepository_Publish_Call {
1223+ _c.Call.Run(func(args mock.Arguments) {
1224+ var arg0 context.Context
1225+ if args[0] != nil {
1226+ arg0 = args[0].(context.Context)
1227+ }
1228+ var arg1 ports.SnapshotPublication
1229+ if args[1] != nil {
1230+ arg1 = args[1].(ports.SnapshotPublication)
1231+ }
1232+ run(
1233+ arg0,
1234+ arg1,
1235+ )
1236+ })
1237+ return _c
1238+}
1239+
1240+func (_c *MockSnapshotRepository_Publish_Call) Return(err error) *MockSnapshotRepository_Publish_Call {
1241+ _c.Call.Return(err)
1242+ return _c
1243+}
1244+
1245+func (_c *MockSnapshotRepository_Publish_Call) RunAndReturn(run func(context1 context.Context, snapshotPublication ports.SnapshotPublication) error) *MockSnapshotRepository_Publish_Call {
1246+ _c.Call.Return(run)
1247+ return _c
1248+}
1249+
1250+// QuarantineDeletionSources provides a mock function for the type MockSnapshotRepository
1251+func (_mock *MockSnapshotRepository) QuarantineDeletionSources(context1 context.Context, deletionTombstone domain.DeletionTombstone, b bool) error {
1252+ ret := _mock.Called(context1, deletionTombstone, b)
1253+
1254+ if len(ret) == 0 {
1255+ panic("no return value specified for QuarantineDeletionSources")
1256+ }
1257+
1258+ var r0 error
1259+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.DeletionTombstone, bool) error); ok {
1260+ r0 = returnFunc(context1, deletionTombstone, b)
1261+ } else {
1262+ r0 = ret.Error(0)
1263+ }
1264+ return r0
1265+}
1266+
1267+// MockSnapshotRepository_QuarantineDeletionSources_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QuarantineDeletionSources'
1268+type MockSnapshotRepository_QuarantineDeletionSources_Call struct {
1269+ *mock.Call
1270+}
1271+
1272+// QuarantineDeletionSources is a helper method to define mock.On call
1273+// - context1 context.Context
1274+// - deletionTombstone domain.DeletionTombstone
1275+// - b bool
1276+func (_e *MockSnapshotRepository_Expecter) QuarantineDeletionSources(context1 any, deletionTombstone any, b any) *MockSnapshotRepository_QuarantineDeletionSources_Call {
1277+ return &MockSnapshotRepository_QuarantineDeletionSources_Call{Call: _e.mock.On("QuarantineDeletionSources", context1, deletionTombstone, b)}
1278+}
1279+
1280+func (_c *MockSnapshotRepository_QuarantineDeletionSources_Call) Run(run func(context1 context.Context, deletionTombstone domain.DeletionTombstone, b bool)) *MockSnapshotRepository_QuarantineDeletionSources_Call {
1281+ _c.Call.Run(func(args mock.Arguments) {
1282+ var arg0 context.Context
1283+ if args[0] != nil {
1284+ arg0 = args[0].(context.Context)
1285+ }
1286+ var arg1 domain.DeletionTombstone
1287+ if args[1] != nil {
1288+ arg1 = args[1].(domain.DeletionTombstone)
1289+ }
1290+ var arg2 bool
1291+ if args[2] != nil {
1292+ arg2 = args[2].(bool)
1293+ }
1294+ run(
1295+ arg0,
1296+ arg1,
1297+ arg2,
1298+ )
1299+ })
1300+ return _c
1301+}
1302+
1303+func (_c *MockSnapshotRepository_QuarantineDeletionSources_Call) Return(err error) *MockSnapshotRepository_QuarantineDeletionSources_Call {
1304+ _c.Call.Return(err)
1305+ return _c
1306+}
1307+
1308+func (_c *MockSnapshotRepository_QuarantineDeletionSources_Call) RunAndReturn(run func(context1 context.Context, deletionTombstone domain.DeletionTombstone, b bool) error) *MockSnapshotRepository_QuarantineDeletionSources_Call {
1309+ _c.Call.Return(run)
1310+ return _c
1311+}
1312+
1313+// QuarantineIncarnation provides a mock function for the type MockSnapshotRepository
1314+func (_mock *MockSnapshotRepository) QuarantineIncarnation(context1 context.Context, incarnationID domain.IncarnationID) error {
1315+ ret := _mock.Called(context1, incarnationID)
1316+
1317+ if len(ret) == 0 {
1318+ panic("no return value specified for QuarantineIncarnation")
1319+ }
1320+
1321+ var r0 error
1322+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.IncarnationID) error); ok {
1323+ r0 = returnFunc(context1, incarnationID)
1324+ } else {
1325+ r0 = ret.Error(0)
1326+ }
1327+ return r0
1328+}
1329+
1330+// MockSnapshotRepository_QuarantineIncarnation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QuarantineIncarnation'
1331+type MockSnapshotRepository_QuarantineIncarnation_Call struct {
1332+ *mock.Call
1333+}
1334+
1335+// QuarantineIncarnation is a helper method to define mock.On call
1336+// - context1 context.Context
1337+// - incarnationID domain.IncarnationID
1338+func (_e *MockSnapshotRepository_Expecter) QuarantineIncarnation(context1 any, incarnationID any) *MockSnapshotRepository_QuarantineIncarnation_Call {
1339+ return &MockSnapshotRepository_QuarantineIncarnation_Call{Call: _e.mock.On("QuarantineIncarnation", context1, incarnationID)}
1340+}
1341+
1342+func (_c *MockSnapshotRepository_QuarantineIncarnation_Call) Run(run func(context1 context.Context, incarnationID domain.IncarnationID)) *MockSnapshotRepository_QuarantineIncarnation_Call {
1343+ _c.Call.Run(func(args mock.Arguments) {
1344+ var arg0 context.Context
1345+ if args[0] != nil {
1346+ arg0 = args[0].(context.Context)
1347+ }
1348+ var arg1 domain.IncarnationID
1349+ if args[1] != nil {
1350+ arg1 = args[1].(domain.IncarnationID)
1351+ }
1352+ run(
1353+ arg0,
1354+ arg1,
1355+ )
1356+ })
1357+ return _c
1358+}
1359+
1360+func (_c *MockSnapshotRepository_QuarantineIncarnation_Call) Return(err error) *MockSnapshotRepository_QuarantineIncarnation_Call {
1361+ _c.Call.Return(err)
1362+ return _c
1363+}
1364+
1365+func (_c *MockSnapshotRepository_QuarantineIncarnation_Call) RunAndReturn(run func(context1 context.Context, incarnationID domain.IncarnationID) error) *MockSnapshotRepository_QuarantineIncarnation_Call {
1366+ _c.Call.Return(run)
1367+ return _c
1368+}
1369+
1370+// Reconcile provides a mock function for the type MockSnapshotRepository
1371+func (_mock *MockSnapshotRepository) Reconcile(context1 context.Context, catalogueRecords []domain.CatalogueRecord, reconcileCursor ports.ReconcileCursor, maintenanceBudget ports.MaintenanceBudget) (ports.ReconcileCursor, []ports.ReconcileFinding, error) {
1372+ ret := _mock.Called(context1, catalogueRecords, reconcileCursor, maintenanceBudget)
1373+
1374+ if len(ret) == 0 {
1375+ panic("no return value specified for Reconcile")
1376+ }
1377+
1378+ var r0 ports.ReconcileCursor
1379+ var r1 []ports.ReconcileFinding
1380+ var r2 error
1381+ if returnFunc, ok := ret.Get(0).(func(context.Context, []domain.CatalogueRecord, ports.ReconcileCursor, ports.MaintenanceBudget) (ports.ReconcileCursor, []ports.ReconcileFinding, error)); ok {
1382+ return returnFunc(context1, catalogueRecords, reconcileCursor, maintenanceBudget)
1383+ }
1384+ if returnFunc, ok := ret.Get(0).(func(context.Context, []domain.CatalogueRecord, ports.ReconcileCursor, ports.MaintenanceBudget) ports.ReconcileCursor); ok {
1385+ r0 = returnFunc(context1, catalogueRecords, reconcileCursor, maintenanceBudget)
1386+ } else {
1387+ r0 = ret.Get(0).(ports.ReconcileCursor)
1388+ }
1389+ if returnFunc, ok := ret.Get(1).(func(context.Context, []domain.CatalogueRecord, ports.ReconcileCursor, ports.MaintenanceBudget) []ports.ReconcileFinding); ok {
1390+ r1 = returnFunc(context1, catalogueRecords, reconcileCursor, maintenanceBudget)
1391+ } else {
1392+ if ret.Get(1) != nil {
1393+ r1 = ret.Get(1).([]ports.ReconcileFinding)
1394+ }
1395+ }
1396+ if returnFunc, ok := ret.Get(2).(func(context.Context, []domain.CatalogueRecord, ports.ReconcileCursor, ports.MaintenanceBudget) error); ok {
1397+ r2 = returnFunc(context1, catalogueRecords, reconcileCursor, maintenanceBudget)
1398+ } else {
1399+ r2 = ret.Error(2)
1400+ }
1401+ return r0, r1, r2
1402+}
1403+
1404+// MockSnapshotRepository_Reconcile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Reconcile'
1405+type MockSnapshotRepository_Reconcile_Call struct {
1406+ *mock.Call
1407+}
1408+
1409+// Reconcile is a helper method to define mock.On call
1410+// - context1 context.Context
1411+// - catalogueRecords []domain.CatalogueRecord
1412+// - reconcileCursor ports.ReconcileCursor
1413+// - maintenanceBudget ports.MaintenanceBudget
1414+func (_e *MockSnapshotRepository_Expecter) Reconcile(context1 any, catalogueRecords any, reconcileCursor any, maintenanceBudget any) *MockSnapshotRepository_Reconcile_Call {
1415+ return &MockSnapshotRepository_Reconcile_Call{Call: _e.mock.On("Reconcile", context1, catalogueRecords, reconcileCursor, maintenanceBudget)}
1416+}
1417+
1418+func (_c *MockSnapshotRepository_Reconcile_Call) Run(run func(context1 context.Context, catalogueRecords []domain.CatalogueRecord, reconcileCursor ports.ReconcileCursor, maintenanceBudget ports.MaintenanceBudget)) *MockSnapshotRepository_Reconcile_Call {
1419+ _c.Call.Run(func(args mock.Arguments) {
1420+ var arg0 context.Context
1421+ if args[0] != nil {
1422+ arg0 = args[0].(context.Context)
1423+ }
1424+ var arg1 []domain.CatalogueRecord
1425+ if args[1] != nil {
1426+ arg1 = args[1].([]domain.CatalogueRecord)
1427+ }
1428+ var arg2 ports.ReconcileCursor
1429+ if args[2] != nil {
1430+ arg2 = args[2].(ports.ReconcileCursor)
1431+ }
1432+ var arg3 ports.MaintenanceBudget
1433+ if args[3] != nil {
1434+ arg3 = args[3].(ports.MaintenanceBudget)
1435+ }
1436+ run(
1437+ arg0,
1438+ arg1,
1439+ arg2,
1440+ arg3,
1441+ )
1442+ })
1443+ return _c
1444+}
1445+
1446+func (_c *MockSnapshotRepository_Reconcile_Call) Return(reconcileCursor1 ports.ReconcileCursor, reconcileFindings []ports.ReconcileFinding, err error) *MockSnapshotRepository_Reconcile_Call {
1447+ _c.Call.Return(reconcileCursor1, reconcileFindings, err)
1448+ return _c
1449+}
1450+
1451+func (_c *MockSnapshotRepository_Reconcile_Call) RunAndReturn(run func(context1 context.Context, catalogueRecords []domain.CatalogueRecord, reconcileCursor ports.ReconcileCursor, maintenanceBudget ports.MaintenanceBudget) (ports.ReconcileCursor, []ports.ReconcileFinding, error)) *MockSnapshotRepository_Reconcile_Call {
1452+ _c.Call.Return(run)
1453+ return _c
1454+}
1455+
1456+// RepairHEAD provides a mock function for the type MockSnapshotRepository
1457+func (_mock *MockSnapshotRepository) RepairHEAD(context1 context.Context, incarnationID domain.IncarnationID, v ports.CheckpointRef) error {
1458+ ret := _mock.Called(context1, incarnationID, v)
1459+
1460+ if len(ret) == 0 {
1461+ panic("no return value specified for RepairHEAD")
1462+ }
1463+
1464+ var r0 error
1465+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.IncarnationID, ports.CheckpointRef) error); ok {
1466+ r0 = returnFunc(context1, incarnationID, v)
1467+ } else {
1468+ r0 = ret.Error(0)
1469+ }
1470+ return r0
1471+}
1472+
1473+// MockSnapshotRepository_RepairHEAD_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RepairHEAD'
1474+type MockSnapshotRepository_RepairHEAD_Call struct {
1475+ *mock.Call
1476+}
1477+
1478+// RepairHEAD is a helper method to define mock.On call
1479+// - context1 context.Context
1480+// - incarnationID domain.IncarnationID
1481+// - v ports.CheckpointRef
1482+func (_e *MockSnapshotRepository_Expecter) RepairHEAD(context1 any, incarnationID any, v any) *MockSnapshotRepository_RepairHEAD_Call {
1483+ return &MockSnapshotRepository_RepairHEAD_Call{Call: _e.mock.On("RepairHEAD", context1, incarnationID, v)}
1484+}
1485+
1486+func (_c *MockSnapshotRepository_RepairHEAD_Call) Run(run func(context1 context.Context, incarnationID domain.IncarnationID, v ports.CheckpointRef)) *MockSnapshotRepository_RepairHEAD_Call {
1487+ _c.Call.Run(func(args mock.Arguments) {
1488+ var arg0 context.Context
1489+ if args[0] != nil {
1490+ arg0 = args[0].(context.Context)
1491+ }
1492+ var arg1 domain.IncarnationID
1493+ if args[1] != nil {
1494+ arg1 = args[1].(domain.IncarnationID)
1495+ }
1496+ var arg2 ports.CheckpointRef
1497+ if args[2] != nil {
1498+ arg2 = args[2].(ports.CheckpointRef)
1499+ }
1500+ run(
1501+ arg0,
1502+ arg1,
1503+ arg2,
1504+ )
1505+ })
1506+ return _c
1507+}
1508+
1509+func (_c *MockSnapshotRepository_RepairHEAD_Call) Return(err error) *MockSnapshotRepository_RepairHEAD_Call {
1510+ _c.Call.Return(err)
1511+ return _c
1512+}
1513+
1514+func (_c *MockSnapshotRepository_RepairHEAD_Call) RunAndReturn(run func(context1 context.Context, incarnationID domain.IncarnationID, v ports.CheckpointRef) error) *MockSnapshotRepository_RepairHEAD_Call {
1515+ _c.Call.Return(run)
1516+ return _c
1517+}
1518+
1519+// SaveQuarantineDescriptor provides a mock function for the type MockSnapshotRepository
1520+func (_mock *MockSnapshotRepository) SaveQuarantineDescriptor(context1 context.Context, quarantineDescriptor domain.QuarantineDescriptor) error {
1521+ ret := _mock.Called(context1, quarantineDescriptor)
1522+
1523+ if len(ret) == 0 {
1524+ panic("no return value specified for SaveQuarantineDescriptor")
1525+ }
1526+
1527+ var r0 error
1528+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.QuarantineDescriptor) error); ok {
1529+ r0 = returnFunc(context1, quarantineDescriptor)
1530+ } else {
1531+ r0 = ret.Error(0)
1532+ }
1533+ return r0
1534+}
1535+
1536+// MockSnapshotRepository_SaveQuarantineDescriptor_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SaveQuarantineDescriptor'
1537+type MockSnapshotRepository_SaveQuarantineDescriptor_Call struct {
1538+ *mock.Call
1539+}
1540+
1541+// SaveQuarantineDescriptor is a helper method to define mock.On call
1542+// - context1 context.Context
1543+// - quarantineDescriptor domain.QuarantineDescriptor
1544+func (_e *MockSnapshotRepository_Expecter) SaveQuarantineDescriptor(context1 any, quarantineDescriptor any) *MockSnapshotRepository_SaveQuarantineDescriptor_Call {
1545+ return &MockSnapshotRepository_SaveQuarantineDescriptor_Call{Call: _e.mock.On("SaveQuarantineDescriptor", context1, quarantineDescriptor)}
1546+}
1547+
1548+func (_c *MockSnapshotRepository_SaveQuarantineDescriptor_Call) Run(run func(context1 context.Context, quarantineDescriptor domain.QuarantineDescriptor)) *MockSnapshotRepository_SaveQuarantineDescriptor_Call {
1549+ _c.Call.Run(func(args mock.Arguments) {
1550+ var arg0 context.Context
1551+ if args[0] != nil {
1552+ arg0 = args[0].(context.Context)
1553+ }
1554+ var arg1 domain.QuarantineDescriptor
1555+ if args[1] != nil {
1556+ arg1 = args[1].(domain.QuarantineDescriptor)
1557+ }
1558+ run(
1559+ arg0,
1560+ arg1,
1561+ )
1562+ })
1563+ return _c
1564+}
1565+
1566+func (_c *MockSnapshotRepository_SaveQuarantineDescriptor_Call) Return(err error) *MockSnapshotRepository_SaveQuarantineDescriptor_Call {
1567+ _c.Call.Return(err)
1568+ return _c
1569+}
1570+
1571+func (_c *MockSnapshotRepository_SaveQuarantineDescriptor_Call) RunAndReturn(run func(context1 context.Context, quarantineDescriptor domain.QuarantineDescriptor) error) *MockSnapshotRepository_SaveQuarantineDescriptor_Call {
1572+ _c.Call.Return(run)
1573+ return _c
1574+}
1575+
1576+// WriteDeletionTombstone provides a mock function for the type MockSnapshotRepository
1577+func (_mock *MockSnapshotRepository) WriteDeletionTombstone(context1 context.Context, deletionTombstone domain.DeletionTombstone) error {
1578+ ret := _mock.Called(context1, deletionTombstone)
1579+
1580+ if len(ret) == 0 {
1581+ panic("no return value specified for WriteDeletionTombstone")
1582+ }
1583+
1584+ var r0 error
1585+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.DeletionTombstone) error); ok {
1586+ r0 = returnFunc(context1, deletionTombstone)
1587+ } else {
1588+ r0 = ret.Error(0)
1589+ }
1590+ return r0
1591+}
1592+
1593+// MockSnapshotRepository_WriteDeletionTombstone_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteDeletionTombstone'
1594+type MockSnapshotRepository_WriteDeletionTombstone_Call struct {
1595+ *mock.Call
1596+}
1597+
1598+// WriteDeletionTombstone is a helper method to define mock.On call
1599+// - context1 context.Context
1600+// - deletionTombstone domain.DeletionTombstone
1601+func (_e *MockSnapshotRepository_Expecter) WriteDeletionTombstone(context1 any, deletionTombstone any) *MockSnapshotRepository_WriteDeletionTombstone_Call {
1602+ return &MockSnapshotRepository_WriteDeletionTombstone_Call{Call: _e.mock.On("WriteDeletionTombstone", context1, deletionTombstone)}
1603+}
1604+
1605+func (_c *MockSnapshotRepository_WriteDeletionTombstone_Call) Run(run func(context1 context.Context, deletionTombstone domain.DeletionTombstone)) *MockSnapshotRepository_WriteDeletionTombstone_Call {
1606+ _c.Call.Run(func(args mock.Arguments) {
1607+ var arg0 context.Context
1608+ if args[0] != nil {
1609+ arg0 = args[0].(context.Context)
1610+ }
1611+ var arg1 domain.DeletionTombstone
1612+ if args[1] != nil {
1613+ arg1 = args[1].(domain.DeletionTombstone)
1614+ }
1615+ run(
1616+ arg0,
1617+ arg1,
1618+ )
1619+ })
1620+ return _c
1621+}
1622+
1623+func (_c *MockSnapshotRepository_WriteDeletionTombstone_Call) Return(err error) *MockSnapshotRepository_WriteDeletionTombstone_Call {
1624+ _c.Call.Return(err)
1625+ return _c
1626+}
1627+
1628+func (_c *MockSnapshotRepository_WriteDeletionTombstone_Call) RunAndReturn(run func(context1 context.Context, deletionTombstone domain.DeletionTombstone) error) *MockSnapshotRepository_WriteDeletionTombstone_Call {
1629+ _c.Call.Return(run)
1630+ return _c
1631+}
M internal/ports/mocks/store_mock.go +0 -434
@@ -387,440 +387,6 @@ func (_c *MockStore_Sync_Call) RunAndReturn(run func() error) *MockStore_Sync_Ca
387387 return _c
388388 }
389389
390-// NewMockSnapshotRepository creates a new instance of MockSnapshotRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
391-// The first argument is typically a *testing.T value.
392-func NewMockSnapshotRepository(t interface {
393- mock.TestingT
394- Cleanup(func())
395-}) *MockSnapshotRepository {
396- mock := &MockSnapshotRepository{}
397- mock.Mock.Test(t)
398-
399- t.Cleanup(func() { mock.AssertExpectations(t) })
400-
401- return mock
402-}
403-
404-// MockSnapshotRepository is an autogenerated mock type for the SnapshotRepository type
405-type MockSnapshotRepository struct {
406- mock.Mock
407-}
408-
409-type MockSnapshotRepository_Expecter struct {
410- mock *mock.Mock
411-}
412-
413-func (_m *MockSnapshotRepository) EXPECT() *MockSnapshotRepository_Expecter {
414- return &MockSnapshotRepository_Expecter{mock: &_m.Mock}
415-}
416-
417-// Delete provides a mock function for the type MockSnapshotRepository
418-func (_mock *MockSnapshotRepository) Delete(context1 context.Context, s string) error {
419- ret := _mock.Called(context1, s)
420-
421- if len(ret) == 0 {
422- panic("no return value specified for Delete")
423- }
424-
425- var r0 error
426- if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
427- r0 = returnFunc(context1, s)
428- } else {
429- r0 = ret.Error(0)
430- }
431- return r0
432-}
433-
434-// MockSnapshotRepository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
435-type MockSnapshotRepository_Delete_Call struct {
436- *mock.Call
437-}
438-
439-// Delete is a helper method to define mock.On call
440-// - context1 context.Context
441-// - s string
442-func (_e *MockSnapshotRepository_Expecter) Delete(context1 any, s any) *MockSnapshotRepository_Delete_Call {
443- return &MockSnapshotRepository_Delete_Call{Call: _e.mock.On("Delete", context1, s)}
444-}
445-
446-func (_c *MockSnapshotRepository_Delete_Call) Run(run func(context1 context.Context, s string)) *MockSnapshotRepository_Delete_Call {
447- _c.Call.Run(func(args mock.Arguments) {
448- var arg0 context.Context
449- if args[0] != nil {
450- arg0 = args[0].(context.Context)
451- }
452- var arg1 string
453- if args[1] != nil {
454- arg1 = args[1].(string)
455- }
456- run(
457- arg0,
458- arg1,
459- )
460- })
461- return _c
462-}
463-
464-func (_c *MockSnapshotRepository_Delete_Call) Return(err error) *MockSnapshotRepository_Delete_Call {
465- _c.Call.Return(err)
466- return _c
467-}
468-
469-func (_c *MockSnapshotRepository_Delete_Call) RunAndReturn(run func(context1 context.Context, s string) error) *MockSnapshotRepository_Delete_Call {
470- _c.Call.Return(run)
471- return _c
472-}
473-
474-// DeleteTombstone provides a mock function for the type MockSnapshotRepository
475-func (_mock *MockSnapshotRepository) DeleteTombstone(context1 context.Context, s string) error {
476- ret := _mock.Called(context1, s)
477-
478- if len(ret) == 0 {
479- panic("no return value specified for DeleteTombstone")
480- }
481-
482- var r0 error
483- if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
484- r0 = returnFunc(context1, s)
485- } else {
486- r0 = ret.Error(0)
487- }
488- return r0
489-}
490-
491-// MockSnapshotRepository_DeleteTombstone_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteTombstone'
492-type MockSnapshotRepository_DeleteTombstone_Call struct {
493- *mock.Call
494-}
495-
496-// DeleteTombstone is a helper method to define mock.On call
497-// - context1 context.Context
498-// - s string
499-func (_e *MockSnapshotRepository_Expecter) DeleteTombstone(context1 any, s any) *MockSnapshotRepository_DeleteTombstone_Call {
500- return &MockSnapshotRepository_DeleteTombstone_Call{Call: _e.mock.On("DeleteTombstone", context1, s)}
501-}
502-
503-func (_c *MockSnapshotRepository_DeleteTombstone_Call) Run(run func(context1 context.Context, s string)) *MockSnapshotRepository_DeleteTombstone_Call {
504- _c.Call.Run(func(args mock.Arguments) {
505- var arg0 context.Context
506- if args[0] != nil {
507- arg0 = args[0].(context.Context)
508- }
509- var arg1 string
510- if args[1] != nil {
511- arg1 = args[1].(string)
512- }
513- run(
514- arg0,
515- arg1,
516- )
517- })
518- return _c
519-}
520-
521-func (_c *MockSnapshotRepository_DeleteTombstone_Call) Return(err error) *MockSnapshotRepository_DeleteTombstone_Call {
522- _c.Call.Return(err)
523- return _c
524-}
525-
526-func (_c *MockSnapshotRepository_DeleteTombstone_Call) RunAndReturn(run func(context1 context.Context, s string) error) *MockSnapshotRepository_DeleteTombstone_Call {
527- _c.Call.Return(run)
528- return _c
529-}
530-
531-// List provides a mock function for the type MockSnapshotRepository
532-func (_mock *MockSnapshotRepository) List(context1 context.Context) ([]string, error) {
533- ret := _mock.Called(context1)
534-
535- if len(ret) == 0 {
536- panic("no return value specified for List")
537- }
538-
539- var r0 []string
540- var r1 error
541- if returnFunc, ok := ret.Get(0).(func(context.Context) ([]string, error)); ok {
542- return returnFunc(context1)
543- }
544- if returnFunc, ok := ret.Get(0).(func(context.Context) []string); ok {
545- r0 = returnFunc(context1)
546- } else {
547- if ret.Get(0) != nil {
548- r0 = ret.Get(0).([]string)
549- }
550- }
551- if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
552- r1 = returnFunc(context1)
553- } else {
554- r1 = ret.Error(1)
555- }
556- return r0, r1
557-}
558-
559-// MockSnapshotRepository_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List'
560-type MockSnapshotRepository_List_Call struct {
561- *mock.Call
562-}
563-
564-// List is a helper method to define mock.On call
565-// - context1 context.Context
566-func (_e *MockSnapshotRepository_Expecter) List(context1 any) *MockSnapshotRepository_List_Call {
567- return &MockSnapshotRepository_List_Call{Call: _e.mock.On("List", context1)}
568-}
569-
570-func (_c *MockSnapshotRepository_List_Call) Run(run func(context1 context.Context)) *MockSnapshotRepository_List_Call {
571- _c.Call.Run(func(args mock.Arguments) {
572- var arg0 context.Context
573- if args[0] != nil {
574- arg0 = args[0].(context.Context)
575- }
576- run(
577- arg0,
578- )
579- })
580- return _c
581-}
582-
583-func (_c *MockSnapshotRepository_List_Call) Return(strings []string, err error) *MockSnapshotRepository_List_Call {
584- _c.Call.Return(strings, err)
585- return _c
586-}
587-
588-func (_c *MockSnapshotRepository_List_Call) RunAndReturn(run func(context1 context.Context) ([]string, error)) *MockSnapshotRepository_List_Call {
589- _c.Call.Return(run)
590- return _c
591-}
592-
593-// Load provides a mock function for the type MockSnapshotRepository
594-func (_mock *MockSnapshotRepository) Load(context1 context.Context, s string) (ports.SnapshotGeneration, error) {
595- ret := _mock.Called(context1, s)
596-
597- if len(ret) == 0 {
598- panic("no return value specified for Load")
599- }
600-
601- var r0 ports.SnapshotGeneration
602- var r1 error
603- if returnFunc, ok := ret.Get(0).(func(context.Context, string) (ports.SnapshotGeneration, error)); ok {
604- return returnFunc(context1, s)
605- }
606- if returnFunc, ok := ret.Get(0).(func(context.Context, string) ports.SnapshotGeneration); ok {
607- r0 = returnFunc(context1, s)
608- } else {
609- r0 = ret.Get(0).(ports.SnapshotGeneration)
610- }
611- if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok {
612- r1 = returnFunc(context1, s)
613- } else {
614- r1 = ret.Error(1)
615- }
616- return r0, r1
617-}
618-
619-// MockSnapshotRepository_Load_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Load'
620-type MockSnapshotRepository_Load_Call struct {
621- *mock.Call
622-}
623-
624-// Load is a helper method to define mock.On call
625-// - context1 context.Context
626-// - s string
627-func (_e *MockSnapshotRepository_Expecter) Load(context1 any, s any) *MockSnapshotRepository_Load_Call {
628- return &MockSnapshotRepository_Load_Call{Call: _e.mock.On("Load", context1, s)}
629-}
630-
631-func (_c *MockSnapshotRepository_Load_Call) Run(run func(context1 context.Context, s string)) *MockSnapshotRepository_Load_Call {
632- _c.Call.Run(func(args mock.Arguments) {
633- var arg0 context.Context
634- if args[0] != nil {
635- arg0 = args[0].(context.Context)
636- }
637- var arg1 string
638- if args[1] != nil {
639- arg1 = args[1].(string)
640- }
641- run(
642- arg0,
643- arg1,
644- )
645- })
646- return _c
647-}
648-
649-func (_c *MockSnapshotRepository_Load_Call) Return(snapshotGeneration ports.SnapshotGeneration, err error) *MockSnapshotRepository_Load_Call {
650- _c.Call.Return(snapshotGeneration, err)
651- return _c
652-}
653-
654-func (_c *MockSnapshotRepository_Load_Call) RunAndReturn(run func(context1 context.Context, s string) (ports.SnapshotGeneration, error)) *MockSnapshotRepository_Load_Call {
655- _c.Call.Return(run)
656- return _c
657-}
658-
659-// Maintain provides a mock function for the type MockSnapshotRepository
660-func (_mock *MockSnapshotRepository) Maintain(context1 context.Context) error {
661- ret := _mock.Called(context1)
662-
663- if len(ret) == 0 {
664- panic("no return value specified for Maintain")
665- }
666-
667- var r0 error
668- if returnFunc, ok := ret.Get(0).(func(context.Context) error); ok {
669- r0 = returnFunc(context1)
670- } else {
671- r0 = ret.Error(0)
672- }
673- return r0
674-}
675-
676-// MockSnapshotRepository_Maintain_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Maintain'
677-type MockSnapshotRepository_Maintain_Call struct {
678- *mock.Call
679-}
680-
681-// Maintain is a helper method to define mock.On call
682-// - context1 context.Context
683-func (_e *MockSnapshotRepository_Expecter) Maintain(context1 any) *MockSnapshotRepository_Maintain_Call {
684- return &MockSnapshotRepository_Maintain_Call{Call: _e.mock.On("Maintain", context1)}
685-}
686-
687-func (_c *MockSnapshotRepository_Maintain_Call) Run(run func(context1 context.Context)) *MockSnapshotRepository_Maintain_Call {
688- _c.Call.Run(func(args mock.Arguments) {
689- var arg0 context.Context
690- if args[0] != nil {
691- arg0 = args[0].(context.Context)
692- }
693- run(
694- arg0,
695- )
696- })
697- return _c
698-}
699-
700-func (_c *MockSnapshotRepository_Maintain_Call) Return(err error) *MockSnapshotRepository_Maintain_Call {
701- _c.Call.Return(err)
702- return _c
703-}
704-
705-func (_c *MockSnapshotRepository_Maintain_Call) RunAndReturn(run func(context1 context.Context) error) *MockSnapshotRepository_Maintain_Call {
706- _c.Call.Return(run)
707- return _c
708-}
709-
710-// Publish provides a mock function for the type MockSnapshotRepository
711-func (_mock *MockSnapshotRepository) Publish(context1 context.Context, snapshotPublication ports.SnapshotPublication) error {
712- ret := _mock.Called(context1, snapshotPublication)
713-
714- if len(ret) == 0 {
715- panic("no return value specified for Publish")
716- }
717-
718- var r0 error
719- if returnFunc, ok := ret.Get(0).(func(context.Context, ports.SnapshotPublication) error); ok {
720- r0 = returnFunc(context1, snapshotPublication)
721- } else {
722- r0 = ret.Error(0)
723- }
724- return r0
725-}
726-
727-// MockSnapshotRepository_Publish_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Publish'
728-type MockSnapshotRepository_Publish_Call struct {
729- *mock.Call
730-}
731-
732-// Publish is a helper method to define mock.On call
733-// - context1 context.Context
734-// - snapshotPublication ports.SnapshotPublication
735-func (_e *MockSnapshotRepository_Expecter) Publish(context1 any, snapshotPublication any) *MockSnapshotRepository_Publish_Call {
736- return &MockSnapshotRepository_Publish_Call{Call: _e.mock.On("Publish", context1, snapshotPublication)}
737-}
738-
739-func (_c *MockSnapshotRepository_Publish_Call) Run(run func(context1 context.Context, snapshotPublication ports.SnapshotPublication)) *MockSnapshotRepository_Publish_Call {
740- _c.Call.Run(func(args mock.Arguments) {
741- var arg0 context.Context
742- if args[0] != nil {
743- arg0 = args[0].(context.Context)
744- }
745- var arg1 ports.SnapshotPublication
746- if args[1] != nil {
747- arg1 = args[1].(ports.SnapshotPublication)
748- }
749- run(
750- arg0,
751- arg1,
752- )
753- })
754- return _c
755-}
756-
757-func (_c *MockSnapshotRepository_Publish_Call) Return(err error) *MockSnapshotRepository_Publish_Call {
758- _c.Call.Return(err)
759- return _c
760-}
761-
762-func (_c *MockSnapshotRepository_Publish_Call) RunAndReturn(run func(context1 context.Context, snapshotPublication ports.SnapshotPublication) error) *MockSnapshotRepository_Publish_Call {
763- _c.Call.Return(run)
764- return _c
765-}
766-
767-// Tombstone provides a mock function for the type MockSnapshotRepository
768-func (_mock *MockSnapshotRepository) Tombstone(context1 context.Context, s string) error {
769- ret := _mock.Called(context1, s)
770-
771- if len(ret) == 0 {
772- panic("no return value specified for Tombstone")
773- }
774-
775- var r0 error
776- if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
777- r0 = returnFunc(context1, s)
778- } else {
779- r0 = ret.Error(0)
780- }
781- return r0
782-}
783-
784-// MockSnapshotRepository_Tombstone_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Tombstone'
785-type MockSnapshotRepository_Tombstone_Call struct {
786- *mock.Call
787-}
788-
789-// Tombstone is a helper method to define mock.On call
790-// - context1 context.Context
791-// - s string
792-func (_e *MockSnapshotRepository_Expecter) Tombstone(context1 any, s any) *MockSnapshotRepository_Tombstone_Call {
793- return &MockSnapshotRepository_Tombstone_Call{Call: _e.mock.On("Tombstone", context1, s)}
794-}
795-
796-func (_c *MockSnapshotRepository_Tombstone_Call) Run(run func(context1 context.Context, s string)) *MockSnapshotRepository_Tombstone_Call {
797- _c.Call.Run(func(args mock.Arguments) {
798- var arg0 context.Context
799- if args[0] != nil {
800- arg0 = args[0].(context.Context)
801- }
802- var arg1 string
803- if args[1] != nil {
804- arg1 = args[1].(string)
805- }
806- run(
807- arg0,
808- arg1,
809- )
810- })
811- return _c
812-}
813-
814-func (_c *MockSnapshotRepository_Tombstone_Call) Return(err error) *MockSnapshotRepository_Tombstone_Call {
815- _c.Call.Return(err)
816- return _c
817-}
818-
819-func (_c *MockSnapshotRepository_Tombstone_Call) RunAndReturn(run func(context1 context.Context, s string) error) *MockSnapshotRepository_Tombstone_Call {
820- _c.Call.Return(run)
821- return _c
822-}
823-
824390 // NewMockLegacySnapshotSource creates a new instance of MockLegacySnapshotSource. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
825391 // The first argument is typically a *testing.T value.
826392 func NewMockLegacySnapshotSource(t interface {
@@ -387,440 +387,6 @@ func (_c *MockStore_Sync_Call) RunAndReturn(run func() error) *MockStore_Sync_Ca
387 return _c387 return _c
388 }388 }
389 389
390-// NewMockSnapshotRepository creates a new instance of MockSnapshotRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
391-// The first argument is typically a *testing.T value.
392-func NewMockSnapshotRepository(t interface {
393- mock.TestingT
394- Cleanup(func())
395-}) *MockSnapshotRepository {
396- mock := &MockSnapshotRepository{}
397- mock.Mock.Test(t)
398-
399- t.Cleanup(func() { mock.AssertExpectations(t) })
400-
401- return mock
402-}
403-
404-// MockSnapshotRepository is an autogenerated mock type for the SnapshotRepository type
405-type MockSnapshotRepository struct {
406- mock.Mock
407-}
408-
409-type MockSnapshotRepository_Expecter struct {
410- mock *mock.Mock
411-}
412-
413-func (_m *MockSnapshotRepository) EXPECT() *MockSnapshotRepository_Expecter {
414- return &MockSnapshotRepository_Expecter{mock: &_m.Mock}
415-}
416-
417-// Delete provides a mock function for the type MockSnapshotRepository
418-func (_mock *MockSnapshotRepository) Delete(context1 context.Context, s string) error {
419- ret := _mock.Called(context1, s)
420-
421- if len(ret) == 0 {
422- panic("no return value specified for Delete")
423- }
424-
425- var r0 error
426- if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
427- r0 = returnFunc(context1, s)
428- } else {
429- r0 = ret.Error(0)
430- }
431- return r0
432-}
433-
434-// MockSnapshotRepository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
435-type MockSnapshotRepository_Delete_Call struct {
436- *mock.Call
437-}
438-
439-// Delete is a helper method to define mock.On call
440-// - context1 context.Context
441-// - s string
442-func (_e *MockSnapshotRepository_Expecter) Delete(context1 any, s any) *MockSnapshotRepository_Delete_Call {
443- return &MockSnapshotRepository_Delete_Call{Call: _e.mock.On("Delete", context1, s)}
444-}
445-
446-func (_c *MockSnapshotRepository_Delete_Call) Run(run func(context1 context.Context, s string)) *MockSnapshotRepository_Delete_Call {
447- _c.Call.Run(func(args mock.Arguments) {
448- var arg0 context.Context
449- if args[0] != nil {
450- arg0 = args[0].(context.Context)
451- }
452- var arg1 string
453- if args[1] != nil {
454- arg1 = args[1].(string)
455- }
456- run(
457- arg0,
458- arg1,
459- )
460- })
461- return _c
462-}
463-
464-func (_c *MockSnapshotRepository_Delete_Call) Return(err error) *MockSnapshotRepository_Delete_Call {
465- _c.Call.Return(err)
466- return _c
467-}
468-
469-func (_c *MockSnapshotRepository_Delete_Call) RunAndReturn(run func(context1 context.Context, s string) error) *MockSnapshotRepository_Delete_Call {
470- _c.Call.Return(run)
471- return _c
472-}
473-
474-// DeleteTombstone provides a mock function for the type MockSnapshotRepository
475-func (_mock *MockSnapshotRepository) DeleteTombstone(context1 context.Context, s string) error {
476- ret := _mock.Called(context1, s)
477-
478- if len(ret) == 0 {
479- panic("no return value specified for DeleteTombstone")
480- }
481-
482- var r0 error
483- if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
484- r0 = returnFunc(context1, s)
485- } else {
486- r0 = ret.Error(0)
487- }
488- return r0
489-}
490-
491-// MockSnapshotRepository_DeleteTombstone_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteTombstone'
492-type MockSnapshotRepository_DeleteTombstone_Call struct {
493- *mock.Call
494-}
495-
496-// DeleteTombstone is a helper method to define mock.On call
497-// - context1 context.Context
498-// - s string
499-func (_e *MockSnapshotRepository_Expecter) DeleteTombstone(context1 any, s any) *MockSnapshotRepository_DeleteTombstone_Call {
500- return &MockSnapshotRepository_DeleteTombstone_Call{Call: _e.mock.On("DeleteTombstone", context1, s)}
501-}
502-
503-func (_c *MockSnapshotRepository_DeleteTombstone_Call) Run(run func(context1 context.Context, s string)) *MockSnapshotRepository_DeleteTombstone_Call {
504- _c.Call.Run(func(args mock.Arguments) {
505- var arg0 context.Context
506- if args[0] != nil {
507- arg0 = args[0].(context.Context)
508- }
509- var arg1 string
510- if args[1] != nil {
511- arg1 = args[1].(string)
512- }
513- run(
514- arg0,
515- arg1,
516- )
517- })
518- return _c
519-}
520-
521-func (_c *MockSnapshotRepository_DeleteTombstone_Call) Return(err error) *MockSnapshotRepository_DeleteTombstone_Call {
522- _c.Call.Return(err)
523- return _c
524-}
525-
526-func (_c *MockSnapshotRepository_DeleteTombstone_Call) RunAndReturn(run func(context1 context.Context, s string) error) *MockSnapshotRepository_DeleteTombstone_Call {
527- _c.Call.Return(run)
528- return _c
529-}
530-
531-// List provides a mock function for the type MockSnapshotRepository
532-func (_mock *MockSnapshotRepository) List(context1 context.Context) ([]string, error) {
533- ret := _mock.Called(context1)
534-
535- if len(ret) == 0 {
536- panic("no return value specified for List")
537- }
538-
539- var r0 []string
540- var r1 error
541- if returnFunc, ok := ret.Get(0).(func(context.Context) ([]string, error)); ok {
542- return returnFunc(context1)
543- }
544- if returnFunc, ok := ret.Get(0).(func(context.Context) []string); ok {
545- r0 = returnFunc(context1)
546- } else {
547- if ret.Get(0) != nil {
548- r0 = ret.Get(0).([]string)
549- }
550- }
551- if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
552- r1 = returnFunc(context1)
553- } else {
554- r1 = ret.Error(1)
555- }
556- return r0, r1
557-}
558-
559-// MockSnapshotRepository_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List'
560-type MockSnapshotRepository_List_Call struct {
561- *mock.Call
562-}
563-
564-// List is a helper method to define mock.On call
565-// - context1 context.Context
566-func (_e *MockSnapshotRepository_Expecter) List(context1 any) *MockSnapshotRepository_List_Call {
567- return &MockSnapshotRepository_List_Call{Call: _e.mock.On("List", context1)}
568-}
569-
570-func (_c *MockSnapshotRepository_List_Call) Run(run func(context1 context.Context)) *MockSnapshotRepository_List_Call {
571- _c.Call.Run(func(args mock.Arguments) {
572- var arg0 context.Context
573- if args[0] != nil {
574- arg0 = args[0].(context.Context)
575- }
576- run(
577- arg0,
578- )
579- })
580- return _c
581-}
582-
583-func (_c *MockSnapshotRepository_List_Call) Return(strings []string, err error) *MockSnapshotRepository_List_Call {
584- _c.Call.Return(strings, err)
585- return _c
586-}
587-
588-func (_c *MockSnapshotRepository_List_Call) RunAndReturn(run func(context1 context.Context) ([]string, error)) *MockSnapshotRepository_List_Call {
589- _c.Call.Return(run)
590- return _c
591-}
592-
593-// Load provides a mock function for the type MockSnapshotRepository
594-func (_mock *MockSnapshotRepository) Load(context1 context.Context, s string) (ports.SnapshotGeneration, error) {
595- ret := _mock.Called(context1, s)
596-
597- if len(ret) == 0 {
598- panic("no return value specified for Load")
599- }
600-
601- var r0 ports.SnapshotGeneration
602- var r1 error
603- if returnFunc, ok := ret.Get(0).(func(context.Context, string) (ports.SnapshotGeneration, error)); ok {
604- return returnFunc(context1, s)
605- }
606- if returnFunc, ok := ret.Get(0).(func(context.Context, string) ports.SnapshotGeneration); ok {
607- r0 = returnFunc(context1, s)
608- } else {
609- r0 = ret.Get(0).(ports.SnapshotGeneration)
610- }
611- if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok {
612- r1 = returnFunc(context1, s)
613- } else {
614- r1 = ret.Error(1)
615- }
616- return r0, r1
617-}
618-
619-// MockSnapshotRepository_Load_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Load'
620-type MockSnapshotRepository_Load_Call struct {
621- *mock.Call
622-}
623-
624-// Load is a helper method to define mock.On call
625-// - context1 context.Context
626-// - s string
627-func (_e *MockSnapshotRepository_Expecter) Load(context1 any, s any) *MockSnapshotRepository_Load_Call {
628- return &MockSnapshotRepository_Load_Call{Call: _e.mock.On("Load", context1, s)}
629-}
630-
631-func (_c *MockSnapshotRepository_Load_Call) Run(run func(context1 context.Context, s string)) *MockSnapshotRepository_Load_Call {
632- _c.Call.Run(func(args mock.Arguments) {
633- var arg0 context.Context
634- if args[0] != nil {
635- arg0 = args[0].(context.Context)
636- }
637- var arg1 string
638- if args[1] != nil {
639- arg1 = args[1].(string)
640- }
641- run(
642- arg0,
643- arg1,
644- )
645- })
646- return _c
647-}
648-
649-func (_c *MockSnapshotRepository_Load_Call) Return(snapshotGeneration ports.SnapshotGeneration, err error) *MockSnapshotRepository_Load_Call {
650- _c.Call.Return(snapshotGeneration, err)
651- return _c
652-}
653-
654-func (_c *MockSnapshotRepository_Load_Call) RunAndReturn(run func(context1 context.Context, s string) (ports.SnapshotGeneration, error)) *MockSnapshotRepository_Load_Call {
655- _c.Call.Return(run)
656- return _c
657-}
658-
659-// Maintain provides a mock function for the type MockSnapshotRepository
660-func (_mock *MockSnapshotRepository) Maintain(context1 context.Context) error {
661- ret := _mock.Called(context1)
662-
663- if len(ret) == 0 {
664- panic("no return value specified for Maintain")
665- }
666-
667- var r0 error
668- if returnFunc, ok := ret.Get(0).(func(context.Context) error); ok {
669- r0 = returnFunc(context1)
670- } else {
671- r0 = ret.Error(0)
672- }
673- return r0
674-}
675-
676-// MockSnapshotRepository_Maintain_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Maintain'
677-type MockSnapshotRepository_Maintain_Call struct {
678- *mock.Call
679-}
680-
681-// Maintain is a helper method to define mock.On call
682-// - context1 context.Context
683-func (_e *MockSnapshotRepository_Expecter) Maintain(context1 any) *MockSnapshotRepository_Maintain_Call {
684- return &MockSnapshotRepository_Maintain_Call{Call: _e.mock.On("Maintain", context1)}
685-}
686-
687-func (_c *MockSnapshotRepository_Maintain_Call) Run(run func(context1 context.Context)) *MockSnapshotRepository_Maintain_Call {
688- _c.Call.Run(func(args mock.Arguments) {
689- var arg0 context.Context
690- if args[0] != nil {
691- arg0 = args[0].(context.Context)
692- }
693- run(
694- arg0,
695- )
696- })
697- return _c
698-}
699-
700-func (_c *MockSnapshotRepository_Maintain_Call) Return(err error) *MockSnapshotRepository_Maintain_Call {
701- _c.Call.Return(err)
702- return _c
703-}
704-
705-func (_c *MockSnapshotRepository_Maintain_Call) RunAndReturn(run func(context1 context.Context) error) *MockSnapshotRepository_Maintain_Call {
706- _c.Call.Return(run)
707- return _c
708-}
709-
710-// Publish provides a mock function for the type MockSnapshotRepository
711-func (_mock *MockSnapshotRepository) Publish(context1 context.Context, snapshotPublication ports.SnapshotPublication) error {
712- ret := _mock.Called(context1, snapshotPublication)
713-
714- if len(ret) == 0 {
715- panic("no return value specified for Publish")
716- }
717-
718- var r0 error
719- if returnFunc, ok := ret.Get(0).(func(context.Context, ports.SnapshotPublication) error); ok {
720- r0 = returnFunc(context1, snapshotPublication)
721- } else {
722- r0 = ret.Error(0)
723- }
724- return r0
725-}
726-
727-// MockSnapshotRepository_Publish_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Publish'
728-type MockSnapshotRepository_Publish_Call struct {
729- *mock.Call
730-}
731-
732-// Publish is a helper method to define mock.On call
733-// - context1 context.Context
734-// - snapshotPublication ports.SnapshotPublication
735-func (_e *MockSnapshotRepository_Expecter) Publish(context1 any, snapshotPublication any) *MockSnapshotRepository_Publish_Call {
736- return &MockSnapshotRepository_Publish_Call{Call: _e.mock.On("Publish", context1, snapshotPublication)}
737-}
738-
739-func (_c *MockSnapshotRepository_Publish_Call) Run(run func(context1 context.Context, snapshotPublication ports.SnapshotPublication)) *MockSnapshotRepository_Publish_Call {
740- _c.Call.Run(func(args mock.Arguments) {
741- var arg0 context.Context
742- if args[0] != nil {
743- arg0 = args[0].(context.Context)
744- }
745- var arg1 ports.SnapshotPublication
746- if args[1] != nil {
747- arg1 = args[1].(ports.SnapshotPublication)
748- }
749- run(
750- arg0,
751- arg1,
752- )
753- })
754- return _c
755-}
756-
757-func (_c *MockSnapshotRepository_Publish_Call) Return(err error) *MockSnapshotRepository_Publish_Call {
758- _c.Call.Return(err)
759- return _c
760-}
761-
762-func (_c *MockSnapshotRepository_Publish_Call) RunAndReturn(run func(context1 context.Context, snapshotPublication ports.SnapshotPublication) error) *MockSnapshotRepository_Publish_Call {
763- _c.Call.Return(run)
764- return _c
765-}
766-
767-// Tombstone provides a mock function for the type MockSnapshotRepository
768-func (_mock *MockSnapshotRepository) Tombstone(context1 context.Context, s string) error {
769- ret := _mock.Called(context1, s)
770-
771- if len(ret) == 0 {
772- panic("no return value specified for Tombstone")
773- }
774-
775- var r0 error
776- if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
777- r0 = returnFunc(context1, s)
778- } else {
779- r0 = ret.Error(0)
780- }
781- return r0
782-}
783-
784-// MockSnapshotRepository_Tombstone_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Tombstone'
785-type MockSnapshotRepository_Tombstone_Call struct {
786- *mock.Call
787-}
788-
789-// Tombstone is a helper method to define mock.On call
790-// - context1 context.Context
791-// - s string
792-func (_e *MockSnapshotRepository_Expecter) Tombstone(context1 any, s any) *MockSnapshotRepository_Tombstone_Call {
793- return &MockSnapshotRepository_Tombstone_Call{Call: _e.mock.On("Tombstone", context1, s)}
794-}
795-
796-func (_c *MockSnapshotRepository_Tombstone_Call) Run(run func(context1 context.Context, s string)) *MockSnapshotRepository_Tombstone_Call {
797- _c.Call.Run(func(args mock.Arguments) {
798- var arg0 context.Context
799- if args[0] != nil {
800- arg0 = args[0].(context.Context)
801- }
802- var arg1 string
803- if args[1] != nil {
804- arg1 = args[1].(string)
805- }
806- run(
807- arg0,
808- arg1,
809- )
810- })
811- return _c
812-}
813-
814-func (_c *MockSnapshotRepository_Tombstone_Call) Return(err error) *MockSnapshotRepository_Tombstone_Call {
815- _c.Call.Return(err)
816- return _c
817-}
818-
819-func (_c *MockSnapshotRepository_Tombstone_Call) RunAndReturn(run func(context1 context.Context, s string) error) *MockSnapshotRepository_Tombstone_Call {
820- _c.Call.Return(run)
821- return _c
822-}
823-
824 // NewMockLegacySnapshotSource creates a new instance of MockLegacySnapshotSource. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.390 // NewMockLegacySnapshotSource creates a new instance of MockLegacySnapshotSource. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
825 // The first argument is typically a *testing.T value.391 // The first argument is typically a *testing.T value.
826 func NewMockLegacySnapshotSource(t interface {392 func NewMockLegacySnapshotSource(t interface {
A internal/ports/recovery.go +143 -0
@@ -0,0 +1,143 @@
1+package ports
2+
3+import (
4+ "context"
5+
6+ "github.com/bnema/vev/internal/domain"
7+)
8+
9+type CheckpointRef = domain.CheckpointRef
10+
11+type SnapshotPublication struct {
12+ IncarnationID domain.IncarnationID
13+ Name string
14+ Generation uint64
15+ ParentCheckpoint *domain.CheckpointRef
16+ Manifest []byte
17+ Objects []SnapshotObject
18+}
19+
20+type SnapshotGeneration struct {
21+ IncarnationID domain.IncarnationID
22+ Name string
23+ Generation uint64
24+ ParentCheckpoint *domain.CheckpointRef
25+ Manifest []byte
26+ Objects map[SnapshotDigest][]byte
27+}
28+
29+type RetentionPlan struct {
30+ IncarnationID domain.IncarnationID
31+ Keep []CheckpointRef
32+ PinAll bool
33+}
34+
35+type MaintenanceBudget struct {
36+ Entries uint64
37+ Bytes uint64
38+}
39+
40+type ReconcileCursor struct{ DirectoryCookie uint64 }
41+
42+type DeletionTombstoneCursor struct{ After string }
43+
44+type DeletionTombstonePage struct {
45+ Tombstones []domain.DeletionTombstone
46+ Next DeletionTombstoneCursor
47+ Done bool
48+}
49+
50+type ReconcileFindingKind uint8
51+
52+const (
53+ ReconcileForwardOrphan ReconcileFindingKind = iota + 1
54+ ReconcileUnknownIncarnation
55+ ReconcileInvalidCandidate
56+)
57+
58+type ReconcileValidationStatus uint8
59+
60+const (
61+ ReconcileValidated ReconcileValidationStatus = iota + 1
62+ ReconcileQuarantined
63+ ReconcileBudgetExhausted
64+)
65+
66+type ReconcileCandidate struct {
67+ IncarnationID domain.IncarnationID
68+ Name string
69+ Ref domain.CheckpointRef
70+ Parent *domain.CheckpointRef
71+}
72+
73+type ValidatedCheckpoint struct {
74+ Ref domain.CheckpointRef
75+ Parent *domain.CheckpointRef
76+}
77+
78+type ReconcileFinding struct {
79+ Kind ReconcileFindingKind
80+ Status ReconcileValidationStatus
81+ Candidate ReconcileCandidate
82+ AncestorChain []ValidatedCheckpoint
83+ Cursor ReconcileCursor
84+ Consumed MaintenanceBudget
85+}
86+
87+type ReconcileDecisionKind uint8
88+
89+const (
90+ ReconcileAdopt ReconcileDecisionKind = iota + 1
91+ ReconcileKeepQuarantined
92+ ReconcileDefer
93+)
94+
95+type ReconcileDecision struct {
96+ Kind ReconcileDecisionKind
97+ Name string
98+ Candidate *domain.CheckpointRef
99+ ReasonCode string
100+}
101+
102+type SnapshotMigrationRequest struct {
103+ LegacyName string
104+ IncarnationID domain.IncarnationID
105+ LegacyRef domain.CheckpointRef
106+}
107+
108+type SnapshotMigration interface {
109+ HasLegacyState(context.Context) (bool, error)
110+ ReadLegacyHEAD(context.Context, string) (domain.CheckpointRef, error)
111+ MigrateV1Checkpoint(context.Context, SnapshotMigrationRequest) (domain.CheckpointRef, error)
112+}
113+
114+type RecoveryJournal interface {
115+ SaveDiscard(context.Context, domain.DiscardIntent) error
116+ ListDiscards(context.Context) ([]domain.DiscardIntent, error)
117+ DeleteDiscard(context.Context, domain.IncarnationID) error
118+}
119+
120+type Catalogue interface {
121+ Records() []domain.CatalogueRecord
122+ Record(string) (domain.CatalogueRecord, bool)
123+ Create(domain.CatalogueRecord) error
124+ Replace(string, domain.CatalogueRecord) error
125+ Rename(string, domain.CatalogueRecord) error
126+ Delete(string) error
127+ Close() error
128+}
129+
130+type SnapshotRepository interface {
131+ Publish(context.Context, SnapshotPublication) error
132+ LoadCheckpoint(context.Context, domain.IncarnationID, string, CheckpointRef) (SnapshotGeneration, error)
133+ RepairHEAD(context.Context, domain.IncarnationID, CheckpointRef) error
134+ WriteDeletionTombstone(context.Context, domain.DeletionTombstone) error
135+ ListDeletionTombstones(context.Context, DeletionTombstoneCursor, MaintenanceBudget) (DeletionTombstonePage, error)
136+ QuarantineDeletionSources(context.Context, domain.DeletionTombstone, bool) error
137+ DeleteDeletionTombstone(context.Context, domain.IncarnationID) error
138+ SaveQuarantineDescriptor(context.Context, domain.QuarantineDescriptor) error
139+ QuarantineIncarnation(context.Context, domain.IncarnationID) error
140+ DeleteIncarnation(context.Context, domain.IncarnationID) error
141+ MaintainSession(context.Context, RetentionPlan, MaintenanceBudget) (bool, error)
142+ Reconcile(context.Context, []domain.CatalogueRecord, ReconcileCursor, MaintenanceBudget) (ReconcileCursor, []ReconcileFinding, error)
143+}
@@ -0,0 +1,143 @@
1+package ports
2+
3+import (
4+ "context"
5+
6+ "github.com/bnema/vev/internal/domain"
7+)
8+
9+type CheckpointRef = domain.CheckpointRef
10+
11+type SnapshotPublication struct {
12+ IncarnationID domain.IncarnationID
13+ Name string
14+ Generation uint64
15+ ParentCheckpoint *domain.CheckpointRef
16+ Manifest []byte
17+ Objects []SnapshotObject
18+}
19+
20+type SnapshotGeneration struct {
21+ IncarnationID domain.IncarnationID
22+ Name string
23+ Generation uint64
24+ ParentCheckpoint *domain.CheckpointRef
25+ Manifest []byte
26+ Objects map[SnapshotDigest][]byte
27+}
28+
29+type RetentionPlan struct {
30+ IncarnationID domain.IncarnationID
31+ Keep []CheckpointRef
32+ PinAll bool
33+}
34+
35+type MaintenanceBudget struct {
36+ Entries uint64
37+ Bytes uint64
38+}
39+
40+type ReconcileCursor struct{ DirectoryCookie uint64 }
41+
42+type DeletionTombstoneCursor struct{ After string }
43+
44+type DeletionTombstonePage struct {
45+ Tombstones []domain.DeletionTombstone
46+ Next DeletionTombstoneCursor
47+ Done bool
48+}
49+
50+type ReconcileFindingKind uint8
51+
52+const (
53+ ReconcileForwardOrphan ReconcileFindingKind = iota + 1
54+ ReconcileUnknownIncarnation
55+ ReconcileInvalidCandidate
56+)
57+
58+type ReconcileValidationStatus uint8
59+
60+const (
61+ ReconcileValidated ReconcileValidationStatus = iota + 1
62+ ReconcileQuarantined
63+ ReconcileBudgetExhausted
64+)
65+
66+type ReconcileCandidate struct {
67+ IncarnationID domain.IncarnationID
68+ Name string
69+ Ref domain.CheckpointRef
70+ Parent *domain.CheckpointRef
71+}
72+
73+type ValidatedCheckpoint struct {
74+ Ref domain.CheckpointRef
75+ Parent *domain.CheckpointRef
76+}
77+
78+type ReconcileFinding struct {
79+ Kind ReconcileFindingKind
80+ Status ReconcileValidationStatus
81+ Candidate ReconcileCandidate
82+ AncestorChain []ValidatedCheckpoint
83+ Cursor ReconcileCursor
84+ Consumed MaintenanceBudget
85+}
86+
87+type ReconcileDecisionKind uint8
88+
89+const (
90+ ReconcileAdopt ReconcileDecisionKind = iota + 1
91+ ReconcileKeepQuarantined
92+ ReconcileDefer
93+)
94+
95+type ReconcileDecision struct {
96+ Kind ReconcileDecisionKind
97+ Name string
98+ Candidate *domain.CheckpointRef
99+ ReasonCode string
100+}
101+
102+type SnapshotMigrationRequest struct {
103+ LegacyName string
104+ IncarnationID domain.IncarnationID
105+ LegacyRef domain.CheckpointRef
106+}
107+
108+type SnapshotMigration interface {
109+ HasLegacyState(context.Context) (bool, error)
110+ ReadLegacyHEAD(context.Context, string) (domain.CheckpointRef, error)
111+ MigrateV1Checkpoint(context.Context, SnapshotMigrationRequest) (domain.CheckpointRef, error)
112+}
113+
114+type RecoveryJournal interface {
115+ SaveDiscard(context.Context, domain.DiscardIntent) error
116+ ListDiscards(context.Context) ([]domain.DiscardIntent, error)
117+ DeleteDiscard(context.Context, domain.IncarnationID) error
118+}
119+
120+type Catalogue interface {
121+ Records() []domain.CatalogueRecord
122+ Record(string) (domain.CatalogueRecord, bool)
123+ Create(domain.CatalogueRecord) error
124+ Replace(string, domain.CatalogueRecord) error
125+ Rename(string, domain.CatalogueRecord) error
126+ Delete(string) error
127+ Close() error
128+}
129+
130+type SnapshotRepository interface {
131+ Publish(context.Context, SnapshotPublication) error
132+ LoadCheckpoint(context.Context, domain.IncarnationID, string, CheckpointRef) (SnapshotGeneration, error)
133+ RepairHEAD(context.Context, domain.IncarnationID, CheckpointRef) error
134+ WriteDeletionTombstone(context.Context, domain.DeletionTombstone) error
135+ ListDeletionTombstones(context.Context, DeletionTombstoneCursor, MaintenanceBudget) (DeletionTombstonePage, error)
136+ QuarantineDeletionSources(context.Context, domain.DeletionTombstone, bool) error
137+ DeleteDeletionTombstone(context.Context, domain.IncarnationID) error
138+ SaveQuarantineDescriptor(context.Context, domain.QuarantineDescriptor) error
139+ QuarantineIncarnation(context.Context, domain.IncarnationID) error
140+ DeleteIncarnation(context.Context, domain.IncarnationID) error
141+ MaintainSession(context.Context, RetentionPlan, MaintenanceBudget) (bool, error)
142+ Reconcile(context.Context, []domain.CatalogueRecord, ReconcileCursor, MaintenanceBudget) (ReconcileCursor, []ReconcileFinding, error)
143+}
M internal/ports/store.go +0 -34
@@ -38,40 +38,6 @@ type SnapshotObject struct {
3838 Data []byte
3939 }
4040
41-// SnapshotPublication atomically publishes a complete VEVM manifest and any
42-// newly reachable content-addressed VEVO objects for one named generation.
43-type SnapshotPublication struct {
44- Name string
45- Generation uint64
46- Manifest []byte
47- Objects []SnapshotObject
48-}
49-
50-// SnapshotGeneration is the caller-owned material needed to restore one
51-// named generation. Fallback indicates that the repository selected an older
52-// valid generation after the requested/current generation was unavailable.
53-type SnapshotGeneration struct {
54- Name string
55- Generation uint64
56- Manifest []byte
57- Objects map[SnapshotDigest][]byte
58- Fallback bool
59-}
60-
61-// SnapshotRepository persists content-addressed incremental snapshot
62-// generations. All returned bytes are owned by the caller.
63-type SnapshotRepository interface {
64- Publish(context.Context, SnapshotPublication) error
65- List(context.Context) ([]string, error)
66- Load(context.Context, string) (SnapshotGeneration, error)
67- Delete(context.Context, string) error
68- // Tombstone fences a named-session purge before either incremental or
69- // legacy data is deleted. It must remain durable until DeleteTombstone.
70- Tombstone(context.Context, string) error
71- DeleteTombstone(context.Context, string) error
72- Maintain(context.Context) error
73-}
74-
7541 // LegacySnapshot is the pre-incremental named-session blob retained only for
7642 // one-way migration. Data is caller-owned.
7743 type LegacySnapshot struct {
@@ -38,40 +38,6 @@ type SnapshotObject struct {
38 Data []byte38 Data []byte
39 }39 }
40 40
41-// SnapshotPublication atomically publishes a complete VEVM manifest and any
42-// newly reachable content-addressed VEVO objects for one named generation.
43-type SnapshotPublication struct {
44- Name string
45- Generation uint64
46- Manifest []byte
47- Objects []SnapshotObject
48-}
49-
50-// SnapshotGeneration is the caller-owned material needed to restore one
51-// named generation. Fallback indicates that the repository selected an older
52-// valid generation after the requested/current generation was unavailable.
53-type SnapshotGeneration struct {
54- Name string
55- Generation uint64
56- Manifest []byte
57- Objects map[SnapshotDigest][]byte
58- Fallback bool
59-}
60-
61-// SnapshotRepository persists content-addressed incremental snapshot
62-// generations. All returned bytes are owned by the caller.
63-type SnapshotRepository interface {
64- Publish(context.Context, SnapshotPublication) error
65- List(context.Context) ([]string, error)
66- Load(context.Context, string) (SnapshotGeneration, error)
67- Delete(context.Context, string) error
68- // Tombstone fences a named-session purge before either incremental or
69- // legacy data is deleted. It must remain durable until DeleteTombstone.
70- Tombstone(context.Context, string) error
71- DeleteTombstone(context.Context, string) error
72- Maintain(context.Context) error
73-}
74-
75 // LegacySnapshot is the pre-incremental named-session blob retained only for41 // LegacySnapshot is the pre-incremental named-session blob retained only for
76 // one-way migration. Data is caller-owned.42 // one-way migration. Data is caller-owned.
77 type LegacySnapshot struct {43 type LegacySnapshot struct {
M internal/usecase/snapshot/manifest.go +65 -9
@@ -7,6 +7,7 @@ import (
77 "hash/crc32"
88 "math"
99
10+ "github.com/bnema/vev/internal/domain"
1011 "github.com/bnema/vev/internal/ports"
1112 "github.com/bnema/vev/internal/usecase/layout"
1213 )
@@ -15,7 +16,7 @@ const (
1516 manifestMagic = "VEVM"
1617 headMagic = "VEVH"
1718 objectMagic = "VEVO"
18- ManifestVersion = uint16(1)
19+ ManifestVersion = uint16(2)
1920 manifestHeaderSize = 16
2021 objectEnvelopeBodyPrefixSize = 1 + 4 // object kind and payload length
2122 minObjectEnvelopeSize = manifestHeaderSize + objectEnvelopeBodyPrefixSize + 1
@@ -96,11 +97,13 @@ func UnmarshalHead(encoded []byte) (Head, error) {
9697
9798 // Manifest is the complete VEVM payload carried directly in a publication.
9899 type Manifest struct {
99- Generation uint64
100- Name string
101- CreatedAt uint64
102- Active uint16
103- Tabs []ManifestTab
100+ Generation uint64
101+ IncarnationID domain.IncarnationID
102+ ParentCheckpoint *domain.CheckpointRef
103+ Name string
104+ CreatedAt uint64
105+ Active uint16
106+ Tabs []ManifestTab
104107 }
105108
106109 type ManifestTab struct {
@@ -129,6 +132,14 @@ func MarshalManifest(m Manifest) ([]byte, error) {
129132 }
130133 var w payloadWriter
131134 w.putUint64(m.Generation)
135+ w.b = append(w.b, m.IncarnationID[:]...)
136+ if m.ParentCheckpoint == nil {
137+ w.putUint8(0)
138+ } else {
139+ w.putUint8(1)
140+ w.putUint64(m.ParentCheckpoint.Generation)
141+ w.b = append(w.b, m.ParentCheckpoint.ManifestDigest[:]...)
142+ }
132143 if err := w.putString(m.Name); err != nil {
133144 return nil, err
134145 }
@@ -168,6 +179,33 @@ func decodeManifest(body []byte) (Manifest, error) {
168179 if err != nil {
169180 return Manifest{}, err
170181 }
182+ if len(r.b) < len(domain.IncarnationID{}) {
183+ return Manifest{}, ErrShortPayload
184+ }
185+ var incarnationID domain.IncarnationID
186+ copy(incarnationID[:], r.b[:len(incarnationID)])
187+ r.b = r.b[len(incarnationID):]
188+ parentPresent, err := r.getUint8()
189+ if err != nil {
190+ return Manifest{}, err
191+ }
192+ var parent *domain.CheckpointRef
193+ switch parentPresent {
194+ case 0:
195+ case 1:
196+ parent = &domain.CheckpointRef{}
197+ parent.Generation, err = r.getUint64()
198+ if err != nil {
199+ return Manifest{}, err
200+ }
201+ if len(r.b) < len(parent.ManifestDigest) {
202+ return Manifest{}, ErrShortPayload
203+ }
204+ copy(parent.ManifestDigest[:], r.b[:len(parent.ManifestDigest)])
205+ r.b = r.b[len(parent.ManifestDigest):]
206+ default:
207+ return Manifest{}, fmt.Errorf("%w: parent checkpoint presence", ErrInvalidData)
208+ }
171209 name, err := r.getString()
172210 if err != nil {
173211 return Manifest{}, err
@@ -184,7 +222,7 @@ func decodeManifest(body []byte) (Manifest, error) {
184222 if err != nil {
185223 return Manifest{}, err
186224 }
187- m := Manifest{Generation: generation, Name: name, CreatedAt: createdAt, Active: active, Tabs: make([]ManifestTab, n)}
225+ m := Manifest{Generation: generation, IncarnationID: incarnationID, ParentCheckpoint: parent, Name: name, CreatedAt: createdAt, Active: active, Tabs: make([]ManifestTab, n)}
188226 for i := range m.Tabs {
189227 if m.Tabs[i], err = readManifestTab(&r); err != nil {
190228 return Manifest{}, err
@@ -257,13 +295,20 @@ func marshalManifestEnvelope(magic string, body []byte) ([]byte, error) {
257295 }
258296 out := make([]byte, manifestHeaderSize+len(body))
259297 copy(out[:4], magic)
260- binary.BigEndian.PutUint16(out[4:6], ManifestVersion)
298+ binary.BigEndian.PutUint16(out[4:6], envelopeVersion(magic))
261299 binary.BigEndian.PutUint32(out[8:12], uint32(len(body)))
262300 binary.BigEndian.PutUint32(out[12:16], crc32.ChecksumIEEE(body))
263301 copy(out[16:], body)
264302 return out, nil
265303 }
266304
305+func envelopeVersion(magic string) uint16 {
306+ if magic == manifestMagic {
307+ return ManifestVersion
308+ }
309+ return 1
310+}
311+
267312 func unmarshalManifestEnvelope(encoded []byte, expectedMagic string) ([]byte, error) {
268313 if len(encoded) < manifestHeaderSize {
269314 return nil, ErrShortPayload
@@ -271,7 +316,7 @@ func unmarshalManifestEnvelope(encoded []byte, expectedMagic string) ([]byte, er
271316 if string(encoded[:4]) != expectedMagic {
272317 return nil, ErrBadMagic
273318 }
274- if binary.BigEndian.Uint16(encoded[4:6]) != ManifestVersion {
319+ if binary.BigEndian.Uint16(encoded[4:6]) != envelopeVersion(expectedMagic) {
275320 return nil, ErrBadVersion
276321 }
277322 if binary.BigEndian.Uint16(encoded[6:8]) != 0 {
@@ -542,6 +587,17 @@ func parseObjectRef(r *payloadReader) (ObjectKind, []byte, uint32, error) {
542587 }
543588
544589 func validateManifest(m Manifest) error {
590+ if m.Generation == 0 {
591+ return fmt.Errorf("%w: manifest generation", ErrInvalidData)
592+ }
593+ if m.IncarnationID == (domain.IncarnationID{}) {
594+ return fmt.Errorf("%w: manifest incarnation", ErrInvalidData)
595+ }
596+ if m.ParentCheckpoint != nil {
597+ if m.ParentCheckpoint.Generation == 0 || m.ParentCheckpoint.Generation >= m.Generation || isZeroDigestBytes(m.ParentCheckpoint.ManifestDigest[:]) {
598+ return fmt.Errorf("%w: parent checkpoint", ErrInvalidData)
599+ }
600+ }
545601 if m.Name == "" {
546602 return fmt.Errorf("%w: manifest name", ErrInvalidData)
547603 }
@@ -7,6 +7,7 @@ import (
7 "hash/crc32"7 "hash/crc32"
8 "math"8 "math"
9 9
10+ "github.com/bnema/vev/internal/domain"
10 "github.com/bnema/vev/internal/ports"11 "github.com/bnema/vev/internal/ports"
11 "github.com/bnema/vev/internal/usecase/layout"12 "github.com/bnema/vev/internal/usecase/layout"
12 )13 )
@@ -15,7 +16,7 @@ const (
15 manifestMagic = "VEVM"16 manifestMagic = "VEVM"
16 headMagic = "VEVH"17 headMagic = "VEVH"
17 objectMagic = "VEVO"18 objectMagic = "VEVO"
18- ManifestVersion = uint16(1)19+ ManifestVersion = uint16(2)
19 manifestHeaderSize = 1620 manifestHeaderSize = 16
20 objectEnvelopeBodyPrefixSize = 1 + 4 // object kind and payload length21 objectEnvelopeBodyPrefixSize = 1 + 4 // object kind and payload length
21 minObjectEnvelopeSize = manifestHeaderSize + objectEnvelopeBodyPrefixSize + 122 minObjectEnvelopeSize = manifestHeaderSize + objectEnvelopeBodyPrefixSize + 1
@@ -96,11 +97,13 @@ func UnmarshalHead(encoded []byte) (Head, error) {
96 97
97 // Manifest is the complete VEVM payload carried directly in a publication.98 // Manifest is the complete VEVM payload carried directly in a publication.
98 type Manifest struct {99 type Manifest struct {
99- Generation uint64100+ Generation uint64
100- Name string101+ IncarnationID domain.IncarnationID
101- CreatedAt uint64102+ ParentCheckpoint *domain.CheckpointRef
102- Active uint16103+ Name string
103- Tabs []ManifestTab104+ CreatedAt uint64
105+ Active uint16
106+ Tabs []ManifestTab
104 }107 }
105 108
106 type ManifestTab struct {109 type ManifestTab struct {
@@ -129,6 +132,14 @@ func MarshalManifest(m Manifest) ([]byte, error) {
129 }132 }
130 var w payloadWriter133 var w payloadWriter
131 w.putUint64(m.Generation)134 w.putUint64(m.Generation)
135+ w.b = append(w.b, m.IncarnationID[:]...)
136+ if m.ParentCheckpoint == nil {
137+ w.putUint8(0)
138+ } else {
139+ w.putUint8(1)
140+ w.putUint64(m.ParentCheckpoint.Generation)
141+ w.b = append(w.b, m.ParentCheckpoint.ManifestDigest[:]...)
142+ }
132 if err := w.putString(m.Name); err != nil {143 if err := w.putString(m.Name); err != nil {
133 return nil, err144 return nil, err
134 }145 }
@@ -168,6 +179,33 @@ func decodeManifest(body []byte) (Manifest, error) {
168 if err != nil {179 if err != nil {
169 return Manifest{}, err180 return Manifest{}, err
170 }181 }
182+ if len(r.b) < len(domain.IncarnationID{}) {
183+ return Manifest{}, ErrShortPayload
184+ }
185+ var incarnationID domain.IncarnationID
186+ copy(incarnationID[:], r.b[:len(incarnationID)])
187+ r.b = r.b[len(incarnationID):]
188+ parentPresent, err := r.getUint8()
189+ if err != nil {
190+ return Manifest{}, err
191+ }
192+ var parent *domain.CheckpointRef
193+ switch parentPresent {
194+ case 0:
195+ case 1:
196+ parent = &domain.CheckpointRef{}
197+ parent.Generation, err = r.getUint64()
198+ if err != nil {
199+ return Manifest{}, err
200+ }
201+ if len(r.b) < len(parent.ManifestDigest) {
202+ return Manifest{}, ErrShortPayload
203+ }
204+ copy(parent.ManifestDigest[:], r.b[:len(parent.ManifestDigest)])
205+ r.b = r.b[len(parent.ManifestDigest):]
206+ default:
207+ return Manifest{}, fmt.Errorf("%w: parent checkpoint presence", ErrInvalidData)
208+ }
171 name, err := r.getString()209 name, err := r.getString()
172 if err != nil {210 if err != nil {
173 return Manifest{}, err211 return Manifest{}, err
@@ -184,7 +222,7 @@ func decodeManifest(body []byte) (Manifest, error) {
184 if err != nil {222 if err != nil {
185 return Manifest{}, err223 return Manifest{}, err
186 }224 }
187- m := Manifest{Generation: generation, Name: name, CreatedAt: createdAt, Active: active, Tabs: make([]ManifestTab, n)}225+ m := Manifest{Generation: generation, IncarnationID: incarnationID, ParentCheckpoint: parent, Name: name, CreatedAt: createdAt, Active: active, Tabs: make([]ManifestTab, n)}
188 for i := range m.Tabs {226 for i := range m.Tabs {
189 if m.Tabs[i], err = readManifestTab(&r); err != nil {227 if m.Tabs[i], err = readManifestTab(&r); err != nil {
190 return Manifest{}, err228 return Manifest{}, err
@@ -257,13 +295,20 @@ func marshalManifestEnvelope(magic string, body []byte) ([]byte, error) {
257 }295 }
258 out := make([]byte, manifestHeaderSize+len(body))296 out := make([]byte, manifestHeaderSize+len(body))
259 copy(out[:4], magic)297 copy(out[:4], magic)
260- binary.BigEndian.PutUint16(out[4:6], ManifestVersion)298+ binary.BigEndian.PutUint16(out[4:6], envelopeVersion(magic))
261 binary.BigEndian.PutUint32(out[8:12], uint32(len(body)))299 binary.BigEndian.PutUint32(out[8:12], uint32(len(body)))
262 binary.BigEndian.PutUint32(out[12:16], crc32.ChecksumIEEE(body))300 binary.BigEndian.PutUint32(out[12:16], crc32.ChecksumIEEE(body))
263 copy(out[16:], body)301 copy(out[16:], body)
264 return out, nil302 return out, nil
265 }303 }
266 304
305+func envelopeVersion(magic string) uint16 {
306+ if magic == manifestMagic {
307+ return ManifestVersion
308+ }
309+ return 1
310+}
311+
267 func unmarshalManifestEnvelope(encoded []byte, expectedMagic string) ([]byte, error) {312 func unmarshalManifestEnvelope(encoded []byte, expectedMagic string) ([]byte, error) {
268 if len(encoded) < manifestHeaderSize {313 if len(encoded) < manifestHeaderSize {
269 return nil, ErrShortPayload314 return nil, ErrShortPayload
@@ -271,7 +316,7 @@ func unmarshalManifestEnvelope(encoded []byte, expectedMagic string) ([]byte, er
271 if string(encoded[:4]) != expectedMagic {316 if string(encoded[:4]) != expectedMagic {
272 return nil, ErrBadMagic317 return nil, ErrBadMagic
273 }318 }
274- if binary.BigEndian.Uint16(encoded[4:6]) != ManifestVersion {319+ if binary.BigEndian.Uint16(encoded[4:6]) != envelopeVersion(expectedMagic) {
275 return nil, ErrBadVersion320 return nil, ErrBadVersion
276 }321 }
277 if binary.BigEndian.Uint16(encoded[6:8]) != 0 {322 if binary.BigEndian.Uint16(encoded[6:8]) != 0 {
@@ -542,6 +587,17 @@ func parseObjectRef(r *payloadReader) (ObjectKind, []byte, uint32, error) {
542 }587 }
543 588
544 func validateManifest(m Manifest) error {589 func validateManifest(m Manifest) error {
590+ if m.Generation == 0 {
591+ return fmt.Errorf("%w: manifest generation", ErrInvalidData)
592+ }
593+ if m.IncarnationID == (domain.IncarnationID{}) {
594+ return fmt.Errorf("%w: manifest incarnation", ErrInvalidData)
595+ }
596+ if m.ParentCheckpoint != nil {
597+ if m.ParentCheckpoint.Generation == 0 || m.ParentCheckpoint.Generation >= m.Generation || isZeroDigestBytes(m.ParentCheckpoint.ManifestDigest[:]) {
598+ return fmt.Errorf("%w: parent checkpoint", ErrInvalidData)
599+ }
600+ }
545 if m.Name == "" {601 if m.Name == "" {
546 return fmt.Errorf("%w: manifest name", ErrInvalidData)602 return fmt.Errorf("%w: manifest name", ErrInvalidData)
547 }603 }
M internal/usecase/snapshot/manifest_preflight.go +34 -1
@@ -3,6 +3,7 @@ package snapshot
33 import (
44 "fmt"
55
6+ "github.com/bnema/vev/internal/domain"
67 "github.com/bnema/vev/internal/usecase/layout"
78 )
89
@@ -11,9 +12,41 @@ import (
1112 func preflightManifest(body []byte) error {
1213 r := payloadReader{b: body}
1314 budget := manifestPreflightBudget{}
14- if _, err := r.getUint64(); err != nil {
15+ generation, err := r.getUint64()
16+ if err != nil {
17+ return err
18+ }
19+ if generation == 0 || len(r.b) < len(domain.IncarnationID{}) {
20+ return ErrShortPayload
21+ }
22+ var incarnationID domain.IncarnationID
23+ copy(incarnationID[:], r.b[:len(incarnationID)])
24+ r.b = r.b[len(incarnationID):]
25+ if incarnationID == (domain.IncarnationID{}) {
26+ return fmt.Errorf("%w: manifest incarnation", ErrInvalidData)
27+ }
28+ parentPresent, err := r.getUint8()
29+ if err != nil {
1530 return err
1631 }
32+ switch parentPresent {
33+ case 0:
34+ case 1:
35+ parentGeneration, err := r.getUint64()
36+ if err != nil {
37+ return err
38+ }
39+ if len(r.b) < 32 {
40+ return ErrShortPayload
41+ }
42+ digest := r.b[:32]
43+ r.b = r.b[32:]
44+ if parentGeneration == 0 || parentGeneration >= generation || isZeroDigestBytes(digest) {
45+ return fmt.Errorf("%w: parent checkpoint", ErrInvalidData)
46+ }
47+ default:
48+ return fmt.Errorf("%w: parent checkpoint presence", ErrInvalidData)
49+ }
1750 name, err := preflightManifestStringBytes(&r, &budget)
1851 if err != nil {
1952 return err
@@ -3,6 +3,7 @@ package snapshot
3 import (3 import (
4 "fmt"4 "fmt"
5 5
6+ "github.com/bnema/vev/internal/domain"
6 "github.com/bnema/vev/internal/usecase/layout"7 "github.com/bnema/vev/internal/usecase/layout"
7 )8 )
8 9
@@ -11,9 +12,41 @@ import (
11 func preflightManifest(body []byte) error {12 func preflightManifest(body []byte) error {
12 r := payloadReader{b: body}13 r := payloadReader{b: body}
13 budget := manifestPreflightBudget{}14 budget := manifestPreflightBudget{}
14- if _, err := r.getUint64(); err != nil {15+ generation, err := r.getUint64()
16+ if err != nil {
17+ return err
18+ }
19+ if generation == 0 || len(r.b) < len(domain.IncarnationID{}) {
20+ return ErrShortPayload
21+ }
22+ var incarnationID domain.IncarnationID
23+ copy(incarnationID[:], r.b[:len(incarnationID)])
24+ r.b = r.b[len(incarnationID):]
25+ if incarnationID == (domain.IncarnationID{}) {
26+ return fmt.Errorf("%w: manifest incarnation", ErrInvalidData)
27+ }
28+ parentPresent, err := r.getUint8()
29+ if err != nil {
15 return err30 return err
16 }31 }
32+ switch parentPresent {
33+ case 0:
34+ case 1:
35+ parentGeneration, err := r.getUint64()
36+ if err != nil {
37+ return err
38+ }
39+ if len(r.b) < 32 {
40+ return ErrShortPayload
41+ }
42+ digest := r.b[:32]
43+ r.b = r.b[32:]
44+ if parentGeneration == 0 || parentGeneration >= generation || isZeroDigestBytes(digest) {
45+ return fmt.Errorf("%w: parent checkpoint", ErrInvalidData)
46+ }
47+ default:
48+ return fmt.Errorf("%w: parent checkpoint presence", ErrInvalidData)
49+ }
17 name, err := preflightManifestStringBytes(&r, &budget)50 name, err := preflightManifestStringBytes(&r, &budget)
18 if err != nil {51 if err != nil {
19 return err52 return err
M internal/usecase/snapshot/manifest_preflight_test.go +2 -1
@@ -6,6 +6,7 @@ import (
66 "hash/crc32"
77 "testing"
88
9+ "github.com/bnema/vev/internal/domain"
910 "github.com/bnema/vev/internal/usecase/layout"
1011 )
1112
@@ -163,7 +164,7 @@ func checksum(body []byte) uint32 {
163164 }
164165
165166 func testManifest(tree *layout.Tree, process *Process) Manifest {
166- return Manifest{Name: "named", Tabs: []ManifestTab{{
167+ return Manifest{Generation: 1, IncarnationID: domain.IncarnationID{1}, Name: "named", Tabs: []ManifestTab{{
167168 StableID: "tab", Cols: 80, Rows: 24, Focus: "one", Tree: tree,
168169 Panes: []ManifestPane{
169170 {ID: "one", StableID: "one", Cwd: "/one", Sealed: []ObjectRef{{Kind: HistoryChunk, Digest: SnapshotDigest{1}, Size: minObjectEnvelopeSize}}, Tail: ObjectRef{Kind: HistoryTail, Digest: SnapshotDigest{2}, Size: minObjectEnvelopeSize}, Visible: ObjectRef{Kind: Visible, Digest: SnapshotDigest{3}, Size: minObjectEnvelopeSize}, Process: process},
@@ -6,6 +6,7 @@ import (
6 "hash/crc32"6 "hash/crc32"
7 "testing"7 "testing"
8 8
9+ "github.com/bnema/vev/internal/domain"
9 "github.com/bnema/vev/internal/usecase/layout"10 "github.com/bnema/vev/internal/usecase/layout"
10 )11 )
11 12
@@ -163,7 +164,7 @@ func checksum(body []byte) uint32 {
163 }164 }
164 165
165 func testManifest(tree *layout.Tree, process *Process) Manifest {166 func testManifest(tree *layout.Tree, process *Process) Manifest {
166- return Manifest{Name: "named", Tabs: []ManifestTab{{167+ return Manifest{Generation: 1, IncarnationID: domain.IncarnationID{1}, Name: "named", Tabs: []ManifestTab{{
167 StableID: "tab", Cols: 80, Rows: 24, Focus: "one", Tree: tree,168 StableID: "tab", Cols: 80, Rows: 24, Focus: "one", Tree: tree,
168 Panes: []ManifestPane{169 Panes: []ManifestPane{
169 {ID: "one", StableID: "one", Cwd: "/one", Sealed: []ObjectRef{{Kind: HistoryChunk, Digest: SnapshotDigest{1}, Size: minObjectEnvelopeSize}}, Tail: ObjectRef{Kind: HistoryTail, Digest: SnapshotDigest{2}, Size: minObjectEnvelopeSize}, Visible: ObjectRef{Kind: Visible, Digest: SnapshotDigest{3}, Size: minObjectEnvelopeSize}, Process: process},170 {ID: "one", StableID: "one", Cwd: "/one", Sealed: []ObjectRef{{Kind: HistoryChunk, Digest: SnapshotDigest{1}, Size: minObjectEnvelopeSize}}, Tail: ObjectRef{Kind: HistoryTail, Digest: SnapshotDigest{2}, Size: minObjectEnvelopeSize}, Visible: ObjectRef{Kind: Visible, Digest: SnapshotDigest{3}, Size: minObjectEnvelopeSize}, Process: process},
M internal/usecase/snapshot/manifest_test.go +49 -4
@@ -3,14 +3,59 @@ package snapshot
33 import (
44 "bytes"
55 "crypto/sha256"
6+ "encoding/binary"
67 "errors"
78 "math"
89 "testing"
910
11+ "github.com/bnema/vev/internal/domain"
1012 "github.com/bnema/vev/internal/ports"
1113 "github.com/bnema/vev/internal/usecase/layout"
1214 )
1315
16+func TestManifestIncarnation(t *testing.T) {
17+ manifest := manifestWithTailSize(minObjectEnvelopeSize)
18+ manifest.Generation = 2
19+ manifest.IncarnationID = domain.IncarnationID{1}
20+ manifest.ParentCheckpoint = &domain.CheckpointRef{Generation: 1, ManifestDigest: [32]byte{2}}
21+
22+ encoded, err := MarshalManifest(manifest)
23+ if err != nil {
24+ t.Fatal(err)
25+ }
26+ decoded, err := UnmarshalManifest(encoded)
27+ if err != nil {
28+ t.Fatal(err)
29+ }
30+ if decoded.IncarnationID != manifest.IncarnationID || decoded.ParentCheckpoint == nil || *decoded.ParentCheckpoint != *manifest.ParentCheckpoint {
31+ t.Fatalf("round trip = %#v", decoded)
32+ }
33+ reencoded, err := MarshalManifest(decoded)
34+ if err != nil || !bytes.Equal(encoded, reencoded) {
35+ t.Fatalf("manifest bytes are not canonical: %v", err)
36+ }
37+ if got := binary.BigEndian.Uint16(encoded[4:6]); got != ManifestVersion {
38+ t.Fatalf("manifest version = %d", got)
39+ }
40+ for n := range len(encoded) {
41+ if _, err := UnmarshalManifest(encoded[:n]); err == nil {
42+ t.Fatalf("prefix %d accepted", n)
43+ }
44+ }
45+ if _, err := UnmarshalManifest(append(encoded, 0)); err == nil {
46+ t.Fatal("trailing data accepted")
47+ }
48+ versionOne := append([]byte(nil), encoded...)
49+ binary.BigEndian.PutUint16(versionOne[4:6], 1)
50+ if _, err := UnmarshalManifest(versionOne); !errors.Is(err, ErrBadVersion) {
51+ t.Fatalf("manifest v1 error = %v", err)
52+ }
53+ manifest.IncarnationID = domain.IncarnationID{}
54+ if _, err := MarshalManifest(manifest); err == nil {
55+ t.Fatal("zero incarnation accepted")
56+ }
57+}
58+
1459 func TestManifestCodecUsesGenerationAndOrderedTypedReferences(t *testing.T) {
1560 chunk, err := MarshalObject(HistoryChunk, []byte("history"))
1661 if err != nil {
@@ -24,7 +69,7 @@ func TestManifestCodecUsesGenerationAndOrderedTypedReferences(t *testing.T) {
2469 if err != nil {
2570 t.Fatal(err)
2671 }
27- manifest := Manifest{Generation: 7, Name: "named", CreatedAt: 9, Active: 0, Tabs: []ManifestTab{{
72+ manifest := Manifest{Generation: 7, IncarnationID: domain.IncarnationID{1}, Name: "named", CreatedAt: 9, Active: 0, Tabs: []ManifestTab{{
2873 StableID: "tab", Cols: 80, Rows: 24, NextPaneID: 2, Focus: "pane", Tree: layout.NewTree("pane"),
2974 Panes: []ManifestPane{{ID: "pane", StableID: "pane-stable", Cwd: "/tmp",
3075 Sealed: []ObjectRef{{Kind: HistoryChunk, Digest: chunk.Digest, Size: uint32(len(chunk.Data))}},
@@ -97,7 +142,7 @@ func TestManifestDimensionBoundaries(t *testing.T) {
97142 {"hostile product", math.MaxUint16, math.MaxUint16, true},
98143 } {
99144 t.Run(tc.name, func(t *testing.T) {
100- m := Manifest{Name: "safe", Tabs: []ManifestTab{{Cols: tc.cols, Rows: tc.rows, Panes: []ManifestPane{{ID: "p", Tail: ref, Visible: visible}}}}}
145+ m := Manifest{Generation: 1, IncarnationID: domain.IncarnationID{1}, Name: "safe", Tabs: []ManifestTab{{Cols: tc.cols, Rows: tc.rows, Panes: []ManifestPane{{ID: "p", Tail: ref, Visible: visible}}}}}
101146 _, err := MarshalManifest(m)
102147 if (err != nil) != tc.wantErr {
103148 t.Fatalf("MarshalManifest() error = %v, want error=%v", err, tc.wantErr)
@@ -107,7 +152,7 @@ func TestManifestDimensionBoundaries(t *testing.T) {
107152 }
108153
109154 func TestManifestRejectsImpossibleObjectEnvelopeSize(t *testing.T) {
110- manifest := Manifest{Name: "named", Tabs: []ManifestTab{{Cols: 1, Rows: 1, Panes: []ManifestPane{{
155+ manifest := Manifest{Generation: 1, IncarnationID: domain.IncarnationID{1}, Name: "named", Tabs: []ManifestTab{{Cols: 1, Rows: 1, Panes: []ManifestPane{{
111156 ID: "pane",
112157 Tail: ObjectRef{Kind: HistoryTail, Digest: SnapshotDigest{1}, Size: 1},
113158 Visible: ObjectRef{Kind: Visible, Digest: SnapshotDigest{2}, Size: minObjectEnvelopeSize},
@@ -171,7 +216,7 @@ func TestObjectRefEnvelopeSizeBoundaries(t *testing.T) {
171216 }
172217
173218 func manifestWithTailSize(size uint32) Manifest {
174- return Manifest{Name: "named", Tabs: []ManifestTab{{
219+ return Manifest{Generation: 1, IncarnationID: domain.IncarnationID{1}, Name: "named", Tabs: []ManifestTab{{
175220 Cols: 1,
176221 Rows: 1,
177222 Panes: []ManifestPane{{
@@ -3,14 +3,59 @@ package snapshot
3 import (3 import (
4 "bytes"4 "bytes"
5 "crypto/sha256"5 "crypto/sha256"
6+ "encoding/binary"
6 "errors"7 "errors"
7 "math"8 "math"
8 "testing"9 "testing"
9 10
11+ "github.com/bnema/vev/internal/domain"
10 "github.com/bnema/vev/internal/ports"12 "github.com/bnema/vev/internal/ports"
11 "github.com/bnema/vev/internal/usecase/layout"13 "github.com/bnema/vev/internal/usecase/layout"
12 )14 )
13 15
16+func TestManifestIncarnation(t *testing.T) {
17+ manifest := manifestWithTailSize(minObjectEnvelopeSize)
18+ manifest.Generation = 2
19+ manifest.IncarnationID = domain.IncarnationID{1}
20+ manifest.ParentCheckpoint = &domain.CheckpointRef{Generation: 1, ManifestDigest: [32]byte{2}}
21+
22+ encoded, err := MarshalManifest(manifest)
23+ if err != nil {
24+ t.Fatal(err)
25+ }
26+ decoded, err := UnmarshalManifest(encoded)
27+ if err != nil {
28+ t.Fatal(err)
29+ }
30+ if decoded.IncarnationID != manifest.IncarnationID || decoded.ParentCheckpoint == nil || *decoded.ParentCheckpoint != *manifest.ParentCheckpoint {
31+ t.Fatalf("round trip = %#v", decoded)
32+ }
33+ reencoded, err := MarshalManifest(decoded)
34+ if err != nil || !bytes.Equal(encoded, reencoded) {
35+ t.Fatalf("manifest bytes are not canonical: %v", err)
36+ }
37+ if got := binary.BigEndian.Uint16(encoded[4:6]); got != ManifestVersion {
38+ t.Fatalf("manifest version = %d", got)
39+ }
40+ for n := range len(encoded) {
41+ if _, err := UnmarshalManifest(encoded[:n]); err == nil {
42+ t.Fatalf("prefix %d accepted", n)
43+ }
44+ }
45+ if _, err := UnmarshalManifest(append(encoded, 0)); err == nil {
46+ t.Fatal("trailing data accepted")
47+ }
48+ versionOne := append([]byte(nil), encoded...)
49+ binary.BigEndian.PutUint16(versionOne[4:6], 1)
50+ if _, err := UnmarshalManifest(versionOne); !errors.Is(err, ErrBadVersion) {
51+ t.Fatalf("manifest v1 error = %v", err)
52+ }
53+ manifest.IncarnationID = domain.IncarnationID{}
54+ if _, err := MarshalManifest(manifest); err == nil {
55+ t.Fatal("zero incarnation accepted")
56+ }
57+}
58+
14 func TestManifestCodecUsesGenerationAndOrderedTypedReferences(t *testing.T) {59 func TestManifestCodecUsesGenerationAndOrderedTypedReferences(t *testing.T) {
15 chunk, err := MarshalObject(HistoryChunk, []byte("history"))60 chunk, err := MarshalObject(HistoryChunk, []byte("history"))
16 if err != nil {61 if err != nil {
@@ -24,7 +69,7 @@ func TestManifestCodecUsesGenerationAndOrderedTypedReferences(t *testing.T) {
24 if err != nil {69 if err != nil {
25 t.Fatal(err)70 t.Fatal(err)
26 }71 }
27- manifest := Manifest{Generation: 7, Name: "named", CreatedAt: 9, Active: 0, Tabs: []ManifestTab{{72+ manifest := Manifest{Generation: 7, IncarnationID: domain.IncarnationID{1}, Name: "named", CreatedAt: 9, Active: 0, Tabs: []ManifestTab{{
28 StableID: "tab", Cols: 80, Rows: 24, NextPaneID: 2, Focus: "pane", Tree: layout.NewTree("pane"),73 StableID: "tab", Cols: 80, Rows: 24, NextPaneID: 2, Focus: "pane", Tree: layout.NewTree("pane"),
29 Panes: []ManifestPane{{ID: "pane", StableID: "pane-stable", Cwd: "/tmp",74 Panes: []ManifestPane{{ID: "pane", StableID: "pane-stable", Cwd: "/tmp",
30 Sealed: []ObjectRef{{Kind: HistoryChunk, Digest: chunk.Digest, Size: uint32(len(chunk.Data))}},75 Sealed: []ObjectRef{{Kind: HistoryChunk, Digest: chunk.Digest, Size: uint32(len(chunk.Data))}},
@@ -97,7 +142,7 @@ func TestManifestDimensionBoundaries(t *testing.T) {
97 {"hostile product", math.MaxUint16, math.MaxUint16, true},142 {"hostile product", math.MaxUint16, math.MaxUint16, true},
98 } {143 } {
99 t.Run(tc.name, func(t *testing.T) {144 t.Run(tc.name, func(t *testing.T) {
100- m := Manifest{Name: "safe", Tabs: []ManifestTab{{Cols: tc.cols, Rows: tc.rows, Panes: []ManifestPane{{ID: "p", Tail: ref, Visible: visible}}}}}145+ m := Manifest{Generation: 1, IncarnationID: domain.IncarnationID{1}, Name: "safe", Tabs: []ManifestTab{{Cols: tc.cols, Rows: tc.rows, Panes: []ManifestPane{{ID: "p", Tail: ref, Visible: visible}}}}}
101 _, err := MarshalManifest(m)146 _, err := MarshalManifest(m)
102 if (err != nil) != tc.wantErr {147 if (err != nil) != tc.wantErr {
103 t.Fatalf("MarshalManifest() error = %v, want error=%v", err, tc.wantErr)148 t.Fatalf("MarshalManifest() error = %v, want error=%v", err, tc.wantErr)
@@ -107,7 +152,7 @@ func TestManifestDimensionBoundaries(t *testing.T) {
107 }152 }
108 153
109 func TestManifestRejectsImpossibleObjectEnvelopeSize(t *testing.T) {154 func TestManifestRejectsImpossibleObjectEnvelopeSize(t *testing.T) {
110- manifest := Manifest{Name: "named", Tabs: []ManifestTab{{Cols: 1, Rows: 1, Panes: []ManifestPane{{155+ manifest := Manifest{Generation: 1, IncarnationID: domain.IncarnationID{1}, Name: "named", Tabs: []ManifestTab{{Cols: 1, Rows: 1, Panes: []ManifestPane{{
111 ID: "pane",156 ID: "pane",
112 Tail: ObjectRef{Kind: HistoryTail, Digest: SnapshotDigest{1}, Size: 1},157 Tail: ObjectRef{Kind: HistoryTail, Digest: SnapshotDigest{1}, Size: 1},
113 Visible: ObjectRef{Kind: Visible, Digest: SnapshotDigest{2}, Size: minObjectEnvelopeSize},158 Visible: ObjectRef{Kind: Visible, Digest: SnapshotDigest{2}, Size: minObjectEnvelopeSize},
@@ -171,7 +216,7 @@ func TestObjectRefEnvelopeSizeBoundaries(t *testing.T) {
171 }216 }
172 217
173 func manifestWithTailSize(size uint32) Manifest {218 func manifestWithTailSize(size uint32) Manifest {
174- return Manifest{Name: "named", Tabs: []ManifestTab{{219+ return Manifest{Generation: 1, IncarnationID: domain.IncarnationID{1}, Name: "named", Tabs: []ManifestTab{{
175 Cols: 1,220 Cols: 1,
176 Rows: 1,221 Rows: 1,
177 Panes: []ManifestPane{{222 Panes: []ManifestPane{{
M internal/usecase/snapshot/weights_test.go +3 -3
@@ -86,10 +86,10 @@ func TestSnapshotEnvelopeCompatibility(t *testing.T) {
8686 const (
8787 headGolden = "56455648000100000000002898ebcac200000000000000070100000000000000000000000000000000000000000000000000000000000000"
8888 objectGolden = "5645564f00010000000000111cb8f09f030000000c63616e6f6e6963616c2d7674"
89- manifestGolden = "5645564d0001000000000122f8f8f0af000000000000000000056e616d6564000000000000000000000001000374616200500018000000000000000000036f6e65010000020000036f6e6500000374776f000200036f6e6500036f6e6500042f6f6e65000101010000000000000000000000000000000000000000000000000000000000000000000016020200000000000000000000000000000000000000000000000000000000000000000000160303000000000000000000000000000000000000000000000000000000000000000000001600000374776f000374776f00042f74776f0000020400000000000000000000000000000000000000000000000000000000000000000000160305000000000000000000000000000000000000000000000000000000000000000000001600"
89+ manifestGolden = "5645564d0002000000000133d118cc030000000000000001010000000000000000000000000000000000056e616d6564000000000000000000000001000374616200500018000000000000000000036f6e65010000020000036f6e6500000374776f000200036f6e6500036f6e6500042f6f6e65000101010000000000000000000000000000000000000000000000000000000000000000000016020200000000000000000000000000000000000000000000000000000000000000000000160303000000000000000000000000000000000000000000000000000000000000000000001600000374776f000374776f00042f74776f0000020400000000000000000000000000000000000000000000000000000000000000000000160305000000000000000000000000000000000000000000000000000000000000000000001600"
9090 )
91- if ManifestVersion != 1 {
92- t.Fatalf("ManifestVersion = %d, want 1", ManifestVersion)
91+ if ManifestVersion != 2 {
92+ t.Fatalf("ManifestVersion = %d, want 2", ManifestVersion)
9393 }
9494
9595 head, err := MarshalHead(Head{Generation: 7, ManifestDigest: SnapshotDigest{1}})
@@ -86,10 +86,10 @@ func TestSnapshotEnvelopeCompatibility(t *testing.T) {
86 const (86 const (
87 headGolden = "56455648000100000000002898ebcac200000000000000070100000000000000000000000000000000000000000000000000000000000000"87 headGolden = "56455648000100000000002898ebcac200000000000000070100000000000000000000000000000000000000000000000000000000000000"
88 objectGolden = "5645564f00010000000000111cb8f09f030000000c63616e6f6e6963616c2d7674"88 objectGolden = "5645564f00010000000000111cb8f09f030000000c63616e6f6e6963616c2d7674"
89- manifestGolden = "5645564d0001000000000122f8f8f0af000000000000000000056e616d6564000000000000000000000001000374616200500018000000000000000000036f6e65010000020000036f6e6500000374776f000200036f6e6500036f6e6500042f6f6e65000101010000000000000000000000000000000000000000000000000000000000000000000016020200000000000000000000000000000000000000000000000000000000000000000000160303000000000000000000000000000000000000000000000000000000000000000000001600000374776f000374776f00042f74776f0000020400000000000000000000000000000000000000000000000000000000000000000000160305000000000000000000000000000000000000000000000000000000000000000000001600"89+ manifestGolden = "5645564d0002000000000133d118cc030000000000000001010000000000000000000000000000000000056e616d6564000000000000000000000001000374616200500018000000000000000000036f6e65010000020000036f6e6500000374776f000200036f6e6500036f6e6500042f6f6e65000101010000000000000000000000000000000000000000000000000000000000000000000016020200000000000000000000000000000000000000000000000000000000000000000000160303000000000000000000000000000000000000000000000000000000000000000000001600000374776f000374776f00042f74776f0000020400000000000000000000000000000000000000000000000000000000000000000000160305000000000000000000000000000000000000000000000000000000000000000000001600"
90 )90 )
91- if ManifestVersion != 1 {91+ if ManifestVersion != 2 {
92- t.Fatalf("ManifestVersion = %d, want 1", ManifestVersion)92+ t.Fatalf("ManifestVersion = %d, want 2", ManifestVersion)
93 }93 }
94 94
95 head, err := MarshalHead(Head{Generation: 7, ManifestDigest: SnapshotDigest{1}})95 head, err := MarshalHead(Head{Generation: 7, ManifestDigest: SnapshotDigest{1}})

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close