simpleaudit-studio 0.3.0__tar.gz → 0.4.0__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 (237) hide show
  1. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/.env.example +3 -2
  2. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/.env.local.example +13 -28
  3. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/PKG-INFO +27 -17
  4. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/README.md +25 -15
  5. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/accounts/services.py +8 -1
  6. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/audits/comparison.py +17 -11
  7. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/audits/experiments.py +111 -25
  8. simpleaudit_studio-0.4.0/audits/migrations/0003_judges.py +92 -0
  9. simpleaudit_studio-0.4.0/audits/migrations/0004_judge_model_per_run.py +72 -0
  10. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/audits/models.py +10 -2
  11. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/audits/monitors.py +57 -18
  12. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/audits/serializers.py +5 -0
  13. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/audits/services.py +106 -10
  14. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/audits/views.py +13 -1
  15. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/config/settings.py +10 -0
  16. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/config/urls.py +9 -0
  17. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/docs/architecture.md +4 -2
  18. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/docs/deployment.md +3 -1
  19. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/docs/domain-model.md +65 -9
  20. simpleaudit_studio-0.4.0/infra/codegen.py +356 -0
  21. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/context_processors.py +40 -0
  22. simpleaudit_studio-0.4.0/infra/engine.py +464 -0
  23. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/hashing.py +5 -1
  24. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/health.py +12 -1
  25. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/management/commands/dev_server.py +30 -8
  26. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/management/commands/seed_demo_audits.py +12 -1
  27. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/management/commands/seed_platform.py +4 -0
  28. simpleaudit_studio-0.4.0/infra/minimal_config.py +210 -0
  29. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/runs_table.py +17 -8
  30. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/seed.py +19 -1
  31. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/seo.py +15 -1
  32. simpleaudit_studio-0.4.0/infra/templatetags/json_filters.py +13 -0
  33. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/factories.py +22 -0
  34. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_api_e2e.py +2 -2
  35. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_audit_clone.py +2 -1
  36. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_audit_poll_results.py +2 -1
  37. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_audit_run_freeze.py +5 -0
  38. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_audit_sse.py +2 -1
  39. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_audit_submission.py +2 -0
  40. simpleaudit_studio-0.4.0/infra/tests/test_bootstrap.py +82 -0
  41. simpleaudit_studio-0.4.0/infra/tests/test_codegen.py +392 -0
  42. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_comparison.py +2 -1
  43. simpleaudit_studio-0.4.0/infra/tests/test_connection_sharing.py +260 -0
  44. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_engine_integration.py +80 -11
  45. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_experiments.py +79 -5
  46. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_health_panel.py +18 -0
  47. simpleaudit_studio-0.4.0/infra/tests/test_judges.py +401 -0
  48. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_monitors.py +10 -2
  49. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_profile_ui.py +1 -1
  50. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_purge_test_data.py +3 -2
  51. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_scenarios.py +55 -0
  52. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_snapshot_secrets.py +38 -0
  53. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_write_guard.py +25 -0
  54. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/ui.py +343 -162
  55. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/worker.py +10 -0
  56. simpleaudit_studio-0.4.0/judges/admin.py +19 -0
  57. simpleaudit_studio-0.4.0/judges/apps.py +7 -0
  58. simpleaudit_studio-0.4.0/judges/migrations/0001_judges.py +122 -0
  59. simpleaudit_studio-0.4.0/judges/migrations/0002_judge_model_per_run.py +18 -0
  60. simpleaudit_studio-0.4.0/judges/migrations/0003_composable_judges.py +96 -0
  61. simpleaudit_studio-0.4.0/judges/models.py +86 -0
  62. simpleaudit_studio-0.4.0/judges/services.py +400 -0
  63. simpleaudit_studio-0.4.0/judges/views.py +271 -0
  64. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/manage.py +5 -0
  65. simpleaudit_studio-0.4.0/model_registry/migrations/0002_descriptions.py +22 -0
  66. simpleaudit_studio-0.4.0/model_registry/migrations/0003_modelconnection_shared_with_and_more.py +33 -0
  67. simpleaudit_studio-0.4.0/model_registry/migrations/__init__.py +0 -0
  68. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/model_registry/models.py +35 -1
  69. simpleaudit_studio-0.4.0/model_registry/services.py +203 -0
  70. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/model_registry/views.py +13 -3
  71. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/pyproject.toml +2 -2
  72. simpleaudit_studio-0.4.0/scenarios/__init__.py +0 -0
  73. simpleaudit_studio-0.4.0/scenarios/migrations/0002_add_severity_ceiling_documents_file_uri.py +27 -0
  74. simpleaudit_studio-0.4.0/scenarios/migrations/__init__.py +0 -0
  75. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/scenarios/models.py +16 -2
  76. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/scenarios/serializers.py +17 -1
  77. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/scenarios/services.py +64 -5
  78. simpleaudit_studio-0.4.0/static/js/version-diff.js +86 -0
  79. simpleaudit_studio-0.4.0/static/og-image.png +0 -0
  80. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/admin.html +2 -0
  81. simpleaudit_studio-0.4.0/templates/base.html +160 -0
  82. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/compare.html +4 -0
  83. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/dashboard.html +3 -1
  84. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/experiment_new.html +44 -11
  85. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/experiment_review.html +5 -0
  86. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/health.html +23 -13
  87. simpleaudit_studio-0.4.0/templates/judge_detail.html +368 -0
  88. simpleaudit_studio-0.4.0/templates/judges.html +104 -0
  89. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/models.html +111 -22
  90. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/monitor_detail.html +3 -3
  91. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/monitors.html +1 -1
  92. simpleaudit_studio-0.4.0/templates/partials/judge_picker.html +71 -0
  93. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/partials/model_pickers.html +19 -6
  94. simpleaudit_studio-0.4.0/templates/partials/need_help.html +5 -0
  95. simpleaudit_studio-0.4.0/templates/partials/pinned_version.html +4 -0
  96. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/partials/result_rep_panel.html +21 -0
  97. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/partials/run_settings.html +11 -5
  98. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/partials/run_settings_js.html +1 -1
  99. simpleaudit_studio-0.4.0/templates/partials/severity_badge.html +1 -0
  100. simpleaudit_studio-0.4.0/templates/partials/sidebar.html +83 -0
  101. simpleaudit_studio-0.4.0/templates/partials/text_file_modal.html +96 -0
  102. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/partials/tooltips.html +2 -2
  103. simpleaudit_studio-0.4.0/templates/partials/version_badge.html +6 -0
  104. simpleaudit_studio-0.4.0/templates/partials/version_diff_js.html +2 -0
  105. simpleaudit_studio-0.4.0/templates/partials/version_diff_modal.html +29 -0
  106. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/run_detail.html +47 -4
  107. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/run_result.html +2 -2
  108. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/scenarios.html +131 -200
  109. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/seo/head.html +4 -1
  110. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/uv.lock +7 -7
  111. simpleaudit_studio-0.3.0/infra/engine.py +0 -636
  112. simpleaudit_studio-0.3.0/infra/minimal_config.py +0 -95
  113. simpleaudit_studio-0.3.0/infra/tests/test_bootstrap.py +0 -42
  114. simpleaudit_studio-0.3.0/model_registry/services.py +0 -78
  115. simpleaudit_studio-0.3.0/templates/base.html +0 -241
  116. simpleaudit_studio-0.3.0/templates/partials/severity_badge.html +0 -1
  117. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/.dockerignore +0 -0
  118. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/.github/workflows/ci.yml +0 -0
  119. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/.github/workflows/docker.yml +0 -0
  120. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/.github/workflows/publish.yml +0 -0
  121. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/.gitignore +0 -0
  122. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/Dockerfile +0 -0
  123. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/LICENSE +0 -0
  124. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/accounts/__init__.py +0 -0
  125. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/accounts/admin.py +0 -0
  126. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/accounts/admin_urls.py +0 -0
  127. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/accounts/apps.py +0 -0
  128. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/accounts/auth_urls.py +0 -0
  129. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/accounts/migrations/0001_initial.py +0 -0
  130. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/accounts/migrations/0002_user_preferences.py +0 -0
  131. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/accounts/migrations/__init__.py +0 -0
  132. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/accounts/models.py +0 -0
  133. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/accounts/project_urls.py +0 -0
  134. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/accounts/serializers.py +0 -0
  135. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/accounts/views.py +0 -0
  136. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/accounts/workos_auth.py +0 -0
  137. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/audits/__init__.py +0 -0
  138. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/audits/admin.py +0 -0
  139. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/audits/apps.py +0 -0
  140. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/audits/events.py +0 -0
  141. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/audits/migrations/0001_initial.py +0 -0
  142. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/audits/migrations/0002_strip_api_keys_from_snapshots.py +0 -0
  143. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/audits/migrations/__init__.py +0 -0
  144. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/audits/urls.py +0 -0
  145. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/config/__init__.py +0 -0
  146. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/config/asgi.py +0 -0
  147. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/config/wsgi.py +0 -0
  148. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/deploy/compose/Dockerfile +0 -0
  149. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/deploy/mock_openai_server.py +0 -0
  150. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/deploy/postgres-init.sql +0 -0
  151. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/docker-compose.yml +0 -0
  152. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/e2e/test_ui.py +0 -0
  153. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/__init__.py +0 -0
  154. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/admin.py +0 -0
  155. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/apps.py +0 -0
  156. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/db.py +0 -0
  157. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/exceptions.py +0 -0
  158. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/fixtures/demo_audit_results.json +0 -0
  159. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/health_api.py +0 -0
  160. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/logging.py +0 -0
  161. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/management/__init__.py +0 -0
  162. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/management/commands/__init__.py +0 -0
  163. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/management/commands/bootstrap_platform.py +0 -0
  164. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/management/commands/purge_test_data.py +0 -0
  165. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/management/commands/run_monitors.py +0 -0
  166. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/management/commands/run_worker.py +0 -0
  167. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/management/commands/setup_local.py +0 -0
  168. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/middleware.py +0 -0
  169. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/readiness.py +0 -0
  170. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/simpleaudit_package.py +0 -0
  171. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/startup_checks.py +0 -0
  172. {simpleaudit_studio-0.3.0/model_registry → simpleaudit_studio-0.4.0/infra/templatetags}/__init__.py +0 -0
  173. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/__init__.py +0 -0
  174. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_admin_api.py +0 -0
  175. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_admin_page.py +0 -0
  176. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_audit_archive.py +0 -0
  177. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_audit_events.py +0 -0
  178. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_audit_rename.py +0 -0
  179. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_audit_results_live.py +0 -0
  180. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_audit_timing.py +0 -0
  181. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_auth_projects.py +0 -0
  182. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_connection_delete.py +0 -0
  183. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_db_resilience.py +0 -0
  184. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_health.py +0 -0
  185. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_middleware.py +0 -0
  186. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_minimal_config.py +0 -0
  187. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_model_registry.py +0 -0
  188. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_profile.py +0 -0
  189. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_runs_table.py +0 -0
  190. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_scenario_import_export.py +0 -0
  191. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_seed_platform.py +0 -0
  192. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_smoke_all_pages.py +0 -0
  193. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_workspace_archive.py +0 -0
  194. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_workspaces.py +0 -0
  195. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/test_workspaces_ui.py +0 -0
  196. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/infra/tests/utils.py +0 -0
  197. {simpleaudit_studio-0.3.0/model_registry/migrations → simpleaudit_studio-0.4.0/judges}/__init__.py +0 -0
  198. {simpleaudit_studio-0.3.0/scenarios → simpleaudit_studio-0.4.0/judges/migrations}/__init__.py +0 -0
  199. {simpleaudit_studio-0.3.0/scenarios/migrations → simpleaudit_studio-0.4.0/model_registry}/__init__.py +0 -0
  200. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/model_registry/admin.py +0 -0
  201. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/model_registry/apps.py +0 -0
  202. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/model_registry/migrations/0001_initial.py +0 -0
  203. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/model_registry/serializers.py +0 -0
  204. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/model_registry/urls.py +0 -0
  205. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/scenarios/admin.py +0 -0
  206. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/scenarios/apps.py +0 -0
  207. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/scenarios/migrations/0001_initial.py +0 -0
  208. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/scenarios/urls.py +0 -0
  209. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/scenarios/views.py +0 -0
  210. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/simpleaudit_studio/__init__.py +0 -0
  211. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/simpleaudit_studio/cli.py +0 -0
  212. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/static/favicon-16x16.svg +0 -0
  213. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/static/favicon-32x32.svg +0 -0
  214. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/static/favicon.svg +0 -0
  215. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/static/js/diff.min.js +0 -0
  216. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/static/logo.png +0 -0
  217. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/static/logo.svg +0 -0
  218. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/404.html +0 -0
  219. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/auth/login.html +0 -0
  220. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/auth/register.html +0 -0
  221. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/auth/workos_login.html +0 -0
  222. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/auth/workos_verify.html +0 -0
  223. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/experiment_detail.html +0 -0
  224. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/experiments.html +0 -0
  225. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/partials/auth_header.html +0 -0
  226. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/partials/chip_input.html +0 -0
  227. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/partials/compare_run_header.html +0 -0
  228. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/partials/footer.html +0 -0
  229. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/partials/local_time_js.html +0 -0
  230. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/partials/multi_filter.html +0 -0
  231. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/partials/not_found.html +0 -0
  232. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/partials/run_results.html +0 -0
  233. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/partials/ui_js.html +0 -0
  234. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/profile.html +0 -0
  235. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/public/base_public.html +0 -0
  236. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/public/landing.html +0 -0
  237. {simpleaudit_studio-0.3.0 → simpleaudit_studio-0.4.0}/templates/workspaces.html +0 -0
