返回 DeepSeek-Reasonix
main.go
1 // pathidentityprobe compares path metadata without loading Reasonix configuration
2 // or acquiring locks. Its only write is a new diagnostic report.
3 package main
4
5 import (
6 "context"
7 "encoding/json"
8 "flag"
9 "fmt"
10 "os"
11 "os/user"
12 "path/filepath"
13 "runtime"
14 "time"
15
16 "reasonix/internal/pathidentity"
17 )
18
19 var sourceCommit = "development"
20
21 type report struct {
22 Schema int `json:"schema"`
23 Started string `json:"started_utc"`
24 GoVersion string `json:"go_version"`
25 Platform string `json:"platform"`
26 SourceCommit string `json:"source_commit"`
27 IdentityVersion int `json:"identity_version"`
28 UserHome string `json:"os_user_home"`
29 Notes []string `json:"notes"`
30 Paths []observation `json:"paths"`
31 TimedOut bool `json:"timed_out"`
32 Incomplete string `json:"incomplete_path,omitempty"`
33 }
34
35 func main() {
36 out := flag.String("out", "", "new report file; default: beside this executable")
37 timeout := flag.Duration("timeout", 60*time.Second, "total collection deadline")
38 flag.Parse()
39 if *timeout <= 0 || *timeout > 5*time.Minute {
40 fmt.Fprintln(os.Stderr, "timeout must be between 0 and 5m")
41 os.Exit(2)
42 }
43 if err := run(*out, *timeout, flag.Args()); err != nil {
44 fmt.Fprintln(os.Stderr, err)
45 os.Exit(1)
46 }
47 }
48
49 func run(output string, timeout time.Duration, paths []string) error {
50 current, err := user.Current()
51 if err != nil {
52 return fmt.Errorf("resolve OS user: %w", err)
53 }
54 if output == "" {
55 exe, err := os.Executable()
56 if err != nil {
57 return err
58 }
59 output = filepath.Join(filepath.Dir(exe), "reasonix-path-probe-"+time.Now().UTC().Format("20060102-150405.000000000")+".json")
60 }
61 // Exclusive creation refuses to overwrite any existing file or final symlink.
62 file, err := os.OpenFile(output, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600)
63 if err != nil {
64 return fmt.Errorf("create new report: %w", err)
65 }
66 defer file.Close()
67 ctx, cancel := context.WithTimeout(context.Background(), timeout)
68 defer cancel()
69 result := collect(ctx, current, paths)
70 raw, err := encodeRedacted(result, current)
71 if err != nil {
72 return err
73 }
74 if _, err := file.Write(append(raw, '\n')); err != nil {
75 return err
76 }
77 if err := file.Close(); err != nil {
78 return err
79 }
80 if result.TimedOut {
81 fmt.Println("Collection timed out; the partial report records the unfinished path.")
82 }
83 fmt.Println("Report:", output)
84 fmt.Println("No settings, credentials, lock files or sessions were changed.")
85 return nil
86 }
87
88 func collect(ctx context.Context, current *user.User, extra []string) report {
89 r := report{Schema: 1, Started: time.Now().UTC().Format(time.RFC3339),
90 GoVersion: runtime.Version(), Platform: runtime.GOOS + "/" + runtime.GOARCH,
91 SourceCommit: sourceCommit, IdentityVersion: pathidentity.Version, UserHome: current.HomeDir,
92 Notes: []string{"Metadata only; no config loading, content reads, lock acquisition or network requests.",
93 "Path identities are from this build's source resolver; filepath_eval_symlinks remains the independent Go baseline.",
94 "A missing synthetic child is intentional. Native resolution is diagnostic, not an applied fix."},
95 Paths: []observation{}}
96 plan := make(chan []string, 1)
97 go func() { plan <- defaultPaths(current, extra) }()
98 var queue []string
99 select {
100 case queue = <-plan:
101 case <-ctx.Done():
102 r.TimedOut, r.Incomplete = true, "enumerating configured lock directory"
103 return r
104 }
105 seen := map[string]bool{}
106 for len(queue) > 0 && len(r.Paths) < 300 {
107 path := queue[0]
108 queue = queue[1:]
109 if seen[path] {
110 continue
111 }
112 seen[path] = true
113 one := make(chan observation, 1)
114 go func() { one <- inspect(path) }()
115 select {
116 case got := <-one:
117 r.Paths = append(r.Paths, got)
118 // Independently inspect the native target: alias-only failures must
119 // be distinguished from failures on the target itself.
120 if got.Native.Path != "" && !seen[got.Native.Path] {
121 queue = append(queue, got.Native.Path)
122 }
123 case <-ctx.Done():
124 r.TimedOut, r.Incomplete = true, path
125 return r
126 }
127 }
128 if len(queue) > 0 {
129 r.Notes = append(r.Notes, "Stopped at the 300-path limit.")
130 }
131 return r
132 }
133
134 func encodeRedacted(r report, current *user.User) ([]byte, error) {
135 raw, err := json.Marshal(r)
136 if err != nil {
137 return nil, err
138 }
139 var value any
140 if err := json.Unmarshal(raw, &value); err != nil {
141 return nil, err
142 }
143 return json.MarshalIndent(redactValue(value, current), "", " ")
144 }
145
145 lines GO