generated from coulomb/repo-seed
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>
This commit is contained in:
119
docs/adr/ADR-0001-choose-go-for-keycape.md
Normal file
119
docs/adr/ADR-0001-choose-go-for-keycape.md
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
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.
|
||||
Reference in New Issue
Block a user