Add the app/intentflow skeleton so HITL state, error codes, REST shapes, and BatchActionPolicy live outside intent and default to the legacy tier. Co-authored-by: Cursor <cursoragent@cursor.com>
138 lines
3.7 KiB
Go
138 lines
3.7 KiB
Go
package intentflow
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"chat-agent-system/internal/intent"
|
|
)
|
|
|
|
const PolicyVersion = "batch-action-policy.v1"
|
|
|
|
// ActionBatchPolicy is the intentflow-owned batch row. It must not copy
|
|
// Risk, RequiredSlots, CapabilityKey, or AllowedStates from ActionRegistry.
|
|
type ActionBatchPolicy struct {
|
|
ActionKey string
|
|
RequiresBatchConfirm bool
|
|
OpenFromTier PlanTier
|
|
AllowedModes []ExecutionMode
|
|
RejectPlan bool
|
|
}
|
|
|
|
// BatchActionPolicy is the versioned fail-closed matrix keyed by ActionSpec.Key.
|
|
type BatchActionPolicy struct {
|
|
Version string
|
|
byKey map[string]ActionBatchPolicy
|
|
}
|
|
|
|
func DefaultBatchActionPolicy() *BatchActionPolicy {
|
|
p, err := NewBatchActionPolicy(defaultBatchRows(), intent.DefaultRegistry())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return p
|
|
}
|
|
|
|
func NewBatchActionPolicy(rows []ActionBatchPolicy, registry *intent.ActionRegistry) (*BatchActionPolicy, error) {
|
|
if registry == nil {
|
|
return nil, fmt.Errorf("intentflow: action registry is required")
|
|
}
|
|
byKey := make(map[string]ActionBatchPolicy, len(rows))
|
|
for _, row := range rows {
|
|
if row.ActionKey == "" {
|
|
return nil, fmt.Errorf("intentflow: empty policy action key")
|
|
}
|
|
if _, exists := byKey[row.ActionKey]; exists {
|
|
return nil, fmt.Errorf("intentflow: duplicate policy key %q", row.ActionKey)
|
|
}
|
|
if _, ok := registry.Get(row.ActionKey); !ok {
|
|
return nil, fmt.Errorf("intentflow: unknown policy action key %q", row.ActionKey)
|
|
}
|
|
byKey[row.ActionKey] = row
|
|
}
|
|
for _, spec := range registry.All() {
|
|
if spec == nil {
|
|
continue
|
|
}
|
|
if _, ok := byKey[spec.Key]; !ok {
|
|
return nil, fmt.Errorf("intentflow: action %q missing batch policy", spec.Key)
|
|
}
|
|
}
|
|
return &BatchActionPolicy{Version: PolicyVersion, byKey: byKey}, nil
|
|
}
|
|
|
|
func (p *BatchActionPolicy) Lookup(key string) (ActionBatchPolicy, error) {
|
|
if p == nil {
|
|
return ActionBatchPolicy{}, fmt.Errorf("intentflow: policy is nil")
|
|
}
|
|
row, ok := p.byKey[key]
|
|
if !ok {
|
|
return ActionBatchPolicy{}, fmt.Errorf("intentflow: unknown action key %q", key)
|
|
}
|
|
return row, nil
|
|
}
|
|
|
|
func (p *BatchActionPolicy) RequiresBatchConfirm(key string) bool {
|
|
row, err := p.Lookup(key)
|
|
if err != nil {
|
|
return true
|
|
}
|
|
return row.RequiresBatchConfirm || row.RejectPlan
|
|
}
|
|
|
|
func defaultBatchRows() []ActionBatchPolicy {
|
|
autoRead := []string{
|
|
"regulation.query",
|
|
"case.query",
|
|
"permission.query",
|
|
"document.reject_reason.query",
|
|
"workflow.status.query",
|
|
"document.status.query",
|
|
"run.status.query",
|
|
"project.advisory",
|
|
"conversation.help",
|
|
}
|
|
confirmGenerate := []string{"task.run", "task.run.adhoc"}
|
|
rows := make([]ActionBatchPolicy, 0, len(intent.DefaultRegistry().All()))
|
|
seen := map[string]struct{}{}
|
|
add := func(row ActionBatchPolicy) {
|
|
rows = append(rows, row)
|
|
seen[row.ActionKey] = struct{}{}
|
|
}
|
|
for _, key := range autoRead {
|
|
add(ActionBatchPolicy{
|
|
ActionKey: key,
|
|
RequiresBatchConfirm: false,
|
|
OpenFromTier: TierAutoRead,
|
|
AllowedModes: []ExecutionMode{ModeLive, ModeValidatedOnly},
|
|
})
|
|
}
|
|
for _, key := range confirmGenerate {
|
|
add(ActionBatchPolicy{
|
|
ActionKey: key,
|
|
RequiresBatchConfirm: true,
|
|
OpenFromTier: TierConfirm,
|
|
AllowedModes: []ExecutionMode{ModeValidatedOnly, ModeLive},
|
|
})
|
|
}
|
|
add(ActionBatchPolicy{
|
|
ActionKey: "security.forbidden",
|
|
RejectPlan: true,
|
|
OpenFromTier: TierFull,
|
|
})
|
|
for _, spec := range intent.DefaultRegistry().All() {
|
|
if spec == nil {
|
|
continue
|
|
}
|
|
if _, ok := seen[spec.Key]; ok {
|
|
continue
|
|
}
|
|
add(ActionBatchPolicy{
|
|
ActionKey: spec.Key,
|
|
RequiresBatchConfirm: true,
|
|
OpenFromTier: TierFull,
|
|
AllowedModes: []ExecutionMode{ModeValidatedOnly},
|
|
})
|
|
}
|
|
return rows
|
|
}
|