module

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: 13 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ActivityItem

type ActivityItem struct {
	Type        string
	Title       string
	Description string
	Timestamp   time.Time
}

ActivityItem represents a recent activity item.

type AssetStatsData

type AssetStatsData struct {
	Total            int
	ByType           map[string]int
	BySubType        map[string]int
	ByStatus         map[string]int
	AverageRiskScore float64
}

AssetStatsData holds raw asset statistics from repository.

type CreateReportScheduleInput

type CreateReportScheduleInput struct {
	TenantID       string
	Name           string
	ReportType     string
	Format         string
	CronExpression string
	Timezone       string
	Recipients     []reportscheduledom.Recipient
	Options        map[string]any
	ActorID        string
}

CreateReportScheduleInput holds input for creating a schedule.

type DashboardAllStats

type DashboardAllStats struct {
	Assets   AssetStatsData
	Findings FindingStatsData
	Repos    RepositoryStatsData
	Activity []ActivityItem
}

DashboardAllStats holds all dashboard stats from the optimized batched query.

type DashboardService

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

DashboardService provides dashboard-related operations.

func NewDashboardService

func NewDashboardService(repo DashboardStatsRepository, log *logger.Logger) *DashboardService

NewDashboardService creates a new DashboardService.

func (*DashboardService) GetDataQualityScorecard

func (s *DashboardService) GetDataQualityScorecard(ctx context.Context, tenantID shared.ID) (*DataQualityScorecard, error)

GetDataQualityScorecard returns data quality metrics (RFC-005 Gap 5).

func (*DashboardService) GetExecutiveSummary

func (s *DashboardService) GetExecutiveSummary(ctx context.Context, tenantID shared.ID, days int) (*ExecutiveSummary, error)

GetExecutiveSummary returns executive-level metrics for a time period.

func (*DashboardService) GetGlobalStats

func (s *DashboardService) GetGlobalStats(ctx context.Context) (*DashboardStats, error)

GetGlobalStats returns global dashboard statistics (not tenant-scoped). Deprecated: Use GetStatsForTenants for proper multi-tenant authorization.

func (*DashboardService) GetMTTRAnalytics

func (s *DashboardService) GetMTTRAnalytics(ctx context.Context, tenantID shared.ID, days int) (*MTTRAnalytics, error)

GetMTTRAnalytics returns MTTR breakdown by severity and priority class.

func (*DashboardService) GetMTTRMetrics

func (s *DashboardService) GetMTTRMetrics(ctx context.Context, tenantID shared.ID) (map[string]float64, error)

GetMTTRMetrics returns MTTR (Mean Time To Remediate) in hours by severity.

func (*DashboardService) GetProcessMetrics

func (s *DashboardService) GetProcessMetrics(ctx context.Context, tenantID shared.ID, days int) (*ProcessMetrics, error)

GetProcessMetrics returns process efficiency metrics.

func (*DashboardService) GetRiskTrend

func (s *DashboardService) GetRiskTrend(ctx context.Context, tenantID shared.ID, days int) ([]RiskTrendPoint, error)

GetRiskTrend returns risk snapshot time-series (RFC-005 Gap 4).

func (*DashboardService) GetRiskVelocity

func (s *DashboardService) GetRiskVelocity(ctx context.Context, tenantID shared.ID, weeks int) ([]RiskVelocityPoint, error)

GetRiskVelocity returns weekly new vs resolved finding counts.

func (*DashboardService) GetStats

func (s *DashboardService) GetStats(ctx context.Context, tenantID shared.ID) (*DashboardStats, error)

GetStats returns dashboard statistics for a tenant. Uses optimized batched query (2 queries instead of 10+).

func (*DashboardService) GetStatsForTenants

func (s *DashboardService) GetStatsForTenants(ctx context.Context, tenantIDs []string) (*DashboardStats, error)

GetStatsForTenants returns dashboard statistics filtered by accessible tenant IDs. This should be used for multi-tenant authorization - only shows data from tenants the user has access to.

type DashboardStats

