- FIND-SEC-01: GetUserContext requires session ownership (handler 403 + SQL/memory owner filter); in-memory HTTP + isolated-PG regressions - FIND-ARCH-01: archtest fails closed on untracked table writes (+noise exemption) and registers 50 existing writer tables in table_owners - FIND-SEC-02: Authorizer without pool denies by default; explicit NewInMemoryAuthorizer seam opt-in via ALLOW_IN_MEMORY at composition - FIND-SEC-03: swboot commands run argv-exec (no sh -c) with exact basename allowlist match; prefix-spoof and no-shell-eval tests - FIND-FLOW-01: confirm rejects validated_only plans; ExecuteClaimed hard-refuses validated_only steps (new error codes + HTTP mapping) - FIND-QUAL-01: intentflow.ValidateFlagConsistency fail-fast at Assemble
143 lines
4.4 KiB
Go
143 lines
4.4 KiB
Go
package intentflow
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
|
|
"chat-agent-system/internal/intent"
|
|
)
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// ValidateFlagConsistency 校验 intentflow 放量档位与 intent 多意图开关的
|
|
// 一致性(FIND-QUAL-01 回归):PlanTier 升档必须建立在 MULTI_INTENT_ENABLED
|
|
// 之上,否则组合根启动期 fail-fast,不允许「intentflow 已升档、intent 仍关」
|
|
// 这类静默漂移配置上线。
|
|
func ValidateFlagConsistency(iflags intent.FeatureFlags, pflags FeatureFlags) error {
|
|
if pflags.PlanTier != TierLegacy && !iflags.MultiIntentEnabled {
|
|
return fmt.Errorf(
|
|
"intentflow: MULTI_INTENT_PLAN_TIER=%q requires MULTI_INTENT_ENABLED=true; fix the flag combination instead of silently degrading",
|
|
pflags.PlanTier,
|
|
)
|
|
}
|
|
return nil
|
|
}
|