evalengine

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Mar 19, 2026 License: MIT Imports: 15 Imported by: 1

README

evalengine

A Go evaluation engine that compiles YAML-defined rules into CEL expressions, resolves their dependencies automatically, and executes them against a protobuf message.

Install

go get github.com/laenen-partners/evalengine

Usage

Define evaluations in YAML
evaluations:
  - name: score_eval
    description: Score meets minimum threshold
    expression: "input.score >= 100"
    reads: [input.score]
    writes: score_sufficient
    resolution_workflow: ScoreBoostWorkflow
    resolution: "Boost score to meet threshold"
    severity: blocking
    category: score
    cache_ttl: "10m"

  - name: active_eval
    description: Account is active
    expression: "input.is_active == true"
    reads: [input.is_active]
    writes: is_active
    resolution: "Activate account"
    severity: blocking
    category: status

  - name: eligible_eval
    description: Eligible when score sufficient and active
    expression: "score_sufficient == true && is_active == true"
    reads: [score_sufficient, is_active]
    writes: eligible
    resolution: "Meet all criteria"
    severity: blocking
    category: combined
    cache_ttl: "1m"

Reads prefixed with input. reference fields on the proto message. Bare names reference upstream evaluator outputs — the dependency graph is built automatically.

Create and run the engine
cfg, _ := evalengine.LoadDefinitionsFromFile("evals.yaml")
eng, _ := evalengine.NewEngine(cfg, &mypb.MyMessage{})

input := &mypb.MyMessage{Score: 150, IsActive: true}
results := eng.Run(input)

for _, r := range results {
    fmt.Printf("%s: passed=%v\n", r.Name, r.Passed)
}
Derive status
status := eng.DeriveStatus(results)

switch status {
case evalengine.StatusAllPassed:
    // all evaluations passed
case evalengine.StatusWorkflowActive:
    // a resolution workflow is running
case evalengine.StatusActionRequired:
    // manual action needed
case evalengine.StatusBlocked:
    // upstream dependencies not met
}
Caching

Some input data may be expensive to retrieve. The cache lets you skip re-evaluation when results are still fresh.

// First run — build cache with fingerprints.
results := eng.Run(input)
cache := eng.ToCachedResults(results, input, time.Now())
// Persist cache (Redis, database, etc.)

// Later — reuse cached results when possible.
results, reused := eng.RunWithCache(input, cache, time.Now())
// reused["score_sufficient"] == true means it was served from cache.

Two tiers:

  1. TTL — if the cached result is within its cache_ttl, reuse it directly.
  2. Fingerprint — if TTL expired but the proto input fields haven't changed (SHA256 match), reuse the result anyway. Only applies to evaluators that read exclusively from input.* fields.
YAML config reference
Field Required Description
name yes Evaluator identifier (used in error messages)
description no Human-readable description
expression yes CEL expression, must return bool
reads yes Dependencies: input.* for proto fields, bare names for upstream outputs
writes yes Output name — used as result key, CEL variable, and graph node
resolution_workflow no Workflow ID to trigger on failure
resolution no Human description of how to resolve failure
severity no Severity level (e.g., blocking)
category no Grouping category
cache_ttl no Go duration (e.g., 10m, 1h). Default: no caching
Dependency graph

The graph is built from reads/writes declarations and validated at engine creation:

graph := eng.Graph()
graph.ExecutionOrder()              // topologically sorted evaluator names
graph.DependenciesMet(name, results) // true if all upstream deps passed
graph.BlockedBy(name, results)       // which upstream deps failed
graph.Issues()                       // validation issues (circular deps, missing producers, etc.)

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ComputeFingerprint

func ComputeFingerprint(reads []FieldRef, msg proto.Message) string

ComputeFingerprint hashes the values of the declared input fields from the given proto message. If the hash matches a cached value, the previous evaluation result can be reused.

func ToCachedResults added in v0.2.0

func ToCachedResults(results []Result, evaluatedAt time.Time) map[string]CachedResult

ToCachedResults converts a slice of results into a cache map keyed by name, stamped with the given evaluation time. No fingerprints are computed; use Engine.ToCachedResults for fingerprint-aware caching.

Types

type CELEvaluator

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

CELEvaluator implements Evaluator using a compiled CEL program.

func NewCELEvaluator

func NewCELEvaluator(env *cel.Env, def EvalDefinition) (*CELEvaluator, error)

NewCELEvaluator compiles a CEL expression and returns an evaluator.

func (*CELEvaluator) CacheTTL

func (e *CELEvaluator) CacheTTL() time.Duration

func (*CELEvaluator) Category added in v0.4.0

func (e *CELEvaluator) Category() string

func (*CELEvaluator) DisplayName added in v0.4.0

func (e *CELEvaluator) DisplayName() string

func (*CELEvaluator) Evaluate

func (e *CELEvaluator) Evaluate(activation map[string]any) Result

