docs: define the cross-platform porting strategy (ADR 0020, macOS first)
CI / clang-format (push) Canceled after 0s
CI / testserver (push) Canceled after 0s
CI / bootstrap-script (push) Canceled after 0s
CI / bootstrap-script-2604 (push) Canceled after 0s
CI / extension-lint (push) Canceled after 0s
CI / build (clang) (push) Canceled after 0s
CI / build (gcc) (push) Canceled after 0s
CI / sanitizers (dev) (push) Canceled after 0s
CI / sanitizers (tsan) (push) Canceled after 0s
CI / clang-tidy (push) Canceled after 0s
CI / conformance (push) Canceled after 0s
CI / nightly-integration (push) Canceled after 0s
CI / gui-dod (push) Canceled after 0s
CI / gui-dod-nightly (push) Canceled after 0s

The tree is Ubuntu-first, but the Linux-specific surface is about ten files and
already sits at the bottom of the I/O and RPC stacks. Rather than fork the repo
or scatter ifdefs, platform differences go behind narrow seams with one backend
directory per OS, selected centrally in cmake/platform.cmake.

Linux stays the reference implementation and its behaviour does not change: the
owning lanes (CORE, DAEMON) move today's Linux code behind the seams on Ubuntu
first, with the suite still green, before any macOS code exists. A new PORT lane
owns only the per-OS backends, so macOS work never writes shared logic and
CLAUDE.md's lane rule stays intact.

Adds ADR 0020 (the decision and phases), docs/08-porting.md (seam list, API
mapping for macOS and Windows, dependency table, verification gates) and
docs/agents/AGENT-PORT.md (the lane brief), plus the PORT row in the ownership
table.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-15 17:36:27 +04:00
co-authored by Claude Opus 5
parent 3e553f08c0
commit facd851824
4 changed files with 205 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
# ADR 0020 — Porting to macOS (and later Windows) without forking the tree
Status: **accepted** · Supersedes nothing · Applies to every lane
## Context
The project was built Ubuntu-first and says so in its own project description. A survey of
`main` (2026-09-15, 57/57 green) found the Linux-specific surface is far smaller than the
Ubuntu-first framing suggests — roughly ten files, all of them already isolated at the
bottom of the stack:
| Area | Linux-only thing | Files |
|---|---|---|
| `core/io` | `posix_fallocate`, `posix_fadvise`, `fdatasync` | `sparse_file.cpp` |
| `core/meta` | `fdatasync` | `veloxpart.cpp` |
| `daemon/rpc` | `eventfd` | `event_loop.{hpp,cpp}`, `main.cpp` |
| `daemon/rpc` | `timerfd` | `main.cpp`, `sched/scheduler.hpp` |
| `daemon/rpc` | `SO_PEERCRED` / `struct ucred` | `uds_server.{hpp,cpp}` |
| `daemon/rpc` | abstract-namespace socket lock | `single_instance.{hpp,cpp}` |
| `daemon/rpc` | `$XDG_RUNTIME_DIR` layout | `runtime_dir.{hpp,cpp}` |
| build | `libsecret-1` is `REQUIRED` at the root | `CMakeLists.txt` |
Everything else is already portable: the event loop is `poll(2)` (POSIX, not `epoll`), the
transfer engine is libcurl, the GUI is Qt 6, `tools/mockd` and `tests/conformance` are
Node, `tools/testserver` is stdlib Python, and the extension is a WebExtension.
## Decision
**One tree, one logic, per-OS backends behind seams.**
1. **No `#ifdef` in logic.** Platform differences live behind a narrow interface, with one
implementation file per OS. A reader of `sparse_file.cpp` must not need to know which OS
they are on. `#ifdef` is allowed only inside a `platform/<os>/` file.
2. **Linux is the reference implementation.** A port may never change Linux behaviour. The
gate is mechanical: the full suite stays green on Ubuntu, and the Linux backend keeps
the same syscalls it uses today. If a port needs a semantic change, that is an ADR of
its own, not a port commit.
3. **Seams are introduced by the owning lane, on Linux, before any port work.** CORE
introduces `core`'s seams; DAEMON introduces `daemon`'s. This is a pure refactor: move
the existing Linux code behind the interface unchanged, prove the suite is still green.
4. **A new lane, PORT, owns only the per-OS backends**`**/platform/<os>/**` and
`cmake/platform*.cmake`. It never writes logic. That keeps CLAUDE.md §1 intact: CORE
still owns `core/`'s logic, DAEMON still owns `daemon/`'s, and macOS work can proceed in
parallel without cross-lane writes.
5. **`VELOX_OS_*` is set once, centrally**, in `cmake/platform.cmake`, and platform sources
are selected there — not by globbing, and not per-target ad hoc.
6. **Optional dependencies are gated, never removed.** `libsecret` stays `REQUIRED` on
Linux and is replaced by Keychain on macOS behind the same credential-store seam.
## Phases
- **Phase 0 — on Ubuntu, by CORE + DAEMON.** Introduce the seams and move today's Linux
code behind them. Zero behaviour change; 57/57 stays green. No macOS code exists yet.
- **Phase 1 — on macOS, by PORT.** Implement the macOS backends until `veloxd`, `velox`
and `libveloxcore` build and their unit tests pass.
- **Phase 2 — parity.** `tools/testserver`, conformance and the engine's hostile-mode
matrix pass on macOS. The GUI builds against Qt 6 for macOS.
- **Phase 3 — packaging.** `.dmg`/Homebrew, `launchd` instead of `systemd`, and the macOS
native-messaging manifest locations. Deliberately last.
## Consequences
- Windows later is the same shape: a third backend directory, no new strategy. The seams
chosen here are POSIX-flavoured but interface-level, so a Win32 backend is additive.
- CI must build both, or macOS rots silently. Until a macOS runner exists, Phase 1 is
verified by hand on the porting machine and the Linux gate stays authoritative.
- The cost is one indirection at the bottom of the I/O and RPC stacks. Measured against
the alternative — `#ifdef` drift, or a forked repo that diverges in a month — it is
cheap.