docs: the Windows port, its inventory struck, its test record and the As built block (P11c)

packaging.md: each row of the Unix-only inventory is struck with where it
landed. architecture.md: the local transport and the Windows arms.
testing.md: the P11c record with its For-P12 paragraph (R-G); every
Windows test is marked unrun until the first Windows job. README.md: the
Windows caveat is now "from a source build, not packaged". served.md: the
unit's As built block (field 8).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
swaitsandClaude Fable 5.1 committed 2026-10-29 15:02:12 -06:00
1 parent 1c3a48899d
commit 2baadb7482
5 files changed
+121 -15

No files matched your search

+6 -4
View File
@@ -67,9 +67,11 @@ Sheet is frozen as of 2026-10-11. The remaining work, its order and its
hand-off are `plan/served.md` section 8.0; the destination is one
installed app for Mac, Windows and Linux (P11), then the hosted service
the plan describes and does not build. Today the agent path runs on
Linux and macOS: tokens live in a 0600 file under the private data root
(D-63); the rest of the host is Unix-only, so Windows is WSL2 only until
its sockets and data root are ported.
Linux, macOS and Windows from a source build: tokens live in a file under
the private data root (D-63), the data root is `%LOCALAPPDATA%\wanderbooks`
with an owner-only access list on Windows (P11c, D-62), and the books'
local channels are named pipes there. Windows is not yet packaged: no
installer, no signing (P11a).
See [`plan/STATUS.md`](plan/STATUS.md) for where things stand and
[`plan/roadmap.md`](plan/roadmap.md) for where it's going.
@@ -88,7 +90,7 @@ them; nothing needs sudo):
curl -fsSL https://wanderbooks.ai/install.sh | sh -s -- acme
```
(Linux and macOS. On Windows run it inside WSL2.)
(Linux and macOS. On Windows build from source: `cargo install --path crates/ledger-cli`, then the three commands below.)
Already installed, three commands: `wanderbooks setup`, `wanderbooks books
create acme`, `wanderbooks connect claude --book acme` (or `connect codex`).
+23
View File
@@ -248,6 +248,29 @@ request (its task, or while it holds the guard) sets `stopped`, and every
later request, on every session, is refused until a restart, which
re-verifies the journal.
## The local transport and the Windows arms (`ledger-mcp::ipc`, `ledger-store::winsec`, P11c, D-62)
The books server, the host and everything that talks to them meet on
channels only this account can open (plan 3.9). `ledger_mcp::ipc` is the
one seam: `Endpoint` (a socket in the root's `run/` on Unix; the pipe
`\\.\pipe\wanderbooks-<hash of run/>-<name>` on Windows), `Listener`
(`bind`, `accept`), `connect` and `connect_std` (blocking, with a read
limit), the `Peer` trait (`peer_is_us`: the effective uid on Unix; the
user SID of the process at the other end of the pipe on Windows), and
`stop_signals`. No other file names a socket or a pipe, a peer check or a
signal. HTTP to the server goes through `ledger_host::channel::http_builder`
(reqwest's Unix socket or named pipe).
`ledger_store::winsec` (Windows only) holds the rest of the Windows FFI:
the owner-only DACL (`set_owner_only`, `is_private`), the account's SID,
the shell's known folders, `MoveFileExW` for the move that must not
replace, and the pipe process ids. `ledger_store::lock` is the one file
lock seam (std's, which is `flock` and `LockFileEx`). `seccomp` and
`close_range` remain Linux-only, doing nothing elsewhere (D-32). Windows
CI is the evidence for every arm that cannot run on the Linux machines
that wrote it; `cargo check --target x86_64-pc-windows-msvc` (the target is
pinned in `mise.toml`) type-checks them, clippy included.
## bookspec (`bookspec/`, P13)
bookspec is the formal accounting specification (D-34): every rule stated
+33 -10
View File
@@ -26,16 +26,39 @@ keyring on every OS (tokens are 0600 files under the data root, unit U0e),
so nothing about tokens is OS-specific now. The Windows host is a port,
not a `cfg` gap. Today Unix-only, found by grep (2026-10-29):
- `ledger-host`: `bridge`, `host`, `start`, `opener` and others are
- ~~`ledger-host`: `bridge`, `host`, `start`, `opener` and others are
`#[cfg(unix)]` modules; `host.rs`/`start.rs` use `setsid`, `umask`,
`close_range`, `geteuid`, Unix sockets and `libc` unguarded;
`commands.rs` calls `isatty`/`tcgetpgrp`.
- `ledger-mcp`: the owner-only socket check reads peer credentials
`commands.rs` calls `isatty`/`tcgetpgrp`.~~ Landed (P11c): every module
builds on every OS. The channels are `ledger_mcp::ipc` (below); the host
starts detached (`DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP`, leaving
the opener's job if it lets it) where Unix uses `setsid`, with a
Windows environment list in `start.rs::host_env`; `close_range` and the
`umask` have nothing to do there (a Windows child inherits no handle it
is not given); the terminal checks use `IsTerminal`; the stop signals
are Ctrl+C, Ctrl+Break, console close and shutdown
(`ipc::stop_signals`); a replaced program is told by its size and last
write, since a running program cannot be replaced in place.
- ~~`ledger-mcp`: the owner-only socket check reads peer credentials
(`SO_PEERCRED`, `getpeereid`); `admin`/`local` listen on Unix sockets.
Windows needs named pipes with a peer SID check (plan 3.9).
- `ledger-store`: `ops.rs` takes `flock` locks; `root.rs` has no owner-only
DACL (`set_mode` is a no-op off Unix) and the data root falls back to
`%USERPROFILE%`, not the known-folder `%LOCALAPPDATA%`; `refuse.rs`
(seccomp) and `renameat2` are Linux-only (macOS needs its own
equivalents or `cfg` fences).
- The `portable` job runs Windows as allowed-to-fail until that port lands.
Windows needs named pipes with a peer SID check (plan 3.9).~~ Landed
(P11c): `ledger_mcp::ipc` is the one seam. A pipe is
`\\.\pipe\wanderbooks-<hash of the root's run/ folder>-<name>`, made with
a DACL of one allow entry (this account's SID), remote clients refused,
the first instance made once, and 16 idle instances waiting; each end
checks the other's process (`GetNamedPipeClientProcessId` /
`GetNamedPipeServerProcessId`, then that process's token user) against
its own SID. Clients open with `SECURITY_IDENTIFICATION`.
- ~~`ledger-store`: `ops.rs` takes `flock` locks; `root.rs` has no
owner-only DACL (`set_mode` is a no-op off Unix) and the data root falls
back to `%USERPROFILE%`, not the known-folder `%LOCALAPPDATA%`;
`refuse.rs` (seccomp) and `renameat2` are Linux-only (macOS needs its
own equivalents or `cfg` fences).~~ Landed (P11c): `lock.rs` is the one
lock seam (std's `try_lock`, `LockFileEx` on Windows); `winsec.rs` sets
and reads the owner-only DACL and finds the profile and `LocalAppData`
from the shell's known folders; `rename_new` is `MoveFileExW` without
the replace flag. `refuse.rs` (seccomp, a test fault injector) and the
host's `close_range` stay Linux-only on purpose (D-32: nothing here
confines a process, so nothing is promised where they are absent).
macOS, which has no `renameat2`, still looks before it moves.
- The `portable` job runs Windows as a gate (`experimental: false`), since the port landed (P11c).
+58
View File
@@ -3669,3 +3669,61 @@ it); the digest's `since_seq` comparison (`>`), its `as_of` bound, the
`zero_net_accounts` branch and the `never_reconciled` split;
`report_status`'s three open conditions; the open-drafts refusal's
`<= as_of` and its decision; the attention `awaiting_evidence` branch.
## P11c: the native Windows port (2026-10-29, D-62)
The Windows arms were written on Linux and type-checked and linted for
`x86_64-pc-windows-msvc` (`cargo check` and `cargo clippy --workspace
--all-targets --target x86_64-pc-windows-msvc -D warnings`, both clean);
they have not run. The `portable (windows-latest)` job, now a gate, is the
evidence, and its first run is the first run of every test marked
"Windows" below.
- **`ledger-store` `tests/windows_root.rs`** (Windows only):
`the_default_root_sits_under_local_app_data`,
`setup_makes_a_private_root_and_its_folders` (the DACL of the root and of
`books`, `run`, `legacy`), `a_root_others_can_open_is_refused_and_setup_makes_it_private_again`
(Everyone granted with `icacls`, then refused, then tightened),
`what_is_made_in_a_private_folder_is_private` (inheritance),
`a_file_made_owner_only_is_private_and_a_loosened_one_is_not`,
`a_rename_never_goes_over_what_is_there`,
`this_process_runs_as_the_account_that_the_token_says`.
- **`ledger-store` `src/ops.rs`:** `a_viewer_s_presence_is_a_shared_lock_others_can_see`
(every OS; the lock seam).
- **`ledger-mcp` `src/ipc.rs`** (every OS):
`a_listener_and_a_client_talk_and_each_knows_the_other_is_us`,
`a_blocking_client_times_out_and_reads_the_end_when_the_server_goes`,
`many_clients_at_once_all_connect` (48 at once: the idle pool),
`nothing_listening_is_not_found`; Windows only:
`a_pipe_is_per_root_and_cannot_be_taken_twice`.
`admin.rs`'s fake-server tests (`an_answer_is_one_line_of_at_most_four_mib`,
`no_server_means_nothing_was_sent`) now run over `ipc` on every OS.
- **`ledger-host` `tests/windows_pipe.rs`:** `a_second_user_cannot_open_the_pipe`
(Windows only). A second account cannot be made on the runner, so it
checks the two things that keep one out: the pipe's DACL names this
account alone, and a SYSTEM process (`services.exe`) is not taken for
this account by the SID check.
- **`ledger-host`:** `start.rs` `windows_tests`, `host.rs`
`a_folder_the_host_can_write_isnt_a_sandbox` and
`only_a_file_has_an_identity_and_a_replaced_one_another` (Windows
variants of the Unix tests of the same names).
- **The over-the-wire suite** (`ledger-cli` `tests/common/mod.rs`) now
runs on Windows: `Home` finds its root by `default_root`, children get
`clean_env`, `mcp_http` reaches the server over its pipe, folders are
made private by `private_folder`. Tests that signal processes
(`libc::kill`), use Unix sockets, modes, symlinks or `/proc` stay
`cfg(unix)` / `cfg(target_os = "linux")`. `one_executable.rs`'s
`without_local_sockets_the_modes_that_hold_the_books_say_so` is gone (the
modes work); `the_books_server_needs_a_root` runs everywhere.
`ledger-gui` `read_only` marks files read-only (not folders) on Windows.
**For P12 (mutation targets, none run, D-33):** `winsec::is_private`'s
three refusals (owner, missing DACL, an allow entry for anyone else) and the
ACE types it skips; `ipc::Endpoint::new`'s hash input (a mutant that drops
the lower-casing or the prefix strip splits one root into two pipes: found
only by two processes spelling the path differently); the Windows
`StdStream::read_some`'s deadline comparison and its `BROKEN_PIPE`
arm; `Listener`'s `IDLE_INSTANCES` pool (a mutant that makes it 1 passes
every test but `many_clients_at_once_all_connect`); `start.rs::host_env`'s
list. None of the Windows arms is reachable by `cargo mutants` on Linux:
they run on the Windows job only.
+1 -1
View File
@@ -1667,7 +1667,7 @@ Each unit follows the 8.0.4 template. "A-n" is an owner answer numbered in 8.3.1
5. **Files:** owns the Windows arms of the files above; touches `docs/packaging.md` (the inventory rows are struck as each lands), `docs/architecture.md`, `docs/testing.md`, `README.md` (the Windows caveat goes).
6. **Tests and proofs:** the existing root, lock, lease and transport tests run on the Windows job (`cargo test --workspace` there); `a_second_user_cannot_open_the_pipe` (new, Windows-only, `crates/ledger-host`); no kernel, Kani or Lean change.
7. **Done when:** (1) the `portable (windows-latest)` job is green with `experimental: false`; (2) `wanderbooks setup`, `books create`, `connect claude`, a bridge session, an upload and `wanderbooks gui` work on Windows from a source build (the engineer or the owner on a Windows machine; recorded in the As-built block); (3) `docs/packaging.md`'s Unix-only inventory is empty.
8. **As built:** none yet.
8. **As built:** (2026-10-29, branch `P11c`; written on Linux, type-checked and linted for `x86_64-pc-windows-msvc` with the target pinned in `mise.toml`, **not run on Windows**: the `portable (windows-latest)` job's first run is the evidence for everything marked unverified here, and Done-when 2 needs a person on a Windows machine.) **Step 1, the data root:** `ledger-store/src/winsec.rs (new, Windows only)` holds the Windows FFI (dependency `windows-sys` 0.61, `cfg(windows)`): `set_owner_only` (a protected DACL of one allow entry for the account's SID, inherited by what a folder holds), `is_private` (owner is the account or Administrators; no allow entry for anyone but the account, SYSTEM or Administrators; a missing DACL is not private; any failure is "no"), `current_user`/`process_user`/`pid_is_us`, the shell's known folders (`profile_folder`, `local_app_data`), `rename_new` (`MoveFileExW` without the replace flag, write-through) and the pipe process-id calls. `root.rs`: `account_home` is the profile known folder; `platform_data_dir(home)` is the shell's `LocalAppData` for the account's own home and `home\AppData\Local` for any other (tests); `set_mode`, `check_private_dir` (now takes the path), `recorded_root` and `rename_new` have their Windows arms beside the Unix ones. **Step 2, locking:** `ledger-store/src/lock.rs (new)` is the one seam, on std's `try_lock`/`try_lock_shared` (flock; `LockFileEx`), replacing the only `libc::flock` (`ops.rs`); the plan's "LockFileEx behind lock.rs" is std's, so there is no second `unsafe` copy. **Step 3, the transport:** `ledger-mcp/src/ipc.rs (new)` is the seam for both families: `Endpoint`, `Listener`, `connect`, `connect_std` (a blocking client with a read limit; on Windows polled with `PeekNamedPipe`, since a pipe read cannot be cut short), `Peer` (`peer_is_us`, `peer_pid`), `stop_signals`, `split_accepted`/`split_connected`. A Windows pipe is `\\.\pipe\wanderbooks-<sha256 of the canonical, lower-cased run/ folder, 8 bytes>-<name>`, made with an owner-only DACL, remote clients refused, `first_pipe_instance` once, and 16 idle instances kept waiting (each its own task, replaced on connect) so a client never finds all busy; clients open with `SECURITY_IDENTIFICATION`; both ends check the other's process token user against their own SID. `local.rs`, `admin.rs`, `host.rs`, `opener.rs`, `bridge.rs`, `commands.rs` use `ipc` (their `peer_uid`/`peer_pid`/`own_uid` moved into it); `ledger-host/src/channel.rs (new)` gives reqwest the Unix socket or `windows_named_pipe`. `lib.rs` of `ledger-host` and `ledger-mcp` and `main.rs` of `ledger-cli` lost every `cfg(unix)` gate and the "comes with P11" stubs. `start.rs`: the Unix start (`setsid`, `umask`, systemd-run, `XDG_RUNTIME_DIR`) is `cfg(unix)`; the Windows start is `DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | CREATE_BREAKAWAY_FROM_JOB` (without the last when the job refuses it) with a fixed environment (`host_env`); `host.rs` told a replaced program by (size, last write) on Windows; `close_range` and `seccomp` stay Linux-only (D-32). Also: `backup.rs` stops a quiet git with `taskkill /T` and finds Git for Windows; `tokens.rs` and the viewer's thumbnail folder take the DACL; `.gitattributes` pins LF (autocrlf would break the byte-for-byte golden and hash tests on the runner). **Tests:** see `docs/testing.md` "P11c": `ledger-store/tests/windows_root.rs`, `ledger-mcp/src/ipc.rs` (4 on every OS), `ledger-host/tests/windows_pipe.rs::a_second_user_cannot_open_the_pipe`, Windows variants in `start.rs` and `host.rs`; the whole over-the-wire suite of `ledger-cli` now compiles for Windows and runs there through `common::clean_env`/`private_folder`/`default_root`/`mcp_http`. **Checked here:** `cargo check` and `cargo clippy -D warnings` for `x86_64-pc-windows-msvc` over the workspace and all targets (C dependencies stubbed with a no-op compiler/archiver shim, which `check` allows); on Linux fmt, clippy on the crates touched and their tests. **Not verifiable here, so the first CI run decides:** everything that runs on `windows-latest`, in particular the pipe code, the DACL reads, `taskkill`, the host's detached start and the over-the-wire suite. **Answers I chose** (alternatives in the report to the EM): the Windows ancestors check is none (CHOICE); the bridge's scripted-server tests (`against_a_server`) stay `cfg(unix)`; vault and TIN-registry files rely on inheritance from the private folder they sit in. **Left open:** Done-when 2 (a person on Windows runs setup, create, connect, a bridge session, an upload, `gui`), and Done-when 1 until CI shows the job green.
#### P12: max testing, again (D-33)