Skip to content

Commit 2d910dc

Browse files
committed
Merge feat/trust-ladder: pitch 01-05 trust ladder
2 parents d473b66 + d22c560 commit 2d910dc

25 files changed

Lines changed: 3477 additions & 16 deletions

internal/cmd/mcp.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ func mcpCmd() *cobra.Command {
2323
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, nil)))
2424

2525
eng := engineFrom(cmd)
26-
srv := mcp.NewServer(os.Stdin, os.Stdout, eng)
26+
teng := trustEngineFrom(cmd)
27+
srv := mcp.NewServer(os.Stdin, os.Stdout, eng, teng)
2728
return srv.Run(ctx)
2829
},
2930
}

internal/cmd/result.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,7 @@ type ForgetResult struct {
4646
Status string `json:"status"`
4747
Reason string `json:"reason,omitempty"`
4848
}
49+
50+
// Trust-command JSON payloads are built by trust.DecisionJSON and assembled
51+
// inline in trust.go. Keeping those shapes in typed structs here would
52+
// fork the schema — the shared helper is the one source of truth.

internal/cmd/root.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,17 @@ import (
1212

1313
"github.com/luuuc/brain/internal/engine"
1414
"github.com/luuuc/brain/internal/markdown"
15+
"github.com/luuuc/brain/internal/trust"
1516
"github.com/luuuc/brain/internal/version"
1617
)
1718

1819
type contextKey string
1920

2021
const (
21-
engineKey contextKey = "engine"
22-
jsonKey contextKey = "json"
22+
engineKey contextKey = "engine"
23+
jsonKey contextKey = "json"
24+
trustEngineKey contextKey = "trust"
25+
brainDirKey contextKey = "brain_dir"
2326
)
2427

