diff --git a/README.md b/README.md
index 748343f..e0a0ef1 100644
--- a/README.md
+++ b/README.md
@@ -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`).
diff --git a/docs/architecture.md b/docs/architecture.md
index a00682d..b2e9c63 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -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--` 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
diff --git a/docs/packaging.md b/docs/packaging.md
index dba150e..a48573e 100644
--- a/docs/packaging.md
+++ b/docs/packaging.md
@@ -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--`, 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).
diff --git a/docs/testing.md b/docs/testing.md
index bd944d8..60c6de8 100644
--- a/docs/testing.md
+++ b/docs/testing.md
@@ -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.
diff --git a/plan/served.md b/plan/served.md
index 5124f44..bdaa07a 100644
--- a/plan/served.md
+++ b/plan/served.md
@@ -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--`, 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)