@@ -76,8 +76,9 @@ WORKER_POOL=cpu
76
76
  # against an unmerged revision, create a gitignored simpleaudit-dependency.yaml
77
77
  # at the project root with a single `git_ref:` key — do NOT edit pyproject.toml.
78
78
 
79
- # --- Model API credentials (resolved at execution time, never stored) ----------
80
- # Add one variable per model endpoint's `secret_reference`, e.g.:
79
+ # --- Model API keys (optional) ---------------------------------------------------
80
+ # Keys can be stored on a connection in the Models page. For connections set to
81
+ # "Environment variable", add that variable here (read when a run starts), e.g.:
81
82
  # SIMULACHAT_API_KEY=sk-...
82
83
 
83
84
  # --- Demo audit seeding ---------------------------------------------------------
@@ -1,17 +1,14 @@
1
1
  # =============================================================================
2
- # SimpleAudit Platform — LOCAL (non-Docker) development environment
2
+ # SimpleAudit Studio — local development
3
3
  #
4
- # Copy to `.env` and edit. `manage.py` loads `.env` automatically.
4
+ # Copy to `.env` (manage.py loads it automatically), then:
5
5
  #
6
- # This setup runs the web server and worker as plain processes on your machine
7
- # (SQLite for the domain DB, Hatchet + Postgres via Docker for the queue).
8
- # It is for development only — the canonical deployment is Docker Compose
9
- # (see `.env.example`).
6
+ # uv sync --extra dev
7
+ # uv run manage.py setup_local # first time: + seed data
8
+ # uv run manage.py dev_server --embedded # daily
10
9
  #
