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.
Files changed (227) hide show
  1. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/.dockerignore +8 -2
  2. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/.env.example +2 -19
  3. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/.env.local.example +1 -9
  4. simpleaudit_studio-0.3.1/.github/workflows/ci.yml +44 -0
  5. simpleaudit_studio-0.3.1/.github/workflows/docker.yml +46 -0
  6. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/.github/workflows/publish.yml +10 -1
  7. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/.gitignore +1 -0
  8. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/Dockerfile +19 -19
  9. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/PKG-INFO +14 -22
  10. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/README.md +9 -17
  11. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/migrations/0001_initial.py +6 -1
  12. 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
  13. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/models.py +3 -0
  14. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/serializers.py +0 -7
  15. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/services.py +10 -1
  16. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/views.py +0 -21
  17. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/workos_auth.py +4 -71
  18. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/comparison.py +12 -11
  19. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/events.py +97 -21
  20. simpleaudit_studio-0.3.1/audits/experiments.py +375 -0
  21. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/migrations/0001_initial.py +177 -7
  22. simpleaudit_studio-0.3.1/audits/migrations/0002_strip_api_keys_from_snapshots.py +41 -0
  23. simpleaudit_studio-0.3.1/audits/models.py +178 -0
  24. simpleaudit_studio-0.3.1/audits/monitors.py +487 -0
  25. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/services.py +90 -11
  26. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/views.py +8 -11
  27. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/config/settings.py +71 -50
  28. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/config/urls.py +49 -22
  29. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/deploy/compose/Dockerfile +1 -1
  30. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/deploy/mock_openai_server.py +0 -3
  31. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/deploy/postgres-init.sql +1 -1
  32. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/docker-compose.yml +8 -32
  33. simpleaudit_studio-0.3.1/docs/architecture.md +87 -0
  34. simpleaudit_studio-0.3.1/docs/deployment.md +118 -0
  35. simpleaudit_studio-0.3.1/docs/domain-model.md +437 -0
  36. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/e2e/test_ui.py +31 -56
  37. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/context_processors.py +56 -12
  38. simpleaudit_studio-0.3.1/infra/db.py +69 -0
  39. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/engine.py +37 -17
  40. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/health.py +42 -44
  41. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/health_api.py +2 -11
  42. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/management/commands/dev_server.py +70 -19
  43. simpleaudit_studio-0.3.1/infra/management/commands/run_monitors.py +24 -0
  44. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/management/commands/seed_demo_audits.py +1 -8
  45. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/management/commands/setup_local.py +3 -3
  46. simpleaudit_studio-0.3.1/infra/runs_table.py +233 -0
  47. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/seed.py +5 -16
  48. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/seo.py +24 -4
  49. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/simpleaudit_package.py +0 -5
  50. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/startup_checks.py +4 -15
  51. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/factories.py +1 -1
  52. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_admin_api.py +16 -38
  53. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_admin_page.py +12 -25
  54. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_audit_archive.py +13 -28
  55. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_audit_clone.py +1 -1
  56. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_audit_rename.py +11 -11
  57. simpleaudit_studio-0.3.1/infra/tests/test_audit_results_live.py +130 -0
  58. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_audit_run_freeze.py +61 -1
  59. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_audit_timing.py +13 -7
  60. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_auth_projects.py +23 -10
  61. simpleaudit_studio-0.3.1/infra/tests/test_bootstrap.py +82 -0
  62. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_connection_delete.py +47 -8
  63. simpleaudit_studio-0.3.1/infra/tests/test_db_resilience.py +68 -0
  64. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_engine_integration.py +176 -3
  65. simpleaudit_studio-0.3.1/infra/tests/test_experiments.py +394 -0
  66. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_health_panel.py +21 -19
  67. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_minimal_config.py +59 -36
  68. simpleaudit_studio-0.3.1/infra/tests/test_monitors.py +518 -0
  69. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_profile.py +14 -22
  70. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_profile_ui.py +2 -2
  71. simpleaudit_studio-0.3.1/infra/tests/test_runs_table.py +208 -0
  72. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_seed_platform.py +3 -26
  73. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_smoke_all_pages.py +38 -10
  74. simpleaudit_studio-0.3.1/infra/tests/test_snapshot_secrets.py +117 -0
  75. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_workspace_archive.py +10 -19
  76. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_workspaces.py +4 -5
  77. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_workspaces_ui.py +1 -1
  78. simpleaudit_studio-0.3.1/infra/tests/test_write_guard.py +116 -0
  79. simpleaudit_studio-0.3.1/infra/tests/utils.py +44 -0
  80. simpleaudit_studio-0.3.1/infra/ui.py +2220 -0
  81. simpleaudit_studio-0.3.1/infra/worker.py +1072 -0
  82. simpleaudit_studio-0.3.1/model_registry/migrations/0001_initial.py +116 -0
  83. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/model_registry/models.py +0 -4
  84. simpleaudit_studio-0.3.1/model_registry/services.py +78 -0
  85. simpleaudit_studio-0.3.1/model_registry/views.py +24 -0
  86. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/pyproject.toml +5 -4
  87. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/migrations/0001_initial.py +1 -1
  88. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/simpleaudit_studio/__init__.py +0 -2
  89. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/simpleaudit_studio/cli.py +37 -12
  90. simpleaudit_studio-0.3.1/static/og-image.png +0 -0
  91. simpleaudit_studio-0.3.1/templates/404.html +4 -0
  92. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/admin.html +9 -22
  93. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/auth/login.html +1 -8
  94. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/auth/register.html +1 -8
  95. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/auth/workos_login.html +1 -8
  96. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/auth/workos_verify.html +1 -8
  97. simpleaudit_studio-0.3.1/templates/base.html +160 -0
  98. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/compare.html +19 -9
  99. simpleaudit_studio-0.3.1/templates/dashboard.html +440 -0
  100. simpleaudit_studio-0.3.1/templates/experiment_detail.html +197 -0
  101. simpleaudit_studio-0.3.1/templates/experiment_new.html +412 -0
  102. simpleaudit_studio-0.3.1/templates/experiment_review.html +248 -0
  103. simpleaudit_studio-0.3.1/templates/experiments.html +46 -0
  104. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/health.html +47 -33
  105. simpleaudit_studio-0.3.1/templates/models.html +424 -0
  106. simpleaudit_studio-0.3.1/templates/monitor_detail.html +117 -0
  107. simpleaudit_studio-0.3.1/templates/monitors.html +63 -0
  108. simpleaudit_studio-0.3.1/templates/partials/auth_header.html +8 -0
  109. simpleaudit_studio-0.3.1/templates/partials/chip_input.html +18 -0
  110. simpleaudit_studio-0.3.1/templates/partials/compare_run_header.html +4 -0
  111. simpleaudit_studio-0.3.1/templates/partials/footer.html +10 -0
  112. simpleaudit_studio-0.3.1/templates/partials/local_time_js.html +37 -0
  113. simpleaudit_studio-0.3.1/templates/partials/model_pickers.html +49 -0
  114. simpleaudit_studio-0.3.1/templates/partials/multi_filter.html +18 -0
  115. simpleaudit_studio-0.2.7/templates/404.html → simpleaudit_studio-0.3.1/templates/partials/not_found.html +1 -5
  116. simpleaudit_studio-0.3.1/templates/partials/result_rep_panel.html +84 -0
  117. simpleaudit_studio-0.3.1/templates/partials/run_results.html +32 -0
  118. simpleaudit_studio-0.3.1/templates/partials/run_settings.html +67 -0
  119. simpleaudit_studio-0.3.1/templates/partials/run_settings_js.html +63 -0
  120. simpleaudit_studio-0.3.1/templates/partials/severity_badge.html +1 -0
  121. simpleaudit_studio-0.3.1/templates/partials/sidebar.html +83 -0
  122. simpleaudit_studio-0.3.1/templates/partials/tooltips.html +73 -0
  123. simpleaudit_studio-0.3.1/templates/partials/ui_js.html +21 -0
  124. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/public/base_public.html +1 -1
  125. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/public/landing.html +2 -2
  126. simpleaudit_studio-0.3.1/templates/run_detail.html +840 -0
  127. simpleaudit_studio-0.3.1/templates/run_result.html +135 -0
  128. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/scenarios.html +53 -59
  129. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/seo/head.html +5 -2
  130. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/templates/workspaces.html +10 -23
  131. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/uv.lock +121 -113
  132. simpleaudit_studio-0.2.7/.github/workflows/ci.yml +0 -50
  133. simpleaudit_studio-0.2.7/accounts/migrations/0003_project_archived.py +0 -17
  134. simpleaudit_studio-0.2.7/audits/migrations/0002_add_audit_run_archived.py +0 -17
  135. simpleaudit_studio-0.2.7/audits/migrations/0003_auditrun_updated_at.py +0 -17
  136. simpleaudit_studio-0.2.7/audits/migrations/0004_swap_endpoint_fks_for_models.py +0 -111
  137. simpleaudit_studio-0.2.7/audits/models.py +0 -77
  138. simpleaudit_studio-0.2.7/deploy/e2e_smoke.py +0 -229
  139. simpleaudit_studio-0.2.7/deploy/seed_bulk.py +0 -414
  140. simpleaudit_studio-0.2.7/docs/architecture.md +0 -384
  141. simpleaudit_studio-0.2.7/docs/deployment.md +0 -418
  142. simpleaudit_studio-0.2.7/docs/domain-model.md +0 -493
  143. simpleaudit_studio-0.2.7/docs/superpowers/specs/2026-09-26-super-admin-dashboard-design.md +0 -254
  144. simpleaudit_studio-0.2.7/infra/permissions.py +0 -31
  145. simpleaudit_studio-0.2.7/infra/tests/test_bootstrap.py +0 -67
  146. simpleaudit_studio-0.2.7/infra/ui.py +0 -1625
  147. simpleaudit_studio-0.2.7/infra/worker.py +0 -737
  148. simpleaudit_studio-0.2.7/model_registry/migrations/0001_initial.py +0 -90
  149. simpleaudit_studio-0.2.7/model_registry/migrations/0002_migrate_legacy_endpoints.py +0 -53
  150. simpleaudit_studio-0.2.7/model_registry/migrations/0003_remove_modeleendpoint.py +0 -18
  151. simpleaudit_studio-0.2.7/model_registry/services.py +0 -3
  152. simpleaudit_studio-0.2.7/model_registry/views.py +0 -51
  153. simpleaudit_studio-0.2.7/templates/audit_detail.html +0 -373
  154. simpleaudit_studio-0.2.7/templates/base.html +0 -265
  155. simpleaudit_studio-0.2.7/templates/dashboard.html +0 -145
  156. simpleaudit_studio-0.2.7/templates/models.html +0 -273
  157. simpleaudit_studio-0.2.7/templates/new_audit.html +0 -235
  158. simpleaudit_studio-0.2.7/templates/scenario_result_detail.html +0 -144
  159. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/LICENSE +0 -0
  160. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/__init__.py +0 -0
  161. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/admin.py +0 -0
  162. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/admin_urls.py +0 -0
  163. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/apps.py +0 -0
  164. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/auth_urls.py +0 -0
  165. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/migrations/__init__.py +0 -0
  166. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/accounts/project_urls.py +0 -0
  167. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/__init__.py +0 -0
  168. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/admin.py +0 -0
  169. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/apps.py +0 -0
  170. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/migrations/__init__.py +0 -0
  171. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/serializers.py +0 -0
  172. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/audits/urls.py +0 -0
  173. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/config/__init__.py +0 -0
  174. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/config/asgi.py +0 -0
  175. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/config/wsgi.py +0 -0
  176. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/__init__.py +0 -0
  177. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/admin.py +0 -0
  178. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/apps.py +0 -0
  179. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/exceptions.py +0 -0
  180. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/fixtures/demo_audit_results.json +0 -0
  181. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/hashing.py +0 -0
  182. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/logging.py +0 -0
  183. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/management/__init__.py +0 -0
  184. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/management/commands/__init__.py +0 -0
  185. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/management/commands/bootstrap_platform.py +0 -0
  186. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/management/commands/purge_test_data.py +0 -0
  187. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/management/commands/run_worker.py +0 -0
  188. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/management/commands/seed_platform.py +0 -0
  189. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/middleware.py +0 -0
  190. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/minimal_config.py +0 -0
  191. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/readiness.py +0 -0
  192. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/__init__.py +0 -0
  193. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_api_e2e.py +0 -0
  194. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_audit_events.py +0 -0
  195. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_audit_poll_results.py +0 -0
  196. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_audit_sse.py +0 -0
  197. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_audit_submission.py +0 -0
  198. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_comparison.py +0 -0
  199. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_health.py +0 -0
  200. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_middleware.py +0 -0
  201. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_model_registry.py +0 -0
  202. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_purge_test_data.py +0 -0
  203. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_scenario_import_export.py +0 -0
  204. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/infra/tests/test_scenarios.py +0 -0
  205. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/manage.py +0 -0
  206. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/model_registry/__init__.py +0 -0
  207. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/model_registry/admin.py +0 -0
  208. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/model_registry/apps.py +0 -0
  209. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/model_registry/migrations/__init__.py +0 -0
  210. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/model_registry/serializers.py +0 -0
  211. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/model_registry/urls.py +0 -0
  212. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/__init__.py +0 -0
  213. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/admin.py +0 -0
  214. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/apps.py +0 -0
  215. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/migrations/__init__.py +0 -0
  216. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/models.py +0 -0
  217. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/serializers.py +0 -0
  218. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/services.py +0 -0
  219. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/urls.py +0 -0
  220. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/scenarios/views.py +0 -0
  221. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/static/favicon-16x16.svg +0 -0
  222. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/static/favicon-32x32.svg +0 -0
  223. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/static/favicon.svg +0 -0
  224. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/static/js/diff.min.js +0 -0
  225. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/static/logo.png +0 -0
  226. {simpleaudit_studio-0.2.7 → simpleaudit_studio-0.3.1}/static/logo.svg +0 -0
  227. {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
- # sync 1790239341
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_*, MINIO_ENDPOINT) so the container-internal hostnames are used
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=localhost,127.0.0.1
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=localhost,127.0.0.1
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
- - name: Install dependencies
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
@@ -8,6 +8,7 @@ venv/
8
8
  # Django