func (*CELEvaluator) EvaluatePreconditions added in v0.4.0

func (e *CELEvaluator) EvaluatePreconditions(activation map[string]any) []string

EvaluatePreconditions runs all precondition programs against the activation. Returns the descriptions of preconditions that evaluated to false (or errored). If a precondition has no description, the expression is returned instead.

func (*CELEvaluator) FailureMode added in v0.4.0

func (e *CELEvaluator) FailureMode() string

func (*CELEvaluator) HasPreconditions added in v0.4.0

func (e *CELEvaluator) HasPreconditions() bool

func (*CELEvaluator) Name

func (e *CELEvaluator) Name() string

Interface accessors — these expose definition metadata through the Evaluator interface so that execute() never needs to type-assert to *CELEvaluator.

func (*CELEvaluator) Reads

func (e *CELEvaluator) Reads() []FieldRef

func (*CELEvaluator) Resolution added in v0.4.0

func (e *CELEvaluator) Resolution() string

func (*CELEvaluator) ResolutionWorkflow

func (e *CELEvaluator) ResolutionWorkflow() string

func (*CELEvaluator) Severity added in v0.4.0

func (e *CELEvaluator) Severity() string

func (*CELEvaluator) Writes

func (e *CELEvaluator) Writes() FieldRef

type CachedResult added in v0.2.0

type CachedResult struct {
	Result      Result
	EvaluatedAt time.Time
	Fingerprint string // hash of proto input fields; empty if not computed
}

CachedResult wraps a Result with the time it was evaluated and an optional fingerprint of the input fields used to produce it. The caller owns persistence — the engine is stateless.

type Engine

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

Engine loads evaluation definitions, compiles CEL expressions, builds the dependency graph, and runs all evaluators against a proto input.

func NewEngine

func NewEngine(cfg *EvalConfig, input proto.Message, opts ...cel.EnvOption) (*Engine, error)

NewEngine creates an evaluation engine from a config and a proto message that serves as the input type. The proto is registered in the CEL environment as the variable "input" — YAML expressions reference fields as "input.<field>". Extra opts are forwarded to NewCELEnvironment for additional declarations.

func NewEngineFromBytes

func NewEngineFromBytes(data []byte, input proto.Message, opts ...cel.EnvOption) (*Engine, error)

NewEngineFromBytes loads evaluation definitions from raw YAML bytes.

func NewEngineFromFile

func NewEngineFromFile(path string, input proto.Message, opts ...cel.EnvOption) (*Engine, error)

NewEngineFromFile loads evaluation definitions from a YAML file.

func (*Engine) DeriveStatus

func (e *Engine) DeriveStatus(results []Result) Status

DeriveStatus derives the overall status from evaluation results.

func (*Engine) Evaluators

func (e *Engine) Evaluators() []Evaluator

Evaluators returns all registered evaluators.

func (*Engine) Graph

func (e *Engine) Graph() *EvalGraph

Graph returns the dependency graph.

func (*Engine) InputFields added in v0.4.0

func (e *Engine) InputFields(name string) []string

InputFields returns the input field paths referenced in the evaluator's CEL expression, extracted from the compiled AST. Only returns paths rooted at "input." (e.g. "input.score", "input.nested_object.is_active").

func (*Engine) Run

func (e *Engine) Run(input proto.Message) []Result

Run executes all evaluators in dependency order against the given input. The proto is bound to the "input" CEL variable. Upstream evaluator results are injected by their writes-field name for downstream expressions.

func (*Engine) RunMap

func (e *Engine) RunMap(input proto.Message) map[string]Result

RunMap executes all evaluators and returns results indexed by name.

func (*Engine) RunWithCache added in v0.2.0

func (e *Engine) RunWithCache(input proto.Message, cache map[string]CachedResult, now time.Time) ([]Result, map[string]bool)

RunWithCache executes evaluators, reusing cached results that are still within their CacheTTL. The caller owns the cache — the engine is stateless. Pass time.Now() as now; a zero now disables caching (equivalent to Run). Returns the full result set and a map indicating which evaluators were served from cache (true = reused, absent = re-evaluated).

func (*Engine) RunWithCacheMap added in v0.2.0

func (e *Engine) RunWithCacheMap(input proto.Message, cache map[string]CachedResult, now time.Time) (map[string]Result, map[string]bool)

RunWithCacheMap is like RunWithCache but returns results indexed by name.

func (*Engine) ToCachedResults added in v0.2.0

func (e *Engine) ToCachedResults(results []Result, input proto.Message, evaluatedAt time.Time) map[string]CachedResult

ToCachedResults converts results into a cache map with fingerprints computed from the evaluator's input reads and the proto message.

type EvalConfig

type EvalConfig struct {
	Evaluations []EvalDefinition `yaml:"evaluations"`
}

EvalConfig is the top-level YAML structure.

func LoadDefinitions

func LoadDefinitions(r io.Reader) (*EvalConfig, error)

