Files
Agent-System/internal/app/intentflow/flags.go
T
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

126 lines
3.7 KiB
Go

package intentflow
import (
"os"
"strings"
"sync"
)
// PlanTier is the intentflow rollout switch. It must not be stored on
// intent.ActionSpec or intent.FeatureFlags.
type PlanTier string
const (
TierLegacy PlanTier = "legacy"
TierShadow PlanTier = "shadow"
TierAutoRead PlanTier = "auto_read"
TierConfirm PlanTier = "confirm"
TierFull PlanTier = "full"
)
type FeatureFlags struct {
PlanTier PlanTier
// ProjectAllowlist gates elevated tiers (shadow/auto_read/confirm/full).
// Empty denies; AllowAllProjects (-1) allows every project.
// Env: INTENT_PLAN_PROJECT_ALLOWLIST (*|id,id,...)
ProjectAllowlist []int64
// Independent PU8 expansion gates. Default OFF; do not couple to PlanTier.
U9Segment bool // MULTI_INTENT_U9_SEGMENT — LLM segment proposal
U10Mutate bool // MULTI_INTENT_U10_MUTATE — low/medium mutate adapters
U11DocGov bool // MULTI_INTENT_U11_DOCGOV — docgov action adapters
U12RunControl bool // MULTI_INTENT_U12_RUN_CONTROL — workflow/run-control adapters
// 逐键 live 放量 flag(P8):域名总 flag 开启后,细粒度键仍须单独打开才走真实引擎;
// 未打开的键保持 validated_only dry-run。默认全 OFF,禁止 full 一次放量。
U11LiveReview bool // MULTI_INTENT_U11_LIVE_REVIEW — document.review.approve/reject
U11LiveFinal bool // MULTI_INTENT_U11_LIVE_FINAL — document.final.approve/reject
U11LiveProposal bool // MULTI_INTENT_U11_LIVE_PROPOSAL — document.proposal.decide
U12LiveSwitch bool // MULTI_INTENT_U12_LIVE_SWITCH — workflow.switch
U12LiveRestart bool // MULTI_INTENT_U12_LIVE_RESTART — run.restart
}
var (
flagsMu sync.RWMutex
activeFlags = defaultFeatureFlags()
)
func defaultFeatureFlags() FeatureFlags {
return FeatureFlags{
PlanTier: parsePlanTier(os.Getenv("MULTI_INTENT_PLAN_TIER")),
ProjectAllowlist: ParseProjectAllowlist(os.Getenv("INTENT_PLAN_PROJECT_ALLOWLIST")),
U9Segment: envBool("MULTI_INTENT_U9_SEGMENT", false),
U10Mutate: envBool("MULTI_INTENT_U10_MUTATE", false),
U11DocGov: envBool("MULTI_INTENT_U11_DOCGOV", false),
U12RunControl: envBool("MULTI_INTENT_U12_RUN_CONTROL", false),
U11LiveReview: envBool("MULTI_INTENT_U11_LIVE_REVIEW", false),
U11LiveFinal: envBool("MULTI_INTENT_U11_LIVE_FINAL", false),
U11LiveProposal: envBool("MULTI_INTENT_U11_LIVE_PROPOSAL", false),
U12LiveSwitch: envBool("MULTI_INTENT_U12_LIVE_SWITCH", false),
U12LiveRestart: envBool("MULTI_INTENT_U12_LIVE_RESTART", false),
}
}
func envBool(key string, def bool) bool {
v := strings.TrimSpace(os.Getenv(key))
if v == "" {
return def
}
return v == "1" || strings.EqualFold(v, "true") || strings.EqualFold(v, "yes")
}
func parsePlanTier(raw string) PlanTier {
switch strings.ToLower(strings.TrimSpace(raw)) {
case "", "legacy":
return TierLegacy
case "shadow":
return TierShadow
case "auto_read", "autoread":
return TierAutoRead
case "confirm":
return TierConfirm
case "full":
return TierFull
default:
return TierLegacy
}
}
func CurrentFeatureFlags() FeatureFlags {
flagsMu.RLock()
defer flagsMu.RUnlock()
return activeFlags
}
func SetFeatureFlagsForTest(f FeatureFlags) {
flagsMu.Lock()
activeFlags = f
flagsMu.Unlock()
}
func ResetFeatureFlagsForTest() {
flagsMu.Lock()
activeFlags = defaultFeatureFlags()
flagsMu.Unlock()
}
// Allows reports whether the current (or supplied) tier may activate a policy row.
func (t PlanTier) Allows(min PlanTier) bool {
return tierRank(t) >= tierRank(min)
}
func tierRank(t PlanTier) int {
switch t {
case TierLegacy:
return 0
case TierShadow:
return 1
case TierAutoRead:
return 2
case TierConfirm:
return 3
case TierFull:
return 4
default:
return 0
}
}