Files
Agent-System/internal/app/live_keys_rollout_test.go
weichen f9ce771954 feat(intentflow): per-key live rollout for remaining U9-U12 validated_only keys
- 5 independent flags (MULTI_INTENT_U11_LIVE_REVIEW/FINAL/PROPOSAL, U12_LIVE_SWITCH/RESTART), default OFF; no full rollout
- DocGovLiveActionKeys/RunControlLiveActionKeys become flag-driven functions; baseline keys unchanged
- DocGovOwner routes review.approve/reject, final.approve/reject, proposal.decide to real engine with slot-driven reason/approve parsing and idempotent receipts
- RunControlOwner routes workflow.switch -> ActivateWorkflow and run.restart -> fresh CreateAndStart run
- policy: 7 keys promoted from TierFull to TierConfirm (real execution still gated per-key)
- tests: default-off isolation, per-key independence, live state advance reviewing->approving, dry-run zero-advance when flag off
2026-09-25 14:38:42 +00:00

200 lines
6.3 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 app
import (
"context"
"testing"
"chat-agent-system/internal/app/intentflow"
"chat-agent-system/internal/docgov"
)
// P8 逐键放量:默认 flag 全关时,新键不得进入 live 集合(仍 dry-run validated_only)。
func TestP8LiveKeysDefaultOff(t *testing.T) {
intentflow.SetFeatureFlagsForTest(intentflow.FeatureFlags{
PlanTier: intentflow.TierConfirm,
})
defer intentflow.ResetFeatureFlagsForTest()
for _, key := range []string{
"document.review.approve", "document.review.reject",
"document.final.approve", "document.final.reject",
"document.proposal.decide",
} {
if intentflow.IsDocGovLiveKey(key) {
t.Fatalf("docgov key %s must stay dry-run when LIVE flags off", key)
}
}
for _, key := range []string{"workflow.switch", "run.restart"} {
if intentflow.IsRunControlLiveKey(key) {
t.Fatalf("run-control key %s must stay dry-run when LIVE flags off", key)
}
}
// 基线键不受 per-key flag 影响。
for _, key := range []string{"document.claim", "document.review.submit"} {
if !intentflow.IsDocGovLiveKey(key) {
t.Fatalf("baseline docgov key %s must stay live", key)
}
}
for _, key := range []string{"workflow.run", "run.resume"} {
if !intentflow.IsRunControlLiveKey(key) {
t.Fatalf("baseline run-control key %s must stay live", key)
}
}
}
// 逐键 flag 打开后对应键进入 live 集合,其余仍关闭(禁 full 一次放量)。
func TestP8LiveKeysPerKeyFlags(t *testing.T) {
intentflow.SetFeatureFlagsForTest(intentflow.FeatureFlags{
PlanTier: intentflow.TierConfirm,
U11LiveReview: true,
U11LiveFinal: true,
U11LiveProposal: true,
U12LiveSwitch: true,
U12LiveRestart: true,
})
defer intentflow.ResetFeatureFlagsForTest()
for _, key := range []string{
"document.review.approve", "document.review.reject",
"document.final.approve", "document.final.reject",
"document.proposal.decide",
} {
if !intentflow.IsDocGovLiveKey(key) {
t.Fatalf("docgov key %s must be live when flag on", key)
}
}
for _, key := range []string{"workflow.switch", "run.restart"} {
if !intentflow.IsRunControlLiveKey(key) {
t.Fatalf("run-control key %s must be live when flag on", key)
}
}
// 只开 review:final/proposal 仍 dry-run。
intentflow.SetFeatureFlagsForTest(intentflow.FeatureFlags{
PlanTier: intentflow.TierConfirm,
U11LiveReview: true,
})
if !intentflow.IsDocGovLiveKey("document.review.approve") {
t.Fatal("review.approve should be live")
}
if intentflow.IsDocGovLiveKey("document.final.approve") {
t.Fatal("final.approve must stay dry-run when only review flag on")
}
if intentflow.IsDocGovLiveKey("document.proposal.decide") {
t.Fatal("proposal.decide must stay dry-run when only review flag on")
}
}
// review.approve 打开后走真实引擎:文档从 in_review → pending_approval。
func TestP8DocGovReviewApproveLive(t *testing.T) {
intentflow.SetFeatureFlagsForTest(intentflow.FeatureFlags{
PlanTier: intentflow.TierConfirm,
U11DocGov: true,
U11LiveReview: true,
})
defer intentflow.ResetFeatureFlagsForTest()
engine := docgov.NewGovernanceEngine(nil)
ctx := context.Background()
doc, err := engine.CreateOrGetDocument(ctx, "doc_p8_review", 9, "intake", "t", "V0.5", "", 1, "A", 2, "R", 3, "P")
if err != nil {
t.Fatal(err)
}
if _, err := engine.SubmitForReview(ctx, doc.DocID, 1, docgov.RoleAuthor, "A"); err != nil {
t.Fatal(err)
}
cur, err := engine.GetDocument(ctx, doc.DocID)
if err != nil {
t.Fatal(err)
}
owner := NewDocGovOwner(engine, func(context.Context, int64, int64) error { return nil }, nil)
rec, err := owner.Execute(ctx, intentflow.StepExec{
ActionKey: "document.review.approve",
IdempotencyKey: "p8-review-approve",
ActionDigest: "d1",
ExpectedResourceVersion: itoa(cur.Version),
Principal: intentflow.Principal{UserID: 2, ProjectID: 9, SessionID: "s"},
SafeSummary: doc.DocID,
})
if err != nil {
t.Fatalf("review approve live: %v", err)
}
if rec.ResultRef != "docgov:"+doc.DocID {
t.Fatalf("unexpected ref %s", rec.ResultRef)
}
after, _ := engine.GetDocument(ctx, doc.DocID)
if after.CurrentState != docgov.StateApproving {
t.Fatalf("state after approve = %s, want %s", after.CurrentState, docgov.StateApproving)
}
// 幂等重放同键返回同一收据。
rec2, err := owner.Execute(ctx, intentflow.StepExec{
ActionKey: "document.review.approve",
IdempotencyKey: "p8-review-approve",
ActionDigest: "d1",
ExpectedResourceVersion: itoa(cur.Version),
Principal: intentflow.Principal{UserID: 2, ProjectID: 9, SessionID: "s"},
SafeSummary: doc.DocID,
})
if err != nil || rec2.IdempotencyKey != rec.IdempotencyKey {
t.Fatalf("idempotent replay: rec=%+v err=%v", rec2, err)
}
}
// flag 关闭时 review.approve 落 dry-run(Forbidden 而非真实状态推进)。
func TestP8DocGovReviewApproveDryRunWhenFlagOff(t *testing.T) {
intentflow.SetFeatureFlagsForTest(intentflow.FeatureFlags{
PlanTier: intentflow.TierConfirm,
U11DocGov: true,
})
defer intentflow.ResetFeatureFlagsForTest()
engine := docgov.NewGovernanceEngine(nil)
ctx := context.Background()
doc, err := engine.CreateOrGetDocument(ctx, "doc_p8_dry", 9, "intake", "t", "V0.5", "", 1, "A", 2, "R", 3, "P")
if err != nil {
t.Fatal(err)
}
owner := NewDocGovOwner(engine, func(context.Context, int64, int64) error { return nil }, nil)
rec, err := owner.Execute(ctx, intentflow.StepExec{
ActionKey: "document.review.approve",
IdempotencyKey: "p8-dry",
ActionDigest: "d1",
ExpectedResourceVersion: "1",
Principal: intentflow.Principal{UserID: 2, ProjectID: 9, SessionID: "s"},
SafeSummary: doc.DocID,
})
if err != nil {
t.Fatalf("dry-run execute: %v", err)
}
if rec.ResultRef == "docgov:"+doc.DocID {
t.Fatal("flag off must not hit real engine receipt")
}
after, _ := engine.GetDocument(ctx, doc.DocID)
if after.CurrentState == docgov.StateApproving {
t.Fatal("dry-run must not advance document state")
}
}
func itoa(v int) string {
if v == 0 {
return "0"
}
neg := v < 0
if neg {
v = -v
}
var b [20]byte
i := len(b)
for v > 0 {
i--
b[i] = byte('0' + v%10)
v /= 10
}
if neg {
i--
b[i] = '-'
}
return string(b[i:])
}