The books can live on a machine of the owner's own (plan 3.8, 8.2 P9a, D-51). Three things make that honest instead of nominal: - `--isolation same_user|enforced` sets `server::Isolation`, carried through `Served` and `BooksServer` to `books.start`. Locally the books stay `same_user (advisory)`; under the self-host unit the server is told `enforced` and reports `service_user (enforced)`. The server never guesses, and the literal it used to report is gone. - `--http [ADDR]` binds the same MCP router on loopback (default 127.0.0.1:7411) beside the Unix sockets, before the ready line, for the TLS proxy or tunnel in front of the server. It is the same bearer door and the same graceful close; a failed bind is reported and is not fatal (3.9). `serve` carries both flags through the host, so the system unit can ask for them once. `deploy/wanderbooks-server.service` is the unit: a dedicated account, a 0700 data root, `ProtectSystem=strict`, no seccomp or Landlock (D-32), and loopback-only networking. `deploy/README.md` is the recipe: install, setup, move the books by `books adopt` with its head check (D-31: a copied book is the transport), mint a bearer token, put Caddy or Tailscale Funnel in front of the loopback door, and point Claude Code and Codex at it. The two-account CI job (`.github/workflows/two-user.yml`) makes the deployment and runs `crates/ledger-cli/tests/two_user.rs` as another account: `agent_uid_cannot_read_tenant` proves the tenant root and its journal are refused, then reads, writes, uploads, files, exports and verifies over HTTP with the bearer (in a second book, so the moved books' head is not advanced); `self_host_adopt_same_head` compares the moved books' head and event count with the source clone's. `isolation_label_is_enforced_under_the_unit` runs everywhere, including the unit-file read that keeps the recipe from drifting. Accounting reason for the shape: nothing here changes an entry, a report or a figure. It changes who can reach them: the move is lossless and verifiable at the head, and the boundary is the operating system's, not a promise in our text. No journal code, kernel function, Kani harness or Lean step is touched, so `just prove` is not required. No go-ahead needed: no new journal feature or event; the live books are unaffected.
1.4 KiB
1.4 KiB
On a machine of the owner's own
These books may live on a machine the owner controls, not on the computer
an agent runs on. The server there runs as a separate account, and the
agent has no account on that machine: books.start says
isolation: "service_user (enforced)". Locally it says
same_user (advisory), which is honest: hidden, not locked.
What that means for you:
- Reach the books only through these tools. The machine they live on is not yours to inspect, and you have no way to reach it except through the server.
- The owner moves the books there with a verified copy or a backup, and
the move is confirmed at the head the preview read. If
books.verifyorbooks.startreports a head the owner does not expect, say so and stop; do not touch the books until the owner has looked. - Each client has its own token. Never ask the owner for one, never print one, and never put one in a note, an entry, a receipt or a file in the books. If a tool says the token was refused, ask the owner to make a new one for that client.
- Everything else is the same as on the owner's computer: the receipt rule, the preview before a post, the books' own settings and conventions. The machine changes where the books are, not how they are kept.
If the owner asks how the machine is set up, point them at the deployment notes that came with the app; do not invent steps for them, and do not try to run any yourself.