From c97888dc1eb1d58203f3ebbcb91ff5c7bcdf128f Mon Sep 17 00:00:00 2001 From: caioopra Date: Sat, 18 Apr 2026 00:41:36 -0300 Subject: [PATCH 01/11] temp: docs for testing phase 3 --- docs/testing_phase3.md | 62 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 docs/testing_phase3.md diff --git a/docs/testing_phase3.md b/docs/testing_phase3.md new file mode 100644 index 0000000..0ae1a88 --- /dev/null +++ b/docs/testing_phase3.md @@ -0,0 +1,62 @@ +# Testing Phase 3 — Manual QA Guide + +Step-by-step guide to manually test the Phase 3 admin console, governance, and observability features locally. + +## Prerequisites + +- Docker running (for PostgreSQL) +- No LLM API key needed for admin console (only for chat/cost features) + +## Step 1: Promote your user to admin + +```bash +source backend/.env +psql "$DATABASE_URL" -c "UPDATE users SET role = 'admin' WHERE email = 'test@test.com';" +``` + +Current dev DB has one user: `test@test.com` with role `user`. No promote CLI exists — use the SQL directly. + +## Step 2: Start the app + +```bash +make dev +``` + +Backend on `:3000`, frontend on `:5173`. + +## Step 3: Log in and visit admin + +1. Go to `http://localhost:5173`, log in as `test@test.com` +2. Navigate to `http://localhost:5173/admin` + +## What you'll see + +| Page | What's there | +|------|-------------| +| **Dashboard** | 4 metric cards: Total Users, Monthly Cost, Active Provider, Chat Status | +| **Providers** | LLM settings form (default provider, Gemini/Claude models). Save requires password re-entry | +| **Users** | User table with "Set Rate Limit" action (also requires password re-entry) | +| **Audit Log** | Timeline of admin actions + auth events. Filter by action prefix, cursor pagination | + +## Key features to test + +| Feature | How | +|---------|-----| +| **Kill-switch** | Toggle on Dashboard → password modal → confirm → chat disabled system-wide | +| **Step-up auth** | Change any setting on Providers → password prompt before save | +| **Audit trail** | After any action, check Audit page — everything is logged | +| **Budget check** | Send chat messages (needs LLM API key) → token usage tracked per message | +| **Rate limiting** | Set rate limit on a user from Users page | +| **Role gating** | Log in as a non-admin user → `/admin` redirects to `/` | + +## What needs an LLM API key + +Chat features (token tracking, budget enforcement, cost metrics on dashboard) require `LLM_GEMINI_API_KEY` or `LLM_CLAUDE_API_KEY` in `.env`. Without them chat returns 503. The admin console itself works fully without keys. + +## Quick start one-liner + +```bash +source backend/.env && psql "$DATABASE_URL" -c "UPDATE users SET role = 'admin' WHERE email = 'test@test.com';" && make dev +``` + +Then open `http://localhost:5173/admin`. From 5440e4785129fccf5633215d87c5ba1b6e5dd7de Mon Sep 17 00:00:00 2001 From: Caio Pra Silva Date: Thu, 23 Apr 2026 09:55:08 -0300 Subject: [PATCH 02/11] cleanup: removes info about unused DB for tests --- .claude/agents/infra.md | 3 +-- .env.example | 1 - .github/workflows/ci.yml | 6 ------ docker-compose.yml | 14 -------------- 4 files changed, 1 insertion(+), 23 deletions(-) diff --git a/.claude/agents/infra.md b/.claude/agents/infra.md index 8af63bb..1577d22 100644 --- a/.claude/agents/infra.md +++ b/.claude/agents/infra.md @@ -32,8 +32,7 @@ Manage deployment configuration, containerization, CI/CD pipelines, and local de - **docker-compose.yml:** - `app` — Rust backend (with hot reload via cargo-watch in dev) - - `postgres` — PostgreSQL 16 for development - - `postgres-test` — separate PostgreSQL instance for integration tests + - `postgres` — PostgreSQL 16 for development (tests reuse this server; `#[sqlx::test]` spins up per-test throwaway DBs) - Volume mounts for persistent data - Environment variables from `.env` diff --git a/.env.example b/.env.example index 714a363..676e6fb 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,5 @@ # Database DATABASE_URL=postgres://planner:planner@localhost:5434/planner -DATABASE_TEST_URL=postgres://planner:planner@localhost:5433/planner_test # Auth JWT_SECRET=change-me-in-production diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 32f68c9..dffdb20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,6 @@ on: env: CARGO_TERM_COLOR: always DATABASE_URL: postgres://planner:planner@localhost:5432/planner - DATABASE_TEST_URL: postgres://planner:planner@localhost:5432/planner_test SQLX_OFFLINE: "true" jobs: @@ -46,10 +45,6 @@ jobs: cache-on-failure: true cache-all-crates: true - - name: Create test database - run: | - PGPASSWORD=planner psql -h localhost -U planner -d planner -c "CREATE DATABASE planner_test;" - - name: Check formatting working-directory: backend run: cargo fmt -- --check @@ -76,7 +71,6 @@ jobs: run: cargo test env: DATABASE_URL: postgres://planner:planner@localhost:5432/planner - DATABASE_TEST_URL: postgres://planner:planner@localhost:5432/planner_test frontend: name: Frontend (React) diff --git a/docker-compose.yml b/docker-compose.yml index c6e2db5..4c750d8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,19 +15,5 @@ services: timeout: 5s retries: 5 - postgres-test: - image: postgres:16-alpine - environment: - POSTGRES_USER: planner - POSTGRES_PASSWORD: planner - POSTGRES_DB: planner_test - ports: - - "5433:5432" - healthcheck: - test: ["CMD-SHELL", "pg_isready -U planner"] - interval: 5s - timeout: 5s - retries: 5 - volumes: pgdata: From 7d5f1d577defe0e3aaccb37e4c59028e5e68ecaf Mon Sep 17 00:00:00 2001 From: caioopra Date: Thu, 23 Apr 2026 11:31:48 -0300 Subject: [PATCH 03/11] fix: ensure admin headings are readable on dark background --- frontend/src/index.css | 5 +++++ frontend/src/pages/admin/AdminProviders.jsx | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/frontend/src/index.css b/frontend/src/index.css index 02c46ee..e081f53 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,5 +1,10 @@ @import "tailwindcss"; +body { + color: #eeedf5; + background-color: #08060f; +} + @theme { /* Surface depth tokens */ --color-base: #08060f; diff --git a/frontend/src/pages/admin/AdminProviders.jsx b/frontend/src/pages/admin/AdminProviders.jsx index ca5131a..29c1e51 100644 --- a/frontend/src/pages/admin/AdminProviders.jsx +++ b/frontend/src/pages/admin/AdminProviders.jsx @@ -132,8 +132,8 @@ export default function AdminProviders() { {/* Chat kill-switch */}