11
- # Prerequisites:
12
- # 1. A Python 3.12+ virtualenv with `uv sync --extra dev --extra postgres`
13
- # 2. Docker, for Postgres + Hatchet (the "partial Docker" pattern below)
14
- # 3. A SimpleAudit engine checkout (for the worker to execute audits)
10
+ # SQLite holds the app data and `--embedded` starts its own Hatchet queue, so
11
+ # no Docker is needed. For production use Docker Compose with `.env.example`.
15
12
  # =============================================================================
16
13
 
17
14
  # --- Django -----------------------------------------------------------------
@@ -40,23 +37,10 @@ POSTGRES_HOST=localhost
40
37
  POSTGRES_PORT=5432
41
38
 
42
39
  # --- Hatchet (durable job queue) ----------------------------------------------
43
- # Run Postgres + Hatchet via Docker ("partial Docker" pattern):
44
- #
45
- # docker compose up -d postgres hatchet-server
46
- #
47
- # That gives you:
48
- # - Postgres on localhost:5432 (Hatchet's own queue DB, created by
49
- # deploy/postgres-init.sql on first init)
50
- # - Hatchet HTTP API on localhost:8888 (dashboard at http://localhost:8888)
51
- # - Hatchet gRPC on localhost:7077
52
- #
53
- # The auth-disabled hatchet-lite-dev image writes its worker JWT to its config
54
- # volume. To use it from a local (non-container) worker, copy the token out:
55
- #
40
+ # Not used with `dev_server --embedded`, which starts its own engine.
41
+ # Only for plain `dev_server` against an external Hatchet, e.g.
42
+ # `docker compose up -d postgres hatchet-server`, then copy its worker token:
56
43
  # docker compose cp hatchet-server:/config/authdisabled-token ./hatchet-token
57
- #
58
- # and point HATCHET_TOKEN_FILE at it (below). Alternatively set HATCHET_API_KEY
59
- # directly to the token's contents.
60
44
  HATCHET_SERVER_URL=http://localhost:8888
61
45
  HATCHET_GRPC_URL=localhost:7077
62
46
  HATCHET_API_KEY=
@@ -75,8 +59,9 @@ WORKER_POOL=cpu
75
59
  # That value is recorded as the run's git_commit provenance. Do NOT edit
76
60
  # pyproject.toml for a dev override.
77
61
 
