# CLAUDE.md > **If `.rules/` exists, read `.rules/AGENTS.md` and use it instead of this file.** > Otherwise use the guidance below. ## Quick Start ```bash make install # Full dev setup: sync deps + install lefthook hooks make check # Run lint (incl. mypy) + secrets audit + arch tests + score ``` ## Rules & Guides Architectural conventions and patterns are enforced by the architecture tests in [`tests/arch/`](tests/arch/) and the scoped rules under [`.cursor/rules/`](.cursor/rules/). Consult those before making changes to: - ORM models, migrations, or repositories - Service-layer code - Web/HTTP handlers - Architecture tests ## Commands | Target | Description | | ------------------- | ------------------------------------------------- | | `help` | Show available targets | | `install` | Full dev setup: sync deps + install lefthook hooks| | `sync` | Install/sync project + dev dependencies | | `lock` | Refresh the lockfile | | `upgrade` | Upgrade locked dependencies | | `fmt` | Format code with ruff | | `fix` | Auto-fix lint issues and reformat code | | `lint` | Lint (ruff check + format check + mypy) | | `typecheck` | Run mypy | | `test` | Run unit tests | | `test-unit` | Run unit tests | | `test-fast` | Run unit + arch tests (no external services) | | `test-integration` | Run integration tests (requires running fixtures) | | `test-arch` | Run architecture enforcement tests | | `test-smoke` | Run smoke tests (requires running services) | | `cov` | Run tests with coverage report | | `score` | Score OpenAPI specs (opt-in: `JENTIC_SCORE_API_KEY`) | | `check` | Run lint, secrets audit, arch tests, and score (no unit tests) | | `hooks` | Install lefthook git hooks (pre-commit + commit-msg) | | `clean` | Remove caches and build artifacts | | `start-fixtures` | Start Docker database fixtures | | `stop-fixtures` | Stop Docker database fixtures | | `destroy-fixtures` | Remove Docker database fixtures and volumes | | `start-app` | Start combined app (registry, admin, control, auth); broker is `start-broker` | | `start-registry` | Start registry surface standalone | | `start-admin` | Start admin surface standalone | | `start-control` | Start control surface standalone | | `build-all` | Build every service Docker image (see deploy/README.md) | | `save-all` | Write image tarballs to `build/jentic--.tar` | | `images` | List locally-built `jentic-one/*` images | The local k8s workflow (cluster, deploy, obs, smoke) is managed by the deploy CLI: ``` uv run python -m tools.deploy --help ``` ## Project Layout ``` tools/ └── deploy/ # Deploy CLI (kind, Helm, obs lifecycle) ├── cli.py # Click commands ├── config.py # Constants and path helpers └── runner.py # Subprocess wrapper + preflight checks src/jentic_one/ ├── __init__.py # Package version ├── __main__.py # CLI entry point (python -m jentic_one) ├── py.typed # PEP 561 marker ├── wiring.py # Top-level composition root for cross-surface wiring ├── registry/ # Registry surface (API spec catalogue + ingest) ├── control/ # Control surface (credential storage + connect flows) ├── admin/ # Admin surface (users, jobs, audit, executions) ├── broker/ # Broker module (credential-injecting data-plane proxy) ├── auth/ # Auth surface (identity, tokens, IdP) │ # each surface above follows: core/ services/ repos/ web/ (+ scoping/) ├── migrations/ # Alembic migration scripts │ ├── env.py # Async migration environment (multi-DB aware) │ ├── targets.py # Per-database metadata mapping │ ├── run.py # Programmatic migration runner │ ├── registry/ # Registry DB migrations │ ├── control/ # Control DB migrations │ └── admin/ # Admin DB migrations └── shared/ # Shared utilities (imported by every module) ├── __init__.py # Re-exports Context, AppConfig, DatabaseSession, etc. ├── config/ # Configuration schema (pydantic) and YAML+env loader ├── context.py # Central Context object (holds config + DB sessions) ├── logging.py # Structured logging facade (no stdlib logging elsewhere) ├── metrics.py # Metrics facade (only sanctioned place to add instruments) ├── tracing.py # Tracing facade ├── redaction.py # Secret/PII redaction helpers ├── pagination.py # Shared pagination primitives ├── url.py # URL utilities (server-variable substitution) ├── url_validation.py # Upstream URL validation (SSRF guard) ├── audit/ # Audit-log helpers ├── auth/ # Shared auth primitives (JWKS, tokens, permission catalogue) ├── broker/ # Shared broker protocols ├── crypto/ # Encryption facade ├── events/ # Event/SSE plumbing ├── executions/ # Execution tracking ├── jobs/ # Background job primitives ├── models/ # Shared ORM/pydantic models ├── resilience/ # Rate limiting, retries ├── schemas/ # Shared API schemas ├── state/ # State backend (in-memory / redis) ├── web/ # Shared web layer (app factory, deps, errors, OpenAPI meta) └── db/ # Database package (SQLAlchemy async) ├── __init__.py # Re-exports DatabaseSession, get_database_url ├── base.py # Declarative bases (RegistryBase, ControlBase, AdminBase) └── session.py # Async engine + session factory management tests/ ├── unit/ # Unit tests (no external services required) ├── integration/ # Integration tests (require running fixtures) ├── arch/ # Architecture enforcement tests └── smoke/ # Smoke/liveness tests (require running services) docker/ └── local-setup/ # Local development Docker infrastructure ├── docker-compose.yaml # Single PostgreSQL instance (3 schemas) ├── docker-compose.test.yaml # Test overlay (tmpfs, no persistent volumes) └── init-schemas.sql # Creates registry/control/admin schemas on first boot alembic.ini # Multi-database Alembic configuration scripts/ ├── setup.sh # Bootstrap local dev environment ├── migrate.sh # Customer-facing migration CLI └── version.sh # Reads version from pyproject.toml (used by Make/Docker/Helm) deploy/ ├── docker/ # Per-service Dockerfiles (multi-stage, uv-based builds) ├── helm/ │ ├── jentic-one/ # Umbrella Helm chart with service subcharts │ ├── observability/# Standalone LGTM chart (Grafana/Loki/Tempo/Prom + OTel collector) │ └── values/ # Per-mode + per-overlay value files └── terraform/ # Terraform modules and environment compositions ``` ## Frontend (`ui/`) The single-page app lives in [`ui/`](ui/) (React + Vite + Tailwind, TanStack Query, MSW for mocks, Vitest browser-mode + Playwright for tests). It is served same-origin behind the admin API; all authenticated routes are namespaced under `/app/*`. ``` ui/src/ ├── shared/ # The app spine — owned collectively, imported by every module │ ├── ui/ # Design-system primitives (Button, Card, Dialog, …) + index.ts BARREL │ ├── app/ # Layout, nav, route + query-client wiring; routes.ts is an APPEND-ONLY registry │ ├── api/ # Generated client + the @/shared/api facade (the only place that talks HTTP) │ ├── auth/ # Auth context, guard, token store │ ├── hooks/ lib/ # Cross-cutting hooks and utilities (barrelled via index.ts) │ └── config.ts # Runtime config ├── modules// # One folder per feature — MIRRORS a backend module/surface │ ├── components/ # View tier (router/view) — UI only │ ├── pages/ # Route entry points (PageShell + PageHeader + PageHelp) │ ├── api/hooks.ts # Service tier — TanStack Query hooks (the ONLY backend access for views) │ ├── api/client.ts # Repository tier — wraps @/shared/api; owns sentinel error types │ ├── routes.tsx # Module routes (relative to /app) — registered additively │ └── nav.ts # Module nav entry (absolute /app/… path) — registered additively └── mocks/handlers.ts # APPEND-ONLY MSW registry (the other sanctioned shared→modules bridge) ``` **Frontend conventions (enforced by `ui/eslint.config.js` + cursor-rules):** - **Modules mirror the backend's module/layer shape.** Views (`components/`, `pages/`) reach the backend only through their module's `api/hooks` (service), which call `api/client` (repository), which calls the `@/shared/api` facade. Views must never import `@/shared/api`, generated services, or call `fetch`/`axios` directly. (Mirrors backend Router → Service → Repository.) - **No sibling-module imports.** Everything shared goes through `@/shared`. - **Barrel discipline in modules.** Import shared surfaces through their barrels (`@/shared`, `@/shared/ui`), never deep paths like `@/shared/ui/Button`. - **Additive registries.** `shared/app/routes.ts` and `mocks/handlers.ts` are append-only (one import + one line per module) so parallel PRs never collide. ## Rules index `alwaysApply` rules are always in context. The rest are scoped by `globs` and should be read before working in the matching area: | Rule | When | | ---- | ---- | | [`git-conventions`](.cursor/rules/git-conventions.mdc) | _always_ — commit/PR conventions | ## Code Style - **Formatter**: ruff format — double-quote strings, space indentation, 100-char line length. - **Linter**: ruff check — pycodestyle, pyflakes, isort, flake8-bugbear, flake8-comprehensions, pyupgrade, pep8-naming, flake8-simplify, ruff-specific rules. - **Type checker**: mypy strict — all functions require type annotations, no implicit `Any`, no untyped defs. - **Target Python**: 3.12. ## Testing Conventions - **No DB mocking**: All database interactions in tests must use real database connections. Never mock `DatabaseSession`, `create_async_engine`, `AsyncSession`, `sqlalchemy`, or `asyncpg`. This is enforced by `tests/arch/test_no_db_mocking.py`. - **Unit tests** cover logic that doesn't require external services (config parsing, access restriction, etc.). - **Integration tests** cover database lifecycle (startup, shutdown, connectivity) using Docker fixtures. - **Coverage threshold**: 70% for unit tests (configured in `pyproject.toml`). DB lifecycle code is covered by integration tests. - **Test style**: pytest-style — plain functions or classes. No `unittest.TestCase` subclassing. ## Metrics - `shared/metrics.py` is the **only** sanctioned way to add application metrics. Use `get_meter(name)` to obtain a meter and create instruments. - No module outside `shared/metrics.py` may import `prometheus_client` or `opentelemetry.exporter.*` directly — enforced by `tests/arch/test_metrics_facade.py`. - The metrics exporter is configured via `AppConfig.observability.metrics.exporter` (values: `otlp`, `prometheus`, `none`). ## Workflow - **Branch naming**: `/` (e.g. `feat/add-broker-api`, `fix/control-timeout`). - **Commits**: Conventional Commits are **enforced repo-wide** by a `commit-msg` hook (`uv run cz check`, pure Python — no Node). Every commit message — backend or UI — must be `type(scope): subject` with a **mandatory scope** (e.g. `feat(broker): ...`, `fix(security): ...`, `ci(ci.yml): ...`). Reserve `fix` for real bugs present on `main`; refactoring your own unshipped branch code is `refactor`, not `fix`. Full rules and type reference: [`.cursor/rules/git-conventions.mdc`](.cursor/rules/git-conventions.mdc). - **PR process**: CI must pass (lint + typecheck + tests). Pre-commit hooks are enforced; PR titles follow the same `type(scope): description` format and become the squash commit on `main`. Always squash + merge. - **Dependencies**: managed by uv. Use `uv add ` for runtime deps, `uv add --group dev ` for dev deps.