rules

package
v1.19.1 Latest Latest
Warning

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

Go to latest
Published: May 29, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package rules is the daemon-side workflow automation engine. It owns rule storage (the rules + rule_runs tables from migration 0014), evaluation (condition tree + action dispatch), and integration with the v1.6 event bus.

The public surface for embedders lives at pkg/compliancekit/rules; this package converts to/from those types at the persistence boundary so internal evaluation can use richer Go types (registered evaluator functions, dispatcher closures) without leaking them onto the v1.x SemVer contract.

Engine lifecycle:

eng := rules.New(store, registry)
eng.Start(ctx)        // subscribes to bus.Producer
defer eng.Stop()

Per-event evaluation:

eng.HandleEvent(ctx, "finding.created", payload)

The HandleEvent path is invoked by the v1.6 event-bus subscriber (Phase 7 wires that). For now this package only exposes the types + storage + registry; phase 1+ layers the condition / action implementations + bus integration on top.

Index

Constants

This section is empty.

Variables

View Source
var DefaultRegistry = NewRegistry()

DefaultRegistry is the package-global registry the engine reads from by default. internal/rules/conditions + internal/rules/actions init() into this — same pattern as the v0.x check registry.

Functions

func SetClock

func SetClock(fn func() time.Time) func() time.Time

SetClock overrides the package clock; returns the previous fn.

func SeverityAtLeast

func SeverityAtLeast(have, want string) bool

SeverityAtLeast returns true when have >= want using the canonical severity ranking. Unknown severity strings rank 0 (never reaches a real threshold).

func SilenceWindow

func SilenceWindow(now time.Time, startHHMM, endHHMM string) bool

SilenceWindow is a small helper for time-of-day style conditions. startHHMM / endHHMM are "HH:MM" strings. now is compared against the local time in the rule's timezone, which the caller is expected to have already loaded.

Types

type ActionDispatcher

type ActionDispatcher func(ctx context.Context, rule *Rule, params map[string]any, ec *EvalContext) ActionResult

ActionDispatcher runs one action. The engine passes the matched rule for context (e.g. rule.ID is useful in audit lines) plus the EvalContext + the action's Params.

type ActionResult

type ActionResult struct {
	Outcome string         `json:"outcome,omitempty"`
	Error   string         `json:"error,omitempty"`
	Data    map[string]any `json:"data,omitempty"`
}

ActionResult is what an action returns after dispatch. Outcome records human-readable text the audit log + simulator surface.

type ConditionEvaluator

type ConditionEvaluator func(ctx context.Context, params map[string]any, ec *EvalContext) (bool, error)

ConditionEvaluator is the signature every built-in condition implements. The bool return is the predicate value; the error return surfaces malformed Params so the engine can mark the rule as broken in the rule_runs table instead of silently no-matching.

type CronLoop

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

CronLoop drives the cron-trigger rules. Construct via NewCronLoop; Run blocks until ctx is canceled.

func NewCronLoop

func NewCronLoop(eng *Engine, repo *Repo, logger *slog.Logger) *CronLoop

NewCronLoop wires the loop. Pass the engine that will dispatch matched rule actions. tickEvery defaults to 30s.

func (*CronLoop) Run

func (c *CronLoop) Run(ctx context.Context) error

Run loops until ctx is canceled. Every tick:

  1. Loads every enabled cron rule.
  2. For each, parses cron_expr against the rule's timezone + compares the computed next-run-after-last-run to now.
  3. Dispatches via Engine.HandleEvent with an EvalContext seeded for a cron-trigger event.

type Engine

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

Engine evaluates rules against incoming events. Construct via New + Start; HandleEvent is the single entry point for upstream event-bus subscribers.

func New

func New(repo *Repo, reg *Registry) *Engine

New builds an Engine over the given repo + registry. Pass DefaultRegistry in production wiring.

func (*Engine) HandleEvent

func (e *Engine) HandleEvent(ctx context.Context, ec *EvalContext) ([]Rule, error)

HandleEvent walks every enabled rule with the matching trigger and evaluates it against the EvalContext. Each rule's outcome is persisted via Repo.RecordRun.

Returns the slice of matched rules (whether their actions succeeded or not) so callers can correlate the event with the downstream effects.

func (*Engine) Simulate

func (e *Engine) Simulate(ctx context.Context, opts SimulateOptions) ([]SimulateResult, error)

Simulate runs the replay + returns one summary per matched rule. The detailed per-fingerprint outcomes land in rule_runs with simulated=1; callers can read them via Repo.RecentRuns.

func (*Engine) WithSimulator

func (e *Engine) WithSimulator() *Engine

WithSimulator returns a sibling engine that records every rule outcome with simulated=1 + suppresses every action dispatch. Used by the v1.9 phase 8 simulator.

type EvalContext

type EvalContext struct {
	Trigger   rules.Trigger
	Now       time.Time
	Finding   FindingFacts   // populated for finding.* triggers
	Scan      ScanFacts      // populated for scan.* triggers
	Resource  ResourceFacts  // populated when the event names a resource
	Extras    map[string]any // free-form trigger-specific payload
	Simulated bool           // true under phase 8 simulator
}

EvalContext is the payload one rule sees during evaluation. The engine builds an EvalContext per trigger event + walks every matching rule against it.

type FindingFacts

type FindingFacts struct {
	Fingerprint  string
	CheckID      string
	Severity     string // "critical"|"high"|...
	Status       string // "pass"|"fail"|"skip"|"error"
	Provider     string
	ResourceID   string
	ResourceType string
	ResourceName string
	Frameworks   []string
	Tags         []string
	FirstSeenAt  time.Time
	LastSeenAt   time.Time
}

