--- id: ADR-0001 title: "Implementation language for KeyCape: Go" status: accepted date: 2026-03-13 decided_by: Bernd hub_decision_id: 620beb04-fa3f-4a9d-9806-02890a7a2b0d workstream: KEY-WP-0001 (keycape-implementation) alternatives_considered: [Rust] --- # ADR-0001 — Implementation Language: Go ## Status Accepted ## Context KeyCape must be implemented in a language that satisfies the requirements of spec §11: > Keycape SHOULD be implemented in Go or Rust. > Key requirements: stateless, small memory footprint, simple deployment, clear logging, > structured telemetry. Both Go and Rust are valid per spec. A decision was needed before T01 (project setup). ### What KeyCape actually does KeyCape is an **orchestrating boundary service**, not a protocol or security engine: - delegates authentication UI and session to **Authelia** - delegates identity storage to **LLDAP** - delegates MFA enforcement to **privacyIDEA** - its own code is: HTTP endpoints, config loading, JWT signing (via library), JSON/JWKS handling, structured telemetry, adapter glue, migration CLI tooling, LDAP schema validator The main risks are **understandability and clean integration boundaries**, not memory safety in hot loops or complex parser internals. ## Decision **Go.** ## Rationale ### Why Go fits KeyCape Go is especially strong for the actual implementation surface of KeyCape: | What KeyCape does | Go fit | |---|---| | HTTP API server | excellent | | Config loading and static validation | excellent | | Adapter code to Authelia, LLDAP, privacyIDEA | excellent | | JWT/JWKS/JSON handling | excellent | | Structured logging and Prometheus metrics | excellent | | CLI tooling (migration, validator, export) | excellent | | Integration tests in containers | excellent | Go also provides: - faster iteration to a working, testable v1 - simpler dependency and build model - easy static binaries and minimal container images - low enough runtime overhead for the stated lightweight target - straightforward output for coding agents across a growing infra codebase ### Why not Rust for this scope Rust's advantages are real — stronger compile-time safety, better memory control, excellent for security-critical infrastructure — but they pay back most clearly when: - substantial protocol machinery is implemented internally - complex async concurrency or parser-heavy code is required - the service is intended as a long-lived, standalone security product for others KeyCape's design **intentionally avoids** all of that. It stays narrow and delegates to existing components. For a façade/orchestrator, developer friction matters more than theoretical maximal correctness. ### Decision rule > **Pick Go if KeyCape is primarily an orchestrating boundary service.** > **Pick Rust if KeyCape starts becoming a real protocol/security engine.** Based on the v0.1 spec, KeyCape is clearly the first. ## Consequences ### Positive - fastest path to a working, testable, operationally simple implementation - simple build, deploy, and CI story - lower friction for coding agents producing coherent infra code ### Negative / risks - weaker compiler-enforced invariants than Rust - easier to write sloppy code if discipline lapses - error handling and domain modeling can drift if not designed carefully ### Compensating guardrails (mandatory) To recover the rigor that Rust would provide via the type system: 1. **Typed domain models** for the profile contract — no raw maps or untyped JSON in business logic 2. **Narrow adapter interfaces** — `server/` layer never sees LDAP, Authelia, or privacyIDEA types directly 3. **Layered architecture** — protocol layer | domain layer | adapter layer | migration layer (hard boundaries) 4. **Strict schema/config validation** at startup and in CI 5. **Fuzz and property tests** around the LDAP schema validator, redirect URI checker, and claim mapping 6. **No cleverness** — small, deterministic functions ## Revisit trigger Reconsider this decision if a subcomponent (e.g. LDAP schema validator, token normalization engine, or a future high-throughput policy evaluator) demonstrably needs stronger guarantees. That subcomponent could be redesigned in Rust without regretting the overall Go choice — Go and Rust interop via CGo or separate binaries is feasible.