返回 DeepSeek-Reasonix
ownership.go
根目录 / desktop / internal / hostrpc / ownership.go
1 package hostrpc
2
3 import (
4 "fmt"
5 "go/ast"
6 "go/parser"
7 "go/token"
8 "os"
9 "path/filepath"
10 "slices"
11 "strconv"
12 "strings"
13 )
14
15 // CommandOwnership records source provenance, not a second authorization
16 // policy. The existing owner resolves scope and validates all wire inputs.
17 type CommandOwnership struct {
18 Domain string `json:"domain,omitempty"`
19 Owner string `json:"owner,omitempty"`
20 Sources []string `json:"sources,omitempty"`
21 Scope CommandScope `json:"scope"`
22 }
23
24 type CommandScope struct {
25 Kind string `json:"kind"`
26 Resolver string `json:"resolver"`
27 Inputs []string `json:"inputs"`
28 }
29
30 // SourceOwnership scans all platform declarations, independent of build tags.
31 // It uses repository-relative filenames and named inputs, never source line
32 // numbers, absolute paths or a guess based on the command's name.
33 func SourceOwnership(dir, target string) (map[string]CommandOwnership, error) {
34 entries, err := os.ReadDir(dir)
35 if err != nil {
36 return nil, err
37 }
38 owners := map[string]CommandOwnership{}
39 for _, entry := range entries {
40 name := entry.Name()
41 if entry.IsDir() || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
42 continue
43 }
44 file, err := parser.ParseFile(token.NewFileSet(), filepath.Join(dir, name), nil, 0)
45 if err != nil {
46 return nil, err
47 }
48 contextAlias := ""
49 for _, imp := range file.Imports {
50 path, _ := strconv.Unquote(imp.Path.Value)
51 if path == "context" {
52 contextAlias = "context"
53 if imp.Name != nil {
54 contextAlias = imp.Name.Name
55 }
56 }
57 }
58 for _, decl := range file.Decls {
59 fn, ok := decl.(*ast.FuncDecl)
60 if !ok || fn.Recv == nil || !fn.Name.IsExported() {
61 continue
62 }
63 receiver := fn.Recv.List[0].Type
64 if ptr, ok := receiver.(*ast.StarExpr); ok {
65 receiver = ptr.X
66 }
67 id, ok := receiver.(*ast.Ident)
68 if !ok || id.Name != target {
69 continue
70 }
71 inputs := sourceWireInputs(fn.Type.Params, contextAlias)
72 owner := target + "." + fn.Name.Name
73 kind := "owner-inputs"
74 if len(inputs) == 0 {
75 kind = "owner-state"
76 }
77 source := "desktop/" + name
78 metadata, exists := owners[fn.Name.Name]
79 if exists {
80 if !slices.Equal(metadata.Scope.Inputs, inputs) {
81 return nil, fmt.Errorf("%s: platform declarations disagree on wire inputs", owner)
82 }
83 metadata.Sources = append(metadata.Sources, source)
84 } else {
85 metadata = CommandOwnership{Owner: owner, Sources: []string{source}, Scope: CommandScope{Kind: kind, Resolver: owner, Inputs: inputs}}
86 }
87 // A domain is the complete set of actual source modules. Platform
88 // alternatives are kept together, so every platform has one digest.
89 slices.Sort(metadata.Sources)
90 metadata.Domain = strings.Join(metadata.Sources, "|")
91 owners[fn.Name.Name] = metadata
92 }
93 }
94 if len(owners) == 0 {
95 return nil, fmt.Errorf("no %s commands in %s", target, dir)
96 }
97 return owners, nil
98 }
99
100 func sourceWireInputs(params *ast.FieldList, contextAlias string) []string {
101 inputs := []string{}
102 for index, field := range params.List {
103 if sel, ok := field.Type.(*ast.SelectorExpr); ok && index == 0 && len(field.Names) <= 1 {
104 if pkg, ok := sel.X.(*ast.Ident); ok && contextAlias != "" && pkg.Name == contextAlias && sel.Sel.Name == "Context" {
105 continue
106 }
107 }
108 if len(field.Names) == 0 {
109 inputs = append(inputs, fmt.Sprintf("arg%d", len(inputs)))
110 continue
111 }
112 for _, param := range field.Names {
113 inputs = append(inputs, param.Name)
114 }
115 }
116 return inputs
117 }
118
118 lines GO