# CLAUDE.md — smartbotic-vectorapi C++20 REST API fronting `smartbotic-database` (gRPC) for n8n + other clients. Stores RAG vectors + JSON, organized into **projects** with **multi-key per-project-grant auth**. React admin UI. Ships as Debian `.deb`s. ## Build / test / run ```bash # Backend + web UI (Release) cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_WEBUI=ON -S . cmake --build build -j"$(nproc)" # Tests (GoogleTest). Integration tests need a running smartbotic-database on localhost:9004; # they GTEST_SKIP() cleanly if it's unreachable. BUILD_WEBUI=OFF to skip the npm build during test cycles. cmake -B build -G Ninja -DBUILD_TESTS=ON -DBUILD_WEBUI=OFF -S . && cmake --build build -j"$(nproc)" ctest --test-dir build --output-on-failure # Run locally (use the repo's api/ for /openapi.json,/llms.txt,/docs and the built UI) SMARTBOTIC_VECTORAPI_KEY=dev ./build/src/smartbotic-vectorapi \ --config config/config.json --share-dir "$PWD/api" --webui-dir "$PWD/build/webui/dist" # Web UI only cd webui && npm install && npm run build # → webui/dist ; `npm run dev` for the :3000 proxy dev server ``` ## Architecture (where things live) - `src/` — the server. `vectorapi_core` static lib + `main.cpp`. Namespace `svapi`. Flat headers, quoted includes. - `db_gateway` (`qualify(project,coll)` + `DbGateway` over `smartbotic::database::Client`), `settings_store` (global settings **and per-project embedding overrides**, hot-reload, one subscribe stream for both), `project_settings` (`ProjectEmbeddingSettings` + the pure `resolveEmbedding()`), `outbound_http` (User-Agent + TLS defaults for every external call), `key_store` (API keys, hot-reload, bootstrap admin key), `collection_registry` (per-project metadata), `embeddings` (OpenAI), `auth` (sessions, bearer/cookie), `server` (ApiServer + auth helpers), `handlers/*` (one file per route group). - Auth model: pre-routing **authenticates** `/api/*` (401); each handler **authorizes** via `requireKey` → `requireAdmin` / `requireProjectAccess` (403). Keys are referenced by a stable **`id`** (not the secret). - `webui/` — React 19 + Vite + TS (strict, `erasableSyntaxOnly`) + Tailwind + Monaco. Cookie-session auth (`credentials:'include'`). Pages read `currentProject`/`admin` from `useAuthStore`; all calls go through `src/api/client.ts`. `pages/Settings.tsx` is a tab shell over `pages/settings/*` (Embeddings, Cache & performance, Access & security, Web UI are admin-only; the per-project embeddings tab is not, so `/settings` is **not** admin-gated in `App.tsx`). Shared controls live in `components/form.tsx`. - `api/` — `openapi.json` (3.1), `llms.txt` (llmstxt.org), `docs/` (Redoc page). Served at `/openapi.json`, `/llms.txt`, `/docs`. Shipped in the server `.deb`. **Keep these in sync when changing routes.** - `packaging/` — `build.sh` (`--local` + Docker trixie), `deb/create-debs.sh`, control templates, systemd unit, maintainer scripts. `docs/superpowers/` — design spec (esp. **Addendum A**) + the 3 implementation plans. ## Conventions - C++20, `nlohmann/json`, `spdlog`, cpp-httplib (FetchContent, OpenSSL on). Handlers `throw svapi::ApiError`; a global exception handler maps to `{error:{code,message}}` + HTTP status (`errors.hpp`: 400/401/403/404/422/503/500). - Filters: `field:op:value` (`op` ∈ eq/ne/gt/gte/lt/lte/in/contains/exists/regex/search). - **Embedding config precedence:** collection `embedding_model` pin → project override → global default; endpoint + API key are the project override when set, else global. Resolved by the pure `resolveEmbedding()` (`project_settings.hpp`) — unit-test precedence there, not through the handlers. - **Every outbound request to an external provider goes through `applyOutboundDefaults()`** (`outbound_http.hpp`): `User-Agent: smartbotic-vectorapi/` and nothing else, TLS verification on, redirects off. Never let cpp-httplib's default UA reach a provider. - TS: no `any`, no TS `enum`, no constructor parameter-property shorthand (tsconfig `erasableSyntaxOnly`). Iterating parsed JSON: bind to a named var first (range-for over `nlohmann::json::parse(...)[...]` dangles). - Commits: conventional prefixes (`feat`/`fix`/`build`/`docs`/`test`). Branch for feature work; `main` is pushed to `ssh://git.smartbotics.ai:10022/fszontagh/smartbotic-vectorapi.git`. ## Data model & runtime config - Bootstrap `config.json` is minimal: `log_level`, `http.{bind_address,port}`, `database.address`. Everything else lives in the DB and **hot-reloads** (subscribe + atomic snapshot swap). - Global service collections (DB `default` project, **no leading underscore** — see gotcha): `vectorapi_keys`, `vectorapi_settings`. The settings collection holds the global doc (`current`) **and** one `project_` doc per project carrying that project's embedding overrides (empty field = inherit; the body repeats `project` because the DB strips `id`). Per-project registry: `:vectorapi_collections`. The `vectorapi_` prefix is reserved (collection-create rejects `_` and `vectorapi_`). - First run with no keys generates an admin key (`projects:["*"], admin:true`) and logs it once; `SMARTBOTIC_VECTORAPI_KEY` seeds it, `OPENAI_API_KEY` seeds embeddings. ## ⚠️ Gotchas (hard-won — don't relearn these) - **DB v2.3.1: `_`-prefixed collections misroute when project-qualified.** Writes land in MemoryStore (mirror skips `_`-names) but reads of `default:_foo` route to LMDB → null. → service collections use **no leading underscore** (`vectorapi_*`). Suspected DB bug. - **DB v2.3.1: `dropProject` does not physically purge collection data.** A recycled project name is otherwise blocked by stale collections/registry → the project-delete handler drops the registry + collections first, and collection-create drops invisible orphans (after the registry "exists" check). - **DB client strips an `id` field from stored doc bodies.** So API keys store their stable id as `key_id` in the body (exposed as `id` externally); document handlers strip `id` before insert and use the separate id param. - **Debian 13 (trixie) gRPC cmake config is broken** unless `protobuf-compiler-grpc` + `libgrpc++-dev` + `libprotobuf-dev` are installed — `find_package(gRPC CONFIG)` (used transitively by `smartbotic-db-clientConfig.cmake`) fails on a `gRPCTargets.cmake` referencing missing plugin files. The Docker base installs them. - **Abseil ABI:** `smartbotic-database`/client debs must be built against the system's current Abseil; a stale build crash-loops on `libabsl_*.so.`. Rebuild the DB if it won't start. - **Client ABI pin:** the server deb is compiled against `DB_CLIENT_VERSION` (`packaging/Dockerfile.base`, default 2.11.3-1 = what prod runs). Every 2.x client shares one SONAME, so a deb built against a newer client installs fine and only segfaults on the next restart - keep the pin equal to the deploy target, and ship a DB upgrade and a vectorapi rebuild together. - **The apt repo drops superseded versions on publish**, so `pkg=` is uninstallable afterwards. Old `.deb`s survive in `$DEB_REPO_DIR/dist`; `build.sh` vendors the pinned client from there into `packaging/vendor/`, and a prod rollback needs them scp'd to the target first. - **Monaco** is bundled locally (`webui/src/monacoSetup.ts`) so the editor works offline — do not revert to the CDN loader. - systemd unit is `Type=notify`; the binary must be built with `libsystemd` (it is, when `libsystemd-dev` is present) or change to `Type=exec`. ## Deploy / publish ```bash ./packaging/build.sh # Debian-13 .debs → dist/debian13/ ./packaging/build.sh --repo --suite trixie --sync # + publish to repository.smartbotics.ai (outward-facing!) ``` Publishing uploads to the live apt repo — only with explicit confirmation. The deb-repo tooling is `/data/dev/smartbotics/smartbotics-deb-repo/scripts/` (override with `DEB_REPO_DIR`). **Release packages always come from the Docker path.** The dev host (zeus) runs Void Linux, so `--local` there compiles, runs the tests and leaves a runnable binary in `build/src/` but refuses to build `.deb`s (and refuses `--install`/`--repo`/`--sync`) - a Void-linked package would not install on Debian 13. `--local` still packages normally on a Debian-family host.