FindingFacts is the slice of a Finding the engine needs to evaluate conditions. Kept separate from compliancekit.Finding so the engine doesn't have to load the full graph on every event.

type Registry

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

Registry holds the named condition + action implementations the engine evaluates against. Registration is package-global by convention (one daemon, one rule set); tests may construct their own Registry to isolate fixtures.

func NewRegistry

func NewRegistry() *Registry

NewRegistry constructs an empty Registry.

func (*Registry) ActionKinds

func (r *Registry) ActionKinds() []string

ActionKinds returns every registered action kind, sorted.

func (*Registry) ConditionKinds

func (r *Registry) ConditionKinds() []string

ConditionKinds returns every registered condition kind, sorted. Used by the v1.9 phase 3 UI builder to populate the picker.

func (*Registry) LookupAction

func (r *Registry) LookupAction(kind string) (ActionDispatcher, bool)

LookupAction returns the dispatcher + true, or nil + false.

func (*Registry) LookupCondition

func (r *Registry) LookupCondition(kind string) (ConditionEvaluator, bool)

LookupCondition returns the evaluator + true, or nil + false.

func (*Registry) RegisterAction

func (r *Registry) RegisterAction(kind string, fn ActionDispatcher)

RegisterAction installs a dispatcher under the given kind.

func (*Registry) RegisterCondition

func (r *Registry) RegisterCondition(kind string, fn ConditionEvaluator)

RegisterCondition installs an evaluator under the given kind. Duplicate kinds overwrite (the second wins) — keeps tests simple; production wiring goes through init() and so naturally deduplicates.

type Repo

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

Repo owns the rules + rule_runs tables.

func NewRepo

func NewRepo(s *store.Store) *Repo

NewRepo wires a Repo against the given store.

func (*Repo) All

func (r *Repo) All(ctx context.Context) ([]Rule, error)

All returns every rule (enabled or not) sorted by name. Drives the /rules list view.

func (*Repo) ByID

func (r *Repo) ByID(ctx context.Context, id string) (Rule, error)

ByID loads a single rule by primary key.

func (*Repo) Create

func (r *Repo) Create(ctx context.Context, in Rule) (Rule, error)

Create persists a new rule. ID is auto-generated when empty.

func (*Repo) Delete

func (r *Repo) Delete(ctx context.Context, id string) error

Delete removes a rule. Cascade clears the rule_runs trail via FK.

func (*Repo) ListByTrigger

func (r *Repo) ListByTrigger(ctx context.Context, trigger rules.Trigger) ([]Rule, error)

ListByTrigger returns enabled rules with the given trigger, in priority order (lowest first). The engine subscribes on bus events + walks this list.

func (*Repo) RecentRuns

func (r *Repo) RecentRuns(ctx context.Context, ruleID string, limit int) ([]RunRecord, error)

RecentRuns returns the most recent rule_runs entries for a rule. Drives the /rules/{id} history pane + the simulator preview.

func (*Repo) RecordRun

func (r *Repo) RecordRun(ctx context.Context, rec RunRecord) error

RecordRun appends a row to rule_runs.

func (*Repo) Update

func (r *Repo) Update(ctx context.Context, in Rule) error

Update rewrites every mutable column in place.

type ResourceFacts

type ResourceFacts struct {
	ID       string
	Type     string
	Provider string
	Tags     []string
}

ResourceFacts is the slice of a Resource the engine needs.

type Rule

type Rule struct {
	rules.Rule
}

Rule is the daemon-side representation of a rules.Rule including the parsed Condition + Actions. The Repo loads + saves these.

type RunRecord

type RunRecord struct {
	RuleID       string
	TriggerEvent string
	Fingerprint  string
	Matched      bool
	Actions      []ActionResult
	Simulated    bool
	Duration     time.Duration
}

RecordRun persists one rule_runs row.

type ScanFacts

type ScanFacts struct {
	ID                 string
	Source             string
	Status             string
	Score              int
	Coverage           int
	TotalFindings      int
	ActionableFindings int
	FinishedAt         time.Time
}

ScanFacts is the slice of a Scan the engine needs.

type SimulateOptions

type SimulateOptions struct {
	// RuleIDs (optional) restricts the simulation to a subset of
	// rules. Empty means "every rule with trigger=finding.created".
	RuleIDs []string
	// Window is the (start, end) range to replay. Both inclusive.
	Start time.Time
	End   time.Time
	// Trigger names the synthetic event class. Defaults to
	// finding.created which is the most operator-useful target.
	Trigger rsdk.Trigger
	// Limit caps the number of findings replayed; 0 = no cap.
	// Default 10_000 to keep the rule_runs table bounded.
	Limit int
}

SimulateOptions controls the replay.

type SimulateResult

type SimulateResult struct {
	RuleID             string
	RuleName           string
	FindingsConsidered int
	WouldFire          int
}

SimulateResult is the per-rule summary returned to the UI.

Directories

Path Synopsis
Package actions ships the built-in action library for the v1.9 rules engine.
Package actions ships the built-in action library for the v1.9 rules engine.
Package approvals owns the v1.9 phase 5 multi-approver waiver flow.
Package approvals owns the v1.9 phase 5 multi-approver waiver flow.
Package conditions ships the built-in condition library for the v1.9 rules engine.
Package conditions ships the built-in condition library for the v1.9 rules engine.
Package expiry runs the v1.9 phase 6 waiver-expiry automation loop.
Package expiry runs the v1.9 phase 6 waiver-expiry automation loop.

Jump to

Keyboard shortcuts

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