TacidOS
DevelopersGitHub

Decision 0036

The root is a snapshot restored at boot

Architecture decision record 0036: The root is a snapshot restored at boot

Status
accepted
Date
2026-09-05

Context

ADR-0012 chose “impermanence by default: an ephemeral root, a declared /persist; the machine is a function of (genome, persist); no hidden state.” What the tree had, until now, was the second half of that: /persist and the bind mounts that carry the genome, the state, the log and the uid map across a boot. The first half — a root that is actually thrown away — was a sentence. The root subvolume kept everything written to it, which is the state of every machine that is not Tacid, and the manifesto’s claim that a machine has no hidden state was a claim about intent.

Decision

The installer keeps a read-only snapshot of the root subvolume while it is still empty, and the initrd makes the root again from it at every boot.

  • The stem’s disk sequence takes @root-blank, a read-only snapshot of @root, right after the subvolumes are created and before anything is written into any of them. It is one more step in the same typed sequence, in the order the test holds it to.
  • body/disks.nix declares tacid.core.root = { device; ephemeral = true; }: the opened LUKS volume, or the partition when the root is not encrypted. The declaration is the body’s; the mechanism is the module’s.
  • The module gives the initrd a service that runs after the root device appears and before sysroot is mounted: it mounts the top of the filesystem on its own, deletes @root and whatever is nested in it, makes a new @root from the blank, and unmounts. On a machine without the declaration nothing is added.
  • What must survive a boot is declared, so it is: the genome and Tacid’s state, the log, the uid map and the machine id as before, bound in from /persist; and people’s homes, which are now a subvolume of their own, @home, mounted from the initrd. The first boot of the change had bound /home in from /persist instead, and found the home that activation had just made hidden underneath the mount: a bind mount that arrives after activation arrives too late for anything activation creates. What a service keeps is the service’s module’s to declare, as ADR-0008 asks — the openssh module already keeps the host keys, and the first evaluation of this change found that out by declaring them twice.

Consequences

  • The installation test writes a file to the root and one to /persist, and one in the owner’s home, reboots the installed machine through its passphrase prompt, and finds the first gone and the others where they were, with the genome’s history and the hostname intact. That is the sentence, tested.
  • /root is not persisted. The root user is not a person on a Tacid machine; the owner has a home, and it is kept. Anything a service keeps under /var/lib that is not Tacid’s own is ephemeral until the service’s module declares otherwise, which is what ADR-0008 asks of every tacid.services.* module and what the openssh module should be held to next.
  • A root that is fresh at every boot is a root on which nothing can be quietly installed by hand and survive. That is the point, and it is also a surprise the first time; the agent detecting writes to ephemeral paths and offering to declare them, from ADR-0012, is the companion piece and is still to be written.
  • The unit runs btrfs subvolume delete --recursive, which needs btrfs-progs 6.5 or later; the release this tree pins has 7.1.

Alternatives rejected

  • A tmpfs root. Simple, and it makes the root’s size a function of memory, which on a machine that is also running a model is the wrong thing to spend memory on.
  • Deleting the root’s contents from stage 2, after mounting. Racing every unit that starts at boot for the files they are about to write. The initrd is the one place where nothing else is running yet.
  • Rolling back to the snapshot only when asked. A root that is ephemeral on request is a root that keeps state, and the promise is that it does not.