# ADR 0009 — `libveloxproto`: generated protocol code is not part of `libveloxcore` **Status:** accepted · **Date:** 2026-09-09 · **Lane:** PROTO ## Context Two rules in `CLAUDE.md` appear to collide, and a later agent will notice: * the layering rule says **`core` → no JSON, no SQL, no Qt, no RPC. Ever.** * the PROTO brief says `gen_cpp.py` emits **`core/generated/velox_proto.{hpp,cpp}`**, with `to_json`/`from_json` built on nlohmann. Read together they say core must not know about JSON, and also that a JSON serialiser goes in `core/`. Left unresolved, someone eventually "fixes" it by moving the generated code, or by deleting the layering rule. ## Decision `core/generated/` is its own CMake target, **`libveloxproto`**, and is **not** part of `libveloxcore`. The layering rule constrains `libveloxcore` — the engine — which continues to know nothing about JSON, SQL, Qt or RPC. `libveloxproto` is the wire types, which are by definition JSON, and it is linked by `veloxd`, the CLI, the GUI and the conformance runner. The directory is `core/generated/` because the PROTO brief and the roadmap both name that path, and moving it would break a written interface for a cosmetic gain. `libveloxcore` must not link `libveloxproto`. The daemon's job is to project the engine's state into the contract's types; if the engine ever needs to know what a `TaskSummary` is, the layering has been violated and the fix is in the daemon, not here. ## Consequences * `add_subdirectory(core)` must produce two targets. Lane PKG/QA owns the root build files; the conformance runner's `CMakeLists.txt` shows the linkage it expects. * A grep for `nlohmann` under `core/` is no longer automatically a review failure — but one under `core/src/` or `core/include/` still is. That is the line, and it is worth stating because the old grep was a nice bright one. * The generated code deliberately does **not** emit nlohmann's ADL `from_json`, which throws. Inbound parsing is `parse() -> std::expected`, so a malformed frame is a value the RPC loop handles rather than an exception unwinding through the daemon. Only the outbound direction is implicit. ## Alternatives rejected **Put the generated code in `daemon/`.** It is also needed by the CLI, the GUI and the conformance runner, and `daemon/` belongs to a different lane than the one that generates it. A shared artifact owned by one consumer is how ownership disputes start. **Relax the layering rule to allow JSON in core.** The rule is the reason the engine stays testable and the reason a GUI bug can never be an engine bug. It is worth more than the convenience of one directory.