Chat Feature

@@ -143,8 +143,8 @@ export default function AdminProviders() { {/* LLM settings form */}

LLM Configuration

From 7d65ce4a7f97c6d0c1eacbc8459202a336a6a9fc Mon Sep 17 00:00:00 2001 From: caioopra Date: Thu, 23 Apr 2026 13:44:06 -0300 Subject: [PATCH 04/11] fix(admin): read correct llm_default_provider key on dashboard --- frontend/src/pages/admin/AdminDashboard.jsx | 2 +- frontend/src/pages/admin/AdminDashboard.test.jsx | 2 +- frontend/src/test/mocks/handlers.js | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/admin/AdminDashboard.jsx b/frontend/src/pages/admin/AdminDashboard.jsx index ab55bf6..c7aee25 100644 --- a/frontend/src/pages/admin/AdminDashboard.jsx +++ b/frontend/src/pages/admin/AdminDashboard.jsx @@ -13,7 +13,7 @@ function sumCost(metrics) { function activeProvider(settings) { if (!settings) return "—"; - const entry = settings.find((s) => s.key === "active_provider"); + const entry = settings.find((s) => s.key === "llm_default_provider"); return entry ? entry.value : "—"; } diff --git a/frontend/src/pages/admin/AdminDashboard.test.jsx b/frontend/src/pages/admin/AdminDashboard.test.jsx index d252036..a7c803b 100644 --- a/frontend/src/pages/admin/AdminDashboard.test.jsx +++ b/frontend/src/pages/admin/AdminDashboard.test.jsx @@ -74,7 +74,7 @@ describe("AdminDashboard", () => { it("displays the active provider from mock settings", async () => { renderDashboard(); - // active_provider setting is "gemini" + // llm_default_provider setting is "gemini" expect(await screen.findByText("gemini")).toBeInTheDocument(); }); diff --git a/frontend/src/test/mocks/handlers.js b/frontend/src/test/mocks/handlers.js index 554d446..51638f6 100644 --- a/frontend/src/test/mocks/handlers.js +++ b/frontend/src/test/mocks/handlers.js @@ -30,7 +30,7 @@ let mockUserRole = "user"; let mockSettings = [ { - key: "active_provider", + key: "llm_default_provider", value: "gemini", updated_at: "2026-01-01T00:00:00Z", }, @@ -93,7 +93,7 @@ const mockAuditLog = [ actor_email: "admin@test.com", action: "setting.update", target_type: "setting", - target_id: "active_provider", + target_id: "llm_default_provider", payload: { old: "claude", new: "gemini" }, created_at: "2026-04-01T12:00:00Z", }, @@ -170,7 +170,7 @@ export function resetMockState() { mockUserRole = "user"; mockSettings = [ { - key: "active_provider", + key: "llm_default_provider", value: "gemini", updated_at: "2026-01-01T00:00:00Z", }, From cba71542bd0737a493c22f5607b567c318a2c572 Mon Sep 17 00:00:00 2001 From: caioopra Date: Thu, 23 Apr 2026 13:47:38 -0300 Subject: [PATCH 05/11] feat(admin): suggest commom model IDs in provider settings --- frontend/src/pages/admin/AdminProviders.jsx | 48 ++++++++++++++++----- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/frontend/src/pages/admin/AdminProviders.jsx b/frontend/src/pages/admin/AdminProviders.jsx index 29c1e51..a19532b 100644 --- a/frontend/src/pages/admin/AdminProviders.jsx +++ b/frontend/src/pages/admin/AdminProviders.jsx @@ -15,13 +15,29 @@ const LLM_SETTINGS = [ key: "llm_gemini_model", label: "Gemini Model", type: "text", - placeholder: "e.g. gemini-2.0-flash", + placeholder: "e.g. gemini-2.5-flash-preview-05-20", + suggestions: [ + "gemini-2.5-pro-preview-05-06", + "gemini-2.5-flash-preview-05-20", + "gemini-2.0-flash", + "gemini-2.0-flash-exp", + "gemini-1.5-pro", + "gemini-1.5-flash", + ], }, { key: "llm_claude_model", label: "Claude Model", type: "text", - placeholder: "e.g. claude-3-5-haiku-20241022", + placeholder: "e.g. claude-sonnet-4-20250514", + suggestions: [ + "claude-opus-4-7", + "claude-sonnet-4-6", + "claude-haiku-4-5-20251001", + "claude-sonnet-4-20250514", + "claude-3-5-sonnet-20241022", + "claude-3-5-haiku-20241022", + ], }, ]; @@ -152,7 +168,7 @@ export default function AdminProviders() {
{LLM_SETTINGS.map( - ({ key, label, type, options, placeholder }) => ( + ({ key, label, type, options, placeholder, suggestions }) => (
), From 4224e0a5d613ab6e7d2a092003699707c4071097 Mon Sep 17 00:00:00 2001 From: caioopra Date: Thu, 23 Apr 2026 13:51:27 -0300 Subject: [PATCH 06/11] test(frontend): add localStorage shim for Zustand persist in jsdom env --- frontend/src/test/setup.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/frontend/src/test/setup.js b/frontend/src/test/setup.js index 4894d9f..5c5f34c 100644 --- a/frontend/src/test/setup.js +++ b/frontend/src/test/setup.js @@ -3,6 +3,37 @@ import { afterAll, afterEach, beforeAll } from "vitest"; import { server } from "./mocks/server"; import { resetMockState } from "./mocks/handlers"; +function createStorageShim() { + const store = new Map(); + return { + getItem: (key) => (store.has(key) ? store.get(key) : null), + setItem: (key, value) => { + store.set(String(key), String(value)); + }, + removeItem: (key) => { + store.delete(key); + }, + clear: () => { + store.clear(); + }, + key: (i) => Array.from(store.keys())[i] ?? null, + get length() { + return store.size; + }, + }; +} + +Object.defineProperty(window, "localStorage", { + value: createStorageShim(), + writable: true, + configurable: true, +}); +Object.defineProperty(window, "sessionStorage", { + value: createStorageShim(), + writable: true, + configurable: true, +}); + Object.defineProperty(window, "matchMedia", { writable: true, value: (query) => ({ From 7675f6696b33933c48595f8743e76b68c528bb20 Mon Sep 17 00:00:00 2001 From: caioopra Date: Thu, 23 Apr 2026 14:48:06 -0300 Subject: [PATCH 07/11] =?UTF-8?q?fix:=20address=20PR=20#2=20review=20?= =?UTF-8?q?=E2=80=94=20clear=20sessionStorage=20in=20tests,=20correct=20ki?= =?UTF-8?q?ll-switch=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/testing_phase3.md | 2 +- frontend/src/test/setup.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/testing_phase3.md b/docs/testing_phase3.md index 0ae1a88..f23a76c 100644 --- a/docs/testing_phase3.md +++ b/docs/testing_phase3.md @@ -42,7 +42,7 @@ Backend on `:3000`, frontend on `:5173`. | Feature | How | |---------|-----| -| **Kill-switch** | Toggle on Dashboard → password modal → confirm → chat disabled system-wide | +| **Kill-switch** | Toggle on Providers page → password modal → confirm → chat disabled system-wide (Dashboard shows status only) | | **Step-up auth** | Change any setting on Providers → password prompt before save | | **Audit trail** | After any action, check Audit page — everything is logged | | **Budget check** | Send chat messages (needs LLM API key) → token usage tracked per message | diff --git a/frontend/src/test/setup.js b/frontend/src/test/setup.js index 5c5f34c..4e272a7 100644 --- a/frontend/src/test/setup.js +++ b/frontend/src/test/setup.js @@ -54,6 +54,7 @@ afterEach(() => { server.resetHandlers(); resetMockState(); localStorage.clear(); + sessionStorage.clear(); }); afterAll(() => server.close()); From e2bfbb9ee0658835099003d78221c50d8650bcea Mon Sep 17 00:00:00 2001 From: caioopra Date: Thu, 23 Apr 2026 14:48:55 -0300 Subject: [PATCH 08/11] cleanup(docs): removes manual testing of phase 3 docs --- docs/testing_phase3.md | 62 ------------------------------------------ 1 file changed, 62 deletions(-) delete mode 100644 docs/testing_phase3.md diff --git a/docs/testing_phase3.md b/docs/testing_phase3.md deleted file mode 100644 index f23a76c..0000000 --- a/docs/testing_phase3.md +++ /dev/null @@ -1,62 +0,0 @@ -# Testing Phase 3 — Manual QA Guide - -Step-by-step guide to manually test the Phase 3 admin console, governance, and observability features locally. - -## Prerequisites - -- Docker running (for PostgreSQL) -- No LLM API key needed for admin console (only for chat/cost features) - -## Step 1: Promote your user to admin - -```bash -source backend/.env -psql "$DATABASE_URL" -c "UPDATE users SET role = 'admin' WHERE email = 'test@test.com';" -``` - -Current dev DB has one user: `test@test.com` with role `user`. No promote CLI exists — use the SQL directly. - -## Step 2: Start the app - -```bash -make dev -``` - -Backend on `:3000`, frontend on `:5173`. - -## Step 3: Log in and visit admin - -1. Go to `http://localhost:5173`, log in as `test@test.com` -2. Navigate to `http://localhost:5173/admin` - -## What you'll see - -| Page | What's there | -|------|-------------| -| **Dashboard** | 4 metric cards: Total Users, Monthly Cost, Active Provider, Chat Status | -| **Providers** | LLM settings form (default provider, Gemini/Claude models). Save requires password re-entry | -| **Users** | User table with "Set Rate Limit" action (also requires password re-entry) | -| **Audit Log** | Timeline of admin actions + auth events. Filter by action prefix, cursor pagination | - -## Key features to test - -| Feature | How | -|---------|-----| -| **Kill-switch** | Toggle on Providers page → password modal → confirm → chat disabled system-wide (Dashboard shows status only) | -| **Step-up auth** | Change any setting on Providers → password prompt before save | -| **Audit trail** | After any action, check Audit page — everything is logged | -| **Budget check** | Send chat messages (needs LLM API key) → token usage tracked per message | -| **Rate limiting** | Set rate limit on a user from Users page | -| **Role gating** | Log in as a non-admin user → `/admin` redirects to `/` | - -## What needs an LLM API key - -Chat features (token tracking, budget enforcement, cost metrics on dashboard) require `LLM_GEMINI_API_KEY` or `LLM_CLAUDE_API_KEY` in `.env`. Without them chat returns 503. The admin console itself works fully without keys. - -## Quick start one-liner - -```bash -source backend/.env && psql "$DATABASE_URL" -c "UPDATE users SET role = 'admin' WHERE email = 'test@test.com';" && make dev -``` - -Then open `http://localhost:5173/admin`. From 68faca9bcf0a7a0daceeaae7c845f59765f520ce Mon Sep 17 00:00:00 2001 From: caioopra Date: Thu, 23 Apr 2026 14:56:11 -0300 Subject: [PATCH 09/11] style: apply prettier formatting to AdminProviders.jsx Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src/pages/admin/AdminProviders.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/admin/AdminProviders.jsx b/frontend/src/pages/admin/AdminProviders.jsx index a19532b..2bf21ff 100644 --- a/frontend/src/pages/admin/AdminProviders.jsx +++ b/frontend/src/pages/admin/AdminProviders.jsx @@ -198,7 +198,9 @@ export default function AdminProviders() { value={form[key]} onChange={(e) => handleChange(key, e.target.value)} placeholder={placeholder} - list={suggestions ? `${key}-suggestions` : undefined} + list={ + suggestions ? `${key}-suggestions` : undefined + } className="w-full rounded-lg border border-purple-500/30 bg-[#1e1836] px-3 py-2 text-sm text-[#f1eff8] placeholder-neutral-500 focus:outline-none focus:ring-2 focus:ring-purple-500/60" /> {suggestions && ( From b85b3984d3eb4b324f21542081f02c915f41143e Mon Sep 17 00:00:00 2001 From: caioopra Date: Thu, 23 Apr 2026 17:32:19 -0300 Subject: [PATCH 10/11] chore: add local CI-mirror targets (make check-{backend,frontend,fullcheck} + npm run check) --- Makefile | 13 ++++++++++++- frontend/package.json | 3 ++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index b50360b..2ea23a2 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help install db-up db-down db-reset migrate prepare backend frontend dev test test-backend test-frontend lint build clean deploy +.PHONY: help install db-up db-down db-reset migrate prepare backend frontend dev test test-backend test-frontend lint build clean deploy check-backend check-frontend fullcheck help: @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}' @@ -53,6 +53,17 @@ lint: ## Run clippy (zero warnings) + prettier check cd backend && cargo clippy -- -D warnings cd backend && cargo fmt --check +check-backend: ## Mirror backend CI: fmt + clippy + sqlx-prepare + tests (DB must be running) + cd backend && cargo fmt -- --check + cd backend && cargo clippy -- -D warnings + cd backend && SQLX_OFFLINE=false cargo sqlx prepare --workspace --check -- --all-targets + cd backend && cargo test + +check-frontend: ## Mirror frontend CI: prettier + tests + build + cd frontend && npm run check + +fullcheck: check-backend check-frontend ## Run backend + frontend CI checks locally (DB must be running) + build: ## Production build for both sides cd backend && cargo build --release cd frontend && npm run build diff --git a/frontend/package.json b/frontend/package.json index b91b888..20b119d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,7 +9,8 @@ "preview": "vite preview", "format": "prettier --write 'src/**/*.{js,jsx,ts,tsx,css,json}'", "test": "vitest", - "format:check": "prettier --check 'src/**/*.{js,jsx,ts,tsx,css,json}'" + "format:check": "prettier --check 'src/**/*.{js,jsx,ts,tsx,css,json}'", + "check": "npm run format:check && npm test -- --run && npm run build" }, "dependencies": { "@tailwindcss/vite": "^4.2.2", From 30605c8a37eac463359098e175ae7e1240f189c3 Mon Sep 17 00:00:00 2001 From: caioopra Date: Thu, 23 Apr 2026 18:28:57 -0300 Subject: [PATCH 11/11] fix(make): set SQLX_OFFLINE=true for clippy and cargo test in check-backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without it, sqlx tries to connect to the live DB at compile time instead of using the checked-in .sqlx metadata — matching what CI does via the workflow-level env. Co-Authored-By: Claude Opus 4.7 (1M context) --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 2ea23a2..f727e32 100644 --- a/Makefile +++ b/Makefile @@ -53,11 +53,11 @@ lint: ## Run clippy (zero warnings) + prettier check cd backend && cargo clippy -- -D warnings cd backend && cargo fmt --check -check-backend: ## Mirror backend CI: fmt + clippy + sqlx-prepare + tests (DB must be running) +check-backend: ## Mirror backend CI: fmt + clippy + sqlx-prepare + tests (requires `make db-up`) cd backend && cargo fmt -- --check - cd backend && cargo clippy -- -D warnings + cd backend && SQLX_OFFLINE=true cargo clippy -- -D warnings cd backend && SQLX_OFFLINE=false cargo sqlx prepare --workspace --check -- --all-targets - cd backend && cargo test + cd backend && SQLX_OFFLINE=true cargo test check-frontend: ## Mirror frontend CI: prettier + tests + build cd frontend && npm run check