summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCalvin <calvinm@kissingerassoc.com>2026-08-17 12:26:43 -0400
committerCalvin <calvinm@kissingerassoc.com>2026-08-17 12:26:43 -0400
commit441c64d81594bb521350dbee6348f552401a1a2e (patch)
tree23f8f2f666a211d621738f1ad186d4cdf14f64ae
parent7ba5bfafc5a8b0f09c4b88aea62e7db9206cfa38 (diff)
doc: the manifesto
Why this exists, what it replaces and what it does not, and the positions that decide later arguments. Claims are cited to file:line in the 9front tree or to Security in Plan 9, so they can be checked rather than believed.
-rw-r--r--doc/manifesto.md236
1 files changed, 236 insertions, 0 deletions
diff --git a/doc/manifesto.md b/doc/manifesto.md
new file mode 100644
index 0000000..96cfcb3
--- /dev/null
+++ b/doc/manifesto.md
@@ -0,0 +1,236 @@
+# A sane 9front
+
+## What this is
+
+An experiment: a 9front system whose userland assumes the machines we
+actually have, rather than the ones Bell Labs had.
+
+It is not a fork, not a research OS, and not an attempt to be different for
+its own sake. The kernel is 9front's and stays 9front's. What gets replaced
+is the layer above it — init, boot, service management, network
+configuration, the identity model — because that layer encodes assumptions
+about computing that stopped being true.
+
+The first brick was init, because nothing else can be supervised without it.
+That was not the goal. It was the only place to start.
+
+## The vocabulary problem
+
+Plan 9 asks what *kind* of machine you have. Answer with a noun — `cpu`,
+`terminal`, `auth` — and the noun decides everything downstream.
+
+This is not an abstraction. It is a switch statement, and you can read it:
+
+- `sys/src/9/boot/bootrc:151` — `if(~ $service cpu) init=($init -c)`,
+ otherwise `-t`. One word in `plan9.ini` picks the flag.
+- `sys/src/cmd/init.c:61` — `iscpu = strcmp(service, "cpu")==0`, then
+ `fexec(cpustart)` or `fexec(rcexec)`.
+- `/rc/bin/termrc` (99 lines) and `/rc/bin/cpurc` (121 lines) are roughly 80%
+ identical. The difference between a machine you can log into and one you
+ cannot is which of two nearly identical scripts runs.
+
+The consequence is visible in `ps`: a machine has no `aux/listen` not because
+anyone decided it should not accept logins, but because `plan9.ini` says
+`terminal`.
+
+Nouns are the wrong interface because real machines are several things at
+once, or change what they are, or are things the noun list never anticipated.
+A machine bridging two networks is not a `cpu` server or a `terminal`. Asking
+which one it is has no answer.
+
+**Replace the noun with the list.** A machine is what it runs. Not a role, a
+service directory. `is keyfs enabled` replaces `is this the auth server`.
+
+## Three tiers
+
+The scope line, so this stays an experiment and not a fork:
+
+| tier | policy | why |
+|---|---|---|
+| kernel | take as-is | it is what 9front actively develops, and the thing we would most regret diverging from |
+| toolchain, libc, core commands | take as-is | `6c`, `mk`, `rc`, `ls`, `sam`, `ed` are solved. Rewriting them is the gatekeeping failure mode |
+| system layer | replace entirely | init, boot, `termrc`/`cpurc`, service management, ndb conventions, `listen`, namespace construction |
+
+What must be tracked upstream is the kernel's interface to userland:
+`#`-devices, the syscall surface, 9P, and the boot protocol — `plan9.ini`
+keys, bootargs, what the kernel expects of the first process.
+
+## Abandon, do not fork
+
+The Plan 9 orbit is mostly a graveyard of forks that could not afford
+themselves. Every one bought a permanent rebase tax on day one and paid it
+out of the budget meant for the actual idea.
+
+The distinction that keeps this cheap: **a fork modifies their files and must
+track them forever. Abandoning means writing our own and never looking
+upstream again.** A patched `termrc` is a liability. An init that never reads
+`termrc` is free.
+
+So: keep an explicit, short list of files we modify in the 9front tree.
+Everything else is ours or theirs, never both. If that list stays at three or
+four entries, this is honest. If it grows, we have quietly become a fork and
+will see it early rather than in two years.
+
+There is a second reason, and it is the better one: **if the thesis is that
+namespaces are the right abstraction, then needing a fork refutes the
+thesis.** A patched tree proves the namespace was insufficient.
+
+## Principles
+
+1. **Adopt the solved interface; invent only where Plan 9 actually wins.**
+ Two-button mice, TCP, TLS, file extensions, `/sys/log` — solved elsewhere,
+ and being different is pure cost. Namespaces and 9P are where Plan 9 is
+ genuinely better, and inventing there earns its keep. Inventing in the
+ first category is most of why Plan 9 has the users it has.
+
+2. **Sane defaults over configurable everything.** A default that is wrong
+ for a rare case is better than a knob every user must understand. No
+ plaintext outside the loopback or the LAN. No config that suggests
+ otherwise.
+
+3. **The identifier carries the fact.** A file extension says what a file is
+ without opening it. A port number should say what transport it speaks: if
+ 9543 always means TLS and nothing ever listens plaintext there, then
+ "accidentally exporting unencrypted to the WAN" stops being a
+ misconfiguration to guard against. (9543 is unused in the entire 9front
+ tree — verified.)
+
+4. **Structured, not textual.** ndb and ctl files, not scripts with
+ conditionals. Configuration should be readable by something other than a
+ shell.
+
+5. **Declare, do not script.** The failure mode of shell startup is not
+ length, it is opacity. `bootrc` declares `fn configlocal {}` empty and then
+ sources `/rc/lib/*.rc`, which redefine it — a plugin system built on
+ function shadowing. You cannot know what boot does by reading it. Two
+ symptoms of the same disease: `diskparts` runs twice (`local.rc:44` and
+ `termrc`), and `mntgen`, `nusbrc` and the device binds each run two or
+ three times, because each pivot loses track of what was already done.
+
+6. **Namespaces are the capability set.** Privilege is what is in your
+ namespace, not what your uid is. You do not protect the raw disk with
+ permission bits; you leave `#S` out of the namespace. Capability by
+ absence.
+
+7. **Build for namespacing, in both directions.** A sandboxed application
+ gets a namespace built for it and cannot name anything else. Export should
+ be the same mechanism pointed outward: `exportfs` should not hand out "my
+ root, minus permissions" — it should hand out a namespace constructed for
+ that client. Same code, same profiles, same audit story.
+
+8. **Per-server authentication, not one realm.** Factotum keys are already
+ `(proto, dom, user)` tuples and nothing about factotum assumes a single
+ realm. What assumes it is the convention built on top: one `authdom` in
+ ndb, `cpurc` deciding it is *the* auth server by matching `$sysname`
+ against an ndb attribute. The mechanism is fine. The convention is
+ centralised, and that is a much smaller thing to fix.
+
+## Boot
+
+Root is a service. The boot method is that service's dependencies.
+
+```
+svc=root svc=root
+ provider=hjfs provider=mount
+ needs=diskparts needs=ipconfig
+ needs=factotum
+```
+
+Local boot and network boot stop being two code paths and become two
+dependency sets. No method table, no `$mt`, no function shadowing, no
+`bootargs is (tcp, tls, il, local!device)` prompt.
+
+The phase split is derived, not declared: **phase 1 is the transitive
+dependencies of `root`.** Everything else is post-pivot. init's one built-in
+action is the pivot itself, triggered when `root` becomes ready.
+
+`bootfs.proto` is already the phase-1 palette. Its contents — `rc`, `bind`,
+`mount`, `srv`, `mntgen`, `ipconfig`, `tlsclient`, `kbdfs`, plus `factotum`
+and `paqfs` from `bootdir`, plus `hjfs`/`gefs` from `disk.proto` — are
+precisely "what you might need before root exists." The constraint on a
+phase-1 service is that its `exec=` is in the archive, which is checkable at
+build time.
+
+Two independent arguments put the console before root: an encrypted root must
+prompt for a passphrase, and a phase-1 failure has no filesystem to log to.
+`console=` already does this — `bootrc` runs `aux/kbdfs -q -s cons /dev/eiaN`
+before `exec $init`, giving a real console with working interrupt (`Kdel` is
+0x7f, and kbdfs turns it into a note to the process group).
+
+Being first is cheap: `boot.c` *execs* `/bin/bootrc` rather than forking it,
+so whatever sits at that path is pid 1. Stock init is pid 2 only because
+`bootrc` runs its `main` as `@{main}`, in a subshell. But pid 1 carries no
+kernel privilege in Plan 9 — what matters is what exists before you, because
+you can never `exec=` those. That is what `adopt=` is for, and being pid 1
+does not shorten the list by one process.
+
+**What we give up by being first:** `bootrc`'s `while(){ @{main} }` loop is a
+supervisor of last resort — it retries a failed boot and re-prompts. Taking
+its place means answering "who restarts init?" ourselves rather than
+inheriting an answer.
+
+## Security
+
+Plan 9's model assumes booting a terminal gets you nothing worth having:
+your data is on the file server, and reaching it requires authenticating.
+The host owner is, in the words of the authors, "just a regular user that
+happens to own the resources of the local machine" — no setuid, no ambient
+superuser, privilege granted through a capability rather than a uid check.
+That part is better than Unix and we keep it.
+
+The premise it rests on is dead. A machine with a local filesystem, keys in
+nvram, and a position on several networks is not a thin client. Booting it
+hands over all three.
+
+Bell Labs knew and said so. *Security in Plan 9*, §2.5:
+
+> We have not addressed leaks possible as a result of someone rebooting or
+> resetting a machine running factotum. [...] We have not found a
+> satisfactory solution to this problem.
+
+They defended thoroughly against an attacker with a process on a *running*
+machine — `private` and `noswap` keys, `/dev/caphash` openable once, a
+`confirm` attribute for high-value keys — and then stopped at the machine's
+power switch. Their unsolved case is our default case.
+
+It is more solvable now than in 2002, and the parts already ship:
+`disk/cryptsetup` for data at rest (and it is in `disk.proto`, so an
+encrypted root is already a supported boot path), password-protected nvram
+for machine identity, `dp9ik` in place of the weak `p9sk1`, TLS. What is
+missing is a default that assembles them. That is policy, not research.
+
+**A machine that boots should be inert until someone gives it a secret.**
+Two secrets, actually, and they are different: the data (disk) and the
+identity (nvram). Losing the second is worse.
+
+And nobody should be running as the host owner all day. The paper worries
+about exactly this — an attacker with a host-owner process cannot read the
+keys but can ask the local factotum to authenticate as you — and answers it
+with a GUI confirmation prompt, which is a band-aid on a structural problem.
+The host owner owns hardware and starts services. Humans log in as a
+separate identity, with their own factotum and a namespace that does not
+contain the disk. `auth/login` already does the mechanism; what was missing
+is a reason to have identities other than the owner.
+
+## What this is not
+
+- Not a performance project.
+- Not a compatibility project. Boot methods, filesystems and transports we
+ do not need are not ported. IL is not removed from 9front; it is simply
+ never used. TCP works.
+- Not a rewrite of things that work. If a 9front command does its job, it
+ keeps doing its job.
+- Not a distribution yet. It becomes one when there is something worth
+ installing.
+
+## Open questions
+
+Live in `design.md` and `todo.md`. The two that block the most:
+
+- **The provider circularity.** A service that provides a namespace to
+ others must both mount and post to `/srv`, making it the exception to
+ "do not bind `#s`" and to freezing the namespace. Root is the archetype of
+ exactly this, which means the boot design and this question are the same
+ problem. Namespace profiles built before it is settled get built twice.
+- **Who restarts init?** Unanswered, and being pid 1 makes it urgent rather
+ than academic.