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,26 @@
1
+ # Generated by Django 5.2.16 on 2026-08-23 13:09
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("integrations", "0012_boardautomation_last_delivery_at_and_more"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.AddField(
13
+ model_name="externalsyncrequest",
14
+ name="attempt_count",
15
+ field=models.PositiveIntegerField(default=0),
16
+ ),
17
+ migrations.AddField(
18
+ model_name="externalsyncrequest",
19
+ name="next_attempt_at",
20
+ field=models.DateTimeField(
21
+ blank=True,
22
+ help_text="Earliest time the drain may re-dispatch this row (rate-limit backoff).",
23
+ null=True,
24
+ ),
25
+ ),
26
+ ]
File without changes
@@ -0,0 +1,622 @@
1
+ """Data model for external integrations (ADR-0049 §3).
2
+
3
+ This module owns two tables:
4
+
5
+ - ``IntegrationCredential`` — per-user PAT storage that the User → Settings →
6
+ Connected Accounts page (#587) lists and ``TaskLink`` refresh consumes.
7
+ - ``TaskLink`` (#637) — a GitLab/GitHub/generic URL attached to a task, with a
8
+ cached status (open / draft / merged / closed / unknown) refreshed on demand.
9
+
10
+ Sync note: ``IntegrationCredential`` does **not** inherit ``VersionedModel`` —
11
+ PATs are intentionally never synced to (or serialized back to) any client,
12
+ including the owner (ADR-0049 §3). ``TaskLink`` **does** inherit
13
+ ``VersionedModel`` for offline-sync parity with ``Task``, so link adds/removes
14
+ and status changes reach the mobile client through the project sync delta.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import uuid
20
+ from typing import Any, ClassVar
21
+
22
+ from django.conf import settings
23
+ from django.contrib.postgres.fields import ArrayField
24
+ from django.db import models
25
+ from django.utils import timezone
26
+
27
+ from trueppm_api.apps.projects.models import VersionedModel
28
+
29
+ from .encryption import encrypt_secret
30
+ from .registry import (
31
+ LINK_STATUS_CHOICES,
32
+ LINK_STATUS_UNKNOWN,
33
+ PREVIEW_TYPE_CHOICES,
34
+ TASK_LINK_PROVIDERS,
35
+ )
36
+
37
+
38
+ class IntegrationCredential(models.Model):
39
+ """A user's encrypted PAT for one integration provider (ADR-0049 §3).
40
+
41
+ The ``provider`` field is a free-form ``CharField`` validated against
42
+ ``TASK_LINK_PROVIDERS.keys()`` at write time so Enterprise can register
43
+ additional providers (``jira``, ``servicenow``, ``bitbucket``,
44
+ ``azure_devops``) without an OSS migration. ``secret_ciphertext`` is
45
+ Fernet-encrypted with ``settings.INTEGRATION_ENCRYPTION_KEY`` and is
46
+ **never** returned to any client — the serializer exposes only an
47
+ ``exists`` flag, the ``base_url``, and timestamps.
48
+
49
+ The ``(user, provider)`` pair is unique: each user has at most one
50
+ credential per provider. Connecting a second time rotates the secret
51
+ via update-or-create rather than appending a row, which keeps the
52
+ credentials list a flat one-per-provider read on the page.
53
+ """
54
+
55
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
56
+ user = models.ForeignKey(
57
+ settings.AUTH_USER_MODEL,
58
+ on_delete=models.CASCADE,
59
+ related_name="integration_credentials",
60
+ )
61
+ provider = models.CharField(max_length=32)
62
+ # Encrypted PAT — Fernet-encrypted bytes blob, never returned to clients.
63
+ secret_ciphertext = models.BinaryField()
64
+ # Self-hosted GitLab / GitHub Enterprise base URL. Empty string for
65
+ # gitlab.com / github.com so we can distinguish "not provided" from
66
+ # "explicitly cleared" without making the column NULL-able.
67
+ base_url = models.CharField(max_length=512, blank=True, default="")
68
+ created_at = models.DateTimeField(auto_now_add=True)
69
+ updated_at = models.DateTimeField(auto_now=True)
70
+ # Stamped by the git-aware tasks refresh endpoint when the credential
71
+ # was last used to fetch link metadata (#637). Null until then.
72
+ last_used_at = models.DateTimeField(null=True, blank=True)
73
+ # Optional expiration the user can record so the UI can warn before a
74
+ # PAT lapses. Not enforced server-side — PAT validity is enforced by
75
+ # the upstream provider — this is purely a UX hint.
76
+ expires_at = models.DateTimeField(null=True, blank=True)
77
+ # Per-connection settings for a user-scoped external task source (ADR-0097 §2).
78
+ # For an EXTERNAL_TASK_SOURCES connection (e.g. ``jira``) this holds
79
+ # ``{"account_email", "jql", "project_keys"}``; for a plain git-PAT
80
+ # TASK_LINK_PROVIDERS credential it stays ``{}``. Extending the existing
81
+ # credential row (rather than a new model) keeps one row per (user, provider)
82
+ # and reuses the Fernet-encrypted ``secret_ciphertext`` store — the ADR-0097
83
+ # §2 "reuse IntegrationCredential" decision. Never carries the secret itself.
84
+ config = models.JSONField(default=dict, blank=True)
85
+
86
+ class Meta:
87
+ constraints = [
88
+ # The unique constraint creates an implicit btree on
89
+ # ``(user, provider)`` in that column order — Postgres uses it
90
+ # for the per-user list lookups too. An explicit ``models.Index``
91
+ # would be a redundant copy that doubles write cost.
92
+ models.UniqueConstraint(
93
+ fields=("user", "provider"),
94
+ name="integrations_credential_unique_per_user_provider",
95
+ )
96
+ ]
97
+ ordering = ("provider",)
98
+ verbose_name = "Integration credential"
99
+ verbose_name_plural = "Integration credentials"
100
+
101
+ def __str__(self) -> str: # pragma: no cover — debugging aid only
102
+ return f"{self.provider} for user {self.user_id}"
103
+
104
+ @classmethod
105
+ def upsert(
106
+ cls,
107
+ *,
108
+ user: object,
109
+ provider: str,
110
+ secret: str,
111
+ base_url: str = "",
112
+ expires_at: object | None = None,
113
+ ) -> IntegrationCredential:
114
+ """Encrypt and store a PAT, replacing any existing row for the pair.
115
+
116
+ Connect and rotate are the same operation — both produce one row
117
+ per ``(user, provider)`` — so callers don't need to branch on
118
+ whether a row already exists. The plaintext ``secret`` is encrypted
119
+ before the row touches the database.
120
+
121
+ Raises:
122
+ ValueError: If ``provider`` is not registered in
123
+ ``TASK_LINK_PROVIDERS``. Validation happens here rather
124
+ than in the serializer so the model is the single source
125
+ of truth for the provider-validation rule.
126
+ """
127
+ if TASK_LINK_PROVIDERS.get(provider) is None:
128
+ raise ValueError(
129
+ f"Unknown provider {provider!r}. Known: {', '.join(TASK_LINK_PROVIDERS.keys())}"
130
+ )
131
+ ciphertext = encrypt_secret(secret)
132
+ obj, _ = cls.objects.update_or_create(
133
+ user=user,
134
+ provider=provider,
135
+ defaults={
136
+ "secret_ciphertext": ciphertext,
137
+ "base_url": base_url or "",
138
+ "expires_at": expires_at,
139
+ },
140
+ )
141
+ return obj
142
+
143
+
144
+ class TaskLink(VersionedModel):
145
+ """A GitLab/GitHub/generic URL attached to a task (ADR-0049 §3, #637).
146
+
147
+ Inherits ``VersionedModel`` (UUID PK, ``server_version``, ``is_deleted``,
148
+ ``deleted_version``) so link adds/removes and status changes flow to the
149
+ mobile client through the project sync delta, exactly like ``Task`` and
150
+ ``Dependency``.
151
+
152
+ ``provider`` is a free-form ``CharField`` validated against
153
+ ``TASK_LINK_PROVIDERS.keys()`` at write time (Enterprise can register
154
+ ``jira``/``bitbucket``/… without an OSS migration). ``status`` is a cached
155
+ classification refreshed on demand via the provider's API — there is **no**
156
+ background polling; it goes stale until the user (or mobile) hits refresh,
157
+ which is why ``fetched_at`` is surfaced so the UI can show "as of …".
158
+ """
159
+
160
+ # Explicit default manager. The django-stubs plugin does not inject the
161
+ # implicit ``objects`` manager for this model (a cross-app abstract-base
162
+ # quirk: ``VersionedModel`` lives in ``projects``), so we declare it so
163
+ # ``TaskLink.objects`` type-checks. Runtime behavior is identical to the
164
+ # implicit default manager.
165
+ objects: ClassVar[models.Manager[TaskLink]] = models.Manager()
166
+
167
+ task = models.ForeignKey(
168
+ "projects.Task",
169
+ on_delete=models.CASCADE,
170
+ related_name="links",
171
+ )
172
+ url = models.URLField(max_length=2048)
173
+ # Validated against TASK_LINK_PROVIDERS at write time (see clean()); not a
174
+ # DB-level choices constraint so Enterprise providers need no OSS migration.
175
+ provider = models.CharField(max_length=32)
176
+ # Human title the provider reported (PR/MR/issue title). Blank until the
177
+ # first successful refresh; the UI falls back to ``custom_title`` then the URL.
178
+ title = models.CharField(max_length=512, blank=True, default="")
179
+ # User-supplied display name (#970). Distinct from the provider-fetched
180
+ # ``title`` so a refresh updates ``title`` only and never clobbers what the
181
+ # user typed — and so a *generic* link (no provider title) can still carry a
182
+ # human name. Display precedence is ``custom_title or title or url``.
183
+ custom_title = models.CharField(max_length=512, blank=True, default="")
184
+ # Free-text categorization tags (#970), e.g. ["spec", "design"]. OSS-simple:
185
+ # no shared per-project taxonomy and no colors — the serializer trims,
186
+ # de-dupes, and caps the list. A plain text-array column (not a join table)
187
+ # because labels are read with the link and never queried independently.
188
+ labels = ArrayField(
189
+ models.CharField(max_length=40),
190
+ blank=True,
191
+ default=list,
192
+ )
193
+ # Cached status — one of LINK_STATUS_VALUES. Starts "unknown" on create
194
+ # (nothing fetched yet) and is updated by the refresh endpoint.
195
+ status = models.CharField(
196
+ max_length=16,
197
+ choices=LINK_STATUS_CHOICES,
198
+ default=LINK_STATUS_UNKNOWN,
199
+ )
200
+ # When the cached status/title was last fetched from the provider. Null
201
+ # until the first refresh — drives the "as of …" / "never refreshed" hint.
202
+ fetched_at = models.DateTimeField(null=True, blank=True)
203
+ # --- Cloud-file preview cache (#571, ADR-0163) ----------------------------
204
+ # Populated by a file provider's OpenGraph unfurl on refresh; empty for git
205
+ # and generic links. Stored here (on the synced row) rather than a side cache
206
+ # so the preview card renders offline on the mobile client from the sync delta.
207
+ # OpenGraph/Twitter description of a previewed file.
208
+ description = models.CharField(max_length=1024, blank=True, default="")
209
+ # Safe https preview-image URL (server drops non-https / blocked candidates).
210
+ thumbnail_url = models.URLField(max_length=2048, blank=True, default="")
211
+ # File class for the card's glyph/chip — one of PREVIEW_TYPE_VALUES, or "" for
212
+ # a link with no file preview (git, generic, not-yet-refreshed file).
213
+ preview_type = models.CharField(
214
+ max_length=16,
215
+ choices=PREVIEW_TYPE_CHOICES,
216
+ blank=True,
217
+ default="",
218
+ )
219
+ # Manual ordering within a task's link list (lower sorts first).
220
+ display_order = models.PositiveIntegerField(default=0)
221
+ # Creation timestamp (#971, ADR-0215). VersionedModel deliberately carries no
222
+ # created/updated timestamps (sync uses ``server_version``), but the unified
223
+ # Assets feed merges links with ``TaskAttachment`` on a shared ``(created_at,
224
+ # id)`` keyset — links need a real creation time to place chronologically.
225
+ # ``default=timezone.now`` (not ``auto_now_add``) so the additive migration
226
+ # backfills existing rows without an interactive one-off default; the value is
227
+ # write-once in practice (nothing mutates it) and never exposed for client
228
+ # write. Not added to ``TaskLinkSerializer`` — the sync/mobile shape is
229
+ # unchanged.
230
+ created_at = models.DateTimeField(default=timezone.now, editable=False)
231
+
232
+ class Meta:
233
+ ordering = ("display_order", "id")
234
+ indexes = [
235
+ # The link list is always read per-task; index the FK + ordering so
236
+ # the section query is a single index range scan.
237
+ models.Index(fields=("task", "display_order"), name="integrations_link_task_order"),
238
+ # Sync delta pull joins via task then filters server_version (#810).
239
+ models.Index(fields=("task", "server_version"), name="tasklink_serverver_idx"),
240
+ models.Index(fields=["task", "sync_seq"], name="tasklink_syncseq_idx"),
241
+ # Assets feed (#971, ADR-0215): each source is scanned newest-first with
242
+ # a small LIMIT before the Python keyset merge — index the sort key so
243
+ # the per-source page is an index range scan, not a filesort.
244
+ models.Index(fields=("-created_at", "-id"), name="tasklink_created_idx"),
245
+ ]
246
+ verbose_name = "Task link"
247
+ verbose_name_plural = "Task links"
248
+
249
+ def __str__(self) -> str: # pragma: no cover — debugging aid only
250
+ return f"{self.provider} link on task {self.task_id}"
251
+
252
+ @property
253
+ def project_id(self) -> Any:
254
+ """Surface the parent project's id for the RBAC resolver.
255
+
256
+ ``access.permissions._get_project_id_from_obj`` traverses ``obj.project_id``
257
+ to gate project-scoped permissions. A task-nested model has no direct
258
+ project FK, so we expose the parent task's project id here — the same
259
+ pattern ``TaskAttachment`` uses, so ``IsProjectMember``/``IsProjectMemberWrite``
260
+ resolve the project for object-level checks (create/destroy/refresh).
261
+ """
262
+ return self.task.project_id
263
+
264
+ def clean(self) -> None:
265
+ """Validate ``provider`` against the live registry.
266
+
267
+ Mirrors ``IntegrationCredential.upsert``'s rule so the model is the
268
+ single source of truth for provider validation — the serializer calls
269
+ ``full_clean`` so an unknown key is a 400, not a 500 at read time.
270
+ """
271
+ super().clean()
272
+ from django.core.exceptions import ValidationError
273
+
274
+ if TASK_LINK_PROVIDERS.get(self.provider) is None:
275
+ raise ValidationError({"provider": f"Unknown provider {self.provider!r}."})
276
+
277
+
278
+ class BoardAutomation(models.Model):
279
+ """Per-project Git-event card automation config (#329, ADR-0158).
280
+
281
+ Holds the off-by-default toggle and the shared webhook secret for a single
282
+ project's inbound Git-event auto-move. When ``enabled`` and a matching
283
+ ``TaskLink`` exists, a verified ``pull_request``/``merge_request`` webhook
284
+ advances that task's board status forward (opened → REVIEW, merged →
285
+ COMPLETE) through the normal ``TaskSerializer`` write path.
286
+
287
+ Plain ``models.Model`` (not ``VersionedModel``) — like ``IntegrationCredential``
288
+ this is project configuration that is **never** synced to the mobile offline
289
+ client; it carries a secret and has no place in the sync delta.
290
+
291
+ The secret is **Fernet-encrypted, not hashed.** Signature verification needs
292
+ the plaintext at request time — GitLab compares ``X-Gitlab-Token`` directly and
293
+ GitHub recomputes ``HMAC-SHA256`` over the raw body — so the one-way SHA-256
294
+ pattern used for ``ApiToken`` cannot work here. We reuse ``IntegrationCredential``'s
295
+ at-rest pattern (``BinaryField`` + ``encryption.encrypt_secret``). The plaintext
296
+ is shown once on rotation and never returned again.
297
+
298
+ ``configured_by`` is the admin who enabled automation; it becomes the
299
+ accountable ``request.user`` for the status write (mirrors ADR-0148 using
300
+ ``token.created_by``) so RBAC and ``django-simple-history`` see a real user,
301
+ while the activity timeline still classifies the edit as automation via the
302
+ ``history_change_reason`` (ADR-0096).
303
+ """
304
+
305
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
306
+ project = models.OneToOneField(
307
+ "projects.Project",
308
+ on_delete=models.CASCADE,
309
+ related_name="git_automation",
310
+ )
311
+ # Off by default (AC1): automation never fires until an admin opts in.
312
+ enabled = models.BooleanField(default=False)
313
+ # Fernet-encrypted webhook secret; never returned to any client. Empty until
314
+ # an admin mints one via the rotate-secret endpoint.
315
+ secret_ciphertext = models.BinaryField(blank=True, default=b"")
316
+ # When the current secret was minted/rotated — drives the "secret set" hint.
317
+ secret_set_at = models.DateTimeField(null=True, blank=True)
318
+ configured_by = models.ForeignKey(
319
+ settings.AUTH_USER_MODEL,
320
+ on_delete=models.SET_NULL,
321
+ null=True,
322
+ blank=True,
323
+ related_name="+",
324
+ )
325
+ # --- Delivery diagnostics (#2882) --------------------------------------------
326
+ # A failed webhook used to be invisible to everyone: every non-auth outcome was
327
+ # a 200, the receiver logged nothing, and the only diagnostic surface was the
328
+ # provider's own Recent Deliveries tab — outside the operator's reach. An
329
+ # operator could complete every documented setup step, get a green check from
330
+ # GitHub, and have zero cards ever move with nothing anywhere saying why (a
331
+ # missing TaskLink is the most common cause and looks identical to success).
332
+ #
333
+ # These fields are that missing consumer, read back by the admin-only config GET
334
+ # and rendered on the settings card. Deliberately the *last* outcome on the
335
+ # existing config row rather than an append-only attempt table: the receiver is
336
+ # anonymous and rate-bounded, so an unbounded history table on that path is a
337
+ # storage-growth surface an attacker controls, while a single overwritten row
338
+ # answers the question an operator actually asks ("what happened to the delivery
339
+ # I just triggered?"). Writes go through a queryset ``.update()`` so they never
340
+ # touch ``updated_at`` — that field means "config last changed" and a webhook
341
+ # does not change config.
342
+ #
343
+ # **Two independent slots, and the split is a security property, not tidiness.**
344
+ # A refusal is recorded before the signature is verified, so anyone holding the
345
+ # project's UUID can force one with two forged headers — and the UUID is not a
346
+ # secret; every project Viewer reads it out of the SPA URL, which is the premise
347
+ # of #2881. One shared slot would let that caller erase the genuine ``no_link``
348
+ # an admin is mid-diagnosis on, and leave the card telling the admin their secret
349
+ # is wrong and to rotate it — an unauthenticated party talking a working
350
+ # integration down. Split, hostile traffic can only reach the slot already
351
+ # labeled "unauthenticated, may not be your provider".
352
+
353
+ # Verified: the caller proved it holds the secret. Trustworthy.
354
+ last_delivery_at = models.DateTimeField(null=True, blank=True)
355
+ # A ``GIT_DELIVERY_*`` / service outcome token. Free-text CharField rather than
356
+ # ``choices``: the vocabulary is owned by the receiver and grows with the event
357
+ # taxonomy, and a stale ``choices`` list would turn adding an outcome into a
358
+ # migration. The API *publishes* the vocabulary through the serializer instead.
359
+ last_delivery_outcome = models.CharField(max_length=32, blank=True, default="")
360
+ last_delivery_provider = models.CharField(max_length=16, blank=True, default="")
361
+
362
+ # Refused before verification: anyone can write here. Diagnostic, not evidence.
363
+ last_refusal_at = models.DateTimeField(null=True, blank=True)
364
+ last_refusal_outcome = models.CharField(max_length=32, blank=True, default="")
365
+ last_refusal_provider = models.CharField(max_length=16, blank=True, default="")
366
+
367
+ created_at = models.DateTimeField(auto_now_add=True)
368
+ updated_at = models.DateTimeField(auto_now=True)
369
+
370
+ class Meta:
371
+ verbose_name = "board automation"
372
+ verbose_name_plural = "board automations"
373
+
374
+ def __str__(self) -> str:
375
+ state = "on" if self.enabled else "off"
376
+ return f"BoardAutomation(project={self.project_id}, {state})"
377
+
378
+ @property
379
+ def has_secret(self) -> bool:
380
+ """Whether a webhook secret has been minted (drives ``secret_set`` in the API)."""
381
+ return bool(self.secret_ciphertext)
382
+
383
+ def set_secret(self, plaintext: str) -> None:
384
+ """Fernet-encrypt and store ``plaintext`` as the webhook secret.
385
+
386
+ Does not save — the caller persists inside its own transaction so the
387
+ secret and ``secret_set_at`` land atomically.
388
+ """
389
+ from django.utils import timezone
390
+
391
+ # ASCII is a *correctness* invariant, not a style preference, and it is
392
+ # enforced here because here is the only place it can be. Signature
393
+ # verification compares the stored secret against a request header, and the
394
+ # header has to be encoded from text back to bytes to make that comparison
395
+ # safe (see ``core.constant_time.constant_time_equal``, #2881/#2929). That
396
+ # encode falls back from latin-1 to utf-8, and the two codecs agree only on
397
+ # ASCII. #2929 made the primitive encode *both* sides the same way, which
398
+ # closed the confusable-match this guard originally existed to prevent
399
+ # (``"é"`` under latin-1 is the same bytes as ``"é"`` under utf-8); what a
400
+ # non-ASCII secret would now do is fail to verify at all, because the wire
401
+ # bytes of the header round-trip through latin-1 and never match the
402
+ # secret's own latin-1 encoding. Refusing it at write time is still the
403
+ # right call — a silently-never-authenticating webhook is a worse bug report
404
+ # than a rejected secret. Every secret today comes from
405
+ # ``secrets.token_urlsafe`` and is ASCII by construction; this guard is what
406
+ # keeps that true the day someone adds a "paste your existing provider
407
+ # secret" path, which is a routine request for a feature shaped like this
408
+ # one.
409
+ if not plaintext.isascii():
410
+ raise ValueError("webhook secret must be ASCII")
411
+
412
+ self.secret_ciphertext = encrypt_secret(plaintext)
413
+ self.secret_set_at = timezone.now()
414
+
415
+
416
+ class ExternalWorkItem(models.Model):
417
+ """A per-user cached snapshot of one remote work item (ADR-0097 §2).
418
+
419
+ Populated read-only by the #1419 sync worker from an ``EXTERNAL_TASK_SOURCES``
420
+ source (e.g. the user's assigned Jira issues) and surfaced in My Work. It is
421
+ a **personal cache, not project data.**
422
+
423
+ **Isolation invariant (ADR-0097 §2, test-enforced in ``tests/``):** this is a
424
+ plain ``models.Model``, deliberately **not** ``VersionedModel``. It carries no
425
+ ``server_version``, never enters the WebSocket board broadcast, never joins
426
+ the project sync delta / offline tombstone protocol, and the pull can **never**
427
+ mint a ``Task``. That line is what keeps the feature OSS-and-read-only: the day
428
+ someone makes this a ``VersionedModel`` "to get mobile offline," the Apache-2.0
429
+ boundary silently breaks, so a regression test asserts every clause here.
430
+
431
+ Strictly personal visibility: every read filters ``user=request.user``; no
432
+ project member, Admin, or Owner can see another user's items (ADR-0097 §3).
433
+ """
434
+
435
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
436
+ user = models.ForeignKey(
437
+ settings.AUTH_USER_MODEL,
438
+ on_delete=models.CASCADE,
439
+ related_name="external_work_items",
440
+ )
441
+ # Source key, validated against EXTERNAL_TASK_SOURCES at write time (clean()).
442
+ # Free-form CharField (not DB choices) so Enterprise sources need no OSS
443
+ # migration — mirrors the IntegrationCredential.provider pattern.
444
+ source = models.CharField(max_length=32)
445
+ # Provider-side identifier — the human key for Jira ("RIV-482").
446
+ external_id = models.CharField(max_length=255)
447
+ external_url = models.URLField(max_length=2000, blank=True, default="")
448
+ title = models.CharField(max_length=512, blank=True, default="")
449
+ # Raw provider status ("In Review") preserved for display; the coarse
450
+ # grouping bucket is ``display_bucket``.
451
+ external_status = models.CharField(max_length=64, blank=True, default="")
452
+ # One of external_sources.DISPLAY_BUCKETS (todo / in_progress / done).
453
+ display_bucket = models.CharField(max_length=12, default="todo")
454
+ # Provider-side due date, or null when the item has none. Drives the My Work
455
+ # ordering (``due_date asc nulls last``, #1422) and today/upcoming grouping;
456
+ # the source already fetches it (Jira ``duedate``) and the DTO carries it.
457
+ due_date = models.DateField(null=True, blank=True)
458
+ last_synced_at = models.DateTimeField(null=True, blank=True)
459
+ # Soft-remove: set True when an item stops being returned by a *successful*
460
+ # pull (ADR-0097 §5), so a transient partial response never wipes the list.
461
+ # My Work hides stale items; the nightly purge hard-deletes old ones (#1419).
462
+ is_stale = models.BooleanField(default=False)
463
+
464
+ class Meta:
465
+ constraints = [
466
+ # One cached row per (user, source, external_id). The unique index it
467
+ # creates leads with ``user``, so the per-user My Work read
468
+ # (``WHERE user_id = X``) uses its prefix — no separate user index.
469
+ models.UniqueConstraint(
470
+ fields=("user", "source", "external_id"),
471
+ name="integrations_external_item_unique",
472
+ ),
473
+ ]
474
+ indexes = [
475
+ # My Work groups a user's live items by bucket; index the group key so
476
+ # the section read is an index range scan over the bounded (≤500) set.
477
+ models.Index(
478
+ fields=("user", "is_stale", "display_bucket"),
479
+ name="external_item_user_bucket_idx",
480
+ ),
481
+ # The nightly purge reaps soft-removed items by age with no user
482
+ # predicate (``is_stale=True AND last_synced_at < cutoff``, #1419).
483
+ # Without a leading-(is_stale) index that is a full-table seq scan
484
+ # whose cost grows with install-wide tenant count; this makes it an
485
+ # index range scan over just the stale rows.
486
+ models.Index(
487
+ fields=("is_stale", "last_synced_at"),
488
+ name="external_item_stale_age_idx",
489
+ ),
490
+ ]
491
+ ordering = ("display_bucket", "external_id")
492
+ verbose_name = "External work item"
493
+ verbose_name_plural = "External work items"
494
+
495
+ def __str__(self) -> str: # pragma: no cover — debugging aid only
496
+ return f"{self.source}:{self.external_id} for user {self.user_id}"
497
+
498
+ def clean(self) -> None:
499
+ """Validate ``source`` against the live EXTERNAL_TASK_SOURCES registry.
500
+
501
+ Defense in depth — the #1419 worker only writes source keys that came
502
+ from a validated connection, but validating here keeps the model the
503
+ single source of truth (mirrors ``IntegrationCredential``/``TaskLink``).
504
+ """
505
+ super().clean()
506
+ from django.core.exceptions import ValidationError
507
+
508
+ from .external_sources import EXTERNAL_TASK_SOURCES
509
+
510
+ if EXTERNAL_TASK_SOURCES.get(self.source) is None:
511
+ raise ValidationError({"source": f"Unknown external task source {self.source!r}."})
512
+
513
+
514
+ class ExternalSyncRequestStatus(models.TextChoices):
515
+ """Lifecycle of a transactional outbox row for an external-source pull.
516
+
517
+ Mirrors ``scheduling.ScheduleRequestStatus`` (ADR-0019 outbox shape) so the
518
+ drain/purge machinery reads the same across the codebase: ``done`` is the
519
+ ADR-0097 §Durable Execution "COMPLETED" terminal, ``dead`` is its "FAILED".
520
+ """
521
+
522
+ PENDING = "pending", "Pending"
523
+ DISPATCHED = "dispatched", "Dispatched"
524
+ DONE = "done", "Done"
525
+ DEAD = "dead", "Dead"
526
+
527
+
528
+ class ExternalSyncRequestReason(models.TextChoices):
529
+ """Why a pull was enqueued — forensics only, does not change dispatch."""
530
+
531
+ # User hit "Refresh" on the connection (POST .../sync/).
532
+ MANUAL = "manual", "Manual"
533
+ # My Work opened and the cache was past the staleness floor (refresh-if-stale).
534
+ ON_OPEN = "on_open", "On Open"
535
+ # The opt-in low-frequency Beat poll fired for an active connection.
536
+ POLL = "poll", "Poll"
537
+
538
+
539
+ class ExternalSyncRequest(models.Model):
540
+ """Transactional outbox record for a user-scoped external-source pull (ADR-0097 §4).
541
+
542
+ One row per queued pull of a ``(user, source)`` connection. Written inside the
543
+ request/task transaction; a best-effort ``transaction.on_commit`` dispatch
544
+ fires ``external_sync.delay`` and the 300 s ``drain-external-sync`` Beat task
545
+ recovers any row a broker blip left ``PENDING`` (ADR-0017/0019 outbox shape).
546
+
547
+ The two partial-unique constraints below enforce at-most-one ``PENDING`` and
548
+ at-most-one ``DISPATCHED`` row per ``(user, source)``, so a burst of manual
549
+ refreshes coalesces onto one in-flight pull rather than stacking N Jira
550
+ fetches — the per-user cooldown (ADR-0097 §Resolution #5) is the outer bound;
551
+ this constraint is the inner idempotency guard (mirrors ``ScheduleRequest``).
552
+
553
+ Deliberately **not** ``VersionedModel`` — an outbox row is server-side
554
+ dispatch state, never synced to a client (same rule as ``ScheduleRequest``).
555
+ """
556
+
557
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
558
+ user = models.ForeignKey(
559
+ settings.AUTH_USER_MODEL,
560
+ on_delete=models.CASCADE,
561
+ related_name="external_sync_requests",
562
+ )
563
+ # Source key (an EXTERNAL_TASK_SOURCES key, e.g. ``jira``). Free-form CharField
564
+ # to match the credential/work-item pattern — Enterprise sources need no OSS
565
+ # migration. The enqueue service validates it against the live registry.
566
+ source = models.CharField(max_length=32)
567
+ status = models.CharField(
568
+ max_length=16,
569
+ choices=ExternalSyncRequestStatus.choices,
570
+ default=ExternalSyncRequestStatus.PENDING,
571
+ )
572
+ reason = models.CharField(
573
+ max_length=16,
574
+ choices=ExternalSyncRequestReason.choices,
575
+ default=ExternalSyncRequestReason.MANUAL,
576
+ )
577
+ requested_at = models.DateTimeField(auto_now_add=True)
578
+ dispatched_at = models.DateTimeField(null=True, blank=True)
579
+ celery_task_id = models.CharField(max_length=255, blank=True, default="")
580
+ # Last failure detail for a DEAD row — surfaced only in ops/forensics, never
581
+ # to the client. Scrubbed of the PAT/Authorization by the worker before it
582
+ # lands here (ADR-0097 §Resolution #2 "never logged").
583
+ last_error = models.CharField(max_length=512, blank=True, default="")
584
+ # The retry clock for a rate-limited pull (#2924). A 429 is the source telling
585
+ # us when to come back, so the row is re-queued PENDING against this time
586
+ # rather than dead-lettered as "unreachable". Null is the ordinary case — the
587
+ # drain treats a null clock as "eligible now", so every pre-existing row and
588
+ # every non-429 path is unaffected.
589
+ next_attempt_at = models.DateTimeField(
590
+ null=True,
591
+ blank=True,
592
+ help_text="Earliest time the drain may re-dispatch this row (rate-limit backoff).",
593
+ )
594
+ # Bounds the re-queue loop so a source that 429s indefinitely retires instead
595
+ # of cycling forever. Counts rate-limit re-queues only, not dispatches.
596
+ attempt_count = models.PositiveIntegerField(default=0)
597
+
598
+ class Meta:
599
+ ordering = ("requested_at",)
600
+ constraints = [
601
+ models.UniqueConstraint(
602
+ fields=("user", "source"),
603
+ condition=models.Q(status="pending"),
604
+ name="external_sync_one_pending_per_user_source",
605
+ ),
606
+ models.UniqueConstraint(
607
+ fields=("user", "source"),
608
+ condition=models.Q(status="dispatched"),
609
+ name="external_sync_one_dispatched_per_user_source",
610
+ ),
611
+ ]
612
+ indexes = [
613
+ # The drain scans PENDING (+ orphaned DISPATCHED) oldest-first; the
614
+ # purge scans DONE/DEAD by age. Index the shared (status, requested_at)
615
+ # sort key so both are index range scans, not filesorts.
616
+ models.Index(fields=("status", "requested_at"), name="external_sync_status_age_idx"),
617
+ ]
618
+ verbose_name = "External sync request"
619
+ verbose_name_plural = "External sync requests"
620
+
621
+ def __str__(self) -> str: # pragma: no cover — debugging aid only
622
+ return f"ExternalSyncRequest({self.source} for user {self.user_id}, {self.status})"