ADR-0001: Implementation language: Go, single static binary¶
| Status | Accepted |
| Date | 2026-07-21 |
| Deciders | Konrad Heimel |
| Context links | ADR-0002 policy frontends · ADR-0004 plugins |
Context¶
The tool runs inside CI pipelines (GitLab CI, later GitHub Actions): it must start fast,
ship as one artifact, and have no runtime dependencies. It must parse JSON/YAML/HCL, evaluate
Rego, talk to forge APIs, and host third-party plugins. The open question raised during
inception: is Go the right language given the plugin requirement? — Go's native plugin
package is famously weak (Linux-only, exact-toolchain ABI lock).
Options¶
| Option | Pros | Cons |
|---|---|---|
| Go | OPA/Rego engine is a native Go library; wazero (pure-Go WASM runtime) and hashicorp/go-plugin (gRPC subprocess) give two solid out-of-process/sandboxed plugin paths; hashicorp/hcl is the reference HCL parser; first-class GitLab/GitHub clients; single static binary, instant CI startup; k8s-ecosystem contributors feel at home |
in-process dynamic loading is impractical (plugin pkg); generics-era but still verbose |
| Rust | performance, WASM story | Rego evaluation via regorus (less battle-tested than OPA); HCL parsing ecosystem thin; slower contribution ramp for the target audience |
| TypeScript/Node | easy contribution | needs a runtime in every CI image; OPA only via WASM bridge; weak HCL support; slow cold start |
| Python | ubiquitous | same runtime problem; OPA only out-of-process; packaging pain in CI |
Decision¶
Go. The decisive observation: the plugin requirement does not imply in-process dynamic
loading. Extensibility is delivered by (a) policy-as-data — Rego/YAML policies are loaded
at runtime, no rebuild — and (b) out-of-process or sandboxed plugins for permission/fact
providers (see ADR-0004), where Go's ecosystem
(hashicorp/go-plugin, wazero, plain HTTP webhooks) is strong. Meanwhile Go is the only
language where the three hardest dependencies — OPA, HCL, forge clients — are all first-party,
mature, native libraries, and the deployment model (one static binary in a CI job) is exactly
Go's sweet spot.
Consequences¶
- One
CGO_ENABLED=0binary + a small container image; CI integration is a one-liner. - Plugin authors are not forced into Go: WASM and gRPC plugin protocols are language-agnostic.
- We accept that "drop a
.soin a folder"-style plugins will never exist.
Counterpoints considered¶
- "A scripting language would make community contribution easier." — The community extends the tool through policies (Rego/YAML), not through core code; the core being Go does not gate rule authors.
- "Rust for correctness." — The correctness-critical part is the decision engine, which is covered by golden/property tests regardless of language; ecosystem fit wins.