trueppm-api 0.4.0b2__py3-none-any.whl

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 (759) hide show
  1. trueppm_api/__init__.py +14 -0
  2. trueppm_api/apps/access/__init__.py +0 -0
  3. trueppm_api/apps/access/apps.py +19 -0
  4. trueppm_api/apps/access/groups.py +518 -0
  5. trueppm_api/apps/access/management/__init__.py +0 -0
  6. trueppm_api/apps/access/management/commands/__init__.py +0 -0
  7. trueppm_api/apps/access/management/commands/create_admin.py +164 -0
  8. trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
  9. trueppm_api/apps/access/management/commands/revoke_api_tokens.py +209 -0
  10. trueppm_api/apps/access/migrations/0001_initial.py +61 -0
  11. trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +167 -0
  12. trueppm_api/apps/access/migrations/0002_alter_projectmembership_id.py +29 -0
  13. trueppm_api/apps/access/migrations/0003_projectmembership_soft_delete.py +24 -0
  14. trueppm_api/apps/access/migrations/0004_alter_projectmembership_role.py +34 -0
  15. trueppm_api/apps/access/migrations/0005_program_entity_and_membership.py +76 -0
  16. trueppm_api/apps/access/migrations/0006_role_ordinal_spacing.py +113 -0
  17. trueppm_api/apps/access/migrations/0007_projectmembership_joined_at_and_more.py +35 -0
  18. trueppm_api/apps/access/migrations/0008_projectmembership_source_group.py +25 -0
  19. trueppm_api/apps/access/migrations/0009_projectmembership_pm_proj_serverver_idx.py +20 -0
  20. trueppm_api/apps/access/migrations/0010_programmembership_joined_at_and_more.py +38 -0
  21. trueppm_api/apps/access/migrations/0011_alter_programmembership_unique_together_and_more.py +36 -0
  22. trueppm_api/apps/access/migrations/0012_programmembership_role_title.py +17 -0
  23. trueppm_api/apps/access/migrations/0013_userdefinedmentiongroup.py +82 -0
  24. trueppm_api/apps/access/migrations/0014_programmembership_progm_serverver_idx.py +19 -0
  25. trueppm_api/apps/access/migrations/0015_programuserdefinedmentiongroup.py +86 -0
  26. trueppm_api/apps/access/migrations/0016_externalstakeholder.py +67 -0
  27. trueppm_api/apps/access/migrations/0017_viewer_ordinal_one.py +132 -0
  28. trueppm_api/apps/access/migrations/0018_programmembership_sync_seq_and_more.py +52 -0
  29. trueppm_api/apps/access/migrations/0019_programmembership_reinstated_at_and_more.py +22 -0
  30. trueppm_api/apps/access/migrations/__init__.py +0 -0
  31. trueppm_api/apps/access/models.py +588 -0
  32. trueppm_api/apps/access/permissions.py +2837 -0
  33. trueppm_api/apps/access/serializers.py +946 -0
  34. trueppm_api/apps/access/services.py +667 -0
  35. trueppm_api/apps/access/signals.py +103 -0
  36. trueppm_api/apps/access/tasks.py +50 -0
  37. trueppm_api/apps/access/throttles.py +95 -0
  38. trueppm_api/apps/access/urls.py +191 -0
  39. trueppm_api/apps/access/views.py +1931 -0
  40. trueppm_api/apps/agents/__init__.py +6 -0
  41. trueppm_api/apps/agents/apps.py +11 -0
  42. trueppm_api/apps/agents/canonical.py +73 -0
  43. trueppm_api/apps/agents/deferred.py +108 -0
  44. trueppm_api/apps/agents/management/__init__.py +0 -0
  45. trueppm_api/apps/agents/management/commands/__init__.py +0 -0
  46. trueppm_api/apps/agents/management/commands/audit_prune.py +137 -0
  47. trueppm_api/apps/agents/management/commands/audit_verify.py +101 -0
  48. trueppm_api/apps/agents/middleware.py +44 -0
  49. trueppm_api/apps/agents/migrations/0001_initial.py +231 -0
  50. trueppm_api/apps/agents/migrations/0002_agentactioncheckpoint.py +71 -0
  51. trueppm_api/apps/agents/migrations/0003_agentactionrefusaldetail.py +58 -0
  52. trueppm_api/apps/agents/migrations/__init__.py +0 -0
  53. trueppm_api/apps/agents/models.py +371 -0
  54. trueppm_api/apps/agents/refusal.py +103 -0
  55. trueppm_api/apps/agents/serializers.py +85 -0
  56. trueppm_api/apps/agents/services.py +397 -0
  57. trueppm_api/apps/agents/signals.py +30 -0
  58. trueppm_api/apps/agents/urls.py +15 -0
  59. trueppm_api/apps/agents/views.py +108 -0
  60. trueppm_api/apps/csvimport/__init__.py +12 -0
  61. trueppm_api/apps/csvimport/apps.py +11 -0
  62. trueppm_api/apps/csvimport/mapping.py +363 -0
  63. trueppm_api/apps/csvimport/migrations/0001_initial.py +74 -0
  64. trueppm_api/apps/csvimport/migrations/0002_schedule_outline_batch_operations.py +37 -0
  65. trueppm_api/apps/csvimport/migrations/0003_csvimportrequest_date_order_and_more.py +26 -0
  66. trueppm_api/apps/csvimport/migrations/__init__.py +0 -0
  67. trueppm_api/apps/csvimport/models.py +111 -0
  68. trueppm_api/apps/csvimport/parser.py +1825 -0
  69. trueppm_api/apps/csvimport/services.py +48 -0
  70. trueppm_api/apps/csvimport/tasks.py +418 -0
  71. trueppm_api/apps/csvimport/template.py +37 -0
  72. trueppm_api/apps/csvimport/urls.py +50 -0
  73. trueppm_api/apps/csvimport/views.py +793 -0
  74. trueppm_api/apps/history/__init__.py +0 -0
  75. trueppm_api/apps/history/apps.py +12 -0
  76. trueppm_api/apps/history/changelog.py +359 -0
  77. trueppm_api/apps/history/diff_policy.py +177 -0
  78. trueppm_api/apps/history/serializers.py +104 -0
  79. trueppm_api/apps/history/signals.py +82 -0
  80. trueppm_api/apps/history/tasks.py +105 -0
  81. trueppm_api/apps/history/urls.py +38 -0
  82. trueppm_api/apps/history/views.py +443 -0
  83. trueppm_api/apps/idempotency/__init__.py +0 -0
  84. trueppm_api/apps/idempotency/apps.py +11 -0
  85. trueppm_api/apps/idempotency/migrations/0001_initial.py +71 -0
  86. trueppm_api/apps/idempotency/migrations/__init__.py +0 -0
  87. trueppm_api/apps/idempotency/mixins.py +226 -0
  88. trueppm_api/apps/idempotency/models.py +75 -0
  89. trueppm_api/apps/idempotency/tasks.py +61 -0
  90. trueppm_api/apps/integrations/__init__.py +13 -0
  91. trueppm_api/apps/integrations/apps.py +59 -0
  92. trueppm_api/apps/integrations/connections.py +806 -0
  93. trueppm_api/apps/integrations/encryption.py +92 -0
  94. trueppm_api/apps/integrations/external_sources.py +837 -0
  95. trueppm_api/apps/integrations/git_automation_services.py +231 -0
  96. trueppm_api/apps/integrations/git_webhook_auth.py +243 -0
  97. trueppm_api/apps/integrations/http.py +403 -0
  98. trueppm_api/apps/integrations/me_work.py +218 -0
  99. trueppm_api/apps/integrations/migrations/0001_initial.py +64 -0
  100. trueppm_api/apps/integrations/migrations/0001_squashed_0006_tasklink_description_tasklink_preview_type_and_more.py +187 -0
  101. trueppm_api/apps/integrations/migrations/0002_tasklink.py +67 -0
  102. trueppm_api/apps/integrations/migrations/0003_tasklink_tasklink_serverver_idx.py +17 -0
  103. trueppm_api/apps/integrations/migrations/0004_tasklink_custom_title_tasklink_labels.py +25 -0
  104. trueppm_api/apps/integrations/migrations/0005_boardautomation.py +56 -0
  105. trueppm_api/apps/integrations/migrations/0006_tasklink_description_tasklink_preview_type_and_more.py +40 -0
  106. trueppm_api/apps/integrations/migrations/0007_tasklink_created_at.py +23 -0
  107. trueppm_api/apps/integrations/migrations/0008_integrationcredential_config_externalworkitem.py +66 -0
  108. trueppm_api/apps/integrations/migrations/0009_externalsyncrequest_and_more.py +96 -0
  109. trueppm_api/apps/integrations/migrations/0010_externalworkitem_due_date.py +17 -0
  110. trueppm_api/apps/integrations/migrations/0011_tasklink_sync_seq_tasklink_tasklink_syncseq_idx.py +22 -0
  111. trueppm_api/apps/integrations/migrations/0012_boardautomation_last_delivery_at_and_more.py +42 -0
  112. trueppm_api/apps/integrations/migrations/0013_externalsyncrequest_attempt_count_and_more.py +26 -0
  113. trueppm_api/apps/integrations/migrations/__init__.py +0 -0
  114. trueppm_api/apps/integrations/models.py +622 -0
  115. trueppm_api/apps/integrations/notification_channels.py +57 -0
  116. trueppm_api/apps/integrations/opengraph.py +229 -0
  117. trueppm_api/apps/integrations/outgoing.py +201 -0
  118. trueppm_api/apps/integrations/providers.py +519 -0
  119. trueppm_api/apps/integrations/registry.py +345 -0
  120. trueppm_api/apps/integrations/serializers.py +453 -0
  121. trueppm_api/apps/integrations/services.py +171 -0
  122. trueppm_api/apps/integrations/tasks.py +731 -0
  123. trueppm_api/apps/integrations/throttles.py +165 -0
  124. trueppm_api/apps/integrations/urls.py +77 -0
  125. trueppm_api/apps/integrations/views.py +1106 -0
  126. trueppm_api/apps/jiraimport/__init__.py +8 -0
  127. trueppm_api/apps/jiraimport/apps.py +11 -0
  128. trueppm_api/apps/jiraimport/migrations/0001_initial.py +72 -0
  129. trueppm_api/apps/jiraimport/migrations/__init__.py +0 -0
  130. trueppm_api/apps/jiraimport/models.py +70 -0
  131. trueppm_api/apps/jiraimport/parser.py +350 -0
  132. trueppm_api/apps/jiraimport/services.py +46 -0
  133. trueppm_api/apps/jiraimport/tasks.py +277 -0
  134. trueppm_api/apps/jiraimport/urls.py +15 -0
  135. trueppm_api/apps/jiraimport/views.py +153 -0
  136. trueppm_api/apps/msproject/__init__.py +0 -0
  137. trueppm_api/apps/msproject/apps.py +11 -0
  138. trueppm_api/apps/msproject/dataclasses.py +153 -0
  139. trueppm_api/apps/msproject/exporter.py +500 -0
  140. trueppm_api/apps/msproject/extended_attributes.py +77 -0
  141. trueppm_api/apps/msproject/importer.py +1095 -0
  142. trueppm_api/apps/msproject/migrations/0001_import_request.py +77 -0
  143. trueppm_api/apps/msproject/migrations/0001_squashed_0002_importrequest_creates_project.py +80 -0
  144. trueppm_api/apps/msproject/migrations/0002_importrequest_creates_project.py +15 -0
  145. trueppm_api/apps/msproject/migrations/__init__.py +0 -0
  146. trueppm_api/apps/msproject/models.py +81 -0
  147. trueppm_api/apps/msproject/parser.py +1231 -0
  148. trueppm_api/apps/msproject/serializers.py +136 -0
  149. trueppm_api/apps/msproject/services.py +58 -0
  150. trueppm_api/apps/msproject/tasks.py +436 -0
  151. trueppm_api/apps/msproject/urls.py +39 -0
  152. trueppm_api/apps/msproject/views.py +531 -0
  153. trueppm_api/apps/notifications/__init__.py +7 -0
  154. trueppm_api/apps/notifications/apps.py +15 -0
  155. trueppm_api/apps/notifications/backfill.py +79 -0
  156. trueppm_api/apps/notifications/categories.py +112 -0
  157. trueppm_api/apps/notifications/delivery_limits.py +258 -0
  158. trueppm_api/apps/notifications/digests.py +447 -0
  159. trueppm_api/apps/notifications/email_backend.py +330 -0
  160. trueppm_api/apps/notifications/email_health.py +128 -0
  161. trueppm_api/apps/notifications/migrations/0001_initial.py +207 -0
  162. trueppm_api/apps/notifications/migrations/0001_squashed_0006_notification_task.py +294 -0
  163. trueppm_api/apps/notifications/migrations/0002_project_notification_preference.py +67 -0
  164. trueppm_api/apps/notifications/migrations/0003_projectnotificationpreference_paused.py +17 -0
  165. trueppm_api/apps/notifications/migrations/0004_clean_unknown_matrix_keys.py +30 -0
  166. trueppm_api/apps/notifications/migrations/0005_notification_body_notification_event_type_and_more.py +27 -0
  167. trueppm_api/apps/notifications/migrations/0006_notification_task.py +25 -0
  168. trueppm_api/apps/notifications/migrations/0007_workspaceemailsettings.py +80 -0
  169. trueppm_api/apps/notifications/migrations/0008_notification_snoozed_until.py +17 -0
  170. trueppm_api/apps/notifications/migrations/0009_usernotificationsettings.py +43 -0
  171. trueppm_api/apps/notifications/migrations/0010_projectnotificationpreference_schema_version.py +20 -0
  172. trueppm_api/apps/notifications/migrations/0011_usernotificationsettings_digest_hour_and_more.py +96 -0
  173. trueppm_api/apps/notifications/migrations/__init__.py +0 -0
  174. trueppm_api/apps/notifications/models.py +1197 -0
  175. trueppm_api/apps/notifications/receivers.py +155 -0
  176. trueppm_api/apps/notifications/schema_migrations.py +56 -0
  177. trueppm_api/apps/notifications/serializers.py +961 -0
  178. trueppm_api/apps/notifications/services.py +1451 -0
  179. trueppm_api/apps/notifications/tasks.py +711 -0
  180. trueppm_api/apps/notifications/throttles.py +98 -0
  181. trueppm_api/apps/notifications/urls.py +97 -0
  182. trueppm_api/apps/notifications/views.py +904 -0
  183. trueppm_api/apps/observability/__init__.py +0 -0
  184. trueppm_api/apps/observability/apps.py +31 -0
  185. trueppm_api/apps/observability/logging.py +213 -0
  186. trueppm_api/apps/observability/migrations/0001_initial.py +35 -0
  187. trueppm_api/apps/observability/migrations/0001_squashed_0003_alter_purgerun_trigger.py +154 -0
  188. trueppm_api/apps/observability/migrations/0002_purgerun_retentionpolicy_retentionschedule.py +131 -0
  189. trueppm_api/apps/observability/migrations/0003_alter_purgerun_trigger.py +21 -0
  190. trueppm_api/apps/observability/migrations/0004_alter_retentionpolicy_key.py +28 -0
  191. trueppm_api/apps/observability/migrations/__init__.py +0 -0
  192. trueppm_api/apps/observability/models.py +141 -0
  193. trueppm_api/apps/observability/otel/__init__.py +45 -0
  194. trueppm_api/apps/observability/otel/attributes.py +247 -0
  195. trueppm_api/apps/observability/otel/export_health.py +533 -0
  196. trueppm_api/apps/observability/otel/instrumentation.py +351 -0
  197. trueppm_api/apps/observability/otel/metrics.py +551 -0
  198. trueppm_api/apps/observability/otel/provider.py +511 -0
  199. trueppm_api/apps/observability/otel/request_attributes.py +204 -0
  200. trueppm_api/apps/observability/purge_registry.py +124 -0
  201. trueppm_api/apps/observability/retention.py +139 -0
  202. trueppm_api/apps/observability/selectors.py +969 -0
  203. trueppm_api/apps/observability/serializers.py +142 -0
  204. trueppm_api/apps/observability/services.py +407 -0
  205. trueppm_api/apps/observability/tasks.py +266 -0
  206. trueppm_api/apps/observability/urls.py +27 -0
  207. trueppm_api/apps/observability/views.py +523 -0
  208. trueppm_api/apps/profiles/__init__.py +0 -0
  209. trueppm_api/apps/profiles/apps.py +15 -0
  210. trueppm_api/apps/profiles/constants.py +54 -0
  211. trueppm_api/apps/profiles/migrations/0001_initial.py +55 -0
  212. trueppm_api/apps/profiles/migrations/0001_squashed_0004_userprofile_role_context.py +125 -0
  213. trueppm_api/apps/profiles/migrations/0002_userprofile_hidden_views.py +17 -0
  214. trueppm_api/apps/profiles/migrations/0003_projectvisit.py +63 -0
  215. trueppm_api/apps/profiles/migrations/0004_userprofile_role_context.py +26 -0
  216. trueppm_api/apps/profiles/migrations/0005_userprofile_schedule_in_deliver.py +20 -0
  217. trueppm_api/apps/profiles/migrations/0006_userprofile_date_format_userprofile_timezone.py +36 -0
  218. trueppm_api/apps/profiles/migrations/0007_userpin.py +91 -0
  219. trueppm_api/apps/profiles/migrations/0008_remove_userprofile_schedule_in_deliver.py +71 -0
  220. trueppm_api/apps/profiles/migrations/0009_auth_user_email_upper_idx.py +81 -0
  221. trueppm_api/apps/profiles/migrations/__init__.py +0 -0
  222. trueppm_api/apps/profiles/models.py +276 -0
  223. trueppm_api/apps/profiles/serializers.py +199 -0
  224. trueppm_api/apps/profiles/services.py +466 -0
  225. trueppm_api/apps/profiles/views.py +124 -0
  226. trueppm_api/apps/projects/__init__.py +0 -0
  227. trueppm_api/apps/projects/actual_date_rules.py +163 -0
  228. trueppm_api/apps/projects/amend.py +133 -0
  229. trueppm_api/apps/projects/apps.py +38 -0
  230. trueppm_api/apps/projects/asset_feed.py +459 -0
  231. trueppm_api/apps/projects/asset_views.py +317 -0
  232. trueppm_api/apps/projects/attachment_policy.py +278 -0
  233. trueppm_api/apps/projects/authentication.py +449 -0
  234. trueppm_api/apps/projects/backfill.py +287 -0
  235. trueppm_api/apps/projects/backlog_services.py +324 -0
  236. trueppm_api/apps/projects/backlog_views.py +302 -0
  237. trueppm_api/apps/projects/batch_operation_views.py +264 -0
  238. trueppm_api/apps/projects/blocker_services.py +510 -0
  239. trueppm_api/apps/projects/board_activity.py +462 -0
  240. trueppm_api/apps/projects/board_activity_views.py +186 -0
  241. trueppm_api/apps/projects/board_lanes.py +134 -0
  242. trueppm_api/apps/projects/bulk_settings.py +194 -0
  243. trueppm_api/apps/projects/bundled_templates.py +215 -0
  244. trueppm_api/apps/projects/calendar_settings.py +180 -0
  245. trueppm_api/apps/projects/ceremony_views.py +244 -0
  246. trueppm_api/apps/projects/commit_moment.py +204 -0
  247. trueppm_api/apps/projects/config_notice.py +1325 -0
  248. trueppm_api/apps/projects/custom_field_values.py +266 -0
  249. trueppm_api/apps/projects/decisions_services.py +93 -0
  250. trueppm_api/apps/projects/decisions_views.py +94 -0
  251. trueppm_api/apps/projects/estimation_scale.py +126 -0
  252. trueppm_api/apps/projects/export_bundle.py +353 -0
  253. trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4256 -0
  254. trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2000 -0
  255. trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1825 -0
  256. trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1905 -0
  257. trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1409 -0
  258. trueppm_api/apps/projects/guardrail_policy_source.py +269 -0
  259. trueppm_api/apps/projects/inbound_sync.py +555 -0
  260. trueppm_api/apps/projects/iteration_label.py +85 -0
  261. trueppm_api/apps/projects/lifecycle.py +191 -0
  262. trueppm_api/apps/projects/management/__init__.py +0 -0
  263. trueppm_api/apps/projects/management/commands/__init__.py +0 -0
  264. trueppm_api/apps/projects/management/commands/backfill_in_progress_status.py +92 -0
  265. trueppm_api/apps/projects/management/commands/create_demo_share_link.py +368 -0
  266. trueppm_api/apps/projects/management/commands/export_program.py +50 -0
  267. trueppm_api/apps/projects/management/commands/import_seed.py +165 -0
  268. trueppm_api/apps/projects/management/commands/load_sample_project.py +247 -0
  269. trueppm_api/apps/projects/management/commands/seed_capacity.py +405 -0
  270. trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +178 -0
  271. trueppm_api/apps/projects/management/commands/shift_sample_dates.py +120 -0
  272. trueppm_api/apps/projects/mcp_settings.py +305 -0
  273. trueppm_api/apps/projects/methodology.py +178 -0
  274. trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
  275. trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5595 -0
  276. trueppm_api/apps/projects/migrations/0002_alter_uuid_pk_serialize_false.py +57 -0
  277. trueppm_api/apps/projects/migrations/0003_rename_task_project_index.py +25 -0
  278. trueppm_api/apps/projects/migrations/0004_soft_delete_and_dependency_versioned.py +70 -0
  279. trueppm_api/apps/projects/migrations/0005_task_assignee.py +33 -0
  280. trueppm_api/apps/projects/migrations/0006_task_utilization_index.py +31 -0
  281. trueppm_api/apps/projects/migrations/0007_task_is_milestone.py +28 -0
  282. trueppm_api/apps/projects/migrations/0008_baseline.py +119 -0
  283. trueppm_api/apps/projects/migrations/0009_historicaldependency_historicalproject_and_more.py +210 -0
  284. trueppm_api/apps/projects/migrations/0010_risk.py +229 -0
  285. trueppm_api/apps/projects/migrations/0011_task_status.py +27 -0
  286. trueppm_api/apps/projects/migrations/0012_task_status.py +27 -0
  287. trueppm_api/apps/projects/migrations/0013_task_planned_start.py +24 -0
  288. trueppm_api/apps/projects/migrations/0014_short_hex_ids.py +74 -0
  289. trueppm_api/apps/projects/migrations/0015_backfill_short_ids.py +48 -0
  290. trueppm_api/apps/projects/migrations/0016_task_actual_dates.py +42 -0
  291. trueppm_api/apps/projects/migrations/0017_boardcolumnconfig.py +43 -0
  292. trueppm_api/apps/projects/migrations/0018_estimation_governance.py +62 -0
  293. trueppm_api/apps/projects/migrations/0019_backfill_wbs_paths.py +28 -0
  294. trueppm_api/apps/projects/migrations/0020_task_status_5col.py +65 -0
  295. trueppm_api/apps/projects/migrations/0021_boardsavedview.py +64 -0
  296. trueppm_api/apps/projects/migrations/0022_task_status_changed_at_priority_rank.py +35 -0
  297. trueppm_api/apps/projects/migrations/0023_risk_framework_fields.py +96 -0
  298. trueppm_api/apps/projects/migrations/0024_riskcomment.py +52 -0
  299. trueppm_api/apps/projects/migrations/0025_sprint.py +352 -0
  300. trueppm_api/apps/projects/migrations/0026_project_methodology.py +30 -0
  301. trueppm_api/apps/projects/migrations/0027_sprint_retro.py +89 -0
  302. trueppm_api/apps/projects/migrations/0028_normalize_notes_field.py +42 -0
  303. trueppm_api/apps/projects/migrations/0029_complete_implies_full_progress.py +38 -0
  304. trueppm_api/apps/projects/migrations/0030_review_implies_full_progress.py +37 -0
  305. trueppm_api/apps/projects/migrations/0031_task_remaining_points.py +22 -0
  306. trueppm_api/apps/projects/migrations/0032_task_is_subtask_and_sprint_scope_change.py +90 -0
  307. trueppm_api/apps/projects/migrations/0033_task_my_work_index.py +38 -0
  308. trueppm_api/apps/projects/migrations/0034_inbound_task_sync.py +313 -0
  309. trueppm_api/apps/projects/migrations/0035_retro_versioned_and_suggested_assignee.py +173 -0
  310. trueppm_api/apps/projects/migrations/0036_program_entity_and_membership.py +157 -0
  311. trueppm_api/apps/projects/migrations/0037_sprint_capacity_points.py +20 -0
  312. trueppm_api/apps/projects/migrations/0038_taskcomment_commentreaction_commentacknowledgement_and_more.py +233 -0
  313. trueppm_api/apps/projects/migrations/0039_apitoken_and_more.py +88 -0
  314. trueppm_api/apps/projects/migrations/0040_program_general_fields.py +119 -0
  315. trueppm_api/apps/projects/migrations/0041_ceremony_template_and_phase_gate_config.py +210 -0
  316. trueppm_api/apps/projects/migrations/0041_project_general_fields.py +106 -0
  317. trueppm_api/apps/projects/migrations/0042_merge_20260522_0410.py +12 -0
  318. trueppm_api/apps/projects/migrations/0042_program_rollup_config.py +95 -0
  319. trueppm_api/apps/projects/migrations/0042_workflow_color_and_custom_fields.py +94 -0
  320. trueppm_api/apps/projects/migrations/0043_merge_20260522_1118.py +10 -0
  321. trueppm_api/apps/projects/migrations/0044_merge_20260522_rollup.py +10 -0
  322. trueppm_api/apps/projects/migrations/0044_project_archive_program_close.py +119 -0
  323. trueppm_api/apps/projects/migrations/0045_program_risk_policy.py +78 -0
  324. trueppm_api/apps/projects/migrations/0046_merge_20260523_2248.py +12 -0
  325. trueppm_api/apps/projects/migrations/0047_apitokenauditentry_program_and_more.py +55 -0
  326. trueppm_api/apps/projects/migrations/0048_historicalprogram_color_program_color.py +32 -0
  327. trueppm_api/apps/projects/migrations/0049_backlog_item.py +112 -0
  328. trueppm_api/apps/projects/migrations/0050_backlog_item_trgm_search.py +26 -0
  329. trueppm_api/apps/projects/migrations/0051_alter_calendar_working_days_alter_dependency_lag_and_more.py +144 -0
  330. trueppm_api/apps/projects/migrations/0052_recurring_tasks.py +278 -0
  331. trueppm_api/apps/projects/migrations/0053_dependency_dep_pred_serverver_idx_and_more.py +56 -0
  332. trueppm_api/apps/projects/migrations/0054_sprintscopechange_goal_impact_and_more.py +117 -0
  333. trueppm_api/apps/projects/migrations/0055_historicaltask_sprint_pending_and_more.py +51 -0
  334. trueppm_api/apps/projects/migrations/0056_historicalproject_prioritization_model_and_more.py +276 -0
  335. trueppm_api/apps/projects/migrations/0057_historicalsprint_binding_committed_snapshot_and_more.py +58 -0
  336. trueppm_api/apps/projects/migrations/0058_historicaltask_delivery_mode_and_more.py +72 -0
  337. trueppm_api/apps/projects/migrations/0059_forecastsnapshot.py +80 -0
  338. trueppm_api/apps/projects/migrations/0060_historicalprojectsignalprivacypolicy_and_more.py +90 -0
  339. trueppm_api/apps/projects/migrations/0061_baselinetask_story_points.py +17 -0
  340. trueppm_api/apps/projects/migrations/0062_project_is_sample.py +22 -0
  341. trueppm_api/apps/projects/migrations/0063_historicalsprint_wip_limit_sprint_wip_limit.py +22 -0
  342. trueppm_api/apps/projects/migrations/0064_sprinttaskoutcome.py +97 -0
  343. trueppm_api/apps/projects/migrations/0065_historicalsprint_goal_outcome_sprint_goal_outcome.py +32 -0
  344. trueppm_api/apps/projects/migrations/0066_historicalproject_lead_project_lead.py +38 -0
  345. trueppm_api/apps/projects/migrations/0067_sprint_exclude_from_velocity.py +22 -0
  346. trueppm_api/apps/projects/migrations/0068_project_recalculated_at.py +23 -0
  347. trueppm_api/apps/projects/migrations/0069_iteration_label.py +22 -0
  348. trueppm_api/apps/projects/migrations/0070_historicalprogram_iteration_label_and_more.py +32 -0
  349. trueppm_api/apps/projects/migrations/0071_backfill_iteration_label_inherit.py +35 -0
  350. trueppm_api/apps/projects/migrations/0072_pulseresponse_retroboarditem.py +129 -0
  351. trueppm_api/apps/projects/migrations/0073_sprinttaskoutcome_demo_ready.py +17 -0
  352. trueppm_api/apps/projects/migrations/0074_historicaltask_blocked_reason_task_blocked_reason.py +30 -0
  353. trueppm_api/apps/projects/migrations/0075_risk_decimal_short_id.py +42 -0
  354. trueppm_api/apps/projects/migrations/0076_sprinttaskoutcome_review_polish.py +45 -0
  355. trueppm_api/apps/projects/migrations/0077_task_blocker_fields.py +111 -0
  356. trueppm_api/apps/projects/migrations/0078_alter_risktask_unique_together_and_more.py +22 -0
  357. trueppm_api/apps/projects/migrations/0079_historicalproject_status_date_project_status_date.py +22 -0
  358. trueppm_api/apps/projects/migrations/0080_alter_historicaltask_type_alter_task_type.py +46 -0
  359. trueppm_api/apps/projects/migrations/0081_historicalprogram_allow_guests_and_more.py +52 -0
  360. trueppm_api/apps/projects/migrations/0082_project_last_sync_version.py +90 -0
  361. trueppm_api/apps/projects/migrations/0083_historicalprogram_mc_history_attribution_audience_and_more.py +108 -0
  362. trueppm_api/apps/projects/migrations/0084_tasknote.py +71 -0
  363. trueppm_api/apps/projects/migrations/0085_sprint_ix_sprint_velocity.py +33 -0
  364. trueppm_api/apps/projects/migrations/0086_board_card_search_trgm.py +33 -0
  365. trueppm_api/apps/projects/migrations/0087_historicalprogram_task_duration_change_percent_policy_and_more.py +155 -0
  366. trueppm_api/apps/projects/migrations/0088_historicalprogram_allowed_attachment_types_and_more.py +61 -0
  367. trueppm_api/apps/projects/migrations/0089_signalceilingraiseproposal_signalceilingraisevote_and_more.py +157 -0
  368. trueppm_api/apps/projects/migrations/0090_historicaltask_proj_histdate_index.py +44 -0
  369. trueppm_api/apps/projects/migrations/0091_project_board_cadence.py +39 -0
  370. trueppm_api/apps/projects/migrations/0092_program_target_date.py +22 -0
  371. trueppm_api/apps/projects/migrations/0093_cross_project_dependency_consent.py +58 -0
  372. trueppm_api/apps/projects/migrations/0094_project_decisions_policy.py +90 -0
  373. trueppm_api/apps/projects/migrations/0095_poker_session_vote.py +127 -0
  374. trueppm_api/apps/projects/migrations/0095_squashed_0097_release_0_3.py +225 -0
  375. trueppm_api/apps/projects/migrations/0096_crossprojectslipconflict.py +91 -0
  376. trueppm_api/apps/projects/migrations/0097_backlogitem_backlogitem_tags_gin_and_more.py +32 -0
  377. trueppm_api/apps/projects/migrations/0098_historicalproject_show_baselines_and_more.py +52 -0
  378. trueppm_api/apps/projects/migrations/0099_apitoken_scopes.py +48 -0
  379. trueppm_api/apps/projects/migrations/0100_task_dep_serverver_idx_concurrent.py +61 -0
  380. trueppm_api/apps/projects/migrations/0101_dependency_deleted_at_task_deleted_at.py +22 -0
  381. trueppm_api/apps/projects/migrations/0102_project_deleted_at_project_proj_isdel_deletedat_idx.py +25 -0
  382. trueppm_api/apps/projects/migrations/0103_taskdurationchangeevent_task_dur_evt_sprint_idx.py +18 -0
  383. trueppm_api/apps/projects/migrations/0104_historicalproject_stale_task_threshold_days_and_more.py +32 -0
  384. trueppm_api/apps/projects/migrations/0104_trash_restore_1113.py +38 -0
  385. trueppm_api/apps/projects/migrations/0105_historical_changelog_indexes.py +62 -0
  386. trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
  387. trueppm_api/apps/projects/migrations/0107_task_activity_event.py +70 -0
  388. trueppm_api/apps/projects/migrations/0108_remove_apitoken_api_token_scope_xor_and_more.py +117 -0
  389. trueppm_api/apps/projects/migrations/0109_projectexportjob.py +82 -0
  390. trueppm_api/apps/projects/migrations/0110_sharelink.py +123 -0
  391. trueppm_api/apps/projects/migrations/0111_projectcalendarlayer.py +67 -0
  392. trueppm_api/apps/projects/migrations/0112_sharelink_expires_at_alter_sharelink_content_kind.py +31 -0
  393. trueppm_api/apps/projects/migrations/0113_alter_historicaltask_percent_complete_and_more.py +35 -0
  394. trueppm_api/apps/projects/migrations/0114_historicalproject_default_member_role_and_more.py +28 -0
  395. trueppm_api/apps/projects/migrations/0115_historicalproject_end_date_shift_threshold_days_and_more.py +22 -0
  396. trueppm_api/apps/projects/migrations/0116_alter_taskactivityevent_event_type.py +28 -0
  397. trueppm_api/apps/projects/migrations/0117_label_tasklabel_label_tasks_and_more.py +126 -0
  398. trueppm_api/apps/projects/migrations/0118_programexportjob.py +82 -0
  399. trueppm_api/apps/projects/migrations/0119_historicalprogram_calendar_program_calendar.py +36 -0
  400. trueppm_api/apps/projects/migrations/0120_alter_backlogitem_item_type.py +29 -0
  401. trueppm_api/apps/projects/migrations/0121_task_three_point_estimate_ordered.py +64 -0
  402. trueppm_api/apps/projects/migrations/0122_task_relation.py +173 -0
  403. trueppm_api/apps/projects/migrations/0123_remove_historicalproject_agile_features_and_more.py +20 -0
  404. trueppm_api/apps/projects/migrations/0124_dependency_is_driving.py +17 -0
  405. trueppm_api/apps/projects/migrations/0125_historicalprogram_estimation_scale_and_more.py +68 -0
  406. trueppm_api/apps/projects/migrations/0126_projectcustomfield_show_on_card_taskcustomfieldvalue.py +80 -0
  407. trueppm_api/apps/projects/migrations/0127_historicalprogram_mcp_enabled_and_more.py +32 -0
  408. trueppm_api/apps/projects/migrations/0128_acceptancecriterion_sync_seq_apitoken_sync_seq_and_more.py +192 -0
  409. trueppm_api/apps/projects/migrations/0129_sharelink_show_milestone_dates.py +20 -0
  410. trueppm_api/apps/projects/migrations/0130_programimportjob.py +85 -0
  411. trueppm_api/apps/projects/migrations/0131_historicalprogram_sprint_picker_ready_only_default_and_more.py +32 -0
  412. trueppm_api/apps/projects/migrations/0132_task_scheduled_start.py +17 -0
  413. trueppm_api/apps/projects/migrations/0133_project_status_date_floor_armed_at.py +17 -0
  414. trueppm_api/apps/projects/migrations/0134_task_edited_at_task_seeded_at_task_source_id_and_more.py +65 -0
  415. trueppm_api/apps/projects/migrations/0135_projecttemplate_templateapplication_and_more.py +175 -0
  416. trueppm_api/apps/projects/migrations/0136_schedule_outline_batch_operations.py +118 -0
  417. trueppm_api/apps/projects/migrations/0137_collapse_dual_scope_api_tokens.py +54 -0
  418. trueppm_api/apps/projects/migrations/0138_apitokenauditentry_api_token_audit_owner_idx.py +18 -0
  419. trueppm_api/apps/projects/migrations/0139_historicaltask_duration_unit_task_duration_unit.py +32 -0
  420. trueppm_api/apps/projects/migrations/0140_historicaltask_auto_container_and_more.py +113 -0
  421. trueppm_api/apps/projects/migrations/0141_historicalproject_draft_started_at_and_more.py +44 -0
  422. trueppm_api/apps/projects/migrations/0142_baseline_calendar_hours_per_day_and_more.py +31 -0
  423. trueppm_api/apps/projects/migrations/0143_sprintcloserequest_failure_reason_and_more.py +42 -0
  424. trueppm_api/apps/projects/migrations/0144_projecttemplate_source_project_and_more.py +35 -0
  425. trueppm_api/apps/projects/migrations/0145_seed_bundled_templates.py +65 -0
  426. trueppm_api/apps/projects/migrations/0146_historicaltask_board_lane_task_board_lane.py +32 -0
  427. trueppm_api/apps/projects/migrations/0147_structuraloperation.py +128 -0
  428. trueppm_api/apps/projects/migrations/0148_task_unique_task_wbs_path_per_project_live.py +76 -0
  429. trueppm_api/apps/projects/migrations/0149_drop_workshops_tables.py +42 -0
  430. trueppm_api/apps/projects/migrations/0150_clear_uncommitted_cpm_output.py +130 -0
  431. trueppm_api/apps/projects/migrations/0151_config_notice_request.py +70 -0
  432. trueppm_api/apps/projects/migrations/0152_aggregate_task_wbs_name_idx.py +20 -0
  433. trueppm_api/apps/projects/migrations/0153_program_sample_anchor_date.py +22 -0
  434. trueppm_api/apps/projects/migrations/__init__.py +0 -0
  435. trueppm_api/apps/projects/models.py +8946 -0
  436. trueppm_api/apps/projects/poker_services.py +154 -0
  437. trueppm_api/apps/projects/poker_views.py +398 -0
  438. trueppm_api/apps/projects/product_backlog_services.py +450 -0
  439. trueppm_api/apps/projects/program_label_services.py +94 -0
  440. trueppm_api/apps/projects/program_label_views.py +189 -0
  441. trueppm_api/apps/projects/program_rollup.py +575 -0
  442. trueppm_api/apps/projects/program_schedule.py +745 -0
  443. trueppm_api/apps/projects/program_views.py +3220 -0
  444. trueppm_api/apps/projects/project_templates.py +422 -0
  445. trueppm_api/apps/projects/receivers.py +234 -0
  446. trueppm_api/apps/projects/refusal_codes.py +140 -0
  447. trueppm_api/apps/projects/reorder_services.py +145 -0
  448. trueppm_api/apps/projects/restructure_hooks.py +54 -0
  449. trueppm_api/apps/projects/retro_board_services.py +368 -0
  450. trueppm_api/apps/projects/retro_services.py +285 -0
  451. trueppm_api/apps/projects/risk_import.py +396 -0
  452. trueppm_api/apps/projects/row_identity.py +113 -0
  453. trueppm_api/apps/projects/schema_migrations.py +228 -0
  454. trueppm_api/apps/projects/schemas/seed_v1.json +493 -0
  455. trueppm_api/apps/projects/schemas/seed_v2.json +892 -0
  456. trueppm_api/apps/projects/seed/__init__.py +61 -0
  457. trueppm_api/apps/projects/seed/exporter.py +1644 -0
  458. trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
  459. trueppm_api/apps/projects/seed/importer.py +2390 -0
  460. trueppm_api/apps/projects/seed/reanchor.py +551 -0
  461. trueppm_api/apps/projects/seed/reldates.py +126 -0
  462. trueppm_api/apps/projects/seed/replace.py +126 -0
  463. trueppm_api/apps/projects/seed/replay.py +1691 -0
  464. trueppm_api/apps/projects/seed/replay_ctx.py +41 -0
  465. trueppm_api/apps/projects/seed/samples.py +427 -0
  466. trueppm_api/apps/projects/seed/validation.py +1149 -0
  467. trueppm_api/apps/projects/serializers.py +12310 -0
  468. trueppm_api/apps/projects/services.py +6966 -0
  469. trueppm_api/apps/projects/share_serializers.py +132 -0
  470. trueppm_api/apps/projects/share_services.py +325 -0
  471. trueppm_api/apps/projects/share_views.py +374 -0
  472. trueppm_api/apps/projects/sharing_settings.py +187 -0
  473. trueppm_api/apps/projects/signal_privacy_services.py +909 -0
  474. trueppm_api/apps/projects/signal_privacy_views.py +476 -0
  475. trueppm_api/apps/projects/signals.py +132 -0
  476. trueppm_api/apps/projects/slip_conflict.py +266 -0
  477. trueppm_api/apps/projects/sprint_cadence.py +453 -0
  478. trueppm_api/apps/projects/sprint_picker_settings.py +132 -0
  479. trueppm_api/apps/projects/standup.py +304 -0
  480. trueppm_api/apps/projects/standup_views.py +99 -0
  481. trueppm_api/apps/projects/structural_operation_services.py +727 -0
  482. trueppm_api/apps/projects/structural_operation_views.py +311 -0
  483. trueppm_api/apps/projects/surface_visibility.py +126 -0
  484. trueppm_api/apps/projects/task_batch_services.py +379 -0
  485. trueppm_api/apps/projects/task_bulk.py +1152 -0
  486. trueppm_api/apps/projects/task_classification.py +561 -0
  487. trueppm_api/apps/projects/task_classification_defaults.py +95 -0
  488. trueppm_api/apps/projects/task_duration_settings.py +142 -0
  489. trueppm_api/apps/projects/task_grouping.py +754 -0
  490. trueppm_api/apps/projects/tasks.py +1917 -0
  491. trueppm_api/apps/projects/template_divergence.py +168 -0
  492. trueppm_api/apps/projects/template_services.py +160 -0
  493. trueppm_api/apps/projects/template_tasks.py +258 -0
  494. trueppm_api/apps/projects/template_views.py +799 -0
  495. trueppm_api/apps/projects/throttles.py +311 -0
  496. trueppm_api/apps/projects/urls.py +1011 -0
  497. trueppm_api/apps/projects/utilization.py +885 -0
  498. trueppm_api/apps/projects/views.py +20572 -0
  499. trueppm_api/apps/projects/wbs_paths.py +119 -0
  500. trueppm_api/apps/resources/__init__.py +0 -0
  501. trueppm_api/apps/resources/apps.py +8 -0
  502. trueppm_api/apps/resources/capacity.py +121 -0
  503. trueppm_api/apps/resources/migrations/0001_initial.py +68 -0
  504. trueppm_api/apps/resources/migrations/0001_squashed_0013_alter_projectresource_unique_together_and_more.py +281 -0
  505. trueppm_api/apps/resources/migrations/0002_alter_uuid_pk_serialize_false.py +36 -0
  506. trueppm_api/apps/resources/migrations/0003_resource_soft_delete.py +30 -0
  507. trueppm_api/apps/resources/migrations/0004_add_taskresource_resource_index.py +25 -0
  508. trueppm_api/apps/resources/migrations/0005_resource_job_role.py +20 -0
  509. trueppm_api/apps/resources/migrations/0006_skill.py +34 -0
  510. trueppm_api/apps/resources/migrations/0007_resource_skill.py +63 -0
  511. trueppm_api/apps/resources/migrations/0008_project_resource.py +63 -0
  512. trueppm_api/apps/resources/migrations/0009_task_skill_requirement.py +60 -0
  513. trueppm_api/apps/resources/migrations/0010_alter_resourceskill_options_and_more.py +20 -0
  514. trueppm_api/apps/resources/migrations/0011_alter_projectresource_notes.py +17 -0
  515. trueppm_api/apps/resources/migrations/0012_add_resource_user_fk.py +26 -0
  516. trueppm_api/apps/resources/migrations/0013_alter_projectresource_unique_together_and_more.py +53 -0
  517. trueppm_api/apps/resources/migrations/0014_projectresource_sync_seq_resource_sync_seq_and_more.py +37 -0
  518. trueppm_api/apps/resources/migrations/0015_projectresource_deactivated_with_resource.py +17 -0
  519. trueppm_api/apps/resources/migrations/__init__.py +0 -0
  520. trueppm_api/apps/resources/models.py +298 -0
  521. trueppm_api/apps/resources/serializers.py +426 -0
  522. trueppm_api/apps/resources/services.py +465 -0
  523. trueppm_api/apps/resources/urls.py +28 -0
  524. trueppm_api/apps/resources/views.py +1749 -0
  525. trueppm_api/apps/scheduling/__init__.py +0 -0
  526. trueppm_api/apps/scheduling/apps.py +153 -0
  527. trueppm_api/apps/scheduling/calendars.py +119 -0
  528. trueppm_api/apps/scheduling/deadletter.py +91 -0
  529. trueppm_api/apps/scheduling/forecast_history_settings.py +215 -0
  530. trueppm_api/apps/scheduling/forecast_staleness.py +313 -0
  531. trueppm_api/apps/scheduling/graph_guard.py +263 -0
  532. trueppm_api/apps/scheduling/management/__init__.py +0 -0
  533. trueppm_api/apps/scheduling/management/commands/__init__.py +0 -0
  534. trueppm_api/apps/scheduling/management/commands/prune_forecast_snapshots.py +53 -0
  535. trueppm_api/apps/scheduling/migrations/0001_failed_task.py +62 -0
  536. trueppm_api/apps/scheduling/migrations/0001_squashed_0007_projectforecastsnapshot.py +309 -0
  537. trueppm_api/apps/scheduling/migrations/0002_schedulerequest.py +78 -0
  538. trueppm_api/apps/scheduling/migrations/0003_add_schedule_request_reason.py +27 -0
  539. trueppm_api/apps/scheduling/migrations/0004_velocity_suggestion.py +94 -0
  540. trueppm_api/apps/scheduling/migrations/0005_montecarlorun.py +61 -0
  541. trueppm_api/apps/scheduling/migrations/0006_montecarlorun_distribution.py +17 -0
  542. trueppm_api/apps/scheduling/migrations/0007_projectforecastsnapshot.py +65 -0
  543. trueppm_api/apps/scheduling/migrations/0008_alter_schedulerequest_reason.py +28 -0
  544. trueppm_api/apps/scheduling/migrations/0008_folded_schedulerequest_reason.py +34 -0
  545. trueppm_api/apps/scheduling/migrations/0009_alter_schedulerequest_reason.py +29 -0
  546. trueppm_api/apps/scheduling/migrations/0010_failedtask_resolution_note_failedtask_resolved_at_and_more.py +43 -0
  547. trueppm_api/apps/scheduling/migrations/0011_failedtask_project.py +26 -0
  548. trueppm_api/apps/scheduling/migrations/0012_montecarlorun_diagnostic.py +17 -0
  549. trueppm_api/apps/scheduling/migrations/0013_montecarlorun_status_date.py +17 -0
  550. trueppm_api/apps/scheduling/migrations/0014_montecarlorun_plan_version.py +17 -0
  551. trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
  552. trueppm_api/apps/scheduling/models.py +516 -0
  553. trueppm_api/apps/scheduling/receivers.py +160 -0
  554. trueppm_api/apps/scheduling/risk_premium.py +283 -0
  555. trueppm_api/apps/scheduling/serializers.py +564 -0
  556. trueppm_api/apps/scheduling/services.py +1182 -0
  557. trueppm_api/apps/scheduling/signals.py +56 -0
  558. trueppm_api/apps/scheduling/tasks.py +1984 -0
  559. trueppm_api/apps/scheduling/telemetry.py +80 -0
  560. trueppm_api/apps/scheduling/units.py +35 -0
  561. trueppm_api/apps/scheduling/urls.py +57 -0
  562. trueppm_api/apps/scheduling/views.py +2564 -0
  563. trueppm_api/apps/sso/__init__.py +0 -0
  564. trueppm_api/apps/sso/apps.py +19 -0
  565. trueppm_api/apps/sso/extensions.py +130 -0
  566. trueppm_api/apps/sso/management/__init__.py +0 -0
  567. trueppm_api/apps/sso/management/commands/__init__.py +0 -0
  568. trueppm_api/apps/sso/management/commands/seed_sso_keycloak.py +172 -0
  569. trueppm_api/apps/sso/migrations/0001_initial.py +119 -0
  570. trueppm_api/apps/sso/migrations/0002_remove_oidcprovider_workspace_ssoproviderpolicy_and_more.py +169 -0
  571. trueppm_api/apps/sso/migrations/__init__.py +0 -0
  572. trueppm_api/apps/sso/models.py +148 -0
  573. trueppm_api/apps/sso/serializers.py +437 -0
  574. trueppm_api/apps/sso/services.py +1385 -0
  575. trueppm_api/apps/sso/urls.py +47 -0
  576. trueppm_api/apps/sso/views.py +935 -0
  577. trueppm_api/apps/sync/__init__.py +0 -0
  578. trueppm_api/apps/sync/apps.py +15 -0
  579. trueppm_api/apps/sync/backfill.py +208 -0
  580. trueppm_api/apps/sync/broadcast.py +320 -0
  581. trueppm_api/apps/sync/conflict.py +262 -0
  582. trueppm_api/apps/sync/consumers.py +471 -0
  583. trueppm_api/apps/sync/migrations/0001_initial.py +59 -0
  584. trueppm_api/apps/sync/migrations/0001_squashed_0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +77 -0
  585. trueppm_api/apps/sync/migrations/0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +41 -0
  586. trueppm_api/apps/sync/migrations/0003_boardevent.py +46 -0
  587. trueppm_api/apps/sync/migrations/0004_seed_sync_seq.py +24 -0
  588. trueppm_api/apps/sync/migrations/0005_program_sync_sequence.py +35 -0
  589. trueppm_api/apps/sync/migrations/__init__.py +0 -0
  590. trueppm_api/apps/sync/models.py +225 -0
  591. trueppm_api/apps/sync/pagination.py +231 -0
  592. trueppm_api/apps/sync/sequence.py +371 -0
  593. trueppm_api/apps/sync/serializers.py +687 -0
  594. trueppm_api/apps/sync/tasks.py +260 -0
  595. trueppm_api/apps/sync/throttles.py +175 -0
  596. trueppm_api/apps/sync/upload.py +501 -0
  597. trueppm_api/apps/sync/urls.py +17 -0
  598. trueppm_api/apps/sync/views.py +930 -0
  599. trueppm_api/apps/sync/ws_auth.py +193 -0
  600. trueppm_api/apps/taskruns/__init__.py +0 -0
  601. trueppm_api/apps/taskruns/apps.py +11 -0
  602. trueppm_api/apps/taskruns/migrations/0001_initial.py +92 -0
  603. trueppm_api/apps/taskruns/migrations/__init__.py +0 -0
  604. trueppm_api/apps/taskruns/models.py +68 -0
  605. trueppm_api/apps/taskruns/serializers.py +92 -0
  606. trueppm_api/apps/taskruns/tasks.py +60 -0
  607. trueppm_api/apps/taskruns/tracker.py +286 -0
  608. trueppm_api/apps/taskruns/urls.py +46 -0
  609. trueppm_api/apps/taskruns/views.py +195 -0
  610. trueppm_api/apps/teams/__init__.py +8 -0
  611. trueppm_api/apps/teams/apps.py +16 -0
  612. trueppm_api/apps/teams/migrations/0001_initial.py +139 -0
  613. trueppm_api/apps/teams/migrations/0001_squashed_0002_default_teams.py +142 -0
  614. trueppm_api/apps/teams/migrations/0002_default_teams.py +62 -0
  615. trueppm_api/apps/teams/migrations/0003_team_sync_seq_teammembership_sync_seq.py +22 -0
  616. trueppm_api/apps/teams/migrations/__init__.py +0 -0
  617. trueppm_api/apps/teams/models.py +145 -0
  618. trueppm_api/apps/teams/permissions.py +149 -0
  619. trueppm_api/apps/teams/serializers.py +95 -0
  620. trueppm_api/apps/teams/services.py +434 -0
  621. trueppm_api/apps/teams/signals.py +61 -0
  622. trueppm_api/apps/teams/urls.py +34 -0
  623. trueppm_api/apps/teams/views.py +201 -0
  624. trueppm_api/apps/timetracking/__init__.py +0 -0
  625. trueppm_api/apps/timetracking/apps.py +9 -0
  626. trueppm_api/apps/timetracking/migrations/0001_initial.py +117 -0
  627. trueppm_api/apps/timetracking/migrations/0002_timesheetsubmission.py +46 -0
  628. trueppm_api/apps/timetracking/migrations/0003_timeentry_deleted_at_timeentry_deleted_by.py +31 -0
  629. trueppm_api/apps/timetracking/migrations/0004_timeentry_sync_seq_and_more.py +24 -0
  630. trueppm_api/apps/timetracking/migrations/__init__.py +0 -0
  631. trueppm_api/apps/timetracking/models.py +178 -0
  632. trueppm_api/apps/timetracking/serializers.py +175 -0
  633. trueppm_api/apps/timetracking/services.py +117 -0
  634. trueppm_api/apps/timetracking/urls.py +47 -0
  635. trueppm_api/apps/timetracking/views.py +526 -0
  636. trueppm_api/apps/webhooks/__init__.py +0 -0
  637. trueppm_api/apps/webhooks/apps.py +8 -0
  638. trueppm_api/apps/webhooks/backfill.py +90 -0
  639. trueppm_api/apps/webhooks/dispatch.py +144 -0
  640. trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
  641. trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +243 -0
  642. trueppm_api/apps/webhooks/migrations/0002_alter_webhook_events.py +32 -0
  643. trueppm_api/apps/webhooks/migrations/0003_delivery_status_created_idx.py +17 -0
  644. trueppm_api/apps/webhooks/migrations/0004_webhook_program_alter_webhook_project_and_more.py +64 -0
  645. trueppm_api/apps/webhooks/migrations/0005_webhook_delivery_sequence_numbers.py +30 -0
  646. trueppm_api/apps/webhooks/migrations/0006_webhook_format_alter_webhook_events_and_more.py +61 -0
  647. trueppm_api/apps/webhooks/migrations/0007_sprint_lifecycle_events.py +63 -0
  648. trueppm_api/apps/webhooks/migrations/0008_risk_baseline_comment_events.py +73 -0
  649. trueppm_api/apps/webhooks/migrations/0009_encrypt_webhook_secret_and_failure_counters.py +90 -0
  650. trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
  651. trueppm_api/apps/webhooks/models.py +372 -0
  652. trueppm_api/apps/webhooks/serializers.py +318 -0
  653. trueppm_api/apps/webhooks/tasks.py +663 -0
  654. trueppm_api/apps/webhooks/urls.py +22 -0
  655. trueppm_api/apps/webhooks/views.py +383 -0
  656. trueppm_api/apps/workflow_engine/__init__.py +0 -0
  657. trueppm_api/apps/workflow_engine/apps.py +23 -0
  658. trueppm_api/apps/workflow_engine/migrations/0001_initial.py +230 -0
  659. trueppm_api/apps/workflow_engine/migrations/0001_squashed_0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +234 -0
  660. trueppm_api/apps/workflow_engine/migrations/0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +16 -0
  661. trueppm_api/apps/workflow_engine/migrations/0003_workflowoutboxrow_workflow_outbox_recovery_idx.py +21 -0
  662. trueppm_api/apps/workflow_engine/migrations/__init__.py +0 -0
  663. trueppm_api/apps/workflow_engine/models.py +265 -0
  664. trueppm_api/apps/workflow_engine/tasks.py +368 -0
  665. trueppm_api/apps/workspace/__init__.py +0 -0
  666. trueppm_api/apps/workspace/apps.py +10 -0
  667. trueppm_api/apps/workspace/backfill.py +111 -0
  668. trueppm_api/apps/workspace/export.py +242 -0
  669. trueppm_api/apps/workspace/migrations/0001_initial.py +326 -0
  670. trueppm_api/apps/workspace/migrations/0001_squashed_0014_workspacemembership_availability_effective_from_and_more.py +829 -0
  671. trueppm_api/apps/workspace/migrations/0002_remove_workspace_fiscal_year_start_and_more.py +85 -0
  672. trueppm_api/apps/workspace/migrations/0003_workspaceexportjob.py +73 -0
  673. trueppm_api/apps/workspace/migrations/0004_alter_workspaceinvite_status.py +27 -0
  674. trueppm_api/apps/workspace/migrations/0005_workspace_iteration_label_and_more.py +26 -0
  675. trueppm_api/apps/workspace/migrations/0006_alter_groupmembership_unique_together_and_more.py +45 -0
  676. trueppm_api/apps/workspace/migrations/0007_workspace_public_sharing_override_policy.py +21 -0
  677. trueppm_api/apps/workspace/migrations/0008_workspace_mc_history_attribution_audience_and_more.py +44 -0
  678. trueppm_api/apps/workspace/migrations/0009_workspace_methodology_and_more.py +186 -0
  679. trueppm_api/apps/workspace/migrations/0010_workspace_logo.py +38 -0
  680. trueppm_api/apps/workspace/migrations/0011_historicalworkspace_task_duration_change_percent_override_policy_and_more.py +56 -0
  681. trueppm_api/apps/workspace/migrations/0012_historicalworkspace_allowed_attachment_types_and_more.py +64 -0
  682. trueppm_api/apps/workspace/migrations/0013_auditevent.py +70 -0
  683. trueppm_api/apps/workspace/migrations/0014_workspacemembership_availability_effective_from_and_more.py +39 -0
  684. trueppm_api/apps/workspace/migrations/0015_trash_restore_1113.py +34 -0
  685. trueppm_api/apps/workspace/migrations/0016_historicalworkspace_calendar_and_more.py +55 -0
  686. trueppm_api/apps/workspace/migrations/0017_historicalworkspace_estimation_scale_and_more.py +38 -0
  687. trueppm_api/apps/workspace/migrations/0018_historicalworkspace_feedback_enabled_and_more.py +32 -0
  688. trueppm_api/apps/workspace/migrations/0019_historicalworkspace_mcp_enabled_and_more.py +22 -0
  689. trueppm_api/apps/workspace/migrations/0020_viewer_ordinal_one.py +34 -0
  690. trueppm_api/apps/workspace/migrations/0021_group_sync_seq_groupmembership_sync_seq_and_more.py +27 -0
  691. trueppm_api/apps/workspace/migrations/0022_historicalworkspace_sprint_picker_ready_only_default_and_more.py +22 -0
  692. trueppm_api/apps/workspace/migrations/0023_alter_auditevent_event_type.py +32 -0
  693. trueppm_api/apps/workspace/migrations/0024_alter_auditevent_event_type.py +33 -0
  694. trueppm_api/apps/workspace/migrations/0025_alter_auditevent_event_type.py +36 -0
  695. trueppm_api/apps/workspace/migrations/0026_alter_auditevent_event_type.py +37 -0
  696. trueppm_api/apps/workspace/migrations/0027_alter_auditevent_event_type.py +42 -0
  697. trueppm_api/apps/workspace/migrations/__init__.py +0 -0
  698. trueppm_api/apps/workspace/models.py +857 -0
  699. trueppm_api/apps/workspace/permissions.py +187 -0
  700. trueppm_api/apps/workspace/serializers.py +590 -0
  701. trueppm_api/apps/workspace/services.py +919 -0
  702. trueppm_api/apps/workspace/signals.py +37 -0
  703. trueppm_api/apps/workspace/tasks.py +555 -0
  704. trueppm_api/apps/workspace/urls.py +113 -0
  705. trueppm_api/apps/workspace/views.py +1502 -0
  706. trueppm_api/asgi.py +42 -0
  707. trueppm_api/celery.py +31 -0
  708. trueppm_api/core/__init__.py +0 -0
  709. trueppm_api/core/admin_site.py +206 -0
  710. trueppm_api/core/auth_views.py +916 -0
  711. trueppm_api/core/constant_time.py +51 -0
  712. trueppm_api/core/csp.py +54 -0
  713. trueppm_api/core/db.py +87 -0
  714. trueppm_api/core/db_session.py +37 -0
  715. trueppm_api/core/exception_handlers.py +157 -0
  716. trueppm_api/core/export_downloads.py +56 -0
  717. trueppm_api/core/extension_providers.py +56 -0
  718. trueppm_api/core/extension_signals.py +147 -0
  719. trueppm_api/core/idempotent.py +260 -0
  720. trueppm_api/core/metadata.py +42 -0
  721. trueppm_api/core/middleware.py +51 -0
  722. trueppm_api/core/openapi.py +949 -0
  723. trueppm_api/core/password_reset.py +500 -0
  724. trueppm_api/core/protect_conflict.py +100 -0
  725. trueppm_api/core/ratelimit.py +148 -0
  726. trueppm_api/core/redis_throttle.py +82 -0
  727. trueppm_api/core/refresh_cookie_policy.py +156 -0
  728. trueppm_api/core/request_body.py +86 -0
  729. trueppm_api/core/security_checks.py +884 -0
  730. trueppm_api/core/storage_config.py +105 -0
  731. trueppm_api/core/text_decode.py +168 -0
  732. trueppm_api/core/throttling.py +344 -0
  733. trueppm_api/core/valkey.py +214 -0
  734. trueppm_api/core/valkey_cache.py +66 -0
  735. trueppm_api/core/valkey_checks.py +104 -0
  736. trueppm_api/core/valkey_config.py +39 -0
  737. trueppm_api/core/worker_broker_wait.py +149 -0
  738. trueppm_api/core/worker_heartbeat.py +134 -0
  739. trueppm_api/fields.py +36 -0
  740. trueppm_api/permissions.py +30 -0
  741. trueppm_api/py.typed +0 -0
  742. trueppm_api/routing.py +11 -0
  743. trueppm_api/settings/__init__.py +0 -0
  744. trueppm_api/settings/base.py +2455 -0
  745. trueppm_api/settings/dev.py +273 -0
  746. trueppm_api/settings/prod.py +228 -0
  747. trueppm_api/urls.py +153 -0
  748. trueppm_api/workflows/__init__.py +38 -0
  749. trueppm_api/workflows/backends/__init__.py +6 -0
  750. trueppm_api/workflows/backends/default.py +297 -0
  751. trueppm_api/workflows/consumers/__init__.py +11 -0
  752. trueppm_api/workflows/consumers/requeue_failed_task.py +65 -0
  753. trueppm_api/workflows/interface.py +148 -0
  754. trueppm_api/workflows/registry.py +106 -0
  755. trueppm_api/workflows/services.py +90 -0
  756. trueppm_api/wsgi.py +11 -0
  757. trueppm_api-0.4.0b2.dist-info/METADATA +70 -0
  758. trueppm_api-0.4.0b2.dist-info/RECORD +759 -0
  759. trueppm_api-0.4.0b2.dist-info/WHEEL +4 -0
