TacidOS
Developers
Documentation

Decision 0073

0073. What one machine learned about its body enters through a door of its own

Architecture decision record 0073: 0073. What one machine learned about its body enters through a door of its own

Status
accepted
Date
2026-09-07

Status: accepted. Builds the hardware-quirk half of ADR-0014, which said “hardware quirks discovered on one machine become proposed hardware modules for all” and left the mechanism unstated.

Context

Phase 6 has three sources of proposals, and until today only two of them existed. The agent editing its own mutable crates works, and a person filing a change by hand works; both go through Queue::file, which takes whatever is changed in the working tree, checks every path against the partition, and refuses if any of them is core.

The third source is the one that makes this a species rather than a fleet: a machine that settled on a working configuration for its own hardware offering that configuration to the next machine of the same model. A hardware module belongs in nix/hardware/.

nix/ is core. Deliberately so, and for a reason that gets sharper rather than weaker here: a .nix file is arbitrary Nix, and there is no such thing as a hardware module that is only a hardware module. One that said users.users.root.openssh.authorizedKeys would be an escalation with an entirely plausible name and a filename nobody would look at twice.

So the loop as ADR-0014 described it cannot be built with the door that exists, and the obvious repair — take nix/hardware/ out of core — is the repair that gives the agent a .nix file it can write whatever it likes into.

Decision

The partition is not widened. A second door is cut, with a fixed shape.

Queue::offer_body does not read a working tree at all. It takes a proposal composed by body::propose out of two things the agent does not author:

  • system.identity — what firmware says this machine is, read by tacid-facts from DMI;
  • the tacid.* lines of this machine’s own body/hardware.nix, which are settings the owner and the agent already arrived at through the ordinary transaction path, with every gate that implies.

Everything else about the module — its path, its name, its header, its rationale — is composed by code. There is no argument to the method. The agent cannot influence the content beyond what it could already put in its own genome, in the open, one approval at a time.

The queue then checks the shape it was handed rather than trusting it: exactly one file, under nix/hardware/, ending in .nix, with no directory of its own. A composer is code and code changes; the check is at the door, where it stays true if the composer stops being.

So the exception is not “this directory is writable”. It is this one composer may write this one shape of file, in this one place.

Three narrower rules fall out.

A machine whose firmware will not say what it is offers nothing. qemu, Default string, To Be Filled By O.E.M. and their neighbours are refused by name. A module keyed on one of them would match every machine and fit none, which is worse than no module.

system.stateVersion does not travel. It is a fact about the birth of one machine; carried into a shared module it is wrong everywhere it lands, and wrong in the quiet way that surfaces years later. It is named in the proposal’s rationale instead, so a reviewer sees that it was considered and left behind rather than forgotten.

A body proposal records no base revision. Every other proposal is a diff against the checkout it was written in and means nothing without it. This one creates a file that does not exist yet, so it applies against any revision — and because the identifier is a hash of the content, two machines of one model file the same identifier even when their checkouts stand at different revisions. A maintainer sees one module proposed twice rather than two modules proposed once, and a second machine agreeing is evidence instead of noise.

The verb is tacid propose body, and the method is propose.body. It is governed by self.modify — the same capability as source.propose, and by nothing weaker, because what it files is still a branch and a commit in this machine’s copy of Tacid’s source.

Consequences

  • Asking twice is the same offer. The identifier is a hash of the content, so a machine that has not changed composes an identical proposal every time it is asked, and the queue hands back the one it already holds. Without that word for it the second asking would have failed on git checkout -b — a true sentence about a branch, which a person would read as the offer being refused.
  • Nothing is pushed, here as everywhere in this crate. The module lives on a local branch until an owner signs it, and the owner who signs it is not the owner of the machine that wrote it.
  • nix/hardware/ gains files whose author is a machine. git log says so: the commit carries the agent’s identity, and the owner’s signature is a separate act on a separate line.
  • The composer reads the genome rather than recomposing the body from facts. A setting the owner changed by hand is exactly the kind of thing worth passing on, and a module rebuilt from what the installer would have chosen loses precisely those.
  • A module that is filed is not a module that is right. Nothing here evaluates it on a second machine of the same model, because there is no second machine — that is the hardware lab of ADR-0023 and Phase 7. What this builds is the path from one machine’s experience to a maintainer’s queue; the evidence that it generalises is a person’s job until there is a lab.

Alternatives rejected

Move nix/hardware/ out of core. The repair that fits in one line of partition.rs, and it hands the agent a writable .nix file. Every argument for the partition is an argument against this.

Have the agent write the module into the working tree and file it with Queue::file. The same hole, reached by a longer route: file would have to stop refusing nix/, and then it stops refusing it for every proposal, not only this one.

Compose the module from facts alone, ignoring the genome. Safer-sounding and worse: it drops the settings a person actually tuned, which are the only ones worth another machine’s attention. The genome’s tacid.* lines are already the product of gated changes, so reading them adds no authority the agent did not have.

Let the machine push the branch to a shared repository. See ADR-0014. A species that evolved by whatever the most confident model believed on a Tuesday is not a species with a maintainer.