返回 DeepSeek-Reasonix
source_identity.go
根目录 / desktop / internal / workspacestate / source_identity.go
1 package workspacestate
2
3 import (
4 "crypto/sha256"
5 "fmt"
6 "slices"
7 "strings"
8 )
9
10 // Source identities written before physical-path normalization remain durable
11 // receipts. Add read aliases without rewriting their keys or operation journals.
12 type sourceIdentityIndex struct {
13 aliases map[string][]string
14 owners map[string][]string
15 }
16
17 func newSourceIdentityIndex(state State) *sourceIdentityIndex {
18 index := &sourceIdentityIndex{aliases: map[string][]string{}, owners: map[string][]string{}}
19 paths := map[string]string{}
20 add := func(mapping SourceMapping, committed bool) {
21 key := mapping.SourceKey
22 if key == "" {
23 return
24 }
25 keys, known := index.aliases[key]
26 if !known {
27 keys = []string{key}
28 path, resolved := paths[mapping.Path]
29 if !resolved {
30 path, _ = sourcePathKey(mapping.Path)
31 paths[mapping.Path] = path
32 }
33 if path != "" {
34 heads := []string{mapping.HeadID}
35 // Old lineage receipts kept the originating head for a single-session
36 // directory; discovery uses no head. Legacy DAG heads stay distinct.
37 if mapping.Format == "canonical" && mapping.HeadID != "" {
38 heads = append(heads, "")
39 }
40 for _, head := range heads {
41 normalized := fmt.Sprintf("%x", sha256.Sum256([]byte(path+"\x00"+head)))
42 if _, version, found := strings.Cut(key, ":review:"); found {
43 normalized += ":review:" + version
44 }
45 if normalized != key {
46 keys = append(keys, normalized)
47 }
48 }
49 }
50 index.aliases[key] = keys
51 }
52 if committed {
53 for _, alias := range keys {
54 index.owners[alias] = append(index.owners[alias], key)
55 }
56 }
57 }
58 for _, mapping := range state.SourceMappings {
59 add(mapping, true)
60 }
61 for _, op := range state.PendingOperations {
62 if op.Mapping != nil {
63 add(*op.Mapping, false)
64 }
65 }
66 return index
67 }
68
69 func (s State) sourceIdentityIndex() *sourceIdentityIndex {
70 if s.sourceIdentities != nil {
71 return s.sourceIdentities
72 }
73 return newSourceIdentityIndex(s)
74 }
75
76 // SourceKeys returns durable storage and current lookup identities. Legacy DAG
77 // heads and reviewed versions remain independent from their parent source.
78 func (s State) SourceKeys(key string) []string {
79 keys := s.sourceIdentityIndex().aliases[key]
80 if len(keys) == 0 {
81 return []string{key}
82 }
83 return slices.Clone(keys)
84 }
85
86 // ResolveSource rejects ambiguous normalized ownership instead of choosing a
87 // destination or admitting another import. Exact durable keys remain usable.
88 func (s State) ResolveSource(key string) (SourceMapping, bool, error) {
89 if mapping, ok := s.SourceMappings[key]; ok {
90 return cloneSourceMapping(mapping), true, nil
91 }
92 var result SourceMapping
93 found := false
94 for _, owner := range s.sourceIdentityIndex().owners[key] {
95 mapping, exists := s.SourceMappings[owner]
96 if !exists {
97 continue
98 }
99 if found && (result.SessionID != mapping.SessionID || result.WorkspaceID != mapping.WorkspaceID || result.Fingerprint != mapping.Fingerprint) {
100 return SourceMapping{}, false, ErrMutationConflict
101 }
102 if !found || mapping.SourceKey < result.SourceKey {
103 result = mapping
104 }
105 found = true
106 }
107 return cloneSourceMapping(result), found, nil
108 }
109
109 lines GO