返回 DeepSeek-Reasonix
derived.go
根目录 / internal / repair / derived.go
1 package repair
2
3 import (
4 "fmt"
5 "os"
6 "path/filepath"
7 "sort"
8 "strings"
9 "time"
10
11 "reasonix/internal/config"
12 )
13
14 func RebuildDerivedState(target string) ([]string, error) {
15 paths, err := derivedStateTargetPaths(target)
16 if err != nil {
17 return nil, err
18 }
19 _, expectedStates := repairPlanDerivedStateSnapshot(strings.ToLower(strings.TrimSpace(target)))
20 unlockTransaction, err := lockRepairTransaction()
21 if err != nil {
22 return nil, err
23 }
24 defer unlockTransaction()
25 if err := reconcilePreparedRepairTransaction(); err != nil {
26 return nil, fmt.Errorf("rebuild derived state: reconcile pending mutation: %w", err)
27 }
28 unlock, err := lockRepairMutations(paths...)
29 if err != nil {
30 return nil, err
31 }
32 defer unlock()
33 if err := verifyRepairPlanFileStates(expectedStates); err != nil {
34 return nil, err
35 }
36 return rebuildDerivedStateBoundUnlocked(target, expectedStates, nil)
37 }
38
39 func rebuildDerivedStateBoundUnlocked(
40 target string,
41 expectedStates map[string]string,
42 planTx *RepairTransaction,
43 ) ([]string, error) {
44 target = strings.ToLower(strings.TrimSpace(target))
45 paths := derivedStatePaths()
46 var names []string
47 if target == "all" {
48 for name := range paths {
49 names = append(names, name)
50 }
51 sort.Strings(names)
52 } else if _, ok := paths[target]; ok {
53 names = []string{target}
54 } else {
55 return nil, fmt.Errorf("unknown derived-state target %q (want tabs|projects|window|zoom|all)", target)
56 }
57 stamp := time.Now().UTC().Format("20060102T150405Z")
58 applied := []string{}
59 tx := planTx
60 if tx == nil {
61 tx = newRepairTransaction(time.Now())
62 }
63 for _, name := range names {
64 path := paths[name]
65 if path == "" {
66 continue
67 }
68 if _, err := os.Lstat(path); err != nil {
69 if os.IsNotExist(err) {
70 continue
71 }
72 return applied, err
73 }
74 if err := verifyRepairPlanFileState(path, expectedStates); err != nil {
75 return applied, err
76 }
77 repairMutationBeforeRename(path)
78 if err := verifyRepairPlanFileState(path, expectedStates); err != nil {
79 return applied, err
80 }
81 quarantine := path + ".reasonix-rebuild-" + stamp
82 changeIndex := len(tx.Changes)
83 tx.Changes = append(tx.Changes, preparedRepairChangeForPrevious("derived:"+name, path, quarantine))
84 if err := persistPreparedRepairTransaction(tx); err != nil {
85 return applied, fmt.Errorf("prepare derived-state quarantine: %w", err)
86 }
87 repairMutationAfterPrepare(path)
88 if err := renameRepairNodeNoReplace(path, quarantine); err != nil {
89 return applied, err
90 }
91 repairMutationAfterRename(path)
92 if expected := expectedStates[path]; expected != "" {
93 if err := verifyRepairPlanStateIDFor(quarantine, path, expected); err != nil {
94 if restoreErr := restoreRepairNodeIfAbsent(quarantine, path); restoreErr != nil {
95 return applied, fmt.Errorf("derived state changed after confirmation and restore failed: %v: %w", restoreErr, err)
96 }
97 return applied, err
98 }
99 }
100 if durable, err := commitPreparedRepairTransaction(tx, changeIndex); err != nil {
101 if durable {
102 return applied, fmt.Errorf("commit derived-state undo state: cleanup pending journal: %w", err)
103 }
104 restoreErr := restoreRepairNodeIfAbsent(quarantine, path)
105 if restoreErr != nil {
106 return applied, fmt.Errorf("commit derived-state undo state: %w; confirmed state retained at %s: %v", err, quarantine, restoreErr)
107 }
108 return applied, fmt.Errorf("commit derived-state undo state: %w", err)
109 }
110 if _, err := os.Lstat(path); err == nil {
111 appendRepairLogBestEffort(tx)
112 return applied, fmt.Errorf("repair plan preview changed since confirmation; target was recreated during quarantine; confirmed state remains at %s", quarantine)
113 } else if !os.IsNotExist(err) {
114 return applied, err
115 }
116 applied = append(applied, quarantine)
117 }
118 if len(tx.Changes) > 0 {
119 appendRepairLogBestEffort(tx)
120 }
121 return applied, nil
122 }
123
124 func derivedStateTargetPaths(target string) ([]string, error) {
125 target = strings.ToLower(strings.TrimSpace(target))
126 paths := derivedStatePaths()
127 if target == "all" {
128 names := make([]string, 0, len(paths))
129 for name := range paths {
130 names = append(names, name)
131 }
132 sort.Strings(names)
133 out := make([]string, 0, len(names))
134 for _, name := range names {
135 if path := paths[name]; path != "" {
136 out = append(out, path)
137 }
138 }
139 return out, nil
140 }
141 path, ok := paths[target]
142 if !ok {
143 return nil, fmt.Errorf("unknown derived-state target %q (want tabs|projects|window|zoom|all)", target)
144 }
145 if path == "" {
146 return []string{}, nil
147 }
148 return []string{path}, nil
149 }
150
151 func derivedStatePaths() map[string]string {
152 paths := map[string]string{}
153 if root := config.ReasonixHomeDir(); root != "" {
154 paths["tabs"] = filepath.Join(root, "desktop-tabs.json")
155 paths["projects"] = filepath.Join(root, "desktop-projects.json")
156 }
157 if root := config.MemoryUserDir(); root != "" {
158 paths["window"] = filepath.Join(root, "desktop-window.json")
159 paths["zoom"] = filepath.Join(root, "desktop-zoom.json")
160 }
161 return paths
162 }
163
163 lines GO