detached 5b0ff57

refactor(ports): collapse the recovery ports to catalogue and repository

commit
5b0ff57197154d22bdf3c66271d420a767f30626
author
brice · · 2026-07-26 11:40 UTC
parent
6bbcb8d
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
10 files · +67 -568
M internal/ports/mocks/recovery_mock.go +0 -477
@@ -12,483 +12,6 @@ import (
1212 mock "github.com/stretchr/testify/mock"
1313 )
1414
15-// NewMockCheckpointCoordinator creates a new instance of MockCheckpointCoordinator. 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 NewMockCheckpointCoordinator(t interface {
18- mock.TestingT
19- Cleanup(func())
20-}) *MockCheckpointCoordinator {
21- mock := &MockCheckpointCoordinator{}
22- mock.Mock.Test(t)
23-
24- t.Cleanup(func() { mock.AssertExpectations(t) })
25-
26- return mock
27-}
28-
29-// MockCheckpointCoordinator is an autogenerated mock type for the CheckpointCoordinator type
30-type MockCheckpointCoordinator struct {
31- mock.Mock
32-}
33-
34-type MockCheckpointCoordinator_Expecter struct {
35- mock *mock.Mock
36-}
37-
38-func (_m *MockCheckpointCoordinator) EXPECT() *MockCheckpointCoordinator_Expecter {
39- return &MockCheckpointCoordinator_Expecter{mock: &_m.Mock}
40-}
41-
42-// PublishCheckpoint provides a mock function for the type MockCheckpointCoordinator
43-func (_mock *MockCheckpointCoordinator) PublishCheckpoint(context1 context.Context, s string, snapshotPublication ports.SnapshotPublication) (domain.CatalogueRecord, error) {
44- ret := _mock.Called(context1, s, snapshotPublication)
45-
46- if len(ret) == 0 {
47- panic("no return value specified for PublishCheckpoint")
48- }
49-
50- var r0 domain.CatalogueRecord
51- var r1 error
52- if returnFunc, ok := ret.Get(0).(func(context.Context, string, ports.SnapshotPublication) (domain.CatalogueRecord, error)); ok {
53- return returnFunc(context1, s, snapshotPublication)
54- }
55- if returnFunc, ok := ret.Get(0).(func(context.Context, string, ports.SnapshotPublication) domain.CatalogueRecord); ok {
56- r0 = returnFunc(context1, s, snapshotPublication)
57- } else {
58- r0 = ret.Get(0).(domain.CatalogueRecord)
59- }
60- if returnFunc, ok := ret.Get(1).(func(context.Context, string, ports.SnapshotPublication) error); ok {
61- r1 = returnFunc(context1, s, snapshotPublication)
62- } else {
63- r1 = ret.Error(1)
64- }
65- return r0, r1
66-}
67-
68-// MockCheckpointCoordinator_PublishCheckpoint_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PublishCheckpoint'
69-type MockCheckpointCoordinator_PublishCheckpoint_Call struct {
70- *mock.Call
71-}
72-
73-// PublishCheckpoint is a helper method to define mock.On call
74-// - context1 context.Context
75-// - s string
76-// - snapshotPublication ports.SnapshotPublication
77-func (_e *MockCheckpointCoordinator_Expecter) PublishCheckpoint(context1 any, s any, snapshotPublication any) *MockCheckpointCoordinator_PublishCheckpoint_Call {
78- return &MockCheckpointCoordinator_PublishCheckpoint_Call{Call: _e.mock.On("PublishCheckpoint", context1, s, snapshotPublication)}
79-}
80-
81-func (_c *MockCheckpointCoordinator_PublishCheckpoint_Call) Run(run func(context1 context.Context, s string, snapshotPublication ports.SnapshotPublication)) *MockCheckpointCoordinator_PublishCheckpoint_Call {
82- _c.Call.Run(func(args mock.Arguments) {
83- var arg0 context.Context
84- if args[0] != nil {
85- arg0 = args[0].(context.Context)
86- }
87- var arg1 string
88- if args[1] != nil {
89- arg1 = args[1].(string)
90- }
91- var arg2 ports.SnapshotPublication
92- if args[2] != nil {
93- arg2 = args[2].(ports.SnapshotPublication)
94- }
95- run(
96- arg0,
97- arg1,
98- arg2,
99- )
100- })
101- return _c
102-}
103-
104-func (_c *MockCheckpointCoordinator_PublishCheckpoint_Call) Return(catalogueRecord domain.CatalogueRecord, err error) *MockCheckpointCoordinator_PublishCheckpoint_Call {
105- _c.Call.Return(catalogueRecord, err)
106- return _c
107-}
108-
109-func (_c *MockCheckpointCoordinator_PublishCheckpoint_Call) RunAndReturn(run func(context1 context.Context, s string, snapshotPublication ports.SnapshotPublication) (domain.CatalogueRecord, error)) *MockCheckpointCoordinator_PublishCheckpoint_Call {
110- _c.Call.Return(run)
111- return _c
112-}
113-
114-// NewMockSessionLifecycleCoordinator creates a new instance of MockSessionLifecycleCoordinator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
115-// The first argument is typically a *testing.T value.
116-func NewMockSessionLifecycleCoordinator(t interface {
117- mock.TestingT
118- Cleanup(func())
119-}) *MockSessionLifecycleCoordinator {
120- mock := &MockSessionLifecycleCoordinator{}
121- mock.Mock.Test(t)
122-
123- t.Cleanup(func() { mock.AssertExpectations(t) })
124-
125- return mock
126-}
127-
128-// MockSessionLifecycleCoordinator is an autogenerated mock type for the SessionLifecycleCoordinator type
129-type MockSessionLifecycleCoordinator struct {
130- mock.Mock
131-}
132-
133-type MockSessionLifecycleCoordinator_Expecter struct {
134- mock *mock.Mock
135-}
136-
137-func (_m *MockSessionLifecycleCoordinator) EXPECT() *MockSessionLifecycleCoordinator_Expecter {
138- return &MockSessionLifecycleCoordinator_Expecter{mock: &_m.Mock}
139-}
140-
141-// Create provides a mock function for the type MockSessionLifecycleCoordinator
142-func (_mock *MockSessionLifecycleCoordinator) Create(context1 context.Context, catalogueRecord domain.CatalogueRecord) (domain.CatalogueRecord, error) {
143- ret := _mock.Called(context1, catalogueRecord)
144-
145- if len(ret) == 0 {
146- panic("no return value specified for Create")
147- }
148-
149- var r0 domain.CatalogueRecord
150- var r1 error
151- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.CatalogueRecord) (domain.CatalogueRecord, error)); ok {
152- return returnFunc(context1, catalogueRecord)
153- }
154- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.CatalogueRecord) domain.CatalogueRecord); ok {
155- r0 = returnFunc(context1, catalogueRecord)
156- } else {
157- r0 = ret.Get(0).(domain.CatalogueRecord)
158- }
159- if returnFunc, ok := ret.Get(1).(func(context.Context, domain.CatalogueRecord) error); ok {
160- r1 = returnFunc(context1, catalogueRecord)
161- } else {
162- r1 = ret.Error(1)
163- }
164- return r0, r1
165-}
166-
167-// MockSessionLifecycleCoordinator_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
168-type MockSessionLifecycleCoordinator_Create_Call struct {
169- *mock.Call
170-}
171-
172-// Create is a helper method to define mock.On call
173-// - context1 context.Context
174-// - catalogueRecord domain.CatalogueRecord
175-func (_e *MockSessionLifecycleCoordinator_Expecter) Create(context1 any, catalogueRecord any) *MockSessionLifecycleCoordinator_Create_Call {
176- return &MockSessionLifecycleCoordinator_Create_Call{Call: _e.mock.On("Create", context1, catalogueRecord)}
177-}
178-
179-func (_c *MockSessionLifecycleCoordinator_Create_Call) Run(run func(context1 context.Context, catalogueRecord domain.CatalogueRecord)) *MockSessionLifecycleCoordinator_Create_Call {
180- _c.Call.Run(func(args mock.Arguments) {
181- var arg0 context.Context
182- if args[0] != nil {
183- arg0 = args[0].(context.Context)
184- }
185- var arg1 domain.CatalogueRecord
186- if args[1] != nil {
187- arg1 = args[1].(domain.CatalogueRecord)
188- }
189- run(
190- arg0,
191- arg1,
192- )
193- })
194- return _c
195-}
196-
197-func (_c *MockSessionLifecycleCoordinator_Create_Call) Return(catalogueRecord1 domain.CatalogueRecord, err error) *MockSessionLifecycleCoordinator_Create_Call {
198- _c.Call.Return(catalogueRecord1, err)
199- return _c
200-}
201-
202-func (_c *MockSessionLifecycleCoordinator_Create_Call) RunAndReturn(run func(context1 context.Context, catalogueRecord domain.CatalogueRecord) (domain.CatalogueRecord, error)) *MockSessionLifecycleCoordinator_Create_Call {
203- _c.Call.Return(run)
204- return _c
205-}
206-
207-// Delete provides a mock function for the type MockSessionLifecycleCoordinator
208-func (_mock *MockSessionLifecycleCoordinator) Delete(context1 context.Context, s string) error {
209- ret := _mock.Called(context1, s)
210-
211- if len(ret) == 0 {
212- panic("no return value specified for Delete")
213- }
214-
215- var r0 error
216- if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
217- r0 = returnFunc(context1, s)
218- } else {
219- r0 = ret.Error(0)
220- }
221- return r0
222-}
223-
224-// MockSessionLifecycleCoordinator_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
225-type MockSessionLifecycleCoordinator_Delete_Call struct {
226- *mock.Call
227-}
228-
229-// Delete is a helper method to define mock.On call
230-// - context1 context.Context
231-// - s string
232-func (_e *MockSessionLifecycleCoordinator_Expecter) Delete(context1 any, s any) *MockSessionLifecycleCoordinator_Delete_Call {
233- return &MockSessionLifecycleCoordinator_Delete_Call{Call: _e.mock.On("Delete", context1, s)}
234-}
235-
236-func (_c *MockSessionLifecycleCoordinator_Delete_Call) Run(run func(context1 context.Context, s string)) *MockSessionLifecycleCoordinator_Delete_Call {
237- _c.Call.Run(func(args mock.Arguments) {
238- var arg0 context.Context
239- if args[0] != nil {
240- arg0 = args[0].(context.Context)
241- }
242- var arg1 string
243- if args[1] != nil {
244- arg1 = args[1].(string)
245- }
246- run(
247- arg0,
248- arg1,
249- )
250- })
251- return _c
252-}
253-
254-func (_c *MockSessionLifecycleCoordinator_Delete_Call) Return(err error) *MockSessionLifecycleCoordinator_Delete_Call {
255- _c.Call.Return(err)
256- return _c
257-}
258-
259-func (_c *MockSessionLifecycleCoordinator_Delete_Call) RunAndReturn(run func(context1 context.Context, s string) error) *MockSessionLifecycleCoordinator_Delete_Call {
260- _c.Call.Return(run)
261- return _c
262-}
263-
264-// PublishCheckpoint provides a mock function for the type MockSessionLifecycleCoordinator
265-func (_mock *MockSessionLifecycleCoordinator) PublishCheckpoint(context1 context.Context, s string, snapshotPublication ports.SnapshotPublication) (domain.CatalogueRecord, error) {
266- ret := _mock.Called(context1, s, snapshotPublication)
267-
268- if len(ret) == 0 {
269- panic("no return value specified for PublishCheckpoint")
270- }
271-
272- var r0 domain.CatalogueRecord
273- var r1 error
274- if returnFunc, ok := ret.Get(0).(func(context.Context, string, ports.SnapshotPublication) (domain.CatalogueRecord, error)); ok {
275- return returnFunc(context1, s, snapshotPublication)
276- }
277- if returnFunc, ok := ret.Get(0).(func(context.Context, string, ports.SnapshotPublication) domain.CatalogueRecord); ok {
278- r0 = returnFunc(context1, s, snapshotPublication)
279- } else {
280- r0 = ret.Get(0).(domain.CatalogueRecord)
281- }
282- if returnFunc, ok := ret.Get(1).(func(context.Context, string, ports.SnapshotPublication) error); ok {
283- r1 = returnFunc(context1, s, snapshotPublication)
284- } else {
285- r1 = ret.Error(1)
286- }
287- return r0, r1
288-}
289-
290-// MockSessionLifecycleCoordinator_PublishCheckpoint_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PublishCheckpoint'
291-type MockSessionLifecycleCoordinator_PublishCheckpoint_Call struct {
292- *mock.Call
293-}
294-
295-// PublishCheckpoint is a helper method to define mock.On call
296-// - context1 context.Context
297-// - s string
298-// - snapshotPublication ports.SnapshotPublication
299-func (_e *MockSessionLifecycleCoordinator_Expecter) PublishCheckpoint(context1 any, s any, snapshotPublication any) *MockSessionLifecycleCoordinator_PublishCheckpoint_Call {
300- return &MockSessionLifecycleCoordinator_PublishCheckpoint_Call{Call: _e.mock.On("PublishCheckpoint", context1, s, snapshotPublication)}
301-}
302-
303-func (_c *MockSessionLifecycleCoordinator_PublishCheckpoint_Call) Run(run func(context1 context.Context, s string, snapshotPublication ports.SnapshotPublication)) *MockSessionLifecycleCoordinator_PublishCheckpoint_Call {
304- _c.Call.Run(func(args mock.Arguments) {
305- var arg0 context.Context
306- if args[0] != nil {
307- arg0 = args[0].(context.Context)
308- }
309- var arg1 string
310- if args[1] != nil {
311- arg1 = args[1].(string)
312- }
313- var arg2 ports.SnapshotPublication
314- if args[2] != nil {
315- arg2 = args[2].(ports.SnapshotPublication)
316- }
317- run(
318- arg0,
319- arg1,
320- arg2,
321- )
322- })
323- return _c
324-}
325-
326-func (_c *MockSessionLifecycleCoordinator_PublishCheckpoint_Call) Return(catalogueRecord domain.CatalogueRecord, err error) *MockSessionLifecycleCoordinator_PublishCheckpoint_Call {
327- _c.Call.Return(catalogueRecord, err)
328- return _c
329-}
330-
331-func (_c *MockSessionLifecycleCoordinator_PublishCheckpoint_Call) RunAndReturn(run func(context1 context.Context, s string, snapshotPublication ports.SnapshotPublication) (domain.CatalogueRecord, error)) *MockSessionLifecycleCoordinator_PublishCheckpoint_Call {
332- _c.Call.Return(run)
333- return _c
334-}
335-
336-// Rename provides a mock function for the type MockSessionLifecycleCoordinator
337-func (_mock *MockSessionLifecycleCoordinator) Rename(context1 context.Context, s string, s1 string) (domain.CatalogueRecord, error) {
338- ret := _mock.Called(context1, s, s1)
339-
340- if len(ret) == 0 {
341- panic("no return value specified for Rename")
342- }
343-
344- var r0 domain.CatalogueRecord
345- var r1 error
346- if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) (domain.CatalogueRecord, error)); ok {
347- return returnFunc(context1, s, s1)
348- }
349- if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) domain.CatalogueRecord); ok {
350- r0 = returnFunc(context1, s, s1)
351- } else {
352- r0 = ret.Get(0).(domain.CatalogueRecord)
353- }
354- if returnFunc, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
355- r1 = returnFunc(context1, s, s1)
356- } else {
357- r1 = ret.Error(1)
358- }
359- return r0, r1
360-}
361-
362-// MockSessionLifecycleCoordinator_Rename_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Rename'
363-type MockSessionLifecycleCoordinator_Rename_Call struct {
364- *mock.Call
365-}
366-
367-// Rename is a helper method to define mock.On call
368-// - context1 context.Context
369-// - s string
370-// - s1 string
371-func (_e *MockSessionLifecycleCoordinator_Expecter) Rename(context1 any, s any, s1 any) *MockSessionLifecycleCoordinator_Rename_Call {
372- return &MockSessionLifecycleCoordinator_Rename_Call{Call: _e.mock.On("Rename", context1, s, s1)}
373-}
374-
375-func (_c *MockSessionLifecycleCoordinator_Rename_Call) Run(run func(context1 context.Context, s string, s1 string)) *MockSessionLifecycleCoordinator_Rename_Call {
376- _c.Call.Run(func(args mock.Arguments) {
377- var arg0 context.Context
378- if args[0] != nil {
379- arg0 = args[0].(context.Context)
380- }
381- var arg1 string
382- if args[1] != nil {
383- arg1 = args[1].(string)
384- }
385- var arg2 string
386- if args[2] != nil {
387- arg2 = args[2].(string)
388- }
389- run(
390- arg0,
391- arg1,
392- arg2,
393- )
394- })
395- return _c
396-}
397-
398-func (_c *MockSessionLifecycleCoordinator_Rename_Call) Return(catalogueRecord domain.CatalogueRecord, err error) *MockSessionLifecycleCoordinator_Rename_Call {
399- _c.Call.Return(catalogueRecord, err)
400- return _c
401-}
402-
403-func (_c *MockSessionLifecycleCoordinator_Rename_Call) RunAndReturn(run func(context1 context.Context, s string, s1 string) (domain.CatalogueRecord, error)) *MockSessionLifecycleCoordinator_Rename_Call {
404- _c.Call.Return(run)
405- return _c
406-}
407-
408-// NewMockDegradedRecoveryCoordinator creates a new instance of MockDegradedRecoveryCoordinator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
409-// The first argument is typically a *testing.T value.
410-func NewMockDegradedRecoveryCoordinator(t interface {
411- mock.TestingT
412- Cleanup(func())
413-}) *MockDegradedRecoveryCoordinator {
414- mock := &MockDegradedRecoveryCoordinator{}
415- mock.Mock.Test(t)
416-
417- t.Cleanup(func() { mock.AssertExpectations(t) })
418-
419- return mock
420-}
421-
422-// MockDegradedRecoveryCoordinator is an autogenerated mock type for the DegradedRecoveryCoordinator type
423-type MockDegradedRecoveryCoordinator struct {
424- mock.Mock
425-}
426-
427-type MockDegradedRecoveryCoordinator_Expecter struct {
428- mock *mock.Mock
429-}
430-
431-func (_m *MockDegradedRecoveryCoordinator) EXPECT() *MockDegradedRecoveryCoordinator_Expecter {
432- return &MockDegradedRecoveryCoordinator_Expecter{mock: &_m.Mock}
433-}
434-
435-// Discard provides a mock function for the type MockDegradedRecoveryCoordinator
436-func (_mock *MockDegradedRecoveryCoordinator) Discard(context1 context.Context, s string) error {
437- ret := _mock.Called(context1, s)
438-
439- if len(ret) == 0 {
440- panic("no return value specified for Discard")
441- }
442-
443- var r0 error
444- if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
445- r0 = returnFunc(context1, s)
446- } else {
447- r0 = ret.Error(0)
448- }
449- return r0
450-}
451-
452-// MockDegradedRecoveryCoordinator_Discard_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Discard'
453-type MockDegradedRecoveryCoordinator_Discard_Call struct {
454- *mock.Call
455-}
456-
457-// Discard is a helper method to define mock.On call
458-// - context1 context.Context
459-// - s string
460-func (_e *MockDegradedRecoveryCoordinator_Expecter) Discard(context1 any, s any) *MockDegradedRecoveryCoordinator_Discard_Call {
461- return &MockDegradedRecoveryCoordinator_Discard_Call{Call: _e.mock.On("Discard", context1, s)}
462-}
463-
464-func (_c *MockDegradedRecoveryCoordinator_Discard_Call) Run(run func(context1 context.Context, s string)) *MockDegradedRecoveryCoordinator_Discard_Call {
465- _c.Call.Run(func(args mock.Arguments) {
466- var arg0 context.Context
467- if args[0] != nil {
468- arg0 = args[0].(context.Context)
469- }
470- var arg1 string
471- if args[1] != nil {
472- arg1 = args[1].(string)
473- }
474- run(
475- arg0,
476- arg1,
477- )
478- })
479- return _c
480-}
481-
482-func (_c *MockDegradedRecoveryCoordinator_Discard_Call) Return(err error) *MockDegradedRecoveryCoordinator_Discard_Call {
483- _c.Call.Return(err)
484- return _c
485-}
486-
487-func (_c *MockDegradedRecoveryCoordinator_Discard_Call) RunAndReturn(run func(context1 context.Context, s string) error) *MockDegradedRecoveryCoordinator_Discard_Call {
488- _c.Call.Return(run)
489- return _c
490-}
491-
49215 // 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.
49316 // The first argument is typically a *testing.T value.
49417 func NewMockCatalogue(t interface {
@@ -12,483 +12,6 @@ import (
12 mock "github.com/stretchr/testify/mock"12 mock "github.com/stretchr/testify/mock"
13 )13 )
14 14
15-// NewMockCheckpointCoordinator creates a new instance of MockCheckpointCoordinator. 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 NewMockCheckpointCoordinator(t interface {
18- mock.TestingT
19- Cleanup(func())
20-}) *MockCheckpointCoordinator {
21- mock := &MockCheckpointCoordinator{}
22- mock.Mock.Test(t)
23-
24- t.Cleanup(func() { mock.AssertExpectations(t) })
25-
26- return mock
27-}
28-
29-// MockCheckpointCoordinator is an autogenerated mock type for the CheckpointCoordinator type
30-type MockCheckpointCoordinator struct {
31- mock.Mock
32-}
33-
34-type MockCheckpointCoordinator_Expecter struct {
35- mock *mock.Mock
36-}
37-
38-func (_m *MockCheckpointCoordinator) EXPECT() *MockCheckpointCoordinator_Expecter {
39- return &MockCheckpointCoordinator_Expecter{mock: &_m.Mock}
40-}
41-
42-// PublishCheckpoint provides a mock function for the type MockCheckpointCoordinator
43-func (_mock *MockCheckpointCoordinator) PublishCheckpoint(context1 context.Context, s string, snapshotPublication ports.SnapshotPublication) (domain.CatalogueRecord, error) {
44- ret := _mock.Called(context1, s, snapshotPublication)
45-
46- if len(ret) == 0 {
47- panic("no return value specified for PublishCheckpoint")
48- }
49-
50- var r0 domain.CatalogueRecord
51- var r1 error
52- if returnFunc, ok := ret.Get(0).(func(context.Context, string, ports.SnapshotPublication) (domain.CatalogueRecord, error)); ok {
53- return returnFunc(context1, s, snapshotPublication)
54- }
55- if returnFunc, ok := ret.Get(0).(func(context.Context, string, ports.SnapshotPublication) domain.CatalogueRecord); ok {
56- r0 = returnFunc(context1, s, snapshotPublication)
57- } else {
58- r0 = ret.Get(0).(domain.CatalogueRecord)
59- }
60- if returnFunc, ok := ret.Get(1).(func(context.Context, string, ports.SnapshotPublication) error); ok {
61- r1 = returnFunc(context1, s, snapshotPublication)
62- } else {
63- r1 = ret.Error(1)
64- }
65- return r0, r1
66-}
67-
68-// MockCheckpointCoordinator_PublishCheckpoint_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PublishCheckpoint'
69-type MockCheckpointCoordinator_PublishCheckpoint_Call struct {
70- *mock.Call
71-}
72-
73-// PublishCheckpoint is a helper method to define mock.On call
74-// - context1 context.Context
75-// - s string
76-// - snapshotPublication ports.SnapshotPublication
77-func (_e *MockCheckpointCoordinator_Expecter) PublishCheckpoint(context1 any, s any, snapshotPublication any) *MockCheckpointCoordinator_PublishCheckpoint_Call {
78- return &MockCheckpointCoordinator_PublishCheckpoint_Call{Call: _e.mock.On("PublishCheckpoint", context1, s, snapshotPublication)}
79-}
80-
81-func (_c *MockCheckpointCoordinator_PublishCheckpoint_Call) Run(run func(context1 context.Context, s string, snapshotPublication ports.SnapshotPublication)) *MockCheckpointCoordinator_PublishCheckpoint_Call {
82- _c.Call.Run(func(args mock.Arguments) {
83- var arg0 context.Context
84- if args[0] != nil {
85- arg0 = args[0].(context.Context)
86- }
87- var arg1 string
88- if args[1] != nil {
89- arg1 = args[1].(string)
90- }
91- var arg2 ports.SnapshotPublication
92- if args[2] != nil {
93- arg2 = args[2].(ports.SnapshotPublication)
94- }
95- run(
96- arg0,
97- arg1,
98- arg2,
99- )
100- })
101- return _c
102-}
103-
104-func (_c *MockCheckpointCoordinator_PublishCheckpoint_Call) Return(catalogueRecord domain.CatalogueRecord, err error) *MockCheckpointCoordinator_PublishCheckpoint_Call {
105- _c.Call.Return(catalogueRecord, err)
106- return _c
107-}
108-
109-func (_c *MockCheckpointCoordinator_PublishCheckpoint_Call) RunAndReturn(run func(context1 context.Context, s string, snapshotPublication ports.SnapshotPublication) (domain.CatalogueRecord, error)) *MockCheckpointCoordinator_PublishCheckpoint_Call {
110- _c.Call.Return(run)
111- return _c
112-}
113-
114-// NewMockSessionLifecycleCoordinator creates a new instance of MockSessionLifecycleCoordinator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
115-// The first argument is typically a *testing.T value.
116-func NewMockSessionLifecycleCoordinator(t interface {
117- mock.TestingT
118- Cleanup(func())
119-}) *MockSessionLifecycleCoordinator {
120- mock := &MockSessionLifecycleCoordinator{}
121- mock.Mock.Test(t)
122-
123- t.Cleanup(func() { mock.AssertExpectations(t) })
124-
125- return mock
126-}
127-
128-// MockSessionLifecycleCoordinator is an autogenerated mock type for the SessionLifecycleCoordinator type
129-type MockSessionLifecycleCoordinator struct {
130- mock.Mock
131-}
132-
133-type MockSessionLifecycleCoordinator_Expecter struct {
134- mock *mock.Mock
135-}
136-
137-func (_m *MockSessionLifecycleCoordinator) EXPECT() *MockSessionLifecycleCoordinator_Expecter {
138- return &MockSessionLifecycleCoordinator_Expecter{mock: &_m.Mock}
139-}
140-
141-// Create provides a mock function for the type MockSessionLifecycleCoordinator
142-func (_mock *MockSessionLifecycleCoordinator) Create(context1 context.Context, catalogueRecord domain.CatalogueRecord) (domain.CatalogueRecord, error) {
143- ret := _mock.Called(context1, catalogueRecord)
144-
145- if len(ret) == 0 {
146- panic("no return value specified for Create")
147- }
148-
149- var r0 domain.CatalogueRecord
150- var r1 error
151- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.CatalogueRecord) (domain.CatalogueRecord, error)); ok {
152- return returnFunc(context1, catalogueRecord)
153- }
154- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.CatalogueRecord) domain.CatalogueRecord); ok {
155- r0 = returnFunc(context1, catalogueRecord)
156- } else {
157- r0 = ret.Get(0).(domain.CatalogueRecord)
158- }
159- if returnFunc, ok := ret.Get(1).(func(context.Context, domain.CatalogueRecord) error); ok {
160- r1 = returnFunc(context1, catalogueRecord)
161- } else {
162- r1 = ret.Error(1)
163- }
164- return r0, r1
165-}
166-
167-// MockSessionLifecycleCoordinator_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
168-type MockSessionLifecycleCoordinator_Create_Call struct {
169- *mock.Call
170-}
171-
172-// Create is a helper method to define mock.On call
173-// - context1 context.Context
174-// - catalogueRecord domain.CatalogueRecord
175-func (_e *MockSessionLifecycleCoordinator_Expecter) Create(context1 any, catalogueRecord any) *MockSessionLifecycleCoordinator_Create_Call {
176- return &MockSessionLifecycleCoordinator_Create_Call{Call: _e.mock.On("Create", context1, catalogueRecord)}
177-}
178-
179-func (_c *MockSessionLifecycleCoordinator_Create_Call) Run(run func(context1 context.Context, catalogueRecord domain.CatalogueRecord)) *MockSessionLifecycleCoordinator_Create_Call {
180- _c.Call.Run(func(args mock.Arguments) {
181- var arg0 context.Context
182- if args[0] != nil {
183- arg0 = args[0].(context.Context)
184- }
185- var arg1 domain.CatalogueRecord
186- if args[1] != nil {
187- arg1 = args[1].(domain.CatalogueRecord)
188- }
189- run(
190- arg0,
191- arg1,
192- )
193- })
194- return _c
195-}
196-
197-func (_c *MockSessionLifecycleCoordinator_Create_Call) Return(catalogueRecord1 domain.CatalogueRecord, err error) *MockSessionLifecycleCoordinator_Create_Call {
198- _c.Call.Return(catalogueRecord1, err)
199- return _c
200-}
201-
202-func (_c *MockSessionLifecycleCoordinator_Create_Call) RunAndReturn(run func(context1 context.Context, catalogueRecord domain.CatalogueRecord) (domain.CatalogueRecord, error)) *MockSessionLifecycleCoordinator_Create_Call {
203- _c.Call.Return(run)
204- return _c
205-}
206-
207-// Delete provides a mock function for the type MockSessionLifecycleCoordinator
208-func (_mock *MockSessionLifecycleCoordinator) Delete(context1 context.Context, s string) error {
209- ret := _mock.Called(context1, s)
210-
211- if len(ret) == 0 {
212- panic("no return value specified for Delete")
213- }
214-
215- var r0 error
216- if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
217- r0 = returnFunc(context1, s)
218- } else {
219- r0 = ret.Error(0)
220- }
221- return r0
222-}
223-
224-// MockSessionLifecycleCoordinator_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
225-type MockSessionLifecycleCoordinator_Delete_Call struct {
226- *mock.Call
227-}
228-
229-// Delete is a helper method to define mock.On call
230-// - context1 context.Context
231-// - s string
232-func (_e *MockSessionLifecycleCoordinator_Expecter) Delete(context1 any, s any) *MockSessionLifecycleCoordinator_Delete_Call {
233- return &MockSessionLifecycleCoordinator_Delete_Call{Call: _e.mock.On("Delete", context1, s)}
234-}
235-
236-func (_c *MockSessionLifecycleCoordinator_Delete_Call) Run(run func(context1 context.Context, s string)) *MockSessionLifecycleCoordinator_Delete_Call {
237- _c.Call.Run(func(args mock.Arguments) {
238- var arg0 context.Context
239- if args[0] != nil {
240- arg0 = args[0].(context.Context)
241- }
242- var arg1 string
243- if args[1] != nil {
244- arg1 = args[1].(string)
245- }
246- run(
247- arg0,
248- arg1,
249- )
250- })
251- return _c
252-}
253-
254-func (_c *MockSessionLifecycleCoordinator_Delete_Call) Return(err error) *MockSessionLifecycleCoordinator_Delete_Call {
255- _c.Call.Return(err)
256- return _c
257-}
258-
259-func (_c *MockSessionLifecycleCoordinator_Delete_Call) RunAndReturn(run func(context1 context.Context, s string) error) *MockSessionLifecycleCoordinator_Delete_Call {
260- _c.Call.Return(run)
261- return _c
262-}
263-
264-// PublishCheckpoint provides a mock function for the type MockSessionLifecycleCoordinator
265-func (_mock *MockSessionLifecycleCoordinator) PublishCheckpoint(context1 context.Context, s string, snapshotPublication ports.SnapshotPublication) (domain.CatalogueRecord, error) {
266- ret := _mock.Called(context1, s, snapshotPublication)
267-
268- if len(ret) == 0 {
269- panic("no return value specified for PublishCheckpoint")
270- }
271-
272- var r0 domain.CatalogueRecord
273- var r1 error
274- if returnFunc, ok := ret.Get(0).(func(context.Context, string, ports.SnapshotPublication) (domain.CatalogueRecord, error)); ok {
275- return returnFunc(context1, s, snapshotPublication)
276- }
277- if returnFunc, ok := ret.Get(0).(func(context.Context, string, ports.SnapshotPublication) domain.CatalogueRecord); ok {
278- r0 = returnFunc(context1, s, snapshotPublication)
279- } else {
280- r0 = ret.Get(0).(domain.CatalogueRecord)
281- }
282- if returnFunc, ok := ret.Get(1).(func(context.Context, string, ports.SnapshotPublication) error); ok {
283- r1 = returnFunc(context1, s, snapshotPublication)
284- } else {
285- r1 = ret.Error(1)
286- }
287- return r0, r1
288-}
289-
290-// MockSessionLifecycleCoordinator_PublishCheckpoint_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PublishCheckpoint'
291-type MockSessionLifecycleCoordinator_PublishCheckpoint_Call struct {
292- *mock.Call
293-}
294-
295-// PublishCheckpoint is a helper method to define mock.On call
296-// - context1 context.Context
297-// - s string
298-// - snapshotPublication ports.SnapshotPublication
299-func (_e *MockSessionLifecycleCoordinator_Expecter) PublishCheckpoint(context1 any, s any, snapshotPublication any) *MockSessionLifecycleCoordinator_PublishCheckpoint_Call {
300- return &MockSessionLifecycleCoordinator_PublishCheckpoint_Call{Call: _e.mock.On("PublishCheckpoint", context1, s, snapshotPublication)}
301-}
302-
303-func (_c *MockSessionLifecycleCoordinator_PublishCheckpoint_Call) Run(run func(context1 context.Context, s string, snapshotPublication ports.SnapshotPublication)) *MockSessionLifecycleCoordinator_PublishCheckpoint_Call {
304- _c.Call.Run(func(args mock.Arguments) {
305- var arg0 context.Context
306- if args[0] != nil {
307- arg0 = args[0].(context.Context)
308- }
309- var arg1 string
310- if args[1] != nil {
311- arg1 = args[1].(string)
312- }
313- var arg2 ports.SnapshotPublication
314- if args[2] != nil {
315- arg2 = args[2].(ports.SnapshotPublication)
316- }
317- run(
318- arg0,
319- arg1,
320- arg2,
321- )
322- })
323- return _c
324-}
325-
326-func (_c *MockSessionLifecycleCoordinator_PublishCheckpoint_Call) Return(catalogueRecord domain.CatalogueRecord, err error) *MockSessionLifecycleCoordinator_PublishCheckpoint_Call {
327- _c.Call.Return(catalogueRecord, err)
328- return _c
329-}
330-
331-func (_c *MockSessionLifecycleCoordinator_PublishCheckpoint_Call) RunAndReturn(run func(context1 context.Context, s string, snapshotPublication ports.SnapshotPublication) (domain.CatalogueRecord, error)) *MockSessionLifecycleCoordinator_PublishCheckpoint_Call {
332- _c.Call.Return(run)
333- return _c
334-}
335-
336-// Rename provides a mock function for the type MockSessionLifecycleCoordinator
337-func (_mock *MockSessionLifecycleCoordinator) Rename(context1 context.Context, s string, s1 string) (domain.CatalogueRecord, error) {
338- ret := _mock.Called(context1, s, s1)
339-
340- if len(ret) == 0 {
341- panic("no return value specified for Rename")
342- }
343-
344- var r0 domain.CatalogueRecord
345- var r1 error
346- if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) (domain.CatalogueRecord, error)); ok {
347- return returnFunc(context1, s, s1)
348- }
349- if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) domain.CatalogueRecord); ok {
350- r0 = returnFunc(context1, s, s1)
351- } else {
352- r0 = ret.Get(0).(domain.CatalogueRecord)
353- }
354- if returnFunc, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
355- r1 = returnFunc(context1, s, s1)
356- } else {
357- r1 = ret.Error(1)
358- }
359- return r0, r1
360-}
361-
362-// MockSessionLifecycleCoordinator_Rename_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Rename'
363-type MockSessionLifecycleCoordinator_Rename_Call struct {
364- *mock.Call
365-}
366-
367-// Rename is a helper method to define mock.On call
368-// - context1 context.Context
369-// - s string
370-// - s1 string
371-func (_e *MockSessionLifecycleCoordinator_Expecter) Rename(context1 any, s any, s1 any) *MockSessionLifecycleCoordinator_Rename_Call {
372- return &MockSessionLifecycleCoordinator_Rename_Call{Call: _e.mock.On("Rename", context1, s, s1)}
373-}
374-
375-func (_c *MockSessionLifecycleCoordinator_Rename_Call) Run(run func(context1 context.Context, s string, s1 string)) *MockSessionLifecycleCoordinator_Rename_Call {
376- _c.Call.Run(func(args mock.Arguments) {
377- var arg0 context.Context
378- if args[0] != nil {
379- arg0 = args[0].(context.Context)
380- }
381- var arg1 string
382- if args[1] != nil {
383- arg1 = args[1].(string)
384- }
385- var arg2 string
386- if args[2] != nil {
387- arg2 = args[2].(string)
388- }
389- run(
390- arg0,
391- arg1,
392- arg2,
393- )
394- })
395- return _c
396-}
397-
398-func (_c *MockSessionLifecycleCoordinator_Rename_Call) Return(catalogueRecord domain.CatalogueRecord, err error) *MockSessionLifecycleCoordinator_Rename_Call {
399- _c.Call.Return(catalogueRecord, err)
400- return _c
401-}
402-
403-func (_c *MockSessionLifecycleCoordinator_Rename_Call) RunAndReturn(run func(context1 context.Context, s string, s1 string) (domain.CatalogueRecord, error)) *MockSessionLifecycleCoordinator_Rename_Call {
404- _c.Call.Return(run)
405- return _c
406-}
407-
408-// NewMockDegradedRecoveryCoordinator creates a new instance of MockDegradedRecoveryCoordinator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
409-// The first argument is typically a *testing.T value.
410-func NewMockDegradedRecoveryCoordinator(t interface {
411- mock.TestingT
412- Cleanup(func())
413-}) *MockDegradedRecoveryCoordinator {
414- mock := &MockDegradedRecoveryCoordinator{}
415- mock.Mock.Test(t)
416-
417- t.Cleanup(func() { mock.AssertExpectations(t) })
418-
419- return mock
420-}
421-
422-// MockDegradedRecoveryCoordinator is an autogenerated mock type for the DegradedRecoveryCoordinator type
423-type MockDegradedRecoveryCoordinator struct {
424- mock.Mock
425-}
426-
427-type MockDegradedRecoveryCoordinator_Expecter struct {
428- mock *mock.Mock
429-}
430-
431-func (_m *MockDegradedRecoveryCoordinator) EXPECT() *MockDegradedRecoveryCoordinator_Expecter {
432- return &MockDegradedRecoveryCoordinator_Expecter{mock: &_m.Mock}
433-}
434-
435-// Discard provides a mock function for the type MockDegradedRecoveryCoordinator
436-func (_mock *MockDegradedRecoveryCoordinator) Discard(context1 context.Context, s string) error {
437- ret := _mock.Called(context1, s)
438-
439- if len(ret) == 0 {
440- panic("no return value specified for Discard")
441- }
442-
443- var r0 error
444- if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
445- r0 = returnFunc(context1, s)
446- } else {
447- r0 = ret.Error(0)
448- }
449- return r0
450-}
451-
452-// MockDegradedRecoveryCoordinator_Discard_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Discard'
453-type MockDegradedRecoveryCoordinator_Discard_Call struct {
454- *mock.Call
455-}
456-
457-// Discard is a helper method to define mock.On call
458-// - context1 context.Context
459-// - s string
460-func (_e *MockDegradedRecoveryCoordinator_Expecter) Discard(context1 any, s any) *MockDegradedRecoveryCoordinator_Discard_Call {
461- return &MockDegradedRecoveryCoordinator_Discard_Call{Call: _e.mock.On("Discard", context1, s)}
462-}
463-
464-func (_c *MockDegradedRecoveryCoordinator_Discard_Call) Run(run func(context1 context.Context, s string)) *MockDegradedRecoveryCoordinator_Discard_Call {
465- _c.Call.Run(func(args mock.Arguments) {
466- var arg0 context.Context
467- if args[0] != nil {
468- arg0 = args[0].(context.Context)
469- }
470- var arg1 string
471- if args[1] != nil {
472- arg1 = args[1].(string)
473- }
474- run(
475- arg0,
476- arg1,
477- )
478- })
479- return _c
480-}
481-
482-func (_c *MockDegradedRecoveryCoordinator_Discard_Call) Return(err error) *MockDegradedRecoveryCoordinator_Discard_Call {
483- _c.Call.Return(err)
484- return _c
485-}
486-
487-func (_c *MockDegradedRecoveryCoordinator_Discard_Call) RunAndReturn(run func(context1 context.Context, s string) error) *MockDegradedRecoveryCoordinator_Discard_Call {
488- _c.Call.Return(run)
489- return _c
490-}
491-
492 // 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.15 // 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.
493 // The first argument is typically a *testing.T value.16 // The first argument is typically a *testing.T value.
494 func NewMockCatalogue(t interface {17 func NewMockCatalogue(t interface {
M internal/ports/recovery.go +0 -19
@@ -26,25 +26,6 @@ type SnapshotGeneration struct {
2626 Objects map[SnapshotDigest][]byte
2727 }
2828
29-type CheckpointCoordinator interface {
30- PublishCheckpoint(context.Context, string, SnapshotPublication) (domain.CatalogueRecord, error)
31-}
32-
33-// SessionLifecycleCoordinator owns durable named-session create, rename, and
34-// delete commit protocols. Runtime registries update only after these calls
35-// complete successfully.
36-type SessionLifecycleCoordinator interface {
37- CheckpointCoordinator
38- Create(context.Context, domain.CatalogueRecord) (domain.CatalogueRecord, error)
39- Rename(context.Context, string, string) (domain.CatalogueRecord, error)
40- Delete(context.Context, string) error
41-}
42-
43-// DegradedRecoveryCoordinator exposes only the explicit operator discard action.
44-type DegradedRecoveryCoordinator interface {
45- Discard(context.Context, string) error
46-}
47-
4829 type Catalogue interface {
4930 Records() ([]domain.CatalogueRecord, error)
5031 Record(string) (domain.CatalogueRecord, bool, error)
@@ -26,25 +26,6 @@ type SnapshotGeneration struct {
26 Objects map[SnapshotDigest][]byte26 Objects map[SnapshotDigest][]byte
27 }27 }
28 28
29-type CheckpointCoordinator interface {
30- PublishCheckpoint(context.Context, string, SnapshotPublication) (domain.CatalogueRecord, error)
31-}
32-
33-// SessionLifecycleCoordinator owns durable named-session create, rename, and
34-// delete commit protocols. Runtime registries update only after these calls
35-// complete successfully.
36-type SessionLifecycleCoordinator interface {
37- CheckpointCoordinator
38- Create(context.Context, domain.CatalogueRecord) (domain.CatalogueRecord, error)
39- Rename(context.Context, string, string) (domain.CatalogueRecord, error)
40- Delete(context.Context, string) error
41-}
42-
43-// DegradedRecoveryCoordinator exposes only the explicit operator discard action.
44-type DegradedRecoveryCoordinator interface {
45- Discard(context.Context, string) error
46-}
47-
48 type Catalogue interface {29 type Catalogue interface {
49 Records() ([]domain.CatalogueRecord, error)30 Records() ([]domain.CatalogueRecord, error)
50 Record(string) (domain.CatalogueRecord, bool, error)31 Record(string) (domain.CatalogueRecord, bool, error)
M internal/usecase/daemon/control.go +2 -2
@@ -455,7 +455,7 @@ func (e controlExec) Toast(severity, message string) error {
455455 }
456456
457457 func (e controlExec) SessionRecovery(action string) (string, error) {
458- if e.d == nil || e.d.degradedRecovery == nil || e.recoveryName == "" || action != "discard" {
458+ if e.d == nil || e.d.recovery == nil || e.recoveryName == "" || action != "discard" {
459459 return "", command.ErrInvalidArguments
460460 }
461461 ctx := e.ctx
@@ -465,7 +465,7 @@ func (e controlExec) SessionRecovery(action string) (string, error) {
465465 if ctx == nil {
466466 ctx = context.Background()
467467 }
468- if err := e.d.degradedRecovery.Discard(ctx, e.recoveryName); err != nil {
468+ if err := e.d.recovery.Discard(ctx, e.recoveryName); err != nil {
469469 return "", err
470470 }
471471 record, ok, err := e.d.catalogue.Record(e.recoveryName)
@@ -455,7 +455,7 @@ func (e controlExec) Toast(severity, message string) error {
455 }455 }
456 456
457 func (e controlExec) SessionRecovery(action string) (string, error) {457 func (e controlExec) SessionRecovery(action string) (string, error) {
458- if e.d == nil || e.d.degradedRecovery == nil || e.recoveryName == "" || action != "discard" {458+ if e.d == nil || e.d.recovery == nil || e.recoveryName == "" || action != "discard" {
459 return "", command.ErrInvalidArguments459 return "", command.ErrInvalidArguments
460 }460 }
461 ctx := e.ctx461 ctx := e.ctx
@@ -465,7 +465,7 @@ func (e controlExec) SessionRecovery(action string) (string, error) {
465 if ctx == nil {465 if ctx == nil {
466 ctx = context.Background()466 ctx = context.Background()
467 }467 }
468- if err := e.d.degradedRecovery.Discard(ctx, e.recoveryName); err != nil {468+ if err := e.d.recovery.Discard(ctx, e.recoveryName); err != nil {
469 return "", err469 return "", err
470 }470 }
471 record, ok, err := e.d.catalogue.Record(e.recoveryName)471 record, ok, err := e.d.catalogue.Record(e.recoveryName)
M internal/usecase/daemon/daemon.go +6 -19
@@ -16,6 +16,7 @@ import (
1616 "github.com/bnema/vev/internal/domain"
1717 "github.com/bnema/vev/internal/ports"
1818 "github.com/bnema/vev/internal/usecase/keys"
19+ recoveryusecase "github.com/bnema/vev/internal/usecase/recovery"
1920 )
2021
2122 // Scheduler debounce bounds. Idle updates use the minimum for low latency;
@@ -123,11 +124,9 @@ type Daemon struct {
123124 catalogue ports.Catalogue
124125 catalogueRecords []domain.CatalogueRecord
125126 catalogueRecordsProvided bool
126- // snapshotRepository is the sole checkpoint contract.
127+ // snapshotRepository is the sole checkpoint storage contract.
127128 snapshotRepository ports.SnapshotRepository
128- checkpointRecovery ports.CheckpointCoordinator
129- lifecycleRecovery ports.SessionLifecycleCoordinator
130- degradedRecovery ports.DegradedRecoveryCoordinator
129+ recovery *recoveryusecase.Coordinator
131130 maintenance maintenanceDependencies
132131 maintenanceWorkerCancel context.CancelFunc
133132 maintenanceWorkerDone chan struct{}
@@ -276,22 +275,10 @@ func WithCatalogue(catalogue ports.Catalogue, records []domain.CatalogueRecord)
276275 }
277276 }
278277
279-// WithCheckpointCoordinator installs the shared durable checkpoint transaction seam.
280-func WithCheckpointCoordinator(coordinator ports.CheckpointCoordinator) Option {
278+// WithRecoveryCoordinator installs the durable recovery coordinator.
279+func WithRecoveryCoordinator(coordinator *recoveryusecase.Coordinator) Option {
281280 return func(d *Daemon) {
282- d.checkpointRecovery = coordinator
283- }
284-}
285-
286-// WithRecoveryCoordinator installs all durable checkpoint and named-session
287-// lifecycle transaction seams.
288-func WithRecoveryCoordinator(coordinator ports.SessionLifecycleCoordinator) Option {
289- return func(d *Daemon) {
290- d.checkpointRecovery = coordinator
291- d.lifecycleRecovery = coordinator
292- if degraded, ok := coordinator.(ports.DegradedRecoveryCoordinator); ok {
293- d.degradedRecovery = degraded
294- }
281+ d.recovery = coordinator
295282 }
296283 }
297284
@@ -16,6 +16,7 @@ import (
16 "github.com/bnema/vev/internal/domain"16 "github.com/bnema/vev/internal/domain"
17 "github.com/bnema/vev/internal/ports"17 "github.com/bnema/vev/internal/ports"
18 "github.com/bnema/vev/internal/usecase/keys"18 "github.com/bnema/vev/internal/usecase/keys"
19+ recoveryusecase "github.com/bnema/vev/internal/usecase/recovery"
19 )20 )
20 21
21 // Scheduler debounce bounds. Idle updates use the minimum for low latency;22 // Scheduler debounce bounds. Idle updates use the minimum for low latency;
@@ -123,11 +124,9 @@ type Daemon struct {
123 catalogue ports.Catalogue124 catalogue ports.Catalogue
124 catalogueRecords []domain.CatalogueRecord125 catalogueRecords []domain.CatalogueRecord
125 catalogueRecordsProvided bool126 catalogueRecordsProvided bool
126- // snapshotRepository is the sole checkpoint contract.127+ // snapshotRepository is the sole checkpoint storage contract.
127 snapshotRepository ports.SnapshotRepository128 snapshotRepository ports.SnapshotRepository
128- checkpointRecovery ports.CheckpointCoordinator129+ recovery *recoveryusecase.Coordinator
129- lifecycleRecovery ports.SessionLifecycleCoordinator
130- degradedRecovery ports.DegradedRecoveryCoordinator
131 maintenance maintenanceDependencies130 maintenance maintenanceDependencies
132 maintenanceWorkerCancel context.CancelFunc131 maintenanceWorkerCancel context.CancelFunc
133 maintenanceWorkerDone chan struct{}132 maintenanceWorkerDone chan struct{}
@@ -276,22 +275,10 @@ func WithCatalogue(catalogue ports.Catalogue, records []domain.CatalogueRecord)
276 }275 }
277 }276 }
278 277
279-// WithCheckpointCoordinator installs the shared durable checkpoint transaction seam.278+// WithRecoveryCoordinator installs the durable recovery coordinator.
280-func WithCheckpointCoordinator(coordinator ports.CheckpointCoordinator) Option {279+func WithRecoveryCoordinator(coordinator *recoveryusecase.Coordinator) Option {
281 return func(d *Daemon) {280 return func(d *Daemon) {
282- d.checkpointRecovery = coordinator281+ d.recovery = coordinator
283- }
284-}
285-
286-// WithRecoveryCoordinator installs all durable checkpoint and named-session
287-// lifecycle transaction seams.
288-func WithRecoveryCoordinator(coordinator ports.SessionLifecycleCoordinator) Option {
289- return func(d *Daemon) {
290- d.checkpointRecovery = coordinator
291- d.lifecycleRecovery = coordinator
292- if degraded, ok := coordinator.(ports.DegradedRecoveryCoordinator); ok {
293- d.degradedRecovery = degraded
294- }
295 }282 }
296 }283 }
297 284
M internal/usecase/daemon/durable_authority_test.go +1 -3
@@ -41,9 +41,7 @@ func WithStore(t testing.TB, store ports.Store) Option {
4141 if repository == nil {
4242 repository = testLifecycleRepository{}
4343 }
44- authority := recoveryusecase.NewCoordinator(catalogue, repository, rand.Reader)
45- d.lifecycleRecovery = authority
46- d.checkpointRecovery = authority
44+ d.recovery = recoveryusecase.NewCoordinator(catalogue, repository, rand.Reader)
4745 }
4846 }
4947
@@ -41,9 +41,7 @@ func WithStore(t testing.TB, store ports.Store) Option {
41 if repository == nil {41 if repository == nil {
42 repository = testLifecycleRepository{}42 repository = testLifecycleRepository{}
43 }43 }
44- authority := recoveryusecase.NewCoordinator(catalogue, repository, rand.Reader)44+ d.recovery = recoveryusecase.NewCoordinator(catalogue, repository, rand.Reader)
45- d.lifecycleRecovery = authority
46- d.checkpointRecovery = authority
47 }45 }
48 }46 }
49 47
M internal/usecase/daemon/durable_recovery_test.go +31 -30
@@ -1,6 +1,7 @@
11 package daemon
22
33 import (
4+ "bytes"
45 "context"
56 "errors"
67 "fmt"
@@ -13,6 +14,7 @@ import (
1314 "testing"
1415 "time"
1516
17+ "github.com/stretchr/testify/mock"
1618 "github.com/stretchr/testify/require"
1719
1820 "github.com/bnema/vev/internal/domain"
@@ -193,7 +195,7 @@ func newDurableRecoveryDaemon(t testing.TB, records []domain.CatalogueRecord, re
193195 t.Helper()
194196 catalogue := newDurableRecoveryCatalogue(records)
195197 coordinator := recoveryusecase.NewCoordinator(catalogue, repository, nil)
196- d := New(nil, stubClock{}, slog.New(slog.NewTextHandler(io.Discard, nil)), WithCatalogue(catalogue, records), WithSnapshotRepository(repository), WithCheckpointCoordinator(coordinator))
198+ d := New(nil, stubClock{}, slog.New(slog.NewTextHandler(io.Discard, nil)), WithCatalogue(catalogue, records), WithSnapshotRepository(repository), WithRecoveryCoordinator(coordinator))
197199 d.serveCtx, d.serveCancel = context.WithCancel(context.Background())
198200 t.Cleanup(d.serveCancel)
199201 return d, catalogue
@@ -212,7 +214,7 @@ func TestFinalCheckpointFailurePreservesCatalogue(t *testing.T) {
212214 catalogue := newDurableRecoveryCatalogue([]domain.CatalogueRecord{prior})
213215 repository := &durableRecoveryRepository{}
214216 coordinator := recoveryusecase.NewCoordinator(catalogue, repository, nil)
215- d := New(nil, stubClock{}, slog.New(slog.NewTextHandler(io.Discard, nil)), WithCatalogue(catalogue, []domain.CatalogueRecord{prior}), WithSnapshotRepository(repository), WithCheckpointCoordinator(coordinator))
217+ d := New(nil, stubClock{}, slog.New(slog.NewTextHandler(io.Discard, nil)), WithCatalogue(catalogue, []domain.CatalogueRecord{prior}), WithSnapshotRepository(repository), WithRecoveryCoordinator(coordinator))
216218 startSnapshotEncodeWorker(t, d)
217219
218220 sess := newSnapshotTestSession(t, prior.Name, false, prior.Cwd)
@@ -351,39 +353,30 @@ func (f *recoveryCountingPTYFactory) Open(context.Context, string, []string, []s
351353 return nil, errors.New("unexpected PTY open")
352354 }
353355
354-type explicitRecoveryStub struct {
355- record domain.CatalogueRecord
356- catalogue ports.Catalogue
357-}
358-
359-func (s explicitRecoveryStub) Discard(_ context.Context, name string) error {
360- return s.catalogue.Replace(name, s.record)
361-}
362-
363-type contextRecoveryStub struct {
364- wantKey any
365- want any
366- seen bool
367- err error
368-}
369-
370-func (s *contextRecoveryStub) Discard(ctx context.Context, _ string) error {
371- s.seen = ctx.Value(s.wantKey) == s.want
372- return s.err
373-}
374-
375356 func TestSessionRecoveryUsesIncomingCommandContext(t *testing.T) {
376357 type contextKey struct{}
377358 key := contextKey{}
378359 ctx := context.WithValue(t.Context(), key, "request")
379360 wantErr := errors.New("stop after context capture")
380- stub := &contextRecoveryStub{wantKey: key, want: "request", err: wantErr}
361+ record := durableRecoveryRecord(0)
362+ record.RecoveryState = domain.RecoveryDegraded
363+ record.DegradedReason = "checkpoint validation failed"
364+ catalogue := portsmocks.NewMockCatalogue(t)
365+ repository := portsmocks.NewMockSnapshotRepository(t)
366+ catalogue.EXPECT().Record(record.Name).Return(record, true, nil).Once()
367+ catalogue.EXPECT().Replace(record.Name, mock.Anything).Return(nil).Once()
368+ seen := false
369+ repository.EXPECT().DeleteIncarnation(mock.Anything, record.IncarnationID).
370+ Run(func(got context.Context, _ domain.IncarnationID) { seen = got.Value(key) == "request" }).
371+ Return(wantErr).Once()
372+
381373 d := newTestDaemon(t, portsmocks.NewMockPTYFactory(t), stubClock{})
382- d.degradedRecovery = stub
374+ d.catalogue = catalogue
375+ d.recovery = recoveryusecase.NewCoordinator(catalogue, repository, bytes.NewReader(bytes.Repeat([]byte{9}, 16)))
383376
384- _, err := (controlExec{ctx: ctx, d: d, recoveryName: "work"}).SessionRecovery("discard")
377+ _, err := (controlExec{ctx: ctx, d: d, recoveryName: record.Name}).SessionRecovery("discard")
385378 require.ErrorIs(t, err, wantErr)
386- require.True(t, stub.seen)
379+ require.True(t, seen)
387380 }
388381
389382 type temporaryRestoreError struct{}
@@ -481,13 +474,21 @@ func TestSessionRecoveryCommand(t *testing.T) {
481474 degraded := durableRecoveryRecord(2)
482475 degraded.RecoveryState = domain.RecoveryDegraded
483476 degraded.DegradedReason = "checkpoint validation failed"
484- d, catalogue := newDurableRecoveryDaemon(t, []domain.CatalogueRecord{degraded}, &durableRecoveryRepository{})
485477 fresh := degraded
486- fresh.IncarnationID = domain.IncarnationID{9}
478+ fresh.IncarnationID = domain.IncarnationID{9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}
487479 fresh.RecoveryState = domain.RecoveryFresh
488480 fresh.Committed = nil
489481 fresh.DegradedReason = ""
490- d.degradedRecovery = explicitRecoveryStub{record: fresh, catalogue: catalogue}
482+ catalogue := portsmocks.NewMockCatalogue(t)
483+ repository := portsmocks.NewMockSnapshotRepository(t)
484+ catalogue.EXPECT().Record(degraded.Name).Return(degraded, true, nil).Once()
485+ catalogue.EXPECT().Replace(degraded.Name, fresh).Return(nil).Once()
486+ repository.EXPECT().DeleteIncarnation(mock.Anything, degraded.IncarnationID).Return(nil).Once()
487+ catalogue.EXPECT().Record(degraded.Name).Return(fresh, true, nil).Once()
488+ coordinator := recoveryusecase.NewCoordinator(catalogue, repository, bytes.NewReader(bytes.Repeat([]byte{9}, 16)))
489+ d := New(nil, stubClock{}, slog.New(slog.NewTextHandler(io.Discard, nil)),
490+ WithCatalogue(catalogue, []domain.CatalogueRecord{degraded}),
491+ WithSnapshotRepository(repository), WithRecoveryCoordinator(coordinator))
491492
492493 _, err := (controlExec{d: d, recoveryName: degraded.Name}).SessionRecovery("discard")
493494 require.NoError(t, err)
@@ -1,6 +1,7 @@
1 package daemon1 package daemon
2 2
3 import (3 import (
4+ "bytes"
4 "context"5 "context"
5 "errors"6 "errors"
6 "fmt"7 "fmt"
@@ -13,6 +14,7 @@ import (
13 "testing"14 "testing"
14 "time"15 "time"
15 16
17+ "github.com/stretchr/testify/mock"
16 "github.com/stretchr/testify/require"18 "github.com/stretchr/testify/require"
17 19
18 "github.com/bnema/vev/internal/domain"20 "github.com/bnema/vev/internal/domain"
@@ -193,7 +195,7 @@ func newDurableRecoveryDaemon(t testing.TB, records []domain.CatalogueRecord, re
193 t.Helper()195 t.Helper()
194 catalogue := newDurableRecoveryCatalogue(records)196 catalogue := newDurableRecoveryCatalogue(records)
195 coordinator := recoveryusecase.NewCoordinator(catalogue, repository, nil)197 coordinator := recoveryusecase.NewCoordinator(catalogue, repository, nil)
196- d := New(nil, stubClock{}, slog.New(slog.NewTextHandler(io.Discard, nil)), WithCatalogue(catalogue, records), WithSnapshotRepository(repository), WithCheckpointCoordinator(coordinator))198+ d := New(nil, stubClock{}, slog.New(slog.NewTextHandler(io.Discard, nil)), WithCatalogue(catalogue, records), WithSnapshotRepository(repository), WithRecoveryCoordinator(coordinator))
197 d.serveCtx, d.serveCancel = context.WithCancel(context.Background())199 d.serveCtx, d.serveCancel = context.WithCancel(context.Background())
198 t.Cleanup(d.serveCancel)200 t.Cleanup(d.serveCancel)
199 return d, catalogue201 return d, catalogue
@@ -212,7 +214,7 @@ func TestFinalCheckpointFailurePreservesCatalogue(t *testing.T) {
212 catalogue := newDurableRecoveryCatalogue([]domain.CatalogueRecord{prior})214 catalogue := newDurableRecoveryCatalogue([]domain.CatalogueRecord{prior})
213 repository := &durableRecoveryRepository{}215 repository := &durableRecoveryRepository{}
214 coordinator := recoveryusecase.NewCoordinator(catalogue, repository, nil)216 coordinator := recoveryusecase.NewCoordinator(catalogue, repository, nil)
215- d := New(nil, stubClock{}, slog.New(slog.NewTextHandler(io.Discard, nil)), WithCatalogue(catalogue, []domain.CatalogueRecord{prior}), WithSnapshotRepository(repository), WithCheckpointCoordinator(coordinator))217+ d := New(nil, stubClock{}, slog.New(slog.NewTextHandler(io.Discard, nil)), WithCatalogue(catalogue, []domain.CatalogueRecord{prior}), WithSnapshotRepository(repository), WithRecoveryCoordinator(coordinator))
216 startSnapshotEncodeWorker(t, d)218 startSnapshotEncodeWorker(t, d)
217 219
218 sess := newSnapshotTestSession(t, prior.Name, false, prior.Cwd)220 sess := newSnapshotTestSession(t, prior.Name, false, prior.Cwd)
@@ -351,39 +353,30 @@ func (f *recoveryCountingPTYFactory) Open(context.Context, string, []string, []s
351 return nil, errors.New("unexpected PTY open")353 return nil, errors.New("unexpected PTY open")
352 }354 }
353 355
354-type explicitRecoveryStub struct {
355- record domain.CatalogueRecord
356- catalogue ports.Catalogue
357-}
358-
359-func (s explicitRecoveryStub) Discard(_ context.Context, name string) error {
360- return s.catalogue.Replace(name, s.record)
361-}
362-
363-type contextRecoveryStub struct {
364- wantKey any
365- want any
366- seen bool
367- err error
368-}
369-
370-func (s *contextRecoveryStub) Discard(ctx context.Context, _ string) error {
371- s.seen = ctx.Value(s.wantKey) == s.want
372- return s.err
373-}
374-
375 func TestSessionRecoveryUsesIncomingCommandContext(t *testing.T) {356 func TestSessionRecoveryUsesIncomingCommandContext(t *testing.T) {
376 type contextKey struct{}357 type contextKey struct{}
377 key := contextKey{}358 key := contextKey{}
378 ctx := context.WithValue(t.Context(), key, "request")359 ctx := context.WithValue(t.Context(), key, "request")
379 wantErr := errors.New("stop after context capture")360 wantErr := errors.New("stop after context capture")
380- stub := &contextRecoveryStub{wantKey: key, want: "request", err: wantErr}361+ record := durableRecoveryRecord(0)
362+ record.RecoveryState = domain.RecoveryDegraded
363+ record.DegradedReason = "checkpoint validation failed"
364+ catalogue := portsmocks.NewMockCatalogue(t)
365+ repository := portsmocks.NewMockSnapshotRepository(t)
366+ catalogue.EXPECT().Record(record.Name).Return(record, true, nil).Once()
367+ catalogue.EXPECT().Replace(record.Name, mock.Anything).Return(nil).Once()
368+ seen := false
369+ repository.EXPECT().DeleteIncarnation(mock.Anything, record.IncarnationID).
370+ Run(func(got context.Context, _ domain.IncarnationID) { seen = got.Value(key) == "request" }).
371+ Return(wantErr).Once()
372+
381 d := newTestDaemon(t, portsmocks.NewMockPTYFactory(t), stubClock{})373 d := newTestDaemon(t, portsmocks.NewMockPTYFactory(t), stubClock{})
382- d.degradedRecovery = stub374+ d.catalogue = catalogue
375+ d.recovery = recoveryusecase.NewCoordinator(catalogue, repository, bytes.NewReader(bytes.Repeat([]byte{9}, 16)))
383 376
384- _, err := (controlExec{ctx: ctx, d: d, recoveryName: "work"}).SessionRecovery("discard")377+ _, err := (controlExec{ctx: ctx, d: d, recoveryName: record.Name}).SessionRecovery("discard")
385 require.ErrorIs(t, err, wantErr)378 require.ErrorIs(t, err, wantErr)
386- require.True(t, stub.seen)379+ require.True(t, seen)
387 }380 }
388 381
389 type temporaryRestoreError struct{}382 type temporaryRestoreError struct{}
@@ -481,13 +474,21 @@ func TestSessionRecoveryCommand(t *testing.T) {
481 degraded := durableRecoveryRecord(2)474 degraded := durableRecoveryRecord(2)
482 degraded.RecoveryState = domain.RecoveryDegraded475 degraded.RecoveryState = domain.RecoveryDegraded
483 degraded.DegradedReason = "checkpoint validation failed"476 degraded.DegradedReason = "checkpoint validation failed"
484- d, catalogue := newDurableRecoveryDaemon(t, []domain.CatalogueRecord{degraded}, &durableRecoveryRepository{})
485 fresh := degraded477 fresh := degraded
486- fresh.IncarnationID = domain.IncarnationID{9}478+ fresh.IncarnationID = domain.IncarnationID{9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}
487 fresh.RecoveryState = domain.RecoveryFresh479 fresh.RecoveryState = domain.RecoveryFresh
488 fresh.Committed = nil480 fresh.Committed = nil
489 fresh.DegradedReason = ""481 fresh.DegradedReason = ""
490- d.degradedRecovery = explicitRecoveryStub{record: fresh, catalogue: catalogue}482+ catalogue := portsmocks.NewMockCatalogue(t)
483+ repository := portsmocks.NewMockSnapshotRepository(t)
484+ catalogue.EXPECT().Record(degraded.Name).Return(degraded, true, nil).Once()
485+ catalogue.EXPECT().Replace(degraded.Name, fresh).Return(nil).Once()
486+ repository.EXPECT().DeleteIncarnation(mock.Anything, degraded.IncarnationID).Return(nil).Once()
487+ catalogue.EXPECT().Record(degraded.Name).Return(fresh, true, nil).Once()
488+ coordinator := recoveryusecase.NewCoordinator(catalogue, repository, bytes.NewReader(bytes.Repeat([]byte{9}, 16)))
489+ d := New(nil, stubClock{}, slog.New(slog.NewTextHandler(io.Discard, nil)),
490+ WithCatalogue(catalogue, []domain.CatalogueRecord{degraded}),
491+ WithSnapshotRepository(repository), WithRecoveryCoordinator(coordinator))
491 492
492 _, err := (controlExec{d: d, recoveryName: degraded.Name}).SessionRecovery("discard")493 _, err := (controlExec{d: d, recoveryName: degraded.Name}).SessionRecovery("discard")
493 require.NoError(t, err)494 require.NoError(t, err)
M internal/usecase/daemon/picker_test.go +1 -1
@@ -275,7 +275,7 @@ func TestRestoreCancellationTransitionsBeforePickerCompletion(t *testing.T) {
275275 coordinator := recoveryusecase.NewCoordinator(catalogue, repository, nil)
276276 WithCatalogue(catalogue, []domain.CatalogueRecord{record})(d)
277277 WithSnapshotRepository(repository)(d)
278- WithCheckpointCoordinator(coordinator)(d)
278+ WithRecoveryCoordinator(coordinator)(d)
279279 restoreCtx, cancelRestore := context.WithCancel(context.Background())
280280 restored := make(chan struct{})
281281 go func() {
@@ -275,7 +275,7 @@ func TestRestoreCancellationTransitionsBeforePickerCompletion(t *testing.T) {
275 coordinator := recoveryusecase.NewCoordinator(catalogue, repository, nil)275 coordinator := recoveryusecase.NewCoordinator(catalogue, repository, nil)
276 WithCatalogue(catalogue, []domain.CatalogueRecord{record})(d)276 WithCatalogue(catalogue, []domain.CatalogueRecord{record})(d)
277 WithSnapshotRepository(repository)(d)277 WithSnapshotRepository(repository)(d)
278- WithCheckpointCoordinator(coordinator)(d)278+ WithRecoveryCoordinator(coordinator)(d)
279 restoreCtx, cancelRestore := context.WithCancel(context.Background())279 restoreCtx, cancelRestore := context.WithCancel(context.Background())
280 restored := make(chan struct{})280 restored := make(chan struct{})
281 go func() {281 go func() {
M internal/usecase/daemon/session.go +9 -9
@@ -321,7 +321,7 @@ func (d *Daemon) createSessionLocked(name string, ephemeral bool, cwd string, sz
321321 if !ephemeral {
322322 if d.persistEnabled {
323323 record := domain.CatalogueRecord{Name: name, IncarnationID: incarnation, Cwd: cwd, CreatedAt: createdAt, UpdatedAt: createdAt, LastUsedSeq: lastUsedSeq, TabNames: names, RecoveryState: domain.RecoveryFresh}
324- if d.lifecycleRecovery == nil {
324+ if d.recovery == nil {
325325 closeTabs(tabs)
326326 cancel()
327327 return nil, domain.UserErr(domain.NoticeSessionSpawn, "couldn't create session", errors.New("durable session authority is not configured"))
@@ -333,7 +333,7 @@ func (d *Daemon) createSessionLocked(name string, ephemeral bool, cwd string, sz
333333 if resuming && authoritativeExists {
334334 err = d.updateCatalogueMetadata(record.MetadataUpdate())
335335 } else {
336- record, err = d.lifecycleRecovery.Create(d.serveCtx, record)
336+ record, err = d.recovery.Create(d.serveCtx, record)
337337 }
338338 d.mu.Lock()
339339 if err != nil {
@@ -348,7 +348,7 @@ func (d *Daemon) createSessionLocked(name string, ephemeral bool, cwd string, sz
348348 if !resuming {
349349 d.mu.Unlock()
350350 rollbackCtx, rollbackCancel := context.WithTimeout(context.WithoutCancel(d.serveCtx), snapshotFinalFlushTimeout)
351- rollbackErr := d.lifecycleRecovery.Delete(rollbackCtx, name)
351+ rollbackErr := d.recovery.Delete(rollbackCtx, name)
352352 rollbackCancel()
353353 d.mu.Lock()
354354 if rollbackErr != nil {
@@ -789,16 +789,16 @@ func (d *Daemon) renameSession(sess *session, name string) error {
789789 // Durable named renames are one atomic catalogue batch. The incarnation-keyed
790790 // snapshot namespace is intentionally untouched.
791791 if (wasEphemeral || oldName != name) && d.persistEnabled {
792- if d.lifecycleRecovery == nil {
792+ if d.recovery == nil {
793793 return rollback(errors.New("durable session authority is not configured"))
794794 }
795795 {
796796 var committed domain.CatalogueRecord
797797 var err error
798798 if wasEphemeral {
799- committed, err = d.lifecycleRecovery.Create(d.serveCtx, record)
799+ committed, err = d.recovery.Create(d.serveCtx, record)
800800 } else {
801- committed, err = d.lifecycleRecovery.Rename(d.serveCtx, oldName, name)
801+ committed, err = d.recovery.Rename(d.serveCtx, oldName, name)
802802 }
803803 if err != nil {
804804 return rollback(err)
@@ -1026,7 +1026,7 @@ func (d *Daemon) closeTab(sess *session, tb *tab, repaint bool) error {
10261026 // identity or persisted metadata is removed. It never runs under daemon or
10271027 // session locks.
10281028 func (d *Daemon) beginSnapshotPurge(_ string, _ domain.IncarnationID) error {
1029- if d.persistEnabled && d.lifecycleRecovery == nil {
1029+ if d.persistEnabled && d.recovery == nil {
10301030 return errors.New("durable session authority is not configured")
10311031 }
10321032 // The lifecycle coordinator performs the complete ordered protocol after
@@ -1041,7 +1041,7 @@ func (d *Daemon) finishSnapshotPurge(ctx context.Context, name string, _ domain.
10411041 if !d.persistEnabled {
10421042 return nil
10431043 }
1044- if d.lifecycleRecovery == nil {
1044+ if d.recovery == nil {
10451045 return errors.New("durable session authority is not configured")
10461046 }
10471047 if ctx == nil {
@@ -1049,7 +1049,7 @@ func (d *Daemon) finishSnapshotPurge(ctx context.Context, name string, _ domain.
10491049 }
10501050 purgeCtx, cancel := context.WithTimeout(ctx, snapshotFinalFlushTimeout)
10511051 defer cancel()
1052- return d.lifecycleRecovery.Delete(purgeCtx, name)
1052+ return d.recovery.Delete(purgeCtx, name)
10531053 }
10541054
10551055 // retryStoppedPurge completes a previously closed session's durable purge.
@@ -321,7 +321,7 @@ func (d *Daemon) createSessionLocked(name string, ephemeral bool, cwd string, sz
321 if !ephemeral {321 if !ephemeral {
322 if d.persistEnabled {322 if d.persistEnabled {
323 record := domain.CatalogueRecord{Name: name, IncarnationID: incarnation, Cwd: cwd, CreatedAt: createdAt, UpdatedAt: createdAt, LastUsedSeq: lastUsedSeq, TabNames: names, RecoveryState: domain.RecoveryFresh}323 record := domain.CatalogueRecord{Name: name, IncarnationID: incarnation, Cwd: cwd, CreatedAt: createdAt, UpdatedAt: createdAt, LastUsedSeq: lastUsedSeq, TabNames: names, RecoveryState: domain.RecoveryFresh}
324- if d.lifecycleRecovery == nil {324+ if d.recovery == nil {
325 closeTabs(tabs)325 closeTabs(tabs)
326 cancel()326 cancel()
327 return nil, domain.UserErr(domain.NoticeSessionSpawn, "couldn't create session", errors.New("durable session authority is not configured"))327 return nil, domain.UserErr(domain.NoticeSessionSpawn, "couldn't create session", errors.New("durable session authority is not configured"))
@@ -333,7 +333,7 @@ func (d *Daemon) createSessionLocked(name string, ephemeral bool, cwd string, sz
333 if resuming && authoritativeExists {333 if resuming && authoritativeExists {
334 err = d.updateCatalogueMetadata(record.MetadataUpdate())334 err = d.updateCatalogueMetadata(record.MetadataUpdate())
335 } else {335 } else {
336- record, err = d.lifecycleRecovery.Create(d.serveCtx, record)336+ record, err = d.recovery.Create(d.serveCtx, record)
337 }337 }
338 d.mu.Lock()338 d.mu.Lock()
339 if err != nil {339 if err != nil {
@@ -348,7 +348,7 @@ func (d *Daemon) createSessionLocked(name string, ephemeral bool, cwd string, sz
348 if !resuming {348 if !resuming {
349 d.mu.Unlock()349 d.mu.Unlock()
350 rollbackCtx, rollbackCancel := context.WithTimeout(context.WithoutCancel(d.serveCtx), snapshotFinalFlushTimeout)350 rollbackCtx, rollbackCancel := context.WithTimeout(context.WithoutCancel(d.serveCtx), snapshotFinalFlushTimeout)
351- rollbackErr := d.lifecycleRecovery.Delete(rollbackCtx, name)351+ rollbackErr := d.recovery.Delete(rollbackCtx, name)
352 rollbackCancel()352 rollbackCancel()
353 d.mu.Lock()353 d.mu.Lock()
354 if rollbackErr != nil {354 if rollbackErr != nil {
@@ -789,16 +789,16 @@ func (d *Daemon) renameSession(sess *session, name string) error {
789 // Durable named renames are one atomic catalogue batch. The incarnation-keyed789 // Durable named renames are one atomic catalogue batch. The incarnation-keyed
790 // snapshot namespace is intentionally untouched.790 // snapshot namespace is intentionally untouched.
791 if (wasEphemeral || oldName != name) && d.persistEnabled {791 if (wasEphemeral || oldName != name) && d.persistEnabled {
792- if d.lifecycleRecovery == nil {792+ if d.recovery == nil {
793 return rollback(errors.New("durable session authority is not configured"))793 return rollback(errors.New("durable session authority is not configured"))
794 }794 }
795 {795 {
796 var committed domain.CatalogueRecord796 var committed domain.CatalogueRecord
797 var err error797 var err error
798 if wasEphemeral {798 if wasEphemeral {
799- committed, err = d.lifecycleRecovery.Create(d.serveCtx, record)799+ committed, err = d.recovery.Create(d.serveCtx, record)
800 } else {800 } else {
801- committed, err = d.lifecycleRecovery.Rename(d.serveCtx, oldName, name)801+ committed, err = d.recovery.Rename(d.serveCtx, oldName, name)
802 }802 }
803 if err != nil {803 if err != nil {
804 return rollback(err)804 return rollback(err)
@@ -1026,7 +1026,7 @@ func (d *Daemon) closeTab(sess *session, tb *tab, repaint bool) error {
1026 // identity or persisted metadata is removed. It never runs under daemon or1026 // identity or persisted metadata is removed. It never runs under daemon or
1027 // session locks.1027 // session locks.
1028 func (d *Daemon) beginSnapshotPurge(_ string, _ domain.IncarnationID) error {1028 func (d *Daemon) beginSnapshotPurge(_ string, _ domain.IncarnationID) error {
1029- if d.persistEnabled && d.lifecycleRecovery == nil {1029+ if d.persistEnabled && d.recovery == nil {
1030 return errors.New("durable session authority is not configured")1030 return errors.New("durable session authority is not configured")
1031 }1031 }
1032 // The lifecycle coordinator performs the complete ordered protocol after1032 // The lifecycle coordinator performs the complete ordered protocol after
@@ -1041,7 +1041,7 @@ func (d *Daemon) finishSnapshotPurge(ctx context.Context, name string, _ domain.
1041 if !d.persistEnabled {1041 if !d.persistEnabled {
1042 return nil1042 return nil
1043 }1043 }
1044- if d.lifecycleRecovery == nil {1044+ if d.recovery == nil {
1045 return errors.New("durable session authority is not configured")1045 return errors.New("durable session authority is not configured")
1046 }1046 }
1047 if ctx == nil {1047 if ctx == nil {
@@ -1049,7 +1049,7 @@ func (d *Daemon) finishSnapshotPurge(ctx context.Context, name string, _ domain.
1049 }1049 }
1050 purgeCtx, cancel := context.WithTimeout(ctx, snapshotFinalFlushTimeout)1050 purgeCtx, cancel := context.WithTimeout(ctx, snapshotFinalFlushTimeout)
1051 defer cancel()1051 defer cancel()
1052- return d.lifecycleRecovery.Delete(purgeCtx, name)1052+ return d.recovery.Delete(purgeCtx, name)
1053 }1053 }
1054 1054
1055 // retryStoppedPurge completes a previously closed session's durable purge.1055 // retryStoppedPurge completes a previously closed session's durable purge.
M internal/usecase/daemon/snapshot_test.go +15 -6
@@ -12,6 +12,7 @@ import (
1212 "github.com/bnema/vev/internal/domain"
1313 "github.com/bnema/vev/internal/ports"
1414 portsmocks "github.com/bnema/vev/internal/ports/mocks"
15+ recoveryusecase "github.com/bnema/vev/internal/usecase/recovery"
1516 "github.com/bnema/vev/pkg/renderer"
1617 )
1718
@@ -38,18 +39,26 @@ func TestDurableWriterFailureNamesIncludesBufferedCapture(t *testing.T) {
3839 require.Equal(t, []string{"work"}, d.durableWriterFailureNames())
3940 }
4041
41-func TestCheckpointPublicationWithoutCommittedRefFailsCapture(t *testing.T) {
42+func TestCheckpointCatalogueFailureKeepsCaptureRetryable(t *testing.T) {
4243 d := newTestDaemon(t, portsmocks.NewMockPTYFactory(t), stubClock{})
43- WithSnapshotRepository(portsmocks.NewMockSnapshotRepository(t))(d)
44- coordinator := portsmocks.NewMockCheckpointCoordinator(t)
45- WithCheckpointCoordinator(coordinator)(d)
44+ catalogue := portsmocks.NewMockCatalogue(t)
45+ repository := portsmocks.NewMockSnapshotRepository(t)
46+ record := domain.CatalogueRecord{
47+ Name: "work",
48+ IncarnationID: domain.IncarnationID{1},
49+ RecoveryState: domain.RecoveryFresh,
50+ }
51+ catalogue.EXPECT().Record("work").Return(record, true, nil).Once()
52+ repository.EXPECT().Publish(mock.Anything, mock.Anything).Return(nil).Once()
53+ catalogue.EXPECT().Replace("work", mock.Anything).Return(errors.New("catalogue unavailable")).Once()
54+ WithSnapshotRepository(repository)(d)
55+ WithRecoveryCoordinator(recoveryusecase.NewCoordinator(catalogue, repository, nil))(d)
4656 startSnapshotEncodeWorker(t, d)
47- coordinator.EXPECT().PublishCheckpoint(mock.Anything, "work", mock.Anything).Return(domain.CatalogueRecord{Name: "work"}, nil).Once()
4857
4958 sess := newSnapshotTestSession(t, "work", false, "/work")
5059 require.True(t, d.captureSession(sess))
5160 awaitSnapshotIdle(t, sess)
52- require.True(t, sess.snapDirty.Load(), "nil committed publication must remain retryable")
61+ require.True(t, sess.snapDirty.Load(), "failed catalogue commit must remain retryable")
5362 }
5463
5564 func TestSnapshotWorkerPublishesContentAddressedCapture(t *testing.T) {
@@ -12,6 +12,7 @@ import (
12 "github.com/bnema/vev/internal/domain"12 "github.com/bnema/vev/internal/domain"
13 "github.com/bnema/vev/internal/ports"13 "github.com/bnema/vev/internal/ports"
14 portsmocks "github.com/bnema/vev/internal/ports/mocks"14 portsmocks "github.com/bnema/vev/internal/ports/mocks"
15+ recoveryusecase "github.com/bnema/vev/internal/usecase/recovery"
15 "github.com/bnema/vev/pkg/renderer"16 "github.com/bnema/vev/pkg/renderer"
16 )17 )
17 18
@@ -38,18 +39,26 @@ func TestDurableWriterFailureNamesIncludesBufferedCapture(t *testing.T) {
38 require.Equal(t, []string{"work"}, d.durableWriterFailureNames())39 require.Equal(t, []string{"work"}, d.durableWriterFailureNames())
39 }40 }
40 41
41-func TestCheckpointPublicationWithoutCommittedRefFailsCapture(t *testing.T) {42+func TestCheckpointCatalogueFailureKeepsCaptureRetryable(t *testing.T) {
42 d := newTestDaemon(t, portsmocks.NewMockPTYFactory(t), stubClock{})43 d := newTestDaemon(t, portsmocks.NewMockPTYFactory(t), stubClock{})
43- WithSnapshotRepository(portsmocks.NewMockSnapshotRepository(t))(d)44+ catalogue := portsmocks.NewMockCatalogue(t)
44- coordinator := portsmocks.NewMockCheckpointCoordinator(t)45+ repository := portsmocks.NewMockSnapshotRepository(t)
45- WithCheckpointCoordinator(coordinator)(d)46+ record := domain.CatalogueRecord{
47+ Name: "work",
48+ IncarnationID: domain.IncarnationID{1},
49+ RecoveryState: domain.RecoveryFresh,
50+ }
51+ catalogue.EXPECT().Record("work").Return(record, true, nil).Once()
52+ repository.EXPECT().Publish(mock.Anything, mock.Anything).Return(nil).Once()
53+ catalogue.EXPECT().Replace("work", mock.Anything).Return(errors.New("catalogue unavailable")).Once()
54+ WithSnapshotRepository(repository)(d)
55+ WithRecoveryCoordinator(recoveryusecase.NewCoordinator(catalogue, repository, nil))(d)
46 startSnapshotEncodeWorker(t, d)56 startSnapshotEncodeWorker(t, d)
47- coordinator.EXPECT().PublishCheckpoint(mock.Anything, "work", mock.Anything).Return(domain.CatalogueRecord{Name: "work"}, nil).Once()
48 57
49 sess := newSnapshotTestSession(t, "work", false, "/work")58 sess := newSnapshotTestSession(t, "work", false, "/work")
50 require.True(t, d.captureSession(sess))59 require.True(t, d.captureSession(sess))
51 awaitSnapshotIdle(t, sess)60 awaitSnapshotIdle(t, sess)
52- require.True(t, sess.snapDirty.Load(), "nil committed publication must remain retryable")61+ require.True(t, sess.snapDirty.Load(), "failed catalogue commit must remain retryable")
53 }62 }
54 63
55 func TestSnapshotWorkerPublishesContentAddressedCapture(t *testing.T) {64 func TestSnapshotWorkerPublishesContentAddressedCapture(t *testing.T) {
M internal/usecase/daemon/snapshot_worker.go +2 -2
@@ -132,9 +132,9 @@ func (d *Daemon) publishSnapshotCapture(workerCtx context.Context, workerID uint
132132 }
133133 if err == nil && workerCtx.Err() == nil {
134134 if err = publicationContext.Err(); err == nil {
135- if d.checkpointRecovery != nil {
135+ if d.recovery != nil {
136136 var record domain.CatalogueRecord
137- record, err = d.checkpointRecovery.PublishCheckpoint(publicationContext, capture.name, publication)
137+ record, err = d.recovery.PublishCheckpoint(publicationContext, capture.name, publication)
138138 if err == nil {
139139 if record.Committed == nil {
140140 err = errors.New("snapshot: checkpoint publication returned no committed checkpoint")
@@ -132,9 +132,9 @@ func (d *Daemon) publishSnapshotCapture(workerCtx context.Context, workerID uint
132 }132 }
133 if err == nil && workerCtx.Err() == nil {133 if err == nil && workerCtx.Err() == nil {
134 if err = publicationContext.Err(); err == nil {134 if err = publicationContext.Err(); err == nil {
135- if d.checkpointRecovery != nil {135+ if d.recovery != nil {
136 var record domain.CatalogueRecord136 var record domain.CatalogueRecord
137- record, err = d.checkpointRecovery.PublishCheckpoint(publicationContext, capture.name, publication)137+ record, err = d.recovery.PublishCheckpoint(publicationContext, capture.name, publication)
138 if err == nil {138 if err == nil {
139 if record.Committed == nil {139 if record.Committed == nil {
140 err = errors.New("snapshot: checkpoint publication returned no committed checkpoint")140 err = errors.New("snapshot: checkpoint publication returned no committed checkpoint")

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