aitriage

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 23, 2026 License: GPL-3.0 Imports: 22 Imported by: 0

Documentation

Index

Constants

View Source
const TypeAITriage = "ai:triage"

TypeAITriage is the asynq task type for AI triage jobs.

Variables

View Source
var (
	// ErrBudgetExceeded is returned by BudgetService.Check when the
	// tenant has used tokens up to or past block_threshold_pct of
	// its monthly limit. Callers MUST fail the triage call without
	// invoking the LLM.
	ErrBudgetExceeded = errors.New("ai-triage monthly token budget exceeded")

	// ErrBudgetUnavailable is returned when the budget repository
	// cannot answer (DB down, Redis down). The service's strict-mode
	// flag controls whether callers treat this as fail-closed.
	ErrBudgetUnavailable = errors.New("ai-triage budget service unavailable")
)

Budget errors — exported so callers can branch on them.

Functions

func CheckTokenLimit

func CheckTokenLimit(usedTokens, limitTokens int) error

CheckTokenLimit checks if a tenant has exceeded their monthly token limit. Returns nil if within limit, TokenLimitError if exceeded.

Types

type AIConfigInfo

type AIConfigInfo struct {
	// Mode is the AI mode: platform, byok, agent, disabled
	Mode string `json:"mode"`
	// Provider is the LLM provider: claude, openai, gemini
	Provider string `json:"provider"`
	// Model is the model being used
	Model string `json:"model"`
	// IsEnabled indicates if AI triage is enabled for this tenant
	IsEnabled bool `json:"is_enabled"`
	// AutoTriageEnabled indicates if auto-triage is enabled
	AutoTriageEnabled bool `json:"auto_triage_enabled"`
	// AutoTriageSeverities is the list of severities for auto-triage
	AutoTriageSeverities []string `json:"auto_triage_severities,omitempty"`
	// MonthlyTokenLimit is the monthly token limit (0 = unlimited)
	MonthlyTokenLimit int `json:"monthly_token_limit"`
	// TokensUsedThisMonth is the number of tokens used this month
	TokensUsedThisMonth int `json:"tokens_used_this_month"`
}

AIConfigInfo represents the AI configuration info returned to the UI.

type AITriageJobEnqueuer

type AITriageJobEnqueuer interface {
	EnqueueAITriage(ctx context.Context, resultID, tenantID, findingID string, delay time.Duration) error
}

AITriageJobEnqueuer defines the interface for enqueueing AI triage jobs.

type AITriageService

type AITriageService struct {
	// contains filtered or unexported fields
}

AITriageService handles AI-powered vulnerability triage operations.

func NewAITriageService

func NewAITriageService(
	triageRepo aitriagedom.Repository,
	findingRepo vulnerability.FindingRepository,
	tenantRepo tenant.Repository,
	activitySvc *activity.FindingActivityService,
	llmFactory *llm.Factory,
	platformCfg config.AITriageConfig,
	log *logger.Logger,
) *AITriageService

NewAITriageService creates a new AITriageService.

func (*AITriageService) EnqueueAutoTriage

func (s *AITriageService) EnqueueAutoTriage(ctx context.Context, tenantID, findingID shared.ID) error

EnqueueAutoTriage enqueues an auto-triage job with optional delay.

func (*AITriageService) GetAIConfig

func (s *AITriageService) GetAIConfig(ctx context.Context, tenantID string) (*AIConfigInfo, error)

GetAIConfig returns the AI configuration info for a tenant. This is used by the UI to display which model is being used.

func (*AITriageService) GetLatestTriageByFinding

func (s *AITriageService) GetLatestTriageByFinding(ctx context.Context, tenantID, findingID string) (*TriageResultResponse, error)

GetLatestTriageByFinding retrieves the latest triage result for a finding.

func (*AITriageService) GetPlanTokenLimit

func (s *AITriageService) GetPlanTokenLimit(_ context.Context, _ string) (int64, error)

GetPlanTokenLimit returns the monthly token limit for a tenant based on their plan. OSS Edition: Always returns -1 (unlimited).

func (*AITriageService) GetTriageResult

func (s *AITriageService) GetTriageResult(ctx context.Context, tenantID, resultID string) (*TriageResultResponse, error)

GetTriageResult retrieves a triage result by ID.

func (*AITriageService) ListTriageHistory

func (s *AITriageService) ListTriageHistory(ctx context.Context, tenantID, findingID string, limit, offset int) ([]*TriageResultResponse, int, error)

ListTriageHistory retrieves triage history for a finding.