type DashboardStats struct {
	// Asset stats
	AssetCount       int
	AssetsByType     map[string]int
	AssetsBySubType  map[string]int
	AssetsByStatus   map[string]int
	AverageRiskScore float64

	// Finding stats
	FindingCount       int
	FindingsBySeverity map[string]int
	FindingsByStatus   map[string]int
	OverdueFindings    int
	AverageCVSS        float64

	// Repository stats (repositories are assets with type 'repository')
	RepositoryCount          int
	RepositoriesWithFindings int

	// Recent activity
	RecentActivity []ActivityItem

	// Finding trend (monthly breakdown by severity)
	FindingTrend []FindingTrendPoint
}

DashboardStats represents aggregated dashboard statistics.

type DashboardStatsRepository

type DashboardStatsRepository interface {
	// GetAssetStats returns asset statistics for a tenant
	GetAssetStats(ctx context.Context, tenantID shared.ID) (AssetStatsData, error)
	// GetFindingStats returns finding statistics for a tenant
	GetFindingStats(ctx context.Context, tenantID shared.ID) (FindingStatsData, error)
	// GetRepositoryStats returns repository statistics for a tenant
	GetRepositoryStats(ctx context.Context, tenantID shared.ID) (RepositoryStatsData, error)
	// GetRecentActivity returns recent activity for a tenant
	GetRecentActivity(ctx context.Context, tenantID shared.ID, limit int) ([]ActivityItem, error)
	// GetFindingTrend returns monthly finding counts by severity for a tenant
	GetFindingTrend(ctx context.Context, tenantID shared.ID, months int) ([]FindingTrendPoint, error)
	// GetAllStats returns all dashboard stats in 2 optimized queries (replaces 10+ individual calls)
	GetAllStats(ctx context.Context, tenantID shared.ID) (*DashboardAllStats, error)

	// Global stats (not tenant-scoped) - deprecated, use filtered versions
	GetGlobalAssetStats(ctx context.Context) (AssetStatsData, error)
	GetGlobalFindingStats(ctx context.Context) (FindingStatsData, error)
	GetGlobalRepositoryStats(ctx context.Context) (RepositoryStatsData, error)
	GetGlobalRecentActivity(ctx context.Context, limit int) ([]ActivityItem, error)

	// MTTR & Trending
	GetMTTRMetrics(ctx context.Context, tenantID shared.ID) (map[string]float64, error)
	GetRiskVelocity(ctx context.Context, tenantID shared.ID, weeks int) ([]RiskVelocityPoint, error)

	// Filtered stats (by accessible tenant IDs) - for multi-tenant authorization
	GetFilteredAssetStats(ctx context.Context, tenantIDs []string) (AssetStatsData, error)
	GetFilteredFindingStats(ctx context.Context, tenantIDs []string) (FindingStatsData, error)
	GetFilteredRepositoryStats(ctx context.Context, tenantIDs []string) (RepositoryStatsData, error)
	GetFilteredRecentActivity(ctx context.Context, tenantIDs []string, limit int) ([]ActivityItem, error)

	// Data Quality Scorecard (RFC-005)
	GetDataQualityScorecard(ctx context.Context, tenantID shared.ID) (*DataQualityScorecard, error)
	// Risk Trend (RFC-005 Gap 4)
	GetRiskTrend(ctx context.Context, tenantID shared.ID, days int) ([]RiskTrendPoint, error)

	// Executive Summary (Phase 2)
	GetExecutiveSummary(ctx context.Context, tenantID shared.ID, days int) (*ExecutiveSummary, error)
	// MTTR Analytics (Phase 2)
	GetMTTRAnalytics(ctx context.Context, tenantID shared.ID, days int) (*MTTRAnalytics, error)
	// Process Metrics (Phase 2)
	GetProcessMetrics(ctx context.Context, tenantID shared.ID, days int) (*ProcessMetrics, error)
}

DashboardStatsRepository defines the interface for dashboard data access.

type DataQualityScorecard

type DataQualityScorecard struct {
	AssetOwnershipPct  float64 `json:"asset_ownership_pct"`
	FindingEvidencePct float64 `json:"finding_evidence_pct"`
	MedianLastSeenDays float64 `json:"median_last_seen_days"`
	DeduplicationRate  float64 `json:"deduplication_rate"`
	TotalAssets        int     `json:"total_assets"`
	TotalFindings      int     `json:"total_findings"`
}

