Skip to content

Architecture Decision Records

Significant decisions in TAPPaaS are made in writing, before the code: each one is an Architecture Decision Record (ADR) in this directory, and it stays here forever — superseded ADRs are marked, never deleted. If you want to know why the platform is the way it is, this is the trail.

The Status column tracks each decision's lifecycle — Draft → Proposed → Accepted → Superseded (ADR-013 §2); Accepted — implemented means the code is live.

The 2.0 spine — the taxonomy family

ADR Status Decides
ADR-007 — TAPPaaS Taxonomy Accepted — implemented The model everything hangs on: one Site, three classification domains (People · Apps · Environments), Health as a cross-cutting lens. Detailed per domain in sub-ADRs 007a–007e (007e partial), realization (managers/controllers) in 007f.
ADR-009 — Composition Meta-Model Proposed How a deployable unit is built (module = atomic deployable unit; <module>:<service> coordinates) — composition, as distinct from ADR-007's classification.
ADR-007b — Apps Accepted — implemented The Module rib of the taxonomy: what a module's config declares and where it lives.
ADR-007d — Site Accepted — implemented site.json — the Site's own state: nodes, repositories, schedule, default environment.
ADR-007e — Health Accepted Health as a cross-cutting lens, and the site notification target.
ADR-007f — Realization Accepted — implemented Manager → Controller → Service: how the control plane is built, and the Stack-promotion rule.

The workload ontology — ADR-022 family

The vocabulary every other ADR now borrows: what a module is, where it sits, who manages it. GLOSSARY.md at the repository root is the consolidated SSOT for these terms.

ADR Status Decides
ADR-022 — Workload Ontology Accepted The umbrella: why the ontology exists and how the ribs fit together.
ADR-022a — Administrative Domain Accepted The boundary of one administrative authority — what external is measured against. The relationship taxonomy (D6/D7) is parked for 2.1.
ADR-022b — Location Accepted Where something physically is: Site ⊃ Building ⊃ Room ⊃ Rack. Facility removed.
ADR-022c — Node and Host Accepted Node returns to its ArchiMate meaning; adds cluster member and Host; namespaces tier.
ADR-022d — Workload Classification Accepted kind — what type of thing a module is, and why it is a dispatch key.
ADR-022e — Module Scope Accepted module.tier becomes scope: site \| environment. Scope is not stack, not multiplicity, not a layer.
ADR-022f — Kind Values and OS Accepted The kind leaves: vm, lxc, machine (was host), application (was app), device — plus the OS facet.
ADR-022g — Management Accepted management: managed \| unmanaged; external means only "outside the Administrative Domain".
ADR-022h — Facet Register Accepted Which attributes are facets beside the single-valued kind, and the gate a new one must pass.

Platform decisions