9
9
  db.sqlite3
10
10
  *.sqlite3
11
+ *.sqlite3-*
11
12
  media/
12
13
  staticfiles/
13
14
 
@@ -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 --extra postgres
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=localhost,127.0.0.1,.hf.space,.huggingface.co \
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
- MAX_CONCURRENT_AUDITS=1 \
68
- MAX_SCENARIOS_PER_RUN=50
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.2.7
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: boto3==1.40.4
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 launch an audit from the Scenario Library.
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/admin123) + seed data
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 profiles: `--profile storage` (MinIO), `--profile mock` (mock model API).
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
- - Browse the scenario library and create new audit scenarios
135
- - Register model endpoints (OpenAI-compatible APIs)
136
- - Launch audits and watch live progress
137
- - Compare results across runs
138
- - Explore the visualizer for detailed analysis
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/Gunicorn (:7860)
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 launch an audit from the Scenario Library.
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/admin123) + seed data
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 profiles: `--profile storage` (MinIO), `--profile mock` (mock model API).
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
- - Browse the scenario library and create new audit scenarios
105
- - Register model endpoints (OpenAI-compatible APIs)
106
- - Launch audits and watch live progress
107
- - Compare results across runs
108
- - Explore the visualizer for detailed analysis
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/Gunicorn (:7860)
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-23 12:05
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-24 13:27
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="workos_user_id",
15
- field=models.CharField(blank=True, max_length=64, null=True, unique=True),
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 ("completed", "failed", "cancelled")
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 AuthKit integration (passwordless email verification + SSO).
1
+ """WorkOS Magic Auth integration (passwordless email verification).
2
2
 
3
3
  Flow:
4
- 1. ``build_authorization_url`` sends the browser to WorkOS's hosted login UI.
5
- 2. WorkOS redirects back to ``/auth/workos/callback/`` with a one-time code.
6
- 3. ``exchange_code_for_user`` trades the code for an authenticated user and
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]