This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
AGENTS.md at the repo root is the canonical agent operating guide. Read it
fully and follow its guardrails before making changes — it records the
release-governance, PR-automation, security-boundary, and anti-pattern rules
that reviews enforce. This file only complements it with commands and a
high-level architecture map; when in doubt, AGENTS.md wins. The frontend has
its own frontend/AGENTS.md (the installed Next.js differs from training
data — read node_modules/next/dist/docs/ before writing frontend code).
Default branch is develop. PRs use .github/PULL_REQUEST_TEMPLATE.md and are
merged by metadata-only robot governance (see
docs/development/merge-gate-policy.md); OpenCode Review, Strix Security Scan,
and the merge scheduler come from central workflows in
ContextualWisdomLab/.github — do not reintroduce repo-local copies.
cd backend
python3 -m pip install -r requirements.txt # CI: --require-hashes -r requirements-hashes.txt
python3 scripts/migrate_db.py # Alembic upgrade head (managed path)
python3 -m pytest -q # full test suite
python -m pytest tests/test_tasks_api.py -q # single test file
python -m ruff check . # lint (CI-enforced)
uvicorn main:app --reload # local dev server only- CI runs backend tests with
PYTHONWARNINGS=errorandDISABLE_BACKGROUND_WORKERS=1, then fails the job if the pytest output containsTimeout,Fatal,Warn, orDenied. Match that locally for merge evidence. - Containers never run
uvicorn main:appdirectly; the entrypoint ispython scripts/start_backend.py, which validates required settings first. scripts/bootstrap_db.pyis the local/dev-only schema compatibility path; Alembic history underbackend/alembicis authoritative.
cd frontend
pnpm install --frozen-lockfile
pnpm test # vitest run
npm test -- src/lib/api-client.test.ts # single test file
pnpm run lint # eslint
pnpm run build
pnpm run typecheck
pnpm run dev
npm run test:e2e -- tests/e2e/dashboard-branding.spec.ts # Playwright (set LIVE_BASE_URL for live stacks)./scripts/verify_threading.sh # backend threading tests + frontend test/lint/build
bash scripts/ci/test_pr_governance_gate.sh # PR governance gate self-testBefore opening a PR, run the focused tests that cover the changed contract and
put the exact commands in the PR body (see AGENTS.md and CONTRIBUTING.md).
The blessed local stack is docker-compose.yml (Postgres+pgvector, an Ollama
container with gemma4:e2b-it-qat and embeddinggemma baked in via
Dockerfile.ollama, the FastAPI backend, and the Next.js frontend). Start it
through the wrapper, which resolves the env file as
NARUON_ENV_FILE > ~/.env > ./.env and passes it only as an interpolation
source (never mount ~/.env as a Compose env_file):
./scripts/naruon_compose.sh up -d --build
./scripts/naruon_compose.sh exec backend python import_fixtures.pyThe other compose files are purpose-specific evaluation/evidence stacks:
docker-compose.live-e2e.yml— live E2E evidence: pre-builtBACKEND_IMAGE/FRONTEND_IMAGE, migrate/seed marker containers, nginx at127.0.0.1:18080.docker-compose.gateway.yml— Traefik + Keycloak edge/OIDC evaluation gateway in front of backend/frontend images.docker-compose.infra.yml— hardened infra evaluation (Traefik, Prometheus, Grafana, Loki, Tempo, otel-collector, Keycloak) with the repo hardening contract (no-new-privileges,read_only, tmpfs mounts).docker-compose.observability.yml— Prometheus/Grafana/Loki/Tempo only.docker-compose.apm.yml— minimal Prometheus + Jaeger (OTLP) pair.docker-compose.postgres-ha.yml— Postgres physical-replication drill (primary +pg_basebackupreplica); driven byscripts/postgres_ha_drill.sh.
DATABASE_URL, AUTH_SESSION_HMAC_SECRET, and ENCRYPTION_KEY have no code
defaults anywhere; Compose/Kubernetes/operators must inject them, and startup
fails closed when they are missing.
Naruon is an AI email workspace: a web client/control plane over
member-configured, customer-owned mail, CalDAV/CardDAV, and WebDAV systems. It
is not an SMTP/IMAP server, MX host, or mailbox capacity provider — the
customer systems stay the source of truth, and Naruon stores bounded metadata,
indexes, and auditable writeback intent. ARCHITECTURE.md and
docs/architecture/ are the detailed references.
Next.js frontend ──> FastAPI backend (control plane) ──> Postgres + pgvector
│
├──> OpenAI-compatible LLM providers (Ollama locally)
└──> outbound-only self-hosted connector (connector/)
└──> customer IMAP/POP3/SMTP + CalDAV/CardDAV/WebDAV
backend/— FastAPI app (main.py, routers inapi/, domain logic inservices/, SQLAlchemy models indb/, Alembic inalembic/). Owns persistence, canonical email threading (services/threading_service.pyis the only thread-id assignment owner), vector search, AI summaries, ticket tasks, and server-authoritative calendar/WebDAV writeback intents. Authorization is deny-first RBAC + ABAC.frontend/— Next.js workspace shell (Today dashboard, Mail, Calendar, Tasks, Projects, Context Search, AI Hub, Data, Security, Settings). Browser writes go through the same-origin/api/*proxy, which converts the HttpOnlynaruon_sessioncookie into the backendAuthorization: Bearersession. Never store tokens inlocalStorage/sessionStorageor emit public identity headers (X-User-Id,X-Organization-Id, etc.).connector/— the self-hosted connector: an outbound-only WebSocket client to the control plane (wss://naruon.net/ws/runner/{registration_token}) that executes only configured local adapters (IMAP fetch, SMTP send, ETag/If-Match-guarded CalDAV/WebDAV PUT). Without an adapter it fails closed withadapter_not_configuredandprovider_write_executed=false— never return placeholder success.Dockerfile— three stages:backend-runtime, a pnpm frontend build, and a combined non-root image that serves both;k8s/andrender.yamldeploy it.- Provider writes are explicit opt-in (
execute_provider=true) and conflict-aware (opaquesource_uidselection, capability checks, ETag/If-Match); intents are the default response. - Auth: signed HS256 bearer sessions (HMAC via
AUTH_SESSION_HMAC_SECRET) or enterprise OIDC/JWKS; private/api/*routers register the defaultget_auth_contextdependency. LLMbase_urland OIDC/SMTP/IMAP/POP3 hosts are strict egress allowlists that resolve only to pinned global addresses. - CI (
.github/workflows/):app-ci.yml(backend ruff+pytest, frontend test/lint/build), plusbandit,codeql,trivy,scorecard,pr-governance,docker-publish(GHCR onv*tags matchingVERSION), andmail-smoke. Actions are pinned to full commit SHAs.
- Treat
Timeout,Fatal,Warn, orDeniedin any execution output as a hard failure; tests must pass without them. - TDD is expected: add or update tests before production code changes, and keep each PR an atomic, focused change.
- Never expose sequential database ids through APIs or UI — use opaque public
ids (
task_uid,source_uid,folder_uid,document_id). New tables and columns use at least two-wordsnake_casenames (task_title, nottitle). - Alembic migrations use structured operations (
op.create_index, …), neversa.text(f"...")DDL. - Services return deterministic
error_codevalues; routes must not derive HTTP status from message substrings. Error responses stay honest and do not leak internals or credential-type details (see "Error-message contract" inREADME.md). - Never commit
.env, real mailbox exports, or credentials; email fixtures inbackend/tests/fixturesstay small and synthetic. - Release bumps keep
VERSION,CHANGELOG.md,frontend/package.json, and FastAPI app metadata synchronized; the backend reads its version fromVERSION. - When a review uncovers a recurring bug pattern, record the anti-pattern in
AGENTS.mdand update the affected tests/mocks/docs in the same PR.