feat(cpp): SQLite integration and gaia::Database - #2816
Conversation
The C++ framework had no database — SessionStore and AllowedToolsStore persist as loose JSON files, and there was nothing for agent memory, scratchpad tables, or the email agent's state store to build on. This adds one. SQLite ships as the vendored amalgamation under cpp/third_party/sqlite/, compiled straight into gaia_core with SQLITE_ENABLE_FTS5. There is deliberately no find_package fallback: a distro SQLite may lack FTS5 or carry different compile-time defaults, and that only surfaces at query time on one platform. Vendoring makes all three platform builds identical. gaia/database.h exposes RAII types over it — Database (WAL + busy_timeout applied at open, serialized threading mode), Statement with typed binds and column accessors, a Transaction scope guard that rolls back unless committed, and an ordered migration helper mirroring MemoryStore._migrate_schema_locked. The migration version lives in PRAGMA user_version so no table is imposed on the caller's schema, and each step runs inside a transaction that also stamps the new version — a step that throws rolls back completely and the stored version does not advance. Every failure raises DatabaseError carrying the SQLite message plus the database path and offending statement. Nothing is swallowed. Ships with no consumer; Phase 5 and the email milestone build on it. Notes: - The version file is SQLITE_VERSION.txt, not VERSION: the directory is on the include path, and on a case-insensitive filesystem a file named VERSION is picked up as libc++'s <version> header. - The amalgamation is an OBJECT library consumed via $<TARGET_OBJECTS:> rather than target_link_libraries, keeping it out of gaia_core's link interface so install(EXPORT) has nothing extra to resolve. - .gitattributes marks the vendored sources linguist-vendored and -diff so a 9.5 MB amalgamation stays out of every diff.
|
Verdict: Approve ✅ This adds a vendored SQLite amalgamation and a RAII The one thing worth a glance before merge is minor and non-blocking: the two security-hardening connection settings (disabling extension loading, enabling defensive mode) don't check their return code, while the adjacent Real-world evidenceN/A — this is a C++ SDK library, not a Python Agent-UI / CLI / MCP / HTTP surface, so none of the rubric's evidence surfaces apply. The appropriate proof for this layer is 🔍 Technical details🟢 Minor — unchecked return on the two hardening
|
The failing
|
…xe (amd#2818) Every `cpp/**` PR has been failing the `C++ Integration Tests (STX)` check before it compiles anything — amd#2807, amd#2809 and amd#2816 are all red for a reason unrelated to their diffs. The self-hosted runner cached CMake under `$env:TEMP` and re-downloaded it only when `bin\cmake.exe` was missing; Windows Temp cleanup deleted `share\cmake-3.31\Modules` and left `bin\`, so the job kept trusting a CMake that cannot resolve `CMAKE_ROOT` and every run died the same way until someone cleared Temp by hand. Now each candidate toolchain is probed for the thing the build actually depends on, a failed probe falls through to a clean re-download, and tools live in the runner tool cache instead of a directory the OS sweeps. One measurement drove the design and is worth flagging for review: **exit codes cannot detect this failure.** A CMake missing its Modules tree prints `Could not find CMAKE_ROOT` to stderr and still exits 0 — for `--version` and for `--help-module-list` (measured on 4.4.2). So the issue's suggested `cmake --version` exit-0 check would not have caught it on its own; validity requires the Modules tree on disk *and* a probe that does not report a broken root. The stale `%TEMP%\cmake` tree on the runner is now inert — nothing reads it — so no manual cleanup is needed to make this work; deleting it just reclaims disk. Closes amd#2817 ## Test plan - [ ] `pwsh -File .github/scripts/tests/CppBuildTools.Tests.ps1` passes (21/21). It asserts the exact regression: `bin/cmake.exe` present + `share/` absent reports **invalid**, both present reports **valid**, and includes negative controls showing the old `Test-Path cmake.exe` check and an exit-code-only check would both have accepted the broken install. - [ ] New `C++ toolchain script tests` job is green (parse-checks every `.github/scripts/*.ps1`, then runs the unit tests). - [ ] `C++ Integration Tests (STX)` on this PR gets past `Ensure C++ build tools are available` and reaches compilation. The step log should name which CMake it accepted and, if it rejected one, why. - [ ] Reproduce the root cause on any machine: copy a `cmake` binary alone into an empty directory and run `--version` — it prints the `CMAKE_ROOT` error and exits 0. - [ ] After merge, re-run CI on amd#2807, amd#2809 and amd#2816 with no changes to their diffs and confirm the STX check goes green.
The C++ framework had no database.
SessionStoreandAllowedToolsStorepersist as loose JSON files, so agent memory, scratchpad tables, and the email agent's state store had nothing to build on — and no C++ agent could do full-text search at all. This lands SQLite as a first-class part ofgaia_core:#include <gaia/database.h>and you get an RAII connection, prepared statements, transactions, ordered schema migrations, and FTS5, with no package to install and no system SQLite to find. It ships with no consumer by design — it is the foundation Phase 5 and the email milestone build on.The amalgamation is vendored rather than fetched, deliberately: a distro SQLite may lack FTS5 or carry different compile-time defaults, and that only surfaces at query time on one platform. Vendoring makes Windows, Linux, and macOS compile byte-identical sources with identical flags. Because a missing
SQLITE_ENABLE_FTS5builds fine and fails only when someone runs a query, three tests exist purely to catch that — verified by building with the flag removed: the build still succeeded and all three failed withno such module: fts5.CI build-time impact (issue asks for it) — clang -O3, Apple M4; the delta is one extra translation unit:
-j1), wholecpp/treesqlite3.calone (best of 3)database.cpp+test_database.cpp-j4, runner width)Incremental builds are unaffected —
sqlite3.conly recompiles when the amalgamation or its flags change. Repo grows 10.2 MB in the working tree (~2.6 MB compressed in git). One-time cost worth flagging: the FetchContent cache key ishashFiles('cpp/CMakeLists.txt'), which this PR changes, so the first run on each OS re-fetches json/httplib/ftxui/gtest.Three things a reviewer may want to know the reasoning for: the version file is
SQLITE_VERSION.txtrather thanVERSIONbecause the directory is on the include path and a case-insensitive filesystem resolves#include <version>to it, breaking every TU ingaia_core(this bit during development); the amalgamation is anOBJECTlibrary consumed via$<TARGET_OBJECTS:>rather thantarget_link_librariesso it stays out ofgaia_core's link interface andinstall(EXPORT)has nothing extra to resolve; and.gitattributesmarks the vendored sources-diffso 9.5 MB stays out of every diff (the reviewable diff is 2,554 lines).Closes #2793
Test plan
cmake -S cpp -B cpp/build -DGAIA_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release && cmake --build cpp/build --parallel && ctest --test-dir cpp/build— 519/519 pass (463 pre-existing + 56 new), zero new warningsMATCH,bm25()ranking, prefix queries, and update/delete reindexingSQLITE_ENABLE_FTS5removed: build still succeeds, all 3 FTS5 tests fail withno such module: fts5IMMEDIATE, and nested savepointsbusy_timeoutwith a concurrent writer thread — the waiter blocks ~300 ms and succeeds; counterpart test withbusyTimeoutMs = 0fails immediately, proving the timeout did the workcmake --installthen afind_package(gaia_core)consumer that opens a database, creates an FTS5 table, and queries it — links and runs (sqlite=3.53.4 fts5=1 hits=1) withoutsqlite3.hon its include pathBUILD_SHARED_LIBS=ONshared-library build links cleanly628a44cf…); both checksums recorded inSQLITE_VERSION.txtsqlite3_open_v2succeeds —leaksreports 0; negative control with the fix removed reports 61 leaks / 161,680 bytes