func (*AITriageService) ProcessTriage

func (s *AITriageService) ProcessTriage(ctx context.Context, resultID, tenantID, findingID shared.ID) error

ProcessTriage processes a triage job. Called by the worker. Uses AcquireTriageSlot for atomic token limit check and status update. This prevents race conditions when multiple workers process jobs concurrently.

func (*AITriageService) RecoverStuckJobs

RecoverStuckJobs finds and marks stuck triage jobs as failed. Jobs are considered stuck if they've been in pending/processing state for longer than stuckDuration. This should be called periodically by a background job.

func (*AITriageService) RequestBulkTriage

func (s *AITriageService) RequestBulkTriage(ctx context.Context, req BulkTriageRequest) (*BulkTriageResponse, error)

RequestBulkTriage creates multiple triage jobs for a list of findings.

func (*AITriageService) RequestTriage

func (s *AITriageService) RequestTriage(ctx context.Context, req TriageRequest) (*TriageResponse, error)

RequestTriage creates a triage job and enqueues it for processing.

func (*AITriageService) SetAuditService

func (s *AITriageService) SetAuditService(auditSvc *auditapp.AuditService)

SetAuditService sets the audit service for logging AI operations.

func (*AITriageService) SetBudgetService

func (s *AITriageService) SetBudgetService(b *BudgetService)

SetBudgetService wires the per-tenant token budget. Optional — when unset, Check/Record calls are no-ops. Ship with the budget constructed and cfg.Enabled=false to do the dry-run / backfill described in RFC-008 Phase 2 before enforcement.

func (*AITriageService) SetJobEnqueuer

func (s *AITriageService) SetJobEnqueuer(enqueuer AITriageJobEnqueuer)

SetJobEnqueuer sets the job enqueuer for async processing.

func (*AITriageService) SetTriageBroadcaster

func (s *AITriageService) SetTriageBroadcaster(broadcaster TriageBroadcaster)

SetTriageBroadcaster sets the broadcaster for real-time WebSocket updates.

func (*AITriageService) SetWorkflowDispatcher

func (s *AITriageService) SetWorkflowDispatcher(dispatcher WorkflowEventDispatcherInterface)

SetWorkflowDispatcher sets the workflow event dispatcher for AI triage events.

func (*AITriageService) ShouldAutoTriage

func (s *AITriageService) ShouldAutoTriage(ctx context.Context, tenantID shared.ID, severity string) (bool, error)

ShouldAutoTriage checks if a finding should be auto-triaged based on tenant settings.

type BudgetRepository

type BudgetRepository interface {
	GetOrCreate(ctx context.Context, tenantID shared.ID, periodStart time.Time, periodEnd time.Time, defaultLimit int64) (*BudgetRow, error)
	IncrementUsed(ctx context.Context, tenantID shared.ID, periodStart time.Time, delta int64) (int64, error)
	UpdateLastWarnSent(ctx context.Context, tenantID shared.ID, periodStart time.Time, used int64) error
	UpdateLastBlockSent(ctx context.Context, tenantID shared.ID, periodStart time.Time, used int64) error
}

BudgetRepository persists and increments the monthly token counter. Implementation lives in internal/infra/postgres/ai_triage_budget_repository.go (not shipped in this PR; RFC-008 Phase 1 scaffolds the domain only).

Contract:

  • GetOrCreate is idempotent per (tenantID, periodStart). First call in a month creates the row; later calls return it.
  • IncrementUsed MUST be atomic (UPDATE … SET tokens_used = tokens_used + $delta WHERE tenant_id = $1 AND period_start = $2 RETURNING tokens_used). Concurrent triage runs in multi-pod deploys would otherwise race.
  • UpdateLastWarnSent + UpdateLastBlockSent record which absolute tokens_used value last fired a notification, so the service doesn't spam after the first crossing.

type BudgetRow

type BudgetRow struct {
	TenantID          shared.ID
	PeriodStart       time.Time
	PeriodEnd         time.Time
	TokenLimit        int64 // 0 == unlimited
	TokensUsed        int64
	WarnThresholdPct  int // service default when stored as 0
	BlockThresholdPct int // service default when stored as 0
	LastWarnSentUsed  int64
	LastBlockSentUsed int64
}

BudgetRow mirrors the ai_triage_budgets schema for in-memory use.

type BudgetService

type BudgetService struct {
	// contains filtered or unexported fields
}

