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,667 @@
1
+ """Service-layer helpers for the access app.
2
+
3
+ The service layer wraps multi-row transactions that must remain atomic — most
4
+ importantly the Program create flow, which inserts a ``Program`` row and an
5
+ OWNER ``ProgramMembership`` row in the same transaction so a Program can never
6
+ exist in the database without at least one Owner.
7
+
8
+ ADR-0070: see §Durable Execution §4 for the atomic-create rationale.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import uuid
14
+ from collections.abc import Sequence
15
+ from typing import Any
16
+
17
+ from django.core.exceptions import ValidationError
18
+ from django.db import models, transaction
19
+ from django.utils import timezone
20
+
21
+ from trueppm_api.apps.access.models import ProgramMembership, ProjectMembership, Role
22
+ from trueppm_api.apps.projects.models import Methodology, Program, Project
23
+
24
+
25
+ def _protecting_relations(model: type[models.Model]) -> list[Any]:
26
+ """Reverse relations into *model* whose FK is ``on_delete=PROTECT``.
27
+
28
+ Derived from ``_meta`` rather than hand-listed. Every hand-maintained version of
29
+ this list in the codebase went stale as new ``PROTECT`` FKs landed — three delete
30
+ paths were each written against the FKs that existed at the time and silently
31
+ started raising ``ProtectedError`` later (#2364). Reflection cannot drift.
32
+
33
+ ``include_hidden=True`` is load-bearing, not defensive: a FK declared with
34
+ ``related_name="+"`` is absent from ``_meta.related_objects`` but Django's
35
+ deletion collector still enforces its ``PROTECT``. Walking the public
36
+ ``related_objects`` would silently miss exactly those — reproducing the bug this
37
+ helper exists to prevent, just one layer down. ``Workspace.calendar`` is a live
38
+ example of a ``+``-named ``PROTECT`` FK in this codebase.
39
+
40
+ ``test_protect_relations_snapshot`` pins the resolved set, so adding a new
41
+ ``PROTECT`` FK fails a test and forces a deliberate call on whether a hard delete
42
+ should purge that child or refuse — rather than silently purging it here.
43
+ """
44
+ return [
45
+ rel
46
+ for rel in model._meta.get_fields(include_hidden=True)
47
+ if rel.is_relation
48
+ and rel.auto_created
49
+ and not rel.concrete
50
+ and getattr(rel, "on_delete", None) is models.PROTECT
51
+ ]
52
+
53
+
54
+ def _purge_protecting_children(model: type[models.Model], pks: Sequence[Any]) -> None:
55
+ """Delete every ``PROTECT``-ing child of *pks* so the parent rows can be removed.
56
+
57
+ Only for paths where the caller has already confirmed a permanent, irreversible
58
+ delete (the archive → ``?force=true`` dialog, sample-data teardown, the retention
59
+ purge). Everywhere else a ``PROTECT`` FK should surface as a 409 refusal, not be
60
+ bulldozed — see :mod:`trueppm_api.core.protect_conflict`.
61
+ """
62
+ if not pks:
63
+ return
64
+ for rel in _protecting_relations(model):
65
+ rel.related_model._base_manager.filter(**{f"{rel.field.name}__in": pks}).delete()
66
+
67
+
68
+ @transaction.atomic
69
+ def hard_delete_projects(project_ids: Sequence[Any]) -> int:
70
+ """Permanently delete projects and the rows whose ``PROTECT`` FKs block them.
71
+
72
+ Returns the number of ``Project`` rows removed. Callers are responsible for
73
+ authorization, audit events, and sync broadcasts — this helper owns only the
74
+ delete order.
75
+ """
76
+ if not project_ids:
77
+ return 0
78
+ _purge_protecting_children(Project, project_ids)
79
+ _, by_model = Project.objects.filter(pk__in=project_ids).delete()
80
+ return int(by_model.get(Project._meta.label, 0))
81
+
82
+
83
+ @transaction.atomic
84
+ def hard_delete_program(program_id: uuid.UUID | str) -> None:
85
+ """Permanently delete a program, its projects, and all ``PROTECT``-ing children.
86
+
87
+ The program's projects are deleted outright rather than detached — the only
88
+ caller is sample-data teardown, where the whole subtree is disposable. Contrast
89
+ :func:`delete_program_cascade`, which *soft*-deletes a real program and detaches
90
+ its projects so they survive as standalone work.
91
+ """
92
+ project_ids = list(Project.objects.filter(program_id=program_id).values_list("pk", flat=True))
93
+ hard_delete_projects(project_ids)
94
+ _purge_protecting_children(Program, [program_id])
95
+ Program.objects.filter(pk=program_id).delete()
96
+
97
+
98
+ @transaction.atomic
99
+ def create_program(
100
+ *,
101
+ name: str,
102
+ description: str,
103
+ methodology: str | Methodology,
104
+ created_by: Any,
105
+ lead: Any | None = None,
106
+ ) -> Program:
107
+ """Create a ``Program`` and the creator's OWNER membership in one transaction.
108
+
109
+ Atomicity is the whole point of this helper — if the OWNER ``ProgramMembership``
110
+ INSERT were a second request (or a non-atomic follow-up), a crash between the
111
+ two writes would leave a Program with no Owner, which permission classes treat
112
+ as "nobody can manage this program." The ``@transaction.atomic`` wrapper makes
113
+ that orphan window impossible.
114
+
115
+ Args:
116
+ name: Display name (max 255). No uniqueness enforcement — two PMs can
117
+ both have a program called "Phase 2".
118
+ description: Optional rich text; empty string allowed.
119
+ methodology: One of ``Methodology`` values; default ``HYBRID``.
120
+ created_by: The user creating the program — recorded on the Program row
121
+ and inserted as the OWNER ``ProgramMembership``.
122
+ lead: Optional program lead (#2025). At create time the only member that
123
+ will exist is ``created_by`` (the OWNER row inserted below), so the
124
+ serializer only admits ``lead == created_by`` — the "lead must be a
125
+ member" invariant holds because the OWNER membership is written in the
126
+ same atomic transaction. Defaults to ``None`` (no lead).
127
+
128
+ Returns:
129
+ The persisted ``Program``. The OWNER membership exists by the time the
130
+ function returns.
131
+ """
132
+ program = Program.objects.create(
133
+ name=name,
134
+ description=description,
135
+ methodology=methodology,
136
+ created_by=created_by,
137
+ lead=lead,
138
+ )
139
+ ProgramMembership.objects.create(
140
+ program=program,
141
+ user=created_by,
142
+ role=Role.OWNER,
143
+ )
144
+ return program
145
+
146
+
147
+ @transaction.atomic
148
+ def delete_program_cascade(program_id: uuid.UUID | str) -> None:
149
+ """Soft-delete all memberships, detach all projects, soft-delete the program.
150
+
151
+ Three things must happen atomically:
152
+
153
+ 1. **Memberships are soft-deleted.** ``ProgramMembership.program`` is ``PROTECT``,
154
+ so a naive ``program.delete()`` raises ``ProtectedError`` while any member
155
+ rows exist. The UX brief surfaces this as a combined "Remove all members
156
+ and delete" action; the cascade implements that intent server-side.
157
+ 2. **Projects are detached.** Django's ``SET_NULL`` only fires on a hard DELETE,
158
+ not on our soft-delete (``is_deleted=True``). Without an explicit detach
159
+ step, ``Project.program_id`` would continue to reference a soft-deleted
160
+ Program — the UI would display "Phase 2 (deleted)" for those projects
161
+ and the program shell would still try to retrieve a 404'd parent. Setting
162
+ ``program=NULL`` here matches the dialog's promise that projects become
163
+ standalone after deletion.
164
+ 3. **The program itself is soft-deleted.** This bumps ``server_version`` so
165
+ sync consumers can drop the row from their caches.
166
+
167
+ Args:
168
+ program_id: The program to delete.
169
+
170
+ Raises:
171
+ Program.DoesNotExist: if the program is missing or already soft-deleted.
172
+ """
173
+ # Local import: Project is in the projects app; importing at module top
174
+ # would create a circular dependency through the projects → access path.
175
+ from trueppm_api.apps.projects.models import Project
176
+
177
+ # Lock the program row so concurrent member-add / project-assign requests
178
+ # cannot land between the cascade steps and re-create a reference to the
179
+ # program we're about to mark deleted.
180
+ program = Program.objects.select_for_update().get(pk=program_id, is_deleted=False)
181
+
182
+ for membership in ProgramMembership.objects.select_for_update().filter(
183
+ program=program, is_deleted=False
184
+ ):
185
+ membership.soft_delete()
186
+
187
+ # Detach projects — they hold a SET_NULL FK, but SET_NULL only fires on
188
+ # hard delete. soft_delete() on the program would otherwise leave dangling
189
+ # references. We must go through ``Project.save()`` (not ``update()``) so
190
+ # ``server_version`` increments and mobile sync clients pick up the change.
191
+ for project in Project.objects.select_for_update().filter(program=program):
192
+ project.program = None
193
+ project.save(update_fields=["program"])
194
+
195
+ program.soft_delete()
196
+
197
+
198
+ @transaction.atomic
199
+ def transfer_project_ownership(
200
+ *,
201
+ project: Project,
202
+ new_owner: Any,
203
+ actor: Any,
204
+ ) -> ProjectMembership:
205
+ """Atomically promote a project member to OWNER, demote the actor to ADMIN.
206
+
207
+ Why the target must already be a member: requiring an existing membership
208
+ forces an explicit invite step and gives the audit trail a clean
209
+ "joined → promoted" sequence. Auto-creating membership on transfer would
210
+ surprise the target with admin authority on a project they were never
211
+ knowingly added to.
212
+
213
+ Args:
214
+ project: The project whose ownership is being transferred.
215
+ new_owner: The user who will become OWNER.
216
+ actor: The current OWNER initiating the transfer; will be downgraded
217
+ to ADMIN. May be the same user as ``new_owner`` — in that case
218
+ both upserts are no-ops and the function returns the actor's row.
219
+
220
+ Returns:
221
+ The new OWNER's ``ProjectMembership`` row.
222
+
223
+ Raises:
224
+ ValidationError: if ``new_owner`` has no existing active membership
225
+ on the project.
226
+ """
227
+ # Defense in depth — the view layer gates with ``IsProjectOwner``, but the
228
+ # service is reusable (management commands, signals, future endpoints). If
229
+ # the actor is not actually the OWNER, a naive ``.update()`` is a silent
230
+ # no-op that would promote the target without removing anyone — creating
231
+ # an unauthorized OWNER. Assert and fail loudly before any state changes.
232
+ try:
233
+ actor_row = ProjectMembership.objects.select_for_update().get(
234
+ project=project,
235
+ user=actor,
236
+ is_deleted=False,
237
+ )
238
+ except ProjectMembership.DoesNotExist as exc:
239
+ raise ValidationError("Only an existing project Owner can transfer ownership.") from exc
240
+ if actor_row.role != Role.OWNER:
241
+ raise ValidationError("Only an existing project Owner can transfer ownership.")
242
+
243
+ try:
244
+ target = ProjectMembership.objects.select_for_update().get(
245
+ project=project,
246
+ user=new_owner,
247
+ is_deleted=False,
248
+ )
249
+ except ProjectMembership.DoesNotExist as exc:
250
+ raise ValidationError(
251
+ "The new owner must already be a member of this project. "
252
+ "Invite them first, then retry the transfer."
253
+ ) from exc
254
+
255
+ if new_owner == actor:
256
+ return target
257
+
258
+ # Demote the actor first so the OWNER count never exceeds the previous
259
+ # state mid-transaction — defensive against future single-owner constraints.
260
+ # role_changed_at is stamped alongside role on both rows so the per-project
261
+ # access-evidence timestamp (#590) stays accurate on this path too, not just
262
+ # on the partial_update endpoint.
263
+ now = timezone.now()
264
+ actor_row.role = Role.ADMIN
265
+ actor_row.role_changed_at = now
266
+ actor_row.save(update_fields=["role", "role_changed_at"])
267
+
268
+ target.role = Role.OWNER
269
+ target.role_changed_at = now
270
+ target.save(update_fields=["role", "role_changed_at"])
271
+ return target
272
+
273
+
274
+ @transaction.atomic
275
+ def transfer_program_sponsorship(
276
+ *,
277
+ program: Program,
278
+ new_owner: Any,
279
+ actor: Any,
280
+ new_lead: Any | None = None,
281
+ ) -> ProgramMembership:
282
+ """Atomically promote a program member to OWNER and optionally rotate the lead.
283
+
284
+ Programs carry both an OWNER membership (RBAC) and a ``lead`` FK (the
285
+ "Program Manager" displayed in the header). Both can move together in a
286
+ sponsorship transfer so the header chip and the access matrix stay in sync.
287
+
288
+ Args:
289
+ program: The program whose sponsorship is being transferred.
290
+ new_owner: The user who will become OWNER.
291
+ actor: The current OWNER initiating the transfer; will be downgraded
292
+ to ADMIN.
293
+ new_lead: Optional user to set as ``program.lead``. ``None`` leaves
294
+ the existing lead unchanged; pass ``new_owner`` explicitly to keep
295
+ owner+lead in lockstep.
296
+
297
+ Returns:
298
+ The new OWNER's ``ProgramMembership`` row.
299
+
300
+ Raises:
301
+ ValidationError: if ``new_owner`` has no existing active program
302
+ membership.
303
+ """
304
+ # Defense in depth (see ``transfer_project_ownership`` for full rationale)
305
+ # — the actor must currently hold the OWNER row, otherwise the demote
306
+ # ``.update()`` is a no-op and the target gets promoted without anyone
307
+ # being removed.
308
+ try:
309
+ actor_row = ProgramMembership.objects.select_for_update().get(
310
+ program=program,
311
+ user=actor,
312
+ is_deleted=False,
313
+ )
314
+ except ProgramMembership.DoesNotExist as exc:
315
+ raise ValidationError("Only an existing Program Admin can transfer sponsorship.") from exc
316
+ if actor_row.role != Role.OWNER:
317
+ raise ValidationError("Only an existing Program Admin can transfer sponsorship.")
318
+
319
+ try:
320
+ target = ProgramMembership.objects.select_for_update().get(
321
+ program=program,
322
+ user=new_owner,
323
+ is_deleted=False,
324
+ )
325
+ except ProgramMembership.DoesNotExist as exc:
326
+ raise ValidationError(
327
+ "The new sponsor must already be a member of this program. "
328
+ "Invite them first, then retry the transfer."
329
+ ) from exc
330
+
331
+ if new_owner != actor:
332
+ # role_changed_at is stamped alongside role on both rows so the
333
+ # per-program access-evidence timestamp (#878) stays accurate on this
334
+ # path too, not just on the partial_update endpoint.
335
+ now = timezone.now()
336
+ actor_row.role = Role.ADMIN
337
+ actor_row.role_changed_at = now
338
+ actor_row.save(update_fields=["role", "role_changed_at"])
339
+
340
+ target.role = Role.OWNER
341
+ target.role_changed_at = now
342
+ target.save(update_fields=["role", "role_changed_at"])
343
+
344
+ if new_lead is not None:
345
+ program.lead = new_lead
346
+ program.save(update_fields=["lead"])
347
+
348
+ return target
349
+
350
+
351
+ @transaction.atomic
352
+ def split_program(
353
+ *,
354
+ program: Program,
355
+ splits: list[dict[str, Any]],
356
+ actor: Any,
357
+ ) -> list[Program]:
358
+ """Split a program into sub-programs and close the original (ADR-0156, #967).
359
+
360
+ For each entry in ``splits`` a new sub-program is created (owned by ``actor``,
361
+ methodology copied from the parent, all inheritable overrides left NULL so the
362
+ new program inherits workspace defaults), and the entry's projects are moved
363
+ under it. After every split is applied the original program is closed — it
364
+ becomes a read-only shell holding any projects that were not redistributed.
365
+
366
+ Only the ``Project.program`` FK moves; tasks, dependencies, baselines,
367
+ memberships, and history are untouched, so each project keeps its full
368
+ schedule and audit trail under its new program. Reassignment goes through
369
+ ``Project.save()`` (not ``.update()``) so ``server_version`` increments and
370
+ mobile sync clients pick up the move — same rationale as
371
+ ``delete_program_cascade``.
372
+
373
+ The whole operation is one transaction: either every sub-program, every
374
+ reassignment, and the parent close commit together, or nothing does. A bad
375
+ payload raises before any INSERT so the error message is precise.
376
+
377
+ Args:
378
+ program: The program being split. Must be open — callers gate on
379
+ ``IsProgramNotClosed``; once closed by this call a replayed request
380
+ is rejected by that gate, which is what makes a network retry safe.
381
+ splits: ``[{"name": str, "project_ids": [uuid]}, ...]``. Each entry
382
+ becomes one sub-program. ``project_ids`` may be empty (an empty
383
+ sub-program shell, symmetric with ``create_program``).
384
+ actor: The current OWNER initiating the split; becomes OWNER of every
385
+ sub-program.
386
+
387
+ Returns:
388
+ The created sub-programs, in input order.
389
+
390
+ Raises:
391
+ ValidationError: if a referenced project is not a live member of this
392
+ program, or if a project appears in more than one split.
393
+ """
394
+ # Lock the parent row so a concurrent project-assign / delete cannot land
395
+ # between validation and reassignment (mirrors delete_program_cascade).
396
+ program = Program.objects.select_for_update().get(pk=program.pk)
397
+
398
+ member_project_ids = {
399
+ str(pk)
400
+ for pk in Project.objects.filter(program=program, is_deleted=False).values_list(
401
+ "pk", flat=True
402
+ )
403
+ }
404
+
405
+ # Validate the full payload up front: every id must be a live member of this
406
+ # program, and no id may be claimed by two sub-programs.
407
+ seen: set[str] = set()
408
+ normalized: list[tuple[str, list[str]]] = []
409
+ for entry in splits:
410
+ name = entry["name"]
411
+ ids = [str(pid) for pid in entry["project_ids"]]
412
+ for pid in ids:
413
+ if pid not in member_project_ids:
414
+ raise ValidationError(f"Project {pid} is not a project of this program.")
415
+ if pid in seen:
416
+ raise ValidationError(f"Project {pid} is assigned to more than one sub-program.")
417
+ seen.add(pid)
418
+ normalized.append((name, ids))
419
+
420
+ sub_programs: list[Program] = []
421
+ for name, ids in normalized:
422
+ sub = create_program(
423
+ name=name,
424
+ description="",
425
+ methodology=program.methodology,
426
+ created_by=actor,
427
+ )
428
+ # Filter by program=program as well as pk so a project moved out by a
429
+ # racing request (despite the lock) is never silently captured.
430
+ for project in Project.objects.select_for_update().filter(
431
+ program=program, pk__in=ids, is_deleted=False
432
+ ):
433
+ project.program = sub
434
+ project.save(update_fields=["program"])
435
+ sub_programs.append(sub)
436
+
437
+ if not program.is_closed:
438
+ program.is_closed = True
439
+ program.closed_at = timezone.now()
440
+ program.closed_by = actor
441
+ program.save(update_fields=["is_closed", "closed_at", "closed_by"])
442
+
443
+ return sub_programs
444
+
445
+
446
+ def revoke_all_refresh_tokens(user: Any) -> int:
447
+ """Blacklist every outstanding simplejwt refresh token for ``user``.
448
+
449
+ This is the "sign out every device" primitive (ADR-0209). A password reset
450
+ (and, later, any explicit account-security action) must invalidate all of the
451
+ user's existing sessions, not just the one that triggered the change. The
452
+ SPA's session lives entirely in a JWT access token (in-memory, 15-min TTL) plus
453
+ an httpOnly refresh cookie (#897), so revoking every *refresh* token is the
454
+ complete server-side session revocation: a blacklisted refresh token can no
455
+ longer be exchanged for a new access token, and the old access token self-
456
+ expires within its short TTL.
457
+
458
+ Django sessions are intentionally left untouched — the SPA never authenticates
459
+ via ``django.contrib.sessions`` (that framework backs only the admin site and
460
+ the DRF browsable API), so there is no app session to clear there.
461
+
462
+ Completeness depends on every live token having an ``OutstandingToken`` row.
463
+ simplejwt writes one only in ``RefreshToken.for_user`` (login); rotation mints a
464
+ new jti and records nothing, so before #2999 this function walked a set of
465
+ already-blacklisted rows and left the *live* session usable while returning a
466
+ plausible non-zero count. ``core.auth_views._record_outstanding`` now upserts the
467
+ row on both login and rotation, which is what makes this function's contract
468
+ true. **Tokens minted before that fix shipped still have no row and remain
469
+ unrevokable** — an operator needing a hard cut across those must rotate
470
+ ``JWT_SIGNING_KEY``.
471
+
472
+ Idempotent: ``get_or_create`` means re-running blacklists nothing twice, and a
473
+ row already blacklisted by rotation stays blacklisted. Safe to call inside the
474
+ reset transaction.
475
+
476
+ Args:
477
+ user: The account whose refresh tokens should all be revoked.
478
+
479
+ Returns:
480
+ The number of tokens newly blacklisted (already-blacklisted tokens are not
481
+ counted). Zero when the account had no outstanding tokens (e.g. it has
482
+ never logged in on this deployment).
483
+ """
484
+ # Local import: the blacklist app's models are only importable once apps are
485
+ # loaded, and keeping the import lazy lets a lean deploy that removes the
486
+ # token_blacklist app degrade gracefully (the ImportError is swallowed → the
487
+ # password is still reset, sessions just fall back to TTL-only expiry).
488
+ try:
489
+ from rest_framework_simplejwt.token_blacklist.models import (
490
+ BlacklistedToken,
491
+ OutstandingToken,
492
+ )
493
+ except ImportError: # pragma: no cover - only when the app is uninstalled
494
+ return 0
495
+
496
+ revoked = 0
497
+ for token in OutstandingToken.objects.filter(user=user):
498
+ _, created = BlacklistedToken.objects.get_or_create(token=token)
499
+ if created:
500
+ revoked += 1
501
+ return revoked
502
+
503
+
504
+ def revoke_all_personal_access_tokens(
505
+ user: Any,
506
+ *,
507
+ actor: Any = None,
508
+ reason: str = "",
509
+ source_ip: str | None = None,
510
+ ) -> int:
511
+ """Soft-revoke every active Personal Access Token owned by ``user`` (ADR-0214).
512
+
513
+ Why this exists: a Personal Access Token is a *full-authority* bearer of the
514
+ user's own credentials — a script authenticating with it acts exactly as the
515
+ user. A password change is the user asserting "my credentials may be
516
+ compromised; cut everything off," so it must invalidate not only live sessions
517
+ (``revoke_all_refresh_tokens``) but also every long-lived personal credential
518
+ they minted. Leaving PATs live after a reset would defeat the whole point of
519
+ the reset — an attacker who phished the old password could keep the account via
520
+ a PAT they created. This is the PAT analogue of the refresh-token revocation and
521
+ is called immediately after it in the same atomic block.
522
+
523
+ Scope discipline: only ``owner=user`` (personal) tokens are revoked. Project-
524
+ and program-scoped tokens are *org assets* minted by an Admin/PM — they are not
525
+ the user's personal credentials and are deliberately left untouched, so a
526
+ password reset never breaks a team's CI integration.
527
+
528
+ Idempotent: already-revoked tokens are filtered out, so re-running is a no-op.
529
+ Uses a bulk ``update()`` (not per-row ``save()``) because revocation is a
530
+ single indexed write with no ``server_version`` sync semantics on the audit
531
+ path — the tokens are not mobile-synced resources whose version consumers track.
532
+
533
+ **Every revoked token gets an** ``ApiTokenAuditEntry`` **row (#2878).** It did
534
+ not, and the trail was inverted: routine self-service revocation through
535
+ ``MyApiTokenViewSet.destroy`` was logged, while the two *security-relevant*
536
+ revocations — password reset and off-boarding, the two that answer "was this
537
+ account contained, and when?" — wrote nothing, on a feature page that promises
538
+ "every mint and revoke is written to an append-only audit log." The rows are
539
+ ``bulk_create``d from the ids the ``update()`` matched, so the cost stays two
540
+ queries regardless of token count and the audit is inside the caller's
541
+ transaction: it commits with the revocation or rolls back with it, never apart.
542
+
543
+ Args:
544
+ user: The account whose personal access tokens should all be revoked.
545
+ actor: The user who caused the revocation, for the audit row. The account
546
+ owner for a password reset; the acting admin for an off-boarding; ``None``
547
+ for an operator running the management command, which has no Django user.
548
+ reason: Short machine-readable cause recorded in ``detail["reason"]``
549
+ (``password_reset``, ``offboarding``, ``operator_bulk_revoke``). It is what
550
+ makes the trail answerable — "revoked" alone cannot distinguish routine
551
+ rotation from incident response.
552
+ source_ip: Client IP when one is available (a request-driven path); ``None``
553
+ from a management command.
554
+
555
+ Returns:
556
+ The number of tokens newly revoked (already-revoked tokens are excluded).
557
+ """
558
+ # Local import: the projects app imports from access, so importing ApiToken at
559
+ # module top would create a circular dependency through the access → projects
560
+ # path (mirrors delete_program_cascade's lazy Project import).
561
+ from trueppm_api.apps.projects.models import (
562
+ ApiToken,
563
+ ApiTokenAuditAction,
564
+ ApiTokenAuditEntry,
565
+ )
566
+
567
+ # Materialize before the update: after it, `revoked_at__isnull=True` no longer
568
+ # selects the rows this call is responsible for, and re-reading without the filter
569
+ # would also pick up tokens revoked earlier by someone else and double-audit them.
570
+ #
571
+ # `select_for_update()` is what keeps the split read/write as idempotent as the
572
+ # single `update()` it replaced. Without the row lock, a password reset racing an
573
+ # admin off-boarding both select the same rows, both write, and the second write
574
+ # moves `revoked_at` *forward* — misdating containment on the one field an auditor
575
+ # reads — while producing a second REVOKED row with a contradicting reason and an
576
+ # inflated return count. Holding the lock makes `doomed` exactly the set this call
577
+ # revokes, so the audit rows below cannot describe someone else's revocation. It
578
+ # requires an open transaction, which every caller has (ATOMIC_REQUESTS for the two
579
+ # request paths, an explicit `atomic()` in the management command); a caller without
580
+ # one gets a loud TransactionManagementError rather than a silent race.
581
+ #
582
+ # Expired tokens are excluded deliberately. An expired token already fails
583
+ # authentication (expiry is folded into the authenticator's hash lookup), so
584
+ # revoking it changes nothing — but auditing it would pad the trail with rows
585
+ # claiming N live credentials were contained when the real number was lower. This
586
+ # matches `ApiToken.active_personal_tokens_for`, the canonical definition of
587
+ # "active", which the 10-token cap already uses.
588
+ now = timezone.now()
589
+ doomed = list(
590
+ ApiToken.objects.select_for_update()
591
+ .filter(
592
+ models.Q(expires_at__isnull=True) | models.Q(expires_at__gt=now),
593
+ owner=user,
594
+ is_deleted=False,
595
+ revoked_at__isnull=True,
596
+ )
597
+ .only("pk", "token_prefix", "name")
598
+ )
599
+ if not doomed:
600
+ return 0
601
+
602
+ revoked = ApiToken.objects.filter(
603
+ pk__in=[token.pk for token in doomed], revoked_at__isnull=True
604
+ ).update(revoked_at=now)
605
+ ApiTokenAuditEntry.objects.bulk_create(
606
+ [
607
+ ApiTokenAuditEntry(
608
+ owner=user,
609
+ token=token,
610
+ token_prefix=token.token_prefix,
611
+ actor=actor,
612
+ action=ApiTokenAuditAction.REVOKED.value,
613
+ source_ip=source_ip,
614
+ detail={"name": token.name, "reason": reason} if reason else {"name": token.name},
615
+ )
616
+ for token in doomed
617
+ ]
618
+ )
619
+ return revoked
620
+
621
+
622
+ def revoke_personal_durable_grants(user: Any = None, *, actor: Any = None) -> tuple[int, int]:
623
+ """Revoke the two non-token durable grants a PAT (or a live session) can mint (#2939).
624
+
625
+ ``IsNotTokenAuthenticated`` (#2878) now blocks *minting* a ``ShareLink`` or
626
+ rotating a ``BoardAutomation`` webhook secret through a bearer token, but a
627
+ grant minted **before** that guard existed, or minted through a live session by
628
+ someone who is now being contained, is exactly as durable as a personal access
629
+ token and sits outside every existing revocation lever
630
+ (``revoke_all_personal_access_tokens``, password reset, off-boarding). This
631
+ closes that gap for the two grants #2939 scoped: a project's public share
632
+ links and its git-automation webhook secret.
633
+
634
+ ``user=None`` means instance-wide — every active grant, not one person's —
635
+ which is what the ``revoke_api_tokens --all`` full-compromise lever needs;
636
+ a scoped ``user`` is the off-boarding / single-account case.
637
+
638
+ Revoking a share link is cheap for a team to recover from (an Admin mints a
639
+ new one); clearing a webhook secret degrades ``GitWebhookIngestView`` to its
640
+ existing "no secret set" 404 refusal (fail-closed by construction, not a new
641
+ code path) until an Admin rotates a fresh one. Both are lower-disruption than
642
+ the project-/program-scoped *API tokens* this module deliberately leaves alone
643
+ on off-boarding (see ``_revoke_offboarded_credentials``) — those are shared
644
+ integration credentials embedded in other people's scripts and CI, not a
645
+ single admin's config action.
646
+
647
+ Returns ``(share_links_revoked, automation_secrets_cleared)``.
648
+ """
649
+ # Local import: projects/integrations import from access, so importing these
650
+ # at module top would create a circular dependency (mirrors the ApiToken
651
+ # import above).
652
+ from trueppm_api.apps.integrations.models import BoardAutomation
653
+ from trueppm_api.apps.projects.models import ShareLink
654
+
655
+ now = timezone.now()
656
+
657
+ links = ShareLink.objects.filter(revoked_at__isnull=True)
658
+ if user is not None:
659
+ links = links.filter(created_by=user)
660
+ links_revoked = links.update(revoked_at=now, revoked_by=actor)
661
+
662
+ secrets_qs = BoardAutomation.objects.exclude(secret_ciphertext=b"")
663
+ if user is not None:
664
+ secrets_qs = secrets_qs.filter(configured_by=user)
665
+ secrets_cleared = secrets_qs.update(secret_ciphertext=b"", secret_set_at=None)
666
+
667
+ return links_revoked, secrets_cleared