The first M1 (Viewer) increment: lift the Spike E RPC shape into src/ipc as a
Qt-free, unit-testable library implementing the §2.1 process split. The UI
process forks a sandboxed document process, hands it the document fd via
SCM_RIGHTS (§2.1: no FS access beyond handed fds), and talks Cap'n Proto
two-party RPC over a socketpair. The document process installs the seccomp
sandbox, receives the fd, and serves the DocumentProcess interface (open,
getPageCount, ping) backed by QPDF reading from the handed fd.
src/ipc/ (new, Qt-free so testable without QApplication):
- ipc.capnp: M1 protocol — DocumentProcess{open(fd via SCM_RIGHTS, pathHint)
-> OpenResult{pageCount, pdfVersion}; getPageCount; ping}. ADR-0004 trust
boundary: the UI validates every response; the doc bounds-checks requests.
- DocumentProcessServer: low-level wrapSocketFd + TwoPartyVatNetwork(SERVER)
+ makeRpcServer (the Spike E proven shape, NOT EzRpcServer which calls
accept() on a connected socketpair end). QPDF reads from the handed fd.
- DocumentProcessClient: the Qt<->KJ event-loop integration (§2.3) — runs the
KJ event loop on a dedicated worker thread (low-level makeRpcClient, not
EzRpcClient which uses a thread-local context that breaks cross-thread
destruction). Synchronous methods block the caller via std::promise.
- ProcessLauncher: fork + socketpair + send_fd + construct client.
- FdPassing: raw SCM_RIGHTS send/recv, orthogonal to the RPC transport.
- Sandbox: seccomp allow-list lifted from Spike E, +sendmsg/recvmsg (fd
passing), +fcntl/fstat/lseek/pread64 (QPDF/stdio on the handed fd).
src/app: wire File>Open and the command-line positional arg to launch the
document process off the GUI thread (QtConcurrent) and show the page count in
the status bar (QMetaObject::invokeMethod back to the GUI thread).
test/test_ipc: end-to-end test — launches the sandboxed document process,
opens a real PDF, verifies ping + pageCount + getPageCount. PASSES under the
seccomp sandbox; ASan-clean with sandbox disabled (ASan's pipe2 conflicts
with the allow-list, an ASan-only artefact). Registered as ctest ipc_end_to_end.
Both Spike E (raw protocol) and this (real RPC) now pass under the sandbox.