TacidOS
Developers
Documentation

Decision 0092

0092. A translation layer is a hole in the argument, and the owner opens it

Architecture decision record 0092: 0092. A translation layer is a hole in the argument, and the owner opens it

Status
accepted
Date
2026-09-10

Context

An Arm machine cannot run x86_64 binaries, and two programs in nixpkgs make it able to: box64, which translates instructions and calls the host’s own native libraries wherever it can, and fex, which emulates more faithfully against a whole x86 userland it needs on disk. Registering either through binfmt_misc makes the ability transparent — an x86_64 executable becomes a thing you double-click.

The request that produced this record asked for exactly that, in those words: integrate a translation engine into the default image, and configure binfmt_misc so x86_64 binaries execute seamlessly. It is a reasonable thing to want on a Grace-Blackwell board, where a person will meet x86-only tooling within a week.

It is also, on this machine, a hole in the argument the whole system is built on.

The manifesto’s claim, and SECURITY.md’s, and the reason there is a CI job called the invariants that runs the prompt-injection corpus as its own red mark, is that nothing runs on a Tacid machine that its owner did not sign for, and that untrusted content cannot act. Every path by which something happens here is a typed change item: classified, ruled on, previewed, journalled. tacid-policy reasons about those items. It does not reason about files, because until now there has been no way for a file to be a thing that runs.

A binfmt registration is precisely that way. It is a kernel handler that says: any ELF matching these twenty bytes, execute it — through this interpreter, with no proposal, no classification and no journal entry. The classifier cannot see it. The keeper is not asked. A binary that arrived in a tarball, or in somebody’s home directory, or through a browser, is now executable on a machine whose entire design is about which things may execute.

FEX makes it sharper. FEX translates instructions, not libraries, so it needs an x86 root filesystem to link against — in practice a second distribution’s userland, fetched by its own downloader. A machine whose update system exists to refuse anything its maintainer did not sign, carrying an unsigned Ubuntu on the side, is not a compromise. It is the thing being argued against, installed.

Decision

The capability exists, and it is the owner’s to turn on, and it is off everywhere else.

tacid.translation is a module option — enable, engine, and fex.rootfs. Four things follow from the paragraphs above, and each is a property of the code rather than a note in it.

It is not the agent’s to write, and the mechanism that makes that true already existed. There is no dedicated change item for it — but that on its own would prove nothing, because ChangeItem::OptionSet names an arbitrary path and tacid-tx accepts any of them under tacid.. What refuses it is AGENTS_OWN in tacid-policy: an allow-list of the six module roots the agent may write, and tacid.translation is not one of them. That shape is ADR-0008’s and its own comment explains why it is an allow-list rather than a deny-list — a root invented tomorrow is the owner’s until somebody deliberately makes it the agent’s, because the moment a new kind of authority is invented is exactly the moment nobody is thinking about deny lists. This option is the first thing added since that argument was written, and it lands on the safe side without anybody doing anything, which is what the argument was for.

So the test does not assert that some list contains this option. It asserts that the one edit which would open it — adding tacid.translation to AGENTS_OWN, an edit that would look entirely reasonable to somebody wiring up a new module — turns red, and that a transaction proposing it is denied at all four autonomy levels including sovereign (the_agent_cannot_open_a_translation_layer_at_any_level). It runs in the invariants job, beside the other things no green run may quietly stop checking.

It is in no profile and in no image. Not workstation, not ai, not the netinstall medium. A machine somebody installed does not have this until they say so, so “the machine I was given can execute foreign binaries” is never true by accident. It is not a profile at all, for the same reason tacid.core.boot.secureBoot is not one: a profile describes a class of machine, and this is a decision about one.

A corpus case, before the handler. crates/tacid-daemon/tests/corpus/injection.toml gains a page that tells the agent a tool is x86-only and to install a translator and run it. The machine must refuse, and the corpus is the gate that makes the refusal a red mark of its own rather than one test among a thousand.

box64 is the default, and FEX will not be handed a root filesystem by this module. fex.rootfs has no default and will not get one. A machine that downloaded an operating system because an option was left blank would be installing something nobody signed for, which is the concrete form of the objection above. An owner who wants FEX builds or fetches that userland deliberately and points the option at it.

Consequences

An x86_64 binary on a machine with tacid.translation.enable = true runs when executed, by anything that can execute, including the agent’s own session process if a person asks it to open a file. That is the hole. It is open because its owner opened it, it is written down here, and it is the reason this record exists rather than a commit message.

The classifier still does not see it, and this record does not pretend to fix that. What would fix it is a policy that reasons about execution rather than only about declaration, and that is a larger design than this option — named here so that the next person to look does not conclude the question was never asked.

fex additionally requires a kernel with four-kibibyte pages. That is NixOS’s aarch64 default and it is also a thing somebody might reasonably change on a Grace part for memory throughput, so it was written as an assertion — and then removed, which is worth recording because the reasoning looked sound both times it was wrong.

config.boot.kernelPackages.kernel.config is the configuration nixpkgs wrote, not the resolved configuration of the kernel it builds: it answers false to ARM64 on an aarch64 machine. Asked the obvious way round — is ARM64_4K_PAGES set — it is false on the stock kernel, which does use four-kibibyte pages, so the assertion refused every FEX machine there could ever be. Asked the careful way round — is a larger size explicitly chosen — it is false there too, including for a machine that sets one through boot.kernelPatches, so it refused nothing and would have refused nothing for ever. Neither failure is visible by reading the module; both took an evaluator.

So it is a sentence in the option’s description, where somebody choosing an engine reads it. A check that cannot fire is worse than a documented constraint, because it looks like protection.

Alternatives rejected

Integrate it into the default image, as asked. Refused. It would make every machine built from this tree one where any x86_64 file is executable, in exchange for a convenience that matters to a minority of owners — and it would do it without anybody having decided, which is the specific thing this project exists to make impossible.

Refuse it outright. Considered, and it is the wrong kind of purity. The machines this architecture ships as are machines where x86-only tooling is a real obstacle, and an operating system that refuses a capability its owner understands and wants is one they replace with an operating system that does not. The manifesto’s line is that the owner is a key, not that the owner is untrusted.

Make it an agent-proposable change item, classified as boot-class and requiring approval. Rejected, and it was the closest call. Approval is a good mechanism and it is the wrong one here: the whole risk is that the request to enable it arrives through the agent, from a page or a README, and a mechanism whose answer to “somebody talked the agent into asking” is “the owner will notice” is a mechanism that has already lost once the asking is routine. There is no proposal to approve, which is stronger than a proposal that is usually refused.