For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Replace nlohmann::json::parse with yyjson at every hot-path callsite (WAL replay, snapshot deserialize, history reads, gRPC request bodies, replication apply) while keeping nlohmann::json as the in-memory document representation. Phase A is a parse-speed win — not a memory win. Phase B is where memory drops.
Architecture: Add yyjson as a build + runtime dep. Introduce one bridge helper (parse_to_nlohmann(string_view) -> nlohmann::json) that uses yyjson under the hood and rebuilds an nlohmann::json tree from the yyjson DOM. Every existing nlohmann::json::parse(s) callsite is rewritten to call this helper. The bridge is the only yyjson surface in the codebase for v1.10 — Phase B will later let yyjson buffers live longer and back binary doc storage.
Tech Stack: C++20, yyjson 0.9+ (libyyjson-dev on Debian 13), nlohmann::json (unchanged), CMake/pkg-config, existing CTest suite.
The 17 parse callsites Phase A touches (verified via rg -n 'json::parse' against the v1.9.5 tree):
| File | Lines | What it parses |
|---|---|---|
service/src/persistence/wal.cpp |
233, 249 | WAL replay: doc data + collection options |
service/src/persistence/snapshot.cpp |
820, 929, 963 | Snapshot deserialize: collection opts, document data, version history |
service/src/persistence/history_store.cpp |
124 | Per-collection history (.hlog) version read |
service/src/database_service.cpp |
722 | Replication apply path |
service/src/database_grpc_impl.cpp |
109, 187, 222, 307, 362, 592, 708, 1288, 1344, 1397 | Insert / Update / Patch / Find / Subscribe / similarity-search / filter values |
Backward compatibility: Wire and on-disk JSON bytes are unchanged. The swap is local to the parser implementation. A v1.10 binary must read v1.9.x snapshots/WAL byte-for-byte identically; tests verify this.
Why yyjson: ~3-5× faster than nlohmann's parser on the kind of documents Zoe stores (modest nesting, mostly strings + ints). License is MIT. Header + tiny static lib (~30 KB). Already packaged for Debian 13 (libyyjson-dev / libyyjson0).
Build infrastructure baseline (verified):
packaging/Dockerfile.base line 10 has the apt-get list; tag is smartbotic-db-build-base:debian13:v3 (we bump to v4).service/CMakeLists.txt uses pkg_check_modules for LZ4 and jemalloc — we follow the same pattern for yyjson.packaging/deb/templates/control.server Depends: line: append libyyjson0.service/src/json_parse.hpp — bridge helper declaration: nlohmann::json parse_to_nlohmann(std::string_view) + parse_to_nlohmann(const char*, size_t).service/src/json_parse.cpp — yyjson-backed implementation. The ONLY file that includes <yyjson.h>.tests/test_json_parse.cpp — unit tests for the bridge against nlohmann::json's own parser on the same inputs.tests/bench_json_parse.cpp — micro-benchmark, std::chrono based, run by hand to capture the parse-time delta on a Zoe-shape snapshot.packaging/Dockerfile.base — append libyyjson-dev to the apt-get install list, bump the base-image tag echoed at the bottom from v3 to v4.packaging/deb/templates/control.server — append , libyyjson0 to the Depends: line.service/CMakeLists.txt — add pkg_check_modules(yyjson REQUIRED yyjson) and link ${yyjson_LIBRARIES} + include ${yyjson_INCLUDE_DIRS}.tests/CMakeLists.txt — register test_json_parse and bench_json_parse.service/src/persistence/wal.cpp — 2 parse-site swaps.service/src/persistence/snapshot.cpp — 3 parse-site swaps.service/src/persistence/history_store.cpp — 1 parse-site swap.service/src/database_service.cpp — 1 parse-site swap.service/src/database_grpc_impl.cpp — 10 parse-site swaps.VERSION — bump 1.9.5 → 1.10.0.CLAUDE.md — add Key Features bullet documenting the yyjson swap.service/src/document.hpp — Document::data stays nlohmann::json for Phase A. Phase B is where this changes.client/ — client library does not parse JSON in any hot path; the proto bindings are already binary.proto/database.proto — wire format unchanged.Files:
packaging/Dockerfile.base:10-31packaging/deb/templates/control.server:18 (the Depends: line)service/CMakeLists.txt:7-10 (where pkg_check_modules for LZ4 is declared)Modify: service/CMakeLists.txt:59-90 (where target_link_libraries clauses live)
[ ] Step 1: Add libyyjson-dev to Dockerfile.base. Open packaging/Dockerfile.base and append libyyjson-dev \ to the apt-get install list (right after libfmt-dev \). Bump the echo at the bottom from v3 to v4:
libfmt-dev \
libyyjson-dev \
dpkg-dev \
&& rm -rf /var/lib/apt/lists/*
# ... existing ENV/WORKDIR lines unchanged ...
RUN echo "smartbotic-db-build-base:debian13:v4" > /etc/smartbotic-db-build-base
[ ] Step 2: Rebuild the Docker base image.
Run: ./packaging/build.sh --rebuild-base
Expected: builds image tagged smartbotic-db-build-base:debian13:v4 with no errors.
[ ] Step 3: Add libyyjson0 to runtime deb dependencies. In packaging/deb/templates/control.server, append , libyyjson0 to the Depends: line:
Depends: libsmartbotic-db-client (= {{VERSION}}), libssl3t64, liblz4-1, libsystemd0, libjemalloc2, libyyjson0
[ ] Step 4: Wire yyjson into CMake. In service/CMakeLists.txt, locate the find_package(PkgConfig) block (line 7) and add a pkg_check_modules call:
find_package(PkgConfig)
if(PkgConfig_FOUND)
pkg_check_modules(LZ4 QUIET liblz4)
pkg_check_modules(yyjson REQUIRED yyjson)
endif()
Then in the target_link_libraries / target_include_directories section, add after the jemalloc clause:
target_include_directories(smartbotic-database PRIVATE ${yyjson_INCLUDE_DIRS})
target_link_libraries(smartbotic-database PRIVATE ${yyjson_LIBRARIES})
Run: cmake -B build -G Ninja && cmake --build build -j$(nproc)
Expected: PASS. No new compile or link errors. yyjson is now linked but unused — that's fine for this task.
[ ] Step 6: Commit.
git add packaging/Dockerfile.base packaging/deb/templates/control.server service/CMakeLists.txt
git commit -m "build: add yyjson build + runtime dependency (phase A scaffolding)"
Files:
service/src/json_parse.hppservice/src/json_parse.cpptests/test_json_parse.cpptests/CMakeLists.txt (register the new test executable)Modify: service/CMakeLists.txt (add json_parse.cpp to the source list)
[ ] Step 1: Write the failing test. Create tests/test_json_parse.cpp:
#include <cassert>
#include <cstring>
#include <iostream>
#include <string>
#include <nlohmann/json.hpp>
#include "json_parse.hpp"
using nlohmann::json;
using smartbotic::db::parse_to_nlohmann;
namespace {
void check(bool cond, const char* msg) {
if (!cond) {
std::cerr << "FAIL: " << msg << "\n";
std::abort();
}
}
void test_empty_object() {
auto j = parse_to_nlohmann("{}");
check(j.is_object() && j.empty(), "empty object");
}
void test_empty_array() {
auto j = parse_to_nlohmann("[]");
check(j.is_array() && j.empty(), "empty array");
}
void test_primitives() {
check(parse_to_nlohmann("true").get<bool>() == true, "true");
check(parse_to_nlohmann("false").get<bool>() == false, "false");
check(parse_to_nlohmann("null").is_null(), "null");
check(parse_to_nlohmann("42").get<int64_t>() == 42, "int");
check(parse_to_nlohmann("-7").get<int64_t>() == -7, "negative int");
check(parse_to_nlohmann("3.14").get<double>() == 3.14, "double");
check(parse_to_nlohmann("\"hello\"").get<std::string>() == "hello", "string");
}
void test_nested() {
std::string s = R"({"a":1,"b":[2,3,{"c":"x"}],"d":null,"e":true})";
auto y = parse_to_nlohmann(s);
auto n = json::parse(s);
check(y == n, "nested equality with nlohmann");
}
void test_unicode_string() {
std::string s = R"({"name":"Árvíztűrő tükörfúrógép","emoji":"🚀"})";
auto y = parse_to_nlohmann(s);
auto n = json::parse(s);
check(y == n, "unicode equality");
}
void test_large_int() {
auto j = parse_to_nlohmann("9223372036854775807");
check(j.get<int64_t>() == 9223372036854775807LL, "int64 max");
}
void test_string_view_overload() {
std::string s = "{\"k\":1}";
auto j = parse_to_nlohmann(std::string_view(s));
check(j["k"].get<int>() == 1, "string_view overload");
}
void test_invalid_json_throws() {
bool threw = false;
try {
(void)parse_to_nlohmann("{not json");
} catch (const std::exception&) {
threw = true;
}
check(threw, "invalid json throws");
}
void test_corpus_equivalence() {
// Sample of doc shapes the production service sees.
const char* corpus[] = {
R"({"_id":"abc","name":"x","tags":["a","b"],"count":0})",
R"({"_id":"xyz","_vector":[0.1,-0.2,0.3,0.4],"meta":{"k":"v"}})",
R"({"_id":"e1","_event":"call.ended","at":1731628800123})",
R"([])",
R"([1,2,3,4,5,6,7,8,9,10])",
R"({"deeply":{"nested":{"object":{"is":{"fine":true}}}}})",
};
for (const char* s : corpus) {
check(parse_to_nlohmann(s) == json::parse(s), s);
}
}
} // namespace
int main() {
test_empty_object();
test_empty_array();
test_primitives();
test_nested();
test_unicode_string();
test_large_int();
test_string_view_overload();
test_invalid_json_throws();
test_corpus_equivalence();
std::cout << "test_json_parse: all passed\n";
return 0;
}
[ ] Step 2: Register the test in CMake. In tests/CMakeLists.txt, add (matching the style of test_vector_storage):
add_executable(test_json_parse test_json_parse.cpp ${CMAKE_SOURCE_DIR}/service/src/json_parse.cpp)
target_include_directories(test_json_parse PRIVATE
${CMAKE_SOURCE_DIR}/service/src
${yyjson_INCLUDE_DIRS})
target_link_libraries(test_json_parse PRIVATE nlohmann_json::nlohmann_json ${yyjson_LIBRARIES})
add_test(NAME test_json_parse COMMAND test_json_parse)
[ ] Step 3: Add json_parse.cpp to the service binary's source list in service/CMakeLists.txt wherever the service source list is declared (search for any other src/ .cpp file added to smartbotic-database and add src/json_parse.cpp alongside it).
[ ] Step 4: Run test to verify it fails to link (helper not yet implemented).
Run: cmake --build build -j$(nproc) --target test_json_parse
Expected: FAIL with undefined reference to smartbotic::db::parse_to_nlohmann.
[ ] Step 5: Write the header. Create service/src/json_parse.hpp:
#pragma once
#include <string_view>
#include <nlohmann/json.hpp>
namespace smartbotic::db {
// Parses a JSON document using yyjson (read-only DOM) and materialises an
// nlohmann::json tree from it. Drop-in replacement for nlohmann::json::parse
// at hot paths where parse speed matters but we still want nlohmann semantics
// downstream. Throws nlohmann::json::parse_error on malformed input so existing
// catch sites keep working.
nlohmann::json parse_to_nlohmann(std::string_view bytes);
nlohmann::json parse_to_nlohmann(const char* data, size_t length);
} // namespace smartbotic::db
[ ] Step 6: Write the implementation. Create service/src/json_parse.cpp:
#include "json_parse.hpp"
#include <yyjson.h>
#include <stdexcept>
#include <string>
namespace smartbotic::db {
namespace {
nlohmann::json convert(yyjson_val* v) {
if (!v) {
return nullptr;
}
switch (yyjson_get_type(v)) {
case YYJSON_TYPE_NULL:
return nullptr;
case YYJSON_TYPE_BOOL:
return yyjson_get_bool(v);
case YYJSON_TYPE_NUM:
switch (yyjson_get_subtype(v)) {
case YYJSON_SUBTYPE_UINT:
return yyjson_get_uint(v);
case YYJSON_SUBTYPE_SINT:
return yyjson_get_sint(v);
case YYJSON_SUBTYPE_REAL:
default:
return yyjson_get_real(v);
}
case YYJSON_TYPE_STR:
return std::string(yyjson_get_str(v), yyjson_get_len(v));
case YYJSON_TYPE_ARR: {
nlohmann::json out = nlohmann::json::array();
size_t idx, max;
yyjson_val* elem;
yyjson_arr_foreach(v, idx, max, elem) {
out.push_back(convert(elem));
}
return out;
}
case YYJSON_TYPE_OBJ: {
nlohmann::json out = nlohmann::json::object();
size_t idx, max;
yyjson_val* key;
yyjson_val* val;
yyjson_obj_foreach(v, idx, max, key, val) {
out[std::string(yyjson_get_str(key), yyjson_get_len(key))] = convert(val);
}
return out;
}
default:
return nullptr;
}
}
} // namespace
nlohmann::json parse_to_nlohmann(const char* data, size_t length) {
if (!data || length == 0) {
throw nlohmann::json::parse_error::create(
101, 0, "empty input", nullptr);
}
yyjson_read_err err{};
yyjson_doc* doc = yyjson_read_opts(
const_cast<char*>(data), length,
YYJSON_READ_NOFLAG, nullptr, &err);
if (!doc) {
std::string msg = "yyjson parse error: ";
msg += err.msg ? err.msg : "unknown";
throw nlohmann::json::parse_error::create(
101, static_cast<size_t>(err.pos), msg, nullptr);
}
try {
nlohmann::json out = convert(yyjson_doc_get_root(doc));
yyjson_doc_free(doc);
return out;
} catch (...) {
yyjson_doc_free(doc);
throw;
}
}
nlohmann::json parse_to_nlohmann(std::string_view bytes) {
return parse_to_nlohmann(bytes.data(), bytes.size());
}
} // namespace smartbotic::db
[ ] Step 7: Run the test.
Run: cmake --build build -j$(nproc) --target test_json_parse && ./build/tests/test_json_parse
Expected: test_json_parse: all passed.
[ ] Step 8: Commit.
git add service/src/json_parse.hpp service/src/json_parse.cpp tests/test_json_parse.cpp tests/CMakeLists.txt service/CMakeLists.txt
git commit -m "feat(json): add yyjson-backed parse_to_nlohmann bridge helper"
Files:
Modify: service/src/persistence/wal.cpp:233,249
[ ] Step 1: Add the include. At the top of service/src/persistence/wal.cpp, near the existing #include <nlohmann/json.hpp>, add:
#include "../json_parse.hpp"
[ ] Step 2: Swap the two parse calls. Replace nlohmann::json::parse(dataStr) at line 233 with smartbotic::db::parse_to_nlohmann(dataStr), and similarly at line 249 for nlohmann::json::parse(optsStr).
[ ] Step 3: Build the service.
Run: cmake --build build -j$(nproc) --target smartbotic-database
Expected: PASS.
Run: ./build/tests/test_snapshot_durability
Expected: PASS. This test exercises WAL replay against real on-disk bytes — proves the swap is binary-equivalent.
Run: cd tests/load_test && ./load_test_crash_insert && ./load_test_crash_verify
Expected: both report success (no document loss, no checksum mismatch).
[ ] Step 6: Commit.
git add service/src/persistence/wal.cpp
git commit -m "perf(wal): use yyjson for replay parses (phase A)"
Files:
Modify: service/src/persistence/snapshot.cpp:820,929,963
[ ] Step 1: Add the include. At the top of service/src/persistence/snapshot.cpp, add:
#include "../json_parse.hpp"
[ ] Step 2: Swap all three sites.
nlohmann::json::parse(optionsJson) → smartbotic::db::parse_to_nlohmann(optionsJson)nlohmann::json::parse(docJson) → smartbotic::db::parse_to_nlohmann(docJson)nlohmann::json::parse(verJson) → smartbotic::db::parse_to_nlohmann(verJson)[ ] Step 3: Build.
Run: cmake --build build -j$(nproc) --target smartbotic-database
Expected: PASS.
Run: ./build/tests/test_snapshot_durability
Expected: PASS. Covers atomic snapshot write + read-back equivalence.
Run: ./build/tests/test_vector_storage && ./build/tests/test_views
Expected: both PASS.
[ ] Step 6: Commit.
git add service/src/persistence/snapshot.cpp
git commit -m "perf(snapshot): use yyjson for deserialize parses (phase A)"
Files:
Modify: service/src/persistence/history_store.cpp:124
[ ] Step 1: Add the include + swap the call. Add #include "../json_parse.hpp" near the existing nlohmann include, then change line 124 from nlohmann::json::parse(verJson) to smartbotic::db::parse_to_nlohmann(verJson).
[ ] Step 2: Build.
Run: cmake --build build -j$(nproc) --target smartbotic-database
Expected: PASS.
test_snapshot_durability and test_eviction (eviction restores from history when a doc is evicted-then-read).Run: ./build/tests/test_snapshot_durability && ./build/tests/test_eviction
Expected: both PASS.
[ ] Step 4: Commit.
git add service/src/persistence/history_store.cpp
git commit -m "perf(history): use yyjson for hlog version-read parse (phase A)"
Files:
Modify: service/src/database_service.cpp:722
[ ] Step 1: Add the include + swap. Add #include "json_parse.hpp" (same directory; relative path is just "json_parse.hpp"). Change line 722 from nlohmann::json::parse(entry.data()) to smartbotic::db::parse_to_nlohmann(entry.data()).
[ ] Step 2: Build.
Run: cmake --build build -j$(nproc) --target smartbotic-database
Expected: PASS.
Run: cd tests/load_test && ./load_test_replication
Expected: leader and follower converge on identical state; no parse errors logged.
Run: cd tests/load_test && ./load_test_replica_eviction
Expected: PASS.
[ ] Step 5: Commit.
git add service/src/database_service.cpp
git commit -m "perf(replication): use yyjson for replicated entry parse (phase A)"
Files:
service/src/database_grpc_impl.cpp:109,187,222,307,362,592,708,1288,1344,1397All ten sites are identical-shape rewrites: nlohmann::json::parse(<expr>) → smartbotic::db::parse_to_nlohmann(<expr>). Listed individually so the reviewer can scan the diff per-RPC.
[ ] Step 1: Add the include at the top of service/src/database_grpc_impl.cpp:
#include "json_parse.hpp"
[ ] Step 2: Swap line 109 (Insert handler — request data parse).
[ ] Step 3: Swap line 187 (Insert handler — proto doc data parse).
[ ] Step 4: Swap line 222 (Update handler — request data parse).
[ ] Step 5: Swap line 307 (Patch handler — patch body parse).
[ ] Step 6: Swap line 362 (Find handler — filter-value path).
[ ] Step 7: Swap line 592 (Subscribe handler — initial-state parse).
[ ] Step 8: Swap line 708 (Similarity-search handler — proto doc parse).
[ ] Step 9: Swap line 1288 (Generic doc-from-proto parse helper).
[ ] Step 10: Swap line 1344 (Filter .value parse).
[ ] Step 11: Swap line 1397 (Nested filter .value parse).
[ ] Step 12: Build.
Run: cmake --build build -j$(nproc) --target smartbotic-database
Expected: PASS.
Run: cd tests/load_test && ./load_test && ./load_test_mixed && ./load_test_integrity && ./load_test_pinned
Expected: all PASS, no parse errors logged.
Run: ./build/tests/test_views && ./build/tests/test_timestamp_precision
Expected: PASS.
[ ] Step 15: Commit.
git add service/src/database_grpc_impl.cpp
git commit -m "perf(grpc): use yyjson for handler body parses (phase A)"
Files:
tests/bench_json_parse.cpptests/CMakeLists.txtEstablish a number for the operator: how much faster is yyjson on Zoe-shape data? Not a regression gate — informational, captured in the commit message.
[ ] Step 1: Write the benchmark. Create tests/bench_json_parse.cpp:
#include <chrono>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
#include "json_parse.hpp"
int main(int argc, char** argv) {
if (argc < 2) {
std::cerr << "usage: bench_json_parse <path-to-json-corpus>\n"
<< " corpus = one JSON document per line\n";
return 1;
}
std::ifstream f(argv[1]);
std::vector<std::string> docs;
std::string line;
while (std::getline(f, line)) {
if (!line.empty()) docs.push_back(line);
}
if (docs.empty()) {
std::cerr << "empty corpus\n";
return 1;
}
constexpr int kIters = 5;
auto bench = [&](const char* name, auto fn) {
double best = 1e18;
for (int i = 0; i < kIters; ++i) {
auto t0 = std::chrono::steady_clock::now();
size_t sink = 0;
for (const auto& s : docs) {
auto j = fn(s);
sink += j.size();
}
auto t1 = std::chrono::steady_clock::now();
double ms = std::chrono::duration<double, std::milli>(t1 - t0).count();
best = std::min(best, ms);
std::cout << name << " iter " << i << ": " << ms << " ms (sink=" << sink << ")\n";
}
return best;
};
double nl = bench("nlohmann", [](const std::string& s) {
return nlohmann::json::parse(s);
});
double yy = bench("yyjson ", [](const std::string& s) {
return smartbotic::db::parse_to_nlohmann(s);
});
std::cout << "\nbest nlohmann: " << nl << " ms\n";
std::cout << "best yyjson: " << yy << " ms\n";
std::cout << "speedup: " << (nl / yy) << "x\n";
return 0;
}
[ ] Step 2: Register the benchmark in tests/CMakeLists.txt. Match the test_json_parse pattern but DO NOT add_test it — this is a manual bench, not a regression gate:
add_executable(bench_json_parse bench_json_parse.cpp ${CMAKE_SOURCE_DIR}/service/src/json_parse.cpp)
target_include_directories(bench_json_parse PRIVATE
${CMAKE_SOURCE_DIR}/service/src
${yyjson_INCLUDE_DIRS})
target_link_libraries(bench_json_parse PRIVATE nlohmann_json::nlohmann_json ${yyjson_LIBRARIES})
[ ] Step 3: Build the bench binary.
Run: cmake --build build -j$(nproc) --target bench_json_parse
Expected: PASS.
[ ] Step 4: Capture a Zoe-shape corpus. Use any existing seeded test data — tests/load_test/ writes JSON via the client; dump 10k docs to a file (one per line) using smartbotic-db-cli export <collection> against a populated local instance. If no instance is handy, generate synthetic shapes:
python3 -c "
import json, random, sys
for i in range(10000):
print(json.dumps({
'_id': f'doc{i}',
'name': f'name-{i}',
'tags': [f't{j}' for j in range(random.randint(0,5))],
'meta': {'k': i, 'flag': bool(i%2)},
'vec': [random.random() for _ in range(8)],
}))
" > /tmp/bench-corpus.jsonl
[ ] Step 5: Run the benchmark.
Run: ./build/tests/bench_json_parse /tmp/bench-corpus.jsonl
Expected: yyjson at least 2× faster than nlohmann on this corpus. Record the numbers.
[ ] Step 6: Commit the bench source with measured numbers in the commit body.
git add tests/bench_json_parse.cpp tests/CMakeLists.txt
git commit -m "$(cat <<'EOF'
test: add bench_json_parse for phase A parse-time delta
Measured on 10k synthetic Zoe-shape docs:
nlohmann: <N> ms
yyjson: <N> ms
speedup: <N>x
EOF
)"
Files:
VERSIONModify: CLAUDE.md
[ ] Step 1: Bump VERSION. Replace 1.9.5 with 1.10.0 in /data/smartbotic-database/VERSION.
[ ] Step 2: Add a CLAUDE.md bullet under "Key Features" (insert after the auto-backup bullet):
yyjson hot-path parsing (v1.10.0+) — every JSON parse on WAL replay, snapshot deserialize, history reads, gRPC request bodies, and replication apply now goes through
smartbotic::db::parse_to_nlohmann(yyjson-backed). In-memory representation staysnlohmann::jsonuntil Phase B. Wire and on-disk JSON bytes unchanged.
Run: ./packaging/build.sh --local
Expected: produces dist/smartbotic-database_1.10.0-1_amd64.deb and the three sibling debs.
Run: dpkg-deb -I dist/smartbotic-database_1.10.0-1_amd64.deb | grep -i depends
Expected: Depends: line contains libyyjson0.
Run: dpkg-deb -x dist/smartbotic-database_1.10.0-1_amd64.deb /tmp/deb-check && ldd /tmp/deb-check/usr/sbin/smartbotic-database | grep -i yyjson
Expected: libyyjson.so.0 => /usr/lib/x86_64-linux-gnu/libyyjson.so.0.
[ ] Step 6: Commit.
git add VERSION CLAUDE.md
git commit -m "release(v1.10.0): yyjson hot-path swap (phase A)"
Files: none (process step)
[ ] Step 1: Push the branch. Operator decides timing — do NOT auto-push.
git push origin feat/v1.9.5-backup # branch name stays even though it now carries v1.10 work
[ ] Step 2: Operator decision on sync. ./packaging/build.sh --sync --suite trixie publishes to repository.smartbotics.ai. Per CLAUDE.md and prior pattern, this requires explicit operator authorization — do NOT run it unprompted.
[ ] Step 3: Zoe upgrade. Same gate — only on explicit operator command. v1.9.5's auto-backup preinst will fire on the upgrade and snapshot the data dir to /var/backups/smartbotic-database/pre-upgrade-*-from-1.9.5/, which is exactly the rollback safety net Phase A inherits from the v1.9.5 work.
Document::data is still nlohmann::json — yyjson is a parser only.nlohmann::json::dump() callsites are untouched. yyjson's writer is fine but parse is the hot path; writer swap can wait.--migrate-from-v1 mode in Phase A — v1.10 reads v1.9.x data dirs natively.nlohmann::json::parse callsites in the hot-path file list are gone (verified by rg -n 'nlohmann::json::parse' service/src returning zero hits, OR only hits in files explicitly listed as out-of-scope cold-path code).test_snapshot_durability, test_eviction, test_views, test_vector_storage, test_timestamp_precision, test_config_dropins.load_test, load_test_mixed, load_test_integrity, load_test_pinned, load_test_crash_insert/verify, load_test_replication, load_test_replica_eviction.bench_json_parse reports ≥ 2× speedup over nlohmann on Zoe-shape corpus.libyyjson0 dep, and ldd confirms the runtime link.None — Phase A is mechanical. Phase B's open question (binary format choice) lives in the Phase B plan.