generated from coulomb/repo-seed
- 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>
120 lines
4.2 KiB
Markdown
120 lines
4.2 KiB
Markdown
---
|
|
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.
|