Files
weichenandCursor 43555e5d08 feat(intent-plan): wire U9 PI segment proposer and per-flag U10–U12 evidence
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>
2026-09-17 03:14:57 +00:00

304 lines
9.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}