2528
func rootCmd() *cobra.Command {
@@ -42,9 +45,16 @@ func rootCmd() *cobra.Command {
4245
if err != nil {
4346
return err
4447
}
48+
trustDir := filepath.Join(brainDir, "trust")
49+
teng, err := trust.NewEngine(cmd.Context(), trustDir, s, trust.WithLockTimeoutFromEnv())
50+
if err != nil {
51+
return err
52+
}
4553
ctx := cmd.Context()
4654
ctx = context.WithValue(ctx, engineKey, eng)
55+
ctx = context.WithValue(ctx, trustEngineKey, teng)
4756
ctx = context.WithValue(ctx, jsonKey, jsonFlag)
57+
ctx = context.WithValue(ctx, brainDirKey, brainDir)
4858
cmd.SetContext(ctx)
4959
return nil
5060
},
@@ -80,6 +90,12 @@ func engineFrom(cmd *cobra.Command) *engine.Engine {
8090
return v
8191
}
8292

93+
// trustEngineFrom extracts the trust engine from the command's context.
94+
func trustEngineFrom(cmd *cobra.Command) *trust.Engine {
95+
v, _ := cmd.Context().Value(trustEngineKey).(*trust.Engine)
96+
return v
97+
}
98+
8399
// isJSON returns whether --json was set on this command.
84100
func isJSON(cmd *cobra.Command) bool {
85101
v, _ := cmd.Context().Value(jsonKey).(bool)
@@ -161,6 +177,7 @@ func registerSubcommands(root *cobra.Command) {
161177
root.AddCommand(listCmd())
162178
root.AddCommand(forgetCmd())
163179
root.AddCommand(mcpCmd())
180+
root.AddCommand(trustCmd())
164181
}
165182

166183
// ExitError wraps an error with an exit code.

internal/cmd/trust.go

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
package cmd
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"path/filepath"
7+
"strings"
8+
9+
"github.com/spf13/cobra"
10+
11+
"github.com/luuuc/brain/internal/store"
12+
"github.com/luuuc/brain/internal/trust"
13+
)
14+
15+
// trustLongHelp is appended to every trust command's Long description so
16+
// operators can see the exit-code contract and supported env vars without
17+
// grepping source.
18+
const trustLongHelp = `
19+
Exit codes:
20+
0 success
21+
3 invalid input (missing flag, bad outcome, invalid urgency, ...)
22+
4 trust state conflict (lock acquisition timeout)
23+
24+
Environment:
25+
BRAIN_TRUST_LOCK_TIMEOUT_MS override the advisory-lock acquisition
26+
timeout in milliseconds (default: 5000).
27+
Intended for tests and operator debugging;
28+
leave unset in production.`
29+
30+
func trustCmd() *cobra.Command {
31+
var (
32+
domain string
33+
urgency string
34+
verbose bool
35+
)
36+
37+
cmd := &cobra.Command{
38+
Use: "trust",
39+
Short: "Check, record, and manage trust levels",
40+
Long: `Trust tracks per-domain autonomy levels for AI-produced work.
41+
42+
With --domain X, prints the current trust level and the recommendation
43+
(escalate / ship_notify / ship). Subcommands record outcomes, record
44+
overrides, repair corrupted state, or list all known domains.` + trustLongHelp,
45+
RunE: func(cmd *cobra.Command, args []string) error {
46+
if domain == "" {
47+
return &ExitError{Code: 3, Err: fmt.Errorf("--domain is required (or pass a subcommand: record, override, list, repair)")}
48+
}
49+
teng := trustEngineFrom(cmd)
50+
51+
opts := trust.CheckOptions{}
52+
if urgency != "" {
53+
if urgency != "hotfix" {
54+
return &ExitError{Code: 3, Err: fmt.Errorf("invalid --urgency %q (valid: hotfix)", urgency)}
55+
}
56+
opts.Hotfix = true
57+
}
58+
59+
dec, err := teng.Check(cmd.Context(), domain, opts)
60+
if err != nil {
61+
return mapTrustErr(err)
62+
}
63+
64+
printResult(cmd, trust.DecisionJSON(dec, verbose), func() string { return formatCheck(dec, verbose) })
65+
return nil
66+
},
67+
}
68+
cmd.Flags().StringVar(&domain, "domain", "", "trust domain to check")
69+
cmd.Flags().StringVar(&urgency, "urgency", "", "urgency override (hotfix)")
70+
cmd.Flags().BoolVar(&verbose, "verbose", false, "include history context")
71+
72+
cmd.AddCommand(trustRecordCmd())
73+
cmd.AddCommand(trustOverrideCmd())
74+
cmd.AddCommand(trustListCmd())
75+
cmd.AddCommand(trustRepairCmd())
76+
return cmd
77+
}
78+
79+
func trustRecordCmd() *cobra.Command {
80+
var (
81+
domain string
82+
outcome string
83+
ref string
84+
reason string
85+
)
86+
cmd := &cobra.Command{
87+
Use: "record",
88+
Short: "Record an outcome (clean or failure) for a domain",
89+
Long: "Record an outcome (clean or failure) for a domain." + trustLongHelp,
90+
RunE: func(cmd *cobra.Command, args []string) error {
91+
if domain == "" {
92+
return &ExitError{Code: 3, Err: fmt.Errorf("--domain is required")}
93+
}
94+
o := trust.Outcome(outcome)
95+
if !o.Valid() {
96+
return &ExitError{Code: 3, Err: fmt.Errorf("invalid --outcome %q (valid: clean, failure)", outcome)}
97+
}
98+
teng := trustEngineFrom(cmd)
99+
r, err := teng.Record(cmd.Context(), domain, o, trust.RecordOptions{Ref: ref, Reason: reason})
100+
if err != nil {
101+
return mapTrustErr(err)
102+
}
103+
104+
printResult(cmd, jsonRecord(r), func() string { return formatRecord(r) })
105+
return nil
106+
},
107+
}
108+
cmd.Flags().StringVar(&domain, "domain", "", "trust domain (required)")
109+
cmd.Flags().StringVar(&outcome, "outcome", "", "clean or failure (required)")
110+
cmd.Flags().StringVar(&ref, "ref", "", "optional reference (e.g. PR #42) for deduplication")
111+
cmd.Flags().StringVar(&reason, "reason", "", "optional reason (typically set on failures)")
112+
return cmd
113+
}
114+
115+
func trustOverrideCmd() *cobra.Command {
116+
var (
117+
domain string
118+
reason string
119+
)
120+
cmd := &cobra.Command{
121+
Use: "override",
122+
Short: "Record a human override for a domain",
123+
Long: "Record a human override for a domain. Writes a correction memory and appends an override event to the trust history." + trustLongHelp,
124+
RunE: func(cmd *cobra.Command, args []string) error {
125+
if domain == "" {
126+
return &ExitError{Code: 3, Err: fmt.Errorf("--domain is required")}
127+
}
128+
if reason == "" {
129+
return &ExitError{Code: 3, Err: fmt.Errorf("--reason is required")}
130+
}
131+
teng := trustEngineFrom(cmd)
132+
dec, err := teng.Override(cmd.Context(), domain, reason)
133+
if err != nil {
134+
return mapTrustErr(err)
135+
}
136+
137+
printResult(cmd, trust.DecisionJSON(dec, false), func() string {
138+
return fmt.Sprintf("Override recorded for %s\nLevel: %s\nRecommendation: %s\n", dec.Domain, dec.Level, dec.Recommendation)
139+
})
140+
return nil
141+
},
142+
}
143+
cmd.Flags().StringVar(&domain, "domain", "", "trust domain (required)")
144+
cmd.Flags().StringVar(&reason, "reason", "", "why the human overrode (required)")
145+
return cmd
146+
}
147+
148+
func trustListCmd() *cobra.Command {
149+
cmd := &cobra.Command{
150+
Use: "list",
151+
Short: "List every domain with its trust level",
152+
Long: "List every domain with its trust level." + trustLongHelp,
153+
RunE: func(cmd *cobra.Command, args []string) error {
154+
teng := trustEngineFrom(cmd)
155+
list, err := teng.List(cmd.Context())
156+
if err != nil {
157+
return mapTrustErr(err)
158+
}
159+
items := make([]map[string]any, 0, len(list))
160+
for _, d := range list {
161+
items = append(items, trust.DecisionJSON(d, false))
162+
}
163+
printResult(cmd, map[string]any{"domains": items, "count": len(list)}, func() string {
164+
var sb strings.Builder
165+
for _, d := range list {
166+
fmt.Fprintf(&sb, "%-14s %-10s clean_ships=%-4d", d.Domain, d.Level, d.CleanShips)
167+
if d.LastFailure != nil {
168+
fmt.Fprintf(&sb, " last_failure=%s", d.LastFailure.Format("2006-01-02"))
169+
}
170+
sb.WriteString("\n")
171+
}
172+
fmt.Fprintf(&sb, "\n%d domains\n", len(list))
173+
return sb.String()
174+
})
175+
return nil
176+
},
177+
}
178+
return cmd
179+
}
180+
181+
func trustRepairCmd() *cobra.Command {
182+
cmd := &cobra.Command{
183+
Use: "repair",
184+
Short: "Repair corrupted trust.yml by restoring from the backup",
185+
Long: `Repair corrupted trust.yml by restoring from a crash-left sibling.
186+
187+
If trust.yml is valid, repair does nothing but sweeps stale .tmp files.
188+
If the live file is missing, repair promotes trust.yml.tmp (the latest
189+
write-in-progress, if present) or falls back to trust.yml.bak (the prior
190+
committed state). Schema-version mismatches block auto-promote — the
191+
operator must migrate by hand.` + trustLongHelp,
192+
RunE: func(cmd *cobra.Command, args []string) error {
193+
dir, ok := cmd.Context().Value(brainDirKey).(string)
194+
if !ok || dir == "" {
195+
return fmt.Errorf("no .brain/ directory resolved")
196+
}
197+
source, err := trust.Repair(cmd.Context(), filepath.Join(dir, "trust"))
198+
if err != nil {
199+
return mapTrustErr(err)
200+
}
201+
202+
// Report exactly what Repair did — operators at 3am need to
203+
// know whether state came from .tmp (mid-rename crash) or
204+
// .bak (ordinary corruption) so they can diagnose the root
205+
// cause, not just observe "it works now."
206+
var msg string
207+
switch source {
208+
case trust.RepairAlreadyValid:
209+
msg = "trust.yml is valid; no repair needed.\n"
210+
case trust.RepairFromTmp:
211+
msg = "Restored trust.yml from trust.yml.tmp (recovered from mid-write crash).\n"
212+
case trust.RepairFromBak:
213+
msg = "Restored trust.yml from trust.yml.bak (previous good state).\n"
214+
default:
215+
msg = fmt.Sprintf("Repair returned unexpected source %q\n", source)
216+
}
217+
payload := map[string]any{
218+
"source": string(source),
219+
"already_valid": source == trust.RepairAlreadyValid,
220+
}
221+
printResult(cmd, payload, func() string { return msg })
222+
return nil
223+
},
224+
}
225+
return cmd
226+
}
227+
228+
func formatCheck(d trust.Decision, verbose bool) string {
229+
var sb strings.Builder
230+
fmt.Fprintf(&sb, "Domain: %s\n", d.Domain)
231+
fmt.Fprintf(&sb, "Level: %s\n", d.Level)
232+
fmt.Fprintf(&sb, "Clean ships: %d\n", d.CleanShips)
233+
if d.LastFailure != nil {
234+
fmt.Fprintf(&sb, "Last failure: %s\n", d.LastFailure.Format("2006-01-02"))
235+
}
236+
if d.LastPromotion != nil {
237+
fmt.Fprintf(&sb, "Last promotion: %s\n", d.LastPromotion.Format("2006-01-02"))
238+
}
239+
if d.Hotfix {
240+
sb.WriteString("Urgency: hotfix\n")
241+
}
242+
fmt.Fprintf(&sb, "Recommendation: %s\n", d.Recommendation)
243+
if verbose && len(d.History) > 0 {
244+
sb.WriteString("History:\n")
245+
for _, e := range d.History {
246+
fmt.Fprintf(&sb, " %s %s", e.At.Format("2006-01-02"), e.Kind)
247+
if e.Outcome != "" {
248+
fmt.Fprintf(&sb, " outcome=%s", e.Outcome)
249+
}
250+
if e.From != "" || e.To != "" {
251+
fmt.Fprintf(&sb, " %s→%s", e.From, e.To)
252+
}
253+
if e.Ref != "" {
254+
fmt.Fprintf(&sb, " ref=%q", e.Ref)
255+
}
256+
if e.Reason != "" {
257+
fmt.Fprintf(&sb, " reason=%q", e.Reason)
258+
}
259+
sb.WriteString("\n")
260+
}
261+
}
262+
return sb.String()
263+
}
264+
265+
func formatRecord(r trust.RecordResult) string {
266+
var sb strings.Builder
267+
if r.Deduplicated {
268+
sb.WriteString("Duplicate ref — outcome not recorded.\n")
269+
}
270+
fmt.Fprintf(&sb, "Domain: %s\n", r.Decision.Domain)
271+
fmt.Fprintf(&sb, "Level: %s\n", r.Decision.Level)
272+
fmt.Fprintf(&sb, "Clean ships: %d\n", r.Decision.CleanShips)
273+
if r.Promoted {
274+
fmt.Fprintf(&sb, "Promoted to %s.\n", r.Decision.Level)
275+
}
276+
if r.Demoted {
277+
sb.WriteString("Demoted to ask after failure.\n")
278+
}
279+
if r.LessonsTouched > 0 {
280+
fmt.Fprintf(&sb, "Lessons ticked: %d", r.LessonsTouched)
281+
if r.LessonsRetired > 0 {
282+
fmt.Fprintf(&sb, " (retired: %d)", r.LessonsRetired)
283+
}
284+
sb.WriteString("\n")
285+
}
286+
if r.EvictedRefs > 0 {
287+
fmt.Fprintf(&sb, "Seen-refs evicted (FIFO): %d\n", r.EvictedRefs)
288+
}
289+
fmt.Fprintf(&sb, "Recommendation: %s\n", r.Decision.Recommendation)
290+
return sb.String()
291+
}
292+
293+
// mapTrustErr translates engine errors into CLI ExitErrors. Lock timeouts
294+
// surface as exit code 4 (conflict).
295+
func mapTrustErr(err error) error {
296+
if err == nil {
297+
return nil
298+
}
299+
if errors.Is(err, store.ErrConflict) {
300+
return &ExitError{Code: 4, Err: err}
301+
}
302+
return err
303+
}
304+
305+
func jsonRecord(r trust.RecordResult) any {
306+
out := map[string]any{
307+
"decision": trust.DecisionJSON(r.Decision, false),
308+
"promoted": r.Promoted,
309+
"demoted": r.Demoted,
310+
"deduplicated": r.Deduplicated,
311+
}
312+
if r.LessonsTouched > 0 {
313+
out["lessons_touched"] = r.LessonsTouched
314+
}
315+
if r.LessonsRetired > 0 {
316+
out["lessons_retired"] = r.LessonsRetired
317+
}
318+
if r.EvictedRefs > 0 {
319+
out["evicted_refs"] = r.EvictedRefs
320+
}
321+
return out
322+
}
323+

0 commit comments

Comments
 (0)