Title here
Summary here
Status: Accepted
Date: 2026-06-01
Context:
ComplyPack needs to validate, test, and package policies across multiple policy languages (OPA Rego, CEL, Kyverno, etc.). Hard-coding OPA-specific logic throughout the codebase would:
We need a policy-language-agnostic design that allows ComplyPack to treat policy content as opaque bytes while delegating language-specific operations to pluggable implementations.
Decision:
We adopt the Evaluator Interface Pattern with a thread-safe registry:
type Evaluator interface {
ID() string // Unique identifier (e.g., "io.complytime.opa")
Validate(filename, src string) []error
CheckContract(filename, src string, schema cue.Value) ([]ContractViolation, error)
Test(ctx context.Context, files map[string]string) (*TestResults, error)
Lint(filename, src string) ([]LintWarning, error)
FileExtension() string // Expected file extension (e.g., ".rego")
}Registry Pattern:
type Registry struct {
mu sync.RWMutex
evaluators map[string]Evaluator
}
func NewRegistry() *Registry
func (r *Registry) Register(e Evaluator)
func (r *Registry) Get(id string) (Evaluator, error)
func (r *Registry) IDs() []stringKey Design Decisions:
evaluator-id in config/metadata, allowing runtime selectionLint() returns nil, nil if linter unavailable (optional tooling)Test() accepts context.Context for cancellation/timeoutCheckContract uses cue.Value to validate input references against any schema formatImplementation:
internal/evaluator/evaluator.go: Interface definitions and shared typesinternal/evaluator/registry.go: Thread-safe registry implementationinternal/evaluator/opa.go: OPA implementation (ID() = "io.complytime.opa")DefaultRegistry(): Pre-registers OPA evaluator for convenienceConsequences:
Benefits:
Drawbacks:
Lint)Future Considerations:
.so/.dylib) for out-of-tree languagesPrepareForEval() method for query compilation cachingRelated: