The M0 Spike E recorded that Cap'n Proto two-party RPC over an AF_UNIX
socketpair stalled (server processed requests, responses never reached the
client). Root cause, confirmed with a standalone reproduction: EzRpcServer(int
fd, ...) expects a LISTENING socket and calls accept() on it; a socketpair end
is already CONNECTED, so accept() fails with EINVAL and the bootstrap never
completes. Fix: server side uses the low-level path —
LowLevelAsyncIoProvider::wrapSocketFd(fd) + TwoPartyVatNetwork(SIDE_SERVER) +
makeRpcServer(network, bootstrap). The client uses EzRpcClient(fd), which is
designed for an already-connected socket. This is the production M2 transport
shape.
- spike/E_sandbox/DocumentProcess.cpp: low-level TwoPartyVatNetwork server
over wrapSocketFd; serves DocumentProcess::Server capability with 16 MiB
payload cap (ADR-0004 bidirectional trust boundary)
- spike/E_sandbox/UIProcess.cpp: EzRpcClient(fd) + parse() RPC; validates
every response field. ASan caught a use-after-free (ParseResult::Reader
outliving its Response) — fixed by validating inside the Response scope
(validates §7.2 sanitizers-from-day-one)
- spike/E_sandbox/Sandbox.cpp: allow ioctl (FIONBIO) — KJ's wrapSocketFd sets
non-blocking mode via ioctl; strace identified it as the denied syscall
- spike/CMakeLists.txt: Cap'n Proto via pkg-config (the Debian CMake config
hard-requires libatomic via a check that fails on x86-64 where it isn't
needed); schema compiled with the capnp tool + --src-prefix
- docs/spike-results/0004: record the resolution; gate still MET (1000/1000
under seccomp, ~58us avg; ASan-clean with sandbox disabled — ASan's pipe2
conflicts with the seccomp allow-list, an ASan-only artefact)
ADR-0005 records the decision deferred in ADR-0003: Rust (staticlib, C ABI)
for the image-codec leaf decoders where the CVE history concentrates
(OpenJPEG ~40 buffer-overflow CVEs incl. 2024, several 'as used in PDFium');
C++ throughout elsewhere. Spike F measures the build-friction cost empirically
(~10s cargo build, one .a link, ASan-clean FFI) rather than estimating it.
- docs/adr/0005-rust-leaf-decoders.md: decision + CVE survey + evidence
- docs/adr/0003 + README: mark deferred portion superseded by ADR-0005
- spike/F_rust_ffi_probe: Rust staticlib leaf + C++ driver, bounded (ptr,len)
FFI matching §7.2; builds clean under ASan+UBSan
- .gitignore: ignore cargo target/ (keep Cargo.lock for reproducibility)
Stand up the repository foundation described in docs/plan.md §14:
- CMake + vcpkg manifest mode (pinned baseline), presets for debug/release/asan/tsan/ci-release
- Release-build hardening module (§7.2): stack protector, libc++ hardening, CFI/CET
- Skeleton CPack packaging for all nine artifact formats (§12): .deb/.rpm/.tar.xz/.AppImage, .msi/.exe/.zip, .dmg/.pkg
- Linux integration: .desktop, hicolor icon, AppStream metainfo, man page, bash completion
- Empty-window Qt Widgets application shell with the §9 chrome layout
- Spike A harness: QPDF open → parse content streams → re-emit verbatim → save, measuring byte-identical round-trip rate over a corpus
- Pixel-diff harness scaffolding (§8.2 gate) with the spike-runner JSON contract
- Contract test pinning the spike-runner report format (dependency-free)
- Gitea Actions CI matrix (§13.3): build × {linux,macos,windows} × {debug,release,asan,tsan}, plus nightly packaging pipeline
- Tooling: clang-format, clang-tidy, gitleaks, REUSE config, pre-commit hooks, CODEOWNERS
- Governance: README, CONTRIBUTING, SECURITY, TRADEMARK, docs/plan.md, four M0 ADRs (§2.5, §4.4, §7.2, §2.1)
- REUSE-compliant SPDX headers and LICENSES/ directory (GPL-3.0-or-later)
No production parsing, rendering, or editing code yet — that lands in M1-M6.
The scaffolding is the hard-to-retrofit foundation: build system, packaging
pipeline, CI gates, governance, and the ADRs that fix the architectural
invariants before any code that depends on them is written.
Signed-off-by: ai-ad4 <ai-ad4@users.noreply.gitea.lm.je>