internal/store/context.go
1
package store
3
import (
4
"fmt"
5
"regexp"
6
"strings"
7
)
9
// GitContext is what was true when an annotation was written. It is written
10
// once and never rewritten: reanchor changes where an annotation aims, not
11
// what it witnessed. ADR 0014.
12
type GitContext struct {
13
Commit string `yaml:"commit"`
14
Path string `yaml:"path"`
15
Line int `yaml:"line,omitempty"`
16
EndLine int `yaml:"end_line,omitempty"`
17
}
19
var fullCommitSHA = regexp.MustCompile(`^[0-9a-f]{40}$|^[0-9a-f]{64}$`)
21
func (g GitContext) Validate() error {
22
if !fullCommitSHA.MatchString(g.Commit) {
23
return fmt.Errorf("git.commit %q is not a full SHA; an abbreviated one stops being unique as history grows", g.Commit)
24
}
25
if _, err := validSourcePath(g.Path); err != nil {
26
return fmt.Errorf("git.path: %w", err)
27
}
28
switch {
29
case g.Line < 0 || g.EndLine < 0:
30
return fmt.Errorf("git.line %d-%d is not a line range", g.Line, g.EndLine)
31
case g.EndLine != 0 && g.Line == 0:
32
return fmt.Errorf("git.end_line %d given without a start line", g.EndLine)
33
case g.EndLine != 0 && g.EndLine < g.Line:
34
return fmt.Errorf("git.end_line %d is before git.line %d", g.EndLine, g.Line)
35
}
36
return nil
37
}
39
// AuthorKind separates a person from an agent, because an agent weighing an
40
// annotation needs to know whether another agent wrote it.
41
type AuthorKind string
43
const (
44
AuthorHuman AuthorKind = "human"
45
AuthorAgent AuthorKind = "agent"
46
AuthorUnknown AuthorKind = "unknown"
47
)
49
// IdentitySource records how much an identity is worth. Git config asserts an
50
// identity; it does not prove one.
51
type IdentitySource string
53
const (
54
FromGitConfig IdentitySource = "git-config"
55
FromSession IdentitySource = "session"
56
FromExplicit IdentitySource = "explicit"
57
FromMigration IdentitySource = "migration"
58
FromOIDCProxy IdentitySource = "oidc-proxy"
59
FromScopedAgent IdentitySource = "bearer-credential"
60
)
62
// Author is a claim, not a proof, unless Verified says otherwise. ADR 0015.
63
type Author struct {
64
Name string `yaml:"name"`
65
Email string `yaml:"email,omitempty"`
66
Kind AuthorKind `yaml:"kind"`
67
Source IdentitySource `yaml:"source"`
68
Account string `yaml:"account,omitempty"`
69
Verified string `yaml:"verified,omitempty"`
70
}
72
func (a Author) Validate() error {
73
if strings.TrimSpace(a.Name) == "" {
74
return fmt.Errorf("author has no name")
75
}
76
switch a.Kind {
77
case AuthorHuman, AuthorAgent, AuthorUnknown:
78
default:
79
return fmt.Errorf("author kind %q, want one of %s, %s, %s", a.Kind, AuthorHuman, AuthorAgent, AuthorUnknown)
80
}
81
switch a.Source {
82
case FromGitConfig, FromSession, FromExplicit, FromMigration, FromOIDCProxy, FromScopedAgent:
83
default:
84
return fmt.Errorf("author source %q is unknown", a.Source)
85
}
86
if a.Kind == AuthorUnknown && a.Source != FromMigration {
87
return fmt.Errorf("unknown author must have migration as its source")
88
}
89
if a.Kind != AuthorUnknown && a.Source == FromMigration {
90
return fmt.Errorf("migration source requires an unknown author")
91
}
92
return nil
93
}
95
// IsProven reports whether the identity was verified by some mechanism. An
96
// unverified identity must not be rendered as though it were checked.
97
func (a Author) IsProven() bool { return a.Verified != "" }