* fix: handle SystemExit and GeneratorExit in SSE event_stream
The long-running SSE poll loop (up to 1 hour) surfaced SystemExit and
GeneratorExit as unhandled errors in Sentry when the worker process was
shut down (SIGTERM during time.sleep) or the client disconnected.
Wrap the polling loop in a try/except that catches these expected
teardown signals, logs at debug level, and re-raises so Python's normal
close semantics apply without surfacing noise in error tracking.
Closes #25
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(server): run gunicorn with gthread workers for SSE live event streams
Gunicorn's default sync worker cannot heartbeat while actively yielding a
streaming response, so the arbiter SIGABRTs it after the 30s timeout, killing
long-lived SSE streams and raising SystemExit(1). Switch to the threaded
gthread worker, whose main loop heartbeats independently of in-flight requests.
- gunicorn.conf.py (new): gthread workers (workers=2, thre…
30 days ago
feat(chat): unify Studio + Open WebUI behind one Caddy front door at /chat/
Replaces the two-port embedded model (Studio :8000 + OWUI :8801 in an
iframe) with a single-origin, single-port wiring, identical for local dev
and Docker:
- Caddy is the ONLY published listener (:8000). Studio and Open WebUI
publish no ports; /chat/* reverse-proxies to the subpath build with the
prefix preserved (handle, not handle_path); /ai/ and everything else
go to Django.
- Uses the subpath-capable Open WebUI fork wheel (v0.11.4-subpath,
WEBUI_SUBPATH=/chat), so OWUI emits every URL prefixed with /chat and
no Caddy-side URL rewriting is needed.
- Forward-auth to /chat/authz injects trusted identity headers
(X-Studio-Email/Name/Role); client-forged values are stripped at site
level; 401 redirects to the Studio login.
- Studio's /chat/css-mask serves the embed stylesheet (admin vs plain
mask) in place of a mounted custom.css.
- Local dev: manage.py dev manages a bundled Caddy (uvx caddy /
caddyserver) with a generated Caddyfile; Docker: caddy:2-alpine
chat-prox…
Remove stale [tool.uv.sources] path override; resolve simpleaudit from PyPI
The [tool.uv.sources] override pointed simpleaudit at a local ../SimpleAudit
checkout, breaking fresh-clone uvx and PyPI publish. The published
simpleaudit==0.3.0 wheel already includes the tracing module (verified:
parse_otlp_json, SpanStore, BuiltinOTLP all present), so the override is
stale.
- Remove [tool.uv.sources] block from pyproject.toml
- Regenerate uv.lock (simpleaudit now resolves from PyPI registry)
- Remove git clone steps from ci.yml, docker.yml, root Dockerfile,
and deploy/compose/Dockerfile (no longer needed)
- Update remaining repo links (SushantGautam/kelkalot → SimulaMet)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fix(docker): clone core for local path dependency
The Dockerfiles run uv sync which fails because pyproject.toml points
simpleaudit at ../SimpleAudit (editable) for the tracing layer. Clone the
core repo into /SimpleAudit (which is ../SimpleAudit relative to /app)
before uv sync. Add git to the base image.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Open WebUI has no embed mode, but its app shell loads /static/custom.css
on every page. Answer that one request with chat/embed.css instead of
forwarding it, so the iframe renders without the sidebar (collapsed rail,
expanded panel, and resizer). The rule lives in this repo and survives
Open WebUI upgrades; both modes share the file — the proxy reads it
directly, Caddy mounts it read-only.
feat: optional Open WebUI chat module with Studio single sign-on
Adds an off-by-default module that embeds Open WebUI in Studio at /chat/,
signed in as the Studio user. With SIMPLEAUDIT_CHAT unset nothing changes:
the routes 404, the sidebar has no Chat entry and no extra process runs.
Open WebUI serves from the root of an origin only — it has no base-path
setting and its HTML references /static, /api and /ws absolutely — so it
cannot be proxied under Studio's own /chat/ path. It gets its own origin,
which Studio embeds in an iframe.
Sign-on uses Open WebUI's trusted-header mode. A proxy in front of it asks
Studio who the browser is (GET /chat/authz, the standard forward-auth
contract) and injects the answer as X-Studio-Email/-Name/-Role. Django stays
the only authority on identity; nothing outside it reads sessions or user
tables. Superusers and workspace admins map to Open WebUI's admin role,
everyone else to user.
Two deployments, same contract:
embedded infra/chat_proxy.py, a stdlib HTTP proxy started by
`uvx simpleaudit-studi…
feat: interactive runs dashboard, security fixes and codebase cleanup
Dashboard
- Tabulator grid with server-side paging/sort/filter (/runs/data/), bulk
archive/unarchive/cancel, CSV export that follows filters
- Multi-select Target / Scenario set / Experiment filters, date presets,
filters kept in the URL; one-line toolbar
- Columns menu: show/hide, drag to reorder, resize; layout saved per user
(User.preferences) and in the browser; saved only on user actions
- Cancelled stat card so the cards add up to All
- Compare page: run names in headers, jumps to the comparison
Security
- Run snapshots no longer store raw API keys; the worker resolves keys at
execution time (migration strips keys from existing runs)
- UI forms require admin/auditor, same rule as the API; edit controls hidden
for viewers and archived workspaces
- Connection ping scoped to the user's workspaces
- Fix stored XSS (run event log, health page, scenario errors) and broken
inline confirm handlers; data-confirm pattern for forms
Cleanup
- Remove MinIO/boto3, CORS, pytest, u…
chore: consolidate deps to pyproject.toml + fix all ruff lint errors
Single source of truth: delete requirements.txt, standardize on uv.
- pyproject.toml is now the only dependency manifest (uv.lock pins versions)
- Makefile, Dockerfiles, CI all use 'uv sync' instead of 'pip install -r'
- Add 'server' extra (gunicorn) for the compose image
- Bump simpleaudit 0.2.0 -> 0.2.1 in uv.lock (was already in pyproject)
Fix all 169 pre-existing ruff errors so CI lint step passes:
- Auto-fix: I001, RUF100, UP017, PLR1730, F401, etc. (94)
- Ignore RUF012 (Django Meta/admin/factory false positives) via config
- BLE001: add justified noqa on intentional broad catches (16)
- SIM117: merge nested with statements (5)
- S110/DTZ005/SIM115/EXE001/TRY004/TRY203: targeted fixes (8)
feat: publish SimpleAudit Studio (docs, PyPI, footer links)
- Rewrite README for public release with Quick Start, live demo link
- Add MIT LICENSE
- Publish to PyPI; pin simpleaudit to 0.2.0
- Allow uvx to build from git (hatchling direct-reference metadata)
- Consolidate self-hosting docs to two paths (uvx + docker compose)
- Remove internal process artifacts (AGENTS.md, ADRs, ROADMAP, plans)
- Add 'Report issue or suggest change?' link to footer
- Remove banner.png from repository
fix: make root Dockerfile the HF Space image (was crash-looping)
Root cause: HF Spaces only build the root Dockerfile (there is no
frontmatter key to point elsewhere). The root Dockerfile was the
compose-only application image — no Postgres, no Hatchet, no start.sh,
serving :8000. But the Space expects the app on :7860, so gunicorn
started yet nothing answered on 7860 -> health check failed -> restart
loop.
Fix:
- Root Dockerfile is now the self-contained single-container Space image
(Postgres + Hatchet + web + worker via supervisord, serves :7860).
- Compose application image moved to deploy/compose/Dockerfile; updated
docker-compose.yml and CI to reference it.
- Removed redundant deploy/hf-space/Dockerfile.
- Made supervisord postgres path version-agnostic (matches start.sh).
Verified locally: root image builds, starts healthy, serves :7860 with
bootstrap + seed completing; compose image still builds for the stack.