DataQualityScorecard holds data quality metrics (RFC-005 Gap 5).

type DependencyEdgeOutput

type DependencyEdgeOutput struct {
	From   string `json:"from"`             // Module that depends on the target.
	To     string `json:"to"`               // Target module required by From.
	Type   string `json:"type"`             // "hard" or "soft".
	Reason string `json:"reason,omitempty"` // Human-readable explanation.
}

DependencyEdgeOutput is the UI-facing shape of one dependency edge.

type DependencyGraphOutput

type DependencyGraphOutput struct {
	Edges []DependencyEdgeOutput `json:"edges"`
}

DependencyGraphOutput is returned by GetDependencyGraph. Flat edge list so the UI can render the graph with any layout library without re-shaping the server response.

type ExecutiveSummary

type ExecutiveSummary struct {
	Period            string    `json:"period"`
	RiskScoreCurrent  float64   `json:"risk_score_current"`
	RiskScoreChange   float64   `json:"risk_score_change"`
	FindingsTotal     int       `json:"findings_total"`
	FindingsResolved  int       `json:"findings_resolved_period"`
	FindingsNew       int       `json:"findings_new_period"`
	P0Open            int       `json:"p0_open"`
	P0Resolved        int       `json:"p0_resolved_period"`
	P1Open            int       `json:"p1_open"`
	P1Resolved        int       `json:"p1_resolved_period"`
	SLACompliancePct  float64   `json:"sla_compliance_pct"`
	SLABreached       int       `json:"sla_breached"`
	MTTRCriticalHrs   float64   `json:"mttr_critical_hours"`
	MTTRHighHrs       float64   `json:"mttr_high_hours"`
	CrownJewelsAtRisk int       `json:"crown_jewels_at_risk"`
	RegressionCount   int       `json:"regression_count"`
	RegressionRatePct float64   `json:"regression_rate_pct"`
	TopRisks          []TopRisk `json:"top_risks"`
}

ExecutiveSummary holds executive-level metrics for a time period.

type FindingStatsData

type FindingStatsData struct {
	Total       int
	BySeverity  map[string]int
	ByStatus    map[string]int
	Overdue     int
	AverageCVSS float64
}

FindingStatsData holds raw finding statistics from repository.

type FindingTrendPoint

type FindingTrendPoint struct {
	Date     string // "Jan", "Feb", etc.
	Critical int
	High     int
	Medium   int
	Low      int
	Info     int
}

FindingTrendPoint represents one month's finding counts by severity.

type GetTenantEnabledModulesOutput

type GetTenantEnabledModulesOutput struct {
	ModuleIDs  []string
	Modules    []*moduledom.Module
	SubModules map[string][]*moduledom.Module
}

GetTenantEnabledModulesOutput represents the output for GetTenantEnabledModules.

type MTTRAnalytics

type MTTRAnalytics struct {
	BySeverity      map[string]float64 `json:"by_severity"`
	ByPriorityClass map[string]float64 `json:"by_priority_class"`
	Overall         float64            `json:"overall_hours"`
	SampleSize      int                `json:"sample_size"`
}

MTTRAnalytics holds MTTR breakdown by severity and priority class.

type ModulePresetOutput

type ModulePresetOutput struct {
	ID             string   `json:"id"`
	Name           string   `json:"name"`
	Description    string   `json:"description"`
	TargetPersona  string   `json:"target_persona"`
	Icon           string   `json:"icon"`
	KeyOutcomes    []string `json:"key_outcomes"`
	RecommendedFor []string `json:"recommended_for"`
	ModuleCount    int      `json:"module_count"` // resolved count incl. core + transitive
}

ModulePresetOutput is the API-facing shape of one preset.

type ModuleRefOutput

type ModuleRefOutput struct {
	ModuleID string `json:"module_id"`
	Name     string `json:"name"`
}

ModuleRefOutput is a thin (id, name) pair used in diff listings.

type ModuleRepository

