返回 DeepSeek-Reasonix
header.go
根目录 / internal / session / header.go
1 package session
2
3 import (
4 "encoding/json"
5 "errors"
6 "fmt"
7 "os"
8 "path/filepath"
9 "strings"
10 "time"
11
12 "reasonix/internal/fileutil"
13 )
14
15 const (
16 sessionHeaderName = "header.json"
17 SessionHeaderSchemaVersion = 1
18 )
19
20 type SessionOrigin string
21
22 const (
23 SessionOriginNew SessionOrigin = "new"
24 SessionOriginFork SessionOrigin = "fork"
25 SessionOriginCanonicalImport SessionOrigin = "canonical-v4-import"
26 SessionOriginLegacyImport SessionOrigin = "legacy-import"
27 )
28
29 // SessionHeader is immutable Desktop ownership metadata. It deliberately
30 // excludes display and model projections so Session remains their sole source.
31 type SessionHeader struct {
32 SchemaVersion int `json:"schemaVersion"`
33 SessionID string `json:"sessionId"`
34 CreatedAt time.Time `json:"createdAt"`
35 CWD string `json:"cwd"`
36 ParentSessionID string `json:"parentSessionId,omitempty"`
37 Origin SessionOrigin `json:"origin"`
38 }
39
40 func headerForCreate(options CreateOptions) (*SessionHeader, error) {
41 cwd := strings.TrimSpace(options.CWD)
42 parentID := strings.TrimSpace(options.ParentSessionID)
43 origin := options.Origin
44 if cwd == "" && parentID == "" && origin == "" {
45 return nil, nil
46 }
47 if cwd != "" {
48 cwd = filepath.Clean(cwd)
49 }
50 if parentID != "" {
51 if err := validateSessionID(parentID); err != nil {
52 return nil, fmt.Errorf("session: invalid parent identity: %w", err)
53 }
54 }
55 if origin == "" {
56 origin = SessionOriginNew
57 }
58 if !validSessionOrigin(origin) {
59 return nil, fmt.Errorf("session: unsupported session origin %q", origin)
60 }
61 return &SessionHeader{
62 SchemaVersion: SessionHeaderSchemaVersion,
63 SessionID: strings.TrimSpace(options.SessionID),
64 CWD: cwd,
65 ParentSessionID: parentID,
66 Origin: origin,
67 }, nil
68 }
69
70 func validSessionOrigin(origin SessionOrigin) bool {
71 switch origin {
72 case SessionOriginNew, SessionOriginFork, SessionOriginCanonicalImport, SessionOriginLegacyImport:
73 return true
74 default:
75 return false
76 }
77 }
78
79 func writeSessionHeader(dir string, header SessionHeader) error {
80 if header.SchemaVersion != SessionHeaderSchemaVersion || header.SessionID == "" || header.CreatedAt.IsZero() || !validSessionOrigin(header.Origin) {
81 return fmt.Errorf("session: invalid session header")
82 }
83 body, err := json.Marshal(header)
84 if err != nil {
85 return err
86 }
87 return fileutil.AtomicWriteFileStrict(filepath.Join(dir, sessionHeaderName), append(body, '\n'), 0o600)
88 }
89
90 func writeSessionHeaderForCreate(dir, sessionID string, createdAt time.Time, options CreateOptions) error {
91 options.SessionID = sessionID
92 header, err := headerForCreate(options)
93 if err != nil || header == nil {
94 return err
95 }
96 header.CreatedAt = createdAt
97 return writeSessionHeader(dir, *header)
98 }
99
100 func validateSessionHeaderForCreate(dir, sessionID string, options CreateOptions) error {
101 expected, err := headerForCreate(options)
102 if err != nil || expected == nil {
103 return err
104 }
105 header, found, err := readSessionHeader(dir, sessionID)
106 if err != nil {
107 return err
108 }
109 if !found {
110 return fmt.Errorf("%w: session header is missing", ErrDamagedStore)
111 }
112 if header.CWD != expected.CWD {
113 return fmt.Errorf("%w: session header workspace does not match requested ownership", ErrDamagedStore)
114 }
115 if header.ParentSessionID != expected.ParentSessionID {
116 return fmt.Errorf("%w: session header lineage does not match requested ownership", ErrDamagedStore)
117 }
118 if header.Origin != expected.Origin {
119 return fmt.Errorf("%w: session header origin does not match requested ownership", ErrDamagedStore)
120 }
121 return nil
122 }
123
124 func readSessionHeader(dir, sessionID string) (SessionHeader, bool, error) {
125 body, err := os.ReadFile(filepath.Join(dir, sessionHeaderName))
126 if errors.Is(err, os.ErrNotExist) {
127 return SessionHeader{}, false, nil
128 }
129 if err != nil {
130 return SessionHeader{}, false, err
131 }
132 var header SessionHeader
133 if err := json.Unmarshal(body, &header); err != nil {
134 return SessionHeader{}, true, fmt.Errorf("%w: decode session header: %w", ErrDamagedStore, err)
135 }
136 if header.SchemaVersion != SessionHeaderSchemaVersion {
137 return SessionHeader{}, true, fmt.Errorf("%w: session header version %d", ErrUnsupportedVersion, header.SchemaVersion)
138 }
139 if header.SessionID != sessionID || header.CreatedAt.IsZero() || !validSessionOrigin(header.Origin) {
140 return SessionHeader{}, true, fmt.Errorf("%w: invalid session header", ErrDamagedStore)
141 }
142 return header, true, nil
143 }
144
144 lines GO