007 Opa SDK In Process
Status: Accepted
Date: 2026-06-01
Context:
ComplyPack needs to validate, test, and evaluate OPA Rego policies. There are two architectural approaches:
- In-process via OPA Go SDK (
github.com/open-policy-agent/opa) - Subprocess via
opaCLI (shell out toopa test,opa eval, etc.)
Subprocess Approach:
func Validate(filename, src string) []error {
cmd := exec.Command("opa", "check", filename)
output, err := cmd.CombinedOutput()
// Parse stdout/stderr for errors
}In-Process Approach:
func Validate(filename, src string) []error {
mod, err := ast.ParseModuleWithOpts(filename, src, ...)
compiler := ast.NewCompiler()
compiler.Compile(map[string]*ast.Module{filename: mod})
return compiler.Errors
}Decision:
We use the OPA Go SDK in-process for all policy operations:
- Validation:
ast.ParseModuleWithOpts+ast.NewCompiler - Testing:
tester.NewRunner().RunTests() - Evaluation:
rego.New().PrepareForEval() - AST inspection:
ast.WalkRefsfor contract validation
Only exception: Regal linting (optional, best-effort subprocess call).
Rationale:
| Criterion | In-Process SDK | Subprocess CLI |
|---|---|---|
| Performance | ✅ No process spawn overhead | ❌ ~50-100ms per invocation |
| Error handling | ✅ Typed errors, structured data | ❌ Parse stdout/stderr strings |
| Determinism | ✅ Same Go version = same behavior | ❌ Depends on opa in $PATH |
| Testability | ✅ Unit tests, no mocks needed | ❌ Requires opa binary in CI |
| AST access | ✅ Direct AST for contract validation | ❌ Would need opa parse --format json |
| Cancellation | ✅ context.Context propagation | ❌ Manual cmd.Cancel() handling |
| Memory control | ✅ Share memory pool | ❌ Each process allocates separately |
| Versioning | ✅ go.mod locks OPA version | ❌ Runtime dependency on system opa |
Key Benefits:
- Fast feedback loops: Validation in <10ms vs 50-100ms subprocess overhead
- Structured errors:
compiler.Errorsis[]ast.Errorwith location/code, not string parsing - AST introspection: Contract validation needs
ast.WalkRefsto findinput.*references (not exposed via CLI) - Single binary: No external dependencies -
complypackbinary is self-contained - Reproducibility:
go.modlocksgithub.com/open-policy-agent/opa v1.16.2- same behavior across environments
Implementation:
internal/validator/rego.go: Usesast.ParseModuleWithOpts,ast.NewCompilerinternal/validator/contract.go: Usesast.WalkRefsto walk policy ASTinternal/tester/runner.go: Usestester.NewRunner().RunTests()internal/tester/fixture.go: Usesrego.New().PrepareForEval()for policy evaluationinternal/evaluator/opa.go: Wires everything together via Evaluator interface
Exception: Regal Linting
Regal ( https://github.com/StyraInc/regal) is OPA’s official linter, but:
- No stable Go API (CLI-only)
- Frequent releases (pinning version is hard)
- Optional quality tool (not blocking)
We shell out to regal with graceful degradation:
func Lint(filename, src string) ([]LintWarning, error) {
if _, err := exec.LookPath("regal"); err != nil {
return nil, nil // Not installed, silently skip
}
// Run regal lint --format json
}This allows users with regal installed to get style warnings without forcing it as a dependency.
Consequences:
Benefits:
- Speed: 5-10x faster validation (no subprocess overhead)
- Reliability: No dependency on external binaries
- Developer experience: Simpler debugging (in-process stack traces)
- CI/CD simplicity: Just
go test, noopainstallation step
Drawbacks:
- OPA version coupling: If OPA SDK introduces breaking changes, we must update code (mitigated by
go.modversion pinning) - Binary size: Includes OPA SDK (~10-15MB) in
complypackbinary (acceptable for a compliance tool) - No Regal in-process: Must shell out for linting (acceptable for optional tooling)
Future Considerations:
- WebAssembly policies: If we add Wasm evaluation, OPA SDK already supports it (
opa.WithWasmRuntime) - Remote bundles: OPA SDK supports bundle downloading/caching natively
- Custom built-ins: Could register Go functions as Rego built-ins via SDK
Alternatives Considered:
Hybrid approach (SDK for validation, subprocess for testing):
- Rejected: Adds complexity, forces CLI dependency anyway
Always subprocess (like
conftestwrapper):- Rejected: Contract validation needs AST access (CLI doesn’t expose it)
Embed
opabinary (package CLI in our binary):- Rejected: Worse than SDK (still subprocess overhead + binary bloat)
Related:
- ADR 005: Evaluator Interface Pattern
- ADR 006: CUE Schema Contract Validation
go.mod: OPA SDK version pinned atv1.16.2