type ModuleRepository interface {
	ListAllModules(ctx context.Context) ([]*moduledom.Module, error)
	ListActiveModules(ctx context.Context) ([]*moduledom.Module, error)
	GetModuleByID(ctx context.Context, id string) (*moduledom.Module, error)
	GetSubModules(ctx context.Context, parentModuleID string) ([]*moduledom.Module, error)
	ListAllSubModules(ctx context.Context) (map[string][]*moduledom.Module, error)
}

ModuleRepository interface for module operations.

type ModuleService

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

ModuleService handles module-related business operations.

toggleLocks serialises concurrent UpdateTenantModules calls for the same tenant. Without this, two admins flipping related modules at the same time could interleave reads and writes such that the dependency check passes for each request individually but the resulting combined state violates the invariant (TOCTOU). The lock is per-tenant so different tenants don't contend.

Caveat: this is an in-process mutex. A multi-replica deployment needs a cross-process lock (Redis SETNX, Postgres advisory lock on a shared connection, etc.). In the CTEM deployment model the API runs as a single replica today; when horizontal scale lands, swap this map for the advisory-lock variant.

func NewModuleService

func NewModuleService(moduleRepo ModuleRepository, log *logger.Logger) *ModuleService

NewModuleService creates a new ModuleService.

func (*ModuleService) ApplyPreset

func (s *ModuleService) ApplyPreset(ctx context.Context, tenantID, presetID string, actx auditapp.AuditContext) (*TenantModuleConfigOutput, error)

ApplyPreset materialises the preset into tenant_modules. Diff is computed, then turned into UpdateTenantModules calls so the dependency-graph validation and audit logging run exactly as if the admin had toggled each module manually. Same locking semantics too.

If tenantID is empty or the audit context has no actor, this still works for the initial tenant-creation code path — the caller is expected to have populated actx in that case.

func (*ModuleService) GetDependencyGraph

func (s *ModuleService) GetDependencyGraph(_ context.Context) *DependencyGraphOutput

GetDependencyGraph returns the static module dependency graph. The graph is read from pkg/domain/module/dependency.go (platform-wide spec, does not change per tenant or at runtime). The UI uses this to render the Settings → Modules page with dependency badges and to show "disabling X will also affect Y, Z" confirmation dialogs.

func (*ModuleService) GetModule

func (s *ModuleService) GetModule(ctx context.Context, moduleID string) (*moduledom.Module, error)

GetModule retrieves a module by ID.

func (*ModuleService) GetTenantEnabledModules

func (s *ModuleService) GetTenantEnabledModules(ctx context.Context, tenantID string) (*GetTenantEnabledModulesOutput, error)

GetTenantEnabledModules returns all enabled modules for a tenant. Filters by tenant module overrides if configured. Optimized: 2 queries (modules + tenant_modules) instead of 3. Sub-modules are extracted from the same ListActiveModules result.

func (*ModuleService) GetTenantModuleConfig

func (s *ModuleService) GetTenantModuleConfig(ctx context.Context, tenantID string) (*TenantModuleConfigOutput, error)

GetTenantModuleConfig returns the full module configuration for a tenant admin. Optimized: 2 queries (modules + tenant_modules) instead of 3.

func (*ModuleService) GetTenantModuleVersion

func (s *ModuleService) GetTenantModuleVersion(ctx context.Context, tenantID string) int

GetTenantModuleVersion returns the current module-config version for a tenant. Used by HTTP handlers to construct ETag headers; the returned value is opaque to callers (treat as a token, not a count).

func (*ModuleService) ListActiveModules

func (s *ModuleService) ListActiveModules(ctx context.Context) ([]*moduledom.Module, error)

ListActiveModules returns all active modules.

func (*ModuleService) ListModulePresets

func (s *ModuleService) ListModulePresets(_ context.Context) []ModulePresetOutput

ListModulePresets returns every preset registered in the domain catalogue, with the module count already resolved so the UI can render "Enables X modules" badges without a second round-trip.

func (*ModuleService) PreviewPreset

func (s *ModuleService) PreviewPreset(ctx context.Context, tenantID, presetID string) (*PresetDiffOutput, error)

PreviewPreset computes the diff between the tenant's current module state and the state that would result from applying the preset. Read-only — never mutates tenant_modules.

func (*ModuleService) ResetTenantModules

func (s *ModuleService) ResetTenantModules(ctx context.Context, tenantID string, actx auditapp.AuditContext) (*TenantModuleConfigOutput, error)

ResetTenantModules resets all module overrides for a tenant (all modules enabled).

func (*ModuleService) SetAuditService

func (s *ModuleService) SetAuditService(svc *auditapp.AuditService)

SetAuditService sets the audit service for logging module changes.

func (*ModuleService) SetTenantModuleRepo

func (s *ModuleService) SetTenantModuleRepo(repo TenantModuleRepository)

SetTenantModuleRepo sets the tenant module repository.

func (*ModuleService) SetVersionService

func (s *ModuleService) SetVersionService(v *VersionService)

SetVersionService wires the per-tenant module-version counter used for ETag generation and Redis cache key suffixes. Optional — without it Get returns 1 forever and ETag never matches (acceptable degraded mode, just no caching benefit).

func (*ModuleService) SetWSBroadcaster

func (s *ModuleService) SetWSBroadcaster(b WSBroadcaster)

SetWSBroadcaster wires the WebSocket fan-out used to push "module.updated" events to clients on the tenant channel. Optional — without it, mutations still succeed but clients have to wait for the next SWR dedup window to see changes.

func (*ModuleService) UpdateTenantModules

func (s *ModuleService) UpdateTenantModules(ctx context.Context, tenantID string, updates []moduledom.TenantModuleUpdate, actx auditapp.AuditContext) (*TenantModuleConfigOutput, error)

UpdateTenantModules toggles modules for a tenant.

func (*ModuleService) ValidateToggle

func (s *ModuleService) ValidateToggle(ctx context.Context, tenantID string, updates []moduledom.TenantModuleUpdate) (*ValidationIssues, error)

ValidateToggle runs the same dependency gate as UpdateTenantModules but WITHOUT persisting. The UI calls this from the module-toggle UI to preview blockers/warnings before the user commits. Hit path:

POST /api/v1/tenants/{t}/settings/modules/validate

type PresetDiffOutput

type PresetDiffOutput struct {
	PresetID    string            `json:"preset_id"`
	PresetName  string            `json:"preset_name"`
	ToEnable    []ModuleRefOutput `json:"to_enable"`
	ToDisable   []ModuleRefOutput `json:"to_disable"`
	Unchanged   int               `json:"unchanged"`
	TotalAfter  int               `json:"total_after"`
	AuditNotice string            `json:"audit_notice,omitempty"`
}

PresetDiffOutput describes what would change if a preset were applied.

type ProcessMetrics

type ProcessMetrics struct {
	ApprovalAvgHours     float64 `json:"approval_avg_hours"`
	ApprovalCount        int     `json:"approval_count"`
	RetestAvgHours       float64 `json:"retest_avg_hours"`
	RetestCount          int     `json:"retest_count"`
	StaleAssets          int     `json:"stale_assets"`
	StaleAssetsPct       float64 `json:"stale_assets_pct"`
	FindingsWithoutOwner int     `json:"findings_without_owner"`
	AvgTimeToAssignHours float64 `json:"avg_time_to_assign_hours"`
}

ProcessMetrics holds process efficiency metrics.

type ReportScheduleService

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

ReportScheduleService handles report schedule business logic.

func NewReportScheduleService

func NewReportScheduleService(repo reportscheduledom.Repository, log *logger.Logger) *ReportScheduleService

NewReportScheduleService creates a new ReportScheduleService.

func (*ReportScheduleService) CreateSchedule

CreateSchedule creates a new report schedule.

func (*ReportScheduleService) DeleteSchedule

func (s *ReportScheduleService) DeleteSchedule(ctx context.Context, tenantID, scheduleID string) error

DeleteSchedule removes a schedule.

func (*ReportScheduleService) GetSchedule

func (s *ReportScheduleService) GetSchedule(ctx context.Context, tenantID, scheduleID string) (*reportscheduledom.ReportSchedule, error)

