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,588 @@
1
+ """RBAC models — ProjectMembership, ProgramMembership, and Role."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import uuid
6
+
7
+ from django.conf import settings
8
+ from django.db import models
9
+ from django.db.models.functions import Lower
10
+ from django.utils import timezone
11
+
12
+ from trueppm_api.apps.projects.models import VersionedModel
13
+
14
+ # The three program-scoped models below (ProgramMembership,
15
+ # ProgramUserDefinedMentionGroup, ExternalStakeholder) all point their `program`
16
+ # ForeignKey at the same target. Naming it once keeps the app-label reference in
17
+ # a single place. Django serializes the FK target to this resolved string, so
18
+ # using the constant produces no migration change.
19
+ _PROGRAM_MODEL = "projects.Program"
20
+
21
+
22
+ class Role(models.IntegerChoices):
23
+ """Project-scoped roles, ordered by privilege level (ADR-0072).
24
+
25
+ Ordinals are spaced in 100-unit bands so Enterprise can register custom roles
26
+ at intermediate values (e.g., a "Senior Scheduler" at 250) without forcing an
27
+ OSS renumber. The band-boundary contract:
28
+
29
+ - role >= Role.X (inequality / threshold) — "at least the X-band";
30
+ Enterprise custom roles at intermediate ordinals DO inherit this band's
31
+ capabilities.
32
+ - role == Role.X (singular-tier equality) — "specifically the OSS X tier";
33
+ custom roles do NOT silently absorb these matches. If Enterprise wants
34
+ override semantics, it goes through the slot-registration pattern
35
+ (ADR-0029), not OSS code changes.
36
+
37
+ Code name │ Ordinal │ Issue #11 label │ Reserved band for Enterprise
38
+ ───────────┼─────────┼──────────────────┼─────────────────────────────────
39
+ VIEWER │ 1 │ Viewer │ the floor; 0 is deliberately unused
40
+ │ 2–99 │ │ read-augmented roles (e.g. Auditor)
41
+ MEMBER │ 100 │ Team Member │ edit own assigned tasks
42
+ │ 101–199 │ │ contributor extensions
43
+ SCHEDULER │ 200 │ Resource Manager │ assign resources; no task edit
44
+ │ 201–299 │ │ resource-management extensions
45
+ ADMIN │ 300 │ Project Manager │ full task/dep edit; create baseline
46
+ │ 301–399 │ │ project-lead extensions
47
+ OWNER │ 400 │ Project Admin │ delete project; manage membership
48
+ │ 401+ │ (RESERVED) │ no role above Owner; OSS contract
49
+ ───────────┴─────────┴──────────────────┴─────────────────────────────────
50
+
51
+ VIEWER is 1, not 0 (#2489). The ordinal is client-visible — it ships in
52
+ membership payloads, invites, and MCP reads — and JavaScript treats 0 as
53
+ falsy, so a single ``role || DEFAULT`` on any consumer silently promotes a
54
+ Viewer to whatever the default is. Every role ordinal is now truthy, which
55
+ removes the failure mode rather than relying on every consumer to remember
56
+ ``??``. It costs one slot from the read-augmented band (2–99, still 98).
57
+
58
+ "No membership" is expressed as ``None`` (a distinct type), never as an
59
+ ordinal, so no sentinel value below VIEWER is needed or reserved.
60
+
61
+ OWNER is kept as the code name (not renamed to PROJECT_ADMIN) because it
62
+ carries the last-Owner guard invariant throughout the codebase. The human-
63
+ readable label is "Project Admin" for API consumers.
64
+
65
+ The labels above are **project-scoped**. A program names the same two top
66
+ ordinals differently — see :data:`PROGRAM_ROLE_LABELS` below, which is the
67
+ single source of that vocabulary; any new member added here must be added
68
+ there too or the mapping test fails.
69
+
70
+ NEVER compare against a raw integer literal (e.g. ``if role < 1``) — always
71
+ use the symbolic name (``if role < Role.MEMBER``) so the comparison stays
72
+ correct if ordinals change.
73
+ """
74
+
75
+ VIEWER = 1, "Viewer"
76
+ MEMBER = 100, "Team Member"
77
+ SCHEDULER = 200, "Resource Manager"
78
+ ADMIN = 300, "Project Manager"
79
+ OWNER = 400, "Project Admin"
80
+
81
+
82
+ # Program-context labels for the shared ``Role`` enum (#1794, #3503). The enum
83
+ # labels are project-scoped ("Project Admin", "Project Manager"); a program
84
+ # surface must read the role as it applies to the *program*, not to some
85
+ # project. Every role the enum defines is mapped so a future enum value can't
86
+ # silently fall back to a project label without a matching test failing. The
87
+ # project-side ``Role.label`` values are intentionally left unchanged.
88
+ #
89
+ # This lives beside ``Role`` rather than in a serializer module because two
90
+ # apps read it — ``access`` (membership rows) and ``projects`` (the program
91
+ # card) — and one rule rendered twice must have one definition (ADR-0133).
92
+ PROGRAM_ROLE_LABELS: dict[Role, str] = {
93
+ Role.VIEWER: "Viewer",
94
+ Role.MEMBER: "Team Member",
95
+ Role.SCHEDULER: "Resource Manager",
96
+ Role.ADMIN: "Program Manager",
97
+ Role.OWNER: "Program Admin",
98
+ }
99
+
100
+
101
+ def program_role_label(role: int | None) -> str | None:
102
+ """Program-context display label for a role ordinal.
103
+
104
+ Returns ``None`` for ``None`` (no membership) and for any ordinal the
105
+ ``Role`` enum does not define. That second branch is not defensive padding:
106
+ ADR-0072 reserves the 2-99, 101-199, 201-299 and 301-399 bands for
107
+ Enterprise custom roles, ``ProgramMembership.role`` is a plain
108
+ ``IntegerField`` whose ``choices`` PostgreSQL does not enforce, and a bare
109
+ ``Role(350)`` raises ``ValueError`` — which DRF does not convert, so it
110
+ 500s the WHOLE response rather than just this field (the failure mode
111
+ #3419 fixed on the project side). ``None`` is honest: the OSS edition
112
+ genuinely has no name for that ordinal, and every caller decides for itself
113
+ how to render the gap.
114
+ """
115
+ if role is None or role not in Role.values:
116
+ return None
117
+ # ``.get``, not ``[]``: the map is exhaustive over ``Role`` and
118
+ # ``test_every_role_has_a_program_label`` is what keeps it that way, but a
119
+ # ``KeyError`` raised here would 500 the whole response — reintroducing, for a
120
+ # new-enum-member input, exactly the failure shape the guard above removes.
121
+ return PROGRAM_ROLE_LABELS.get(Role(role))
122
+
123
+
124
+ class ProjectMembership(VersionedModel):
125
+ """Through table linking a user to a project with a specific role.
126
+
127
+ Deliberately kept as a standalone model (not a ManyToManyField through=)
128
+ so that it participates in the offline sync protocol via VersionedModel
129
+ and can be queried directly in permission checks without a join through Project.
130
+ """
131
+
132
+ project = models.ForeignKey(
133
+ "projects.Project",
134
+ on_delete=models.PROTECT,
135
+ related_name="memberships",
136
+ )
137
+ user = models.ForeignKey(
138
+ settings.AUTH_USER_MODEL,
139
+ on_delete=models.CASCADE,
140
+ related_name="memberships",
141
+ )
142
+ role = models.IntegerField(choices=Role.choices)
143
+ # Per-project access evidence (#590): the OSS surface needs a minimum-viable
144
+ # "who has access and since when" answer for compliance questionnaires.
145
+ # VersionedModel deliberately omits created_at/updated_at (sync uses
146
+ # server_version), so these are explicit columns rather than inherited.
147
+ # Scope of the evidence (#3410): joined_at is the *first* join. Re-adding a
148
+ # revoked member revives their original row and keeps this date, so the span
149
+ # it describes may contain revoked intervals — use reinstated_at below as the
150
+ # discriminator between "joined once, never left" and "was away and came
151
+ # back". Do not present joined_at alone as proof of uninterrupted access.
152
+ # joined_at uses default=timezone.now (not auto_now_add) so the AddField
153
+ # migration backfills existing rows non-interactively at migration time.
154
+ joined_at = models.DateTimeField(default=timezone.now, editable=False)
155
+ # NULL means the role has never changed since the member joined; it is
156
+ # stamped with timezone.now() only on an actual role change (the viewset
157
+ # partial_update and the ownership-transfer service). The UI shows the
158
+ # "role changed" line only when this is set.
159
+ role_changed_at = models.DateTimeField(null=True, blank=True, editable=False)
160
+ # Per-project reinstatement evidence (#3436). NULL means this membership has
161
+ # never been revoked and re-added — a fresh add leaves it null. Stamped with
162
+ # timezone.now() only in _revive_revoked_membership, each time a revoked row
163
+ # is revived (a second revive advances it, it does not accumulate history).
164
+ # This is the durable server-side fact #3410 shipped without: neither model
165
+ # carries HistoricalRecords, deleted_version is cleared on revive, and the
166
+ # access app writes no AuditEvent, so before this field there was no trace of
167
+ # a revocation surviving past TRUEPPM_BOARD_EVENT_RETENTION_HOURS (24h
168
+ # default). A machine caller reading joined_at alone cannot tell a
169
+ # never-lapsed membership from a revived one; reinstated_at is the
170
+ # discriminator.
171
+ reinstated_at = models.DateTimeField(null=True, blank=True, editable=False)
172
+ # Set when this membership was materialized by a workspace Group→project
173
+ # cascade (ADR-0087 §5) rather than a direct invite. A direct grant
174
+ # (source_group IS NULL) always wins: group reconciliation never alters or
175
+ # revokes a direct membership, and only removes rows it created itself.
176
+ source_group = models.ForeignKey(
177
+ "workspace.Group",
178
+ on_delete=models.SET_NULL,
179
+ null=True,
180
+ blank=True,
181
+ related_name="granted_memberships",
182
+ )
183
+
184
+ class Meta:
185
+ db_table = "access_project_membership"
186
+ constraints = [
187
+ models.UniqueConstraint(
188
+ fields=["project", "user"], name="uniq_project_membership_project_user"
189
+ ),
190
+ ]
191
+ indexes = [
192
+ # Sync delta pull: WHERE project_id = X AND server_version > since (#810).
193
+ models.Index(fields=["project", "server_version"], name="pm_proj_serverver_idx"),
194
+ models.Index(fields=["project", "sync_seq"], name="pm_proj_syncseq_idx"),
195
+ ]
196
+
197
+ @classmethod
198
+ def live(cls) -> models.QuerySet[ProjectMembership]:
199
+ """Membership rows that have not been revoked — the soft-delete floor, in one place.
200
+
201
+ It floors the *membership* only. A row on a soft-deleted **project** passes this
202
+ and confers nothing, so a caller that does not already scope to a live project
203
+ needs ``project__is_deleted=False`` as well (as
204
+ ``notifications.digests.build_resource_overallocation_digest`` does). All current
205
+ callers supply their own project scope.
206
+
207
+ The uniqueness constraint above is **unconditional**: revoking access soft-deletes
208
+ the row rather than removing it, and re-adding the member revives that same row
209
+ (#3410). So a ``(project, user)`` lookup that omits ``is_deleted`` resolves a
210
+ *revoked* member to their old role and reads as if they were still present — the
211
+ defect class fixed on the write gates in #3386 and on the read paths in #3411.
212
+
213
+ **Scope, stated narrowly on purpose.** This is a convenience for a *direct*
214
+ ``ProjectMembership`` lookup; it is not, and does not claim to be, the only place
215
+ the predicate lives. Being a classmethod it cannot compose through a related
216
+ manager or a join, so the ~30 ``memberships__is_deleted=False`` join filters and
217
+ the ``obj.memberships.filter(is_deleted=False)`` reads elsewhere necessarily
218
+ restate it and are correct as they stand. Making it a queryset method on a custom
219
+ manager would let ``project.memberships.live()`` and ``Prefetch`` compose too;
220
+ that is the natural next step and is deliberately not taken in this fix.
221
+
222
+ Since #3458 a new direct read that omits the floor **is** caught:
223
+ ``scripts/check-membership-live-floor.py`` (``make pre-push`` and the
224
+ ``api:membership-live-floor`` CI job) rejects an unfloored read of either
225
+ membership manager outside a short allowlist. It does not require the read to go
226
+ through *this* method — an inline ``is_deleted=False`` satisfies it — so this
227
+ stays a shared definition to prefer rather than an invariant. Note what the gate
228
+ cannot see: a defect *inside* an allowlisted module, and a join filter, which is
229
+ why the paragraph above still matters.
230
+
231
+ Some direct reads must see revoked rows and stay on ``objects`` by design: the
232
+ ``pre_save`` receiver in :mod:`~trueppm_api.apps.access.signals`, which reads the
233
+ prior row precisely to detect the revocation transition; the add-member path in
234
+ :mod:`~trueppm_api.apps.access.views`, which has to find a revoked row to revive
235
+ it against the unconditional constraint, and to narrow its ``IntegrityError``
236
+ against that same constraint; the sync delta, which ships tombstones by protocol
237
+ so a client can learn the membership went away; and the workspace group-cascade
238
+ reconciliation. Writes (``create``, ``get_or_create``, seeds, data migrations)
239
+ are not reads and are equally out of scope.
240
+ """
241
+ return cls.objects.filter(is_deleted=False)
242
+
243
+ def __str__(self) -> str:
244
+ return f"{self.user} — {self.project} ({Role(self.role).label})"
245
+
246
+
247
+ class ProgramMembership(VersionedModel):
248
+ """Through table linking a user to a program with a specific role (ADR-0070).
249
+
250
+ Mirrors :class:`ProjectMembership` exactly — standalone model (not M2M
251
+ ``through=``) so it participates in the offline sync protocol and supports
252
+ direct permission checks without joining through ``Program``.
253
+
254
+ Program membership controls access to program-level views (backlog, projects
255
+ list, members). It does **not** automatically grant or modify project-level
256
+ access — a user must be invited to each project separately. This is the
257
+ deliberate "explicit grants only" boundary called out in ADR-0070 §RBAC.
258
+
259
+ Uses the same :class:`Role` enum as :class:`ProjectMembership`; the role
260
+ ordinals share semantics: VIEWER reads, MEMBER edits, SCHEDULER assigns,
261
+ ADMIN manages member/projects, OWNER deletes program.
262
+ """
263
+
264
+ program = models.ForeignKey(
265
+ _PROGRAM_MODEL,
266
+ on_delete=models.PROTECT,
267
+ related_name="memberships",
268
+ )
269
+ user = models.ForeignKey(
270
+ settings.AUTH_USER_MODEL,
271
+ on_delete=models.CASCADE,
272
+ related_name="program_memberships",
273
+ )
274
+ role = models.IntegerField(choices=Role.choices)
275
+ # Per-program access evidence (#878): mirrors ProjectMembership exactly so
276
+ # ADR-0070's "mirrors ProjectMembership" claim holds and the program members
277
+ # view can answer "who has access and since when". VersionedModel omits
278
+ # created_at/updated_at (sync uses server_version), so these are explicit.
279
+ # The same scope caveat applies as on the project side (#3410): joined_at is
280
+ # the first join and survives a revoke-then-re-add, so it is not proof of
281
+ # uninterrupted access — reinstated_at below is the discriminator.
282
+ # joined_at uses default=timezone.now (not auto_now_add) so the AddField
283
+ # migration backfills existing rows non-interactively at migration time.
284
+ joined_at = models.DateTimeField(default=timezone.now, editable=False)
285
+ # NULL means the role has never changed since the member joined; it is
286
+ # stamped with timezone.now() only on an actual role change (the viewset
287
+ # partial_update and transfer_program_sponsorship).
288
+ role_changed_at = models.DateTimeField(null=True, blank=True, editable=False)
289
+ # Per-program reinstatement evidence (#3436) — mirrors ProjectMembership's
290
+ # field exactly. NULL means never revoked and re-added; stamped with
291
+ # timezone.now() each time _revive_revoked_membership revives this row.
292
+ reinstated_at = models.DateTimeField(null=True, blank=True, editable=False)
293
+ # Freeform functional-role label (#565), e.g. "Product Owner" / "Tech Lead" /
294
+ # "Scrum Master" — distinct from, and orthogonal to, the access ``role`` enum
295
+ # above. It is purely descriptive: not enforced anywhere, it anchors the
296
+ # PO-vs-PM sovereignty signals #501 will surface (a PM-labeled member dragging
297
+ # a story into an active sprint). Empty string is the single "unset" state
298
+ # (no nullable string, per project DJ001 convention); the serializer strips a
299
+ # whitespace-only submission back to "".
300
+ role_title = models.CharField(max_length=50, blank=True, default="")
301
+
302
+ class Meta:
303
+ db_table = "access_program_membership"
304
+ constraints = [
305
+ models.UniqueConstraint(
306
+ fields=["program", "user"], name="uniq_program_membership_program_user"
307
+ ),
308
+ ]
309
+ indexes = [
310
+ # Sync delta pull: WHERE program_id = X AND server_version > since
311
+ # (ADR-0070 §Sync). Mirrors ProjectMembership's pm_proj_serverver_idx
312
+ # so the standard offline-sync query pattern is index-backed on the
313
+ # program side too — introduced with the #561 user-scoped program
314
+ # sync endpoint.
315
+ models.Index(fields=["program", "server_version"], name="progm_serverver_idx"),
316
+ models.Index(fields=["program", "sync_seq"], name="progm_syncseq_idx"),
317
+ ]
318
+
319
+ @classmethod
320
+ def live(cls) -> models.QuerySet[ProgramMembership]:
321
+ """Program membership rows that have not been revoked — the soft-delete floor.
322
+
323
+ The program-axis twin of :meth:`ProjectMembership.live`, added in #3458. The
324
+ class docstring above says this model "mirrors ``ProjectMembership`` exactly";
325
+ until this method existed that claim was false in the one place it mattered
326
+ most, and every program-axis read restated its own ``is_deleted=False`` or
327
+ forgot to. Two of the five known instances of the class were program-axis
328
+ (#3456, the weekly program-health digest; #3457, the seed exporter), and both
329
+ were found by a gate sweep on an unrelated MR rather than by anything that runs
330
+ on a branch.
331
+
332
+ It floors the *membership* only. A row on a soft-deleted **program** passes this
333
+ and confers nothing, so a caller that does not already scope to a live program
334
+ needs ``program__is_deleted=False`` as well (as
335
+ ``notifications.digests.build_program_health_digest`` does). Same division as on
336
+ the project side.
337
+
338
+ The mechanism is the same too: ``uniq_program_membership_program_user`` is
339
+ **unconditional**, so revoking access soft-deletes the row rather than removing
340
+ it and re-adding the member revives that same row (#3410). A ``(program, user)``
341
+ lookup that omits ``is_deleted`` therefore resolves a *revoked* member to their
342
+ old role and reads as if they were still present.
343
+
344
+ **Scope, stated as narrowly as the project-side twin.** This is a convenience
345
+ for a *direct* ``ProgramMembership`` lookup. Being a classmethod it cannot
346
+ compose through a related manager or a join, so ``memberships__is_deleted=False``
347
+ join filters elsewhere necessarily restate the predicate and are correct as they
348
+ stand. Migrating every inline filter to it was deliberately **not** done in
349
+ #3458: the project axis carries 95 correctly-floored reads and only 4 of them go
350
+ through ``ProjectMembership.live()``, so "mirrors exactly" means the helper
351
+ exists and is preferred, not that every call site was rewritten.
352
+
353
+ What is new in #3458 and is *not* on the project side is that the floor is now
354
+ checked rather than merely preferred: ``scripts/check-membership-live-floor.py``
355
+ (``make pre-push`` and the ``api:membership-live-floor`` CI job) fails any
356
+ unfloored read of either membership manager outside a short allowlist of modules
357
+ that must see revoked rows. The reads that must stay unfloored are named there,
358
+ with a reason each, rather than restated here.
359
+ """
360
+ return cls.objects.filter(is_deleted=False)
361
+
362
+ def __str__(self) -> str:
363
+ # Program vocabulary, not ``Role.label`` — this row is a membership of a
364
+ # *program*, and the admin/log line that renders it should not call the
365
+ # holder a "Project Admin" (#3503). Falls back to the raw ordinal for an
366
+ # Enterprise custom-band role the OSS enum cannot name (ADR-0072).
367
+ label = program_role_label(self.role) or f"Role {self.role}"
368
+ return f"{self.user} — {self.program} ({label})"
369
+
370
+
371
+ class UserDefinedMentionGroup(VersionedModel):
372
+ """Admin-curated, project-scoped ``@mention`` group (ADR-0212, #515).
373
+
374
+ Complements the RBAC-derived auto-groups (``@admins``, ``@scrum-team``, …)
375
+ resolved in ``access/groups.py`` with workflow-shaped groupings a PM defines
376
+ by hand — e.g. ``@subcontractors``, ``@inspectors``, ``@team-private`` — that
377
+ do not map onto a role band.
378
+
379
+ ``name`` is the mention key *without* the leading ``@`` and is bounded to 32
380
+ chars so it fits ``notifications.Mention.mentioned_group_key``. It is
381
+ case-insensitively unique per project and may not collide with an auto-group
382
+ key (validated in the serializer against ``ALL_AUTO_GROUP_KEYS`` — project-
383
+ and program-scoped keys alike).
384
+
385
+ Mention resolution snapshots the member list at write time — the same
386
+ semantics as the auto-group resolver — so members added after a mention are
387
+ not retroactively notified and departed members are not re-pinged.
388
+ """
389
+
390
+ project = models.ForeignKey(
391
+ "projects.Project",
392
+ on_delete=models.PROTECT,
393
+ related_name="mention_groups",
394
+ )
395
+ # The mention key without the leading @ (e.g. "subcontractors").
396
+ name = models.CharField(max_length=32)
397
+ # Optional one-line purpose shown in the manager UI. DJ001: "" not NULL.
398
+ description = models.CharField(max_length=140, blank=True, default="")
399
+ # Per-group email default (ADR-0212 §5). Default OFF preserves the un-opted-
400
+ # email hard-NO (ADR-0075 V2); the group manager flips it on.
401
+ email_default_on = models.BooleanField(default=False)
402
+ created_by = models.ForeignKey(
403
+ settings.AUTH_USER_MODEL,
404
+ on_delete=models.SET_NULL,
405
+ null=True,
406
+ blank=True,
407
+ related_name="created_mention_groups",
408
+ )
409
+ # Curated members. Plain M2M (no sync stream): resolution is server-side at
410
+ # comment-write time, so offline clients never resolve groups.
411
+ members = models.ManyToManyField(
412
+ settings.AUTH_USER_MODEL,
413
+ related_name="mention_groups",
414
+ blank=True,
415
+ )
416
+ # Per-user override / per-group mute (ADR-0212 §5). A member who mutes a group
417
+ # receives neither in-app nor email for that group's mentions; a direct
418
+ # @user mention still reaches them (mute is group-scoped).
419
+ muted_by = models.ManyToManyField(
420
+ settings.AUTH_USER_MODEL,
421
+ related_name="muted_mention_groups",
422
+ blank=True,
423
+ )
424
+
425
+ class Meta:
426
+ db_table = "access_user_defined_mention_group"
427
+ constraints = [
428
+ # Case-insensitive project-unique name, enforced only across LIVE rows
429
+ # (condition mirrors the serializer's is_deleted=False uniqueness check).
430
+ # A soft-deleted group therefore frees its name for reuse rather than
431
+ # reserving it — without the condition, re-creating a name after delete
432
+ # would pass serializer validation but hit the DB constraint as a 500.
433
+ models.UniqueConstraint(
434
+ "project",
435
+ Lower("name"),
436
+ condition=models.Q(is_deleted=False),
437
+ name="uniq_mention_group_project_name_ci",
438
+ ),
439
+ ]
440
+ indexes = [
441
+ # Sync delta pull: WHERE project_id = X AND server_version > since.
442
+ models.Index(fields=["project", "server_version"], name="udmg_proj_serverver_idx"),
443
+ models.Index(fields=["project", "sync_seq"], name="udmg_proj_syncseq_idx"),
444
+ ]
445
+
446
+ def __str__(self) -> str:
447
+ return f"@{self.name} ({self.project_id})"
448
+
449
+
450
+ class ProgramUserDefinedMentionGroup(VersionedModel):
451
+ """Owner-curated, program-scoped ``@mention`` group (ADR-0248, #516).
452
+
453
+ The program-scoped parallel of :class:`UserDefinedMentionGroup`: a program
454
+ manager hand-curates a collection of members drawn from across the program's
455
+ projects — e.g. ``@program-tech-leads``, ``@program-vendor-x`` — that does not
456
+ map onto a role band (the role-banded cases are the ADR-0240 auto-groups).
457
+
458
+ Mentioned as a plain ``@name`` (no ``program-`` prefix — that prefix is
459
+ reserved for the ADR-0240 auto-groups) from a comment on any task in a project
460
+ of the program. Resolution precedence is member → project group → program
461
+ group, so a program group is the widest, least-specific match (ADR-0248 §4).
462
+
463
+ ``members`` are selectable across *all* projects in the program (the union of
464
+ ``ProjectMembership``, matching ADR-0240's program-membership semantics), and
465
+ resolution snapshots the member list at write time — members added after a
466
+ mention are not retroactively notified, departed members are not re-pinged.
467
+ """
468
+
469
+ program = models.ForeignKey(
470
+ _PROGRAM_MODEL,
471
+ on_delete=models.PROTECT,
472
+ related_name="mention_groups",
473
+ )
474
+ # The mention key without the leading @ (e.g. "tech-leads").
475
+ name = models.CharField(max_length=32)
476
+ # Optional one-line purpose shown in the manager UI. DJ001: "" not NULL.
477
+ description = models.CharField(max_length=140, blank=True, default="")
478
+ # Per-group email default (ADR-0248 §1, mirrors ADR-0212 §5). Default OFF
479
+ # preserves the un-opted-email hard-NO (ADR-0075 V2); the manager flips it on.
480
+ email_default_on = models.BooleanField(default=False)
481
+ created_by = models.ForeignKey(
482
+ settings.AUTH_USER_MODEL,
483
+ on_delete=models.SET_NULL,
484
+ null=True,
485
+ blank=True,
486
+ related_name="created_program_mention_groups",
487
+ )
488
+ # Curated members. Plain M2M (no sync stream): resolution is server-side at
489
+ # comment-write time, so offline clients never resolve groups.
490
+ members = models.ManyToManyField(
491
+ settings.AUTH_USER_MODEL,
492
+ related_name="program_mention_groups",
493
+ blank=True,
494
+ )
495
+ # Per-user override / per-group mute (ADR-0248 §1). A member who mutes a group
496
+ # receives neither in-app nor email for that group's mentions; a direct
497
+ # @user mention still reaches them (mute is group-scoped).
498
+ muted_by = models.ManyToManyField(
499
+ settings.AUTH_USER_MODEL,
500
+ related_name="muted_program_mention_groups",
501
+ blank=True,
502
+ )
503
+
504
+ class Meta:
505
+ db_table = "access_program_user_defined_mention_group"
506
+ constraints = [
507
+ # Case-insensitive program-unique name across LIVE rows only — mirrors
508
+ # the ADR-0212 project constraint so a soft-deleted group frees its name.
509
+ models.UniqueConstraint(
510
+ "program",
511
+ Lower("name"),
512
+ condition=models.Q(is_deleted=False),
513
+ name="uniq_program_mention_group_program_name_ci",
514
+ ),
515
+ ]
516
+ indexes = [
517
+ # Sync delta pull: WHERE program_id = X AND server_version > since.
518
+ models.Index(fields=["program", "server_version"], name="pudmg_prog_serverver_idx"),
519
+ models.Index(fields=["program", "sync_seq"], name="pudmg_prog_syncseq_idx"),
520
+ ]
521
+
522
+ def __str__(self) -> str:
523
+ return f"@{self.name} ({self.program_id})"
524
+
525
+
526
+ class ExternalStakeholder(models.Model):
527
+ """A non-account external stakeholder registered against a program (#1658, ADR-0264).
528
+
529
+ A snapshot-resolved registry of people who are *not* TruePPM users — client
530
+ sponsors, vendor contacts, external reviewers — whom a program manager wants
531
+ reachable through the ``@program-stakeholders`` mention fan-out alongside the
532
+ program's Viewer-role members. The resolver
533
+ (:func:`trueppm_api.apps.access.groups.resolve_external_stakeholders`) reads
534
+ this table at comment-write time; later membership edits are not retroactive,
535
+ matching the auto-group snapshot semantics.
536
+
537
+ Registry/config, **not** sync state: like ``ShareLink`` and ``ApiToken`` this is
538
+ a plain :class:`django.db.models.Model` (no ``server_version``) and is never part
539
+ of the mobile offline delta — external stakeholders are a server-side program
540
+ setting, not a board object a client edits offline.
541
+
542
+ Email delivery to these addresses is **deferred to #1675**: #1658 ships the
543
+ registry model + resolver so the recipient count can be surfaced, but no
544
+ outbound mail is sent to an external stakeholder yet.
545
+ """
546
+
547
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
548
+ program = models.ForeignKey(
549
+ _PROGRAM_MODEL,
550
+ on_delete=models.CASCADE,
551
+ related_name="external_stakeholders",
552
+ )
553
+ name = models.CharField(max_length=200)
554
+ email = models.EmailField()
555
+ note = models.TextField(blank=True, default="")
556
+ created_by = models.ForeignKey(
557
+ settings.AUTH_USER_MODEL,
558
+ on_delete=models.SET_NULL,
559
+ null=True,
560
+ related_name="+",
561
+ )
562
+ created_at = models.DateTimeField(auto_now_add=True)
563
+ updated_at = models.DateTimeField(auto_now=True)
564
+ # Soft-delete so a removed stakeholder's email frees up for re-add and the
565
+ # case-insensitive unique constraint only binds live rows (see Meta).
566
+ is_deleted = models.BooleanField(default=False)
567
+
568
+ class Meta:
569
+ db_table = "access_external_stakeholder"
570
+ constraints = [
571
+ # Case-insensitive (program, email) uniqueness across LIVE rows only —
572
+ # mirrors the mention-group constraint pattern so a soft-deleted row
573
+ # frees its email for re-add rather than reserving it (a re-add would
574
+ # otherwise pass the serializer check but hit the DB as a 500).
575
+ models.UniqueConstraint(
576
+ "program",
577
+ Lower("email"),
578
+ condition=models.Q(is_deleted=False),
579
+ name="uniq_external_stakeholder_program_email_ci",
580
+ ),
581
+ ]
582
+ indexes = [
583
+ # List query: WHERE program_id = X AND is_deleted = False.
584
+ models.Index(fields=["program", "is_deleted"], name="extstake_prog_deleted_idx"),
585
+ ]
586
+
587
+ def __str__(self) -> str:
588
+ return f"{self.name} <{self.email}> ({self.program_id})"