Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
1659a10
docs(cli): update README for v0.0.4 multi-provider behavior
Dodothereal Jul 17, 2026
90e21d1
docs: fix broken issue-tracker link (trainable-monorepo -> trainable)…
Jul 19, 2026
a99bae5
fix(frontend): use relative /api URLs instead of hardcoded http://hos…
Jul 19, 2026
facd3ba
fix(cli): chmod 0600 the generated ~/.trainable/.env secrets file (#127)
Jul 19, 2026
57181cc
chore(dev): add .pre-commit-config.yaml matching CONTRIBUTING (#122)
Jul 19, 2026
5161fa9
fix(compose): require operator secrets + stop publicly exposing datas…
Jul 19, 2026
7e65878
chore(lint): add explicit ruff config in backend/pyproject.toml (#124)
Jul 19, 2026
9b7fb6d
feat(backend): opt-in bearer-token auth for /api/* (API_AUTH_TOKEN)
Jul 19, 2026
ef068aa
perf(backend): offload DuckDB/pandas work off the event loop (#93)
Jul 19, 2026
67f6157
fix(backend): restrict CORS — configurable origins, never wildcard+cr…
Jul 19, 2026
8c25d28
fix(s3): bound /api/s3/upload memory and validate bucket/key targets
Jul 19, 2026
57b29ee
fix(backend): enforce wall-clock timeout on provider LLM calls (#95)
Jul 19, 2026
0dad7d7
perf(s3): offload blocking boto3 calls to worker threads
Jul 19, 2026
2c7a815
feat(backend): real readiness probe (/api/readyz) + compose healthchecks
Jul 19, 2026
fe9341c
fix(experiments): bound memory in the multi-file upload endpoint
Jul 19, 2026
417283e
ci: add pytest-cov coverage measurement and gate to backend-test
Jul 19, 2026
451c18f
fix(backend): capture unhandled + background-task errors in Sentry (#…
Jul 19, 2026
33c847e
refactor(db): introduce Alembic migrations (replace boot-time _run_mi…
Jul 19, 2026
586302f
ci: run backend tests against Postgres in CI, not only in-memory SQLite
Jul 19, 2026
1a809a9
test(backend): cover agent orchestration + sandbox lifecycle (#115)
Jul 19, 2026
f578a08
ci: add advisory dependency and container image vulnerability scanning
Jul 19, 2026
8b728fa
feat(frontend): comparison leaderboard consuming the /compare endpoint
Jul 19, 2026
3017c28
feat: add "Reproduce" action that replays a snapshot and diffs metric…
lucastononro Jul 19, 2026
553e8f9
feat(models): in-app prediction playground on the model card (#109)
lucastononro Jul 19, 2026
ae8d05d
feat(training): pre-flight training controls — metric, model families…
lucastononro Jul 19, 2026
a42d0d0
feat(budget): enforce per-project cost budgets with a hard-stop guard…
lucastononro Jul 19, 2026
bdf190f
docs(design): multi-user auth + ownership design note (#113)
lucastononro Jul 19, 2026
890ebce
feat(data): raw dataset preview + quick-profile on upload
lucastononro Jul 19, 2026
a879e39
feat: surface sample datasets with a guided first-run gallery
lucastononro Jul 19, 2026
3020b76
fix(review): address Greptile findings on #135
lucastononro Jul 20, 2026
2e313f8
fix(review): address Greptile findings on #136
lucastononro Jul 20, 2026
4259bdf
fix(review): address Greptile findings on #134
lucastononro Jul 20, 2026
b2488b4
fix(review): address Greptile findings on #133
lucastononro Jul 20, 2026
6936b64
fix(review): address Greptile findings on #139
lucastononro Jul 20, 2026
eab79ba
fix(review): address Greptile findings on #138
lucastononro Jul 20, 2026
ed82a07
fix(review): address Greptile findings on #141
lucastononro Jul 20, 2026
029859c
fix(review): address Greptile findings on #144
lucastononro Jul 20, 2026
3f336df
fix(review): address Greptile findings on #140
lucastononro Jul 20, 2026
39929e6
fix(review): address Greptile findings on #142
lucastononro Jul 20, 2026
8391323
fix(review): address Greptile findings on #147
lucastononro Jul 20, 2026
6f9e2a8
fix(review): address Greptile findings on #143
lucastononro Jul 20, 2026
382db8f
fix(review): address Greptile findings on #151
lucastononro Jul 20, 2026
cd58511
fix(review): address Greptile findings on #149
lucastononro Jul 20, 2026
7dfaf43
fix(review): address Greptile findings on #153
lucastononro Jul 20, 2026
4a0ca9b
fix(review): address Greptile findings on #156
lucastononro Jul 20, 2026
befc75f
fix(review): address Greptile findings on #155
lucastononro Jul 20, 2026
de197b8
fix(review): address Greptile findings on #154
lucastononro Jul 20, 2026
a86a93f
fix(review): address Greptile findings on #161
lucastononro Jul 20, 2026
09a7284
fix(review): address Greptile findings on #163
lucastononro Jul 20, 2026
c57f596
fix(review): address Greptile findings on #162
lucastononro Jul 20, 2026
4e5a0ed
fix(review): address Greptile findings on #164
lucastononro Jul 20, 2026
1bfed27
fix(review): address Greptile findings on #165
lucastononro Jul 20, 2026
7f6394e
chore(staging): open staging-v0.0.5 merge ledger
lucastononro Jul 29, 2026
4b970ca
merge(pr-132): docs: fix broken issue-tracker link
lucastononro Jul 29, 2026
7c7ed13
merge(pr-129): docs(cli): update stale README for v0.0.4 multi-provid…
lucastononro Jul 29, 2026
69ce961
merge(pr-137): chore(lint): add explicit ruff config in backend/pypro…
lucastononro Jul 29, 2026
8476718
merge(pr-135): chore(dev): add .pre-commit-config.yaml matching CONTR…
lucastononro Jul 29, 2026
a18cd6e
fix(review): address Greptile findings on #167
lucastononro Jul 29, 2026
5775313
merge(pr-167): docs(design): multi-user auth + ownership design note
lucastononro Jul 29, 2026
fe73312
chore(staging): ledger — wave 1 (pr 132, 129, 137, 135, 167)
lucastononro Jul 29, 2026
24f1900
fix(review): address Greptile P1 on #151 — await task after wait_for …
lucastononro Jul 29, 2026
c91b669
merge(pr-151): fix(backend): capture unhandled + background-task erro…
lucastononro Jul 29, 2026
453e978
merge(pr-155): test(backend): cover agent orchestration + sandbox lif…
lucastononro Jul 29, 2026
cb817e8
merge(pr-139): perf(backend): offload DuckDB/pandas work off the even…
lucastononro Jul 29, 2026
8155755
merge(pr-134): fix(cli): chmod 0600 the generated ~/.trainable/.env s…
lucastononro Jul 29, 2026
3280854
merge(pr-136): fix(compose): require operator secrets + stop publicly…
lucastononro Jul 29, 2026
1308ba3
merge(pr-142): fix(backend): enforce wall-clock timeout on provider L…
lucastononro Jul 29, 2026
a34f34f
chore(staging): ledger — wave 2 (pr 151, 155, 139, 134, 136, 142)
lucastononro Jul 29, 2026
aa06255
merge(pr-133): fix(frontend): use relative /api URLs, drop hardcoded …
lucastononro Jul 29, 2026
a565010
merge(pr-138): opt-in bearer-token auth for /api/* via API_AUTH_TOKEN…
lucastononro Jul 29, 2026
2c384da
merge(pr-140): restrict CORS — configurable origins, never wildcard+c…
lucastononro Jul 29, 2026
d415817
merge(pr-144): real readiness probe /api/readyz + compose healthcheck…
lucastononro Jul 29, 2026
c793d77
chore(staging): ledger — wave 3 (pr 133, 138, 140, 144)
lucastononro Jul 29, 2026
6684446
merge(pr-141): bound /api/s3/upload memory + validate bucket/key targ…
lucastononro Jul 29, 2026
f2c3c0f
merge staging-v0.0.5 into fix/92-offload-boto3
lucastononro Jul 29, 2026
c81651e
merge(pr-143): offload blocking boto3 S3 calls out of the async event…
lucastononro Jul 29, 2026
0bdfc38
Merge branch 'staging-v0.0.5' into fix/94-upload-memory
lucastononro Jul 29, 2026
2198d27
fix(review): stream attach_data file uploads through temp files
lucastononro Jul 29, 2026
b25d8db
merge(pr-147): bound memory in the multi-file experiment upload endpo…
lucastononro Jul 29, 2026
74afce1
chore(staging): ledger — wave 4 (pr 141, 143, 147)
lucastononro Jul 29, 2026
dd98199
merge: PR #149 — ci: coverage measurement + gate on backend-test (clo…
lucastononro Jul 29, 2026
57dafb8
merge: PR #154 — ci: run backend tests against Postgres (closes #117)
lucastononro Jul 29, 2026
1ba405c
merge: PR #156 — ci: advisory dependency + container image vulnerabil…
lucastononro Jul 29, 2026
7fe06d7
fix(review): batch-mode SQLite migrations + ruff format on #153
lucastononro Jul 29, 2026
29bcc0e
merge: PR #153 — refactor(db): introduce Alembic migrations (closes #…
lucastononro Jul 29, 2026
7b4b03d
chore(staging): ledger — wave 5 (pr 149, 154, 156, 153)
lucastononro Jul 29, 2026
fe831f0
merge: PR #161 — feat(frontend): comparison leaderboard UI (closes #105)
lucastononro Jul 29, 2026
3f9a7ee
style: prettier format frontend/src/components/experiments/SnapshotRe…
lucastononro Jul 29, 2026
3abe262
merge: PR #162 — feat: "Reproduce" action — replay a snapshot and dif…
lucastononro Jul 29, 2026
e59549a
style: prettier format frontend/src/app/models/page.tsx
lucastononro Jul 29, 2026
a479866
merge: PR #163 — feat: in-app prediction playground for deployed mode…
lucastononro Jul 29, 2026
102e88d
fix(review): address Greptile findings on #168 + prettier format
lucastononro Jul 29, 2026
a38d930
merge: PR #168 — feat(data): raw dataset preview + quick-profile on u…
lucastononro Jul 29, 2026
719088a
fix(review): address Greptile findings on #169 + format fixes
lucastononro Jul 29, 2026
c4176cf
merge: PR #169 — feat: sample datasets gallery + guided first-run (cl…
lucastononro Jul 29, 2026
23d54e1
chore(staging): ledger — wave 6 (pr 161, 162, 163, 168, 169)
lucastononro Jul 29, 2026
565ec01
merge: PR #164 — feat(training): pre-flight training controls — metri…
lucastononro Jul 29, 2026
712e37f
Merge branch 'staging-v0.0.5' into fix/107-cost-budget
lucastononro Jul 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 58 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,35 @@ CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxxxxxxxxxxx
# BACKEND_PORT=8001


# ============================================================
# Optional — security
# ============================================================
# Opt-in bearer-token auth for the API. Unset (default) = every
# endpoint stays open, exactly as before — fine for localhost dev.
# When set, all /api/* requests must send
# Authorization: Bearer <token>
# Exempt: /api/health, /api/readyz, and CORS preflight. The SSE
# stream endpoint (/api/sessions/<id>/stream) also accepts
# ?token=<token>, since the browser EventSource API cannot send
# an Authorization header. Set this whenever the backend is
# exposed beyond localhost (e.g. docker-compose.prod.yml).
#
# NOTE: ?token= is part of the URL, so uvicorn/nginx/proxy access
# logs record it verbatim. If the stream endpoint is used behind
# such a component, redact the token query parameter from access
# logs (custom uvicorn access-log format, nginx log masking) or
# restrict log access.
# API_AUTH_TOKEN=change-me-to-a-long-random-string

# Allowed browser origins for CORS, comma-separated. Defaults to
# the local frontend (http://localhost:3000, http://127.0.0.1:3000).
# The frontend normally proxies /api through Next.js (same-origin),
# so you only need this when a browser calls the backend origin
# directly. A `*` entry is honored but credentials are then
# disabled — never both.
# CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000


# ============================================================
# Optional — observability
# ============================================================
Expand All @@ -94,11 +123,36 @@ CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxxxxxxxxxxx
# SENTRY_ENVIRONMENT=local


# ============================================================
# Datastore secrets — REQUIRED by docker-compose.prod.yml
# ============================================================
# Prod has NO built-in defaults: docker-compose.prod.yml fails fast
# (`config`/`up` errors out) if any of these are unset. The backend's
# DATABASE_URL and AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY are derived
# from these values, so the app credentials always match the datastores.
# Replace the placeholders below with strong, unique secrets.
#
# Postgres superuser/database (postgres service + backend DATABASE_URL):
POSTGRES_USER=trainable
POSTGRES_PASSWORD=change-me-strong-db-password
POSTGRES_DB=trainable
#
# MinIO root credentials (minio service + backend AWS_ACCESS_KEY_ID/SECRET):
MINIO_ROOT_USER=change-me-minio-user
MINIO_ROOT_PASSWORD=change-me-strong-minio-password
#
# Container image tags for the prebuilt prod images (default: latest):
# TRAINABLE_BACKEND_TAG=latest
# TRAINABLE_FRONTEND_TAG=latest


# ============================================================
# Advanced — overridden by docker-compose.prod.yml
# ============================================================
# Don't set these unless you're running outside docker-compose.
# DATABASE_URL=postgresql+asyncpg://trainable:trainable@postgres:5432/trainable
# docker-compose.prod.yml sets these for the backend container from the
# datastore secrets above; only set them by hand when running the
# backend OUTSIDE docker-compose (docker-compose.yml uses fixed dev creds).
# DATABASE_URL=postgresql+asyncpg://<POSTGRES_USER>:<POSTGRES_PASSWORD>@postgres:5432/<POSTGRES_DB>
# S3_ENDPOINT=http://minio:9000
# AWS_ACCESS_KEY_ID=minioadmin
# AWS_SECRET_ACCESS_KEY=minioadmin
# AWS_ACCESS_KEY_ID=<same as MINIO_ROOT_USER>
# AWS_SECRET_ACCESS_KEY=<same as MINIO_ROOT_PASSWORD>
128 changes: 127 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ jobs:
with:
python-version: "3.11"
cache: pip
- run: pip install ruff
# Keep in sync with the ruff-pre-commit rev in .pre-commit-config.yaml.
- run: pip install "ruff==0.15.22"
- run: ruff check .
- run: ruff format --check .

Expand All @@ -33,13 +34,57 @@ jobs:
defaults:
run:
working-directory: backend
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: trainable_test
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 5s
--health-timeout 5s
--health-retries 10
env:
TEST_DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/trainable_test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip
- run: pip install -r requirements.txt
# Coverage is scoped to production code: backend/.coveragerc omits
# tests/ so the gate reflects untested application code (with tests
# included the total was ~66%; production-only it is ~51%).
- run: |
pytest tests/ -v --tb=short \
--cov=. \
--cov-report=xml \
--cov-report=term-missing \
--cov-fail-under=50
- uses: actions/upload-artifact@v4
if: always()
with:
name: backend-coverage
path: backend/coverage.xml

cli-test:
name: CLI Tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: cli
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip
- run: pip install pytest .
- run: pytest tests/ -v --tb=short

backend-security:
Expand All @@ -58,6 +103,86 @@ jobs:
- run: bandit -r . -x ./tests --severity-level medium -f json -o bandit-report.json || true
- run: bandit -r . -x ./tests --severity-level high

backend-vuln-scan:
name: Backend Dependency Vulnerability Scan (advisory)
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
# Match backend/Dockerfile (python:3.13-slim) so the audited
# dependency resolution mirrors what actually ships.
python-version: "3.13"
cache: pip
- run: pip install -r requirements.txt pip-audit
# Advisory only: known advisories (e.g. transitive starlette/mcp/
# python-multipart CVEs) already exist in the dependency tree today,
# and shouldn't block CI. Flip this to blocking (drop
# continue-on-error) once those are triaged and cleared.
- name: pip-audit
run: pip-audit
continue-on-error: true

frontend-vuln-scan:
name: Frontend Dependency Vulnerability Scan (advisory)
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: frontend/package-lock.json
- run: npm ci
# Advisory only: known advisories (next/glob/prismjs/postcss/js-yaml)
# already exist in the dependency tree today, and shouldn't block CI.
# Flip this to blocking (drop continue-on-error) once those are
# triaged and cleared.
- name: npm audit
run: npm audit
continue-on-error: true

image-scan:
name: Container Image Vulnerability Scan (advisory)
runs-on: ubuntu-latest
strategy:
matrix:
include:
- image: trainable-backend
context: ./backend
- image: trainable-frontend
context: ./frontend
target: runner
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Build ${{ matrix.image }} image (local only, not pushed)
uses: docker/build-push-action@v5
with:
context: ${{ matrix.context }}
target: ${{ matrix.target || '' }}
push: false
load: true
tags: ${{ matrix.image }}:ci-scan
# Advisory only: base-image and OS-package advisories aren't triaged
# yet and shouldn't block CI. Flip to blocking (drop
# continue-on-error, or set exit-code back to 1 without the wrapper)
# once they are.
- name: Trivy scan
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
continue-on-error: true
with:
image-ref: ${{ matrix.image }}:ci-scan
format: table
severity: "CRITICAL,HIGH"
exit-code: "1"

frontend-lint:
name: Frontend Lint & Typecheck
runs-on: ubuntu-latest
Expand All @@ -75,6 +200,7 @@ jobs:
- run: npx next lint
- run: npx tsc --noEmit
- run: npx prettier --check 'src/**/*.{ts,tsx,css}'
- run: npm test

frontend-build:
name: Frontend Build
Expand Down
32 changes: 32 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Pre-commit hooks for Trainable. See CONTRIBUTING.md.
# Install: pip install pre-commit && pre-commit install
# Run manually: pre-commit run --all-files
# Generated / vendored artifacts: not hand-edited, so keep the hooks off them.
exclude: |
(?x)^(
package-lock\.json|
frontend/package-lock\.json|
frontend/tsconfig\.tsbuildinfo|
.*\.excalidraw|
sample-data/.*
)$

repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Keep in sync with the pinned ruff version in .github/workflows/ci.yml.
rev: v0.15.22
hooks:
# Lint (with autofix) the backend Python code using backend/pyproject.toml.
- id: ruff
args: [--fix]
files: ^backend/
# Format the backend Python code.
- id: ruff-format
files: ^backend/

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: detect-private-key
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ This automatically runs ruff (lint + format), trailing whitespace fixes, and pri

### Python (backend)

- Formatter/linter: [Ruff](https://docs.astral.sh/ruff/) (configured in `pyproject.toml`)
- Formatter/linter: [Ruff](https://docs.astral.sh/ruff/) (configured in `backend/pyproject.toml`)
- Run manually: `cd backend && ruff check . && ruff format .`
- Type hints are expected on all function signatures
- Use `logger` (not `print`) for all logging
Expand Down Expand Up @@ -77,6 +77,6 @@ All tests must pass before submitting a PR.

## Reporting Issues

- Use [GitHub Issues](https://github.com/lucastononro/trainable-monorepo/issues)
- Use [GitHub Issues](https://github.com/lucastononro/trainable/issues)
- Include steps to reproduce, expected vs actual behavior, and environment details
- For security vulnerabilities, please email the maintainer directly instead of opening a public issue
Loading
Loading