GetSchedule retrieves a single schedule.

func (*ReportScheduleService) ListSchedules

ListSchedules returns schedules for a tenant.

func (*ReportScheduleService) ToggleSchedule

func (s *ReportScheduleService) ToggleSchedule(ctx context.Context, tenantID, scheduleID string, active bool) error

ToggleSchedule activates or deactivates a schedule.

type RepositoryStatsData

type RepositoryStatsData struct {
	Total        int
	WithFindings int
}

RepositoryStatsData holds raw repository statistics from repository.

type RiskTrendPoint

type RiskTrendPoint struct {
	Date             string  `json:"date"`
	RiskScoreAvg     float64 `json:"risk_score_avg"`
	FindingsOpen     int     `json:"findings_open"`
	SLACompliancePct float64 `json:"sla_compliance_pct"`
	P0Open           int     `json:"p0_open"`
	P1Open           int     `json:"p1_open"`
	P2Open           int     `json:"p2_open"`
	P3Open           int     `json:"p3_open"`
}

RiskTrendPoint represents a single point in a risk trend time-series.

type RiskVelocityPoint

type RiskVelocityPoint struct {
	Week          time.Time `json:"week"`
	NewCount      int       `json:"new_count"`
	ResolvedCount int       `json:"resolved_count"`
	Velocity      int       `json:"velocity"` // new - resolved (positive = losing ground)
}

RiskVelocityPoint represents weekly new vs resolved finding counts.

type SubModuleInfo

type SubModuleInfo struct {
	Module    *moduledom.Module
	IsEnabled bool
}

SubModuleInfo combines sub-module metadata with tenant-specific enabled state.

type TenantModuleConfigOutput

type TenantModuleConfigOutput struct {
	Modules []*TenantModuleInfo
	Summary TenantModuleSummary
	// Warnings is a best-effort list of soft-dependency degradations
	// introduced by the MOST RECENT toggle. On GET the field is empty;
	// on PATCH it carries the warnings the service decided not to
	// escalate to a blocker. Handlers render these as toast/banner on
	// the Settings → Modules page.
	Warnings []ToggleIssue
}

TenantModuleConfigOutput represents the full module configuration for a tenant.

type TenantModuleInfo

type TenantModuleInfo struct {
	Module     *moduledom.Module
	IsEnabled  bool
	SubModules []*SubModuleInfo
}

TenantModuleInfo combines module metadata with tenant-specific enabled state.

type TenantModuleRepository

type TenantModuleRepository interface {
	ListByTenant(ctx context.Context, tenantID shared.ID) ([]*moduledom.TenantModuleOverride, error)
	UpsertBatch(ctx context.Context, tenantID shared.ID, updates []moduledom.TenantModuleUpdate, updatedBy *shared.ID) error
	DeleteByTenant(ctx context.Context, tenantID shared.ID) error
}

TenantModuleRepository interface for per-tenant module configuration.

type TenantModuleSummary

type TenantModuleSummary struct {
	Total    int
	Enabled  int
	Disabled int
	Core     int
}

TenantModuleSummary provides counts of module states.

type ToggleError

type ToggleError struct {
	// ModuleID is the one the caller tried to toggle.
	ModuleID string `json:"module_id"`
	// ModuleName is the display name (used in the top-level message).
	ModuleName string `json:"module_name"`
	// Action is "enable" or "disable" — tells the UI which flow the
	// error came from so it can phrase the dialog correctly.
	Action string `json:"action"`
	// Blockers are hard-dependent modules that stop the toggle. Each
	// entry is suitable for a bullet list in the UI.
	Blockers []ToggleIssue `json:"blockers,omitempty"`
	// Required is the inverse — when enabling, these modules must be
	// enabled first.
	Required []ToggleIssue `json:"required,omitempty"`
}

ToggleError is returned when a module toggle is rejected by the dependency gate. It implements error so callers can errors.As() or errors.Is() against shared.ErrValidation; the HTTP handler type- asserts on ToggleError to render a structured 400 body the UI can parse without regex-ing the message.

func (*ToggleError) Error

func (e *ToggleError) Error() string

