Add the app/intentflow skeleton so HITL state, error codes, REST shapes, and BatchActionPolicy live outside intent and default to the legacy tier. Co-authored-by: Cursor <cursoragent@cursor.com>
97 lines
2.6 KiB
Go
97 lines
2.6 KiB
Go
package intentflow
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"chat-agent-system/internal/intent"
|
|
)
|
|
|
|
func TestDefaultPolicyCoversRegistryAndFailClosed(t *testing.T) {
|
|
p := DefaultBatchActionPolicy()
|
|
if p.Version != PolicyVersion {
|
|
t.Fatalf("version = %s", p.Version)
|
|
}
|
|
for _, spec := range intent.DefaultRegistry().All() {
|
|
if _, err := p.Lookup(spec.Key); err != nil {
|
|
t.Fatalf("registry key %s missing from policy: %v", spec.Key, err)
|
|
}
|
|
}
|
|
if _, err := p.Lookup("not.a.real.action"); err == nil {
|
|
t.Fatal("unknown key must fail closed")
|
|
}
|
|
}
|
|
|
|
func TestBatchConfirmMatrix(t *testing.T) {
|
|
p := DefaultBatchActionPolicy()
|
|
row, err := p.Lookup("task.run")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !row.RequiresBatchConfirm || row.OpenFromTier != TierConfirm {
|
|
t.Fatalf("task.run policy = %+v", row)
|
|
}
|
|
adhoc, err := p.Lookup("task.run.adhoc")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !adhoc.RequiresBatchConfirm {
|
|
t.Fatal("task.run.adhoc must require batch confirm")
|
|
}
|
|
query, err := p.Lookup("regulation.query")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if query.RequiresBatchConfirm || query.OpenFromTier != TierAutoRead {
|
|
t.Fatalf("regulation.query policy = %+v", query)
|
|
}
|
|
forbidden, err := p.Lookup("security.forbidden")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !forbidden.RejectPlan {
|
|
t.Fatal("security.forbidden must reject the whole plan")
|
|
}
|
|
}
|
|
|
|
func TestUnknownPolicyKeyRejectedAtConstruction(t *testing.T) {
|
|
rows := append(defaultBatchRows(), ActionBatchPolicy{ActionKey: "invented.action"})
|
|
_, err := NewBatchActionPolicy(rows, intent.DefaultRegistry())
|
|
if err == nil {
|
|
t.Fatal("expected unknown policy key to fail")
|
|
}
|
|
}
|
|
|
|
func TestMissingRegistryKeyRejectedAtConstruction(t *testing.T) {
|
|
var trimmed []ActionBatchPolicy
|
|
for _, row := range defaultBatchRows() {
|
|
if row.ActionKey == "regulation.query" {
|
|
continue
|
|
}
|
|
trimmed = append(trimmed, row)
|
|
}
|
|
_, err := NewBatchActionPolicy(trimmed, intent.DefaultRegistry())
|
|
if err == nil {
|
|
t.Fatal("expected missing regulation.query policy to fail")
|
|
}
|
|
}
|
|
|
|
func TestActionSpecHasNoBatchOrTierFields(t *testing.T) {
|
|
rt := reflect.TypeOf(intent.ActionSpec{})
|
|
for i := 0; i < rt.NumField(); i++ {
|
|
name := strings.ToLower(rt.Field(i).Name)
|
|
for _, banned := range []string{"batch", "tier", "adapter", "hitl"} {
|
|
if strings.Contains(name, banned) {
|
|
t.Fatalf("ActionSpec.%s smuggles %s into L1", rt.Field(i).Name, banned)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidatedOnlyIsNotSuccess(t *testing.T) {
|
|
if ModeValidatedOnly == ExecutionMode(PlanCompleted) || ModeValidatedOnly == ExecutionMode(StepSucceeded) {
|
|
t.Fatal("validated_only must stay distinct from succeeded/completed")
|
|
}
|
|
}
|