# 00 — Vision & Scope ## Vision Build a home platform that behaves like a small private cloud. An operator declares a service — a container app *or* a virtual machine — in git, and the platform provisions it, secures it, exposes it under a trusted TLS URL, routes its traffic on the correct WAN, protects its data, updates it automatically, and reports on its health. Onboarding a new service is a pull request against a catalog, not a weekend of manual work. The platform runs as one live environment: **prod**, on 3 bare-metal mini-PCs (ADR-0017). The dev/test cluster that proved its viability was decommissioned in 2026-09; every change reaches prod through the PR gate and Flux. ## Personas - **Operator (primary user)**: runs the home network; wants to onboard/offboard services with minimal ceremony and zero data loss. - **Platform (the system)**: reconciles declared state, enforces isolation and egress policy, renews certificates, updates workloads, and raises alerts. - **Implementation agents**: the AI teams building the platform from this spec. ## In scope - A k3s-based platform with distributed storage (Longhorn) across 3 nodes. - Container workloads (PaaS) and VM workloads (IaaS) through one Kubernetes surface. - GitOps delivery, automated updates, trusted TLS, DNS, ingress. - Network isolation (default-deny east-west) and declared egress classes, including VPN routing. - Observability with email + dashboard alerting. - A command-and-control web UI at the root of `home.keithjackson.dev`. - The service catalog (see [`13-service-catalog.md`](13-service-catalog.md)). ## Out of scope (for now) - Multi-tenant / multi-operator RBAC beyond a single operator + platform automation. - Public multi-user SaaS concerns (billing, per-tenant isolation beyond network policy). - Non-`keithjackson.dev` domains. ## The domain & DNS model - Public apex `*.keithjackson.dev` is available in Cloudflare DNS. `public` services (catalog `exposure: public` or a registered platform route) under `*.home.keithjackson.dev` are published through a **Cloudflare Tunnel** (proxied records, no inbound 80/443 at the edge — ADR-0016); the network's public IP stays in use for WireGuard and Plex only. - All platform services live under `*.home.keithjackson.dev`. - Each service is reachable at `.home.keithjackson.dev` (e.g. `plex.home.keithjackson.dev`). - The **root** `home.keithjackson.dev` serves the **Command & Control (C2) center** UI. ## Requirements (stable IDs) Requirement IDs are stable references used across ADRs, specs, the task board, and PRs. `MUST` is mandatory; `SHOULD` is strongly preferred and requires an ADR to deviate. ### Platform & API surface — `R-PLAT-*` - **R-PLAT-01** — The platform MUST run on k3s across 3 nodes with production-grade HA control plane in prod. - **R-PLAT-02** — Container workloads MUST be onboarded and managed declaratively via the Kubernetes API, `kubectl`-class CLI, and YAML. - **R-PLAT-03** — Virtual-machine workloads MUST be created, managed, and destroyed through the *same* Kubernetes API/CLI/YAML surface as containers (no separate hypervisor management plane for application VMs). - **R-PLAT-04** — The platform MUST support create / destroy / rebuild of any workload on demand. ### GitOps & delivery — `R-GITOPS-*` - **R-GITOPS-01** — All application and platform desired state MUST be defined in git and reconciled automatically (pull-based GitOps). - **R-GITOPS-02** — Onboarding a new service MUST be expressible as a change to a versioned catalog in git. - **R-GITOPS-03** — The system MUST detect and correct drift between git and cluster state. ### Networking, isolation & egress — `R-NET-*` - **R-NET-01** — East-west traffic between services MUST be **denied by default**; communication is allowed only by explicit, port-level configuration (firewall-like). - **R-NET-02** — Allow rules MUST be declarative, reviewable in git, and scoped per source → destination → port/protocol. - **R-NET-03** — Networks MUST be configurable and isolatable (segments/namespaces with enforced boundaries). - **R-NET-04** — The platform MUST support **multiple egress paths** selected by routing rules. Per OQ-1 the environment has **one physical commercial WAN and no multi-ISP plans**, so this is realized as two paths — the local `internet` path and the in-cluster `vpn` path — not multi-ISP routing. Physical multi-WAN (multiple ISP uplinks) is **out of scope** unless a future ADR reopens it. - **R-NET-05** — A service MUST be able to declare its **egress class**: `internet` (standard local network/internet) or `vpn` (routed through a custom VPN connection). VPN routing MUST be enforced by a single shared node-level gateway outside the service (ADR-0015, ADR-0020), so a service cannot leak by misconfiguration (OQ-3). - **R-NET-06** — VPN-class egress MUST enforce a **kill-switch**: if the VPN tunnel is down, the service MUST NOT leak traffic to the standard internet. - **R-NET-07** — Egress class MUST be a declarative property of the service, changeable via GitOps without re-architecting the workload. ### Storage & state — `R-STATE-*` - **R-STATE-01** — Stateful data MUST survive destroy/rebuild of the workload (PaaS and IaaS). - **R-STATE-02** — Persistent storage MUST be distributed/replicated across the 3 nodes (Longhorn) so a single node loss does not lose data. - **R-STATE-03** — The platform MUST provide scheduled backups to an **off-cluster** target and a tested restore path (disaster recovery). - **R-STATE-04** — Bulk media storage (Plex library) MUST have a capacity strategy distinct from replicated app-config storage (replicating multi-TB media 3× is out of scope; see ADR-0003). ### Automated updates — `R-UPD-*` - **R-UPD-01** — Services MUST update to the latest **stable** versions on a schedule. - **R-UPD-02** — Updates in the allowed classes MUST occur with **no human, no AI, and no hand-authored GitOps change**; automation acting on declarative policy opens machine-authored changes through the merge gate. Major versions and operator-designated blast-radius components MAY wait for human review of the machine-authored change. - **R-UPD-03** — Update policy MUST be per-service configurable (cadence, stability channel, pin/hold, auto-rollback on failed health). ### TLS & certificates — `R-TLS-*` - **R-TLS-01** — Every externally reachable service MUST present TLS from a **credible, trusted, non-self-signed** authority. - **R-TLS-02** — Certificates MUST be automatically **issued and renewed** without manual steps. - **R-TLS-03** — Certificate issuance MUST work for internal-only services (no requirement to expose a service publicly just to validate a certificate). ### DNS & ingress — `R-DNS-*` - **R-DNS-01** — Services MUST be reachable at `.home.keithjackson.dev`. - **R-DNS-02** — DNS records MUST be managed automatically from declared service intent. - **R-DNS-03** — Internal clients MUST resolve service names correctly (split-horizon where public and internal answers differ). - **R-DNS-04** — `public` HTTP(S) services MUST be reachable from the internet **without inbound 80/443 port-forwarding at the edge**, and LAN clients MUST reach them correctly **whichever resolver answers** (public or internal) — no dependency on NAT hairpin (ADR-0016). - **R-DNS-05** — Only services declared public (catalog `exposure: public` or a registered platform route) MAY be reachable through the public ingress path. A request for a LAN/VPN-only hostname at that path MUST be refused (HTTP 404). `internal` services MUST have no public record, except non-HTTP L4 names, which MAY hold DNS-only records at LAN addresses (ADR-0016). ### Observability & alerting — `R-OBS-*` - **R-OBS-01** — Services MUST be monitored (metrics, logs, health). - **R-OBS-02** — Issues MUST be reported by **email**. - **R-OBS-03** — Issues and health MUST be visible on **dashboards**. - **R-OBS-04** — The platform MUST alert on its own health (nodes, storage, GitOps reconciliation, certificate expiry, VPN tunnel state, backup success). ### Command & control — `R-C2-*` - **R-C2-01** — The root `home.keithjackson.dev` MUST serve a UI that surfaces the services running in the cluster and links to them. - **R-C2-02** — The C2 UI MUST surface platform health (cluster, storage, GitOps, alerts) and provide controlled operational actions where safe. ### Security & secrets — `R-SEC-*` - **R-SEC-01** — Secrets MUST NOT be stored in plaintext in git; they MUST be encrypted at rest in the repo and decrypted only in-cluster. - **R-SEC-02** — The platform MUST follow least-privilege for workload identities and network reach. - **R-SEC-03** — All the above properties MUST be demonstrable/auditable (an operator can prove isolation, egress path, cert validity, and backup integrity). - **R-SEC-04** — The Kubernetes API MUST NOT be reachable from the public internet by any path (no tunnel route, no edge forward, no public DNS record); it is LAN/VPN-only (ADR-0016). ### Identity & access (SSO) — `R-IDP-*` - **R-IDP-01** — The platform MUST provide a central Identity Provider (SSO) as the single user directory and login for platform services. - **R-IDP-02** — Services MUST authenticate against the IdP — natively via OIDC where supported, or behind an identity-aware (forward-auth) proxy otherwise. No management UI may be exposed unauthenticated on the network. (Documented exceptions: Plex/Seerr use Plex auth; Minecraft uses Mojang/Microsoft; UniFi uses its native login on its own L4 VIP; the read-only runbooks at `/runbooks` carry no login; Crafty web uses forward-auth plus its own local login, no OIDC.) - **R-IDP-03** — Identity configuration (realms, clients, groups, roles) MUST be declarative and GitOps-managed, not console-only. - **R-IDP-04** — IdP secrets (admin credential, OIDC client secrets) MUST be SOPS-encrypted; MFA MUST be available to all users and required for privileged access. - **R-IDP-05** — The IdP MUST be treated as DR-critical (replicated + off-cluster-backed datastore) with a documented **break-glass** path so an IdP outage does not lock the operator out of recovery tools. - **R-IDP-06** — Access MUST be least-privilege via groups/roles; sensitive surfaces (C2, cluster console, alerting) require an authenticated user in an authorized group, not merely "logged in." ### Environments — `R-ENV-*` - **R-ENV-01** — Nodes MUST be baselined, cluster-joined and managed as code; machine creation on bare metal is an imaged, checklist-driven step. - **R-ENV-02** — GitOps configuration MUST be authored once as a base plus environment-scoped overlay; any future environment MUST need overrides only. - **R-ENV-03** — Production runs on 3× mini-PC bare metal. Changes MUST reach it only through the PR gate and Flux. ### Conformance testing & governance — `R-TEST-*` - **R-TEST-01** — Every requirement MUST be verified by **automated, behavior-based tests** (Given/When/Then style) that assert observable system behavior, not implementation internals. - **R-TEST-02** — Tests MUST be **reproducible**: deterministic, hermetic (create and tear down their own fixtures), version-pinned, and runnable by a single command locally and in CI against any conforming cluster, producing machine-readable, requirement-tagged results. - **R-TEST-03** — A **conformance suite** MUST gate delivery: merges are blocked unless the tests for the touched requirements pass, and phase exits are blocked unless the phase's full suite passes. "Demonstrated" means "the automated test passes," never a manual one-off. Fault-injection tests (R-TEST-05) are not automated: the operator runs them deliberately and records the result, and they do not gate individual merges (ADR-0026). - **R-TEST-04** — Tests MUST be **traceable**: each test declares the requirement ID(s) it verifies, and a coverage report maps every `R-*` requirement to its verifying test(s). Uncovered requirements are a gap to close, not an acceptable state. - **R-TEST-05** — Safety-critical behaviors (default-deny, VPN kill-switch, data-survival on rebuild, DR restore, auto-rollback) MUST be verified by **fault-injection** behavior tests that prove fail-closed / no-data-loss outcomes, not merely happy-path checks. - **R-TEST-06 (governance)** — **AI agents MUST NOT modify the specifications** (this `docs/` tree, the requirements, or accepted ADRs). Specs are changed only by the human operator/architect. Agents that find a spec wrong, blocking, ambiguous, or missing MUST **stop and escalate** and MAY draft a proposal, but MUST NOT self-approve or merge a spec/ADR change (see [`AGENTS.md`](../AGENTS.md) §2 and §11). ## Acceptance philosophy Each requirement class maps to an **automated, reproducible, behavior-based conformance test** (`R-TEST-*`). "Done" means *the conformance test for the touched requirements passes* — a green, reproducible result traced to requirement IDs — never "manifest merged" and never a manual demonstration. The testing approach is specified in [`docs/14-testing-and-conformance.md`](14-testing-and-conformance.md) and [ADR-0010](../decisions/adr-0010-conformance-testing.md); the gate checklist lives in [`agents/definition-of-done.md`](../agents/definition-of-done.md). **Governance:** the specifications are human-owned and **immutable to agents** (R-TEST-06). Agents implement against the spec and prove conformance with tests; they never edit the spec to fit the implementation. ## Open questions ### Resolved - **OQ-1 (WAN / edge)** — **RESOLVED**: one commercial WAN, no multi-ISP plans; dumb SOHO edge (no VLAN/VPN/services). All segmentation + VPN in-cluster; edge does NAT + port-forward only. Physical multi-WAN out of scope. See [`03`](03-networking-isolation-and-egress.md), [`09`](09-dns-and-ingress.md). **Amended 2026-11-08 (ADR-0016):** public web ingress moved to an outbound-only **Cloudflare Tunnel**; the edge no longer forwards 80/443. Its remaining forwards are WireGuard udp/51820 and Plex tcp/32400. - **OQ-2 (bulk media)** — **RESOLVED (ADR-0018)**: Plex bulk media lives **outside the cluster** on a standalone TrueNAS NFS server on the production LAN, and sits outside platform backup. Plex's metadata DB still lives on replicated Longhorn. See [`04`](04-storage-and-state.md). - **OQ-3 (VPN)** — **RESOLVED**: **PIA over OpenVPN-UDP** (non-TCP; ADR-0013). Egress is a **node-level host tunnel steered by Cilium Egress Gateway**, failing closed under Cilium's own enforcement (ADR-0015, ADR-0020) — `vpn`-class pods routed through it, rules enforced outside the service. See [`03`](03-networking-isolation-and-egress.md). - **OQ-4 (TLS/DNS challenge access)** — **RESOLVED**: a **scoped Cloudflare API token** (`Zone·DNS·Edit`, `keithjackson.dev`), SOPS-encrypted, drives cert-manager DNS-01 and ExternalDNS. The static `*.keithjackson.dev` wildcard covers reachability but not TXT-challenge writes; the token handles issuance + record automation. Operator provides the token once. See [`08`](08-tls-and-certificates.md). - **OQ-BKP (off-cluster DR backup target)** — **RESOLVED (ADR-0018)**: Cloudflare R2 stores Velero backups (kopia file-system volume data) and CNPG Barman archives of the irreplaceable tier; Longhorn has no backup target of its own. See [`04`](04-storage-and-state.md). ### Still open None tracked here. Unmet requirements are recorded in [known gaps](16-known-gaps.md).