返回 DeepSeek-Reasonix
topic_removal_sessions.go
根目录 / desktop / internal / workspacestate / topic_removal_sessions.go
1 package workspacestate
2
3 import (
4 "crypto/sha256"
5 "encoding/hex"
6 "encoding/json"
7 "slices"
8 "strings"
9 )
10
11 // TopicSessionRemovalToken is shared by the inspection and durable commit
12 // owners. Rechecking only before staging leaves a cross-process rename gap.
13 func TopicSessionRemovalToken(state State, workspaceID, topicID string) (string, string, error) {
14 associations := map[string]any{}
15 owner := ""
16 for id, status := range state.SessionStates {
17 presentation := state.Presentation[id]
18 if (presentation.TopicID != topicID && "canonical-"+id != topicID) || status.Lifecycle == Deleted {
19 continue
20 }
21 owners := 0
22 for wid, workspace := range state.Workspaces {
23 if !slices.Contains(workspace.SessionIDs, id) {
24 continue
25 }
26 owners++
27 if (owner != "" && owner != wid) || (workspaceID != "" && workspaceID != wid) {
28 return "", "", ErrMutationConflict
29 }
30 owner = wid
31 }
32 if owners != 1 {
33 return "", "", ErrMutationConflict
34 }
35 associations[id] = []any{presentation, status}
36 }
37 if len(associations) == 0 {
38 return "", "", nil
39 }
40 body, err := json.Marshal([]any{owner, topicID, associations, state.Workspaces[owner].Organization})
41 if err != nil {
42 return "", "", err
43 }
44 digest := sha256.Sum256(body)
45 return hex.EncodeToString(digest[:]), owner, nil
46 }
47
48 // Runs under the same cross-process transaction as lifecycle publication,
49 // including startup replay. A committed child is an idempotent receipt.
50 func validateTopicRemovalArchive(state State, op Operation) error {
51 id, topicArchive := strings.CutPrefix(op.ID, "topic-sessions-")
52 if !topicArchive || op.Kind != "archive" || op.Phase == "committed" {
53 return nil
54 }
55 removal, exists := state.TopicRemovals[id]
56 if !exists {
57 return nil
58 }
59 token, _, err := TopicSessionRemovalToken(state, removal.WorkspaceID, removal.TopicID)
60 if err != nil {
61 return err
62 }
63 if removal.SessionToken == "" {
64 // A legacy-only intent cannot silently acquire a new formal identity.
65 if token != "" {
66 return ErrMutationConflict
67 }
68 return nil
69 }
70 if token != removal.SessionToken {
71 return ErrMutationConflict
72 }
73 return nil
74 }
75
75 lines GO