返回 DeepSeek-Reasonix
cache_shape_test.go
根目录 / internal / recovery / cache_shape_test.go
1 package recovery_test
2
3 import (
4 "context"
5 "encoding/json"
6 "testing"
7
8 "reasonix/internal/agent"
9 "reasonix/internal/event"
10 "reasonix/internal/recovery"
11 "reasonix/internal/tool"
12 )
13
14 // TestRecoveryWiringPreservesSuccessPathCacheShape pins the product rule that
15 // enabling Auto Guard must not change the provider-visible
16 // system prompt or tool schemas on the success path. Only failure-path dynamic
17 // user-tail and the independent reviewer session may add tokens.
18 func TestRecoveryWiringPreservesSuccessPathCacheShape(t *testing.T) {
19 reg := tool.NewRegistry()
20 reg.Add(&shapeProbeTool{name: "read_file", readOnly: true})
21 reg.Add(&shapeProbeTool{name: "write_file", readOnly: false})
22 reg.Add(&shapeProbeTool{name: "bash", readOnly: false})
23
24 sys := "You are a coding agent. Keep the stable system prompt byte-stable."
25 schemas := reg.Schemas()
26
27 baseline := agent.CaptureShape(sys, schemas, 0)
28
29 // Gate with recovery enabled but no failures must not alter agent schemas.
30 gate := recovery.NewGate(recovery.Options{Mode: func() string { return "auto" }})
31 sess := agent.NewSession(sys)
32 ag := agent.New(nil, reg, sess, agent.Options{
33 RecoveryGate: gate,
34 }, event.Discard)
35
36 if ag.RecoveryGate() == nil {
37 t.Fatal("expected recovery gate attached")
38 }
39 // Provider-visible prefix is still the stable system string + registry schemas.
40 _ = ag
41 withRecovery := agent.CaptureShape(sys, reg.Schemas(), 0)
42 if baseline.SystemHash != withRecovery.SystemHash {
43 t.Fatalf("system prompt hash changed with recovery gate: %s vs %s", baseline.SystemHash, withRecovery.SystemHash)
44 }
45 if baseline.ToolsHash != withRecovery.ToolsHash {
46 t.Fatalf("tool schema hash changed with recovery gate: %s vs %s", baseline.ToolsHash, withRecovery.ToolsHash)
47 }
48 if baseline.PrefixHash != withRecovery.PrefixHash {
49 t.Fatalf("prefix hash changed with recovery gate: %s vs %s", baseline.PrefixHash, withRecovery.PrefixHash)
50 }
51
52 // Reviewer uses an isolated policy prompt — must not leak into main agent.
53 reviewer := recovery.NewSession(nil, nil)
54 if reviewer == nil {
55 t.Fatal("expected reviewer session")
56 }
57 // Independent reviewer system prompt is not the main agent system prompt.
58 if recovery.PolicyPrompt == sys {
59 t.Fatal("reviewer policy must stay distinct from main system prompt")
60 }
61 }
62
63 type shapeProbeTool struct {
64 name string
65 readOnly bool
66 }
67
68 func (t *shapeProbeTool) Name() string { return t.name }
69 func (t *shapeProbeTool) Description() string { return t.name }
70 func (t *shapeProbeTool) Schema() json.RawMessage {
71 return json.RawMessage(`{"type":"object","properties":{}}`)
72 }
73 func (t *shapeProbeTool) ReadOnly() bool { return t.readOnly }
74 func (t *shapeProbeTool) Execute(context.Context, json.RawMessage) (string, error) {
75 return "ok", nil
76 }
77
77 lines GO