BudgetService is the enforcement + visibility surface called from triage service.go. It is safe to construct with a nil repo when Enabled = false — every method short-circuits. This is what lets RFC-008 Phase 1 ship with zero behaviour change.

func NewBudgetService

func NewBudgetService(repo BudgetRepository, cfg BudgetServiceConfig, log *logger.Logger) *BudgetService

NewBudgetService returns a fully configured service. Zero-value warn/block percentages are filled to 80 / 100.

func (*BudgetService) Check

func (s *BudgetService) Check(ctx context.Context, tenantID shared.ID, estimatedTokens int) error

Check is invoked BEFORE the LLM call. estimatedTokens is the caller's best-effort guess for (prompt + expected completion) so that Check can reject a call whose estimate would push the tenant past block_threshold_pct. The check uses the CURRENT tokens_used; actual usage is recorded post-call via Record.

Phase 1: returns nil unconditionally when cfg.Enabled == false.

func (*BudgetService) Record

func (s *BudgetService) Record(ctx context.Context, tenantID shared.ID, tokens int) error

Record is invoked AFTER the LLM call with the actual token count from the provider response (prompt + completion). It atomically increments tokens_used and detects threshold crossings.

Phase 1: no-op when cfg.Enabled == false. When enabled but repo fails, we log loud and return the error — caller decides to swallow or surface. Default handling in service.go is to log and continue (the LLM already ran, so we've already spent the tokens; refusing to record would just make the next tenant less accurately billed).

func (*BudgetService) Status

func (s *BudgetService) Status(ctx context.Context, tenantID shared.ID) (*BudgetStatus, error)

Status returns a read-model for UI / dashboard consumption. Works regardless of cfg.Enabled — admins need to see usage even before enforcement kicks in.

type BudgetServiceConfig

type BudgetServiceConfig struct {
	// Enabled gates the whole service. When false, Check always
	// returns nil and Record becomes a no-op. Phase 1 default.
	Enabled bool
	// Strict controls repo-failure behaviour. See ErrBudgetUnavailable.
	Strict bool
	// DefaultTokensPerMonth applied when a tenant's row has
	// token_limit = 0 AND the tenant has no plan override. 0 =
	// unlimited; see RFC-008 §3.5.
	DefaultTokensPerMonth int64
	// Threshold defaults when a row stores 0.
	DefaultWarnPct  int
	DefaultBlockPct int
}

BudgetServiceConfig controls the service-layer defaults and the flag that governs whether Check enforces at all.

type BudgetStatus

type BudgetStatus struct {
	TenantID    shared.ID
	PeriodStart time.Time
	PeriodEnd   time.Time
	Limit       int64
	Used        int64
	Remaining   int64 // 0 when Limit == 0 (unlimited)
	UsedPct     int   // 0 when Limit == 0

	// NeedsWarn is true when the current run (or the pre-check) has
	// crossed warn_threshold_pct and no warn event has been emitted
	// yet for the period. The service queries this when deciding
	// whether to enqueue a notification.
	NeedsWarn bool
}

BudgetStatus is the read-model for dashboards and admin endpoints. Zero-valued Limit means "unlimited" per the schema convention.

type BulkTriageJob

type BulkTriageJob struct {
	FindingID string `json:"finding_id"`
	JobID     string `json:"job_id,omitempty"`
	Status    string `json:"status"`
	Error     string `json:"error,omitempty"`
}

BulkTriageJob represents a single job in bulk triage.

type BulkTriageRequest

type BulkTriageRequest struct {
	TenantID   string   `validate:"required,uuid"`
	FindingIDs []string `validate:"required,min=1,max=100,dive,uuid"`
	UserID     *string  `validate:"omitempty,uuid"`
}

BulkTriageRequest represents a request to triage multiple findings.

type BulkTriageResponse

type BulkTriageResponse struct {
	Jobs       []BulkTriageJob `json:"jobs"`
	TotalCount int             `json:"total_count"`
	Queued     int             `json:"queued"`
	Failed     int             `json:"failed"`
}

BulkTriageResponse represents the response from a bulk triage request.

type PromptSanitizer

type PromptSanitizer struct {
	// contains filtered or unexported fields
}

PromptSanitizer sanitizes user-provided data before including in prompts.

func NewPromptSanitizer

func NewPromptSanitizer() *PromptSanitizer

NewPromptSanitizer creates a new prompt sanitizer.

func (*PromptSanitizer) SanitizeCodeSnippet

func (s *PromptSanitizer) SanitizeCodeSnippet(code string) string

SanitizeCodeSnippet sanitizes a code snippet for inclusion in prompts.

func (*PromptSanitizer) SanitizeForPrompt

func (s *PromptSanitizer) SanitizeForPrompt(text string) string

SanitizeForPrompt sanitizes a string for inclusion in an LLM prompt. Applies unicode normalization to prevent bypass via homoglyphs and special characters.

type RecoverStuckJobsInput

type RecoverStuckJobsInput struct {
	// StuckDuration is how long a job must be stuck before being recovered.
	// Default: 15 minutes
	StuckDuration time.Duration
	// Limit is the maximum number of stuck jobs to recover in one batch.
	// Default: 50
	Limit int
}

RecoverStuckJobsInput contains parameters for stuck job recovery.

type RecoverStuckJobsOutput

type RecoverStuckJobsOutput struct {
	// Total is the number of stuck jobs found.
	Total int `json:"total"`
	// Recovered is the number of jobs successfully marked as failed.
	Recovered int `json:"recovered"`
	// Skipped is the number of jobs that were already in a terminal state.
	Skipped int `json:"skipped"`
	// Errors is the number of jobs that failed to update.
	Errors int `json:"errors"`
}

RecoverStuckJobsOutput contains the results of stuck job recovery.

type TokenLimitError

type TokenLimitError struct {
	Used  int
	Limit int
}

TokenLimitError is returned when token limit is exceeded.

func (*TokenLimitError) Error

func (e *TokenLimitError) Error() string

type TriageBroadcaster

type TriageBroadcaster interface {
	// BroadcastTriage sends a triage event to subscribers.
	// channel: the channel to broadcast to (e.g., "triage:{finding_id}")
	// data: the triage event data
	// tenantID: tenant isolation for the broadcast
	BroadcastTriage(channel string, data any, tenantID string)
}

TriageBroadcaster broadcasts triage events for real-time WebSocket updates.

type TriageOutputValidator

type TriageOutputValidator struct {
	// contains filtered or unexported fields
}

TriageOutputValidator validates LLM triage output for security and correctness.

func NewTriageOutputValidator

func NewTriageOutputValidator() *TriageOutputValidator

NewTriageOutputValidator creates a new validator with default settings.

func (*TriageOutputValidator) ValidateAndSanitize

func (v *TriageOutputValidator) ValidateAndSanitize(content string) (*aitriagedom.TriageAnalysis, error)

ValidateAndSanitize validates and sanitizes the LLM output. Returns a sanitized analysis or an error if validation fails.

type TriageRequest

type TriageRequest struct {
	TenantID   string  `validate:"required,uuid"`
	FindingID  string  `validate:"required,uuid"`
	TriageType string  `validate:"required,oneof=auto manual bulk"`
	UserID     *string `validate:"omitempty,uuid"` // For manual requests
}

TriageRequest represents a request to triage a finding.

type TriageResponse

type TriageResponse struct {
	JobID  string `json:"job_id"`
	Status string `json:"status"`
}

TriageResponse represents the response from a triage request.

type TriageResultResponse

type TriageResultResponse struct {
	ID                      string     `json:"id"`
	Status                  string     `json:"status"`
	SeverityAssessment      string     `json:"severity_assessment,omitempty"`
	SeverityJustification   string     `json:"severity_justification,omitempty"`
	RiskScore               float64    `json:"risk_score,omitempty"`
	Exploitability          string     `json:"exploitability,omitempty"`
	ExploitabilityDetails   string     `json:"exploitability_details,omitempty"`
	BusinessImpact          string     `json:"business_impact,omitempty"`
	PriorityRank            int        `json:"priority_rank,omitempty"`
	FalsePositiveLikelihood float64    `json:"false_positive_likelihood,omitempty"`
	FalsePositiveReason     string     `json:"false_positive_reason,omitempty"`
	Summary                 string     `json:"summary,omitempty"`
	CreatedAt               time.Time  `json:"created_at"`
	CompletedAt             *time.Time `json:"completed_at,omitempty"`
	ErrorMessage            string     `json:"error_message,omitempty"`
}

TriageResultResponse represents the detailed triage result.

type WorkflowEventDispatcherInterface

type WorkflowEventDispatcherInterface interface {
	DispatchAITriageCompleted(ctx context.Context, tenantID, findingID, triageID shared.ID, triageData map[string]any)
	DispatchAITriageFailed(ctx context.Context, tenantID, findingID, triageID shared.ID, errorMessage string)
}

WorkflowEventDispatcherInterface defines the interface for dispatching workflow events. This avoids circular dependency with workflow package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL