Files
weichenandCursor 9364da4974 feat(intentflow): freeze plan lifecycle contracts and batch policy
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>
2026-09-15 16:22:52 +00:00

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")
}
}