Documentation
¶
Index ¶
- Constants
- Variables
- func CheckTokenLimit(usedTokens, limitTokens int) error
- type AIConfigInfo
- type AITriageJobEnqueuer
- type AITriageService
- func (s *AITriageService) EnqueueAutoTriage(ctx context.Context, tenantID, findingID shared.ID) error
- func (s *AITriageService) GetAIConfig(ctx context.Context, tenantID string) (*AIConfigInfo, error)
- func (s *AITriageService) GetLatestTriageByFinding(ctx context.Context, tenantID, findingID string) (*TriageResultResponse, error)
- func (s *AITriageService) GetPlanTokenLimit(_ context.Context, _ string) (int64, error)
- func (s *AITriageService) GetTriageResult(ctx context.Context, tenantID, resultID string) (*TriageResultResponse, error)
- func (s *AITriageService) ListTriageHistory(ctx context.Context, tenantID, findingID string, limit, offset int) ([]*TriageResultResponse, int, error)
- func (s *AITriageService) ProcessTriage(ctx context.Context, resultID, tenantID, findingID shared.ID) error
- func (s *AITriageService) RecoverStuckJobs(ctx context.Context, input RecoverStuckJobsInput) (*RecoverStuckJobsOutput, error)
- func (s *AITriageService) RequestBulkTriage(ctx context.Context, req BulkTriageRequest) (*BulkTriageResponse, error)
- func (s *AITriageService) RequestTriage(ctx context.Context, req TriageRequest) (*TriageResponse, error)
- func (s *AITriageService) SetAuditService(auditSvc *auditapp.AuditService)
- func (s *AITriageService) SetBudgetService(b *BudgetService)
- func (s *AITriageService) SetJobEnqueuer(enqueuer AITriageJobEnqueuer)
- func (s *AITriageService) SetTriageBroadcaster(broadcaster TriageBroadcaster)
- func (s *AITriageService) SetWorkflowDispatcher(dispatcher WorkflowEventDispatcherInterface)
- func (s *AITriageService) ShouldAutoTriage(ctx context.Context, tenantID shared.ID, severity string) (bool, error)
- type BudgetRepository
- type BudgetRow
- type BudgetService
- type BudgetServiceConfig
- type BudgetStatus
- type BulkTriageJob
- type BulkTriageRequest
- type BulkTriageResponse
- type PromptSanitizer
- type RecoverStuckJobsInput
- type RecoverStuckJobsOutput
- type TokenLimitError
- type TriageBroadcaster
- type TriageOutputValidator
- type TriageRequest
- type TriageResponse
- type TriageResultResponse
- type WorkflowEventDispatcherInterface
Constants ¶
const TypeAITriage = "ai:triage"
TypeAITriage is the asynq task type for AI triage jobs.
Variables ¶
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") // 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 ¶
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 ¶
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 ¶
func (s *AITriageService) RecoverStuckJobs(ctx context.Context, input RecoverStuckJobsInput) (*RecoverStuckJobsOutput, error)
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 ¶
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 ¶
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 ¶
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 ¶
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.