Rules that already split skip the LLM. Independent flags stay off by default and do not raise MULTI_INTENT_PLAN_TIER. Co-authored-by: Cursor <cursoragent@cursor.com>
304 lines
9.8 KiB
Go
304 lines
9.8 KiB
Go
package intentflow
|
||
|
||
import (
|
||
"context"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
func TestExpansionFlagsDefaultOff(t *testing.T) {
|
||
t.Setenv("MULTI_INTENT_U9_SEGMENT", "")
|
||
t.Setenv("MULTI_INTENT_U10_MUTATE", "")
|
||
t.Setenv("MULTI_INTENT_U11_DOCGOV", "")
|
||
t.Setenv("MULTI_INTENT_U12_RUN_CONTROL", "")
|
||
t.Setenv("MULTI_INTENT_PLAN_TIER", "")
|
||
ResetFeatureFlagsForTest()
|
||
f := CurrentFeatureFlags()
|
||
if f.PlanTier != TierLegacy {
|
||
t.Fatalf("PlanTier = %s, want legacy", f.PlanTier)
|
||
}
|
||
if f.U9Segment || f.U10Mutate || f.U11DocGov || f.U12RunControl {
|
||
t.Fatalf("expansion flags must default off: %+v", f)
|
||
}
|
||
}
|
||
|
||
func TestExpansionFlagsEnvOn(t *testing.T) {
|
||
t.Setenv("MULTI_INTENT_U9_SEGMENT", "true")
|
||
t.Setenv("MULTI_INTENT_U10_MUTATE", "1")
|
||
t.Setenv("MULTI_INTENT_U11_DOCGOV", "yes")
|
||
t.Setenv("MULTI_INTENT_U12_RUN_CONTROL", "TRUE")
|
||
t.Setenv("MULTI_INTENT_PLAN_TIER", "legacy")
|
||
ResetFeatureFlagsForTest()
|
||
f := CurrentFeatureFlags()
|
||
if !f.U9Segment || !f.U10Mutate || !f.U11DocGov || !f.U12RunControl {
|
||
t.Fatalf("expected all expansion flags on: %+v", f)
|
||
}
|
||
if f.PlanTier != TierLegacy {
|
||
t.Fatal("enabling U9–U12 must not raise MULTI_INTENT_PLAN_TIER")
|
||
}
|
||
}
|
||
|
||
func TestActiveSegmentProposerDisabledByDefault(t *testing.T) {
|
||
t.Setenv("MULTI_INTENT_U9_SEGMENT", "")
|
||
ResetFeatureFlagsForTest()
|
||
_, err := ActiveSegmentProposer().Propose(context.Background(), SegmentProposalRequest{
|
||
Text: "查法规然后生成报告", RequestDigest: "d1",
|
||
})
|
||
if err != ErrSegmentProposalDisabled {
|
||
t.Fatalf("want DISABLED, got %v", err)
|
||
}
|
||
}
|
||
|
||
func TestStubSegmentProposerFailClosed(t *testing.T) {
|
||
t.Setenv("MULTI_INTENT_U9_SEGMENT", "true")
|
||
ResetFeatureFlagsForTest()
|
||
p := ActiveSegmentProposer()
|
||
|
||
if _, err := p.Propose(context.Background(), SegmentProposalRequest{}); err != ErrSegmentProposalMalformed {
|
||
t.Fatalf("empty request: %v", err)
|
||
}
|
||
if _, err := p.Propose(context.Background(), SegmentProposalRequest{
|
||
Text: "ok", RequestDigest: "d", SchemaVersion: "wrong.v0",
|
||
}); err != ErrSegmentProposalMalformed {
|
||
t.Fatalf("bad schema: %v", err)
|
||
}
|
||
|
||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond)
|
||
defer cancel()
|
||
slow := NewStubSegmentProposer(50 * time.Millisecond)
|
||
if _, err := slow.Propose(ctx, SegmentProposalRequest{Text: "ok", RequestDigest: "d"}); err != ErrSegmentProposalTimeout {
|
||
t.Fatalf("timeout: %v", err)
|
||
}
|
||
|
||
if err := ValidateProposedSpans("ab", []SegmentSpan{{Start: 0, End: 5}}); err != ErrSegmentProposalOutOfBounds {
|
||
t.Fatalf("oob: %v", err)
|
||
}
|
||
|
||
got, err := p.Propose(context.Background(), SegmentProposalRequest{
|
||
Text: "查法规", RequestDigest: "digest-1",
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if got.SchemaVersion != SegmentProposalSchemaV1 || len(got.Spans) != 1 {
|
||
t.Fatalf("proposal = %+v", got)
|
||
}
|
||
}
|
||
|
||
func TestRuleGatedProposerSkipsWhenRulesDecide(t *testing.T) {
|
||
t.Setenv("MULTI_INTENT_U9_SEGMENT", "true")
|
||
ResetFeatureFlagsForTest()
|
||
t.Cleanup(func() { SetLLMSegmentProposer(nil) })
|
||
|
||
_, err := ActiveSegmentProposer().Propose(context.Background(), SegmentProposalRequest{
|
||
Text: "先查法规,然后生成报告", RequestDigest: "d",
|
||
})
|
||
if err != ErrSegmentRulesDecided {
|
||
t.Fatalf("connector split want RULES_DECIDED, got %v", err)
|
||
}
|
||
_, err = ActiveSegmentProposer().Propose(context.Background(), SegmentProposalRequest{
|
||
Text: "查询并总结法规要点", RequestDigest: "d",
|
||
})
|
||
if err != ErrSegmentRulesDecided {
|
||
t.Fatalf("internal chain want RULES_DECIDED, got %v", err)
|
||
}
|
||
}
|
||
|
||
func TestU10IndependentFlagDryRunConfirmReceipt(t *testing.T) {
|
||
t.Setenv("MULTI_INTENT_U10_MUTATE", "true")
|
||
t.Setenv("MULTI_INTENT_U11_DOCGOV", "")
|
||
t.Setenv("MULTI_INTENT_U12_RUN_CONTROL", "")
|
||
t.Setenv("MULTI_INTENT_PLAN_TIER", "legacy")
|
||
ResetFeatureFlagsForTest()
|
||
if CurrentFeatureFlags().U11DocGov || CurrentFeatureFlags().U12RunControl {
|
||
t.Fatal("U10 must not enable U11/U12")
|
||
}
|
||
if CurrentFeatureFlags().PlanTier != TierLegacy {
|
||
t.Fatal("must not raise PLAN_TIER")
|
||
}
|
||
mux := BuildOwnerMux(NewFakeActionOwner(), NewFakeActionOwner())
|
||
if mux.Mutate == nil || mux.DocGov != nil || mux.RunControl != nil {
|
||
t.Fatal("only mutate adapter when U10 on")
|
||
}
|
||
ctx := context.Background()
|
||
exec := StepExec{
|
||
ActionKey: "project.fact.supplement", StepID: "s1",
|
||
IdempotencyKey: "u10-k", ActionDigest: "d1", ExpectedResourceVersion: "rev-1",
|
||
}
|
||
rec, err := mux.Execute(ctx, exec)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if !strings.HasPrefix(rec.ResultDigest, "validated_only:") {
|
||
t.Fatalf("want dry-run receipt, got %+v", rec)
|
||
}
|
||
again, err := mux.Execute(ctx, exec)
|
||
if err != nil || again.ResultRef != rec.ResultRef {
|
||
t.Fatalf("idempotent confirm replay: %v %+v", err, again)
|
||
}
|
||
exec.ActionDigest = "other"
|
||
if _, err := mux.Execute(ctx, exec); err != ErrPlanDecisionConflict {
|
||
t.Fatalf("digest mismatch: %v", err)
|
||
}
|
||
if _, err := mux.Execute(ctx, StepExec{
|
||
ActionKey: "project.fact.supplement", IdempotencyKey: "no-rev", ActionDigest: "d",
|
||
}); err != ErrPlanActionVersionConflict {
|
||
t.Fatalf("missing rev: %v", err)
|
||
}
|
||
}
|
||
|
||
func TestU11DocGovProject143ReadOnly(t *testing.T) {
|
||
t.Setenv("MULTI_INTENT_U11_DOCGOV", "true")
|
||
t.Setenv("MULTI_INTENT_U10_MUTATE", "")
|
||
t.Setenv("MULTI_INTENT_U12_RUN_CONTROL", "")
|
||
ResetFeatureFlagsForTest()
|
||
mux := BuildOwnerMux(NewFakeActionOwner(), NewFakeActionOwner())
|
||
if mux.DocGov == nil || mux.Mutate != nil {
|
||
t.Fatal("U11 must not enable U10")
|
||
}
|
||
ctx := context.Background()
|
||
okExec := StepExec{
|
||
ActionKey: "document.claim", IdempotencyKey: "u11-ok", ActionDigest: "d",
|
||
ExpectedResourceVersion: "doc-v1", Principal: Principal{ProjectID: 99},
|
||
}
|
||
if _, err := mux.Execute(ctx, okExec); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if _, err := mux.Execute(ctx, StepExec{
|
||
ActionKey: "document.claim", IdempotencyKey: "u11-143", ActionDigest: "d",
|
||
ExpectedResourceVersion: "doc-v1", Principal: Principal{ProjectID: 143},
|
||
}); err != ErrPlanActionForbidden {
|
||
t.Fatalf("project 143 must stay read-only, got %v", err)
|
||
}
|
||
if _, ok, _ := mux.LookupReceipt(ctx, "u11-143"); ok {
|
||
t.Fatal("143 deny must not store a receipt")
|
||
}
|
||
}
|
||
|
||
func TestU12RunControlIndependentDryRun(t *testing.T) {
|
||
t.Setenv("MULTI_INTENT_U12_RUN_CONTROL", "true")
|
||
t.Setenv("MULTI_INTENT_U10_MUTATE", "")
|
||
t.Setenv("MULTI_INTENT_U11_DOCGOV", "")
|
||
ResetFeatureFlagsForTest()
|
||
mux := BuildOwnerMux(NewFakeActionOwner(), NewFakeActionOwner())
|
||
if mux.RunControl == nil || mux.Mutate != nil || mux.DocGov != nil {
|
||
t.Fatal("U12 must not enable U10/U11")
|
||
}
|
||
ctx := context.Background()
|
||
rec, err := mux.Execute(ctx, StepExec{
|
||
ActionKey: "workflow.run", StepID: "s", IdempotencyKey: "u12-k", ActionDigest: "d",
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if rec.SafeSummary == "" {
|
||
t.Fatal("expected dry-run summary")
|
||
}
|
||
denied := NewRunControlDryRunAdapter()
|
||
denied.Denied = true
|
||
if _, err := denied.Execute(ctx, StepExec{
|
||
ActionKey: "run.resume", IdempotencyKey: "nope", ActionDigest: "d",
|
||
}); err != ErrPlanActionForbidden {
|
||
t.Fatalf("denied: %v", err)
|
||
}
|
||
}
|
||
|
||
func TestBuildOwnerMuxFlagOffNoExpansion(t *testing.T) {
|
||
t.Setenv("MULTI_INTENT_U10_MUTATE", "")
|
||
t.Setenv("MULTI_INTENT_U11_DOCGOV", "")
|
||
t.Setenv("MULTI_INTENT_U12_RUN_CONTROL", "")
|
||
ResetFeatureFlagsForTest()
|
||
mux := BuildOwnerMux(NewFakeActionOwner(), NewFakeActionOwner())
|
||
if mux.Mutate != nil || mux.DocGov != nil || mux.RunControl != nil {
|
||
t.Fatal("flag-off must not register expansion adapters")
|
||
}
|
||
q := &routeCountOwner{}
|
||
mux = BuildOwnerMux(q, NewFakeActionOwner())
|
||
_, err := mux.Execute(context.Background(), StepExec{
|
||
ActionKey: "project.fact.supplement", IdempotencyKey: "k", ActionDigest: "d",
|
||
ExpectedResourceVersion: "1",
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if q.n != 1 {
|
||
t.Fatalf("flag-off mutate must hit Query, got n=%d", q.n)
|
||
}
|
||
}
|
||
|
||
func TestBuildOwnerMuxEnablePathsNoPanic(t *testing.T) {
|
||
t.Setenv("MULTI_INTENT_U10_MUTATE", "true")
|
||
t.Setenv("MULTI_INTENT_U11_DOCGOV", "true")
|
||
t.Setenv("MULTI_INTENT_U12_RUN_CONTROL", "true")
|
||
ResetFeatureFlagsForTest()
|
||
mux := BuildOwnerMux(NewFakeActionOwner(), NewFakeActionOwner())
|
||
if mux.Mutate == nil || mux.DocGov == nil || mux.RunControl == nil {
|
||
t.Fatal("flag-on must register dry-run adapters")
|
||
}
|
||
|
||
ctx := context.Background()
|
||
cases := []struct {
|
||
key string
|
||
rev string
|
||
}{
|
||
{"project.fact.supplement", "rev-1"},
|
||
{"document.claim", "doc-v1"},
|
||
{"workflow.run", ""},
|
||
{"run.resume", ""},
|
||
}
|
||
for i, tc := range cases {
|
||
_, err := mux.Execute(ctx, StepExec{
|
||
ActionKey: tc.key, StepID: "s", IdempotencyKey: "id-" + tc.key,
|
||
ActionDigest: "d", ExpectedResourceVersion: tc.rev,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("case %d %s: %v", i, tc.key, err)
|
||
}
|
||
}
|
||
|
||
_, err := mux.Execute(ctx, StepExec{
|
||
ActionKey: "project.fact.supplement", IdempotencyKey: "no-rev", ActionDigest: "d",
|
||
})
|
||
if err != ErrPlanActionVersionConflict {
|
||
t.Fatalf("mutate without revision: %v", err)
|
||
}
|
||
|
||
bare := OwnerMux{Query: NewFakeActionOwner(), Task: NewFakeActionOwner()}
|
||
SetFeatureFlagsForTest(FeatureFlags{PlanTier: TierLegacy, U10Mutate: true})
|
||
defer ResetFeatureFlagsForTest()
|
||
_, err = bare.Execute(ctx, StepExec{
|
||
ActionKey: "project.fact.supplement", IdempotencyKey: "nil", ActionDigest: "d",
|
||
ExpectedResourceVersion: "1",
|
||
})
|
||
if err != ErrPlanActionForbidden {
|
||
t.Fatalf("nil Mutate must fail closed, got %v", err)
|
||
}
|
||
}
|
||
|
||
func TestDryRunAdapterRejectsForeignKeys(t *testing.T) {
|
||
a := NewMutateDryRunAdapter()
|
||
_, err := a.Execute(context.Background(), StepExec{
|
||
ActionKey: "document.claim", IdempotencyKey: "x", ActionDigest: "d",
|
||
ExpectedResourceVersion: "1",
|
||
})
|
||
if err != ErrPlanActionForbidden {
|
||
t.Fatalf("mutate adapter must not accept docgov key: %v", err)
|
||
}
|
||
}
|
||
|
||
type routeCountOwner struct {
|
||
n int
|
||
}
|
||
|
||
func (r *routeCountOwner) Authorize(context.Context, StepExec) error { return nil }
|
||
|
||
func (r *routeCountOwner) Execute(_ context.Context, exec StepExec) (Receipt, error) {
|
||
r.n++
|
||
return Receipt{IdempotencyKey: exec.IdempotencyKey, ActionDigest: exec.ActionDigest}, nil
|
||
}
|
||
|
||
func (r *routeCountOwner) LookupReceipt(context.Context, string) (Receipt, bool, error) {
|
||
return Receipt{}, false, nil
|
||
}
|