Decision 0046
A project is what its files say, and Tacid writes nothing into it
Architecture decision record 0046: A project is what its files say, and Tacid writes nothing into it
Context
The plan’s developer workflow starts with a project the machine understands: detection of
Cargo, npm, CMake, PlatformIO, Zephyr, ESP-IDF, Python, Go and Nix projects; generated
development shells loaded by direnv; and a rule above them — never rewrite an existing
flake.nix, shell.nix or devenv.nix. The machine had none of it: a person’s project was a
directory it could read file by file.
ADR-0044 settled how a person’s things are looked at: by their executor, as them, with the home read-only. A development shell written into a project is a write. So the reading half comes first here too, and the shell Tacid would write is shown rather than written.
Prior decisions
ADR-0035 gives every person an executor that reads their files as them and writes none of them. ADR-0029 names the project kinds and the never-rewrite rule. ADR-0010 says content from the world is never an instruction.
Decision
A project is detected from its files, its root is the nearest directory that says what it is, its own shell is kept, and the shell Tacid would write is shown for the person to save.
executor.project takes a path in the person’s home and finds the project it is in: the
nearest directory, walking up no further than the home, with a file that names a kind —
Cargo.toml, package.json, platformio.ini, west.yml or prj.conf, sdkconfig or
idf_component.yml, CMakeLists.txt, pyproject.toml or setup.py or requirements.txt,
go.mod, flake.nix or default.nix. A directory can be several kinds; a CMake file inside a
Zephyr or ESP-IDF tree is those projects’ own and not a second kind. It says whether the project
brings its own shell, in which file, and whether direnv already loads it; it reads the two keys
of .tacid/project.toml a person may have written — the project’s name and its embedded target
— and nothing Tacid would act on unasked; and, for a project with no shell of its own, it returns
the flake.nix Tacid would write, with the tools each kind needs, as text.
The daemon’s project.look reaches the person’s executor for a home and looks at a project
anywhere else as itself; the rules model turns “what kind of project is ~/x” and “set up a dev
shell for ~/x” into a look. What comes back is untrusted: a file named the kind, and a name in a
declaration is the world’s.
Writing the shell — and the per-project policy the plan sketches — waits for an executor that may write, as ADR-0044 says of commits.
Consequences
- The never-rewrite rule holds by construction here: nothing is written, so nothing is rewritten. When writing arrives it will be the rule the writer checks first, and this record names the files it protects.
- The suggested shell is nixpkgs attribute names a person can read, in a flake they can save; it is a suggestion in the plan’s own words, and it is not run.
- The lifecycle test makes a Cargo project in a person’s home, asks what it is, reads the
suggestion, and finds no
flake.nixwritten.
Alternatives rejected
- Writing the shell, with the never-rewrite rule as the safeguard. The executor cannot, and should not until a policy says what it may write.
- A TOML parser for
.tacid/project.toml. Two keys on two lines; a parser would be a dependency for the sake of a file nobody has written yet.