Error formats a plain-text fallback for log lines / CLI. The HTTP handler should NOT rely on this and instead serialise the struct itself as JSON.

func (*ToggleError) Unwrap

func (e *ToggleError) Unwrap() error

Unwrap lets callers errors.Is(err, shared.ErrValidation) succeed.

type ToggleIssue

type ToggleIssue struct {
	// ModuleID is the dependent module (the one that would be broken
	// by the toggle). For blockers this is the module that stops the
	// toggle; for warnings this is the module that will degrade.
	ModuleID string `json:"module_id"`
	// Name is the human-readable display name from the modules row.
	Name string `json:"name"`
	// Reason is the short sentence from the dependency spec explaining
	// why the edge exists.
	Reason string `json:"reason"`
}

ToggleIssue is one blocker or warning raised by module-toggle validation. The fields mirror the shape rendered on the Settings → Modules page: module ID for programmatic lookup, name for display, reason for a tooltip.

type TopRisk

type TopRisk struct {
	FindingTitle  string   `json:"title"`
	Severity      string   `json:"severity"`
	PriorityClass string   `json:"priority_class"`
	AssetName     string   `json:"asset_name"`
	EPSSScore     *float64 `json:"epss_score"`
	IsInKEV       bool     `json:"is_in_kev"`
}

TopRisk represents a high-priority open finding for executive view.

type ValidationIssues

type ValidationIssues struct {
	// Blockers — HARD-dependent modules still enabled. Non-empty
	// means the toggle is rejected.
	Blockers []ToggleIssue `json:"blockers,omitempty"`
	// Warnings — SOFT-dependent modules still enabled. Non-empty
	// means the toggle proceeds but the UI should confirm with the
	// user first.
	Warnings []ToggleIssue `json:"warnings,omitempty"`
	// Required — when enabling, modules that must be enabled first.
	Required []ToggleIssue `json:"required,omitempty"`
}

ValidationIssues pairs blockers + warnings so callers that preview without applying still get both arrays. Warnings are not fatal — the toggle is allowed but the UI should surface them.

type VersionService

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

VersionService tracks a per-tenant "module configuration version" in Redis. The counter increments on every mutation (toggle, reset, preset apply). Consumers use the version for:

  1. ETag headers → 304 Not Modified when the client's cached response matches the current version.
  2. Redis cache key suffix → old payloads auto-expire rather than needing active DELETEs.
  3. WebSocket "module.updated" payload → clients compare to their cached version and invalidate their SWR cache accordingly.

The pattern mirrors accesscontrol.PermissionVersionService — same INCR-based atomicity, same TTL refresh. One key per tenant rather than per (tenant, user) because module state is tenant-scoped.

Key format: mod_ver:{tenant_id} → integer version (starts at 1)

Graceful degradation: on Redis failure, reads return 1 and writes are best-effort — the HTTP layer still works, ETag just never matches (serving fresh payloads), and WS broadcasts still fire.

func NewVersionService

func NewVersionService(redisClient *redis.Client, log *logger.Logger) *VersionService

NewVersionService creates a module version tracker. A nil redisClient is OK — every method degrades to the default (version 1) so the HTTP layer continues to work in tests or when Redis is down.

func (*VersionService) Get

func (s *VersionService) Get(ctx context.Context, tenantID string) int

Get returns the current module version for the tenant. Returns 1 when no version exists yet or Redis is unavailable — callers must treat this as "unknown / probably changed" rather than a literal version.

func (*VersionService) Increment

func (s *VersionService) Increment(ctx context.Context, tenantID string) int

Increment atomically bumps the module version for a tenant and refreshes the TTL. Called after every successful toggle / reset / preset apply. The returned version is the fresh value, useful for embedding in WebSocket broadcasts and log lines.

type WSBroadcaster

type WSBroadcaster interface {
	Broadcast(channel string, event any)
}

WSBroadcaster is the minimal interface ModuleService needs to fan out "module.updated" events to subscribers on the tenant channel. Defined locally rather than imported from the websocket package to keep the dependency direction app→infra (not the other way) and to allow nil/no-op in tests.

Jump to

Keyboard shortcuts

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