Files
key-cape/docs/adr/ADR-0001-choose-go-for-keycape.md
tegwick 329e996619 feat: implement T01-T04 — Go module, canonical model, LDAP validator, error taxonomy
- T01: Go module (keycape), full directory skeleton, Makefile, CI workflow
- T02: spec/canonical-model.yaml with 6 entities + Go domain types
- T03: spec/ldap-schema.yaml + validator binary with structural/semantic rules
- T04: Error taxonomy — 4 stable error types, JSON format, HTTP helpers

28 tests pass, go vet clean, go build clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 01:27:54 +01:00

4.2 KiB

id, title, status, date, decided_by, hub_decision_id, workstream, alternatives_considered
id title status date decided_by hub_decision_id workstream alternatives_considered
ADR-0001 Implementation language for KeyCape: Go accepted 2026-03-13 Bernd 620beb04-fa3f-4a9d-9806-02890a7a2b0d KEY-WP-0001 (keycape-implementation)
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 interfacesserver/ 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.