78
- # --- Model API credentials (resolved at execution time, never stored) ----------
79
- # Add one variable per model endpoint's `secret_reference`, e.g.:
62
+ # --- Model API keys (optional) ---------------------------------------------------
63
+ # Keys can be stored on a connection in the Models page. For connections set to
64
+ # "Environment variable", add that variable here (read when a run starts), e.g.:
80
65
  # SIMULACHAT_API_KEY=sk-...
81
66
 
82
67
  # --- Optional (leave empty to disable) ------------------------------------------
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: simpleaudit-studio
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Production AI model audit platform — one-liner demo mode via uvx
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -15,7 +15,7 @@ Requires-Dist: pydantic<3,>=2.7
15
15
  Requires-Dist: python-dotenv==1.1.1
16
16
  Requires-Dist: requests==2.32.5
17
17
  Requires-Dist: sentry-sdk[django]>=2.0
18
- Requires-Dist: simpleaudit==0.2.1
18
+ Requires-Dist: simpleaudit==0.2.3
19
19
  Requires-Dist: workos==10.4.0
20
20
  Provides-Extra: dev
21
21
  Requires-Dist: factory-boy==3.3.2; extra == 'dev'
@@ -82,37 +82,45 @@ Set the API key in the connection form (leave blank for local servers that don't
82
82
 
83
83
  ## 🛠️ Local Development
84
84
 
85
- For contributors working on the codebase, use [uv](https://docs.astral.sh/uv/) — no manual venv steps, no Makefile. Two verbs cover everything:
85
+ Needs Python 3.11+ and [uv](https://docs.astral.sh/uv/). No Docker, no Makefile.
86
86
 
87
- - **`uv sync`** — create/update `.venv` from `pyproject.toml` + `uv.lock`
88
- - **`uv run <cmd>`** — run any command inside `.venv` (auto-syncs first if the lockfile changed)
89
-
90
- ### Setup (one time)
87
+ ### Setup (once)
91
88
 
92
89
  ```bash
93
90
  git clone https://github.com/SushantGautam/SimpleAuditStudio
94
91
  cd SimpleAuditStudio
95
- cp .env.local.example .env # minimal local config (SQLite + mock models)
92
+ cp .env.local.example .env # SQLite + embedded queue; admin is studio / BOOTSTRAP_PASSWORD
93
+ uv sync --extra dev # create .venv with app + dev tools
94
+ uv run manage.py setup_local # migrate, create admin + workspace, seed scenario packs & models
95
+ ```
96
96
 
97
- uv sync --extra dev --extra postgres # create .venv + install all deps
98
- uv run manage.py setup_local # migrate + create admin (studio / BOOTSTRAP_PASSWORD from .env) + seed data
97
+ ### Daily work
98
+
99
+ ```bash
100
+ uv run manage.py dev_server --embedded # web UI + API + worker + embedded Hatchet → http://localhost:8000
99
101
  ```
100
102
 
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.
103
+ Every start applies migrations and makes sure the admin (a superuser) and default workspace exist, so pulling new code needs no extra steps. Keep in mind:
102
104
 
103
- ### Daily work
105
+ - Only one `dev_server --embedded` (or `uvx simpleaudit-studio`) can run at a time: they share the embedded queue database.
106
+ - The web server reloads on code changes; the worker does not. Restart `dev_server` after changing `infra/worker.py` or `infra/engine.py`.
107
+
108
+ ### Tests and lint
104
109
 
105
110
  ```bash
106
- uv run manage.py dev_server --embedded # easiest: web UI + API + audit worker, zero-Docker → http://localhost:8000
111
+ SIMPLEAUDIT_LOCAL_SQLITE=1 uv run manage.py test infra --exclude-tag embedded_hatchet
112
+ uv run ruff check .
107
113
  ```
108
114
 
109
- Other options:
115
+ ### Other setups
116
+
110
117
  ```bash
111
- uv run manage.py runserver # web UI + API only (no worker)
112
- uv run manage.py dev_server # web UI + worker, uses .env (edit POSTGRES_*/HATCHET_* to point at your infra)
113
- SIMPLEAUDIT_LOCAL_SQLITE=1 uv run manage.py test infra # tests
118
+ uv run manage.py dev_server --no-worker # web UI + API only
119
+ uv run manage.py dev_server # use the Postgres + Hatchet configured in .env (see .env.example)
114
120
  ```
115
121
 
122
+ See [docs/deployment.md](docs/deployment.md) for every environment variable and management command.
123
+
116
124
  ## 🐳 Self-Hosting
117
125
 
118
126
  For teams or multi-user setups, use Docker Compose:
@@ -132,6 +140,8 @@ See [docs/deployment.md](docs/deployment.md) for production hardening, backups,
132
140
  ## ✨ What You Can Do
133
141
 
134
142
  - Build versioned scenario sets and register OpenAI-compatible models
143
+ - Define versioned judges: start from a SimpleAudit judge (safety, harm, helpfulness, factuality, abstention, checklist, …) and edit its criteria, or write your own criteria as a severity, 1–10 score (with your own dimensions) or yes/no judge; run with any judge model
144
+ - Test your deployment's target system prompt, and compare prompts or judges side by side
135
145
  - Run experiments across models, scenario versions and settings, with a review step before launch
136
146
  - Watch runs live, including per-repetition results
137
147
  - Monitor models for drift on a schedule (interval or cron)
@@ -52,37 +52,45 @@ Set the API key in the connection form (leave blank for local servers that don't
52
52
 
53
53
  ## 🛠️ Local Development
54
54
 
55
- For contributors working on the codebase, use [uv](https://docs.astral.sh/uv/) — no manual venv steps, no Makefile. Two verbs cover everything:
55
+ Needs Python 3.11+ and [uv](https://docs.astral.sh/uv/). No Docker, no Makefile.
56
56
 
57
- - **`uv sync`** — create/update `.venv` from `pyproject.toml` + `uv.lock`
58
- - **`uv run <cmd>`** — run any command inside `.venv` (auto-syncs first if the lockfile changed)
59
-
60
- ### Setup (one time)
57
+ ### Setup (once)
61
58
 
62
59
  ```bash
63
60
  git clone https://github.com/SushantGautam/SimpleAuditStudio
64
61
  cd SimpleAuditStudio
65
- cp .env.local.example .env # minimal local config (SQLite + mock models)
62
+ cp .env.local.example .env # SQLite + embedded queue; admin is studio / BOOTSTRAP_PASSWORD
63
+ uv sync --extra dev # create .venv with app + dev tools
64
+ uv run manage.py setup_local # migrate, create admin + workspace, seed scenario packs & models
65
+ ```
66
66
 
67
- uv sync --extra dev --extra postgres # create .venv + install all deps
68
- uv run manage.py setup_local # migrate + create admin (studio / BOOTSTRAP_PASSWORD from .env) + seed data
67
+ ### Daily work
68
+
69
+ ```bash
70
+ uv run manage.py dev_server --embedded # web UI + API + worker + embedded Hatchet → http://localhost:8000
69
71
  ```
70
72
 
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.
73
+ Every start applies migrations and makes sure the admin (a superuser) and default workspace exist, so pulling new code needs no extra steps. Keep in mind:
72
74
 
73
- ### Daily work
75
+ - Only one `dev_server --embedded` (or `uvx simpleaudit-studio`) can run at a time: they share the embedded queue database.
76
+ - The web server reloads on code changes; the worker does not. Restart `dev_server` after changing `infra/worker.py` or `infra/engine.py`.
77
+
78
+ ### Tests and lint
74
79
 
75
80
  ```bash
76
- uv run manage.py dev_server --embedded # easiest: web UI + API + audit worker, zero-Docker → http://localhost:8000
81
+ SIMPLEAUDIT_LOCAL_SQLITE=1 uv run manage.py test infra --exclude-tag embedded_hatchet
82
+ uv run ruff check .
77
83
  ```
78
84
 
79
- Other options:
85
+ ### Other setups
86
+
80
87
  ```bash
81
- uv run manage.py runserver # web UI + API only (no worker)
82
- uv run manage.py dev_server # web UI + worker, uses .env (edit POSTGRES_*/HATCHET_* to point at your infra)
83
- SIMPLEAUDIT_LOCAL_SQLITE=1 uv run manage.py test infra # tests
88
+ uv run manage.py dev_server --no-worker # web UI + API only
89
+ uv run manage.py dev_server # use the Postgres + Hatchet configured in .env (see .env.example)
84
90
  ```
85
91
 
92
+ See [docs/deployment.md](docs/deployment.md) for every environment variable and management command.
93
+
86
94
  ## 🐳 Self-Hosting
87
95
 
88
96
  For teams or multi-user setups, use Docker Compose:
@@ -102,6 +110,8 @@ See [docs/deployment.md](docs/deployment.md) for production hardening, backups,
102
110
  ## ✨ What You Can Do
103
111
 
104
112
  - Build versioned scenario sets and register OpenAI-compatible models
113
+ - Define versioned judges: start from a SimpleAudit judge (safety, harm, helpfulness, factuality, abstention, checklist, …) and edit its criteria, or write your own criteria as a severity, 1–10 score (with your own dimensions) or yes/no judge; run with any judge model
114
+ - Test your deployment's target system prompt, and compare prompts or judges side by side
105
115
  - Run experiments across models, scenario versions and settings, with a review step before launch
106
116
  - Watch runs live, including per-repetition results
107
117
  - Monitor models for drift on a schedule (interval or cron)
@@ -68,6 +68,10 @@ def bootstrap_admin_and_default_project(
68
68
  user=user,
69
69
  defaults={"role": ProjectMembership.Role.ADMIN},
70
70
  )
71
+ if created:
72
+ from infra.seed import seed_workspace
73
+
74
+ seed_workspace(project, user) # standard scenario sets and SimpleAudit's judges
71
75
  return user, project
72
76
 
73
77
 
@@ -140,11 +144,14 @@ def create_workspace(*, user, name: str, description: str = "") -> Project:
140
144
  with transaction.atomic():
141
145
  project = Project.objects.create(name=clean_name, slug=candidate, description=(description or "").strip())
142
146
  ProjectMembership.objects.create(project=project, user=user, role=ProjectMembership.Role.ADMIN)
143
- return project
144
147
  except IntegrityError:
145
148
  if attempt == 4:
146
149
  raise StableAPIError(detail="A workspace with this name already exists.", code="workspace_name_conflict", http_status=409)
147
150
  continue
151
+ from infra.seed import seed_workspace
152
+
153
+ seed_workspace(project, user) # standard scenario sets and SimpleAudit's judges
154
+ return project
148
155
  raise StableAPIError(detail="A workspace with this name already exists.", code="workspace_name_conflict", http_status=409)
149
156
 
150
157
 
@@ -10,6 +10,7 @@ from __future__ import annotations
10
10
 
11
11
  from audits.events import ScenarioResult
12
12
  from audits.models import AuditRun
13
+ from audits.services import frozen_judge, frozen_name
13
14
 
14
15
 
15
16
  class ComparisonIncompatible(Exception):
@@ -46,15 +47,15 @@ def compare_runs(project, run_ids: list[int]) -> dict:
46
47
  warnings.append(f"Scenario set versions differ: {', '.join('v'+str(v) for v in vnums)}. Results may not be directly comparable.")
47
48
 
48
49
  # Check: same judge?
49
- judge_ids = {r.judge_model_id for r in runs}
50
+ judge_ids = {r.judge_version_id for r in runs}
50
51
  if len(judge_ids) > 1:
51
- judge_names = sorted({r.judge_model.display_name for r in runs})
52
- warnings.append(f"Different judge models used: {', '.join(judge_names)}. Judge effects can dominate model effects — interpret with caution.")
52
+ judge_names = sorted({f"{frozen_judge(r)['label']} ({frozen_name(r, 'judge')})" for r in runs})
53
+ warnings.append(f"Different judges used: {', '.join(judge_names)}. Judge effects can dominate model effects — interpret with caution.")
53
54
 
54
55
  # Check: same auditor?
55
56
  auditor_ids = {r.auditor_model_id for r in runs}
56
57
  if len(auditor_ids) > 1:
57
- auditor_names = sorted({r.auditor_model.display_name for r in runs})
58
+ auditor_names = sorted({frozen_name(r, "auditor", with_id=True) for r in runs})
58
59
  warnings.append(f"Different auditor models used: {', '.join(auditor_names)}.")
59
60
 
60
61
  # Check: same SimpleAudit version?
@@ -119,7 +120,7 @@ def compare_runs(project, run_ids: list[int]) -> dict:
119
120
  r = run_results.get(run.id, {}).get(key)
120
121
  entry["runs"][str(run.id)] = {
121
122
  "name": run.name,
122
- "target": run.target_model.display_name if run.target_model else None,
123
+ "target": frozen_name(run, "target"),
123
124
  **(r or {"status": "missing", "severity": None}),
124
125
  }
125
126
  results.append(entry)
@@ -127,10 +128,13 @@ def compare_runs(project, run_ids: list[int]) -> dict:
127
128
  # Build inputs comparison: key parameters that differ between runs
128
129
  input_rows = []
129
130
  param_defs = [
130
- ("Target model", lambda r: r.target_model.display_name if r.target_model else "—"),
131
- ("Auditor model", lambda r: r.auditor_model.display_name if r.auditor_model else "—"),
132
- ("Judge model", lambda r: r.judge_model.display_name if r.judge_model else "—"),
133
- ("Scenario set version", lambda r: f"{r.scenario_set_version.scenario_set.name} (v{r.scenario_set_version.version})" if r.scenario_set_version else "—"),
131
+ ("Target model", lambda r: frozen_name(r, "target", with_id=True)),
132
+ ("Auditor model", lambda r: frozen_name(r, "auditor", with_id=True)),
133
+ ("Judge", lambda r: frozen_judge(r)["label"]),
134
+ ("Judge format", lambda r: frozen_judge(r)["output_label"]),
135
+ ("Judge model", lambda r: frozen_name(r, "judge", with_id=True)),
136
+ ("System prompt", lambda r: (r.generation_parameters_snapshot or {}).get("system_prompt") or "none"),
137
+ ("Scenario set version", lambda r: r.scenario_set_version.label if r.scenario_set_version else "—"),
134
138
  ("SimpleAudit version", lambda r: f"{r.simpleaudit_version} ({r.git_commit[:8]}…)" if r.simpleaudit_version and r.git_commit else (r.simpleaudit_version or "—")),
135
139
  ("Temperature (target)", lambda r: (r.generation_parameters_snapshot or {}).get("temperature_target", "—")),
136
140
  ("Temperature (auditor)", lambda r: (r.generation_parameters_snapshot or {}).get("temperature_auditor", "—")),
@@ -155,6 +159,8 @@ def compare_runs(project, run_ids: list[int]) -> dict:
155
159
  url = f"/models/#conn-{r.target_model.connection_id}"
156
160
  elif label == "Auditor model" and r.auditor_model_id:
157
161
  url = f"/models/#conn-{r.auditor_model.connection_id}"
162
+ elif label == "Judge" and frozen_judge(r)["judge_id"]:
163
+ url = f"/judges/{frozen_judge(r)['judge_id']}/?v={frozen_judge(r)['version']}"
158
164
  elif label == "Judge model" and r.judge_model_id:
159
165
  url = f"/models/#conn-{r.judge_model.connection_id}"
160
166
  elif label == "Scenario set version" and r.scenario_set_version:
@@ -171,8 +177,8 @@ def compare_runs(project, run_ids: list[int]) -> dict:
171
177
  "id": r.id,
172
178
  "name": r.name,
173
179
  "status": r.status,
174
- "target": r.target_model.display_name if r.target_model else None,
175
- "judge": r.judge_model.display_name if r.judge_model else None,
180
+ "target": frozen_name(r, "target"),
181
+ "judge": frozen_judge(r)["label"],
176
182
  "scenario_set_version": f"v{r.scenario_set_version.version}" if r.scenario_set_version else None,
177
183
  "total_scenarios": r.total_scenarios,
178
184
  "successful_scenarios": r.successful_scenarios,
@@ -15,6 +15,7 @@ from django.db import transaction
15
15
 
16
16
  from audits.models import AuditRun, Experiment
17
17
  from audits.monitors import pass_counts, wilson
18
+ from audits.services import frozen_judge, frozen_name
18
19
 
19
20
  MAX_RUNS_PER_EXPERIMENT = 50
20
21
 
@@ -23,15 +24,22 @@ DESIGN_AXES = {
23
24
  "scenario_set": "Scenario set",
24
25
  "target": "Target",
25
26
  "auditor": "Auditor",
27
+ "judge_model": "Judge model",
26
28
  "judge": "Judge",
27
29
  "max_turns": "Max turns",
28
30
  "language": "Language",
29
31
  }
30
32
  # Everything an experiment can compare: the design axes plus per-run edits
31
- # made on the review screen (repetitions, generation config).
32
- FACTORS = {**DESIGN_AXES, "n_repetitions": "Repetitions", "params": "Parameters"}
33
- _MODEL_ROLES = ("target", "auditor", "judge")
34
- _FORM_KEYS = ("max_turns", "language", "n_repetitions")
33
+ # made on the review screen (repetitions, system prompt, generation config).
34
+ FACTORS = {**DESIGN_AXES, "n_repetitions": "Repetitions", "system_prompt": "System prompt", "params": "Parameters"}
35
+ # Axes whose values are registered models (axis -> form field and label);
36
+ # "judge" values are JudgeVersions.
37
+ _MODEL_AXES = {"target": ("target_model", "target"), "auditor": ("auditor_model", "auditor"),
38
+ "judge_model": ("judge_model", "judge")}
39
+ _MODEL_ROLES = tuple(_MODEL_AXES)
40
+ _ROLE_AXES = ("target", "auditor", "judge_model", "judge")
41
+ # Generation keys with their own form fields (not "parameters").
42
+ _FORM_KEYS = ("max_turns", "language", "n_repetitions", "system_prompt")
35
43
 
36
44
 
37
45
  class DesignError(ValueError):
@@ -66,6 +74,7 @@ def parse_design(post, project) -> dict:
66
74
 
67
75
  Raises DesignError with a user-facing message.
68
76
  """
77
+ from judges.models import Judge, JudgeVersion
69
78
  from model_registry.models import RegisteredModel
70
79
  from scenarios.models import ScenarioSet, ScenarioSetVersion
71
80
 
@@ -106,15 +115,45 @@ def parse_design(post, project) -> dict:
106
115
  )
107
116
 
108
117
  models = {}
109
- for role in _MODEL_ROLES:
110
- ids = [m for m in post.getlist(f"{role}_model") if m]
118
+ for axis, (field, label) in _MODEL_AXES.items():
119
+ ids = [m for m in post.getlist(field) if m]
111
120
  if not ids:
112
- raise DesignError(f"Pick at least one {role} model.")
113
- found = {str(m.pk): m for m in RegisteredModel.objects.filter(project=project, pk__in=ids).select_related("connection")}
121
+ raise DesignError(f"Pick at least one {label} model.")
122
+ # A model is usable when it belongs to this workspace OR its connection
123
+ # is shared into this workspace (public / admin-shared / explicit).
124
+ from model_registry.services import visible_connection_ids_for
125
+
126
+ allowed_conn_ids = set(visible_connection_ids_for(project))
127
+ found = {
128
+ str(m.pk): m
129
+ for m in RegisteredModel.objects.filter(pk__in=ids).select_related("connection")
130
+ if m.project_id == project.id or m.connection_id in allowed_conn_ids
131
+ }
114
132
  missing = [i for i in ids if i not in found]
115
133
  if missing:
116
- raise DesignError(f"A selected {role} model is not in this workspace.")
117
- models[role] = [found[i] for i in dict.fromkeys(ids)]
134
+ raise DesignError(f"A selected {label} model isn't available in this workspace.")
135
+ models[axis] = [found[i] for i in dict.fromkeys(ids)]
136
+
137
+ # Judges: version ids, and/or "latest:<judge id>" (always latest: unpinned,
138
+ # so repeating runs follow new versions).
139
+ raw = [v for v in dict.fromkeys(post.getlist("judge")) if v]
140
+ if not raw:
141
+ raise DesignError("Pick at least one judge.")
142
+ follow_ids = [v.split(":", 1)[1] for v in raw if v.startswith("latest:")]
143
+ pinned_ids = [v for v in raw if not v.startswith("latest:")]
144
+ judges = list(
145
+ JudgeVersion.objects.filter(pk__in=pinned_ids, judge__project=project).select_related("judge")
146
+ )
147
+ follow = list(Judge.objects.filter(pk__in=follow_ids, project=project))
148
+ if len(judges) != len(pinned_ids) or len(follow) != len(follow_ids):
149
+ raise DesignError("A selected judge is not in this workspace.")
150
+ for judge in follow:
151
+ latest = judge.versions.select_related("judge").order_by("-version").first()
152
+ if latest is None:
153
+ raise DesignError(f"Judge “{judge.name}” has no version.")
154
+ latest.follow_latest = True
155
+ judges.append(latest)
156
+ judges.sort(key=lambda v: (v.judge.name, is_follow(v), v.version))
118
157
 
119
158
  n_reps_raw = (post.get("n_repetitions") or "").strip()
120
159
  try:
@@ -140,11 +179,18 @@ def parse_design(post, project) -> dict:
140
179
  raise DesignError(f"Max turns: {exc} Use whole numbers from 1 to 50.") from exc
141
180
  languages = _split_values(post.get("language", ""), str)
142
181
 
182
+ # The target's system prompt lives in the generation config (the engine
183
+ # reads it there); the field wins over a key typed into the JSON.
184
+ system_prompt = (post.get("system_prompt") or "").strip()
185
+ if system_prompt:
186
+ gen_config = {**(gen_config or {}), "system_prompt": system_prompt}
187
+
143
188
  return {
144
189
  "scenario_set": versions,
145
190
  "target": models["target"],
146
191
  "auditor": models["auditor"],
147
- "judge": models["judge"],
192
+ "judge_model": models["judge_model"],
193
+ "judge": judges,
148
194
  "max_turns": max_turns,
149
195
  "language": languages,
150
196
  "n_repetitions": n_reps if n_reps and n_reps > 1 else None,
@@ -153,7 +199,7 @@ def parse_design(post, project) -> dict:
153
199
 
154
200
 
155
201
  def is_follow(version) -> bool:
156
- """Whether this scenario set version was picked as "Always latest"."""
202
+ """Whether this scenario set / judge version was picked as "Always latest"."""
157
203
  return bool(getattr(version, "follow_latest", False))
158
204
 
159
205
 
@@ -184,13 +230,34 @@ def expand(design: dict) -> list[dict]:
184
230
  return specs
185
231
 
186
232
 
233
+ def spec_to_run(spec: dict, name: str) -> dict:
234
+ """A design spec (from ``expand``) as a planned run: the ``launch_experiment``
235
+ run item shape, also used by script generation (infra.codegen)."""
236
+ return {
237
+ "name": name,
238
+ "version": spec["scenario_set"],
239
+ "target": spec["target"],
240
+ "auditor": spec["auditor"],
241
+ "judge_model": spec["judge_model"],
242
+ "judge": spec["judge"],
243
+ "max_turns": spec["max_turns"],
244
+ "language": spec["language"],
245
+ "n_repetitions": spec["n_repetitions"],
246
+ "gen_config": spec["gen_config"],
247
+ }
248
+
249
+
187
250
  def factor_value_label(key: str, value) -> str:
188
251
  if value is None:
189
252
  return "default"
190
253
  if key == "scenario_set":
191
254
  if is_follow(value):
192
255
  return f"{value.scenario_set.name} (always latest, now v{value.version})"
193
- return f"{value.scenario_set.name} v{value.version}"
256
+ return value.label
257
+ if key == "judge":
258
+ if is_follow(value):
259
+ return f"{value.judge.name} (always latest, now v{value.version})"
260
+ return value.label
194
261
  if key in _MODEL_ROLES:
195
262
  return value.display_name
196
263
  return str(value)
@@ -203,17 +270,19 @@ def spec_label(spec: dict, factors: list[str]) -> str:
203
270
 
204
271
  def spec_warnings(spec: dict) -> list[str]:
205
272
  warnings = []
206
- if spec["judge"].pk == spec["target"].pk:
207
- warnings.append("Judge is the target model: it grades itself.")
208
- for role in _MODEL_ROLES:
209
- if not spec[role].has_key:
210
- warnings.append(f"{role.title()} model has no API key set.")
273
+ if spec["judge_model"].pk == spec["target"].pk:
274
+ warnings.append("Judge model is the target model: it grades itself.")
275
+ for role, model in (("Target", spec["target"]), ("Auditor", spec["auditor"]), ("Judge", spec["judge_model"])):
276
+ if not model.has_key:
277
+ warnings.append(f"{role} model has no API key set.")
278
+ elif not (model.enabled and model.connection.enabled):
279
+ warnings.append(f"{role} model is disabled.")
211
280
  return warnings
212
281
 
213
282
 
214
283
  def design_warnings(factors: list[str]) -> list[str]:
215
284
  """Experiment-level cautions about how results can be read."""
216
- roles = [f for f in factors if f in _MODEL_ROLES]
285
+ roles = [f for f in factors if f in _ROLE_AXES]
217
286
  warnings = []
218
287
  if "target" in roles and len(roles) > 1:
219
288
  others = " and ".join(FACTORS[r].lower() for r in roles if r != "target")
@@ -238,6 +307,14 @@ def params_label(params: dict | None) -> str:
238
307
  return json.dumps(rest, sort_keys=True, separators=(", ", ": ")) if rest else "default"
239
308
 
240
309
 
310
+ def prompt_label(prompt: str | None) -> str:
311
+ """Short text for a system prompt in tables: its first line, clipped."""
312
+ first = (prompt or "").strip().splitlines()[0] if (prompt or "").strip() else ""
313
+ if not first:
314
+ return "none"
315
+ return first if len(first) <= 48 else first[:47] + "…"
316
+
317
+
241
318
  def factors_from_runs(runs: list[dict]) -> list[str]:
242
319
  """Which inputs actually differ across the runs about to be launched.
243
320
 
@@ -247,8 +324,12 @@ def factors_from_runs(runs: list[dict]) -> list[str]:
247
324
  def value(run, key):
248
325
  if key == "scenario_set":
249
326
  return (run["version"].pk, is_follow(run["version"]))
327
+ if key == "judge":
328
+ return (run["judge"].pk, is_follow(run["judge"]))
250
329
  if key in _MODEL_ROLES:
251
330
  return run[key].pk
331
+ if key == "system_prompt":
332
+ return (run["gen_config"] or {}).get("system_prompt") or ""
252
333
  if key == "params":
253
334
  return params_label(run["gen_config"])
254
335
  return run[key]
@@ -263,14 +344,16 @@ def spec_to_row(spec: dict) -> str:
263
344
  "f": 1 if is_follow(spec["scenario_set"]) else 0,
264
345
  "t": spec["target"].pk,
265
346
  "a": spec["auditor"].pk,
347
+ "jm": spec["judge_model"].pk,
266
348
  "j": spec["judge"].pk,
349
+ "jf": 1 if is_follow(spec["judge"]) else 0,
267
350
  })
268
351
 
269
352
 
270
353
  def launch_experiment(*, project, user, name: str, runs: list[dict], repeat: dict | None = None) -> Experiment:
271
354
  """Create the Experiment and its frozen runs, then submit them.
272
355
 
273
- ``runs`` items: {"name", "version", "target", "auditor", "judge",
356
+ ``runs`` items: {"name", "version", "target", "auditor", "judge_model", "judge",
274
357
  "max_turns", "language", "n_repetitions", "gen_config"}. With ``repeat``
275
358
  (see audits.monitors.parse_repeat) each run setup also gets a Monitor
276
359
  linked to the experiment; runs start now unless repeat["start"] is "at".
@@ -302,7 +385,8 @@ def launch_experiment(*, project, user, name: str, runs: list[dict], repeat: dic
302
385
  scenario_set_version=r["version"],
303
386
  target_model=r["target"],
304
387
  auditor_model=r["auditor"],
305
- judge_model=r["judge"],
388
+ judge_model=r["judge_model"],
389
+ judge=r["judge"],
306
390
  max_turns_override=r["max_turns"],
307
391
  language_override=r["language"],
308
392
  n_repetitions_override=r["n_repetitions"],
@@ -334,13 +418,15 @@ def launch_experiment(*, project, user, name: str, runs: list[dict], repeat: dic
334
418
  def run_factor_values(run: AuditRun) -> dict:
335
419
  params = run.generation_parameters_snapshot or {}
336
420
  return {
337
- "scenario_set": f"{run.scenario_set_version.scenario_set.name} v{run.scenario_set_version.version}",
338
- "target": run.target_model.display_name,
339
- "auditor": run.auditor_model.display_name,
340
- "judge": run.judge_model.display_name,
421
+ "scenario_set": run.scenario_set_version.label,
422
+ "target": frozen_name(run, "target"),
423
+ "auditor": frozen_name(run, "auditor"),
424
+ "judge_model": frozen_name(run, "judge"),
425
+ "judge": frozen_judge(run)["label"],
341
426
  "max_turns": str(params.get("max_turns") or "default"),
342
427
  "language": params.get("language") or "default",
343
428
  "n_repetitions": f"{params.get('n_repetitions') or 1}×",
429
+ "system_prompt": prompt_label(params.get("system_prompt")),
344
430
  "params": params_label(params),
345
431
  }
346
432