Developer Guide¶
Prerequisites¶
- Python 3.13 (pinned in
.python-version) uv0.11+
No other tools needed for running tests. Full local dev stack additionally needs: Docker, k3d 5.7+, kubectl, Helm 3.14+, Tilt 0.33+.
Quick start¶
uv sync # install / sync deps
uv run pytest -v # run all tests
uv run trevor # run app (uvicorn on :8000)
uv run ruff check . # lint
uv run ruff format . # format
uv run zensical serve # serve docs locally
All commands go through uv run. No make, just, or task.
Local dev stack (Tilt + k3d)¶
Brings up PostgreSQL, Redis, SeaweedFS (S3), and Keycloak inside a local k3d cluster with live-reload.
Devcontainer (recommended)¶
Open the repo in VS Code and choose Reopen in Container. The post-create script:
- Installs uv, k3d, Tilt, and prek hooks
- Runs
uv sync - Copies
sample.env→.env(if.envdoesn't already exist) - Creates the k3d cluster
trevor-devwith a local image registry
Then start the dev stack:
Tilt brings up all services, runs DB migrations, and seeds the database with test users and project memberships automatically via the seed-dev-db resource.
Bare-metal¶
# One-time cluster setup (requires Docker, k3d, Tilt, Helm, uv)
./scripts/dev-setup.sh
# Start dev stack
tilt up
# Teardown
./scripts/dev-teardown.sh
Port forwards¶
Active while Tilt is running:
| Port | Service | Credentials |
|---|---|---|
| 8000 | trevor API | — |
| 8080 | Keycloak admin console | admin / admin |
| 8333 | SeaweedFS S3 gateway | devaccess / devsecret |
| 5432 | PostgreSQL | trevor / trevor |
| 6379 | Redis | — |
Test users¶
All test users have password password and are created automatically in Keycloak from deploy/dev/keycloak-realm.yaml on first Keycloak startup.
| Username | Role | Project |
|---|---|---|
researcher-1 |
researcher | Interstellar |
checker-1 |
output_checker | Interstellar |
checker-2 |
output_checker + senior_checker | Interstellar |
admin-user |
tre_admin | — (global admin via Keycloak realm role) |
These users are seeded into postgres by the seed-dev-db Tilt resource, which runs automatically after trevor and Keycloak are healthy. You can also run it manually:
Environment variables¶
post-create.sh copies sample.env → .env on devcontainer creation. The .env file is gitignored — edit it for local overrides without affecting other developers.
Key variables and their defaults for the Tilt stack:
| Variable | Default | Notes |
|---|---|---|
DATABASE_URL |
postgresql+asyncpg://trevor:trevor@localhost:5432/trevor |
Port-forwarded from k3d |
DEV_AUTH_BYPASS |
false |
Real Keycloak OIDC login required |
KEYCLOAK_URL |
http://localhost:8080 |
Browser-facing; also JWT issuer base |
KEYCLOAK_INTERNAL_URL |
(empty) | In-cluster URL for server-side OIDC calls — set to http://keycloak:8080 in Tiltfile |
KEYCLOAK_ADMIN_USERNAME |
admin |
Used by seed-dev-db.py to query Keycloak admin API |
KEYCLOAK_ADMIN_PASSWORD |
admin |
Must match KC_BOOTSTRAP_ADMIN_PASSWORD in deploy/dev/keycloak.yaml |
S3_ENDPOINT_URL |
http://localhost:8333 |
SeaweedFS S3 gateway |
S3_ACCESS_KEY_ID |
devaccess |
|
S3_SECRET_ACCESS_KEY |
devsecret |
CR8TOR sample project¶
Tilt automatically applies KARECTL CRD definitions and the Interstellar development project with User and Group CRDs for all dev users. These live in:
deploy/dev/crds/— CustomResourceDefinition schemas (Project, User, Group, KeycloakClient, VDIInstance)deploy/dev/sample-project/— CR instances:project-interstellar.yaml,users-dev.yaml,group-interstellar-analyst.yaml
The Project CRD spec.display_name field is used as the human-readable project name in the UI. The CRD sync worker reconciles these into the postgres DB every 5 minutes; seed-dev-db also creates them immediately on startup so the project is available before the first sync fires.
Run Alembic migrations against the Tilt PostgreSQL¶
# With port-forward active (handled automatically by Tilt):
DATABASE_URL=postgresql+asyncpg://trevor:trevor@localhost:5432/trevor uv run alembic upgrade head
SQLite-only (no Kubernetes)¶
For fast iteration without any infrastructure:
uv sync
# Set DEV_AUTH_BYPASS=true and a SQLite DATABASE_URL in .env, then:
uv run trevor # API on :8000
uv run pytest -v # tests (in-memory SQLite, no .env needed)
Testing¶
Tests use in-memory SQLite with DEV_AUTH_BYPASS=true. No external services needed.
uv run pytest -v # full suite (195 tests)
uv run pytest tests/test_ui.py # just UI tests
uv run pytest -k "test_rules" # just rule engine tests
Test fixtures¶
Defined in tests/conftest.py:
client— async HTTP client as regular user (dev-bypass-user)admin_client— async HTTP client as admin (dev-bypass-admin)db_session— direct async SQLModel sessionresearcher_setup— creates user + project + researcher membership, returns(client, project_id)sample_user,sample_project,sample_membership— pre-created DB records
Writing tests¶
@pytest.mark.anyio
async def test_something(client: AsyncClient) -> None:
r = await client.get("/some/endpoint")
assert r.status_code == 200
Database migrations¶
uv run alembic upgrade head # run migrations
uv run alembic revision --autogenerate -m "desc" # generate migration
SQLite gotchas
- SQLite doesn't support
ALTER COLUMN. Useop.batch_alter_table()in migrations. - Alembic autogenerate always misses
import sqlmodel— add manually. - Alembic may detect phantom
projects.statustype changes (VARCHAR→Enum) — remove manually.
Git workflow¶
- Commit after each discrete piece of work
- Conventional Commits format, subject ≤50 chars
- Run before every commit:
Documentation¶
Documentation lives in docs/ and is built with zensical (Material for MkDocs alternative).
Document after each iteration
Every iteration must include documentation updates. Update the relevant docs pages and AGENTS.md as part of the iteration deliverables.
Adding a new endpoint¶
- Add the model/migration if needed (
models/,alembic/) - Add Pydantic schemas (
schemas/) - Add the router function (
routers/) - Add audit events via
audit_service.emit() - Add tests
- Add UI template + route if applicable
- Update
docs/api.md - Run lint + format + tests