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.