Why: F06 - the wrapped-executor cache keyed only backend/role/model, so allow->deny permission tightening and factory replacement never took effect on subsequent resolves. Evidence: squad and adapters/native tests pass (verify-u4.json); config identity helper covers permission/tool/limiter changes. Impact: stale executors can no longer widen capabilities; in-flight tasks are not rolled back (documented). Refs: feature/agent-stack-audit-remediation-b1 U4
132 lines
4.3 KiB
Go
132 lines
4.3 KiB
Go
package native
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"agent-native/runtime/llm"
|
|
"agent-native/runtime/tools"
|
|
"agent-squad/squad"
|
|
)
|
|
|
|
func TestBuildOptionsBashPermissionGating(t *testing.T) {
|
|
base := Config{
|
|
Client: &llm.Client{BaseURL: "http://x", APIKey: "k"},
|
|
DefaultModel: "m1", MaxTurns: 5, Timeout: time.Minute,
|
|
Exec: &tools.ExecConfig{Bash: "/bin/bash"},
|
|
SkillsRoots: []string{"/skills"},
|
|
}
|
|
deny, err := buildOptions(base, squad.RoleSpec{Role: "r", BashPermission: squad.BashPermissionDeny})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if deny.Exec != nil {
|
|
t.Fatal("deny role must not get exec tools even when config provides them")
|
|
}
|
|
ask, _ := buildOptions(base, squad.RoleSpec{Role: "r", BashPermission: squad.BashPermissionAsk})
|
|
if ask.Exec == nil {
|
|
t.Fatal("ask role gets exec tools")
|
|
}
|
|
allow, _ := buildOptions(base, squad.RoleSpec{Role: "r", BashPermission: squad.BashPermissionAllow})
|
|
if allow.Exec == nil {
|
|
t.Fatal("allow role gets exec tools")
|
|
}
|
|
empty, _ := buildOptions(base, squad.RoleSpec{Role: "r"})
|
|
if empty.Exec != nil {
|
|
t.Fatal("unset bash_permission defaults to deny")
|
|
}
|
|
none, _ := buildOptions(Config{Client: base.Client, DefaultModel: "m"}, squad.RoleSpec{Role: "r", BashPermission: squad.BashPermissionAllow})
|
|
if none.Exec != nil {
|
|
t.Fatal("no Exec config = no exec tools")
|
|
}
|
|
roleModel, _ := buildOptions(base, squad.RoleSpec{Role: "r", Model: "big"})
|
|
if roleModel.Model != "big" {
|
|
t.Fatalf("role model must win, got %s", roleModel.Model)
|
|
}
|
|
if _, err := buildOptions(Config{Client: base.Client}, squad.RoleSpec{Role: "r"}); err == nil {
|
|
t.Fatal("missing model must fail-closed")
|
|
}
|
|
}
|
|
|
|
func TestConfigIdentityReflectsToolAndClient(t *testing.T) {
|
|
base := Config{
|
|
Client: &llm.Client{BaseURL: "http://x", APIKey: "k"},
|
|
DefaultModel: "m1", MaxTurns: 5, Timeout: time.Minute,
|
|
Exec: &tools.ExecConfig{Bash: "/bin/bash"},
|
|
}
|
|
otherExec := base
|
|
otherExec.Exec = &tools.ExecConfig{Bash: "/bin/zsh"}
|
|
if base.Identity() == otherExec.Identity() {
|
|
t.Fatal("Exec tool-config change must change Config.Identity")
|
|
}
|
|
otherClient := base
|
|
otherClient.Client = &llm.Client{BaseURL: "http://y", APIKey: "k"}
|
|
if base.Identity() == otherClient.Identity() {
|
|
t.Fatal("client change must change Config.Identity")
|
|
}
|
|
same := base
|
|
if base.Identity() != same.Identity() {
|
|
t.Fatal("identical config must keep Identity stable")
|
|
}
|
|
}
|
|
|
|
func TestRegisterFullAllowDenyTakesEffectOnNextResolve(t *testing.T) {
|
|
r := squad.NewBackendResolver()
|
|
cfg := Config{
|
|
Client: &llm.Client{BaseURL: "http://x", APIKey: "k"},
|
|
DefaultModel: "m1", MaxTurns: 1, Timeout: time.Second,
|
|
Exec: &tools.ExecConfig{Bash: "/bin/bash"},
|
|
}
|
|
RegisterFull(r, cfg)
|
|
|
|
allowRole := squad.RoleSpec{Role: "eng", Backend: "agent-native", Model: "m1", BashPermission: squad.BashPermissionAllow}
|
|
denyRole := squad.RoleSpec{Role: "eng", Backend: "agent-native", Model: "m1", BashPermission: squad.BashPermissionDeny}
|
|
|
|
eAllow, _, err := r.Resolve(allowRole, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
eDeny, _, err := r.Resolve(denyRole, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if eAllow == eDeny {
|
|
t.Fatal("allow→deny must not reuse cached executor on next Resolve")
|
|
}
|
|
// Identity of role configs must diverge so cache keys differ.
|
|
if squad.ConfigIdentity(allowRole) == squad.ConfigIdentity(denyRole) {
|
|
t.Fatal("role ConfigIdentity must include BashPermission")
|
|
}
|
|
}
|
|
|
|
func TestRegisterFullToolConfigReplacementTakesEffect(t *testing.T) {
|
|
r := squad.NewBackendResolver()
|
|
cfg1 := Config{
|
|
Client: &llm.Client{BaseURL: "http://x", APIKey: "k"},
|
|
DefaultModel: "m1", MaxTurns: 1, Timeout: time.Second,
|
|
Exec: &tools.ExecConfig{Bash: "/bin/bash"},
|
|
}
|
|
cfg2 := Config{
|
|
Client: &llm.Client{BaseURL: "http://x", APIKey: "k"},
|
|
DefaultModel: "m1", MaxTurns: 1, Timeout: time.Second,
|
|
Exec: &tools.ExecConfig{Bash: "/bin/zsh"},
|
|
}
|
|
if cfg1.Identity() == cfg2.Identity() {
|
|
t.Fatal("precondition: tool configs must differ in Identity")
|
|
}
|
|
RegisterFull(r, cfg1)
|
|
role := squad.RoleSpec{Role: "eng", Backend: "agent-native", Model: "m1", BashPermission: squad.BashPermissionAllow}
|
|
e1, _, err := r.Resolve(role, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
RegisterFull(r, cfg2)
|
|
e2, _, err := r.Resolve(role, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if e1 == e2 {
|
|
t.Fatal("RegisterFull tool-config replacement must invalidate cache")
|
|
}
|
|
}
|