LoadDefinitions parses evaluation definitions from a reader.

func LoadDefinitionsFromFile

func LoadDefinitionsFromFile(path string) (*EvalConfig, error)

LoadDefinitionsFromFile loads evaluation definitions from a YAML file.

type EvalDefinition

type EvalDefinition struct {
	Name               string         `yaml:"name"`
	Description        string         `yaml:"description"`
	Expression         string         `yaml:"expression"`
	Reads              []FieldRef     `yaml:"reads"`
	Writes             FieldRef       `yaml:"writes"`
	ResolutionWorkflow string         `yaml:"resolution_workflow"`
	Resolution         string         `yaml:"resolution"`
	Severity           string         `yaml:"severity"`
	Category           string         `yaml:"category"`
	FailureMode        string         `yaml:"failure_mode"`
	Preconditions      []Precondition `yaml:"preconditions"`
	CacheTTL           string         `yaml:"cache_ttl"`
	CacheTTLDuration   time.Duration  `yaml:"-"`
}

EvalDefinition is a single evaluator loaded from YAML.

type EvalGraph

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

EvalGraph holds the auto-calculated dependency graph derived from evaluator reads/writes declarations.

func BuildGraph

func BuildGraph(evaluators []Evaluator) (*EvalGraph, error)

BuildGraph derives the dependency graph from evaluator declarations. Reads prefixed with "input." are treated as raw proto field references — no producer dependency is created for them.

func (*EvalGraph) BlockedBy

func (g *EvalGraph) BlockedBy(name string, results map[string]Result) []string

BlockedBy returns the names of upstream evaluators that have not passed.

func (*EvalGraph) Blocks added in v0.4.0

func (g *EvalGraph) Blocks(name string) []string

Blocks returns the names of evaluators that directly depend on the given evaluator (reverse dependency lookup).

func (*EvalGraph) DependenciesMet

func (g *EvalGraph) DependenciesMet(name string, results map[string]Result) bool

DependenciesMet returns true if all upstream dependencies of the given evaluator have passed.

func (*EvalGraph) ExecutionOrder

func (g *EvalGraph) ExecutionOrder() []string

ExecutionOrder returns the topologically sorted evaluator names.

func (*EvalGraph) Issues

func (g *EvalGraph) Issues() []Issue

Issues returns all validation issues found during graph construction.

func (*EvalGraph) MaxDepth

func (g *EvalGraph) MaxDepth() int

MaxDepth returns the longest dependency chain length.

type Evaluator

type Evaluator interface {
	Name() string
	DisplayName() string
	Reads() []FieldRef
	Writes() FieldRef
	CacheTTL() time.Duration
	Resolution() string
	ResolutionWorkflow() string
	Severity() string
	Category() string
	FailureMode() string
	HasPreconditions() bool
	EvaluatePreconditions(activation map[string]any) []string
	Evaluate(activation map[string]any) Result
}

Evaluator is the interface for all evaluators.

type FieldRef

type FieldRef string

FieldRef is a dependency reference — either an evaluator output (bare name like "score_sufficient") or an input field path (like "input.email_verified"). Reads prefixed with "input." refer to the proto passed to Engine.Run.

func (FieldRef) String

func (f FieldRef) String() string

String returns the string representation.

type Issue

type Issue struct {
	Type     string // "circular_dependency", "missing_producer", "duplicate_producer", "orphan_output"
	Severity string // "error", "warning", "info"
	Message  string
}

Issue represents a validation issue found in the dependency graph.

type Precondition added in v0.4.0

type Precondition struct {
	Expression  string `yaml:"expression"`
	Description string `yaml:"description"`
}

Precondition is a CEL expression that must evaluate to true before the main expression runs. If it fails, the evaluator is marked Pending rather than Failed.

type Result

type Result struct {
	Name                 string
	DisplayName          string
	Passed               bool
	Pending              bool
	Error                string
	Resolution           string
	ResolutionWorkflow   string
	Severity             string
	Category             string
	FailureMode          string
	PendingPreconditions []string
}

Result represents the outcome of a single evaluator run.

type Status

type Status string

Status represents the logical outcome of evaluating all results.

const (
	StatusAllPassed      Status = "StatusAllPassed"      // every evaluation passed
	StatusWorkflowActive Status = "StatusWorkflowActive" // a resolution workflow is running
	StatusActionRequired Status = "StatusActionRequired" // a failing eval needs manual action
	StatusPending        Status = "StatusPending"        // a failing eval is pending preconditions
	StatusBlocked        Status = "StatusBlocked"        // a failing eval's dependencies aren't met
)

func DeriveStatus

func DeriveStatus(results []Result, graph *EvalGraph) Status

DeriveStatus determines the overall status from evaluation results. Status is derived, never stored directly — it reflects the current state of all evaluations.

Directories

Path Synopsis
proto

Jump to

Keyboard shortcuts

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