返回 DeepSeek-Reasonix
shell_contract_test.go
根目录 / internal / evidence / shell_contract_test.go
1 package evidence
2
3 import (
4 "encoding/json"
5 "testing"
6 )
7
8 func TestBashToolCallUsesNonTerminalInlineInterpreter(t *testing.T) {
9 tests := []struct {
10 command string
11 want bool
12 }{
13 {command: `python3 -c 'open("x","w").write("y")' ; node verify_frontend_logic.js`, want: true},
14 {command: `node -e 'console.log(1)' || go test ./...`, want: true},
15 {command: `node -e 'console.log(1)' | tee out.txt`, want: true},
16 // `&&` short-circuits: a failing interpreter is still the call's exit
17 // status, so nothing is hidden and the shape stays allowed.
18 {command: `node -e 'console.log(1)' && go test ./...`, want: false},
19 {command: `python3 -c 'print(1)'`, want: false},
20 {command: `go test ./...`, want: false},
21 {command: `node --check app.js`, want: false},
22 }
23 for _, tt := range tests {
24 args, err := json.Marshal(map[string]string{"command": tt.command})
25 if err != nil {
26 t.Fatal(err)
27 }
28 if got := BashToolCallUsesNonTerminalInlineInterpreter(args); got != tt.want {
29 t.Errorf("%q => %v, want %v", tt.command, got, tt.want)
30 }
31 }
32 }
33
34 func TestOrdinaryModeShellContractClassifiers(t *testing.T) {
35 // deliveryMixed is the broad receipt-integrity classifier Delivery keeps;
36 // ordinaryMixed additionally requires that the earlier failure can be hidden.
37 cases := []struct {
38 command string
39 deliveryMixed bool
40 ordinaryMixed bool
41 mask bool
42 inline bool
43 }{
44 // Arbitrary node scripts are not host-recognized verifiers; the
45 // non-terminal inline interpreter rule still blocks this shape.
46 {command: `python3 -c 'open("/tmp/x","w").write("x")' ; node verify_frontend_logic.js`, inline: true},
47 // `;` lets the verifier's status stand in for the generate step's.
48 {command: `go generate ./... ; go test ./...`, deliveryMixed: true, ordinaryMixed: true},
49 // `&&` reports the failing step, so ordinary mode has nothing to protect.
50 {command: `go generate ./... && go test ./...`, deliveryMixed: true},
51 {command: `go build ./... && go test ./...`, deliveryMixed: true},
52 {command: `npm install && npm test`, deliveryMixed: true},
53 {command: `cargo build && cargo clippy`, deliveryMixed: true},
54 // Masked exit is also mixed (echo is not a verifier); agent checks mask first.
55 {command: `go test ./...; echo $?`, deliveryMixed: true, ordinaryMixed: true, mask: true},
56 {command: `python3 -c 'print(1)'`},
57 {command: `go test ./...`},
58 {command: `tail -n +1 file | node --check -`},
59 }
60 for _, tt := range cases {
61 args, _ := json.Marshal(map[string]string{"command": tt.command})
62 if got := BashToolCallMixesMutationAndVerification(args); got != tt.deliveryMixed {
63 t.Errorf("deliveryMixed(%q)=%v want %v", tt.command, got, tt.deliveryMixed)
64 }
65 if got := BashToolCallMixesMutationAndMaskableVerification(args); got != tt.ordinaryMixed {
66 t.Errorf("ordinaryMixed(%q)=%v want %v", tt.command, got, tt.ordinaryMixed)
67 }
68 if got := BashToolCallMasksVerificationExit(args); got != tt.mask {
69 t.Errorf("mask(%q)=%v want %v", tt.command, got, tt.mask)
70 }
71 if got := BashToolCallUsesNonTerminalInlineInterpreter(args); got != tt.inline {
72 t.Errorf("inlineNT(%q)=%v want %v", tt.command, got, tt.inline)
73 }
74 }
75 }
76
77 // TestOrdinaryModeAllowsShortCircuitBuildAndVerify pins the regression that
78 // motivated the narrow ordinary-mode classifier: the everyday
79 // "build, then verify" chain must stay runnable outside Delivery.
80 func TestOrdinaryModeAllowsShortCircuitBuildAndVerify(t *testing.T) {
81 allowed := []string{
82 `go build ./... && go test ./...`,
83 `npm install && npm test`,
84 `pnpm install && pnpm test`,
85 `cargo build && cargo test`,
86 `make build && make test`,
87 `git pull && go test ./...`,
88 `mkdir -p out && go test ./...`,
89 `go mod tidy && go test ./...`,
90 }
91 for _, command := range allowed {
92 args, _ := json.Marshal(map[string]string{"command": command})
93 if BashToolCallMixesMutationAndMaskableVerification(args) {
94 t.Errorf("ordinary mode must allow %q: bash reports the failing step's status", command)
95 }
96 if !BashToolCallMixesMutationAndVerification(args) {
97 t.Errorf("delivery mode should still classify %q as mixed", command)
98 }
99 }
100 }
101
101 lines GO