1package recovery
2
3import (
4 "context"
5 "errors"
6 "fmt"
7
8 "github.com/bnema/vev/internal/domain"
9)
10
11// ResetIncompatible replaces an exact healthy checkpoint authority with a
12// fresh incarnation. Stale observations are harmless no-ops.
13func (c *Coordinator) ResetIncompatible(ctx context.Context, name string, expectedIncarnation domain.IncarnationID, expectedCheckpoint domain.CheckpointRef) (domain.CatalogueRecord, bool, error) {
14 if c == nil || c.catalogue == nil || c.repository == nil || c.locks == nil || c.random == nil {
15 return domain.CatalogueRecord{}, false, errors.New("recovery: incomplete incompatible-reset dependencies")
16 }
17 if err := ctx.Err(); err != nil {
18 return domain.CatalogueRecord{}, false, err
19 }
20 c.mutationMu.Lock()
21 defer c.mutationMu.Unlock()
22 unlock := c.locks.Lock([]string{name})
23 defer unlock()
24
25 record, ok, err := c.catalogue.Record(name)
26 if err != nil {
27 return domain.CatalogueRecord{}, false, err
28 }
29 if !ok || record.IncarnationID != expectedIncarnation || record.DegradedReason != "" ||
30 record.Committed == nil || *record.Committed != expectedCheckpoint {
31 return domain.CatalogueRecord{}, false, nil
32 }
33 return c.replaceIncarnationLocked(ctx, record, true, "reset")
34}
35
36// replaceIncarnationLocked commits a fresh catalogue authority before removing
37// the old repository incarnation. The caller must hold mutationMu and the
38// record name's KeyLock.
39func (c *Coordinator) replaceIncarnationLocked(ctx context.Context, record domain.CatalogueRecord, clearTabNames bool, operation string) (domain.CatalogueRecord, bool, error) {
40 old := record.IncarnationID
41 next, err := domain.NewIncarnationID(c.random)
42 if err != nil {
43 return domain.CatalogueRecord{}, false, fmt.Errorf("recovery: generate replacement incarnation: %w", err)
44 }
45 fresh := record
46 fresh.IncarnationID = next
47 fresh.Committed = nil
48 fresh.DegradedReason = ""
49 if clearTabNames {
50 fresh.TabNames = nil
51 }
52 if err := fresh.Validate(); err != nil {
53 return domain.CatalogueRecord{}, false, fmt.Errorf("recovery: invalid %s replacement: %w", operation, err)
54 }
55 if err := c.catalogue.Replace(record.Name, fresh); err != nil {
56 return domain.CatalogueRecord{}, false, err
57 }
58 if err := c.repository.DeleteIncarnation(ctx, old); err != nil {
59 return fresh, true, err
60 }
61 return fresh, true, nil
62}