返回 DeepSeek-Reasonix
historical_import_recovery.go
根目录 / desktop / historical_import_recovery.go
1 package main
2
3 import (
4 "context"
5 "errors"
6
7 "reasonix/desktop/internal/workspacestate"
8 "reasonix/internal/session"
9 )
10
11 func validateDesktopOperationSources(state workspacestate.State, op workspacestate.Operation) error {
12 checks := []workspacestate.Operation{op}
13 for _, dependency := range op.Dependencies {
14 checks = append(checks, state.PendingOperations[dependency])
15 }
16 for _, check := range checks {
17 if check.Mapping == nil {
18 continue
19 }
20 // Published import content is validated through the target snapshot and
21 // workspace guards. The mapping retains evidence of its original source.
22 if (check.Kind == "import" || check.Kind == "restore") && check.Phase == "content_ready" {
23 continue
24 }
25 fingerprint, err := desktopSourceFingerprint(check.Mapping.Path)
26 if err != nil || fingerprint != check.Mapping.Fingerprint {
27 return errors.Join(err, workspacestate.ErrMutationConflict)
28 }
29 }
30 return nil
31 }
32
33 func pendingHistoricalOperation(state workspacestate.State, id string) *workspacestate.Operation {
34 var resume *workspacestate.Operation
35 for _, candidate := range state.PendingOperations {
36 if candidate.Phase == "committed" || candidate.Mapping == nil || !historicalSourceKeyMatches(candidate.Mapping.SourceKey, id) {
37 continue
38 }
39 if candidate.Kind != "import" && candidate.Kind != "restore" {
40 continue
41 }
42 if resume == nil || historicalOperationRank(candidate) < historicalOperationRank(*resume) ||
43 (historicalOperationRank(candidate) == historicalOperationRank(*resume) && candidate.ID < resume.ID) {
44 copy := candidate
45 resume = &copy
46 }
47 }
48 return resume
49 }
50
51 // Committed mappings and published content are independent of retained sources.
52 // Check them before touching a source lock, path, fingerprint, or version.
53 func (a *App) resumeReadyHistoricalImport(ctx context.Context, state workspacestate.State, id string, source historicalSource) (SessionRestoreResult, bool, error) {
54 mapping, mapped := historicalMappingForSource(state, id)
55 if source.version != "" {
56 mapping, mapped = state.SourceMappings[id]
57 }
58 if mapped {
59 if state.SessionStates[mapping.SessionID].Lifecycle != workspacestate.Active {
60 return SessionRestoreResult{}, true, errors.New("historical session was archived or deleted; use the archive to restore it")
61 }
62 ref := session.SessionRef{HostID: localDesktopHostID, SessionID: mapping.SessionID}
63 if _, err := a.desktopSessionService("").Query().Stat(ctx, ref); err != nil {
64 return SessionRestoreResult{}, true, err
65 }
66 return SessionRestoreResult{Session: ref, WorkspaceID: mapping.WorkspaceID, Generation: state.Generation}, true, nil
67 }
68 op := pendingHistoricalOperation(state, id)
69 if op == nil || op.Phase != "content_ready" || op.Lifecycle != workspacestate.Active {
70 return SessionRestoreResult{}, false, nil
71 }
72 if err := a.replayDesktopSessionOperation(ctx, state, *op); err != nil {
73 return SessionRestoreResult{}, true, err
74 }
75 updated, err := a.workspaceRegistry().Load(ctx)
76 if err != nil {
77 return SessionRestoreResult{}, true, err
78 }
79 committed := updated.PendingOperations[op.ID]
80 if committed.Phase != "committed" || len(committed.SessionIDs) != 1 {
81 return SessionRestoreResult{}, true, workspacestate.ErrMutationConflict
82 }
83 return SessionRestoreResult{Session: session.SessionRef{HostID: localDesktopHostID, SessionID: committed.SessionIDs[0]}, WorkspaceID: committed.WorkspaceID, Generation: committed.ResultGeneration}, true, nil
84 }
85
85 lines GO