simpleaudit-studio 0.2.7__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/.dockerignore +8 -2
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/.env.example +2 -19
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/.env.local.example +1 -9
- simpleaudit_studio-0.3.1/.github/workflows/ci.yml +44 -0
- simpleaudit_studio-0.3.1/.github/workflows/docker.yml +46 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/.github/workflows/publish.yml +10 -1
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/.gitignore +1 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/Dockerfile +19 -19
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/PKG-INFO +14 -22
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/README.md +9 -17
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/migrations/0001_initial.py +6 -1
- simpleaudit_studio-0.2.7/accounts/migrations/0002_user_workos_user_id.py → simpleaudit_studio-0.3.1/accounts/migrations/0002_user_preferences.py +3 -3
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/models.py +3 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/serializers.py +0 -7
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/services.py +10 -1
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/views.py +0 -21
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/workos_auth.py +4 -71
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/comparison.py +12 -11
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/events.py +97 -21
- simpleaudit_studio-0.3.1/audits/experiments.py +375 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/migrations/0001_initial.py +177 -7
- simpleaudit_studio-0.3.1/audits/migrations/0002_strip_api_keys_from_snapshots.py +41 -0
- simpleaudit_studio-0.3.1/audits/models.py +178 -0
- simpleaudit_studio-0.3.1/audits/monitors.py +487 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/services.py +90 -11
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/views.py +8 -11
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/config/settings.py +71 -50
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/config/urls.py +49 -22
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/deploy/compose/Dockerfile +1 -1
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/deploy/mock_openai_server.py +0 -3
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/deploy/postgres-init.sql +1 -1
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/docker-compose.yml +8 -32
- simpleaudit_studio-0.3.1/docs/architecture.md +87 -0
- simpleaudit_studio-0.3.1/docs/deployment.md +118 -0
- simpleaudit_studio-0.3.1/docs/domain-model.md +437 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/e2e/test_ui.py +31 -56
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/context_processors.py +56 -12
- simpleaudit_studio-0.3.1/infra/db.py +69 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/engine.py +37 -17
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/health.py +42 -44
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/health_api.py +2 -11
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/management/commands/dev_server.py +70 -19
- simpleaudit_studio-0.3.1/infra/management/commands/run_monitors.py +24 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/management/commands/seed_demo_audits.py +1 -8
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/management/commands/setup_local.py +3 -3
- simpleaudit_studio-0.3.1/infra/runs_table.py +233 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/seed.py +5 -16
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/seo.py +24 -4
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/simpleaudit_package.py +0 -5
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/startup_checks.py +4 -15
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/factories.py +1 -1
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_admin_api.py +16 -38
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_admin_page.py +12 -25
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_audit_archive.py +13 -28
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_audit_clone.py +1 -1
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_audit_rename.py +11 -11
- simpleaudit_studio-0.3.1/infra/tests/test_audit_results_live.py +130 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_audit_run_freeze.py +61 -1
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_audit_timing.py +13 -7
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_auth_projects.py +23 -10
- simpleaudit_studio-0.3.1/infra/tests/test_bootstrap.py +82 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_connection_delete.py +47 -8
- simpleaudit_studio-0.3.1/infra/tests/test_db_resilience.py +68 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_engine_integration.py +176 -3
- simpleaudit_studio-0.3.1/infra/tests/test_experiments.py +394 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_health_panel.py +21 -19
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_minimal_config.py +59 -36
- simpleaudit_studio-0.3.1/infra/tests/test_monitors.py +518 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_profile.py +14 -22
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_profile_ui.py +2 -2
- simpleaudit_studio-0.3.1/infra/tests/test_runs_table.py +208 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_seed_platform.py +3 -26
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_smoke_all_pages.py +38 -10
- simpleaudit_studio-0.3.1/infra/tests/test_snapshot_secrets.py +117 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_workspace_archive.py +10 -19
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_workspaces.py +4 -5
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_workspaces_ui.py +1 -1
- simpleaudit_studio-0.3.1/infra/tests/test_write_guard.py +116 -0
- simpleaudit_studio-0.3.1/infra/tests/utils.py +44 -0
- simpleaudit_studio-0.3.1/infra/ui.py +2220 -0
- simpleaudit_studio-0.3.1/infra/worker.py +1072 -0
- simpleaudit_studio-0.3.1/model_registry/migrations/0001_initial.py +116 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/model_registry/models.py +0 -4
- simpleaudit_studio-0.3.1/model_registry/services.py +78 -0
- simpleaudit_studio-0.3.1/model_registry/views.py +24 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/pyproject.toml +5 -4
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/migrations/0001_initial.py +1 -1
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/simpleaudit_studio/__init__.py +0 -2
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/simpleaudit_studio/cli.py +37 -12
- simpleaudit_studio-0.3.1/static/og-image.png +0 -0
- simpleaudit_studio-0.3.1/templates/404.html +4 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/admin.html +9 -22
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/auth/login.html +1 -8
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/auth/register.html +1 -8
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/auth/workos_login.html +1 -8
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/auth/workos_verify.html +1 -8
- simpleaudit_studio-0.3.1/templates/base.html +160 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/compare.html +19 -9
- simpleaudit_studio-0.3.1/templates/dashboard.html +440 -0
- simpleaudit_studio-0.3.1/templates/experiment_detail.html +197 -0
- simpleaudit_studio-0.3.1/templates/experiment_new.html +412 -0
- simpleaudit_studio-0.3.1/templates/experiment_review.html +248 -0
- simpleaudit_studio-0.3.1/templates/experiments.html +46 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/health.html +47 -33
- simpleaudit_studio-0.3.1/templates/models.html +424 -0
- simpleaudit_studio-0.3.1/templates/monitor_detail.html +117 -0
- simpleaudit_studio-0.3.1/templates/monitors.html +63 -0
- simpleaudit_studio-0.3.1/templates/partials/auth_header.html +8 -0
- simpleaudit_studio-0.3.1/templates/partials/chip_input.html +18 -0
- simpleaudit_studio-0.3.1/templates/partials/compare_run_header.html +4 -0
- simpleaudit_studio-0.3.1/templates/partials/footer.html +10 -0
- simpleaudit_studio-0.3.1/templates/partials/local_time_js.html +37 -0
- simpleaudit_studio-0.3.1/templates/partials/model_pickers.html +49 -0
- simpleaudit_studio-0.3.1/templates/partials/multi_filter.html +18 -0
- simpleaudit_studio-0.2.7/templates/404.html → simpleaudit_studio-0.3.1/templates/partials/not_found.html +1 -5
- simpleaudit_studio-0.3.1/templates/partials/result_rep_panel.html +84 -0
- simpleaudit_studio-0.3.1/templates/partials/run_results.html +32 -0
- simpleaudit_studio-0.3.1/templates/partials/run_settings.html +67 -0
- simpleaudit_studio-0.3.1/templates/partials/run_settings_js.html +63 -0
- simpleaudit_studio-0.3.1/templates/partials/severity_badge.html +1 -0
- simpleaudit_studio-0.3.1/templates/partials/sidebar.html +83 -0
- simpleaudit_studio-0.3.1/templates/partials/tooltips.html +73 -0
- simpleaudit_studio-0.3.1/templates/partials/ui_js.html +21 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/public/base_public.html +1 -1
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/public/landing.html +2 -2
- simpleaudit_studio-0.3.1/templates/run_detail.html +840 -0
- simpleaudit_studio-0.3.1/templates/run_result.html +135 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/scenarios.html +53 -59
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/seo/head.html +5 -2
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/workspaces.html +10 -23
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/uv.lock +121 -113
- simpleaudit_studio-0.2.7/.github/workflows/ci.yml +0 -50
- simpleaudit_studio-0.2.7/accounts/migrations/0003_project_archived.py +0 -17
- simpleaudit_studio-0.2.7/audits/migrations/0002_add_audit_run_archived.py +0 -17
- simpleaudit_studio-0.2.7/audits/migrations/0003_auditrun_updated_at.py +0 -17
- simpleaudit_studio-0.2.7/audits/migrations/0004_swap_endpoint_fks_for_models.py +0 -111
- simpleaudit_studio-0.2.7/audits/models.py +0 -77
- simpleaudit_studio-0.2.7/deploy/e2e_smoke.py +0 -229
- simpleaudit_studio-0.2.7/deploy/seed_bulk.py +0 -414
- simpleaudit_studio-0.2.7/docs/architecture.md +0 -384
- simpleaudit_studio-0.2.7/docs/deployment.md +0 -418
- simpleaudit_studio-0.2.7/docs/domain-model.md +0 -493
- simpleaudit_studio-0.2.7/docs/superpowers/specs/2026-09-26-super-admin-dashboard-design.md +0 -254
- simpleaudit_studio-0.2.7/infra/permissions.py +0 -31
- simpleaudit_studio-0.2.7/infra/tests/test_bootstrap.py +0 -67
- simpleaudit_studio-0.2.7/infra/ui.py +0 -1625
- simpleaudit_studio-0.2.7/infra/worker.py +0 -737
- simpleaudit_studio-0.2.7/model_registry/migrations/0001_initial.py +0 -90
- simpleaudit_studio-0.2.7/model_registry/migrations/0002_migrate_legacy_endpoints.py +0 -53
- simpleaudit_studio-0.2.7/model_registry/migrations/0003_remove_modeleendpoint.py +0 -18
- simpleaudit_studio-0.2.7/model_registry/services.py +0 -3
- simpleaudit_studio-0.2.7/model_registry/views.py +0 -51
- simpleaudit_studio-0.2.7/templates/audit_detail.html +0 -373
- simpleaudit_studio-0.2.7/templates/base.html +0 -265
- simpleaudit_studio-0.2.7/templates/dashboard.html +0 -145
- simpleaudit_studio-0.2.7/templates/models.html +0 -273
- simpleaudit_studio-0.2.7/templates/new_audit.html +0 -235
- simpleaudit_studio-0.2.7/templates/scenario_result_detail.html +0 -144
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/LICENSE +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/__init__.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/admin.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/admin_urls.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/apps.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/auth_urls.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/migrations/__init__.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/project_urls.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/__init__.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/admin.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/apps.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/migrations/__init__.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/serializers.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/urls.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/config/__init__.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/config/asgi.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/config/wsgi.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/__init__.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/admin.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/apps.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/exceptions.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/fixtures/demo_audit_results.json +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/hashing.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/logging.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/management/__init__.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/management/commands/__init__.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/management/commands/bootstrap_platform.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/management/commands/purge_test_data.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/management/commands/run_worker.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/management/commands/seed_platform.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/middleware.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/minimal_config.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/readiness.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/__init__.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_api_e2e.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_audit_events.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_audit_poll_results.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_audit_sse.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_audit_submission.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_comparison.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_health.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_middleware.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_model_registry.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_purge_test_data.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_scenario_import_export.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_scenarios.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/manage.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/model_registry/__init__.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/model_registry/admin.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/model_registry/apps.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/model_registry/migrations/__init__.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/model_registry/serializers.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/model_registry/urls.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/__init__.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/admin.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/apps.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/migrations/__init__.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/models.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/serializers.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/services.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/urls.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/views.py +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/static/favicon-16x16.svg +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/static/favicon-32x32.svg +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/static/favicon.svg +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/static/js/diff.min.js +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/static/logo.png +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/static/logo.svg +0 -0
- {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/profile.html +0 -0
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
.git
|
|
2
|
+
.github
|
|
2
3
|
.env
|
|
4
|
+
.env.local
|
|
3
5
|
.venv
|
|
4
6
|
__pycache__/
|
|
5
7
|
*.pyc
|
|
8
|
+
*.sqlite3
|
|
9
|
+
*.sqlite3-*
|
|
10
|
+
app.pid
|
|
6
11
|
data/
|
|
7
12
|
staticfiles/
|
|
8
|
-
media/
|
|
9
13
|
.pytest_cache/
|
|
10
14
|
.mypy_cache/
|
|
11
15
|
.ruff_cache/
|
|
12
|
-
|
|
16
|
+
docs/
|
|
17
|
+
e2e/
|
|
18
|
+
copilot-instructions.md
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
# For a local (non-Docker) development setup, copy `.env.local.example` instead.
|
|
9
9
|
#
|
|
10
10
|
# NOTE: docker-compose.yml overrides a few values per service (POSTGRES_HOST,
|
|
11
|
-
# HATCHET_
|
|
11
|
+
# HATCHET_*) so the container-internal hostnames are used
|
|
12
12
|
# regardless of what is written here.
|
|
13
13
|
# =============================================================================
|
|
14
14
|
|
|
15
15
|
# --- Django -----------------------------------------------------------------
|
|
16
16
|
DJANGO_SECRET_KEY=change-me
|
|
17
17
|
DJANGO_DEBUG=false
|
|
18
|
-
DJANGO_ALLOWED_HOSTS
|
|
18
|
+
DJANGO_ALLOWED_HOSTS=*
|
|
19
19
|
LOG_LEVEL=INFO
|
|
20
20
|
|
|
21
21
|
# --- Bootstrap (idempotent; run automatically by the web container) ----------
|
|
@@ -46,14 +46,6 @@ POSTGRES_PASSWORD=change-me
|
|
|
46
46
|
POSTGRES_HOST=postgres
|
|
47
47
|
POSTGRES_PORT=5432
|
|
48
48
|
|
|
49
|
-
# --- MinIO / S3-compatible object storage (OFF by default) --------------------
|
|
50
|
-
# Enable with: docker compose --profile storage up -d
|
|
51
|
-
# Then uncomment and set real credentials:
|
|
52
|
-
# MINIO_ENDPOINT=http://minio:9000
|
|
53
|
-
# MINIO_ACCESS_KEY=change-me
|
|
54
|
-
# MINIO_SECRET_KEY=change-me
|
|
55
|
-
# MINIO_BUCKET=simpleaudit-artifacts
|
|
56
|
-
|
|
57
49
|
# --- Durable workflow system (Hatchet) ----------------------------------------
|
|
58
50
|
# Docker Compose: the compose service name `hatchet-server` resolves inside the
|
|
59
51
|
# compose network. HTTP API on :8888, gRPC on :7077 (see docker-compose.yml).
|
|
@@ -76,9 +68,6 @@ HATCHET_TLS_STRATEGY=none
|
|
|
76
68
|
WORKER_POOL=cpu
|
|
77
69
|
|
|
78
70
|
# --- Operational limits --------------------------------------------------------
|
|
79
|
-
MAX_CONCURRENT_AUDITS=2
|
|
80
|
-
MAX_SCENARIOS_PER_RUN=500
|
|
81
|
-
SSE_MAX_CONNECTIONS_PER_USER=10
|
|
82
71
|
|
|
83
72
|
# --- SimpleAudit engine ---------------------------------------------------------
|
|
84
73
|
# No configuration needed here. The engine is a normal pip dependency pinned in
|
|
@@ -99,10 +88,6 @@ SSE_MAX_CONNECTIONS_PER_USER=10
|
|
|
99
88
|
SEED_DEMO_AUDITS=true
|
|
100
89
|
|
|
101
90
|
# --- Optional observability -----------------------------------------------------
|
|
102
|
-
OTEL_EXPORTER_OTLP_ENDPOINT=
|
|
103
|
-
LANGFUSE_PUBLIC_KEY=
|
|
104
|
-
LANGFUSE_SECRET_KEY=
|
|
105
|
-
LANGFUSE_HOST=
|
|
106
91
|
|
|
107
92
|
# --- Sentry error tracking & tracing (leave empty to disable) -------------------
|
|
108
93
|
SENTRY_DSN=
|
|
@@ -113,5 +98,3 @@ SENTRY_ENVIRONMENT=development
|
|
|
113
98
|
# WORKOS_CLIENT_ID is public; WORKOS_API_KEY is server-side secret (keep out of git).
|
|
114
99
|
WORKOS_CLIENT_ID=
|
|
115
100
|
WORKOS_API_KEY=
|
|
116
|
-
# Public base URL used to build the OAuth redirect URI, e.g. http://localhost:8000
|
|
117
|
-
APP_BASE_URL=http://localhost:8000
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
# --- Django -----------------------------------------------------------------
|
|
18
18
|
DJANGO_SECRET_KEY=local-dev-key-not-for-prod
|
|
19
19
|
DJANGO_DEBUG=true
|
|
20
|
-
DJANGO_ALLOWED_HOSTS
|
|
20
|
+
DJANGO_ALLOWED_HOSTS=*
|
|
21
21
|
LOG_LEVEL=DEBUG
|
|
22
22
|
|
|
23
23
|
# --- Bootstrap ----------------------------------------------------------------
|
|
@@ -80,13 +80,5 @@ WORKER_POOL=cpu
|
|
|
80
80
|
# SIMULACHAT_API_KEY=sk-...
|
|
81
81
|
|
|
82
82
|
# --- Optional (leave empty to disable) ------------------------------------------
|
|
83
|
-
MINIO_ENDPOINT=
|
|
84
|
-
MINIO_ACCESS_KEY=
|
|
85
|
-
MINIO_SECRET_KEY=
|
|
86
|
-
MINIO_BUCKET=simpleaudit-artifacts
|
|
87
|
-
OTEL_EXPORTER_OTLP_ENDPOINT=
|
|
88
|
-
LANGFUSE_PUBLIC_KEY=
|
|
89
|
-
LANGFUSE_SECRET_KEY=
|
|
90
|
-
LANGFUSE_HOST=
|
|
91
83
|
SENTRY_DSN=
|
|
92
84
|
SENTRY_ENVIRONMENT=development
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths-ignore: ["docs/**", "**.md", "LICENSE"]
|
|
7
|
+
pull_request:
|
|
8
|
+
paths-ignore: ["docs/**", "**.md", "LICENSE"]
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
ci:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
env:
|
|
18
|
+
DJANGO_SETTINGS_MODULE: config.settings
|
|
19
|
+
DJANGO_SECRET_KEY: ci-secret-key
|
|
20
|
+
DJANGO_DEBUG: "false"
|
|
21
|
+
DJANGO_ALLOWED_HOSTS: "*"
|
|
22
|
+
SIMPLEAUDIT_LOCAL_SQLITE: "1"
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
with:
|
|
26
|
+
fetch-depth: 0 # the embedded-Hatchet step diffs against origin/main
|
|
27
|
+
- uses: actions/setup-python@v5
|
|
28
|
+
with:
|
|
29
|
+
python-version: "3.12"
|
|
30
|
+
- uses: astral-sh/setup-uv@v7
|
|
31
|
+
- name: Install dependencies
|
|
32
|
+
run: uv sync --frozen --extra dev
|
|
33
|
+
- name: Lint (ruff)
|
|
34
|
+
run: uv run ruff check .
|
|
35
|
+
- name: Run tests (parallel)
|
|
36
|
+
run: uv run python manage.py test infra --parallel auto --exclude-tag embedded_hatchet
|
|
37
|
+
- name: Check if embedded Hatchet files changed
|
|
38
|
+
id: hatchet-changed
|
|
39
|
+
run: |
|
|
40
|
+
CHANGED=$(git diff --name-only origin/main...HEAD -- infra/minimal_config.py infra/worker.py 2>/dev/null || true)
|
|
41
|
+
if [ -n "$CHANGED" ]; then echo "changed=true" >> "$GITHUB_OUTPUT"; else echo "changed=false" >> "$GITHUB_OUTPUT"; fi
|
|
42
|
+
- name: Run tests (embedded Hatchet, serial)
|
|
43
|
+
if: steps.hatchet-changed.outputs.changed == 'true'
|
|
44
|
+
run: uv run python manage.py test infra.tests.test_minimal_config.TestEmbeddedHatchetLifecycle
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Docker build
|
|
2
|
+
|
|
3
|
+
# Builds both images (no push) only when something that affects them changes,
|
|
4
|
+
# so Dockerfile / dependency / settings breakage is caught before deploying.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
paths:
|
|
9
|
+
- "Dockerfile"
|
|
10
|
+
- "deploy/**"
|
|
11
|
+
- "docker-compose.yml"
|
|
12
|
+
- ".dockerignore"
|
|
13
|
+
- "pyproject.toml"
|
|
14
|
+
- "uv.lock"
|
|
15
|
+
- "config/settings.py"
|
|
16
|
+
- "infra/minimal_config.py"
|
|
17
|
+
- "simpleaudit_studio/**"
|
|
18
|
+
- ".github/workflows/docker.yml"
|
|
19
|
+
pull_request:
|
|
20
|
+
paths:
|
|
21
|
+
- "Dockerfile"
|
|
22
|
+
- "deploy/**"
|
|
23
|
+
- "docker-compose.yml"
|
|
24
|
+
- ".dockerignore"
|
|
25
|
+
- "pyproject.toml"
|
|
26
|
+
- "uv.lock"
|
|
27
|
+
- "config/settings.py"
|
|
28
|
+
- "infra/minimal_config.py"
|
|
29
|
+
- "simpleaudit_studio/**"
|
|
30
|
+
- ".github/workflows/docker.yml"
|
|
31
|
+
|
|
32
|
+
concurrency:
|
|
33
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
34
|
+
cancel-in-progress: true
|
|
35
|
+
|
|
36
|
+
jobs:
|
|
37
|
+
docker:
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
- name: Build HF Space / demo image
|
|
42
|
+
run: docker build -t simpleaudit-studio:ci .
|
|
43
|
+
- name: Build Compose image
|
|
44
|
+
run: docker build -f deploy/compose/Dockerfile -t simpleaudit-studio-compose:ci .
|
|
45
|
+
- name: Validate compose file
|
|
46
|
+
run: cp .env.example .env && docker compose config --quiet
|
|
@@ -20,7 +20,16 @@ jobs:
|
|
|
20
20
|
uses: actions/setup-python@v5
|
|
21
21
|
with:
|
|
22
22
|
python-version: "3.12"
|
|
23
|
-
-
|
|
23
|
+
- uses: astral-sh/setup-uv@v7
|
|
24
|
+
- name: Test before publishing
|
|
25
|
+
env:
|
|
26
|
+
DJANGO_SECRET_KEY: ci-secret-key
|
|
27
|
+
SIMPLEAUDIT_LOCAL_SQLITE: "1"
|
|
28
|
+
run: |
|
|
29
|
+
uv sync --frozen --extra dev
|
|
30
|
+
uv run ruff check .
|
|
31
|
+
uv run python manage.py test infra --parallel auto --exclude-tag embedded_hatchet
|
|
32
|
+
- name: Install build tooling
|
|
24
33
|
run: |
|
|
25
34
|
python -m pip install --upgrade pip
|
|
26
35
|
pip install build
|
|
@@ -29,47 +29,47 @@ RUN apt-get update \
|
|
|
29
29
|
|
|
30
30
|
# --- Python dependencies -----------------------------------------------------
|
|
31
31
|
# pyproject.toml is the single source of truth; uv.lock pins exact versions.
|
|
32
|
-
# The postgres extra is included because Django imports the psycopg driver at
|
|
33
|
-
# startup even when SIMPLEAUDIT_MINIMAL=1 uses SQLite for the domain DB.
|
|
34
32
|
# uv sync creates /app/.venv; the PATH update keeps the `python` entrypoint.
|
|
35
33
|
COPY pyproject.toml uv.lock README.md ./
|
|
36
34
|
RUN pip install uv \
|
|
37
|
-
&& uv sync --frozen --no-install-project --no-dev
|
|
35
|
+
&& uv sync --frozen --no-install-project --no-dev
|
|
38
36
|
ENV PATH="/app/.venv/bin:$PATH"
|
|
39
37
|
|
|
40
|
-
# --- Application code --------------------------------------------------------
|
|
41
|
-
COPY . .
|
|
42
|
-
|
|
43
|
-
# Collect static files so Django can serve them without DEBUG.
|
|
44
|
-
RUN python manage.py collectstatic --noinput
|
|
45
|
-
|
|
46
|
-
# --- Non-root user ------------------------------------------------------------
|
|
47
|
-
RUN useradd --create-home appuser \
|
|
48
|
-
&& mkdir -p /app/staticfiles /app/media \
|
|
49
|
-
&& chown -R appuser:appuser /app
|
|
50
|
-
USER appuser
|
|
51
|
-
|
|
52
38
|
# --- Environment defaults for the Space --------------------------------------
|
|
53
39
|
# SIMPLEAUDIT_MINIMAL=1 enables the minimal config path (SQLite + embedded Hatchet).
|
|
54
40
|
# Override via HF Space Secrets for anything sensitive.
|
|
41
|
+
# Must be set BEFORE collectstatic: Django settings select the DB engine from it,
|
|
42
|
+
# and the postgres driver is an optional extra not installed in this image.
|
|
55
43
|
ENV SIMPLEAUDIT_MINIMAL=1 \
|
|
56
44
|
PORT=7860 \
|
|
57
45
|
DJANGO_SECRET_KEY=hf-space-demo-secret-key-change-in-production \
|
|
58
46
|
DJANGO_DEBUG=false \
|
|
59
|
-
DJANGO_ALLOWED_HOSTS
|
|
47
|
+
DJANGO_ALLOWED_HOSTS=* \
|
|
60
48
|
BOOTSTRAP_USERNAME=studio \
|
|
61
49
|
BOOTSTRAP_EMAIL=studio@example.local \
|
|
62
50
|
BOOTSTRAP_PASSWORD=admin123 \
|
|
63
51
|
BOOTSTRAP_PROJECT_NAME=Default \
|
|
64
52
|
DEMO_MODE=true \
|
|
65
53
|
DEMO_USERNAME=studio \
|
|
66
|
-
DEMO_PASSWORD=admin123
|
|
67
|
-
|
|
68
|
-
|
|
54
|
+
DEMO_PASSWORD=admin123
|
|
55
|
+
|
|
56
|
+
# --- Application code --------------------------------------------------------
|
|
57
|
+
COPY . .
|
|
58
|
+
|
|
59
|
+
# Collect static files so Django can serve them without DEBUG.
|
|
60
|
+
RUN python manage.py collectstatic --noinput
|
|
61
|
+
|
|
62
|
+
# --- Non-root user ------------------------------------------------------------
|
|
63
|
+
RUN useradd --create-home appuser \
|
|
64
|
+
&& mkdir -p /app/staticfiles \
|
|
65
|
+
&& chown -R appuser:appuser /app
|
|
66
|
+
USER appuser
|
|
69
67
|
|
|
70
68
|
EXPOSE 7860
|
|
71
69
|
|
|
72
70
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=90s --retries=5 \
|
|
73
71
|
CMD curl -fsS http://localhost:7860/healthz || exit 1
|
|
74
72
|
|
|
73
|
+
# Model connections point at OpenAI's real base URL (https://api.openai.com/v1).
|
|
74
|
+
# Users add their API key in the UI to run real audits.
|
|
75
75
|
CMD ["python", "-m", "simpleaudit_studio.cli"]
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: simpleaudit-studio
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Production AI model audit platform — one-liner demo mode via uvx
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Requires-Python: >=3.11
|
|
8
|
-
Requires-Dist:
|
|
9
|
-
Requires-Dist: django-cors-headers==4.7.0
|
|
8
|
+
Requires-Dist: cronsim==2.7
|
|
10
9
|
Requires-Dist: django==5.2.4
|
|
11
10
|
Requires-Dist: djangorestframework==3.16.0
|
|
12
11
|
Requires-Dist: drf-spectacular==0.28.0
|
|
13
12
|
Requires-Dist: hatchet-sdk==1.41.0
|
|
13
|
+
Requires-Dist: httpx>=0.27
|
|
14
14
|
Requires-Dist: pydantic<3,>=2.7
|
|
15
15
|
Requires-Dist: python-dotenv==1.1.1
|
|
16
16
|
Requires-Dist: requests==2.32.5
|
|
@@ -19,9 +19,9 @@ Requires-Dist: simpleaudit==0.2.1
|
|
|
19
19
|
Requires-Dist: workos==10.4.0
|
|
20
20
|
Provides-Extra: dev
|
|
21
21
|
Requires-Dist: factory-boy==3.3.2; extra == 'dev'
|
|
22
|
-
Requires-Dist: pytest; extra == 'dev'
|
|
23
|
-
Requires-Dist: pytest-django; extra == 'dev'
|
|
24
22
|
Requires-Dist: ruff; extra == 'dev'
|
|
23
|
+
Provides-Extra: e2e
|
|
24
|
+
Requires-Dist: playwright>=1.45; extra == 'e2e'
|
|
25
25
|
Provides-Extra: postgres
|
|
26
26
|
Requires-Dist: psycopg[binary]==3.2.13; extra == 'postgres'
|
|
27
27
|
Provides-Extra: server
|
|
@@ -78,7 +78,7 @@ The pre-seeded connections point at a built-in mock server. To run real audits,
|
|
|
78
78
|
| Together AI | `https://api.together.xyz/v1` | Many open models |
|
|
79
79
|
| Groq | `https://api.groq.com/openai/v1` | Fast inference |
|
|
80
80
|
|
|
81
|
-
Set the API key in the connection form (leave blank for local servers that don't require auth), then
|
|
81
|
+
Set the API key in the connection form (leave blank for local servers that don't require auth), then start a run from **New Experiment**.
|
|
82
82
|
|
|
83
83
|
## 🛠️ Local Development
|
|
84
84
|
|
|
@@ -95,7 +95,7 @@ cd SimpleAuditStudio
|
|
|
95
95
|
cp .env.local.example .env # minimal local config (SQLite + mock models)
|
|
96
96
|
|
|
97
97
|
uv sync --extra dev --extra postgres # create .venv + install all deps
|
|
98
|
-
uv run manage.py setup_local # migrate + create admin (studio/
|
|
98
|
+
uv run manage.py setup_local # migrate + create admin (studio / BOOTSTRAP_PASSWORD from .env) + seed data
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
`setup_local` chains the three idempotent first-run steps (migrate → bootstrap admin → seed scenario packs & model connections) into one command. Re-run it any time — it skips what already exists.
|
|
@@ -125,22 +125,22 @@ cp .env.example .env
|
|
|
125
125
|
docker compose up -d
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
Services: Web UI (:8000), PostgreSQL, Hatchet queue (:8888), Worker. Optional
|
|
128
|
+
Services: Web UI (:8000), PostgreSQL, Hatchet queue (:8888), Worker. Optional profile: `--profile mock` (mock model API).
|
|
129
129
|
|
|
130
130
|
See [docs/deployment.md](docs/deployment.md) for production hardening, backups, and upgrades.
|
|
131
131
|
|
|
132
132
|
## ✨ What You Can Do
|
|
133
133
|
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
134
|
+
- Build versioned scenario sets and register OpenAI-compatible models
|
|
135
|
+
- Run experiments across models, scenario versions and settings, with a review step before launch
|
|
136
|
+
- Watch runs live, including per-repetition results
|
|
137
|
+
- Monitor models for drift on a schedule (interval or cron)
|
|
138
|
+
- Filter, customise and compare runs on an interactive dashboard
|
|
139
139
|
|
|
140
140
|
## 🏗️ Architecture
|
|
141
141
|
|
|
142
142
|
```
|
|
143
|
-
Browser → Django
|
|
143
|
+
Browser → Django (gunicorn :8000 in Compose; runserver :7860 in the HF Space / uvx demo)
|
|
144
144
|
↓
|
|
145
145
|
PostgreSQL 16 (domain + queue DBs)
|
|
146
146
|
↓
|
|
@@ -159,11 +159,3 @@ Browser → Django/Gunicorn (:7860)
|
|
|
159
159
|
## 🤝 Contributing
|
|
160
160
|
|
|
161
161
|
Architecture decisions are documented in [docs/architecture.md](docs/architecture.md).
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
@@ -48,7 +48,7 @@ The pre-seeded connections point at a built-in mock server. To run real audits,
|
|
|
48
48
|
| Together AI | `https://api.together.xyz/v1` | Many open models |
|
|
49
49
|
| Groq | `https://api.groq.com/openai/v1` | Fast inference |
|
|
50
50
|
|
|
51
|
-
Set the API key in the connection form (leave blank for local servers that don't require auth), then
|
|
51
|
+
Set the API key in the connection form (leave blank for local servers that don't require auth), then start a run from **New Experiment**.
|
|
52
52
|
|
|
53
53
|
## 🛠️ Local Development
|
|
54
54
|
|
|
@@ -65,7 +65,7 @@ cd SimpleAuditStudio
|
|
|
65
65
|
cp .env.local.example .env # minimal local config (SQLite + mock models)
|
|
66
66
|
|
|
67
67
|
uv sync --extra dev --extra postgres # create .venv + install all deps
|
|
68
|
-
uv run manage.py setup_local # migrate + create admin (studio/
|
|
68
|
+
uv run manage.py setup_local # migrate + create admin (studio / BOOTSTRAP_PASSWORD from .env) + seed data
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
`setup_local` chains the three idempotent first-run steps (migrate → bootstrap admin → seed scenario packs & model connections) into one command. Re-run it any time — it skips what already exists.
|
|
@@ -95,22 +95,22 @@ cp .env.example .env
|
|
|
95
95
|
docker compose up -d
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
Services: Web UI (:8000), PostgreSQL, Hatchet queue (:8888), Worker. Optional
|
|
98
|
+
Services: Web UI (:8000), PostgreSQL, Hatchet queue (:8888), Worker. Optional profile: `--profile mock` (mock model API).
|
|
99
99
|
|
|
100
100
|
See [docs/deployment.md](docs/deployment.md) for production hardening, backups, and upgrades.
|
|
101
101
|
|
|
102
102
|
## ✨ What You Can Do
|
|
103
103
|
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
-
|
|
107
|
-
-
|
|
108
|
-
-
|
|
104
|
+
- Build versioned scenario sets and register OpenAI-compatible models
|
|
105
|
+
- Run experiments across models, scenario versions and settings, with a review step before launch
|
|
106
|
+
- Watch runs live, including per-repetition results
|
|
107
|
+
- Monitor models for drift on a schedule (interval or cron)
|
|
108
|
+
- Filter, customise and compare runs on an interactive dashboard
|
|
109
109
|
|
|
110
110
|
## 🏗️ Architecture
|
|
111
111
|
|
|
112
112
|
```
|
|
113
|
-
Browser → Django
|
|
113
|
+
Browser → Django (gunicorn :8000 in Compose; runserver :7860 in the HF Space / uvx demo)
|
|
114
114
|
↓
|
|
115
115
|
PostgreSQL 16 (domain + queue DBs)
|
|
116
116
|
↓
|
|
@@ -129,11 +129,3 @@ Browser → Django/Gunicorn (:7860)
|
|
|
129
129
|
## 🤝 Contributing
|
|
130
130
|
|
|
131
131
|
Architecture decisions are documented in [docs/architecture.md](docs/architecture.md).
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Generated by Django 5.2.4 on 2026-09-
|
|
1
|
+
# Generated by Django 5.2.4 on 2026-09-27 19:42
|
|
2
2
|
|
|
3
3
|
import django.contrib.auth.models
|
|
4
4
|
import django.contrib.auth.validators
|
|
@@ -31,6 +31,7 @@ class Migration(migrations.Migration):
|
|
|
31
31
|
("name", models.CharField(max_length=200, unique=True)),
|
|
32
32
|
("slug", models.SlugField(max_length=200, unique=True)),
|
|
33
33
|
("description", models.TextField(blank=True)),
|
|
34
|
+
("archived", models.BooleanField(db_index=True, default=False)),
|
|
34
35
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
35
36
|
("updated_at", models.DateTimeField(auto_now=True)),
|
|
36
37
|
],
|
|
@@ -121,6 +122,10 @@ class Migration(migrations.Migration):
|
|
|
121
122
|
default=django.utils.timezone.now, verbose_name="date joined"
|
|
122
123
|
),
|
|
123
124
|
),
|
|
125
|
+
(
|
|
126
|
+
"workos_user_id",
|
|
127
|
+
models.CharField(blank=True, max_length=64, null=True, unique=True),
|
|
128
|
+
),
|
|
124
129
|
(
|
|
125
130
|
"groups",
|
|
126
131
|
models.ManyToManyField(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Generated by Django 5.2.4 on 2026-09-
|
|
1
|
+
# Generated by Django 5.2.4 on 2026-09-27 20:23
|
|
2
2
|
|
|
3
3
|
from django.db import migrations, models
|
|
4
4
|
|
|
@@ -11,7 +11,7 @@ class Migration(migrations.Migration):
|
|
|
11
11
|
operations = [
|
|
12
12
|
migrations.AddField(
|
|
13
13
|
model_name="user",
|
|
14
|
-
name="
|
|
15
|
-
field=models.
|
|
14
|
+
name="preferences",
|
|
15
|
+
field=models.JSONField(blank=True, default=dict),
|
|
16
16
|
),
|
|
17
17
|
]
|
|
@@ -21,6 +21,9 @@ class User(AbstractUser):
|
|
|
21
21
|
|
|
22
22
|
# Stable WorkOS AuthKit user id; null for locally-created accounts.
|
|
23
23
|
workos_user_id = models.CharField(max_length=64, unique=True, null=True, blank=True)
|
|
24
|
+
# Per-user UI preferences (e.g. dashboard column layout). Keys are
|
|
25
|
+
# whitelisted by the preference endpoint; values are small JSON.
|
|
26
|
+
preferences = models.JSONField(default=dict, blank=True)
|
|
24
27
|
|
|
25
28
|
groups = models.ManyToManyField(
|
|
26
29
|
"auth.Group",
|
|
@@ -36,13 +36,6 @@ class RegisterSerializer(serializers.Serializer):
|
|
|
36
36
|
return User.objects.create_user(**validated_data)
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
class ProjectSerializer(serializers.ModelSerializer):
|
|
40
|
-
class Meta:
|
|
41
|
-
model = Project
|
|
42
|
-
fields = ("id", "name", "slug", "description", "created_at", "updated_at")
|
|
43
|
-
read_only_fields = ("id", "created_at", "updated_at")
|
|
44
|
-
|
|
45
|
-
|
|
46
39
|
class WorkspaceItemSerializer(serializers.ModelSerializer):
|
|
47
40
|
"""Workspace as shown in lists/detail, including whether the requesting
|
|
48
41
|
user can administer it (drives UI affordances)."""
|
|
@@ -392,7 +392,7 @@ def admin_stats_payload() -> dict:
|
|
|
392
392
|
"audit_run_completed": run_status_totals.get("completed", 0),
|
|
393
393
|
"audit_run_failed": run_status_totals.get("failed", 0),
|
|
394
394
|
"audit_run_active": sum(
|
|
395
|
-
n for status, n in run_status_totals.items() if status not in
|
|
395
|
+
n for status, n in run_status_totals.items() if status not in AuditRun.TERMINAL_STATUSES
|
|
396
396
|
),
|
|
397
397
|
"scenario_count": Scenario.objects.count(),
|
|
398
398
|
"scenario_set_count": ScenarioSet.objects.count(),
|
|
@@ -400,3 +400,12 @@ def admin_stats_payload() -> dict:
|
|
|
400
400
|
},
|
|
401
401
|
"workspaces": workspaces,
|
|
402
402
|
}
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def is_any_project_admin(user) -> bool:
|
|
406
|
+
"""Superuser, or ADMIN in at least one workspace (gates system-wide pages like Health)."""
|
|
407
|
+
if not user or not user.is_authenticated:
|
|
408
|
+
return False
|
|
409
|
+
if user.is_superuser:
|
|
410
|
+
return True
|
|
411
|
+
return ProjectMembership.objects.filter(user=user, role=ProjectMembership.Role.ADMIN).exists()
|
|
@@ -327,27 +327,6 @@ def update_or_remove_member(request, project_id, user_id):
|
|
|
327
327
|
return Response(ProjectMembershipSerializer(membership).data)
|
|
328
328
|
|
|
329
329
|
|
|
330
|
-
# ─── Legacy aliases (kept for backward compatibility) ────────────────────────
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
@api_view(["GET"])
|
|
334
|
-
@permission_classes([IsAuthenticated])
|
|
335
|
-
def list_projects(request):
|
|
336
|
-
return list_workspaces(request)
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
@api_view(["POST"])
|
|
340
|
-
@permission_classes([IsAuthenticated])
|
|
341
|
-
def create_project(request):
|
|
342
|
-
return create_workspace_view(request)
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
@api_view(["GET"])
|
|
346
|
-
@permission_classes([IsAuthenticated])
|
|
347
|
-
def get_project(request, project_id):
|
|
348
|
-
return workspace_detail(request, project_id)
|
|
349
|
-
|
|
350
|
-
|
|
351
330
|
def _get_project_or_404(project_id) -> Project:
|
|
352
331
|
try:
|
|
353
332
|
return Project.objects.get(pk=project_id)
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
"""WorkOS
|
|
1
|
+
"""WorkOS Magic Auth integration (passwordless email verification).
|
|
2
2
|
|
|
3
3
|
Flow:
|
|
4
|
-
1. ``
|
|
5
|
-
2.
|
|
6
|
-
|
|
7
|
-
returns the local Django user (created on first sign-in).
|
|
4
|
+
1. ``send_magic_auth_code`` emails the user a one-time code.
|
|
5
|
+
2. ``authenticate_magic_auth`` checks the code and returns the local Django
|
|
6
|
+
user (created on first sign-in).
|
|
8
7
|
|
|
9
8
|
The WorkOS API key is server-side only; the client ID is safe to expose in
|
|
10
9
|
templates. Neither secret is ever stored on the User model — identity is
|
|
@@ -26,31 +25,6 @@ def _client():
|
|
|
26
25
|
return WorkOSClient(api_key=settings.WORKOS_API_KEY)
|
|
27
26
|
|
|
28
27
|
|
|
29
|
-
def build_authorization_url(
|
|
30
|
-
redirect_uri: str,
|
|
31
|
-
state: str,
|
|
32
|
-
login_hint: str | None = None,
|
|
33
|
-
provider: str | None = None,
|
|
34
|
-
) -> str:
|
|
35
|
-
"""Build the URL that starts the WorkOS AuthKit flow.
|
|
36
|
-
|
|
37
|
-
``login_hint`` pre-fills the user's email and routes to Magic Auth
|
|
38
|
-
(email code) instead of auto-redirecting to an SSO/OAuth connection.
|
|
39
|
-
``provider`` pre-selects a social login provider (e.g. ``GoogleOAuth``,
|
|
40
|
-
``MicrosoftOAuth``) so the user goes straight to that IdP.
|
|
41
|
-
"""
|
|
42
|
-
kwargs: dict = {
|
|
43
|
-
"client_id": settings.WORKOS_CLIENT_ID,
|
|
44
|
-
"redirect_uri": redirect_uri,
|
|
45
|
-
"state": state,
|
|
46
|
-
}
|
|
47
|
-
if login_hint:
|
|
48
|
-
kwargs["login_hint"] = login_hint
|
|
49
|
-
if provider:
|
|
50
|
-
kwargs["provider"] = provider
|
|
51
|
-
return _client().user_management.get_authorization_url(**kwargs)
|
|
52
|
-
|
|
53
|
-
|
|
54
28
|
def send_magic_auth_code(email: str, *, ip_address: str | None = None, user_agent: str | None = None):
|
|
55
29
|
"""Send a 6-digit Magic Auth code to the given email via WorkOS."""
|
|
56
30
|
_client().user_management.create_magic_auth(
|
|
@@ -104,47 +78,6 @@ def authenticate_magic_auth(code: str, email: str, *, ip_address: str | None = N
|
|
|
104
78
|
return user, created
|
|
105
79
|
|
|
106
80
|
|
|
107
|
-
def exchange_code_for_user(code: str, *, ip_address: str | None = None, user_agent: str | None = None):
|
|
108
|
-
"""Exchange an OAuth callback code for a local Django user (SSO/OAuth flow).
|
|
109
|
-
|
|
110
|
-
Kept for future SSO support; the primary flow uses Magic Auth directly.
|
|
111
|
-
"""
|
|
112
|
-
response = _client().user_management.authenticate_with_code(
|
|
113
|
-
code=code,
|
|
114
|
-
ip_address=ip_address,
|
|
115
|
-
user_agent=user_agent,
|
|
116
|
-
)
|
|
117
|
-
wo_user = response.user
|
|
118
|
-
if wo_user is None:
|
|
119
|
-
raise ValueError("WorkOS returned no user for the authentication code.")
|
|
120
|
-
|
|
121
|
-
username = _derive_username(wo_user.email or wo_user.id)
|
|
122
|
-
user, created = User.objects.get_or_create(
|
|
123
|
-
workos_user_id=wo_user.id,
|
|
124
|
-
defaults={
|
|
125
|
-
"username": username,
|
|
126
|
-
"email": wo_user.email or "",
|
|
127
|
-
"first_name": wo_user.first_name or "",
|
|
128
|
-
"last_name": wo_user.last_name or "",
|
|
129
|
-
"is_staff": False,
|
|
130
|
-
},
|
|
131
|
-
)
|
|
132
|
-
if not created:
|
|
133
|
-
changed = False
|
|
134
|
-
if wo_user.email and user.email != wo_user.email:
|
|
135
|
-
user.email = wo_user.email
|
|
136
|
-
changed = True
|
|
137
|
-
if wo_user.first_name and user.first_name != wo_user.first_name:
|
|
138
|
-
user.first_name = wo_user.first_name
|
|
139
|
-
changed = True
|
|
140
|
-
if wo_user.last_name and user.last_name != wo_user.last_name:
|
|
141
|
-
user.last_name = wo_user.last_name
|
|
142
|
-
changed = True
|
|
143
|
-
if changed:
|
|
144
|
-
user.save(update_fields=["email", "first_name", "last_name"])
|
|
145
|
-
return user, created
|
|
146
|
-
|
|
147
|
-
|
|
148
81
|
def _derive_username(email_or_id: str) -> str:
|
|
149
82
|
"""Derive a unique-ish username from the WorkOS email (or user id)."""
|
|
150
83
|
base = (email_or_id.split("@")[0] if "@" in email_or_id else email_or_id)[:30]
|