ADR Status Decides
ADR-001 — Trunk-mode VLAN connectivity Superseded (not adopted) VMs attach on trunk ports; zones are VLANs.
ADR-002 — Dynamic VLAN configuration Superseded Zone/VLAN wiring happens at deploy time, driven by module config.
ADR-003 — Dependency management Accepted — implemented Modules declare dependsOn; install order is derived, never hardcoded.
ADR-004 — Module catalog & config cascade Accepted — L2 live; L1/L3 superseded by ADR-007 Where module configuration comes from and how overrides cascade.
ADR-005 — Variant domain architecture Superseded → ADR-007c Per-client variants of the platform (variants → environments).
ADR-006 — Identity: users and roles Accepted — SSO live; people model → ADR-007a The identity model behind SSO (Authentik) — users, groups, roles.
ADR-008 — Switch module / network infrastructure Partially implemented (as network-manager) Physical switches and APs become managed parts of the platform.
ADR-010 — VPS satellite Accepted — implemented (Debian variant) The optional off-premises satellite: public ingress, off-site backup, admin VPN.
ADR-012 — Backup enhancement Accepted (v1.0) — v1.0 additions not built yet Where PBS lives (install-resolved placement, or an external PBS consumed by URL), what a module asks to have backed up (backup:vm / backup:filesystem), and how often (site → environment → module cascade).
ADR-014 — Zone and Environment Lifecycle Accepted — implemented Creating, binding, enabling/disabling and retiring zones and environments through managers, not hand-edited zones.json.
ADR-016 — Source NAT for subnet-filtering devices Proposed Masquerade into a zone for IoT appliances that only accept sessions from their own subnet: zone-owned snat-allowed-from gate, module-local snat.json, network-manager snat verbs.
ADR-017 — Update scheduling and mothership self-update Accepted When the sweep runs and how the mothership updates itself (systemd ExecStartPre=+, schedule from site.json).
ADR-018 — SSH Identity Resolution Under Sudo Superseded in part Why the manager estate runs as tappaas, not root; the per-call-site -i sweep is superseded by the #533 ownership guard.
ADR-019 — HA and Cross-Node VM Migration Policy Proposed The full migrate-vm.sh matrix — HA/non-HA, live-vs-offline by CPU compatibility (--force for downtime), strict/comment round-trip, when module.json.node is rewritten.
ADR-020 — Declared-Field Change Model Accepted — implemented Unifies validate · drift · modify behind one desired-state resolver, one differ and one change-class taxonomy (immutable / in-place / grow-only / migrate / …); each <provider>:<service> declares how to change — or refuse — the fields it owns, in services/<svc>/fields.json. modify --set field=value is the sanctioned path (#498/#557), with a static pre-gate for what cannot change in place and a disruption gate (--force / rebootOk) decoupled from update-tappaas --force. network-manager modify <zone> --set is the second manager (#538). ADR-019 plugs into the update-node.sh hook. How it is built: ADR-020 realization.
ADR-021 — Split-Horizon DNS and Service Reachability Accepted — implemented The internal answer for a published name is always the DMZ gateway, for every zone: DNS says "go to Caddy", Caddy + Authentik decide who gets in. Authorization moves out of the address (which caused the drift) and into identity. One resolver for all three writers (network-manager split-horizon-target, D5), ending the drift where three code paths resolved the answer from different zones (#577). Reaching Caddy is a host-scoped firewall rule — the DMZ gateway /32 on tcp/80+443 (D3) — and no zone may hold dmz in access-to (D3b, invariant I5): that grant reached the whole DMZ subnet including the firewall's own GUI and SSH (#618). A wildcard cert no longer implies a wildcard record (D4), and a name with no public DNS degrades cleanly instead of failing (R3).
ADR-023 — Reverse Proxy Access Rules Proposed What Caddy lets through once a caller reaches it. Every route — the primary and each proxyRoutes entry — has its own default proxyAllowedZones plus optional proxyAccess path exceptions, so a module can publish one webhook path without publishing the whole app (#642, #643 merged). Unlisted paths fall back to the route's own access list; none denies all. Access lists sit on every handle, never on the domain, because Caddy tries path handles first.
ADR-025 — Config migrations and the upgrade path Accepted How a release brings a site's config/ forward, and what a release may assume about where a site is upgrading from. Ordered, idempotent migrations/NNNN-<slug>.sh run by tappaas-self-prepare.sh — before the rebuild and before any module, which pre-update.sh cannot be (D2) — each with --check, a backup under config/.migrations/backup/NNNN/ and a ledger entry in config/.migrations/applied (#545 backs both up). A failed migration stops the run before anything is updated and is notified through #651. The review rule: a change that renames or re-schemas anything under config/ ships with its migration and a before→after fixture test in the fast tier. No down-migrations — rollback is the backup. Every release names the oldest upgrade source it supports (D10), which is how interim code finally gets deleted. The runner's own release carries none; ADR-017 D7's updateSchedule object is the first (D12).
ADR-024 — Site Fabric Draft — placeholder Several Administrative Domains cooperating — the backup-buddy case generalised. No content decided.
ADR-026 — Managed Machines as Modules Proposed Every machine TAPPaaS manages is a module of kind: machine — cluster nodes, the satellite, and a Debian host carrying a PBS. Adds debianhost for the OS lifecycle.
ADR-027 — Module Blueprint Proposed What every module is made of, apart from its documents: the executable artifact set with MUST/SHOULD/MAY on each, the six files of a services/<service>/ directory, all of it co-located in the module's directory, and one blueprint check for every module plus a sweep to make the estate meet it. Documents are ADR-013's, contribution files ADR-015's. 00-Template is the blueprint in files, and its stubs warn that they are stubs.
ADR-028 — Release Cadence and Patching Accepted When TAPPaaS releases and how patches reach a site. The two OS families work by opposite principles: a Debian machine is changed in place and patches itself every sweep (three apt paths — guest, managed machine, locked-down vault), while a NixOS machine is built, not upgraded — it holds no flake and no channel of its own, update-os.sh forces one revision onto it, and rebuilding against the same revision changes nothing, so NixOS patches arrive only when a human moves a lock. Decides: one estate pin (templates/flake.lock, the mothership's flake following it, keeping its own build path for upgrade safety); an unstable → staging → production train on two-week boundaries (the branches main → staging → stable), putting production at most four weeks behind, where a CVE accelerates the train rather than cherry-picking a pin onto code it was never built against; a guest proves each revision before the mothership takes it; the ref stays a Hydra-built nixos-* channel so guests substitute instead of compiling; and the #680 pin-age tripwire tightens to 45 days, which detects branch EOL for free. Opens: the control plane can still not roll itself back (D10), and the satellite's dead --os nixos remains should go (#712). Includes a primer for readers new to Nix.

Governance

ADR Status Decides
ADR-011 — SBOM Governance Proposed What is running on an installation and which of it needs a fix: generated per installation, one scanner per layer (Nix closure, image, Debian host, OPNsense), coverage-checked, triage committed as VEX.
ADR-013 — Documentation Structure and Standards Accepted — implemented Where documentation lives, which artifact serves which audience, and how the site syncs from source.
ADR-015 — Community Governance and Contribution Files Draft The community-health file set (CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, GOVERNANCE, CODEOWNERS, templates) — names, per-repo placement, and contents, plus the per-module AUTHORS.md. The companion to ADR-027, which defers every contribution file here.

Writing a new ADR? Decide in writing first, before the code; the process and standards are in ADR-013 — Documentation Structure and Standards.