# Architecture Decision Records (ADRs) ADRs are an **append-only ledger** of the significant technology and pattern choices for this platform. Each records the context, the decision, the rejected alternatives, and the requirements it serves. **Do not edit an accepted decision in place** — supersede it with a new ADR. > **ADRs are specifications and are immutable to AI agents** (R-TEST-06). Agents may *draft* a > proposed ADR and escalate it, but only the **human operator/architect** approves/accepts or edits > ADRs. See [`../AGENTS.md` §3 and §11](../AGENTS.md). **These ADRs are not the implementation's handoff log.** The dated files in the [implementation repo](https://gitea.home.keithjackson.dev/Home-Network/home-network-k3s-impl)'s `decisions/` directory record what each change did and how it was verified. ADRs here record locked technology and pattern choices. **Partial supersession.** When a new ADR replaces only part of an accepted one, the older ADR stays `Accepted` and its Status line names the clause superseded (the ADR-0013 precedent). ## Process (read before proposing a change) 1. To change a decision, create a **new** ADR with the next number and set `Status: Proposed`, `Supersedes: ADR-XXXX`. (Agents draft + escalate; the human approves.) 2. State what new information invalidates the old decision. 3. On approval, set the new ADR `Accepted` and the old one `Superseded by: ADR-YYYY`. 4. Never silently re-interpret an accepted ADR (see [`../AGENTS.md` §3](../AGENTS.md)). ## Template ```markdown # ADR-XXXX — - Status: Proposed | Accepted | Superseded by ADR-YYYY - Date: <YYYY-MM-DD> - Serves: <requirement IDs, e.g. R-NET-01, R-NET-06> - Supersedes: <ADR-ZZZZ or —> ## Context <forces, constraints, why a decision is needed> ## Decision <what we chose, stated decisively> ## Alternatives considered <options and why rejected> ## Consequences <positive, negative, follow-ups, risks> ``` ## Index | ADR | Title | Status | Serves | |---|---|---|---| | [0001](adr-0001-kubernetes-distribution-k3s.md) | Kubernetes distribution: k3s | Accepted — dev provisioning clause superseded by 0017 | R-PLAT-01 | | [0002](adr-0002-cni-cilium.md) | CNI & network policy & ingress: Cilium + Gateway API | Accepted | R-NET-01..07, R-DNS-01 | | [0003](adr-0003-storage-longhorn.md) | Distributed storage: Longhorn (+ bulk tier, Velero) | Accepted — backup and bulk-backing clauses superseded by 0018 | R-STATE-01..04 | | [0004](adr-0004-gitops-flux.md) | GitOps engine: Flux CD | Accepted | R-GITOPS-01..03 | | [0005](adr-0005-vm-workloads-kubevirt.md) | VM workloads: KubeVirt + CDI | Accepted | R-PLAT-03 | | [0006](adr-0006-tls-cert-manager-letsencrypt.md) | TLS: cert-manager + Let's Encrypt (DNS-01) | Accepted | R-TLS-01..03 | | [0007](adr-0007-secrets-sops-age.md) | Secrets: SOPS + age | Accepted | R-SEC-01 | | [0008](adr-0008-vpn-egress-model.md) | VPN egress model: Gluetun sidecar + Cilium Egress Gateway | ~~Superseded by 0012~~ | R-NET-05/06 | | [0009](adr-0009-automated-image-updates.md) | Automated updates: Flux image automation + Renovate | ~~Superseded by 0023~~ | R-UPD-01..03 | | [0010](adr-0010-conformance-testing.md) | Conformance testing: automated, reproducible, behavior-based | Accepted | R-TEST-01..06 | | [0011](adr-0011-test-tooling-kyverno-chainsaw.md) | Conformance test tooling: Kyverno + Chainsaw | Accepted | R-TEST-01..05 | | [0012](adr-0012-vpn-egress-single-expressvpn-gateway.md) | VPN egress: single shared in-cluster ExpressVPN gateway | ~~Superseded by 0013~~ | R-NET-05/06/07 | | [0013](adr-0013-vpn-provider-pia-gluetun-wireguard.md) | VPN provider: PIA over OpenVPN-UDP + port forwarding | Accepted — provider/protocol only (client+steering superseded by 0015) | R-NET-05/06/07 | | [0014](adr-0014-idp-keycloak.md) | Identity Provider: Keycloak (SSO via OIDC + forward-auth) | Accepted — forward-auth enforcement point and app list superseded by 0022 | R-IDP-01..06 | | [0015](adr-0015-vpn-egress-steering-node-tunnel-cilium.md) | VPN egress steering: node-level tunnel + Cilium Egress Gateway (host kill-switch) | Accepted — Kill-switch and Resilience bullets superseded by 0020 | R-NET-05/06/07 | | [0016](adr-0016-public-ingress-cloudflare-tunnel.md) | Public ingress: Cloudflare Tunnel (outbound-only) instead of edge port-forwarding | Accepted | R-DNS-01..05, R-TLS-01/02, R-SEC-02/04 | | [0017](adr-0017-single-environment-bare-metal-production.md) | Single environment: bare-metal production; dev retired | Accepted | R-PLAT-01, R-ENV-01/02/03, R-STATE-01/02 | | [0018](adr-0018-storage-backup-truenas-r2.md) | Storage and backup: TrueNAS NFS bulk tier; Velero/kopia + Barman to Cloudflare R2 | Accepted | R-STATE-03, R-STATE-04 | | [0019](adr-0019-postgresql-cloudnativepg.md) | PostgreSQL via CloudNativePG | Accepted | R-IDP-05, R-STATE-01, R-STATE-02, R-STATE-03 | | [0020](adr-0020-vpn-egress-single-gateway-cilium-fail-closed.md) | VPN egress: single gateway node, Cilium-enforced fail-closed | Accepted | R-NET-05, R-NET-06 | | [0021](adr-0021-inbound-remote-access-wireguard.md) | Inbound remote access: WireGuard on a hostNetwork DaemonSet | Accepted | remote operator access (no dedicated requirement ID); bounds R-NET-01, R-SEC-02, R-SEC-04 | | [0022](adr-0022-forward-auth-per-app-oauth2-proxy-sidecars.md) | Forward-auth via per-app oauth2-proxy sidecars | Accepted | R-IDP-02, R-IDP-06 | | [0023](adr-0023-automated-updates-renovate.md) | Automated updates: Renovate for images and charts, every change PR-gated | Accepted | R-UPD-01, R-UPD-02, R-UPD-03, R-TEST-03 | | [0024](adr-0024-delivery-authority-ci-gate.md) | Delivery authority: the CI gate is the approver; sensitive-path review; attributed admin override | Accepted | R-TEST-03, R-TEST-06, R-GITOPS-01, R-SEC-02 | | [0025](adr-0025-in-cluster-ai-program-manager.md) | In-cluster AI Program Manager (pm-agent) | Accepted | R-GITOPS-01, R-TEST-03, R-TEST-06, R-SEC-02 |