@@ -0,0 +1,857 @@
1
+ """Workspace-level models — singleton config, membership, invites, groups (ADR-0087).
2
+
3
+ The *workspace* is the whole self-hosted installation (single-tenant; multi-tenancy
4
+ is Enterprise). It is introduced here for the first time to back the Workspace
5
+ settings pages (#517 General, #518 Members, #519 Groups & teams).
6
+
7
+ Key design points (see ADR-0087):
8
+ - ``Workspace`` is a singleton config row (``singleton_key=1``), lazily materialized
9
+ on first access — no data migration. It does NOT extend ``VersionedModel`` (config
10
+ is not a mobile-offline sync entity).
11
+ - ``WorkspaceRole`` is deliberately separate from the project-scoped ``Role``
12
+ (#518): workspace permission tiers are coarser than project scheduling roles.
13
+ - ``WorkspaceMembership``/``Group``/``GroupMembership`` extend ``VersionedModel`` so
14
+ they participate in the offline sync protocol and support direct permission
15
+ queries — mirroring the ``ProjectMembership`` pattern (ADR-0070).
16
+ - A ``Group`` confers project access: ``GroupProject`` links a group to a project
17
+ with a granted project ``Role``; membership is reconciled into ``ProjectMembership``
18
+ rows by ``services.reconcile_group_access`` (the cascade).
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import datetime
24
+ import uuid
25
+
26
+ from django.conf import settings
27
+ from django.contrib.postgres.fields import ArrayField
28
+ from django.core.validators import MaxValueValidator, MinValueValidator
29
+ from django.db import models
30
+ from django.utils import timezone
31
+ from simple_history.models import HistoricalRecords
32
+
33
+ from trueppm_api.apps.access.models import Role
34
+ from trueppm_api.apps.projects.models import (
35
+ DurationChangePercentPolicy,
36
+ EstimationScale,
37
+ Methodology,
38
+ VersionedModel,
39
+ )
40
+
41
+ # MCAttributionAudience lives in scheduling.models (the gate consumer); imported
42
+ # here only as a choices set for the column. scheduling never imports workspace,
43
+ # so there is no cycle (ADR-0144).
44
+ from trueppm_api.apps.scheduling.models import MCAttributionAudience
45
+
46
+ INVITE_TTL_DAYS = 7 # ADR-0087 §4 — invite token validity window
47
+
48
+ # English month names, indexed 1–12, for the fiscal-year display label (#756).
49
+ # Hard-coded rather than derived from ``calendar``/``strftime`` because those are
50
+ # locale-sensitive and the workspace anchor must read identically regardless of
51
+ # the server's LC_TIME (UI copy is US-English per project convention).
52
+ _MONTH_NAMES = (
53
+ "",
54
+ "January",
55
+ "February",
56
+ "March",
57
+ "April",
58
+ "May",
59
+ "June",
60
+ "July",
61
+ "August",
62
+ "September",
63
+ "October",
64
+ "November",
65
+ "December",
66
+ )
67
+
68
+
69
+ class WorkspaceRole(models.IntegerChoices):
70
+ """Workspace-level permission tiers, separate from the project ``Role`` (#518).
71
+
72
+ Ordinals follow the ADR-0072 100-unit banding so Enterprise can register
73
+ intermediate roles (e.g. a billing-admin at 250) without an OSS renumber.
74
+ Write access gates on ``role >= WorkspaceRole.ADMIN``; OWNER carries the
75
+ last-owner guard (a workspace must never be left without an owner).
76
+
77
+ Account *lifecycle* (active/guest/deactivated) is the orthogonal
78
+ ``WorkspaceMembership.status`` field — a "guest" is a permission-limited
79
+ external collaborator, not a separate role tier.
80
+ """
81
+
82
+ MEMBER = 100, "Member"
83
+ ADMIN = 300, "Admin"
84
+ OWNER = 400, "Owner"
85
+
86
+
87
+ class MemberStatus(models.TextChoices):
88
+ """Account lifecycle for a workspace member (orthogonal to role)."""
89
+
90
+ ACTIVE = "active", "Active"
91
+ GUEST = "guest", "Guest"
92
+ DEACTIVATED = "deactivated", "Deactivated"
93
+
94
+
95
+ class InviteStatus(models.TextChoices):
96
+ PENDING = "pending", "Pending"
97
+ ACCEPTED = "accepted", "Accepted"
98
+ REVOKED = "revoked", "Revoked"
99
+ EXPIRED = "expired", "Expired"
100
+ # Terminal email-delivery failure (retries exhausted). Distinct from EXPIRED
101
+ # (token lifetime lapsed): the raw token is cleared regardless so it cannot
102
+ # linger at rest after the send path gives up. An admin can re-invite.
103
+ FAILED = "failed", "Failed"
104
+
105
+
106
+ def _default_work_week() -> list[bool]:
107
+ """Mon–Sun working-day flags; default Mon–Fri working, weekend off.
108
+
109
+ A callable default is required so each row gets its own list rather than
110
+ sharing one mutable instance across rows.
111
+ """
112
+ return [True, True, True, True, True, False, False]
113
+
114
+
115
+ def _default_allowed_attachment_types() -> list[str]:
116
+ """Workspace seed allow-list — the system default (ADR-0153, #976).
117
+
118
+ Sourced from the single canonical set so the column default and the
119
+ historic hardcoded gate cannot drift. A callable default (not a literal)
120
+ keeps the migration state stable and gives each row its own list; sorted so
121
+ the ArrayField has a deterministic order.
122
+ """
123
+ from trueppm_api.apps.projects.attachment_policy import (
124
+ SYSTEM_DEFAULT_ATTACHMENT_TYPES,
125
+ )
126
+
127
+ return sorted(SYSTEM_DEFAULT_ATTACHMENT_TYPES)
128
+
129
+
130
+ def _workspace_logo_upload_to(instance: Workspace, filename: str) -> str:
131
+ """Storage key for an uploaded workspace logo (ADR-0149).
132
+
133
+ A fresh UUID prefix per upload means a *Replace* never overwrites the prior
134
+ file in place — the old blob is deleted explicitly on commit (avoiding a
135
+ storage race) and the new URL cache-busts for free. Mirrors the
136
+ ``TaskAttachment`` ``upload_to`` precedent (ADR-0075).
137
+ """
138
+ suffix = ".webp" if filename.lower().endswith(".webp") else ".png"
139
+ # Stored under the shared local-dev branding root (gitignored, like
140
+ # ``attachments/`` and ``media/``); prod points STORAGES at object storage.
141
+ return f"branding/workspace-logo/{uuid.uuid4().hex}{suffix}"
142
+
143
+
144
+ class TermOverridePolicy(models.TextChoices):
145
+ """How a workspace terminology default cascades to programs/projects (ADR-0116).
146
+
147
+ OSS honors ``INHERIT`` and ``SUGGEST`` identically as "lower levels may
148
+ override" — they differ only in whether the workspace default pre-fills a new
149
+ project's create form. ``ENFORCE`` (locking the term so a program/project
150
+ cannot override it) is an Enterprise capability: in the community edition it
151
+ degrades to ``SUGGEST`` (no-op) unless a terminology-enforcement provider is
152
+ registered (``apps.projects.iteration_label``, ``trueppm-enterprise#154``).
153
+ """
154
+
155
+ INHERIT = "inherit", "Inherit"
156
+ SUGGEST = "suggest", "Suggest"
157
+ ENFORCE = "enforce", "Enforce"
158
+
159
+
160
+ class Workspace(models.Model):
161
+ """Singleton installation-wide configuration (#517).
162
+
163
+ Enforced single-row via the unique ``singleton_key`` (ADR-0081 pattern) and
164
+ lazily created on first GET via :meth:`load` (ADR-0079 ``PhaseGateConfig``
165
+ precedent) — there is intentionally no data migration to seed it.
166
+ """
167
+
168
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
169
+ # Always 1 — the unique constraint makes a second row impossible.
170
+ singleton_key = models.PositiveSmallIntegerField(default=1, editable=False, unique=True)
171
+
172
+ name = models.CharField(max_length=255, default="TruePPM Workspace")
173
+ # Read-only via the API. Self-hosted installs have no subdomain; the column
174
+ # exists so a future hosted edition can populate it without a schema change.
175
+ subdomain = models.CharField(max_length=63, blank=True, default="")
176
+ # Fallback IANA timezone for a project that sets none. Its single reader is
177
+ # ``notifications.services._project_timezone`` (#3377), which interprets a
178
+ # project's quiet-hours window in it — Project.timezone wins where set, and
179
+ # this value is the next link before ``settings.TIME_ZONE``. Not a display
180
+ # timezone: an instant shown to a person is re-clocked from that person's
181
+ # own ``profiles.Profile.timezone``.
182
+ timezone = models.CharField(max_length=64, default="UTC")
183
+ # Structured fiscal-year anchor (#756): month (1–12) + day (1–31), replacing
184
+ # the former free-text ``fiscal_year_start`` CharField. Year-agnostic — a
185
+ # workspace whose FY starts April 1 stores (4, 1). Drives quarter labels and
186
+ # boundaries across the workspace, including the Schedule timeline (#755).
187
+ # Day-vs-month validity (no Feb 30) is enforced in the settings serializer,
188
+ # which is the only write path; the model carries the coarse range validators.
189
+ fiscal_year_start_month = models.PositiveSmallIntegerField(
190
+ default=1,
191
+ validators=[MinValueValidator(1), MaxValueValidator(12)],
192
+ )
193
+ fiscal_year_start_day = models.PositiveSmallIntegerField(
194
+ default=1,
195
+ validators=[MinValueValidator(1), MaxValueValidator(31)],
196
+ )
197
+ work_week = ArrayField(models.BooleanField(), size=7, default=_default_work_week)
198
+ default_project_view = models.CharField(max_length=32, default="board")
199
+ allow_guests = models.BooleanField(default=True)
200
+ public_sharing = models.BooleanField(default=False)
201
+ # Iteration-container label default for the whole workspace (ADR-0116, #1106) —
202
+ # the non-null root of the Workspace → Program → Project inheritance chain. A
203
+ # program/project whose own override is NULL resolves up to this value.
204
+ # Display-only (ADR-0038/0111); never gates tabs, routes, or CPM.
205
+ iteration_label = models.CharField(max_length=32, default="Sprint")
206
+ # INHERIT/SUGGEST (OSS) allow lower-level overrides; ENFORCE (Enterprise) locks
207
+ # the term downstream and is a no-op in the community edition (ADR-0116).
208
+ iteration_label_override_policy = models.CharField(
209
+ max_length=16,
210
+ choices=TermOverridePolicy.choices,
211
+ default=TermOverridePolicy.SUGGEST,
212
+ )
213
+ # Governs whether a program/project may override the workspace sharing
214
+ # settings (public_sharing / allow_guests) — ADR-0135, #978. SUGGEST (OSS
215
+ # default) lets lower scopes loosen *or* tighten freely; ENFORCE (Enterprise)
216
+ # makes the workspace value a hard ceiling so downstream cannot loosen it.
217
+ # ENFORCE is a no-op in the community edition (degrades to SUGGEST) unless a
218
+ # sharing-enforcement provider is registered (``apps.projects.sharing_settings``).
219
+ public_sharing_override_policy = models.CharField(
220
+ max_length=16,
221
+ choices=TermOverridePolicy.choices,
222
+ default=TermOverridePolicy.SUGGEST,
223
+ )
224
+ # Per-workspace Monte Carlo forecast-history config (ADR-0144, #1232) — the
225
+ # non-null root of the Workspace → Program → Project inheritance chain. A
226
+ # program/project whose own override is NULL resolves up to these values via
227
+ # ``scheduling.forecast_history_settings``. All three carry model-level defaults
228
+ # that reproduce the pre-0143 behavior exactly (history on, cap 100, attribution
229
+ # to Admin/Owner), so the migration is purely additive.
230
+ mc_history_enabled = models.BooleanField(default=True)
231
+ # Default matches settings.MC_HISTORY_CAP; the resolver clamps reads to
232
+ # settings.MC_HISTORY_HARD_CAP (500) so a misconfigured value can't unbound the
233
+ # nightly purge.
234
+ mc_history_retention_cap = models.PositiveIntegerField(default=100)
235
+ mc_history_attribution_audience = models.CharField(
236
+ max_length=16,
237
+ choices=MCAttributionAudience.choices,
238
+ default=MCAttributionAudience.ADMIN_OWNER,
239
+ )
240
+ # Whether programs/projects may override the forecast-history config above
241
+ # (ADR-0144, mirroring public_sharing_override_policy). SUGGEST (OSS default) =
242
+ # downstream may override freely; ENFORCE = Enterprise hard lock (no-op in OSS —
243
+ # stored but never enforced; the enforcement seam lives in
244
+ # ``scheduling.forecast_history_settings``).
245
+ mc_history_override_policy = models.CharField(
246
+ max_length=16,
247
+ choices=TermOverridePolicy.choices,
248
+ default=TermOverridePolicy.SUGGEST,
249
+ )
250
+ # Workspace-wide default planning methodology (ADR-0107, issue 955) — the
251
+ # non-null root of the Workspace → Program → Project methodology inheritance
252
+ # chain (the "experience preset"). New projects pre-fill from this default;
253
+ # the *effective* methodology a project displays is resolved computed-on-read
254
+ # in ``apps.projects.methodology`` (project ?? program ?? workspace, gated by
255
+ # the policy below). Default HYBRID mirrors Project/Program (ADR-0041), so the
256
+ # migration is purely additive — every existing project keeps its own value.
257
+ methodology = models.CharField(
258
+ max_length=16,
259
+ choices=Methodology.choices,
260
+ default=Methodology.HYBRID,
261
+ )
262
+ # How the workspace default cascades to programs/projects (ADR-0107). Unlike
263
+ # iteration_label/sharing, methodology is NOT-NULL on every scope (no null
264
+ # "inherit" sentinel), so inheritance is POLICY-driven, not override-presence
265
+ # driven: INHERIT/active-ENFORCE → the workspace default wins and the
266
+ # per-scope picker is read-only; SUGGEST → each scope's own methodology is
267
+ # honored (default — preserves today's behavior where each project owns its
268
+ # methodology). ENFORCE is the Enterprise lock seam (trueppm-enterprise#144);
269
+ # OSS registers no enforcement provider, so ENFORCE degrades to SUGGEST (the
270
+ # methodology PATCH is allowed and the per-scope override wins).
271
+ methodology_override_policy = models.CharField(
272
+ max_length=16,
273
+ choices=TermOverridePolicy.choices,
274
+ default=TermOverridePolicy.SUGGEST,
275
+ )
276
+ # Workspace-wide default for how percent_complete reacts to a task duration
277
+ # change (ADR-0151, #414) — the non-null root of the Workspace → Program →
278
+ # Project inheritance chain. Default KEEP reproduces today's behavior exactly
279
+ # (the PM-entered % is left untouched), so the migration is purely additive.
280
+ # Resolved computed-on-read in ``apps.projects.task_duration_settings``.
281
+ task_duration_change_percent_policy = models.CharField(
282
+ max_length=16,
283
+ choices=DurationChangePercentPolicy.choices,
284
+ default=DurationChangePercentPolicy.KEEP,
285
+ )
286
+ # Whether programs/projects may override the policy above (ADR-0151, mirroring
287
+ # public_sharing_override_policy). SUGGEST (OSS default) = downstream may
288
+ # override freely; ENFORCE = Enterprise hard lock (stored but never enforced in
289
+ # OSS — the enforcement seam lives in ``apps.projects.task_duration_settings``,
290
+ # which registers no provider in the community edition, so ENFORCE → SUGGEST).
291
+ task_duration_change_percent_override_policy = models.CharField(
292
+ max_length=16,
293
+ choices=TermOverridePolicy.choices,
294
+ default=TermOverridePolicy.SUGGEST,
295
+ )
296
+
297
+ # Per-workspace estimation scale (ADR-0510, #2027) — the non-null root of the
298
+ # Workspace → Program → Project inheritance chain. Default FIBONACCI reproduces
299
+ # today's de-facto scale (the only hardcoded picker, StoryDetailDrawer, used
300
+ # Fibonacci), so the migration is purely additive. Resolved computed-on-read in
301
+ # ``apps.projects.estimation_scale``. There is NO override_policy — unlike the
302
+ # duration policy / calendar, the scale is a plain PO/team preference with no
303
+ # enforcement seam; it is freely overridable at every scope (OSS by
304
+ # construction, ADR-0510). Display/input-only: it never touches the stored
305
+ # integer ``story_points`` or any velocity/rollup math.
306
+ estimation_scale = models.CharField(
307
+ max_length=16,
308
+ choices=EstimationScale.choices,
309
+ default=EstimationScale.FIBONACCI,
310
+ )
311
+
312
+ # Per-workspace default for the sprint story picker's "Ready only" filter
313
+ # (ADR-0758, #2670) — the non-null root of the Workspace → Program → Project
314
+ # inheritance chain. Default True: a story marked below Ready (unestimated,
315
+ # missing/unmet acceptance criteria) is hidden from the picker's default view
316
+ # rather than offered for commit. Resolved computed-on-read in
317
+ # ``apps.projects.sprint_picker_settings``. Mirrors ``estimation_scale``: there
318
+ # is NO override_policy — this is advisory UI guidance, never a commit-time
319
+ # block (OSS by construction; a hard ENFORCE gate is out of scope, Enterprise).
320
+ sprint_picker_ready_only_default = models.BooleanField(default=True)
321
+
322
+ # Per-workspace attachment policy (ADR-0153, #976) — the non-null root of the
323
+ # Workspace → Program → Project inheritance chain. ``attachments_enabled``
324
+ # gates whether task FILE uploads are permitted (external links are a separate
325
+ # capability, unaffected). ``allowed_attachment_types`` is the MIME allow-list,
326
+ # seeded from the system default so the migration is purely additive and the
327
+ # pre-0150 behavior is reproduced exactly. Both resolve computed-on-read in
328
+ # ``apps.projects.attachment_policy``; clients read the serializer's
329
+ # ``effective_*``/``inherited_*`` fields. A non-overridable security denylist
330
+ # (text/html etc.) is subtracted on every read regardless of this value.
331
+ attachments_enabled = models.BooleanField(default=True)
332
+ allowed_attachment_types = ArrayField(
333
+ models.CharField(max_length=255),
334
+ default=_default_allowed_attachment_types,
335
+ )
336
+ # Whether programs/projects may override the attachment policy above
337
+ # (ADR-0153, mirroring public_sharing_override_policy). SUGGEST (OSS default) =
338
+ # downstream may narrow or widen freely; ENFORCE = Enterprise hard lock (no-op
339
+ # in OSS — stored but never enforced; the enforcement seam lives in
340
+ # ``apps.projects.attachment_policy``).
341
+ attachments_override_policy = models.CharField(
342
+ max_length=16,
343
+ choices=TermOverridePolicy.choices,
344
+ default=TermOverridePolicy.SUGGEST,
345
+ )
346
+ # Workspace-scope MCP/agent read consent (ADR-0678, #2482) — the non-null root
347
+ # of the Workspace → Program → Project chain. Default True preserves the
348
+ # pre-0678 behavior exactly (the instance switch, ADR-0497, remains the
349
+ # operator's lever).
350
+ #
351
+ # why no ``mcp_override_policy`` here, breaking symmetry with the four
352
+ # settings above: this cascade is RESTRICTIVE-ONLY (AND), not override. Any
353
+ # scope may deny for itself and everything beneath it; no scope may grant over
354
+ # another's denial. An ENFORCE lock would let a workspace admin force MCP back
355
+ # ON for a team, which is exactly the "consent in name only" objection (#2415)
356
+ # this control exists to answer. Turning it off org-wide — the only coherent
357
+ # governance lever — already works through the AND.
358
+ mcp_enabled = models.BooleanField(default=True)
359
+
360
+ # In-product feedback / report-a-bug link (#2392).
361
+ #
362
+ # A LINK, never a beacon: nothing is transmitted by rendering the control or
363
+ # by opening it. The client assembles a prefilled URL the user reads and
364
+ # edits before choosing to submit it, so a self-hosted (or air-gapped)
365
+ # instance never phones home as a side effect of the feature existing.
366
+ #
367
+ # `feedback_url` empty means "use the built-in public tracker" — storing the
368
+ # default here instead would freeze today's URL into every existing
369
+ # workspace row, so an operator who never touched the setting could not be
370
+ # moved off it by an upgrade.
371
+ feedback_enabled = models.BooleanField(default=True)
372
+ feedback_url = models.URLField(blank=True, default="")
373
+
374
+ # Workspace-wide default working calendar (ADR-0441, #1987) — the root of the
375
+ # Project → Program → Workspace → system-default inheritance chain. NULL = fall
376
+ # through to the system default (Mon-Fri/8h/UTC); we do not seed a system-default
377
+ # Calendar row, so the migration is purely additive and every existing project keeps
378
+ # its own behavior (a project with no calendar resolved to the system default before
379
+ # and still does when the workspace calendar is unset). Resolved computed-on-read in
380
+ # ``apps.projects.calendar_settings`` and fed to CPM via ``scheduling.calendars``.
381
+ # PROTECT so a Calendar still in use as the workspace default cannot be deleted.
382
+ calendar = models.ForeignKey(
383
+ "projects.Calendar",
384
+ on_delete=models.PROTECT,
385
+ related_name="+",
386
+ null=True,
387
+ blank=True,
388
+ )
389
+ # Whether programs/projects may override the workspace calendar above (ADR-0441,
390
+ # mirroring the other *_override_policy fields). SUGGEST (OSS default) = downstream
391
+ # may override freely; ENFORCE = Enterprise hard lock (no-op in OSS — stored but
392
+ # never enforced; the enforcement seam lives in ``apps.projects.calendar_settings``,
393
+ # which registers no provider in the community edition, so ENFORCE degrades to
394
+ # SUGGEST). INHERIT hides the per-scope override affordance entirely.
395
+ calendar_override_policy = models.CharField(
396
+ max_length=16,
397
+ choices=TermOverridePolicy.choices,
398
+ default=TermOverridePolicy.SUGGEST,
399
+ )
400
+
401
+ # Workspace branding logo (ADR-0149, #969). Raster only (PNG/WebP) — SVG is
402
+ # rejected at the serializer because it can embed <script> and the logo is
403
+ # served from a public (AllowAny) endpoint. A plain FileField, not ImageField,
404
+ # so the app carries no Pillow dependency (matches the TaskAttachment precedent,
405
+ # ADR-0075); the validated content type is pinned in ``logo_mime`` so the serve
406
+ # endpoint sets Content-Type from a trusted source rather than sniffing on read.
407
+ logo = models.FileField(upload_to=_workspace_logo_upload_to, blank=True, default="")
408
+ logo_mime = models.CharField(max_length=64, blank=True, default="")
409
+
410
+ created_at = models.DateTimeField(auto_now_add=True)
411
+ updated_at = models.DateTimeField(auto_now=True)
412
+
413
+ # Team-readable audit of singleton config changes (ADR-0107 §5). Workspace had
414
+ # no history table before; this captures who/when/old→new for the methodology
415
+ # default + override policy (and every other singleton field) via the existing
416
+ # ``history_record_created`` signal that enterprise consumes for retention.
417
+ history = HistoricalRecords()
418
+
419
+ class Meta:
420
+ db_table = "workspace_workspace"
421
+
422
+ def __str__(self) -> str:
423
+ return f"Workspace({self.name})"
424
+
425
+ @classmethod
426
+ def load(cls) -> Workspace:
427
+ """Return the singleton, creating it on first access."""
428
+ obj, _ = cls.objects.get_or_create(singleton_key=1)
429
+ return obj
430
+
431
+ @property
432
+ def fiscal_year_start_display(self) -> str:
433
+ """Human label for the fiscal-year anchor, e.g. ``"April 6"`` (#756).
434
+
435
+ Read-only convenience exposed on the settings serializer for back-compat
436
+ with any reader that previously consumed the free-text ``fiscal_year_start``.
437
+ """
438
+ return f"{_MONTH_NAMES[self.fiscal_year_start_month]} {self.fiscal_year_start_day}"
439
+
440
+
441
+ class WorkspaceMembership(VersionedModel):
442
+ """A user's workspace-level role + account status (#518).
443
+
444
+ Standalone through-model (not an M2M ``through=``) so it participates in
445
+ offline sync and can be queried directly in permission checks — mirrors
446
+ ``ProjectMembership`` (ADR-0070). A membership row is created lazily the
447
+ first time an admin sets a user's role/status; users without a row default
448
+ to OWNER if they are a Django superuser, else MEMBER (see
449
+ ``permissions._workspace_membership_role``).
450
+ """
451
+
452
+ workspace = models.ForeignKey(
453
+ Workspace,
454
+ on_delete=models.CASCADE,
455
+ related_name="memberships",
456
+ )
457
+ user = models.ForeignKey(
458
+ settings.AUTH_USER_MODEL,
459
+ on_delete=models.CASCADE,
460
+ related_name="workspace_memberships",
461
+ )
462
+ role = models.IntegerField(choices=WorkspaceRole.choices, default=WorkspaceRole.MEMBER)
463
+ status = models.CharField(
464
+ max_length=16, choices=MemberStatus.choices, default=MemberStatus.ACTIVE
465
+ )
466
+ # Mirrors ProjectMembership (#590): joined_at backfills via default=timezone.now;
467
+ # role_changed_at is NULL until an actual role change.
468
+ joined_at = models.DateTimeField(default=timezone.now, editable=False)
469
+ role_changed_at = models.DateTimeField(null=True, blank=True, editable=False)
470
+ # Resource-availability baseline (#542): the workspace-member denominator the
471
+ # project-level allocation model (#489) compares its summed per-project
472
+ # percentages against. Models the member not being fully available — parental
473
+ # leave (40%), a part-time contract (60%), a known side commitment. The
474
+ # effective_* bounds carve out a temporary baseline (e.g. a single quarter);
475
+ # both NULL means it applies indefinitely. default=100 backfills every
476
+ # existing row to "fully available" so the AddField migration is non-
477
+ # interactive and the historical behaviour (everyone at 100%) is preserved.
478
+ availability_percent = models.PositiveSmallIntegerField(
479
+ default=100,
480
+ validators=[MinValueValidator(0), MaxValueValidator(100)],
481
+ )
482
+ availability_effective_from = models.DateField(null=True, blank=True)
483
+ availability_effective_to = models.DateField(null=True, blank=True)
484
+ availability_notes = models.TextField(blank=True, default="")
485
+
486
+ class Meta:
487
+ db_table = "workspace_membership"
488
+ constraints = [
489
+ models.UniqueConstraint(
490
+ fields=["workspace", "user"], name="uniq_workspace_membership_workspace_user"
491
+ ),
492
+ ]
493
+
494
+ def __str__(self) -> str:
495
+ return f"{self.user} — {WorkspaceRole(self.role).label} ({self.status})"
496
+
497
+
498
+ class WorkspaceInvite(models.Model):
499
+ """A pending email invitation to join the workspace (#518, ADR-0087 §4).
500
+
501
+ The raw token (``secrets.token_urlsafe``) is emailed in the accept link and
502
+ never stored — only its SHA-256 hash lives here, so a database leak cannot
503
+ be replayed into account takeover. Email delivery uses the transactional
504
+ outbox pattern (``email_pending`` drained by ``tasks.drain_invite_emails``),
505
+ mirroring ``Notification``.
506
+ """
507
+
508
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
509
+ workspace = models.ForeignKey(
510
+ Workspace,
511
+ on_delete=models.CASCADE,
512
+ related_name="invites",
513
+ )
514
+ email = models.EmailField()
515
+ role = models.IntegerField(choices=WorkspaceRole.choices, default=WorkspaceRole.MEMBER)
516
+ # SHA-256 of the raw token — the durable credential the accept endpoint
517
+ # authenticates against. The raw token is never stored long-term.
518
+ token_hash = models.CharField(max_length=64, unique=True, db_index=True)
519
+ # Transient raw token, populated at create time so the async email drain can
520
+ # build the accept link, then CLEARED on first successful send (and on
521
+ # accept). Bounds at-rest exposure to the orphan window; after send only the
522
+ # hash remains, so a later DB leak cannot be replayed.
523
+ email_token = models.CharField(max_length=64, blank=True, default="")
524
+ invited_by = models.ForeignKey(
525
+ settings.AUTH_USER_MODEL,
526
+ on_delete=models.SET_NULL,
527
+ null=True,
528
+ blank=True,
529
+ related_name="workspace_invites_sent",
530
+ )
531
+ status = models.CharField(
532
+ max_length=16, choices=InviteStatus.choices, default=InviteStatus.PENDING
533
+ )
534
+ created_at = models.DateTimeField(auto_now_add=True, db_index=True)
535
+ expires_at = models.DateTimeField()
536
+ accepted_at = models.DateTimeField(null=True, blank=True)
537
+ accepted_user = models.ForeignKey(
538
+ settings.AUTH_USER_MODEL,
539
+ on_delete=models.SET_NULL,
540
+ null=True,
541
+ blank=True,
542
+ related_name="workspace_invites_accepted",
543
+ )
544
+
545
+ # Email delivery state — drained by tasks.drain_invite_emails (ADR-0087 §Durable).
546
+ email_pending = models.BooleanField(default=False, db_index=True)
547
+ email_sent_at = models.DateTimeField(null=True, blank=True)
548
+ email_failed_at = models.DateTimeField(null=True, blank=True)
549
+ email_attempts = models.PositiveIntegerField(default=0)
550
+
551
+ class Meta:
552
+ db_table = "workspace_invite"
553
+ ordering = ["-created_at"]
554
+ indexes = [
555
+ models.Index(
556
+ fields=["email_pending", "-created_at"],
557
+ name="ix_invite_email_pending",
558
+ condition=models.Q(email_pending=True),
559
+ ),
560
+ models.Index(fields=["status", "-created_at"], name="ix_invite_status"),
561
+ ]
562
+
563
+ def __str__(self) -> str:
564
+ return f"WorkspaceInvite({self.email} → {WorkspaceRole(self.role).label}, {self.status})"
565
+
566
+ @staticmethod
567
+ def default_expiry() -> datetime.datetime:
568
+ return timezone.now() + datetime.timedelta(days=INVITE_TTL_DAYS)
569
+
570
+ @property
571
+ def is_expired(self) -> bool:
572
+ return timezone.now() >= self.expires_at
573
+
574
+
575
+ class Group(VersionedModel):
576
+ """A workspace team/group (#519).
577
+
578
+ Members are linked via the ``GroupMembership`` through-model; project
579
+ associations via ``GroupProject``. Adding a group to a project confers the
580
+ group's granted ``Role`` on every member by reconciling ``ProjectMembership``
581
+ rows (``services.reconcile_group_access``).
582
+ """
583
+
584
+ workspace = models.ForeignKey(
585
+ Workspace,
586
+ on_delete=models.CASCADE,
587
+ related_name="groups",
588
+ )
589
+ name = models.CharField(max_length=120)
590
+ description = models.TextField(blank=True, default="")
591
+ lead = models.ForeignKey(
592
+ settings.AUTH_USER_MODEL,
593
+ on_delete=models.SET_NULL,
594
+ null=True,
595
+ blank=True,
596
+ related_name="groups_led",
597
+ )
598
+
599
+ class Meta:
600
+ db_table = "workspace_group"
601
+ ordering = ["name"]
602
+
603
+ def __str__(self) -> str:
604
+ return f"Group({self.name})"
605
+
606
+
607
+ class GroupMembership(VersionedModel):
608
+ """Through-model linking a user to a :class:`Group` (#519)."""
609
+
610
+ group = models.ForeignKey(
611
+ Group,
612
+ on_delete=models.CASCADE,
613
+ related_name="memberships",
614
+ )
615
+ user = models.ForeignKey(
616
+ settings.AUTH_USER_MODEL,
617
+ on_delete=models.CASCADE,
618
+ related_name="group_memberships",
619
+ )
620
+ joined_at = models.DateTimeField(default=timezone.now, editable=False)
621
+
622
+ class Meta:
623
+ db_table = "workspace_group_membership"
624
+ constraints = [
625
+ models.UniqueConstraint(
626
+ fields=["group", "user"], name="uniq_group_membership_group_user"
627
+ ),
628
+ ]
629
+
630
+ def __str__(self) -> str:
631
+ return f"{self.user} ∈ {self.group}"
632
+
633
+
634
+ class GroupProject(models.Model):
635
+ """Links a :class:`Group` to a project with the project ``Role`` it confers (#519).
636
+
637
+ Uses the project-scoped ``Role`` enum (not ``WorkspaceRole``) because the
638
+ cascade materializes ``ProjectMembership`` rows. The conferred role is
639
+ capped below OWNER by serializer validation — a group can never confer
640
+ project ownership (the last-owner guard must remain meaningful).
641
+ """
642
+
643
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
644
+ group = models.ForeignKey(
645
+ Group,
646
+ on_delete=models.CASCADE,
647
+ related_name="project_links",
648
+ )
649
+ project = models.ForeignKey(
650
+ "projects.Project",
651
+ on_delete=models.CASCADE,
652
+ related_name="group_links",
653
+ )
654
+ role = models.IntegerField(choices=Role.choices, default=Role.MEMBER)
655
+ added_by = models.ForeignKey(
656
+ settings.AUTH_USER_MODEL,
657
+ on_delete=models.SET_NULL,
658
+ null=True,
659
+ blank=True,
660
+ related_name="group_projects_added",
661
+ )
662
+ created_at = models.DateTimeField(auto_now_add=True)
663
+
664
+ class Meta:
665
+ db_table = "workspace_group_project"
666
+ constraints = [
667
+ models.UniqueConstraint(
668
+ fields=["group", "project"], name="uniq_group_project_group_project"
669
+ ),
670
+ ]
671
+
672
+ def __str__(self) -> str:
673
+ return f"{self.group} → {self.project_id} ({Role(self.role).label})"
674
+
675
+
676
+ class ExportJobStatus(models.TextChoices):
677
+ """Lifecycle of an async workspace export (ADR-0174)."""
678
+
679
+ PENDING = "pending", "Pending"
680
+ RUNNING = "running", "Running"
681
+ SUCCESS = "success", "Success"
682
+ FAILED = "failed", "Failed"
683
+
684
+
685
+ class WorkspaceExportJob(models.Model):
686
+ """Tracks a full-workspace archive export (ADR-0174, #641).
687
+
688
+ Plain (non-synced) model — an export job is server-side bookkeeping, never a
689
+ mobile-offline entity. There is no FK to ``Workspace`` because it is a
690
+ singleton; a job always concerns the one workspace. Mirrors ``taskruns.TaskRun``
691
+ for the status/timestamp shape. The drain re-dispatches ``pending`` rows whose
692
+ ``celery_task_id`` never got set (broker down at ``on_commit``); the nightly
693
+ purge deletes rows past ``expires_at`` and their stored files.
694
+ """
695
+
696
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
697
+ requested_by = models.ForeignKey(
698
+ settings.AUTH_USER_MODEL,
699
+ on_delete=models.SET_NULL,
700
+ null=True,
701
+ blank=True,
702
+ related_name="workspace_exports",
703
+ )
704
+ status = models.CharField(
705
+ max_length=10,
706
+ choices=ExportJobStatus.choices,
707
+ default=ExportJobStatus.PENDING,
708
+ db_index=True,
709
+ )
710
+ celery_task_id = models.CharField(max_length=255, blank=True, default="", db_index=True)
711
+ # Storage key (relative to the configured default storage), populated on success.
712
+ file_path = models.CharField(max_length=512, blank=True, default="")
713
+ file_size = models.BigIntegerField(null=True, blank=True)
714
+ error_detail = models.TextField(blank=True, default="")
715
+ # Download-link validity; past this the purge deletes the row + file (410 Gone).
716
+ expires_at = models.DateTimeField(null=True, blank=True)
717
+ created_at = models.DateTimeField(auto_now_add=True, db_index=True)
718
+ started_at = models.DateTimeField(null=True, blank=True)
719
+ completed_at = models.DateTimeField(null=True, blank=True)
720
+
721
+ class Meta:
722
+ db_table = "workspace_export_job"
723
+ ordering = ["-created_at"]
724
+ indexes = [
725
+ # Drives the drain scan: pending rows awaiting (re-)dispatch.
726
+ models.Index(fields=["status", "created_at"], name="wsexport_status_created_idx"),
727
+ # Drives the nightly purge scan of expired download links.
728
+ models.Index(fields=["expires_at"], name="wsexport_expires_idx"),
729
+ ]
730
+
731
+ def __str__(self) -> str:
732
+ return f"WorkspaceExportJob({self.id}, {self.status})"
733
+
734
+
735
+ class AuditEventType(models.TextChoices):
736
+ """Operational audit event verbs (ADR-0157, #859).
737
+
738
+ The set is **additive-only**: new verbs may be appended over time, but an
739
+ existing value is never renamed or removed — enterprise audit receivers and
740
+ already-stored rows must stay valid against it.
741
+ """
742
+
743
+ MEMBER_ADDED = "member_added", "Member added"
744
+ MEMBER_REMOVED = "member_removed", "Member removed"
745
+ MEMBER_ROLE_CHANGED = "member_role_changed", "Member role changed"
746
+ # #2867. A status-only PATCH used to flip is_active and a membership status —
747
+ # annoying, fully reversible, low audit value. Since #2832 it also revokes the
748
+ # target's refresh tokens and Personal Access Tokens, and that revocation is
749
+ # deliberately durable: reactivation does not restore them. The action's blast
750
+ # radius changed and its audit treatment did not, so one Admin could permanently
751
+ # destroy another member's credentials with the log holding no evidence — while
752
+ # the DELETE path, which does strictly less, recorded MEMBER_REMOVED.
753
+ MEMBER_STATUS_CHANGED = "member_status_changed", "Member status changed"
754
+ OWNERSHIP_TRANSFERRED = "ownership_transferred", "Ownership transferred"
755
+ PROJECT_CREATED = "project_created", "Project created"
756
+ PROJECT_DELETED = "project_deleted", "Project deleted"
757
+ PROJECT_RESTORED = "project_restored", "Project restored"
758
+ # #2909. Publishing takes one project's shape — task names included — and
759
+ # makes it readable by everyone in the workspace, which is a wider audience
760
+ # than the source project's own members. Republishing additionally changes
761
+ # what the house template resolves to for every future adopter. Both are
762
+ # Admin-gated; neither was legible afterwards.
763
+ TEMPLATE_PUBLISHED = "template_published", "Template published"
764
+ SETTINGS_CHANGED = "workspace_settings_changed", "Workspace settings changed"
765
+ EXPORT_TRIGGERED = "workspace_export_triggered", "Workspace export triggered"
766
+ # #2911. An invite is the account-provisioning path, and it was the one
767
+ # administrative action that erased its own record: the admin list filters to
768
+ # PENDING, so accepting or revoking made the row vanish from the only surface
769
+ # that showed it, and no verb here replaced it. INVITE_ACCEPTED additionally
770
+ # carries the inviter in metadata — the MEMBER_ADDED row written on the same
771
+ # transaction has the *invitee* as its actor (the accept endpoint is
772
+ # unauthenticated), so without this the log can say "X joined via invite" and
773
+ # never who sent it.
774
+ INVITE_SENT = "invite_sent", "Invite sent"
775
+ INVITE_ACCEPTED = "invite_accepted", "Invite accepted"
776
+ INVITE_REVOKED = "invite_revoked", "Invite revoked"
777
+ # #3174. Calendars are shared org-level resources gated on IsOrgAdmin, which
778
+ # passes anyone holding ADMIN on at least one *active* project (#3569 narrowed
779
+ # the derivation to exclude archived and soft-deleted projects; it did not
780
+ # change who may edit a calendar). Editing working_days,
781
+ # hours_per_day, or a single holiday exception fans a CPM recompute across every
782
+ # project bound to that calendar — including projects the actor is not a member
783
+ # of, whose owners saw their finish dates move with nothing naming who did it or
784
+ # why. The permission question is deliberately left open (see the issue); this
785
+ # verb is the attribution half, so the movement is at least traceable.
786
+ CALENDAR_CHANGED = "calendar_changed", "Calendar changed"
787
+ # #3552, ADR-1120. The auth surface recorded failures and no successes: an SSO
788
+ # provider could be created, repointed, widened, disabled or torn down, and its
789
+ # client secret rotated, with nothing naming who did it or what the value was
790
+ # before — while the serializer already treated an issuer repoint as dangerous
791
+ # enough to refuse. These five are the admin-actioned and once-per-identity half
792
+ # of that gap; login success is deliberately a ``trueppm.auth`` log line instead,
793
+ # because its rate is set by request traffic and this table has no OSS retention.
794
+ SSO_PROVIDER_CREATED = "sso_provider_created", "SSO provider created"
795
+ SSO_PROVIDER_UPDATED = "sso_provider_updated", "SSO provider updated"
796
+ SSO_PROVIDER_DELETED = "sso_provider_deleted", "SSO provider deleted"
797
+ SSO_SECRET_ROTATED = "sso_secret_rotated", "SSO client secret rotated"
798
+ # Branch 3 of ``sso.services.resolve_user`` — an IdP identity binding itself to an
799
+ # account that already exists. Branch 4 (auto-create) wrote MEMBER_ADDED and this
800
+ # one wrote nothing, so the path that grants a federated credential over an
801
+ # *existing* local account was the only one leaving no trace.
802
+ SSO_ACCOUNT_LINKED = "sso_account_linked", "SSO account linked"
803
+
804
+
805
+ class AuditEvent(models.Model):
806
+ """Append-only* operational audit row for workspace administration (ADR-0157, #859).
807
+
808
+ *Mutable in OSS — the model makes no immutability/signing/retention guarantee;
809
+ those are Enterprise concerns layered on via the ``audit_event_created`` signal
810
+ (``workspace/signals.py``). OSS just records "who did what, when" at the
811
+ workspace-admin grain (member/role/project/settings/export changes).
812
+
813
+ Plain (non-synced) ``models.Model`` — like ``WorkspaceExportJob`` and
814
+ ``SprintScopeChange``, an audit row is server-side operational metadata read
815
+ only through the Owner/Admin surface; it is **never** a mobile-offline sync
816
+ entity, so it carries no ``server_version``. There is no FK to ``Workspace``
817
+ because the OSS workspace is a singleton — a row always concerns the one
818
+ workspace (mirrors the ``WorkspaceExportJob`` rationale). The enterprise
819
+ receiver resolves the workspace itself when mirroring events into its
820
+ multi-tenant schema.
821
+
822
+ Actor and target are stored both as a best-effort FK/id **and** a denormalized
823
+ human-readable label, so the log stays readable after the user or target is
824
+ deleted (the ``SprintScopeChange.subtask_name`` idiom).
825
+ """
826
+
827
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
828
+ actor = models.ForeignKey(
829
+ settings.AUTH_USER_MODEL,
830
+ on_delete=models.SET_NULL,
831
+ null=True,
832
+ blank=True,
833
+ related_name="audit_events",
834
+ )
835
+ # Denormalized actor display captured at event time (email/name) so the row
836
+ # remains human-readable even after the user is deleted (actor FK → NULL).
837
+ actor_label = models.CharField(max_length=255, blank=True, default="")
838
+ event_type = models.CharField(max_length=40, choices=AuditEventType.choices, db_index=True)
839
+ target_type = models.CharField(max_length=40, blank=True, default="")
840
+ # Best-effort pointer; may dangle after a hard delete — the denormalized
841
+ # ``target_label`` is the source of truth for display.
842
+ target_id = models.UUIDField(null=True, blank=True)
843
+ target_label = models.CharField(max_length=512, blank=True, default="")
844
+ metadata = models.JSONField(default=dict, blank=True)
845
+ created_at = models.DateTimeField(auto_now_add=True, db_index=True)
846
+
847
+ class Meta:
848
+ db_table = "workspace_auditevent"
849
+ ordering = ["-created_at"]
850
+ indexes = [
851
+ # Covers the ``?event_type=`` filter combined with the default
852
+ # reverse-chron ordering. Name ≤ 30 chars (Postgres identifier limit).
853
+ models.Index(fields=["event_type", "created_at"], name="auditevent_type_created_idx"),
854
+ ]
855
+
856
+ def __str__(self) -> str:
857
+ return f"AuditEvent({self.event_type}, {self.created_at:%Y-%m-%d})"